simo 2.4.2__py3-none-any.whl → 2.5.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/backups/tasks.py +11 -1
- simo/core/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/__pycache__/api.cpython-38.pyc +0 -0
- simo/core/__pycache__/app_widgets.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__/middleware.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__/tasks.cpython-38.pyc +0 -0
- simo/core/admin.py +4 -4
- simo/core/api.py +20 -4
- simo/core/app_widgets.py +5 -0
- simo/core/controllers.py +2 -2
- simo/core/events.py +2 -0
- simo/core/forms.py +2 -0
- simo/core/management/commands/gateways_manager.py +0 -3
- simo/core/middleware.py +7 -1
- simo/core/models.py +26 -6
- simo/core/serializers.py +17 -17
- simo/core/tasks.py +10 -7
- 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/controllers.py +86 -22
- simo/fleet/forms.py +84 -9
- simo/fleet/migrations/0039_auto_20241016_1047.py +28 -0
- simo/fleet/migrations/0040_alter_colonel_pwm_frequency.py +18 -0
- simo/fleet/migrations/__pycache__/0039_auto_20241016_1047.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0040_alter_colonel_pwm_frequency.cpython-38.pyc +0 -0
- simo/fleet/models.py +2 -2
- 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/controllers.py +41 -2
- simo/generic/forms.py +71 -7
- simo/generic/models.py +0 -1
- simo/generic/scripting/__init__.py +16 -0
- simo/generic/scripting/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/generic/scripting/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/generic/scripting/helpers.py +35 -0
- simo/generic/scripting/serializers.py +77 -0
- simo/generic/templates/admin/controller_widgets/weather_forecast.html +2 -2
- simo/notifications/__pycache__/utils.cpython-38.pyc +0 -0
- simo/notifications/utils.py +30 -12
- simo/scripting.py +2 -2
- simo/users/__pycache__/api.cpython-38.pyc +0 -0
- simo/users/__pycache__/managers.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__/utils.cpython-38.pyc +0 -0
- simo/users/api.py +36 -7
- simo/users/managers.py +5 -1
- simo/users/migrations/0034_instanceuser_last_seen_location_and_more.py +24 -0
- simo/users/migrations/__pycache__/0034_instanceuser_last_seen_location_and_more.cpython-38.pyc +0 -0
- simo/users/models.py +37 -32
- simo/users/serializers.py +11 -8
- simo/users/utils.py +14 -3
- {simo-2.4.2.dist-info → simo-2.5.1.dist-info}/METADATA +1 -1
- {simo-2.4.2.dist-info → simo-2.5.1.dist-info}/RECORD +66 -54
- {simo-2.4.2.dist-info → simo-2.5.1.dist-info}/WHEEL +1 -1
- {simo-2.4.2.dist-info → simo-2.5.1.dist-info}/LICENSE.md +0 -0
- {simo-2.4.2.dist-info → simo-2.5.1.dist-info}/entry_points.txt +0 -0
- {simo-2.4.2.dist-info → simo-2.5.1.dist-info}/top_level.txt +0 -0
simo/backups/tasks.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os, subprocess, json, uuid, datetime, shutil, pytz
|
|
2
|
-
from datetime import datetime,
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
|
+
from django.utils import timezone
|
|
3
4
|
from celeryc import celery_app
|
|
4
5
|
from simo.conf import dynamic_settings
|
|
5
6
|
from simo.core.utils.helpers import get_random_string
|
|
@@ -407,8 +408,17 @@ def restore_backup(backup_id):
|
|
|
407
408
|
subprocess.run('reboot', shell=True)
|
|
408
409
|
|
|
409
410
|
|
|
411
|
+
@celery_app.task
|
|
412
|
+
def clean_old_logs():
|
|
413
|
+
from .models import BackupLog
|
|
414
|
+
BackupLog.objects.filter(
|
|
415
|
+
datetime__lt=timezone.now() - timedelta(days=90)
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
|
|
410
419
|
@celery_app.on_after_finalize.connect
|
|
411
420
|
def setup_periodic_tasks(sender, **kwargs):
|
|
412
421
|
sender.add_periodic_task(60 * 60, check_backups.s())
|
|
413
422
|
# perform auto backup every 12 hours
|
|
414
423
|
sender.add_periodic_task(60 * 60 * 12, perform_backup.s())
|
|
424
|
+
sender.add_periodic_task(60 * 60, clean_old_logs.s())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/core/admin.py
CHANGED
|
@@ -232,11 +232,11 @@ class ComponentPermissionInline(admin.TabularInline):
|
|
|
232
232
|
return qs.filter(role__instance__in=request.user.instances)
|
|
233
233
|
|
|
234
234
|
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
def has_delete_permission(self, request, obj=None):
|
|
236
|
+
return False
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
def has_add_permission(self, request, obj=None):
|
|
239
|
+
return False
|
|
240
240
|
|
|
241
241
|
|
|
242
242
|
@admin.register(Component)
|
simo/core/api.py
CHANGED
|
@@ -167,6 +167,12 @@ def get_components_queryset(instance, user):
|
|
|
167
167
|
).values('id').first()
|
|
168
168
|
if main_alarm_group:
|
|
169
169
|
c_ids.add(main_alarm_group['id'])
|
|
170
|
+
state = Component.objects.filter(
|
|
171
|
+
zone__instance=instance,
|
|
172
|
+
base_type='state-select', config__is_main=True
|
|
173
|
+
).values('id').first()
|
|
174
|
+
if state:
|
|
175
|
+
c_ids.add(state['id'])
|
|
170
176
|
|
|
171
177
|
user_role = user.get_role(instance)
|
|
172
178
|
|
|
@@ -369,7 +375,7 @@ class ComponentHistoryViewSet(InstanceMixin, viewsets.ReadOnlyModelViewSet):
|
|
|
369
375
|
if not component.controller:
|
|
370
376
|
return
|
|
371
377
|
|
|
372
|
-
history_display_example = component.controller.
|
|
378
|
+
history_display_example = component.controller._history_display([component.value])
|
|
373
379
|
if not history_display_example:
|
|
374
380
|
return None
|
|
375
381
|
|
|
@@ -393,9 +399,9 @@ class ComponentHistoryViewSet(InstanceMixin, viewsets.ReadOnlyModelViewSet):
|
|
|
393
399
|
values = []
|
|
394
400
|
for item in history_items:
|
|
395
401
|
values.append(item.value)
|
|
396
|
-
val = component.controller.
|
|
402
|
+
val = component.controller._history_display(values)
|
|
397
403
|
else:
|
|
398
|
-
val = component.controller.
|
|
404
|
+
val = component.controller._history_display([])
|
|
399
405
|
|
|
400
406
|
if not val:
|
|
401
407
|
val = prev_val
|
|
@@ -425,7 +431,7 @@ class ComponentHistoryViewSet(InstanceMixin, viewsets.ReadOnlyModelViewSet):
|
|
|
425
431
|
component=component, date__lt=start_from, type='value'
|
|
426
432
|
).order_by('date').last()
|
|
427
433
|
if last_event:
|
|
428
|
-
prev_val = component.controller.
|
|
434
|
+
prev_val = component.controller._history_display([last_event.value])
|
|
429
435
|
else:
|
|
430
436
|
prev_val = history_display_example
|
|
431
437
|
|
|
@@ -544,6 +550,15 @@ class SettingsViewSet(InstanceMixin, viewsets.GenericViewSet):
|
|
|
544
550
|
if main_alarm_group:
|
|
545
551
|
main_alarm_group_id = main_alarm_group.id
|
|
546
552
|
|
|
553
|
+
main_state = Component.objects.filter(
|
|
554
|
+
zone__instance=self.instance,
|
|
555
|
+
base_type='state-select', config__is_main=True
|
|
556
|
+
).first()
|
|
557
|
+
if main_state:
|
|
558
|
+
main_state = main_state.id
|
|
559
|
+
else:
|
|
560
|
+
main_state = None
|
|
561
|
+
|
|
547
562
|
return RESTResponse({
|
|
548
563
|
'hub_uid': dynamic_settings['core__hub_uid'],
|
|
549
564
|
'instance_name': self.instance.name,
|
|
@@ -553,6 +568,7 @@ class SettingsViewSet(InstanceMixin, viewsets.GenericViewSet):
|
|
|
553
568
|
'last_event': last_event,
|
|
554
569
|
'weather_forecast': wf_comp_id,
|
|
555
570
|
'main_alarm_group': main_alarm_group_id,
|
|
571
|
+
'main_state': main_state,
|
|
556
572
|
# TODO: Remove these two when the app is updated for everybody.
|
|
557
573
|
'remote_http': dynamic_settings['core__remote_http'],
|
|
558
574
|
'local_http': 'https://%s' % get_self_ip(),
|
simo/core/app_widgets.py
CHANGED
simo/core/controllers.py
CHANGED
|
@@ -302,7 +302,7 @@ class ControllerBase(ABC):
|
|
|
302
302
|
from .models import Component
|
|
303
303
|
Component.objects.bulk_send(bulk_send_map)
|
|
304
304
|
|
|
305
|
-
def
|
|
305
|
+
def _history_display(self, values):
|
|
306
306
|
assert type(values) in (list, tuple)
|
|
307
307
|
|
|
308
308
|
if type(self.component.value) in (int, float):
|
|
@@ -445,7 +445,7 @@ class MultiSensor(ControllerBase):
|
|
|
445
445
|
))
|
|
446
446
|
return value
|
|
447
447
|
|
|
448
|
-
def
|
|
448
|
+
def _history_display(self, values):
|
|
449
449
|
assert type(values) in (list, tuple)
|
|
450
450
|
|
|
451
451
|
vectors = []
|
simo/core/events.py
CHANGED
simo/core/forms.py
CHANGED
|
@@ -212,6 +212,8 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
212
212
|
controller_type = None
|
|
213
213
|
has_icon = True
|
|
214
214
|
has_alarm = True
|
|
215
|
+
# do not allow modification via app of these fields
|
|
216
|
+
app_exclude_fields = []
|
|
215
217
|
|
|
216
218
|
# fields that can be edited via SIMO.io app by instance owners.
|
|
217
219
|
# Users who have is_owner enabled on their user role.
|
simo/core/middleware.py
CHANGED
|
@@ -30,12 +30,18 @@ def get_current_instance(request=None):
|
|
|
30
30
|
if not instance and request and request.session.get('instance_id'):
|
|
31
31
|
from simo.core.models import Instance
|
|
32
32
|
instance = Instance.objects.filter(
|
|
33
|
-
id=request.session['instance_id']
|
|
33
|
+
id=request.session['instance_id'], is_active=True
|
|
34
34
|
).first()
|
|
35
35
|
if not instance:
|
|
36
36
|
del request.session['instance_id']
|
|
37
37
|
else:
|
|
38
38
|
introduce_instance(instance, request)
|
|
39
|
+
|
|
40
|
+
if not instance:
|
|
41
|
+
from .models import Instance
|
|
42
|
+
instance = Instance.objects.filter(is_active=True).first()
|
|
43
|
+
if instance:
|
|
44
|
+
introduce_instance(instance)
|
|
39
45
|
return instance
|
|
40
46
|
|
|
41
47
|
|
simo/core/models.py
CHANGED
|
@@ -406,8 +406,8 @@ def is_in_alarm(self):
|
|
|
406
406
|
|
|
407
407
|
_controller_initiated = False
|
|
408
408
|
|
|
409
|
-
|
|
410
|
-
|
|
409
|
+
|
|
410
|
+
|
|
411
411
|
_obj_ct_id = 0
|
|
412
412
|
|
|
413
413
|
class Meta:
|
|
@@ -517,11 +517,13 @@ def is_in_alarm(self):
|
|
|
517
517
|
actor.last_action = timezone.now()
|
|
518
518
|
actor.save()
|
|
519
519
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
['value', 'arm_status', 'battery_level', 'alive', 'meta']
|
|
523
|
-
):
|
|
520
|
+
changing_fields = ['value', 'arm_status', 'battery_level', 'alive', 'meta']
|
|
521
|
+
if any(f in dirty_fields for f in changing_fields):
|
|
524
522
|
self.last_change = timezone.now()
|
|
523
|
+
if 'update_fields' in kwargs \
|
|
524
|
+
and 'last_change' not in kwargs['update_fields']:
|
|
525
|
+
kwargs['update_fields'].append('last_change')
|
|
526
|
+
|
|
525
527
|
|
|
526
528
|
modifying_fields = (
|
|
527
529
|
'name', 'icon', 'zone', 'category', 'config', 'meta',
|
|
@@ -529,6 +531,9 @@ def is_in_alarm(self):
|
|
|
529
531
|
)
|
|
530
532
|
if any(f in dirty_fields for f in modifying_fields):
|
|
531
533
|
self.last_modified = timezone.now()
|
|
534
|
+
if 'update_fields' in kwargs \
|
|
535
|
+
and 'last_modified' not in kwargs['update_fields']:
|
|
536
|
+
kwargs['update_fields'].append('last_modified')
|
|
532
537
|
|
|
533
538
|
obj = super().save(*args, **kwargs)
|
|
534
539
|
|
|
@@ -581,6 +586,21 @@ def is_in_alarm(self):
|
|
|
581
586
|
return False
|
|
582
587
|
return perm.write
|
|
583
588
|
|
|
589
|
+
def get_controller_methods(self):
|
|
590
|
+
c_methods = []
|
|
591
|
+
for m in inspect.getmembers(
|
|
592
|
+
self.controller, predicate=inspect.ismethod
|
|
593
|
+
):
|
|
594
|
+
method = m[0]
|
|
595
|
+
if method.startswith('_'):
|
|
596
|
+
continue
|
|
597
|
+
if method in ('info', 'set'):
|
|
598
|
+
continue
|
|
599
|
+
c_methods.append(method)
|
|
600
|
+
if self.alarm_category:
|
|
601
|
+
c_methods.extend(['arm', 'disarm'])
|
|
602
|
+
return c_methods
|
|
603
|
+
|
|
584
604
|
|
|
585
605
|
class ComponentHistory(models.Model):
|
|
586
606
|
component = models.ForeignKey(
|
simo/core/serializers.py
CHANGED
|
@@ -27,6 +27,8 @@ from .forms import ComponentAdminForm
|
|
|
27
27
|
from .models import Category, Zone, Icon, ComponentHistory
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
|
|
31
|
+
|
|
30
32
|
class TimestampField(serializers.Field):
|
|
31
33
|
|
|
32
34
|
def to_representation(self, value):
|
|
@@ -318,7 +320,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
318
320
|
form_field = form[field_name]
|
|
319
321
|
|
|
320
322
|
cls = form_field.field.__class__
|
|
321
|
-
if
|
|
323
|
+
if isinstance(form_field.field.widget, forms.Textarea):
|
|
322
324
|
serializer_field_class = TextAreaSerializerField
|
|
323
325
|
else:
|
|
324
326
|
try:
|
|
@@ -416,16 +418,19 @@ class ComponentSerializer(FormSerializer):
|
|
|
416
418
|
controller_uid=controller_uid, instance=instance,
|
|
417
419
|
**kwargs
|
|
418
420
|
)
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
421
|
+
|
|
422
|
+
user_role = self.context['request'].user.get_role(
|
|
423
|
+
self.context['instance']
|
|
424
|
+
)
|
|
425
|
+
for field_name in list(form.fields.keys()):
|
|
426
|
+
if field_name in form.app_exclude_fields:
|
|
427
|
+
del form.fields[field_name]
|
|
428
|
+
continue
|
|
429
|
+
if field_name in form.basic_fields:
|
|
430
|
+
continue
|
|
431
|
+
if self.context['request'].user.is_master or user_role.is_superuser:
|
|
432
|
+
continue
|
|
433
|
+
del form.fields[field_name]
|
|
429
434
|
|
|
430
435
|
if form_key is not None:
|
|
431
436
|
self.context['forms'][form_key] = form
|
|
@@ -492,12 +497,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
492
497
|
raise serializers.ValidationError(form.errors)
|
|
493
498
|
|
|
494
499
|
def get_controller_methods(self, obj):
|
|
495
|
-
|
|
496
|
-
obj.controller, predicate=inspect.ismethod
|
|
497
|
-
) if not m[0].startswith('_')]
|
|
498
|
-
if obj.alarm_category:
|
|
499
|
-
c_methods.extend(['arm', 'disarm'])
|
|
500
|
-
return c_methods
|
|
500
|
+
return obj.get_controller_methods()
|
|
501
501
|
|
|
502
502
|
def get_info(self, obj):
|
|
503
503
|
if obj.controller:
|
simo/core/tasks.py
CHANGED
|
@@ -211,7 +211,11 @@ def sync_with_remote():
|
|
|
211
211
|
).first()
|
|
212
212
|
if weather_component:
|
|
213
213
|
weather_component.track_history = False
|
|
214
|
-
weather_component.controller.set(
|
|
214
|
+
weather_component.controller.set(
|
|
215
|
+
weather_forecast.pop('current', None)
|
|
216
|
+
)
|
|
217
|
+
weather_component.meta['forecast'] = weather_forecast
|
|
218
|
+
weather_component.save()
|
|
215
219
|
|
|
216
220
|
for email, options in users_data.items():
|
|
217
221
|
|
|
@@ -380,15 +384,14 @@ def low_battery_notifications():
|
|
|
380
384
|
zone__instance=instance,
|
|
381
385
|
battery_level__isnull=False, battery_level__lt=20
|
|
382
386
|
):
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if users:
|
|
387
|
+
iusers = comp.zone.instance.instance_users.filter(
|
|
388
|
+
is_active=True, role__is_owner=True
|
|
389
|
+
)
|
|
390
|
+
if iusers:
|
|
388
391
|
notify_users(
|
|
389
392
|
comp.zone.instance, 'warning',
|
|
390
393
|
f"Low battery ({comp.battery_level}%) on {comp}",
|
|
391
|
-
component=comp,
|
|
394
|
+
component=comp, instance_users=iusers
|
|
392
395
|
)
|
|
393
396
|
|
|
394
397
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/fleet/controllers.py
CHANGED
|
@@ -10,8 +10,7 @@ from simo.core.controllers import (
|
|
|
10
10
|
Switch as BaseSwitch, Dimmer as BaseDimmer,
|
|
11
11
|
MultiSensor as BaseMultiSensor, RGBWLight as BaseRGBWLight
|
|
12
12
|
)
|
|
13
|
-
from simo.
|
|
14
|
-
from simo.core.app_widgets import NumericSensorWidget
|
|
13
|
+
from simo.core.app_widgets import NumericSensorWidget, AirQualityWidget
|
|
15
14
|
from simo.core.controllers import Lock, ControllerBase, SingleSwitchWidget
|
|
16
15
|
from simo.core.utils.helpers import heat_index
|
|
17
16
|
from simo.core.utils.serialization import (
|
|
@@ -23,7 +22,7 @@ from .gateways import FleetGatewayHandler
|
|
|
23
22
|
from .forms import (
|
|
24
23
|
ColonelPinChoiceField,
|
|
25
24
|
ColonelBinarySensorConfigForm, ColonelButtonConfigForm,
|
|
26
|
-
ColonelSwitchConfigForm, ColonelPWMOutputConfigForm,
|
|
25
|
+
ColonelSwitchConfigForm, ColonelPWMOutputConfigForm, DCDriverConfigForm,
|
|
27
26
|
ColonelNumericSensorConfigForm, ColonelRGBLightConfigForm,
|
|
28
27
|
ColonelDHTSensorConfigForm, DS18B20SensorConfigForm,
|
|
29
28
|
BME680SensorConfigForm, MPC9808SensorConfigForm, ENS160SensorConfigForm,
|
|
@@ -239,6 +238,7 @@ class ENS160AirQualitySensor(FleeDeviceMixin, BaseMultiSensor):
|
|
|
239
238
|
gateway_class = FleetGatewayHandler
|
|
240
239
|
config_form = ENS160SensorConfigForm
|
|
241
240
|
name = "ENS160 Air Quality Sensor (I2C)"
|
|
241
|
+
app_widget = AirQualityWidget
|
|
242
242
|
|
|
243
243
|
default_value = [
|
|
244
244
|
["CO2", 0, "ppm"],
|
|
@@ -386,42 +386,87 @@ class PWMOutput(FadeMixin, FleeDeviceMixin, BasicOutputMixin, BaseDimmer):
|
|
|
386
386
|
value = conf.get('min', 0)
|
|
387
387
|
|
|
388
388
|
if value >= conf.get('max', 100):
|
|
389
|
-
|
|
390
|
-
pwm_value = 0
|
|
391
|
-
else:
|
|
392
|
-
pwm_value = 1023
|
|
389
|
+
pwm_value = 0
|
|
393
390
|
elif value <= conf.get('min', 100):
|
|
394
|
-
|
|
395
|
-
pwm_value = 1023
|
|
396
|
-
else:
|
|
397
|
-
pwm_value = 0
|
|
391
|
+
pwm_value = 1023
|
|
398
392
|
else:
|
|
399
393
|
val_amplitude = conf.get('max', 100) - conf.get('min', 0)
|
|
400
394
|
val_relative = value / val_amplitude
|
|
401
|
-
pwm_amplitude = conf.get('duty_max', 1023) - conf.get('duty_min', 0.0)
|
|
402
|
-
pwm_value = conf.get('duty_min', 0.0) + pwm_amplitude * val_relative
|
|
403
395
|
|
|
404
|
-
|
|
405
|
-
|
|
396
|
+
duty_max = 1023 - (conf.get('device_min', 0) * 0.01 * 1023)
|
|
397
|
+
duty_min = 1023 - conf.get('device_max', 100) * 0.01 * 1023
|
|
398
|
+
|
|
399
|
+
pwm_amplitude = duty_max - duty_min
|
|
400
|
+
pwm_value = duty_min + pwm_amplitude * val_relative
|
|
401
|
+
|
|
402
|
+
pwm_value = duty_max - pwm_value + duty_min
|
|
406
403
|
|
|
407
404
|
return pwm_value
|
|
408
405
|
|
|
409
406
|
def _prepare_for_set(self, pwm_value):
|
|
410
407
|
conf = self.component.config
|
|
411
|
-
|
|
408
|
+
duty_max = 1023 - (conf.get('device_min', 0) * 0.01 * 1023)
|
|
409
|
+
duty_min = 1023 - conf.get('device_max', 100) * 0.01 * 1023
|
|
410
|
+
|
|
411
|
+
if pwm_value > duty_max:
|
|
412
412
|
value = conf.get('max', 100)
|
|
413
|
-
elif pwm_value <
|
|
413
|
+
elif pwm_value < duty_min:
|
|
414
414
|
value = conf.get('min', 0)
|
|
415
415
|
else:
|
|
416
|
-
pwm_amplitude =
|
|
417
|
-
relative_value = (pwm_value -
|
|
416
|
+
pwm_amplitude =duty_max - duty_min
|
|
417
|
+
relative_value = (pwm_value - duty_min) / pwm_amplitude
|
|
418
418
|
val_amplitude = conf.get('max', 100) - conf.get('min', 0)
|
|
419
419
|
value = conf.get('min', 0) + val_amplitude * relative_value
|
|
420
420
|
|
|
421
|
-
|
|
422
|
-
value = conf.get('max', 100) - value + conf.get('min', 0)
|
|
421
|
+
value = conf.get('max', 100) - value + conf.get('min', 0)
|
|
423
422
|
|
|
424
|
-
return value
|
|
423
|
+
return round(value, 3)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
class DCDriver(FadeMixin, FleeDeviceMixin, BasicOutputMixin, BaseDimmer):
|
|
427
|
+
name = "0 - 24V DC Driver"
|
|
428
|
+
config_form = DCDriverConfigForm
|
|
429
|
+
default_value_units = 'V'
|
|
430
|
+
|
|
431
|
+
def _prepare_for_send(self, value):
|
|
432
|
+
conf = self.component.config
|
|
433
|
+
if value >= conf.get('max', 24):
|
|
434
|
+
value = conf.get('max', 24)
|
|
435
|
+
elif value < conf.get('min', 0):
|
|
436
|
+
value = conf.get('min', 0)
|
|
437
|
+
|
|
438
|
+
if value >= conf.get('max', 24):
|
|
439
|
+
pwm_value = 1023
|
|
440
|
+
elif value <= conf.get('min', 100):
|
|
441
|
+
pwm_value = 0
|
|
442
|
+
else:
|
|
443
|
+
val_amplitude = conf.get('max', 24) - conf.get('min', 0)
|
|
444
|
+
val_relative = value / val_amplitude
|
|
445
|
+
|
|
446
|
+
duty_max = conf.get('device_max', 24) / 24 * 1023
|
|
447
|
+
duty_min = conf.get('device_min', 0) / 24 * 1023
|
|
448
|
+
|
|
449
|
+
pwm_amplitude = duty_max - duty_min
|
|
450
|
+
pwm_value = duty_min + pwm_amplitude * val_relative
|
|
451
|
+
|
|
452
|
+
return pwm_value
|
|
453
|
+
|
|
454
|
+
def _prepare_for_set(self, pwm_value):
|
|
455
|
+
conf = self.component.config
|
|
456
|
+
duty_max = conf.get('device_max', 24) / 24 * 1023
|
|
457
|
+
duty_min = conf.get('device_min', 0) / 24 * 1023
|
|
458
|
+
|
|
459
|
+
if pwm_value > duty_max:
|
|
460
|
+
value = conf.get('max', 24)
|
|
461
|
+
elif pwm_value < duty_min:
|
|
462
|
+
value = conf.get('min', 0)
|
|
463
|
+
else:
|
|
464
|
+
pwm_amplitude = duty_max - duty_min
|
|
465
|
+
relative_value = (pwm_value - duty_min) / pwm_amplitude
|
|
466
|
+
val_amplitude = conf.get('max', 24) - conf.get('min', 0)
|
|
467
|
+
value = conf.get('min', 0) + val_amplitude * relative_value
|
|
468
|
+
|
|
469
|
+
return round(value, 3)
|
|
425
470
|
|
|
426
471
|
|
|
427
472
|
class RGBLight(FleeDeviceMixin, BasicOutputMixin, BaseRGBWLight):
|
|
@@ -460,6 +505,25 @@ class DualMotorValve(FleeDeviceMixin, BasicOutputMixin, BaseDimmer):
|
|
|
460
505
|
|
|
461
506
|
self.component.save()
|
|
462
507
|
|
|
508
|
+
def _prepare_for_send(self, value):
|
|
509
|
+
conf = self.component.config
|
|
510
|
+
if value >= conf.get('max', 100):
|
|
511
|
+
value = conf.get('max', 100)
|
|
512
|
+
elif value < conf.get('min', 0):
|
|
513
|
+
value = conf.get('min', 0)
|
|
514
|
+
val_amplitude = conf.get('max', 100) - conf.get('min', 0)
|
|
515
|
+
return ((value - conf.get('min', 0)) / val_amplitude) * 100
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def _prepare_for_set(self, value):
|
|
519
|
+
conf = self.component.config
|
|
520
|
+
if value > conf.get('max', 100):
|
|
521
|
+
value = conf.get('max', 100)
|
|
522
|
+
elif value < conf.get('min', 0.0):
|
|
523
|
+
value = conf.get('min', 0)
|
|
524
|
+
val_amplitude = conf.get('max', 100) - conf.get('min', 0)
|
|
525
|
+
return conf.get('min', 0) + (value / 100) * val_amplitude
|
|
526
|
+
|
|
463
527
|
|
|
464
528
|
class Blinds(FleeDeviceMixin, BasicOutputMixin, GenericBlinds):
|
|
465
529
|
gateway_class = FleetGatewayHandler
|
simo/fleet/forms.py
CHANGED
|
@@ -741,14 +741,21 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
741
741
|
help_text="Maximum component value"
|
|
742
742
|
)
|
|
743
743
|
value_units = forms.CharField(required=False)
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
744
|
+
|
|
745
|
+
device_min = forms.IntegerField(
|
|
746
|
+
label="Device minimum (%).",
|
|
747
|
+
help_text="Device will turn off once it reaches this internal value. "
|
|
748
|
+
"Usually it is a good idea to "
|
|
749
|
+
"set this somewhere in between of 5 - 15 %. ",
|
|
750
|
+
initial=10, min_value=0, max_value=100,
|
|
747
751
|
)
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
help_text="
|
|
752
|
+
device_max = forms.IntegerField(
|
|
753
|
+
label="Device maximum (%).",
|
|
754
|
+
help_text="Can be used to prevent reaching maximum values. "
|
|
755
|
+
"Default is 100%",
|
|
756
|
+
initial=100, min_value=0, max_value=100,
|
|
751
757
|
)
|
|
758
|
+
|
|
752
759
|
turn_on_time = forms.IntegerField(
|
|
753
760
|
min_value=0, max_value=60000, initial=1000,
|
|
754
761
|
help_text="Turn on speed in ms. 1500 is a great quick default. "
|
|
@@ -763,9 +770,6 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
763
770
|
initial='easeOutSine', choices=EASING_CHOICES,
|
|
764
771
|
help_text="easeOutSine - offers most naturally looking effect."
|
|
765
772
|
)
|
|
766
|
-
inverse = forms.BooleanField(
|
|
767
|
-
label=_("Inverse dimmer signal"), required=False, initial=True
|
|
768
|
-
)
|
|
769
773
|
on_value = forms.FloatField(
|
|
770
774
|
required=True, initial=100,
|
|
771
775
|
help_text="ON value when used with toggle switch"
|
|
@@ -854,6 +858,71 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
|
|
|
854
858
|
return obj
|
|
855
859
|
|
|
856
860
|
|
|
861
|
+
class DCDriverConfigForm(ColonelComponentForm):
|
|
862
|
+
output_pin = Select2ModelChoiceField(
|
|
863
|
+
label="Port",
|
|
864
|
+
queryset=ColonelPin.objects.filter(output=True),
|
|
865
|
+
url='autocomplete-colonel-pins',
|
|
866
|
+
forward=[
|
|
867
|
+
forward.Self(),
|
|
868
|
+
forward.Field('colonel'),
|
|
869
|
+
forward.Const({'output': True}, 'filters')
|
|
870
|
+
]
|
|
871
|
+
)
|
|
872
|
+
min = forms.FloatField(
|
|
873
|
+
required=True, initial=0,
|
|
874
|
+
help_text="Minimum component value displayed to the user."
|
|
875
|
+
)
|
|
876
|
+
max = forms.FloatField(
|
|
877
|
+
required=True, initial=24,
|
|
878
|
+
help_text="Maximum component value displayed to the user."
|
|
879
|
+
)
|
|
880
|
+
value_units = forms.CharField(required=False)
|
|
881
|
+
|
|
882
|
+
device_min = forms.FloatField(
|
|
883
|
+
label="Device minimum Voltage.",
|
|
884
|
+
help_text="This will be the lowest possible voltage value of a device.\n"
|
|
885
|
+
"Don't forget to adjust your component min value accordingly "
|
|
886
|
+
"if you change this.",
|
|
887
|
+
initial=0, min_value=0, max_value=24,
|
|
888
|
+
)
|
|
889
|
+
device_max = forms.IntegerField(
|
|
890
|
+
label="Device maximum Voltage.",
|
|
891
|
+
help_text="Can be set lower than it's natural maximum of 24V. \n"
|
|
892
|
+
"Don't forget to adjust your component max value accordingly "
|
|
893
|
+
"if you change this.",
|
|
894
|
+
initial=24, min_value=0, max_value=24,
|
|
895
|
+
)
|
|
896
|
+
|
|
897
|
+
def clean(self):
|
|
898
|
+
super().clean()
|
|
899
|
+
if 'output_pin' in self.cleaned_data:
|
|
900
|
+
self._clean_pin('output_pin')
|
|
901
|
+
return self.cleaned_data
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
def save(self, commit=True):
|
|
905
|
+
if 'output_pin' in self.cleaned_data:
|
|
906
|
+
self.instance.config['output_pin_no'] = self.cleaned_data['output_pin'].no
|
|
907
|
+
|
|
908
|
+
update_colonel = False
|
|
909
|
+
if not self.instance.pk:
|
|
910
|
+
update_colonel = True
|
|
911
|
+
elif 'output_pin' in self.changed_data:
|
|
912
|
+
update_colonel = True
|
|
913
|
+
|
|
914
|
+
obj = super().save(commit=commit)
|
|
915
|
+
|
|
916
|
+
if not update_colonel:
|
|
917
|
+
GatewayObjectCommand(
|
|
918
|
+
obj.gateway, self.cleaned_data['colonel'], id=obj.id,
|
|
919
|
+
command='call', method='update_config', args=[
|
|
920
|
+
obj.controller._get_colonel_config()
|
|
921
|
+
]
|
|
922
|
+
).publish()
|
|
923
|
+
return obj
|
|
924
|
+
|
|
925
|
+
|
|
857
926
|
class ColonelRGBLightConfigForm(ColonelComponentForm):
|
|
858
927
|
output_pin = Select2ModelChoiceField(
|
|
859
928
|
label="Port",
|
|
@@ -1010,6 +1079,12 @@ class DualMotorValveForm(ColonelComponentForm):
|
|
|
1010
1079
|
required=True, min_value=0.01, max_value=1000000000,
|
|
1011
1080
|
initial=10, help_text="Time in seconds to close."
|
|
1012
1081
|
)
|
|
1082
|
+
min = forms.FloatField(
|
|
1083
|
+
label="Minimum displayed value", required=True, initial=0
|
|
1084
|
+
)
|
|
1085
|
+
max = forms.FloatField(
|
|
1086
|
+
label="Maximum displayed value", required=True, initial=100
|
|
1087
|
+
)
|
|
1013
1088
|
|
|
1014
1089
|
|
|
1015
1090
|
def clean(self):
|