Skip to content

Control Agent Development

ARIbemoss edited this page May 11, 2017 · 1 revision

[Back to Agent Development]

Control Agent thread path of execution ====================================

alternate text

Fig.1 Agent thread path of execution

The development processes of control agent according to the agent thread path of execution are explained in detail as follows:

Step1: Load Agent Configuration

alternate text

1.1 load parameters setting for agent

python

#1. @params agent agent_id = get_config('agent_id') LOG_DATA_PERIOD = get_config('poll_time') device_monitor_time = get_config('device_monitor_time') publish_address = 'ipc:///tmp/volttron-lite-agent-publish' subscribe_address = 'ipc:///tmp/volttron-lite-agent-subscribe' debug_agent = False agentknowledge = dict(day=["day"], hour=["hour"], minute=["minute"], temperature=["temp", "temperature", "current_temp"], thermostat_mode=["tmode", "ther_mode", "thermostat_mode"], fan_mode=["fmode", "fan_mode"], heat_setpoint=["t_heat", "temp_heat", "heat_setpoint"], cool_setpoint=["t_cool", "temp_cool", "cool_setpoint"], thermostat_state=["tstate", "thermostat_state"], fan_state=["fstate", "fan_state"]) agentAPImapping = dict(temperature=[], thermostat_mode=[], fan_mode=[], heat_setpoint=[], cool_setpoint=[], thermostat_state=[], fan_state=[])

1.2 load parameters setting device information

python

#2. @params device_info # building = get_config('building') # zone = get_config('zone') # room = get_config('room') model = get_config('model') device_type = get_config('type') address = get_config('address') # mac_address = get_config('mac_address')

1.3 load parameters for database interface

python

#3. @params agent & DB interfaces 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')

_topic_Agent_UI = 'building1/999/thermostat/'+agent_id+'/'

_topic_Agent_sMAP = 'datalogger/log/building1/999/thermostat/'+agent_id


Step2: Initialize Device Object

Screen Shot 2014-08-21 at 3.12.40 PM.png

2.1 create a device object from the API interface (with initial configurations)

python

#4. @params device_api api = get_config('api') apiLib = importlib.import_module("testAPI."+api)

#4.1 initialize thermostat device object Thermostat = apiLib.API(model=model, device_type=device_type, api=api, address=address, agent_id=agent_id)

print("{0}agent is initialized for {1} using API={2} at {3}".format(agent_id, Thermostat.get_variable('model'),

Thermostat.get_variable('api'), Thermostat.get_variable('address')))

2.2 setup notification information

python

#5. @params notification_info send_notification = False email_fromaddr = settings.NOTIFICATION['email']['fromaddr'] email_recipients = settings.NOTIFICATION['email']['recipients'] email_username = settings.NOTIFICATION['email']['username'] email_password = settings.NOTIFICATION['email']['password'] email_mailServer = settings.NOTIFICATION['email']['mailServer'] alert_too_hot = settings.NOTIFICATION['thermostat']['too_hot'] alert_too_cold = settings.NOTIFICATION['thermostat']['too_cold'] send_notification_status = False time_delay_send_notification = 600


Step3: Initialize Agent

Screen Shot 2014-08-21 at 3.26.37 PM.png

There are two essential methods to initialize an agent

3.1 __init__() method

3.1.1 initialize agent variables

3.1.2 setup connections with databases

3.1.3 send notification to building admin/user (optional)

python

class Agent(PublishMixin, BaseAgent):

#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.first_time_update = True self.topic = _topic_Agent_sMAP self.flag = 1 #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 perfomm 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.send_notification_status = send_notification_status self.time_send_notification = 0 if self.send_notification: self.subject = 'Message from ' + agent_id self.text = 'Now an agent device_type {} for {} with API {} at address {} is launched!'.format( Thermostat.get_variable('device_type'), Thermostat.get_variable('model'), Thermostat.get_variable('api'), Thermostat.get_variable('address')) emailService = EmailService() emailService.sendEmail(email_fromaddr, email_recipients, email_username, email_password, self.subject, self.text, email_mailServer)

#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

3.2 setup() method

python

