Skip to content

Commit c3ac3a6

Browse files
committed
Add all complex types and schedule.
1 parent e00a4ec commit c3ac3a6

File tree

3 files changed

+65
-12
lines changed

3 files changed

+65
-12
lines changed

aiotcloud/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,46 @@
2323
# THE SOFTWARE.
2424
from .ucloud import AIOTClient
2525
from .ucloud import AIOTObject
26+
from .ucloud import timestamp
27+
try:
28+
import asyncio
29+
except ImportError:
30+
import uasyncio as asyncio
2631

2732
class Location(AIOTObject):
2833
def __init__(self, name, **kwargs):
2934
super().__init__(name, keys={"lat", "lon"}, **kwargs)
3035

36+
class Color(AIOTObject):
37+
def __init__(self, name, **kwargs):
38+
super().__init__(name, keys={"hue", "sat", "bri"}, **kwargs)
39+
3140
class ColoredLight(AIOTObject):
3241
def __init__(self, name, **kwargs):
3342
super().__init__(name, keys={"swi", "hue", "sat", "bri"}, **kwargs)
43+
44+
class DimmedLight(AIOTObject):
45+
def __init__(self, name, **kwargs):
46+
super().__init__(name, keys={"swi", "bri"}, **kwargs)
47+
48+
class Schedule(AIOTObject):
49+
def __init__(self, name, **kwargs):
50+
kwargs.update({("runnable", True)}) # Force task creation.
51+
kwargs.update({("interval", 1.0)}) # Will run on every second
52+
self.on_active = kwargs.pop("on_active", None)
53+
# Uncomment to allow the schedule to change in runtime.
54+
#kwargs["on_write"] = kwargs.get("on_write", lambda aiot, value: None)
55+
self.active = False
56+
super().__init__(name, keys={"frm", "to", "len", "msk"}, **kwargs)
57+
58+
async def run(self, aiot):
59+
while True:
60+
if self.initialized:
61+
ts = timestamp() + aiot.get("tz_offset", 0)
62+
if ts > self.frm and ts < (self.frm + self.len):
63+
if not self.active and self.on_active is not None:
64+
self.on_active(aiot, self.value)
65+
self.active = True
66+
else:
67+
self.active = False
68+
await asyncio.sleep(self.interval)

aiotcloud/ucloud.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def __init__(self, name, **kwargs):
4848
self.on_read = kwargs.pop("on_read", None)
4949
self.on_write = kwargs.pop("on_write", None)
5050
self.interval = kwargs.pop("interval", 1.0)
51+
self._runnable = kwargs.pop("runnable", False)
5152
value = kwargs.pop("value", None)
5253
if keys := kwargs.pop("keys", {}): # Create a complex object (with sub-records).
5354
mkrec = lambda k, v: AIOTObject(f"{name}:{k}", value=v, callback=self.senml_callback)
@@ -86,6 +87,10 @@ def initialized(self):
8687
return all(r.initialized for r in self.value.values())
8788
return self.value is not None
8889

90+
@property
91+
def runnable(self):
92+
return self.on_read is not None or self.on_write is not None or self._runnable
93+
8994
@SenmlRecord.value.setter
9095
def value(self, value):
9196
if value is not None:
@@ -114,18 +119,16 @@ def __setattr__(self, name, value):
114119
def _build_rec_dict(self, naming_map, appendTo):
115120
if isinstance(self.value, dict):
116121
for r in self.value.values():
117-
# NOTE: should filter by updated when it's supported.
118-
if r.value is not None: # and r.updated
122+
if r.value is not None: # NOTE: should filter by updated when it's supported.
119123
r._build_rec_dict(naming_map, appendTo)
120124
else:
121125
super()._build_rec_dict(naming_map, appendTo)
122126

