simo 2.0.41__py3-none-any.whl → 2.1.0__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 (94) hide show
  1. simo/__pycache__/asgi.cpython-38.pyc +0 -0
  2. simo/__pycache__/on_http_start.cpython-38.pyc +0 -0
  3. simo/__pycache__/settings.cpython-38.pyc +0 -0
  4. simo/__pycache__/wsgi.cpython-38.pyc +0 -0
  5. simo/core/__init__.py +1 -0
  6. simo/core/__pycache__/__init__.cpython-38.pyc +0 -0
  7. simo/core/__pycache__/admin.cpython-38.pyc +0 -0
  8. simo/core/__pycache__/api.cpython-38.pyc +0 -0
  9. simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
  10. simo/core/__pycache__/app_widgets.cpython-38.pyc +0 -0
  11. simo/core/__pycache__/apps.cpython-38.pyc +0 -0
  12. simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
  13. simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
  14. simo/core/__pycache__/form_fields.cpython-38.pyc +0 -0
  15. simo/core/__pycache__/forms.cpython-38.pyc +0 -0
  16. simo/core/__pycache__/gateways.cpython-38.pyc +0 -0
  17. simo/core/__pycache__/managers.cpython-38.pyc +0 -0
  18. simo/core/__pycache__/models.cpython-38.pyc +0 -0
  19. simo/core/__pycache__/permissions.cpython-38.pyc +0 -0
  20. simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
  21. simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
  22. simo/core/admin.py +28 -10
  23. simo/core/api.py +24 -5
  24. simo/core/api_meta.py +23 -13
  25. simo/core/app_widgets.py +6 -0
  26. simo/core/apps.py +10 -0
  27. simo/core/base_types.py +1 -0
  28. simo/core/controllers.py +57 -0
  29. simo/core/form_fields.py +93 -0
  30. simo/core/forms.py +15 -3
  31. simo/core/gateways.py +1 -1
  32. simo/core/managers.py +14 -1
  33. simo/core/migrations/0037_auto_20240606_1057.py +33 -0
  34. simo/core/migrations/__pycache__/0037_auto_20240606_1057.cpython-38.pyc +0 -0
  35. simo/core/models.py +28 -9
  36. simo/core/permissions.py +6 -3
  37. simo/core/serializers.py +77 -5
  38. simo/core/signal_receivers.py +25 -0
  39. simo/core/static/admin/css/simo.css +14 -0
  40. simo/core/templates/admin/controller_widgets/button.html +8 -0
  41. simo/core/templates/admin/core/component_change_form.html +97 -0
  42. simo/core/templates/admin/formset_widget.html +88 -118
  43. simo/core/templates/admin/formset_widget_old.html +122 -0
  44. simo/core/templates/admin/wizard/wizard_add.html +16 -9
  45. simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
  46. simo/core/utils/__pycache__/cache.cpython-38.pyc +0 -0
  47. simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
  48. simo/core/utils/admin.py +11 -0
  49. simo/core/utils/cache.py +15 -0
  50. simo/core/utils/formsets.py +11 -18
  51. simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
  52. simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
  53. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  54. simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
  55. simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
  56. simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  57. simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
  58. simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
  59. simo/fleet/auto_urls.py +7 -1
  60. simo/fleet/controllers.py +193 -30
  61. simo/fleet/forms.py +223 -87
  62. simo/fleet/gateways.py +53 -2
  63. simo/fleet/migrations/0036_auto_20240605_0702.py +68 -0
  64. simo/fleet/migrations/__pycache__/0036_auto_20240605_0702.cpython-38.pyc +0 -0
  65. simo/fleet/models.py +35 -6
  66. simo/fleet/socket_consumers.py +1 -1
  67. simo/fleet/templates/fleet/controllers_info/button.md +16 -0
  68. simo/fleet/utils.py +31 -1
  69. simo/fleet/views.py +45 -0
  70. simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
  71. simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
  72. simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
  73. simo/generic/controllers.py +59 -14
  74. simo/generic/forms.py +4 -3
  75. simo/generic/gateways.py +2 -0
  76. simo/generic/templates/admin/controller_widgets/blinds.html +2 -1
  77. simo/generic/templates/generic/controllers_info/dummy.md +3 -0
  78. simo/generic/templates/generic/controllers_info/stateselect.md +2 -0
  79. simo/settings.py +20 -4
  80. simo/users/__init__.py +1 -0
  81. simo/users/__pycache__/__init__.cpython-38.pyc +0 -0
  82. simo/users/__pycache__/admin.cpython-38.pyc +0 -0
  83. simo/users/__pycache__/apps.cpython-38.pyc +0 -0
  84. simo/users/__pycache__/models.cpython-38.pyc +0 -0
  85. simo/users/apps.py +9 -0
  86. simo/users/migrations/__pycache__/0029_alter_instanceuser_options_instanceuser_order.cpython-38.pyc +0 -0
  87. simo/users/migrations/__pycache__/0030_alter_instanceuser_options_remove_instanceuser_order.cpython-38.pyc +0 -0
  88. simo/users/models.py +16 -3
  89. {simo-2.0.41.dist-info → simo-2.1.0.dist-info}/METADATA +5 -3
  90. {simo-2.0.41.dist-info → simo-2.1.0.dist-info}/RECORD +93 -74
  91. simo/wsgi.py +0 -7
  92. {simo-2.0.41.dist-info → simo-2.1.0.dist-info}/LICENSE.md +0 -0
  93. {simo-2.0.41.dist-info → simo-2.1.0.dist-info}/WHEEL +0 -0
  94. {simo-2.0.41.dist-info → simo-2.1.0.dist-info}/top_level.txt +0 -0
