Skip to content

Commit 0ab787f

Browse files
authored
Merge pull request #9 from myDevicesIoT/development
Development
2 parents 8a7b310 + 4a09a77 commit 0ab787f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2944
-2589
lines changed

README.rst

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
=============
22
Cayenne Agent
33
=============
4-
The Cayenne agent is a full featured client for the `Cayenne IoT project builder <https://mydevices.com>`_. It sends system information as well as sensor and actuator date and responds to actuator messages initiated from the Cayenne dashboard and mobile apps. The Cayenne agent currently supports Rasbian on the Raspberry Pi but it can be extended to support additional Linux flavors and other platforms.
4+
The Cayenne agent is a full featured client for the `Cayenne IoT project builder <https://mydevices.com>`_. It sends system information as well as sensor and actuator data and responds to actuator messages initiated from the Cayenne dashboard and mobile apps. The Cayenne agent currently supports Rasbian on the Raspberry Pi but it can be extended to support additional Linux flavors and other platforms.
55

66
************
77
Requirements
88
************
9-
* `Python 3 <https://www.python.org/downloads/>`_.
9+
* `Python 3.3 or newer <https://www.python.org/downloads/>`_.
1010
* pip3 - Python 3 package manager. This should already be available in Python 3.4+ and above. If it isn't in can be installed using the system package manager. Via `apt-get` this would be:
1111
::
1212

@@ -127,9 +127,9 @@ To verify that the sensor/actuator works correctly you can test it with the foll
127127
* Create a new sensor using ``myDevices.sensors.SensorsClient.AddSensor`` using the appropriate device name and any args required by your device.
128128
* Get the sensor values using ``myDevices.sensors.SensorsClient.SensorsInfo`` and make sure the sensor data is correct.
129129
* If the new device is an actuator set the actuator value using ``myDevices.sensors.SensorsClient.SensorCommand``.
130-
* Delete the sensor using ``myDevices.sensors.SensorsClient.DeleteSensor``.
130+
* Delete the sensor using ``myDevices.sensors.SensorsClient.RemoveSensor``.
131131

132-
An example demonstrating these functions is available in ``myDevices.test.client_test.py``.
132+
An example demonstrating these functions is available in ``myDevices.test.sensors_test.py``.
133133

134134
*Note:* For security reasons the Cayenne agent is designed to be able to run from an account without root privileges. If any of your sensor/actuator code requires root access consider running just that portion of your code via a separate process that can be launched using sudo. For example, the ``myDevices.devices.digital.ds2408`` module uses this method to write data.
135135

@@ -164,19 +164,19 @@ System info
164164
Information about the device, including CPU, RAM, etc., is currently retrieved via a few different modules. To support a different board you may need to update the agent code for the following items, if applicable:
165165

166166
General System Info
167-
General system info, including CPU, RAM, memory, etc. is retrieved via ``myDevices.os.systeminfo.py`` and ``myDevices.os.cpu.py``. These are mostly implemented using cross platform libraries so they may already provide support for your board. If not, they should be modified or overridden to provide the appropriate system info. If your board does not support all the data values currently implemented you can just provide default values where necessary, though this may affect the data display in the Cayenne dashboard.
167+
General system info, including CPU, RAM, memory, etc. is retrieved via ``myDevices.system.systeminfo.py`` and ``myDevices.system.cpu.py``. These are mostly implemented using cross platform libraries so they may already provide support for your board. If not, they should be modified or overridden to provide the appropriate system info. If your board does not support all the data values currently implemented you can just provide default values where necessary, though this may affect the data display in the Cayenne dashboard.
168168

169169
Hardware Info
170-
Hardware info, including make, model, etc. is retrieved via ``myDevices.os.hardware.py``. This should be modified or overridden to provide the appropriate hardware info for your board.
170+
Hardware info, including make, model, etc. is retrieved via ``myDevices.system.hardware.py``. This should be modified or overridden to provide the appropriate hardware info for your board.
171171

