espark-core 0.4.9__py3-none-any.whl → 0.5.0__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 espark-core might be problematic. Click here for more details.
- {espark_core-0.4.9.dist-info → espark_core-0.5.0.dist-info}/METADATA +7 -7
- {espark_core-0.4.9.dist-info → espark_core-0.5.0.dist-info}/RECORD +19 -13
- esparkcore/constants.py +1 -1
- esparkcore/data/models/__init__.py +2 -0
- esparkcore/data/models/notification.py +11 -0
- esparkcore/data/models/trigger.py +13 -0
- esparkcore/data/repositories/__init__.py +2 -0
- esparkcore/data/repositories/notification_repository.py +7 -0
- esparkcore/data/repositories/telemetry_repository.py +24 -1
- esparkcore/data/repositories/trigger_repository.py +7 -0
- esparkcore/routers/__init__.py +2 -0
- esparkcore/routers/device_router.py +29 -1
- esparkcore/routers/notification_router.py +10 -0
- esparkcore/routers/telemetry_router.py +10 -0
- esparkcore/routers/trigger_router.py +10 -0
- esparkcore/services/mqtt.py +48 -11
- {espark_core-0.4.9.dist-info → espark_core-0.5.0.dist-info}/WHEEL +0 -0
- {espark_core-0.4.9.dist-info → espark_core-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {espark_core-0.4.9.dist-info → espark_core-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: espark-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: The core module of the Espark ESP32-based IoT device management framework.
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.8
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Requires-Dist: aiomqtt>=2.4.0
|
|
10
|
-
Requires-Dist: apscheduler>=3.11.
|
|
11
|
-
Requires-Dist: fastapi>=0.
|
|
10
|
+
Requires-Dist: apscheduler>=3.11.2
|
|
11
|
+
Requires-Dist: fastapi>=0.127.0
|
|
12
12
|
Requires-Dist: packaging>=25.0
|
|
13
13
|
Requires-Dist: slack-sdk==3.39.0
|
|
14
14
|
Requires-Dist: sqlalchemy>=2.0.45
|
|
15
|
-
Requires-Dist: sqlmodel>=0.0.
|
|
16
|
-
Requires-Dist: uvicorn[standard]>=0.
|
|
15
|
+
Requires-Dist: sqlmodel>=0.0.29
|
|
16
|
+
Requires-Dist: uvicorn[standard]>=0.40.0
|
|
17
17
|
Provides-Extra: dev
|
|
18
|
-
Requires-Dist: aiosqlite==0.22.
|
|
18
|
+
Requires-Dist: aiosqlite==0.22.1; extra == "dev"
|
|
19
19
|
Requires-Dist: autopep8==2.3.2; extra == "dev"
|
|
20
20
|
Requires-Dist: build==1.3.0; extra == "dev"
|
|
21
|
-
Requires-Dist: fastapi-cli[standard-no-fastapi-cloud-cli]==0.0.
|
|
21
|
+
Requires-Dist: fastapi-cli[standard-no-fastapi-cloud-cli]==0.0.20; extra == "dev"
|
|
22
22
|
Requires-Dist: httpx==0.28.1; extra == "dev"
|
|
23
23
|
Requires-Dist: pycodestyle==2.14.0; extra == "dev"
|
|
24
24
|
Requires-Dist: pylint==4.0.4; extra == "dev"
|
|
@@ -1,35 +1,41 @@
|
|
|
1
|
-
espark_core-0.
|
|
1
|
+
espark_core-0.5.0.dist-info/licenses/LICENSE,sha256=wkIXJHYIspOGVvn_ajKyLucpIyw9_pO3QExFcNTCY78,1065
|
|
2
2
|
esparkcore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
esparkcore/constants.py,sha256=
|
|
3
|
+
esparkcore/constants.py,sha256=2Bmx62j4kykkPhcQ9ygEJ08uyVlykXMNFhpO26-Czlg,417
|
|
4
4
|
esparkcore/data/__init__.py,sha256=abMOPq6jC5qOQCl9j7iPDv8iyjzDF-oVd_gW0la5kL4,45
|
|
5
5
|
esparkcore/data/database.py,sha256=OaJSBDW6Qge-rsKkSIksJXOg3xF_y6LuBew--6uFbG4,532
|
|
6
|
-
esparkcore/data/models/__init__.py,sha256=
|
|
6
|
+
esparkcore/data/models/__init__.py,sha256=hJwtKxw02gDB6jot_l8I47AeU2UwT9plrd5haIGB3fw,192
|
|
7
7
|
esparkcore/data/models/device.py,sha256=a1Tfw8xHzDLEiVxANZ-lR2aVaWaWJFa8tZQwHBzDgu0,1135
|
|
8
|
+
esparkcore/data/models/notification.py,sha256=NhkTZbNsY1ljTmVJNrDC68W0Jd2BcACxfWomrgkO4zM,621
|
|
8
9
|
esparkcore/data/models/outbox.py,sha256=r5bgjIbj9H-OsGhE-CWKGRf4Tp88qMvmxPYHNGOApY4,1309
|
|
9
10
|
esparkcore/data/models/telemetry.py,sha256=OfzGqGj2Y1sdK519sSOEXhfuErHoeEdpy5qmJMIc27A,652
|
|
11
|
+
esparkcore/data/models/trigger.py,sha256=3gApG5RHtwIKL_CX_Fx8oqCaLfgYveqzHy5mSmFpWxM,986
|
|
10
12
|
esparkcore/data/models/version.py,sha256=h2O_Owx-exx5tiQiiauCSELkUHDx-QZjiIGqFPybT4M,269
|
|
11
|
-
esparkcore/data/repositories/__init__.py,sha256=
|
|
13
|
+
esparkcore/data/repositories/__init__.py,sha256=zlTYJ_KKBqkY7KpIi1kJlCG5UA6zXlLm5wK9INLpZQY,358
|
|
12
14
|
esparkcore/data/repositories/base_repository.py,sha256=L5APTp8SczndbWqgJyEqKe-4NQoL_YYOapUN6i572cM,2097
|
|
13
15
|
esparkcore/data/repositories/device_repository.py,sha256=P9KK-Ta9MgMdrnct5hbGEoipPOPrrDQ4wINQnZZ6iZY,1131
|
|
16
|
+
esparkcore/data/repositories/notification_repository.py,sha256=N-bcprhnG6noTdhIzLMBJtCmGeMtnZw2bPx8JdjamW8,205
|
|
14
17
|
esparkcore/data/repositories/outbox_repository.py,sha256=PdPyKq2Km4_7732O6HhA4BlV_zBjYF91GkMltNKUovg,1015
|
|
15
|
-
esparkcore/data/repositories/telemetry_repository.py,sha256=
|
|
18
|
+
esparkcore/data/repositories/telemetry_repository.py,sha256=9wkgE2SU6_fqJMxpnG_IOF_ONxi9qi0coy_gVSJG2q8,2105
|
|
19
|
+
esparkcore/data/repositories/trigger_repository.py,sha256=xHYky1ZAT2H0cLPGTjVbmnH6ZHPgFXzga2SeOx-xSNo,185
|
|
16
20
|
esparkcore/data/repositories/version_repository.py,sha256=QZZX2BpFx1i0oeFJ6j0nZvlZH7cViKU8VERBE8AU3ZY,395
|
|
17
21
|
esparkcore/notifications/__init__.py,sha256=WrdUPsRS0tg2rAhv4aj4eGDekkfAsKjcRPRlT6btzQs,77
|
|
18
22
|
esparkcore/notifications/notifier.py,sha256=d4a8leU-oTwWe7ZTLw-vxfCrYlFnCIvbcXabiXkT6Lc,446
|
|
19
23
|
esparkcore/notifications/slack_notifier.py,sha256=-H-3YYm0CRTV8pZgggrNPJKz6815HTumKvE7VsvvSAA,644
|
|
20
|
-
esparkcore/routers/__init__.py,sha256=
|
|
24
|
+
esparkcore/routers/__init__.py,sha256=sZv812GJl-JEWJQ7IR1gAQ5BdCf5nTkfbJZXv5jZv_4,225
|
|
21
25
|
esparkcore/routers/base_router.py,sha256=t-x8tYZVzBk9bD__9yaGYhm66glq_oM_lAHn0eplM2M,3902
|
|
22
|
-
esparkcore/routers/device_router.py,sha256=
|
|
23
|
-
esparkcore/routers/
|
|
26
|
+
esparkcore/routers/device_router.py,sha256=KM6MI6BaxDbZJaqDPEi6iJYPwnOIujtAFG8J1oGRq3I,2506
|
|
27
|
+
esparkcore/routers/notification_router.py,sha256=vLG99eW-b9UlO9NiV1yZZeqkG4XLVv5Yj5JvHZLP6Dg,411
|
|
28
|
+
esparkcore/routers/telemetry_router.py,sha256=3690D4603gVK3-yaJT-sAVKwhIYEYKxQZpIu84a9flg,2584
|
|
29
|
+
esparkcore/routers/trigger_router.py,sha256=YSNYrLTQYeGToVBOlSK4PtDKpdFpesk-ZnNn_lXu6Ec,366
|
|
24
30
|
esparkcore/routers/version_router.py,sha256=7HYD6sbGWow3DDvK62RXPZu5Qz8DCTTs7wkksQKBq9c,383
|
|
25
31
|
esparkcore/schedules/__init__.py,sha256=v5Wy3Cbt2AnudGMGXv_Cyaa10s6NAmP66rDjcMSvf0o,74
|
|
26
32
|
esparkcore/schedules/outbox.py,sha256=GGANvjkI7w1BdmmVRgKog498ASD_xRN0g8bG6yC_SN4,811
|
|
27
33
|
esparkcore/schedules/scheduler.py,sha256=aYQXImLZ4tW-hRavn3ObuV3lYjmOZD813YkeG1NsYfY,170
|
|
28
34
|
esparkcore/services/__init__.py,sha256=RIZOHBfkWn3HsApdA5rH9EZbbXGq0NfiGoOWDO_JX1k,30
|
|
29
|
-
esparkcore/services/mqtt.py,sha256=
|
|
35
|
+
esparkcore/services/mqtt.py,sha256=5h_mhXhFt11TyhGwNhaY08Y4tttvSTPkiR3veTqpb3A,8972
|
|
30
36
|
esparkcore/utils/__init__.py,sha256=rzuCJUAaQl-yPhM1vcWB_1zVXqtA71ChYP2aM_3UfKk,42
|
|
31
37
|
esparkcore/utils/logging.py,sha256=sRO8uOcPkFH-DwepqLOTS2qGaopXpMdVOjzZL6RpGs4,257
|
|
32
|
-
espark_core-0.
|
|
33
|
-
espark_core-0.
|
|
34
|
-
espark_core-0.
|
|
35
|
-
espark_core-0.
|
|
38
|
+
espark_core-0.5.0.dist-info/METADATA,sha256=Ez2PWfUcMCe7__fNBbp9bl0cGAGdPuyqfaL3OXYcNfg,6329
|
|
39
|
+
espark_core-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
40
|
+
espark_core-0.5.0.dist-info/top_level.txt,sha256=hXVyhIPB4aGskFm5queWALxDPhcDkrN7_8vcjwA1iE4,11
|
|
41
|
+
espark_core-0.5.0.dist-info/RECORD,,
|
esparkcore/constants.py
CHANGED
|
@@ -7,5 +7,5 @@ TOPIC_ACTION : str = 'espark/action'
|
|
|
7
7
|
TOPIC_DEVICE : str = 'espark/device'
|
|
8
8
|
TOPIC_REGISTRATION : str = 'espark/registration'
|
|
9
9
|
TOPIC_TELEMETRY : str = 'espark/telemetry'
|
|
10
|
-
|
|
10
|
+
TOPIC_OTA : str = 'espark/ota'
|
|
11
11
|
TOPIC_CRASH : str = 'espark/crash'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import Column
|
|
4
|
+
from sqlmodel import SQLModel, Field, JSON
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Notification(SQLModel, table=True):
|
|
8
|
+
id : int = Field(primary_key=True, description='Unique identifier for the notification')
|
|
9
|
+
name : str = Field(index=True, unique=True, description='Name of the notification')
|
|
10
|
+
provider : str = Field(index=True, description='Notification provider (e.g., Slack, Twilio)')
|
|
11
|
+
config : Dict[str, str] = Field(default_factory=dict, sa_column=Column(JSON), description='JSON string of provider-specific configuration parameters')
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from sqlmodel import Field, SQLModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Trigger(SQLModel, table=True):
|
|
7
|
+
id : str = Field(primary_key=True, description='Unique identifier for the trigger')
|
|
8
|
+
name : str = Field(index=True, unique=True, description='Name of the trigger')
|
|
9
|
+
device_id : Optional[str] = Field(index=True, default=None, description='Identifier of the associated device')
|
|
10
|
+
data_type : Optional[str] = Field(index=True, default=None, description='Type of telemetry data the trigger monitors (e.g., temperature, humidity)')
|
|
11
|
+
condition : str = Field(description='Condition to evaluate (e.g., ">", "<=")')
|
|
12
|
+
value : int = Field(description='Value to compare against for the trigger condition')
|
|
13
|
+
notification_ids : Optional[str] = Field(default=None, description='Comma-separated list of associated notification IDs to be sent when the trigger condition is met')
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from .base_repository import AsyncRepository
|
|
2
2
|
from .device_repository import DeviceRepository
|
|
3
|
+
from .notification_repository import NotificationRepository
|
|
3
4
|
from .outbox_repository import OutboxRepository
|
|
4
5
|
from .telemetry_repository import TelemetryRepository
|
|
6
|
+
from .trigger_repository import TriggerRepository
|
|
5
7
|
from .version_repository import AppVersionRepository
|
|
@@ -2,7 +2,7 @@ from typing import Optional, Sequence
|
|
|
2
2
|
|
|
3
3
|
from sqlalchemy import ColumnElement
|
|
4
4
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
5
|
-
from sqlmodel import and_
|
|
5
|
+
from sqlmodel import and_, select
|
|
6
6
|
|
|
7
7
|
from ..models import Telemetry
|
|
8
8
|
from .base_repository import AsyncRepository
|
|
@@ -23,3 +23,26 @@ class TelemetryRepository(AsyncRepository[Telemetry]):
|
|
|
23
23
|
order_by = Telemetry.timestamp.desc()
|
|
24
24
|
|
|
25
25
|
return await super().list(session, *conditions, offset=offset, order_by=order_by, limit=limit)
|
|
26
|
+
|
|
27
|
+
async def search(self, session: AsyncSession, device_id: str = None, data_type: str = None, condition: str = None, value: int = None):
|
|
28
|
+
query = select(self.model)
|
|
29
|
+
|
|
30
|
+
if device_id is not None:
|
|
31
|
+
query = query.where(Telemetry.device_id == device_id)
|
|
32
|
+
|
|
33
|
+
if data_type is not None:
|
|
34
|
+
query = query.where(Telemetry.data_type == data_type)
|
|
35
|
+
|
|
36
|
+
if condition is not None and value is not None:
|
|
37
|
+
if condition == '<':
|
|
38
|
+
query = query.where(Telemetry.value < value)
|
|
39
|
+
elif condition == '<=':
|
|
40
|
+
query = query.where(Telemetry.value <= value)
|
|
41
|
+
elif condition == '==':
|
|
42
|
+
query = query.where(Telemetry.value == value)
|
|
43
|
+
elif condition == '>=':
|
|
44
|
+
query = query.where(Telemetry.value >= value)
|
|
45
|
+
elif condition == '>':
|
|
46
|
+
query = query.where(Telemetry.value > value)
|
|
47
|
+
|
|
48
|
+
return (await session.execute(query)).scalars().all()
|
esparkcore/routers/__init__.py
CHANGED
|
@@ -2,11 +2,12 @@ from json import dumps
|
|
|
2
2
|
from os import getenv
|
|
3
3
|
|
|
4
4
|
from aiomqtt import Client
|
|
5
|
+
from fastapi import Depends, Query, Response
|
|
5
6
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
7
|
|
|
7
8
|
from ..constants import ENV_MQTT_HOST, ENV_MQTT_PORT, TOPIC_DEVICE
|
|
8
9
|
from ..data.models import Device
|
|
9
|
-
from ..data.repositories import DeviceRepository
|
|
10
|
+
from ..data.repositories import DeviceRepository, TelemetryRepository
|
|
10
11
|
from ..utils import log_debug
|
|
11
12
|
from .base_router import BaseRouter
|
|
12
13
|
|
|
@@ -27,3 +28,30 @@ class DeviceRouter(BaseRouter):
|
|
|
27
28
|
await super()._after_update(entity, session)
|
|
28
29
|
|
|
29
30
|
await self._publish_update(entity)
|
|
31
|
+
|
|
32
|
+
def _setup_routes(self) -> None:
|
|
33
|
+
@self.router.get('/all')
|
|
34
|
+
async def list_all(response: Response, session: AsyncSession = Depends(BaseRouter._get_session), offset: int = Query(None, ge=0), limit: int = Query(None, ge=1, le=100)):
|
|
35
|
+
response.headers['X-Total-Count'] = str(await self.repo.count(session))
|
|
36
|
+
|
|
37
|
+
devices = await self.repo.list(session, offset=offset, limit=limit)
|
|
38
|
+
devices_with_telemetry = []
|
|
39
|
+
telemetry_repo = TelemetryRepository()
|
|
40
|
+
|
|
41
|
+
for device in devices:
|
|
42
|
+
battery = await telemetry_repo.get_latest_for_device(session, device.id, 'battery')
|
|
43
|
+
|
|
44
|
+
devices_with_telemetry.append({
|
|
45
|
+
'id' : device.id,
|
|
46
|
+
'display_name' : device.display_name,
|
|
47
|
+
'app_name' : device.app_name,
|
|
48
|
+
'app_version' : device.app_version,
|
|
49
|
+
'capabilities' : device.capabilities,
|
|
50
|
+
'parameters' : device.parameters,
|
|
51
|
+
'last_seen' : device.last_seen,
|
|
52
|
+
'battery' : battery.value if battery else None,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
return devices_with_telemetry
|
|
56
|
+
|
|
57
|
+
super()._setup_routes()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from ..data.models import Notification
|
|
2
|
+
from ..data.repositories import NotificationRepository
|
|
3
|
+
from .base_router import BaseRouter
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NotificationRouter(BaseRouter):
|
|
7
|
+
def __init__(self, repo: NotificationRepository = None) -> None:
|
|
8
|
+
self.repo : NotificationRepository = repo or NotificationRepository()
|
|
9
|
+
|
|
10
|
+
super().__init__(Notification, self.repo, '/api/v1/notifications', ['notification'])
|
|
@@ -3,6 +3,7 @@ from typing import Sequence
|
|
|
3
3
|
|
|
4
4
|
from fastapi import Depends, Query, Response
|
|
5
5
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
|
+
from sqlmodel import and_
|
|
6
7
|
|
|
7
8
|
from ..data.models import Telemetry
|
|
8
9
|
from ..data.repositories import DeviceRepository, TelemetryRepository
|
|
@@ -16,6 +17,15 @@ class TelemetryRouter(BaseRouter):
|
|
|
16
17
|
super().__init__(Telemetry, self.repo, '/api/v1/telemetry', ['telemetry'])
|
|
17
18
|
|
|
18
19
|
def _setup_routes(self) -> None:
|
|
20
|
+
@self.router.get('/history', response_model=Sequence[Telemetry])
|
|
21
|
+
async def list_history(response: Response, session: AsyncSession = Depends(BaseRouter._get_session), device_id: int = Query(..., ge=1), offset: int = Query(..., min=0)) -> Sequence[Telemetry]:
|
|
22
|
+
from_date = datetime.now(timezone.utc) - timedelta(seconds=offset)
|
|
23
|
+
results = await self.repo.list(session, and_(Telemetry.device_id == device_id, Telemetry.timestamp >= from_date))
|
|
24
|
+
|
|
25
|
+
response.headers['X-Total-Count'] = str(len(results))
|
|
26
|
+
|
|
27
|
+
return results
|
|
28
|
+
|
|
19
29
|
@self.router.get('/recent', response_model=Sequence[Telemetry])
|
|
20
30
|
async def list_recent(response: Response, session: AsyncSession = Depends(BaseRouter._get_session), offset: int = Query(..., min=0)) -> Sequence[Telemetry]:
|
|
21
31
|
from_date = datetime.now(timezone.utc) - timedelta(seconds=offset)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from ..data.models import Trigger
|
|
2
|
+
from ..data.repositories import TriggerRepository
|
|
3
|
+
from .base_router import BaseRouter
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TriggerRouter(BaseRouter):
|
|
7
|
+
def __init__(self, repo: TriggerRepository = None) -> None:
|
|
8
|
+
self.repo : TriggerRepository = repo or TriggerRepository()
|
|
9
|
+
|
|
10
|
+
super().__init__(Trigger, self.repo, '/api/v1/triggers', ['trigger'])
|
esparkcore/services/mqtt.py
CHANGED
|
@@ -7,23 +7,26 @@ from uuid import getnode
|
|
|
7
7
|
from aiomqtt import Client, MqttError
|
|
8
8
|
from packaging.version import Version
|
|
9
9
|
|
|
10
|
-
from ..constants import ENV_MQTT_HOST, ENV_MQTT_PORT, TOPIC_CRASH,
|
|
10
|
+
from ..constants import ENV_MQTT_HOST, ENV_MQTT_PORT, TOPIC_CRASH, TOPIC_OTA, TOPIC_REGISTRATION, TOPIC_TELEMETRY
|
|
11
11
|
from ..data import async_session
|
|
12
12
|
from ..data.models import AppVersion, Device, Telemetry
|
|
13
|
-
from ..data.repositories import AppVersionRepository, DeviceRepository, TelemetryRepository
|
|
13
|
+
from ..data.repositories import AppVersionRepository, DeviceRepository, NotificationRepository, TelemetryRepository, TriggerRepository
|
|
14
|
+
from ..notifications import SlackNotifier
|
|
14
15
|
from ..utils import log_debug, log_error
|
|
15
16
|
|
|
16
17
|
MQTT_RETRY_DELAY: int = 5
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class MQTTManager:
|
|
20
|
-
def __init__(self, version_repo: AppVersionRepository = None, device_repo: DeviceRepository = None, telemetry_repo: TelemetryRepository = None):
|
|
21
|
-
self.mqtt_host
|
|
22
|
-
self.mqtt_port
|
|
23
|
-
self.version_repo
|
|
24
|
-
self.device_repo
|
|
25
|
-
self.
|
|
26
|
-
self.
|
|
21
|
+
def __init__(self, version_repo: AppVersionRepository = None, device_repo: DeviceRepository = None, notification_repo: NotificationRepository = None, telemetry_repo: TelemetryRepository = None, trigger_repo: TriggerRepository = None) -> None:
|
|
22
|
+
self.mqtt_host : str = getenv(ENV_MQTT_HOST, 'localhost')
|
|
23
|
+
self.mqtt_port : int = int(getenv(ENV_MQTT_PORT, '1883'))
|
|
24
|
+
self.version_repo : AppVersionRepository = version_repo if version_repo else AppVersionRepository()
|
|
25
|
+
self.device_repo : DeviceRepository = device_repo if device_repo else DeviceRepository()
|
|
26
|
+
self.notification_repo : NotificationRepository = notification_repo if notification_repo else NotificationRepository()
|
|
27
|
+
self.telemetry_repo : TelemetryRepository = telemetry_repo if telemetry_repo else TelemetryRepository()
|
|
28
|
+
self.trigger_repo : TriggerRepository = trigger_repo if trigger_repo else TriggerRepository()
|
|
29
|
+
self.queue : Queue = Queue()
|
|
27
30
|
|
|
28
31
|
async def _handle_registration(self, device_id: str, payload: dict) -> None:
|
|
29
32
|
try:
|
|
@@ -61,11 +64,11 @@ class MQTTManager:
|
|
|
61
64
|
log_debug(f'Device {device_id} is running an outdated version ({current_version} < {latest_version.version})')
|
|
62
65
|
|
|
63
66
|
async with Client(self.mqtt_host, self.mqtt_port, identifier=f'espark-core-{hex(getnode())}') as client:
|
|
64
|
-
await client.publish(f'{
|
|
67
|
+
await client.publish(f'{TOPIC_OTA}/{device_id}', dumps({
|
|
65
68
|
'device_id' : device_id,
|
|
66
69
|
'app_name' : payload['app_name'],
|
|
67
70
|
'app_version' : latest_version.version,
|
|
68
|
-
'download_url' :
|
|
71
|
+
'download_url' : f'/downloads/{payload["app_name"]}/{latest_version.version}',
|
|
69
72
|
}), qos=1)
|
|
70
73
|
# pylint: disable=broad-exception-caught
|
|
71
74
|
except Exception as e:
|
|
@@ -88,6 +91,40 @@ class MQTTManager:
|
|
|
88
91
|
except Exception as e:
|
|
89
92
|
log_error(e)
|
|
90
93
|
|
|
94
|
+
await self._handle_triggers(device_id, payload.get('data_type'), payload.get('value'))
|
|
95
|
+
|
|
96
|
+
async def _handle_triggers(self, device_id: str, data_type: str, value: int) -> None:
|
|
97
|
+
async with async_session() as session:
|
|
98
|
+
triggers = await self.trigger_repo.list(session)
|
|
99
|
+
for trigger in triggers:
|
|
100
|
+
matched_device_id = trigger.device_id is None or trigger.device_id == device_id
|
|
101
|
+
matched_data_type = trigger.data_type is None or trigger.data_type == data_type
|
|
102
|
+
if matched_device_id and matched_data_type:
|
|
103
|
+
condition_met = False
|
|
104
|
+
if trigger.condition is None:
|
|
105
|
+
condition_met = True
|
|
106
|
+
elif trigger.condition == '==' and value == trigger.value:
|
|
107
|
+
condition_met = True
|
|
108
|
+
elif trigger.condition == '>' and value > trigger.value:
|
|
109
|
+
condition_met = True
|
|
110
|
+
elif trigger.condition == '>=' and value >= trigger.value:
|
|
111
|
+
condition_met = True
|
|
112
|
+
elif trigger.condition == '<' and value < trigger.value:
|
|
113
|
+
condition_met = True
|
|
114
|
+
elif trigger.condition == '<=' and value <= trigger.value:
|
|
115
|
+
condition_met = True
|
|
116
|
+
|
|
117
|
+
if condition_met:
|
|
118
|
+
log_debug(f'Trigger {trigger.name} activated for device {device_id} with data type {data_type} and value {value}')
|
|
119
|
+
|
|
120
|
+
notifications = await self.notification_repo.list(session)
|
|
121
|
+
for notification in notifications:
|
|
122
|
+
if trigger.notification_ids.index(str(notification.id)) != -1:
|
|
123
|
+
log_debug(f'Sending notification {notification.name} for trigger {trigger.name}')
|
|
124
|
+
|
|
125
|
+
if notification.provider == 'Slack':
|
|
126
|
+
await SlackNotifier(notification.config['slack_token'], notification.config['slack_channel']).notify(device_id, data_type, value)
|
|
127
|
+
|
|
91
128
|
async def _process_queue(self) -> None:
|
|
92
129
|
while True:
|
|
93
130
|
topic, payload = await self.queue.get()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|