Skip to content

Commit feb82a8

Browse files
Merge pull request #20 from Botts-Innovative-Research/csapi-4-py-integration
Remove external dependency on CSAPI4Py by rolling that code into this library. Add publishing workflow Also update workflows to use more recent plugins
2 parents 111c7a4 + 28549ad commit feb82a8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+3501
-425
lines changed

.github/workflows/docs_pages.yaml

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
name: Docs2Pages
2-
on: [push, pull_request, workflow_dispatch]
2+
on: [ push, pull_request, workflow_dispatch ]
33
permissions:
44
contents: write
55

66
jobs:
77
build-docs:
88
runs-on: ubuntu-latest
99
steps:
10-
- name: Checkout
11-
uses: actions/checkout@v3
12-
- uses: actions/setup-python@v5
13-
with:
14-
python-version: '3.12'
10+
- name: Checkout
11+
uses: actions/checkout@v5
1512

16-
- name: Install dependencies
17-
run: |
18-
pip install uv
19-
uv sync --all-extras
13+
- name: Install uv
14+
uses: astral-sh/setup-uv@v6
2015

21-
- name: Sphinx build
22-
run: |
23-
uv run sphinx-build -b html docs/source docs/build/html
16+
- name: Install Python 3.13
17+
run: uv python install 3.13
2418

25-
- name: Deploy documentation
26-
uses: peaceiris/actions-gh-pages@v4
27-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
28-
with:
29-
publish_branch: gh-pages
30-
github_token: ${{ secrets.GITHUB_TOKEN }}
31-
publish_dir: ./docs/build/html
32-
force_orphan: true
19+
- name: Install dependencies
20+
run: uv sync --all-extras
21+
22+
- name: Sphinx build
23+
run: |
24+
uv run sphinx-build -b html docs/source docs/build/html
25+
26+
- name: Deploy documentation
27+
uses: peaceiris/actions-gh-pages@v4
28+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
29+
with:
30+
publish_branch: gh-pages
31+
github_token: ${{ secrets.GITHUB_TOKEN }}
32+
publish_dir: ./docs/build/html
33+
force_orphan: true

.github/workflows/linting.yaml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ jobs:
44
lint:
55
runs-on: ubuntu-latest
66
steps:
7-
- uses: actions/checkout@v3
8-
- uses: actions/setup-python@v5
9-
with:
10-
python-version: '3.12'
7+
- name: Checkout
8+
uses: actions/checkout@v5
9+
10+
- name: Install uv
11+
uses: astral-sh/setup-uv@v6
12+
13+
- name: Install Python 3.13
14+
run: uv python install 3.13
1115

1216
- name: Install dependencies
13-
run: |
14-
pip install uv
15-
uv sync --all-extras
17+
run: uv sync --all-extras
1618

1719
- name: Lint
1820
run: |

.github/workflows/publish.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: publish.yml
2+
on:
3+
push:
4+
tags:
5+
# publishes any tag starting with 'v' as in 'v.1.0'
6+
- v*
7+
8+
jobs:
9+
run:
10+
runs-on: ubuntu-latest
11+
environment:
12+
name: pypi
13+
permissions:
14+
id-token: write
15+
contents: read
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v5
19+
- name: Install uv
20+
uses: astral-sh/setup-uv@v6
21+
- name: Install Python 3.13
22+
run: uv python install 3.13
23+
- name: Build
24+
run: uv build
25+
# Need to add a test that verifies the builds
26+
- name: Publish
27+
run: uv publish

oshconnect/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
# Contact Email: ian@botts-inc.com
66
# ==============================================================================
77

8-
class Example:
9-
pass
8+
from .oshconnectapi import OSHConnect
9+
from .osh_connect_datamodels import System, Node, Datastream, Observation, ControlChannel

oshconnect/control.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
# Contact Email: ian@botts-inc.com
66
# ==============================================================================
77
import websockets
8-
from consys4py.comm.mqtt import MQTTCommClient
9-
from consys4py.datamodels.commands import CommandJSON
10-
from consys4py.datamodels.control_streams import ControlStreamJSONSchema
8+
from oshconnect.csapi4py.comm.mqtt import MQTTCommClient
9+
from oshconnect.datamodels.commands import CommandJSON
10+
from oshconnect.datamodels.control_streams import ControlStreamJSONSchema
1111