172172
Pin Mapping
173-
The mapping of the on-board pins is provided in ``myDevices.utils.version.py`` with the ``MAPPING`` list. This list provides the available GPIO pin numbers as well as the voltage ("V33", "V50"), ground ("GND") and do-not-connect ("DNC") pins. This should be updated with the mapping for your board. However, the Cayenne dashboard is currently built to display the Raspberry Pi GPIO layout so if your board's pin layout is significantly different it may not display correctly in the GPIO tab.
173+
The mapping of the on-board pins is provided in ``myDevices.devices.digital.gpio.py`` with the ``MAPPING`` list. This list provides the available GPIO pin numbers as well as the voltage ("V33", "V50"), ground ("GND") and do-not-connect ("DNC") pins. This should be updated with the mapping for your board. However, the Cayenne dashboard is currently built to display the Raspberry Pi GPIO layout so if your board's pin layout is significantly different it may not display correctly in the GPIO tab.
174174

175175
Settings
176176
--------
177-
Currently the Raspberry Pi agent has settings for enabling/disabling the device tree, SPI, I²C, serial and camera. These are set via the ``myDevices.os.raspiconfig`` module which runs a separate Bash script at ``/etc/myDevices/scripts/config.sh``. If any of these settings are available on your board and you would like to support them you can override or replace ``myDevices.os.raspiconfig.py``. Otherwise the settings functionality can be ignored.
177+
Currently the Raspberry Pi agent has settings for enabling/disabling the device tree, SPI, I²C, serial and camera. These are set via the ``myDevices.system.raspiconfig`` module which runs a separate Bash script at ``/etc/myDevices/scripts/config.sh``. If any of these settings are available on your board and you would like to support them you can override or replace ``myDevices.system.raspiconfig.py``. Otherwise the settings functionality can be ignored.
178178

179-
*Note:* For security reasons the Cayenne agent is designed to be able to run from an account without root privileges. If any of your I/O, system info or settings code requires root access consider running it via a separate process that can be launched using sudo. For example, the ``myDevices.os.raspiconfig`` module uses this method to update config settings.
179+
*Note:* For security reasons the Cayenne agent is designed to be able to run from an account without root privileges. If any of your I/O, system info or settings code requires root access consider running it via a separate process that can be launched using sudo. For example, the ``myDevices.system.raspiconfig`` module uses this method to update config settings.
180180

181181
************
182182
Contributing

myDevices/__init__.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
from time import sleep
2-
3-
try:
4-
import ipgetter
5-
except:
6-
pass
7-
1+
"""
2+
This package contains the Cayenne agent, which is a full featured client for the Cayenne IoT project builder: https://cayenne.mydevices.com. It sends system information as well as sensor and actuator data and responds to actuator messages initiated from the Cayenne dashboard and mobile apps.
3+
"""
4+
__version__ = '2.0.0'

myDevices/__main__.py

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,48 @@
1-
from myDevices.utils.config import Config
1+
"""
2+
This module is the main entry point for the Cayenne agent. It processes any command line parameters and launches the client.
3+
"""
24
from os import path, getpid, remove
5+
from sys import __excepthook__, argv, maxsize
6+
from threading import Thread
7+
from myDevices.utils.config import Config
38
from myDevices.cloud.client import CloudServerClient
49
from myDevices.utils.logger import exception, setDebug, info, debug, error, logToFile, setInfo
5-
from sys import excepthook, __excepthook__, argv, maxsize
6-
from threading import Thread
710
from signal import signal, SIGUSR1, SIGINT
811
from resource import getrlimit, setrlimit, RLIMIT_AS
9-
from myDevices.os.services import ProcessInfo
10-
from myDevices.os.daemon import Daemon
12+
from myDevices.system.services import ProcessInfo
13+
from myDevices.utils.daemon import Daemon
1114

