Skip to content

Commit 2f3430f

Browse files
committed
Merge branch 'development' of https://github.com/myDevicesIoT/Cayenne-Agent into development
2 parents e115350 + 00d3912 commit 2f3430f

File tree

17 files changed

+692
-998
lines changed

17 files changed

+692
-998
lines changed

myDevices/cloud/apiclient.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
from myDevices.requests_futures.sessions import FuturesSession
2-
from concurrent.futures import ThreadPoolExecutor
31
import json
4-
from myDevices.utils.logger import error, exception
5-
from myDevices.system.hardware import Hardware
6-
from myDevices.system.systeminfo import SystemInfo
2+
from concurrent.futures import ThreadPoolExecutor
3+
4+
from myDevices import __version__
75
from myDevices.cloud import cayennemqtt
86
from myDevices.devices.digital.gpio import NativeGPIO
7+
from myDevices.requests_futures.sessions import FuturesSession
8+
from myDevices.system.hardware import Hardware
9+
from myDevices.system.systeminfo import SystemInfo
10+
from myDevices.utils.config import Config, APP_SETTINGS
11+
from myDevices.utils.logger import error, exception
12+
913

1014
class CayenneApiClient:
1115
def __init__(self, host):
@@ -56,6 +60,8 @@ def getMessageBody(self, inviteCode):
5660
system_data = []
5761
cayennemqtt.DataChannel.add(system_data, cayennemqtt.SYS_HARDWARE_MAKE, value=hardware.getManufacturer(), type='string', unit='utf8')
5862
cayennemqtt.DataChannel.add(system_data, cayennemqtt.SYS_HARDWARE_MODEL, value=hardware.getModel(), type='string', unit='utf8')
63+
config = Config(APP_SETTINGS)
64+
cayennemqtt.DataChannel.add(system_data, cayennemqtt.AGENT_VERSION, value=config.get('Agent', 'Version', __version__))
5965
system_info = SystemInfo()
6066
capacity_data = system_info.getMemoryInfo((cayennemqtt.CAPACITY,))
6167
capacity_data += system_info.getDiskInfo((cayennemqtt.CAPACITY,))

myDevices/cloud/cayennemqtt.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
COMMAND_TOPIC = 'cmd'
1010
COMMAND_JSON_TOPIC = 'cmd.json'
1111
COMMAND_RESPONSE_TOPIC = 'response'
12+
JOBS_TOPIC = 'jobs.json'
1213

1314
# Data Channels
1415
SYS_HARDWARE_MAKE = 'sys:hw:make'
@@ -30,6 +31,7 @@
3031
AGENT_VERSION = 'agent:version'
3132
AGENT_DEVICES = 'agent:devices'
3233
AGENT_MANAGE = 'agent:manage'
34+
AGENT_SCHEDULER = 'agent:scheduler'
3335
DEV_SENSOR = 'dev'
3436

