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.

Files changed (52) hide show
  1. simo/core/__pycache__/admin.cpython-38.pyc +0 -0
  2. simo/core/__pycache__/api.cpython-38.pyc +0 -0
  3. simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
  4. simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
  5. simo/core/__pycache__/forms.cpython-38.pyc +0 -0
  6. simo/core/__pycache__/models.cpython-38.pyc +0 -0
  7. simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
  8. simo/core/admin.py +4 -2
  9. simo/core/api.py +5 -3
  10. simo/core/controllers.py +1 -0
  11. simo/core/forms.py +1 -1
  12. simo/core/migrations/0030_alter_instance_timezone.py +18 -0
  13. simo/core/migrations/__pycache__/0030_alter_instance_timezone.cpython-38.pyc +0 -0
  14. simo/core/models.py +29 -19
  15. simo/core/serializers.py +1 -1
  16. simo/core/utils/__pycache__/serialization.cpython-38.pyc +0 -0
  17. simo/core/utils/serialization.py +4 -2
  18. simo/fleet/__pycache__/admin.cpython-38.pyc +0 -0
  19. simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
  20. simo/fleet/__pycache__/base_types.cpython-38.pyc +0 -0
  21. simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
  22. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  23. simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
  24. simo/fleet/__pycache__/managers.cpython-38.pyc +0 -0
  25. simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
  26. simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  27. simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
  28. simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
  29. simo/fleet/admin.py +6 -6
  30. simo/fleet/auto_urls.py +4 -4
  31. simo/fleet/base_types.py +5 -0
  32. simo/fleet/controllers.py +123 -13
  33. simo/fleet/forms.py +78 -49
  34. simo/fleet/gateways.py +21 -8
  35. simo/fleet/managers.py +4 -2
  36. simo/fleet/migrations/0032_auto_20240415_0736.py +33 -0
  37. simo/fleet/migrations/0033_auto_20240415_0736.py +28 -0
  38. simo/fleet/migrations/__pycache__/0032_auto_20240415_0736.cpython-38.pyc +0 -0
  39. simo/fleet/migrations/__pycache__/0033_auto_20240415_0736.cpython-38.pyc +0 -0
  40. simo/fleet/models.py +83 -14
  41. simo/fleet/socket_consumers.py +22 -19
  42. simo/fleet/utils.py +6 -1
  43. simo/fleet/views.py +11 -10
  44. simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
  45. simo/generic/controllers.py +1 -1
  46. simo/users/__pycache__/api.cpython-38.pyc +0 -0
  47. simo/users/api.py +4 -2
  48. {simo-2.0.6.dist-info → simo-2.0.8.dist-info}/METADATA +1 -1
  49. {simo-2.0.6.dist-info → simo-2.0.8.dist-info}/RECORD +52 -44
  50. {simo-2.0.6.dist-info → simo-2.0.8.dist-info}/LICENSE.md +0 -0
  51. {simo-2.0.6.dist-info → simo-2.0.8.dist-info}/WHEEL +0 -0
  52. {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 BaseComponentForm, ValueLimitForm, NumericSensorForm
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, I2CInterface, i2c_interface_no_choices
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 I2CInterfaceAdminForm(forms.ModelForm):
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 = I2CInterface
66
+ model = Interface
81
67
  fields = '__all__'
82
68
 
83
- def clean_scl_pin(self):
84
- if self.cleaned_data['scl_pin'].occupied_by \
85
- and self.cleaned_data['scl_pin'].occupied_by != self.instance:
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
- def clean_sda_pin(self):
93
- if self.cleaned_data['sda_pin'].occupied_by \
94
- and self.cleaned_data['sda_pin'].occupied_by != self.instance:
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
- return self.cleaned_data['sda_pin']
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
- i2c_interface = forms.TypedChoiceField(
420
- coerce=int, choices=i2c_interface_no_choices,
401
+ interface = ColonelInterfacesChoiceField(
402
+ queryset=Interface.objects.filter(type='i2c'),
421
403
  widget=autocomplete.ListSelect2(
422
- url='autocomplete-colonel-i2c_interfaces',
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
- i2c_interface = forms.TypedChoiceField(
443
- coerce=int, choices=i2c_interface_no_choices,
431
+ interface = ColonelInterfacesChoiceField(
432
+ queryset=Interface.objects.filter(type='i2c'),
444
433
  widget=autocomplete.ListSelect2(
445
- url='autocomplete-colonel-i2c_interfaces',
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', 3),
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
- print("Publish discover-ttlock command!")
47
- GatewayObjectCommand(
48
- gw, colonel, command='discover-ttlock',
49
- ).publish()
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 I2CInterfacesManager(models.Manager):
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
+ ]
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, I2CInterfacesManager
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.occupied_by = interface
185
- interface.sda_pin.save()
186
- interface.scl_pin.occupied_by = interface
187
- interface.scl_pin.save()
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 = I2CInterfacesManager()
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(post_save, sender=I2CInterface)
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
+
@@ -218,15 +218,14 @@ class FleetConsumer(AsyncWebsocketConsumer):
218
218
  }
219
219
  }
220
220
  config_data['settings'].update(instance_options)
221
- i2c_interfaces = await sync_to_async(list, thread_sensitive=True)(
222
- self.colonel.i2c_interfaces.all().select_related(
223
- 'scl_pin', 'sda_pin'
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 i2c_interface in i2c_interfaces:
227
- config_data['interfaces']['i2c-%d' % i2c_interface.no] = {
228
- 'scl': i2c_interface.scl_pin.no, 'sda': i2c_interface.sda_pin.no,
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-ttlock':
318
- print("SEND discover-ttlock command!")
319
- asyncio.run(self.send_data({
320
- 'command': 'discover-ttlock'
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 save_codes(codes):
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
- save_codes, thread_sensitive=True
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 'discover-ttlock' in data:
423
+ elif 'discovery-result' in data:
424
424
  def process_discovery_result():
425
- self.gateway.refresh_from_db()
426
- if self.gateway.discovery.get('finished'):
427
- return Component.objects.filter(
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 (4, 12, 13, 14, 15, 23, 32, 33, 34, 36, 39):
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, I2CInterface
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 I2CInterfaceSelectAutocomplete(autocomplete.Select2ListView):
45
+ class InterfaceSelectAutocomplete(autocomplete.Select2QuerySetView):
47
46
 
48
- def get_list(self):
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
@@ -542,7 +542,7 @@ class Gate(ControllerBase, TimerMixin):
542
542
 
543
543
 
544
544
  class Blinds(ControllerBase, TimerMixin):
545
- name = _("Blinds")
545
+ name = _("Blind")
546
546
  base_type = 'blinds'
547
547
  gateway_class = GenericGatewayHandler
548
548
  config_form = BlindsConfigForm
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
- return Fingerprint.objects.filter(
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simo
3
- Version: 2.0.6
3
+ Version: 2.0.8
4
4
  Summary: Smart Home on Steroids!
5
5
  Author-email: Simanas Venčkauskas <simanas@simo.io>
6
6
  Project-URL: Homepage, https://simo.io