#2. agent setup method def setup(self): super(Agent, self).setup() #1. Do a one time push when we start up so we don't have to wait for the periodic Thermostat.identifyDevice() self.timer(1, self.deviceMonitorBehavior)


Step4: DeviceMonitor Behavior

Screen Shot 2014-08-21 at 3.48.33 PM.png

4.1 declare the DeviceMonitor Behavior to be the TickerBehavior (periodically run by specifying "device_monitor_time")

python

#deviceMonitorBehavior (TickerBehavior) @periodic(device_monitor_time) def deviceMonitorBehavior(self):

4.2 get current status of a device by calling getDeviceStatus() method of an API interface, then map keywords and variables to agent knowledge

python

#step1: get current status of a thermostat, then map keywords and variables to agent knowledge
try:

Thermostat.getDeviceStatus() #mapping variables from API to Agent's knowledge for APIKeyword, APIvariable in Thermostat.variables.items(): if debug_agent: print (APIKeyword, APIvariable) self.set_variable(self.getKeyword(APIKeyword), APIvariable) # set variables of agent from API variables agentAPImapping[self.getKeyword(APIKeyword)] = APIKeyword # map keyword of agent and API

except:

print("device connection is not successful")

if self.first_time_update:
if self.get_variable('heat_setpoint') is None:

self.set_variable('heat_setpoint', 70)

else:

pass

if self.get_variable('cool_setpoint') is None:

self.set_variable('cool_setpoint', 70)

else:

pass

self.first_time_update = False

else:

pass

4.3 update PostgreSQL (meta-data database) with the current status of a device

python

#step2: update PostgresQL (meta-data) database
try:
self.cur.execute("UPDATE dashboard_current_status SET temperature=%s WHERE id=%s",

(self.get_variable('temperature'), agent_id))

self.con.commit() self.cur.execute("UPDATE dashboard_current_status SET fmode=%s WHERE id=%s", (self.get_variable('fan_mode'), agent_id)) self.con.commit() #TODO check thermostat mode if self.get_variable('thermostat_mode') == "HEAT": self.cur.execute("UPDATE dashboard_current_status SET setpoint=%s WHERE id=%s", (self.get_variable('heat_setpoint'), agent_id)) self.con.commit() self.cur.execute("UPDATE dashboard_current_status SET mode=%s WHERE id=%s", ('HEAT', agent_id)) self.con.commit() elif self.get_variable('thermostat_mode') == "COOL": self.cur.execute("UPDATE dashboard_current_status SET setpoint=%s WHERE id=%s", (self.get_variable('cool_setpoint'), agent_id)) self.con.commit() self.cur.execute("UPDATE dashboard_current_status SET mode=%s WHERE id=%s", ('COOL', agent_id)) self.con.commit() elif self.get_variable('thermostat_mode') == "OFF": self.cur.execute("UPDATE dashboard_current_status SET mode=%s WHERE id=%s", ('OFF', agent_id)) self.con.commit() elif self.get_variable('thermostat_mode') == "AUTO": self.cur.execute("UPDATE dashboard_current_status SET mode=%s WHERE id=%s", ('AUTO', agent_id)) self.con.commit() else: pass print("{} updates database name {} during deviceMonitorBehavior successfully".format(agent_id,db_database))

except:

print("ERROR: {} failed to update database name {}".format(agent_id, db_database))

4.4 update sMAP (time-series) database

python

#step3: update sMAP (time-series) database try: self.publish_logdata1() self.publish_logdata2() self.publish_logdata3() self.publish_logdata4() self.publish_logdata5() self.publish_logdata6() self.publish_logdata7() print "{} success update sMAP database".format(agent_id) except: print("ERROR: {} fails to update sMAP database".format(agent_id))

4.5 send notification to building admin/user if the notified conditions is met

python