12-
def setMemoryLimit(rsrc, megs = 200):
15+
def setMemoryLimit(rsrc, megs=200):
16+
"""Set the memory usage limit for the agent process"""
1317
size = megs * 1048576
1418
soft, hard = getrlimit(rsrc)
15-
setrlimit(rsrc, (size, hard)) #limit to one kilobyte
16-
soft, hard = getrlimit(rsrc)
17-
info ('Limit changed to :'+ str( soft))
19+
setrlimit(rsrc, (size, hard))
20+
1821
try:
1922
#Only set memory limit on 32-bit systems
2023
if maxsize <= 2**32:
2124
setMemoryLimit(RLIMIT_AS)
22-
except Exception as e:
23-
error('Cannot set limit to memory: ' + str(e))
25+
except Exception as ex:
26+
print('Cannot set limit to memory: ' + str(ex))
2427

2528
client = None
2629
pidfile = '/var/run/myDevices/cayenne.pid'
2730
def signal_handler(signal, frame):
28-
if client:
31+
"""Handle program interrupt so the agent can exit cleanly"""
32+
if client and client.connected:
2933
if signal == SIGINT:
3034
info('Program interrupt received, client exiting')
3135
client.Destroy()
3236
remove(pidfile)
3337
else:
3438
client.Restart()
39+
elif signal == SIGINT:
40+
remove(pidfile)
41+
raise SystemExit
3542
signal(SIGUSR1, signal_handler)
3643
signal(SIGINT, signal_handler)
3744

45+
3846
def exceptionHook(exc_type, exc_value, exc_traceback):
3947
"""Make sure any uncaught exceptions are logged"""
4048
debug('Daemon::exceptionHook ')
@@ -46,7 +54,7 @@ def exceptionHook(exc_type, exc_value, exc_traceback):
4654

4755
def threadExceptionHook():
4856
"""Make sure any child threads hook exceptions. This should be called before any threads are created."""
49-
debug('Daemon::threadExceptionHook ')
57+
debug('Daemon::threadExceptionHook')
5058
init_original = Thread.__init__
5159
def init(self, *args, **kwargs):
5260
init_original(self, *args, **kwargs)
@@ -79,51 +87,50 @@ def displayHelp():
7987
exit()
8088

8189
def writePidToFile(pidfile):
90+
"""Write the process ID to a file to prevent multiple agents from running at the same time"""
8291
if path.isfile(pidfile):
8392
info(pidfile + " already exists, exiting")
8493
with open(pidfile, 'r') as file:
8594
pid = int(file.read())
8695
if ProcessInfo.IsRunning(pid) and pid != getpid():
87-
Daemon.Exit()
88-
return
96+
raise SystemExit
8997
pid = str(getpid())
9098
with open(pidfile, 'w') as file:
9199
file.write(pid)
100+
92101
def main(argv):
102+
"""Main entry point for starting the agent client"""
93103
global pidfile
94104
configfile = None
95-
scriptfile = None
96105
logfile = None
97-
isDebug = False
98106
i = 1
99107
setInfo()
100108
while i < len(argv):
101109
if argv[i] in ["-c", "-C", "--config-file"]:
102110
configfile = argv[i+1]
103-
i+=1
111+
i += 1
104112
elif argv[i] in ["-l", "-L", "--log-file"]:
105113
logfile = argv[i+1]
106-
i+=1
114+
i += 1
107115
elif argv[i] in ["-h", "-H", "--help"]:
108116
displayHelp()
109117
elif argv[i] in ["-d", "--debug"]:
110118
setDebug()
111119
elif argv[i] in ["-P", "--pidfile"]:
112120
pidfile = argv[i+1]
113-
i+=1
114-
i+=1
121+
i += 1
122+
i += 1
115123
if configfile == None:
116124
configfile = '/etc/myDevices/Network.ini'
117125
writePidToFile(pidfile)
118126
logToFile(logfile)
119-
# SET HOST AND PORT
120127
config = Config(configfile)
121-
HOST = config.get('CONFIG','ServerAddress', 'cloud.mydevices.com')
122-
PORT = config.getInt('CONFIG','ServerPort', 8181)
128+
HOST = config.get('CONFIG', 'ServerAddress', 'mqtt.mydevices.com')
129+
PORT = config.getInt('CONFIG', 'ServerPort', 8883)
123130
CayenneApiHost = config.get('CONFIG', 'CayenneApi', 'https://api.mydevices.com')
124-
# CREATE SOCKET
125-
global client
131+
global client
126132
client = CloudServerClient(HOST, PORT, CayenneApiHost)
133+
client.Start()
127134

