indipyserver 0.0.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.
- indipyserver-0.0.1/LICENSE +21 -0
- indipyserver-0.0.1/PKG-INFO +85 -0
- indipyserver-0.0.1/README.md +68 -0
- indipyserver-0.0.1/indipyserver/__init__.py +9 -0
- indipyserver-0.0.1/indipyserver/exdriver.py +314 -0
- indipyserver-0.0.1/indipyserver/ipyserver.py +704 -0
- indipyserver-0.0.1/indipyserver/remote.py +429 -0
- indipyserver-0.0.1/pyproject.toml +18 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 bernie-skipole
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: indipyserver
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Server for the INDI protocol.
|
|
5
|
+
Keywords: indi,server,astronomy,instrument,remote,control
|
|
6
|
+
Author-email: Bernard Czenkusz <bernie@skipole.co.uk>
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Project-URL: Documentation, https://indipyserver.readthedocs.io
|
|
15
|
+
Project-URL: Source, https://github.com/bernie-skipole/indipyserver
|
|
16
|
+
|
|
17
|
+
# indipyserver
|
|
18
|
+
Server for the INDI protocol, written in Python
|
|
19
|
+
|
|
20
|
+
This package provides an IPyServer class used to serve the INDI protocol on a port.
|
|
21
|
+
|
|
22
|
+
INDI - Instrument Neutral Distributed Interface.
|
|
23
|
+
|
|
24
|
+
See https://en.wikipedia.org/wiki/Instrument_Neutral_Distributed_Interface
|
|
25
|
+
|
|
26
|
+
Drivers controlling instrumentation can be written, typically using the IPyDriver class from the associated indipydriver package, this server opens a port to which an INDI client can connect.
|
|
27
|
+
|
|
28
|
+
You would create a script something like:
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
import asyncio
|
|
32
|
+
from indipyserver import IPyServer
|
|
33
|
+
import ... your own modules creating driver1, driver2 ...
|
|
34
|
+
|
|
35
|
+
server = IPyServer(driver1, driver2, host="localhost", port=7624, maxconnections=5)
|
|
36
|
+
asyncio.run(server.asyncrun())
|
|
37
|
+
|
|
38
|
+
A connected client can then control all the drivers. The above illustrates multiple drivers can be served, however it could equally be one or none at all.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Third party drivers
|
|
42
|
+
|
|
43
|
+
IPyServer can also run third party INDI drivers created with other languages or tools, using an add_exdriver method to include executable drivers.
|
|
44
|
+
|
|
45
|
+
For example, using drivers available from indilib:
|
|
46
|
+
|
|
47
|
+
import asyncio
|
|
48
|
+
from indipyserver import IPyServer
|
|
49
|
+
|
|
50
|
+
server = IPyServer(host="localhost", port=7624, maxconnections=5)
|
|
51
|
+
|
|
52
|
+
server.add_exdriver("indi_simulator_telescope")
|
|
53
|
+
server.add_exdriver("indi_simulator_ccd")
|
|
54
|
+
asyncio.run(server.asyncrun())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
Please note: The author has no relationship with indilib, these indipyserver and indipydriver packages are independently developed implementations. However they were developed with reference to the INDI version 1.7 specification, and are intended to interwork with other implementations that also meets that spec.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## Networked instruments
|
|
61
|
+
|
|
62
|
+
IPyServer also has an add_remote method which can be used to add connections to remote servers, creating a tree network of servers:
|
|
63
|
+
|
|
64
|
+
import asyncio
|
|
65
|
+
from indipyserver import IPyServer
|
|
66
|
+
import ... your own modules creating DriverA, DriverB ...
|
|
67
|
+
|
|
68
|
+
server = IPyServer(DriverA, DriverB, host="localhost", port=7624, maxconnections=5)
|
|
69
|
+
|
|
70
|
+
server.add_remote(host="nameofserverB", port=7624, blob_enable=True)
|
|
71
|
+
server.add_remote(host="nameofserverC", port=7624, blob_enable=True)
|
|
72
|
+
|
|
73
|
+
asyncio.run(server.asyncrun())
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
With such a layout, the client can control all the instruments.
|
|
79
|
+
|
|
80
|
+
Drivers made with indipydriver, third party executable drivers and remote connections can all be served together.
|
|
81
|
+
|
|
82
|
+
Further documentation can be found at:
|
|
83
|
+
|
|
84
|
+
https://indipyserver.readthedocs.io/en/latest/index.html
|
|
85
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# indipyserver
|
|
2
|
+
Server for the INDI protocol, written in Python
|
|
3
|
+
|
|
4
|
+
This package provides an IPyServer class used to serve the INDI protocol on a port.
|
|
5
|
+
|
|
6
|
+
INDI - Instrument Neutral Distributed Interface.
|
|
7
|
+
|
|
8
|
+
See https://en.wikipedia.org/wiki/Instrument_Neutral_Distributed_Interface
|
|
9
|
+
|
|
10
|
+
Drivers controlling instrumentation can be written, typically using the IPyDriver class from the associated indipydriver package, this server opens a port to which an INDI client can connect.
|
|
11
|
+
|
|
12
|
+
You would create a script something like:
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
from indipyserver import IPyServer
|
|
17
|
+
import ... your own modules creating driver1, driver2 ...
|
|
18
|
+
|
|
19
|
+
server = IPyServer(driver1, driver2, host="localhost", port=7624, maxconnections=5)
|
|
20
|
+
asyncio.run(server.asyncrun())
|
|
21
|
+
|
|
22
|
+
A connected client can then control all the drivers. The above illustrates multiple drivers can be served, however it could equally be one or none at all.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Third party drivers
|
|
26
|
+
|
|
27
|
+
IPyServer can also run third party INDI drivers created with other languages or tools, using an add_exdriver method to include executable drivers.
|
|
28
|
+
|
|
29
|
+
For example, using drivers available from indilib:
|
|
30
|
+
|
|
31
|
+
import asyncio
|
|
32
|
+
from indipyserver import IPyServer
|
|
33
|
+
|
|
34
|
+
server = IPyServer(host="localhost", port=7624, maxconnections=5)
|
|
35
|
+
|
|
36
|
+
server.add_exdriver("indi_simulator_telescope")
|
|
37
|
+
server.add_exdriver("indi_simulator_ccd")
|
|
38
|
+
asyncio.run(server.asyncrun())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
Please note: The author has no relationship with indilib, these indipyserver and indipydriver packages are independently developed implementations. However they were developed with reference to the INDI version 1.7 specification, and are intended to interwork with other implementations that also meets that spec.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## Networked instruments
|
|
45
|
+
|
|
46
|
+
IPyServer also has an add_remote method which can be used to add connections to remote servers, creating a tree network of servers:
|
|
47
|
+
|
|
48
|
+
import asyncio
|
|
49
|
+
from indipyserver import IPyServer
|
|
50
|
+
import ... your own modules creating DriverA, DriverB ...
|
|
51
|
+
|
|
52
|
+
server = IPyServer(DriverA, DriverB, host="localhost", port=7624, maxconnections=5)
|
|
53
|
+
|
|
54
|
+
server.add_remote(host="nameofserverB", port=7624, blob_enable=True)
|
|
55
|
+
server.add_remote(host="nameofserverC", port=7624, blob_enable=True)
|
|
56
|
+
|
|
57
|
+
asyncio.run(server.asyncrun())
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+

