def do_query(self, query, timeout=DEFAULT_TIMEOUT, tz=pytz.timezone("US/Pacific")):
        """
        Query structure is as follows:

        query = {
            # We bind UUIDs found as the result of a Brick query to a variable name
            # that we can use later.
            # Each variable definition has the following:
            #  - name: how we will refer to this group of UUIDs
            #  - definition: a Brick query. The SELECT clause should return variables that end in '_uuid', which can be found as the
            #          object of a 'bf:uuid' relationship
            #  - units: what units we want to retrieve this stream as. Currently supports W/kW, Wh/kWh, F/C, Lux
            "Variables": [
                {"Name": "meter",
                 "Definition": "SELECT ?meter_uuid WHERE { ?meter rdf:type/rdfs:subClassOf* brick:Electric_Meter . ?meter bf:uuid ?meter_uuid . };",
                 "Units": "kW",
                },
                {"Name": "temp",
                 "Definition": "SELECT ?temp_uuid WHERE { ?temp rdf:type/rdfs:subClassOf* brick:Temperature_Sensor . ?temp bf:uuid ?temp_uuid . };",
                 "Units": "F",
                },
            ],

            # this is the composition of the data matrix we are returning. Below, all the uuids for the "meter" variable will be placed before
            # all of the uuids for the "temp" variable. We cannot guarantee order of uuids within those groups, but the ordering of the groups
            # will be preserved. Explicit UUIDs can also be used here
            "Composition": ["meter", "temp"],

            # If we are retrieving statistical data, then we need to say which statistical elements we want to download.
            # The options are RAW, MEAN, MIN, MAX and COUNT. To query multiple, you can OR them together (e.g. MEAN|MAX).
            # This maps 1-1 to the "Composition" field
            "Selectors": [MEAN, MEAN],

            # Themporal parameters for the query. Retrieves data in the range [T0, T1]. By convention, T0 < T1,
            # but MDAL will take care of it if this is reversed.
            # WindowSize is the size of the resample window in nanoseconds
            # if Aligned is true, then MDAL will snap all data to the begining of the window (e.g. if 5min window + Aligned=true,
            # then all timestamps will be on 00:05:00, 00:10:00, 00:15:00, etc)
            "Time": {
                "T0": "2017-08-01 00:00:00",
                "T1": "2017-08-08 00:00:00",
                "WindowSize": '2h',
                "Aligned": True,
            },
        }
        """
        nonce = str(random.randint(0, 2**32))
        query['Nonce'] = nonce
        ev = threading.Event()
        response = {}
        def _handleresult(msg):
            got_response = False
            for po in msg.payload_objects:
                if po.type_dotted != (2,0,10,4): continue
                data = msgpack.unpackb(po.content)
                if data['Nonce'] != query['Nonce']: continue

                if 'error' in data:
                    response['error'] = data['error']
                    response['df'] = None
                    got_response=True
                    continue

                uuids = [str(uuid.UUID(bytes=x)) for x in data['Rows']]
                data = data_capnp.StreamCollection.from_bytes_packed(data['Data'])
                if hasattr(data, 'times') and len(data.times):
                    times = list(data.times)
                    if len(times) == 0:
                        response['df'] = pd.DataFrame(columns=uuids)
                        got_response = True
                        break
                    df = pd.DataFrame(index=pd.to_datetime(times, unit='ns', utc=False))
                    for idx, s in enumerate(data.streams):
                        if len(s.values) == 0:
                            df[uuids[idx]] = None
                        else:
                            df[uuids[idx]] = s.values
                    df.index = df.index.tz_localize(pytz.utc).tz_convert(tz)
                    response['df'] = df
                    got_response = True
                else:
                    df = pd.DataFrame()
                    for idx, s in enumerate(data.streams):
                        if hasattr(s, 'times'):
                            newdf = pd.DataFrame(list(s.values), index=list(s.times), columns=[uuids[idx]])
                            newdf.index = pd.to_datetime(newdf.index, unit='ns').tz_localize(pytz.utc).tz_convert(tz)
                            df = df.join(newdf, how='outer')
                        else:
                            raise Exception("Does this ever happen? Tell gabe!")
                    response['df'] = df
                    got_response = True
            df = response.get('df')
            if df is not None:
                response['df'] = df#[df.index.duplicated(keep='first')]
            if got_response:
                ev.set()
        h = self.c.subscribe("{0}/s.mdal/_/i.mdal/signal/{1}".format(self.url, self.vk[:-1]), _handleresult)
        po = PayloadObject((2,0,10,3), None, msgpack.packb(query))
        self.c.publish("{0}/s.mdal/_/i.mdal/slot/query".format(self.url), payload_objects=(po,))
        ev.wait(timeout)
        self.c.unsubscribe(h)
        if 'error' in response:
            raise Exception(response['error'])
        return response
Query structure is as follows:

        query = {
            # We bind UUIDs found as the result of a Brick query to a variable name
            # that we can use later.
            # Each variable definition has the following:
            #  - name: how we will refer to this group of UUIDs
            #  - definition: a Brick query. The SELECT clause should return variables that end in '_uuid', which can be found as the
            #          object of a 'bf:uuid' relationship
            #  - units: what units we want to retrieve this stream as. Currently supports W/kW, Wh/kWh, F/C, Lux
            "Variables": [
                {"Name": "meter",
                 "Definition": "SELECT ?meter_uuid WHERE { ?meter rdf:type/rdfs:subClassOf* brick:Electric_Meter . ?meter bf:uuid ?meter_uuid . };",
                 "Units": "kW",
                },
                {"Name": "temp",
                 "Definition": "SELECT ?temp_uuid WHERE { ?temp rdf:type/rdfs:subClassOf* brick:Temperature_Sensor . ?temp bf:uuid ?temp_uuid . };",
                 "Units": "F",
                },
            ],

            # this is the composition of the data matrix we are returning. Below, all the uuids for the "meter" variable will be placed before
            # all of the uuids for the "temp" variable. We cannot guarantee order of uuids within those groups, but the ordering of the groups
            # will be preserved. Explicit UUIDs can also be used here
            "Composition": ["meter", "temp"],

            # If we are retrieving statistical data, then we need to say which statistical elements we want to download.
            # The options are RAW, MEAN, MIN, MAX and COUNT. To query multiple, you can OR them together (e.g. MEAN|MAX).
            # This maps 1-1 to the "Composition" field
            "Selectors": [MEAN, MEAN],

            # Themporal parameters for the query. Retrieves data in the range [T0, T1]. By convention, T0 < T1,
            # but MDAL will take care of it if this is reversed.
            # WindowSize is the size of the resample window in nanoseconds
            # if Aligned is true, then MDAL will snap all data to the begining of the window (e.g. if 5min window + Aligned=true,
            # then all timestamps will be on 00:05:00, 00:10:00, 00:15:00, etc)
            "Time": {
                "T0": "2017-08-01 00:00:00",
                "T1": "2017-08-08 00:00:00",
                "WindowSize": '2h',
                "Aligned": True,
            },
        }