#step4: send notification to a user if required if self.send_notification: if self.send_notification_status: pass else: if self.get_variable('temperature') >= alert_too_hot: # notify the user if there is something error print('send notification message from {}'.format(agent_id)) self.text = 'Too hot! now the current temperature is {0} F which is exceed {1} F'.format( self.get_variable('temperature'), alert_too_hot) emailService = EmailService() emailService.sendEmail(email_fromaddr, email_recipients, email_username, email_password, self.subject, self.text, email_mailServer) self.send_notification_status = True self.time_send_notification = time.time() elif self.get_variable('temperature') <= alert_too_cold: print('send notification message from {}'.format(agent_id)) self.text = 'Too cold! now the current temperature is {0} F which is below {1} F'.format( self.get_variable('temperature'), alert_too_hot) emailService = EmailService() emailService.sendEmail(email_fromaddr, email_recipients, email_username, email_password, self.subject, self.text, email_mailServer) self.send_notification_status = True self.time_send_notification = time.time()

if time.time() - self.time_send_notification >= time_delay_send_notification:

self.send_notification_status = False

4.6 (if required) printout agent's knowledge and mapping fields for debugging purpose

python

#step5: 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)


Step5: UpdateDeviceStatus Behavior

Screen Shot 2014-08-21 at 3.48.53 PM.png

5.1 declare the UpdateDeviceStatus Behavior to be the GenericBehavior which will be run according to the filtered message with the specific topic defined in @matching.match_exact() decorator

In this example, the topic is "'/ui/agent/'+_topic_Agent_UI+'device_status'" where "_topic_Agent_UI" is defined in the (1) Load Agent Configuration Step

python

#updateUIBehavior (generic behavior) @matching.match_exact('/ui/agent/'+_topic_Agent_UI+'device_status')

Note: for the inter-exchange topic between Applications layer and OS layer, please check the API between APP layer and OS layer

for the inter-exchange topic between agents in the OS layer, please check #TODO add agent communication page

5.2 the main UpdateDeviceStatus behavior that will send back a current device status of an agent's knowledge to any entity (e.g. UI or another agent) that triggers this behavior

python

def updateUIBehavior(self, topic, headers, message, match):

print agent_id + " gotnTopic: {topic}".format(topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}n".format(message=message) #reply message topic = '/agent/ui/'+_topic_Agent_UI+'device_status/response' now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, headers_mod.DATE: now, headers_mod.FROM: agent_id, headers_mod.TO: 'ui' } _data = {'temperature': self.get_variable('temperature'), 'thermostat_mode': self.get_variable('thermostat_mode'), 'fan_mode': self.get_variable('fan_mode'), 'heat_setpoint': self.get_variable('heat_setpoint'), 'cool_setpoint': self.get_variable('cool_setpoint'), 'thermostat_state': self.get_variable('thermostat_state'), 'fan_state': self.get_variable('fan_state') } message = json.dumps(_data) message = message.encode(encoding='utf_8') self.publish(topic, headers, message)


Step6: DeviceControl Behavior

Screen Shot 2014-08-21 at 3.48.40 PM.png

6.1 declare the DeviceControl Behavior to be the GenericBehavior which will be run according to the filtered message with the specific topic defined in @matching.match_exact() decorator

In this example, the topic is "'/ui/agent/'+_topic_Agent_UI+'update'" where "_topic_Agent_UI" is defined in the (1) Load Agent Configuration Step

python

#deviceControlBehavior (generic behavior) @matching.match_exact('/ui/agent/'+_topic_Agent_UI+'update')

Note: for the inter-exchange topic between Applications layer and OS layer, please check the API between APP layer and OS layer

for the inter-exchange topic between agents in the OS layer, please check #TODO add agent communication page

6.2 the main DeviceControl behavior will send a control message to change a current status of a device based on the received message from another entity (e.g. UI or another agent) that triggers this behavior

python

def deviceControlBehavior(self, topic, headers, message, match):

print agent_id + " gotnTopic: {topic}".format(topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}n".format(message=message) #step1: change device status according to the receive message if self.isPostmsgValid(message[0]): # check if the data is valid setDeviceStatusResult = Thermostat.setDeviceStatus(json.loads(message[0])) # convert received message from string to JSON #TODO need to do additional checking whether the device setting is actually success!!!!!!!! #step2: update agent's knowledge on this device Thermostat.getDeviceStatus() #step3: send reply message back to the UI topic = '/agent/ui/'+_topic_Agent_UI+'update/response' now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.PLAIN_TEXT, headers_mod.DATE: now, } if setDeviceStatusResult: message = 'success' else: message = 'failure' else: print("The POST message is invalid, check thermostat_mode, heat_setpoint, cool_coolsetpoint " "setting and try againn") message = 'failure' self.publish(topic, headers, message)

