コード例 #1
0
    def __init__(self, config_path, **kwargs):
        super(TransactiveIlcCoordinator, self).__init__(**kwargs)
        config = utils.load_config(config_path)
        campus = config.get("campus", "")
        building = config.get("building", "")
        logging_topic = config.get("logging_topic", "record")
        self.target_topic = '/'.join(
            ['record', 'target_agent', campus, building, 'goal'])
        self.logging_topic = '/'.join(
            [logging_topic, campus, building, "TCILC"])
        cluster_configs = config["clusters"]
        self.clusters = ClusterContainer()

        for cluster_config in cluster_configs:
            device_cluster_config = cluster_config["device_cluster_file"]
            load_type = cluster_config.get("load_type", "discreet")

            if device_cluster_config[0] == "~":
                device_cluster_config = os.path.expanduser(
                    device_cluster_config)

            cluster_config = utils.load_config(device_cluster_config)
            cluster = DeviceClusters(cluster_config, load_type)
            self.clusters.add_curtailment_cluster(cluster)

        self.device_topic_list = []
        self.device_topic_map = {}
        self.static_price_flag = config.get('static_price_flag', False)
        self.default_min_price = config.get('static_minimum_price', 0.01)
        self.default_max_price = config.get('static_maximum_price', 0.1)
        all_devices = self.clusters.get_device_name_list()
        occupancy_schedule = config.get("occupancy_schedule", False)
        self.occupancy_schedule = init_schedule(occupancy_schedule)
        for device_name in all_devices:
            device_topic = topics.DEVICES_VALUE(campus=campus,
                                                building=building,
                                                unit=device_name,
                                                path="",
                                                point="all")

            self.device_topic_list.append(device_topic)
            self.device_topic_map[device_topic] = device_name

        power_token = config["power_meter"]
        power_meter = power_token["device"]
        self.power_point = power_token["point"]
        self.current_time = None
        self.power_meter_topic = topics.DEVICES_VALUE(campus=campus,
                                                      building=building,
                                                      unit=power_meter,
                                                      path="",
                                                      point="all")
        self.demand_limit = None
        self.bldg_power = []
        self.avg_power = 0.
        self.last_demand_update = None
        self.demand_curve = None
        self.power_prices = None
        self.power_min = None
        self.power_max = None
        self.current_price = None

        self.average_building_power_window = td(
            minutes=config.get("average_building_power_window", 15))
        self.minimum_update_time = td(
            minutes=config.get("minimum_update_time", 5))
        self.market_name = config.get("market", "electric_0")
        self.tz = config.get("timezone", "US/Pacific")
        # self.prices = power_prices
        self.oat_predictions = []
        self.comfort_to_dollar = config.get('comfort_to_dollar', 1.0)

        self.prices_from = config.get("prices_from", 'pubsub')
        self.prices_topic = config.get("price_topic", "prices")
        self.prices_file = config.get("price_file")
        self.join_market(self.market_name, BUYER, None, self.offer_callback,
                         None, self.price_callback, self.error_callback)
コード例 #2
0
    def __init__(self, config_path, **kwargs):
        super(TransactiveIlcCoordinator, self).__init__(**kwargs)
        config = utils.load_config(config_path)
        campus = config.get("campus", "")
        building = config.get("building", "")
        logging_topic = config.get("logging_topic", "tnc")
        self.target_topic = '/'.join(['record', 'target_agent', campus, building, 'goal'])
        self.logging_topic = '/'.join([logging_topic, campus, building, "TCILC"])
        cluster_configs = config["clusters"]
        self.clusters = ClusterContainer()

        for cluster_config in cluster_configs:
            device_cluster_config = cluster_config["device_cluster_file"]
            load_type = cluster_config.get("load_type", "discreet")

            if device_cluster_config[0] == "~":
                device_cluster_config = os.path.expanduser(device_cluster_config)

            cluster_config = utils.load_config(device_cluster_config)
            cluster = DeviceClusters(cluster_config, load_type)
            self.clusters.add_curtailment_cluster(cluster)

        self.device_topic_list = []
        self.device_topic_map = {}
        all_devices = self.clusters.get_device_name_list()
        occupancy_schedule = config.get("occupancy_schedule", False)
        self.occupancy_schedule = init_schedule(occupancy_schedule)
        for device_name in all_devices:
            device_topic = topics.DEVICES_VALUE(campus=campus,
                                                building=building,
                                                unit=device_name,
                                                path="",
                                                point="all")

            self.device_topic_list.append(device_topic)
            self.device_topic_map[device_topic] = device_name

        power_token = config["power_meter"]
        power_meter = power_token["device"]
        self.power_point = power_token["point"]
        self.current_time = None
        self.power_meter_topic = topics.DEVICES_VALUE(campus=campus,
                                                      building=building,
                                                      unit=power_meter,
                                                      path="",
                                                      point="all")
        self.demand_limit = None
        self.bldg_power = []
        self.avg_power = 0.
        self.last_demand_update = None
        self.demand_curve = None
        self.power_prices = None
        self.power_min = None
        self.power_max = None

        self.average_building_power_window = td(minutes=config.get("average_building_power_window", 15))
        self.minimum_update_time = td(minutes=config.get("minimum_update_time", 5))
        self.market_name = config.get("market", "electric")
        self.tz = None
        # self.prices = power_prices
        self.oat_predictions = []
        self.comfort_to_dollar = config.get('comfort_to_dollar', 1.0)

        self.prices_from = config.get("prices_from", 'pubsub')
        self.prices_topic = config.get("price_topic", "prices")
        self.prices_file = config.get("price_file")
        self.join_market(self.market_name, BUYER, None, self.offer_callback, None, self.price_callback, self.error_callback)

        # Setup topics for use later
        self.campus = campus
        self.building = building
        topic_tmpl = "{campus}/{building}/{unit}/{point}"
        self.ts_name = "Date"
        self.oat = "OutdoorAirTemperature"
        self.wbp = "WholeBuildingPower"
        oat_point = topic_tmpl.format(campus=self.campus,
                                      building=self.building,
                                      unit=self.unit,
                                      point="OutdoorAirTemperature")
        bldg_power = topic_tmpl.format(campus=self.campus,
                                       building=self.building,
                                       unit=self.unit,
                                       point="WholeBuildingPower")
        self.points = [oat_point, bldg_power]

        # Query baseline data and schedule estimation run
        self.estimate = []
        self.df_baseline = self.query_baseline()
        self.df_Q = self.cal_Q(self.df_baseline)
        self.df_adj = None
        self.schedule_estimate(False)
