simo 2.8.15__py3-none-any.whl → 2.9.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/core/__pycache__/admin.cpython-312.pyc +0 -0
- simo/core/__pycache__/auto_urls.cpython-312.pyc +0 -0
- simo/core/__pycache__/controllers.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__/views.cpython-312.pyc +0 -0
- simo/core/admin.py +5 -2
- simo/core/auto_urls.py +4 -1
- simo/core/controllers.py +1 -3
- simo/core/models.py +32 -16
- simo/core/serializers.py +2 -2
- simo/core/templates/admin/core/component_change_form.html +1 -1
- simo/core/templates/admin/wizard/discovery.html +3 -4
- simo/core/templates/admin/wizard/wizard_add.html +1 -1
- simo/core/views.py +26 -2
- 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__/managers.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/__pycache__/socket_consumers.cpython-312.pyc +0 -0
- simo/fleet/api.py +26 -3
- simo/fleet/base_types.py +1 -0
- simo/fleet/controllers.py +240 -7
- simo/fleet/custom_dali_operations.py +275 -0
- simo/fleet/forms.py +132 -3
- simo/fleet/managers.py +3 -1
- simo/fleet/migrations/0045_alter_colonel_type_customdalidevice.py +29 -0
- simo/fleet/migrations/0046_delete_customdalidevice.py +16 -0
- simo/fleet/migrations/0047_customdalidevice.py +28 -0
- simo/fleet/migrations/0048_remove_customdalidevice_colonel_and_more.py +28 -0
- simo/fleet/migrations/__pycache__/0045_alter_colonel_type_customdalidevice.cpython-312.pyc +0 -0
- simo/fleet/migrations/__pycache__/0046_delete_customdalidevice.cpython-312.pyc +0 -0
- simo/fleet/migrations/__pycache__/0047_customdalidevice.cpython-312.pyc +0 -0
- simo/fleet/migrations/__pycache__/0048_remove_customdalidevice_colonel_and_more.cpython-312.pyc +0 -0
- simo/fleet/models.py +54 -9
- simo/fleet/serializers.py +15 -1
- simo/fleet/socket_consumers.py +6 -0
- simo/fleet/tasks.py +22 -2
- simo/fleet/templates/fleet/controllers_info/RoomZonePresenceSensor.md +8 -0
- {simo-2.8.15.dist-info → simo-2.9.1.dist-info}/METADATA +1 -1
- {simo-2.8.15.dist-info → simo-2.9.1.dist-info}/RECORD +48 -38
- {simo-2.8.15.dist-info → simo-2.9.1.dist-info}/WHEEL +0 -0
- {simo-2.8.15.dist-info → simo-2.9.1.dist-info}/entry_points.txt +0 -0
- {simo-2.8.15.dist-info → simo-2.9.1.dist-info}/licenses/LICENSE.md +0 -0
- {simo-2.8.15.dist-info → simo-2.9.1.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/core/admin.py
CHANGED
|
@@ -366,7 +366,7 @@ class ComponentAdmin(EasyObjectsDeleteMixin, admin.ModelAdmin):
|
|
|
366
366
|
pop_fields_from_form(ctx['form'])
|
|
367
367
|
if ctx['form'].is_valid():
|
|
368
368
|
if ctx['form'].controller.is_discoverable:
|
|
369
|
-
ctx['form'].controller.
|
|
369
|
+
ctx['form'].controller._init_discovery(
|
|
370
370
|
ctx['form'].cleaned_data
|
|
371
371
|
)
|
|
372
372
|
ctx['discovery_msg'] = ctx['form'].controller.discovery_msg
|
|
@@ -383,6 +383,9 @@ class ComponentAdmin(EasyObjectsDeleteMixin, admin.ModelAdmin):
|
|
|
383
383
|
'components-list',
|
|
384
384
|
kwargs={'instance_slug': instance.slug}
|
|
385
385
|
)
|
|
386
|
+
ctx['finish_url'] = reverse(
|
|
387
|
+
'finish-discovery',
|
|
388
|
+
) + f"?uid={ctx['form'].controller.uid}"
|
|
386
389
|
return render(
|
|
387
390
|
request, 'admin/wizard/discovery.html', ctx
|
|
388
391
|
)
|
|
@@ -486,7 +489,7 @@ class ComponentAdmin(EasyObjectsDeleteMixin, admin.ModelAdmin):
|
|
|
486
489
|
def info(self, obj):
|
|
487
490
|
if not obj.controller:
|
|
488
491
|
return
|
|
489
|
-
info = obj.controller.info()
|
|
492
|
+
info = obj.controller.info(obj)
|
|
490
493
|
if not info:
|
|
491
494
|
return
|
|
492
495
|
return mark_safe(
|
simo/core/auto_urls.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from django.urls import path
|
|
2
2
|
from .views import (
|
|
3
|
-
get_timestamp, upgrade, restart, reboot, set_instance, delete_instance
|
|
3
|
+
get_timestamp, upgrade, restart, reboot, set_instance, delete_instance,
|
|
4
|
+
finish_discovery
|
|
4
5
|
)
|
|
5
6
|
from .autocomplete_views import (
|
|
6
7
|
IconModelAutocomplete,
|
|
@@ -31,6 +32,8 @@ urlpatterns = [
|
|
|
31
32
|
ComponentAutocomplete.as_view(), name='autocomplete-component'
|
|
32
33
|
),
|
|
33
34
|
path('set-instance/<slug:instance_slug>/', set_instance, name='set-instance'),
|
|
35
|
+
path('finish-discovery/',
|
|
36
|
+
finish_discovery, name='finish-discovery'),
|
|
34
37
|
path('upgrade/', upgrade, name='upgrade'),
|
|
35
38
|
path('restart/', restart, name='restart'),
|
|
36
39
|
path('reboot/', reboot, name='reboot'),
|
simo/core/controllers.py
CHANGED
simo/core/models.py
CHANGED
|
@@ -294,28 +294,44 @@ class Gateway(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
294
294
|
)
|
|
295
295
|
if result:
|
|
296
296
|
self.refresh_from_db()
|
|
297
|
-
|
|
298
|
-
for res in result:
|
|
299
|
-
if isinstance(res, models.Model):
|
|
300
|
-
if res.pk not in self.discovery['result']:
|
|
301
|
-
self.discovery['result'].append(res.pk)
|
|
302
|
-
else:
|
|
303
|
-
if res not in self.discovery['result']:
|
|
304
|
-
self.discovery['result'].append(res)
|
|
305
|
-
else:
|
|
306
|
-
if isinstance(result, models.Model):
|
|
307
|
-
if result.pk not in self.discovery['result']:
|
|
308
|
-
self.discovery['result'].append(result.pk)
|
|
309
|
-
else:
|
|
310
|
-
if result not in self.discovery['result']:
|
|
311
|
-
self.discovery['result'].append(result)
|
|
312
|
-
|
|
297
|
+
self.append_discovery_result(result)
|
|
313
298
|
|
|
314
299
|
self.save(update_fields=['discovery'])
|
|
315
300
|
|
|
301
|
+
|
|
316
302
|
def finish_discovery(self):
|
|
303
|
+
from .utils.type_constants import CONTROLLER_TYPES_MAP
|
|
317
304
|
self.discovery['finished'] = time.time()
|
|
318
305
|
self.save(update_fields=['discovery'])
|
|
306
|
+
ControllerClass = CONTROLLER_TYPES_MAP.get(
|
|
307
|
+
self.discovery['controller_uid']
|
|
308
|
+
)
|
|
309
|
+
if hasattr(ControllerClass, '_finish_discovery'):
|
|
310
|
+
result = ControllerClass._finish_discovery(
|
|
311
|
+
self.discovery['init_data']
|
|
312
|
+
)
|
|
313
|
+
self.append_discovery_result(result)
|
|
314
|
+
self.save(update_fields=['discovery'])
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def append_discovery_result(self, result):
|
|
318
|
+
if not isinstance(result, dict) and isinstance(result, Iterable):
|
|
319
|
+
for res in result:
|
|
320
|
+
if isinstance(res, models.Model):
|
|
321
|
+
if res.pk not in self.discovery['result']:
|
|
322
|
+
self.discovery['result'].append(res.pk)
|
|
323
|
+
else:
|
|
324
|
+
if res not in self.discovery['result']:
|
|
325
|
+
self.discovery['result'].append(res)
|
|
326
|
+
else:
|
|
327
|
+
if isinstance(result, models.Model):
|
|
328
|
+
if result.pk not in self.discovery['result']:
|
|
329
|
+
self.discovery['result'].append(result.pk)
|
|
330
|
+
else:
|
|
331
|
+
if result not in self.discovery['result']:
|
|
332
|
+
self.discovery['result'].append(result)
|
|
333
|
+
|
|
334
|
+
|
|
319
335
|
|
|
320
336
|
|
|
321
337
|
class Component(DirtyFieldsMixin, models.Model, SimoAdminMixin, OnChangeMixin):
|
simo/core/serializers.py
CHANGED
|
@@ -544,7 +544,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
544
544
|
)
|
|
545
545
|
if form.is_valid():
|
|
546
546
|
if form.controller.is_discoverable:
|
|
547
|
-
form.controller.
|
|
547
|
+
form.controller._init_discovery(form.cleaned_data)
|
|
548
548
|
return form.save(commit=False)
|
|
549
549
|
return form.save(commit=True)
|
|
550
550
|
raise serializers.ValidationError(form.errors)
|
|
@@ -554,7 +554,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
554
554
|
|
|
555
555
|
def get_info(self, obj):
|
|
556
556
|
if obj.controller:
|
|
557
|
-
return obj.controller.info()
|
|
557
|
+
return obj.controller.info(obj)
|
|
558
558
|
|
|
559
559
|
def get_read_only(self, obj):
|
|
560
560
|
user = self.context.get('user')
|
|
@@ -64,8 +64,7 @@
|
|
|
64
64
|
|
|
65
65
|
<div id="running-discovery">
|
|
66
66
|
<p style="margin: 30px 15px">
|
|
67
|
-
<i class="fas fa-spinner fa-spin fa-lg" style="margin-right: 10px"></i> DISCOVERY MODE ACTIVATED!
|
|
68
|
-
Your new components will appear down bellow once discovered.
|
|
67
|
+
<i class="fas fa-spinner fa-spin fa-lg" style="margin-right: 10px"></i> DISCOVERY MODE ACTIVATED!
|
|
69
68
|
</p>
|
|
70
69
|
{% if discovery_msg %}
|
|
71
70
|
<p style="margin: 30px 15px">{{ discovery_msg }}</p>
|
|
@@ -108,8 +107,8 @@
|
|
|
108
107
|
|
|
109
108
|
<div class="submit-row" style="text-align: left;">
|
|
110
109
|
|
|
111
|
-
<a href="{
|
|
112
|
-
<i class="fa fa-check"></i>
|
|
110
|
+
<a href="{{ finish_url }}" class="cancel-link" style="display: block; padding:9px 15px">
|
|
111
|
+
<i class="fa fa-check"></i> Finish
|
|
113
112
|
</a>
|
|
114
113
|
|
|
115
114
|
</div>
|
simo/core/views.py
CHANGED
|
@@ -9,7 +9,7 @@ from django.http import (
|
|
|
9
9
|
)
|
|
10
10
|
from django.contrib import messages
|
|
11
11
|
from simo.conf import dynamic_settings
|
|
12
|
-
from .models import Instance
|
|
12
|
+
from .models import Instance, Component, Gateway
|
|
13
13
|
from .tasks import update as update_task, supervisor_restart, hardware_reboot
|
|
14
14
|
from .middleware import introduce_instance
|
|
15
15
|
|
|
@@ -91,4 +91,28 @@ def hub_info(request):
|
|
|
91
91
|
request.get_host()
|
|
92
92
|
):
|
|
93
93
|
data['hub_secret'] = dynamic_settings['core__hub_secret']
|
|
94
|
-
return JsonResponse(data)
|
|
94
|
+
return JsonResponse(data)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@login_required
|
|
98
|
+
def finish_discovery(request):
|
|
99
|
+
# finish discovery function for admin discovery view
|
|
100
|
+
if not request.user.is_authenticated:
|
|
101
|
+
raise Http404()
|
|
102
|
+
if not request.user.is_master:
|
|
103
|
+
raise Http404()
|
|
104
|
+
result = None
|
|
105
|
+
for gateway in Gateway.objects.filter(
|
|
106
|
+
discovery__start__gt=time.time() - 60 * 60, # no more than an hour
|
|
107
|
+
discovery__controller_uid=request.GET['uid']
|
|
108
|
+
):
|
|
109
|
+
gateway.finish_discovery()
|
|
110
|
+
for res in gateway.discovery['result']:
|
|
111
|
+
comp = Component.objects.filter(
|
|
112
|
+
controller_uid=request.GET['uid'], pk=res
|
|
113
|
+
).first()
|
|
114
|
+
if comp:
|
|
115
|
+
result = comp
|
|
116
|
+
if result:
|
|
117
|
+
return redirect(result.get_admin_url())
|
|
118
|
+
return reverse('admin:core_component_changelist')
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/fleet/api.py
CHANGED
|
@@ -4,11 +4,13 @@ from rest_framework import viewsets
|
|
|
4
4
|
from rest_framework.response import Response as RESTResponse
|
|
5
5
|
from rest_framework.decorators import action
|
|
6
6
|
from rest_framework.exceptions import ValidationError as APIValidationError
|
|
7
|
+
from django_filters.rest_framework import DjangoFilterBackend
|
|
7
8
|
from simo.core.api import InstanceMixin
|
|
8
9
|
from simo.core.permissions import IsInstanceSuperuser
|
|
9
|
-
from .models import InstanceOptions, Colonel, Interface
|
|
10
|
+
from .models import InstanceOptions, Colonel, Interface, CustomDaliDevice
|
|
10
11
|
from .serializers import (
|
|
11
|
-
InstanceOptionsSerializer, ColonelSerializer, ColonelInterfaceSerializer
|
|
12
|
+
InstanceOptionsSerializer, ColonelSerializer, ColonelInterfaceSerializer,
|
|
13
|
+
CustomDaliDeviceSerializer
|
|
12
14
|
)
|
|
13
15
|
|
|
14
16
|
|
|
@@ -99,4 +101,25 @@ class InterfaceViewSet(
|
|
|
99
101
|
return permissions
|
|
100
102
|
|
|
101
103
|
def get_queryset(self):
|
|
102
|
-
return Interface.objects.filter(colonel__instance=self.instance)
|
|
104
|
+
return Interface.objects.filter(colonel__instance=self.instance)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class CustomDaliDeviceViewSet(
|
|
108
|
+
InstanceMixin,
|
|
109
|
+
viewsets.mixins.RetrieveModelMixin, viewsets.mixins.UpdateModelMixin,
|
|
110
|
+
viewsets.mixins.ListModelMixin, viewsets.mixins.CreateModelMixin,
|
|
111
|
+
viewsets.GenericViewSet
|
|
112
|
+
):
|
|
113
|
+
url = 'fleet/custom-dali-devices'
|
|
114
|
+
basename = 'custom-dali-devices'
|
|
115
|
+
serializer_class = CustomDaliDeviceSerializer
|
|
116
|
+
filter_backends = [DjangoFilterBackend]
|
|
117
|
+
filterset_fields = ['random_address', 'name']
|
|
118
|
+
|
|
119
|
+
def get_permissions(self):
|
|
120
|
+
permissions = super().get_permissions()
|
|
121
|
+
permissions.append(IsInstanceSuperuser())
|
|
122
|
+
return permissions
|
|
123
|
+
|
|
124
|
+
def get_queryset(self):
|
|
125
|
+
return CustomDaliDevice.objects.filter(instance=self.instance)
|
simo/fleet/base_types.py
CHANGED
simo/fleet/controllers.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import json
|
|
1
|
+
import json, ast
|
|
2
2
|
from django.utils.translation import gettext_lazy as _
|
|
3
3
|
from django.db.transaction import atomic
|
|
4
4
|
from simo.core.middleware import get_current_instance
|
|
@@ -9,7 +9,7 @@ from simo.core.controllers import (
|
|
|
9
9
|
NumericSensor as BaseNumericSensor,
|
|
10
10
|
Switch as BaseSwitch, Dimmer as BaseDimmer,
|
|
11
11
|
MultiSensor as BaseMultiSensor, RGBWLight as BaseRGBWLight,
|
|
12
|
-
Blinds as BaseBlinds, Gate as BaseGate
|
|
12
|
+
Blinds as BaseBlinds, Gate as BaseGate,
|
|
13
13
|
)
|
|
14
14
|
from simo.core.app_widgets import NumericSensorWidget, AirQualityWidget
|
|
15
15
|
from simo.core.controllers import Lock, ControllerBase, SingleSwitchWidget
|
|
@@ -17,7 +17,8 @@ from simo.core.utils.helpers import heat_index
|
|
|
17
17
|
from simo.core.utils.serialization import (
|
|
18
18
|
serialize_form_data, deserialize_form_data
|
|
19
19
|
)
|
|
20
|
-
from .
|
|
20
|
+
from simo.core.forms import BaseComponentForm
|
|
21
|
+
from .models import Colonel, CustomDaliDevice
|
|
21
22
|
from .gateways import FleetGatewayHandler
|
|
22
23
|
from .forms import (
|
|
23
24
|
ColonelPinChoiceField,
|
|
@@ -31,7 +32,8 @@ from .forms import (
|
|
|
31
32
|
TTLockConfigForm, DALIDeviceConfigForm, DaliLampForm, DaliGearGroupForm,
|
|
32
33
|
DaliSwitchConfigForm,
|
|
33
34
|
DaliOccupancySensorConfigForm, DALILightSensorConfigForm,
|
|
34
|
-
DALIButtonConfigForm
|
|
35
|
+
DALIButtonConfigForm, RoomSensorDeviceConfigForm,
|
|
36
|
+
RoomZonePresenceConfigForm
|
|
35
37
|
)
|
|
36
38
|
|
|
37
39
|
|
|
@@ -465,7 +467,7 @@ class TTLock(FleeDeviceMixin, Lock):
|
|
|
465
467
|
discovery_msg = _("Please activate your TTLock so it can be discovered.")
|
|
466
468
|
|
|
467
469
|
@classmethod
|
|
468
|
-
def
|
|
470
|
+
def _init_discovery(self, form_cleaned_data):
|
|
469
471
|
from simo.core.models import Gateway
|
|
470
472
|
print("INIT discovery form cleaned data: ", form_cleaned_data)
|
|
471
473
|
print("Serialized form: ", serialize_form_data(form_cleaned_data))
|
|
@@ -663,7 +665,7 @@ class DALIDevice(FleeDeviceMixin, ControllerBase):
|
|
|
663
665
|
return value
|
|
664
666
|
|
|
665
667
|
@classmethod
|
|
666
|
-
def
|
|
668
|
+
def _init_discovery(self, form_cleaned_data):
|
|
667
669
|
from simo.core.models import Gateway
|
|
668
670
|
gateway = Gateway.objects.filter(type=self.gateway_class.uid).first()
|
|
669
671
|
gateway.start_discovery(
|
|
@@ -824,4 +826,235 @@ class DALIButton(BaseButton, DALIDevice):
|
|
|
824
826
|
family = 'dali'
|
|
825
827
|
manual_add = False
|
|
826
828
|
name = 'DALI Button'
|
|
827
|
-
config_form = DALIButtonConfigForm
|
|
829
|
+
config_form = DALIButtonConfigForm
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
class RoomSensor(FleeDeviceMixin, ControllerBase):
|
|
833
|
+
gateway_class = FleetGatewayHandler
|
|
834
|
+
config_form = RoomSensorDeviceConfigForm
|
|
835
|
+
name = "Room Sensor"
|
|
836
|
+
base_type = 'room-sensor'
|
|
837
|
+
default_value = 0
|
|
838
|
+
app_widget = NumericSensorWidget
|
|
839
|
+
|
|
840
|
+
def _validate_val(self, value, occasion=None):
|
|
841
|
+
return value
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
class AirQualitySensor(FleeDeviceMixin, BaseMultiSensor):
|
|
845
|
+
gateway_class = FleetGatewayHandler
|
|
846
|
+
config_form = BaseComponentForm
|
|
847
|
+
name = "Air Quality Sensor"
|
|
848
|
+
app_widget = AirQualityWidget
|
|
849
|
+
manual_add = False
|
|
850
|
+
|
|
851
|
+
default_value = [
|
|
852
|
+
["TVOC", 0, "ppb"],
|
|
853
|
+
["AQI (UBA)", 0, ""]
|
|
854
|
+
]
|
|
855
|
+
|
|
856
|
+
def _receive_from_device(self, value, *args, **kwargs):
|
|
857
|
+
aqi = 5
|
|
858
|
+
if value < 812:
|
|
859
|
+
aqi = 4
|
|
860
|
+
if value < 325:
|
|
861
|
+
aqi = 3
|
|
862
|
+
if value < 162:
|
|
863
|
+
aqi = 2
|
|
864
|
+
if value < 65:
|
|
865
|
+
aqi = 1
|
|
866
|
+
value = [
|
|
867
|
+
["TVOC", value, "ppb"],
|
|
868
|
+
["AQI (UBA)", aqi, ""]
|
|
869
|
+
]
|
|
870
|
+
return super()._receive_from_device(value, *args, **kwargs)
|
|
871
|
+
|
|
872
|
+
def get_tvoc(self):
|
|
873
|
+
try:
|
|
874
|
+
for entry in self.component.value:
|
|
875
|
+
if entry[0] == 'TVOC':
|
|
876
|
+
return entry[1]
|
|
877
|
+
except:
|
|
878
|
+
return
|
|
879
|
+
|
|
880
|
+
def get_aqi(self):
|
|
881
|
+
try:
|
|
882
|
+
for entry in self.component.value:
|
|
883
|
+
if entry[0] == 'AQI (UBA)':
|
|
884
|
+
return entry[1]
|
|
885
|
+
except:
|
|
886
|
+
return
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
class TempHumSensor(FleeDeviceMixin, BasicSensorMixin, BaseMultiSensor):
|
|
890
|
+
gateway_class = FleetGatewayHandler
|
|
891
|
+
config_form = BaseComponentForm
|
|
892
|
+
name = "Temperature & Humidity sensor"
|
|
893
|
+
app_widget = NumericSensorWidget
|
|
894
|
+
manual_add = False
|
|
895
|
+
|
|
896
|
+
def __init__(self, *args, **kwargs):
|
|
897
|
+
super().__init__(*args, **kwargs)
|
|
898
|
+
self.sys_temp_units = 'C'
|
|
899
|
+
if hasattr(self.component, 'zone') \
|
|
900
|
+
and self.component.zone.instance.units_of_measure == 'imperial':
|
|
901
|
+
self.sys_temp_units = 'F'
|
|
902
|
+
|
|
903
|
+
@property
|
|
904
|
+
def default_value(self):
|
|
905
|
+
return [
|
|
906
|
+
['temperature', 0, self.sys_temp_units],
|
|
907
|
+
['humidity', 20, '%'],
|
|
908
|
+
['real_feel', 0, self.sys_temp_units]
|
|
909
|
+
]
|
|
910
|
+
|
|
911
|
+
def _receive_from_device(self, value, *args, **kwargs):
|
|
912
|
+
|
|
913
|
+
if isinstance(value, dict):
|
|
914
|
+
temp = value['temp']
|
|
915
|
+
humidity = value['humidity']
|
|
916
|
+
else:
|
|
917
|
+
buf = bytes.fromhex(value)
|
|
918
|
+
humidity = (
|
|
919
|
+
(buf[0] << 12) | (buf[1] << 4) | (buf[2] >> 4)
|
|
920
|
+
)
|
|
921
|
+
humidity = (humidity * 100) / 0x100000
|
|
922
|
+
humidity = int(round(humidity, 0))
|
|
923
|
+
temp = ((buf[2] & 0xF) << 16) | (buf[3] << 8) | buf[4]
|
|
924
|
+
temp = ((temp * 200.0) / 0x100000) - 50
|
|
925
|
+
temp = round(temp, 1)
|
|
926
|
+
|
|
927
|
+
new_val = [
|
|
928
|
+
['temperature', temp, self.sys_temp_units],
|
|
929
|
+
['humidity', humidity, '%'],
|
|
930
|
+
['real_feel', 0, self.sys_temp_units]
|
|
931
|
+
]
|
|
932
|
+
|
|
933
|
+
if self.sys_temp_units == 'F':
|
|
934
|
+
new_val[0][1] = round((new_val[0][1] * 9 / 5) + 32, 1)
|
|
935
|
+
|
|
936
|
+
real_feel = heat_index(
|
|
937
|
+
new_val[0][1], new_val[1][1], self.sys_temp_units == 'F'
|
|
938
|
+
)
|
|
939
|
+
new_val[2] = ['real_feel', real_feel, self.sys_temp_units]
|
|
940
|
+
|
|
941
|
+
return super()._receive_from_device(new_val, *args, **kwargs)
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
class AmbientLightSensor(FleeDeviceMixin, BaseNumericSensor):
|
|
945
|
+
gateway_class = FleetGatewayHandler
|
|
946
|
+
name = "Ambient lighting sensor"
|
|
947
|
+
manual_add = False
|
|
948
|
+
default_config = {
|
|
949
|
+
'widget': 'numeric-sensor',
|
|
950
|
+
'value_units': 'lux',
|
|
951
|
+
'limits': [
|
|
952
|
+
{"name": "Dark", "value": 20},
|
|
953
|
+
{"name": "Moderate", "value": 300},
|
|
954
|
+
{"name": "Bright", "value": 800},
|
|
955
|
+
]
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
class RoomPresenceSensor(FleeDeviceMixin, BaseBinarySensor):
|
|
960
|
+
gateway_class = FleetGatewayHandler
|
|
961
|
+
name = "Human presence sensor"
|
|
962
|
+
manual_add = False
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
class RoomZonePresenceSensor(FleeDeviceMixin, BaseBinarySensor):
|
|
966
|
+
gateway_class = FleetGatewayHandler
|
|
967
|
+
add_form = RoomZonePresenceConfigForm
|
|
968
|
+
config_form = BaseComponentForm
|
|
969
|
+
name = "Room zone presence"
|
|
970
|
+
discovery_msg = _(
|
|
971
|
+
"Move vigorously in particular zone of the room, "
|
|
972
|
+
"where presence needs to be detected. "
|
|
973
|
+
"Your movements are being recorded. "
|
|
974
|
+
"Hit Done, once you are done."
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
@classmethod
|
|
978
|
+
def _init_discovery(self, form_cleaned_data):
|
|
979
|
+
from simo.core.models import Gateway
|
|
980
|
+
gateway = Gateway.objects.filter(type=self.gateway_class.uid).first()
|
|
981
|
+
gateway.start_discovery(
|
|
982
|
+
self.uid, serialize_form_data(form_cleaned_data),
|
|
983
|
+
timeout=60
|
|
984
|
+
)
|
|
985
|
+
if form_cleaned_data['device'].startswith('wifi'):
|
|
986
|
+
colonel = Colonel.objects.filter(
|
|
987
|
+
id=form_cleaned_data['device'][5:]
|
|
988
|
+
).first()
|
|
989
|
+
GatewayObjectCommand(
|
|
990
|
+
gateway, colonel,
|
|
991
|
+
command='discover', type=self.uid.split('.')[-1],
|
|
992
|
+
).publish()
|
|
993
|
+
else:
|
|
994
|
+
dali_device = CustomDaliDevice.objects.filter(
|
|
995
|
+
id=form_cleaned_data['device'][5:]
|
|
996
|
+
).first()
|
|
997
|
+
from .custom_dali_operations import Frame
|
|
998
|
+
frame = Frame(40, bytes(bytearray(5)))
|
|
999
|
+
frame[8:11] = 15 # command to custom dali device
|
|
1000
|
+
frame[12:15] = 0 # action to perform: start room zone discovery
|
|
1001
|
+
dali_device.transmit(frame)
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
@classmethod
|
|
1005
|
+
@atomic
|
|
1006
|
+
def _finish_discovery(cls, started_with):
|
|
1007
|
+
started_with = deserialize_form_data(started_with)
|
|
1008
|
+
form = cls.add_form(
|
|
1009
|
+
controller_uid=cls.uid, data=started_with
|
|
1010
|
+
)
|
|
1011
|
+
form.is_valid()
|
|
1012
|
+
if form.cleaned_data['device'].startswith('wifi'):
|
|
1013
|
+
form.instance.alive = False
|
|
1014
|
+
form.instance.config['colonel'] = int(
|
|
1015
|
+
form.cleaned_data['device'][5:]
|
|
1016
|
+
)
|
|
1017
|
+
new_component = form.save()
|
|
1018
|
+
GatewayObjectCommand(
|
|
1019
|
+
new_component.gateway, Colonel(
|
|
1020
|
+
id=new_component.config['colonel']
|
|
1021
|
+
), command='finalize',
|
|
1022
|
+
data={
|
|
1023
|
+
'comp_config': {
|
|
1024
|
+
'type': str(cls).split('.')[-1],
|
|
1025
|
+
'family': new_component.controller.family,
|
|
1026
|
+
'config': json.loads(json.dumps(new_component.config))
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
).publish()
|
|
1030
|
+
else:
|
|
1031
|
+
from simo.core.models import Component
|
|
1032
|
+
dali_device = CustomDaliDevice.objects.filter(
|
|
1033
|
+
id=form.cleaned_data['device'][5:]
|
|
1034
|
+
).first()
|
|
1035
|
+
free_slots = {0, 1, 2, 3, 4, 5, 6, 7}
|
|
1036
|
+
for comp in Component.objects.filter(
|
|
1037
|
+
controller_uid=cls.uid, config__dali_device=dali_device.id
|
|
1038
|
+
):
|
|
1039
|
+
try:
|
|
1040
|
+
free_slots.remove(int(comp.config['slot']))
|
|
1041
|
+
except:
|
|
1042
|
+
continue
|
|
1043
|
+
if not free_slots:
|
|
1044
|
+
return []
|
|
1045
|
+
|
|
1046
|
+
form.instance.alive = False
|
|
1047
|
+
form.instance.config['dali_device'] = int(
|
|
1048
|
+
form.cleaned_data['device'][5:]
|
|
1049
|
+
)
|
|
1050
|
+
form.instance.config['slot'] = free_slots.pop()
|
|
1051
|
+
new_component = form.save()
|
|
1052
|
+
from .custom_dali_operations import Frame
|
|
1053
|
+
frame = Frame(40, bytes(bytearray(5)))
|
|
1054
|
+
frame[8:11] = 15 # command to custom dali device
|
|
1055
|
+
frame[12:15] = 1 # action to perform: stop room zone discovery
|
|
1056
|
+
frame[16:18] = new_component.config['slot']
|
|
1057
|
+
dali_device.transmit(frame)
|
|
1058
|
+
|
|
1059
|
+
print("NEW COMPONENT: ", new_component)
|
|
1060
|
+
return new_component
|