indipyclient 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.
- indipyclient-0.0.1/LICENSE +21 -0
- indipyclient-0.0.1/PKG-INFO +66 -0
- indipyclient-0.0.1/README.md +50 -0
- indipyclient-0.0.1/indipyclient/__init__.py +5 -0
- indipyclient-0.0.1/indipyclient/__main__.py +101 -0
- indipyclient-0.0.1/indipyclient/console/__init__.py +0 -0
- indipyclient-0.0.1/indipyclient/console/consoleclient.py +438 -0
- indipyclient-0.0.1/indipyclient/console/widgets.py +1353 -0
- indipyclient-0.0.1/indipyclient/console/windows.py +3686 -0
- indipyclient-0.0.1/indipyclient/error.py +10 -0
- indipyclient-0.0.1/indipyclient/events.py +630 -0
- indipyclient-0.0.1/indipyclient/ipyclient.py +771 -0
- indipyclient-0.0.1/indipyclient/propertymembers.py +386 -0
- indipyclient-0.0.1/indipyclient/propertyvectors.py +682 -0
- indipyclient-0.0.1/pyproject.toml +21 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023,2024 Bernard Czenkusz
|
|
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,66 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: indipyclient
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Pure python package, providing a terminal client and a set of classes which can be used to create scripts or clients to control remote instruments using the INDI protocol.
|
|
5
|
+
Keywords: indi,client,astronomy,instrument
|
|
6
|
+
Author-email: Bernard Czenkusz <bernie@skipole.co.uk>
|
|
7
|
+
Requires-Python: >=3.10
|
|
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
|
+
Project-URL: Documentation, https://indipyclient.readthedocs.io
|
|
14
|
+
Project-URL: Source, https://github.com/bernie-skipole/indipyclient
|
|
15
|
+
|
|
16
|
+
# indipyclient
|
|
17
|
+
INDI terminal client to communicate to an indi service.
|
|
18
|
+
|
|
19
|
+
This is a pure python package, with no dependencies, providing an INDI client terminal
|
|
20
|
+
|
|
21
|
+
It also provides a set of classes which can be used to create an INDI client. Either a script, or a GUI implementation could use this to generate the INDI protocol XML, and to create the connection to a port serving INDI drivers.
|
|
22
|
+
|
|
23
|
+
The client can be called with python3 -m indipyclent.
|
|
24
|
+
|
|
25
|
+
usage: python3 -m indipyclient [options]
|
|
26
|
+
|
|
27
|
+
INDI terminal client communicating to indi service.
|
|
28
|
+
|
|
29
|
+
options:
|
|
30
|
+
-h, --help show this help message and exit
|
|
31
|
+
-p PORT, --port PORT Port of the indi server (default 7624).
|
|
32
|
+
--host HOST Hostname/IP of the indi server (default localhost).
|
|
33
|
+
-b BLOBS, --blobs BLOBS
|
|
34
|
+
Optional folder where BLOB's will be saved.
|
|
35
|
+
--loglevel LOGLEVEL Enables logging, value 1, 2 or 3.
|
|
36
|
+
--logfile LOGFILE File where logs will be saved
|
|
37
|
+
--version show program's version number and exit
|
|
38
|
+
|
|
39
|
+
The BLOB's folder can also be set from within the session.
|
|
40
|
+
Setting loglevel and logfile should only be used for brief
|
|
41
|
+
diagnostic purposes, the logfile could grow very big.
|
|
42
|
+
loglevel:1 log vector tags without members or contents,
|
|
43
|
+
loglevel:2 log vectors and members - but not BLOB contents,
|
|
44
|
+
loglevel:3 log vectors and all contents
|
|
45
|
+
|
|
46
|
+
A typical sesssion would look like:
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
This is a companion package to 'indipydriver' which can be used to create INDI drivers.
|
|
52
|
+
|
|
53
|
+
INDI - Instrument Neutral Distributed Interface.
|
|
54
|
+
|
|
55
|
+
See https://en.wikipedia.org/wiki/Instrument_Neutral_Distributed_Interface
|
|
56
|
+
|
|
57
|
+
The INDI protocol is defined so that drivers should operate with any INDI client.
|
|
58
|
+
|
|
59
|
+
The protocol defines the format of the data sent, such as light, number, text, switch or BLOB (Binary Large Object) and the client can send commands to control the instrument. The client can be general purpose, taking the format of switches, numbers etc., from the protocol.
|
|
60
|
+
|
|
61
|
+
INDI is often used with astronomical instruments, but is a general purpose protocol which can be used for any instrument control providing drivers are available.
|
|
62
|
+
|
|
63
|
+
Further documentation is available at:
|
|
64
|
+
|
|
65
|
+
https://indipyclient.readthedocs.io
|
|
66
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# indipyclient
|
|
2
|
+
INDI terminal client to communicate to an indi service.
|
|
3
|
+
|
|
4
|
+
This is a pure python package, with no dependencies, providing an INDI client terminal
|
|
5
|
+
|
|
6
|
+
It also provides a set of classes which can be used to create an INDI client. Either a script, or a GUI implementation could use this to generate the INDI protocol XML, and to create the connection to a port serving INDI drivers.
|
|
7
|
+
|
|
8
|
+
The client can be called with python3 -m indipyclent.
|
|
9
|
+
|
|
10
|
+
usage: python3 -m indipyclient [options]
|
|
11
|
+
|
|
12
|
+
INDI terminal client communicating to indi service.
|
|
13
|
+
|
|
14
|
+
options:
|
|
15
|
+
-h, --help show this help message and exit
|
|
16
|
+
-p PORT, --port PORT Port of the indi server (default 7624).
|
|
17
|
+
--host HOST Hostname/IP of the indi server (default localhost).
|
|
18
|
+
-b BLOBS, --blobs BLOBS
|
|
19
|
+
Optional folder where BLOB's will be saved.
|
|
20
|
+
--loglevel LOGLEVEL Enables logging, value 1, 2 or 3.
|
|
21
|
+
--logfile LOGFILE File where logs will be saved
|
|
22
|
+
--version show program's version number and exit
|
|
23
|
+
|
|
24
|
+
The BLOB's folder can also be set from within the session.
|
|
25
|
+
Setting loglevel and logfile should only be used for brief
|
|
26
|
+
diagnostic purposes, the logfile could grow very big.
|
|
27
|
+
loglevel:1 log vector tags without members or contents,
|
|
28
|
+
loglevel:2 log vectors and members - but not BLOB contents,
|
|
29
|
+
loglevel:3 log vectors and all contents
|
|
30
|
+
|
|
31
|
+
A typical sesssion would look like:
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
This is a companion package to 'indipydriver' which can be used to create INDI drivers.
|
|
37
|
+
|
|
38
|
+
INDI - Instrument Neutral Distributed Interface.
|
|
39
|
+
|
|
40
|
+
See https://en.wikipedia.org/wiki/Instrument_Neutral_Distributed_Interface
|
|
41
|
+
|
|
42
|
+
The INDI protocol is defined so that drivers should operate with any INDI client.
|
|
43
|
+
|
|
44
|
+
The protocol defines the format of the data sent, such as light, number, text, switch or BLOB (Binary Large Object) and the client can send commands to control the instrument. The client can be general purpose, taking the format of switches, numbers etc., from the protocol.
|
|
45
|
+
|
|
46
|
+
INDI is often used with astronomical instruments, but is a general purpose protocol which can be used for any instrument control providing drivers are available.
|
|
47
|
+
|
|
48
|
+
Further documentation is available at:
|
|
49
|
+
|
|
50
|
+
https://indipyclient.readthedocs.io
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from .ipyclient import IPyClient
|
|
2
|
+
from .events import (delProperty, defSwitchVector, defTextVector, defNumberVector, defLightVector, defBLOBVector,
|
|
3
|
+
setSwitchVector, setTextVector, setNumberVector, setLightVector, setBLOBVector, Message, VectorTimeOut)
|
|
4
|
+
|
|
5
|
+
version = "0.0.1"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
|
|
2
|
+
"""Usage is
|
|
3
|
+
|
|
4
|
+
python3 -m indipyclient
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import os, sys, argparse, asyncio, collections, contextlib, pathlib
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from . import version
|
|
13
|
+
|
|
14
|
+
from .console.consoleclient import ConsoleClient, ConsoleControl
|
|
15
|
+
|
|
16
|
+
from .console.widgets import ERRORDATA
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def runclient(client, control):
|
|
20
|
+
try:
|
|
21
|
+
t1 = asyncio.create_task(client.asyncrun())
|
|
22
|
+
t2 = asyncio.create_task(control.asyncrun())
|
|
23
|
+
await asyncio.gather(t1, t2)
|
|
24
|
+
except Exception:
|
|
25
|
+
t1.cancel()
|
|
26
|
+
t2.cancel()
|
|
27
|
+
# wait for tasks to be done
|
|
28
|
+
while (not t1.done()) and (not t2.done()):
|
|
29
|
+
await asyncio.sleep(0)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main():
|
|
33
|
+
"""The main routine."""
|
|
34
|
+
|
|
35
|
+
parser = argparse.ArgumentParser(usage="python3 -m indipyclient [options]",
|
|
36
|
+
description="INDI terminal client communicating to indi service.",
|
|
37
|
+
epilog="""The BLOB's folder can also be set from within the session.
|
|
38
|
+
Setting loglevel and logfile should only be used for brief
|
|
39
|
+
diagnostic purposes, the logfile could grow very big.
|
|
40
|
+
loglevel:1 log vector tags without members or contents,
|
|
41
|
+
loglevel:2 log vectors and members - but not BLOB contents,
|
|
42
|
+
loglevel:3 log vectors and all contents
|
|
43
|
+
""")
|
|
44
|
+
parser.add_argument("-p", "--port", type=int, default=7624, help="Port of the indi server (default 7624).")
|
|
45
|
+
parser.add_argument("--host", default="localhost", help="Hostname/IP of the indi server (default localhost).")
|
|
46
|
+
parser.add_argument("-b", "--blobs", help="Optional folder where BLOB's will be saved.")
|
|
47
|
+
parser.add_argument("--loglevel", help="Enables logging, value 1, 2 or 3.")
|
|
48
|
+
parser.add_argument("--logfile", help="File where logs will be saved")
|
|
49
|
+
|
|
50
|
+
parser.add_argument("--version", action="version", version=version)
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
|
|
53
|
+
eventque = collections.deque(maxlen=4)
|
|
54
|
+
|
|
55
|
+
if args.blobs:
|
|
56
|
+
try:
|
|
57
|
+
blobfolder = pathlib.Path(args.blobs).expanduser().resolve()
|
|
58
|
+
except Exception:
|
|
59
|
+
print("Error: If given, the BLOB's folder should be an existing directory")
|
|
60
|
+
return 1
|
|
61
|
+
else:
|
|
62
|
+
if not blobfolder.is_dir():
|
|
63
|
+
print("Error: If given, the BLOB's folder should be an existing directory")
|
|
64
|
+
return 1
|
|
65
|
+
else:
|
|
66
|
+
blobfolder = None
|
|
67
|
+
|
|
68
|
+
# On receiving an event, the client appends it into eventque
|
|
69
|
+
client = ConsoleClient(indihost=args.host, indiport=args.port, eventque=eventque)
|
|
70
|
+
|
|
71
|
+
if args.loglevel and args.logfile:
|
|
72
|
+
try:
|
|
73
|
+
loglevel = int(args.loglevel)
|
|
74
|
+
if loglevel not in (1,2,3):
|
|
75
|
+
print("Error: If given, the loglevel should be 1, 2 or 3")
|
|
76
|
+
return 1
|
|
77
|
+
except:
|
|
78
|
+
print("Error: If given, the loglevel should be 1, 2 or 3")
|
|
79
|
+
return 1
|
|
80
|
+
client.setlogging(loglevel, args.logfile)
|
|
81
|
+
|
|
82
|
+
# Monitors eventque and acts on the events, creates the console screens
|
|
83
|
+
control = ConsoleControl(client, blobfolder=blobfolder)
|
|
84
|
+
|
|
85
|
+
asyncio.run(runclient(client, control))
|
|
86
|
+
|
|
87
|
+
# ERRORDATA is a list of any traceback.TracebackException() objects
|
|
88
|
+
# which records exceptions which may have occurred. They are printed
|
|
89
|
+
# here, after the console has closed down to avoid messing up
|
|
90
|
+
# the console formatting
|
|
91
|
+
|
|
92
|
+
if ERRORDATA:
|
|
93
|
+
for errortrace in ERRORDATA:
|
|
94
|
+
print("".join(errortrace.format()))
|
|
95
|
+
print("------------------")
|
|
96
|
+
|
|
97
|
+
return 0
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
sys.exit(main())
|
|
File without changes
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
|
|
2
|
+
import asyncio, sys
|
|
3
|
+
|
|
4
|
+
import curses
|
|
5
|
+
|
|
6
|
+
from traceback import TracebackException as TBE
|
|
7
|
+
|
|
8
|
+
from ..ipyclient import IPyClient
|
|
9
|
+
from ..events import (delProperty, defSwitchVector, defTextVector, defNumberVector, defLightVector, defBLOBVector,
|
|
10
|
+
setSwitchVector, setTextVector, setNumberVector, setLightVector, setBLOBVector, Message, VectorTimeOut)
|
|
11
|
+
|
|
12
|
+
from . import windows
|
|
13
|
+
from . import widgets
|
|
14
|
+
|
|
15
|
+
# set limit to terminal size
|
|
16
|
+
MINROWS = 22
|
|
17
|
+
MINCOLS = 78
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ConsoleClient(IPyClient):
|
|
21
|
+
|
|
22
|
+
"""Overrides IPyClient
|
|
23
|
+
On receiving an event, appends it into eventque
|
|
24
|
+
Which will pass the received event into ConsoleControl"""
|
|
25
|
+
|
|
26
|
+
async def rxevent(self, event):
|
|
27
|
+
"""Add event to eventque"""
|
|
28
|
+
self.clientdata['eventque'].appendleft(event)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ConsoleControl:
|
|
32
|
+
|
|
33
|
+
def __init__(self, client, blobfolder=None):
|
|
34
|
+
"""client is an instance of ConsoleClient
|
|
35
|
+
If given, blobfolder will be the folder where BLOBs will be saved"""
|
|
36
|
+
self.client = client
|
|
37
|
+
self.blobfolder = blobfolder
|
|
38
|
+
if self.blobfolder:
|
|
39
|
+
self.blobenabled = True
|
|
40
|
+
else:
|
|
41
|
+
self.blobenabled = False
|
|
42
|
+
|
|
43
|
+
# this is populated with events as they are received
|
|
44
|
+
self.eventque = self.client.clientdata['eventque']
|
|
45
|
+
|
|
46
|
+
# set up screen
|
|
47
|
+
self.stdscr = curses.initscr()
|
|
48
|
+
curses.start_color()
|
|
49
|
+
curses.noecho()
|
|
50
|
+
curses.cbreak()
|
|
51
|
+
curses.curs_set(0)
|
|
52
|
+
self.stdscr.keypad(True)
|
|
53
|
+
curses.mousemask(curses.BUTTON1_RELEASED)
|
|
54
|
+
|
|
55
|
+
self.maxrows, self.maxcols = self.stdscr.getmaxyx()
|
|
56
|
+
|
|
57
|
+
if self.maxrows < MINROWS or self.maxcols < MINCOLS:
|
|
58
|
+
curses.nocbreak()
|
|
59
|
+
self.stdscr.keypad(False)
|
|
60
|
+
curses.curs_set(1)
|
|
61
|
+
curses.echo()
|
|
62
|
+
curses.endwin()
|
|
63
|
+
print("Terminal too small! Try 80 columns x 24 rows")
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Idle, OK, Busy or Alert.
|
|
68
|
+
# gray, green, yellow and red
|
|
69
|
+
|
|
70
|
+
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_GREEN)
|
|
71
|
+
curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_YELLOW)
|
|
72
|
+
curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_RED)
|
|
73
|
+
|
|
74
|
+
# this keeps track of which screen is being displayed,
|
|
75
|
+
# initially start with the messages screen
|
|
76
|
+
self.screen = windows.MessagesScreen(self.stdscr, self)
|
|
77
|
+
self.screen.show()
|
|
78
|
+
|
|
79
|
+
# this is set to True, to shut down the client
|
|
80
|
+
self._shutdown = False
|
|
81
|
+
# and shutdown routine sets this to True to stop coroutines
|
|
82
|
+
self.stop = False
|
|
83
|
+
# these are set to True when asyncrun is finished
|
|
84
|
+
self.updatescreenstopped = False
|
|
85
|
+
self.getinputstopped = False
|
|
86
|
+
|
|
87
|
+
# list of known device names, this is needed to send
|
|
88
|
+
# BLOB's enabled
|
|
89
|
+
self.devicenames = []
|
|
90
|
+
|
|
91
|
+
# BLOBfiles, is a dictionary of {(devicename,vectorname,membername):filepath}
|
|
92
|
+
self.BLOBfiles = {}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def color(self, state):
|
|
97
|
+
"Returns curses.color_pair given a state"
|
|
98
|
+
if not curses.has_colors():
|
|
99
|
+
return curses.color_pair(0)
|
|
100
|
+
state = state.lower()
|
|
101
|
+
if state == "ok":
|
|
102
|
+
return curses.color_pair(1)
|
|
103
|
+
elif state == "busy":
|
|
104
|
+
return curses.color_pair(2)
|
|
105
|
+
elif state == "alert":
|
|
106
|
+
return curses.color_pair(3)
|
|
107
|
+
return curses.color_pair(0)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def connected(self):
|
|
111
|
+
return self.client.connected
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def shutdown(self):
|
|
115
|
+
self._shutdown = True
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def _checkshutdown(self):
|
|
119
|
+
"If self._shutdown becomes True, shutdown"
|
|
120
|
+
while not self._shutdown:
|
|
121
|
+
await asyncio.sleep(0)
|
|
122
|
+
await self.client.report("Shutting down client - please wait")
|
|
123
|
+
self.client.shutdown()
|
|
124
|
+
while not self.client.stopped:
|
|
125
|
+
await asyncio.sleep(0)
|
|
126
|
+
# now stop co-routines
|
|
127
|
+
self.stop = True
|
|
128
|
+
while (not self.updatescreenstopped) and (not self.getinputstopped):
|
|
129
|
+
await asyncio.sleep(0)
|
|
130
|
+
# async tasks finished, clear up the terminal
|
|
131
|
+
curses.nocbreak()
|
|
132
|
+
self.stdscr.keypad(False)
|
|
133
|
+
curses.curs_set(1)
|
|
134
|
+
curses.echo()
|
|
135
|
+
curses.endwin()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
async def updatescreen(self):
|
|
139
|
+
"Update while events are being received"
|
|
140
|
+
try:
|
|
141
|
+
while not self.stop:
|
|
142
|
+
await asyncio.sleep(0)
|
|
143
|
+
if isinstance(self.screen, windows.TooSmall):
|
|
144
|
+
continue
|
|
145
|
+
if not self.connected:
|
|
146
|
+
if isinstance(self.screen, windows.MessagesScreen):
|
|
147
|
+
# set disconnected status and focus on the quit button
|
|
148
|
+
if not self.screen.disconnectionflag:
|
|
149
|
+
self.devicenames.clear()
|
|
150
|
+
self.screen.showunconnected() # sets disconnectionflag
|
|
151
|
+
else:
|
|
152
|
+
# when not connected, show messages screen
|
|
153
|
+
self.screen.close("Messages")
|
|
154
|
+
continue
|
|
155
|
+
# act when an event is received
|
|
156
|
+
try:
|
|
157
|
+
event = self.eventque.pop()
|
|
158
|
+
except IndexError:
|
|
159
|
+
# no event received, so do not update screen
|
|
160
|
+
continue
|
|
161
|
+
if isinstance(event, VectorTimeOut) and (event.devicename == self.screen.devicename):
|
|
162
|
+
if isinstance(self.screen, windows.ChooseVectorScreen):
|
|
163
|
+
self.screen.timeout(event)
|
|
164
|
+
continue
|
|
165
|
+
if isinstance(self.screen, windows.VectorScreen) and (self.screen.vectorname == event.vectorname):
|
|
166
|
+
self.screen.timeout(event)
|
|
167
|
+
continue
|
|
168
|
+
if hasattr(event, 'devicename'):
|
|
169
|
+
# if this is a new device, update it with BLOB status
|
|
170
|
+
if event.devicename and (event.devicename not in self.devicenames):
|
|
171
|
+
if event.devicename in self.client:
|
|
172
|
+
device = self.client[event.devicename]
|
|
173
|
+
if device.enable:
|
|
174
|
+
# new devicename
|
|
175
|
+
self.devicenames.append(event.devicename)
|
|
176
|
+
if self.blobenabled:
|
|
177
|
+
self.client.send_enableBLOB('Also', event.devicename)
|
|
178
|
+
else:
|
|
179
|
+
self.client.send_enableBLOB('Never', event.devicename)
|
|
180
|
+
# If the event is a received BLOB, save it to the BLOB Folder
|
|
181
|
+
if isinstance(event, setBLOBVector):
|
|
182
|
+
# make filename from timestamp
|
|
183
|
+
timestampstring = event.timestamp.strftime('%Y%m%d_%H_%M_%S')
|
|
184
|
+
for membername, membervalue in event.items():
|
|
185
|
+
sizeformat = event.sizeformat[membername]
|
|
186
|
+
filename = membername + "_" + timestampstring + sizeformat[1]
|
|
187
|
+
counter = 0
|
|
188
|
+
while True:
|
|
189
|
+
filepath = self.blobfolder / filename
|
|
190
|
+
if filepath.exists():
|
|
191
|
+
# append a digit to the filename
|
|
192
|
+
counter += 1
|
|
193
|
+
filename = membername + "_" + timestampstring + "_" + str(counter) + sizeformat[1]
|
|
194
|
+
else:
|
|
195
|
+
# filepath does not exist, so a new file with this filepath can be created
|
|
196
|
+
break
|
|
197
|
+
filepath.write_bytes(membervalue)
|
|
198
|
+
# record the filepath
|
|
199
|
+
self.BLOBfiles[(event.devicename, event.vectorname, membername)] = filepath
|
|
200
|
+
if isinstance(self.screen, windows.MessagesScreen):
|
|
201
|
+
self.screen.update(event)
|
|
202
|
+
continue
|
|
203
|
+
if isinstance(self.screen, windows.DevicesScreen):
|
|
204
|
+
self.screen.update(event)
|
|
205
|
+
continue
|
|
206
|
+
if isinstance(self.screen, windows.EnableBLOBsScreen):
|
|
207
|
+
self.screen.update(event)
|
|
208
|
+
continue
|
|
209
|
+
if event.devicename != self.screen.devicename:
|
|
210
|
+
# the remaining screens are only affected if the event devicename
|
|
211
|
+
# is the device they refer to
|
|
212
|
+
continue
|
|
213
|
+
if isinstance(event, delProperty):
|
|
214
|
+
if event.vectorname is None:
|
|
215
|
+
# the whole device is disabled,
|
|
216
|
+
if event.devicename and (event.devicename in self.devicenames):
|
|
217
|
+
self.devicenames.remove(event.devicename)
|
|
218
|
+
# show devicesscreen
|
|
219
|
+
self.screen.close("Devices")
|
|
220
|
+
continue
|
|
221
|
+
if isinstance(self.screen, windows.ChooseVectorScreen):
|
|
222
|
+
# one vector has been disabled, update the ChooseVectorScreen
|
|
223
|
+
self.screen.update(event)
|
|
224
|
+
continue
|
|
225
|
+
if isinstance(self.screen, windows.VectorScreen) and (self.screen.vectorname == event.vectorname):
|
|
226
|
+
# This vector has been disabled, show ChooseVectorScreen
|
|
227
|
+
self.screen.close("Vectors")
|
|
228
|
+
continue
|
|
229
|
+
continue
|
|
230
|
+
# so its not a delete property
|
|
231
|
+
if isinstance(self.screen, windows.ChooseVectorScreen):
|
|
232
|
+
self.screen.update(event)
|
|
233
|
+
continue
|
|
234
|
+
if isinstance(self.screen, windows.VectorScreen) and (self.screen.vectorname == event.vectorname):
|
|
235
|
+
# The event refers to this vector
|
|
236
|
+
self.screen.update(event)
|
|
237
|
+
|
|
238
|
+
except asyncio.CancelledError:
|
|
239
|
+
self._shutdown = True
|
|
240
|
+
raise
|
|
241
|
+
except Exception as e:
|
|
242
|
+
widgets.ERRORDATA.append(TBE.from_exception(e))
|
|
243
|
+
self._shutdown = True
|
|
244
|
+
finally:
|
|
245
|
+
self.updatescreenstopped = True
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
async def getinput(self):
|
|
249
|
+
try:
|
|
250
|
+
while not self.stop:
|
|
251
|
+
await asyncio.sleep(0)
|
|
252
|
+
if isinstance(self.screen, windows.TooSmall):
|
|
253
|
+
result = await self.screen.inputs()
|
|
254
|
+
if result == "Resize":
|
|
255
|
+
self.maxrows, self.maxcols = self.stdscr.getmaxyx()
|
|
256
|
+
if self.maxrows < 10 or self.maxcols < 40:
|
|
257
|
+
self.shutdown()
|
|
258
|
+
continue
|
|
259
|
+
if self.maxrows < MINROWS or self.maxcols < MINCOLS:
|
|
260
|
+
self.screen = windows.TooSmall(self.stdscr, self)
|
|
261
|
+
self.screen.show()
|
|
262
|
+
continue
|
|
263
|
+
# so resize has increased to proper size
|
|
264
|
+
self.screen = windows.MessagesScreen(self.stdscr, self)
|
|
265
|
+
self.screen.show()
|
|
266
|
+
continue
|
|
267
|
+
if result == "Quit":
|
|
268
|
+
self._shutdown = True
|
|
269
|
+
break
|
|
270
|
+
|
|
271
|
+
if isinstance(self.screen, windows.MessagesScreen):
|
|
272
|
+
result = await self.screen.inputs()
|
|
273
|
+
if result == "Resize":
|
|
274
|
+
self.maxrows, self.maxcols = self.stdscr.getmaxyx()
|
|
275
|
+
if self.maxrows < 16 or self.maxcols < 40:
|
|
276
|
+
self.shutdown()
|
|
277
|
+
continue
|
|
278
|
+
if self.maxrows < MINROWS or self.maxcols < MINCOLS:
|
|
279
|
+
self.screen = windows.TooSmall(self.stdscr, self)
|
|
280
|
+
self.screen.show()
|
|
281
|
+
continue
|
|
282
|
+
self.screen = windows.MessagesScreen(self.stdscr, self)
|
|
283
|
+
self.screen.show()
|
|
284
|
+
continue
|
|
285
|
+
if result == "Quit":
|
|
286
|
+
self._shutdown = True
|
|
287
|
+
break
|
|
288
|
+
if result == "Devices":
|
|
289
|
+
self.screen = windows.DevicesScreen(self.stdscr, self)
|
|
290
|
+
self.screen.show()
|
|
291
|
+
continue
|
|
292
|
+
if result == "EnableBLOBs":
|
|
293
|
+
self.screen = windows.EnableBLOBsScreen(self.stdscr, self)
|
|
294
|
+
self.screen.show()
|
|
295
|
+
continue
|
|
296
|
+
if isinstance(self.screen, windows.EnableBLOBsScreen):
|
|
297
|
+
result = await self.screen.inputs()
|
|
298
|
+
if result == "Resize":
|
|
299
|
+
self.maxrows, self.maxcols = self.stdscr.getmaxyx()
|
|
300
|
+
if self.maxrows < 16 or self.maxcols < 40:
|
|
301
|
+
self.shutdown()
|
|
302
|
+
continue
|
|
303
|
+
if self.maxrows < MINROWS or self.maxcols < MINCOLS:
|
|
304
|
+
self.screen = windows.TooSmall(self.stdscr, self)
|
|
305
|
+
self.screen.show()
|
|
306
|
+
continue
|
|
307
|
+
self.screen = windows.EnableBLOBsScreen(self.stdscr, self)
|
|
308
|
+
self.screen.show()
|
|
309
|
+
continue
|
|
310
|
+
if result == "Quit":
|
|
311
|
+
self._shutdown = True
|
|
312
|
+
break
|
|
313
|
+
if result == "Messages":
|
|
314
|
+
self.screen = windows.MessagesScreen(self.stdscr, self)
|
|
315
|
+
self.screen.show()
|
|
316
|
+
continue
|
|
317
|
+
if result == "Devices":
|
|
318
|
+
self.screen = windows.DevicesScreen(self.stdscr, self)
|
|
319
|
+
self.screen.show()
|
|
320
|
+
continue
|
|
321
|
+
if isinstance(self.screen, windows.DevicesScreen):
|
|
322
|
+
result = await self.screen.inputs()
|
|
323
|
+
if result == "Resize":
|
|
324
|
+
self.maxrows, self.maxcols = self.stdscr.getmaxyx()
|
|
325
|
+
if self.maxrows < 16 or self.maxcols < 40:
|
|
326
|
+
self.shutdown()
|
|
327
|
+
continue
|
|
328
|
+
if self.maxrows < MINROWS or self.maxcols < MINCOLS:
|
|
329
|
+
self.screen = windows.TooSmall(self.stdscr, self)
|
|
330
|
+
self.screen.show()
|
|
331
|
+
continue
|
|
332
|
+
self.screen = windows.DevicesScreen(self.stdscr, self)
|
|
333
|
+
self.screen.show()
|
|
334
|
+
continue
|
|
335
|
+
if result == "Quit":
|
|
336
|
+
self._shutdown = True
|
|
337
|
+
break
|
|
338
|
+
if result == "Messages":
|
|
339
|
+
self.screen = windows.MessagesScreen(self.stdscr, self)
|
|
340
|
+
self.screen.show()
|
|
341
|
+
continue
|
|
342
|
+
devices = {devicename.lower():device for devicename, device in self.client.items() if device.enable}
|
|
343
|
+
if result in devices:
|
|
344
|
+
devicename = devices[result].devicename
|
|
345
|
+
self.screen = windows.ChooseVectorScreen(self.stdscr, self, devicename)
|
|
346
|
+
self.screen.show()
|
|
347
|
+
continue
|
|
348
|
+
if isinstance(self.screen, windows.ChooseVectorScreen):
|
|
349
|
+
result = await self.screen.inputs()
|
|
350
|
+
if result == "Resize":
|
|
351
|
+
self.maxrows, self.maxcols = self.stdscr.getmaxyx()
|
|
352
|
+
if self.maxrows < 16 or self.maxcols < 40:
|
|
353
|
+
self.shutdown()
|
|
354
|
+
continue
|
|
355
|
+
if self.maxrows < MINROWS or self.maxcols < MINCOLS:
|
|
356
|
+
self.screen = windows.TooSmall(self.stdscr, self)
|
|
357
|
+
self.screen.show()
|
|
358
|
+
continue
|
|
359
|
+
self.screen = windows.ChooseVectorScreen(self.stdscr, self, self.screen.devicename, group=self.screen.groupwin.active)
|
|
360
|
+
self.screen.show()
|
|
361
|
+
continue
|
|
362
|
+
if result == "Quit":
|
|
363
|
+
self._shutdown = True
|
|
364
|
+
break
|
|
365
|
+
if result == "Messages":
|
|
366
|
+
self.screen = windows.MessagesScreen(self.stdscr, self)
|
|
367
|
+
self.screen.show()
|
|
368
|
+
continue
|
|
369
|
+
if result == "Devices":
|
|
370
|
+
self.screen = windows.DevicesScreen(self.stdscr, self)
|
|
371
|
+
self.screen.show()
|
|
372
|
+
continue
|
|
373
|
+
if result == "Vectors":
|
|
374
|
+
# get device, vector and show VectorScreen
|
|
375
|
+
self.screen = windows.VectorScreen(self.stdscr, self, self.screen.devicename, self.screen.vectorname)
|
|
376
|
+
self.screen.show()
|
|
377
|
+
continue
|
|
378
|
+
if isinstance(self.screen, windows.VectorScreen):
|
|
379
|
+
result = await self.screen.inputs()
|
|
380
|
+
if result == "Resize":
|
|
381
|
+
self.maxrows, self.maxcols = self.stdscr.getmaxyx()
|
|
382
|
+
if self.maxrows < 16 or self.maxcols < 40:
|
|
383
|
+
self.shutdown()
|
|
384
|
+
continue
|
|
385
|
+
if self.maxrows < MINROWS or self.maxcols < MINCOLS:
|
|
386
|
+
self.screen = windows.TooSmall(self.stdscr, self)
|
|
387
|
+
self.screen.show()
|
|
388
|
+
continue
|
|
389
|
+
self.screen = windows.VectorScreen(self.stdscr, self, self.screen.devicename, self.screen.vectorname)
|
|
390
|
+
self.screen.show()
|
|
391
|
+
continue
|
|
392
|
+
if result == "Quit":
|
|
393
|
+
self._shutdown = True
|
|
394
|
+
break
|
|
395
|
+
if result == "Messages":
|
|
396
|
+
self.screen = windows.MessagesScreen(self.stdscr, self)
|
|
397
|
+
self.screen.show()
|
|
398
|
+
continue
|
|
399
|
+
if result == "Devices":
|
|
400
|
+
self.screen = windows.DevicesScreen(self.stdscr, self)
|
|
401
|
+
self.screen.show()
|
|
402
|
+
continue
|
|
403
|
+
if result == "Vectors":
|
|
404
|
+
self.screen = windows.ChooseVectorScreen(self.stdscr, self, self.screen.devicename, group=self.screen.vector.group)
|
|
405
|
+
self.screen.show()
|
|
406
|
+
continue
|
|
407
|
+
|
|
408
|
+
except asyncio.CancelledError:
|
|
409
|
+
self._shutdown = True
|
|
410
|
+
raise
|
|
411
|
+
except Exception as e:
|
|
412
|
+
widgets.ERRORDATA.append(TBE.from_exception(e))
|
|
413
|
+
self._shutdown = True
|
|
414
|
+
finally:
|
|
415
|
+
self.getinputstopped = True
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def send_enableBLOB(self):
|
|
419
|
+
"Sends Also to enable blobs for all devices"
|
|
420
|
+
if not self.blobenabled:
|
|
421
|
+
return
|
|
422
|
+
for devicename,device in self.client.items():
|
|
423
|
+
if device.enable:
|
|
424
|
+
self.client.send_enableBLOB('Also', devicename)
|
|
425
|
+
|
|
426
|
+
def send_disableBLOB(self):
|
|
427
|
+
"Sends Never to disable blobs for all devices"
|
|
428
|
+
if self.blobenabled:
|
|
429
|
+
return
|
|
430
|
+
for devicename,device in self.client.items():
|
|
431
|
+
if device.enable:
|
|
432
|
+
self.client.send_enableBLOB('Never', devicename)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
async def asyncrun(self):
|
|
436
|
+
"""Gathers tasks to be run simultaneously"""
|
|
437
|
+
self.stop = False
|
|
438
|
+
await asyncio.gather(self.updatescreen(), self.getinput(), self._checkshutdown())
|