simo 2.11.4__py3-none-any.whl → 3.0.4__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/__pycache__/settings.cpython-312.pyc +0 -0
- simo/asgi.py +25 -6
- simo/automation/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/automation/controllers.py +18 -2
- simo/automation/forms.py +15 -24
- simo/automation/gateways.py +32 -16
- simo/core/__pycache__/admin.cpython-312.pyc +0 -0
- simo/core/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/core/__pycache__/forms.cpython-312.pyc +0 -0
- simo/core/__pycache__/models.cpython-312.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-312.pyc +0 -0
- simo/core/__pycache__/signal_receivers.cpython-312.pyc +0 -0
- simo/core/__pycache__/tasks.cpython-312.pyc +0 -0
- simo/core/admin.py +5 -4
- simo/core/base_types.py +191 -18
- simo/core/controllers.py +259 -26
- simo/core/forms.py +10 -2
- simo/core/management/_hub_template/hub/nginx.conf +23 -50
- simo/core/management/_hub_template/hub/supervisor.conf +15 -0
- simo/core/mcp.py +154 -0
- simo/core/migrations/0051_instance_ai_memory.py +18 -0
- simo/core/migrations/__pycache__/0051_instance_ai_memory.cpython-312.pyc +0 -0
- simo/core/models.py +3 -0
- simo/core/serializers.py +120 -0
- simo/core/signal_receivers.py +1 -1
- simo/core/tasks.py +1 -3
- simo/core/utils/__pycache__/type_constants.cpython-312.pyc +0 -0
- simo/core/utils/type_constants.py +78 -17
- simo/fleet/__pycache__/admin.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/api.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/serializers.cpython-312.pyc +0 -0
- simo/fleet/admin.py +5 -1
- simo/fleet/api.py +2 -27
- simo/fleet/base_types.py +35 -4
- simo/fleet/controllers.py +162 -156
- simo/fleet/forms.py +58 -88
- simo/fleet/gateways.py +8 -15
- simo/fleet/migrations/0055_colonel_is_vo_active_colonel_last_wake_and_more.py +28 -0
- simo/fleet/migrations/0056_delete_customdalidevice.py +16 -0
- simo/fleet/migrations/__pycache__/0055_colonel_is_vo_active_colonel_last_wake_and_more.cpython-312.pyc +0 -0
- simo/fleet/migrations/__pycache__/0056_delete_customdalidevice.cpython-312.pyc +0 -0
- simo/fleet/models.py +13 -72
- simo/fleet/serializers.py +1 -48
- simo/fleet/socket_consumers.py +100 -39
- simo/fleet/tasks.py +2 -22
- simo/fleet/voice_assistant.py +903 -0
- simo/generic/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/generic/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/generic/base_types.py +70 -10
- simo/generic/controllers.py +104 -17
- simo/generic/gateways.py +10 -10
- simo/mcp_server/__init__.py +0 -0
- simo/mcp_server/__pycache__/__init__.cpython-312.pyc +0 -0
- simo/mcp_server/__pycache__/admin.cpython-312.pyc +0 -0
- simo/mcp_server/__pycache__/models.cpython-312.pyc +0 -0
- simo/mcp_server/admin.py +18 -0
- simo/mcp_server/app.py +4 -0
- simo/mcp_server/auth.py +34 -0
- simo/mcp_server/dummy.py +22 -0
- simo/mcp_server/migrations/0001_initial.py +30 -0
- simo/mcp_server/migrations/0002_alter_instanceaccesstoken_date_expired.py +18 -0
- simo/mcp_server/migrations/0003_instanceaccesstoken_issuer.py +18 -0
- simo/mcp_server/migrations/__init__.py +0 -0
- simo/mcp_server/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/0002_alter_instanceaccesstoken_date_expired.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/0003_instanceaccesstoken_issuer.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
- simo/mcp_server/models.py +27 -0
- simo/mcp_server/server.py +60 -0
- simo/mcp_server/tasks.py +19 -0
- simo/multimedia/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/multimedia/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/multimedia/base_types.py +29 -4
- simo/multimedia/controllers.py +66 -19
- simo/settings.py +1 -0
- simo/users/__pycache__/utils.cpython-312.pyc +0 -0
- simo/users/utils.py +10 -0
- {simo-2.11.4.dist-info → simo-3.0.4.dist-info}/METADATA +11 -4
- {simo-2.11.4.dist-info → simo-3.0.4.dist-info}/RECORD +90 -64
- simo/fleet/custom_dali_operations.py +0 -287
- {simo-2.11.4.dist-info → simo-3.0.4.dist-info}/WHEEL +0 -0
- {simo-2.11.4.dist-info → simo-3.0.4.dist-info}/entry_points.txt +0 -0
- {simo-2.11.4.dist-info → simo-3.0.4.dist-info}/licenses/LICENSE.md +0 -0
- {simo-2.11.4.dist-info → simo-3.0.4.dist-info}/top_level.txt +0 -0
simo/fleet/socket_consumers.py
CHANGED
|
@@ -6,7 +6,6 @@ import traceback
|
|
|
6
6
|
import sys
|
|
7
7
|
import zlib
|
|
8
8
|
import time
|
|
9
|
-
from django.db import transaction
|
|
10
9
|
from logging.handlers import RotatingFileHandler
|
|
11
10
|
from django.utils import timezone
|
|
12
11
|
from django.conf import settings
|
|
@@ -24,12 +23,11 @@ from simo.users.models import Fingerprint
|
|
|
24
23
|
from .gateways import FleetGatewayHandler
|
|
25
24
|
from .models import Colonel
|
|
26
25
|
from .controllers import TTLock
|
|
26
|
+
from .voice_assistant import VoiceAssistantSession, VoiceAssistantArbitrator
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@capture_socket_errors
|
|
30
30
|
class FleetConsumer(AsyncWebsocketConsumer):
|
|
31
|
-
|
|
32
|
-
|
|
33
31
|
def __init__(self, *args, **kwargs):
|
|
34
32
|
super().__init__(*args, **kwargs)
|
|
35
33
|
self.colonel = None
|
|
@@ -37,18 +35,50 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
37
35
|
self.connected = False
|
|
38
36
|
self.mqtt_client = None
|
|
39
37
|
self.last_seen = 0
|
|
38
|
+
self._va = None
|
|
39
|
+
self._arb = None
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
async def disconnect(self, code):
|
|
43
|
-
|
|
43
|
+
try:
|
|
44
|
+
print(f"Colonel {self.colonel} socket disconnected! code={code}")
|
|
45
|
+
except Exception:
|
|
46
|
+
print("Colonel socket disconnected!")
|
|
44
47
|
self.connected = False
|
|
45
48
|
if self.mqtt_client:
|
|
46
49
|
self.mqtt_client.loop_stop()
|
|
50
|
+
try:
|
|
51
|
+
if self._va and (self._va.active or self.colonel.is_vo_active):
|
|
52
|
+
await self._va._end_session(cloud_also=True)
|
|
53
|
+
elif getattr(self.colonel, 'is_vo_active', False):
|
|
54
|
+
def _save():
|
|
55
|
+
self.colonel.is_vo_active = False
|
|
56
|
+
self.colonel.save(update_fields=['is_vo_active'])
|
|
57
|
+
await sync_to_async(_save, thread_sensitive=True)()
|
|
58
|
+
try:
|
|
59
|
+
base = await sync_to_async(lambda: dynamic_settings.get('core__remote_http'), thread_sensitive=True)()
|
|
60
|
+
hub_uid = await sync_to_async(lambda: dynamic_settings['core__hub_uid'], thread_sensitive=True)()
|
|
61
|
+
hub_secret = await sync_to_async(lambda: dynamic_settings['core__hub_secret'], thread_sensitive=True)()
|
|
62
|
+
base = base or 'https://simo.io'
|
|
63
|
+
url = base.rstrip('/') + '/ai/finish-session/'
|
|
64
|
+
payload = {
|
|
65
|
+
'hub_uid': hub_uid,
|
|
66
|
+
'hub_secret': hub_secret,
|
|
67
|
+
'instance_uid': self.instance.uid,
|
|
68
|
+
}
|
|
69
|
+
import requests
|
|
70
|
+
loop = asyncio.get_running_loop()
|
|
71
|
+
await loop.run_in_executor(None, lambda: requests.post(url, json=payload, timeout=5))
|
|
72
|
+
except Exception:
|
|
73
|
+
pass
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
47
76
|
|
|
48
77
|
def save_disconect():
|
|
49
78
|
if self.colonel:
|
|
50
79
|
self.colonel.socket_connected = False
|
|
51
|
-
self.colonel.
|
|
80
|
+
self.colonel.is_vo_active = False
|
|
81
|
+
self.colonel.save(update_fields=['socket_connected', 'is_vo_active'])
|
|
52
82
|
await sync_to_async(save_disconect, thread_sensitive=True)()
|
|
53
83
|
|
|
54
84
|
|
|
@@ -102,9 +132,6 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
102
132
|
'last_seen': timezone.now(),
|
|
103
133
|
'enabled': True
|
|
104
134
|
}
|
|
105
|
-
# !!!!! ATETION! !!!!!!!
|
|
106
|
-
# update_or_create and get_or_create doesn't
|
|
107
|
-
# provide reliable operation in socket/async environment.
|
|
108
135
|
new = False
|
|
109
136
|
colonel = Colonel.objects.filter(uid=headers['colonel-uid']).first()
|
|
110
137
|
if not colonel:
|
|
@@ -138,6 +165,14 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
138
165
|
self.connected = True
|
|
139
166
|
|
|
140
167
|
await self.log_colonel_connected()
|
|
168
|
+
def _reset_vo():
|
|
169
|
+
if self.colonel.is_vo_active:
|
|
170
|
+
self.colonel.is_vo_active = False
|
|
171
|
+
self.colonel.save(update_fields=['is_vo_active'])
|
|
172
|
+
try:
|
|
173
|
+
await sync_to_async(_reset_vo, thread_sensitive=True)()
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
141
176
|
|
|
142
177
|
|
|
143
178
|
def get_gateway():
|
|
@@ -167,18 +202,6 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
167
202
|
port=settings.MQTT_PORT)
|
|
168
203
|
self.mqtt_client.loop_start()
|
|
169
204
|
|
|
170
|
-
# DO NOT FORCE CONFIG DATA!!!!
|
|
171
|
-
# as colonels might already have config and want to
|
|
172
|
-
# send updated values of components, like for example
|
|
173
|
-
# somebody turned some lights on/off while colonel was
|
|
174
|
-
# not connected to the main hub.
|
|
175
|
-
# If we force this, vales get overridden by what is last
|
|
176
|
-
# known by the hub
|
|
177
|
-
# config = await self.get_config_data()
|
|
178
|
-
# await self.send_data(
|
|
179
|
-
# 'command': 'set_config', 'data': config
|
|
180
|
-
# })
|
|
181
|
-
|
|
182
205
|
await self.send_data({'command': 'hello'})
|
|
183
206
|
|
|
184
207
|
asyncio.create_task(self.watch_connection())
|
|
@@ -195,13 +218,15 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
195
218
|
self.colonel.minor_upgrade_available
|
|
196
219
|
)
|
|
197
220
|
|
|
198
|
-
# Default pinging system sometimes get's lost somewhere,
|
|
199
|
-
# therefore we use our own to ensure connection and understand if
|
|
200
|
-
# colonel is connected or not
|
|
201
|
-
|
|
202
221
|
if time.time() - self.last_seen > 2:
|
|
203
222
|
await self.send_data({'command': 'ping'})
|
|
204
223
|
|
|
224
|
+
try:
|
|
225
|
+
if self._va and self._va.active and (time.time() - self.last_seen) > 60:
|
|
226
|
+
await self._va._end_session(cloud_also=True)
|
|
227
|
+
except Exception:
|
|
228
|
+
pass
|
|
229
|
+
|
|
205
230
|
await asyncio.sleep(2)
|
|
206
231
|
|
|
207
232
|
|
|
@@ -339,7 +364,7 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
339
364
|
config = await self.get_config_data()
|
|
340
365
|
await self.send_data({
|
|
341
366
|
'command': 'set_config', 'data': config
|
|
342
|
-
}, compress=
|
|
367
|
+
}, compress=self.colonel.type != 'sentinel')
|
|
343
368
|
asyncio.run(send_config())
|
|
344
369
|
elif payload.get('command') == 'discover':
|
|
345
370
|
print(f"SEND discover command for {payload['type']}")
|
|
@@ -385,7 +410,7 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
385
410
|
print("Send config: ", config)
|
|
386
411
|
await self.send_data({
|
|
387
412
|
'command': 'set_config', 'data': config
|
|
388
|
-
}, compress=
|
|
413
|
+
}, compress=self.colonel.type != 'sentinel')
|
|
389
414
|
elif 'comp' in data:
|
|
390
415
|
try:
|
|
391
416
|
try:
|
|
@@ -450,25 +475,64 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
450
475
|
process_discovery_result, thread_sensitive=True
|
|
451
476
|
)()
|
|
452
477
|
|
|
453
|
-
elif 'dali-raw' in data:
|
|
454
|
-
from .custom_dali_operations import process_frame
|
|
455
|
-
await sync_to_async(process_frame, thread_sensitive=True)(
|
|
456
|
-
self.colonel.id, data['dali-raw'], data['data']
|
|
457
|
-
)
|
|
458
478
|
|
|
479
|
+
elif 'va' in data and isinstance(data['va'], dict):
|
|
480
|
+
va = data['va']
|
|
481
|
+
if va.get('session') == 'finish':
|
|
482
|
+
if not self._va:
|
|
483
|
+
self._va = VoiceAssistantSession(self)
|
|
484
|
+
await self._va._end_session(cloud_also=True)
|
|
485
|
+
|
|
486
|
+
elif 'wake-stats' in data and self.colonel.type == 'sentinel':
|
|
487
|
+
def update_wake_stats():
|
|
488
|
+
va_component = Component.objects.filter(
|
|
489
|
+
config__colonel=self.colonel.id,
|
|
490
|
+
pk=data.get('id', 0)
|
|
491
|
+
).select_related('zone').first()
|
|
492
|
+
self.colonel.wake_stats = data['wake-stats']
|
|
493
|
+
self.colonel.last_wake = timezone.now()
|
|
494
|
+
self.colonel.save()
|
|
495
|
+
return va_component
|
|
496
|
+
va_component = await sync_to_async(
|
|
497
|
+
update_wake_stats, thread_sensitive=True
|
|
498
|
+
)()
|
|
499
|
+
if not self._va:
|
|
500
|
+
self._va = VoiceAssistantSession(self)
|
|
501
|
+
self._va.voice = data.get('voice', 'male')
|
|
502
|
+
self._va.zone = va_component.zone.id
|
|
459
503
|
|
|
460
504
|
elif bytes_data:
|
|
461
|
-
if
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
505
|
+
if self.colonel.type == 'sentinel':
|
|
506
|
+
if bytes_data[0] == 32:
|
|
507
|
+
await self.capture_logs(bytes_data[1:])
|
|
508
|
+
else:
|
|
509
|
+
if not self._va:
|
|
510
|
+
self._va = VoiceAssistantSession(self)
|
|
511
|
+
if not self._arb:
|
|
512
|
+
self._arb = VoiceAssistantArbitrator(self, self._va)
|
|
513
|
+
await self._va.prewarm_on_first_audio()
|
|
514
|
+
if await self._arb.maybe_reject_busy():
|
|
515
|
+
return
|
|
516
|
+
self._arb.start_window_if_needed()
|
|
517
|
+
await self._va.on_audio_chunk(bytes_data[1:])
|
|
518
|
+
else:
|
|
519
|
+
if bytes_data[0] == 32:
|
|
520
|
+
await self.capture_logs(bytes_data[1:])
|
|
521
|
+
else:
|
|
522
|
+
await self.capture_logs(bytes_data)
|
|
466
523
|
|
|
467
524
|
await self.log_colonel_connected()
|
|
468
525
|
except Exception as e:
|
|
469
526
|
print(traceback.format_exc(), file=sys.stderr)
|
|
470
527
|
|
|
471
528
|
|
|
529
|
+
async def capture_logs(self, bytes_data):
|
|
530
|
+
if not self.colonel_logger:
|
|
531
|
+
await self.start_logger()
|
|
532
|
+
|
|
533
|
+
for logline in bytes_data.decode(errors='replace').split('\n'):
|
|
534
|
+
self.colonel_logger.log(logging.INFO, logline)
|
|
535
|
+
|
|
472
536
|
|
|
473
537
|
async def log_colonel_connected(self):
|
|
474
538
|
self.last_seen = time.time()
|
|
@@ -513,6 +577,3 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
513
577
|
)
|
|
514
578
|
file_handler.setFormatter(formatter)
|
|
515
579
|
self.colonel_logger.addHandler(file_handler)
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
simo/fleet/tasks.py
CHANGED
|
@@ -20,8 +20,7 @@ def check_colonels_connected():
|
|
|
20
20
|
@celery_app.task
|
|
21
21
|
def check_colonel_components_alive():
|
|
22
22
|
from simo.core.models import Component
|
|
23
|
-
from .
|
|
24
|
-
from .models import Colonel, CustomDaliDevice
|
|
23
|
+
from .models import Colonel
|
|
25
24
|
drop_current_instance()
|
|
26
25
|
for lost_colonel in Colonel.objects.filter(
|
|
27
26
|
last_seen__lt=timezone.now() - datetime.timedelta(seconds=60)
|
|
@@ -33,28 +32,9 @@ def check_colonel_components_alive():
|
|
|
33
32
|
print(f"{comp} is no longer alive!")
|
|
34
33
|
comp.alive = False
|
|
35
34
|
comp.save()
|
|
36
|
-
for interface in lost_colonel.interfaces.all():
|
|
37
|
-
if interface.type == 'dali':
|
|
38
|
-
for device in interface.custom_devices.all():
|
|
39
|
-
for comp in Component.objects.filter(
|
|
40
|
-
gateway__type=FleetGatewayHandler.uid,
|
|
41
|
-
config__dali_device=device.id, alive=True
|
|
42
|
-
):
|
|
43
|
-
comp.alive = False
|
|
44
|
-
comp.save()
|
|
45
|
-
|
|
46
|
-
for device in CustomDaliDevice.objects.filter(
|
|
47
|
-
last_seen__gt=timezone.now() - datetime.timedelta(seconds=60)
|
|
48
|
-
):
|
|
49
|
-
for comp in Component.objects.filter(
|
|
50
|
-
gateway__type=FleetGatewayHandler.uid,
|
|
51
|
-
config__dali_device=device.id, alive=True
|
|
52
|
-
):
|
|
53
|
-
comp.alive = False
|
|
54
|
-
comp.save()
|
|
55
35
|
|
|
56
36
|
|
|
57
37
|
@celery_app.on_after_finalize.connect
|
|
58
38
|
def setup_periodic_tasks(sender, **kwargs):
|
|
59
39
|
sender.add_periodic_task(10, check_colonels_connected.s())
|
|
60
|
-
sender.add_periodic_task(20, check_colonel_components_alive.s())
|
|
40
|
+
sender.add_periodic_task(20, check_colonel_components_alive.s())
|