コード例 #3
0
class TransactiveIlcCoordinator(MarketAgent):
    def __init__(self, config_path, **kwargs):
        super(TransactiveIlcCoordinator, self).__init__(**kwargs)
        config = utils.load_config(config_path)
        campus = config.get("campus", "")
        building = config.get("building", "")
        logging_topic = config.get("logging_topic", "record")
        self.target_topic = '/'.join(
            ['record', 'target_agent', campus, building, 'goal'])
        self.logging_topic = '/'.join(
            [logging_topic, campus, building, "TCILC"])
        cluster_configs = config["clusters"]
        self.clusters = ClusterContainer()

        for cluster_config in cluster_configs:
            device_cluster_config = cluster_config["device_cluster_file"]
            load_type = cluster_config.get("load_type", "discreet")

            if device_cluster_config[0] == "~":
                device_cluster_config = os.path.expanduser(
                    device_cluster_config)

            cluster_config = utils.load_config(device_cluster_config)
            cluster = DeviceClusters(cluster_config, load_type)
            self.clusters.add_curtailment_cluster(cluster)

        self.device_topic_list = []
        self.device_topic_map = {}
        self.static_price_flag = config.get('static_price_flag', False)
        self.default_min_price = config.get('static_minimum_price', 0.01)
        self.default_max_price = config.get('static_maximum_price', 0.1)
        all_devices = self.clusters.get_device_name_list()
        occupancy_schedule = config.get("occupancy_schedule", False)
        self.occupancy_schedule = init_schedule(occupancy_schedule)
        for device_name in all_devices:
            device_topic = topics.DEVICES_VALUE(campus=campus,
                                                building=building,
                                                unit=device_name,
                                                path="",
                                                point="all")

            self.device_topic_list.append(device_topic)
            self.device_topic_map[device_topic] = device_name

        power_token = config["power_meter"]
        power_meter = power_token["device"]
        self.power_point = power_token["point"]
        self.current_time = None
        self.power_meter_topic = topics.DEVICES_VALUE(campus=campus,
                                                      building=building,
                                                      unit=power_meter,
                                                      path="",
                                                      point="all")
        self.demand_limit = None
        self.bldg_power = []
        self.avg_power = 0.
        self.last_demand_update = None
        self.demand_curve = None
        self.power_prices = None
        self.power_min = None
        self.power_max = None
        self.current_price = None

        self.average_building_power_window = td(
            minutes=config.get("average_building_power_window", 15))
        self.minimum_update_time = td(
            minutes=config.get("minimum_update_time", 5))
        self.market_name = config.get("market", "electric_0")
        self.tz = config.get("timezone", "US/Pacific")
        # self.prices = power_prices
        self.oat_predictions = []
        self.comfort_to_dollar = config.get('comfort_to_dollar', 1.0)

        self.prices_from = config.get("prices_from", 'pubsub')
        self.prices_topic = config.get("price_topic", "prices")
        self.prices_file = config.get("price_file")
        self.join_market(self.market_name, BUYER, None, self.offer_callback,
                         None, self.price_callback, self.error_callback)

    def setup_prices(self):
        _log.debug("Prices from {}".format(self.prices_from))
        if self.prices_from == "file":
            self.power_prices = pd.read_csv(self.prices_file)
            self.power_prices = self.power_prices.set_index(
                self.power_prices.columns[0])
            self.power_prices.index = pd.to_datetime(self.power_prices.index)
            self.power_prices.resample('H').mean()
            self.power_prices['MA'] = self.power_prices[::-1].rolling(
                window=24, min_periods=1).mean()[::-1]
            self.power_prices['STD'] = self.power_prices[
                "price"][::-1].rolling(window=24, min_periods=1).std()[::-1]
            self.power_prices['month'] = self.power_prices.index.month.astype(
                int)
            self.power_prices['day'] = self.power_prices.index.day.astype(int)
            self.power_prices['hour'] = self.power_prices.index.hour.astype(
                int)
        elif self.prices_from == "pubsub":
            self.vip.pubsub.subscribe(peer="pubsub",
                                      prefix=self.prices_topic,
                                      callback=self.update_prices)

    def update_prices(self, peer, sender, bus, topic, headers, message):
        # self.power_prices = pd.DataFrame(message)
        # self.power_prices = self.power_prices.set_index(self.power_prices.columns[0])
        # self.power_prices.index = pd.to_datetime(self.power_prices.index)
        # self.power_prices["price"] = self.power_prices
        # self.power_prices.resample('H').mean()
        # self.power_prices['MA'] = self.power_prices["price"][::-1].rolling(window=24, min_periods=1).mean()[::-1]
        # self.power_prices['STD'] = self.power_prices["price"][::-1].rolling(window=24, min_periods=1).std()[::-1]
        # self.power_prices['month'] = self.power_prices.index.month.astype(int)
        # self.power_prices['day'] = self.power_prices.index.day.astype(int)
        # self.power_prices['hour'] = self.power_prices.index.hour.astype(int)
        hour = float(message['hour'])
        self.power_prices = message["prices"]
        _log.debug("Get RTP Prices: {}".format(self.power_prices))
        self.current_price = self.power_prices[-1]

    @Core.receiver("onstart")
    def starting_base(self, sender, **kwargs):
        """
        Startup method:
         - Setup subscriptions to  devices.
         - Setup subscription to building power meter.
        :param sender:
        :param kwargs:
        :return:
        """
        for device_topic in self.device_topic_list:
            _log.debug("Subscribing to " + device_topic)
            self.vip.pubsub.subscribe(peer="pubsub",
                                      prefix=device_topic,
                                      callback=self.new_data)
        _log.debug("Subscribing to " + self.power_meter_topic)
        self.vip.pubsub.subscribe(peer="pubsub",
                                  prefix=self.power_meter_topic,
                                  callback=self.load_message_handler)
        self.setup_prices()

    def offer_callback(self, timestamp, market_name, buyer_seller):
        if self.current_time is not None:
            demand_curve = self.create_demand_curve()
            if demand_curve is not None:
                self.make_offer(market_name, buyer_seller, demand_curve)
                topic_suffix = "/".join([self.logging_topic, "DemandCurve"])
                message = {
                    "Curve": demand_curve.tuppleize(),
                    "Commodity": "Electricity"
                }
                self.publish_record(topic_suffix, message)

    def create_demand_curve(self):
        if self.power_min is not None and self.power_max is not None:
            demand_curve = PolyLine()
            price_min, price_max = self.generate_price_points()
            demand_curve.add(Point(price=price_max, quantity=self.power_min))
            demand_curve.add(Point(price=price_min, quantity=self.power_max))
        else:
            demand_curve = None
        self.demand_curve = demand_curve
        return demand_curve

    def price_callback(self, timestamp, market_name, buyer_seller, price,
                       quantity):
        if self.bldg_power:
            _log.debug("Price is {} at {}".format(price,
                                                  self.bldg_power[-1][0]))
            dt = self.bldg_power[-1][0]
            occupied = check_schedule(dt, self.occupancy_schedule)
        if price is None and self.price is not None:
            _log.debug(
                "Using stored price information! - market price: %x -- price: %s",
                price, self.current_price)
            price = self.current_price

        if self.demand_curve is not None and price is not None and occupied:
            demand_goal = self.demand_curve.x(price)
            self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
        elif not occupied:
            demand_goal = None
            self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
        else:
            _log.debug(
                "Possible market problem price: {} - quantity: {}".format(
                    price, quantity))
            demand_goal = None

        if price is None:
            price = "None"
            demand_goal = "None"
        message = {
            "Price": price,
            "Quantity": demand_goal,
            "Commodity": "Electricity"
        }
        topic_suffix = "/".join([self.logging_topic, "MarketClear"])
        self.publish_record(topic_suffix, message)

    def publish_demand_limit(self, demand_goal, task_id):
        """
        Publish the demand goal determined by clearing price.
        :param demand_goal:
        :param task_id:
        :return:
        """
        _log.debug("Updating demand limit: {}".format(demand_goal))
        self.demand_limit = demand_goal
        if self.last_demand_update is not None:
            if (self.current_time -
                    self.last_demand_update) < self.minimum_update_time:
                _log.debug("Minimum demand update time has not elapsed.")
                return
        if self.current_time is None:
            _log.debug("No data received, not updating demand goal!")
            return

        self.last_demand_update = self.current_time

        start_time = format(self.current_time)
        end_time = format_timestamp(
            self.current_time.replace(hour=23, minute=59, second=59))
        _log.debug("Publish target: {}".format(demand_goal))
        headers = {'Date': start_time}
        target_msg = [{
            "value": {
                "target": self.demand_limit,
                "start": start_time,
                "end": end_time,
                "id": task_id
            }
        }, {
            "value": {
                "tz": "UTC"
            }
        }]
        self.vip.pubsub.publish('pubsub', self.target_topic, headers,
                                target_msg).get(timeout=15)

    def new_data(self, peer, sender, bus, topic, headers, message):
        """
        Call back method for device data subscription.
        :param peer:
        :param sender:
        :param bus:
        :param topic:
        :param headers:
        :param message:
        :return:
        """
        _log.info("Data Received for {}".format(topic))
        # topic of form:  devices/campus/building/device
        device_name = self.device_topic_map[topic]
        data = message[0]
        self.current_time = parse(headers["Date"])
        parsed_data = parse_sympy(data)
        self.clusters.get_device(device_name).ingest_data(parsed_data)

    def generate_price_points(self):
        # need to figure out where we are getting the pricing information and the form
        # probably via RPC
        # _log.debug("DEBUG_PRICES: {}".format(self.current_time))
        # df_query = self.power_prices[(self.power_prices["hour"] == self.current_time.hour) & (self.power_prices["day"] == self.current_time.day) & (self.power_prices["month"] == self.current_time.month)]
        # price_min = df_query['MA'] - df_query['STD']*self.comfort_to_dollar
        # price_max = df_query['MA'] + df_query['STD']*self.comfort_to_dollar
        # _log.debug("DEBUG TCC price - min {} - max {}".format(float(price_min), float(price_max)))
        # return max(float(price_min), 0.0), float(price_max)
        if self.power_prices and not self.static_price_flag:
            avg_price = np.mean(self.power_prices)
            std_price = np.std(self.power_prices)
            price_min = avg_price - self.comfort_to_dollar * std_price
            price_max = avg_price + self.comfort_to_dollar * std_price
        else:
            avg_price = None
            std_price = None
            price_min = self.default_min_price
            price_max = self.default_max_price
        _log.debug("Prices: {} - avg: {} - std: {}".format(
            self.power_prices, avg_price, std_price))
        price_array = np.linspace(price_min, price_max, 11)
        return price_min, price_max

    def generate_power_points(self, current_power):
        positive_power, negative_power = self.clusters.get_power_bounds()
        _log.debug("DEBUG TCC - pos {} - neg {}".format(
            positive_power, negative_power))
        return float(current_power +
                     sum(positive_power)), float(current_power -
                                                 sum(negative_power))

    def load_message_handler(self, peer, sender, bus, topic, headers, message):
        """
        Call back method for building power meter. Calculates the average
        building demand over a configurable time and manages the curtailment
        time and curtailment break times.
        :param peer:
        :param sender:
        :param bus:
        :param topic:
        :param headers:
        :param message:
        :return:
        """
        # Use instantaneous power or average building power.
        data = message[0]
        current_power = data[self.power_point]
        tz_info = dateutil.tz.gettz(self.tz)
        current_time = parse(headers["Date"]).astimezone(tz_info)

        power_max, power_min = self.generate_power_points(current_power)
        _log.debug("QUANTITIES: max {} - min {} - cur {}".format(
            power_max, power_min, current_power))

        topic_suffix = "/".join([self.logging_topic, "BuildingFlexibility"])
        message = {
            "MaximumPower": power_max,
            "MinimumPower": power_min,
            "AveragePower": current_power
        }
        self.publish_record(topic_suffix, message)

        if self.bldg_power:
            current_average_window = self.bldg_power[-1][0] - self.bldg_power[
                0][0] + td(seconds=15)
        else:
            current_average_window = td(minutes=0)

        if current_average_window >= self.average_building_power_window and current_power > 0:
            self.bldg_power.append(
                (current_time, current_power, power_min, power_max))
            self.bldg_power.pop(0)
        elif current_power > 0:
            self.bldg_power.append(
                (current_time, current_power, power_min, power_max))

        smoothing_constant = 2.0 / (len(self.bldg_power) +
                                    1.0) * 2.0 if self.bldg_power else 1.0
        smoothing_constant = smoothing_constant if smoothing_constant <= 1.0 else 1.0
        power_sort = list(self.bldg_power)
        power_sort.sort(reverse=True)
        avg_power_max = 0.
        avg_power_min = 0.
        avg_power = 0.

        for n in range(len(self.bldg_power)):
            avg_power += power_sort[n][1] * smoothing_constant * (
                1.0 - smoothing_constant)**n
            avg_power_min += power_sort[n][2] * smoothing_constant * (
                1.0 - smoothing_constant)**n
            avg_power_max += power_sort[n][3] * smoothing_constant * (
                1.0 - smoothing_constant)**n
        self.avg_power = avg_power
        self.power_min = avg_power_min
        self.power_max = avg_power_max

    def error_callback(self, timestamp, market_name, buyer_seller, error_code,
                       error_message, aux):
        # figure out what to send if the market is not formed or curves don't intersect.
        _log.debug("AUX: {}".format(aux))
        if market_name == "electric":
            if self.bldg_power:
                dt = self.bldg_power[-1][0]
                occupied = check_schedule(dt, self.occupancy_schedule)

            _log.debug("AUX: {}".format(aux))
            if not occupied:
                demand_goal = None
                self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
            else:
                if aux.get('SQn,DQn', 0) == -1 and aux.get('SQx,DQx', 0) == -1:
                    demand_goal = self.demand_curve.min_x()
                    self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
                elif aux.get('SPn,DPn', 0) == 1 and aux.get('SPx,DPx', 0) == 1:
                    demand_goal = self.demand_curve.min_x()
                    self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
                elif aux.get('SPn,DPn', 0) == -1 and aux.get('SPx,DPx',
                                                             0) == -1:
                    demand_goal = self.demand_curve.max_x()
                    self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
                else:
                    demand_goal = None
                    self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
        return

    def publish_record(self, topic, message):
        headers = {headers_mod.DATE: format_timestamp(get_aware_utc_now())}
        message["TimeStamp"] = format_timestamp(self.current_time)
        self.vip.pubsub.publish("pubsub", topic, headers, message).get()
