From 429823af96a32a7972a6ba2693e422feb8bd2457 Mon Sep 17 00:00:00 2001 From: mchlburton Date: Sun, 26 Mar 2017 20:43:18 -0400 Subject: [PATCH 01/12] Device API for awair; agent in next push --- DeviceAPI/classAPI/classAPI_Awair.py | 101 +++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100755 DeviceAPI/classAPI/classAPI_Awair.py diff --git a/DeviceAPI/classAPI/classAPI_Awair.py b/DeviceAPI/classAPI/classAPI_Awair.py new file mode 100755 index 0000000..30ac7a4 --- /dev/null +++ b/DeviceAPI/classAPI/classAPI_Awair.py @@ -0,0 +1,101 @@ +''' +Copyright (c) 2017, BlocPower +All rights reserved. + + +#__author__ = "Mike" +#__credits__ = "" +#__version__ = "2.0" +#__maintainer__ = "Mike" +#__email__ = "michael@blocpower.io" +#__website__ = "www.blocpower.io" +#__created__ = "2017-03-24" +#__lastUpdated__ = "2017-03-24" +''' + +import requests as req +import datetime + +class API: + #1. constructor : gets call everytime when create a new class + #requirements for instantiation1. model, 2.type, 3.api, 4. address + def __init__(self,**kwargs): + #Initialized common attributes + self.variables = kwargs + self.set_variable('offline_count',0) + self.set_variable('connection_renew_interval',6000) #nothing to renew + + + def renewConnection(self): + pass + + def set_variable(self,k,v): #k=key, v=value + self.variables[k] = v + + def get_variable(self,k): + return self.variables.get(k, None) #default of get_variable is none + + #2. Attributes from Attributes table + ''' + Attributes: + ------------------------------------------------------------------------------------------ + co2 GET Carbon Dioxide (floating point in ppm) + VOC GET Volatile Organic Chemicals (floating point in ppb) + dust GET PM10 and PM 2.5 (floating point in ug/m^3) + temperature GET temperature (floating point in deg F) + humidity GET relative humidity (floating point in %) + time GET timestamp from awair (reported as ISO8601 string) + ------------------------------------------------------------------------------------------ + ''' + + #3. Capabilites (methods) from Capabilities table + ''' + API available methods: + 1. getDeviceStatus() Read + ''' + + + def getDeviceStatus(self): + getDeviceStatusResult = True + account = self.get_variable('account') + token = self.get_variable('token') + awair_id = self.get_variable('awair_id') + + start = (datetime.datetime.now()-datetime.timedelta(minutes = 15)).isoformat() + end = datetime.datetime.now().isoformat() + head_auth = {'Authorization': token} + timespan = {'from': start, 'to': end} + + device_url = 'https://beta-api.awair.is/v1/devices/' + str(awair_id) + '/events/15min-avg' + + try: + data_req = req.get( + device_url, + headers = token, + params = timespan, + ) + + data_json = data_req.json() + data_list = [] + for row in data_json['data']: + data = row['sensor'].values() + time = row['timestamp'] + + + self.set_variable('dust', data[0]) + self.set_variable('co2', data[1]) + self.set_variable('humidity', data[2]) + self.set_variable('temperature', data[3]) + self.set_variable('voc', data[4]) + self.set_variable('time', time) + + except Exception as er: + print er + getDeviceStatusResult = False + + if getDeviceStatusResult==True: + self.set_variable('offline_count', 0) + + else: + self.set_variable('offline_count', self.get_variable('offline_count')+1) + -- GitLab From 6c38fbd12edc8de5ed26d3946c401a31e9b953e5 Mon Sep 17 00:00:00 2001 From: mchlburton Date: Tue, 28 Mar 2017 13:34:02 -0400 Subject: [PATCH 02/12] Awair API and beginning of air quality agent --- Agents/AirQualityAgent/airquality/agent.py | 448 +++++++++++++++++++++ DeviceAPI/classAPI/classAPI_Awair.py | 4 +- 2 files changed, 450 insertions(+), 2 deletions(-) create mode 100755 Agents/AirQualityAgent/airquality/agent.py diff --git a/Agents/AirQualityAgent/airquality/agent.py b/Agents/AirQualityAgent/airquality/agent.py new file mode 100755 index 0000000..c5ef6f6 --- /dev/null +++ b/Agents/AirQualityAgent/airquality/agent.py @@ -0,0 +1,448 @@ +''' +#__author__ = "Mike" +#__credits__ = "" +#__version__ = "2.0" +#__maintainer__ = "Mike" +#__email__ = "michael@blocpower.io" +#__website__ = "www.blocpower.io" +#__created__ = "2017-03-27" +#__lastUpdated__ = "" +''' + +import sys +import json +import logging +import importlib +from volttron.platform.agent import BaseAgent, PublishMixin, periodic +from volttron.platform.agent import utils, matching +from volttron.platform.messaging import headers as headers_mod +import datetime +from bemoss_lib.communication.Email import EmailService +from bemoss_lib.communication.sms import SMSService +import psycopg2 +import psycopg2.extras +import socket +import settings + +from bemoss_lib.databases.cassandraAPI import cassandraDB +utils.setup_logging() +_log = logging.getLogger(__name__) + +# Step1: Agent Initialization +def AwairAgent(config_path, **kwargs): + config = utils.load_config(config_path) + + def get_config(name): + try: + kwargs.pop(name) + except KeyError: + return config.get(name, '') + + #1. @params agent + agent_id = get_config('agent_id') + device_monitor_time = get_config('device_monitor_time') + max_monitor_time = int(settings.DEVICES['max_monitor_time']) + + debug_agent = False + #List of all keywords for a lighting agent + agentAPImapping = dict(co2=[], VOC=[], dust=[], temperature=[],humidity=[], time=[]) + log_variables = dict(co2='float', VOC='float', dust='float', temperature='float',humidity='float', time='string',offline_count='int') + #2. @params device_info + #TODO correct the launchfile in Device Discovery Agent + building_name = get_config('building_name') + zone_id = get_config('zone_id') + model = get_config('model') + if model == "Philips hue bridge": + hue_username = get_config('username') + else: + hue_username = '' + device_type = get_config('type') + address = get_config('address') + _address = address + _address = _address.replace('http://', '') + _address = _address.replace('https://', '') + try: # validate whether or not address is an ip address + socket.inet_aton(_address) + ip_address = _address + except socket.error: + ip_address = None + identifiable = get_config('identifiable') + + #3. @params agent & DB interfaces + #TODO delete variable topic + topic = get_config('topic') + + #TODO get database parameters from settings.py, add db_table for specific table + db_host = get_config('db_host') + db_port = get_config('db_port') + db_database = get_config('db_database') + db_user = get_config('db_user') + db_password = get_config('db_password') + db_table_lighting = settings.DATABASES['default']['TABLE_lighting'] + db_table_active_alert = settings.DATABASES['default']['TABLE_active_alert'] + db_table_bemoss_notify = settings.DATABASES['default']['TABLE_bemoss_notify'] + db_table_alerts_notificationchanneladdress = settings.DATABASES['default'][ + 'TABLE_alerts_notificationchanneladdress'] + db_table_temp_time_counter = settings.DATABASES['default']['TABLE_temp_time_counter'] + db_table_priority = settings.DATABASES['default']['TABLE_priority'] + + #construct _topic_Agent_UI based on data obtained from DB + _topic_Agent_UI_tail = building_name + '/' + str(zone_id) + '/' + agent_id + + #4. @params device_api + api = get_config('api') + apiLib = importlib.import_module("DeviceAPI.classAPI."+api) + + #4.1 initialize device object + Awair = apiLib.API(model=model, device_type=device_type, api=api, address=address, username = hue_username, agent_id=agent_id, db_host=db_host, db_port=db_port, db_user=db_user, db_password=db_password, db_database=db_database) + print("{0}agent is initialized for {1} using API={2} at {3}".format(agent_id, Awair.get_variable('model'), + Awair.get_variable('api'), + Awair.get_variable('address'))) + + #5. @params notification_info + send_notification = True + email_fromaddr = settings.NOTIFICATION['email']['fromaddr'] + email_username = settings.NOTIFICATION['email']['username'] + email_password = settings.NOTIFICATION['email']['password'] + email_mailServer = settings.NOTIFICATION['email']['mailServer'] + notify_heartbeat = settings.NOTIFICATION['heartbeat'] + + class Agent(PublishMixin, BaseAgent): + """Agent for querying WeatherUndergrounds API""" + + #1. agent initialization + def __init__(self, **kwargs): + super(Agent, self).__init__(**kwargs) + #1. initialize all agent variables + self.variables = kwargs + self.valid_data = False + self._keep_alive = True + self.flag = 1 + self.topic = topic + self.time_send_notification = 0 + self.event_ids = list() + self.time_sent_notifications = {} + self.notify_heartbeat = notify_heartbeat + self.ip_address = ip_address if ip_address != None else None + self.changed_variables = None + self.lastUpdateTime = None + self.already_offline = False + + #2. setup connection with db -> Connect to bemossdb database + try: + self.con = psycopg2.connect(host=db_host, port=db_port, database=db_database, user=db_user, + password=db_password) + self.cur = self.con.cursor() # open a cursor to perform database operations + print("{} connects to the database name {} successfully".format(agent_id, db_database)) + except: + print("ERROR: {} fails to connect to the database name {}".format(agent_id, db_database)) + #3. send notification to notify building admin + self.send_notification = send_notification + self.subject = 'Message from ' + agent_id + + #These set and get methods allow scalability + def set_variable(self,k,v): # k=key, v=value + self.variables[k] = v + + def get_variable(self,k): + return self.variables.get(k, None) # default of get_variable is none + + #2. agent setup method + def setup(self): + super(Agent, self).setup() + #Do a one time push when we start up so we don't have to wait for the periodic + self.timer(1, self.deviceMonitorBehavior) + + @periodic(max_monitor_time) #save all data every max_monitor_time + def backupSaveData(self): + try: + Awair.getDeviceStatus() + cassandraDB.insert(agent_id,Awair.variables,log_variables) + print('Every Data Pushed to cassandra') + except Exception as er: + print("ERROR: {} fails to update cassandra database".format(agent_id)) + print er + + #3. deviceMonitorBehavior (TickerBehavior) + @periodic(device_monitor_time) + def deviceMonitorBehavior(self): + #step1: get current status of a thermostat, then map keywords and variables to agent knowledge + try: + Awair.getDeviceStatus() + except: + print("device connection is not successful") + + self.changed_variables = dict() + for v in log_variables: + if v in Awair.variables: + if not v in self.variables or self.variables[v] != Awair.variables[v]: + self.variables[v] = Awair.variables[v] + self.changed_variables[v] = log_variables[v] + else: + if v not in self.variables: #it won't be in self.variables either (in the first time) + self.changed_variables[v] = log_variables[v] + self.variables[v] = None + try: + # Step: Check if any Device is OFFLINE + self.cur.execute("SELECT id FROM " + db_table_active_alert + " WHERE event_trigger_id=%s", ('5',)) + if self.cur.rowcount != 0: + self.device_offline_detection() + + # Put scan time in database + _time_stamp_last_scanned = datetime.datetime.now() + self.cur.execute("UPDATE "+db_table_lighting+" SET last_scanned_time=%s " + "WHERE lighting_id=%s", + (_time_stamp_last_scanned, agent_id)) + self.con.commit() + except Exception as er: + print er + print("ERROR: {} failed to update last scanned time".format(agent_id)) + + if len(self.changed_variables) == 0: + print 'nothing changed' + return + + self.updateUI() + #step4: update PostgresQL (meta-data) database + try: + self.cur.execute("UPDATE "+db_table_lighting+" SET status=%s WHERE lighting_id=%s", + (self.get_variable('status'), agent_id)) + self.con.commit() + self.cur.execute("UPDATE "+db_table_lighting+" SET brightness=%s WHERE lighting_id=%s", + (self.get_variable('brightness'), agent_id)) + self.con.commit() + self.cur.execute("UPDATE "+db_table_lighting+" SET color=%s WHERE lighting_id=%s", + (self.get_variable('hexcolor'), agent_id)) + self.con.commit() + + #TODO check ip_address + if self.ip_address != None: + psycopg2.extras.register_inet() + _ip_address = psycopg2.extras.Inet(self.ip_address) + self.cur.execute("UPDATE "+db_table_lighting+" SET ip_address=%s WHERE lighting_id=%s", + (_ip_address, agent_id)) + self.con.commit() + + + if self.get_variable('offline_count') >= 3: + self.cur.execute("UPDATE "+db_table_lighting+" SET network_status=%s WHERE lighting_id=%s", + ('OFFLINE', agent_id)) + self.con.commit() + if self.already_offline is False: + self.already_offline = True + _time_stamp_last_offline = str(datetime.datetime.now()) + self.cur.execute("UPDATE "+db_table_lighting+" SET last_offline_time=%s WHERE lighting_id=%s", + (_time_stamp_last_offline, agent_id)) + self.con.commit() + else: + self.already_offline = False + self.cur.execute("UPDATE "+db_table_lighting+" SET network_status=%s WHERE lighting_id=%s", + ('ONLINE', agent_id)) + self.con.commit() + print("{} updates database name {} during deviceMonitorBehavior successfully".format(agent_id, db_database)) + except: + print("ERROR: {} fails to update the database name {}".format(agent_id,db_database)) + + #step5: update Cassandra (time-series) database + try: + cassandraDB.insert(agent_id,self.variables,log_variables) + print('Data Pushed to cassandra') + print "{} success update".format(agent_id) + except Exception as er: + print("ERROR: {} fails to update cassandra database".format(agent_id)) + print er + #step6: debug agent knowledge + if debug_agent: + print("printing agent's knowledge") + for k,v in self.variables.items(): + print (k,v) + print('') + + if debug_agent: + print("printing agentAPImapping's fields") + for k, v in agentAPImapping.items(): + if k is None: + agentAPImapping.update({v: v}) + agentAPImapping.pop(k) + for k, v in agentAPImapping.items(): + print (k, v) + + def device_offline_detection(self): + self.cur.execute("SELECT nickname FROM " + db_table_lighting + " WHERE lighting_id=%s", + (agent_id,)) + print agent_id + if self.cur.rowcount != 0: + device_nickname=self.cur.fetchone()[0] + print device_nickname + else: + device_nickname = '' + _db_notification_subject = 'BEMOSS Device {} {} went OFFLINE!!!'.format(device_nickname,agent_id) + _email_subject = '#Attention: BEMOSS Device {} {} went OFFLINE!!!'.format(device_nickname,agent_id) + _email_text = '#Attention: BEMOSS Device {} {} went OFFLINE!!!'.format(device_nickname,agent_id) + self.cur.execute("SELECT network_status FROM " + db_table_lighting + " WHERE lighting_id=%s", + (agent_id,)) + self.network_status = self.cur.fetchone()[0] + print self.network_status + if self.network_status=="OFFLINE": + print "Found Device OFFLINE" + self.cur.execute("SELECT id FROM " + db_table_active_alert + " WHERE event_trigger_id=%s", ('5',)) + self._active_alert_id = self.cur.fetchone()[0] + self.cur.execute( + "SELECT id FROM " + db_table_temp_time_counter + " WHERE alert_id=%s AND device_id=%s", + (str(self._active_alert_id), agent_id,)) + # If this is the first detected violation + if self.cur.rowcount == 0: + print "first device offline detected" + # create counter in DB + self.cur.execute( + "INSERT INTO " + db_table_temp_time_counter + " VALUES(DEFAULT,%s,%s,%s,%s,%s)", + (self._active_alert_id, agent_id, '0', '0', '0')) + self.con.commit() + self.send_device_notification_db(_db_notification_subject, self._active_alert_id) + + # Send email if exist + self.cur.execute("SELECT notify_address FROM " + db_table_alerts_notificationchanneladdress + " WHERE active_alert_id=%s AND notification_channel_id=%s",(self._active_alert_id,'1')) + if self.cur.rowcount != 0: + self._alert_email = self.cur.fetchall() + for single_email_1 in self._alert_email: + print single_email_1[0] + self.send_device_notification_email(single_email_1[0], _email_subject, _email_text) + + # Send SMS if provided by user + self.cur.execute("SELECT notify_address FROM " + db_table_alerts_notificationchanneladdress + " WHERE active_alert_id=%s AND notification_channel_id=%s",(self._active_alert_id,'2')) + if self.cur.rowcount != 0: + self._alert_sms_phone_no = self.cur.fetchall() + for single_number in self._alert_sms_phone_no: + print single_number[0] + self.send_device_notification_sms(single_number[0], _email_subject) + else: + self.priority_counter(self._active_alert_id, _db_notification_subject) + else: + print "The Device is ONLINE" + + def send_device_notification_db(self, _tampering_device_msg, _active_alert_id): + print " INSIDE send_device_notification_db" + + # Find the priority id + self.cur.execute( + "SELECT priority_id FROM " + db_table_active_alert + " WHERE id=%s", + (str(_active_alert_id),)) + self.priority_id = self.cur.fetchone()[0] + + # Find the priority level + self.cur.execute( + "SELECT priority_level FROM " + db_table_priority + " WHERE id=%s", + str(self.priority_id)) + self.priority_level = self.cur.fetchone()[0] + + # Insert into DB the notification + self.cur.execute("INSERT INTO " + db_table_bemoss_notify + " VALUES(DEFAULT,%s,%s,%s,%s)", + (_tampering_device_msg, + str(datetime.datetime.now()), 'Alert', str(self.priority_level))) + self.con.commit() + + # Find the number of notifications sent for the same alert and device + self.cur.execute( + "SELECT no_notifications_sent FROM " + db_table_temp_time_counter + " WHERE alert_id=%s AND device_id=%s", + (str(_active_alert_id), agent_id,)) + self._no_notifications_sent = self.cur.fetchone()[0] + self.con.commit() + print self._no_notifications_sent + self._no_notifications_sent = int(self._no_notifications_sent) + 1 + print self._no_notifications_sent + self.cur.execute( + "UPDATE " + db_table_temp_time_counter + " SET no_notifications_sent=%s WHERE alert_id=%s AND device_id=%s", + (str(self._no_notifications_sent), str(_active_alert_id), agent_id,)) + self.con.commit() + + def send_device_notification_email(self, _active_alert_email, _email_subject, _email_text): + emailService = EmailService() + # Send Email + emailService.sendEmail(email_fromaddr, _active_alert_email, email_username, + email_password, _email_subject, _email_text, email_mailServer) + + def send_device_notification_sms(self, _active_alert_phone_number_misoperation, _sms_subject): + print "INSIDE send_device_notification_sms" + print _active_alert_phone_number_misoperation + smsService = SMSService() + smsService.sendSMS(email_fromaddr, _active_alert_phone_number_misoperation, email_username, email_password, _sms_subject, email_mailServer) + + # TODO: this function is in all other agents, need to get rid of those redundent codes. + def priority_counter(self, _active_alert_id, _tampering_device_msg_1): + # Find the priority counter limit then compare it with priority_counter in priority table + # if greater than the counter limit then send notification and reset the value + # else just increase the counter + print "INSIDE the priority_counter" + _email_subject = '#Attention: BEMOSS Device {} went OFFLINE!!!'.format(agent_id) + _email_text = '#Attention: BEMOSS Device {} went OFFLINE!!!'.format(agent_id) + self.cur.execute( + "SELECT priority_counter FROM " + db_table_temp_time_counter + " WHERE alert_id=%s AND device_id=%s", + (str(_active_alert_id), agent_id,)) + self.priority_count = self.cur.fetchone()[0] + self.con.commit() + + # Find the priority id from active alert table + self.cur.execute( + "SELECT priority_id FROM " + db_table_active_alert + " WHERE id=%s", + (str(_active_alert_id),)) + self.priority_id = self.cur.fetchone()[0] + self.con.commit() + + # Find the priority limit from the priority table + self.cur.execute( + "SELECT priority_counter FROM " + db_table_priority + " WHERE id=%s", + (str(self.priority_id),)) + self.priority_limit = self.cur.fetchone()[0] + self.con.commit() + + # If the counter reaches the limit + if int(self.priority_count) > int(self.priority_limit): + self.send_device_notification_db(_tampering_device_msg_1, _active_alert_id) + self.cur.execute( + "UPDATE " + db_table_temp_time_counter + " SET priority_counter=%s WHERE alert_id=%s AND device_id=%s", + ('0', str(_active_alert_id), agent_id,)) + self.con.commit() + + print "INSIDE the priority counter exceeded the defined range" + # Send email if exist + self.cur.execute("SELECT notify_address FROM " + db_table_alerts_notificationchanneladdress + " WHERE active_alert_id=%s AND notification_channel_id=%s",(self._active_alert_id,'1')) + if self.cur.rowcount != 0: + self._alert_email = self.cur.fetchall() + for single_email_1 in self._alert_email: + print single_email_1[0] + self.send_device_notification_email(single_email_1[0], _email_subject, _email_text) + + # Send SMS if provided by user + self.cur.execute("SELECT notify_address FROM " + db_table_alerts_notificationchanneladdress + " WHERE active_alert_id=%s AND notification_channel_id=%s",(self._active_alert_id,'2')) + if self.cur.rowcount != 0: + self._alert_sms_phone_no = self.cur.fetchall() + for single_number in self._alert_sms_phone_no: + print single_number[0] + self.send_device_notification_sms(single_number[0], _email_subject) + else: + self.priority_count = int(self.priority_count) + 1 + self.cur.execute( + "UPDATE " + db_table_temp_time_counter + " SET priority_counter=%s WHERE alert_id=%s AND device_id=%s", + (str(self.priority_count), str(_active_alert_id), agent_id,)) + + def updateUI(self): + topic = '/agent/ui/'+device_type+'/device_status_response/'+_topic_Agent_UI_tail + headers = { + 'AgentID': agent_id, + headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, + headers_mod.FROM: agent_id, + headers_mod.TO: 'ui' + } + _data={'status': self.get_variable('status'), + 'brightness': self.get_variable('brightness'), 'color': self.get_variable('hexcolor'), + 'saturation': self.get_variable('saturation')} + message = json.dumps(_data) + message = message.encode(encoding='utf_8') + self.publish(topic, headers, message) + + #4. updateUIBehavior (generic behavior) + @matching.match_exact('/ui/agent/'+device_type+'/device_status/'+_topic_Agent_UI_tail) + def updateUIBehavior(self,topic,headers,message,match): + print "{} agent got\nTopic: {topic}".format(self.get_variable("agent_id"),topic=topic) + print "Headers: {headers}".format(headers=headers) + print "Message: {message}\n".format(message=message) diff --git a/DeviceAPI/classAPI/classAPI_Awair.py b/DeviceAPI/classAPI/classAPI_Awair.py index 30ac7a4..408fc7f 100755 --- a/DeviceAPI/classAPI/classAPI_Awair.py +++ b/DeviceAPI/classAPI/classAPI_Awair.py @@ -44,7 +44,7 @@ class API: dust GET PM10 and PM 2.5 (floating point in ug/m^3) temperature GET temperature (floating point in deg F) humidity GET relative humidity (floating point in %) - time GET timestamp from awair (reported as ISO8601 string) + time GET timestamp from airquality (reported as ISO8601 string) ------------------------------------------------------------------------------------------ ''' @@ -66,7 +66,7 @@ class API: head_auth = {'Authorization': token} timespan = {'from': start, 'to': end} - device_url = 'https://beta-api.awair.is/v1/devices/' + str(awair_id) + '/events/15min-avg' + device_url = 'https://beta-api.airquality.is/v1/devices/' + str(awair_id) + '/events/15min-avg' try: data_req = req.get( -- GitLab From 8ab096d66157539c02b878877c7f6d1087052835 Mon Sep 17 00:00:00 2001 From: mchlburton Date: Tue, 28 Mar 2017 13:35:20 -0400 Subject: [PATCH 03/12] preliminary agent, not functional --- Agents/AirQualityAgent/airquality/agent.py | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Agents/AirQualityAgent/airquality/agent.py b/Agents/AirQualityAgent/airquality/agent.py index c5ef6f6..42ffb12 100755 --- a/Agents/AirQualityAgent/airquality/agent.py +++ b/Agents/AirQualityAgent/airquality/agent.py @@ -29,7 +29,7 @@ utils.setup_logging() _log = logging.getLogger(__name__) # Step1: Agent Initialization -def AwairAgent(config_path, **kwargs): +def AirQualityAgent(config_path, **kwargs): config = utils.load_config(config_path) def get_config(name): @@ -52,10 +52,10 @@ def AwairAgent(config_path, **kwargs): building_name = get_config('building_name') zone_id = get_config('zone_id') model = get_config('model') - if model == "Philips hue bridge": - hue_username = get_config('username') + if model == "Awair": + awair_account = get_config('account') else: - hue_username = '' + awair_account = '' device_type = get_config('type') address = get_config('address') _address = address @@ -94,10 +94,10 @@ def AwairAgent(config_path, **kwargs): apiLib = importlib.import_module("DeviceAPI.classAPI."+api) #4.1 initialize device object - Awair = apiLib.API(model=model, device_type=device_type, api=api, address=address, username = hue_username, agent_id=agent_id, db_host=db_host, db_port=db_port, db_user=db_user, db_password=db_password, db_database=db_database) - print("{0}agent is initialized for {1} using API={2} at {3}".format(agent_id, Awair.get_variable('model'), - Awair.get_variable('api'), - Awair.get_variable('address'))) + AirQuality = apiLib.API(model=model, device_type=device_type, api=api, address=address, account=awair_account, agent_id=agent_id, db_host=db_host, db_port=db_port, db_user=db_user, db_password=db_password, db_database=db_database) + print("{0}agent is initialized for {1} using API={2} at {3}".format(agent_id, AirQuality.get_variable('model'), + AirQuality.get_variable('api'), + AirQuality.get_variable('address'))) #5. @params notification_info send_notification = True @@ -156,8 +156,8 @@ def AwairAgent(config_path, **kwargs): @periodic(max_monitor_time) #save all data every max_monitor_time def backupSaveData(self): try: - Awair.getDeviceStatus() - cassandraDB.insert(agent_id,Awair.variables,log_variables) + AirQuality.getDeviceStatus() + cassandraDB.insert(agent_id,AirQuality.variables,log_variables) print('Every Data Pushed to cassandra') except Exception as er: print("ERROR: {} fails to update cassandra database".format(agent_id)) @@ -168,15 +168,15 @@ def AwairAgent(config_path, **kwargs): def deviceMonitorBehavior(self): #step1: get current status of a thermostat, then map keywords and variables to agent knowledge try: - Awair.getDeviceStatus() + AirQuality.getDeviceStatus() except: print("device connection is not successful") self.changed_variables = dict() for v in log_variables: - if v in Awair.variables: - if not v in self.variables or self.variables[v] != Awair.variables[v]: - self.variables[v] = Awair.variables[v] + if v in AirQuality.variables: + if not v in self.variables or self.variables[v] != AirQuality.variables[v]: + self.variables[v] = AirQuality.variables[v] self.changed_variables[v] = log_variables[v] else: if v not in self.variables: #it won't be in self.variables either (in the first time) -- GitLab From 830d2b30117b75375650c6b2f9a134c0378fded7 Mon Sep 17 00:00:00 2001 From: mchlburton Date: Thu, 30 Mar 2017 12:44:17 -0400 Subject: [PATCH 04/12] modified WiFi discover API and created awair agent, postgres table information still needs to be updated for agent --- Agents/AirQualityAgent/airquality/agent.py | 17 ++- DeviceAPI/classAPI/classAPI_Awair.py | 1 - DeviceAPI/discoverAPI/WiFi.py | 115 ++++++++++++--------- 3 files changed, 76 insertions(+), 57 deletions(-) diff --git a/Agents/AirQualityAgent/airquality/agent.py b/Agents/AirQualityAgent/airquality/agent.py index 42ffb12..cbd2e41 100755 --- a/Agents/AirQualityAgent/airquality/agent.py +++ b/Agents/AirQualityAgent/airquality/agent.py @@ -44,7 +44,7 @@ def AirQualityAgent(config_path, **kwargs): max_monitor_time = int(settings.DEVICES['max_monitor_time']) debug_agent = False - #List of all keywords for a lighting agent + #List of all keywords for a air quality agent agentAPImapping = dict(co2=[], VOC=[], dust=[], temperature=[],humidity=[], time=[]) log_variables = dict(co2='float', VOC='float', dust='float', temperature='float',humidity='float', time='string',offline_count='int') #2. @params device_info @@ -52,10 +52,10 @@ def AirQualityAgent(config_path, **kwargs): building_name = get_config('building_name') zone_id = get_config('zone_id') model = get_config('model') - if model == "Awair": - awair_account = get_config('account') + if model == "Awair+": + awair_token = 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNTUxOSJ9.3YE0APseF-aLMcjTMSbN4bHgE--ZgcuWBEXVs-5TTO4' else: - awair_account = '' + awair_token = '' device_type = get_config('type') address = get_config('address') _address = address @@ -94,7 +94,7 @@ def AirQualityAgent(config_path, **kwargs): apiLib = importlib.import_module("DeviceAPI.classAPI."+api) #4.1 initialize device object - AirQuality = apiLib.API(model=model, device_type=device_type, api=api, address=address, account=awair_account, agent_id=agent_id, db_host=db_host, db_port=db_port, db_user=db_user, db_password=db_password, db_database=db_database) + AirQuality = apiLib.API(model=model, device_type=device_type, api=api, address=address, token=awair_token, agent_id=agent_id, db_host=db_host, db_port=db_port, db_user=db_user, db_password=db_password, db_database=db_database) print("{0}agent is initialized for {1} using API={2} at {3}".format(agent_id, AirQuality.get_variable('model'), AirQuality.get_variable('api'), AirQuality.get_variable('address'))) @@ -108,7 +108,6 @@ def AirQualityAgent(config_path, **kwargs): notify_heartbeat = settings.NOTIFICATION['heartbeat'] class Agent(PublishMixin, BaseAgent): - """Agent for querying WeatherUndergrounds API""" #1. agent initialization def __init__(self, **kwargs): @@ -433,9 +432,9 @@ def AirQualityAgent(config_path, **kwargs): headers_mod.FROM: agent_id, headers_mod.TO: 'ui' } - _data={'status': self.get_variable('status'), - 'brightness': self.get_variable('brightness'), 'color': self.get_variable('hexcolor'), - 'saturation': self.get_variable('saturation')} + _data={'co2': self.get_variable('co2'), + 'VOC': self.get_variable('VOC'), 'dust': self.get_variable('dust'), + 'temperature': self.get_variable('temperature'), 'humidity': self.get_variable('humidity')} message = json.dumps(_data) message = message.encode(encoding='utf_8') self.publish(topic, headers, message) diff --git a/DeviceAPI/classAPI/classAPI_Awair.py b/DeviceAPI/classAPI/classAPI_Awair.py index 408fc7f..6b95f2c 100755 --- a/DeviceAPI/classAPI/classAPI_Awair.py +++ b/DeviceAPI/classAPI/classAPI_Awair.py @@ -57,7 +57,6 @@ class API: def getDeviceStatus(self): getDeviceStatusResult = True - account = self.get_variable('account') token = self.get_variable('token') awair_id = self.get_variable('awair_id') diff --git a/DeviceAPI/discoverAPI/WiFi.py b/DeviceAPI/discoverAPI/WiFi.py index 2ded595..0d52e20 100755 --- a/DeviceAPI/discoverAPI/WiFi.py +++ b/DeviceAPI/discoverAPI/WiFi.py @@ -51,10 +51,10 @@ import os import re import json import urllib2 +import requests from xml.dom import minidom from bemoss_lib.utils import find_own_ip import sys - sys.path.append(os.path.expanduser("~")+"/workspace") import settings @@ -85,54 +85,73 @@ def parseJSONresponse(data,key): def discover(type, timeout=2, retries=1): - group = ("239.255.255.250", 1900) - if type=='thermostat': - message="TYPE: WM-DISCOVER\r\nVERSION: 1.0\r\n\r\nservices: com.marvell.wm.system*\r\n\r\n" + if type=='Awair': + token = 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNTUxOSJ9.3YE0APseF-aLMcjTMSbN4bHgE--ZgcuWBEXVs-5TTO4' + head_auth = {'Authorization' : token} + devices_url = 'https://beta-api.awair.is/v1/users/self/devices' + devices_req = None + try: + devices_req = requests.get(devices_url, headers = head_auth) + except Exception as er: + print er + pass + + responses = list() + + if devices_req is not None: + devices_json = devices_req.json() + for device in devices_json['data']: + responses.append(device['device_id']) + else: - message = "\r\n".join([ - 'M-SEARCH * HTTP/1.1', - 'HOST: {0}:{1}', - 'MAN: "ssdp:discover"', - 'ST: {st}','MX: 3','','']) - if type=='WeMo': - service="upnp:rootdevice" - message=message.format(*group, st=service) - elif type=='Philips': - service="urn:schemas-upnp-org:device:Basic:1" - message=message.format(*group, st=service) - - socket.setdefaulttimeout(timeout) - responses = list() - IPs = find_own_ip.getIPs() - for IP in IPs: - for _ in range(retries): - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) - sock.bind((IP, 1900)) - try: - sock.sendto(message, group) - except: - print("[Errno 101] Network is unreachable") - - while True: + group = ("239.255.255.250", 1900) + if type=='thermostat': + message="TYPE: WM-DISCOVER\r\nVERSION: 1.0\r\n\r\nservices: com.marvell.wm.system*\r\n\r\n" + else: + message = "\r\n".join([ + 'M-SEARCH * HTTP/1.1', + 'HOST: {0}:{1}', + 'MAN: "ssdp:discover"', + 'ST: {st}','MX: 3','','']) + if type=='WeMo': + service="upnp:rootdevice" + message=message.format(*group, st=service) + elif type=='Philips': + service="urn:schemas-upnp-org:device:Basic:1" + message=message.format(*group, st=service) + + socket.setdefaulttimeout(timeout) + responses = list() + IPs = find_own_ip.getIPs() + for IP in IPs: + for _ in range(retries): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) + sock.bind((IP, 1900)) try: - raw = sock.recv(1024) - response = str(SSDPResponseLocation(raw)) - if debug: print response - else: pass - if type=="thermostat": - if "/sys" in response and response not in responses: - responses.append(response) - elif type=="WeMo": - if (':49153/setup.xml' in response or ':49154/setup.xml' in response or '/setup.xml' in response) and response not in responses: - responses.append(response) - elif type=="Philips": - if ":80/description.xml" in response and response not in responses: - print "response {}".format(response) - responses.append(response) - except socket.timeout: - break + sock.sendto(message, group) + except: + print("[Errno 101] Network is unreachable") + + while True: + try: + raw = sock.recv(1024) + response = str(SSDPResponseLocation(raw)) + if debug: print response + else: pass + if type=="thermostat": + if "/sys" in response and response not in responses: + responses.append(response) + elif type=="WeMo": + if (':49153/setup.xml' in response or ':49154/setup.xml' in response or '/setup.xml' in response) and response not in responses: + responses.append(response) + elif type=="Philips": + if ":80/description.xml" in response and response not in responses: + print "response {}".format(response) + responses.append(response) + except socket.timeout: + break return responses @@ -198,6 +217,8 @@ def getmodelvendor(type,ipaddress): deviceModel = 'Unknown' deviceUrl.close() return {'model':deviceModel,'vendor':deviceVendor,'nickname':nickname} + elif type=="Awair": + return {'model':'Awair+','vendor':'Awair'} # This main method will not be executed when this class is used as a module -- GitLab From 2ca12c3c257c27c1a8107b03e01d6af09b16678d Mon Sep 17 00:00:00 2001 From: mchlburton Date: Thu, 30 Mar 2017 12:46:51 -0400 Subject: [PATCH 05/12] changes to device discovery agent --- Agents/DeviceDiscoveryAgent/devicediscovery/agent.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Agents/DeviceDiscoveryAgent/devicediscovery/agent.py b/Agents/DeviceDiscoveryAgent/devicediscovery/agent.py index 3248182..eb18e84 100755 --- a/Agents/DeviceDiscoveryAgent/devicediscovery/agent.py +++ b/Agents/DeviceDiscoveryAgent/devicediscovery/agent.py @@ -135,11 +135,13 @@ def DeviceDiscoveryAgent(config_path, **kwargs): if self.findWiFi: self.discovery_list.append('CT30 V1.94') self.discovery_list.append('CT50 V1.94') + self.discovery_list.append('Awair+') if self.findWiFiWeMo: self.discovery_list.append('Socket') self.discovery_list.append('LightSwitch') self.discovery_list.append('Insight') - if self.findWiFiHue: self.discovery_list.append('Philips hue bridge') + if self.findWiFiHue: + self.discovery_list.append('Philips hue bridge') if self.findBACnet: self.discovery_list.append('LMPL-201') self.discovery_list.append('LMRC-212') @@ -253,7 +255,7 @@ def DeviceDiscoveryAgent(config_path, **kwargs): print "{} >> is finding available {} {} devices ...".format(agent_id,com_type,discovery_type) discovery_module = importlib.import_module("DeviceAPI.discoverAPI."+com_type) - if (com_type == 'BACnet') or (com_type == 'Serial'): + if (com_type == 'BACnet') or (com_type == 'Serial') or (discovery_type == 'Awair'): discovery_returns_ip = False else: discovery_returns_ip = True -- GitLab From 743a59e0200afa2c6d051bd81cf656ef31c36ddb Mon Sep 17 00:00:00 2001 From: mchlburton Date: Thu, 30 Mar 2017 18:14:42 -0400 Subject: [PATCH 06/12] edits to platform initiator and build agents --- bemoss_lib/utils/buildAgents.sh | 1 + bemoss_lib/utils/platform_initiator.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/bemoss_lib/utils/buildAgents.sh b/bemoss_lib/utils/buildAgents.sh index c94ed65..96b345d 100755 --- a/bemoss_lib/utils/buildAgents.sh +++ b/bemoss_lib/utils/buildAgents.sh @@ -53,6 +53,7 @@ sudo rm -rf ~/.volttron/agents/* cd ~/workspace/bemoss_os/ volttron-pkg package ~/workspace/bemoss_os/Agents/ThermostatAgent +volttron-pkg package ~/workspace/bemoss_os/Agents/AirQualityAgent volttron-pkg package ~/workspace/bemoss_os/Agents/PlugloadAgent volttron-pkg package ~/workspace/bemoss_os/Agents/LightingAgent volttron-pkg package ~/workspace/bemoss_os/Agents/NetworkAgent diff --git a/bemoss_lib/utils/platform_initiator.py b/bemoss_lib/utils/platform_initiator.py index d1955a7..35d1488 100755 --- a/bemoss_lib/utils/platform_initiator.py +++ b/bemoss_lib/utils/platform_initiator.py @@ -356,6 +356,8 @@ cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s, ("Insight","Belkin International Inc.","WiFi","plugload","WeMo","3WIS","classAPI_WeMo",True,False,4,4,True)) cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", ("Tekmar Bypass Controller","BlocPower","Serial","plugload","USB-RLY02","3WSP","classAPI_Tekmar_Bypass",True,False,4,4,True)) +cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", + ("Awair+","Awair","Wifi","lighting","Awair","1AIR","classAPI_Awair",False,True,4,4,True)) conn.commit() print "Table supported_devices populated successfully!" -- GitLab From b8e5113c50b6f0c322fe80a9876f6ed77fa7b9e5 Mon Sep 17 00:00:00 2001 From: mchlburton Date: Mon, 3 Apr 2017 14:00:25 -0400 Subject: [PATCH 07/12] working classAPI_Awair --- DeviceAPI/classAPI/classAPI_Awair.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/DeviceAPI/classAPI/classAPI_Awair.py b/DeviceAPI/classAPI/classAPI_Awair.py index 6b95f2c..b555949 100755 --- a/DeviceAPI/classAPI/classAPI_Awair.py +++ b/DeviceAPI/classAPI/classAPI_Awair.py @@ -58,29 +58,25 @@ class API: def getDeviceStatus(self): getDeviceStatusResult = True token = self.get_variable('token') - awair_id = self.get_variable('awair_id') + awair_id = self.get_variable('address') + print awair_id - start = (datetime.datetime.now()-datetime.timedelta(minutes = 15)).isoformat() - end = datetime.datetime.now().isoformat() head_auth = {'Authorization': token} - timespan = {'from': start, 'to': end} - - device_url = 'https://beta-api.airquality.is/v1/devices/' + str(awair_id) + '/events/15min-avg' + device_url = 'https://beta-api.awair.is/v1/devices/' + str(awair_id) + '/events/15min-avg' + start = (datetime.datetime.now()-datetime.timedelta(minutes=15)).isoformat() + end = datetime.datetime.now().isoformat() + timespan = {'from': start, 'to': end} try: - data_req = req.get( - device_url, - headers = token, - params = timespan, - ) + data_req = req.get(device_url,headers = head_auth, params = timespan) data_json = data_req.json() + print data_json data_list = [] for row in data_json['data']: data = row['sensor'].values() time = row['timestamp'] - self.set_variable('dust', data[0]) self.set_variable('co2', data[1]) self.set_variable('humidity', data[2]) @@ -96,5 +92,4 @@ class API: self.set_variable('offline_count', 0) else: - self.set_variable('offline_count', self.get_variable('offline_count')+1) - + self.set_variable('offline_count', self.get_variable('offline_count')+1) \ No newline at end of file -- GitLab From bbfa6333fba115722454877a4e746d13b055001e Mon Sep 17 00:00:00 2001 From: mchlburton Date: Tue, 4 Apr 2017 16:12:33 -0400 Subject: [PATCH 08/12] Awair agent with proper postgres interaction --- Agents/AirQualityAgent/airquality/__init__.py | 0 Agents/AirQualityAgent/airquality/agent.py | 45 +++++++++---------- settings.py | 5 ++- 3 files changed, 25 insertions(+), 25 deletions(-) create mode 100755 Agents/AirQualityAgent/airquality/__init__.py diff --git a/Agents/AirQualityAgent/airquality/__init__.py b/Agents/AirQualityAgent/airquality/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/Agents/AirQualityAgent/airquality/agent.py b/Agents/AirQualityAgent/airquality/agent.py index cbd2e41..dff5a9b 100755 --- a/Agents/AirQualityAgent/airquality/agent.py +++ b/Agents/AirQualityAgent/airquality/agent.py @@ -40,7 +40,7 @@ def AirQualityAgent(config_path, **kwargs): #1. @params agent agent_id = get_config('agent_id') - device_monitor_time = get_config('device_monitor_time') + device_monitor_time = 900 max_monitor_time = int(settings.DEVICES['max_monitor_time']) debug_agent = False @@ -58,14 +58,7 @@ def AirQualityAgent(config_path, **kwargs): awair_token = '' device_type = get_config('type') address = get_config('address') - _address = address - _address = _address.replace('http://', '') - _address = _address.replace('https://', '') - try: # validate whether or not address is an ip address - socket.inet_aton(_address) - ip_address = _address - except socket.error: - ip_address = None + ip_address = None identifiable = get_config('identifiable') #3. @params agent & DB interfaces @@ -78,7 +71,7 @@ def AirQualityAgent(config_path, **kwargs): db_database = get_config('db_database') db_user = get_config('db_user') db_password = get_config('db_password') - db_table_lighting = settings.DATABASES['default']['TABLE_lighting'] + db_table_airquality = settings.DATABASES['default']['TABLE_airqaulity'] db_table_active_alert = settings.DATABASES['default']['TABLE_active_alert'] db_table_bemoss_notify = settings.DATABASES['default']['TABLE_bemoss_notify'] db_table_alerts_notificationchanneladdress = settings.DATABASES['default'][ @@ -189,8 +182,8 @@ def AirQualityAgent(config_path, **kwargs): # Put scan time in database _time_stamp_last_scanned = datetime.datetime.now() - self.cur.execute("UPDATE "+db_table_lighting+" SET last_scanned_time=%s " - "WHERE lighting_id=%s", + self.cur.execute("UPDATE "+db_table_airquality+" SET last_scanned_time=%s " + "WHERE airquality_id=%s", (_time_stamp_last_scanned, agent_id)) self.con.commit() except Exception as er: @@ -204,14 +197,20 @@ def AirQualityAgent(config_path, **kwargs): self.updateUI() #step4: update PostgresQL (meta-data) database try: - self.cur.execute("UPDATE "+db_table_lighting+" SET status=%s WHERE lighting_id=%s", - (self.get_variable('status'), agent_id)) + self.cur.execute("UPDATE "+db_table_airquality+" SET co2=%s WHERE airquality_id=%s", + (self.get_variable('co2'), agent_id)) self.con.commit() - self.cur.execute("UPDATE "+db_table_lighting+" SET brightness=%s WHERE lighting_id=%s", - (self.get_variable('brightness'), agent_id)) + self.cur.execute("UPDATE "+db_table_airquality+" SET VOC=%s WHERE airquality_id=%s", + (self.get_variable('VOC'), agent_id)) self.con.commit() - self.cur.execute("UPDATE "+db_table_lighting+" SET color=%s WHERE lighting_id=%s", - (self.get_variable('hexcolor'), agent_id)) + self.cur.execute("UPDATE "+db_table_airquality+" SET dust=%s WHERE airquality_id=%s", + (self.get_variable('dust'), agent_id)) + self.con.commit() + self.cur.execute("UPDATE "+db_table_airquality+" SET temperature=%s WHERE airquality_id=%s", + (self.get_variable('temperature'), agent_id)) + self.con.commit() + self.cur.execute("UPDATE "+db_table_airquality+" SET humidity=%s WHERE airquality_id=%s", + (self.get_variable('humidity'), agent_id)) self.con.commit() #TODO check ip_address @@ -224,18 +223,18 @@ def AirQualityAgent(config_path, **kwargs): if self.get_variable('offline_count') >= 3: - self.cur.execute("UPDATE "+db_table_lighting+" SET network_status=%s WHERE lighting_id=%s", + self.cur.execute("UPDATE "+db_table_airquality+" SET network_status=%s WHERE airquality_id=%s", ('OFFLINE', agent_id)) self.con.commit() if self.already_offline is False: self.already_offline = True _time_stamp_last_offline = str(datetime.datetime.now()) - self.cur.execute("UPDATE "+db_table_lighting+" SET last_offline_time=%s WHERE lighting_id=%s", + self.cur.execute("UPDATE "+db_table_airquality+" SET last_offline_time=%s WHERE airquality_id=%s", (_time_stamp_last_offline, agent_id)) self.con.commit() else: self.already_offline = False - self.cur.execute("UPDATE "+db_table_lighting+" SET network_status=%s WHERE lighting_id=%s", + self.cur.execute("UPDATE "+db_table_airquality+" SET network_status=%s WHERE airquality_id=%s", ('ONLINE', agent_id)) self.con.commit() print("{} updates database name {} during deviceMonitorBehavior successfully".format(agent_id, db_database)) @@ -267,7 +266,7 @@ def AirQualityAgent(config_path, **kwargs): print (k, v) def device_offline_detection(self): - self.cur.execute("SELECT nickname FROM " + db_table_lighting + " WHERE lighting_id=%s", + self.cur.execute("SELECT nickname FROM " + db_table_airquality + " WHERE airquality_id=%s", (agent_id,)) print agent_id if self.cur.rowcount != 0: @@ -278,7 +277,7 @@ def AirQualityAgent(config_path, **kwargs): _db_notification_subject = 'BEMOSS Device {} {} went OFFLINE!!!'.format(device_nickname,agent_id) _email_subject = '#Attention: BEMOSS Device {} {} went OFFLINE!!!'.format(device_nickname,agent_id) _email_text = '#Attention: BEMOSS Device {} {} went OFFLINE!!!'.format(device_nickname,agent_id) - self.cur.execute("SELECT network_status FROM " + db_table_lighting + " WHERE lighting_id=%s", + self.cur.execute("SELECT network_status FROM " + db_table_airquality + " WHERE airquality_id=%s", (agent_id,)) self.network_status = self.cur.fetchone()[0] print self.network_status diff --git a/settings.py b/settings.py index 43d1a5d..6ad5f63 100755 --- a/settings.py +++ b/settings.py @@ -100,6 +100,7 @@ DATABASES = { 'TABLE_application_registered': 'application_registered', 'TABLE_plugload': 'plugload', 'TABLE_thermostat': 'thermostat', + 'TABLE_airquality': 'airquality' 'TABLE_lighting': 'lighting', 'TABLE_device_metadata': 'device_metadata', 'TABLE_vav': 'vav', @@ -153,10 +154,10 @@ LANGUAGE_CODE = 'en-us' FIND_DEVICE_SETTINGS = { - 'findWiFi': False, + 'findWiFi': True, 'findWiFiHue': False, 'findWiFiWeMo': False, 'findBACnet': False, 'findModbus': False, - 'findSerial': True, + 'findSerial': False, } -- GitLab From 28af7c4ff69d73bf19aa232a076ada3d7390242f Mon Sep 17 00:00:00 2001 From: mchlburton Date: Tue, 4 Apr 2017 16:15:03 -0400 Subject: [PATCH 09/12] singular "lighting" reference removed --- Agents/AirQualityAgent/airquality/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Agents/AirQualityAgent/airquality/agent.py b/Agents/AirQualityAgent/airquality/agent.py index dff5a9b..baaf34b 100755 --- a/Agents/AirQualityAgent/airquality/agent.py +++ b/Agents/AirQualityAgent/airquality/agent.py @@ -217,7 +217,7 @@ def AirQualityAgent(config_path, **kwargs): if self.ip_address != None: psycopg2.extras.register_inet() _ip_address = psycopg2.extras.Inet(self.ip_address) - self.cur.execute("UPDATE "+db_table_lighting+" SET ip_address=%s WHERE lighting_id=%s", + self.cur.execute("UPDATE "+db_table_airquality+" SET ip_address=%s WHERE airquality_id=%s", (_ip_address, agent_id)) self.con.commit() -- GitLab From e413bc8266c4dc229c9b937e28e8778db755da29 Mon Sep 17 00:00:00 2001 From: mchlburton Date: Tue, 4 Apr 2017 16:23:13 -0400 Subject: [PATCH 10/12] platform_initiator edits --- bemoss_lib/utils/platform_initiator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bemoss_lib/utils/platform_initiator.py b/bemoss_lib/utils/platform_initiator.py index 35d1488..49eec8c 100755 --- a/bemoss_lib/utils/platform_initiator.py +++ b/bemoss_lib/utils/platform_initiator.py @@ -76,6 +76,7 @@ db_table_application_registered = settings.DATABASES['default']['TABLE_applicati db_table_plugload = settings.DATABASES['default']['TABLE_plugload'] db_table_thermostat = settings.DATABASES['default']['TABLE_thermostat'] db_table_lighting = settings.DATABASES['default']['TABLE_lighting'] +db_table_airquality = setting.DATABASES['default']['TABLE_airquality'] db_table_device_metadata = settings.DATABASES['default']['TABLE_device_metadata'] db_table_vav = settings.DATABASES['default']['TABLE_vav'] db_table_rtu = settings.DATABASES['default']['TABLE_rtu'] @@ -104,6 +105,7 @@ print "{} >> Done 1: connect to database name {}".format(agent_id, db_database) #2. clean tables cur.execute("DELETE FROM "+db_table_thermostat) cur.execute("DELETE FROM "+db_table_lighting) +cur.execute("DELETE FROM "+db_table_airquality) cur.execute("DELETE FROM "+db_table_plugload) cur.execute("DELETE FROM "+db_table_vav) cur.execute("DELETE FROM "+db_table_rtu) @@ -357,7 +359,7 @@ cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s, cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", ("Tekmar Bypass Controller","BlocPower","Serial","plugload","USB-RLY02","3WSP","classAPI_Tekmar_Bypass",True,False,4,4,True)) cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", - ("Awair+","Awair","Wifi","lighting","Awair","1AIR","classAPI_Awair",False,True,4,4,True)) + ("Awair+","Awair","Wifi","airquality","Awair","1AIR","classAPI_Awair",False,True,4,4,True)) conn.commit() print "Table supported_devices populated successfully!" -- GitLab From bff6a4992944e79a635f1edba8de9168aca33908 Mon Sep 17 00:00:00 2001 From: mchlburton Date: Thu, 6 Apr 2017 10:29:53 -0400 Subject: [PATCH 11/12] Awair agent with some changes for db and main added --- Agents/AirQualityAgent/airquality/agent.py | 19 ++++- Agents/AirQualityAgent/setup.py | 76 +++++++++++++++++++ .../devicediscovery/agent.py | 5 +- DeviceAPI/discoverAPI/WiFi.py | 3 + bemoss_lib/utils/platform_initiator.py | 4 +- settings.py | 2 +- 6 files changed, 101 insertions(+), 8 deletions(-) create mode 100755 Agents/AirQualityAgent/setup.py diff --git a/Agents/AirQualityAgent/airquality/agent.py b/Agents/AirQualityAgent/airquality/agent.py index baaf34b..3f79f68 100755 --- a/Agents/AirQualityAgent/airquality/agent.py +++ b/Agents/AirQualityAgent/airquality/agent.py @@ -40,7 +40,7 @@ def AirQualityAgent(config_path, **kwargs): #1. @params agent agent_id = get_config('agent_id') - device_monitor_time = 900 + device_monitor_time = 10 max_monitor_time = int(settings.DEVICES['max_monitor_time']) debug_agent = False @@ -71,7 +71,7 @@ def AirQualityAgent(config_path, **kwargs): db_database = get_config('db_database') db_user = get_config('db_user') db_password = get_config('db_password') - db_table_airquality = settings.DATABASES['default']['TABLE_airqaulity'] + db_table_airquality = settings.DATABASES['default']['TABLE_airquality'] db_table_active_alert = settings.DATABASES['default']['TABLE_active_alert'] db_table_bemoss_notify = settings.DATABASES['default']['TABLE_bemoss_notify'] db_table_alerts_notificationchanneladdress = settings.DATABASES['default'][ @@ -444,3 +444,18 @@ def AirQualityAgent(config_path, **kwargs): print "{} agent got\nTopic: {topic}".format(self.get_variable("agent_id"),topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}\n".format(message=message) + + Agent.__name__ = 'AirQualityAgent' + return Agent(**kwargs) + +def main(argv=sys.argv): + '''Main method called by the eggsecutable.''' + utils.default_main(AirQualityAgent, + description='Air Quality agent', + argv=argv) + +if __name__ == '__main__': + try: + sys.exit(main()) + except KeyboardInterrupt: + pass \ No newline at end of file diff --git a/Agents/AirQualityAgent/setup.py b/Agents/AirQualityAgent/setup.py new file mode 100755 index 0000000..90215b1 --- /dev/null +++ b/Agents/AirQualityAgent/setup.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: + +# Copyright (c) 2013, Battelle Memorial Institute +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# The views and conclusions contained in the software and documentation +# are those of the authors and should not be interpreted as representing +# official policies, either expressed or implied, of the FreeBSD +# Project. +# +# This material was prepared as an account of work sponsored by an +# agency of the United States Government. Neither the United States +# Government nor the United States Department of Energy, nor Battelle, +# nor any of their employees, nor any jurisdiction or organization that +# has cooperated in the development of these materials, makes any +# warranty, express or implied, or assumes any legal liability or +# responsibility for the accuracy, completeness, or usefulness or any +# information, apparatus, product, software, or process disclosed, or +# represents that its use would not infringe privately owned rights. +# +# Reference herein to any specific commercial product, process, or +# service by trade name, trademark, manufacturer, or otherwise does not +# necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors +# expressed herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY +# operated by BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 + +#}}} + +from setuptools import setup, find_packages + +#get environ for agent name/identifier +packages = find_packages('.') +package = packages[0] + +setup( + name = package + 'agent', + version = "0.1", + install_requires = ['volttron'], + packages = packages, + entry_points = { + 'setuptools.installation': [ + 'eggsecutable = ' + package + '.agent:main', + ] + } +) + diff --git a/Agents/DeviceDiscoveryAgent/devicediscovery/agent.py b/Agents/DeviceDiscoveryAgent/devicediscovery/agent.py index eb18e84..5e95531 100755 --- a/Agents/DeviceDiscoveryAgent/devicediscovery/agent.py +++ b/Agents/DeviceDiscoveryAgent/devicediscovery/agent.py @@ -255,7 +255,7 @@ def DeviceDiscoveryAgent(config_path, **kwargs): print "{} >> is finding available {} {} devices ...".format(agent_id,com_type,discovery_type) discovery_module = importlib.import_module("DeviceAPI.discoverAPI."+com_type) - if (com_type == 'BACnet') or (com_type == 'Serial') or (discovery_type == 'Awair'): + if (com_type == 'BACnet') or (com_type == 'Serial') or (discovery_type=='Awair'): discovery_returns_ip = False else: discovery_returns_ip = True @@ -286,8 +286,7 @@ def DeviceDiscoveryAgent(config_path, **kwargs): else: # this device does not return IP, wait until it get the right mac try: - ip_address = '' # specifically for cloud devices - macaddress = discovery_module.getMACaddress(discovery_type, ip_address) + macaddress = discovery_module.getMACaddress(discovery_type, address) # print 'type macaddress: {} and macaddress: {}'.format(type(macaddress),macaddress) ip_address = None diff --git a/DeviceAPI/discoverAPI/WiFi.py b/DeviceAPI/discoverAPI/WiFi.py index 0d52e20..e456078 100755 --- a/DeviceAPI/discoverAPI/WiFi.py +++ b/DeviceAPI/discoverAPI/WiFi.py @@ -180,6 +180,9 @@ def getMACaddress(type,ipaddress): macid=dom.getElementsByTagName('serialNumber')[0].firstChild.data deviceUrl.close() return macid + elif type=="Awair": + awairid = ipaddress + return awairid else: print "This device: {} is not supported by the WiFi discovery module".format(type) diff --git a/bemoss_lib/utils/platform_initiator.py b/bemoss_lib/utils/platform_initiator.py index 49eec8c..677c5af 100755 --- a/bemoss_lib/utils/platform_initiator.py +++ b/bemoss_lib/utils/platform_initiator.py @@ -76,7 +76,7 @@ db_table_application_registered = settings.DATABASES['default']['TABLE_applicati db_table_plugload = settings.DATABASES['default']['TABLE_plugload'] db_table_thermostat = settings.DATABASES['default']['TABLE_thermostat'] db_table_lighting = settings.DATABASES['default']['TABLE_lighting'] -db_table_airquality = setting.DATABASES['default']['TABLE_airquality'] +db_table_airquality = settings.DATABASES['default']['TABLE_airquality'] db_table_device_metadata = settings.DATABASES['default']['TABLE_device_metadata'] db_table_vav = settings.DATABASES['default']['TABLE_vav'] db_table_rtu = settings.DATABASES['default']['TABLE_rtu'] @@ -359,7 +359,7 @@ cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s, cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", ("Tekmar Bypass Controller","BlocPower","Serial","plugload","USB-RLY02","3WSP","classAPI_Tekmar_Bypass",True,False,4,4,True)) cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", - ("Awair+","Awair","Wifi","airquality","Awair","1AIR","classAPI_Awair",False,True,4,4,True)) + ("Awair+","Awair","WiFi","airquality","Awair","1AIR","classAPI_Awair",False,True,4,4,True)) conn.commit() print "Table supported_devices populated successfully!" diff --git a/settings.py b/settings.py index 6ad5f63..5319e1f 100755 --- a/settings.py +++ b/settings.py @@ -100,7 +100,7 @@ DATABASES = { 'TABLE_application_registered': 'application_registered', 'TABLE_plugload': 'plugload', 'TABLE_thermostat': 'thermostat', - 'TABLE_airquality': 'airquality' + 'TABLE_airquality': 'airquality', 'TABLE_lighting': 'lighting', 'TABLE_device_metadata': 'device_metadata', 'TABLE_vav': 'vav', -- GitLab From 03e005c637d8ddd77f52a34ea19fd331e675f193 Mon Sep 17 00:00:00 2001 From: Avijit Saha Date: Thu, 6 Apr 2017 12:05:43 -0400 Subject: [PATCH 12/12] Fix issues to run awair agent, push data to postgres, fix discovery return string issue --- Agents/AirQualityAgent/airquality/agent.py | 12 ++++++------ DeviceAPI/discoverAPI/WiFi.py | 2 +- bemoss_lib/utils/platform_initiator.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Agents/AirQualityAgent/airquality/agent.py b/Agents/AirQualityAgent/airquality/agent.py index 3f79f68..5817d80 100755 --- a/Agents/AirQualityAgent/airquality/agent.py +++ b/Agents/AirQualityAgent/airquality/agent.py @@ -40,13 +40,13 @@ def AirQualityAgent(config_path, **kwargs): #1. @params agent agent_id = get_config('agent_id') - device_monitor_time = 10 + device_monitor_time = 900 max_monitor_time = int(settings.DEVICES['max_monitor_time']) debug_agent = False #List of all keywords for a air quality agent - agentAPImapping = dict(co2=[], VOC=[], dust=[], temperature=[],humidity=[], time=[]) - log_variables = dict(co2='float', VOC='float', dust='float', temperature='float',humidity='float', time='string',offline_count='int') + agentAPImapping = dict(co2=[], voc=[], dust=[], temperature=[],humidity=[], time=[]) + log_variables = dict(co2='float', voc='float', dust='float', temperature='float',humidity='float', time='string',offline_count='int') #2. @params device_info #TODO correct the launchfile in Device Discovery Agent building_name = get_config('building_name') @@ -200,8 +200,8 @@ def AirQualityAgent(config_path, **kwargs): self.cur.execute("UPDATE "+db_table_airquality+" SET co2=%s WHERE airquality_id=%s", (self.get_variable('co2'), agent_id)) self.con.commit() - self.cur.execute("UPDATE "+db_table_airquality+" SET VOC=%s WHERE airquality_id=%s", - (self.get_variable('VOC'), agent_id)) + self.cur.execute("UPDATE "+db_table_airquality+" SET voc=%s WHERE airquality_id=%s", + (self.get_variable('voc'), agent_id)) self.con.commit() self.cur.execute("UPDATE "+db_table_airquality+" SET dust=%s WHERE airquality_id=%s", (self.get_variable('dust'), agent_id)) @@ -432,7 +432,7 @@ def AirQualityAgent(config_path, **kwargs): headers_mod.TO: 'ui' } _data={'co2': self.get_variable('co2'), - 'VOC': self.get_variable('VOC'), 'dust': self.get_variable('dust'), + 'voc': self.get_variable('voc'), 'dust': self.get_variable('dust'), 'temperature': self.get_variable('temperature'), 'humidity': self.get_variable('humidity')} message = json.dumps(_data) message = message.encode(encoding='utf_8') diff --git a/DeviceAPI/discoverAPI/WiFi.py b/DeviceAPI/discoverAPI/WiFi.py index e456078..cb9ab97 100755 --- a/DeviceAPI/discoverAPI/WiFi.py +++ b/DeviceAPI/discoverAPI/WiFi.py @@ -101,7 +101,7 @@ def discover(type, timeout=2, retries=1): if devices_req is not None: devices_json = devices_req.json() for device in devices_json['data']: - responses.append(device['device_id']) + responses.append(str(device['device_id'])) else: group = ("239.255.255.250", 1900) diff --git a/bemoss_lib/utils/platform_initiator.py b/bemoss_lib/utils/platform_initiator.py index 677c5af..114ccee 100755 --- a/bemoss_lib/utils/platform_initiator.py +++ b/bemoss_lib/utils/platform_initiator.py @@ -359,7 +359,7 @@ cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s, cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", ("Tekmar Bypass Controller","BlocPower","Serial","plugload","USB-RLY02","3WSP","classAPI_Tekmar_Bypass",True,False,4,4,True)) cur.execute("INSERT INTO supported_devices VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", - ("Awair+","Awair","WiFi","airquality","Awair","1AIR","classAPI_Awair",False,True,4,4,True)) + ("Awair+","Awair","WiFi","airquality","Awair","4AIR","classAPI_Awair",False,True,4,4,True)) conn.commit() print "Table supported_devices populated successfully!" -- GitLab