128135
if __name__ == "__main__":
129136
try:

myDevices/cloud/apiclient.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
from concurrent.futures import ThreadPoolExecutor
33
import json
44
from myDevices.utils.logger import error, exception
5+
from myDevices.system.hardware import Hardware
6+
from myDevices.system.systeminfo import SystemInfo
7+
from myDevices.cloud import cayennemqtt
8+
from myDevices.devices.digital.gpio import NativeGPIO
59

610
class CayenneApiClient:
711
def __init__(self, host):
@@ -36,31 +40,55 @@ def sendRequest(self, method, uri, body=None):
3640
return None
3741
return response
3842
exception("No data received")
43+
44+
def getMessageBody(self, inviteCode):
45+
body = {'id': inviteCode}
46+
hardware = Hardware()
47+
if hardware.Serial and hardware.isRaspberryPi():
48+
body['type'] = 'rpi'
49+
body['hardware_id'] = hardware.Serial
50+
else:
51+
hardware_id = hardware.getMac()
52+
if hardware_id:
53+
body['type'] = 'mac'
54+
body['hardware_id'] = hardware_id
55+
try:
56+
system_data = []
57+
cayennemqtt.DataChannel.add(system_data, cayennemqtt.SYS_HARDWARE_MAKE, value=hardware.getManufacturer(), type='string', unit='utf8')
58+
cayennemqtt.DataChannel.add(system_data, cayennemqtt.SYS_HARDWARE_MODEL, value=hardware.getModel(), type='string', unit='utf8')
59+
system_info = SystemInfo()
60+
capacity_data = system_info.getMemoryInfo((cayennemqtt.CAPACITY,))
61+
capacity_data += system_info.getDiskInfo((cayennemqtt.CAPACITY,))
62+
for item in capacity_data:
63+
system_data.append(item)
64+
body['properties'] = {}
65+
body['properties']['pinmap'] = NativeGPIO().MAPPING
66+
if system_data:
67+
body['properties']['sysinfo'] = system_data
68+
except:
69+
exception('Error getting system info')
70+
return json.dumps(body)
3971

4072
def authenticate(self, inviteCode):
41-
body = json.dumps({'id': inviteCode})
73+
body = self.getMessageBody(inviteCode)
4274
url = '/things/key/authenticate'
4375
return self.sendRequest('POST', url, body)
4476

4577
def activate(self, inviteCode):
46-
body = json.dumps({'id': inviteCode})
78+
body = self.getMessageBody(inviteCode)
4779
url = '/things/key/activate'
4880
return self.sendRequest('POST', url, body)
4981

50-
def getId(self, content):
82+
def getCredentials(self, content):
5183
if content is None:
5284
return None
5385
body = content.decode("utf-8")
5486
if body is None or body is "":
5587
return None
56-
return json.loads(body)['id']
88+
return json.loads(body)
5789

5890
def loginDevice(self, inviteCode):
59-
response = self.authenticate(inviteCode)
91+
response = self.activate(inviteCode)
6092
if response and response.status_code == 200:
61-
return self.getId(response.content)
62-
if not response or response.status_code == 412:
63-
response = self.activate(inviteCode)
64-
if response and response.status_code == 200:
65-
return self.getId(response.content)
93+
return self.getCredentials(response.content)
6694
return None

0 commit comments

Comments
 (0)