layrz-sdk 3.1.14__py3-none-any.whl → 3.1.16__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 layrz-sdk might be problematic. Click here for more details.
- layrz_sdk/__init__.py +1 -1
- layrz_sdk/constants.py +19 -5
- layrz_sdk/entities/__init__.py +138 -129
- layrz_sdk/entities/asset.py +76 -60
- layrz_sdk/entities/asset_operation_mode.py +31 -31
- layrz_sdk/entities/broadcast_request.py +12 -12
- layrz_sdk/entities/broadcast_response.py +12 -12
- layrz_sdk/entities/broadcast_result.py +20 -20
- layrz_sdk/entities/broadcast_status.py +28 -28
- layrz_sdk/entities/case.py +52 -52
- layrz_sdk/entities/case_ignored_status.py +26 -26
- layrz_sdk/entities/case_status.py +23 -23
- layrz_sdk/entities/charts/axis_config.py +15 -15
- layrz_sdk/entities/charts/bar_chart.py +175 -175
- layrz_sdk/entities/charts/chart_alignment.py +27 -27
- layrz_sdk/entities/charts/chart_color.py +44 -44
- layrz_sdk/entities/charts/chart_configuration.py +10 -10
- layrz_sdk/entities/charts/chart_data_serie.py +19 -19
- layrz_sdk/entities/charts/chart_data_serie_type.py +28 -28
- layrz_sdk/entities/charts/chart_data_type.py +27 -27
- layrz_sdk/entities/charts/chart_render_technology.py +30 -30
- layrz_sdk/entities/charts/column_chart.py +201 -201
- layrz_sdk/entities/charts/html_chart.py +38 -38
- layrz_sdk/entities/charts/line_chart.py +248 -248
- layrz_sdk/entities/charts/map_center_type.py +22 -22
- layrz_sdk/entities/charts/map_chart.py +108 -108
- layrz_sdk/entities/charts/map_point.py +22 -22
- layrz_sdk/entities/charts/number_chart.py +54 -54
- layrz_sdk/entities/charts/pie_chart.py +131 -131
- layrz_sdk/entities/charts/radar_chart.py +81 -81
- layrz_sdk/entities/charts/radial_bar_chart.py +131 -131
- layrz_sdk/entities/charts/scatter_chart.py +210 -210
- layrz_sdk/entities/charts/scatter_serie.py +13 -13
- layrz_sdk/entities/charts/scatter_serie_item.py +8 -8
- layrz_sdk/entities/charts/table_chart.py +54 -54
- layrz_sdk/entities/charts/table_header.py +8 -8
- layrz_sdk/entities/charts/table_row.py +9 -9
- layrz_sdk/entities/charts/timeline_chart.py +79 -79
- layrz_sdk/entities/charts/timeline_serie.py +10 -10
- layrz_sdk/entities/charts/timeline_serie_item.py +12 -12
- layrz_sdk/entities/checkpoint.py +17 -17
- layrz_sdk/entities/comment.py +16 -16
- layrz_sdk/entities/custom_field.py +10 -10
- layrz_sdk/entities/custom_report_page.py +40 -40
- layrz_sdk/entities/device.py +18 -13
- layrz_sdk/entities/event.py +23 -23
- layrz_sdk/entities/geofence.py +11 -11
- layrz_sdk/entities/last_message.py +12 -12
- layrz_sdk/entities/message.py +23 -23
- layrz_sdk/entities/modbus/__init__.py +9 -0
- layrz_sdk/entities/modbus/config.py +19 -0
- layrz_sdk/entities/modbus/parameter.py +110 -0
- layrz_sdk/entities/modbus/schema.py +10 -0
- layrz_sdk/entities/modbus/status.py +16 -0
- layrz_sdk/entities/modbus/wait.py +134 -0
- layrz_sdk/entities/outbound_service.py +10 -10
- layrz_sdk/entities/position.py +116 -116
- layrz_sdk/entities/presence_type.py +16 -16
- layrz_sdk/entities/report.py +289 -289
- layrz_sdk/entities/report_col.py +40 -40
- layrz_sdk/entities/report_configuration.py +8 -8
- layrz_sdk/entities/report_data_type.py +28 -28
- layrz_sdk/entities/report_format.py +27 -27
- layrz_sdk/entities/report_header.py +43 -43
- layrz_sdk/entities/report_page.py +15 -15
- layrz_sdk/entities/report_row.py +28 -28
- layrz_sdk/entities/sensor.py +11 -11
- layrz_sdk/entities/static_position.py +17 -0
- layrz_sdk/entities/telemetry/__init__.py +6 -0
- layrz_sdk/entities/telemetry/assetmessage.py +159 -0
- layrz_sdk/entities/telemetry/devicemessage.py +122 -0
- layrz_sdk/entities/text_alignment.py +26 -26
- layrz_sdk/entities/trigger.py +11 -11
- layrz_sdk/entities/user.py +10 -10
- layrz_sdk/entities/waypoint.py +18 -18
- layrz_sdk/helpers/__init__.py +5 -5
- layrz_sdk/helpers/color.py +44 -44
- layrz_sdk/lcl/__init__.py +5 -5
- layrz_sdk/lcl/core.py +848 -848
- {layrz_sdk-3.1.14.dist-info → layrz_sdk-3.1.16.dist-info}/METADATA +54 -49
- layrz_sdk-3.1.16.dist-info/RECORD +85 -0
- {layrz_sdk-3.1.14.dist-info → layrz_sdk-3.1.16.dist-info}/licenses/LICENSE +6 -6
- layrz_sdk-3.1.14.dist-info/RECORD +0 -75
- {layrz_sdk-3.1.14.dist-info → layrz_sdk-3.1.16.dist-info}/WHEEL +0 -0
- {layrz_sdk-3.1.14.dist-info → layrz_sdk-3.1.16.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from pydantic import BaseModel, Field
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class ReportConfiguration(BaseModel):
|
|
5
|
-
"""Report configuration entity"""
|
|
6
|
-
|
|
7
|
-
title: str = Field(description='Report title')
|
|
8
|
-
pages_count: int = Field(description='Number of pages in the report')
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ReportConfiguration(BaseModel):
|
|
5
|
+
"""Report configuration entity"""
|
|
6
|
+
|
|
7
|
+
title: str = Field(description='Report title')
|
|
8
|
+
pages_count: int = Field(description='Number of pages in the report')
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from enum import Enum
|
|
3
|
-
|
|
4
|
-
if sys.version_info >= (3, 11):
|
|
5
|
-
from typing import Self
|
|
6
|
-
else:
|
|
7
|
-
from typing_extensions import Self
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ReportDataType(Enum):
|
|
11
|
-
"""
|
|
12
|
-
Report date type
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
STR = 'str'
|
|
16
|
-
INT = 'int'
|
|
17
|
-
FLOAT = 'float'
|
|
18
|
-
DATETIME = 'datetime'
|
|
19
|
-
BOOL = 'bool'
|
|
20
|
-
CURRENCY = 'currency'
|
|
21
|
-
|
|
22
|
-
def __str__(self: Self) -> str:
|
|
23
|
-
"""Readable property"""
|
|
24
|
-
return self.name
|
|
25
|
-
|
|
26
|
-
def __repr__(self: Self) -> str:
|
|
27
|
-
"""Readable property"""
|
|
28
|
-
return f'ReportDataType.{self.value}'
|
|
1
|
+
import sys
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
if sys.version_info >= (3, 11):
|
|
5
|
+
from typing import Self
|
|
6
|
+
else:
|
|
7
|
+
from typing_extensions import Self
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ReportDataType(Enum):
|
|
11
|
+
"""
|
|
12
|
+
Report date type
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
STR = 'str'
|
|
16
|
+
INT = 'int'
|
|
17
|
+
FLOAT = 'float'
|
|
18
|
+
DATETIME = 'datetime'
|
|
19
|
+
BOOL = 'bool'
|
|
20
|
+
CURRENCY = 'currency'
|
|
21
|
+
|
|
22
|
+
def __str__(self: Self) -> str:
|
|
23
|
+
"""Readable property"""
|
|
24
|
+
return self.name
|
|
25
|
+
|
|
26
|
+
def __repr__(self: Self) -> str:
|
|
27
|
+
"""Readable property"""
|
|
28
|
+
return f'ReportDataType.{self.value}'
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
"""Report formats"""
|
|
2
|
-
|
|
3
|
-
import sys
|
|
4
|
-
from enum import Enum
|
|
5
|
-
|
|
6
|
-
if sys.version_info >= (3, 11):
|
|
7
|
-
from typing import Self
|
|
8
|
-
else:
|
|
9
|
-
from typing_extensions import Self
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ReportFormat(Enum):
|
|
13
|
-
"""
|
|
14
|
-
Report format definition.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
MICROSOFT_EXCEL = 'MICROSOFT_EXCEL'
|
|
18
|
-
JSON = 'JSON'
|
|
19
|
-
PDF = 'PDF'
|
|
20
|
-
|
|
21
|
-
def __str__(self: Self) -> str:
|
|
22
|
-
"""Readable property"""
|
|
23
|
-
return self.name
|
|
24
|
-
|
|
25
|
-
def __repr__(self: Self) -> str:
|
|
26
|
-
"""Readable property"""
|
|
27
|
-
return f'ReportFormat.{self.value}'
|
|
1
|
+
"""Report formats"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
if sys.version_info >= (3, 11):
|
|
7
|
+
from typing import Self
|
|
8
|
+
else:
|
|
9
|
+
from typing_extensions import Self
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ReportFormat(Enum):
|
|
13
|
+
"""
|
|
14
|
+
Report format definition.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
MICROSOFT_EXCEL = 'MICROSOFT_EXCEL'
|
|
18
|
+
JSON = 'JSON'
|
|
19
|
+
PDF = 'PDF'
|
|
20
|
+
|
|
21
|
+
def __str__(self: Self) -> str:
|
|
22
|
+
"""Readable property"""
|
|
23
|
+
return self.name
|
|
24
|
+
|
|
25
|
+
def __repr__(self: Self) -> str:
|
|
26
|
+
"""Readable property"""
|
|
27
|
+
return f'ReportFormat.{self.value}'
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
"""Report header"""
|
|
2
|
-
|
|
3
|
-
import warnings
|
|
4
|
-
from typing import Any, Optional
|
|
5
|
-
|
|
6
|
-
from pydantic import BaseModel, Field, field_validator
|
|
7
|
-
|
|
8
|
-
from .text_alignment import TextAlignment
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class ReportHeader(BaseModel):
|
|
12
|
-
"""Report header entity"""
|
|
13
|
-
|
|
14
|
-
content: Any = Field(description='Header content')
|
|
15
|
-
color: str = Field(description='Header color', default='#ffffff')
|
|
16
|
-
text_color: Optional[str] = Field(description='Header text color', default=None)
|
|
17
|
-
align: TextAlignment = Field(description='Header text alignment', default=TextAlignment.CENTER)
|
|
18
|
-
bold: bool = Field(description='Bold text', default=False)
|
|
19
|
-
width: Optional[float] = Field(description='Header width', default=None)
|
|
20
|
-
|
|
21
|
-
@field_validator('text_color', mode='before')
|
|
22
|
-
def _validate_text_color(cls, value: Any) -> Any:
|
|
23
|
-
"""Validate text color"""
|
|
24
|
-
if value is not None:
|
|
25
|
-
warnings.warn(
|
|
26
|
-
'text_color is deprecated, the algorithm will calculate the rigth text color instead',
|
|
27
|
-
DeprecationWarning,
|
|
28
|
-
stacklevel=2,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
return value
|
|
32
|
-
|
|
33
|
-
@field_validator('width', mode='before')
|
|
34
|
-
def _validate_width(cls, value: Any) -> Any:
|
|
35
|
-
"""Validate width"""
|
|
36
|
-
if value is not None:
|
|
37
|
-
warnings.warn(
|
|
38
|
-
'width is deprecated, the algorithm will calculate the rigth width instead',
|
|
39
|
-
DeprecationWarning,
|
|
40
|
-
stacklevel=2,
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
return value
|
|
1
|
+
"""Report header"""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, field_validator
|
|
7
|
+
|
|
8
|
+
from .text_alignment import TextAlignment
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ReportHeader(BaseModel):
|
|
12
|
+
"""Report header entity"""
|
|
13
|
+
|
|
14
|
+
content: Any = Field(description='Header content')
|
|
15
|
+
color: str = Field(description='Header color', default='#ffffff')
|
|
16
|
+
text_color: Optional[str] = Field(description='Header text color', default=None)
|
|
17
|
+
align: TextAlignment = Field(description='Header text alignment', default=TextAlignment.CENTER)
|
|
18
|
+
bold: bool = Field(description='Bold text', default=False)
|
|
19
|
+
width: Optional[float] = Field(description='Header width', default=None)
|
|
20
|
+
|
|
21
|
+
@field_validator('text_color', mode='before')
|
|
22
|
+
def _validate_text_color(cls, value: Any) -> Any:
|
|
23
|
+
"""Validate text color"""
|
|
24
|
+
if value is not None:
|
|
25
|
+
warnings.warn(
|
|
26
|
+
'text_color is deprecated, the algorithm will calculate the rigth text color instead',
|
|
27
|
+
DeprecationWarning,
|
|
28
|
+
stacklevel=2,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return value
|
|
32
|
+
|
|
33
|
+
@field_validator('width', mode='before')
|
|
34
|
+
def _validate_width(cls, value: Any) -> Any:
|
|
35
|
+
"""Validate width"""
|
|
36
|
+
if value is not None:
|
|
37
|
+
warnings.warn(
|
|
38
|
+
'width is deprecated, the algorithm will calculate the rigth width instead',
|
|
39
|
+
DeprecationWarning,
|
|
40
|
+
stacklevel=2,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return value
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
"""Report page"""
|
|
2
|
-
|
|
3
|
-
from pydantic import BaseModel, Field
|
|
4
|
-
|
|
5
|
-
from .report_header import ReportHeader
|
|
6
|
-
from .report_row import ReportRow
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ReportPage(BaseModel):
|
|
10
|
-
"""Report page definition"""
|
|
11
|
-
|
|
12
|
-
name: str = Field(description='Name of the page. Length should be less than 60 characters')
|
|
13
|
-
headers: list[ReportHeader] = Field(description='List of report headers', default_factory=list)
|
|
14
|
-
rows: list[ReportRow] = Field(description='List of report rows', default_factory=list)
|
|
15
|
-
freeze_header: bool = Field(description='Freeze header', default=False)
|
|
1
|
+
"""Report page"""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from .report_header import ReportHeader
|
|
6
|
+
from .report_row import ReportRow
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ReportPage(BaseModel):
|
|
10
|
+
"""Report page definition"""
|
|
11
|
+
|
|
12
|
+
name: str = Field(description='Name of the page. Length should be less than 60 characters')
|
|
13
|
+
headers: list[ReportHeader] = Field(description='List of report headers', default_factory=list)
|
|
14
|
+
rows: list[ReportRow] = Field(description='List of report rows', default_factory=list)
|
|
15
|
+
freeze_header: bool = Field(description='Freeze header', default=False)
|
layrz_sdk/entities/report_row.py
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
"""Report row"""
|
|
2
|
-
|
|
3
|
-
import warnings
|
|
4
|
-
from typing import Any, Optional
|
|
5
|
-
|
|
6
|
-
from pydantic import BaseModel, Field, field_validator
|
|
7
|
-
|
|
8
|
-
from .report_col import ReportCol
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class ReportRow(BaseModel):
|
|
12
|
-
"""Report row definition"""
|
|
13
|
-
|
|
14
|
-
content: list[ReportCol] = Field(description='List of report columns', default_factory=list)
|
|
15
|
-
height: Optional[float] = Field(description='Row height', default=None)
|
|
16
|
-
compact: bool = Field(description='Compact mode', default=False)
|
|
17
|
-
|
|
18
|
-
@field_validator('height', mode='before')
|
|
19
|
-
def _validate_height(cls, value: Any) -> Any:
|
|
20
|
-
"""Validate height"""
|
|
21
|
-
if value is not None:
|
|
22
|
-
warnings.warn(
|
|
23
|
-
'height is deprecated, the algorithm will calculate the rigth text color instead',
|
|
24
|
-
DeprecationWarning,
|
|
25
|
-
stacklevel=2,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
return value
|
|
1
|
+
"""Report row"""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, field_validator
|
|
7
|
+
|
|
8
|
+
from .report_col import ReportCol
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ReportRow(BaseModel):
|
|
12
|
+
"""Report row definition"""
|
|
13
|
+
|
|
14
|
+
content: list[ReportCol] = Field(description='List of report columns', default_factory=list)
|
|
15
|
+
height: Optional[float] = Field(description='Row height', default=None)
|
|
16
|
+
compact: bool = Field(description='Compact mode', default=False)
|
|
17
|
+
|
|
18
|
+
@field_validator('height', mode='before')
|
|
19
|
+
def _validate_height(cls, value: Any) -> Any:
|
|
20
|
+
"""Validate height"""
|
|
21
|
+
if value is not None:
|
|
22
|
+
warnings.warn(
|
|
23
|
+
'height is deprecated, the algorithm will calculate the rigth text color instead',
|
|
24
|
+
DeprecationWarning,
|
|
25
|
+
stacklevel=2,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return value
|
layrz_sdk/entities/sensor.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""Sensor entity"""
|
|
2
|
-
|
|
3
|
-
from pydantic import BaseModel, Field
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Sensor(BaseModel):
|
|
7
|
-
"""Sensor entity"""
|
|
8
|
-
|
|
9
|
-
pk: int = Field(description='Defines the primary key of the sensor')
|
|
10
|
-
name: str = Field(description='Defines the name of the sensor')
|
|
11
|
-
slug: str = Field(description='Defines the slug of the sensor')
|
|
1
|
+
"""Sensor entity"""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Sensor(BaseModel):
|
|
7
|
+
"""Sensor entity"""
|
|
8
|
+
|
|
9
|
+
pk: int = Field(description='Defines the primary key of the sensor')
|
|
10
|
+
name: str = Field(description='Defines the name of the sensor')
|
|
11
|
+
slug: str = Field(description='Defines the slug of the sensor')
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StaticPosition(BaseModel):
|
|
5
|
+
latitude: float = Field(
|
|
6
|
+
...,
|
|
7
|
+
description='Latitude of the static position',
|
|
8
|
+
)
|
|
9
|
+
longitude: float = Field(
|
|
10
|
+
...,
|
|
11
|
+
description='Longitude of the static position',
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
altitude: float | None = Field(
|
|
15
|
+
default=None,
|
|
16
|
+
description='Altitude of the static position',
|
|
17
|
+
)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Asset message"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from typing import Any, cast
|
|
9
|
+
|
|
10
|
+
from layrz_sdk.entities.position import Position
|
|
11
|
+
|
|
12
|
+
if sys.version_info >= (3, 11):
|
|
13
|
+
from typing import Self
|
|
14
|
+
else:
|
|
15
|
+
from typing_extensions import Self
|
|
16
|
+
|
|
17
|
+
from geopy.distance import geodesic
|
|
18
|
+
from pydantic import BaseModel, Field
|
|
19
|
+
from shapely.geometry import MultiPoint
|
|
20
|
+
|
|
21
|
+
from layrz_sdk.constants import UTC
|
|
22
|
+
from layrz_sdk.entities.asset import Asset
|
|
23
|
+
from layrz_sdk.entities.asset_operation_mode import AssetOperationMode
|
|
24
|
+
from layrz_sdk.entities.message import Message
|
|
25
|
+
from layrz_sdk.entities.telemetry.devicemessage import DeviceMessage
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AssetMessage(BaseModel):
|
|
29
|
+
"""Asset message model"""
|
|
30
|
+
|
|
31
|
+
pk: int | None = Field(
|
|
32
|
+
default=None,
|
|
33
|
+
description='Message ID',
|
|
34
|
+
alias='id',
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
asset_id: int = Field(
|
|
38
|
+
...,
|
|
39
|
+
description='Asset ID',
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
position: dict[str, float | int] = Field(
|
|
43
|
+
default_factory=dict,
|
|
44
|
+
description='Current position of the device',
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
payload: dict[str, Any] = Field(
|
|
48
|
+
default_factory=dict,
|
|
49
|
+
description='Payload data of the device message',
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
sensors: dict[str, Any] = Field(
|
|
53
|
+
default_factory=dict,
|
|
54
|
+
description='Sensor data of the device message',
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
geofences_ids: list[int] = Field(
|
|
58
|
+
default_factory=list,
|
|
59
|
+
description='List of geofence IDs associated with the message',
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
distance_traveled: float = Field(
|
|
63
|
+
default=0.0,
|
|
64
|
+
description='Distance traveled since the last message',
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
received_at: datetime = Field(
|
|
68
|
+
default_factory=lambda: datetime.now(UTC),
|
|
69
|
+
description='Timestamp when the message was received',
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
elapsed_time: timedelta = Field(
|
|
73
|
+
default_factory=lambda: timedelta(seconds=0),
|
|
74
|
+
description='Elapsed time since the last message',
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def datum_gis(self: Self) -> int:
|
|
79
|
+
"""Get the GIS datum of the message."""
|
|
80
|
+
return 4326
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def point_gis(self: Self) -> str | None:
|
|
84
|
+
"""Get the GIS point of the message on WKT (Well-Known Text) format for OGC (Open Geospatial Consortium)."""
|
|
85
|
+
latitude = self.position.get('latitude')
|
|
86
|
+
longitude = self.position.get('longitude')
|
|
87
|
+
|
|
88
|
+
if latitude is not None and longitude is not None:
|
|
89
|
+
return f'POINT({longitude} {latitude})'
|
|
90
|
+
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def has_point(self: Self) -> bool:
|
|
95
|
+
"""Check if the message has a point."""
|
|
96
|
+
latitude = self.position.get('latitude')
|
|
97
|
+
longitude = self.position.get('longitude')
|
|
98
|
+
|
|
99
|
+
return latitude is not None and longitude is not None
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def parse_from_devicemessage(cls, *, device_message: DeviceMessage, asset: Asset) -> AssetMessage:
|
|
103
|
+
obj = cls(
|
|
104
|
+
asset_id=asset.pk,
|
|
105
|
+
position={},
|
|
106
|
+
payload={},
|
|
107
|
+
sensors={},
|
|
108
|
+
received_at=device_message.received_at,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
match asset.operation_mode:
|
|
112
|
+
case AssetOperationMode.DISCONNECTED:
|
|
113
|
+
obj.position = {}
|
|
114
|
+
case AssetOperationMode.STATIC:
|
|
115
|
+
obj.position = asset.static_position.model_dump(exclude_none=True) if asset.static_position else {}
|
|
116
|
+
case AssetOperationMode.ZONE:
|
|
117
|
+
points = MultiPoint([(p.longitude, p.latitude) for p in asset.points])
|
|
118
|
+
obj.position = {'latitude': points.centroid.y, 'longitude': points.centroid.x}
|
|
119
|
+
case _:
|
|
120
|
+
obj.position = device_message.position
|
|
121
|
+
|
|
122
|
+
for key, value in device_message.payload.items():
|
|
123
|
+
obj.payload[f'{device_message.ident}.{key}'] = value
|
|
124
|
+
|
|
125
|
+
return obj
|
|
126
|
+
|
|
127
|
+
def compute_distance_traveled(self: Self, *, previous_message: AssetMessage | None = None) -> float:
|
|
128
|
+
"""Compute the distance traveled since the last message."""
|
|
129
|
+
if not self.has_point or not previous_message or not previous_message.has_point:
|
|
130
|
+
return 0.0
|
|
131
|
+
|
|
132
|
+
return cast(
|
|
133
|
+
float,
|
|
134
|
+
geodesic(
|
|
135
|
+
(self.position['latitude'], self.position['longitude']),
|
|
136
|
+
(previous_message.position['latitude'], previous_message.position['longitude']),
|
|
137
|
+
).meters,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def compute_elapsed_time(self: Self, *, previous_message: AssetMessage | None = None) -> timedelta:
|
|
141
|
+
"""Compute the elapsed time since the last message."""
|
|
142
|
+
if not previous_message:
|
|
143
|
+
return timedelta(seconds=0)
|
|
144
|
+
|
|
145
|
+
if self.received_at < previous_message.received_at:
|
|
146
|
+
return timedelta(seconds=0)
|
|
147
|
+
|
|
148
|
+
return self.received_at - previous_message.received_at
|
|
149
|
+
|
|
150
|
+
def to_message(self: Self) -> Message:
|
|
151
|
+
"""Convert the asset message to a Message object."""
|
|
152
|
+
return Message(
|
|
153
|
+
pk=self.pk if self.pk is not None else 0,
|
|
154
|
+
asset_id=self.asset_id,
|
|
155
|
+
position=Position.model_validate(self.position),
|
|
156
|
+
payload=self.payload,
|
|
157
|
+
sensors=self.sensors,
|
|
158
|
+
received_at=self.received_at,
|
|
159
|
+
)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Device message"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from layrz_sdk.entities.message import Message
|
|
11
|
+
from layrz_sdk.entities.position import Position
|
|
12
|
+
|
|
13
|
+
if sys.version_info >= (3, 11):
|
|
14
|
+
from typing import Self
|
|
15
|
+
else:
|
|
16
|
+
from typing_extensions import Self
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, Field
|
|
19
|
+
|
|
20
|
+
from layrz_sdk.constants import REJECTED_KEYS, UTC
|
|
21
|
+
from layrz_sdk.entities.device import Device
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DeviceMessage(BaseModel):
|
|
25
|
+
"""Device message model"""
|
|
26
|
+
|
|
27
|
+
pk: int | None = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description='Device message ID',
|
|
30
|
+
alias='id',
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
ident: str = Field(
|
|
34
|
+
...,
|
|
35
|
+
description='Device identifier',
|
|
36
|
+
)
|
|
37
|
+
device_id: int = Field(
|
|
38
|
+
...,
|
|
39
|
+
description='Device ID',
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
protocol_id: int = Field(
|
|
43
|
+
...,
|
|
44
|
+
description='Protocol ID',
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
position: dict[str, float | int] = Field(
|
|
48
|
+
default_factory=dict,
|
|
49
|
+
description='Current position of the device',
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
payload: dict[str, Any] = Field(
|
|
53
|
+
default_factory=dict,
|
|
54
|
+
description='Payload data of the device message',
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
received_at: datetime = Field(
|
|
58
|
+
default_factory=lambda: datetime.now(UTC),
|
|
59
|
+
description='Timestamp when the message was received',
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def datum_gis(self: Self) -> int:
|
|
64
|
+
"""Get the GIS datum of the message."""
|
|
65
|
+
return 4326
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def point_gis(self: Self) -> str | None:
|
|
69
|
+
"""Get the GIS point of the message on WKT (Well-Known Text) format for OGC (Open Geospatial Consortium)."""
|
|
70
|
+
latitude = self.position.get('latitude')
|
|
71
|
+
longitude = self.position.get('longitude')
|
|
72
|
+
|
|
73
|
+
if latitude is not None and longitude is not None:
|
|
74
|
+
return f'POINT({longitude} {latitude})'
|
|
75
|
+
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def has_point(self: Self) -> bool:
|
|
80
|
+
"""Check if the message has a point."""
|
|
81
|
+
latitude = self.position.get('latitude')
|
|
82
|
+
longitude = self.position.get('longitude')
|
|
83
|
+
|
|
84
|
+
return latitude is not None and longitude is not None
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def parse_from_dict(cls, *, raw_payload: dict[str, Any], device: Device) -> DeviceMessage:
|
|
88
|
+
"""Format a DeviceMessage from a dictionary."""
|
|
89
|
+
received_at: datetime
|
|
90
|
+
position: dict[str, float | int] = {}
|
|
91
|
+
payload: dict[str, Any] = {}
|
|
92
|
+
|
|
93
|
+
if 'timestamp' in raw_payload:
|
|
94
|
+
received_at = datetime.fromtimestamp(raw_payload['timestamp'], tz=UTC)
|
|
95
|
+
else:
|
|
96
|
+
received_at = datetime.now(UTC)
|
|
97
|
+
|
|
98
|
+
for key, value in raw_payload.items():
|
|
99
|
+
if key.startswith('position.'):
|
|
100
|
+
position[key[9:]] = value
|
|
101
|
+
|
|
102
|
+
if key not in REJECTED_KEYS:
|
|
103
|
+
payload[key] = value
|
|
104
|
+
|
|
105
|
+
return cls(
|
|
106
|
+
ident=device.ident,
|
|
107
|
+
device_id=device.pk,
|
|
108
|
+
protocol_id=device.protocol_id,
|
|
109
|
+
position=position,
|
|
110
|
+
payload=payload,
|
|
111
|
+
received_at=received_at,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def to_message(self: Self) -> Message:
|
|
115
|
+
"""Convert the asset message to a Message object."""
|
|
116
|
+
return Message(
|
|
117
|
+
pk=self.pk if self.pk is not None else 0,
|
|
118
|
+
asset_id=self.device_id if self.device_id is not None else 0,
|
|
119
|
+
position=Position.model_validate(self.position),
|
|
120
|
+
payload=self.payload,
|
|
121
|
+
received_at=self.received_at,
|
|
122
|
+
)
|