1212
from oshconnect.osh_connect_datamodels import System
1313

oshconnect/core_datamodels.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99
from typing import List
1010

11-
from consys4py.datamodels.swe_components import GeometrySchema
12-
from consys4py.datamodels.datastreams import DatastreamSchema
13-
from consys4py.datamodels.api_utils import Link
11+
from oshconnect.datamodels.geometry import Geometry
12+
from oshconnect.datamodels.datastreams import DatastreamSchema
13+
from oshconnect.datamodels.api_utils import Link
1414
from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny
1515
from shapely import Point
1616

@@ -94,7 +94,7 @@ class SystemResource(BaseModel):
9494
feature_type: str = Field(None, serialization_alias="type")
9595
system_id: str = Field(None, serialization_alias="id")
9696
properties: dict = Field(None)
97-
geometry: GeometrySchema | None = Field(None)
97+
geometry: Geometry | None = Field(None)
9898
bbox: BoundingBox = Field(None)
9999
links: List[Link] = Field(None)
100100
description: str = Field(None)

oshconnect/csapi4py/__init__.py

Whitespace-only changes.

oshconnect/csapi4py/comm/__init__.py

Whitespace-only changes.

oshconnect/csapi4py/comm/mqtt.py

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import paho.mqtt.client as mqtt
2+
3+
4+
class MQTTCommClient:
5+
def __init__(self, url, port=1883, username=None, password=None, path='mqtt', client_id="", transport='tcp'):
6+
"""
7+
Wraps a paho mqtt client to provide a simple interface for interacting with the mqtt server that is customized
8+
for this library.
9+
10+
:param url: url of the mqtt server
11+
:param port: port the mqtt server is communicating over, default is 1883 or whichever port the main node is
12+
using if in websocket mode
13+
:param username: used if node is requiring authentication to access this service
14+
:param password: used if node is requiring authentication to access this service
15+
:param path: used for setting the path when using websockets (usually sensorhub/mqtt by default)
16+
"""
17+
self.__url = url
18+
self.__port = port
19+
self.__path = path
20+
self.__client_id = client_id
21+
self.__transport = transport
22+
23+
self.__client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=client_id)
24+
25+
if self.__transport == 'websockets':
26+
self.__client.ws_set_options(path=self.__path)
27+
28+
if username is not None and password is not None:
29+
self.__client.username_pw_set(username, password)
30+
self.__client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLSv1_2)
31+
32+
self.__client.on_connect = self.on_connect
33+
self.__client.on_subscribe = self.on_subscribe
34+
self.__client.on_message = self.on_message
35+
self.__client.on_publish = self.on_publish
36+
self.__client.on_log = self.on_log
37+
self.__client.on_disconnect = self.on_disconnect
38+
39+
self.__is_connected = False
40+
41+
@staticmethod
42+
def on_connect(client, userdata, flags, rc, properties):
43+
print(f'Connected with result code: {rc}')
44+
print(f'{properties}')
45+
46+
@staticmethod
47+
def on_subscribe(client, userdata, mid, granted_qos, properties):
48+
print(f'Subscribed: {mid} {granted_qos}')
49+
50+
@staticmethod
51+
def on_message(client, userdata, msg):
52+
print(f'{msg.payload.decode("utf-8")}')
53+
54+
@staticmethod
55+
def on_publish(client, userdata, mid, info, properties):
56+
print(f'Published: {mid}')
57+
58+
@staticmethod
59+
def on_log(client, userdata, level, buf):
60+
print(f'Log: {buf}')
61+
62+
@staticmethod
63+
def on_disconnect(client, userdata, dc_flag, rc, properties):
64+
print(f'Client {client} disconnected: {dc_flag} {rc}')
65+
66+
def connect(self, keepalive=60):
67+
# print(f'Connecting to {self.__url}:{self.__port}')
68+
self.__client.connect(self.__url, self.__port, keepalive=keepalive)
69+
70+
def subscribe(self, topic, qos=0, msg_callback=None):
71+
"""
72+
Subscribe to a topic, and optionally set a callback for when a message is received on that topic. To actually
73+
retrieve any information you must set a callback.
74+
75+
:param topic: MQTT topic to subscribe to (example/topic)
76+
:param qos: quality of service, 0, 1, or 2
77+
:param msg_callback: callback with the form: callback(client, userdata, msg)
78+
:return:
79+
"""
80+
self.__client.subscribe(topic, qos)
81+
if msg_callback is not None:
82+
self.__client.message_callback_add(topic, msg_callback)
83+
84+
def publish(self, topic, payload=None, qos=0, retain=False):
85+
self.__client.publish(topic, payload, qos, retain=retain)
86+
87+
def unsubscribe(self, topic):
88+
self.__client.unsubscribe(topic)
89+
90+
def disconnect(self):
91+
self.__client.disconnect()
92+
93+
def set_on_connect(self, on_connect):
94+
"""
95+
Set the on_connect callback for the MQTT client.
96+
97+
:param on_connect:
98+
:return:
99+
"""
100+
self.__client.on_connect = on_connect
101+
102+
def set_on_disconnect(self, on_disconnect):
103+
"""
104+
Set the on_disconnect callback for the MQTT client.
105+
106+
:param on_disconnect:
107+
:return:
108+
"""
109+
self.__client.on_disconnect = on_disconnect
110+
111+
def set_on_subscribe(self, on_subscribe):
112+
"""
113+
Set the on_subscribe callback for the MQTT client.
114+
115+
:param on_subscribe:
116+
:return:
117+
"""
118+
self.__client.on_subscribe = on_subscribe
119+
120+
def set_on_unsubscribe(self, on_unsubscribe):
121+
"""
122+
Set the on_unsubscribe callback for the MQTT client.
123+
124+
:param on_unsubscribe:
125+
:return:
126+
"""
127+
self.__client.on_unsubscribe = on_unsubscribe
128+
129+
def set_on_publish(self, on_publish):
130+
"""
131+
Set the on_publish callback for the MQTT client.
132+
133+
:param on_publish:
134+
:return:
135+
"""
136+
self.__client.on_publish = on_publish
137+
138+
def set_on_message(self, on_message):
139+
"""
140+
Set the on_message callback for the MQTT client. It is recommended to set individual callbacks for each
141+
subscribed topic.
142+
143+
:param on_message:
144+
:return:
145+
"""
146+
self.__client.on_message = on_message
147+
148+
def set_on_log(self, on_log):
149+
"""
150+
Set the on_log callback for the MQTT client.
151+
152+
:param on_log:
153+
:return:
154+
"""
155+
self.__client.on_log = on_log
156+
157+
def set_on_message_callback(self, sub, on_message_callback):
158+
"""
159+
Set the on_message callback for a specific topic.
160+
:param sub:
161+
:param on_message_callback:
162+
:return:
163+
"""
164+
self.__client.message_callback_add(sub, on_message_callback)
165+
166+
def start(self):
167+
"""
168+
Start the MQTT client in a separate thread. This is required for the client to be able to receive messages.
169+
170+
:return:
171+
"""
172+
self.__client.loop_start()
173+
174+
def stop(self):
175+
"""
176+
Stop the MQTT client.\
177+
178+
:return:
179+
"""
180+
self.__client.loop_stop()
181+
182+
def __toggle_is_connected(self):
183+
self.__is_connected = not self.__is_connected
184+
185+
def is_connected(self):
186+
return self.__is_connected
187+
188+
@staticmethod
189+
def publish_single(self, topic, msg):
190+
self.__client.single(topic, msg, 0)
191+
192+
@staticmethod
193+
def publish_multiple(self, topic, msgs):
194+
self.__client.multiple(msgs, )
195+
196+
def tls_set(self):
197+
self.__client.tls_set()

0 commit comments

Comments
 (0)