simo 2.0.42__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.
- simo/__pycache__/asgi.cpython-38.pyc +0 -0
- simo/__pycache__/on_http_start.cpython-38.pyc +0 -0
- simo/__pycache__/settings.cpython-38.pyc +0 -0
- simo/__pycache__/wsgi.cpython-38.pyc +0 -0
- simo/core/__init__.py +1 -0
- simo/core/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/__pycache__/api.cpython-38.pyc +0 -0
- simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
- simo/core/__pycache__/app_widgets.cpython-38.pyc +0 -0
- simo/core/__pycache__/apps.cpython-38.pyc +0 -0
- simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/core/__pycache__/form_fields.cpython-38.pyc +0 -0
- simo/core/__pycache__/forms.cpython-38.pyc +0 -0
- simo/core/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/core/__pycache__/managers.cpython-38.pyc +0 -0
- simo/core/__pycache__/models.cpython-38.pyc +0 -0
- simo/core/__pycache__/permissions.cpython-38.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
- simo/core/admin.py +28 -10
- simo/core/api.py +21 -2
- simo/core/api_meta.py +23 -13
- simo/core/app_widgets.py +6 -0
- simo/core/apps.py +10 -0
- simo/core/base_types.py +1 -0
- simo/core/controllers.py +57 -0
- simo/core/form_fields.py +93 -0
- simo/core/forms.py +15 -3
- simo/core/gateways.py +1 -1
- simo/core/managers.py +14 -1
- simo/core/migrations/0037_auto_20240606_1057.py +33 -0
- simo/core/migrations/__pycache__/0037_auto_20240606_1057.cpython-38.pyc +0 -0
- simo/core/models.py +28 -9
- simo/core/permissions.py +6 -3
- simo/core/serializers.py +77 -5
- simo/core/signal_receivers.py +25 -0
- simo/core/static/admin/css/simo.css +14 -0
- simo/core/templates/admin/controller_widgets/button.html +8 -0
- simo/core/templates/admin/core/component_change_form.html +97 -0
- simo/core/templates/admin/formset_widget.html +88 -118
- simo/core/templates/admin/formset_widget_old.html +122 -0
- simo/core/templates/admin/wizard/wizard_add.html +16 -9
- simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/cache.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
- simo/core/utils/admin.py +11 -0
- simo/core/utils/cache.py +15 -0
- simo/core/utils/formsets.py +11 -18
- simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
- simo/fleet/auto_urls.py +7 -1
- simo/fleet/controllers.py +193 -30
- simo/fleet/forms.py +223 -87
- simo/fleet/gateways.py +53 -2
- simo/fleet/migrations/0036_auto_20240605_0702.py +68 -0
- simo/fleet/migrations/__pycache__/0036_auto_20240605_0702.cpython-38.pyc +0 -0
- simo/fleet/models.py +35 -6
- simo/fleet/socket_consumers.py +1 -1
- simo/fleet/templates/fleet/controllers_info/button.md +16 -0
- simo/fleet/utils.py +31 -1
- simo/fleet/views.py +45 -0
- simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/generic/controllers.py +59 -14
- simo/generic/gateways.py +2 -0
- simo/generic/templates/admin/controller_widgets/blinds.html +2 -1
- simo/generic/templates/generic/controllers_info/dummy.md +3 -0
- simo/generic/templates/generic/controllers_info/stateselect.md +2 -0
- simo/settings.py +20 -4
- simo/users/__init__.py +1 -0
- simo/users/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/users/__pycache__/admin.cpython-38.pyc +0 -0
- simo/users/__pycache__/apps.cpython-38.pyc +0 -0
- simo/users/__pycache__/models.cpython-38.pyc +0 -0
- simo/users/apps.py +9 -0
- simo/users/migrations/__pycache__/0029_alter_instanceuser_options_instanceuser_order.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0030_alter_instanceuser_options_remove_instanceuser_order.cpython-38.pyc +0 -0
- simo/users/models.py +16 -3
- {simo-2.0.42.dist-info → simo-2.1.0.dist-info}/METADATA +5 -3
- {simo-2.0.42.dist-info → simo-2.1.0.dist-info}/RECORD +91 -72
- simo/wsgi.py +0 -7
- {simo-2.0.42.dist-info → simo-2.1.0.dist-info}/LICENSE.md +0 -0
- {simo-2.0.42.dist-info → simo-2.1.0.dist-info}/WHEEL +0 -0
- {simo-2.0.42.dist-info → simo-2.1.0.dist-info}/top_level.txt +0 -0
simo/fleet/forms.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import time
|
|
1
2
|
from django import forms
|
|
2
3
|
from django.utils.translation import gettext_lazy as _
|
|
3
4
|
from django.forms import formset_factory
|
|
@@ -15,8 +16,9 @@ from simo.core.utils.easing import EASING_CHOICES
|
|
|
15
16
|
from simo.core.utils.validators import validate_slaves
|
|
16
17
|
from simo.core.utils.admin import AdminFormActionForm
|
|
17
18
|
from simo.core.events import GatewayObjectCommand
|
|
19
|
+
from simo.core.form_fields import Select2ModelChoiceField, Select2ListChoiceField
|
|
18
20
|
from .models import Colonel, ColonelPin, Interface
|
|
19
|
-
from .utils import INTERFACES_PINS_MAP
|
|
21
|
+
from .utils import INTERFACES_PINS_MAP, get_all_control_input_choices
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
class ColonelPinChoiceField(forms.ModelChoiceField):
|
|
@@ -122,12 +124,17 @@ class ColonelComponentForm(BaseComponentForm):
|
|
|
122
124
|
updated_vals = {}
|
|
123
125
|
for key, val in control.items():
|
|
124
126
|
updated_vals[key] = val
|
|
125
|
-
if key == '
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
if key == 'input':
|
|
128
|
+
if val.startswith('pin'):
|
|
129
|
+
pin = ColonelPin.objects.get(
|
|
130
|
+
id=int(self.cleaned_data['controls'][i]['input'][4:])
|
|
131
|
+
)
|
|
132
|
+
pin_instances[i] = pin
|
|
133
|
+
updated_vals['pin_no'] = pin.no
|
|
134
|
+
elif val.startswith('button'):
|
|
135
|
+
updated_vals['button'] = int(
|
|
136
|
+
self.cleaned_data['controls'][i]['input'][7:]
|
|
137
|
+
)
|
|
131
138
|
elif key == 'touch_threshold':
|
|
132
139
|
updated_vals[key] = int(val)
|
|
133
140
|
self.cleaned_data['controls'][i] = updated_vals
|
|
@@ -135,6 +142,8 @@ class ColonelComponentForm(BaseComponentForm):
|
|
|
135
142
|
pins_in_use = []
|
|
136
143
|
formset_errors = {}
|
|
137
144
|
for i, control in enumerate(self.cleaned_data['controls']):
|
|
145
|
+
if not control['input'].startswith('pin'):
|
|
146
|
+
continue
|
|
138
147
|
if pin_instances[i].colonel != self.cleaned_data['colonel']:
|
|
139
148
|
formset_errors[i] = {
|
|
140
149
|
'pin': f"{pin_instances[i]} must be from the same Colonel!"
|
|
@@ -159,44 +168,27 @@ class ColonelComponentForm(BaseComponentForm):
|
|
|
159
168
|
if 'controls' in self.cleaned_data:
|
|
160
169
|
del self.cleaned_data['controls']
|
|
161
170
|
|
|
162
|
-
def save(self, commit=True, update_colonel=True):
|
|
163
|
-
obj = super().save(commit)
|
|
164
|
-
if commit and 'colonel' in self.cleaned_data and update_colonel:
|
|
165
|
-
self.cleaned_data['colonel'].components.add(obj)
|
|
166
|
-
self.cleaned_data['colonel'].rebuild_occupied_pins()
|
|
167
|
-
self.cleaned_data['colonel'].save()
|
|
168
|
-
self.cleaned_data['colonel'].update_config()
|
|
169
|
-
return obj
|
|
170
|
-
|
|
171
171
|
|
|
172
|
-
class
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
forward
|
|
178
|
-
|
|
179
|
-
forward.Field('colonel'),
|
|
180
|
-
forward.Const({'input': True}, 'filters')
|
|
181
|
-
]
|
|
182
|
-
)
|
|
172
|
+
class ControlForm(forms.Form):
|
|
173
|
+
input = Select2ListChoiceField(
|
|
174
|
+
choices=get_all_control_input_choices,
|
|
175
|
+
url='autocomplete-control_inputs', forward=[
|
|
176
|
+
forward.Self(), forward.Field('colonel'),
|
|
177
|
+
forward.Const({'input': True}, 'pin_filters')
|
|
178
|
+
]
|
|
183
179
|
)
|
|
184
|
-
active = forms.ChoiceField(required=True, choices=(
|
|
185
|
-
('LOW', 'LOW'), ('HIGH', 'HIGH')
|
|
186
|
-
), initial='LOW')
|
|
187
180
|
method = forms.ChoiceField(
|
|
181
|
+
label="Button type",
|
|
188
182
|
required=True, choices=(
|
|
189
183
|
('momentary', "Momentary"), ('toggle', "Toggle"),
|
|
190
184
|
),
|
|
191
185
|
)
|
|
192
|
-
debounce = forms.IntegerField(
|
|
193
|
-
required=False, help_text="Add debounce value in ms (10 - 200ms)"
|
|
194
|
-
)
|
|
195
186
|
prefix = 'controls'
|
|
196
187
|
|
|
197
188
|
|
|
198
189
|
class ColonelBinarySensorConfigForm(ColonelComponentForm):
|
|
199
190
|
pin = ColonelPinChoiceField(
|
|
191
|
+
label='Port',
|
|
200
192
|
queryset=ColonelPin.objects.filter(input=True),
|
|
201
193
|
widget=autocomplete.ListSelect2(
|
|
202
194
|
url='autocomplete-colonel-pins',
|
|
@@ -283,8 +275,42 @@ class ColonelBinarySensorConfigForm(ColonelComponentForm):
|
|
|
283
275
|
return super().save(commit=commit)
|
|
284
276
|
|
|
285
277
|
|
|
278
|
+
class ColonelButtonConfigForm(ColonelComponentForm):
|
|
279
|
+
pin = ColonelPinChoiceField(
|
|
280
|
+
label="Port",
|
|
281
|
+
queryset=ColonelPin.objects.filter(input=True),
|
|
282
|
+
widget=autocomplete.ListSelect2(
|
|
283
|
+
url='autocomplete-colonel-pins',
|
|
284
|
+
forward=[
|
|
285
|
+
forward.Self(),
|
|
286
|
+
forward.Field('colonel'),
|
|
287
|
+
forward.Const({'input': True}, 'filters')
|
|
288
|
+
]
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
def __init__(self, *args, **kwargs):
|
|
293
|
+
super().__init__(*args, **kwargs)
|
|
294
|
+
|
|
295
|
+
def clean(self):
|
|
296
|
+
super().clean()
|
|
297
|
+
if 'colonel' not in self.cleaned_data:
|
|
298
|
+
return self.cleaned_data
|
|
299
|
+
if 'pin' not in self.cleaned_data:
|
|
300
|
+
return self.cleaned_data
|
|
301
|
+
self._clean_pin('pin')
|
|
302
|
+
return self.cleaned_data
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def save(self, commit=True):
|
|
306
|
+
if 'pin' in self.cleaned_data:
|
|
307
|
+
self.instance.config['pin_no'] = self.cleaned_data['pin'].no
|
|
308
|
+
return super().save(commit=commit)
|
|
309
|
+
|
|
310
|
+
|
|
286
311
|
class ColonelNumericSensorConfigForm(ColonelComponentForm, NumericSensorForm):
|
|
287
312
|
pin = ColonelPinChoiceField(
|
|
313
|
+
label="Port",
|
|
288
314
|
queryset=ColonelPin.objects.filter(adc=True, input=True, native=True),
|
|
289
315
|
widget=autocomplete.ListSelect2(
|
|
290
316
|
url='autocomplete-colonel-pins',
|
|
@@ -337,6 +363,7 @@ class ColonelNumericSensorConfigForm(ColonelComponentForm, NumericSensorForm):
|
|
|
337
363
|
|
|
338
364
|
class DS18B20SensorConfigForm(ColonelComponentForm, NumericSensorForm):
|
|
339
365
|
pin = ColonelPinChoiceField(
|
|
366
|
+
label="Port",
|
|
340
367
|
queryset=ColonelPin.objects.filter(input=True, native=True),
|
|
341
368
|
widget=autocomplete.ListSelect2(
|
|
342
369
|
url='autocomplete-colonel-pins',
|
|
@@ -376,6 +403,7 @@ class DS18B20SensorConfigForm(ColonelComponentForm, NumericSensorForm):
|
|
|
376
403
|
|
|
377
404
|
class ColonelDHTSensorConfigForm(ColonelComponentForm):
|
|
378
405
|
pin = ColonelPinChoiceField(
|
|
406
|
+
label="Port",
|
|
379
407
|
queryset=ColonelPin.objects.filter(input=True, native=True),
|
|
380
408
|
widget=autocomplete.ListSelect2(
|
|
381
409
|
url='autocomplete-colonel-pins',
|
|
@@ -506,6 +534,7 @@ class MPC9808SensorConfigForm(ColonelComponentForm):
|
|
|
506
534
|
|
|
507
535
|
class ColonelTouchSensorConfigForm(ColonelComponentForm):
|
|
508
536
|
pin = ColonelPinChoiceField(
|
|
537
|
+
label="Port",
|
|
509
538
|
queryset=ColonelPin.objects.filter(input=True, capacitive=True),
|
|
510
539
|
widget=autocomplete.ListSelect2(
|
|
511
540
|
url='autocomplete-colonel-pins',
|
|
@@ -544,16 +573,15 @@ class ColonelTouchSensorConfigForm(ColonelComponentForm):
|
|
|
544
573
|
|
|
545
574
|
|
|
546
575
|
class ColonelSwitchConfigForm(ColonelComponentForm):
|
|
547
|
-
output_pin =
|
|
576
|
+
output_pin = Select2ModelChoiceField(
|
|
577
|
+
label="Port",
|
|
548
578
|
queryset=ColonelPin.objects.filter(output=True),
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
forward
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
]
|
|
556
|
-
)
|
|
579
|
+
url='autocomplete-colonel-pins',
|
|
580
|
+
forward=[
|
|
581
|
+
forward.Self(),
|
|
582
|
+
forward.Field('colonel'),
|
|
583
|
+
forward.Const({'output': True}, 'filters')
|
|
584
|
+
]
|
|
557
585
|
)
|
|
558
586
|
auto_off = forms.FloatField(
|
|
559
587
|
required=False, min_value=0.01, max_value=1000000000,
|
|
@@ -578,7 +606,7 @@ class ColonelSwitchConfigForm(ColonelComponentForm):
|
|
|
578
606
|
|
|
579
607
|
controls = FormsetField(
|
|
580
608
|
formset_factory(
|
|
581
|
-
|
|
609
|
+
ControlForm, can_delete=True, can_order=True, extra=0, max_num=10
|
|
582
610
|
)
|
|
583
611
|
)
|
|
584
612
|
|
|
@@ -605,7 +633,9 @@ class ColonelSwitchConfigForm(ColonelComponentForm):
|
|
|
605
633
|
|
|
606
634
|
if self.cleaned_data.get('output_pin') and self.cleaned_data.get('controls'):
|
|
607
635
|
for ctrl in self.cleaned_data['controls']:
|
|
608
|
-
if ctrl['
|
|
636
|
+
if not ctrl['input'].startswith('pin'):
|
|
637
|
+
continue
|
|
638
|
+
if int(ctrl['input'][4:]) == self.cleaned_data['output_pin'].id:
|
|
609
639
|
self.add_error(
|
|
610
640
|
"output_pin",
|
|
611
641
|
"Can't be used as control pin at the same time!"
|
|
@@ -613,18 +643,22 @@ class ColonelSwitchConfigForm(ColonelComponentForm):
|
|
|
613
643
|
|
|
614
644
|
return self.cleaned_data
|
|
615
645
|
|
|
616
|
-
|
|
617
646
|
def save(self, commit=True):
|
|
618
647
|
if 'output_pin' in self.cleaned_data:
|
|
619
648
|
self.instance.config['output_pin_no'] = self.cleaned_data['output_pin'].no
|
|
620
649
|
obj = super().save(commit=commit)
|
|
621
650
|
if commit and 'slaves' in self.cleaned_data:
|
|
622
651
|
obj.slaves.set(self.cleaned_data['slaves'])
|
|
652
|
+
if commit and self.cleaned_data.get('controls'):
|
|
653
|
+
GatewayObjectCommand(
|
|
654
|
+
self.instance.gateway, obj, command='watch_buttons'
|
|
655
|
+
).publish()
|
|
623
656
|
return obj
|
|
624
657
|
|
|
625
658
|
|
|
626
659
|
class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
627
660
|
output_pin = ColonelPinChoiceField(
|
|
661
|
+
label="Port",
|
|
628
662
|
queryset=ColonelPin.objects.filter(output=True),
|
|
629
663
|
widget=autocomplete.ListSelect2(
|
|
630
664
|
url='autocomplete-colonel-pins',
|
|
@@ -671,7 +705,7 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
671
705
|
)
|
|
672
706
|
on_value = forms.FloatField(
|
|
673
707
|
required=True, initial=100,
|
|
674
|
-
help_text="
|
|
708
|
+
help_text="ON value when used with toggle switch"
|
|
675
709
|
)
|
|
676
710
|
|
|
677
711
|
slaves = forms.ModelMultipleChoiceField(
|
|
@@ -686,7 +720,7 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
686
720
|
)
|
|
687
721
|
controls = FormsetField(
|
|
688
722
|
formset_factory(
|
|
689
|
-
|
|
723
|
+
ControlForm, can_delete=True, can_order=True, extra=0, max_num=10
|
|
690
724
|
)
|
|
691
725
|
)
|
|
692
726
|
|
|
@@ -711,9 +745,12 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
711
745
|
self._clean_pin('output_pin')
|
|
712
746
|
if 'controls' in self.cleaned_data:
|
|
713
747
|
self._clean_controls()
|
|
748
|
+
|
|
714
749
|
if self.cleaned_data.get('output_pin') and self.cleaned_data.get('controls'):
|
|
715
750
|
for ctrl in self.cleaned_data['controls']:
|
|
716
|
-
if ctrl['
|
|
751
|
+
if not ctrl['input'].startswith('pin'):
|
|
752
|
+
continue
|
|
753
|
+
if int(ctrl['input'][4:]) == self.cleaned_data['output_pin'].id:
|
|
717
754
|
self.add_error(
|
|
718
755
|
"output_pin",
|
|
719
756
|
"Can't be used as control pin at the same time!"
|
|
@@ -737,7 +774,7 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
737
774
|
if old.config.get('controls') != self.cleaned_data.get('controls'):
|
|
738
775
|
update_colonel = True
|
|
739
776
|
|
|
740
|
-
obj = super().save(commit=commit
|
|
777
|
+
obj = super().save(commit=commit)
|
|
741
778
|
if commit and 'slaves' in self.cleaned_data:
|
|
742
779
|
obj.slaves.set(self.cleaned_data['slaves'])
|
|
743
780
|
if not update_colonel:
|
|
@@ -747,11 +784,16 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
747
784
|
obj.controller._get_colonel_config()
|
|
748
785
|
]
|
|
749
786
|
).publish()
|
|
787
|
+
if commit and self.cleaned_data.get('controls'):
|
|
788
|
+
GatewayObjectCommand(
|
|
789
|
+
self.instance.gateway, obj, command='watch_buttons'
|
|
790
|
+
).publish()
|
|
750
791
|
return obj
|
|
751
792
|
|
|
752
793
|
|
|
753
794
|
class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
754
795
|
output_pin = ColonelPinChoiceField(
|
|
796
|
+
label="Port",
|
|
755
797
|
queryset=ColonelPin.objects.filter(output=True, native=True),
|
|
756
798
|
widget=autocomplete.ListSelect2(
|
|
757
799
|
url='autocomplete-colonel-pins',
|
|
@@ -800,7 +842,7 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
|
800
842
|
)
|
|
801
843
|
controls = FormsetField(
|
|
802
844
|
formset_factory(
|
|
803
|
-
|
|
845
|
+
ControlForm, can_delete=True, can_order=True, extra=0, max_num=2
|
|
804
846
|
)
|
|
805
847
|
)
|
|
806
848
|
|
|
@@ -808,7 +850,12 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
|
808
850
|
if 'output_pin' in self.cleaned_data:
|
|
809
851
|
self.instance.config['output_pin_no'] = \
|
|
810
852
|
self.cleaned_data['output_pin'].no
|
|
811
|
-
|
|
853
|
+
obj = super().save(commit)
|
|
854
|
+
if commit and self.cleaned_data.get('controls'):
|
|
855
|
+
GatewayObjectCommand(
|
|
856
|
+
self.instance.gateway, obj, command='watch_buttons'
|
|
857
|
+
).publish()
|
|
858
|
+
return obj
|
|
812
859
|
|
|
813
860
|
def clean_custom_timing(self):
|
|
814
861
|
custom_timing = self.cleaned_data.get('custom_timing')
|
|
@@ -840,7 +887,9 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
|
840
887
|
|
|
841
888
|
if self.cleaned_data.get('output_pin') and self.cleaned_data.get('controls'):
|
|
842
889
|
for ctrl in self.cleaned_data['controls']:
|
|
843
|
-
if ctrl['
|
|
890
|
+
if not ctrl['input'].startswith('pin'):
|
|
891
|
+
continue
|
|
892
|
+
if int(ctrl['input'][4:]) == self.cleaned_data['output_pin'].id:
|
|
844
893
|
self.add_error(
|
|
845
894
|
"output_pin",
|
|
846
895
|
"Can't be used as control pin at the same time!"
|
|
@@ -867,6 +916,7 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
|
867
916
|
|
|
868
917
|
class DualMotorValveForm(ColonelComponentForm):
|
|
869
918
|
open_pin = ColonelPinChoiceField(
|
|
919
|
+
label="Open Relay Port",
|
|
870
920
|
queryset=ColonelPin.objects.filter(output=True),
|
|
871
921
|
widget=autocomplete.ListSelect2(
|
|
872
922
|
url='autocomplete-colonel-pins',
|
|
@@ -885,6 +935,7 @@ class DualMotorValveForm(ColonelComponentForm):
|
|
|
885
935
|
initial=2, help_text="Time in seconds to open."
|
|
886
936
|
)
|
|
887
937
|
close_pin = ColonelPinChoiceField(
|
|
938
|
+
label="Close Relay Port",
|
|
888
939
|
queryset=ColonelPin.objects.filter(output=True),
|
|
889
940
|
widget=autocomplete.ListSelect2(
|
|
890
941
|
url='autocomplete-colonel-pins',
|
|
@@ -925,11 +976,13 @@ class DualMotorValveForm(ColonelComponentForm):
|
|
|
925
976
|
if 'close_pin' in self.cleaned_data:
|
|
926
977
|
self.instance.config['close_pin_no'] = \
|
|
927
978
|
self.cleaned_data['close_pin'].no
|
|
928
|
-
|
|
979
|
+
obj = super().save(commit=commit)
|
|
980
|
+
return obj
|
|
929
981
|
|
|
930
982
|
|
|
931
983
|
class BlindsConfigForm(ColonelComponentForm):
|
|
932
984
|
open_pin = ColonelPinChoiceField(
|
|
985
|
+
label="Open Relay Port",
|
|
933
986
|
queryset=ColonelPin.objects.filter(output=True),
|
|
934
987
|
widget=autocomplete.ListSelect2(
|
|
935
988
|
url='autocomplete-colonel-pins',
|
|
@@ -944,6 +997,7 @@ class BlindsConfigForm(ColonelComponentForm):
|
|
|
944
997
|
choices=(('HIGH', "HIGH"), ('LOW', "LOW")),
|
|
945
998
|
)
|
|
946
999
|
close_pin = ColonelPinChoiceField(
|
|
1000
|
+
label="Close Relay Port",
|
|
947
1001
|
queryset=ColonelPin.objects.filter(output=True),
|
|
948
1002
|
widget=autocomplete.ListSelect2(
|
|
949
1003
|
url='autocomplete-colonel-pins',
|
|
@@ -996,7 +1050,7 @@ class BlindsConfigForm(ColonelComponentForm):
|
|
|
996
1050
|
)
|
|
997
1051
|
controls = FormsetField(
|
|
998
1052
|
formset_factory(
|
|
999
|
-
|
|
1053
|
+
ControlForm, can_delete=True, can_order=True, extra=0, max_num=2
|
|
1000
1054
|
)
|
|
1001
1055
|
)
|
|
1002
1056
|
|
|
@@ -1016,8 +1070,8 @@ class BlindsConfigForm(ColonelComponentForm):
|
|
|
1016
1070
|
self._clean_pin('close_pin')
|
|
1017
1071
|
|
|
1018
1072
|
if 'controls' in self.cleaned_data:
|
|
1019
|
-
if len(self.cleaned_data['controls']) not in (0, 2):
|
|
1020
|
-
self.add_error('controls', "Must have 0 or 2 controls")
|
|
1073
|
+
if len(self.cleaned_data['controls']) not in (0, 1, 2):
|
|
1074
|
+
self.add_error('controls', "Must have 0, 1 or 2 controls")
|
|
1021
1075
|
return self.cleaned_data
|
|
1022
1076
|
|
|
1023
1077
|
if len(self.cleaned_data['controls']) == 2:
|
|
@@ -1032,22 +1086,25 @@ class BlindsConfigForm(ColonelComponentForm):
|
|
|
1032
1086
|
|
|
1033
1087
|
self._clean_controls()
|
|
1034
1088
|
|
|
1035
|
-
if self.cleaned_data.get('open_pin'):
|
|
1089
|
+
if self.cleaned_data.get('open_pin') and self.cleaned_data.get('controls'):
|
|
1036
1090
|
for ctrl in self.cleaned_data['controls']:
|
|
1037
|
-
if ctrl['
|
|
1091
|
+
if not ctrl['input'].startswith('pin'):
|
|
1092
|
+
continue
|
|
1093
|
+
if int(ctrl['input'][4:]) == self.cleaned_data['open_pin'].id:
|
|
1038
1094
|
self.add_error(
|
|
1039
1095
|
"open_pin",
|
|
1040
1096
|
"Can't be used as control pin at the same time!"
|
|
1041
1097
|
)
|
|
1042
1098
|
|
|
1043
|
-
if self.cleaned_data.get('close_pin'):
|
|
1099
|
+
if self.cleaned_data.get('close_pin') and self.cleaned_data.get('controls'):
|
|
1044
1100
|
for ctrl in self.cleaned_data['controls']:
|
|
1045
|
-
if ctrl['
|
|
1101
|
+
if not ctrl['input'].startswith('pin'):
|
|
1102
|
+
continue
|
|
1103
|
+
if int(ctrl['input'][4:]) == self.cleaned_data['close_pin'].id:
|
|
1046
1104
|
self.add_error(
|
|
1047
1105
|
"close_pin",
|
|
1048
1106
|
"Can't be used as control pin at the same time!"
|
|
1049
1107
|
)
|
|
1050
|
-
|
|
1051
1108
|
return self.cleaned_data
|
|
1052
1109
|
|
|
1053
1110
|
def save(self, commit=True):
|
|
@@ -1057,11 +1114,17 @@ class BlindsConfigForm(ColonelComponentForm):
|
|
|
1057
1114
|
if 'close_pin' in self.cleaned_data:
|
|
1058
1115
|
self.instance.config['close_pin_no'] = \
|
|
1059
1116
|
self.cleaned_data['close_pin'].no
|
|
1060
|
-
|
|
1117
|
+
obj = super().save(commit=commit)
|
|
1118
|
+
if commit and self.cleaned_data.get('controls'):
|
|
1119
|
+
GatewayObjectCommand(
|
|
1120
|
+
self.instance.gateway, obj, command='watch_buttons'
|
|
1121
|
+
).publish()
|
|
1122
|
+
return obj
|
|
1061
1123
|
|
|
1062
1124
|
|
|
1063
1125
|
class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
|
|
1064
1126
|
power_pin = ColonelPinChoiceField(
|
|
1127
|
+
label="Port",
|
|
1065
1128
|
queryset=ColonelPin.objects.filter(output=True),
|
|
1066
1129
|
widget=autocomplete.ListSelect2(
|
|
1067
1130
|
url='autocomplete-colonel-pins',
|
|
@@ -1073,6 +1136,7 @@ class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
|
|
|
1073
1136
|
)
|
|
1074
1137
|
)
|
|
1075
1138
|
sensor_pin = ColonelPinChoiceField(
|
|
1139
|
+
label="Port",
|
|
1076
1140
|
queryset=ColonelPin.objects.filter(input=True),
|
|
1077
1141
|
widget=autocomplete.ListSelect2(
|
|
1078
1142
|
url='autocomplete-colonel-pins',
|
|
@@ -1144,8 +1208,6 @@ class TTLockConfigForm(ColonelComponentForm):
|
|
|
1144
1208
|
|
|
1145
1209
|
def save(self, commit=True):
|
|
1146
1210
|
obj = super(ColonelComponentForm, self).save(commit)
|
|
1147
|
-
if commit:
|
|
1148
|
-
self.cleaned_data['colonel'].components.add(obj)
|
|
1149
1211
|
if commit and 'door_sensor' in self.cleaned_data:
|
|
1150
1212
|
GatewayObjectCommand(
|
|
1151
1213
|
self.instance.gateway, self.cleaned_data['door_sensor'],
|
|
@@ -1190,18 +1252,14 @@ class DALIDeviceConfigForm(ColonelComponentForm):
|
|
|
1190
1252
|
)
|
|
1191
1253
|
return self.cleaned_data
|
|
1192
1254
|
|
|
1193
|
-
def save(self, commit=True
|
|
1255
|
+
def save(self, commit=True):
|
|
1194
1256
|
if 'interface' in self.cleaned_data:
|
|
1195
1257
|
self.instance.config['dali_interface'] = \
|
|
1196
1258
|
self.cleaned_data['interface'].no
|
|
1197
|
-
|
|
1198
|
-
# prevent immediate config update on colonel as dali devices are
|
|
1199
|
-
# added via pairing process, which uses finalization procedure.
|
|
1200
1259
|
is_new = not self.instance.pk
|
|
1201
|
-
obj = super(
|
|
1260
|
+
obj = super().save(commit=commit)
|
|
1202
1261
|
if commit:
|
|
1203
|
-
|
|
1204
|
-
if not is_new and update_colonel_config:
|
|
1262
|
+
if not is_new:
|
|
1205
1263
|
GatewayObjectCommand(
|
|
1206
1264
|
obj.gateway, self.cleaned_data['colonel'], id=obj.id,
|
|
1207
1265
|
command='call', method='update_config', args=[
|
|
@@ -1212,12 +1270,9 @@ class DALIDeviceConfigForm(ColonelComponentForm):
|
|
|
1212
1270
|
|
|
1213
1271
|
|
|
1214
1272
|
class DaliLampForm(DALIDeviceConfigForm, BaseComponentForm):
|
|
1215
|
-
boot_update = forms.BooleanField(
|
|
1216
|
-
initial=False, required=False,
|
|
1217
|
-
help_text="Update device config on colonel boot."
|
|
1218
|
-
)
|
|
1219
1273
|
fade_time = forms.TypedChoiceField(
|
|
1220
|
-
initial=
|
|
1274
|
+
initial=1, coerce=int, choices=(
|
|
1275
|
+
(0, "Instant"),
|
|
1221
1276
|
(1, "0.7 s"), (2, "1.0 s"), (3, "1.4 s"), (4, "2.0 s"), (5, "2.8 s"),
|
|
1222
1277
|
(6, "4.0 s"), (7, "5.7 s"), (8, "8.0 s")
|
|
1223
1278
|
)
|
|
@@ -1232,11 +1287,73 @@ class DaliLampForm(DALIDeviceConfigForm, BaseComponentForm):
|
|
|
1232
1287
|
"beings dying out when you hit it with lower value than it "
|
|
1233
1288
|
"is capable of supplying."
|
|
1234
1289
|
)
|
|
1290
|
+
on_value = forms.FloatField(
|
|
1291
|
+
required=True, initial=100,
|
|
1292
|
+
help_text="ON value when used with toggle switch"
|
|
1293
|
+
)
|
|
1235
1294
|
auto_off = forms.FloatField(
|
|
1236
1295
|
required=False, min_value=0.01, max_value=1000000000,
|
|
1237
1296
|
help_text="If provided, lamp will be turned off after "
|
|
1238
1297
|
"given amount of seconds after last turn on event."
|
|
1239
1298
|
)
|
|
1299
|
+
controls = FormsetField(
|
|
1300
|
+
formset_factory(
|
|
1301
|
+
ControlForm, can_delete=True, can_order=True, extra=0, max_num=999
|
|
1302
|
+
)
|
|
1303
|
+
)
|
|
1304
|
+
|
|
1305
|
+
def clean(self):
|
|
1306
|
+
if 'controls' in self.cleaned_data:
|
|
1307
|
+
self._clean_controls()
|
|
1308
|
+
return self.cleaned_data
|
|
1309
|
+
|
|
1310
|
+
def save(self, commit=True):
|
|
1311
|
+
obj = super().save(commit=commit)
|
|
1312
|
+
if commit:
|
|
1313
|
+
if self.cleaned_data.get('controls'):
|
|
1314
|
+
GatewayObjectCommand(
|
|
1315
|
+
self.instance.gateway, obj, command='watch_buttons'
|
|
1316
|
+
).publish()
|
|
1317
|
+
if self.instance.pk:
|
|
1318
|
+
old_controls = Component.objects.get(
|
|
1319
|
+
pk=self.instance.pk
|
|
1320
|
+
).config.get('controls')
|
|
1321
|
+
if old_controls != self.cleaned_data.get('controls'):
|
|
1322
|
+
self.cleaned_data['colonel'].update_config()
|
|
1323
|
+
return obj
|
|
1324
|
+
|
|
1325
|
+
|
|
1326
|
+
class DaliSwitchConfigForm(DALIDeviceConfigForm, BaseComponentForm):
|
|
1327
|
+
auto_off = forms.FloatField(
|
|
1328
|
+
required=False, min_value=0.01, max_value=1000000000,
|
|
1329
|
+
help_text="If provided, lamp will be turned off after "
|
|
1330
|
+
"given amount of seconds after last turn on event."
|
|
1331
|
+
)
|
|
1332
|
+
controls = FormsetField(
|
|
1333
|
+
formset_factory(
|
|
1334
|
+
ControlForm, can_delete=True, can_order=True, extra=0, max_num=999
|
|
1335
|
+
)
|
|
1336
|
+
)
|
|
1337
|
+
|
|
1338
|
+
def clean(self):
|
|
1339
|
+
if 'controls' in self.cleaned_data:
|
|
1340
|
+
self._clean_controls()
|
|
1341
|
+
return self.cleaned_data
|
|
1342
|
+
|
|
1343
|
+
def save(self, commit=True):
|
|
1344
|
+
obj = super().save(commit=commit)
|
|
1345
|
+
if commit:
|
|
1346
|
+
if self.cleaned_data.get('controls'):
|
|
1347
|
+
GatewayObjectCommand(
|
|
1348
|
+
self.instance.gateway, obj, command='watch_buttons'
|
|
1349
|
+
).publish()
|
|
1350
|
+
if self.instance.pk:
|
|
1351
|
+
old_controls = Component.objects.get(
|
|
1352
|
+
pk=self.instance.pk
|
|
1353
|
+
).config.get('controls')
|
|
1354
|
+
if old_controls != self.cleaned_data.get('controls'):
|
|
1355
|
+
self.cleaned_data['colonel'].update_config()
|
|
1356
|
+
return obj
|
|
1240
1357
|
|
|
1241
1358
|
|
|
1242
1359
|
class DaliGearGroupForm(DALIDeviceConfigForm, BaseComponentForm):
|
|
@@ -1259,6 +1376,15 @@ class DaliGearGroupForm(DALIDeviceConfigForm, BaseComponentForm):
|
|
|
1259
1376
|
)
|
|
1260
1377
|
)
|
|
1261
1378
|
)
|
|
1379
|
+
on_value = forms.FloatField(
|
|
1380
|
+
required=True, initial=100,
|
|
1381
|
+
help_text="ON value when used with toggle switch"
|
|
1382
|
+
)
|
|
1383
|
+
controls = FormsetField(
|
|
1384
|
+
formset_factory(
|
|
1385
|
+
ControlForm, can_delete=True, can_order=True, extra=0, max_num=999
|
|
1386
|
+
)
|
|
1387
|
+
)
|
|
1262
1388
|
|
|
1263
1389
|
def clean(self):
|
|
1264
1390
|
if 'members' in self.cleaned_data:
|
|
@@ -1290,15 +1416,16 @@ class DaliGearGroupForm(DALIDeviceConfigForm, BaseComponentForm):
|
|
|
1290
1416
|
)
|
|
1291
1417
|
else:
|
|
1292
1418
|
self.group_addr = self.instance.config['da']
|
|
1419
|
+
if 'controls' in self.cleaned_data:
|
|
1420
|
+
self._clean_controls()
|
|
1293
1421
|
return self.cleaned_data
|
|
1294
1422
|
|
|
1295
|
-
def save(self, commit=True
|
|
1423
|
+
def save(self, commit=True):
|
|
1296
1424
|
old_members = self.instance.config.get('members', [])
|
|
1297
1425
|
self.instance.config['da'] = self.group_addr
|
|
1298
1426
|
is_new = not self.instance.pk
|
|
1299
|
-
obj = super().save(commit
|
|
1427
|
+
obj = super().save(commit)
|
|
1300
1428
|
if commit:
|
|
1301
|
-
self.cleaned_data['colonel'].components.add(obj)
|
|
1302
1429
|
new_members = obj.config.get('members', [])
|
|
1303
1430
|
for removed_member in Component.objects.filter(
|
|
1304
1431
|
id__in=set(old_members) - set(new_members)
|
|
@@ -1329,14 +1456,22 @@ class DaliGearGroupForm(DALIDeviceConfigForm, BaseComponentForm):
|
|
|
1329
1456
|
obj.controller._get_colonel_config()
|
|
1330
1457
|
]
|
|
1331
1458
|
).publish()
|
|
1459
|
+
|
|
1460
|
+
if commit:
|
|
1461
|
+
if self.cleaned_data.get('controls'):
|
|
1462
|
+
GatewayObjectCommand(
|
|
1463
|
+
self.instance.gateway, obj, command='watch_buttons'
|
|
1464
|
+
).publish()
|
|
1465
|
+
if self.instance.pk:
|
|
1466
|
+
old_controls = Component.objects.get(
|
|
1467
|
+
pk=self.instance.pk
|
|
1468
|
+
).config.get('controls')
|
|
1469
|
+
if old_controls != self.cleaned_data.get('controls'):
|
|
1470
|
+
self.cleaned_data['colonel'].update_config()
|
|
1332
1471
|
return obj
|
|
1333
1472
|
|
|
1334
1473
|
|
|
1335
1474
|
class DaliOccupancySensorConfigForm(DALIDeviceConfigForm, BaseComponentForm):
|
|
1336
|
-
boot_update = forms.BooleanField(
|
|
1337
|
-
initial=False, required=False,
|
|
1338
|
-
help_text="Update device config on colonel boot."
|
|
1339
|
-
)
|
|
1340
1475
|
hold_time = forms.TypedChoiceField(
|
|
1341
1476
|
initial=3, coerce=int, choices=(
|
|
1342
1477
|
(1, "10 s"), (2, "20 s"), (3, "30 s"), (4, "40 s"), (5, "50 s"),
|
|
@@ -1347,7 +1482,8 @@ class DaliOccupancySensorConfigForm(DALIDeviceConfigForm, BaseComponentForm):
|
|
|
1347
1482
|
|
|
1348
1483
|
|
|
1349
1484
|
class DALILightSensorConfigForm(DALIDeviceConfigForm, BaseComponentForm):
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1485
|
+
pass
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
class DALIButtonConfigForm(DALIDeviceConfigForm, BaseComponentForm):
|
|
1489
|
+
pass
|