simo 2.11.4__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.

Files changed (90) hide show
  1. simo/__pycache__/settings.cpython-312.pyc +0 -0
  2. simo/asgi.py +25 -6
  3. simo/automation/__pycache__/controllers.cpython-312.pyc +0 -0
  4. simo/automation/controllers.py +18 -2
  5. simo/automation/forms.py +15 -24
  6. simo/core/__pycache__/admin.cpython-312.pyc +0 -0
  7. simo/core/__pycache__/base_types.cpython-312.pyc +0 -0
  8. simo/core/__pycache__/controllers.cpython-312.pyc +0 -0
  9. simo/core/__pycache__/forms.cpython-312.pyc +0 -0
  10. simo/core/__pycache__/models.cpython-312.pyc +0 -0
  11. simo/core/__pycache__/serializers.cpython-312.pyc +0 -0
  12. simo/core/__pycache__/signal_receivers.cpython-312.pyc +0 -0
  13. simo/core/__pycache__/tasks.cpython-312.pyc +0 -0
  14. simo/core/admin.py +5 -4
  15. simo/core/base_types.py +191 -18
  16. simo/core/controllers.py +259 -26
  17. simo/core/forms.py +10 -2
  18. simo/core/management/_hub_template/hub/nginx.conf +23 -50
  19. simo/core/management/_hub_template/hub/supervisor.conf +15 -0
  20. simo/core/mcp.py +154 -0
  21. simo/core/migrations/0051_instance_ai_memory.py +18 -0
  22. simo/core/migrations/__pycache__/0051_instance_ai_memory.cpython-312.pyc +0 -0
  23. simo/core/models.py +3 -0
  24. simo/core/serializers.py +120 -0
  25. simo/core/signal_receivers.py +1 -1
  26. simo/core/tasks.py +1 -3
  27. simo/core/utils/__pycache__/type_constants.cpython-312.pyc +0 -0
  28. simo/core/utils/type_constants.py +78 -17
  29. simo/fleet/__pycache__/admin.cpython-312.pyc +0 -0
  30. simo/fleet/__pycache__/api.cpython-312.pyc +0 -0
  31. simo/fleet/__pycache__/base_types.cpython-312.pyc +0 -0
  32. simo/fleet/__pycache__/controllers.cpython-312.pyc +0 -0
  33. simo/fleet/__pycache__/forms.cpython-312.pyc +0 -0
  34. simo/fleet/__pycache__/gateways.cpython-312.pyc +0 -0
  35. simo/fleet/__pycache__/models.cpython-312.pyc +0 -0
  36. simo/fleet/__pycache__/serializers.cpython-312.pyc +0 -0
  37. simo/fleet/admin.py +5 -1
  38. simo/fleet/api.py +2 -27
  39. simo/fleet/base_types.py +35 -4
  40. simo/fleet/controllers.py +150 -156
  41. simo/fleet/forms.py +56 -88
  42. simo/fleet/gateways.py +8 -15
  43. simo/fleet/migrations/0055_colonel_is_vo_active_colonel_last_wake_and_more.py +28 -0
  44. simo/fleet/migrations/0056_delete_customdalidevice.py +16 -0
  45. simo/fleet/migrations/__pycache__/0055_colonel_is_vo_active_colonel_last_wake_and_more.cpython-312.pyc +0 -0
  46. simo/fleet/migrations/__pycache__/0056_delete_customdalidevice.cpython-312.pyc +0 -0
  47. simo/fleet/models.py +13 -72
  48. simo/fleet/serializers.py +1 -48
  49. simo/fleet/socket_consumers.py +100 -39
  50. simo/fleet/tasks.py +2 -22
  51. simo/fleet/voice_assistant.py +893 -0
  52. simo/generic/__pycache__/base_types.cpython-312.pyc +0 -0
  53. simo/generic/__pycache__/controllers.cpython-312.pyc +0 -0
  54. simo/generic/__pycache__/gateways.cpython-312.pyc +0 -0
  55. simo/generic/base_types.py +70 -10
  56. simo/generic/controllers.py +102 -15
  57. simo/generic/gateways.py +10 -10
  58. simo/mcp_server/__init__.py +0 -0
  59. simo/mcp_server/__pycache__/__init__.cpython-312.pyc +0 -0
  60. simo/mcp_server/__pycache__/admin.cpython-312.pyc +0 -0
  61. simo/mcp_server/__pycache__/models.cpython-312.pyc +0 -0
  62. simo/mcp_server/admin.py +18 -0
  63. simo/mcp_server/app.py +4 -0
  64. simo/mcp_server/auth.py +34 -0
  65. simo/mcp_server/dummy.py +22 -0
  66. simo/mcp_server/migrations/0001_initial.py +30 -0
  67. simo/mcp_server/migrations/0002_alter_instanceaccesstoken_date_expired.py +18 -0
  68. simo/mcp_server/migrations/0003_instanceaccesstoken_issuer.py +18 -0
  69. simo/mcp_server/migrations/__init__.py +0 -0
  70. simo/mcp_server/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  71. simo/mcp_server/migrations/__pycache__/0002_alter_instanceaccesstoken_date_expired.cpython-312.pyc +0 -0
  72. simo/mcp_server/migrations/__pycache__/0003_instanceaccesstoken_issuer.cpython-312.pyc +0 -0
  73. simo/mcp_server/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  74. simo/mcp_server/models.py +27 -0
  75. simo/mcp_server/server.py +60 -0
  76. simo/mcp_server/tasks.py +19 -0
  77. simo/multimedia/__pycache__/base_types.cpython-312.pyc +0 -0
  78. simo/multimedia/__pycache__/controllers.cpython-312.pyc +0 -0
  79. simo/multimedia/base_types.py +29 -4
  80. simo/multimedia/controllers.py +66 -19
  81. simo/settings.py +1 -0
  82. simo/users/__pycache__/utils.cpython-312.pyc +0 -0
  83. simo/users/utils.py +10 -0
  84. {simo-2.11.4.dist-info → simo-3.0.1.dist-info}/METADATA +12 -4
  85. {simo-2.11.4.dist-info → simo-3.0.1.dist-info}/RECORD +89 -63
  86. simo/fleet/custom_dali_operations.py +0 -287
  87. {simo-2.11.4.dist-info → simo-3.0.1.dist-info}/WHEEL +0 -0
  88. {simo-2.11.4.dist-info → simo-3.0.1.dist-info}/entry_points.txt +0 -0
  89. {simo-2.11.4.dist-info → simo-3.0.1.dist-info}/licenses/LICENSE.md +0 -0
  90. {simo-2.11.4.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