123127
def add_to_pack(self, pack):
124128
if isinstance(self.value, dict):
125129
for r in self.value.values():
126-
# NOTE: should filter by updated when it's supported.
127-
if r.value is not None: # and r.updated
128-
pack.add(r)
130+
# NOTE: If record value is None it can still be added to the pack for initialization.
131+
pack.add(r) # NOTE: should filter by updated when it's supported.
129132
else:
130133
pack.add(self)
131134
self.updated = False
@@ -159,15 +162,26 @@ def __init__(self, device_id, ssl_params=None, server="mqtts-sa.iot.oniudra.cc",
159162
self.device_topic = b"/a/d/" + device_id + b"/e/i"
160163
self.senmlpack = SenmlPack("urn:uuid:"+device_id.decode("utf-8"), self.senml_generic_callback)
161164
self.mqtt_client = MQTTClient(device_id, server, port, ssl_params, keepalive=keepalive, callback=self.mqtt_callback)
162-
# Note: this object is set by the cloud discovery protocol.
165+
# Note: the following objects are initialized by the cloud.
163166
self.register("thing_id", value=None, on_write=self.discovery_callback)
167+
self.register("tz_offset", value=None)
164168

165169
def __getitem__(self, key):
166-
return self.records[key]
170+
if isinstance(self.records[key].value, dict):
171+
return self.records[key]
172+
return self.records[key].value
167173

168174
def __setitem__(self, key, value):
169175
self.records[key].value = value
170176

177+
def __contains__(self, key):
178+
return key in self.records
179+
180+
def get(self, key, default=None):
181+
if key in self and self[key] is not None:
182+
return self[key]
183+
return default
184+
171185
def update_systime(self):
172186
try:
173187
from aiotcloud import ntptime
@@ -195,7 +209,7 @@ def register(self, aiotobj, **kwargs):
195209
self.records[aiotobj.name] = aiotobj
196210

197211
# Create a task for this object if it has any callbacks.
198-
if aiotobj.on_read is not None or aiotobj.on_write is not None:
212+
if aiotobj.runnable:
199213
self.create_new_task(aiotobj.run, self, name=aiotobj.name)
200214

201215
# Check if object needs to be initialized from the cloud.
@@ -219,14 +233,12 @@ def discovery_callback(self, aiot, thing_id):
219233
self.thing_id = bytes(thing_id, "utf-8")
220234
self.topic_in = b"/a/t/" + self.thing_id + b"/e/i"
221235
self.topic_out = b"/a/t/" + self.thing_id + b"/e/o"
222-
223-
shadow_in = b"/a/t/" + self.thing_id + b"/shadow/i"
224-
shadow_out= b"/a/t/" + self.thing_id + b"/shadow/o"
225-
226236
logging.info(f"Subscribing to thing topic {self.topic_in}.")
227237
self.mqtt_client.subscribe(self.topic_in)
228238

229239
if lastval_record := self.records.pop("r:m", None):
240+
shadow_in = b"/a/t/" + self.thing_id + b"/shadow/i"
241+
shadow_out= b"/a/t/" + self.thing_id + b"/shadow/o"
230242
lastval_record.add_to_pack(self.senmlpack)
231243
logging.info(f"Subscribing to shadow topic {shadow_in}.")
232244
self.mqtt_client.subscribe(shadow_in)

aiotcloud_example.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from ulogging.ustrftime import strftime
3636
from aiotcloud import AIOTClient
3737
from aiotcloud import Location
38+
from aiotcloud import Schedule
3839
from aiotcloud import ColoredLight
3940
from random import randint, choice
4041

@@ -98,6 +99,11 @@ async def main():
9899
# This variable is updated manually from user_main.
99100
aiot.register("user", value="")
100101

102+
# This object allows scheduling recurring events from the cloud UI. On activation of the event, if
103+
# on_active callback is provided, it gets called with the aiot object and the schedule object value.
104+
# The activation status of the object can also be polled using aiot["schedule"].active.
105+
aiot.register(Schedule("schedule", on_active=lambda aiot, value: logging.info(f"Schedule activated {value}!")))
106+
101107
# Start the AIoT client.
102108
await aiot.run(user_main, debug=DEBUG_ENABLED)
103109

0 commit comments

Comments
 (0)