|
|
61
|
+
|
|
62
|
+
With such a layout, the client can control all the instruments.
|
|
63
|
+
|
|
64
|
+
Drivers made with indipydriver, third party executable drivers and remote connections can all be served together.
|
|
65
|
+
|
|
66
|
+
Further documentation can be found at:
|
|
67
|
+
|
|
68
|
+
https://indipyserver.readthedocs.io/en/latest/index.html
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
|
|
2
|
+
import asyncio, copy
|
|
3
|
+
|
|
4
|
+
import xml.etree.ElementTree as ET
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# All xml data sent from the driver should be contained in one of the following tags
|
|
11
|
+
TAGS = (b'message',
|
|
12
|
+
b'delProperty',
|
|
13
|
+
b'defSwitchVector',
|
|
14
|
+
b'setSwitchVector',
|
|
15
|
+
b'defLightVector',
|
|
16
|
+
b'setLightVector',
|
|
17
|
+
b'defTextVector',
|
|
18
|
+
b'setTextVector',
|
|
19
|
+
b'defNumberVector',
|
|
20
|
+
b'setNumberVector',
|
|
21
|
+
b'defBLOBVector',
|
|
22
|
+
b'setBLOBVector',
|
|
23
|
+
b'getProperties' # for snooping
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Note these are strings, as they are used for checking xmldata.tag values
|
|
27
|
+
|
|
28
|
+
DEFTAGS = ( 'defSwitchVector',
|
|
29
|
+
'defLightVector',
|
|
30
|
+
'defTextVector',
|
|
31
|
+
'defNumberVector',
|
|
32
|
+
'defBLOBVector'
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
NEWTAGS = ('newTextVector',
|
|
37
|
+
'newNumberVector',
|
|
38
|
+
'newSwitchVector',
|
|
39
|
+
'newBLOBVector'
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# _STARTTAGS is a tuple of ( b'<defTextVector', ... ) data received will be tested to start with such a starttag
|
|
43
|
+
_STARTTAGS = tuple(b'<' + tag for tag in TAGS)
|
|
44
|
+
|
|
45
|
+
# _ENDTAGS is a tuple of ( b'</defTextVector>', ... ) data received will be tested to end with such an endtag
|
|
46
|
+
_ENDTAGS = tuple(b'</' + tag + b'>' for tag in TAGS)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ExVector:
|
|
50
|
+
|
|
51
|
+
def __init__(self, name, deftag):
|
|
52
|
+
"Object which only holds a name and vectortype"
|
|
53
|
+
self.name = name
|
|
54
|
+
self.vectortype = deftag[3:]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ExDriver:
|
|
58
|
+
|
|
59
|
+
def __init__(self, program, *args, debug_enable=False):
|
|
60
|
+
"Executes a third party indi driver, communicates by stdin stdout"
|
|
61
|
+
|
|
62
|
+
self.program = program
|
|
63
|
+
self.args = args
|
|
64
|
+
self.debug_enable = debug_enable
|
|
65
|
+
self.proc = None
|
|
66
|
+
|
|
67
|
+
# An object for communicating is set when this driver is added to the server
|
|
68
|
+
self._commsobj = None
|
|
69
|
+
|
|
70
|
+
# This dictionary will be populated with devicename:{vectorname:vector}
|
|
71
|
+
# where vector is an ExVector object
|
|
72
|
+
self.devicenames = {}
|
|
73
|
+
|
|
74
|
+
self.snoopall = False # gets set to True if it is snooping everything
|
|
75
|
+
self.snoopdevices = set() # gets set to a set of device names
|
|
76
|
+
self.snoopvectors = set() # gets set to a set of (devicename,vectorname) tuples
|
|
77
|
+
|
|
78
|
+
self._remainder = b"" # Used to store intermediate data
|
|
79
|
+
self._stop = False # Gets set to True to stop communications
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def shutdown(self):
|
|
83
|
+
self._stop = True
|
|
84
|
+
if self._commsobj is not None:
|
|
85
|
+
self._commsobj.shutdown()
|
|
86
|
+
if self.proc is not None:
|
|
87
|
+
self.proc.terminate()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def __contains__(self, item):
|
|
91
|
+
"So a devicename can easily be checked if it is in this driver"
|
|
92
|
+
return item in self.devicenames
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def _readdata(self, root):
|
|
96
|
+
"""Called from communications object with received xmldata
|
|
97
|
+
and sends it towards the driver"""
|
|
98
|
+
|
|
99
|
+
if self._stop:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
if root.tag == "enableBLOB":
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
if root.tag == "getProperties":
|
|
106
|
+
await self.xml_to_ext(root)
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
devicename = root.get("device")
|
|
110
|
+
propertyname = root.get("name")
|
|
111
|
+
|
|
112
|
+
if root.tag in NEWTAGS:
|
|
113
|
+
if devicename is None:
|
|
114
|
+
# invalid
|
|
115
|
+
return
|
|
116
|
+
if devicename in self.devicenames:
|
|
117
|
+
await self.xml_to_ext(root)
|
|
118
|
+
return
|
|
119
|
+
else:
|
|
120
|
+
# not for this driver
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
# so not in NEWTAGS
|
|
124
|
+
# Check if this driver is snooping on this device/vector
|
|
125
|
+
if self.snoopall:
|
|
126
|
+
await self.xml_to_ext(root)
|
|
127
|
+
elif devicename and (devicename in self.snoopdevices):
|
|
128
|
+
await self.xml_to_ext(root)
|
|
129
|
+
elif devicename and propertyname and ((devicename, propertyname) in self.snoopvectors):
|
|
130
|
+
await self.xml_to_ext(root)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def xml_to_ext(self, root):
|
|
134
|
+
"Pass data to the external driver"
|
|
135
|
+
|
|
136
|
+
binarydata = ET.tostring(root)
|
|
137
|
+
|
|
138
|
+
# log the received data
|
|
139
|
+
if logger.isEnabledFor(logging.DEBUG) and self.debug_enable:
|
|
140
|
+
logger.debug(f"RX:: {binarydata.decode('utf-8')}")
|
|
141
|
+
|
|
142
|
+
binarydata += b"\n"
|
|
143
|
+
self.proc.stdin.write(binarydata)
|
|
144
|
+
await self.proc.stdin.drain()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async def _run_tx(self):
|
|
149
|
+
"Get data from driver and send it towards the server"
|
|
150
|
+
try:
|
|
151
|
+
# get block of xml.etree.ElementTree data
|
|
152
|
+
# from self._xmlinput and append it to self.writerque
|
|
153
|
+
while not self._stop:
|
|
154
|
+
txdata = await self._xmlinput()
|
|
155
|
+
if txdata is None:
|
|
156
|
+
return
|
|
157
|
+
# get block of xml.etree.ElementTree data
|
|
158
|
+
devicename = txdata.get("device")
|
|
159
|
+
if devicename and txdata.tag in DEFTAGS:
|
|
160
|
+
# its a definition
|
|
161
|
+
if devicename not in self.devicenames:
|
|
162
|
+
self.devicenames[devicename] = {}
|
|
163
|
+
vectorname = txdata.get("name")
|
|
164
|
+
if vectorname:
|
|
165
|
+
if vectorname not in self.devicenames[devicename]:
|
|
166
|
+
# add this vector to the self.devicenames[devicename] dictionary
|
|
167
|
+
self.devicenames[devicename][vectorname] = ExVector(vectorname, txdata.tag)
|
|
168
|
+
# check for a getProperties being sent, record what is being snooped
|
|
169
|
+
if txdata.tag == "getProperties":
|
|
170
|
+
vectorname = txdata.get("name")
|
|
171
|
+
if devicename is None:
|
|
172
|
+
self.snoopall = True
|
|
173
|
+
elif vectorname is None:
|
|
174
|
+
self.snoopdevices.add(devicename)
|
|
175
|
+
else:
|
|
176
|
+
self.snoopvectors.add((devicename,vectorname))
|
|
177
|
+
|
|
178
|
+
# transmit this data towards the server
|
|
179
|
+
await self._commsobj.run_tx(txdata)
|
|
180
|
+
|
|
181
|
+
if logger.isEnabledFor(logging.DEBUG) and self.debug_enable:
|
|
182
|
+
binarydata = ET.tostring(txdata)
|
|
183
|
+
logger.debug(f"TX:: {binarydata.decode('utf-8')}")
|
|
184
|
+
|
|
185
|
+
except Exception:
|
|
186
|
+
logger.exception("Exception report from ExDriver._run_tx")
|
|
187
|
+
raise
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def _xmlinput(self):
|
|
191
|
+
"""get data from driver, parse it, and return it as xml.etree.ElementTree object
|
|
192
|
+
Returns None if stop flags arises"""
|
|
193
|
+
message = b''
|
|
194
|
+
messagetagnumber = None
|
|
195
|
+
while not self._stop:
|
|
196
|
+
data = await self._datainput()
|
|
197
|
+
# data is either None, or binary data ending in b">"
|
|
198
|
+
if data is None:
|
|
199
|
+
return
|
|
200
|
+
if self._stop:
|
|
201
|
+
return
|
|
202
|
+
if not message:
|
|
203
|
+
# data is expected to start with <tag, first strip any newlines
|
|
204
|
+
data = data.strip()
|
|
205
|
+
for index, st in enumerate(_STARTTAGS):
|
|
206
|
+
if data.startswith(st):
|
|
207
|
+
messagetagnumber = index
|
|
208
|
+
break
|
|
209
|
+
elif st in data:
|
|
210
|
+
# remove any data prior to a starttag
|
|
211
|
+
positionofst = data.index(st)
|
|
212
|
+
data = data[positionofst:]
|
|
213
|
+
messagetagnumber = index
|
|
214
|
+
break
|
|
215
|
+
else:
|
|
216
|
+
# data does not start with a recognised tag, so ignore it
|
|
217
|
+
# and continue waiting for a valid message start
|
|
218
|
+
continue
|
|
219
|
+
# set this data into the received message
|
|
220
|
+
message = data
|
|
221
|
+
# either further children of this tag are coming, or maybe its a single tag ending in "/>"
|
|
222
|
+
if message.endswith(b'/>'):
|
|
223
|
+
# the message is complete, handle message here
|
|
224
|
+
try:
|
|
225
|
+
root = ET.fromstring(message.decode("us-ascii"))
|
|
226
|
+
except Exception:
|
|
227
|
+
# failed to parse the message, continue at beginning
|
|
228
|
+
message = b''
|
|
229
|
+
messagetagnumber = None
|
|
230
|
+
continue
|
|
231
|
+
# xml datablock done, return it
|
|
232
|
+
return root
|
|
233
|
+
# and read either the next message, or the children of this tag
|
|
234
|
+
continue
|
|
235
|
+
# To reach this point, the message is in progress, with a messagetagnumber set
|
|
236
|
+
# keep adding the received data to message, until an endtag is reached
|
|
237
|
+
message += data
|
|
238
|
+
if message.endswith(_ENDTAGS[messagetagnumber]):
|
|
239
|
+
# the message is complete, handle message here
|
|
240
|
+
try:
|
|
241
|
+
root = ET.fromstring(message.decode("us-ascii"))
|
|
242
|
+
except Exception:
|
|
243
|
+
# failed to parse the message, continue at beginning
|
|
244
|
+
message = b''
|
|
245
|
+
messagetagnumber = None
|
|
246
|
+
continue
|
|
247
|
+
# xml datablock done, return it
|
|
248
|
+
return root
|
|
249
|
+
# so message is in progress, with a messagetagnumber set
|
|
250
|
+
# but no valid endtag received yet, so continue the loop
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
async def _datainput(self):
|
|
254
|
+
"""Waits for binary string of data ending in > from the driver
|
|
255
|
+
Returns None if stop flags arises"""
|
|
256
|
+
remainder = self._remainder
|
|
257
|
+
if b">" in remainder:
|
|
258
|
+
# This returns with binary data ending in > as long
|
|
259
|
+
# as there are > characters in self._remainder
|
|
260
|
+
binarydata, self._remainder = remainder.split(b'>', maxsplit=1)
|
|
261
|
+
binarydata += b">"
|
|
262
|
+
return binarydata
|
|
263
|
+
# As soon as there are no > characters left in self._remainder
|
|
264
|
+
# get more data from the driver
|
|
265
|
+
while not self._stop:
|
|
266
|
+
await asyncio.sleep(0)
|
|
267
|
+
indata = await self.proc.stdout.read(100)
|
|
268
|
+
if not indata:
|
|
269
|
+
await asyncio.sleep(0.02)
|
|
270
|
+
continue
|
|
271
|
+
remainder += indata
|
|
272
|
+
if b">" in indata:
|
|
273
|
+
binarydata, self._remainder = remainder.split(b'>', maxsplit=1)
|
|
274
|
+
binarydata += b">"
|
|
275
|
+
return binarydata
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
async def _run_err(self):
|
|
279
|
+
"""gets binary string of data from exdriver proc.stderr
|
|
280
|
+
and logs it to logging.error."""
|
|
281
|
+
|
|
282
|
+
while not self._stop:
|
|
283
|
+
bindata = await self.proc.stderr.readline()
|
|
284
|
+
if not bindata:
|
|
285
|
+
await asyncio.sleep(0.02)
|
|
286
|
+
continue
|
|
287
|
+
logger.error(bindata.decode('utf-8'))
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
async def asyncrun(self):
|
|
291
|
+
"Runs the external driver"
|
|
292
|
+
|
|
293
|
+
self._stop = False
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
|
|
297
|
+
self.proc = await asyncio.create_subprocess_exec(self.program,
|
|
298
|
+
*self.args,
|
|
299
|
+
stdin=asyncio.subprocess.PIPE,
|
|
300
|
+
stdout=asyncio.subprocess.PIPE,
|
|
301
|
+
stderr=asyncio.subprocess.PIPE)
|
|
302
|
+
|
|
303
|
+
# wait for external program to start
|
|
304
|
+
await asyncio.sleep(0.1)
|
|
305
|
+
# send a getProperties into the driver
|
|
306
|
+
await self._readdata( ET.fromstring("""<getProperties version="1.7" />""") )
|
|
307
|
+
|
|
308
|
+
async with asyncio.TaskGroup() as tg:
|
|
309
|
+
tg.create_task( self._run_tx() ) # Get data from driver and send it towards the server
|
|
310
|
+
tg.create_task( self._run_err() ) # data from exdriver proc.stderr and logs it to logging.error
|
|
311
|
+
except Exception:
|
|
312
|
+
logger.exception("Driver shutdown")
|
|
313
|
+
finally:
|
|
314
|
+
self._stop = True
|