Note: isPostmsgValid() method is called to verify the received message content from another entity (e.g. UI or another agent) before parsing and calling an API interface to actually communicate and control a device.


Step7: IdentifyDevice Behavior

Screen Shot 2014-08-21 at 3.49.01 PM.png

7.1 declare the DeviceIdentify Behavior to be the GenericBehavior which will be run according to the filtered message with the specific topic defined in @matching.match_exact() decorator

In this example, the topic is "'/ui/agent/'+_topic_Agent_UI+'identify'" where "_topic_Agent_UI" is defined in the (1) Load Agent Configuration Step

python

#deviceIdentifyBehavior (generic behavior) @matching.match_exact('/ui/agent/'+_topic_Agent_UI+'identify')

Note: for the inter-exchange topic between Applications layer and OS layer, please check the API between APP layer and OS layer

for the inter-exchange topic between agents in the OS layer, please check agent communication page

7.2 the main DeviceIdentify behavior will call a method in an API Interface to identify a device which is triggered by the received message from another entity (e.g. UI or another agent). The identification process depends on device type, available identification methods (e.g. LED blinking or sound beeping) which are defined in a corresponding API Interface of a particular device.

python

def deviceIdentifyBehavior(self,topic,headers,message,match): print agent_id+ " gotnTopic: {topic}".format(topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}n".format(message=message) #step1: change device status according to the receive message identifyDeviceResult = Thermostat.identifyDevice() #TODO need to do additional checking whether the device setting is actually success!!!!!!!! #step2: send reply message back to the UI topic = '/agent/ui/'+_topic_Agent_UI+'identify/response' now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.PLAIN_TEXT, headers_mod.DATE: now, } if identifyDeviceResult: message = 'success' else: message = 'failure' self.publish(topic, headers, message)


Helper methods for the Control Agent

Helper method 1: set() method to insert or update an agent's knowledge

python

def set_variable(self, k, v): # k=key, v=value self.variables[k] = v

Helper method 2: get() method to retrieve an agent's knowledge

python

def get_variable(self, k): return self.variables.get(k, None)

Helper method 3: getKeyword() method for agent's knowledge mapping

python

def getKeyword(self, APIKeyword):
for k, v in agentknowledge.items():
if APIKeyword in agentknowledge[k]:

return k flag = 1 break

else:

flag = 0 pass

if flag == 0: # if flag still 0 means that a APIKeyword is not in an agent knowledge,

# then add it to agent knowledge

return APIKeyword

Helper method 4: isPostmsgValid() method for checking validity of a post message that will be used by the deviceControl Behavior

python

def isPostmsgValid(self, postmsg): # check validity of postmsg

dataValidity = True try: _data = json.dumps(postmsg) _data = json.loads(_data) for k,v in _data.items(): if k == 'thermostat_mode': if _data.get('thermostat_mode') == "HEAT": for k, v in _data.items(): if k == 'cool_setpoint': dataValidity = False break elif _data.get('thermostat_mode') == "COOL": for k, v in _data.items(): if k == 'heat_setpoint': dataValidity = False break except: dataValidity = True print("dataValidity failed to validate data comes from UI") return dataValidity

Helper method 5: publish_logdata() to send a device current status to the sMAP database by publishing a data to the Information Exchange Bus (IEB) that will be filtered and retrieved by the sMAP driver

python

def publish_logdata1(self): headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } mytime = int(time.time()) content = { "temperature": { "Readings": mytime, float(self.get_variable("temperature")), "Units": "F", "data_type": "double" } } print("{} published temperature to an IEB".format(agent_id)) self.publish(_topic_Agent_sMAP, headers, json.dumps(content))

Reference: Final Agent Code Structure in Python

Screen Shot 2014-08-21 at 5.58.21 PM.png


[Back to Agent Development]

Clone this wiki locally