syndesi 0.2.0__tar.gz → 0.2.1__tar.gz
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.
- {syndesi-0.2.0 → syndesi-0.2.1}/LICENSE +0 -0
- {syndesi-0.2.0/syndesi.egg-info → syndesi-0.2.1}/PKG-INFO +1 -1
- {syndesi-0.2.0 → syndesi-0.2.1}/setup.py +1 -1
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/adapters/proxy.py +1 -1
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/adapters/serialport.py +18 -12
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/adapters/stop_conditions.py +13 -1
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/adapters/timeout.py +9 -0
- syndesi-0.2.1/syndesi/api/api.py +77 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/protocols/delimited.py +23 -36
- syndesi-0.2.1/syndesi/proxy/__init__.py +0 -0
- syndesi-0.2.1/syndesi/proxy/proxy.py +136 -0
- syndesi-0.2.1/syndesi/proxy/proxy_api.py +93 -0
- syndesi-0.2.1/syndesi/tools/__init__.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1/syndesi.egg-info}/PKG-INFO +1 -1
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi.egg-info/SOURCES.txt +5 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/README.md +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/setup.cfg +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/__init__.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/adapters/__init__.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/adapters/adapter.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/adapters/auto.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/adapters/ip.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/adapters/ip_server.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/adapters/timed_queue.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/adapters/visa.py +0 -0
- {syndesi-0.2.0/syndesi/tools → syndesi-0.2.1/syndesi/api}/__init__.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/protocols/__init__.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/protocols/protocol.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/protocols/raw.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/protocols/scpi.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/protocols/sdp.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/tools/exceptions.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/tools/log.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/tools/others.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/tools/shell.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi/tools/types.py +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi.egg-info/dependency_links.txt +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi.egg-info/entry_points.txt +0 -0
- {syndesi-0.2.0 → syndesi-0.2.1}/syndesi.egg-info/top_level.txt +0 -0
|
File without changes
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import os
|
|
2
2
|
import serial
|
|
3
|
-
from ..tools.types import to_bytes
|
|
4
|
-
from .stop_conditions import *
|
|
5
|
-
from .timeout import Timeout
|
|
6
|
-
from .timed_queue import TimedQueue
|
|
7
3
|
from threading import Thread
|
|
8
4
|
from typing import Union
|
|
9
5
|
import select
|
|
10
6
|
import argparse
|
|
7
|
+
#from collections.abc import Sequence
|
|
8
|
+
|
|
9
|
+
from .adapter import Adapter
|
|
10
|
+
from ..tools.types import to_bytes
|
|
11
|
+
from .stop_conditions import *
|
|
12
|
+
from .timeout import Timeout
|
|
13
|
+
from .timed_queue import TimedQueue
|
|
11
14
|
from ..tools import shell
|
|
12
|
-
from
|
|
15
|
+
from ..tools.others import DEFAULT
|
|
13
16
|
|
|
14
17
|
# From pyserial - serialposix.py
|
|
15
18
|
import fcntl
|
|
@@ -20,16 +23,15 @@ if hasattr(termios, 'TIOCINQ'):
|
|
|
20
23
|
else:
|
|
21
24
|
TIOCINQ = getattr(termios, 'FIONREAD', 0x541B)
|
|
22
25
|
TIOCM_zero_str = struct.pack('I', 0)
|
|
23
|
-
import os
|
|
24
26
|
|
|
25
|
-
DEFAULT_TIMEOUT = Timeout(response=
|
|
27
|
+
DEFAULT_TIMEOUT = Timeout(response=1, continuation=200e-3, total=None)
|
|
26
28
|
|
|
27
29
|
class SerialPort(Adapter):
|
|
28
30
|
def __init__(self,
|
|
29
31
|
port : str,
|
|
30
32
|
baudrate : int,
|
|
31
|
-
timeout : Union[Timeout, float] =
|
|
32
|
-
stop_condition : StopCondition =
|
|
33
|
+
timeout : Union[Timeout, float] = DEFAULT,
|
|
34
|
+
stop_condition : StopCondition = DEFAULT,
|
|
33
35
|
rts_cts : bool = False): # rts_cts experimental
|
|
34
36
|
"""
|
|
35
37
|
Serial communication adapter
|
|
@@ -39,8 +41,11 @@ class SerialPort(Adapter):
|
|
|
39
41
|
port : str
|
|
40
42
|
Serial port (COMx or ttyACMx)
|
|
41
43
|
"""
|
|
44
|
+
if timeout == DEFAULT:
|
|
45
|
+
timeout = DEFAULT_TIMEOUT
|
|
46
|
+
|
|
42
47
|
super().__init__(timeout=timeout, stop_condition=stop_condition)
|
|
43
|
-
self._logger.info("Setting up SerialPort adapter")
|
|
48
|
+
self._logger.info(f"Setting up SerialPort adapter timeout:{timeout}, stop_condition:{stop_condition}")
|
|
44
49
|
self._port = serial.Serial(port=port, baudrate=baudrate)
|
|
45
50
|
if self._port.isOpen():
|
|
46
51
|
self._status = self.Status.CONNECTED
|
|
@@ -94,7 +99,8 @@ class SerialPort(Adapter):
|
|
|
94
99
|
while True:
|
|
95
100
|
# It looks like using the raw implementation of port.in_waiting and port.read is better, there's no more warnings
|
|
96
101
|
# Equivalent of port.in_waiting :
|
|
97
|
-
in_waiting = struct.unpack('I', fcntl.ioctl(port.fd, TIOCINQ, TIOCM_zero_str))[0]
|
|
102
|
+
#in_waiting = struct.unpack('I', fcntl.ioctl(port.fd, TIOCINQ, TIOCM_zero_str))[0]
|
|
103
|
+
in_waiting = self._port.in_waiting # This is a temporary fix to get windows compatiblity back, an error might pop up
|
|
98
104
|
if in_waiting == 0:
|
|
99
105
|
ready, _, _ = select.select([port.fd, stop_event_pipe], [], [], None)
|
|
100
106
|
if stop_event_pipe in ready:
|
|
@@ -134,6 +134,12 @@ class Termination(StopCondition):
|
|
|
134
134
|
self._fragment_store = b''
|
|
135
135
|
return output
|
|
136
136
|
|
|
137
|
+
def __repr__(self) -> str:
|
|
138
|
+
return self.__str__()
|
|
139
|
+
|
|
140
|
+
def __str__(self) -> str:
|
|
141
|
+
return f'Termination({repr(self._termination)})'
|
|
142
|
+
|
|
137
143
|
class Length(StopCondition):
|
|
138
144
|
def __init__(self, N : int) -> None:
|
|
139
145
|
"""
|
|
@@ -158,4 +164,10 @@ class Length(StopCondition):
|
|
|
158
164
|
deferred_fragment = data[remaining_bytes:]
|
|
159
165
|
self._counter += len(kept_fragment)
|
|
160
166
|
remaining_bytes = self._N - self._counter
|
|
161
|
-
return remaining_bytes == 0, kept_fragment, deferred_fragment
|
|
167
|
+
return remaining_bytes == 0, kept_fragment, deferred_fragment
|
|
168
|
+
|
|
169
|
+
def __repr__(self) -> str:
|
|
170
|
+
return self.__str__()
|
|
171
|
+
|
|
172
|
+
def __str__(self) -> str:
|
|
173
|
+
return f'Length({self._N})'
|
|
@@ -235,6 +235,15 @@ class Timeout():
|
|
|
235
235
|
"""
|
|
236
236
|
return self._data_strategy, self._last_data_strategy_origin
|
|
237
237
|
|
|
238
|
+
def __str__(self) -> str:
|
|
239
|
+
response = f'r:{self._response:.3f}ms/{self._on_response},' if self._response is not None else ''
|
|
240
|
+
continuation = f'c:{self._continuation:.3f}ms/{self._on_continuation},' if self._continuation is not None else ''
|
|
241
|
+
total = f't:{self._total:.3f}ms/{self._on_total}' if self._total is not None else ''
|
|
242
|
+
return f'Timeout({response}{continuation}{total})'
|
|
243
|
+
|
|
244
|
+
def __repr__(self) -> str:
|
|
245
|
+
return self.__str__()
|
|
246
|
+
|
|
238
247
|
|
|
239
248
|
class TimeoutException(Exception):
|
|
240
249
|
def __init__(self, type : Timeout.TimeoutType) -> None:
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import quopri
|
|
2
|
+
from typing import Tuple, Union, Dict
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from dataclasses import dataclass, fields, Field
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
APIS = {}
|
|
8
|
+
|
|
9
|
+
def register_api(apis : dict):
|
|
10
|
+
"""
|
|
11
|
+
Register apis
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
apis : dict
|
|
16
|
+
{'action' : APICall} class dictionary
|
|
17
|
+
"""
|
|
18
|
+
APIS.update(apis)
|
|
19
|
+
|
|
20
|
+
ACTION_ATTRIBUTE = 'action'
|
|
21
|
+
|
|
22
|
+
class APIItem:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
class APICall:
|
|
26
|
+
action = ''
|
|
27
|
+
_keyword = ''
|
|
28
|
+
|
|
29
|
+
def encode(self) -> bytes:
|
|
30
|
+
cls_fields: Tuple[Field, ...] = fields(self)
|
|
31
|
+
data = {}
|
|
32
|
+
|
|
33
|
+
# Add action field
|
|
34
|
+
data[ACTION_ATTRIBUTE] = self.action
|
|
35
|
+
# Add other fields
|
|
36
|
+
for field in cls_fields:
|
|
37
|
+
field_data = getattr(self, field.name)
|
|
38
|
+
if isinstance(field_data, Enum):
|
|
39
|
+
entry = field_data.value
|
|
40
|
+
elif isinstance(field_data, bytes):
|
|
41
|
+
entry = quopri.encodestring(field_data).decode('ASCII')
|
|
42
|
+
elif isinstance(field_data, str):
|
|
43
|
+
entry = quopri.encodestring(field_data.encode('utf-8')).decode('ASCII')
|
|
44
|
+
else:
|
|
45
|
+
entry = field_data
|
|
46
|
+
|
|
47
|
+
data[field.name] = entry
|
|
48
|
+
return json.dumps(data)
|
|
49
|
+
|
|
50
|
+
def parse(data : Union[str, bytes]) -> APICall:
|
|
51
|
+
json_data = json.loads(data)
|
|
52
|
+
action = json_data[ACTION_ATTRIBUTE]
|
|
53
|
+
json_data.pop(ACTION_ATTRIBUTE)
|
|
54
|
+
arguments = json_data
|
|
55
|
+
# Find the right API call and return it
|
|
56
|
+
if action not in APIS:
|
|
57
|
+
raise RuntimeError(f"API action '{action}' not registered")
|
|
58
|
+
|
|
59
|
+
converted_arguments = {}
|
|
60
|
+
# Convert each argument according to the class field types
|
|
61
|
+
api_fields: Tuple[Field, ...] = fields(APIS[action])
|
|
62
|
+
|
|
63
|
+
for field in api_fields:
|
|
64
|
+
if field.name not in arguments:
|
|
65
|
+
raise RuntimeError(f"Field '{field.name}' missing from arguments")
|
|
66
|
+
|
|
67
|
+
if field.type == bytes:
|
|
68
|
+
# Convert back
|
|
69
|
+
converted_arguments[field.name] = quopri.decodestring(arguments[field.name])
|
|
70
|
+
elif field.type == str:
|
|
71
|
+
converted_arguments[field.name] = quopri.decodestring(arguments[field.name]).decode('utf-8')
|
|
72
|
+
elif field.type == Enum:
|
|
73
|
+
converted_arguments[field.name] = field.type(arguments[field.name])
|
|
74
|
+
else:
|
|
75
|
+
converted_arguments[field.name] = arguments[field.name]
|
|
76
|
+
|
|
77
|
+
return APIS[action](**converted_arguments)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from .protocol import Protocol
|
|
2
|
-
from ..adapters import Adapter
|
|
2
|
+
from ..adapters import Adapter, Timeout, Termination
|
|
3
3
|
from ..tools.types import assert_byte_instance, assert_byte_instance
|
|
4
4
|
from time import time
|
|
5
5
|
import warnings
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class Delimited(Protocol):
|
|
9
|
-
def __init__(self, adapter : Adapter, termination='\n', format_response=True) -> None:
|
|
9
|
+
def __init__(self, adapter : Adapter, termination='\n', format_response=True, encoding : str = 'utf-8', timeout : Timeout = None) -> None:
|
|
10
10
|
"""
|
|
11
11
|
Protocol with delimiter, like LF, CR, etc... '\\n' is used by default
|
|
12
12
|
|
|
@@ -15,18 +15,21 @@ class Delimited(Protocol):
|
|
|
15
15
|
Parameters
|
|
16
16
|
----------
|
|
17
17
|
adapter : IAdapter
|
|
18
|
-
|
|
18
|
+
termination : bytes
|
|
19
19
|
Command termination, '\\n' by default
|
|
20
20
|
format_response : bool
|
|
21
|
-
Apply formatting to the response (i.e removing the termination)
|
|
21
|
+
Apply formatting to the response (i.e removing the termination), True by default
|
|
22
|
+
encoding : str
|
|
23
|
+
timeout : Timeout
|
|
24
|
+
None by default (default timeout)
|
|
22
25
|
"""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# Temporary solution before implementing stop conditions
|
|
26
|
-
self._buffer = ''
|
|
26
|
+
adapter.set_default_stop_condition(stop_condition=Termination(sequence=termination))
|
|
27
|
+
super().__init__(adapter, timeout=timeout)
|
|
27
28
|
|
|
28
29
|
if not isinstance(termination, str):
|
|
29
30
|
raise ValueError(f"end argument must be of type str, not {type(termination)}")
|
|
31
|
+
|
|
32
|
+
self._encoding = encoding
|
|
30
33
|
self._termination = termination
|
|
31
34
|
self._response_formatting = format_response
|
|
32
35
|
|
|
@@ -62,40 +65,24 @@ class Delimited(Protocol):
|
|
|
62
65
|
self.write(command)
|
|
63
66
|
return self.read()
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
# adapter will take care of that using the stop conditions
|
|
67
|
-
#
|
|
68
|
-
# For now the delimited module will take care of it
|
|
69
|
-
#
|
|
70
|
-
# Stop conditions should also be added inside the delimited module (unclear yet how)
|
|
71
|
-
|
|
72
|
-
def read(self, timeout=2) -> str:
|
|
68
|
+
def read(self, timeout : Timeout = None, decode : str = True) -> str:
|
|
73
69
|
"""
|
|
74
70
|
Reads command and formats it as a str
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
timeout : Timeout
|
|
75
|
+
decode : bool
|
|
76
|
+
Decode incoming data, True by default
|
|
75
77
|
"""
|
|
76
|
-
if self._termination not in self._buffer:
|
|
77
|
-
# Read the adapter only if there isn't a fragment already in the buffer
|
|
78
|
-
start = time()
|
|
79
|
-
while True:
|
|
80
|
-
# Continuously read the adapter as long as no termination is caught
|
|
81
|
-
data = self._from_bytes(self._adapter.read())
|
|
82
|
-
self._buffer += data
|
|
83
|
-
if self._termination in data or time() > start + timeout:
|
|
84
|
-
break
|
|
85
78
|
|
|
86
79
|
# Send up to the termination
|
|
87
|
-
|
|
80
|
+
data = self._adapter.read(timeout=timeout)
|
|
81
|
+
if decode:
|
|
82
|
+
data = data.decode(self._encoding)
|
|
88
83
|
if self._response_formatting:
|
|
89
84
|
# Only send the fragment (no termination)
|
|
90
|
-
return
|
|
85
|
+
return data
|
|
91
86
|
else:
|
|
92
87
|
# Add the termination back in
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
def read_raw(self) -> bytes:
|
|
96
|
-
"""
|
|
97
|
-
Returns the raw bytes instead of str
|
|
98
|
-
"""
|
|
99
|
-
if len(self._buffer) > 0:
|
|
100
|
-
warnings.warn("Warning : The buffer wasn't empty, standard (non raw) data is still in it")
|
|
101
|
-
return self._adapter.read()
|
|
88
|
+
return data + self._termination
|
|
File without changes
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# proxy.py
|
|
2
|
+
# Sébastien Deriaz
|
|
3
|
+
# 28.05.2024
|
|
4
|
+
import argparse
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from ..adapters import SerialPort
|
|
7
|
+
from ..adapters.adapter import AdapterDisconnected
|
|
8
|
+
from ..adapters.proxy import DEFAULT_PORT
|
|
9
|
+
from ..adapters.ip_server import IPServer
|
|
10
|
+
from typing import Union
|
|
11
|
+
from .proxy_api import *
|
|
12
|
+
from ..api.api import *
|
|
13
|
+
import logging
|
|
14
|
+
from ..tools.log import LoggerAlias, set_log_stream
|
|
15
|
+
|
|
16
|
+
class AdapterType(Enum):
|
|
17
|
+
SERIAL = 'serial'
|
|
18
|
+
IP = 'ip'
|
|
19
|
+
|
|
20
|
+
DEFAULT_BAUDRATE = 115200
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
parser = argparse.ArgumentParser(
|
|
24
|
+
prog='syndesi-proxy',
|
|
25
|
+
description='Syndesi proxy server',
|
|
26
|
+
epilog='')
|
|
27
|
+
# Parse subcommand
|
|
28
|
+
parser.add_argument('-t', '--adapter_type', choices=[x.value for x in AdapterType], default=AdapterType.IP)
|
|
29
|
+
parser.add_argument('-p', '--port', type=int, default=DEFAULT_PORT, help='IP port')
|
|
30
|
+
parser.add_argument('-a', '--address', default=None, type=str, help='IP address or serial port')
|
|
31
|
+
parser.add_argument('-b', '--baudrate', type=int, default=DEFAULT_BAUDRATE, help='Serial baudrate')
|
|
32
|
+
parser.add_argument('-v', '--verbose', action='store_true')
|
|
33
|
+
|
|
34
|
+
args = parser.parse_args()
|
|
35
|
+
|
|
36
|
+
if args.verbose:
|
|
37
|
+
set_log_stream(True, 'DEBUG')
|
|
38
|
+
|
|
39
|
+
proxy_server = ProxyServer(adapter_type=args.adapter_type, port=args.port, address=args.address, baudrate=args.baudrate)
|
|
40
|
+
|
|
41
|
+
proxy_server.start()
|
|
42
|
+
|
|
43
|
+
class ProxyServer:
|
|
44
|
+
def __init__(self, adapter_type : AdapterType, port : Union[str, int], address : str, baudrate : int) -> None:
|
|
45
|
+
self._adapter_type = AdapterType(adapter_type)
|
|
46
|
+
self._adapter = None
|
|
47
|
+
self._port = port
|
|
48
|
+
self._address = address
|
|
49
|
+
self._baudrate = baudrate
|
|
50
|
+
self._logger = logging.getLogger(LoggerAlias.PROXY_SERVER.value)
|
|
51
|
+
self._logger.info('Initializing proxy server')
|
|
52
|
+
|
|
53
|
+
def start(self):
|
|
54
|
+
self._logger.info(f"Starting proxy server with {self._adapter_type.value} adapter")
|
|
55
|
+
|
|
56
|
+
if self._adapter_type == AdapterType.SERIAL:
|
|
57
|
+
# If adapter type is serial, create the adapter directly
|
|
58
|
+
self._master_adapter = SerialPort(self._address, baudrate=self._baudrate)
|
|
59
|
+
elif self._adapter_type == AdapterType.IP:
|
|
60
|
+
# Otherwise, create a server to get IP clients
|
|
61
|
+
server = IPServer(port=self._port, transport='TCP', address=self._address, max_clients=1, stop_condition=None)
|
|
62
|
+
server.open()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# If the adapter type is IP, use the external while loop to get clients
|
|
66
|
+
while True:
|
|
67
|
+
self._master_adapter = server.get_client()
|
|
68
|
+
self._logger.info(f'Client connected : {self._master_adapter._address}:{self._master_adapter._port}')
|
|
69
|
+
|
|
70
|
+
while True:
|
|
71
|
+
try:
|
|
72
|
+
call_raw = self._master_adapter.read()
|
|
73
|
+
except AdapterDisconnected:
|
|
74
|
+
self._logger.info('Client disconnected')
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
api_call = parse(call_raw)
|
|
78
|
+
|
|
79
|
+
self._logger.debug(f'Received {type(api_call)}')
|
|
80
|
+
|
|
81
|
+
output = self.manage_call(api_call)
|
|
82
|
+
|
|
83
|
+
self._master_adapter.write(output.encode())
|
|
84
|
+
|
|
85
|
+
if self._adapter_type == AdapterType.IP:
|
|
86
|
+
if not self._master_adapter.read_thread_alive():
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
# Loop only if we need to get a new client
|
|
90
|
+
if self._adapter_type != AdapterType.IP:
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
def manage_call(self, c : APICall) -> APICall:
|
|
94
|
+
output = None
|
|
95
|
+
# IP Specific
|
|
96
|
+
if isinstance(c, IPInstanciate):
|
|
97
|
+
self._adapter = IP(
|
|
98
|
+
address=c.address,
|
|
99
|
+
port=c.port)
|
|
100
|
+
output = ReturnStatus(True)
|
|
101
|
+
# Serial specific
|
|
102
|
+
if isinstance(c, SerialPortInstanciate):
|
|
103
|
+
self._adapter = SerialPort(
|
|
104
|
+
port=c.port,
|
|
105
|
+
baudrate=c.baudrate
|
|
106
|
+
)
|
|
107
|
+
# Adapter
|
|
108
|
+
elif isinstance(c, AdapterOpen):
|
|
109
|
+
if self._adapter is None:
|
|
110
|
+
output = ReturnStatus(False, 'Cannot open uninstanciated adapter')
|
|
111
|
+
self._adapter.open()
|
|
112
|
+
output = ReturnStatus(True)
|
|
113
|
+
elif isinstance(c, AdapterClose):
|
|
114
|
+
if self._adapter is None:
|
|
115
|
+
output = ReturnStatus(False, 'Cannot close uninstanciated adapter')
|
|
116
|
+
else:
|
|
117
|
+
self._adapter.close()
|
|
118
|
+
output = ReturnStatus(True)
|
|
119
|
+
elif isinstance(c, AdapterWrite):
|
|
120
|
+
if self._adapter is None:
|
|
121
|
+
output = ReturnStatus(False, 'Cannot write to uninstanciated adapter')
|
|
122
|
+
else:
|
|
123
|
+
self._adapter.write(c.data)
|
|
124
|
+
output = ReturnStatus(True)
|
|
125
|
+
elif isinstance(c, AdapterFlushRead):
|
|
126
|
+
self._adapter.flushRead()
|
|
127
|
+
output = ReturnStatus(True)
|
|
128
|
+
elif isinstance(c, AdapterRead):
|
|
129
|
+
# TODO : Implement return_metrics
|
|
130
|
+
data = self._adapter.read()
|
|
131
|
+
output = AdapterReadReturn(data=data)
|
|
132
|
+
|
|
133
|
+
return output
|
|
134
|
+
|
|
135
|
+
if __name__ == '__main__':
|
|
136
|
+
main()
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# proxy_api.py
|
|
2
|
+
# Sébastien Deriaz
|
|
3
|
+
# 29.05.2024
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from ..api.api import APICall, ACTION_ATTRIBUTE, register_api, APIItem
|
|
9
|
+
|
|
10
|
+
class ProxyException(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
# IP specific
|
|
14
|
+
@dataclass
|
|
15
|
+
class IPInstanciate(APICall):
|
|
16
|
+
action = 'ip_adapter_inst'
|
|
17
|
+
address : str
|
|
18
|
+
port : int
|
|
19
|
+
transport : str
|
|
20
|
+
buffer_size : int
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class TimeoutAPI(APIItem):
|
|
24
|
+
name = 'timeout'
|
|
25
|
+
response : float
|
|
26
|
+
continuation : float
|
|
27
|
+
total : float
|
|
28
|
+
on_response : str
|
|
29
|
+
on_continuation : str
|
|
30
|
+
on_total : str
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class StopConditionAPI(APIItem):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class TerminationAPI(StopConditionAPI):
|
|
38
|
+
name = 'termination'
|
|
39
|
+
sequence : bytes
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class LengthAPI(StopConditionAPI):
|
|
43
|
+
name = 'length'
|
|
44
|
+
length : int
|
|
45
|
+
|
|
46
|
+
# Serial specific
|
|
47
|
+
@dataclass
|
|
48
|
+
class SerialPortInstanciate(APICall):
|
|
49
|
+
action = 'serial_adapter_inst'
|
|
50
|
+
port : str
|
|
51
|
+
baudrate : int
|
|
52
|
+
timeout : TimeoutAPI
|
|
53
|
+
stop_condition : StopConditionAPI
|
|
54
|
+
rts_cts : bool
|
|
55
|
+
|
|
56
|
+
# Adapters common
|
|
57
|
+
@dataclass
|
|
58
|
+
class AdapterOpen(APICall):
|
|
59
|
+
action = 'adapter_open'
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class AdapterClose(APICall):
|
|
63
|
+
action = 'adapter_close'
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class AdapterWrite(APICall):
|
|
67
|
+
action = 'adapter_write'
|
|
68
|
+
data : bytes
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class AdapterFlushRead(APICall):
|
|
72
|
+
action = 'adapter_flush_read'
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class AdapterRead(APICall):
|
|
76
|
+
action = 'adapter_read'
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class AdapterReadReturn(APICall):
|
|
80
|
+
action = 'adapter_read_return'
|
|
81
|
+
data : bytes
|
|
82
|
+
return_metrics : dict = None
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class ReturnStatus(APICall):
|
|
86
|
+
action = 'return_status'
|
|
87
|
+
success : bool
|
|
88
|
+
error_message : str = ''
|
|
89
|
+
|
|
90
|
+
# Register apis
|
|
91
|
+
current_module = sys.modules[__name__]
|
|
92
|
+
API_CALLS_PER_ACTION = {getattr(obj, ACTION_ATTRIBUTE) : obj for obj in current_module.__dict__.values() if hasattr(obj, ACTION_ATTRIBUTE)}
|
|
93
|
+
register_api(API_CALLS_PER_ACTION)
|
|
File without changes
|
|
@@ -18,12 +18,17 @@ syndesi/adapters/stop_conditions.py
|
|
|
18
18
|
syndesi/adapters/timed_queue.py
|
|
19
19
|
syndesi/adapters/timeout.py
|
|
20
20
|
syndesi/adapters/visa.py
|
|
21
|
+
syndesi/api/__init__.py
|
|
22
|
+
syndesi/api/api.py
|
|
21
23
|
syndesi/protocols/__init__.py
|
|
22
24
|
syndesi/protocols/delimited.py
|
|
23
25
|
syndesi/protocols/protocol.py
|
|
24
26
|
syndesi/protocols/raw.py
|
|
25
27
|
syndesi/protocols/scpi.py
|
|
26
28
|
syndesi/protocols/sdp.py
|
|
29
|
+
syndesi/proxy/__init__.py
|
|
30
|
+
syndesi/proxy/proxy.py
|
|
31
|
+
syndesi/proxy/proxy_api.py
|
|
27
32
|
syndesi/tools/__init__.py
|
|
28
33
|
syndesi/tools/exceptions.py
|
|
29
34
|
syndesi/tools/log.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|