simo 2.0.32__py3-none-any.whl → 2.0.33__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/cli.py +2 -17
- simo/core/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/__pycache__/api.cpython-38.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/core/__pycache__/forms.cpython-38.pyc +0 -0
- simo/core/__pycache__/managers.cpython-38.pyc +0 -0
- simo/core/__pycache__/models.cpython-38.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
- simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
- simo/core/admin.py +2 -2
- simo/core/api.py +25 -2
- simo/core/controllers.py +11 -7
- simo/core/forms.py +1 -2
- simo/core/managers.py +1 -5
- simo/core/migrations/0003_create_default_zones_and_categories.py +2 -39
- simo/core/migrations/0004_create_generic.py +22 -21
- simo/core/migrations/0013_auto_20231003_0754.py +45 -43
- simo/core/migrations/0018_auto_20231005_0622.py +18 -16
- simo/core/migrations/0033_auto_20240509_0821.py +25 -0
- simo/core/migrations/0034_component_error_msg.py +18 -0
- simo/core/migrations/0035_remove_instance_share_location.py +17 -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__/0013_auto_20231003_0754.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0018_auto_20231005_0622.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0033_auto_20240509_0821.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0034_component_error_msg.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0035_remove_instance_share_location.cpython-38.pyc +0 -0
- simo/core/models.py +25 -23
- simo/core/serializers.py +5 -3
- simo/core/signal_receivers.py +82 -1
- simo/core/tasks.py +7 -4
- simo/fleet/__pycache__/admin.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__/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/admin.py +25 -6
- simo/fleet/controllers.py +82 -37
- simo/fleet/forms.py +142 -10
- simo/fleet/migrations/0035_auto_20240514_0855.py +32 -0
- simo/fleet/migrations/__pycache__/0035_auto_20240514_0855.cpython-38.pyc +0 -0
- simo/fleet/models.py +96 -82
- simo/fleet/serializers.py +8 -1
- simo/fleet/socket_consumers.py +3 -15
- simo/users/__pycache__/models.cpython-38.pyc +0 -0
- simo/users/__pycache__/utils.cpython-38.pyc +0 -0
- simo/users/migrations/0003_create_roles_and_system_user.py +24 -23
- simo/users/migrations/0019_auto_20231221_1155.py +9 -8
- simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0019_auto_20231221_1155.cpython-38.pyc +0 -0
- simo/users/models.py +6 -7
- simo/users/utils.py +0 -4
- {simo-2.0.32.dist-info → simo-2.0.33.dist-info}/METADATA +1 -1
- {simo-2.0.32.dist-info → simo-2.0.33.dist-info}/RECORD +60 -52
- {simo-2.0.32.dist-info → simo-2.0.33.dist-info}/LICENSE.md +0 -0
- {simo-2.0.32.dist-info → simo-2.0.33.dist-info}/WHEEL +0 -0
- {simo-2.0.32.dist-info → simo-2.0.33.dist-info}/top_level.txt +0 -0
simo/core/models.py
CHANGED
|
@@ -1,35 +1,25 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
1
|
import inspect
|
|
4
2
|
import time
|
|
5
3
|
from collections.abc import Iterable
|
|
6
4
|
from django.utils.text import slugify
|
|
7
|
-
from django.core.cache import cache
|
|
8
5
|
from django.utils.functional import cached_property
|
|
9
6
|
from django.urls import reverse_lazy
|
|
10
7
|
from django.utils.translation import gettext_lazy as _
|
|
11
8
|
from django.db import models
|
|
12
|
-
from django.contrib.auth import get_user_model
|
|
13
|
-
from django.conf import settings
|
|
14
9
|
from django.db.models.signals import post_delete
|
|
15
10
|
from django.dispatch import receiver
|
|
16
|
-
from django.utils import timezone
|
|
17
11
|
from timezone_utils.choices import ALL_TIMEZONES_CHOICES
|
|
18
12
|
from location_field.models.plain import PlainLocationField
|
|
19
13
|
from model_utils import FieldTracker
|
|
20
14
|
from dirtyfields import DirtyFieldsMixin
|
|
21
|
-
from easy_thumbnails.fields import ThumbnailerImageField
|
|
22
|
-
from taggit.managers import TaggableManager
|
|
23
15
|
from simo.core.utils.mixins import SimoAdminMixin
|
|
24
16
|
from simo.core.storage import OverwriteStorage
|
|
25
17
|
from simo.core.utils.validators import validate_svg
|
|
18
|
+
from simo.users.models import User
|
|
26
19
|
from .managers import ZonesManager, CategoriesManager, ComponentsManager
|
|
27
20
|
from .events import GatewayObjectCommand, OnChangeMixin
|
|
28
21
|
|
|
29
22
|
|
|
30
|
-
User = get_user_model()
|
|
31
|
-
|
|
32
|
-
|
|
33
23
|
class Icon(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
34
24
|
slug = models.SlugField(unique=True, db_index=True, primary_key=True)
|
|
35
25
|
keywords = models.CharField(max_length=500, blank=True, null=True)
|
|
@@ -93,12 +83,6 @@ class Instance(models.Model, SimoAdminMixin):
|
|
|
93
83
|
max_length=100, default='metric',
|
|
94
84
|
choices=(('metric', "Metric"), ('imperial', "Imperial"))
|
|
95
85
|
)
|
|
96
|
-
share_location = models.BooleanField(
|
|
97
|
-
default=True,
|
|
98
|
-
help_text="Share exact instance location with SIMO.io remote or not?"
|
|
99
|
-
"Sharing it helps better identify if user is at home or not."
|
|
100
|
-
|
|
101
|
-
)
|
|
102
86
|
indoor_climate_sensor = models.ForeignKey(
|
|
103
87
|
'Component', null=True, blank=True, on_delete=models.SET_NULL,
|
|
104
88
|
limit_choices_to={'base_type__in': ['numeric-sensor', 'multi-sensor']}
|
|
@@ -298,14 +282,19 @@ class Gateway(DirtyFieldsMixin, models.Model, SimoAdminMixin):
|
|
|
298
282
|
if not isinstance(result, dict) and isinstance(result, Iterable):
|
|
299
283
|
for res in result:
|
|
300
284
|
if isinstance(res, models.Model):
|
|
301
|
-
self.discovery['result']
|
|
285
|
+
if res.pk not in self.discovery['result']:
|
|
286
|
+
self.discovery['result'].append(res.pk)
|
|
302
287
|
else:
|
|
303
|
-
self.discovery['result']
|
|
288
|
+
if res not in self.discovery['result']:
|
|
289
|
+
self.discovery['result'].append(res)
|
|
304
290
|
else:
|
|
305
291
|
if isinstance(result, models.Model):
|
|
306
|
-
self.discovery['result']
|
|
292
|
+
if result.pk not in self.discovery['result']:
|
|
293
|
+
self.discovery['result'].append(result.pk)
|
|
307
294
|
else:
|
|
308
|
-
self.discovery['result']
|
|
295
|
+
if result not in self.discovery['result']:
|
|
296
|
+
self.discovery['result'].append(result)
|
|
297
|
+
|
|
309
298
|
|
|
310
299
|
self.save(update_fields=['discovery'])
|
|
311
300
|
|
|
@@ -359,11 +348,17 @@ class Component(DirtyFieldsMixin, models.Model, SimoAdminMixin, OnChangeMixin):
|
|
|
359
348
|
on_delete=models.SET_NULL
|
|
360
349
|
)
|
|
361
350
|
last_change = models.DateTimeField(
|
|
362
|
-
null=True, editable=False, auto_now_add=True
|
|
351
|
+
null=True, editable=False, auto_now_add=True,
|
|
352
|
+
help_text="Last time component state was changed."
|
|
353
|
+
)
|
|
354
|
+
last_modified = models.DateTimeField(
|
|
355
|
+
auto_now_add=True, db_index=True, editable=False,
|
|
356
|
+
help_text="Last time component was modified."
|
|
363
357
|
)
|
|
364
358
|
|
|
365
359
|
last_update = models.DateTimeField(auto_now=True)
|
|
366
360
|
alive = models.BooleanField(default=True)
|
|
361
|
+
error_msg = models.TextField(null=True, blank=True, editable=False)
|
|
367
362
|
battery_level = models.PositiveIntegerField(null=True, editable=False)
|
|
368
363
|
|
|
369
364
|
show_in_app = models.BooleanField(default=True, db_index=True)
|
|
@@ -480,7 +475,7 @@ def is_in_alarm(self):
|
|
|
480
475
|
else:
|
|
481
476
|
self.arm_status = 'disarmed'
|
|
482
477
|
|
|
483
|
-
dirty_fields = self.get_dirty_fields()
|
|
478
|
+
dirty_fields = self.get_dirty_fields(check_relationship=True)
|
|
484
479
|
|
|
485
480
|
if self.pk:
|
|
486
481
|
actor = get_current_user()
|
|
@@ -503,6 +498,13 @@ def is_in_alarm(self):
|
|
|
503
498
|
actor.last_action = timezone.now()
|
|
504
499
|
actor.save()
|
|
505
500
|
|
|
501
|
+
modifying_fields = (
|
|
502
|
+
'name', 'icon', 'zone', 'category', 'config', 'meta',
|
|
503
|
+
'value_units', 'slaves', 'show_in_app', 'alarm_category'
|
|
504
|
+
)
|
|
505
|
+
if any(f in dirty_fields for f in modifying_fields):
|
|
506
|
+
self.last_modified = timezone.now()
|
|
507
|
+
|
|
506
508
|
obj = super().save(*args, **kwargs)
|
|
507
509
|
|
|
508
510
|
return obj
|
simo/core/serializers.py
CHANGED
|
@@ -232,6 +232,7 @@ class ComponentManyToManyRelatedField(serializers.Field):
|
|
|
232
232
|
class ComponentSerializer(FormSerializer):
|
|
233
233
|
id = ObjectSerializerMethodField()
|
|
234
234
|
last_change = TimestampField(read_only=True)
|
|
235
|
+
last_modified = TimestampField(read_only=True)
|
|
235
236
|
read_only = serializers.SerializerMethodField()
|
|
236
237
|
app_widget = serializers.SerializerMethodField()
|
|
237
238
|
slaves = serializers.SerializerMethodField()
|
|
@@ -239,9 +240,11 @@ class ComponentSerializer(FormSerializer):
|
|
|
239
240
|
show_in_app = ObjectSerializerMethodField()
|
|
240
241
|
controller_uid = ObjectSerializerMethodField()
|
|
241
242
|
alive = ObjectSerializerMethodField()
|
|
243
|
+
error_msg = ObjectSerializerMethodField()
|
|
242
244
|
value = ObjectSerializerMethodField()
|
|
243
245
|
config = ObjectSerializerMethodField()
|
|
244
246
|
meta = ObjectSerializerMethodField()
|
|
247
|
+
alarm_category = ObjectSerializerMethodField()
|
|
245
248
|
arm_status = ObjectSerializerMethodField()
|
|
246
249
|
battery_level = ObjectSerializerMethodField()
|
|
247
250
|
controller_methods = serializers.SerializerMethodField()
|
|
@@ -435,9 +438,8 @@ class ComponentSerializer(FormSerializer):
|
|
|
435
438
|
if form.is_valid():
|
|
436
439
|
if form.controller.is_discoverable:
|
|
437
440
|
form.controller.init_discovery(form.cleaned_data)
|
|
438
|
-
return
|
|
439
|
-
|
|
440
|
-
return instance
|
|
441
|
+
return form.save(commit=False)
|
|
442
|
+
return form.save(commit=True)
|
|
441
443
|
raise serializers.ValidationError(form.errors)
|
|
442
444
|
|
|
443
445
|
def get_controller_methods(self, obj):
|
simo/core/signal_receivers.py
CHANGED
|
@@ -1,8 +1,89 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
1
3
|
from django.db import transaction
|
|
2
4
|
from django.db.models.signals import post_save, post_delete
|
|
3
5
|
from django.dispatch import receiver
|
|
4
6
|
from django.utils import timezone
|
|
5
|
-
from .
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from simo.users.models import PermissionsRole
|
|
9
|
+
from .models import Instance, Gateway, Component, Icon, Zone, Category
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@receiver(post_save, sender=Instance)
|
|
13
|
+
def create_instance_defaults(sender, instance, created, **kwargs):
|
|
14
|
+
if not created:
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
# Create default zones
|
|
18
|
+
|
|
19
|
+
for zone_name in (
|
|
20
|
+
'Living Room', 'Kitchen', 'Bathroom', 'Porch', 'Garage', 'Yard', 'Other'
|
|
21
|
+
):
|
|
22
|
+
Zone.objects.create(instance=instance, name=zone_name)
|
|
23
|
+
|
|
24
|
+
core_dir_path = os.path.dirname(os.path.realpath(__file__))
|
|
25
|
+
imgs_folder = os.path.join(
|
|
26
|
+
core_dir_path, 'static/defaults/category_headers'
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
categories_media_dir = os.path.join(settings.MEDIA_ROOT, 'categories')
|
|
30
|
+
if not os.path.exists(categories_media_dir):
|
|
31
|
+
os.makedirs(categories_media_dir)
|
|
32
|
+
|
|
33
|
+
# Create default categories
|
|
34
|
+
|
|
35
|
+
for i, data in enumerate([
|
|
36
|
+
("All", 'star'), ("Climate", 'temperature-half'),
|
|
37
|
+
("Lights", 'lightbulb'), ("Security", 'eye'),
|
|
38
|
+
("Watering", 'faucet'), ("Other", 'flag-pennant')
|
|
39
|
+
]):
|
|
40
|
+
shutil.copy(
|
|
41
|
+
os.path.join(imgs_folder, "%s.jpg" % data[0].lower()),
|
|
42
|
+
os.path.join(
|
|
43
|
+
settings.MEDIA_ROOT, 'categories', "%s.jpg" % data[0].lower()
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
Category.objects.create(
|
|
47
|
+
instance=instance,
|
|
48
|
+
name=data[0], icon=Icon.objects.get(slug=data[1]),
|
|
49
|
+
all=i == 0, header_image=os.path.join(
|
|
50
|
+
'categories', "%s.jpg" % data[0].lower()
|
|
51
|
+
), order=i + 10
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Create generic gateway and components
|
|
55
|
+
|
|
56
|
+
generic, new = Gateway.objects.get_or_create(
|
|
57
|
+
type='simo.generic.gateways.GenericGatewayHandler'
|
|
58
|
+
)
|
|
59
|
+
generic.start()
|
|
60
|
+
dummy, new = Gateway.objects.get_or_create(
|
|
61
|
+
type='simo.generic.gateways.DummyGatewayHandler'
|
|
62
|
+
)
|
|
63
|
+
dummy.start()
|
|
64
|
+
weather_icon = Icon.objects.get(slug='cloud-bolt-sun')
|
|
65
|
+
other_zone = Zone.objects.get(name='Other')
|
|
66
|
+
climate_category = Category.objects.get(name='Climate')
|
|
67
|
+
Component.objects.create(
|
|
68
|
+
name='Weather', icon=weather_icon,
|
|
69
|
+
zone=other_zone,
|
|
70
|
+
category=climate_category,
|
|
71
|
+
gateway=generic, base_type='weather-forecast',
|
|
72
|
+
controller_uid='simo.generic.controllers.WeatherForecast',
|
|
73
|
+
config={'is_main': True}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Create default User permission roles
|
|
77
|
+
|
|
78
|
+
PermissionsRole.objects.create(
|
|
79
|
+
instance=instance, name="Admin", is_superuser=True
|
|
80
|
+
)
|
|
81
|
+
PermissionsRole.objects.create(
|
|
82
|
+
instance=instance, name="Owner", is_owner=True, is_default=True
|
|
83
|
+
)
|
|
84
|
+
PermissionsRole.objects.create(
|
|
85
|
+
instance=instance, name="Guest", is_owner=True
|
|
86
|
+
)
|
|
6
87
|
|
|
7
88
|
|
|
8
89
|
@receiver(post_save, sender=Component)
|
simo/core/tasks.py
CHANGED
|
@@ -143,8 +143,7 @@ def sync_with_remote():
|
|
|
143
143
|
).order_by('-date').first()
|
|
144
144
|
if last_event:
|
|
145
145
|
instance_data['last_event'] = last_event.date.timestamp()
|
|
146
|
-
|
|
147
|
-
instance_data['location'] = instance.location
|
|
146
|
+
|
|
148
147
|
if instance.cover_image and not instance.cover_image_synced:
|
|
149
148
|
thumbnailer = get_thumbnailer(instance.cover_image.path)
|
|
150
149
|
cover_imb_path = thumbnailer.get_thumbnail(
|
|
@@ -163,13 +162,17 @@ def sync_with_remote():
|
|
|
163
162
|
print("Faled! Response code: ", response.status_code)
|
|
164
163
|
return
|
|
165
164
|
|
|
166
|
-
|
|
165
|
+
r_json = response.json()
|
|
166
|
+
|
|
167
|
+
print("Responded with: ", json.dumps(r_json))
|
|
168
|
+
|
|
169
|
+
if 'hub_uid' in response:
|
|
170
|
+
dynamic_settings['core__hub_uid'] = r_json['hub_uid']
|
|
167
171
|
|
|
168
172
|
for instance in instances:
|
|
169
173
|
instance.cover_image_synced = True
|
|
170
174
|
instance.save()
|
|
171
175
|
|
|
172
|
-
r_json = response.json()
|
|
173
176
|
dynamic_settings['core__remote_http'] = r_json.get('hub_remote_http')
|
|
174
177
|
if 'new_secret' in r_json:
|
|
175
178
|
dynamic_settings['core__hub_secret'] = r_json['new_secret']
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/fleet/admin.py
CHANGED
|
@@ -168,10 +168,29 @@ class ColonelAdmin(admin.ModelAdmin):
|
|
|
168
168
|
return mark_safe('<img src="%s" alt="False">' % static('admin/img/icon-no.svg'))
|
|
169
169
|
|
|
170
170
|
|
|
171
|
+
@admin.register(Interface)
|
|
172
|
+
class InterfaceAdmin(admin.ModelAdmin):
|
|
173
|
+
list_filter = 'colonel', 'type'
|
|
174
|
+
actions = 'broadcast_reset'
|
|
175
|
+
|
|
176
|
+
def broadcast_reset(self, request, queryset):
|
|
177
|
+
broadcasted = 0
|
|
178
|
+
for interface in queryset.filter(
|
|
179
|
+
colonel__socket_connected=True
|
|
180
|
+
):
|
|
181
|
+
interface.broadcast_reset()
|
|
182
|
+
broadcasted += 1
|
|
183
|
+
|
|
184
|
+
if broadcasted:
|
|
185
|
+
self.message_user(
|
|
186
|
+
request,
|
|
187
|
+
f"Reset command was broadcased to {broadcasted} interfaces."
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
self.message_user(
|
|
191
|
+
request,
|
|
192
|
+
f"No reset command was broadcasted, "
|
|
193
|
+
f"probably because they are out of reach at the moment."
|
|
194
|
+
)
|
|
171
195
|
|
|
172
|
-
|
|
173
|
-
# @admin.register(BLEDevice)
|
|
174
|
-
# class BLEDeviceAdmin(admin.ModelAdmin):
|
|
175
|
-
# list_display = ['name', 'mac', 'type', 'last_seen']
|
|
176
|
-
# readonly_fields = list_display + ['addr']
|
|
177
|
-
# fields = readonly_fields
|
|
196
|
+
broadcast_reset.short_description = "Broadcast RESET command"
|
simo/fleet/controllers.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from django.utils.translation import gettext_lazy as _
|
|
2
3
|
from simo.core.events import GatewayObjectCommand
|
|
3
4
|
from simo.core.controllers import (
|
|
@@ -24,7 +25,7 @@ from .forms import (
|
|
|
24
25
|
ColonelDHTSensorConfigForm, DS18B20SensorConfigForm,
|
|
25
26
|
BME680SensorConfigForm, MPC9808SensorConfigForm,
|
|
26
27
|
DualMotorValveForm, BlindsConfigForm, BurglarSmokeDetectorConfigForm,
|
|
27
|
-
TTLockConfigForm, DALIDeviceConfigForm,
|
|
28
|
+
TTLockConfigForm, DALIDeviceConfigForm, DaliLampForm, DaliGearGroupForm
|
|
28
29
|
)
|
|
29
30
|
|
|
30
31
|
|
|
@@ -182,14 +183,6 @@ class BasicOutputMixin:
|
|
|
182
183
|
pins.append(control_unit['pin_no'])
|
|
183
184
|
return pins
|
|
184
185
|
|
|
185
|
-
def _send_to_device(self, value):
|
|
186
|
-
GatewayObjectCommand(
|
|
187
|
-
self.component.gateway,
|
|
188
|
-
Colonel(id=self.component.config['colonel']),
|
|
189
|
-
set_val=value,
|
|
190
|
-
component_id=self.component.id,
|
|
191
|
-
).publish()
|
|
192
|
-
|
|
193
186
|
|
|
194
187
|
class Switch(FleeDeviceMixin, BasicOutputMixin, BaseSwitch):
|
|
195
188
|
config_form = ColonelSwitchConfigForm
|
|
@@ -240,7 +233,6 @@ class PWMOutput(FleeDeviceMixin, BasicOutputMixin, BaseDimmer):
|
|
|
240
233
|
else:
|
|
241
234
|
pwm_value = 0
|
|
242
235
|
else:
|
|
243
|
-
|
|
244
236
|
val_amplitude = conf.get('max', 100) - conf.get('min', 0)
|
|
245
237
|
val_relative = value / val_amplitude
|
|
246
238
|
pwm_amplitude = conf.get('duty_max', 1023) - conf.get('duty_min', 0.0)
|
|
@@ -306,14 +298,6 @@ class TTLock(FleeDeviceMixin, Lock):
|
|
|
306
298
|
name = 'TTLock'
|
|
307
299
|
discovery_msg = _("Please activate your TTLock so it can be discovered.")
|
|
308
300
|
|
|
309
|
-
def _send_to_device(self, value):
|
|
310
|
-
GatewayObjectCommand(
|
|
311
|
-
self.component.gateway,
|
|
312
|
-
Colonel(id=self.component.config['colonel']),
|
|
313
|
-
set_val=value,
|
|
314
|
-
component_id=self.component.id,
|
|
315
|
-
).publish()
|
|
316
|
-
|
|
317
301
|
@classmethod
|
|
318
302
|
def init_discovery(self, form_cleaned_data):
|
|
319
303
|
from simo.core.models import Gateway
|
|
@@ -508,7 +492,7 @@ class DALIDevice(FleeDeviceMixin, ControllerBase):
|
|
|
508
492
|
app_widget = SingleSwitchWidget
|
|
509
493
|
|
|
510
494
|
def _validate_val(self, value, occasion=None):
|
|
511
|
-
|
|
495
|
+
return value
|
|
512
496
|
|
|
513
497
|
@classmethod
|
|
514
498
|
def init_discovery(self, form_cleaned_data):
|
|
@@ -529,63 +513,124 @@ class DALIDevice(FleeDeviceMixin, ControllerBase):
|
|
|
529
513
|
if data['discovery-result'] == 'fail':
|
|
530
514
|
if data['result'] == 1:
|
|
531
515
|
return {'error': 'DALI interface is unavailable!'}
|
|
516
|
+
elif data['result'] == 2:
|
|
517
|
+
return {'error': 'No new DALI devices were found!'}
|
|
518
|
+
elif data['result'] == 2:
|
|
519
|
+
return {'error': 'DALI line is fully occupied, no more devices can be included!'}
|
|
532
520
|
else:
|
|
533
521
|
return {'error': 'Unknown error!'}
|
|
534
522
|
|
|
535
|
-
|
|
523
|
+
from simo.core.models import Component
|
|
524
|
+
from simo.core.utils.type_constants import CONTROLLER_TYPES_MAP
|
|
525
|
+
controller_uid = 'simo.fleet.controllers.' + data['result']['type']
|
|
526
|
+
if controller_uid not in CONTROLLER_TYPES_MAP:
|
|
527
|
+
return {'error': f"Unknown controller type: {controller_uid}"}
|
|
528
|
+
|
|
529
|
+
comp = Component.objects.filter(
|
|
530
|
+
controller_uid=controller_uid,
|
|
531
|
+
meta__finalization_data__temp_id=data['result']['id']
|
|
532
|
+
).first()
|
|
533
|
+
if comp:
|
|
534
|
+
print(f"{comp} is already created.")
|
|
535
|
+
GatewayObjectCommand(
|
|
536
|
+
comp.gateway, Colonel(
|
|
537
|
+
id=comp.config['colonel']
|
|
538
|
+
), command='finalize',
|
|
539
|
+
data=comp.meta['finalization_data']
|
|
540
|
+
).publish()
|
|
541
|
+
return [comp]
|
|
542
|
+
|
|
543
|
+
controller_cls = CONTROLLER_TYPES_MAP[controller_uid]
|
|
536
544
|
|
|
537
545
|
started_with = deserialize_form_data(started_with)
|
|
538
546
|
started_with['name'] += f" {data['result']['config']['da']}"
|
|
547
|
+
started_with['controller_uid'] = controller_uid
|
|
548
|
+
started_with['base_type'] = controller_cls.base_type
|
|
539
549
|
form = controller_cls.config_form(
|
|
540
550
|
controller_uid=controller_cls.uid, data=started_with
|
|
541
551
|
)
|
|
542
552
|
|
|
543
553
|
if form.is_valid():
|
|
544
554
|
new_component = form.save()
|
|
555
|
+
new_component = Component.objects.get(id=new_component.id)
|
|
545
556
|
new_component.config.update(data.get('result', {}).get('config'))
|
|
557
|
+
|
|
558
|
+
# saving it to meta, for repeated delivery
|
|
546
559
|
new_component.meta['finalization_data'] = {
|
|
547
560
|
'temp_id': data['result']['id'],
|
|
548
561
|
'permanent_id': new_component.id,
|
|
549
|
-
'
|
|
550
|
-
'type':
|
|
551
|
-
'
|
|
552
|
-
|
|
562
|
+
'comp_config': {
|
|
563
|
+
'type': controller_uid.split('.')[-1],
|
|
564
|
+
'family': new_component.controller.family,
|
|
565
|
+
'config': json.loads(json.dumps(new_component.config))
|
|
566
|
+
}
|
|
553
567
|
}
|
|
568
|
+
# Perform default config update on initial component setup
|
|
569
|
+
new_component.meta[
|
|
570
|
+
'finalization_data'
|
|
571
|
+
]['comp_config']['config']['boot_update'] = True
|
|
554
572
|
new_component.save()
|
|
555
573
|
GatewayObjectCommand(
|
|
556
574
|
new_component.gateway, Colonel(
|
|
557
575
|
id=new_component.config['colonel']
|
|
558
576
|
), command='finalize',
|
|
559
|
-
data=new_component.meta['finalization_data']
|
|
577
|
+
data=new_component.meta['finalization_data']
|
|
560
578
|
).publish()
|
|
561
579
|
return [new_component]
|
|
562
580
|
|
|
563
581
|
# Literally impossible, but just in case...
|
|
564
582
|
return {'error': 'INVALID INITIAL DISCOVERY FORM!'}
|
|
565
583
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
584
|
+
def replace(self):
|
|
585
|
+
"""
|
|
586
|
+
Hook up brand new replacement device to the dali line
|
|
587
|
+
and execute this command on existing (dead) component instance,
|
|
588
|
+
so that it can be replaced by the new physical device.
|
|
589
|
+
"""
|
|
571
590
|
GatewayObjectCommand(
|
|
572
591
|
self.component.gateway,
|
|
573
592
|
Colonel(id=self.component.config['colonel']),
|
|
574
|
-
|
|
575
|
-
component_id=self.component.id,
|
|
593
|
+
id=self.component.id, command='call', method='replace'
|
|
576
594
|
).publish()
|
|
577
595
|
|
|
578
596
|
|
|
579
|
-
class DALILamp(
|
|
597
|
+
class DALILamp(BaseDimmer, DALIDevice):
|
|
580
598
|
family = 'dali'
|
|
581
599
|
manual_add = False
|
|
582
600
|
name = 'DALI Lamp'
|
|
583
|
-
config_form =
|
|
601
|
+
config_form = DaliLampForm
|
|
584
602
|
|
|
585
603
|
|
|
586
|
-
class
|
|
604
|
+
class DALIGearGroup(FleeDeviceMixin, BaseDimmer):
|
|
605
|
+
gateway_class = FleetGatewayHandler
|
|
587
606
|
family = 'dali'
|
|
588
|
-
manual_add =
|
|
589
|
-
name = 'DALI
|
|
607
|
+
manual_add = True
|
|
608
|
+
name = 'DALI Gear Group'
|
|
609
|
+
config_form = DaliGearGroupForm
|
|
610
|
+
|
|
611
|
+
def _modify_member_group(self, member, group, remove=False):
|
|
612
|
+
groups = set(member.config.get('groups', []))
|
|
613
|
+
if remove:
|
|
614
|
+
if group in groups:
|
|
615
|
+
groups.remove(group)
|
|
616
|
+
else:
|
|
617
|
+
if group not in groups:
|
|
618
|
+
groups.add(group)
|
|
619
|
+
member.config['groups'] = list(groups)
|
|
620
|
+
member.save()
|
|
621
|
+
colonel = Colonel.objects.filter(
|
|
622
|
+
id=member.config.get('colonel', 0)
|
|
623
|
+
).first()
|
|
624
|
+
if not colonel:
|
|
625
|
+
return
|
|
626
|
+
GatewayObjectCommand(
|
|
627
|
+
member.gateway, colonel, id=member.id,
|
|
628
|
+
command='call', method='update_config',
|
|
629
|
+
args=[member.controller._get_colonel_config()]
|
|
630
|
+
).publish()
|
|
590
631
|
|
|
591
632
|
|
|
633
|
+
class DALIRelay(BaseSwitch, DALIDevice):
|
|
634
|
+
family = 'dali'
|
|
635
|
+
manual_add = False
|
|
636
|
+
name = 'DALI Relay'
|