Skip to content

Commit ff92513

Browse files
author
Mike Stegeman
committed
Initial commit of bindings.
1 parent 6feb874 commit ff92513

File tree

13 files changed

+645
-2
lines changed

13 files changed

+645
-2
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.egg
2+
*.egg-info/
3+
*.py[cod]
4+
*.swp
5+
*~
6+
__pycache__/
7+
build/
8+
dist/
File renamed without changes.

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include LICENSE.txt

README.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

README.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
gateway_addon
2+
=============
3+
4+
Python bindings for developing Python add-ons for Mozilla IoT Gateway.

gateway_addon/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""This module provides a high-level interface for creating Gateway add-ons."""
2+
# flake8: noqa
3+
from .adapter import Adapter
4+
from .addon_manager_proxy import AddonManagerProxy
5+
from .device import Device
6+
from .ipc import IpcClient
7+
from .property import Property

gateway_addon/adapter.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"""High-level Adapter base class implementation."""
2+
3+
from .addon_manager_proxy import AddonManagerProxy
4+
from .ipc import IpcClient
5+
6+
7+
class Adapter:
8+
"""An Adapter represents a way of communicating with a set of devices."""
9+
10+
def __init__(self, _id, package_name, verbose=False):
11+
"""
12+
Initialize the object.
13+
14+
As part of initialization, a connection is established between the
15+
adapter and the Gateway via nanomsg IPC.
16+
17+
_id -- the adapter's individual ID
18+
package_name -- the adapter's package name
19+
verbose -- whether or not to enable verbose logging
20+
"""
21+
self.id = _id
22+
self.package_name = package_name
23+
self.devices = {}
24+
self.actions = {}
25+
26+
# We assume that the adapter is ready right away. If, for some reason,
27+
# a particular adapter needs some time, then it should set ready to
28+
# False in it's constructor.
29+
self.ready = True
30+
31+
self.ipc_client = IpcClient(self.id, verbose=verbose)
32+
self.manager_proxy = AddonManagerProxy(self.ipc_client.plugin_socket,
33+
self.id,
34+
verbose=verbose)
35+
self.manager_proxy.add_adapter(self)
36+
37+
def dump(self):
38+
"""Dump the state of the adapter to the log."""
39+
print('Adapter:', self.name, '- dump() not implemented')
40+
41+
def get_id(self):
42+
"""
43+
Get the ID of the adapter.
44+
45+
Returns the ID as a string.
46+
"""
47+
return self.id
48+
49+
def get_package_name(self):
50+
"""
51+
Get the package name of the adapter.
52+
53+
Returns the package name as a string.
54+
"""
55+
return self.package_name
56+
57+
def get_device(self, device_id):
58+
"""
59+
Get the device with the given ID.
60+
61+
device_id -- ID of device to retrieve
62+
63+
Returns a Device object, if found, else None.
64+
"""
65+
return self.devices.get(device_id, None)
66+
67+
def get_devices(self):
68+
"""
69+
Get all the devices managed by this adapter.
70+
71+
Returns a dictionary of device_id -> Device.
72+
"""
73+
return self.devices
74+
75+
def get_name(self):
76+
"""
77+
Get the name of this adapter.
78+
79+
Returns the name as a string.
80+
"""
81+
return self.name
82+
83+
def is_ready(self):
84+
"""
85+
Get the ready state of this adapter.
86+
87+
Returns the ready state as a boolean.
88+
"""
89+
return self.ready
90+
91+
def as_dict(self):
92+
"""
93+
Get the adapter state as a dictionary.
94+
95+
Returns the state as a dictionary.
96+
"""
97+
return {
98+
'id': self.id,
99+
'name': self.name,
100+
'ready': self.ready,
101+
}
102+
103+
def handle_device_added(self, device):
104+
"""
105+
Notify the Gateway that a new device is being managed by this adapter.
106+
107+
device -- Device object
108+
"""
109+
self.devices[device.id] = device
110+
self.manager_proxy.handle_device_added(device)
111+
112+
def handle_device_removed(self, device):
113+
"""
114+
Notify the Gateway that a device has been removed.
115+
116+
device -- Device object
117+
"""
118+
if device.id in self.devices:
119+
del self.devices[device.id]
120+
121+
self.manager_proxy.handle_device_removed(device)
122+
123+
def start_pairing(self, timeout):
124+
"""
125+
Start the pairing process.
126+
127+
timeout -- Timeout in seconds at which to quit pairing
128+
"""
129+
print('Adapter:', self.name, 'id', self.id, 'pairing started')
130+
131+
def cancel_pairing(self):
132+
"""Cancel the pairing process."""
133+
print('Adapter:', self.name, 'id', self.id, 'pairing cancelled')
134+
135+
def remove_thing(self, device_id):
136+
"""
137+
Unpair a device with the adapter.
138+
139+
device_id -- ID of device to unpair
140+
"""
141+
device = self.get_device(device_id)
142+
if device:
143+
print('Adapter:', self.name, 'id', self.id,
144+
'remove_thing(' + device.id + ')')
145+
146+
def cancel_remove_thing(self, device_id):
147+
"""
148+
Cancel unpairing of a device.
149+
150+
device_id -- ID of device to cancel unpairing with
151+
"""
152+
device = self.get_device(device_id)
153+
if device:
154+
print('Adapter:', self.name, 'id', self.id,
155+
'cancel_remove_thing(' + device.id + ')')
156+
157+
def unload(self):
158+
"""Perform any necessary cleanup before adapter is shut down."""
159+
print('Adapter:', self.name, 'unloaded')
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
"""Proxy for sending messages between the Gateway and an add-on."""
2+
3+
import json
4+
import threading
5+
6+
7+
class AddonManagerProxy:
8+
"""
9+
Proxy for communicating with the Gateway's AddonManager.
10+
11+
This proxy interprets all of the required incoming message types that need
12+
to be handled by add-ons and sends back responses as appropriate.
13+
"""
14+
15+
def __init__(self, socket, plugin_id, verbose=False):
16+
"""
17+
Initialize the object.
18+
19+
socket -- the IPC socket
20+
plugin_id -- ID of this plugin
21+
verbose -- whether or not to enable verbose logging
22+
"""
23+
self.adapter = None
24+
self.socket = socket
25+
self.plugin_id = plugin_id
26+
self.verbose = verbose
27+
self.running = True
28+
self.thread = threading.Thread(target=self.recv)
29+
self.thread.start()
30+
31+
def add_adapter(self, adapter):
32+
"""
33+
Send a notification that an adapter has been added.
34+
35+
adapter -- the Adapter that was added
36+
"""
37+
if self.verbose:
38+
print('AddonManagerProxy: add_adapter:', adapter.id)
39+
40+
self.adapter = adapter
41+
self.send('addAdapter', {
42+
'adapterId': adapter.id,
43+
'name': adapter.name
44+
})
45+
46+
def handle_device_added(self, device):
47+
"""
48+
Send a notification that a new device has been added.
49+
50+
device -- the Device that was added
51+
"""
52+
if self.verbose:
53+
print('AddonManagerProxy: handle_device_added:', device.id)
54+
55+
device_dict = device.as_dict()
56+
device_dict['adapterId'] = device.adapter.id
57+
self.send('handleDeviceAdded', device_dict)
58+
59+
def handle_device_removed(self, device):
60+
"""
61+
Send a notification that a managed device was removed.
62+
63+
device -- the Device that was removed
64+
"""
65+
if self.verbose:
66+
print('AddonManagerProxy: handle_device_removed:', device.id)
67+
68+
self.send('handleDeviceRemoved', {
69+
'adapterId': device.adapter.id,
70+
'id': device.id,
71+
})
72+
73+
def send_property_changed_notification(self, prop):
74+
"""
75+
Send a notification that a device property changed.
76+
77+
prop -- the Property that changed
78+
"""
79+
self.send('propertyChanged', {
80+
'adapterId': prop.device.adapter.id,
81+
'deviceId': prop.device.id,
82+
'property': prop.as_dict(),
83+
})
84+
85+
def send(self, msg_type, data):
86+
"""
87+
Send a message through the IPC socket.
88+
89+
msg_type -- the message type
90+
data -- the data to send, as a dictionary
91+
"""
92+
if data is None:
93+
data = {}
94+
95+
data['pluginId'] = self.plugin_id
96+
97+
self.socket.send(json.dumps({
98+
'messageType': msg_type,
99+
'data': data,
100+
}))
101+
102+
def recv(self):
103+
"""Read a message from the IPC socket."""
104+
while self.running:
105+
msg = self.socket.recv()
106+
107+
if self.verbose:
108+
print('AddonMangerProxy: recv:', msg)
109+
110+
if not msg:
111+
break
112+
113+
if not self.adapter:
114+
print('AddonManagerProxy: No adapter added yet, ignoring '
115+
'message.')
116+
continue
117+
118+
try:
119+
msg = json.loads(msg)
120+
except ValueError:
121+
print('AddonManagerProxy: Error parsing message as JSON')
122+
continue
123+
124+
if 'messageType' not in msg:
125+
print('AddonManagerProxy: Invalid message')
126+
continue
127+
128+
msg_type = msg['messageType']
129+
130+
# High-level adapter messages
131+
if msg_type == 'startPairing':
132+
self.adapter.start_pairing(msg['data']['timeout'])
133+
continue
134+
135+
if msg_type == 'cancelPairing':
136+
self.adapter.cancel_pairing()
137+
continue
138+
139+
if msg_type == 'unloadAdapter':
140+
self.adapter.unload()
141+
self.send('adapterUnloaded', {'adapterId', self.adapter.id})
142+
continue
143+
144+
if msg_type == 'unloadPlugin':
145+
self.send('pluginUnloaded', {})
146+
continue
147+
148+
# All messages from here on are assumed to require a valid deviceId
149+
if 'data' not in msg or 'deviceId' not in msg['data']:
150+
print('AddonManagerProxy: No deviceId present in message, '
151+
'ignoring.')
152+
continue
153+
154+
device_id = msg['data']['device_id']
155+
if msg_type == 'removeThing':
156+
self.adapter.remove_thing(device_id)
157+
continue
158+
159+
if msg_type == 'cancelRemoveThing':
160+
self.adapter.cancel_remove_thing(device_id)
161+
continue
162+
163+
if msg_type == 'setProperty':
164+
dev = self.adapter.get_device(device_id)
165+
if dev:
166+
prop = dev.get_property(msg['data']['propertyName'])
167+
if prop:
168+
prop.set_value(msg['data']['propertyValue'])
169+
continue

0 commit comments

Comments
 (0)