simo 2.11.4__py3-none-any.whl → 3.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of simo might be problematic. Click here for more details.
- simo/__pycache__/settings.cpython-312.pyc +0 -0
- simo/asgi.py +25 -6
- simo/automation/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/automation/controllers.py +18 -2
- simo/automation/forms.py +15 -24
- simo/core/__pycache__/admin.cpython-312.pyc +0 -0
- simo/core/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/core/__pycache__/forms.cpython-312.pyc +0 -0
- simo/core/__pycache__/models.cpython-312.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-312.pyc +0 -0
- simo/core/__pycache__/signal_receivers.cpython-312.pyc +0 -0
- simo/core/__pycache__/tasks.cpython-312.pyc +0 -0
- simo/core/admin.py +5 -4
- simo/core/base_types.py +191 -18
- simo/core/controllers.py +259 -26
- simo/core/forms.py +10 -2
- simo/core/management/_hub_template/hub/nginx.conf +23 -50
- simo/core/management/_hub_template/hub/supervisor.conf +15 -0
- simo/core/mcp.py +154 -0
- simo/core/migrations/0051_instance_ai_memory.py +18 -0
- simo/core/migrations/__pycache__/0051_instance_ai_memory.cpython-312.pyc +0 -0
- simo/core/models.py +3 -0
- simo/core/serializers.py +120 -0
- simo/core/signal_receivers.py +1 -1
- simo/core/tasks.py +1 -3
- simo/core/utils/__pycache__/type_constants.cpython-312.pyc +0 -0
- simo/core/utils/type_constants.py +78 -17
- simo/fleet/__pycache__/admin.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/api.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/serializers.cpython-312.pyc +0 -0
- simo/fleet/admin.py +5 -1
- simo/fleet/api.py +2 -27
- simo/fleet/base_types.py +35 -4
- simo/fleet/controllers.py +150 -156
- simo/fleet/forms.py +56 -88
- simo/fleet/gateways.py +8 -15
- simo/fleet/migrations/0055_colonel_is_vo_active_colonel_last_wake_and_more.py +28 -0
- simo/fleet/migrations/0056_delete_customdalidevice.py +16 -0
- simo/fleet/migrations/__pycache__/0055_colonel_is_vo_active_colonel_last_wake_and_more.cpython-312.pyc +0 -0
- simo/fleet/migrations/__pycache__/0056_delete_customdalidevice.cpython-312.pyc +0 -0
- simo/fleet/models.py +13 -72
- simo/fleet/serializers.py +1 -48
- simo/fleet/socket_consumers.py +100 -39
- simo/fleet/tasks.py +2 -22
- simo/fleet/voice_assistant.py +893 -0
- simo/generic/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/generic/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/generic/base_types.py +70 -10
- simo/generic/controllers.py +102 -15
- simo/generic/gateways.py +10 -10
- simo/mcp_server/__init__.py +0 -0
- simo/mcp_server/__pycache__/__init__.cpython-312.pyc +0 -0
- simo/mcp_server/__pycache__/admin.cpython-312.pyc +0 -0
- simo/mcp_server/__pycache__/models.cpython-312.pyc +0 -0
- simo/mcp_server/admin.py +18 -0
- simo/mcp_server/app.py +4 -0
- simo/mcp_server/auth.py +34 -0
- simo/mcp_server/dummy.py +22 -0
- simo/mcp_server/migrations/0001_initial.py +30 -0
- simo/mcp_server/migrations/0002_alter_instanceaccesstoken_date_expired.py +18 -0
- simo/mcp_server/migrations/0003_instanceaccesstoken_issuer.py +18 -0
- simo/mcp_server/migrations/__init__.py +0 -0
- simo/mcp_server/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/0002_alter_instanceaccesstoken_date_expired.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/0003_instanceaccesstoken_issuer.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
- simo/mcp_server/models.py +27 -0
- simo/mcp_server/server.py +60 -0
- simo/mcp_server/tasks.py +19 -0
- simo/multimedia/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/multimedia/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/multimedia/base_types.py +29 -4
- simo/multimedia/controllers.py +66 -19
- simo/settings.py +1 -0
- simo/users/__pycache__/utils.cpython-312.pyc +0 -0
- simo/users/utils.py +10 -0
- {simo-2.11.4.dist-info → simo-3.0.1.dist-info}/METADATA +12 -4
- {simo-2.11.4.dist-info → simo-3.0.1.dist-info}/RECORD +89 -63
- simo/fleet/custom_dali_operations.py +0 -287
- {simo-2.11.4.dist-info → simo-3.0.1.dist-info}/WHEEL +0 -0
- {simo-2.11.4.dist-info → simo-3.0.1.dist-info}/entry_points.txt +0 -0
- {simo-2.11.4.dist-info → simo-3.0.1.dist-info}/licenses/LICENSE.md +0 -0
- {simo-2.11.4.dist-info → simo-3.0.1.dist-info}/top_level.txt +0 -0
simo/fleet/base_types.py
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
from simo.core.base_types import BaseComponentType
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
'
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
class DaliDeviceType(BaseComponentType):
|
|
6
|
+
slug = 'dali'
|
|
7
|
+
name = _("Dali Device")
|
|
8
|
+
description = _("DALI bus device discovered and managed by Fleet.")
|
|
9
|
+
purpose = _("Use for DALI-compliant gear integration.")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SentinelType(BaseComponentType):
|
|
13
|
+
slug = 'sentinel'
|
|
14
|
+
name = _("Sentinel")
|
|
15
|
+
description = _("Room environment sensor reporting readings, alarm siren, AI voice assistant.")
|
|
16
|
+
purpose = _("Use to capture ambient conditions of a zone, raise alarms, provice AI voice assistant.")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class VoiceAssistantType(BaseComponentType):
|
|
20
|
+
slug = 'voice-assistant'
|
|
21
|
+
name = _("Voice Assistant")
|
|
22
|
+
description = _("SIMO AI smart home voice assistant.")
|
|
23
|
+
purpose = _("Control smart home instance using voice commands.")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _export_base_types_dict():
|
|
28
|
+
import inspect as _inspect
|
|
29
|
+
mapping = {}
|
|
30
|
+
for _name, _obj in globals().items():
|
|
31
|
+
if _inspect.isclass(_obj) and issubclass(_obj, BaseComponentType) \
|
|
32
|
+
and _obj is not BaseComponentType and getattr(_obj, 'slug', None):
|
|
33
|
+
mapping[_obj.slug] = _obj.name
|
|
34
|
+
return mapping
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
BASE_TYPES = _export_base_types_dict()
|
simo/fleet/controllers.py
CHANGED
|
@@ -13,13 +13,14 @@ from simo.core.controllers import (
|
|
|
13
13
|
Lock, ControllerBase, SingleSwitchWidget
|
|
14
14
|
)
|
|
15
15
|
from simo.core.app_widgets import NumericSensorWidget, AirQualityWidget
|
|
16
|
+
from .base_types import DaliDeviceType, SentinelType, VoiceAssistantType
|
|
16
17
|
from simo.core.utils.helpers import heat_index
|
|
17
18
|
from simo.core.utils.serialization import (
|
|
18
19
|
serialize_form_data, deserialize_form_data
|
|
19
20
|
)
|
|
20
21
|
from simo.core.forms import BaseComponentForm
|
|
21
22
|
from simo.generic.controllers import StateSelect
|
|
22
|
-
from .models import Colonel
|
|
23
|
+
from .models import Colonel
|
|
23
24
|
from .gateways import FleetGatewayHandler
|
|
24
25
|
from .forms import (
|
|
25
26
|
ColonelPinChoiceField,
|
|
@@ -33,14 +34,19 @@ from .forms import (
|
|
|
33
34
|
TTLockConfigForm, DALIDeviceConfigForm, DaliLampForm, DaliGearGroupForm,
|
|
34
35
|
DaliSwitchConfigForm,
|
|
35
36
|
DaliOccupancySensorConfigForm, DALILightSensorConfigForm,
|
|
36
|
-
DALIButtonConfigForm,
|
|
37
|
-
RoomZonePresenceConfigForm
|
|
37
|
+
DALIButtonConfigForm, SentinelDeviceConfigForm,
|
|
38
|
+
RoomZonePresenceConfigForm, VoiceAssistantConfigForm
|
|
38
39
|
)
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
class FleetDeviceMixin:
|
|
42
43
|
|
|
43
44
|
def update_options(self, options):
|
|
45
|
+
"""Update runtime options on the device via the Colonel gateway.
|
|
46
|
+
|
|
47
|
+
Parameters:
|
|
48
|
+
- options (dict): Device-specific options; merged on the device side.
|
|
49
|
+
"""
|
|
44
50
|
GatewayObjectCommand(
|
|
45
51
|
self.component.gateway,
|
|
46
52
|
Colonel(id=self.component.config['colonel']),
|
|
@@ -50,12 +56,14 @@ class FleetDeviceMixin:
|
|
|
50
56
|
).publish()
|
|
51
57
|
|
|
52
58
|
def disable_controls(self):
|
|
59
|
+
"""Disable device controls temporarily (e.g., lock UI inputs)."""
|
|
53
60
|
options = self.component.meta.get('options', {})
|
|
54
61
|
if options.get('controls_enabled', True) != False:
|
|
55
62
|
options['controls_enabled'] = False
|
|
56
63
|
self.update_options(options)
|
|
57
64
|
|
|
58
65
|
def enable_controls(self):
|
|
66
|
+
"""Enable device controls if previously disabled."""
|
|
59
67
|
options = self.component.meta.get('options', {})
|
|
60
68
|
if options.get('controls_enabled', True) != True:
|
|
61
69
|
options['controls_enabled'] = True
|
|
@@ -86,6 +94,7 @@ class FleetDeviceMixin:
|
|
|
86
94
|
|
|
87
95
|
class BasicSensorMixin:
|
|
88
96
|
gateway_class = FleetGatewayHandler
|
|
97
|
+
accepts_value = False
|
|
89
98
|
|
|
90
99
|
def _get_occupied_pins(self):
|
|
91
100
|
return [
|
|
@@ -565,6 +574,11 @@ class TTLock(FleetDeviceMixin, Lock):
|
|
|
565
574
|
|
|
566
575
|
|
|
567
576
|
def add_code(self, code):
|
|
577
|
+
"""Add a numeric access code to the smart lock.
|
|
578
|
+
|
|
579
|
+
Parameters:
|
|
580
|
+
- code (str|int): 4–8 digit numeric code.
|
|
581
|
+
"""
|
|
568
582
|
code = str(code)
|
|
569
583
|
assert 4 <= len(code) <= 8
|
|
570
584
|
for no in code:
|
|
@@ -580,6 +594,11 @@ class TTLock(FleetDeviceMixin, Lock):
|
|
|
580
594
|
).publish()
|
|
581
595
|
|
|
582
596
|
def delete_code(self, code):
|
|
597
|
+
"""Delete a numeric access code from the lock.
|
|
598
|
+
|
|
599
|
+
Parameters:
|
|
600
|
+
- code (str|int): The exact code to remove.
|
|
601
|
+
"""
|
|
583
602
|
GatewayObjectCommand(
|
|
584
603
|
self.component.gateway,
|
|
585
604
|
Colonel(id=self.component.config['colonel']),
|
|
@@ -588,6 +607,7 @@ class TTLock(FleetDeviceMixin, Lock):
|
|
|
588
607
|
).publish()
|
|
589
608
|
|
|
590
609
|
def clear_codes(self):
|
|
610
|
+
"""Remove all numeric access codes from the lock."""
|
|
591
611
|
GatewayObjectCommand(
|
|
592
612
|
self.component.gateway,
|
|
593
613
|
Colonel(id=self.component.config['colonel']),
|
|
@@ -596,6 +616,7 @@ class TTLock(FleetDeviceMixin, Lock):
|
|
|
596
616
|
).publish()
|
|
597
617
|
|
|
598
618
|
def get_codes(self):
|
|
619
|
+
"""Request the list of numeric access codes from the lock."""
|
|
599
620
|
GatewayObjectCommand(
|
|
600
621
|
self.component.gateway,
|
|
601
622
|
Colonel(id=self.component.config['colonel']),
|
|
@@ -604,6 +625,7 @@ class TTLock(FleetDeviceMixin, Lock):
|
|
|
604
625
|
).publish()
|
|
605
626
|
|
|
606
627
|
def add_fingerprint(self):
|
|
628
|
+
"""Start lock-side enrollment of a new fingerprint."""
|
|
607
629
|
GatewayObjectCommand(
|
|
608
630
|
self.component.gateway,
|
|
609
631
|
Colonel(id=self.component.config['colonel']),
|
|
@@ -612,6 +634,7 @@ class TTLock(FleetDeviceMixin, Lock):
|
|
|
612
634
|
).publish()
|
|
613
635
|
|
|
614
636
|
def delete_fingerprint(self, code):
|
|
637
|
+
"""Delete a fingerprint by its identifier on the lock."""
|
|
615
638
|
GatewayObjectCommand(
|
|
616
639
|
self.component.gateway,
|
|
617
640
|
Colonel(id=self.component.config['colonel']),
|
|
@@ -620,6 +643,7 @@ class TTLock(FleetDeviceMixin, Lock):
|
|
|
620
643
|
).publish()
|
|
621
644
|
|
|
622
645
|
def clear_fingerprints(self):
|
|
646
|
+
"""Remove all fingerprints from the lock."""
|
|
623
647
|
self.component.meta['clear_fingerprints'] = True
|
|
624
648
|
self.component.save(update_fields=['meta'])
|
|
625
649
|
GatewayObjectCommand(
|
|
@@ -630,6 +654,7 @@ class TTLock(FleetDeviceMixin, Lock):
|
|
|
630
654
|
).publish()
|
|
631
655
|
|
|
632
656
|
def get_fingerprints(self):
|
|
657
|
+
"""Request the list of fingerprint identifiers from the lock."""
|
|
633
658
|
GatewayObjectCommand(
|
|
634
659
|
self.component.gateway,
|
|
635
660
|
Colonel(id=self.component.config['colonel']),
|
|
@@ -638,22 +663,12 @@ class TTLock(FleetDeviceMixin, Lock):
|
|
|
638
663
|
).publish()
|
|
639
664
|
|
|
640
665
|
def check_locked_status(self):
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
periodically
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
If anything new is observer, connection is made to the lock
|
|
648
|
-
and reported back to the system.
|
|
649
|
-
This helps to save batteries of a lock,
|
|
650
|
-
however it is not always as timed as we would want to.
|
|
651
|
-
Sometimes it can take even up to 20s for these updates to occur.
|
|
652
|
-
|
|
653
|
-
This method is here to force immediate connection to the lock
|
|
654
|
-
to check it's current status. After this method is called,
|
|
655
|
-
we might expect to receive an update within 2 seconds or less.
|
|
656
|
-
'''
|
|
666
|
+
"""Force an immediate connection to the lock to refresh status.
|
|
667
|
+
|
|
668
|
+
The lock usually reports status periodically to save batteries; this
|
|
669
|
+
method asks the gateway to connect proactively so that an update is
|
|
670
|
+
expected within a couple of seconds.
|
|
671
|
+
"""
|
|
657
672
|
GatewayObjectCommand(
|
|
658
673
|
self.component.gateway,
|
|
659
674
|
Colonel(id=self.component.config['colonel']),
|
|
@@ -687,13 +702,21 @@ class DALIDevice(FleetDeviceMixin, ControllerBase):
|
|
|
687
702
|
name = "DALI Device"
|
|
688
703
|
discovery_msg = _("Please hook up your new DALI device to your DALI bus.")
|
|
689
704
|
|
|
690
|
-
base_type =
|
|
705
|
+
base_type = DaliDeviceType
|
|
691
706
|
default_value = False
|
|
692
707
|
app_widget = SingleSwitchWidget
|
|
693
708
|
|
|
694
709
|
def _validate_val(self, value, occasion=None):
|
|
695
710
|
return value
|
|
696
711
|
|
|
712
|
+
def send(self, value):
|
|
713
|
+
"""Control DALI device on/off.
|
|
714
|
+
|
|
715
|
+
Parameters:
|
|
716
|
+
- value (bool): True to turn on; False to turn off.
|
|
717
|
+
"""
|
|
718
|
+
return super().send(value)
|
|
719
|
+
|
|
697
720
|
@classmethod
|
|
698
721
|
def _init_discovery(self, form_cleaned_data):
|
|
699
722
|
from simo.core.models import Gateway
|
|
@@ -748,7 +771,9 @@ class DALIDevice(FleetDeviceMixin, ControllerBase):
|
|
|
748
771
|
if data['result'].get('di') is not None:
|
|
749
772
|
started_with['name'] += f" - {data['result']['di']}"
|
|
750
773
|
started_with['controller_uid'] = controller_uid
|
|
751
|
-
|
|
774
|
+
# Normalize base type to slug for form
|
|
775
|
+
bt = getattr(controller_cls, 'base_type', None)
|
|
776
|
+
started_with['base_type'] = bt if isinstance(bt, str) else getattr(bt, 'slug', None)
|
|
752
777
|
form = controller_cls.config_form(
|
|
753
778
|
controller_uid=controller_cls.uid, data=started_with
|
|
754
779
|
)
|
|
@@ -781,10 +806,10 @@ class DALIDevice(FleetDeviceMixin, ControllerBase):
|
|
|
781
806
|
return {'error': 'INVALID INITIAL DISCOVERY FORM!'}
|
|
782
807
|
|
|
783
808
|
def replace(self):
|
|
784
|
-
"""
|
|
785
|
-
|
|
786
|
-
and
|
|
787
|
-
|
|
809
|
+
"""Replace a failed DALI device with a brand new one on the line.
|
|
810
|
+
|
|
811
|
+
Connect a new device to the DALI bus and invoke this method on the
|
|
812
|
+
existing (dead) component to transfer configuration to the newcomer.
|
|
788
813
|
"""
|
|
789
814
|
GatewayObjectCommand(
|
|
790
815
|
self.component.gateway,
|
|
@@ -857,13 +882,14 @@ class DALIButton(BaseButton, DALIDevice):
|
|
|
857
882
|
manual_add = False
|
|
858
883
|
name = 'DALI Button'
|
|
859
884
|
config_form = DALIButtonConfigForm
|
|
885
|
+
accepts_value = False
|
|
860
886
|
|
|
861
887
|
|
|
862
|
-
class
|
|
888
|
+
class Sentinel(FleetDeviceMixin, ControllerBase):
|
|
863
889
|
gateway_class = FleetGatewayHandler
|
|
864
|
-
config_form =
|
|
865
|
-
name = "
|
|
866
|
-
base_type =
|
|
890
|
+
config_form = SentinelDeviceConfigForm
|
|
891
|
+
name = "Sentinel"
|
|
892
|
+
base_type = SentinelType
|
|
867
893
|
default_value = 0
|
|
868
894
|
app_widget = NumericSensorWidget
|
|
869
895
|
|
|
@@ -894,21 +920,9 @@ class RoomSiren(FleetDeviceMixin, StateSelect):
|
|
|
894
920
|
self.send('silent')
|
|
895
921
|
|
|
896
922
|
def _send_to_device(self, value):
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
).publish()
|
|
901
|
-
else:
|
|
902
|
-
dali_device = CustomDaliDevice.objects.filter(
|
|
903
|
-
id=self.component.config['dali_device']
|
|
904
|
-
).first()
|
|
905
|
-
from .custom_dali_operations import Frame
|
|
906
|
-
frame = Frame(40, bytes(bytearray(5)))
|
|
907
|
-
frame[8:11] = 15 # command to custom dali device
|
|
908
|
-
frame[12:15] = 6 # action to perform: set value
|
|
909
|
-
frame[16:20] = 0 # device on which to perform value set
|
|
910
|
-
frame[21:24] = self.VALUES_MAP[value]
|
|
911
|
-
dali_device.transmit(frame)
|
|
923
|
+
GatewayObjectCommand(
|
|
924
|
+
self.component.gateway, self.component, set_val=value
|
|
925
|
+
).publish()
|
|
912
926
|
|
|
913
927
|
|
|
914
928
|
class AirQualitySensor(FleetDeviceMixin, BaseMultiSensor):
|
|
@@ -917,6 +931,7 @@ class AirQualitySensor(FleetDeviceMixin, BaseMultiSensor):
|
|
|
917
931
|
name = "Air Quality Sensor"
|
|
918
932
|
app_widget = AirQualityWidget
|
|
919
933
|
manual_add = False
|
|
934
|
+
accepts_value = False
|
|
920
935
|
|
|
921
936
|
default_value = [
|
|
922
937
|
["TVOC", 0, "ppb"],
|
|
@@ -975,14 +990,16 @@ class TempHumSensor(FleetDeviceMixin, BasicSensorMixin, BaseMultiSensor):
|
|
|
975
990
|
return [
|
|
976
991
|
['temperature', 0, self.sys_temp_units],
|
|
977
992
|
['humidity', 20, '%'],
|
|
978
|
-
['real_feel', 0, self.sys_temp_units]
|
|
993
|
+
['real_feel', 0, self.sys_temp_units],
|
|
994
|
+
['core', 0, 'C'],
|
|
995
|
+
['outside', 0, 'C']
|
|
979
996
|
]
|
|
980
997
|
|
|
981
998
|
def _receive_from_device(self, value, *args, **kwargs):
|
|
982
999
|
|
|
983
1000
|
if isinstance(value, dict):
|
|
984
1001
|
temp = value['temp']
|
|
985
|
-
humidity = value['
|
|
1002
|
+
humidity = value['hum']
|
|
986
1003
|
else:
|
|
987
1004
|
buf = bytes.fromhex(value)
|
|
988
1005
|
humidity = (
|
|
@@ -997,7 +1014,9 @@ class TempHumSensor(FleetDeviceMixin, BasicSensorMixin, BaseMultiSensor):
|
|
|
997
1014
|
new_val = [
|
|
998
1015
|
['temperature', temp, self.sys_temp_units],
|
|
999
1016
|
['humidity', humidity, '%'],
|
|
1000
|
-
['real_feel', 0, self.sys_temp_units]
|
|
1017
|
+
['real_feel', 0, self.sys_temp_units],
|
|
1018
|
+
['core', value.get('core'), 'C'],
|
|
1019
|
+
['outside', value.get('out'), 'C']
|
|
1001
1020
|
]
|
|
1002
1021
|
|
|
1003
1022
|
if self.sys_temp_units == 'F':
|
|
@@ -1056,23 +1075,16 @@ class RoomZonePresenceSensor(FleetDeviceMixin, BaseBinarySensor):
|
|
|
1056
1075
|
self.uid, serialize_form_data(form_cleaned_data),
|
|
1057
1076
|
timeout=60
|
|
1058
1077
|
)
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1078
|
+
colonel = Colonel.objects.filter(
|
|
1079
|
+
id=form_cleaned_data.get('colonel')
|
|
1080
|
+
if isinstance(form_cleaned_data.get('colonel'), int)
|
|
1081
|
+
else getattr(form_cleaned_data.get('colonel'), 'id', None)
|
|
1082
|
+
).first()
|
|
1083
|
+
if colonel:
|
|
1063
1084
|
GatewayObjectCommand(
|
|
1064
1085
|
gateway, colonel,
|
|
1065
1086
|
command='discover', type=self.uid.split('.')[-1],
|
|
1066
1087
|
).publish()
|
|
1067
|
-
else:
|
|
1068
|
-
from .custom_dali_operations import Frame
|
|
1069
|
-
dali_device = CustomDaliDevice.objects.filter(
|
|
1070
|
-
id=form_cleaned_data['device'][5:]
|
|
1071
|
-
).first()
|
|
1072
|
-
frame = Frame(40, bytes(bytearray(5)))
|
|
1073
|
-
frame[8:11] = 15 # command to custom dali device
|
|
1074
|
-
frame[12:15] = 0 # action to perform: start room zone discovery
|
|
1075
|
-
dali_device.transmit(frame)
|
|
1076
1088
|
|
|
1077
1089
|
|
|
1078
1090
|
@classmethod
|
|
@@ -1088,112 +1100,94 @@ class RoomZonePresenceSensor(FleetDeviceMixin, BaseBinarySensor):
|
|
|
1088
1100
|
controller_uid=cls.uid, data=started_with
|
|
1089
1101
|
)
|
|
1090
1102
|
form.is_valid()
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
form.
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
new_component.
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
'
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
'config': json.loads(json.dumps(new_component.config)),
|
|
1107
|
-
}
|
|
1103
|
+
form.instance.alive = False
|
|
1104
|
+
form.instance.config['colonel'] = int(
|
|
1105
|
+
getattr(form.cleaned_data['colonel'], 'id', form.cleaned_data['colonel'])
|
|
1106
|
+
)
|
|
1107
|
+
new_component = form.save()
|
|
1108
|
+
GatewayObjectCommand(
|
|
1109
|
+
new_component.gateway, Colonel(
|
|
1110
|
+
id=new_component.config['colonel']
|
|
1111
|
+
), command='finalize',
|
|
1112
|
+
data={
|
|
1113
|
+
'permanent_id': new_component.id,
|
|
1114
|
+
'comp_config': {
|
|
1115
|
+
'type': cls.uid.split('.')[-1],
|
|
1116
|
+
'family': new_component.controller.family,
|
|
1117
|
+
'config': json.loads(json.dumps(new_component.config)),
|
|
1108
1118
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
from simo.core.models import Component
|
|
1112
|
-
from .custom_dali_operations import Frame
|
|
1113
|
-
dali_device = CustomDaliDevice.objects.filter(
|
|
1114
|
-
id=form.cleaned_data['device'][5:]
|
|
1115
|
-
).first()
|
|
1116
|
-
free_slots = {0, 1, 2, 3, 4, 5, 6, 7}
|
|
1117
|
-
for comp in Component.objects.filter(
|
|
1118
|
-
controller_uid=cls.uid, config__dali_device=dali_device.id
|
|
1119
|
-
):
|
|
1120
|
-
try:
|
|
1121
|
-
free_slots.remove(int(comp.config['slot']))
|
|
1122
|
-
except:
|
|
1123
|
-
continue
|
|
1124
|
-
if not free_slots:
|
|
1125
|
-
return []
|
|
1126
|
-
|
|
1127
|
-
form.instance.alive = False
|
|
1128
|
-
form.instance.config['dali_device'] = int(
|
|
1129
|
-
form.cleaned_data['device'][5:]
|
|
1130
|
-
)
|
|
1131
|
-
form.instance.config['slot'] = free_slots.pop()
|
|
1132
|
-
new_component = form.save()
|
|
1133
|
-
frame = Frame(40, bytes(bytearray(5)))
|
|
1134
|
-
frame[8:11] = 15 # command to custom dali device
|
|
1135
|
-
frame[12:15] = 1 # action to perform: stop room zone discovery
|
|
1136
|
-
frame[16:18] = new_component.config['slot']
|
|
1137
|
-
dali_device.transmit(frame)
|
|
1119
|
+
}
|
|
1120
|
+
).publish()
|
|
1138
1121
|
|
|
1139
1122
|
return new_component
|
|
1140
1123
|
|
|
1141
1124
|
def repaint(self):
|
|
1142
1125
|
"""Repaint included 3D space"""
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
self.component.
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
).publish()
|
|
1149
|
-
else:
|
|
1150
|
-
dali_device = CustomDaliDevice.objects.filter(
|
|
1151
|
-
id=self.component.config['device'][5:]
|
|
1152
|
-
).first()
|
|
1153
|
-
from .custom_dali_operations import Frame
|
|
1154
|
-
frame = Frame(40, bytes(bytearray(5)))
|
|
1155
|
-
frame[8:11] = 15 # command to custom dali device
|
|
1156
|
-
frame[12:15] = 3 # action to perform: repaint
|
|
1157
|
-
frame[16:18] = self.component.config['slot']
|
|
1158
|
-
dali_device.transmit(frame)
|
|
1126
|
+
GatewayObjectCommand(
|
|
1127
|
+
self.component.gateway, Colonel(
|
|
1128
|
+
id=self.component.config['colonel']
|
|
1129
|
+
), command='call', method='repaint', id=self.component.id
|
|
1130
|
+
).publish()
|
|
1159
1131
|
|
|
1160
1132
|
def finish_repaint(self):
|
|
1161
1133
|
"""Finish repainting of 3D space"""
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
self.component.
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
).publish()
|
|
1169
|
-
else:
|
|
1170
|
-
dali_device = CustomDaliDevice.objects.filter(
|
|
1171
|
-
id=self.component.config['device'][5:]
|
|
1172
|
-
).first()
|
|
1173
|
-
from .custom_dali_operations import Frame
|
|
1174
|
-
frame = Frame(40, bytes(bytearray(5)))
|
|
1175
|
-
frame[8:11] = 15 # command to custom dali device
|
|
1176
|
-
frame[12:15] = 4 # action to perform: finish repaint
|
|
1177
|
-
frame[16:18] = self.component.config['slot']
|
|
1178
|
-
dali_device.transmit(frame)
|
|
1134
|
+
GatewayObjectCommand(
|
|
1135
|
+
self.component.gateway, Colonel(
|
|
1136
|
+
id=self.component.config['colonel']
|
|
1137
|
+
), command='call', method='finish_repaint',
|
|
1138
|
+
id=self.component.id
|
|
1139
|
+
).publish()
|
|
1179
1140
|
|
|
1180
1141
|
def cancel_repaint(self):
|
|
1181
1142
|
"""Finish repainting of 3D space"""
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
self.component.
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1143
|
+
GatewayObjectCommand(
|
|
1144
|
+
self.component.gateway, Colonel(
|
|
1145
|
+
id=self.component.config['colonel']
|
|
1146
|
+
), command='call', method='cancel_repaint',
|
|
1147
|
+
id=self.component.id
|
|
1148
|
+
).publish()
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
class VoiceAssistant(FleetDeviceMixin, BaseBinarySensor):
|
|
1152
|
+
base_type = VoiceAssistantType
|
|
1153
|
+
name = _("AI Voice Assistant")
|
|
1154
|
+
gateway_class = FleetGatewayHandler
|
|
1155
|
+
config_form = VoiceAssistantConfigForm
|
|
1156
|
+
manual_add = False
|
|
1157
|
+
default_config = {'voice': 'male', 'enabled': True}
|
|
1158
|
+
|
|
1159
|
+
def arm(self):
|
|
1160
|
+
"""
|
|
1161
|
+
Arming voice assistant is means disabling it,
|
|
1162
|
+
so that it can not be used by invaders.
|
|
1163
|
+
"""
|
|
1164
|
+
self.component.refresh_from_db()
|
|
1165
|
+
self.component.config['enabled'] = False
|
|
1166
|
+
self.component.arm_status = 'armed'
|
|
1167
|
+
self.component.save()
|
|
1168
|
+
GatewayObjectCommand(
|
|
1169
|
+
self.component.gateway, Colonel(
|
|
1170
|
+
id=self.component.config['colonel']
|
|
1171
|
+
), command='call', method='disable',
|
|
1172
|
+
id=self.component.id
|
|
1173
|
+
).publish()
|
|
1174
|
+
|
|
1175
|
+
def disarm(self):
|
|
1176
|
+
"""
|
|
1177
|
+
Disarming voice assistant is means enabling it,
|
|
1178
|
+
so that anyone can use it to control the smart home system.
|
|
1179
|
+
"""
|
|
1180
|
+
self.component.refresh_from_db()
|
|
1181
|
+
self.component.config['enabled'] = True
|
|
1182
|
+
self.component.arm_status = 'disarmed'
|
|
1183
|
+
self.component.save()
|
|
1184
|
+
GatewayObjectCommand(
|
|
1185
|
+
self.component.gateway, Colonel(
|
|
1186
|
+
id=self.component.config['colonel']
|
|
1187
|
+
), command='call', method='enable',
|
|
1188
|
+
id=self.component.id
|
|
1189
|
+
).publish()
|
|
1199
1190
|
|
|
1191
|
+
def is_in_alarm(self):
|
|
1192
|
+
"""Returns always False"""
|
|
1193
|
+
return False
|