simo/fleet/controllers.py CHANGED
@@ -1,8 +1,10 @@
1
1
  import json
2
2
  from django.utils.translation import gettext_lazy as _
3
+ from django.db.transaction import atomic
3
4
  from simo.core.events import GatewayObjectCommand
4
5
  from simo.core.controllers import (
5
6
  BinarySensor as BaseBinarySensor,
7
+ Button as BaseButton,
6
8
  NumericSensor as BaseNumericSensor,
7
9
  Switch as BaseSwitch, Dimmer as BaseDimmer,
8
10
  MultiSensor as BaseMultiSensor, RGBWLight as BaseRGBWLight
@@ -15,18 +17,20 @@ from simo.core.utils.serialization import (
15
17
  serialize_form_data, deserialize_form_data
16
18
  )
17
19
  from simo.generic.controllers import Blinds as GenericBlinds
18
- from .models import Colonel
20
+ from .models import Colonel, ColonelPin
19
21
  from .gateways import FleetGatewayHandler
20
22
  from .forms import (
21
23
  ColonelPinChoiceField,
22
- ColonelBinarySensorConfigForm, ColonelTouchSensorConfigForm,
24
+ ColonelBinarySensorConfigForm, ColonelButtonConfigForm,
23
25
  ColonelSwitchConfigForm, ColonelPWMOutputConfigForm,
24
26
  ColonelNumericSensorConfigForm, ColonelRGBLightConfigForm,
25
27
  ColonelDHTSensorConfigForm, DS18B20SensorConfigForm,
26
28
  BME680SensorConfigForm, MPC9808SensorConfigForm,
27
29
  DualMotorValveForm, BlindsConfigForm, BurglarSmokeDetectorConfigForm,
28
30
  TTLockConfigForm, DALIDeviceConfigForm, DaliLampForm, DaliGearGroupForm,
29
- DaliOccupancySensorConfigForm, DALILightSensorConfigForm
31
+ DaliSwitchConfigForm,
32
+ DaliOccupancySensorConfigForm, DALILightSensorConfigForm,
33
+ DALIButtonConfigForm
30
34
  )
31
35
 
32
36
 
@@ -67,19 +71,39 @@ class FleeDeviceMixin:
67
71
  config[key] = val
68
72
  return config
69
73
 
74
+ def _fix_pin_relations(self):
75
+ pass
76
+
70
77
 
71
78
  class BasicSensorMixin:
72
79
  gateway_class = FleetGatewayHandler
73
80
 
81
+ def _get_occupied_pins(self):
82
+ return [
83
+ self.component.config['pin_no'],
84
+ ]
85
+
86
+ @atomic
87
+ def _fix_pin_relations(self):
88
+ colonel = Colonel.objects.filter(
89
+ id=self.component.config.get('colonel', 0)
90
+ ).first()
91
+ if not colonel:
92
+ return
93
+ cp = ColonelPin.objects.filter(
94
+ colonel=colonel, no=self.component.config['pin_no']
95
+ ).first()
96
+ if self.component.config.get('pin') != cp.id:
97
+ self.component.config['pin'] = cp.id
98
+ self.component.save()
74
99
 
75
100
 
76
101
  class BinarySensor(FleeDeviceMixin, BasicSensorMixin, BaseBinarySensor):
77
102
  config_form = ColonelBinarySensorConfigForm
78
103
 
79
- def _get_occupied_pins(self):
80
- return [
81
- self.component.config['pin_no'],
82
- ]
104
+
105
+ class Button(FleeDeviceMixin, BasicSensorMixin, BaseButton):
106
+ config_form = ColonelButtonConfigForm
83
107
 
84
108
 
85
109
  class BurglarSmokeDetector(BinarySensor):
@@ -92,6 +116,26 @@ class BurglarSmokeDetector(BinarySensor):
92
116
  self.component.config['sensor_pin_no']
93
117
  ]
