indipyclient 0.0.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.
@@ -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())