From ffec8ab47be192540b1773fa39a5198bcc57a3c9 Mon Sep 17 00:00:00 2001 From: kini136 Date: Mon, 1 Feb 2021 22:26:59 -0800 Subject: [PATCH 01/14] Added simulation mode --- AFDDSchedulerAgent/agentdir/agent.py | 62 +++++++++++++++++++++++++++- AFDDSchedulerAgent/config | 1 + 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/agentdir/agent.py index 99fe0da..4081ef7 100644 --- a/AFDDSchedulerAgent/agentdir/agent.py +++ b/AFDDSchedulerAgent/agentdir/agent.py @@ -19,6 +19,9 @@ from volttron.platform.messaging import topics from datetime import datetime, timedelta, date, time import holidays +from volttron.platform.agent.utils import (get_aware_utc_now, format_timestamp) +from volttron.platform.scheduling import cron +from dateutil import parser utils.setup_logging() _log = logging.getLogger(__name__) @@ -77,6 +80,7 @@ def __init__(self, config_path, **kwargs): self.condition_data = [] self.rthr = 0 self.device_name = [] + self.simulation = True self.default_config = utils.load_config(config_path) self.vip.config.set_default("config", self.default_config) @@ -99,6 +103,7 @@ def configure(self, config_name, action, contents): self.excess_operation = self.current_config.get("excess_operation") self.timezone = self.current_config.get("timezone", "PDT") self.condition_list = self.current_config.get("condition_list", {}) + self.simulation = self.current_config.get("simulation", True) self.device_true_time = 0 _log.info("current date time {}".format(datetime.utcnow())) self.on_subscribe() @@ -111,6 +116,60 @@ def configure(self, config_name, action, contents): schedule_time = self.schedule_time self.core.schedule(cron(schedule_time), self.run_schedule) + if not self.simulation: + self.core.schedule(cron(self.run_schedule), self.scheduled_run_process) + else: + self.simulation_setup() + + def simulation_setup(self): + _log.debug("Running with simulation using topic %s", + self.simulation_data_topic) + campus = self.device["campus"] + building = self.device["building"] + device_config = self.device["unit"] + self.publish_topics = "/".join([self.analysis_name, campus, building]) + multiple_devices = isinstance(device_config, dict) + self.command_devices = device_config.keys() + + try: + for device_name in device_config: + device_topic = topics.DEVICES_VALUE(campus=campus, building=building, \ + unit=device_name, path="", \ + point="all") + + self.device_topic_list.update({device_topic: device_name}) + self.device_name.append(device_name) + + except Exception as e: + _log.error('Error configuring signal: {}'.format(e)) + + try: + for device in self.device_topic_list: + _log.info("Subscribing to " + device) + self.vip.pubsub.subscribe(peer="pubsub", prefix=device, + callback=self.simulation_time_handler) + except Exception as e: + _log.error('Error configuring signal: {}'.format(e)) + _log.error("Missing {} data to execute the AIRx process".format(device)) + + def simulation_time_handler(self, peer, sender, bus, topic, header, message): + current_time = parser.parse(header["Date"]) + _log.debug("Simulation time handler current_time: %s", current_time) + if (self.simulation_initial_time and self.simulation_start_timedelta) is None: + self.time_delta(current_time) + retraining_time_delta = current_time - self.simulation_initial_time + _log.debug(f"Simulation initial time {self.simulation_initial_time}," + f" time handler time delta {retraining_time_delta}") + # convert all the required data into a dict + self.current_data = dict(zip(self.data_point_name, + [float(message[0][x]) for x in + self.data_point_name])) + self.current_data["Date"] = current_time + # _log.debug(self.current_data) + if retraining_time_delta >= self.simulation_start_timedelta: + self.time_delta(current_time) + self.parameter_identification(current_time) + def on_subscribe(self): """ @@ -165,7 +224,6 @@ def run_schedule(self): execute the condition of the device, If all condition are true then add time into true_time. If true time is exceed the threshold time (mht) flag the excess operation TODO:The output for the agent should be similar to the EconomizerRCx agent - """ conditions = self.condition_list.get("conditions") try: @@ -190,7 +248,7 @@ def run_schedule(self): self.publish_daily_record(device_topic) def publish_daily_record(self, device_topic): - headers = {'Date': utils.format_timestamp(datetime.utcnow() \ + headers = {'Date': utils.format_timestamp(datetime.utcnow()\ .astimezone(dateutil.tz.gettz(self.timezone)))} message = {'excess_operation': bool(self.excess_operation), 'device_status': bool(self.device_status), diff --git a/AFDDSchedulerAgent/config b/AFDDSchedulerAgent/config index 9bdbf9e..79c988c 100644 --- a/AFDDSchedulerAgent/config +++ b/AFDDSchedulerAgent/config @@ -13,6 +13,7 @@ "excess_operation": false, "interval": 60, "timezone": "US/Pacific", + "simulation": true, "condition_list": { "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] From 103cd64ccd3a2438690139d4cef22c355407baa9 Mon Sep 17 00:00:00 2001 From: kini136 Date: Mon, 1 Feb 2021 23:18:34 -0800 Subject: [PATCH 02/14] Added simulation mode --- AFDDSchedulerAgent/agentdir/agent.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/agentdir/agent.py index 4081ef7..c2f90e9 100644 --- a/AFDDSchedulerAgent/agentdir/agent.py +++ b/AFDDSchedulerAgent/agentdir/agent.py @@ -76,6 +76,8 @@ def __init__(self, config_path, **kwargs): self.subdevices_list = [] self.device_status = False self.excess_operation = False + self.simulation_initial_time = None + self.simulation_start_timedelta = None self.day = None self.condition_data = [] self.rthr = 0 @@ -141,7 +143,7 @@ def simulation_setup(self): self.device_name.append(device_name) except Exception as e: - _log.error('Error configuring signal: {}'.format(e)) + _log.error('Error configuring device topic {}'.format(e)) try: for device in self.device_topic_list: @@ -160,15 +162,24 @@ def simulation_time_handler(self, peer, sender, bus, topic, header, message): retraining_time_delta = current_time - self.simulation_initial_time _log.debug(f"Simulation initial time {self.simulation_initial_time}," f" time handler time delta {retraining_time_delta}") + # convert all the required data into a dict - self.current_data = dict(zip(self.data_point_name, - [float(message[0][x]) for x in - self.data_point_name])) - self.current_data["Date"] = current_time - # _log.debug(self.current_data) + self.condition_data = [] + condition_args = self.condition_list.get("condition_args") + symbols(condition_args) + + for args in condition_args: + self.condition_data.append((args, message[0][args])) + + _log.info("condition data {}".format(self.condition_data)) if retraining_time_delta >= self.simulation_start_timedelta: self.time_delta(current_time) - self.parameter_identification(current_time) + self.run_schedule(current_time) + + def time_delta(self, current_time): + self.simulation_initial_time = current_time + self.simulation_start_timedelta = (self.simulation_initial_time.replace(hour=0, minute=0, second=0) + td( + days=self.model_start_interval)) - self.simulation_initial_time def on_subscribe(self): """ From 2f4a177abf02128b2ef68bdca3bd5e3261223488 Mon Sep 17 00:00:00 2001 From: kini136 Date: Wed, 3 Feb 2021 09:47:53 -0800 Subject: [PATCH 03/14] modified to publish it on midnight --- AFDDSchedulerAgent/agentdir/agent.py | 110 ++++++--------------------- AFDDSchedulerAgent/config | 7 ++ 2 files changed, 31 insertions(+), 86 deletions(-) diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/agentdir/agent.py index c2f90e9..2006ca9 100644 --- a/AFDDSchedulerAgent/agentdir/agent.py +++ b/AFDDSchedulerAgent/agentdir/agent.py @@ -52,7 +52,7 @@ def __init__(self, config_path, **kwargs): } } } - self.maximum_hour_threshold: 5.0 + self.maximum_hour_threshold = 5.0 self.excess_operation: False self.interval: 60 self.timezone = "US/Pacific" @@ -76,14 +76,13 @@ def __init__(self, config_path, **kwargs): self.subdevices_list = [] self.device_status = False self.excess_operation = False - self.simulation_initial_time = None - self.simulation_start_timedelta = None self.day = None self.condition_data = [] self.rthr = 0 self.device_name = [] self.simulation = True - + self.year = 2021 + self.schedule = {"weekday_sch": ["5:30","18:30"], "weekend_holiday_sch": ["0:00","0:00"]} self.default_config = utils.load_config(config_path) self.vip.config.set_default("config", self.default_config) self.vip.config.subscribe(self.configure, actions=["NEW", "UPDATE"], \ @@ -106,26 +105,18 @@ def configure(self, config_name, action, contents): self.timezone = self.current_config.get("timezone", "PDT") self.condition_list = self.current_config.get("condition_list", {}) self.simulation = self.current_config.get("simulation", True) + self.year = self.current_config.get("year", 2021) + self.schedule = self.current_config.get("schedule", "") self.device_true_time = 0 _log.info("current date time {}".format(datetime.utcnow())) self.on_subscribe() # self.core.periodic(self.interval, self.run_schedule) self.device_true_time = 0 #at mid night zero the total minute - date_today = datetime.utcnow().astimezone(dateutil.tz.gettz(self.timezone)) - if date_today in holidays.US(years=2020) or date_today.weekday() == 5 and 6: - schedule_time = "* * * * *" - else: - schedule_time = self.schedule_time - self.core.schedule(cron(schedule_time), self.run_schedule) - - if not self.simulation: - self.core.schedule(cron(self.run_schedule), self.scheduled_run_process) - else: - self.simulation_setup() - def simulation_setup(self): + def on_subscribe(self): _log.debug("Running with simulation using topic %s", self.simulation_data_topic) + campus = self.device["campus"] building = self.device["building"] device_config = self.device["unit"] @@ -149,21 +140,23 @@ def simulation_setup(self): for device in self.device_topic_list: _log.info("Subscribing to " + device) self.vip.pubsub.subscribe(peer="pubsub", prefix=device, - callback=self.simulation_time_handler) + callback=self.scheduler_time_handler) except Exception as e: _log.error('Error configuring signal: {}'.format(e)) _log.error("Missing {} data to execute the AIRx process".format(device)) - def simulation_time_handler(self, peer, sender, bus, topic, header, message): - current_time = parser.parse(header["Date"]) + def scheduler_time_handler(self, peer, sender, bus, topic, header, message): + if self.simulation: + current_time = parse(header["Date"]) + else: + current_time = get_aware_utc_now() _log.debug("Simulation time handler current_time: %s", current_time) - if (self.simulation_initial_time and self.simulation_start_timedelta) is None: - self.time_delta(current_time) - retraining_time_delta = current_time - self.simulation_initial_time - _log.debug(f"Simulation initial time {self.simulation_initial_time}," - f" time handler time delta {retraining_time_delta}") + date_today = current_time.date() + if date_today in holidays.US(years=self.year) or date_today.weekday() == 5 and 6: + schedule = self.schedule["weekday"] + else: + schedule = self.schedule["weekend_holiday"] - # convert all the required data into a dict self.condition_data = [] condition_args = self.condition_list.get("condition_args") symbols(condition_args) @@ -172,65 +165,11 @@ def simulation_time_handler(self, peer, sender, bus, topic, header, message): self.condition_data.append((args, message[0][args])) _log.info("condition data {}".format(self.condition_data)) - if retraining_time_delta >= self.simulation_start_timedelta: - self.time_delta(current_time) + # convert all the required data into a dict + if current_time.time() < parse(schedule[0]).time() or current_time.time() > parse(schedule[1]).time(): self.run_schedule(current_time) - def time_delta(self, current_time): - self.simulation_initial_time = current_time - self.simulation_start_timedelta = (self.simulation_initial_time.replace(hour=0, minute=0, second=0) + td( - days=self.model_start_interval)) - self.simulation_initial_time - - def on_subscribe(self): - """ - - :return: - """ - campus = self.device["campus"] - building = self.device["building"] - device_config = self.device["unit"] - self.publish_topics = "/".join([self.analysis_name, campus, building]) - multiple_devices = isinstance(device_config, dict) - self.command_devices = device_config.keys() - - try: - for device_name in device_config: - device_topic = topics.DEVICES_VALUE(campus=campus, building=building, \ - unit=device_name, path="", \ - point="all") - - self.device_topic_list.update({device_topic: device_name}) - self.device_name.append(device_name) - - except Exception as e: - _log.error('Error configuring signal: {}'.format(e)) - - try: - for device in self.device_topic_list: - _log.info("Subscribing to " + device) - self.vip.pubsub.subscribe(peer="pubsub", prefix=device, - callback=self.on_data) - except Exception as e: - _log.error('Error configuring signal: {}'.format(e)) - _log.error("Missing {} data to execute the AIRx process".format(device)) - - - def on_data(self, peer, sender, bus, topic, headers, message): - """ - Subscribe device data. - - """ - self.condition_data = [] - self.input_datetime = parse(headers.get("Date")).astimezone(dateutil.tz.gettz(self.timezone)) - condition_args = self.condition_list.get("condition_args") - symbols(condition_args) - - for args in condition_args: - self.condition_data.append((args, message[0][args])) - - _log.info("condition data {}".format(self.condition_data)) - - def run_schedule(self): + def run_schedule(self, current_time): """ execute the condition of the device, If all condition are true then add time into true_time. If true time is exceed the threshold time (mht) flag the excess operation @@ -256,11 +195,10 @@ def run_schedule(self): self.excess_operation = True for device_topic in self.device_topic_list: - self.publish_daily_record(device_topic) + self.publish_daily_record(device_topic, current_time) - def publish_daily_record(self, device_topic): - headers = {'Date': utils.format_timestamp(datetime.utcnow()\ - .astimezone(dateutil.tz.gettz(self.timezone)))} + def publish_daily_record(self, device_topic, current_time): + headers = {'Date': format_timestamp(current_time)} message = {'excess_operation': bool(self.excess_operation), 'device_status': bool(self.device_status), 'device_true_time': int(self.device_true_time)} diff --git a/AFDDSchedulerAgent/config b/AFDDSchedulerAgent/config index 79c988c..547518f 100644 --- a/AFDDSchedulerAgent/config +++ b/AFDDSchedulerAgent/config @@ -14,6 +14,13 @@ "interval": 60, "timezone": "US/Pacific", "simulation": true, + "year": 2021, + "schedule" : { + "weekday": ["6:00","18:00"], + "weekend_holiday": ["0:00","0:00"], + } + "publish_time": "0:00" + "condition_list": { "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] From 8c6a12185d747b64ba9569612cb5518b245d31aa Mon Sep 17 00:00:00 2001 From: kini136 Date: Wed, 10 Feb 2021 22:03:46 -0800 Subject: [PATCH 04/14] modified to publish it on midnight --- AFDDSchedulerAgent/agentdir/agent.py | 30 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/agentdir/agent.py index 2006ca9..19a34ff 100644 --- a/AFDDSchedulerAgent/agentdir/agent.py +++ b/AFDDSchedulerAgent/agentdir/agent.py @@ -190,22 +190,32 @@ def run_schedule(self, current_time): self.device_status = False _log.Info("one of the condition is false") - runtime_threshold = self.device_true_time / 3600 - if runtime_threshold > self.maximum_hour_threshold: + if self.initial_time: + self.initial_time = current_time + time_delta = current_time - self.simulation_initial_time + _log.debug(f"Initial time = {self.simulation_initial_time}," + f"time delta = {time_delta}") + + if time_delta >= self.maximum_hour_threshold: self.excess_operation = True - for device_topic in self.device_topic_list: - self.publish_daily_record(device_topic, current_time) + #for device_topic in self.device_topic_list: + message = {'excess_operation': bool(self.excess_operation), + 'device_status': bool(self.device_status) + } + self.publish_daily_record(self.affd_topic_name, message, current_time) + + if self.midnight(): + message = {'device_true_time': int(self.device_true_time)} + self.publish_daily_record(self.affd_topic_name, message, current_time) + self.device_true_time = 0 - def publish_daily_record(self, device_topic, current_time): + def publish_daily_record(self, topic, message, current_time): headers = {'Date': format_timestamp(current_time)} - message = {'excess_operation': bool(self.excess_operation), - 'device_status': bool(self.device_status), - 'device_true_time': int(self.device_true_time)} - device_topic = device_topic.replace("all", "report/all") + #device_topic = topic.replace("all", "report/all") try: self.vip.pubsub.publish(peer='pubsub', - topic=device_topic, + topic=topic, message=message, headers=headers) except Exception as e: From 83264b4989008bd2ad922c725b2b488153c11ddb Mon Sep 17 00:00:00 2001 From: kini136 Date: Thu, 11 Feb 2021 18:03:41 -0800 Subject: [PATCH 05/14] modified to run multiple conditions --- AFDDSchedulerAgent/agentdir/agent.py | 16 +++++++++-- .../config_multiple_conditionlist | 28 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 AFDDSchedulerAgent/config_multiple_conditionlist diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/agentdir/agent.py index 19a34ff..6150c2a 100644 --- a/AFDDSchedulerAgent/agentdir/agent.py +++ b/AFDDSchedulerAgent/agentdir/agent.py @@ -82,6 +82,7 @@ def __init__(self, config_path, **kwargs): self.device_name = [] self.simulation = True self.year = 2021 + self.midnight_time = None self.schedule = {"weekday_sch": ["5:30","18:30"], "weekend_holiday_sch": ["0:00","0:00"]} self.default_config = utils.load_config(config_path) self.vip.config.set_default("config", self.default_config) @@ -140,12 +141,12 @@ def on_subscribe(self): for device in self.device_topic_list: _log.info("Subscribing to " + device) self.vip.pubsub.subscribe(peer="pubsub", prefix=device, - callback=self.scheduler_time_handler) + callback=self.time_scheduler_handler) except Exception as e: _log.error('Error configuring signal: {}'.format(e)) _log.error("Missing {} data to execute the AIRx process".format(device)) - def scheduler_time_handler(self, peer, sender, bus, topic, header, message): + def time_scheduler_handler(self, peer, sender, bus, topic, header, message): if self.simulation: current_time = parse(header["Date"]) else: @@ -205,11 +206,20 @@ def run_schedule(self, current_time): } self.publish_daily_record(self.affd_topic_name, message, current_time) - if self.midnight(): + if self.midnight(current_time): message = {'device_true_time': int(self.device_true_time)} self.publish_daily_record(self.affd_topic_name, message, current_time) self.device_true_time = 0 + def midnight(self, current_time): + if self.midnight_time: + self.midnight_time = datetime.combine(current_time, time.max) + if current_time >= self.midnight_time: + self.midnight_time = datetime.combine(current_time, time.max) + return True + else: + return False + def publish_daily_record(self, topic, message, current_time): headers = {'Date': format_timestamp(current_time)} #device_topic = topic.replace("all", "report/all") diff --git a/AFDDSchedulerAgent/config_multiple_conditionlist b/AFDDSchedulerAgent/config_multiple_conditionlist new file mode 100644 index 0000000..216b13e --- /dev/null +++ b/AFDDSchedulerAgent/config_multiple_conditionlist @@ -0,0 +1,28 @@ +{ + "analysis_name": "devices", + # this is initiated based on a cron scheduling string - https://crontab.guru + "schedule_time": "* 0-6, 18-23 * * 0-5", + "device": { + "campus": "campus", + "building": "building", + "unit": { + "rtu4" + } + }, + "mht": 3600, + "excess_operation": false, + "interval": 60, + "timezone": "US/Pacific", + "simulation": true, + "year": 2021, + "schedule" : { + "weekday": ["6:00","18:00"], + "weekend_holiday": ["0:00","0:00"], + } + "publish_time": "0:00" + + "condition_list": { + "conditions": {"rtu": ["ReturnAirTemperature > 50.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], "ahu" : [] + "condition_args":{"rtu": ["SupplyFanStatus", "ReturnAirTemperature"] + }, +} From 5bfbe7ceb0078c4669329ebc9257ce17a1abe1b6 Mon Sep 17 00:00:00 2001 From: kini136 Date: Thu, 11 Feb 2021 22:24:33 -0800 Subject: [PATCH 06/14] modified to run multiple conditions --- AFDDSchedulerAgent/agentdir/agent.py | 21 ++++++-------- AFDDSchedulerAgent/config | 29 +++++++++---------- .../config_multiple_conditionlist | 5 ++-- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/agentdir/agent.py index 6150c2a..b98a3a5 100644 --- a/AFDDSchedulerAgent/agentdir/agent.py +++ b/AFDDSchedulerAgent/agentdir/agent.py @@ -40,21 +40,18 @@ def __init__(self, config_path, **kwargs): # Set up default configuration and config store self.analysis_name = "Scheduler" self.schedule_time = "* 18 * * *" + self.campus = "campus" + self.building = "building" self.device = { - "campus": "campus", - "building": "building", - "unit": { "rtu1": { "subdevices": [] }, "rtu4": { "subdevices": [] } - } } self.maximum_hour_threshold = 5.0 self.excess_operation: False - self.interval: 60 self.timezone = "US/Pacific" self.condition_list = None @@ -83,6 +80,7 @@ def __init__(self, config_path, **kwargs): self.simulation = True self.year = 2021 self.midnight_time = None + self.initial_time = None self.schedule = {"weekday_sch": ["5:30","18:30"], "weekend_holiday_sch": ["0:00","0:00"]} self.default_config = utils.load_config(config_path) self.vip.config.set_default("config", self.default_config) @@ -100,7 +98,8 @@ def configure(self, config_name, action, contents): self.analysis_name = self.current_config.get("analysis_name") self.schedule_time = self.current_config.get("schedule_time") - self.device = self.current_config.get("device") + self.campus = self.current_config.get("campus") + self.building = self.current_config.get("building") self.maximum_hour_threshold = self.current_config.get("mht") self.excess_operation = self.current_config.get("excess_operation") self.timezone = self.current_config.get("timezone", "PDT") @@ -117,17 +116,14 @@ def configure(self, config_name, action, contents): def on_subscribe(self): _log.debug("Running with simulation using topic %s", self.simulation_data_topic) - - campus = self.device["campus"] - building = self.device["building"] device_config = self.device["unit"] - self.publish_topics = "/".join([self.analysis_name, campus, building]) + self.publish_topics = "/".join([self.analysis_name, self.campus, self.building]) multiple_devices = isinstance(device_config, dict) self.command_devices = device_config.keys() try: for device_name in device_config: - device_topic = topics.DEVICES_VALUE(campus=campus, building=building, \ + device_topic = topics.DEVICES_VALUE(campus=self.campus, building=self.building, \ unit=device_name, path="", \ point="all") @@ -189,10 +185,11 @@ def run_schedule(self, current_time): _log.Info('All condition true time {}'.format(self.device_true_time)) else: self.device_status = False - _log.Info("one of the condition is false") + _log.Info("One of the condition is false") if self.initial_time: self.initial_time = current_time + time_delta = current_time - self.simulation_initial_time _log.debug(f"Initial time = {self.simulation_initial_time}," f"time delta = {time_delta}") diff --git a/AFDDSchedulerAgent/config b/AFDDSchedulerAgent/config index 547518f..87d0b5e 100644 --- a/AFDDSchedulerAgent/config +++ b/AFDDSchedulerAgent/config @@ -1,14 +1,5 @@ { - "analysis_name": "devices", - # this is initiated based on a cron scheduling string - https://crontab.guru - "schedule_time": "* 0-6, 18-23 * * 0-5", - "device": { - "campus": "campus", - "building": "building", - "unit": { - "rtu4" - } - }, + "analysis_name": "devices",, "mht": 3600, "excess_operation": false, "interval": 60, @@ -19,10 +10,16 @@ "weekday": ["6:00","18:00"], "weekend_holiday": ["0:00","0:00"], } - "publish_time": "0:00" - - "condition_list": { - "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], - "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] - }, + afdd_scheduler : + "device": { + "campus": "campus", + "building": "building", + "unit": { + "rtu4" + } + }, + "condition_list": { + "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], + "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] + }, } diff --git a/AFDDSchedulerAgent/config_multiple_conditionlist b/AFDDSchedulerAgent/config_multiple_conditionlist index 216b13e..f54cd48 100644 --- a/AFDDSchedulerAgent/config_multiple_conditionlist +++ b/AFDDSchedulerAgent/config_multiple_conditionlist @@ -22,7 +22,8 @@ "publish_time": "0:00" "condition_list": { - "conditions": {"rtu": ["ReturnAirTemperature > 50.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], "ahu" : [] - "condition_args":{"rtu": ["SupplyFanStatus", "ReturnAirTemperature"] + "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], + "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] }, } + From cdc22c3b3cd2b54184254ea416a4a575c3e9f6db Mon Sep 17 00:00:00 2001 From: kini136 Date: Thu, 11 Feb 2021 22:56:06 -0800 Subject: [PATCH 07/14] Added README.md file --- AFDDSchedulerAgent/README.md | 67 +++++++++++++++++++ AFDDSchedulerAgent/agentdir/agent.py | 43 ++++++------ AFDDSchedulerAgent/config | 23 +++---- .../config_multiple_conditionlist | 28 ++++---- 4 files changed, 113 insertions(+), 48 deletions(-) create mode 100644 AFDDSchedulerAgent/README.md diff --git a/AFDDSchedulerAgent/README.md b/AFDDSchedulerAgent/README.md new file mode 100644 index 0000000..2dd4cbf --- /dev/null +++ b/AFDDSchedulerAgent/README.md @@ -0,0 +1,67 @@ +# AFDDSchedulerAgents Agent +There may be a secondary AFDD +(analyze hours of operation for RTUs with inherent heating capability) or simply forego using discharge +temperature as a proxy for proof of fan operations. Optimal Start is not accounted for +(in determination of an earlier start time than the schedule) nor is the night setback +minimum and maximum space temperature configuration that will cause the RTU to operate during +unoccupied hours to maintain the building space temperatures at the specified minimum and maximum temperatures. + +## AFDDSchedulerAgent Agent Configuration + +The json format of the config files are specified below. + +* Agent config file: + +```json +{ + "analysis_name": "devices", + "campus": "campus", + "building": "building", + "device": "rtu4", + "mht": 3600, + "excess_operation": false, + "interval": 60, + "timezone": "US/Pacific", + "simulation": true, + "year": 2021, + "schedule" : { + "weekday": ["6:00","18:00"], + "weekend_holiday": ["0:00","0:00"] + }, + "condition_list": { + "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], + "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] + } +} +```` + +## Install and activate VOLTTRON environment +For installing, starting, and activating the VOLTTRON environment, refer to the following VOLTTRON readthedocs: +https://volttron.readthedocs.io/en/develop/introduction/platform-install.html + +## Installing and Running AFDDSchedulerAgent Agent +Install and start the AFDDSchedulerAgent Agent using the script install-agent.py as describe below: + +``` +python VOLTTRON_ROOT/scripts/install-agent.py -s + -c \ + -i agent.AFDDSchedulerAgent \ + -t AFDDSchedulerAgent \ + --start \ + --force +``` +, where VOLTTRON_ROOT is the root of the source directory of VOLTTRON. + +-s : path of top most folder of the ILC agent + +-c : path of the agent config file + +-i : agent VIP identity + +-t : agent tag + +--start (optional): start after installation + +--force (optional): overwrites existing AFDDSchedulerAgents agent with identity "agent.AFDDSchedulerAgent" + + diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/agentdir/agent.py index b98a3a5..f8d687f 100644 --- a/AFDDSchedulerAgent/agentdir/agent.py +++ b/AFDDSchedulerAgent/agentdir/agent.py @@ -39,16 +39,15 @@ def __init__(self, config_path, **kwargs): super(AFDDSchedulerAgent, self).__init__(**kwargs) # Set up default configuration and config store self.analysis_name = "Scheduler" - self.schedule_time = "* 18 * * *" self.campus = "campus" self.building = "building" self.device = { - "rtu1": { - "subdevices": [] - }, - "rtu4": { - "subdevices": [] - } + "rtu1": { + "subdevices": [] + }, + "rtu4": { + "subdevices": [] + } } self.maximum_hour_threshold = 5.0 self.excess_operation: False @@ -58,7 +57,8 @@ def __init__(self, config_path, **kwargs): # Set up default configuration and config store self.default_config = { "analysis_name": self.analysis_name, - "schedule_time": self.schedule_time, + "campus": self.campus, + "building": self.building, "device": self.device, "mht": 3600, "excess_operation": False, @@ -81,7 +81,7 @@ def __init__(self, config_path, **kwargs): self.year = 2021 self.midnight_time = None self.initial_time = None - self.schedule = {"weekday_sch": ["5:30","18:30"], "weekend_holiday_sch": ["0:00","0:00"]} + self.schedule = {"weekday_sch": ["5:30", "18:30"], "weekend_holiday_sch": ["0:00", "0:00"]} self.default_config = utils.load_config(config_path) self.vip.config.set_default("config", self.default_config) self.vip.config.subscribe(self.configure, actions=["NEW", "UPDATE"], \ @@ -97,9 +97,9 @@ def configure(self, config_name, action, contents): self.current_config.update(contents) self.analysis_name = self.current_config.get("analysis_name") - self.schedule_time = self.current_config.get("schedule_time") self.campus = self.current_config.get("campus") self.building = self.current_config.get("building") + self.device = self.current_config.get("device") self.maximum_hour_threshold = self.current_config.get("mht") self.excess_operation = self.current_config.get("excess_operation") self.timezone = self.current_config.get("timezone", "PDT") @@ -111,18 +111,17 @@ def configure(self, config_name, action, contents): _log.info("current date time {}".format(datetime.utcnow())) self.on_subscribe() # self.core.periodic(self.interval, self.run_schedule) - self.device_true_time = 0 #at mid night zero the total minute + self.device_true_time = 0 # at mid night zero the total minute def on_subscribe(self): _log.debug("Running with simulation using topic %s", self.simulation_data_topic) - device_config = self.device["unit"] self.publish_topics = "/".join([self.analysis_name, self.campus, self.building]) - multiple_devices = isinstance(device_config, dict) - self.command_devices = device_config.keys() + multiple_devices = isinstance(self.device, dict) + self.command_devices = self.device.keys() try: - for device_name in device_config: + for device_name in self.device: device_topic = topics.DEVICES_VALUE(campus=self.campus, building=self.building, \ unit=device_name, path="", \ point="all") @@ -168,9 +167,11 @@ def time_scheduler_handler(self, peer, sender, bus, topic, header, message): def run_schedule(self, current_time): """ + + :param current_time: + :return: this function publishes --- execute the condition of the device, If all condition are true then add time into true_time. If true time is exceed the threshold time (mht) flag the excess operation - TODO:The output for the agent should be similar to the EconomizerRCx agent """ conditions = self.condition_list.get("conditions") try: @@ -197,7 +198,7 @@ def run_schedule(self, current_time): if time_delta >= self.maximum_hour_threshold: self.excess_operation = True - #for device_topic in self.device_topic_list: + # for device_topic in self.device_topic_list: message = {'excess_operation': bool(self.excess_operation), 'device_status': bool(self.device_status) } @@ -209,6 +210,11 @@ def run_schedule(self, current_time): self.device_true_time = 0 def midnight(self, current_time): + """ + + :param current_time: + :return: If it is midnight returns true otherwise false + """ if self.midnight_time: self.midnight_time = datetime.combine(current_time, time.max) if current_time >= self.midnight_time: @@ -219,7 +225,7 @@ def midnight(self, current_time): def publish_daily_record(self, topic, message, current_time): headers = {'Date': format_timestamp(current_time)} - #device_topic = topic.replace("all", "report/all") + # device_topic = topic.replace("all", "report/all") try: self.vip.pubsub.publish(peer='pubsub', topic=topic, @@ -253,7 +259,6 @@ def get_point(self, point, tries=None): return False - def main(argv=sys.argv): """Main method called by the eggsecutable.""" try: diff --git a/AFDDSchedulerAgent/config b/AFDDSchedulerAgent/config index 87d0b5e..8841fbb 100644 --- a/AFDDSchedulerAgent/config +++ b/AFDDSchedulerAgent/config @@ -1,5 +1,8 @@ { - "analysis_name": "devices",, + "analysis_name": "devices", + "campus": "campus", + "building": "building", + "device": "rtu4", "mht": 3600, "excess_operation": false, "interval": 60, @@ -8,18 +11,10 @@ "year": 2021, "schedule" : { "weekday": ["6:00","18:00"], - "weekend_holiday": ["0:00","0:00"], + "weekend_holiday": ["0:00","0:00"] + }, + "condition_list": { + "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], + "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] } - afdd_scheduler : - "device": { - "campus": "campus", - "building": "building", - "unit": { - "rtu4" - } - }, - "condition_list": { - "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], - "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] - }, } diff --git a/AFDDSchedulerAgent/config_multiple_conditionlist b/AFDDSchedulerAgent/config_multiple_conditionlist index f54cd48..d5a717c 100644 --- a/AFDDSchedulerAgent/config_multiple_conditionlist +++ b/AFDDSchedulerAgent/config_multiple_conditionlist @@ -1,14 +1,7 @@ { "analysis_name": "devices", - # this is initiated based on a cron scheduling string - https://crontab.guru - "schedule_time": "* 0-6, 18-23 * * 0-5", - "device": { - "campus": "campus", - "building": "building", - "unit": { - "rtu4" - } - }, + "campus": "campus", + "building": "building", "mht": 3600, "excess_operation": false, "interval": 60, @@ -17,13 +10,18 @@ "year": 2021, "schedule" : { "weekday": ["6:00","18:00"], - "weekend_holiday": ["0:00","0:00"], - } - "publish_time": "0:00" - + "weekend_holiday": ["0:00","0:00"] + }, + afdd_devices:[{"device": "rtu4", "condition_list": { "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] + } }, -} - + {"device": "rtu5", + "condition_list": { + "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], + "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] + } + } +} \ No newline at end of file From 060a0114edc7f5245b452bf1b7bf6b6b033c84fd Mon Sep 17 00:00:00 2001 From: kini136 Date: Mon, 15 Feb 2021 18:00:40 -0800 Subject: [PATCH 08/14] Added comments and fixed typos --- AFDDSchedulerAgent/agentdir/agent.py | 131 +++++++++++++++++---------- AFDDSchedulerAgent/config | 8 +- 2 files changed, 86 insertions(+), 53 deletions(-) diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/agentdir/agent.py index f8d687f..817f916 100644 --- a/AFDDSchedulerAgent/agentdir/agent.py +++ b/AFDDSchedulerAgent/agentdir/agent.py @@ -32,7 +32,7 @@ class AFDDSchedulerAgent(Agent): """ This agent - write a description of the agent + TODO:write a description of the agent """ def __init__(self, config_path, **kwargs): @@ -41,14 +41,7 @@ def __init__(self, config_path, **kwargs): self.analysis_name = "Scheduler" self.campus = "campus" self.building = "building" - self.device = { - "rtu1": { - "subdevices": [] - }, - "rtu4": { - "subdevices": [] - } - } + self.device = None self.maximum_hour_threshold = 5.0 self.excess_operation: False self.timezone = "US/Pacific" @@ -60,9 +53,8 @@ def __init__(self, config_path, **kwargs): "campus": self.campus, "building": self.building, "device": self.device, - "mht": 3600, + "maximum_hour_threshold": 5.0, "excess_operation": False, - "interval": 60, "timezone": self.timezone, "conditions_list": None } @@ -73,14 +65,15 @@ def __init__(self, config_path, **kwargs): self.subdevices_list = [] self.device_status = False self.excess_operation = False + self.condition_true_time_delta = None self.day = None self.condition_data = [] - self.rthr = 0 self.device_name = [] self.simulation = True self.year = 2021 self.midnight_time = None self.initial_time = None + self.condition_true_time = None self.schedule = {"weekday_sch": ["5:30", "18:30"], "weekend_holiday_sch": ["0:00", "0:00"]} self.default_config = utils.load_config(config_path) self.vip.config.set_default("config", self.default_config) @@ -99,8 +92,8 @@ def configure(self, config_name, action, contents): self.analysis_name = self.current_config.get("analysis_name") self.campus = self.current_config.get("campus") self.building = self.current_config.get("building") - self.device = self.current_config.get("device") - self.maximum_hour_threshold = self.current_config.get("mht") + self.device = self.current_config.get("device", "") + self.maximum_hour_threshold = self.current_config.get("maximum_hour_threshold") self.excess_operation = self.current_config.get("excess_operation") self.timezone = self.current_config.get("timezone", "PDT") self.condition_list = self.current_config.get("condition_list", {}) @@ -114,41 +107,63 @@ def configure(self, config_name, action, contents): self.device_true_time = 0 # at mid night zero the total minute def on_subscribe(self): - _log.debug("Running with simulation using topic %s", - self.simulation_data_topic) - self.publish_topics = "/".join([self.analysis_name, self.campus, self.building]) - multiple_devices = isinstance(self.device, dict) - self.command_devices = self.device.keys() + """Setup the device subscriptions""" + # If self.device is a dict then devices contain subdevices otherwise it is a list + multiple_devices = isinstance(self.device, dict)# check whether self.device is a dict + if self.device: + try: + if multiple_devices: # create a device topic list for devices with subdevices + for device_name in self.device: + for subdevices in self.device[device_name]: + device_topic = topics.DEVICES_VALUE(campus=self.campus, building=self.building, \ + unit=device_name, path=subdevices, \ + point="all") - try: - for device_name in self.device: - device_topic = topics.DEVICES_VALUE(campus=self.campus, building=self.building, \ - unit=device_name, path="", \ - point="all") + self.device_topic_list.update({device_topic: device_name}) + self.device_name.append(device_name) - self.device_topic_list.update({device_topic: device_name}) - self.device_name.append(device_name) + else: + for device_name in self.device: + device_topic = topics.DEVICES_VALUE(campus=self.campus, building=self.building, \ + unit=device_name, path="", \ + point="all") - except Exception as e: - _log.error('Error configuring device topic {}'.format(e)) + self.device_topic_list.update({device_topic: device_name}) + self.device_name.append(device_name) + + except Exception as e: + _log.error('Error configuring device topic {}'.format(e)) try: for device in self.device_topic_list: _log.info("Subscribing to " + device) self.vip.pubsub.subscribe(peer="pubsub", prefix=device, callback=self.time_scheduler_handler) + # subscribe to each devices with self.time_schedule_handler except Exception as e: _log.error('Error configuring signal: {}'.format(e)) _log.error("Missing {} data to execute the AIRx process".format(device)) def time_scheduler_handler(self, peer, sender, bus, topic, header, message): + """ + :param peer: + :param sender: + :param bus: + :param topic: + :param header: + :param message: + :return: This function runs afdd schedule during unoccupied period + """ + # if running in simulation use header datetime if self.simulation: current_time = parse(header["Date"]) else: current_time = get_aware_utc_now() _log.debug("Simulation time handler current_time: %s", current_time) date_today = current_time.date() - if date_today in holidays.US(years=self.year) or date_today.weekday() == 5 and 6: + # check today's data is holiday or weekend. + # if yes then use weekend schedule otherwise use weekdays schedule + if not date_today in holidays.US(years=self.year) or date_today.weekday() == 5 and 6: schedule = self.schedule["weekday"] else: schedule = self.schedule["weekend_holiday"] @@ -156,22 +171,23 @@ def time_scheduler_handler(self, peer, sender, bus, topic, header, message): self.condition_data = [] condition_args = self.condition_list.get("condition_args") symbols(condition_args) - + # create a list with key(point name) and value pair for args in condition_args: self.condition_data.append((args, message[0][args])) _log.info("condition data {}".format(self.condition_data)) - # convert all the required data into a dict + print(topic) + # run afdd scheduler between unoccupied period using predefine occupied schedule if current_time.time() < parse(schedule[0]).time() or current_time.time() > parse(schedule[1]).time(): - self.run_schedule(current_time) + self.run_schedule(current_time, topic) - def run_schedule(self, current_time): + def run_schedule(self, current_time, topic): """ :param current_time: :return: this function publishes --- execute the condition of the device, If all condition are true then add time into true_time. - If true time is exceed the threshold time (mht) flag the excess operation + If true time is exceed the threshold time (maximum_hour_threshold) flag the excess operation """ conditions = self.condition_list.get("conditions") try: @@ -181,54 +197,69 @@ def run_schedule(self, current_time): if condition_status: # Sum the number of minutes when both conditions are true and log each day - self.device_true_time += self.interval + if self.condition_true_time: + self.condition_true_time = current_time + self.condition_true_time_delta = (current_time - self.condition_true_time).seconds self.device_status = True - _log.Info('All condition true time {}'.format(self.device_true_time)) + _log.info('All condition true time {}'.format(self.device_true_time)) + _log.debug(f'Condition true time delta is {self.condition_true_time_delta}') else: - self.device_status = False - _log.Info("One of the condition is false") + if self.device_status: + self.device_true_time += self.condition_true_time_delta + self.device_status = False + self.condition_true_time = None + _log.info("One of the condition is false") - if self.initial_time: + if not self.initial_time: self.initial_time = current_time - time_delta = current_time - self.simulation_initial_time - _log.debug(f"Initial time = {self.simulation_initial_time}," + time_delta = current_time - self.initial_time + _log.debug(f"Initial time = {self.initial_time}," f"time delta = {time_delta}") - if time_delta >= self.maximum_hour_threshold: + if (time_delta.seconds / 3600) >= self.maximum_hour_threshold: self.excess_operation = True # for device_topic in self.device_topic_list: message = {'excess_operation': bool(self.excess_operation), 'device_status': bool(self.device_status) } - self.publish_daily_record(self.affd_topic_name, message, current_time) + print(topic) + self.publish_analysis(topic, message, current_time) if self.midnight(current_time): message = {'device_true_time': int(self.device_true_time)} - self.publish_daily_record(self.affd_topic_name, message, current_time) + self.publish_analysis(topic, message, current_time) self.device_true_time = 0 def midnight(self, current_time): """ - :param current_time: :return: If it is midnight returns true otherwise false """ - if self.midnight_time: - self.midnight_time = datetime.combine(current_time, time.max) + if not self.midnight_time: + self.midnight_time = datetime.combine(current_time, time.max).\ + astimezone(dateutil.tz.gettz(self.timezone)) if current_time >= self.midnight_time: self.midnight_time = datetime.combine(current_time, time.max) return True else: return False - def publish_daily_record(self, topic, message, current_time): + def publish_analysis(self, topic, message, current_time): + """ + + :param topic: + :param message: + :param current_time: + :return: this publishes the message on the volttron message bus + """ headers = {'Date': format_timestamp(current_time)} - # device_topic = topic.replace("all", "report/all") + device_topic = topic.replace("devices", "analysis") + print(device_topic) try: self.vip.pubsub.publish(peer='pubsub', - topic=topic, + topic=device_topic, message=message, headers=headers) except Exception as e: diff --git a/AFDDSchedulerAgent/config b/AFDDSchedulerAgent/config index 8841fbb..760304a 100644 --- a/AFDDSchedulerAgent/config +++ b/AFDDSchedulerAgent/config @@ -2,10 +2,12 @@ "analysis_name": "devices", "campus": "campus", "building": "building", - "device": "rtu4", - "mht": 3600, + "device": { + "AHU1": ["VAV102", "VAV118"], + "AHU3": ["VAV104", "VAV105"] + }, + "maximum_hour_threshold" :5.0, "excess_operation": false, - "interval": 60, "timezone": "US/Pacific", "simulation": true, "year": 2021, From be634ba5beb3d2c0a93a8fa02606829f5a3fc018 Mon Sep 17 00:00:00 2001 From: kini136 Date: Mon, 15 Feb 2021 18:04:35 -0800 Subject: [PATCH 09/14] updated config file --- AFDDSchedulerAgent/README.md | 20 ++++++++++++-------- AFDDSchedulerAgent/agentdir/agent.py | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/AFDDSchedulerAgent/README.md b/AFDDSchedulerAgent/README.md index 2dd4cbf..9d4f3a0 100644 --- a/AFDDSchedulerAgent/README.md +++ b/AFDDSchedulerAgent/README.md @@ -12,25 +12,29 @@ The json format of the config files are specified below. * Agent config file: -```json +``` { - "analysis_name": "devices", - "campus": "campus", - "building": "building", - "device": "rtu4", - "mht": 3600, + "analysis_name": "analysis", + "campus": "PNNL", + "building": "BUILDING1", + "maximum_hour_threshold" :5.0, "excess_operation": false, "interval": 60, "timezone": "US/Pacific", "simulation": true, "year": 2021, + #"device": { + # "AHU1": ["VAV102", "VAV118"], + # "AHU3": ["VAV104", "VAV105"] + # }, + "device":["AHU1", "AHU3"], "schedule" : { "weekday": ["6:00","18:00"], "weekend_holiday": ["0:00","0:00"] }, "condition_list": { - "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], - "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] + "conditions": ["DischargeAirTemperature > 75.0", "SupplyFanStatus"], + "condition_args": ["SupplyFanStatus", "DischargeAirTemperature"] } } ```` diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/agentdir/agent.py index 817f916..294b70c 100644 --- a/AFDDSchedulerAgent/agentdir/agent.py +++ b/AFDDSchedulerAgent/agentdir/agent.py @@ -255,7 +255,7 @@ def publish_analysis(self, topic, message, current_time): :return: this publishes the message on the volttron message bus """ headers = {'Date': format_timestamp(current_time)} - device_topic = topic.replace("devices", "analysis") + device_topic = topic.replace("devices", self.analysis_name) print(device_topic) try: self.vip.pubsub.publish(peer='pubsub', From 97b1f3417e64d21690a75820a3f3189e9315c0b9 Mon Sep 17 00:00:00 2001 From: kini136 Date: Mon, 15 Feb 2021 18:15:52 -0800 Subject: [PATCH 10/14] Added introduction for afddscheduler agent --- AFDDSchedulerAgent/README.md | 11 +++++------ AFDDSchedulerAgent/agentdir/agent.py | 8 +++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/AFDDSchedulerAgent/README.md b/AFDDSchedulerAgent/README.md index 9d4f3a0..f46714d 100644 --- a/AFDDSchedulerAgent/README.md +++ b/AFDDSchedulerAgent/README.md @@ -1,10 +1,9 @@ # AFDDSchedulerAgents Agent -There may be a secondary AFDD -(analyze hours of operation for RTUs with inherent heating capability) or simply forego using discharge -temperature as a proxy for proof of fan operations. Optimal Start is not accounted for -(in determination of an earlier start time than the schedule) nor is the night setback -minimum and maximum space temperature configuration that will cause the RTU to operate during -unoccupied hours to maintain the building space temperatures at the specified minimum and maximum temperatures. +AFDD agent check if all conditions are true for each devices, +if it is true then at midnight it sums the number of minute for each device +where both conditions are true and publish a devices true time. +if the devices true time exsits the maximum hour threshould then it flag the +device for excess daily operating hours ## AFDDSchedulerAgent Agent Configuration diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/agentdir/agent.py index 294b70c..b9242f3 100644 --- a/AFDDSchedulerAgent/agentdir/agent.py +++ b/AFDDSchedulerAgent/agentdir/agent.py @@ -30,9 +30,11 @@ class AFDDSchedulerAgent(Agent): """ - This agent - - TODO:write a description of the agent + AFDD agent check if all conditions are true for each devices, + if it is true then at midnight it sums the number of minute for each device + where both conditions are true and publish a devices true time. + if the devices true time exsits the maximum hour threshould then it flag the + device for excess daily operating hours """ def __init__(self, config_path, **kwargs): From a10d45cd7e7f284aa5ee808f19af35b94d69a12f Mon Sep 17 00:00:00 2001 From: kini136 Date: Tue, 16 Feb 2021 11:35:46 -0800 Subject: [PATCH 11/14] Removed additional time delta from the run scheduler function --- AFDDSchedulerAgent/agentdir/agent.py | 39 +++++++------------ .../config_multiple_conditionlist | 2 +- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/agentdir/agent.py index b9242f3..11cf1d8 100644 --- a/AFDDSchedulerAgent/agentdir/agent.py +++ b/AFDDSchedulerAgent/agentdir/agent.py @@ -48,6 +48,7 @@ def __init__(self, config_path, **kwargs): self.excess_operation: False self.timezone = "US/Pacific" self.condition_list = None + self.previous_true_time = 0 # Set up default configuration and config store self.default_config = { @@ -63,11 +64,9 @@ def __init__(self, config_path, **kwargs): self.device_topic_list = {} self.device_data = [] - self.device_true_time = 0 - self.subdevices_list = [] self.device_status = False self.excess_operation = False - self.condition_true_time_delta = None + self.condition_true_time_delta = 0 self.day = None self.condition_data = [] self.device_name = [] @@ -178,7 +177,6 @@ def time_scheduler_handler(self, peer, sender, bus, topic, header, message): self.condition_data.append((args, message[0][args])) _log.info("condition data {}".format(self.condition_data)) - print(topic) # run afdd scheduler between unoccupied period using predefine occupied schedule if current_time.time() < parse(schedule[0]).time() or current_time.time() > parse(schedule[1]).time(): self.run_schedule(current_time, topic) @@ -198,41 +196,32 @@ def run_schedule(self, current_time, topic): _log.error("Conditions are not correctly implemented in the config file : {}".format(str(e))) if condition_status: - # Sum the number of minutes when both conditions are true and log each day - if self.condition_true_time: - self.condition_true_time = current_time - self.condition_true_time_delta = (current_time - self.condition_true_time).seconds + # Sum the number of minutes when both conditions are true and log each self.device_status = True - _log.info('All condition true time {}'.format(self.device_true_time)) - _log.debug(f'Condition true time delta is {self.condition_true_time_delta}') + if not self.condition_true_time: + self.condition_true_time = current_time + self.condition_true_time_delta = self.previous_true_time + (current_time - self.condition_true_time).seconds + _log.info(f'Condition true time delta is {self.condition_true_time_delta}') else: - if self.device_status: - self.device_true_time += self.condition_true_time_delta - self.device_status = False - self.condition_true_time = None + self.condition_true_time = None + self.device_status = False + if self.condition_true_time_delta: + self.previous_true_time = self.condition_true_time_delta _log.info("One of the condition is false") - if not self.initial_time: - self.initial_time = current_time - - time_delta = current_time - self.initial_time - _log.debug(f"Initial time = {self.initial_time}," - f"time delta = {time_delta}") - - if (time_delta.seconds / 3600) >= self.maximum_hour_threshold: + if (self.condition_true_time_delta / 3600) >= self.maximum_hour_threshold: self.excess_operation = True # for device_topic in self.device_topic_list: message = {'excess_operation': bool(self.excess_operation), 'device_status': bool(self.device_status) } - print(topic) self.publish_analysis(topic, message, current_time) if self.midnight(current_time): message = {'device_true_time': int(self.device_true_time)} self.publish_analysis(topic, message, current_time) - self.device_true_time = 0 + self.condition_true_time_delta = 0 def midnight(self, current_time): """ @@ -258,7 +247,7 @@ def publish_analysis(self, topic, message, current_time): """ headers = {'Date': format_timestamp(current_time)} device_topic = topic.replace("devices", self.analysis_name) - print(device_topic) + try: self.vip.pubsub.publish(peer='pubsub', topic=device_topic, diff --git a/AFDDSchedulerAgent/config_multiple_conditionlist b/AFDDSchedulerAgent/config_multiple_conditionlist index d5a717c..3d1ab74 100644 --- a/AFDDSchedulerAgent/config_multiple_conditionlist +++ b/AFDDSchedulerAgent/config_multiple_conditionlist @@ -18,7 +18,7 @@ "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] } }, - {"device": "rtu5", + {"device": "AHU", "condition_list": { "conditions": ["ReturnAirTemperature > 65.0", "ReturnAirTemperature < 75.0", "SupplyFanStatus"], "condition_args": ["SupplyFanStatus", "ReturnAirTemperature"] From 8feb078721d7f189c726de3a5d7aaf18770b3d5f Mon Sep 17 00:00:00 2001 From: kini136 Date: Tue, 16 Feb 2021 12:42:21 -0800 Subject: [PATCH 12/14] rename directory name --- AFDDSchedulerAgent/{agentdir => afddscheduler}/__init__.py | 0 AFDDSchedulerAgent/{agentdir => afddscheduler}/agent.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename AFDDSchedulerAgent/{agentdir => afddscheduler}/__init__.py (100%) rename AFDDSchedulerAgent/{agentdir => afddscheduler}/agent.py (100%) diff --git a/AFDDSchedulerAgent/agentdir/__init__.py b/AFDDSchedulerAgent/afddscheduler/__init__.py similarity index 100% rename from AFDDSchedulerAgent/agentdir/__init__.py rename to AFDDSchedulerAgent/afddscheduler/__init__.py diff --git a/AFDDSchedulerAgent/agentdir/agent.py b/AFDDSchedulerAgent/afddscheduler/agent.py similarity index 100% rename from AFDDSchedulerAgent/agentdir/agent.py rename to AFDDSchedulerAgent/afddscheduler/agent.py From b17e8e116442d7639d562cf4b20fb0a5e5feca09 Mon Sep 17 00:00:00 2001 From: kini136 Date: Tue, 16 Feb 2021 12:51:04 -0800 Subject: [PATCH 13/14] copied listener's setup.py --- AFDDSchedulerAgent/setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AFDDSchedulerAgent/setup.py b/AFDDSchedulerAgent/setup.py index ba63b17..4bc83cf 100644 --- a/AFDDSchedulerAgent/setup.py +++ b/AFDDSchedulerAgent/setup.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- {{{ # vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: # -# Copyright 2017, Battelle Memorial Institute. +# Copyright 2021, Battelle Memorial Institute. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -55,14 +55,14 @@ # Find the version number from the main module agent_module = agent_package + '.' + MAIN_MODULE -_temp = __import__(agent_module, globals(), locals(), ['__version__'], -1) +_temp = __import__(agent_module, globals(), locals(), ['__version__'], 0) __version__ = _temp.__version__ # Setup setup( - name=agent_package, + name=agent_package + 'agent', version=__version__, - install_requires=['volttron', 'future'], + install_requires=['volttron'], packages=packages, entry_points={ 'setuptools.installation': [ From 60055438446a060176381468757ad0ec339f2371 Mon Sep 17 00:00:00 2001 From: Roshan Laxman Kini <61986575+rkini-pnnl@users.noreply.github.com> Date: Wed, 17 Mar 2021 10:42:32 -0700 Subject: [PATCH 14/14] Update README.md Fixed typos --- AFDDSchedulerAgent/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AFDDSchedulerAgent/README.md b/AFDDSchedulerAgent/README.md index f46714d..ec8c780 100644 --- a/AFDDSchedulerAgent/README.md +++ b/AFDDSchedulerAgent/README.md @@ -2,7 +2,7 @@ AFDD agent check if all conditions are true for each devices, if it is true then at midnight it sums the number of minute for each device where both conditions are true and publish a devices true time. -if the devices true time exsits the maximum hour threshould then it flag the +if the devices true time exceed the maximum hour threshold then it flag the device for excess daily operating hours ## AFDDSchedulerAgent Agent Configuration