94
118
 
119
+ @atomic
120
+ def _fix_pin_relations(self):
121
+ colonel = Colonel.objects.filter(
122
+ id=self.component.config.get('colonel', 0)
123
+ ).first()
124
+ if not colonel:
125
+ return
126
+ cp = ColonelPin.objects.filter(
127
+ colonel=colonel, no=self.component.config['power_pin_no']
128
+ ).first()
129
+ if self.component.config.get('power_pin') != cp.id:
130
+ self.component.config['power_pin'] = cp.id
131
+ self.component.save()
132
+ cp = ColonelPin.objects.filter(
133
+ colonel=colonel, no=self.component.config['sensor_pin_no']
134
+ ).first()
135
+ if self.component.config.get('sensor_pin') != cp.id:
136
+ self.component.config['sensor_pin'] = cp.id
137
+ self.component.save()
138
+
95
139
 
96
140
  # class AnalogSensor(FleeDeviceMixin, BasicSensorMixin, BaseNumericSensor):
97
141
  # config_form = ColonelNumericSensorConfigForm
@@ -107,11 +151,6 @@ class DS18B20Sensor(FleeDeviceMixin, BasicSensorMixin, BaseNumericSensor):
107
151
  config_form = DS18B20SensorConfigForm
108
152
  name = "DS18B20 Temperature sensor"
109
153
 
110
- def _get_occupied_pins(self):
111
- return [
112
- self.component.config['pin_no'],
113
- ]
114
-
115
154
 
116
155
  class BaseClimateSensor(FleeDeviceMixin, BasicSensorMixin, BaseMultiSensor):
117
156
  app_widget = NumericSensorWidget
@@ -158,18 +197,15 @@ class DHTSensor(BaseClimateSensor):
158
197
  config_form = ColonelDHTSensorConfigForm
159
198
  name = "DHT climate sensor"
160
199
 
161
- def _get_occupied_pins(self):
162
- return [
163
- self.component.config['pin_no'],
164
- ]
165
-
166
200
 
167
- class BME680Sensor(BaseClimateSensor):
201
+ class BME680Sensor(FleeDeviceMixin, BaseMultiSensor):
202
+ gateway_class = FleetGatewayHandler
168
203
  config_form = BME680SensorConfigForm
169
204
  name = "BME680 Climate Sensor (I2C)"
170
205
 
171
206
 
172
- class MPC9808TempSensor(FleeDeviceMixin, BasicSensorMixin, BaseNumericSensor):
207
+ class MPC9808TempSensor(FleeDeviceMixin, BaseNumericSensor):
208
+ gateway_class = FleetGatewayHandler
173
209
  config_form = MPC9808SensorConfigForm
174
210
  name = "MPC9808 Temperature Sensor (I2C)"
175
211
 
@@ -180,10 +216,47 @@ class BasicOutputMixin:
180
216
 
181
217
  def _get_occupied_pins(self):
182
218
  pins = [self.component.config['output_pin_no']]
183
- for control_unit in self.component.config.get('controls', []):
184
- pins.append(control_unit['pin_no'])
219
+ for ctrl in self.component.config.get('controls', []):
220
+ if 'pin_no' in ctrl:
221
+ pins.append(ctrl['pin_no'])
185
222
  return pins
186
223
 
224
+ @atomic
225
+ def _fix_pin_relations(self):
226
+ colonel = Colonel.objects.filter(
227
+ id=self.component.config.get('colonel', 0)
228
+ ).first()
229
+ if not colonel:
230
+ return
231
+ cp = ColonelPin.objects.filter(
232
+ colonel=colonel, no=self.component.config['output_pin_no']
233
+ ).first()
234
+ if self.component.config.get('output_pin') != cp.id:
235
+ self.component.config['output_pin'] = cp.id
236
+
237
+ for ctrl in self.component.config.get('controls', []):
238
+ if 'pin_no' not in ctrl:
239
+ continue
240
+ if not ctrl.get('input').startswith('pin-'):
241
+ continue
242
+ cp = ColonelPin.objects.filter(
243
+ colonel=colonel, no=ctrl['pin_no']
244
+ ).first()
245
+ if not cp:
246
+ continue
247
+ ctrl['input'] = f'pin-{cp.id}'
248
+
249
+ self.component.save()
250
+
251
+
252
+ def _ctrl(self, ctrl_no, ctrl_event, method):
253
+ GatewayObjectCommand(
254
+ self.component.gateway,
255
+ Colonel(id=self.component.config['colonel']),
256
+ id=self.component.id, command='call', method='ctrl',
257
+ args=[ctrl_no, ctrl_event, method]
258
+ ).publish()
259
+
187
260
 
