simo 2.0.6__py3-none-any.whl → 2.0.8__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-38.pyc +0 -0
- simo/core/__pycache__/api.cpython-38.pyc +0 -0
- simo/core/__pycache__/base_types.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__/models.cpython-38.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/core/admin.py +4 -2
- simo/core/api.py +5 -3
- simo/core/controllers.py +1 -0
- simo/core/forms.py +1 -1
- simo/core/migrations/0030_alter_instance_timezone.py +18 -0
- simo/core/migrations/__pycache__/0030_alter_instance_timezone.cpython-38.pyc +0 -0
- simo/core/models.py +29 -19
- simo/core/serializers.py +1 -1
- simo/core/utils/__pycache__/serialization.cpython-38.pyc +0 -0
- simo/core/utils/serialization.py +4 -2
- simo/fleet/__pycache__/admin.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/managers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
- simo/fleet/admin.py +6 -6
- simo/fleet/auto_urls.py +4 -4
- simo/fleet/base_types.py +5 -0
- simo/fleet/controllers.py +123 -13
- simo/fleet/forms.py +78 -49
- simo/fleet/gateways.py +21 -8
- simo/fleet/managers.py +4 -2
- simo/fleet/migrations/0032_auto_20240415_0736.py +33 -0
- simo/fleet/migrations/0033_auto_20240415_0736.py +28 -0
- simo/fleet/migrations/__pycache__/0032_auto_20240415_0736.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0033_auto_20240415_0736.cpython-38.pyc +0 -0
- simo/fleet/models.py +83 -14
- simo/fleet/socket_consumers.py +22 -19
- simo/fleet/utils.py +6 -1
- simo/fleet/views.py +11 -10
- simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/generic/controllers.py +1 -1
- simo/users/__pycache__/api.cpython-38.pyc +0 -0
- simo/users/api.py +4 -2
- {simo-2.0.6.dist-info → simo-2.0.8.dist-info}/METADATA +1 -1
- {simo-2.0.6.dist-info → simo-2.0.8.dist-info}/RECORD +52 -44
- {simo-2.0.6.dist-info → simo-2.0.8.dist-info}/LICENSE.md +0 -0
- {simo-2.0.6.dist-info → simo-2.0.8.dist-info}/WHEEL +0 -0
- {simo-2.0.6.dist-info → simo-2.0.8.dist-info}/top_level.txt +0 -0
simo/fleet/forms.py
CHANGED
|
@@ -6,13 +6,16 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
6
6
|
from dal import autocomplete
|
|
7
7
|
from dal import forward
|
|
8
8
|
from simo.core.models import Component
|
|
9
|
-
from simo.core.forms import
|
|
9
|
+
from simo.core.forms import (
|
|
10
|
+
BaseComponentForm, ValueLimitForm, NumericSensorForm, SwitchForm
|
|
11
|
+
)
|
|
10
12
|
from simo.core.utils.formsets import FormsetField
|
|
11
13
|
from simo.core.widgets import LogOutputWidget
|
|
12
14
|
from simo.core.utils.easing import EASING_CHOICES
|
|
13
15
|
from simo.core.utils.validators import validate_slaves
|
|
14
16
|
from simo.core.utils.admin import AdminFormActionForm
|
|
15
|
-
from .models import Colonel, ColonelPin,
|
|
17
|
+
from .models import Colonel, ColonelPin, Interface
|
|
18
|
+
from .utils import INTERFACES_PINS_MAP
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
class ColonelPinChoiceField(forms.ModelChoiceField):
|
|
@@ -23,6 +26,11 @@ class ColonelPinChoiceField(forms.ModelChoiceField):
|
|
|
23
26
|
filter_by = 'colonel'
|
|
24
27
|
|
|
25
28
|
|
|
29
|
+
class ColonelInterfacesChoiceField(forms.ModelChoiceField):
|
|
30
|
+
filter_by = 'colonel'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
26
34
|
class ColonelAdminForm(forms.ModelForm):
|
|
27
35
|
log = forms.CharField(
|
|
28
36
|
widget=forms.HiddenInput, required=False
|
|
@@ -52,51 +60,25 @@ class MoveColonelForm(AdminFormActionForm):
|
|
|
52
60
|
)
|
|
53
61
|
|
|
54
62
|
|
|
55
|
-
class
|
|
56
|
-
scl_pin = ColonelPinChoiceField(
|
|
57
|
-
queryset=ColonelPin.objects.filter(output=True, native=True),
|
|
58
|
-
widget=autocomplete.ListSelect2(
|
|
59
|
-
url='autocomplete-colonel-pins',
|
|
60
|
-
forward=[
|
|
61
|
-
forward.Self(),
|
|
62
|
-
forward.Field('colonel'),
|
|
63
|
-
forward.Const({'output': True, 'native': True}, 'filters')
|
|
64
|
-
]
|
|
65
|
-
)
|
|
66
|
-
)
|
|
67
|
-
sda_pin = ColonelPinChoiceField(
|
|
68
|
-
queryset=ColonelPin.objects.filter(output=True, native=True),
|
|
69
|
-
widget=autocomplete.ListSelect2(
|
|
70
|
-
url='autocomplete-colonel-pins',
|
|
71
|
-
forward=[
|
|
72
|
-
forward.Self(),
|
|
73
|
-
forward.Field('colonel'),
|
|
74
|
-
forward.Const({'output': True, 'native': True}, 'filters')
|
|
75
|
-
]
|
|
76
|
-
)
|
|
77
|
-
)
|
|
63
|
+
class InterfaceAdminForm(forms.ModelForm):
|
|
78
64
|
|
|
79
65
|
class Meta:
|
|
80
|
-
model =
|
|
66
|
+
model = Interface
|
|
81
67
|
fields = '__all__'
|
|
82
68
|
|
|
83
|
-
def
|
|
84
|
-
if self.
|
|
85
|
-
|
|
86
|
-
raise forms.ValidationError(
|
|
87
|
-
f"This pin is already occupied by "
|
|
88
|
-
f"{self.cleaned_data['scl_pin'].occupied_by}!"
|
|
89
|
-
)
|
|
90
|
-
return self.cleaned_data['scl_pin']
|
|
69
|
+
def clean(self):
|
|
70
|
+
if self.instance.pk:
|
|
71
|
+
return self.cleaned_data
|
|
91
72
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
raise forms.ValidationError(
|
|
96
|
-
f"This pin is already occupied by "
|
|
97
|
-
f"{self.cleaned_data['sda_pin'].occupied_by}!"
|
|
73
|
+
for pin_no in INTERFACES_PINS_MAP[self.cleaned_data['no']]:
|
|
74
|
+
cpin = ColonelPin.objects.get(
|
|
75
|
+
colonel=self.instance.colonel, no=pin_no
|
|
98
76
|
)
|
|
99
|
-
|
|
77
|
+
if cpin.occupied_by:
|
|
78
|
+
raise forms.ValidationError(
|
|
79
|
+
f"Interface can not be created, because "
|
|
80
|
+
f"GPIO{cpin} is already occupied by {cpin.occupied_by}."
|
|
81
|
+
)
|
|
100
82
|
|
|
101
83
|
|
|
102
84
|
class ColonelComponentForm(BaseComponentForm):
|
|
@@ -416,13 +398,16 @@ class ColonelDHTSensorConfigForm(ColonelComponentForm):
|
|
|
416
398
|
|
|
417
399
|
|
|
418
400
|
class BME680SensorConfigForm(ColonelComponentForm):
|
|
419
|
-
|
|
420
|
-
|
|
401
|
+
interface = ColonelInterfacesChoiceField(
|
|
402
|
+
queryset=Interface.objects.filter(type='i2c'),
|
|
421
403
|
widget=autocomplete.ListSelect2(
|
|
422
|
-
url='autocomplete-
|
|
404
|
+
url='autocomplete-interfaces',
|
|
423
405
|
forward=[
|
|
424
406
|
forward.Self(),
|
|
425
407
|
forward.Field('colonel'),
|
|
408
|
+
forward.Const(
|
|
409
|
+
{'type': 'i2c'}, 'filters'
|
|
410
|
+
)
|
|
426
411
|
]
|
|
427
412
|
)
|
|
428
413
|
)
|
|
@@ -437,15 +422,22 @@ class BME680SensorConfigForm(ColonelComponentForm):
|
|
|
437
422
|
|
|
438
423
|
)
|
|
439
424
|
|
|
425
|
+
def save(self, commit=True):
|
|
426
|
+
self.instance.config['i2c_interface'] = self.cleaned_data['interface'].no
|
|
427
|
+
return super().save(commit=commit)
|
|
428
|
+
|
|
440
429
|
|
|
441
430
|
class MPC9808SensorConfigForm(ColonelComponentForm):
|
|
442
|
-
|
|
443
|
-
|
|
431
|
+
interface = ColonelInterfacesChoiceField(
|
|
432
|
+
queryset=Interface.objects.filter(type='i2c'),
|
|
444
433
|
widget=autocomplete.ListSelect2(
|
|
445
|
-
url='autocomplete-
|
|
434
|
+
url='autocomplete-interfaces',
|
|
446
435
|
forward=[
|
|
447
436
|
forward.Self(),
|
|
448
437
|
forward.Field('colonel'),
|
|
438
|
+
forward.Const(
|
|
439
|
+
{'type': 'i2c'}, 'filters'
|
|
440
|
+
)
|
|
449
441
|
]
|
|
450
442
|
)
|
|
451
443
|
)
|
|
@@ -460,6 +452,10 @@ class MPC9808SensorConfigForm(ColonelComponentForm):
|
|
|
460
452
|
|
|
461
453
|
)
|
|
462
454
|
|
|
455
|
+
def save(self, commit=True):
|
|
456
|
+
self.instance.config['i2c_interface'] = self.cleaned_data['interface'].no
|
|
457
|
+
return super().save(commit=commit)
|
|
458
|
+
|
|
463
459
|
|
|
464
460
|
class ColonelTouchSensorConfigForm(ColonelComponentForm):
|
|
465
461
|
pin = ColonelPinChoiceField(
|
|
@@ -987,7 +983,6 @@ class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
|
|
|
987
983
|
|
|
988
984
|
|
|
989
985
|
class TTLockConfigForm(ColonelComponentForm):
|
|
990
|
-
pass
|
|
991
986
|
|
|
992
987
|
def clean(self):
|
|
993
988
|
if not self.instance or not self.instance.pk:
|
|
@@ -1002,7 +997,6 @@ class TTLockConfigForm(ColonelComponentForm):
|
|
|
1002
997
|
)
|
|
1003
998
|
return self.cleaned_data
|
|
1004
999
|
|
|
1005
|
-
|
|
1006
1000
|
def save(self, commit=True):
|
|
1007
1001
|
obj = super(ColonelComponentForm, self).save(commit)
|
|
1008
1002
|
if commit:
|
|
@@ -1010,3 +1004,38 @@ class TTLockConfigForm(ColonelComponentForm):
|
|
|
1010
1004
|
self.cleaned_data['colonel'].save()
|
|
1011
1005
|
return obj
|
|
1012
1006
|
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
class DALIDeviceConfigForm(ColonelComponentForm):
|
|
1010
|
+
interface = ColonelInterfacesChoiceField(
|
|
1011
|
+
queryset=Interface.objects.filter(type='dali'),
|
|
1012
|
+
widget=autocomplete.ListSelect2(
|
|
1013
|
+
url='autocomplete-interfaces',
|
|
1014
|
+
forward=[
|
|
1015
|
+
forward.Self(),
|
|
1016
|
+
forward.Field('colonel'),
|
|
1017
|
+
forward.Const(
|
|
1018
|
+
{'type': 'dali'}, 'filters'
|
|
1019
|
+
)
|
|
1020
|
+
]
|
|
1021
|
+
)
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
def save(self, commit=True):
|
|
1025
|
+
self.instance.config['dali_interface'] = self.cleaned_data['interface'].no
|
|
1026
|
+
return super().save(commit=commit)
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
class DaliSwitchForm(DALIDeviceConfigForm, SwitchForm):
|
|
1030
|
+
|
|
1031
|
+
auto_off = forms.FloatField(
|
|
1032
|
+
required=False, min_value=0.01, max_value=1000000000,
|
|
1033
|
+
help_text="If provided, switch will be turned off after "
|
|
1034
|
+
"given amount of seconds after last turn on event."
|
|
1035
|
+
)
|
|
1036
|
+
inverse = forms.BooleanField(
|
|
1037
|
+
label=_("Inverse switch value"), required=False
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
|
simo/fleet/gateways.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import datetime
|
|
2
|
+
import time
|
|
2
3
|
from django.utils import timezone
|
|
3
4
|
from simo.core.gateways import BaseObjectCommandsGatewayHandler
|
|
4
5
|
from simo.core.forms import BaseGatewayForm
|
|
5
6
|
from simo.core.models import Gateway
|
|
6
7
|
from simo.core.events import GatewayObjectCommand
|
|
7
|
-
|
|
8
|
+
from simo.core.utils.serialization import deserialize_form_data
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class FleetGatewayHandler(BaseObjectCommandsGatewayHandler):
|
|
@@ -14,7 +15,7 @@ class FleetGatewayHandler(BaseObjectCommandsGatewayHandler):
|
|
|
14
15
|
periodic_tasks = (
|
|
15
16
|
('look_for_updates', 600),
|
|
16
17
|
('watch_colonels_connection', 30),
|
|
17
|
-
('push_discoveries',
|
|
18
|
+
('push_discoveries', 6),
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
def _on_mqtt_message(self, client, userdata, msg):
|
|
@@ -37,13 +38,25 @@ class FleetGatewayHandler(BaseObjectCommandsGatewayHandler):
|
|
|
37
38
|
def push_discoveries(self):
|
|
38
39
|
from .models import Colonel
|
|
39
40
|
for gw in Gateway.objects.filter(
|
|
40
|
-
type=self.uid,
|
|
41
|
-
discovery__has_key='start',
|
|
41
|
+
type=self.uid, discovery__has_key='start',
|
|
42
42
|
).exclude(discovery__has_key='finished'):
|
|
43
|
+
if time.time() - gw.discovery.get('last_check') > 10:
|
|
44
|
+
gw.finish_discovery()
|
|
45
|
+
continue
|
|
46
|
+
|
|
43
47
|
colonel = Colonel.objects.get(
|
|
44
48
|
id=gw.discovery['init_data']['colonel']['val'][0]['pk']
|
|
45
49
|
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
if gw.discovery['controller_uid'] == 'simo.fleet.controllers.TTLock':
|
|
51
|
+
GatewayObjectCommand(
|
|
52
|
+
gw, colonel, command='discover',
|
|
53
|
+
type=gw.discovery['controller_uid']
|
|
54
|
+
).publish()
|
|
55
|
+
elif gw.discovery['controller_uid'] == 'simo.fleet.controllers.DALIDevice':
|
|
56
|
+
form_cleaned_data = deserialize_form_data(gw.discovery['init_data'])
|
|
57
|
+
GatewayObjectCommand(
|
|
58
|
+
gw, colonel,
|
|
59
|
+
command=f'discover',
|
|
60
|
+
type=gw.discovery['controller_uid'],
|
|
61
|
+
i=form_cleaned_data['interface'].no
|
|
62
|
+
).publish()
|
simo/fleet/managers.py
CHANGED
|
@@ -22,11 +22,13 @@ class ColonelPinsManager(models.Manager):
|
|
|
22
22
|
return qs
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class
|
|
25
|
+
class InterfacesManager(models.Manager):
|
|
26
26
|
|
|
27
27
|
def get_queryset(self):
|
|
28
28
|
qs = super().get_queryset()
|
|
29
29
|
instance = get_current_instance()
|
|
30
30
|
if instance:
|
|
31
31
|
qs = qs.filter(colonel__instance=instance)
|
|
32
|
-
return qs
|
|
32
|
+
return qs
|
|
33
|
+
|
|
34
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Generated by Django 3.2.9 on 2024-04-15 07:36
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('fleet', '0031_alter_colonel_type'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterField(
|
|
15
|
+
model_name='colonel',
|
|
16
|
+
name='logs_stream',
|
|
17
|
+
field=models.BooleanField(default=False, help_text='ATENTION! Causes serious overhead and significantly degrades the lifespan of a chip due to a lot of writes to the memory. It also causes Colonel websocket to run out of memory and reset if a lot of data is being transmitted. Leave this off, unleess you know what you are doing!'),
|
|
18
|
+
),
|
|
19
|
+
migrations.CreateModel(
|
|
20
|
+
name='Interface',
|
|
21
|
+
fields=[
|
|
22
|
+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
23
|
+
('no', models.PositiveIntegerField(choices=[(1, '1'), (2, '2')])),
|
|
24
|
+
('type', models.CharField(choices=[('i2c', 'I2C'), ('dali', 'DALI')], max_length=20)),
|
|
25
|
+
('pin_a', models.ForeignKey(editable=False, limit_choices_to={'native': True, 'output': True}, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interface_a', to='fleet.colonelpin')),
|
|
26
|
+
('pin_b', models.ForeignKey(editable=False, limit_choices_to={'native': True, 'output': True}, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interface_b', to='fleet.colonelpin')),
|
|
27
|
+
('colonel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='fleet.colonel')),
|
|
28
|
+
],
|
|
29
|
+
options={
|
|
30
|
+
'unique_together': {('colonel', 'no')},
|
|
31
|
+
},
|
|
32
|
+
),
|
|
33
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Generated by Django 3.2.9 on 2024-04-15 07:36
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
def create_objects(apps, schema_editor):
|
|
6
|
+
I2CInterface = apps.get_model("fleet", "I2CInterface")
|
|
7
|
+
Interface = apps.get_model("fleet", "Interface")
|
|
8
|
+
for i2c_i in I2CInterface.objects.filter(no__gt=0):
|
|
9
|
+
Interface.objects.create(
|
|
10
|
+
colonel=i2c_i.colonel, type='i2c',
|
|
11
|
+
pin_a=i2c_i.scl_pin, pin_b=i2c_i.sda_pin
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def delete_objects(apps, schema_editor):
|
|
16
|
+
Interface = apps.get_model("fleet", "Interface")
|
|
17
|
+
Interface.delete()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Migration(migrations.Migration):
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
('fleet', '0032_auto_20240415_0736'),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
operations = [
|
|
27
|
+
migrations.RunPython(create_objects, delete_objects),
|
|
28
|
+
]
|
|
Binary file
|
|
Binary file
|
simo/fleet/models.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
import time
|
|
3
|
+
from django.core.exceptions import ValidationError
|
|
3
4
|
from django.db import transaction
|
|
4
5
|
from django.db import models
|
|
5
6
|
from django.db.models.signals import post_save, pre_delete, post_delete
|
|
@@ -11,8 +12,8 @@ from simo.core.models import Instance, Gateway, Component
|
|
|
11
12
|
from simo.core.utils.helpers import get_random_string
|
|
12
13
|
from simo.core.events import GatewayObjectCommand
|
|
13
14
|
from .gateways import FleetGatewayHandler
|
|
14
|
-
from .managers import ColonelsManager, ColonelPinsManager,
|
|
15
|
-
from .utils import GPIO_PINS
|
|
15
|
+
from .managers import ColonelsManager, ColonelPinsManager, InterfacesManager
|
|
16
|
+
from .utils import GPIO_PINS, INTERFACES_PINS_MAP
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
|
|
@@ -181,10 +182,12 @@ class Colonel(DirtyFieldsMixin, models.Model):
|
|
|
181
182
|
pin.save()
|
|
182
183
|
|
|
183
184
|
for interface in self.i2c_interfaces.all():
|
|
184
|
-
interface.sda_pin
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
interface.scl_pin
|
|
185
|
+
if interface.sda_pin:
|
|
186
|
+
interface.sda_pin.occupied_by = interface
|
|
187
|
+
interface.sda_pin.save()
|
|
188
|
+
if interface.scl_pin:
|
|
189
|
+
interface.scl_pin.occupied_by = interface
|
|
190
|
+
interface.scl_pin.save()
|
|
188
191
|
|
|
189
192
|
|
|
190
193
|
def move_to(self, other_colonel):
|
|
@@ -256,12 +259,6 @@ def after_colonel_save(sender, instance, created, *args, **kwargs):
|
|
|
256
259
|
capacitive=data.get('capacitive'), adc=data.get('adc'),
|
|
257
260
|
native=data.get('native'), note=data.get('note')
|
|
258
261
|
)
|
|
259
|
-
if instance.type in ('ample-wall', 'game-changer'):
|
|
260
|
-
I2CInterface.objects.create(
|
|
261
|
-
colonel=instance, name='Main', no=0,
|
|
262
|
-
scl_pin=ColonelPin.objects.get(colonel=instance, no=4),
|
|
263
|
-
sda_pin=ColonelPin.objects.get(colonel=instance, no=15),
|
|
264
|
-
)
|
|
265
262
|
|
|
266
263
|
|
|
267
264
|
@receiver(pre_delete, sender=Component)
|
|
@@ -310,7 +307,7 @@ class I2CInterface(models.Model):
|
|
|
310
307
|
default=100000, help_text="100000 - is a good middle point!"
|
|
311
308
|
)
|
|
312
309
|
|
|
313
|
-
objects =
|
|
310
|
+
objects = InterfacesManager()
|
|
314
311
|
|
|
315
312
|
class Meta:
|
|
316
313
|
unique_together = 'colonel', 'no'
|
|
@@ -319,7 +316,7 @@ class I2CInterface(models.Model):
|
|
|
319
316
|
return self.name
|
|
320
317
|
|
|
321
318
|
|
|
322
|
-
@receiver(
|
|
319
|
+
@receiver(post_delete, sender=I2CInterface)
|
|
323
320
|
def post_i2c_interface_delete(sender, instance, *args, **kwargs):
|
|
324
321
|
with transaction.atomic():
|
|
325
322
|
ct = ContentType.objects.get_for_model(instance)
|
|
@@ -334,3 +331,75 @@ def post_i2c_interface_delete(sender, instance, *args, **kwargs):
|
|
|
334
331
|
instance.scl_pin.save()
|
|
335
332
|
instance.sda_pin.occupied_by = instance
|
|
336
333
|
instance.sda_pin.save()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class Interface(models.Model):
|
|
337
|
+
colonel = models.ForeignKey(
|
|
338
|
+
Colonel, on_delete=models.CASCADE, related_name='interfaces'
|
|
339
|
+
)
|
|
340
|
+
no = models.PositiveIntegerField(choices=((1, "1"), (2, "2")))
|
|
341
|
+
type = models.CharField(
|
|
342
|
+
max_length=20, choices=(('i2c', "I2C"), ('dali', "DALI"))
|
|
343
|
+
)
|
|
344
|
+
pin_a = models.ForeignKey(
|
|
345
|
+
ColonelPin, on_delete=models.CASCADE, limit_choices_to={
|
|
346
|
+
'native': True, 'output': True,
|
|
347
|
+
}, verbose_name="Pin A (scl)", null=True, related_name='interface_a',
|
|
348
|
+
editable=False
|
|
349
|
+
)
|
|
350
|
+
pin_b = models.ForeignKey(
|
|
351
|
+
ColonelPin, on_delete=models.CASCADE, limit_choices_to={
|
|
352
|
+
'native': True, 'output': True,
|
|
353
|
+
}, verbose_name="Pin B (sda)", null=True, related_name='interface_b',
|
|
354
|
+
editable=False
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
objects = InterfacesManager()
|
|
358
|
+
|
|
359
|
+
class Meta:
|
|
360
|
+
unique_together = 'colonel', 'no'
|
|
361
|
+
|
|
362
|
+
def __str__(self):
|
|
363
|
+
return f"{self.no} - {self.get_type_display()}"
|
|
364
|
+
|
|
365
|
+
def save(self, *args, **kwargs):
|
|
366
|
+
if not self.pk:
|
|
367
|
+
for pin_no in INTERFACES_PINS_MAP[self.no]:
|
|
368
|
+
cpin = ColonelPin.objects.get(colonel=self.colonel, no=pin_no)
|
|
369
|
+
if cpin.occupied_by:
|
|
370
|
+
raise ValidationError(
|
|
371
|
+
f"Interface can not be created, because "
|
|
372
|
+
f"GPIO{cpin} is already occupied by {cpin.occupied_by}."
|
|
373
|
+
)
|
|
374
|
+
return super().save(*args, **kwargs)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
@receiver(post_save, sender=Interface)
|
|
378
|
+
def post_interface_save(sender, instance, created, *args, **kwargs):
|
|
379
|
+
if created:
|
|
380
|
+
instance.pin_a = ColonelPin.objects.get(
|
|
381
|
+
colonel=instance.colonel, no=INTERFACES_PINS_MAP[instance.no][0]
|
|
382
|
+
)
|
|
383
|
+
instance.pin_a.occupied_by = instance
|
|
384
|
+
instance.pin_a.save()
|
|
385
|
+
instance.pin_b = ColonelPin.objects.get(
|
|
386
|
+
colonel=instance.colonel, no=INTERFACES_PINS_MAP[instance.no][1]
|
|
387
|
+
)
|
|
388
|
+
instance.pin_b.occupied_by = instance
|
|
389
|
+
instance.pin_b.save()
|
|
390
|
+
instance.save()
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@receiver(post_delete, sender=Interface)
|
|
394
|
+
def post_interface_delete(sender, instance, *args, **kwargs):
|
|
395
|
+
with transaction.atomic():
|
|
396
|
+
ct = ContentType.objects.get_for_model(instance)
|
|
397
|
+
for pin in ColonelPin.objects.filter(
|
|
398
|
+
occupied_by_content_type=ct,
|
|
399
|
+
occupied_by_id=instance.id
|
|
400
|
+
):
|
|
401
|
+
pin.occupied_by_content_type = None
|
|
402
|
+
pin.occupied_by_content_id = None
|
|
403
|
+
pin.save()
|
|
404
|
+
|
|
405
|
+
|
simo/fleet/socket_consumers.py
CHANGED
|
@@ -218,15 +218,14 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
config_data['settings'].update(instance_options)
|
|
221
|
-
|
|
222
|
-
self.colonel.
|
|
223
|
-
'
|
|
221
|
+
interfaces = await sync_to_async(list, thread_sensitive=True)(
|
|
222
|
+
self.colonel.interfaces.all().select_related(
|
|
223
|
+
'pin_a', 'pin_b'
|
|
224
224
|
)
|
|
225
225
|
)
|
|
226
|
-
for
|
|
227
|
-
config_data['interfaces']['
|
|
228
|
-
'
|
|
229
|
-
'freq': i2c_interface.freq
|
|
226
|
+
for interface in interfaces:
|
|
227
|
+
config_data['interfaces'][f'{interface.type}-{interface.no}'] = {
|
|
228
|
+
'pin_a': interface.pin_a.no, 'pin_b': interface.pin_b.no,
|
|
230
229
|
}
|
|
231
230
|
components = await sync_to_async(
|
|
232
231
|
list, thread_sensitive=True
|
|
@@ -241,6 +240,8 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
241
240
|
),
|
|
242
241
|
'config': comp.controller._get_colonel_config()
|
|
243
242
|
}
|
|
243
|
+
if hasattr(comp.controller, 'family'):
|
|
244
|
+
comp_config['family'] = comp.controller.family
|
|
244
245
|
slaves = [
|
|
245
246
|
s.id for s in comp.slaves.all()
|
|
246
247
|
if s.config.get('colonel') == self.colonel.id
|
|
@@ -314,11 +315,10 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
314
315
|
'command': 'set_config', 'data': config
|
|
315
316
|
}, compress=True)
|
|
316
317
|
asyncio.run(send_config())
|
|
317
|
-
elif payload.get('command') == 'discover
|
|
318
|
-
print("SEND discover
|
|
319
|
-
asyncio.run(self.send_data(
|
|
320
|
-
|
|
321
|
-
}))
|
|
318
|
+
elif payload.get('command') == 'discover':
|
|
319
|
+
print(f"SEND discover command for {payload['type']}")
|
|
320
|
+
asyncio.run(self.send_data(payload))
|
|
321
|
+
|
|
322
322
|
elif payload.get('command') == 'finalize':
|
|
323
323
|
asyncio.run(self.send_data({
|
|
324
324
|
'command': 'finalize',
|
|
@@ -405,7 +405,7 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
405
405
|
save_codes, thread_sensitive=True
|
|
406
406
|
)(data['codes'])
|
|
407
407
|
if 'fingerprints' in data and component.controller_uid == TTLock.uid:
|
|
408
|
-
def
|
|
408
|
+
def save_fingerprints(codes):
|
|
409
409
|
component.meta['fingerprints'] = codes
|
|
410
410
|
for code in codes:
|
|
411
411
|
Fingerprint.objects.get_or_create(
|
|
@@ -414,24 +414,27 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
414
414
|
)
|
|
415
415
|
component.save()
|
|
416
416
|
await sync_to_async(
|
|
417
|
-
|
|
417
|
+
save_fingerprints, thread_sensitive=True
|
|
418
418
|
)(data['fingerprints'])
|
|
419
419
|
|
|
420
420
|
except Exception as e:
|
|
421
421
|
print(traceback.format_exc(), file=sys.stderr)
|
|
422
422
|
|
|
423
|
-
elif '
|
|
423
|
+
elif 'discovery-result' in data:
|
|
424
424
|
def process_discovery_result():
|
|
425
|
-
|
|
426
|
-
if
|
|
427
|
-
|
|
425
|
+
# check if component is already created
|
|
426
|
+
if data['discovery-result'] == 'success':
|
|
427
|
+
comp = Component.objects.filter(
|
|
428
428
|
meta__finalization_data__temp_id=data['result']['id']
|
|
429
429
|
).first()
|
|
430
|
+
if comp:
|
|
431
|
+
return comp
|
|
432
|
+
|
|
433
|
+
self.gateway.refresh_from_db()
|
|
430
434
|
try:
|
|
431
435
|
self.gateway.process_discovery(data)
|
|
432
436
|
except Exception as e:
|
|
433
437
|
print(traceback.format_exc(), file=sys.stderr)
|
|
434
|
-
self.gateway.finish_discovery()
|
|
435
438
|
|
|
436
439
|
finished_comp = await sync_to_async(
|
|
437
440
|
process_discovery_result, thread_sensitive=True
|
simo/fleet/utils.py
CHANGED
|
@@ -58,7 +58,7 @@ for no, data in BASE_ESP32_GPIO_PINS.items():
|
|
|
58
58
|
|
|
59
59
|
# ample-wall
|
|
60
60
|
for no, data in BASE_ESP32_GPIO_PINS.items():
|
|
61
|
-
if no in (
|
|
61
|
+
if no in (12, 13, 14, 23, 32, 33, 34, 36, 39):
|
|
62
62
|
GPIO_PINS['ample-wall'][no] = GPIO_PIN_DEFAULTS.copy()
|
|
63
63
|
GPIO_PINS['ample-wall'][no].update(data)
|
|
64
64
|
|
|
@@ -114,3 +114,8 @@ for no, data in BASE_ESP32_GPIO_PINS.items():
|
|
|
114
114
|
GPIO_PINS['4-relays'][no]['note'] = 'Relay4'
|
|
115
115
|
else:
|
|
116
116
|
GPIO_PINS['4-relays'][no].update(data)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
INTERFACES_PINS_MAP = {
|
|
120
|
+
1: [13, 23], 2: [32, 33]
|
|
121
|
+
}
|
simo/fleet/views.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
from django.contrib.contenttypes.models import ContentType
|
|
2
1
|
from django.http import HttpResponse, Http404
|
|
3
2
|
from django.db.models import Q
|
|
4
3
|
from dal import autocomplete
|
|
5
4
|
from simo.core.utils.helpers import search_queryset
|
|
6
|
-
from .models import Colonel, ColonelPin,
|
|
5
|
+
from .models import Colonel, ColonelPin, Interface
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
def colonels_ping(request):
|
|
@@ -43,20 +42,22 @@ class PinsSelectAutocomplete(autocomplete.Select2QuerySetView):
|
|
|
43
42
|
return qs
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
class
|
|
45
|
+
class InterfaceSelectAutocomplete(autocomplete.Select2QuerySetView):
|
|
47
46
|
|
|
48
|
-
def
|
|
47
|
+
def get_queryset(self):
|
|
49
48
|
if not self.request.user.is_staff:
|
|
50
|
-
return
|
|
49
|
+
return Interface.objects.none()
|
|
51
50
|
|
|
52
51
|
try:
|
|
53
52
|
colonel = Colonel.objects.get(
|
|
54
53
|
pk=self.forwarded.get("colonel")
|
|
55
54
|
)
|
|
56
55
|
except:
|
|
57
|
-
return
|
|
56
|
+
return Interface.objects.none()
|
|
57
|
+
|
|
58
|
+
qs = Interface.objects.filter(colonel=colonel)
|
|
59
|
+
|
|
60
|
+
if self.forwarded.get('filters'):
|
|
61
|
+
qs = qs.filter(**self.forwarded.get('filters'))
|
|
58
62
|
|
|
59
|
-
return
|
|
60
|
-
(i.no, i.get_no_display()) for i in
|
|
61
|
-
I2CInterface.objects.filter(colonel=colonel)
|
|
62
|
-
]
|
|
63
|
+
return qs
|
|
Binary file
|
simo/generic/controllers.py
CHANGED
|
Binary file
|
simo/users/api.py
CHANGED
|
@@ -227,11 +227,13 @@ class FingerprintViewSet(
|
|
|
227
227
|
basename = 'fingerprints'
|
|
228
228
|
serializer_class = FingerprintSerializer
|
|
229
229
|
|
|
230
|
-
|
|
231
230
|
def get_queryset(self):
|
|
232
|
-
|
|
231
|
+
qs = Fingerprint.objects.filter(
|
|
233
232
|
Q(user=None) | Q(user__roles__instance=self.instance)
|
|
234
233
|
)
|
|
234
|
+
if 'values' in self.request.GET:
|
|
235
|
+
qs = qs.filter(value__in=self.request.GET['values'].split(','))
|
|
236
|
+
return qs
|
|
235
237
|
|
|
236
238
|
def check_can_manage_user(self, request):
|
|
237
239
|
user_role = request.user.get_role(self.instance)
|