コード例 #4
0
class TransactiveIlcCoordinator(MarketAgent):
    def __init__(self, config_path, **kwargs):
        super(TransactiveIlcCoordinator, self).__init__(**kwargs)
        config = utils.load_config(config_path)
        campus = config.get("campus", "")
        building = config.get("building", "")
        logging_topic = config.get("logging_topic", "tnc")
        self.target_topic = '/'.join(['record', 'target_agent', campus, building, 'goal'])
        self.logging_topic = '/'.join([logging_topic, campus, building, "TCILC"])
        cluster_configs = config["clusters"]
        self.clusters = ClusterContainer()

        for cluster_config in cluster_configs:
            device_cluster_config = cluster_config["device_cluster_file"]
            load_type = cluster_config.get("load_type", "discreet")

            if device_cluster_config[0] == "~":
                device_cluster_config = os.path.expanduser(device_cluster_config)

            cluster_config = utils.load_config(device_cluster_config)
            cluster = DeviceClusters(cluster_config, load_type)
            self.clusters.add_curtailment_cluster(cluster)

        self.device_topic_list = []
        self.device_topic_map = {}
        all_devices = self.clusters.get_device_name_list()
        occupancy_schedule = config.get("occupancy_schedule", False)
        self.occupancy_schedule = init_schedule(occupancy_schedule)
        for device_name in all_devices:
            device_topic = topics.DEVICES_VALUE(campus=campus,
                                                building=building,
                                                unit=device_name,
                                                path="",
                                                point="all")

            self.device_topic_list.append(device_topic)
            self.device_topic_map[device_topic] = device_name

        power_token = config["power_meter"]
        power_meter = power_token["device"]
        self.power_point = power_token["point"]
        self.current_time = None
        self.power_meter_topic = topics.DEVICES_VALUE(campus=campus,
                                                      building=building,
                                                      unit=power_meter,
                                                      path="",
                                                      point="all")
        self.demand_limit = None
        self.bldg_power = []
        self.avg_power = 0.
        self.last_demand_update = None
        self.demand_curve = None
        self.power_prices = None
        self.power_min = None
        self.power_max = None

        self.average_building_power_window = td(minutes=config.get("average_building_power_window", 15))
        self.minimum_update_time = td(minutes=config.get("minimum_update_time", 5))
        self.market_name = config.get("market", "electric")
        self.tz = None
        # self.prices = power_prices
        self.oat_predictions = []
        self.comfort_to_dollar = config.get('comfort_to_dollar', 1.0)

        self.prices_from = config.get("prices_from", 'pubsub')
        self.prices_topic = config.get("price_topic", "prices")
        self.prices_file = config.get("price_file")
        self.join_market(self.market_name, BUYER, None, self.offer_callback, None, self.price_callback, self.error_callback)

        # Setup topics for use later
        self.campus = campus
        self.building = building
        topic_tmpl = "{campus}/{building}/{unit}/{point}"
        self.ts_name = "Date"
        self.oat = "OutdoorAirTemperature"
        self.wbp = "WholeBuildingPower"
        oat_point = topic_tmpl.format(campus=self.campus,
                                      building=self.building,
                                      unit=self.unit,
                                      point="OutdoorAirTemperature")
        bldg_power = topic_tmpl.format(campus=self.campus,
                                       building=self.building,
                                       unit=self.unit,
                                       point="WholeBuildingPower")
        self.points = [oat_point, bldg_power]

        # Query baseline data and schedule estimation run
        self.estimate = []
        self.df_baseline = self.query_baseline()
        self.df_Q = self.cal_Q(self.df_baseline)
        self.df_adj = None
        self.schedule_estimate(False)

    def next_scheduled_time(self):
        # Next scheduled time is minute 50 of this hour or next hour
        now = dt.now()
        next_scheduled_time = now.replace(minute=50)
        if now.minute >= 50:
            next_scheduled_time = next_scheduled_time + td(hours=1)
            _log.debug("It's too late. Schedule for next hour.")

        return next_scheduled_time

    def query_data(self, count, timeout=10000):
        df = None
        for point in self.points:
            result = self.vip.rpc.call('platform.historian',
                                       'query',
                                       topic=point,
                                       count=count,
                                       order="LAST_TO_FIRST").get(timeout=timeout)
            df2 = pd.DataFrame(result['values'], columns=[self.ts_name, point])
            df2[self.ts_name] = pd.to_datetime(df2[self.ts_name])
            df2 = df2.resample('H').mean()
            df = df2 if df is None else pd.merge(df, df2, on=self.ts_name, how='outer')

        if df is not None:
            # Convert to local time and do hourly resample
            df[self.ts_name] = pd.to_datetime(df[self.ts_name], utc=True)
            df[self.ts_name] = df[self.ts_name].dt.tz_convert('US/Pacific')
            df[self.ts_name] = df[self.ts_name].dt.tz_localize(None)
            df[self.ts_name] = df[self.ts_name].values.astype('<M8[m]')

        return df

    def query_baseline(self):
        # Get previous event days
        ev_days = self.config.get('event_days', [])
        parsed_ev_days = []
        for ev_day in ev_days:
            try:
                parsed_ev_day = parse(ev_day)
                if parsed_ev_day.tzinfo is None:
                    parsed_ev_day = self.local_tz.localize(parsed_ev_day)
                parsed_ev_days.append(format_timestamp(parsed_ev_day))
            except Exception as e:
                _log.error(e.message)

        # Query baseline data
        df = self.query_data(count=30*24*60, timeout=10000)

        # Filter out non-business and event days
        if df is not None:
            filter = (df[self.ts_name].dt.weekday < 5)
            for day in parsed_ev_days:
                filter |= (df[self.ts_name].dt != day)
            df = df[filter]
        else:
            _log.error("No baseline data.")

        return df

    def query_new_data(self):
        # Query last X hours
        df = self.query_data(count=4*60, timeout=10000)

        # Filter out to get data for last hour
        if df is not None:
            now = dt.now()
            prev = now - td(hours=1)
            start = prev.replace(minute=0, second=0)
            end = now.replace(minute=0, second=0)

            df = df[(df[self.ts_name] >= start) & (df[self.ts_name] < end)]

        return df

    def cal_Q(self, df):
        # Calculate Q_min and Q_max for each record in dataframe
        df_Q = df
        return df_Q

    def estimate(self, df_baseline, df_adj):
        """
        Estimate using hourly profile for last X business day
        """

        # Baseline
        q_min = []
        q_max = []
        for hr in range(0, 24):
            cur_df = df_baseline[df_baseline[self.ts_name].dt.hour == hr]
            q_min.append(cur_df['Qmin'].mean())
            q_max.append(cur_df['Qmax'].mean())

        # Caculate adjustment
        now = dt.now()
        adj_1 = now - td(hours=4)
        adj_2 = now - td(hours=3)
        adj_3 = now - td(hours=2)
        sum_qmin = q_min[adj_1] + q_min[adj_2] + q_min[adj_3]
        sum_qmax = q_max[adj_1] + q_max[adj_2] + q_max[adj_3]
        sum_c_qmin = df_adj['Qmin'].mean()
        sum_c_qmax = df_adj['Qmax'].mean()
        adj_qmin = sum_c_qmin / sum_qmin
        adj_qmax = sum_c_qmax / sum_qmax

        # Apply day-of adjustment
        qmin_est = [x * adj_qmin for x in q_min]
        qmax_est = [x * adj_qmax for x in q_max]


        # Pubhslish the whole qmin and qmax on tnc topic

        return qmin_est, qmax_est

    def schedule_estimate(self, scheduled_call):
        # Query past hours if this is actual scheduled call (ie. not the call when first init())
        if scheduled_call:
            df = self.query_new_data()

            # Estimate Qmin & Qmax for each record
            if df is not None:
                self.df_adj = self.cal_Q(df)
                q_min, q_max = self.estimate(self.df_Q, self.df_adj)
            else:
                _log.error("No adjustment data")

        # Schedule to run every hour (put in onstart later) to update estimate & do adjustment
        next_scheduled_time = self.next_scheduled_time()
        self.core.schedule(next_scheduled_time, self.schedule_estimate, True)

    def setup_prices(self):
        _log.debug("Prices from {}".format(self.prices_from))
        if self.prices_from == "file":
            self.power_prices = pd.read_csv(self.prices_file)
            self.power_prices = self.power_prices.set_index(self.power_prices.columns[0])
            self.power_prices.index = pd.to_datetime(self.power_prices.index)
            self.power_prices.resample('H').mean()
            self.power_prices['MA'] = self.power_prices[::-1].rolling(window=24, min_periods=1).mean()[::-1]
            self.power_prices['STD'] = self.power_prices["price"][::-1].rolling(window=24, min_periods=1).std()[::-1]
            self.power_prices['month'] = self.power_prices.index.month.astype(int)
            self.power_prices['day'] = self.power_prices.index.day.astype(int)
            self.power_prices['hour'] = self.power_prices.index.hour.astype(int)
        elif self.prices_from == "pubsub":
            self.vip.pubsub.subscribe(peer="pubsub", prefix=self.prices_topic, callback=self.update_prices)

    def update_prices(self, peer, sender, bus, topic, headers, message):
        self.power_prices = pd.DataFrame(message)
        self.power_prices = self.power_prices.set_index(self.power_prices.columns[0])
        self.power_prices.index = pd.to_datetime(self.power_prices.index)
        self.power_prices["price"] = self.power_prices
        self.power_prices.resample('H').mean()
        self.power_prices['MA'] = self.power_prices["price"][::-1].rolling(window=24, min_periods=1).mean()[::-1]
        self.power_prices['STD'] = self.power_prices["price"][::-1].rolling(window=24, min_periods=1).std()[::-1]
        self.power_prices['month'] = self.power_prices.index.month.astype(int)
        self.power_prices['day'] = self.power_prices.index.day.astype(int)
        self.power_prices['hour'] = self.power_prices.index.hour.astype(int)

    @Core.receiver("onstart")
    def starting_base(self, sender, **kwargs):
        """
        Startup method:
         - Setup subscriptions to  devices.
         - Setup subscription to building power meter.
        :param sender:
        :param kwargs:
        :return:
        """
        for device_topic in self.device_topic_list:
            _log.debug("Subscribing to " + device_topic)
            self.vip.pubsub.subscribe(peer="pubsub", prefix=device_topic, callback=self.new_data)
        _log.debug("Subscribing to " + self.power_meter_topic)
        self.vip.pubsub.subscribe(peer="pubsub", prefix=self.power_meter_topic, callback=self.load_message_handler)
        self.setup_prices()

    def offer_callback(self, timestamp, market_name, buyer_seller):
        if self.current_time is not None:
            demand_curve = self.create_demand_curve()
            if demand_curve is not None:
                self.make_offer(market_name, buyer_seller, demand_curve)
                topic_suffix = "/".join([self.logging_topic, "DemandCurve"])
                message = {"Curve": demand_curve.tuppleize(), "Commodity": "Electricity"}
                self.publish_record(topic_suffix, message)

    def create_demand_curve(self):
        if self.power_min is not None and self.power_max is not None:
            demand_curve = PolyLine()
            price_min, price_max = self.generate_price_points()
            demand_curve.add(Point(price=price_max, quantity=self.power_min))
            demand_curve.add(Point(price=price_min, quantity=self.power_max))
        else:
            demand_curve = None
        self.demand_curve = demand_curve
        return demand_curve

    def price_callback(self, timestamp, market_name, buyer_seller, price, quantity):
        if self.bldg_power:
            _log.debug("Price is {} at {}".format(price, self.bldg_power[-1][0]))
            dt = self.bldg_power[-1][0]
            occupied = check_schedule(dt, self.occupancy_schedule)

        if self.demand_curve is not None and price is not None and occupied:
            demand_goal = self.demand_curve.x(price)
            self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
        elif not occupied:
            demand_goal = None
            self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
        if price is None:
            price = "None"
        message = {"Price": price, "Quantity": demand_goal, "Commodity": "Electricity"}
        topic_suffix = "/".join([self.logging_topic, "MarketClear"])
        self.publish_record(topic_suffix, message)

    def publish_demand_limit(self, demand_goal, task_id):
        """
        Publish the demand goal determined by clearing price.
        :param demand_goal:
        :param task_id:
        :return:
        """
        _log.debug("Updating demand limit: {}".format(demand_goal))
        self.demand_limit = demand_goal
        if self.last_demand_update is not None:
            if (self.current_time - self.last_demand_update) < self.minimum_update_time:
                _log.debug("Minimum demand update time has not elapsed.")
                return
        if self.current_time is None:
            _log.debug("No data received, not updating demand goal!")
            return

        self.last_demand_update = self.current_time

        start_time = format(self.current_time)
        end_time = format_timestamp(self.current_time.replace(hour=23, minute=59, second=59))
        _log.debug("Publish target: {}".format(demand_goal))
        headers = {'Date': start_time}
        target_msg = [
            {
                "value": {
                    "target": self.demand_limit,
                    "start": start_time,
                    "end": end_time,
                    "id": task_id
                    }
            },
            {
                "value": {"tz": "UTC"}
            }
        ]
        self.vip.pubsub.publish('pubsub', self.target_topic, headers, target_msg).get(timeout=15)

    def new_data(self, peer, sender, bus, topic, headers, message):
        """
        Call back method for device data subscription.
        :param peer:
        :param sender:
        :param bus:
        :param topic:
        :param headers:
        :param message:
        :return:
        """
        _log.info("Data Received for {}".format(topic))
        # topic of form:  devices/campus/building/device
        device_name = self.device_topic_map[topic]
        data = message[0]
        self.current_time = parse(headers["Date"])
        parsed_data = parse_sympy(data)


        parsed_data = [{'OATemp': 72, 'ZoneTem': 73}, {'meta': ''}]
        header = {'Date': '2091-01-01 00:00:00'}
        topic = 'devices/"querytopicfrom database"/all

        self.clusters.get_device(device_name).ingest_data(parsed_data)





    def generate_price_points(self):
        # need to figure out where we are getting the pricing information and the form
        # probably via RPC
        _log.debug("DEBUG_PRICES: {}".format(self.current_time))
        df_query = self.power_prices[(self.power_prices["hour"] == self.current_time.hour) & (self.power_prices["day"] == self.current_time.day) & (self.power_prices["month"] == self.current_time.month)]
        price_min = df_query['MA'] - df_query['STD']*self.comfort_to_dollar
        price_max = df_query['MA'] + df_query['STD']*self.comfort_to_dollar
        _log.debug("DEBUG TCC price - min {} - max {}".format(float(price_min), float(price_max)))
        return max(float(price_min), 0.0), float(price_max)

    def generate_power_points(self, current_power):
        positive_power, negative_power = self.clusters.get_power_bounds()
        _log.debug("DEBUG TCC - pos {} - neg {}".format(positive_power, negative_power))
        return float(current_power + sum(positive_power)), float(current_power - sum(negative_power))

    def load_message_handler(self, peer, sender, bus, topic, headers, message):
        """
        Call back method for building power meter. Calculates the average
        building demand over a configurable time and manages the curtailment
        time and curtailment break times.
        :param peer:
        :param sender:
        :param bus:
        :param topic:
        :param headers:
        :param message:
        :return:
        """
        # Trigger this by using power
        # Use instantaneous power or average building power.
        data = message[0]
        current_power = data[self.power_point]
        current_time = parse(headers["Date"])

        power_max, power_min = self.generate_power_points(current_power)
        _log.debug("QUANTITIES: max {} - min {} - cur {}".format(power_max, power_min, current_power))

        if self.bldg_power:
            current_average_window = self.bldg_power[-1][0] - self.bldg_power[0][0] + td(seconds=15)
        else:
            current_average_window = td(minutes=0)

        if current_average_window >= self.average_building_power_window and current_power > 0:
            self.bldg_power.append((current_time, current_power, power_min, power_max))
            self.bldg_power.pop(0)
        elif current_power > 0:
            self.bldg_power.append((current_time, current_power, power_min, power_max))

        smoothing_constant = 2.0 / (len(self.bldg_power) + 1.0) * 2.0 if self.bldg_power else 1.0
        smoothing_constant = smoothing_constant if smoothing_constant <= 1.0 else 1.0
        power_sort = list(self.bldg_power)
        power_sort.sort(reverse=True)
        avg_power_max = 0.
        avg_power_min = 0.
        avg_power = 0.

        for n in xrange(len(self.bldg_power)):
            avg_power += power_sort[n][1] * smoothing_constant * (1.0 - smoothing_constant) ** n
            avg_power_min += power_sort[n][2] * smoothing_constant * (1.0 - smoothing_constant) ** n
            avg_power_max += power_sort[n][3] * smoothing_constant * (1.0 - smoothing_constant) ** n
        self.avg_power = avg_power
        self.power_min = avg_power_min
        self.power_max = avg_power_max

    def error_callback(self, timestamp, market_name, buyer_seller, error_code, error_message, aux):
        # figure out what to send if the market is not formed or curves don't intersect.
        _log.debug("AUX: {}".format(aux))
        if market_name == "electric":
            if self.bldg_power:
                dt = self.bldg_power[-1][0]
                occupied = check_schedule(dt, self.occupancy_schedule)

            _log.debug("AUX: {}".format(aux))
            if not occupied:
                demand_goal = None
                self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
            else:
                if aux.get('SQn,DQn', 0) == -1 and aux.get('SQx,DQx', 0) == -1:
                    demand_goal = self.demand_curve.min_x()
                    self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
                elif aux.get('SPn,DPn', 0) == 1 and aux.get('SPx,DPx', 0) == 1:
                    demand_goal = self.demand_curve.min_x()
                    self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
                elif aux.get('SPn,DPn', 0) == -1 and aux.get('SPx,DPx', 0) == -1:
                    demand_goal = self.demand_curve.max_x()
                    self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
                else:
                    demand_goal = None
                    self.publish_demand_limit(demand_goal, str(uuid.uuid1()))
        return

    def publish_record(self, topic_suffix, message):
        headers = {headers_mod.DATE: format_timestamp(get_aware_utc_now())}
        message["TimeStamp"] = format_timestamp(self.current_time)
        topic = "/".join([self.record_topic, topic_suffix])
        self.vip.pubsub.publish("pubsub", topic, headers, message).get()