188
261
  class Switch(FleeDeviceMixin, BasicOutputMixin, BaseSwitch):
189
262
  config_form = ColonelSwitchConfigForm
@@ -211,8 +284,37 @@ class Switch(FleeDeviceMixin, BasicOutputMixin, BaseSwitch):
211
284
  ).publish()
212
285
 
213
286
 
287
+ class FadeMixin:
288
+
289
+ def __init__(self, *args, **kwargs):
290
+ super().__init__(*args, **kwargs)
291
+ self.component.last_fade_direction = 0
292
+
293
+ def fade_up(self):
294
+ self.component.last_fade_direction = 1
295
+ GatewayObjectCommand(
296
+ self.component.gateway,
297
+ Colonel(id=self.component.config['colonel']),
298
+ id=self.component.id, command='call', method='fade_up'
299
+ ).publish()
300
+
301
+ def fade_down(self):
302
+ self.component.last_fade_direction = -1
303
+ GatewayObjectCommand(
304
+ self.component.gateway,
305
+ Colonel(id=self.component.config['colonel']),
306
+ id=self.component.id, command='call', method='fade_down'
307
+ ).publish()
308
+
309
+ def fade_stop(self):
310
+ GatewayObjectCommand(
311
+ self.component.gateway,
312
+ Colonel(id=self.component.config['colonel']),
313
+ id=self.component.id, command='call', method='fade_stop'
314
+ ).publish()
315
+
214
316
 
215
- class PWMOutput(FleeDeviceMixin, BasicOutputMixin, BaseDimmer):
317
+ class PWMOutput(FadeMixin, FleeDeviceMixin, BasicOutputMixin, BaseDimmer):
216
318
  name = "Dimmer"
217
319
  config_form = ColonelPWMOutputConfigForm
218
320
 
@@ -262,6 +364,7 @@ class PWMOutput(FleeDeviceMixin, BasicOutputMixin, BaseDimmer):
262
364
  return value
263
365
 
264
366
 
367
+
265
368
  class RGBLight(FleeDeviceMixin, BasicOutputMixin, BaseRGBWLight):
266
369
  config_form = ColonelRGBLightConfigForm
267
370
 
@@ -278,6 +381,26 @@ class DualMotorValve(FleeDeviceMixin, BasicOutputMixin, BaseSwitch):
278
381
  self.component.config['close_pin_no']
279
382
  ]
280
383
 
384
+ @atomic
385
+ def _fix_pin_relations(self):
386
+ colonel = Colonel.objects.filter(
387
+ id=self.component.config.get('colonel', 0)
388
+ ).first()
389
+ if not colonel:
390
+ return
391
+ cp = ColonelPin.objects.filter(
392
+ colonel=colonel, no=self.component.config['open_pin_no']
393
+ ).first()
394
+ if self.component.config.get('open_pin') != cp.id:
395
+ self.component.config['open_pin'] = cp.id
396
+ cp = ColonelPin.objects.filter(
397
+ colonel=colonel, no=self.component.config['close_pin_no']
398
+ ).first()
399
+ if self.component.config.get('close_pin') != cp.id:
400
+ self.component.config['close_pin'] = cp.id
401
+
402
+ self.component.save()
403
+
281
404
 
282
405
  class Blinds(FleeDeviceMixin, BasicOutputMixin, GenericBlinds):
283
406
  gateway_class = FleetGatewayHandler
@@ -288,10 +411,41 @@ class Blinds(FleeDeviceMixin, BasicOutputMixin, GenericBlinds):
288
411
  self.component.config['open_pin_no'],
289
412
  self.component.config['close_pin_no']
290
413
  ]
291
- for p in self.component.config.get('controls', []):
292
- pins.append(p['pin'])
414
+ for ctrl in self.component.config.get('controls', []):
415
+ if 'pin_no' in ctrl:
416
+ pins.append(ctrl['pin_no'])
293
417
  return pins
294
418
 
