def create_demand_curve(self, index): hvac_index = self.determine_hvac_index(index) demand_curve = PolyLine() oat = self.oat_predictions[index] if self.oat_predictions else self.tOut prices = self.determine_prices() price_max_bound = max( max(prices) + 0.1 * max(prices), max(self.prices) + max(self.prices) * 0.1) price_min_bound = min( min(prices) + 0.1 * min(prices), min(self.prices) - min(self.prices) * 0.1) temp = self.temp[index] quantities = [] for i in range(len(prices)): if self.hvac_avail[hvac_index]: temp_stpt = self.tsets[i] else: temp_stpt = self.tMinAdjUnoc quantity = min( max(self.getM(oat, temp, temp_stpt, hvac_index), self.mDotMin), self.mDotMax) quantities.append(quantity) demand_curve.add(Point(price=price_max_bound, quantity=min(quantities))) prices.sort(reverse=True) quantities.sort() for i in range(len(prices)): demand_curve.add(Point(price=prices[i], quantity=quantities[i])) demand_curve.add(Point(price=price_min_bound, quantity=max(quantities))) _log.debug("{} debug demand_curve4 - curve: {}".format( self.agent_name, demand_curve.points)) _log.debug("{} market {} has cleared airflow: {}".format( self.agent_name, index, demand_curve.x(self.prices[index]))) return demand_curve
class LightAgent(MarketAgent, FirstOrderZone): """ The SampleElectricMeterAgent serves as a sample of an electric meter that sells electricity for a single building at a fixed price. """ def __init__(self, market_name, agent_name, k, qmax, Pabsnom, nonResponsive, verbose_logging, subscribing_topic, **kwargs): super(LightAgent, self).__init__(verbose_logging, **kwargs) self.market_name = market_name self.agent_name = agent_name self.k = k self.qmax = qmax self.Pabsnom = Pabsnom self.nonResponsive = nonResponsive self.iniState() self.subscribing_topic = subscribing_topic self.join_market(self.market_name, BUYER, None, self.offer_callback, None, self.price_callback, self.error_callback) @Core.receiver('onstart') def setup(self, sender, **kwargs): _log.debug('Subscribing to ' + 'devices/CAMPUS/BUILDING1/AHU1/all') self.vip.pubsub.subscribe(peer='pubsub', prefix='devices/CAMPUS/BUILDING1/AHU1/all', callback=self.updateState) def offer_callback(self, timestamp, market_name, buyer_seller): result, message = self.make_offer(market_name, buyer_seller, self.create_demand_curve()) _log.debug("results of the make offer {}".format(result)) if not result: _log.debug("the new lightingt (maintain{}".format(self.qMax)) gevent.sleep(random.random()) self.vip.rpc.call('platform.actuator', 'set_point', self.agent_name, self.subscribing_topic + '/' + self.agent_name, round(self.qNorm, 2)).get(timeout=6) def create_demand_curve(self): self.demand_curve = PolyLine() pMin = 10 pMax = 100 if (self.hvacAvail > 0): self.demand_curve.add( Point(price=min(pMin, pMax), quantity=max(self.qMin, self.qMax) * self.Pabsnom)) self.demand_curve.add( Point(price=max(pMin, pMax), quantity=min(self.qMin, self.qMax) * self.Pabsnom)) else: self.demand_curve.add(Point(price=max(pMin, pMax), quantity=0)) self.demand_curve.add(Point(price=min(pMin, pMax), quantity=0)) return self.demand_curve def iniState(self): self.hvacAvail = 1 self.pClear = None self.qMin = 0.7 self.qMax = self.qmax self.qNorm = self.qMax self.qClear = self.qNorm def updateState(self, peer, sender, bus, topic, headers, message): '''Subscribe to device data from message bus ''' _log.debug('Received one new dataset') info = message[0].copy() self.hvacAvail = info['SupplyFanStatus'] if (self.hvacAvail > 0): self.qNorm = self.qMax else: self.qNorm = 0 def updateSet(self): if self.pClear is not None and not self.nonResponsive and self.hvacAvail: self.qClear = self.clamp(self.demand_curve.x(self.pClear), self.qMax, self.qMin) else: self.qClear = 0 # if self.qClear is None: # self.qClear = 0. def clamp(self, value, x1, x2): minValue = min(x1, x2) maxValue = max(x1, x2) return min(max(value, minValue), maxValue) def price_callback(self, timestamp, market_name, buyer_seller, price, quantity): _log.debug("the price is {}".format(price)) self.pClear = price if self.pClear is not None: self.updateSet() _log.debug("the new lightingt is {}".format(self.qClear)) gevent.sleep(random.random()) self.vip.rpc.call('platform.actuator', 'set_point', self.agent_name, self.subscribing_topic + '/' + self.agent_name, round(self.qClear, 2)).get(timeout=5) def error_callback(self, timestamp, market_name, buyer_seller, error_code, error_message, aux): _log.debug("the new lightingt is {}".format(self.qNorm)) self.vip.rpc.call('platform.actuator', 'set_point', self.agent_name, self.subscribing_topic + '/' + self.agent_name, round(self.qNorm, 2)).get(timeout=5) def ease(self, target, current, limit): return current - np.sign(current - target) * min( abs(current - target), abs(limit))
class VAVAgent(MarketAgent, FirstOrderZone): """ The SampleElectricMeterAgent serves as a sample of an electric meter that sells electricity for a single building at a fixed price. """ def __init__(self, market_name, agent_name, x0, x1, x2, x3, x4, c0, c1, c2, c3, c4, tMinAdj, tMaxAdj, mDotMin, mDotMax, tIn, nonResponsive, verbose_logging, device_topic, device_points, parent_device_topic, parent_device_points, base_rpc_path, activate_topic, actuator, mode, setpoint_mode, sim_flag, **kwargs): super(VAVAgent, self).__init__(verbose_logging, **kwargs) self.market_name = market_name self.agent_name = agent_name self.x0 = x0 self.x1 = x1 self.x2 = x2 self.x3 = x3 self.x4 = x4 self.c0 = c0 self.c1 = c1 self.c2 = c2 self.c3 = c3 self.c4 = c4 self.hvac_avail = 0 self.tOut = 32 self.zone_airflow = 10 self.zone_datemp = 12.78 self.tDel = 0.25 self.t_ease = 0.25 self.tNomAdj = tIn self.temp_stpt = self.tNomAdj self.tIn = self.tNomAdj self.p_clear = None self.q_clear = None self.demand_curve = None self.tMinAdj = tMinAdj self.tMaxAdj = tMaxAdj self.mDotMin = mDotMin self.mDotMax = mDotMax self.qHvacSens = self.zone_airflow * 1006. * (self.zone_datemp - self.tIn) self.qMin = min(0, self.mDotMin * 1006. * (self.zone_datemp - self.tIn)) self.qMax = min(0, self.mDotMax * 1006. * (self.zone_datemp - self.tIn)) self.default = None self.actuator = actuator self.mode = mode self.nonResponsive = nonResponsive self.sim_flag = sim_flag if self.sim_flag: self.actuate_enabled = 1 else: self.actuate_enabled = 0 self.setpoint_offset = 0.0 if isinstance(setpoint_mode, dict): self.mode_status = True self.status_point = setpoint_mode["point"] self.setpoint_mode_true_offset = setpoint_mode["true_value"] self.setpoint_mode_false_offset = setpoint_mode["false_value"] else: self.mode_status = False self.device_topic = device_topic self.parent_device_topic = parent_device_topic self.actuator_topic = base_rpc_path self.activate_topic = activate_topic # Parent device point mapping (AHU level points) self.supply_fan_status = parent_device_points.get("supply_fan_status", "SupplyFanStatus") self.outdoor_air_temperature = parent_device_points.get("outdoor_air_temperature", "OutdoorAirTemperature") # Device point mapping (VAV level points) self.zone_datemp_name = device_points.get("zone_dat", "ZoneDischargeAirTemperature") self.zone_airflow_name = device_points.get("zone_airflow", "ZoneAirFlow") self.zone_temp_name = device_points.get("zone_temperature", "ZoneTemperature") self.join_market(self.market_name, BUYER, None, self.offer_callback, None, self.price_callback, self.error_callback) @Core.receiver('onstart') def setup(self, sender, **kwargs): _log.debug('Subscribing to device' + self.device_topic) self.vip.pubsub.subscribe(peer='pubsub', prefix=self.device_topic, callback=self.update_zone_state) _log.debug('Subscribing to parent' + self.parent_device_topic) self.vip.pubsub.subscribe(peer='pubsub', prefix=self.parent_device_topic, callback=self.update_state) _log.debug('Subscribing to ' + self.activate_topic) self.vip.pubsub.subscribe(peer='pubsub', prefix=self.activate_topic, callback=self.update_actuation_state) def offer_callback(self, timestamp, market_name, buyer_seller): result, message = self.make_offer(market_name, buyer_seller, self.create_demand_curve()) _log.debug("{}: demand max {} and min {} at {}".format(self.agent_name, -self.demand_curve.x(10), -self.demand_curve.x(100), timestamp)) _log.debug("{}: result of the make offer {} at {}".format(self.agent_name, result, timestamp)) if not result: _log.debug("{}: maintain old set point {}".format(self.agent_name, self.temp_stpt)) if self.sim_flag: self.actuate_setpoint() def create_demand_curve(self): self.demand_curve = PolyLine() p_min = 10. p_max = 100. qMin = abs(self.get_q_min()) qMax = abs(self.get_q_max()) if self.hvac_avail: self.demand_curve.add(Point(price=max(p_min, p_max), quantity=min(qMin, qMax))) self.demand_curve.add(Point(price=min(p_min, p_max), quantity=max(qMin, qMax))) else: self.demand_curve.add(Point(price=max(p_min, p_max), quantity=0.1)) self.demand_curve.add(Point(price=min(p_min, p_max), quantity=0.1)) if self.hvac_avail: _log.debug("{} - Tout {} - Tin {} - q {}".format(self.agent_name, self.tOut, self.tIn, self.qHvacSens)) return self.demand_curve def update_zone_state(self, peer, sender, bus, topic, headers, message): """ Subscribe to device data from message bus :param peer: :param sender: :param bus: :param topic: :param headers: :param message: :return: """ _log.debug('{} received zone info'.format(self.agent_name)) info = message[0] if not self.sim_flag: self.zone_datemp = temp_f2c(info[self.zone_datemp_name]) self.zone_airflow = flow_cfm2cms(info[self.zone_airflow_name]) self.tIn = temp_f2c(info[self.zone_temp_name]) else: self.zone_datemp = info[self.zone_datemp_name] self.zone_airflow = info[self.zone_airflow_name] self.tIn = info[self.zone_temp_name] if self.mode_status: if info[self.status_point]: self.setpoint_offset = self.setpoint_mode_true_offset _log.debug("Setpoint offset: {}".format(self.setpoint_offset)) else: self.setpoint_offset = self.setpoint_mode_false_offset _log.debug("Setpoint offset: {}".format(self.setpoint_offset)) self.qHvacSens = self.zone_airflow * 1006. * (self.zone_datemp - self.tIn) self.qMin = min(0, self.mDotMin * 1006. * (self.zone_datemp - self.tIn)) self.qMax = min(0, self.mDotMax * 1006. * (self.zone_datemp - self.tIn)) def update_state(self, peer, sender, bus, topic, headers, message): """ Subscribe to device data from message bus. :param peer: :param sender: :param bus: :param topic: :param headers: :param message: :return: """ _log.debug('{} received one parent_device ' 'information on: {}'.format(self.agent_name, topic)) info = message[0] if not self.sim_flag: self.tOut = temp_f2c(info[self.outdoor_air_temperature]) else: self.tOut = info[self.outdoor_air_temperature] self.hvac_avail = info[self.supply_fan_status] def update_actuation_state(self, peer, sender, bus, topic, headers, message): """ Subscribe to device data from message bus. :param peer: :param sender: :param bus: :param topic: :param headers: :param message: :return: """ _log.debug('{} received update actuation.'.format(self.agent_name)) _log.debug("Current actuation state: {} - '" "update actuation state: {}".format(self.actuate_enabled, message)) if not self.actuate_enabled and message: self.default = self.vip.rpc.call(self.actuator, 'get_point', self.actuator_topic).get(timeout=10) self.actuate_enabled = message if not self.actuate_enabled: if self.mode == 1: self.vip.rpc.call(self.actuator, 'set_point', self.agent_name, self.actuator_topic, None).get(timeout=10) else: if self.default is not None: self.vip.rpc.call(self.actuator, 'set_point', self.agent_name, self.actuator_topic, self.default).get(timeout=10) def update_setpoint(self): if self.p_clear is not None and not self.nonResponsive and self.hvac_avail: self.q_clear = clamp(-self.demand_curve.x(self.p_clear), self.qMax, self.qMin) self.temp_stpt = clamp(self.getT(self.q_clear), self.tMinAdj, self.tMaxAdj) else: self.temp_stpt = clamp(ease(self.tNomAdj, self.temp_stpt, self.t_ease), self.tMinAdj, self.tMaxAdj) self.q_clear = clamp(self.getQ(self.temp_stpt), self.qMax, self.qMin) if self.q_clear is None: self.q_clear = 0. def get_q_min(self): t = self.tMaxAdj q = clamp(self.getQ(t), self.qMax, self.qMin) return q def get_q_max(self): # t = self.clamp(self.temp_stpt-self.tDel, self.tMinAdj, self.tMaxAdj) t = self.tMinAdj q = clamp(self.getQ(t), self.qMax, self.qMin) return q def price_callback(self, timestamp, market_name, buyer_seller, price, quantity): _log.debug("{} - price of {} for market: {}".format(self.agent_name, price, market_name)) self.p_clear = price if not self.qMax and not self.qMin and not self.sim_flag: self.update_actuation_state(None, None, None, None, None, 0) return if self.p_clear is not None: self.update_setpoint() _log.debug("New set point is {}".format(self.temp_stpt)) self.actuate_setpoint() def error_callback(self, timestamp, market_name, buyer_seller, error_code, error_message, aux): _log.debug("{} - error for Market: {} {}, Message: {}".format(self.agent_name, market_name, buyer_seller, aux)) if self.actuate_enabled: if market_name == "electric": if aux.get('SQx,DQn', 0) == -1: self.temp_stpt = self.tMaxAdj self.actuate_setpoint() return if self.sim_flag: self.temp_stpt = self.tNomAdj self.actuate_setpoint() def actuate_setpoint(self): temp_stpt = self.temp_stpt - self.setpoint_offset if self.actuate_enabled: _log.debug("{} - setting {} with value {}".format(self.agent_name, self.actuator_topic, temp_stpt)) self.vip.rpc.call(self.actuator, 'set_point', self.agent_name, self.actuator_topic, temp_stpt).get(timeout=10)
class LightAgent(MarketAgent, FirstOrderZone): """ The SampleElectricMeterAgent serves as a sample of an electric meter that sells electricity for a single building at a fixed price. """ def __init__(self, market_name,agent_name,k,qmax,Pabsnom,nonResponsive,verbose_logging,subscribing_topic, **kwargs): super(LightAgent, self).__init__(verbose_logging, **kwargs) self.market_name = market_name self.agent_name = agent_name self.k = k self.qmax = qmax self.Pabsnom=Pabsnom self.nonResponsive = nonResponsive self.iniState() self.subscribing_topic=subscribing_topic self.join_market(self.market_name, BUYER, None, self.offer_callback, None, self.price_callback, self.error_callback) @Core.receiver('onstart') def setup(self, sender, **kwargs): _log.debug('Subscribing to '+'devices/CAMPUS/BUILDING1/AHU1/all') self.vip.pubsub.subscribe(peer='pubsub', prefix='devices/CAMPUS/BUILDING1/AHU1/all', callback=self.updateState) def offer_callback(self, timestamp, market_name, buyer_seller): result,message=self.make_offer(market_name, buyer_seller, self.create_demand_curve()) _log.debug("results of the make offer {}".format(result)) if not result: _log.debug("the new lightingt (maintain{}".format(self.qMax)) gevent.sleep(random.random()) self.vip.rpc.call('platform.actuator','set_point', self.agent_name,self.subscribing_topic+'/'+self.agent_name,round(self.qNorm,2)).get(timeout=6) def create_demand_curve(self): self.demand_curve = PolyLine() pMin = 10 pMax = 100 if (self.hvacAvail > 0): self.demand_curve.add(Point(price=min(pMin, pMax),quantity=max(self.qMin, self.qMax)*self.Pabsnom)) self.demand_curve.add(Point(price=max(pMin, pMax),quantity=min(self.qMin, self.qMax)*self.Pabsnom)) else: self.demand_curve.add(Point(price=max(pMin, pMax), quantity=0)) self.demand_curve.add(Point(price=min(pMin, pMax),quantity=0)) return self.demand_curve def iniState(self): self.hvacAvail = 1 self.pClear = None self.qMin = 0.7 self.qMax = self.qmax self.qNorm=self.qMax self.qClear=self.qNorm def updateState(self, peer, sender, bus, topic, headers, message): '''Subscribe to device data from message bus ''' _log.debug('Received one new dataset') info = {} for key, value in message[0].items(): info[key] = value self.hvacAvail = info['SupplyFanStatus'] if (self.hvacAvail > 0): self.qNorm=self.qMax else: self.qNorm=0 def updateSet(self): if self.pClear is not None and not self.nonResponsive and self.hvacAvail: self.qClear = self.clamp(self.demand_curve.x(self.pClear), self.qMax, self.qMin) else: self.qClear = 0 # if self.qClear is None: # self.qClear = 0. def clamp(self, value, x1, x2): minValue = min(x1, x2) maxValue = max(x1, x2) return min(max(value, minValue), maxValue) def price_callback(self, timestamp, market_name, buyer_seller, price, quantity): _log.debug("the price is {}".format(price)) self.pClear=price if self.pClear is not None: self.updateSet() _log.debug("the new lightingt is {}".format(self.qClear)) gevent.sleep(random.random()) self.vip.rpc.call('platform.actuator','set_point', self.agent_name,self.subscribing_topic+'/'+self.agent_name,round(self.qClear,2)).get(timeout=5) def error_callback(self, timestamp, market_name, buyer_seller, error_code, error_message, aux): _log.debug("the new lightingt is {}".format(self.qNorm)) self.vip.rpc.call('platform.actuator','set_point', self.agent_name,self.subscribing_topic+'/'+self.agent_name,round(self.qNorm,2)).get(timeout=5) def ease(self, target, current, limit): return current - np.sign(current-target)*min(abs(current-target), abs(limit))
class LightAgent(MarketAgent): """ Transactive control lighting agent. """ def __init__(self, market_name, agent_name, min_occupied_lighting_level, default_occ_lighting_level, power_absnom, non_responsive, verbose_logging, base_rpc_path, schedule_topic, schedule_point, actuator, **kwargs): super(LightAgent, self).__init__(verbose_logging, **kwargs) self.market_name = market_name self.agent_name = agent_name self.qmin = min_occupied_lighting_level/100.0 self.qmax = default_occ_lighting_level/100.0 self.power_absnom = power_absnom self.non_responsive = non_responsive self.actuation_topic = base_rpc_path self.actuator = actuator self.schedule_topic = schedule_topic self.schedule_point = schedule_point self.demand_curve = None self.hvac_avail = 1 self.price_cleared = None self.qnorm = float(self.qmax) self.lighting_stpt = float(self.qnorm) self.join_market(self.market_name, BUYER, None, self.offer_callback, None, self.price_callback, self.error_callback) @Core.receiver('onstart') def setup(self, sender, **kwargs): _log.debug("{}: schedule topic for HVAC - {}".format(self.agent_name, self.schedule_topic)) self.vip.pubsub.subscribe(peer='pubsub', prefix=self.schedule_topic, callback=self.update_state) def offer_callback(self, timestamp, market_name, buyer_seller): result, message = self.make_offer(market_name, buyer_seller, self.create_demand_curve()) _log.debug("{}: result of the make offer {} at {}".format(self.agent_name, result, timestamp)) if not result: _log.debug("{}: maintain current lighting level: {}".format(self.agent_name, self.qnorm)) self.vip.rpc.call(self.actuator, 'set_point', self.agent_name, self.actuation_topic, round(self.qnorm, 2)).get(timeout=10) def create_demand_curve(self): """ Create electric demand curve for agents respective lighting zone. :return: """ self.demand_curve = PolyLine() p_min = 10. p_max = 100. if self.hvac_avail: self.demand_curve.add(Point(price=min(p_min, p_max), quantity=max(self.qmin, self.qmax) * self.power_absnom)) self.demand_curve.add(Point(price=max(p_min, p_max), quantity=min(self.qmin, self.qmax)* self.power_absnom)) else: self.demand_curve.add(Point(price=max(p_min, p_max), quantity=0.)) self.demand_curve.add(Point(price=min(p_min, p_max), quantity=0.)) return self.demand_curve def update_state(self, peer, sender, bus, topic, headers, message): """ Update state from device data from message bus. :param peer: :param sender: :param bus: :param topic: :param headers: :param message: :return: """ _log.debug('{}: received one new data set'.format(self.agent_name)) info = message[0] self.hvac_avail = info[self.schedule_point] if self.hvac_avail: self.qnorm = self.qmax else: self.qnorm = 0.0 def update_set(self): """ Determine new set point for the zone lighting level. :return: """ if self.price_cleared is not None and not self.non_responsive and self.hvac_avail: self.lighting_stpt = clamp(self.demand_curve.x(self.price_cleared) / self.power_absnom, self.qmax, self.qmin) else: self.lighting_stpt = self.qnorm def price_callback(self, timestamp, market_name, buyer_seller, price, quantity): """ Price callback for agent when interacting with electric market. :param timestamp: :param market_name: :param buyer_seller: :param price: :param quantity: :return: """ _log.debug("{}: price {} quantity{}, for {} as {} at {}".format(self.agent_name, price, quantity, market_name, buyer_seller, timestamp)) self.price_cleared = price if self.price_cleared is not None: self.update_set() _log.debug("{}: new lighting level is {}".format(self.agent_name, self.lighting_stpt)) self.vip.rpc.call(self.actuator, 'set_point', self.agent_name, self.actuation_topic, round(self.lighting_stpt, 2)).get(timeout=10) def error_callback(self, timestamp, market_name, buyer_seller, error_code, error_message, aux): """ Error callback for agent when interacting with electric market. :param timestamp: :param market_name: :param buyer_seller: :param error_code: :param error_message: :param aux: :return: """ _log.debug("{}: error for {} at {} - Message: {}".format(self.agent_name, market_name, timestamp, error_message)) if market_name == "electric": # if aux.get('Sn,Dn', 0) == -1 and aux.get('Sx,Dx', 0) == -1: if aux.get('SQx,DQn', 0) == -1: _log.debug("{}: use minimum lighting level: {}".format(self.agent_name, self.qmin)) self.vip.rpc.call(self.actuator, 'set_point', self.agent_name, self.actuation_topic, self.qmin).get(timeout=10) return else: _log.debug("{}: maintain default lighting level at: {}".format(self.agent_name, self.qnorm)) self.vip.rpc.call(self.actuator, 'set_point', self.agent_name, self.actuation_topic, round(self.qnorm, 2)).get(timeout=10)