simo 1.7.20__py3-none-any.whl → 2.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__/asgi.cpython-38.pyc +0 -0
- simo/__pycache__/settings.cpython-38.pyc +0 -0
- simo/__pycache__/urls.cpython-38.pyc +0 -0
- simo/__pycache__/wsgi.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__/auto_urls.cpython-38.pyc +0 -0
- simo/core/__pycache__/autocomplete_views.cpython-38.pyc +0 -0
- simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/core/__pycache__/context.cpython-38.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/core/__pycache__/events.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__/middleware.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/__pycache__/socket_consumers.cpython-38.pyc +0 -0
- simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
- simo/core/__pycache__/views.cpython-38.pyc +0 -0
- simo/core/admin.py +28 -18
- simo/core/api.py +157 -16
- simo/core/api_meta.py +87 -0
- simo/core/auto_urls.py +4 -1
- simo/core/autocomplete_views.py +8 -4
- simo/core/base_types.py +1 -0
- simo/core/context.py +3 -1
- simo/core/controllers.py +134 -36
- simo/core/db_backend/base.py +7 -22
- simo/core/drf_braces/README +3 -0
- simo/core/drf_braces/__init__.py +7 -0
- simo/core/drf_braces/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/drf_braces/__pycache__/utils.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__init__.py +5 -0
- simo/core/drf_braces/fields/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/_fields.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/custom.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/mixins.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/__pycache__/modified.cpython-38.pyc +0 -0
- simo/core/drf_braces/fields/_fields.py +48 -0
- simo/core/drf_braces/fields/custom.py +107 -0
- simo/core/drf_braces/fields/mixins.py +58 -0
- simo/core/drf_braces/fields/modified.py +41 -0
- simo/core/drf_braces/forms/__init__.py +0 -0
- simo/core/drf_braces/forms/fields.py +20 -0
- simo/core/drf_braces/forms/serializer_form.py +156 -0
- simo/core/drf_braces/mixins.py +52 -0
- simo/core/drf_braces/models.py +0 -0
- simo/core/drf_braces/parsers.py +72 -0
- simo/core/drf_braces/renderers.py +37 -0
- simo/core/drf_braces/serializers/__init__.py +0 -0
- simo/core/drf_braces/serializers/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/drf_braces/serializers/__pycache__/form_serializer.cpython-38.pyc +0 -0
- simo/core/drf_braces/serializers/enforce_validation_serializer.py +214 -0
- simo/core/drf_braces/serializers/form_serializer.py +391 -0
- simo/core/drf_braces/serializers/swapping.py +48 -0
- simo/core/drf_braces/tests/__init__.py +0 -0
- simo/core/drf_braces/tests/fields/__init__.py +0 -0
- simo/core/drf_braces/tests/fields/test_custom.py +94 -0
- simo/core/drf_braces/tests/fields/test_fields.py +13 -0
- simo/core/drf_braces/tests/fields/test_mixins.py +96 -0
- simo/core/drf_braces/tests/fields/test_modified.py +40 -0
- simo/core/drf_braces/tests/forms/__init__.py +0 -0
- simo/core/drf_braces/tests/forms/test_fields.py +46 -0
- simo/core/drf_braces/tests/forms/test_serializer_form.py +256 -0
- simo/core/drf_braces/tests/serializers/__init__.py +0 -0
- simo/core/drf_braces/tests/serializers/test_enforce_validation_serializer.py +169 -0
- simo/core/drf_braces/tests/serializers/test_form_serializer.py +387 -0
- simo/core/drf_braces/tests/serializers/test_swapping.py +40 -0
- simo/core/drf_braces/tests/test_mixins.py +111 -0
- simo/core/drf_braces/tests/test_parsers.py +73 -0
- simo/core/drf_braces/tests/test_renderers.py +23 -0
- simo/core/drf_braces/tests/test_utils.py +73 -0
- simo/core/drf_braces/utils.py +209 -0
- simo/core/events.py +3 -3
- simo/core/forms.py +79 -37
- simo/core/gateways.py +31 -14
- simo/core/management/commands/gateways_manager.py +0 -1
- simo/core/managers.py +81 -0
- simo/core/middleware.py +25 -0
- simo/core/migrations/0026_category_instance.py +20 -0
- simo/core/migrations/0027_remove_component_tags.py +17 -0
- simo/core/migrations/0028_rename_subcomponents_component_slaves.py +18 -0
- simo/core/migrations/0029_auto_20240229_1331.py +33 -0
- simo/core/migrations/__pycache__/0026_category_instance.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0027_remove_component_tags.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0028_rename_subcomponents_component_slaves.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0029_auto_20240229_1331.cpython-38.pyc +0 -0
- simo/core/models.py +103 -66
- simo/core/permissions.py +28 -2
- simo/core/serializers.py +330 -26
- simo/core/socket_consumers.py +5 -14
- simo/core/tasks.py +11 -1
- simo/core/templates/admin/base.html +37 -10
- simo/core/templates/admin/wizard/discovery.html +188 -0
- simo/core/templates/admin/wizard/wizard_add.html +5 -5
- simo/core/utils/__pycache__/serialization.cpython-38.pyc +0 -0
- simo/core/utils/admin.py +9 -2
- simo/core/utils/formsets.py +17 -16
- simo/core/utils/helpers.py +1 -0
- simo/core/utils/serialization.py +56 -0
- simo/core/utils/type_constants.py +1 -1
- simo/core/utils/validators.py +14 -1
- simo/core/views.py +13 -0
- simo/fleet/__pycache__/admin.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/api.cpython-38.pyc +0 -0
- 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__/managers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/serializers.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/admin.py +53 -29
- simo/fleet/api.py +59 -3
- simo/fleet/auto_urls.py +2 -3
- simo/fleet/controllers.py +199 -16
- simo/fleet/forms.py +325 -483
- simo/fleet/gateways.py +44 -2
- simo/fleet/managers.py +32 -0
- simo/fleet/migrations/0025_auto_20240130_1334.py +27 -0
- simo/fleet/migrations/0026_rename_i2cinterface_scl_pin_and_more.py +64 -0
- simo/fleet/migrations/0027_auto_20240306_0802.py +170 -0
- simo/fleet/migrations/0028_remove_i2cinterface_scl_pin_no_and_more.py +21 -0
- simo/fleet/migrations/0029_alter_i2cinterface_scl_pin_and_more.py +24 -0
- simo/fleet/migrations/0030_colonelpin_label_alter_colonel_type.py +24 -0
- simo/fleet/migrations/0031_alter_colonel_type.py +18 -0
- simo/fleet/migrations/__pycache__/0025_auto_20240130_1334.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0026_rename_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0027_auto_20240306_0802.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0028_remove_i2cinterface_scl_pin_no_and_more.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0029_alter_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0030_colonelpin_label_alter_colonel_type.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0031_alter_colonel_type.cpython-38.pyc +0 -0
- simo/fleet/models.py +140 -82
- simo/fleet/serializers.py +35 -1
- simo/fleet/socket_consumers.py +239 -76
- simo/fleet/utils.py +15 -53
- simo/fleet/views.py +28 -14
- simo/generic/controllers.py +13 -89
- simo/generic/forms.py +29 -18
- simo/generic/gateways.py +73 -2
- simo/generic/models.py +3 -3
- simo/multimedia/controllers.py +9 -8
- simo/settings.py +7 -4
- simo/urls.py +4 -8
- simo/users/__pycache__/admin.cpython-38.pyc +0 -0
- simo/users/__pycache__/api.cpython-38.pyc +0 -0
- simo/users/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/users/__pycache__/models.cpython-38.pyc +0 -0
- simo/users/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/users/__pycache__/sso_urls.cpython-38.pyc +0 -0
- simo/users/admin.py +8 -1
- simo/users/api.py +38 -2
- simo/users/auto_urls.py +2 -2
- simo/users/migrations/0025_rename_name_fingerprint_type_and_more.py +22 -0
- simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-38.pyc +0 -0
- simo/users/models.py +2 -3
- simo/users/serializers.py +15 -1
- simo/users/sso_urls.py +3 -3
- simo/wsgi.py +7 -0
- {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/METADATA +8 -9
- {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/RECORD +173 -189
- {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/WHEEL +1 -1
- simo/core/db_backend/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/db_backend/__pycache__/base.cpython-38.pyc +0 -0
- simo/core/management/commands/__pycache__/gateways_manager.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0003_create_default_zones_and_categories.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0004_create_generic.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0005_component_subcomponents.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0006_alter_component_subcomponents.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0007_component_change_init_to.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0008_alter_component_change_init_to.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0009_auto_20220707_1404.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0010_historyaggregate.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0011_component_last_change.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0012_instance.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0013_auto_20231003_0754.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0014_zone_instance.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0015_auto_20231004_1113.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0016_auto_20231004_1113.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0017_auto_20231004_1313.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0018_auto_20231005_0622.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0019_alter_gateway_type.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0020_component_meta.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0021_auto_20231020_1041.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/templatetags/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/templatetags/__pycache__/components_list.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/config_values.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/easing.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/form_fields.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/form_widgets.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/helpers.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/logs.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/mixins.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/model_helpers.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/relay.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/type_constants.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/validators.cpython-38.pyc +0 -0
- simo/fleet/tasks.py +0 -25
- simo/generic/__pycache__/__init__.cpython-37.pyc +0 -0
- simo/generic/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/generic/__pycache__/app_widgets.cpython-38.pyc +0 -0
- simo/generic/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/generic/__pycache__/controllers.cpython-37.pyc +0 -0
- simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/generic/__pycache__/models.cpython-38.pyc +0 -0
- simo/generic/__pycache__/routing.cpython-38.pyc +0 -0
- simo/generic/__pycache__/socket_consumers.cpython-38.pyc +0 -0
- simo/generic/__pycache__/widgets.cpython-37.pyc +0 -0
- simo/multimedia/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/admin.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/api.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/app_widgets.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/forms.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/models.cpython-38.pyc +0 -0
- simo/multimedia/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0002_sound_length.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0003_alter_sound_length.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/0004_auto_20231023_1055.cpython-38.pyc +0 -0
- simo/multimedia/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/admin.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/api.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/models.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/utils.cpython-38.pyc +0 -0
- simo/notifications/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-38.pyc +0 -0
- simo/notifications/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0002_componentpermission.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0004_user_secret_key.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0005_permissionsrole_instance.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0006_auto_20231003_0850.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0007_auto_20231003_1228.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0008_auto_20231003_1229.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0009_remove_user_role.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0010_auto_20231004_1313.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0011_auto_20231004_1313.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0012_alter_userinstancerole_unique_together.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0013_remove_user_roles.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0014_user_roles.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0015_remove_user_at_home.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0016_auto_20231005_1050.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
- {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/LICENSE.md +0 -0
- {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/top_level.txt +0 -0
simo/fleet/forms.py
CHANGED
|
@@ -5,12 +5,22 @@ from django.urls.base import get_script_prefix
|
|
|
5
5
|
from django.contrib.contenttypes.models import ContentType
|
|
6
6
|
from dal import autocomplete
|
|
7
7
|
from dal import forward
|
|
8
|
+
from simo.core.models import Component
|
|
8
9
|
from simo.core.forms import BaseComponentForm, ValueLimitForm, NumericSensorForm
|
|
9
10
|
from simo.core.utils.formsets import FormsetField
|
|
10
11
|
from simo.core.widgets import LogOutputWidget
|
|
11
12
|
from simo.core.utils.easing import EASING_CHOICES
|
|
12
|
-
from .
|
|
13
|
-
from .utils import
|
|
13
|
+
from simo.core.utils.validators import validate_slaves
|
|
14
|
+
from simo.core.utils.admin import AdminFormActionForm
|
|
15
|
+
from .models import Colonel, ColonelPin, I2CInterface, i2c_interface_no_choices
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ColonelPinChoiceField(forms.ModelChoiceField):
|
|
19
|
+
'''
|
|
20
|
+
Required for API, so that SIMO app could properly handle
|
|
21
|
+
fleet components configuration.
|
|
22
|
+
'''
|
|
23
|
+
filter_by = 'colonel'
|
|
14
24
|
|
|
15
25
|
|
|
16
26
|
class ColonelAdminForm(forms.ModelForm):
|
|
@@ -36,32 +46,32 @@ class ColonelAdminForm(forms.ModelForm):
|
|
|
36
46
|
)
|
|
37
47
|
|
|
38
48
|
|
|
39
|
-
class MoveColonelForm(
|
|
49
|
+
class MoveColonelForm(AdminFormActionForm):
|
|
40
50
|
colonel = forms.ModelChoiceField(
|
|
41
51
|
label="Move to:", queryset=Colonel.objects.filter(components=None),
|
|
42
52
|
)
|
|
43
53
|
|
|
44
54
|
|
|
45
55
|
class I2CInterfaceAdminForm(forms.ModelForm):
|
|
46
|
-
scl_pin =
|
|
47
|
-
|
|
56
|
+
scl_pin = ColonelPinChoiceField(
|
|
57
|
+
queryset=ColonelPin.objects.filter(output=True, native=True),
|
|
48
58
|
widget=autocomplete.ListSelect2(
|
|
49
59
|
url='autocomplete-colonel-pins',
|
|
50
60
|
forward=[
|
|
51
61
|
forward.Self(),
|
|
52
62
|
forward.Field('colonel'),
|
|
53
|
-
forward.Const({'output': True}, 'filters')
|
|
63
|
+
forward.Const({'output': True, 'native': True}, 'filters')
|
|
54
64
|
]
|
|
55
65
|
)
|
|
56
66
|
)
|
|
57
|
-
sda_pin =
|
|
58
|
-
|
|
67
|
+
sda_pin = ColonelPinChoiceField(
|
|
68
|
+
queryset=ColonelPin.objects.filter(output=True, native=True),
|
|
59
69
|
widget=autocomplete.ListSelect2(
|
|
60
70
|
url='autocomplete-colonel-pins',
|
|
61
71
|
forward=[
|
|
62
72
|
forward.Self(),
|
|
63
73
|
forward.Field('colonel'),
|
|
64
|
-
forward.Const({'output': True}, 'filters')
|
|
74
|
+
forward.Const({'output': True, 'native': True}, 'filters')
|
|
65
75
|
]
|
|
66
76
|
)
|
|
67
77
|
)
|
|
@@ -71,31 +81,21 @@ class I2CInterfaceAdminForm(forms.ModelForm):
|
|
|
71
81
|
fields = '__all__'
|
|
72
82
|
|
|
73
83
|
def clean_scl_pin(self):
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
if self.cleaned_data['scl_pin'] not in available_pins:
|
|
82
|
-
raise forms.ValidationError("Pin is unavailable.")
|
|
84
|
+
if self.cleaned_data['scl_pin'].occupied_by \
|
|
85
|
+
and self.cleaned_data['scl_pin'].occupied_by != self.instance:
|
|
86
|
+
raise forms.ValidationError(
|
|
87
|
+
f"This pin is already occupied by "
|
|
88
|
+
f"{self.cleaned_data['scl_pin'].occupied_by}!"
|
|
89
|
+
)
|
|
83
90
|
return self.cleaned_data['scl_pin']
|
|
84
91
|
|
|
85
92
|
def clean_sda_pin(self):
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
)
|
|
93
|
-
if self.cleaned_data['sda_pin'] not in available_pins:
|
|
94
|
-
raise forms.ValidationError("Pin is unavailable.")
|
|
95
|
-
|
|
96
|
-
if self.cleaned_data.get('scl_pin') == self.cleaned_data['sda_pin']:
|
|
97
|
-
raise forms.ValidationError("Can not be the same as SCL pin!")
|
|
98
|
-
|
|
93
|
+
if self.cleaned_data['sda_pin'].occupied_by \
|
|
94
|
+
and self.cleaned_data['sda_pin'].occupied_by != self.instance:
|
|
95
|
+
raise forms.ValidationError(
|
|
96
|
+
f"This pin is already occupied by "
|
|
97
|
+
f"{self.cleaned_data['sda_pin'].occupied_by}!"
|
|
98
|
+
)
|
|
99
99
|
return self.cleaned_data['sda_pin']
|
|
100
100
|
|
|
101
101
|
|
|
@@ -105,14 +105,66 @@ class ColonelComponentForm(BaseComponentForm):
|
|
|
105
105
|
help_text="ATENTION! Changing Colonel after component creation is not recommended!"
|
|
106
106
|
)
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
def clean_colonel(self):
|
|
109
|
+
org = self.instance.config.get('colonel')
|
|
110
|
+
if org and org != self.cleaned_data['colonel'].id:
|
|
111
|
+
raise forms.ValidationError(
|
|
112
|
+
"Changing colonel after component is created "
|
|
113
|
+
"it is not allowed!"
|
|
114
|
+
)
|
|
115
|
+
return self.cleaned_data['colonel']
|
|
116
|
+
|
|
117
|
+
def _clean_pin(self, field_name):
|
|
118
|
+
if self.cleaned_data[field_name].colonel != self.cleaned_data['colonel']:
|
|
119
|
+
self.add_error(
|
|
120
|
+
field_name, "Pin must be from the same Colonel!"
|
|
121
|
+
)
|
|
122
|
+
return
|
|
123
|
+
if self.cleaned_data[field_name].occupied_by \
|
|
124
|
+
and self.cleaned_data[field_name].occupied_by != self.instance:
|
|
125
|
+
self.add_error(
|
|
126
|
+
field_name,
|
|
127
|
+
f"Pin is already occupied by {self.cleaned_data[field_name].occupied_by}!"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def _clean_controls(self):
|
|
131
|
+
# TODO: Formset factory should return proper field value types instead of str type
|
|
132
|
+
|
|
133
|
+
pin_instances = {}
|
|
134
|
+
for i, control in enumerate(self.cleaned_data['controls']):
|
|
135
|
+
updated_vals = {}
|
|
136
|
+
for key, val in control.items():
|
|
137
|
+
updated_vals[key] = val
|
|
138
|
+
if key == 'pin':
|
|
139
|
+
pin = ColonelPin.objects.get(
|
|
140
|
+
id=self.cleaned_data['controls'][i]['pin']
|
|
141
|
+
)
|
|
142
|
+
pin_instances[i] = pin
|
|
143
|
+
updated_vals['pin_no'] = pin.no
|
|
144
|
+
elif key == 'touch_threshold':
|
|
145
|
+
updated_vals[key] = int(val)
|
|
146
|
+
self.cleaned_data['controls'][i] = updated_vals
|
|
147
|
+
|
|
148
|
+
formset_errors = {}
|
|
149
|
+
for i, control in enumerate(self.cleaned_data['controls']):
|
|
150
|
+
if pin_instances[i].colonel != self.cleaned_data['colonel']:
|
|
151
|
+
formset_errors[i] = {
|
|
152
|
+
'pin': f"{pin_instances[i]} must be from the same Colonel!"
|
|
153
|
+
}
|
|
154
|
+
elif pin_instances[i].occupied_by \
|
|
155
|
+
and pin_instances[i].occupied_by != self.instance:
|
|
156
|
+
formset_errors[i] = {
|
|
157
|
+
'pin': f"{pin_instances[i]} is already occupied by {pin_instances[i].occupied_by}!"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
errors_list = []
|
|
161
|
+
if formset_errors:
|
|
162
|
+
for i, control in enumerate(self.cleaned_data['controls']):
|
|
163
|
+
errors_list.append(formset_errors.get(i, {}))
|
|
164
|
+
if errors_list:
|
|
165
|
+
self._errors['controls'] = errors_list
|
|
166
|
+
if 'controls' in self.cleaned_data:
|
|
167
|
+
del self.cleaned_data['controls']
|
|
116
168
|
|
|
117
169
|
def save(self, commit=True):
|
|
118
170
|
obj = super().save(commit)
|
|
@@ -125,9 +177,8 @@ class ColonelComponentForm(BaseComponentForm):
|
|
|
125
177
|
|
|
126
178
|
|
|
127
179
|
class ControlPinForm(forms.Form):
|
|
128
|
-
pin =
|
|
129
|
-
|
|
130
|
-
help_text="Use this if you also want to wire up a wall switch",
|
|
180
|
+
pin = ColonelPinChoiceField(
|
|
181
|
+
queryset=ColonelPin.objects.filter(input=True),
|
|
131
182
|
widget=autocomplete.ListSelect2(
|
|
132
183
|
url='autocomplete-colonel-pins',
|
|
133
184
|
forward=[
|
|
@@ -143,23 +194,14 @@ class ControlPinForm(forms.Form):
|
|
|
143
194
|
method = forms.ChoiceField(
|
|
144
195
|
required=True, choices=(
|
|
145
196
|
('momentary', "Momentary"), ('toggle', "Toggle"),
|
|
146
|
-
('touch', "Touch")
|
|
147
197
|
),
|
|
148
198
|
)
|
|
149
|
-
touch_threshold = forms.IntegerField(
|
|
150
|
-
min_value=0, max_value=999999999, required=False, initial=1000,
|
|
151
|
-
help_text="Used to detect touch events. "
|
|
152
|
-
"Smaller value means a higher sensitivity. "
|
|
153
|
-
"1000 offers good starting point. <br> "
|
|
154
|
-
"Used only when controll method is set to Touch."
|
|
155
|
-
|
|
156
|
-
)
|
|
157
199
|
prefix = 'controls'
|
|
158
200
|
|
|
159
201
|
|
|
160
202
|
class ColonelBinarySensorConfigForm(ColonelComponentForm):
|
|
161
|
-
pin =
|
|
162
|
-
|
|
203
|
+
pin = ColonelPinChoiceField(
|
|
204
|
+
queryset=ColonelPin.objects.filter(input=True),
|
|
163
205
|
widget=autocomplete.ListSelect2(
|
|
164
206
|
url='autocomplete-colonel-pins',
|
|
165
207
|
forward=[
|
|
@@ -193,27 +235,15 @@ class ColonelBinarySensorConfigForm(ColonelComponentForm):
|
|
|
193
235
|
|
|
194
236
|
def clean(self):
|
|
195
237
|
super().clean()
|
|
196
|
-
if
|
|
238
|
+
if not self.cleaned_data.get('colonel'):
|
|
197
239
|
return self.cleaned_data
|
|
198
240
|
if 'pin' not in self.cleaned_data:
|
|
199
241
|
return self.cleaned_data
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
self.cleaned_data['colonel'], filters={'input': True},
|
|
206
|
-
selected=selected
|
|
207
|
-
)
|
|
208
|
-
if self.cleaned_data['pin'] not in input_pins:
|
|
209
|
-
self.add_error(
|
|
210
|
-
'pin',
|
|
211
|
-
"Sorry, but GPIO%d pin can not be used as input pin "
|
|
212
|
-
% self.cleaned_data['pin']
|
|
213
|
-
)
|
|
214
|
-
return
|
|
215
|
-
if self.cleaned_data['pin'] > 100:
|
|
216
|
-
if self.cleaned_data['pin'] < 126:
|
|
242
|
+
|
|
243
|
+
self._clean_pin('pin')
|
|
244
|
+
|
|
245
|
+
if self.cleaned_data['pin'].no > 100:
|
|
246
|
+
if self.cleaned_data['pin'].no < 126:
|
|
217
247
|
if self.cleaned_data.get('pull') == 'HIGH':
|
|
218
248
|
self.add_error(
|
|
219
249
|
'pull',
|
|
@@ -233,30 +263,33 @@ class ColonelBinarySensorConfigForm(ColonelComponentForm):
|
|
|
233
263
|
)
|
|
234
264
|
|
|
235
265
|
elif self.cleaned_data.get('pull') != 'FLOATING':
|
|
236
|
-
|
|
237
|
-
self.cleaned_data['colonel'], filters={'output': True},
|
|
238
|
-
selected=selected
|
|
239
|
-
)
|
|
240
|
-
if self.cleaned_data['pin'] not in pins_available_for_pull:
|
|
266
|
+
if not self.cleaned_data['pin'].output:
|
|
241
267
|
self.add_error(
|
|
242
268
|
'pin',
|
|
243
|
-
"Sorry, but
|
|
244
|
-
"
|
|
269
|
+
f"Sorry, but {self.cleaned_data['pin']} "
|
|
270
|
+
f"does not have internal pull HIGH/LOW"
|
|
271
|
+
" resistance capability"
|
|
245
272
|
)
|
|
246
|
-
return
|
|
247
273
|
|
|
248
274
|
return self.cleaned_data
|
|
249
275
|
|
|
250
276
|
|
|
277
|
+
def save(self, commit=True):
|
|
278
|
+
self.instance.config['pin_no'] = self.cleaned_data['pin'].no
|
|
279
|
+
return super().save(commit=commit)
|
|
280
|
+
|
|
281
|
+
|
|
251
282
|
class ColonelNumericSensorConfigForm(ColonelComponentForm, NumericSensorForm):
|
|
252
|
-
pin =
|
|
253
|
-
|
|
283
|
+
pin = ColonelPinChoiceField(
|
|
284
|
+
queryset=ColonelPin.objects.filter(adc=True, input=True, native=True),
|
|
254
285
|
widget=autocomplete.ListSelect2(
|
|
255
286
|
url='autocomplete-colonel-pins',
|
|
256
287
|
forward=[
|
|
257
288
|
forward.Self(),
|
|
258
289
|
forward.Field('colonel'),
|
|
259
|
-
forward.Const(
|
|
290
|
+
forward.Const(
|
|
291
|
+
{'adc': True, 'native': True, 'input': True}, 'filters'
|
|
292
|
+
)
|
|
260
293
|
]
|
|
261
294
|
)
|
|
262
295
|
)
|
|
@@ -282,37 +315,32 @@ class ColonelNumericSensorConfigForm(ColonelComponentForm, NumericSensorForm):
|
|
|
282
315
|
|
|
283
316
|
def clean(self):
|
|
284
317
|
super().clean()
|
|
285
|
-
if
|
|
318
|
+
if not self.cleaned_data.get('colonel'):
|
|
286
319
|
return self.cleaned_data
|
|
287
320
|
if 'pin' not in self.cleaned_data:
|
|
288
321
|
return self.cleaned_data
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
selected = None
|
|
293
|
-
input_pins = get_available_gpio_pins(
|
|
294
|
-
self.cleaned_data['colonel'], filters={'adc': True},
|
|
295
|
-
selected=selected
|
|
296
|
-
)
|
|
297
|
-
if self.cleaned_data['pin'] not in input_pins:
|
|
298
|
-
self.add_error(
|
|
299
|
-
'pin',
|
|
300
|
-
"Sorry, but GPIO%d pin can not be used as ADC input pin "
|
|
301
|
-
% self.cleaned_data['pin']
|
|
302
|
-
)
|
|
303
|
-
return
|
|
322
|
+
|
|
323
|
+
self._clean_pin('pin')
|
|
324
|
+
|
|
304
325
|
return self.cleaned_data
|
|
305
326
|
|
|
306
327
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
328
|
+
def save(self, commit=True):
|
|
329
|
+
self.instance.config['pin_no'] = self.cleaned_data['pin'].no
|
|
330
|
+
return super().save(commit=commit)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class DS18B20SensorConfigForm(ColonelComponentForm, NumericSensorForm):
|
|
334
|
+
pin = ColonelPinChoiceField(
|
|
335
|
+
queryset=ColonelPin.objects.filter(input=True, native=True),
|
|
310
336
|
widget=autocomplete.ListSelect2(
|
|
311
337
|
url='autocomplete-colonel-pins',
|
|
312
338
|
forward=[
|
|
313
339
|
forward.Self(),
|
|
314
340
|
forward.Field('colonel'),
|
|
315
|
-
forward.Const(
|
|
341
|
+
forward.Const(
|
|
342
|
+
{'native': True, 'input': True}, 'filters'
|
|
343
|
+
)
|
|
316
344
|
]
|
|
317
345
|
)
|
|
318
346
|
)
|
|
@@ -325,37 +353,32 @@ class DS18B20SensorConfigForm(ColonelComponentForm):
|
|
|
325
353
|
|
|
326
354
|
def clean(self):
|
|
327
355
|
super().clean()
|
|
328
|
-
if
|
|
356
|
+
if not self.cleaned_data.get('colonel'):
|
|
329
357
|
return self.cleaned_data
|
|
330
358
|
if 'pin' not in self.cleaned_data:
|
|
331
359
|
return self.cleaned_data
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
selected = None
|
|
336
|
-
input_pins = get_available_gpio_pins(
|
|
337
|
-
self.cleaned_data['colonel'], filters={'adc': True},
|
|
338
|
-
selected=selected
|
|
339
|
-
)
|
|
340
|
-
if self.cleaned_data['pin'] not in input_pins:
|
|
341
|
-
self.add_error(
|
|
342
|
-
'pin',
|
|
343
|
-
"Sorry, but GPIO%d pin can not be used"
|
|
344
|
-
% self.cleaned_data['pin']
|
|
345
|
-
)
|
|
346
|
-
return
|
|
360
|
+
|
|
361
|
+
self._clean_pin('pin')
|
|
362
|
+
|
|
347
363
|
return self.cleaned_data
|
|
348
364
|
|
|
365
|
+
def save(self, commit=True):
|
|
366
|
+
self.instance.config['pin_no'] = self.cleaned_data['pin'].no
|
|
367
|
+
return super().save(commit=commit)
|
|
368
|
+
|
|
369
|
+
|
|
349
370
|
|
|
350
371
|
class ColonelDHTSensorConfigForm(ColonelComponentForm):
|
|
351
|
-
pin =
|
|
352
|
-
|
|
372
|
+
pin = ColonelPinChoiceField(
|
|
373
|
+
queryset=ColonelPin.objects.filter(input=True, native=True),
|
|
353
374
|
widget=autocomplete.ListSelect2(
|
|
354
375
|
url='autocomplete-colonel-pins',
|
|
355
376
|
forward=[
|
|
356
377
|
forward.Self(),
|
|
357
378
|
forward.Field('colonel'),
|
|
358
|
-
forward.Const(
|
|
379
|
+
forward.Const(
|
|
380
|
+
{'native': True, 'input': True}, 'filters'
|
|
381
|
+
)
|
|
359
382
|
]
|
|
360
383
|
)
|
|
361
384
|
)
|
|
@@ -376,27 +399,19 @@ class ColonelDHTSensorConfigForm(ColonelComponentForm):
|
|
|
376
399
|
|
|
377
400
|
def clean(self):
|
|
378
401
|
super().clean()
|
|
379
|
-
if
|
|
402
|
+
if not self.cleaned_data.get('colonel'):
|
|
380
403
|
return self.cleaned_data
|
|
381
404
|
if 'pin' not in self.cleaned_data:
|
|
382
405
|
return self.cleaned_data
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
selected = None
|
|
387
|
-
input_pins = get_available_gpio_pins(
|
|
388
|
-
self.cleaned_data['colonel'], filters={'adc': True},
|
|
389
|
-
selected=selected
|
|
390
|
-
)
|
|
391
|
-
if self.cleaned_data['pin'] not in input_pins:
|
|
392
|
-
self.add_error(
|
|
393
|
-
'pin',
|
|
394
|
-
"Sorry, but GPIO%d pin can not be used"
|
|
395
|
-
% self.cleaned_data['pin']
|
|
396
|
-
)
|
|
397
|
-
return
|
|
406
|
+
|
|
407
|
+
self._clean_pin('pin')
|
|
408
|
+
|
|
398
409
|
return self.cleaned_data
|
|
399
410
|
|
|
411
|
+
def save(self, commit=True):
|
|
412
|
+
self.instance.config['pin_no'] = self.cleaned_data['pin'].no
|
|
413
|
+
return super().save(commit=commit)
|
|
414
|
+
|
|
400
415
|
|
|
401
416
|
class BME680SensorConfigForm(ColonelComponentForm):
|
|
402
417
|
i2c_interface = forms.TypedChoiceField(
|
|
@@ -410,7 +425,8 @@ class BME680SensorConfigForm(ColonelComponentForm):
|
|
|
410
425
|
)
|
|
411
426
|
)
|
|
412
427
|
i2c_address = forms.IntegerField(
|
|
413
|
-
|
|
428
|
+
min_value=0, max_value=127, initial=118,
|
|
429
|
+
help_text="Integer: 0 - 127. Dafault: 118"
|
|
414
430
|
)
|
|
415
431
|
read_frequency_s = forms.IntegerField(
|
|
416
432
|
initial=60, min_value=1, max_value=60*60*24,
|
|
@@ -420,15 +436,38 @@ class BME680SensorConfigForm(ColonelComponentForm):
|
|
|
420
436
|
)
|
|
421
437
|
|
|
422
438
|
|
|
439
|
+
class MPC9808SensorConfigForm(ColonelComponentForm):
|
|
440
|
+
i2c_interface = forms.TypedChoiceField(
|
|
441
|
+
coerce=int, choices=i2c_interface_no_choices,
|
|
442
|
+
widget=autocomplete.ListSelect2(
|
|
443
|
+
url='autocomplete-colonel-i2c_interfaces',
|
|
444
|
+
forward=[
|
|
445
|
+
forward.Self(),
|
|
446
|
+
forward.Field('colonel'),
|
|
447
|
+
]
|
|
448
|
+
)
|
|
449
|
+
)
|
|
450
|
+
i2c_address = forms.IntegerField(
|
|
451
|
+
min_value=0, max_value=127, initial=24,
|
|
452
|
+
help_text="Integer: 0 - 127. Default: 24",
|
|
453
|
+
)
|
|
454
|
+
read_frequency_s = forms.IntegerField(
|
|
455
|
+
initial=60, min_value=1, max_value=60 * 60 * 24,
|
|
456
|
+
help_text='read and report temperature value every s. '
|
|
457
|
+
'Can not be less than 1s.'
|
|
458
|
+
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
|
|
423
462
|
class ColonelTouchSensorConfigForm(ColonelComponentForm):
|
|
424
|
-
pin =
|
|
425
|
-
|
|
463
|
+
pin = ColonelPinChoiceField(
|
|
464
|
+
queryset=ColonelPin.objects.filter(input=True, capacitive=True),
|
|
426
465
|
widget=autocomplete.ListSelect2(
|
|
427
466
|
url='autocomplete-colonel-pins',
|
|
428
467
|
forward=[
|
|
429
468
|
forward.Self(),
|
|
430
469
|
forward.Field('colonel'),
|
|
431
|
-
forward.Const({'capacitive': True}, 'filters')
|
|
470
|
+
forward.Const({'input': True, 'capacitive': True}, 'filters')
|
|
432
471
|
]
|
|
433
472
|
)
|
|
434
473
|
)
|
|
@@ -439,45 +478,28 @@ class ColonelTouchSensorConfigForm(ColonelComponentForm):
|
|
|
439
478
|
"1000 offers good starting point."
|
|
440
479
|
|
|
441
480
|
)
|
|
442
|
-
inverse = forms.ChoiceField(choices=((
|
|
481
|
+
inverse = forms.ChoiceField(choices=((0, "No"), (1, "Yes")))
|
|
443
482
|
|
|
444
483
|
def clean(self):
|
|
445
484
|
super().clean()
|
|
446
|
-
if
|
|
485
|
+
if not self.cleaned_data.get('colonel'):
|
|
447
486
|
return self.cleaned_data
|
|
448
487
|
if 'pin' not in self.cleaned_data:
|
|
449
488
|
return self.cleaned_data
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
selected = None
|
|
454
|
-
free_pins = get_available_gpio_pins(
|
|
455
|
-
self.cleaned_data['colonel'], selected=selected
|
|
456
|
-
)
|
|
457
|
-
if self.cleaned_data['pin'] not in free_pins:
|
|
458
|
-
self.add_error(
|
|
459
|
-
'pin',
|
|
460
|
-
"Sorry, but GPIO%d pin is occupied."
|
|
461
|
-
% self.cleaned_data['pin']
|
|
462
|
-
)
|
|
463
|
-
return
|
|
464
|
-
touch_pins = get_available_gpio_pins(
|
|
465
|
-
self.cleaned_data['colonel'], filters={'capacitive': True},
|
|
466
|
-
selected=selected
|
|
467
|
-
)
|
|
468
|
-
if self.cleaned_data['pin'] not in touch_pins:
|
|
469
|
-
self.add_error(
|
|
470
|
-
'pin',
|
|
471
|
-
"Sorry, but GPIO%d pin can not be used as input pin "
|
|
472
|
-
% self.cleaned_data['pin']
|
|
473
|
-
)
|
|
474
|
-
return
|
|
489
|
+
|
|
490
|
+
self._clean_pin('pin')
|
|
491
|
+
|
|
475
492
|
return self.cleaned_data
|
|
476
493
|
|
|
477
494
|
|
|
495
|
+
def save(self, commit=True):
|
|
496
|
+
self.instance.config['pin_no'] = self.cleaned_data['pin'].no
|
|
497
|
+
return super().save(commit=commit)
|
|
498
|
+
|
|
499
|
+
|
|
478
500
|
class ColonelSwitchConfigForm(ColonelComponentForm):
|
|
479
|
-
output_pin =
|
|
480
|
-
|
|
501
|
+
output_pin = ColonelPinChoiceField(
|
|
502
|
+
queryset=ColonelPin.objects.filter(output=True),
|
|
481
503
|
widget=autocomplete.ListSelect2(
|
|
482
504
|
url='autocomplete-colonel-pins',
|
|
483
505
|
forward=[
|
|
@@ -498,6 +520,20 @@ class ColonelSwitchConfigForm(ColonelComponentForm):
|
|
|
498
520
|
inverse = forms.BooleanField(
|
|
499
521
|
label=_("Inverse switch value"), required=False
|
|
500
522
|
)
|
|
523
|
+
slaves = forms.ModelMultipleChoiceField(
|
|
524
|
+
required=False,
|
|
525
|
+
queryset=Component.objects.filter(
|
|
526
|
+
base_type__in=(
|
|
527
|
+
'dimmer', 'switch', 'blinds', 'script'
|
|
528
|
+
)
|
|
529
|
+
),
|
|
530
|
+
widget=autocomplete.ModelSelect2Multiple(
|
|
531
|
+
url='autocomplete-component', attrs={'data-html': True},
|
|
532
|
+
forward=(forward.Const(
|
|
533
|
+
['dimmer', 'switch', 'blinds', 'script'], 'base_type'),
|
|
534
|
+
)
|
|
535
|
+
)
|
|
536
|
+
)
|
|
501
537
|
|
|
502
538
|
controls = FormsetField(
|
|
503
539
|
formset_factory(
|
|
@@ -505,62 +541,37 @@ class ColonelSwitchConfigForm(ColonelComponentForm):
|
|
|
505
541
|
)
|
|
506
542
|
)
|
|
507
543
|
|
|
544
|
+
def clean_slaves(self):
|
|
545
|
+
if not self.cleaned_data['slaves'] or not self.instance:
|
|
546
|
+
return self.cleaned_data['slaves']
|
|
547
|
+
return validate_slaves(self.cleaned_data['slaves'], self.instance)
|
|
548
|
+
|
|
549
|
+
|
|
508
550
|
def clean(self):
|
|
509
551
|
super().clean()
|
|
510
|
-
if
|
|
552
|
+
if not self.cleaned_data.get('colonel'):
|
|
511
553
|
return self.cleaned_data
|
|
512
554
|
if not self.cleaned_data.get('output_pin'):
|
|
513
555
|
return self.cleaned_data
|
|
514
|
-
if self.instance.pk:
|
|
515
|
-
selected = self.instance.config.get('output_pin')
|
|
516
|
-
else:
|
|
517
|
-
selected = None
|
|
518
|
-
output_pins = get_available_gpio_pins(
|
|
519
|
-
self.cleaned_data['colonel'], filters={'output': True},
|
|
520
|
-
selected=selected
|
|
521
|
-
)
|
|
522
|
-
if self.cleaned_data['output_pin'] not in output_pins:
|
|
523
|
-
self.add_error(
|
|
524
|
-
'output_pin',
|
|
525
|
-
"Sorry, but GPIO%d pin can not be used as output pin "
|
|
526
|
-
% self.cleaned_data['output_pin']
|
|
527
|
-
)
|
|
528
|
-
return self.cleaned_data
|
|
529
556
|
|
|
530
|
-
|
|
531
|
-
return self.cleaned_data
|
|
557
|
+
self._clean_pin('output_pin')
|
|
532
558
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
for key, val in control.items():
|
|
536
|
-
if key in ('pin', 'touch_threshold'):
|
|
537
|
-
self.cleaned_data['controls'][i][key] = int(val)
|
|
538
|
-
else:
|
|
539
|
-
self.cleaned_data['controls'][i][key] = val
|
|
559
|
+
if not self.cleaned_data.get('controls'):
|
|
560
|
+
return self.cleaned_data
|
|
540
561
|
|
|
541
|
-
|
|
542
|
-
try:
|
|
543
|
-
selected = self.instance.config['controls'][i]['pin']
|
|
544
|
-
except:
|
|
545
|
-
selected = None
|
|
546
|
-
free_pins = get_available_gpio_pins(
|
|
547
|
-
self.cleaned_data['colonel'], filters={'input': True},
|
|
548
|
-
selected=selected
|
|
549
|
-
)
|
|
550
|
-
if control['pin'] not in free_pins:
|
|
551
|
-
self.add_error(
|
|
552
|
-
'controls',
|
|
553
|
-
"Sorry, but GPIO%d pin is occupied."
|
|
554
|
-
% control['pin']
|
|
555
|
-
)
|
|
556
|
-
return
|
|
562
|
+
self._clean_controls()
|
|
557
563
|
|
|
558
564
|
return self.cleaned_data
|
|
559
565
|
|
|
560
566
|
|
|
567
|
+
def save(self, commit=True):
|
|
568
|
+
self.instance.config['output_pin_no'] = self.cleaned_data['output_pin'].no
|
|
569
|
+
return super().save(commit=commit)
|
|
570
|
+
|
|
571
|
+
|
|
561
572
|
class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
562
|
-
output_pin =
|
|
563
|
-
|
|
573
|
+
output_pin = ColonelPinChoiceField(
|
|
574
|
+
queryset=ColonelPin.objects.filter(output=True),
|
|
564
575
|
widget=autocomplete.ListSelect2(
|
|
565
576
|
url='autocomplete-colonel-pins',
|
|
566
577
|
forward=[
|
|
@@ -613,70 +624,52 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
613
624
|
required=True, initial=100,
|
|
614
625
|
help_text="Component ON value when used with toggle switch"
|
|
615
626
|
)
|
|
627
|
+
slaves = forms.ModelMultipleChoiceField(
|
|
628
|
+
required=False,
|
|
629
|
+
queryset=Component.objects.filter(
|
|
630
|
+
base_type__in=('dimmer', ),
|
|
631
|
+
),
|
|
632
|
+
widget=autocomplete.ModelSelect2Multiple(
|
|
633
|
+
url='autocomplete-component', attrs={'data-html': True},
|
|
634
|
+
forward=(forward.Const(['dimmer', ], 'base_type'),)
|
|
635
|
+
)
|
|
636
|
+
)
|
|
616
637
|
controls = FormsetField(
|
|
617
638
|
formset_factory(
|
|
618
639
|
ControlPinForm, can_delete=True, can_order=True, extra=0, max_num=1
|
|
619
640
|
)
|
|
620
641
|
)
|
|
621
642
|
|
|
643
|
+
def clean_slaves(self):
|
|
644
|
+
if not self.cleaned_data['slaves'] or not self.instance:
|
|
645
|
+
return self.cleaned_data['slaves']
|
|
646
|
+
return validate_slaves(self.cleaned_data['slaves'], self.instance)
|
|
647
|
+
|
|
622
648
|
def clean(self):
|
|
623
649
|
super().clean()
|
|
624
|
-
if
|
|
650
|
+
if not self.cleaned_data.get('colonel'):
|
|
625
651
|
return self.cleaned_data
|
|
626
652
|
if not self.cleaned_data.get('output_pin'):
|
|
627
653
|
return self.cleaned_data
|
|
628
|
-
if self.instance.pk:
|
|
629
|
-
selected = self.instance.config.get('output_pin')
|
|
630
|
-
else:
|
|
631
|
-
self.cleaned_data['value_units'] = '%'
|
|
632
|
-
selected = None
|
|
633
|
-
output_pins = get_available_gpio_pins(
|
|
634
|
-
self.cleaned_data['colonel'], filters={'output': True},
|
|
635
|
-
selected=selected
|
|
636
|
-
)
|
|
637
|
-
if self.cleaned_data['output_pin'] not in output_pins:
|
|
638
|
-
self.add_error(
|
|
639
|
-
'output_pin',
|
|
640
|
-
"Sorry, but GPIO%d pin can not be used as output pin "
|
|
641
|
-
% self.cleaned_data['output_pin']
|
|
642
|
-
)
|
|
643
|
-
return self.cleaned_data
|
|
644
654
|
|
|
645
|
-
|
|
655
|
+
self._clean_pin('output_pin')
|
|
656
|
+
|
|
657
|
+
if not self.cleaned_data.get('controls'):
|
|
646
658
|
return self.cleaned_data
|
|
647
659
|
|
|
660
|
+
self._clean_controls()
|
|
648
661
|
|
|
649
|
-
|
|
650
|
-
for i, control in enumerate(self.cleaned_data['controls']):
|
|
651
|
-
for key, val in control.items():
|
|
652
|
-
if key in ('pin', 'touch_threshold'):
|
|
653
|
-
self.cleaned_data['controls'][i][key] = int(val)
|
|
654
|
-
else:
|
|
655
|
-
self.cleaned_data['controls'][i][key] = val
|
|
662
|
+
return self.cleaned_data
|
|
656
663
|
|
|
657
|
-
for i, control in enumerate(self.cleaned_data['controls']):
|
|
658
|
-
try:
|
|
659
|
-
selected = self.instance.config['controls'][i]['pin']
|
|
660
|
-
except:
|
|
661
|
-
selected = None
|
|
662
|
-
free_pins = get_available_gpio_pins(
|
|
663
|
-
self.cleaned_data['colonel'], filters={'input': True},
|
|
664
|
-
selected=selected
|
|
665
|
-
)
|
|
666
|
-
if control['pin'] not in free_pins:
|
|
667
|
-
self.add_error(
|
|
668
|
-
'controls',
|
|
669
|
-
"Sorry, but GPIO%d pin is occupied."
|
|
670
|
-
% control['pin']
|
|
671
|
-
)
|
|
672
|
-
return
|
|
673
664
|
|
|
674
|
-
|
|
665
|
+
def save(self, commit=True):
|
|
666
|
+
self.instance.config['output_pin_no'] = self.cleaned_data['output_pin'].no
|
|
667
|
+
return super().save(commit=commit)
|
|
675
668
|
|
|
676
669
|
|
|
677
670
|
class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
678
|
-
output_pin =
|
|
679
|
-
|
|
671
|
+
output_pin = ColonelPinChoiceField(
|
|
672
|
+
queryset=ColonelPin.objects.filter(output=True, native=True),
|
|
680
673
|
widget=autocomplete.ListSelect2(
|
|
681
674
|
url='autocomplete-colonel-pins',
|
|
682
675
|
forward=[
|
|
@@ -705,6 +698,7 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
|
705
698
|
)
|
|
706
699
|
|
|
707
700
|
def save(self, commit=True):
|
|
701
|
+
self.instance.config['output_pin_no'] = self.cleaned_data['output_pin'].no
|
|
708
702
|
if len(self.cleaned_data['order']) > 3:
|
|
709
703
|
self.instance.config['has_white'] = True
|
|
710
704
|
else:
|
|
@@ -714,62 +708,25 @@ class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
|
714
708
|
|
|
715
709
|
def clean(self):
|
|
716
710
|
super().clean()
|
|
717
|
-
if
|
|
711
|
+
if not self.cleaned_data.get('colonel'):
|
|
718
712
|
return self.cleaned_data
|
|
719
713
|
if not self.cleaned_data.get('output_pin'):
|
|
720
714
|
return self.cleaned_data
|
|
721
|
-
if self.instance.pk:
|
|
722
|
-
selected = self.instance.config.get('output_pin')
|
|
723
|
-
else:
|
|
724
|
-
self.cleaned_data['value_units'] = '%'
|
|
725
|
-
selected = None
|
|
726
|
-
output_pins = get_available_gpio_pins(
|
|
727
|
-
self.cleaned_data['colonel'], filters={'output': True, 'native': True},
|
|
728
|
-
selected=selected
|
|
729
|
-
)
|
|
730
|
-
if self.cleaned_data['output_pin'] not in output_pins:
|
|
731
|
-
self.add_error(
|
|
732
|
-
'output_pin',
|
|
733
|
-
"Sorry, but GPIO%d pin can not be used as output pin "
|
|
734
|
-
% self.cleaned_data['output_pin']
|
|
735
|
-
)
|
|
736
|
-
return self.cleaned_data
|
|
737
715
|
|
|
738
|
-
|
|
739
|
-
return self.cleaned_data
|
|
716
|
+
self._clean_pin('output_pin')
|
|
740
717
|
|
|
718
|
+
if not self.cleaned_data.get('controls'):
|
|
719
|
+
return self.cleaned_data
|
|
741
720
|
|
|
742
|
-
|
|
743
|
-
for i, control in enumerate(self.cleaned_data['controls']):
|
|
744
|
-
for key, val in control.items():
|
|
745
|
-
if key in ('pin', 'touch_threshold'):
|
|
746
|
-
self.cleaned_data['controls'][i][key] = int(val)
|
|
747
|
-
else:
|
|
748
|
-
self.cleaned_data['controls'][i][key] = val
|
|
749
|
-
|
|
750
|
-
for i, control in enumerate(self.cleaned_data['controls']):
|
|
751
|
-
try:
|
|
752
|
-
selected = self.instance.config['controls'][i]['pin']
|
|
753
|
-
except:
|
|
754
|
-
selected = None
|
|
755
|
-
free_pins = get_available_gpio_pins(
|
|
756
|
-
self.cleaned_data['colonel'], filters={'input': True},
|
|
757
|
-
selected=selected
|
|
758
|
-
)
|
|
759
|
-
if control['pin'] not in free_pins:
|
|
760
|
-
self.add_error(
|
|
761
|
-
'controls',
|
|
762
|
-
"Sorry, but GPIO%d pin is occupied."
|
|
763
|
-
% control['pin']
|
|
764
|
-
)
|
|
765
|
-
return
|
|
721
|
+
self._clean_controls()
|
|
766
722
|
|
|
767
723
|
return self.cleaned_data
|
|
768
724
|
|
|
769
725
|
|
|
726
|
+
|
|
770
727
|
class DualMotorValveForm(ColonelComponentForm):
|
|
771
|
-
open_pin =
|
|
772
|
-
|
|
728
|
+
open_pin = ColonelPinChoiceField(
|
|
729
|
+
queryset=ColonelPin.objects.filter(output=True),
|
|
773
730
|
widget=autocomplete.ListSelect2(
|
|
774
731
|
url='autocomplete-colonel-pins',
|
|
775
732
|
forward=[
|
|
@@ -786,8 +743,8 @@ class DualMotorValveForm(ColonelComponentForm):
|
|
|
786
743
|
required=True, min_value=0.01, max_value=1000000000,
|
|
787
744
|
help_text="Time in seconds to open."
|
|
788
745
|
)
|
|
789
|
-
close_pin =
|
|
790
|
-
|
|
746
|
+
close_pin = ColonelPinChoiceField(
|
|
747
|
+
queryset=ColonelPin.objects.filter(output=True),
|
|
791
748
|
widget=autocomplete.ListSelect2(
|
|
792
749
|
url='autocomplete-colonel-pins',
|
|
793
750
|
forward=[
|
|
@@ -805,52 +762,30 @@ class DualMotorValveForm(ColonelComponentForm):
|
|
|
805
762
|
help_text="Time in seconds to close."
|
|
806
763
|
)
|
|
807
764
|
|
|
765
|
+
|
|
808
766
|
def clean(self):
|
|
809
767
|
super().clean()
|
|
810
|
-
if
|
|
768
|
+
if not self.cleaned_data.get('colonel'):
|
|
811
769
|
return self.cleaned_data
|
|
812
770
|
if not self.cleaned_data.get('open_pin'):
|
|
813
771
|
return self.cleaned_data
|
|
814
772
|
if not self.cleaned_data.get('close_pin'):
|
|
815
773
|
return self.cleaned_data
|
|
816
774
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
else:
|
|
820
|
-
selected = None
|
|
821
|
-
output_pins = get_available_gpio_pins(
|
|
822
|
-
self.cleaned_data['colonel'], filters={'output': True},
|
|
823
|
-
selected=selected
|
|
824
|
-
)
|
|
825
|
-
if self.cleaned_data['open_pin'] not in output_pins:
|
|
826
|
-
self.add_error(
|
|
827
|
-
'open_pin',
|
|
828
|
-
"Sorry, but GPIO%d pin can not be used as output pin "
|
|
829
|
-
% self.cleaned_data['open_pin']
|
|
830
|
-
)
|
|
831
|
-
return
|
|
775
|
+
self._clean_pin('open_pin')
|
|
776
|
+
self._clean_pin('close_pin')
|
|
832
777
|
|
|
833
|
-
if self.instance.pk:
|
|
834
|
-
selected = self.instance.config.get('close_pin')
|
|
835
|
-
else:
|
|
836
|
-
selected = None
|
|
837
|
-
output_pins = get_available_gpio_pins(
|
|
838
|
-
self.cleaned_data['colonel'], filters={'output': True},
|
|
839
|
-
selected=selected
|
|
840
|
-
)
|
|
841
|
-
if self.cleaned_data['close_pin'] not in output_pins:
|
|
842
|
-
self.add_error(
|
|
843
|
-
'close_pin',
|
|
844
|
-
"Sorry, but GPIO%d pin can not be used as output pin "
|
|
845
|
-
% self.cleaned_data['close_pin']
|
|
846
|
-
)
|
|
847
|
-
return
|
|
848
778
|
return self.cleaned_data
|
|
849
779
|
|
|
780
|
+
def save(self, commit=True):
|
|
781
|
+
self.instance.config['open_pin_no'] = self.cleaned_data['open_pin'].no
|
|
782
|
+
self.instance.config['close_pin_no'] = self.cleaned_data['close_pin'].no
|
|
783
|
+
return super().save(commit=commit)
|
|
784
|
+
|
|
850
785
|
|
|
851
786
|
class BlindsConfigForm(ColonelComponentForm):
|
|
852
|
-
open_pin =
|
|
853
|
-
|
|
787
|
+
open_pin = ColonelPinChoiceField(
|
|
788
|
+
queryset=ColonelPin.objects.filter(output=True),
|
|
854
789
|
widget=autocomplete.ListSelect2(
|
|
855
790
|
url='autocomplete-colonel-pins',
|
|
856
791
|
forward=[
|
|
@@ -863,8 +798,8 @@ class BlindsConfigForm(ColonelComponentForm):
|
|
|
863
798
|
open_action = forms.ChoiceField(
|
|
864
799
|
choices=(('HIGH', "HIGH"), ('LOW', "LOW")),
|
|
865
800
|
)
|
|
866
|
-
close_pin =
|
|
867
|
-
|
|
801
|
+
close_pin = ColonelPinChoiceField(
|
|
802
|
+
queryset=ColonelPin.objects.filter(output=True),
|
|
868
803
|
widget=autocomplete.ListSelect2(
|
|
869
804
|
url='autocomplete-colonel-pins',
|
|
870
805
|
forward=[
|
|
@@ -922,47 +857,15 @@ class BlindsConfigForm(ColonelComponentForm):
|
|
|
922
857
|
|
|
923
858
|
def clean(self):
|
|
924
859
|
super().clean()
|
|
925
|
-
if
|
|
860
|
+
if not self.cleaned_data.get('colonel'):
|
|
926
861
|
return self.cleaned_data
|
|
927
862
|
if not self.cleaned_data.get('open_pin'):
|
|
928
863
|
return self.cleaned_data
|
|
929
864
|
if not self.cleaned_data.get('close_pin'):
|
|
930
865
|
return self.cleaned_data
|
|
931
866
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
else:
|
|
935
|
-
selected = None
|
|
936
|
-
output_pins = get_available_gpio_pins(
|
|
937
|
-
self.cleaned_data['colonel'],
|
|
938
|
-
filters={'output': True},
|
|
939
|
-
selected=selected
|
|
940
|
-
)
|
|
941
|
-
if self.cleaned_data['open_pin'] not in output_pins:
|
|
942
|
-
self.add_error(
|
|
943
|
-
'open_pin',
|
|
944
|
-
"Sorry, but GPIO%d pin can not be used as output pin "
|
|
945
|
-
% self.cleaned_data['open_pin']
|
|
946
|
-
)
|
|
947
|
-
return
|
|
948
|
-
|
|
949
|
-
if self.instance.pk:
|
|
950
|
-
selected = self.instance.config.get('close_pin')
|
|
951
|
-
else:
|
|
952
|
-
selected = None
|
|
953
|
-
output_pins = get_available_gpio_pins(
|
|
954
|
-
self.cleaned_data['colonel'],
|
|
955
|
-
filters={'output': True},
|
|
956
|
-
selected=selected
|
|
957
|
-
)
|
|
958
|
-
if self.cleaned_data['close_pin'] not in output_pins:
|
|
959
|
-
self.add_error(
|
|
960
|
-
'close_pin',
|
|
961
|
-
"Sorry, but GPIO%d pin can not be used as output pin "
|
|
962
|
-
% self.cleaned_data['close_pin']
|
|
963
|
-
)
|
|
964
|
-
return
|
|
965
|
-
|
|
867
|
+
self._clean_pin('open_pin')
|
|
868
|
+
self._clean_pin('close_pin')
|
|
966
869
|
|
|
967
870
|
if 'controls' not in self.cleaned_data:
|
|
968
871
|
return self.cleaned_data
|
|
@@ -981,38 +884,19 @@ class BlindsConfigForm(ColonelComponentForm):
|
|
|
981
884
|
self.add_error('controls', "Both must use the same control method.")
|
|
982
885
|
return self.cleaned_data
|
|
983
886
|
|
|
984
|
-
|
|
985
|
-
# TODO: Formset factory should return proper field value types instead of str type
|
|
986
|
-
for i, control in enumerate(self.cleaned_data['controls']):
|
|
987
|
-
for key, val in control.items():
|
|
988
|
-
if key in ('pin', 'touch_threshold'):
|
|
989
|
-
self.cleaned_data['controls'][i][key] = int(val)
|
|
990
|
-
else:
|
|
991
|
-
self.cleaned_data['controls'][i][key] = val
|
|
992
|
-
|
|
993
|
-
for i, control in enumerate(self.cleaned_data['controls']):
|
|
994
|
-
try:
|
|
995
|
-
selected = self.instance.config['controls'][i]['pin']
|
|
996
|
-
except:
|
|
997
|
-
selected = None
|
|
998
|
-
free_pins = get_available_gpio_pins(
|
|
999
|
-
self.cleaned_data['colonel'], filters={'input': True},
|
|
1000
|
-
selected=selected
|
|
1001
|
-
)
|
|
1002
|
-
if control['pin'] not in free_pins:
|
|
1003
|
-
self.add_error(
|
|
1004
|
-
'controls',
|
|
1005
|
-
"Sorry, but GPIO%d pin is occupied."
|
|
1006
|
-
% control['pin']
|
|
1007
|
-
)
|
|
1008
|
-
return
|
|
887
|
+
self._clean_controls()
|
|
1009
888
|
|
|
1010
889
|
return self.cleaned_data
|
|
1011
890
|
|
|
891
|
+
def save(self, commit=True):
|
|
892
|
+
self.instance.config['open_pin_no'] = self.cleaned_data['open_pin'].no
|
|
893
|
+
self.instance.config['close_pin_no'] = self.cleaned_data['close_pin'].no
|
|
894
|
+
return super().save(commit=commit)
|
|
895
|
+
|
|
1012
896
|
|
|
1013
897
|
class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
|
|
1014
|
-
power_pin =
|
|
1015
|
-
|
|
898
|
+
power_pin = ColonelPinChoiceField(
|
|
899
|
+
queryset=ColonelPin.objects.filter(output=True),
|
|
1016
900
|
widget=autocomplete.ListSelect2(
|
|
1017
901
|
url='autocomplete-colonel-pins',
|
|
1018
902
|
forward=[
|
|
@@ -1025,8 +909,8 @@ class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
|
|
|
1025
909
|
power_action = forms.ChoiceField(
|
|
1026
910
|
choices=(('HIGH', "HIGH"), ('LOW', "LOW")),
|
|
1027
911
|
)
|
|
1028
|
-
sensor_pin =
|
|
1029
|
-
|
|
912
|
+
sensor_pin = ColonelPinChoiceField(
|
|
913
|
+
queryset=ColonelPin.objects.filter(input=True),
|
|
1030
914
|
widget=autocomplete.ListSelect2(
|
|
1031
915
|
url='autocomplete-colonel-pins',
|
|
1032
916
|
forward=[
|
|
@@ -1053,49 +937,19 @@ class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
|
|
|
1053
937
|
|
|
1054
938
|
def clean(self):
|
|
1055
939
|
super().clean()
|
|
1056
|
-
if
|
|
940
|
+
if not self.cleaned_data.get('colonel'):
|
|
1057
941
|
return self.cleaned_data
|
|
1058
942
|
if 'sensor_pin' not in self.cleaned_data:
|
|
1059
943
|
return self.cleaned_data
|
|
1060
944
|
if 'power_pin' not in self.cleaned_data:
|
|
1061
945
|
return self.cleaned_data
|
|
1062
946
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
selected = self.instance.config.get('power_pin')
|
|
1066
|
-
else:
|
|
1067
|
-
selected = None
|
|
1068
|
-
output_pins = get_available_gpio_pins(
|
|
1069
|
-
self.cleaned_data['colonel'], filters={'output': True},
|
|
1070
|
-
selected=selected
|
|
1071
|
-
)
|
|
1072
|
-
if self.cleaned_data['power_pin'] not in output_pins:
|
|
1073
|
-
self.add_error(
|
|
1074
|
-
'output_pin',
|
|
1075
|
-
"Sorry, but GPIO%d pin can not be used as output pin "
|
|
1076
|
-
% self.cleaned_data['power_pin']
|
|
1077
|
-
)
|
|
1078
|
-
return self.cleaned_data
|
|
1079
|
-
|
|
947
|
+
self._clean_pin('sensor_pin')
|
|
948
|
+
self._clean_pin('power_pin')
|
|
1080
949
|
|
|
1081
950
|
|
|
1082
|
-
if self.
|
|
1083
|
-
|
|
1084
|
-
else:
|
|
1085
|
-
selected = None
|
|
1086
|
-
input_pins = get_available_gpio_pins(
|
|
1087
|
-
self.cleaned_data['colonel'], filters={'input': True},
|
|
1088
|
-
selected=selected
|
|
1089
|
-
)
|
|
1090
|
-
if self.cleaned_data['sensor_pin'] not in input_pins:
|
|
1091
|
-
self.add_error(
|
|
1092
|
-
'pin',
|
|
1093
|
-
"Sorry, but GPIO%d pin can not be used as input pin "
|
|
1094
|
-
% self.cleaned_data['pin']
|
|
1095
|
-
)
|
|
1096
|
-
return
|
|
1097
|
-
if self.cleaned_data['sensor_pin'] > 100:
|
|
1098
|
-
if self.cleaned_data['sensor_pin'] < 126:
|
|
951
|
+
if self.cleaned_data['sensor_pin'].no > 100:
|
|
952
|
+
if self.cleaned_data['sensor_pin'].no < 126:
|
|
1099
953
|
if self.cleaned_data.get('sensor_pull') == 'HIGH':
|
|
1100
954
|
self.add_error(
|
|
1101
955
|
'sensor_pull',
|
|
@@ -1114,55 +968,43 @@ class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
|
|
|
1114
968
|
"if that's what you want to do."
|
|
1115
969
|
)
|
|
1116
970
|
elif self.cleaned_data.get('sensor_pull') != 'FLOATING':
|
|
1117
|
-
|
|
1118
|
-
self.cleaned_data['colonel'], filters={'output': True},
|
|
1119
|
-
selected=selected
|
|
1120
|
-
)
|
|
1121
|
-
if self.cleaned_data['sensor_pin'] not in pins_available_for_pull:
|
|
971
|
+
if not self.cleaned_data['sensor_pin'].output:
|
|
1122
972
|
self.add_error(
|
|
1123
|
-
'
|
|
1124
|
-
"Sorry, but
|
|
1125
|
-
"
|
|
973
|
+
'sensor_pin',
|
|
974
|
+
f"Sorry, but {self.cleaned_data['sensor_pin']} "
|
|
975
|
+
f"does not have internal pull HIGH/LOW"
|
|
976
|
+
" resistance capability"
|
|
1126
977
|
)
|
|
1127
|
-
return
|
|
1128
978
|
|
|
1129
979
|
return self.cleaned_data
|
|
1130
980
|
|
|
981
|
+
def save(self, commit=True):
|
|
982
|
+
self.instance.config['sensor_pin_no'] = self.cleaned_data['sensor_pin'].no
|
|
983
|
+
self.instance.config['power_pin_no'] = self.cleaned_data['power_pin'].no
|
|
984
|
+
return super().save(commit=commit)
|
|
985
|
+
|
|
1131
986
|
|
|
987
|
+
class TTLockConfigForm(ColonelComponentForm):
|
|
988
|
+
pass
|
|
989
|
+
|
|
990
|
+
def clean(self):
|
|
991
|
+
if not self.instance or not self.instance.pk:
|
|
992
|
+
from .controllers import TTLock
|
|
993
|
+
other_lock = self.cleaned_data['colonel'].components.filter(
|
|
994
|
+
controller_uid=TTLock.uid
|
|
995
|
+
).first()
|
|
996
|
+
if other_lock:
|
|
997
|
+
raise forms.ValidationError(
|
|
998
|
+
f"Single Colonel can support single TTLock only.\n"
|
|
999
|
+
f"You already have {other_lock} on this Colonel."
|
|
1000
|
+
)
|
|
1001
|
+
return self.cleaned_data
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
def save(self, commit=True):
|
|
1005
|
+
obj = super(ColonelComponentForm, self).save(commit)
|
|
1006
|
+
if commit:
|
|
1007
|
+
self.cleaned_data['colonel'].components.add(obj)
|
|
1008
|
+
self.cleaned_data['colonel'].save()
|
|
1009
|
+
return obj
|
|
1132
1010
|
|
|
1133
|
-
#
|
|
1134
|
-
# class ColonelBLEClimateSensorConfigForm(
|
|
1135
|
-
# ColonelComponentMixin, BaseComponentForm
|
|
1136
|
-
# ):
|
|
1137
|
-
# colonel = forms.ModelChoiceField(queryset=Colonel.objects.all())
|
|
1138
|
-
# ble_device = forms.ModelChoiceField(
|
|
1139
|
-
# queryset=BLEDevice.objects.filter(
|
|
1140
|
-
# type=BLE_DEVICE_TYPE_GOVEE_MULTISENSOR
|
|
1141
|
-
# )
|
|
1142
|
-
# )
|
|
1143
|
-
# additional_fields = ('colonel', 'ble_device')
|
|
1144
|
-
#
|
|
1145
|
-
# def __init__(self, *args, **kwargs):
|
|
1146
|
-
# super().__init__(*args, **kwargs)
|
|
1147
|
-
# qs = self.fields['ble_device'].queryset
|
|
1148
|
-
# if self.instance.pk:
|
|
1149
|
-
# self.fields['ble_device'].queryset = qs.filter(
|
|
1150
|
-
# Q(component__isnull=True) | Q(component=self.instance)
|
|
1151
|
-
# )
|
|
1152
|
-
# else:
|
|
1153
|
-
# self.fields['ble_device'].queryset = qs.filter(component__isnull=True)
|
|
1154
|
-
#
|
|
1155
|
-
# def clean(self):
|
|
1156
|
-
# colonel_ble_devices = self.cleaned_data['colonel'].ble_devices.all()
|
|
1157
|
-
# if self.cleaned_data['ble_device'] not in colonel_ble_devices:
|
|
1158
|
-
# available_colonels = self.cleaned_data['ble_device'].colonels.all()
|
|
1159
|
-
# self.add_error(
|
|
1160
|
-
# 'ble_device',
|
|
1161
|
-
# _("This BLE device is available only on colonel%s: %s" %
|
|
1162
|
-
# (
|
|
1163
|
-
# 's' if len(available_colonels) > 1 else '',
|
|
1164
|
-
# ', '.join([str(c) for c in available_colonels])
|
|
1165
|
-
# )
|
|
1166
|
-
# )
|
|
1167
|
-
# )
|
|
1168
|
-
# return self.cleaned_data
|