419
+ @atomic
420
+ def _fix_pin_relations(self):
421
+ colonel = Colonel.objects.filter(
422
+ id=self.component.config.get('colonel', 0)
423
+ ).first()
424
+ if not colonel:
425
+ return
426
+ cp = ColonelPin.objects.filter(
427
+ colonel=colonel, no=self.component.config['open_pin_no']
428
+ ).first()
429
+ if self.component.config.get('open_pin') != cp.id:
430
+ self.component.config['open_pin'] = cp.id
431
+ cp = ColonelPin.objects.filter(
432
+ colonel=colonel, no=self.component.config['close_pin_no']
433
+ ).first()
434
+ if self.component.config.get('close_pin') != cp.id:
435
+ self.component.config['close_pin'] = cp.id
436
+ for ctrl in self.component.config.get('controls', []):
437
+ if 'pin_no' not in ctrl:
438
+ continue
439
+ if not ctrl.get('input').startswith('pin-'):
440
+ continue
441
+ cp = ColonelPin.objects.filter(
442
+ colonel=colonel, no=ctrl['pin_no']
443
+ ).first()
444
+ if not cp:
445
+ continue
446
+ ctrl['input'] = f'pin-{cp.id}'
447
+ self.component.save()
448
+
295
449
 
296
450
  class TTLock(FleeDeviceMixin, Lock):
297
451
  gateway_class = FleetGatewayHandler
@@ -315,6 +469,7 @@ class TTLock(FleeDeviceMixin, Lock):
315
469
  ).publish()
316
470
 
317
471
  @classmethod
472
+ @atomic
318
473
  def _process_discovery(cls, started_with, data):
319
474
  if data['discovery-result'] == 'fail':
320
475
  if data['result'] == 0:
@@ -510,6 +665,7 @@ class DALIDevice(FleeDeviceMixin, ControllerBase):
510
665
  ).publish()
511
666
 
512
667
  @classmethod
668
+ @atomic
513
669
  def _process_discovery(cls, started_with, data):
514
670
  if data['discovery-result'] == 'fail':
515
671
  if data['result'] == 1:
@@ -545,6 +701,8 @@ class DALIDevice(FleeDeviceMixin, ControllerBase):
545
701
 
546
702
  started_with = deserialize_form_data(started_with)
547
703
  started_with['name'] += f" {data['result']['config']['da']}"
704
+ if data['result'].get('di') is not None:
705
+ started_with['name'] += f" - {data['result']['di']}"
548
706
  started_with['controller_uid'] = controller_uid
549
707
  started_with['base_type'] = controller_cls.base_type
550
708
  form = controller_cls.config_form(
@@ -566,10 +724,6 @@ class DALIDevice(FleeDeviceMixin, ControllerBase):
566
724
  'config': json.loads(json.dumps(new_component.config))
567
725
  }
568
726
  }
569
- # Perform default config update on initial component setup
570
- new_component.meta[
571
- 'finalization_data'
572
- ]['comp_config']['config']['boot_update'] = True
573
727
  new_component.save()
574
728
  GatewayObjectCommand(
575
729
  new_component.gateway, Colonel(
@@ -595,14 +749,14 @@ class DALIDevice(FleeDeviceMixin, ControllerBase):
595
749
  ).publish()
596
750
 
597
751
 
598
- class DALILamp(BaseDimmer, DALIDevice):
752
+ class DALILamp(FadeMixin, BaseDimmer, DALIDevice):
599
753
  family = 'dali'
600
754
  manual_add = False
601
755
  name = 'DALI Lamp'
602
756
  config_form = DaliLampForm
603
757
 
604
758
 
605
- class DALIGearGroup(FleeDeviceMixin, BaseDimmer):
759
+ class DALIGearGroup(FadeMixin, FleeDeviceMixin, BaseDimmer):
606
760
  gateway_class = FleetGatewayHandler
607
761
  family = 'dali'
608
762
  manual_add = True
@@ -632,9 +786,11 @@ class DALIGearGroup(FleeDeviceMixin, BaseDimmer):
632
786
 
633
787
 
634
788
  class DALIRelay(BaseSwitch, DALIDevice):
789
+ '''Not tested with a real device yet'''
635
790
  family = 'dali'
636
791
  manual_add = False
637
792
  name = 'DALI Relay'
793
+ config_form = DaliSwitchConfigForm
638
794
 
639
795
 
640
796
  class DALIOccupancySensor(BaseBinarySensor, DALIDevice):
@@ -650,3 +806,10 @@ class DALILightSensor(BaseNumericSensor, DALIDevice):
650
806
  name = 'DALI Light Sensor'
651
807
  default_value_units = 'lux'
652
808
  config_form = DALILightSensorConfigForm
809
+
810
+
811
+ class DALIButton(BaseButton, DALIDevice):
812
+ family = 'dali'
813
+ manual_add = False
814
+ name = 'DALI Button'
815
+ config_form = DALIButtonConfigForm