simo 2.11.3__py3-none-any.whl → 3.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of simo might be problematic. Click here for more details.
- simo/__pycache__/settings.cpython-312.pyc +0 -0
- simo/asgi.py +25 -6
- simo/automation/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/automation/controllers.py +18 -2
- simo/automation/forms.py +15 -24
- simo/backups/rescue.img.xz +0 -0
- simo/core/__pycache__/admin.cpython-312.pyc +0 -0
- simo/core/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/core/__pycache__/forms.cpython-312.pyc +0 -0
- simo/core/__pycache__/models.cpython-312.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-312.pyc +0 -0
- simo/core/__pycache__/signal_receivers.cpython-312.pyc +0 -0
- simo/core/__pycache__/tasks.cpython-312.pyc +0 -0
- simo/core/admin.py +5 -4
- simo/core/base_types.py +191 -18
- simo/core/controllers.py +259 -26
- simo/core/forms.py +10 -2
- simo/core/management/_hub_template/hub/nginx.conf +23 -50
- simo/core/management/_hub_template/hub/supervisor.conf +15 -0
- simo/core/mcp.py +154 -0
- simo/core/migrations/0051_instance_ai_memory.py +18 -0
- simo/core/migrations/__pycache__/0051_instance_ai_memory.cpython-312.pyc +0 -0
- simo/core/models.py +3 -0
- simo/core/serializers.py +120 -0
- simo/core/signal_receivers.py +1 -1
- simo/core/tasks.py +1 -3
- simo/core/utils/__pycache__/type_constants.cpython-312.pyc +0 -0
- simo/core/utils/type_constants.py +78 -17
- simo/fleet/__pycache__/admin.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/api.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/serializers.cpython-312.pyc +0 -0
- simo/fleet/admin.py +5 -1
- simo/fleet/api.py +2 -27
- simo/fleet/base_types.py +35 -4
- simo/fleet/controllers.py +150 -156
- simo/fleet/forms.py +56 -88
- simo/fleet/gateways.py +8 -15
- simo/fleet/migrations/0055_colonel_is_vo_active_colonel_last_wake_and_more.py +28 -0
- simo/fleet/migrations/0056_delete_customdalidevice.py +16 -0
- simo/fleet/migrations/__pycache__/0055_colonel_is_vo_active_colonel_last_wake_and_more.cpython-312.pyc +0 -0
- simo/fleet/migrations/__pycache__/0056_delete_customdalidevice.cpython-312.pyc +0 -0
- simo/fleet/models.py +13 -72
- simo/fleet/serializers.py +1 -48
- simo/fleet/socket_consumers.py +100 -39
- simo/fleet/tasks.py +2 -22
- simo/fleet/voice_assistant.py +893 -0
- simo/generic/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/generic/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/generic/base_types.py +70 -10
- simo/generic/controllers.py +102 -15
- simo/generic/gateways.py +10 -10
- simo/mcp_server/__init__.py +0 -0
- simo/mcp_server/__pycache__/__init__.cpython-312.pyc +0 -0
- simo/mcp_server/__pycache__/admin.cpython-312.pyc +0 -0
- simo/mcp_server/__pycache__/models.cpython-312.pyc +0 -0
- simo/mcp_server/admin.py +18 -0
- simo/mcp_server/app.py +4 -0
- simo/mcp_server/auth.py +34 -0
- simo/mcp_server/dummy.py +22 -0
- simo/mcp_server/migrations/0001_initial.py +30 -0
- simo/mcp_server/migrations/0002_alter_instanceaccesstoken_date_expired.py +18 -0
- simo/mcp_server/migrations/0003_instanceaccesstoken_issuer.py +18 -0
- simo/mcp_server/migrations/__init__.py +0 -0
- simo/mcp_server/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/0002_alter_instanceaccesstoken_date_expired.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/0003_instanceaccesstoken_issuer.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
- simo/mcp_server/models.py +27 -0
- simo/mcp_server/server.py +60 -0
- simo/mcp_server/tasks.py +19 -0
- simo/multimedia/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/multimedia/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/multimedia/base_types.py +29 -4
- simo/multimedia/controllers.py +66 -19
- simo/settings.py +1 -0
- simo/users/__pycache__/utils.cpython-312.pyc +0 -0
- simo/users/utils.py +10 -0
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/METADATA +12 -4
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/RECORD +90 -64
- simo/fleet/custom_dali_operations.py +0 -287
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/WHEEL +0 -0
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/entry_points.txt +0 -0
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/licenses/LICENSE.md +0 -0
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/top_level.txt +0 -0
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
from django.utils import timezone
|
|
2
|
-
from simo.core.models import Component
|
|
3
|
-
from .models import Interface, CustomDaliDevice
|
|
4
|
-
from .controllers import (
|
|
5
|
-
RoomSiren, TempHumSensor, AirQualitySensor, AmbientLightSensor,
|
|
6
|
-
RoomPresenceSensor, RoomZonePresenceSensor
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Frame:
|
|
11
|
-
"""A DALI frame.
|
|
12
|
-
|
|
13
|
-
A Frame consists of one start bit, n data bits, and one stop
|
|
14
|
-
condition. The most significant bit is always transmitted first.
|
|
15
|
-
|
|
16
|
-
Instances of this object are mutable.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
def __init__(self, bits, data=0):
|
|
20
|
-
"""Initialise a Frame with the supplied number of data bits.
|
|
21
|
-
|
|
22
|
-
:parameter bits: the number of data bits in the Frame
|
|
23
|
-
:parameter data: initial data for the Frame as an integer or
|
|
24
|
-
an iterable sequence of integers
|
|
25
|
-
"""
|
|
26
|
-
if not isinstance(bits, int):
|
|
27
|
-
raise TypeError(
|
|
28
|
-
"Number of bits must be an integer")
|
|
29
|
-
if bits < 1:
|
|
30
|
-
raise ValueError(
|
|
31
|
-
"Frames must contain at least 1 data bit")
|
|
32
|
-
self._bits = bits
|
|
33
|
-
if isinstance(data, int):
|
|
34
|
-
self._data = data
|
|
35
|
-
else:
|
|
36
|
-
self._data = int.from_bytes(data, 'big')
|
|
37
|
-
if self._data < 0:
|
|
38
|
-
raise ValueError("Initial data must not be negative")
|
|
39
|
-
if len(self.pack) > bits:
|
|
40
|
-
raise ValueError(
|
|
41
|
-
"Initial data will not fit in {} bits".format(bits))
|
|
42
|
-
self._error = False
|
|
43
|
-
|
|
44
|
-
@property
|
|
45
|
-
def error(self):
|
|
46
|
-
"""Frame was received with a framing error."""
|
|
47
|
-
return self._error
|
|
48
|
-
|
|
49
|
-
def __len__(self):
|
|
50
|
-
return self._bits
|
|
51
|
-
|
|
52
|
-
def __eq__(self, other):
|
|
53
|
-
try:
|
|
54
|
-
return self._bits == other._bits and self._data == other._data
|
|
55
|
-
except Exception:
|
|
56
|
-
return False
|
|
57
|
-
|
|
58
|
-
def __ne__(self, other):
|
|
59
|
-
try:
|
|
60
|
-
return self._bits != other._bits or self._data != other._data
|
|
61
|
-
except Exception:
|
|
62
|
-
return True
|
|
63
|
-
|
|
64
|
-
def _readslice(self, key):
|
|
65
|
-
"""Check that a slice is valid, return indices
|
|
66
|
-
|
|
67
|
-
The slice must have indices that are integers. The indices
|
|
68
|
-
must be in the range 0..(len(self)-1).
|
|
69
|
-
"""
|
|
70
|
-
if not isinstance(key.start, int) or not isinstance(key.stop, int):
|
|
71
|
-
raise TypeError("slice indices must be integers")
|
|
72
|
-
if key.step not in (None, 1):
|
|
73
|
-
raise TypeError("slice with step not supported")
|
|
74
|
-
hi = max(key.start, key.stop)
|
|
75
|
-
lo = min(key.start, key.stop)
|
|
76
|
-
if hi < 0 or lo < 0:
|
|
77
|
-
raise IndexError("slice indices must be >= 0")
|
|
78
|
-
if hi >= self._bits or lo >= self._bits:
|
|
79
|
-
raise IndexError("slice index out of range")
|
|
80
|
-
return hi, lo
|
|
81
|
-
|
|
82
|
-
def __getitem__(self, key):
|
|
83
|
-
"""Read a bit or group of bits from the frame
|
|
84
|
-
|
|
85
|
-
If the key is an integer, return that bit as True or False or
|
|
86
|
-
raise IndexError if the key is out of bounds.
|
|
87
|
-
|
|
88
|
-
If the key is a slice, return that slice as an integer or
|
|
89
|
-
raise IndexError if out of bounds. We abuse the slice
|
|
90
|
-
mechanism slightly such that slice(5,7) and slice(7,5) are
|
|
91
|
-
treated the same. Slices with a step or a negative index are
|
|
92
|
-
not supported.
|
|
93
|
-
"""
|
|
94
|
-
if isinstance(key, slice):
|
|
95
|
-
hi, lo = self._readslice(key)
|
|
96
|
-
d = self._data >> lo
|
|
97
|
-
return d & ((1 << (hi + 1 - lo)) - 1)
|
|
98
|
-
elif isinstance(key, int):
|
|
99
|
-
if key < 0 or key >= self._bits:
|
|
100
|
-
raise IndexError("index out of range")
|
|
101
|
-
return (self._data & (1 << key)) != 0
|
|
102
|
-
raise TypeError
|
|
103
|
-
|
|
104
|
-
def __setitem__(self, key, value):
|
|
105
|
-
"""Write a bit or a group of bits to the frame
|
|
106
|
-
|
|
107
|
-
If the key is an integer, set that bit to the truth value of
|
|
108
|
-
value or raise IndexError if the key is out of bounds.
|
|
109
|
-
|
|
110
|
-
If the key is a slice, value must be an integer that fits
|
|
111
|
-
within the slice; set that slice to value or raise IndexError
|
|
112
|
-
if out of bounds. We abuse the slice mechanism slightly such
|
|
113
|
-
that slice(5,7) and slice(7,5) are treated the same. Slices
|
|
114
|
-
with a step or a negative index are not supported.
|
|
115
|
-
"""
|
|
116
|
-
if isinstance(key, slice):
|
|
117
|
-
hi, lo = self._readslice(key)
|
|
118
|
-
if not isinstance(value, int):
|
|
119
|
-
raise TypeError("value must be an integer")
|
|
120
|
-
if len(bin(value)) - 2 > (hi + 1 - lo):
|
|
121
|
-
raise ValueError("value will not fit in supplied slice")
|
|
122
|
-
if value < 0:
|
|
123
|
-
raise ValueError("value must not be negative")
|
|
124
|
-
template = ((1 << hi + 1 - lo) - 1) << lo
|
|
125
|
-
mask = ((1 << self._bits) - 1) ^ template
|
|
126
|
-
self._data = self._data & mask | (value << lo)
|
|
127
|
-
elif isinstance(key, int):
|
|
128
|
-
if key < 0 or key >= self._bits:
|
|
129
|
-
raise IndexError("index out of range")
|
|
130
|
-
if value:
|
|
131
|
-
self._data = self._data | (1 << key)
|
|
132
|
-
else:
|
|
133
|
-
self._data = self._data \
|
|
134
|
-
& (((1 << self._bits) - 1) ^ (1 << key))
|
|
135
|
-
else:
|
|
136
|
-
raise TypeError
|
|
137
|
-
|
|
138
|
-
def __contains__(self, item):
|
|
139
|
-
if item is True:
|
|
140
|
-
return self._data != 0
|
|
141
|
-
if item is False:
|
|
142
|
-
return self._data != (1 << self._bits) - 1
|
|
143
|
-
return False
|
|
144
|
-
|
|
145
|
-
def __add__(self, other):
|
|
146
|
-
try:
|
|
147
|
-
return Frame(self._bits + other._bits,
|
|
148
|
-
self._data << other._bits | other._data)
|
|
149
|
-
except Exception:
|
|
150
|
-
raise TypeError("Frame can only be added to another Frame")
|
|
151
|
-
|
|
152
|
-
@property
|
|
153
|
-
def as_integer(self):
|
|
154
|
-
"""The contents of the frame represented as an integer."""
|
|
155
|
-
return self._data
|
|
156
|
-
|
|
157
|
-
@property
|
|
158
|
-
def as_byte_sequence(self):
|
|
159
|
-
"""The contents of the frame represented as a sequence.
|
|
160
|
-
|
|
161
|
-
Returns a sequence of integers each in the range 0..255
|
|
162
|
-
representing the data in the frame, with the most-significant
|
|
163
|
-
bits first. If the frame is not an exact multiple of 8 bits
|
|
164
|
-
long, the first element in the sequence contains fewer than 8
|
|
165
|
-
bits.
|
|
166
|
-
"""
|
|
167
|
-
return list(self.pack)
|
|
168
|
-
|
|
169
|
-
@property
|
|
170
|
-
def pack(self):
|
|
171
|
-
"""The contents of the frame represented as a byte string.
|
|
172
|
-
|
|
173
|
-
If the frame is not an exact multiple of 8 bits long, the
|
|
174
|
-
first byte in the string will contain fewer than 8 bits.
|
|
175
|
-
"""
|
|
176
|
-
return self._data.to_bytes(
|
|
177
|
-
(len(self) // 8) + (1 if len(self) % 8 else 0),
|
|
178
|
-
'big')
|
|
179
|
-
|
|
180
|
-
def pack_len(self, l):
|
|
181
|
-
"""The contents of the frame represented as a fixed length byte string.
|
|
182
|
-
|
|
183
|
-
The least significant bit of the frame is aligned to the end
|
|
184
|
-
of the byte string. The start of the byte string is padded
|
|
185
|
-
with zeroes.
|
|
186
|
-
|
|
187
|
-
If the frame will not fit in the byte string, raises
|
|
188
|
-
OverflowError.
|
|
189
|
-
"""
|
|
190
|
-
return self._data.to_bytes(l, 'big')
|
|
191
|
-
|
|
192
|
-
def __str__(self):
|
|
193
|
-
return "{}({},{})".format(self.__class__.__name__, len(self),
|
|
194
|
-
self.as_byte_sequence)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def process_frame(colonel_id, interface_no, data):
|
|
198
|
-
interface = Interface.objects.filter(
|
|
199
|
-
colonel_id=colonel_id, no=interface_no
|
|
200
|
-
).first()
|
|
201
|
-
if not interface:
|
|
202
|
-
return
|
|
203
|
-
|
|
204
|
-
data = bytes.fromhex(data)
|
|
205
|
-
frame = Frame(len(data) * 8, data)
|
|
206
|
-
|
|
207
|
-
device_address = frame[0:7]
|
|
208
|
-
device = CustomDaliDevice.objects.filter(
|
|
209
|
-
random_address=device_address, instance=interface.colonel.instance
|
|
210
|
-
).first()
|
|
211
|
-
if not device:
|
|
212
|
-
return
|
|
213
|
-
|
|
214
|
-
device.interface = interface
|
|
215
|
-
device.last_seen = timezone.now()
|
|
216
|
-
device.save()
|
|
217
|
-
|
|
218
|
-
print("Frame received: ", frame.pack)
|
|
219
|
-
|
|
220
|
-
if frame[8:11] == 0:
|
|
221
|
-
# climate and air quality data
|
|
222
|
-
temp = (frame[12:21] - 512) / 10
|
|
223
|
-
humidity = round(frame[22:27] / 64 * 100)
|
|
224
|
-
comp = Component.objects.filter(
|
|
225
|
-
controller_uid=TempHumSensor.uid, config__dali_device=device.id
|
|
226
|
-
).first()
|
|
227
|
-
if comp:
|
|
228
|
-
comp.controller._receive_from_device({'temp': temp, 'humidity': humidity})
|
|
229
|
-
voc = frame[28:38]
|
|
230
|
-
comp = Component.objects.filter(
|
|
231
|
-
controller_uid=AirQualitySensor.uid, config__dali_device=device.id
|
|
232
|
-
).first()
|
|
233
|
-
if comp:
|
|
234
|
-
comp.controller._receive_from_device(voc)
|
|
235
|
-
|
|
236
|
-
elif frame[8:11] == 1:
|
|
237
|
-
# presence sensors
|
|
238
|
-
comp = Component.objects.filter(
|
|
239
|
-
controller_uid=AmbientLightSensor.uid, config__dali_device=device.id
|
|
240
|
-
).first()
|
|
241
|
-
if comp:
|
|
242
|
-
comp.controller._receive_from_device(frame[12:22] * 2)
|
|
243
|
-
comp = Component.objects.filter(
|
|
244
|
-
controller_uid=RoomPresenceSensor.uid, config__dali_device=device.id
|
|
245
|
-
).first()
|
|
246
|
-
if comp:
|
|
247
|
-
comp.controller._receive_from_device(frame[23])
|
|
248
|
-
|
|
249
|
-
zone_sensors = {}
|
|
250
|
-
for slot in range(8):
|
|
251
|
-
comp = Component.objects.filter(
|
|
252
|
-
controller_uid=RoomZonePresenceSensor.uid,
|
|
253
|
-
config__dali_device=device.id, config__slot=slot
|
|
254
|
-
).first()
|
|
255
|
-
if comp:
|
|
256
|
-
zone_sensors[slot] = comp
|
|
257
|
-
|
|
258
|
-
for slot in range(8):
|
|
259
|
-
if frame[24 + slot * 2]:
|
|
260
|
-
comp = zone_sensors.get(slot)
|
|
261
|
-
if comp:
|
|
262
|
-
comp.controller._receive_from_device(frame[25 + slot * 2])
|
|
263
|
-
else:
|
|
264
|
-
# component no longer exists, probably deleted by user
|
|
265
|
-
# need to inform device about that!
|
|
266
|
-
f = Frame(40, bytes(bytearray(5)))
|
|
267
|
-
f[8:11] = 15 # command to custom dali device
|
|
268
|
-
f[12:15] = 2 # action to perform: delete zone sensor
|
|
269
|
-
f[16:18] = slot
|
|
270
|
-
device.transmit(f)
|
|
271
|
-
elif zone_sensors.get(slot):
|
|
272
|
-
# not yet picked up by the device itself or
|
|
273
|
-
# was never successfully created
|
|
274
|
-
if zone_sensors[slot].alive:
|
|
275
|
-
zone_sensors[slot].alive = False
|
|
276
|
-
zone_sensors[slot].save()
|
|
277
|
-
|
|
278
|
-
elif frame[8:11] == 2:
|
|
279
|
-
# siren and others
|
|
280
|
-
comp = Component.objects.filter(
|
|
281
|
-
controller_uid=RoomSiren.uid, config__dali_device=device.id
|
|
282
|
-
).first()
|
|
283
|
-
if comp:
|
|
284
|
-
VALUES_MAP = {
|
|
285
|
-
int_v: str_v for str_v, int_v in RoomSiren.VALUES_MAP.items()
|
|
286
|
-
}
|
|
287
|
-
comp.controller._receive_from_device(VALUES_MAP[frame[12:16]])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|