Skip to content

Commit 029d553

Browse files
add stubs for more user-friendly options to the library
1 parent ba69477 commit 029d553

File tree

6 files changed

+250
-12
lines changed

6 files changed

+250
-12
lines changed

conSys/comm/mqtt.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import paho.mqtt.client as mqtt
22

33

4-
class MQTTClient:
4+
class MQTTCommClient:
55
def __init__(self, url, port=1883, username=None, password=None, path='mqtt', client_id="", transport='tcp'):
66
"""
77
Wraps a paho mqtt client to provide a simple interface for interacting with the mqtt server that is customized
@@ -82,7 +82,7 @@ def subscribe(self, topic, qos=0, msg_callback=None):
8282
self.__client.message_callback_add(topic, msg_callback)
8383

8484
def publish(self, topic, payload=None, qos=0, retain=False):
85-
self.__client.publish(topic, payload, qos, retain)
85+
self.__client.publish(topic, payload, qos, retain=retain)
8686

8787
def disconnect(self):
8888
self.__client.disconnect()
@@ -182,8 +182,8 @@ def __toggle_is_connected(self):
182182
def is_connected(self):
183183
return self.__is_connected
184184

185-
def publish(self, topic, msg):
186-
self.__client.publish(topic, msg, 1)
185+
# def publish(self, topic, msg):
186+
# self.__client.publish(topic, msg, 1)
187187

188188
@staticmethod
189189
def publish_single(self, topic, msg):

conSys/constants.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,22 @@ class GeometryTypes(Enum):
7171
MULTI_POINT = "MultiPoint"
7272
MULTI_LINESTRING = "MultiLineString"
7373
MULTI_POLYGON = "MultiPolygon"
74+
75+
76+
class APIResourceTypes(Enum):
77+
"""
78+
Defines the resource types
79+
"""
80+
COLLECTION = "Collection"
81+
COMMAND = "Command"
82+
COMPONENT = "Component"
83+
CONTROL_CHANNEL = "ControlChannel"
84+
DATASTREAM = "Datastream"
85+
DEPLOYMENT = "Deployment"
86+
OBSERVATION = "Observation"
87+
PROCEDURE = "Procedure"
88+
PROPERTY = "Property"
89+
SAMPLING_FEATURE = "SamplingFeature"
90+
SYSTEM = "System"
91+
SYSTEM_EVENT = "SystemEvent"
92+
SYSTEM_HISTORY = "SystemHistory"

conSys/core/default_api_helpers.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
from abc import ABC
2+
from dataclasses import dataclass
3+
4+
from conSys import APIResourceTypes, APITerms
5+
6+
7+
def determine_parent_type(res_type: APIResourceTypes):
8+
match res_type:
9+
case APIResourceTypes.SYSTEM:
10+
return APIResourceTypes.SYSTEM
11+
case APIResourceTypes.COLLECTION:
12+
return None
13+
case APIResourceTypes.CONTROL_CHANNEL:
14+
return APIResourceTypes.SYSTEM
15+
case APIResourceTypes.COMMAND:
16+
return APIResourceTypes.CONTROL_CHANNEL
17+
case APIResourceTypes.DATASTREAM:
18+
return APIResourceTypes.SYSTEM
19+
case APIResourceTypes.OBSERVATION:
20+
return APIResourceTypes.DATASTREAM
21+
case APIResourceTypes.SYSTEM_EVENT:
22+
return APIResourceTypes.SYSTEM
23+
case APIResourceTypes.SAMPLING_FEATURE:
24+
return APIResourceTypes.SYSTEM
25+
case APIResourceTypes.PROCEDURE:
26+
return None
27+
case APIResourceTypes.PROPERTY:
28+
return None
29+
case APIResourceTypes.SYSTEM_HISTORY:
30+
return None
31+
case APIResourceTypes.DEPLOYMENT:
32+
return None
33+
case _:
34+
return None
35+
36+
37+
def resource_type_to_endpoint(res_type: APIResourceTypes, parent_type: APIResourceTypes = None):
38+
if parent_type is APIResourceTypes.COLLECTION:
39+
return APITerms.ITEMS.value
40+
41+
match res_type:
42+
case APIResourceTypes.SYSTEM:
43+
return APITerms.SYSTEMS.value
44+
case APIResourceTypes.COLLECTION:
45+
return APITerms.COLLECTIONS.value
46+
case APIResourceTypes.CONTROL_CHANNEL:
47+
return APITerms.CONTROL_STREAMS.value
48+
case APIResourceTypes.COMMAND:
49+
return APITerms.COMMANDS.value
50+
case APIResourceTypes.DATASTREAM:
51+
return APITerms.DATASTREAMS.value
52+
case APIResourceTypes.OBSERVATION:
53+
return APITerms.OBSERVATIONS.value
54+
case APIResourceTypes.SYSTEM:
55+
return APITerms.SYSTEMS.value
56+
case APIResourceTypes.SYSTEM_EVENT:
57+
return APITerms.SYSTEM_EVENTS.value
58+
case APIResourceTypes.SAMPLING_FEATURE:
59+
return APITerms.SAMPLING_FEATURES.value
60+
case APIResourceTypes.PROCEDURE:
61+
return APITerms.PROCEDURES.value
62+
case APIResourceTypes.PROPERTY:
63+
return APITerms.PROPERTIES.value
64+
case APIResourceTypes.SYSTEM_HISTORY:
65+
return APITerms.HISTORY.value
66+
case APIResourceTypes.DEPLOYMENT:
67+
return APITerms.DEPLOYMENTS.value
68+
case _:
69+
raise ValueError('Invalid resource type')
70+
71+
72+
@dataclass
73+
class APIHelper(ABC):
74+
server_url = None
75+
api_endpoint = "/api"
76+
username = None
77+
password = None
78+
user_auth = False
79+
80+
def create_resource(self, res_type: APIResourceTypes, json_data: any, parent_res_id: str = None):
81+
"""
82+
Creates a resource of the given type with the given data, will attempt to create a sub-resource if parent_res_id
83+
is provided.
84+
:param res_type:
85+
:param json_data:
86+
:param parent_res_id:
87+
:return:
88+
"""
89+
pass
90+
91+
def retrieve_resource(self, res_type: APIResourceTypes, res_id: str, parent_res_id: str = None,
92+
from_collection: bool = False,
93+
collection_id: str = None):
94+
pass
95+
96+
def update_resource(self, res_type: APIResourceTypes, res_id: str, json_data: any, parent_res_id: str = None):
97+
pass
98+
99+
def delete_resource(self, res_type: APIResourceTypes, res_id: str, parent_res_id: str = None):
100+
pass
101+
102+
# Helpers
103+
def resource_url_resolver(self, res_type: APIResourceTypes, res_id: str, parent_res_id: str = None,
104+
from_collection: bool = False):
105+
if res_type is None:
106+
raise ValueError('Resource type must contain a valid APIResourceType')
107+
if res_type is APIResourceTypes.COLLECTION and from_collection:
108+
raise ValueError('Collections are not sub-resources of other collections')
109+
110+
parent_type = None
111+
if parent_res_id and not from_collection:
112+
parent_type = determine_parent_type(res_type)
113+
elif parent_res_id and from_collection:
114+
parent_type = APIResourceTypes.COLLECTION
115+
116+
return self.construct_url(parent_type, res_id, res_type, parent_res_id)
117+
118+
def construct_url(self, parent_type, res_id, res_type, parent_res_id):
119+
# TODO: Test for less common cases to ensure that the URL is being constructed correctly
120+
base_url = f'{self.server_url}/{self.api_endpoint}'
121+
resource_endpoint = resource_type_to_endpoint(res_type, parent_type)
122+
url = f'{base_url}/{resource_endpoint}'
123+
124+
if parent_type:
125+
parent_endpoint = resource_type_to_endpoint(parent_type)
126+
url = f'{base_url}/{parent_endpoint}/{parent_res_id}/{resource_endpoint}'
127+
128+
if res_id:
129+
url = f'{url}/{res_id}'
130+
131+
return url
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
from __future__ import annotations
3+
4+
from pydantic import BaseModel, Field, HttpUrl
5+
6+
from conSys import Geometry
7+
from conSys.datamodels.api_utils import Link, URI
8+
9+
10+
class SystemEventOMJSON(BaseModel):
11+
"""
12+
A class to represent the schema of a system event
13+
"""
14+
label: str = Field(...)
15+
description: str = Field(None)
16+
definition: HttpUrl = Field(...)
17+
identifiers: list = Field(None)
18+
classifiers: list = Field(None)
19+
contacts: list = Field(None)
20+
documentation: list = Field(None)
21+
time: str = Field(...)
22+
properties: list = Field(None)
23+
configuration: dict = Field(None)
24+
links: list[Link] = Field(None)
25+
26+
27+
class SystemHistoryGeoJSON(BaseModel):
28+
"""
29+
A class to represent the schema of a system history
30+
"""
31+
type: str = Field(...)
32+
id: str = Field(None)
33+
properties: SystemHistoryProperties = Field(...)
34+
geometry: Geometry = Field(None)
35+
bbox: list = Field(None)
36+
links: list[Link] = Field(None)
37+
38+
39+
class SystemHistoryProperties(BaseModel):
40+
feature_type: str = Field(...)
41+
uid: URI = Field(...)
42+
name: str = Field(...)
43+
description: str = Field(None)
44+
asset_type: str = Field(None)
45+
valid_time: list = Field(None)
46+
parent_system_link: str = Field(None, serialization_alias='parentSystem@link')
47+
procedure_link: str = Field(None, serialization_alias='procedure@link')

tests/test_commands.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from conSys.datamodels.commands import CommandJSON
77
from conSys.datamodels.control_streams import ControlStreamJSONSchema, JSONControlChannelSchema
88
from conSys.datamodels.swe_components import DataRecordSchema, TimeSchema, CountSchema, URI
9-
from conSys.comm.mqtt import MQTTClient
9+
from conSys.comm.mqtt import MQTTCommClient
1010

1111
server_url = "http://localhost:8282/sensorhub"
1212
geo_json_headers = {"Content-Type": "application/geo+json"}
@@ -63,7 +63,7 @@ def test_setup():
6363

6464

6565
def test_subscribe_and_command():
66-
mqtt_client = MQTTClient(url='localhost')
66+
mqtt_client = MQTTCommClient(url='localhost')
6767

6868
control_streams = ControlChannels.list_all_control_streams(server_url).json()
6969
control_id = control_streams["items"][0]["id"]
@@ -97,12 +97,16 @@ def on_message_all(client, userdata, msg):
9797
issue_time=datetime.now().isoformat() + 'Z',
9898
params={"timestamp": datetime.now().timestamp() * 1000, "testcount": 1})
9999

100-
print(f'Issuing Command: {command_json.model_dump_json(exclude_none=True, by_alias=True)}')
101-
cmd_resp = Commands.send_commands_to_specific_control_stream(server_url, control_streams["items"][0]["id"],
102-
command_json.model_dump_json(exclude_none=True,
103-
by_alias=True),
104-
headers=json_headers)
105-
print(f'\n*****Command Response: {cmd_resp}*****')
100+
# print(f'Issuing Command: {command_json.model_dump_json(exclude_none=True, by_alias=True)}')
101+
# cmd_resp = Commands.send_commands_to_specific_control_stream(server_url, control_streams["items"][0]["id"],
102+
# command_json.model_dump_json(exclude_none=True,
103+
# by_alias=True),
104+
# headers=json_headers)
105+
# try issuing a command from the MQTT client
106+
mqtt_client.publish(f'/api/controls/{control_id}/commands', command_json.model_dump_json(exclude_none=True,
107+
by_alias=True),
108+
1)
109+
# print(f'\n*****Command Response: {cmd_resp}*****')
106110
status_resp = {
107111
'id': '*******',
108112
'command@id': "unknown",
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import pytest
2+
3+
from datetime import datetime
4+
5+
from conSys import Systems, SamplingFeatures, Datastreams, SmlJSONBody, GeoJSONBody, model_utils, \
6+
DatastreamBodyJSON, ObservationFormat, URI, Procedures, Geometry, Deployments, ControlChannels, Observations, \
7+
Commands
8+
from conSys.datamodels.control_streams import ControlStreamJSONSchema, SWEControlChannelSchema, JSONControlChannelSchema
9+
from conSys.datamodels.datastreams import SWEDatastreamSchema
10+
from conSys.datamodels.encoding import JSONEncoding
11+
from conSys.datamodels.swe_components import BooleanSchema, TimeSchema, DataRecordSchema, CountSchema
12+
from conSys.datamodels.observations import ObservationOMJSONInline
13+
from conSys.datamodels.commands import CommandJSON
14+
from conSys.datamodels.system_events_and_history import SystemEventOMJSON
15+
16+
server_url = "http://localhost:8282/sensorhub"
17+
geo_json_headers = {"Content-Type": "application/geo+json"}
18+
sml_json_headers = {"Content-Type": "application/sml+json"}
19+
json_headers = {"Content-Type": "application/json"}
20+
21+
system_json = []
22+
retrieved_systems = []
23+
procedure_json = []
24+
deployment_json = []
25+
component_json = []
26+
command_json = []
27+
control_channel_json = []
28+
test_time_start = datetime.utcnow()
29+
30+
31+
def test_add_system_events():
32+
sys_event_schema = SystemEventOMJSON(label="Test System Event", definition="http://test.com/SystemEvent",
33+
time=test_time_start.isoformat() + 'Z')
34+
35+
resp = Systems.add_system_events_to_system(server_url, sys_event_schema.model_dump_json(exclude_none=True,
36+
by_alias=True),
37+
headers=json_headers)

0 commit comments

Comments
 (0)