3537
# Channel Suffixes
@@ -68,7 +70,7 @@ def add(data_list, prefix, channel=None, suffix=None, value=None, type=None, uni
6870
class CayenneMQTTClient:
6971
"""Cayenne MQTT Client class.
7072
71-
This is the main client class for connecting to Cayenne and sending and recFUeiving data.
73+
This is the main client class for connecting to Cayenne and sending and receiving data.
7274
7375
Standard usage:
7476
* Set on_message callback, if you are receiving data.
@@ -150,6 +152,26 @@ def disconnect_callback(self, client, userdata, rc):
150152
print("Reconnect failed, retrying")
151153
time.sleep(5)
152154

155+
def transform_command(self, command, payload=[], channel=[]):
156+
"""Transform a command message into an object.
157+
158+
command is the command object that will be transformed in place.
159+
payload is an optional list of payload data items.
160+
channel is an optional list containing channel and suffix data.
161+
"""
162+
if not payload:
163+
command['payload'] = command.pop('value')
164+
channel = command['channel'].split('/')[-1].split(';')
165+
else:
166+
if len(payload) > 1:
167+
command['cmdId'] = payload[0]
168+
command['payload'] = payload[1]
169+
else:
170+
command['payload'] = payload[0]
171+
command['channel'] = channel[0]
172+
if len(channel) > 1:
173+
command['suffix'] = channel[1]
174+
153175
def message_callback(self, client, userdata, msg):
154176
"""The callback for when a message is received from the server.
155177
@@ -160,21 +182,10 @@ def message_callback(self, client, userdata, msg):
160182
try:
161183
message = {}
162184
if msg.topic[-len(COMMAND_JSON_TOPIC):] == COMMAND_JSON_TOPIC:
163-
payload = loads(msg.payload.decode())
164-
message['payload'] = payload['value']
165-
message['cmdId'] = payload['cmdId']
166-
channel = payload['channel'].split('/')[-1].split(';')
185+
message = loads(msg.payload.decode())
186+
self.transform_command(message)
167187
else:
168-
payload = msg.payload.decode().split(',')
169-
if len(payload) > 1:
170-
message['cmdId'] = payload[0]
171-
message['payload'] = payload[1]
172-
else:
173-
message['payload'] = payload[0]
174-
channel = msg.topic.split('/')[-1].split(';')
175-
message['channel'] = channel[0]
176-
if len(channel) > 1:
177-
message['suffix'] = channel[1]
188+
self.transform_command(message, msg.payload.decode().split(','), msg.topic.split('/')[-1].split(';'))
178189
debug('message_callback: {}'.format(message))
179190
if self.on_message:
180191
self.on_message(message)

myDevices/cloud/client.py

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from myDevices.utils.logger import exception, info, warn, error, debug, logJson
1414
from myDevices.sensors import sensors
1515
from myDevices.system.hardware import Hardware
16-
# from myDevices.cloud.scheduler import SchedulerEngine
16+
from myDevices.cloud.scheduler import SchedulerEngine
1717
from myDevices.cloud.download_speed import DownloadSpeed
1818
from myDevices.cloud.updater import Updater
1919
from myDevices.system.systemconfig import SystemConfig
@@ -115,7 +115,7 @@ def run(self):
115115
if topic or message:
116116
got_packet = True
117117
try:
118-
if message:
118+
if message or topic == cayennemqtt.JOBS_TOPIC:
119119
# debug('WriterThread, topic: {} {}'.format(topic, message))
120120
if not isinstance(message, str):
121121
message = dumps(message)
@@ -195,7 +195,7 @@ def Start(self):
195195
if not self.Connect():
196196
error('Error starting agent')
197197
return
198-
# self.schedulerEngine = SchedulerEngine(self, 'client_scheduler')
198+
self.schedulerEngine = SchedulerEngine(self, 'client_scheduler')
199199
self.sensorsClient = sensors.SensorsClient()
200200
self.readQueue = Queue()
201201
self.writeQueue = Queue()
@@ -214,6 +214,8 @@ def Start(self):
214214
TimerThread(self.SendSystemState, 30, 5)
215215
self.updater = Updater(self.config)
216216
self.updater.start()
217+
events = self.schedulerEngine.get_scheduled_events()
218+
self.EnqueuePacket(events, cayennemqtt.JOBS_TOPIC)
217219
# self.sentHistoryData = {}
218220
# self.historySendFails = 0
219221
# self.historyThread = Thread(target=self.SendHistoryData)
@@ -367,8 +369,12 @@ def OnMessage(self, message):
367369

368370
def RunAction(self, action):
369371
"""Run a specified action"""
370-
debug('RunAction')
371-
self.ExecuteMessage(action)
372+
debug('RunAction: {}'.format(action))
373+
result = True
374+
command = action.copy()
375+
self.mqttClient.transform_command(command)
376+
result = self.ExecuteMessage(command)
377+
return result
372378

373379
def ProcessMessage(self):
374380
"""Process a message from the server"""
@@ -381,28 +387,36 @@ def ProcessMessage(self):
381387
self.ExecuteMessage(messageObject)
382388

383389
def ExecuteMessage(self, message):
384-
"""Execute an action described in a message object"""
390+
"""Execute an action described in a message object
391+
392+
Returns: True if action was executed, False otherwise."""
393+
result = False
385394
if not message:
386-
return
395+
return result
387396
channel = message['channel']
388397
info('ExecuteMessage: {}'.format(message))
389398
if channel in (cayennemqtt.SYS_POWER_RESET, cayennemqtt.SYS_POWER_HALT):
390-
self.ProcessPowerCommand(message)
399+
result = self.ProcessPowerCommand(message)
391400
elif channel.startswith(cayennemqtt.DEV_SENSOR):
392-
self.ProcessSensorCommand(message)
401+
result = self.ProcessSensorCommand(message)
393402
elif channel.startswith(cayennemqtt.SYS_GPIO):
394-
self.ProcessGpioCommand(message)
403+
result = self.ProcessGpioCommand(message)
395404
elif channel == cayennemqtt.AGENT_DEVICES:
396-
self.ProcessDeviceCommand(message)
405+
result = self.ProcessDeviceCommand(message)
397406
elif channel in (cayennemqtt.SYS_I2C, cayennemqtt.SYS_SPI, cayennemqtt.SYS_UART, cayennemqtt.SYS_ONEWIRE):
398-
self.ProcessConfigCommand(message)
407+
result = self.ProcessConfigCommand(message)
399408
elif channel == cayennemqtt.AGENT_MANAGE:
400-
self.ProcessAgentCommand(message)
409+
result = self.ProcessAgentCommand(message)
410+
elif channel == cayennemqtt.AGENT_SCHEDULER:
411+
result = self.ProcessSchedulerCommand(message)
401412
else:
402413
info('Unknown message')
414+
return result
403415

404416
def ProcessPowerCommand(self, message):
405-
"""Process command to reboot/shutdown the system"""
417+
"""Process command to reboot/shutdown the system
418+
419+
Returns: True if command was processed, False otherwise."""
406420
error_message = None
407421
try:
408422
self.EnqueueCommandResponse(message, error_message)
@@ -425,9 +439,13 @@ def ProcessPowerCommand(self, message):
425439
data = []
426440
cayennemqtt.DataChannel.add(data, message['channel'], value=0)
427441
self.EnqueuePacket(data)
442+
raise ExecuteMessageError(error_message)
443+
return error_message == None
428444

429445
def ProcessAgentCommand(self, message):
430-
"""Process command to manage the agent state"""
446+
"""Process command to manage the agent state
447+
448+
Returns: True if command was processed, False otherwise."""
431449
error = None
432450
try:
433451
if message['suffix'] == 'uninstall':
@@ -448,9 +466,14 @@ def ProcessAgentCommand(self, message):
448466
except Exception as ex:
449467
error = '{}: {}'.format(type(ex).__name__, ex)
450468
self.EnqueueCommandResponse(message, error)
469+
if error:
470+
raise ExecuteMessageError(error)
471+
return error == None
451472

452473
def ProcessConfigCommand(self, message):
453-
"""Process system configuration command"""
474+
"""Process system configuration command
475+
476+
Returns: True if command was processed, False otherwise."""
454477
error = None
455478
try:
456479
value = 1 - int(message['payload']) #Invert the value since the config script uses 0 for enable and 1 for disable
@@ -462,9 +485,12 @@ def ProcessConfigCommand(self, message):
462485
except Exception as ex:
463486
error = '{}: {}'.format(type(ex).__name__, ex)
464487
self.EnqueueCommandResponse(message, error)
465-
488+
return error == None
489+
466490
def ProcessGpioCommand(self, message):
467-
"""Process GPIO command"""
491+
"""Process GPIO command
492+
493+
Returns: True if command was processed, False otherwise."""
468494
error = None
469495
try:
470496
channel = int(message['channel'].replace(cayennemqtt.SYS_GPIO + ':', ''))
@@ -475,9 +501,12 @@ def ProcessGpioCommand(self, message):
475501
except Exception as ex:
476502
error = '{}: {}'.format(type(ex).__name__, ex)
477503
self.EnqueueCommandResponse(message, error)
504+
return error == None
478505

479506
def ProcessSensorCommand(self, message):
480-
"""Process sensor command"""
507+
"""Process sensor command
508+
509+
Returns: True if command was processed, False otherwise."""
481510
error = None
482511
try:
483512
sensor_info = message['channel'].replace(cayennemqtt.DEV_SENSOR + ':', '').split(':')
@@ -492,9 +521,12 @@ def ProcessSensorCommand(self, message):
492521
except Exception as ex:
493522
error = '{}: {}'.format(type(ex).__name__, ex)
494523
self.EnqueueCommandResponse(message, error)
524+
return error == None
495525

496526
def ProcessDeviceCommand(self, message):
497-
"""Process a device command to add/edit/remove a sensor"""
527+
"""Process a device command to add/edit/remove a sensor
528+
529+
Returns: True if command was processed, False otherwise."""
498530
error = None
499531
try:
500532
payload = message['payload']
@@ -513,9 +545,38 @@ def ProcessDeviceCommand(self, message):
513545
except Exception as ex:
514546
error = '{}: {}'.format(type(ex).__name__, ex)
515547
self.EnqueueCommandResponse(message, error)
548+
return error == None
549+
550+
def ProcessSchedulerCommand(self, message):
551+
"""Process command to add/edit/remove a scheduled action
552+
553+
Returns: True if command was processed, False otherwise."""
554+
error = None
555+
try:
556+
if message['suffix'] == 'add':
557+
result = self.schedulerEngine.add_scheduled_event(message['payload'], True)
558+
elif message['suffix'] == 'edit':
559+
result = self.schedulerEngine.update_scheduled_event(message['payload'])
560+
elif message['suffix'] == 'delete':
561+
result = self.schedulerEngine.remove_scheduled_event(message['payload'])
562+
elif message['suffix'] == 'get':
563+
events = self.schedulerEngine.get_scheduled_events()
564+
self.EnqueuePacket(events, cayennemqtt.JOBS_TOPIC)
565+
else:
566+
error = 'Unknown schedule command: {}'.format(message['suffix'])
567+
debug('ProcessSchedulerCommand result: {}'.format(result))
568+
if result is False:
569+
error = 'Schedule command failed'
570+
except Exception as ex:
571+
error = '{}: {}'.format(type(ex).__name__, ex)
572+
self.EnqueueCommandResponse(message, error)
573+
return error == None
516574

517575
def EnqueueCommandResponse(self, message, error):
518576
"""Send response after processing a command message"""
577+
if not hasattr(message, 'cmdId'):
578+
# If there is no command idea we assume this is a scheduled command and don't send a response message.
579+
return
519580
debug('EnqueueCommandResponse error: {}'.format(error))
520581
if error:
521582
response = 'error,{}={}'.format(message['cmdId'], error)

myDevices/cloud/dbmanager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,6 @@ def test():
112112
cursor = connection.cursor()
113113
except Exception as ex:
114114
error('DbManager failed to initialize: ' + str(ex))
115-
DbManager.CreateTable('scheduled_settings', "id TEXT PRIMARY KEY, data TEXT", ['id', 'data'])
115+
# DbManager.CreateTable('scheduled_events', "id TEXT PRIMARY KEY, data TEXT", ['id', 'data'])
116116
DbManager.CreateTable('disabled_sensors', "id TEXT PRIMARY KEY", ['id'])
117117
DbManager.CreateTable('historical_averages', "id INTEGER PRIMARY KEY, data TEXT, count INTEGER, start TIMESTAMP, end TIMESTAMP, interval TEXT, send TEXT, count_sensor TEXT", ['id', 'data', 'count', 'start', 'end', 'interval', 'send', 'count_sensor'])

0 commit comments

Comments
 (0)