farsounder 0.1.0__py3-none-any.whl → 0.1.1__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.
- farsounder/client/requests.py +29 -26
- farsounder/client/subscriber.py +77 -60
- {farsounder-0.1.0.dist-info → farsounder-0.1.1.dist-info}/METADATA +37 -14
- {farsounder-0.1.0.dist-info → farsounder-0.1.1.dist-info}/RECORD +8 -7
- farsounder-0.1.1.dist-info/licenses/LICENSE +15 -0
- {farsounder-0.1.0.dist-info → farsounder-0.1.1.dist-info}/WHEEL +0 -0
- {farsounder-0.1.0.dist-info → farsounder-0.1.1.dist-info}/entry_points.txt +0 -0
- {farsounder-0.1.0.dist-info → farsounder-0.1.1.dist-info}/top_level.txt +0 -0
farsounder/client/requests.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
from typing import TypeVar
|
|
5
4
|
|
|
6
5
|
import httpx
|
|
7
6
|
import zmq
|
|
8
|
-
import zmq.asyncio
|
|
9
7
|
from google.protobuf.message import Message
|
|
10
8
|
|
|
11
9
|
from farsounder.proto.proto import nav_api_pb2 as nav_api
|
|
@@ -21,24 +19,27 @@ from farsounder.client.history_types import HistoryData
|
|
|
21
19
|
ResponseT = TypeVar("ResponseT", bound=Message)
|
|
22
20
|
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
def _request(
|
|
25
23
|
config: ClientConfig,
|
|
26
24
|
endpoint: ReqRepEndpoint,
|
|
27
25
|
request: Message,
|
|
28
26
|
response_cls: type[ResponseT],
|
|
29
27
|
) -> ResponseT:
|
|
30
|
-
ctx = zmq.
|
|
28
|
+
ctx = zmq.Context.instance()
|
|
31
29
|
socket = ctx.socket(zmq.REQ)
|
|
32
30
|
try:
|
|
33
31
|
port = resolve_reqrep_port(config, endpoint)
|
|
34
32
|
socket.connect(f"tcp://{config.host}:{port}")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
socket.send(request.SerializeToString())
|
|
34
|
+
poller = zmq.Poller()
|
|
35
|
+
poller.register(socket, zmq.POLLIN)
|
|
36
|
+
timeout_ms = int(config.timeout_seconds * 1000)
|
|
37
|
+
events = dict(poller.poll(timeout=timeout_ms))
|
|
38
|
+
if socket not in events:
|
|
39
39
|
raise RequestTimeoutError(
|
|
40
40
|
f"Timeout waiting for {endpoint} after {config.timeout_seconds:.1f}s"
|
|
41
|
-
)
|
|
41
|
+
)
|
|
42
|
+
data = socket.recv()
|
|
42
43
|
finally:
|
|
43
44
|
socket.close(linger=0)
|
|
44
45
|
|
|
@@ -47,7 +48,7 @@ async def _request(
|
|
|
47
48
|
return response
|
|
48
49
|
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
def get_processor_settings(
|
|
51
52
|
config: ClientConfig,
|
|
52
53
|
) -> nav_api.GetProcessorSettingsResponse:
|
|
53
54
|
"""Request the current processor settings.
|
|
@@ -64,7 +65,7 @@ async def get_processor_settings(
|
|
|
64
65
|
"""
|
|
65
66
|
|
|
66
67
|
request = nav_api.GetProcessorSettingsRequest()
|
|
67
|
-
return
|
|
68
|
+
return _request(
|
|
68
69
|
config,
|
|
69
70
|
"GetProcessorSettings",
|
|
70
71
|
request,
|
|
@@ -72,7 +73,7 @@ async def get_processor_settings(
|
|
|
72
73
|
)
|
|
73
74
|
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
def set_field_of_view(
|
|
76
77
|
fov: nav_api.FieldOfView,
|
|
77
78
|
config: ClientConfig,
|
|
78
79
|
) -> nav_api.SetFieldOfViewResponse:
|
|
@@ -92,7 +93,7 @@ async def set_field_of_view(
|
|
|
92
93
|
"""
|
|
93
94
|
|
|
94
95
|
request = nav_api.SetFieldOfViewRequest(fov=fov)
|
|
95
|
-
return
|
|
96
|
+
return _request(
|
|
96
97
|
config,
|
|
97
98
|
"SetFieldOfView",
|
|
98
99
|
request,
|
|
@@ -100,7 +101,7 @@ async def set_field_of_view(
|
|
|
100
101
|
)
|
|
101
102
|
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
def set_bottom_detection(
|
|
104
105
|
enable: bool,
|
|
105
106
|
config: ClientConfig,
|
|
106
107
|
) -> nav_api.SetBottomDetectionResponse:
|
|
@@ -120,7 +121,7 @@ async def set_bottom_detection(
|
|
|
120
121
|
"""
|
|
121
122
|
|
|
122
123
|
request = nav_api.SetBottomDetectionRequest(enable_bottom_detection=enable)
|
|
123
|
-
return
|
|
124
|
+
return _request(
|
|
124
125
|
config,
|
|
125
126
|
"SetBottomDetection",
|
|
126
127
|
request,
|
|
@@ -128,7 +129,7 @@ async def set_bottom_detection(
|
|
|
128
129
|
)
|
|
129
130
|
|
|
130
131
|
|
|
131
|
-
|
|
132
|
+
def set_inwater_squelch(
|
|
132
133
|
value: float,
|
|
133
134
|
config: ClientConfig,
|
|
134
135
|
) -> nav_api.SetInWaterSquelchResponse:
|
|
@@ -148,7 +149,7 @@ async def set_inwater_squelch(
|
|
|
148
149
|
"""
|
|
149
150
|
|
|
150
151
|
request = nav_api.SetInWaterSquelchRequest(new_squelch_val=value)
|
|
151
|
-
return
|
|
152
|
+
return _request(
|
|
152
153
|
config,
|
|
153
154
|
"SetInWaterSquelch",
|
|
154
155
|
request,
|
|
@@ -156,7 +157,7 @@ async def set_inwater_squelch(
|
|
|
156
157
|
)
|
|
157
158
|
|
|
158
159
|
|
|
159
|
-
|
|
160
|
+
def set_squelchless_inwater_detector(
|
|
160
161
|
enable: bool,
|
|
161
162
|
config: ClientConfig,
|
|
162
163
|
) -> nav_api.SetSquelchlessInWaterDetectorResponse:
|
|
@@ -178,7 +179,7 @@ async def set_squelchless_inwater_detector(
|
|
|
178
179
|
request = nav_api.SetSquelchlessInWaterDetectorRequest(
|
|
179
180
|
enable_squelchless_detection=enable
|
|
180
181
|
)
|
|
181
|
-
return
|
|
182
|
+
return _request(
|
|
182
183
|
config,
|
|
183
184
|
"SetSquelchlessInWaterDetector",
|
|
184
185
|
request,
|
|
@@ -186,7 +187,7 @@ async def set_squelchless_inwater_detector(
|
|
|
186
187
|
)
|
|
187
188
|
|
|
188
189
|
|
|
189
|
-
|
|
190
|
+
def get_vessel_info(
|
|
190
191
|
config: ClientConfig,
|
|
191
192
|
) -> nav_api.GetVesselInfoResponse:
|
|
192
193
|
"""Request the current vessel info.
|
|
@@ -203,9 +204,7 @@ async def get_vessel_info(
|
|
|
203
204
|
"""
|
|
204
205
|
|
|
205
206
|
request = nav_api.GetVesselInfoRequest()
|
|
206
|
-
return
|
|
207
|
-
config, "GetVesselInfo", request, nav_api.GetVesselInfoResponse
|
|
208
|
-
)
|
|
207
|
+
return _request(config, "GetVesselInfo", request, nav_api.GetVesselInfoResponse)
|
|
209
208
|
|
|
210
209
|
|
|
211
210
|
def _build_history_params(
|
|
@@ -233,7 +232,7 @@ def _build_history_params(
|
|
|
233
232
|
return params
|
|
234
233
|
|
|
235
234
|
|
|
236
|
-
|
|
235
|
+
def get_history_data(
|
|
237
236
|
config: ClientConfig,
|
|
238
237
|
*,
|
|
239
238
|
latitude: float,
|
|
@@ -287,13 +286,17 @@ async def get_history_data(
|
|
|
287
286
|
)
|
|
288
287
|
url = f"http://{config.host}:{port}/api/history_data"
|
|
289
288
|
try:
|
|
290
|
-
|
|
291
|
-
response =
|
|
289
|
+
with httpx.Client(timeout=config.timeout_seconds) as client:
|
|
290
|
+
response = client.get(url, params=params)
|
|
292
291
|
response.raise_for_status()
|
|
293
292
|
except httpx.ConnectError as exc:
|
|
294
293
|
raise httpx.ConnectError(
|
|
295
294
|
f"Failed to connect to {url}, is the server running?"
|
|
296
295
|
) from exc
|
|
296
|
+
except httpx.ConnectTimeout as exc:
|
|
297
|
+
raise RequestTimeoutError(
|
|
298
|
+
f"Timeout waiting for {url} after {config.timeout_seconds:.1f}s"
|
|
299
|
+
) from exc
|
|
297
300
|
|
|
298
301
|
history_count = None
|
|
299
302
|
if include_count:
|
farsounder/client/subscriber.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import logging
|
|
4
|
+
import threading
|
|
5
5
|
from collections.abc import Callable, Iterable
|
|
6
|
-
from
|
|
6
|
+
from concurrent.futures import Future, ThreadPoolExecutor
|
|
7
|
+
from typing import Literal
|
|
7
8
|
|
|
8
9
|
import zmq
|
|
9
|
-
import zmq.asyncio
|
|
10
10
|
from google.protobuf.message import Message
|
|
11
11
|
|
|
12
12
|
from farsounder.proto.proto import nav_api_pb2 as nav_api
|
|
@@ -19,7 +19,7 @@ from farsounder.client.config import (
|
|
|
19
19
|
|
|
20
20
|
logger = logging.getLogger(__name__)
|
|
21
21
|
|
|
22
|
-
MessageCallback = Callable[[Message],
|
|
22
|
+
MessageCallback = Callable[[Message], None]
|
|
23
23
|
|
|
24
24
|
PUBSUB_MESSAGE_CLASSES: dict[PubSubMessage, type[Message]] = {
|
|
25
25
|
"HydrophoneData": nav_api.HydrophoneData,
|
|
@@ -30,18 +30,20 @@ PUBSUB_MESSAGE_CLASSES: dict[PubSubMessage, type[Message]] = {
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class Subscription:
|
|
33
|
-
"""
|
|
33
|
+
"""Subscription to pub-sub messages using background threads."""
|
|
34
34
|
|
|
35
35
|
def __init__(self, config: ClientConfig) -> None:
|
|
36
36
|
self._config = config
|
|
37
37
|
self._callbacks: dict[PubSubMessage, list[MessageCallback]] = {
|
|
38
38
|
message_type: [] for message_type in PUBSUB_MESSAGE_CLASSES
|
|
39
39
|
}
|
|
40
|
-
self.
|
|
41
|
-
self._sockets: dict[PubSubMessage, zmq.asyncio.Socket] = {}
|
|
42
|
-
self._tasks: list[asyncio.Task[None]] = []
|
|
40
|
+
self._threads: list[threading.Thread] = []
|
|
43
41
|
self._running = False
|
|
42
|
+
self._stop_event = threading.Event()
|
|
44
43
|
self._use_threadpool = config.callback_executor == "threadpool"
|
|
44
|
+
self._executor: ThreadPoolExecutor | None = (
|
|
45
|
+
ThreadPoolExecutor() if self._use_threadpool else None
|
|
46
|
+
)
|
|
45
47
|
|
|
46
48
|
def on(self, message_type: PubSubMessage, callback: MessageCallback) -> None:
|
|
47
49
|
"""Register a callback for a pub-sub message type.
|
|
@@ -59,92 +61,107 @@ class Subscription:
|
|
|
59
61
|
raise ValueError(f"Unknown pub-sub message type: {message_type}")
|
|
60
62
|
self._callbacks[message_type].append(callback)
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
"""Start background receive
|
|
64
|
+
def start(self) -> None:
|
|
65
|
+
"""Start background receive threads for subscribed message types."""
|
|
64
66
|
|
|
65
67
|
if self._running:
|
|
66
68
|
return
|
|
67
69
|
self._running = True
|
|
70
|
+
self._stop_event.clear()
|
|
71
|
+
if self._use_threadpool and self._executor is None:
|
|
72
|
+
self._executor = ThreadPoolExecutor()
|
|
68
73
|
|
|
69
74
|
for message_type in self._config.subscribe:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
self._tasks.append(
|
|
76
|
-
asyncio.create_task(self._recv_loop(message_type, socket))
|
|
75
|
+
thread = threading.Thread(
|
|
76
|
+
target=self._recv_loop,
|
|
77
|
+
args=(message_type,),
|
|
78
|
+
name=f"farsounder-sub-{message_type}",
|
|
79
|
+
daemon=True,
|
|
77
80
|
)
|
|
81
|
+
thread.start()
|
|
82
|
+
self._threads.append(thread)
|
|
78
83
|
|
|
79
|
-
|
|
80
|
-
"""Stop background receive
|
|
84
|
+
def stop(self) -> None:
|
|
85
|
+
"""Stop background receive threads."""
|
|
81
86
|
|
|
82
87
|
if not self._running:
|
|
83
88
|
return
|
|
84
89
|
self._running = False
|
|
90
|
+
self._stop_event.set()
|
|
85
91
|
|
|
86
|
-
for
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
self._tasks.clear()
|
|
92
|
+
for thread in self._threads:
|
|
93
|
+
thread.join()
|
|
94
|
+
self._threads.clear()
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
if self._executor is not None:
|
|
97
|
+
self._executor.shutdown(wait=True)
|
|
98
|
+
self._executor = None
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
def __enter__(self) -> "Subscription":
|
|
101
|
+
self.start()
|
|
97
102
|
return self
|
|
98
103
|
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
105
|
+
self.stop()
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
self, message_type
|
|
104
|
-
|
|
107
|
+
def _recv_loop(self, message_type: PubSubMessage) -> None:
|
|
108
|
+
port = resolve_pubsub_port(self._config, message_type)
|
|
109
|
+
socket = zmq.Context.instance().socket(zmq.SUB)
|
|
110
|
+
socket.setsockopt(zmq.SUBSCRIBE, b"")
|
|
111
|
+
socket.connect(f"tcp://{self._config.host}:{port}")
|
|
112
|
+
poller = zmq.Poller()
|
|
113
|
+
poller.register(socket, zmq.POLLIN)
|
|
105
114
|
message_cls = PUBSUB_MESSAGE_CLASSES[message_type]
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
try:
|
|
116
|
+
while not self._stop_event.is_set():
|
|
117
|
+
try:
|
|
118
|
+
events = dict(poller.poll(timeout=1000))
|
|
119
|
+
except Exception:
|
|
120
|
+
if self._stop_event.is_set():
|
|
121
|
+
break
|
|
122
|
+
logger.exception("Failed to poll %s", message_type)
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
if socket not in events:
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
data = socket.recv()
|
|
130
|
+
except Exception:
|
|
131
|
+
if self._stop_event.is_set():
|
|
132
|
+
break
|
|
133
|
+
logger.exception("Failed to receive %s", message_type)
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
message = message_cls()
|
|
138
|
+
message.ParseFromString(data)
|
|
139
|
+
except Exception:
|
|
140
|
+
logger.exception("Failed to parse %s", message_type)
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
self._dispatch(message_type, message)
|
|
144
|
+
finally:
|
|
145
|
+
socket.close(linger=0)
|
|
123
146
|
|
|
124
147
|
def _dispatch(self, message_type: PubSubMessage, message: Message) -> None:
|
|
125
148
|
callbacks = list(self._callbacks.get(message_type, []))
|
|
126
149
|
if not callbacks:
|
|
127
150
|
return
|
|
128
151
|
|
|
129
|
-
loop = asyncio.get_running_loop()
|
|
130
152
|
for callback in callbacks:
|
|
131
|
-
if self._use_threadpool and not
|
|
132
|
-
future =
|
|
153
|
+
if self._use_threadpool and self._executor is not None:
|
|
154
|
+
future = self._executor.submit(callback, message)
|
|
133
155
|
future.add_done_callback(self._log_task_result)
|
|
134
156
|
continue
|
|
135
157
|
|
|
136
158
|
try:
|
|
137
|
-
|
|
159
|
+
callback(message)
|
|
138
160
|
except Exception:
|
|
139
161
|
logger.exception("Callback error for %s", message_type)
|
|
140
|
-
continue
|
|
141
|
-
|
|
142
|
-
if asyncio.iscoroutine(result):
|
|
143
|
-
task = asyncio.create_task(result)
|
|
144
|
-
task.add_done_callback(self._log_task_result)
|
|
145
162
|
|
|
146
163
|
@staticmethod
|
|
147
|
-
def _log_task_result(task:
|
|
164
|
+
def _log_task_result(task: Future[object]) -> None:
|
|
148
165
|
try:
|
|
149
166
|
task.result()
|
|
150
167
|
except Exception:
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: farsounder
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Python client to communicate with FarSounder's ARGOS sensors
|
|
5
5
|
Author-email: FarSounder Software Team <sw@farsounder.com>
|
|
6
6
|
Requires-Python: >=3.13
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
8
9
|
Requires-Dist: httpx
|
|
9
10
|
Requires-Dist: protobuf
|
|
10
11
|
Requires-Dist: pyzmq
|
|
12
|
+
Dynamic: license-file
|
|
11
13
|
|
|
12
|
-
#
|
|
14
|
+
# Python API Client for Argos data
|
|
13
15
|
|
|
14
16
|
Python client to communicate with SonaSoft via API.
|
|
15
17
|
|
|
18
|
+
>[!NOTE]
|
|
19
|
+
> This is still under active development and testing. But may serve as an example for
|
|
20
|
+
> integration, beyond the lower level example in [sdk repo](https://github.com/farsounder/SDK-Integration-Examples).
|
|
21
|
+
> For now the docs are the interfaces and the examples in examples/
|
|
22
|
+
|
|
16
23
|
## Usage
|
|
17
24
|
|
|
18
25
|
Clone locally and install (uv add .) or install from pypi like:
|
|
@@ -25,47 +32,63 @@ uv add farsounder
|
|
|
25
32
|
Then - for example to subscribe to `TargetData` messages:
|
|
26
33
|
|
|
27
34
|
```python
|
|
28
|
-
import
|
|
35
|
+
import time
|
|
29
36
|
|
|
30
37
|
from farsounder import config, requests, subscriber
|
|
31
38
|
from farsounder.proto import nav_api_pb2
|
|
32
39
|
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
def main() -> None:
|
|
42
|
+
|
|
43
|
+
# Build the configuration:
|
|
44
|
+
# - where is the api running?
|
|
45
|
+
# - what messages do you care about subscribing to?
|
|
46
|
+
# - if ports are changed, they can overridden here
|
|
35
47
|
cfg = config.build_config(
|
|
36
48
|
host="127.0.0.1",
|
|
37
49
|
subscribe=["TargetData"],
|
|
38
50
|
)
|
|
39
51
|
|
|
40
|
-
sub = subscriber.subscribe(config)
|
|
41
52
|
|
|
53
|
+
# Pub/Sub
|
|
54
|
+
sub = subscriber.subscribe(cfg)
|
|
55
|
+
# A callback to run when there are new target data messages
|
|
56
|
+
# - don't do a lot of processing in here it will block (thank GIL - for now...)
|
|
57
|
+
# - use sync mechanism if you're accessing shared data (callbacks
|
|
58
|
+
# run on thread pool [default] or on the receive thread for the message
|
|
59
|
+
# type)
|
|
42
60
|
def on_targets(message: nav_api_pb2.TargetData) -> None:
|
|
43
61
|
print("Got a TargetData!")
|
|
44
62
|
print("Targets:", len(message.groups))
|
|
45
63
|
|
|
46
|
-
#
|
|
64
|
+
# register the callback(s)
|
|
47
65
|
sub.on("TargetData", on_targets)
|
|
48
|
-
|
|
66
|
+
|
|
67
|
+
# start the receive loop
|
|
68
|
+
sub.start()
|
|
49
69
|
|
|
50
70
|
# Req/rep
|
|
51
|
-
|
|
71
|
+
# for each of the request / reply endpoints - there are specific
|
|
72
|
+
# get/set functions
|
|
73
|
+
settings = requests.get_processor_settings(cfg)
|
|
52
74
|
print(settings)
|
|
53
75
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
config,
|
|
76
|
+
history = requests.get_history_data(
|
|
77
|
+
cfg,
|
|
57
78
|
latitude=41.7223,
|
|
58
79
|
longitude=-71.35,
|
|
59
80
|
radius_meters=500,
|
|
60
81
|
)
|
|
61
82
|
print(history.gridded_bottom_detections)
|
|
62
83
|
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
time.sleep(1.0)
|
|
85
|
+
|
|
86
|
+
# Stop receive loop
|
|
87
|
+
sub.stop()
|
|
65
88
|
|
|
66
89
|
|
|
67
90
|
if __name__ == "__main__":
|
|
68
|
-
|
|
91
|
+
main()
|
|
69
92
|
```
|
|
70
93
|
|
|
71
94
|
## Simulated backend
|
|
@@ -3,8 +3,8 @@ farsounder/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
|
3
3
|
farsounder/client/config.py,sha256=toTQ9hjLps4OJJYdN8WMk9O3yRTTmUQbKDLXWdZmxM0,4596
|
|
4
4
|
farsounder/client/exceptions.py,sha256=CLMtiQwe7zRKL4swq5Xl8p1Xo_jutpKov3YCmDMp0PI,88
|
|
5
5
|
farsounder/client/history_types.py,sha256=NkZV9wwPdUMOz_hamyUjMvx8p9BPbAE6WNKA1OZd7yQ,3358
|
|
6
|
-
farsounder/client/requests.py,sha256=
|
|
7
|
-
farsounder/client/subscriber.py,sha256=
|
|
6
|
+
farsounder/client/requests.py,sha256=LhX1KYQv91k_GlHZp2T3qC5Xu7ggT32pSZHZfSo1IJQ,8218
|
|
7
|
+
farsounder/client/subscriber.py,sha256=O_KWCR0kqt8E1cfiKNypAGS8gC-fL68idK2Vj1ybFdE,6803
|
|
8
8
|
farsounder/proto/__init__.py,sha256=srUEbuHuHHVMvPgQY9z4PmprVai78cleAYN34oiKcHI,824
|
|
9
9
|
farsounder/proto/proto/__init__.py,sha256=jNeAvw99gdQ6m2XO8vtp05Sp3v-MVFzPhDxtttOBXUg,76
|
|
10
10
|
farsounder/proto/proto/array_pb2.py,sha256=7q7vr6AtUIc1z31Q521XRHb5CeCuZEFvmp5E4nPLWuo,2170
|
|
@@ -19,8 +19,9 @@ farsounder/proto/proto/nmea_pb2.py,sha256=jTmHhkrP1EOJxIy9coh2WM2_Po_2BUt41ovGqV
|
|
|
19
19
|
farsounder/proto/proto/nmea_pb2.pyi,sha256=Jz18Kr61182b1JLNLSSoajuODeIGP4vzQnjImxRw9AE,795
|
|
20
20
|
farsounder/proto/proto/time_pb2.py,sha256=WEc9S8-Qk0mE3W5H76ruancCxKrA5y6sGYkM4Jjz2tU,1542
|
|
21
21
|
farsounder/proto/proto/time_pb2.pyi,sha256=dVAMCH_pxbYeCKDgIBAZPYa9Rpj52hB8MXeRFWyPeD0,950
|
|
22
|
-
farsounder-0.1.
|
|
23
|
-
farsounder-0.1.
|
|
24
|
-
farsounder-0.1.
|
|
25
|
-
farsounder-0.1.
|
|
26
|
-
farsounder-0.1.
|
|
22
|
+
farsounder-0.1.1.dist-info/licenses/LICENSE,sha256=Emjj_IK4M3YYqy_ziQr96CSknkIfzQG3za-kQw9gcW0,908
|
|
23
|
+
farsounder-0.1.1.dist-info/METADATA,sha256=Fj731yUyJqy83iJtWRz_FxQx88tM2bWKLAREpzIRpBU,3083
|
|
24
|
+
farsounder-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
25
|
+
farsounder-0.1.1.dist-info/entry_points.txt,sha256=JKbKYe7jA1DvRuucj2jO8mXd3Bipb2BoPvxF0clcQWg,64
|
|
26
|
+
farsounder-0.1.1.dist-info/top_level.txt,sha256=B7BR-Oj3C_D1nEICNpZt19anMsV8Ozxt836-R4VhTcs,11
|
|
27
|
+
farsounder-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Licensing
|
|
2
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
3
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
4
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
5
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
6
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
7
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
8
|
+
SOFTWARE.
|
|
9
|
+
|
|
10
|
+
The SonaSoft SDK is provided free of charge for non-commercial applications,
|
|
11
|
+
development partners are still required to sign a licensing agreement with
|
|
12
|
+
FarSounder to receive the complete SDK materials (the SonaSoft demo software)
|
|
13
|
+
and eventually to become an authorized third-party integrator. Please contact
|
|
14
|
+
us at service@farsounder.com to complete the licensing agreement or discuss use
|
|
15
|
+
for commercial applications.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|