Coverage for pyilper/pilthreads.py: 58%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3# pyILPER 1.6.0 for Linux
4#
5# An emulator for virtual HP-IL devices for the PIL-Box
6# derived from ILPER 1.4.5 for Windows
7# Copyright (c) 2008-2013 Jean-Francois Garnier
8# C++ version (c) 2013 Christoph Gießelink
9# Python Version (c) 2017 Joachim Siebold
10#
11# This program is free software; you can redistribute it and/or
12# modify it under the terms of the GNU General Public License
13# as published by the Free Software Foundation; either version 2
14# of the License, or (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24#
25# pyILPER thread classes -------------------------------------------------------
26#
27# Changelog
28# 07.09.2017 jsi:
29# - refactored from pilboxthread.py, piltcpipthread.py, pilsocketthread.py
30# 21.09.2017 jsi:
31# - removed message "suspended"
32# 05.10.2017 jsi
33# - added global frame counter and store counter of most recent fraame that
34# addressed a device
35# 12.11.2017 jsi
36# - removed PipPipeThread
37# - raise PilThreadError instead of SocketError in cls_PilSocketThread
38# - set self.running= False on thread exit
39# 13.11.2017 cg
40# - made code more robust against illegal ACK in the pil box and pil box
41# simulation interface when receiving byte data from the pil box
42# - removed ACK in pil box simulation after receiving a high byte
43# - fixed detection and acknowledge of a pil box command
44#
45import sys
46import threading
47from PySide6 import QtCore
48from .pilconfig import PILCONFIG
49from .pilbox import cls_pilbox, PilBoxError
50from .piltcpip import cls_piltcpip, TcpIpError
51from .pilcore import assemble_frame, disassemble_frame, COMTMOUTREAD, COMTMOUTACK
52from .pilsocket import cls_pilsocket, SocketError
53#
54class PilThreadError(Exception):
55 def __init__(self,msg,add_msg=None):
56 self.msg=msg
57 self.add_msg= add_msg
59#
60# abstract class for communication thread
61#
62class cls_pilthread_generic(QtCore.QThread):
64 def __init__(self, parent):
65 super().__init__(parent)
66 self.parent=parent
67 self.pause= False
68 self.running=True
69 self.cond=QtCore.QMutex()
70 self.stop=QtCore.QMutex()
71 self.pauseCond=QtCore.QWaitCondition()
72 self.stoppedCond=QtCore.QWaitCondition()
73 self.commobject= None # i/o object (PIL-Box, TCP/IP...)
74 self.framecounter=0 # global frame counter
75 self.addr_framecounter=0 # frame counter of the last
76 # HP-IL addressing command
77 self.devices= [] # list of devices
78#
79# report if thread is running
80#
81 def isRunning(self):
82 return(self.running)
83#
84# send message to status line of GUI
85#
86 def send_message(self,message):
87 self.parent.emit_message(message)
88#
89# signal crash to the main program
90#
91 def signal_crash(self):
92 self.parent.emit_crash()
93#
94# pause thread
95#
96 def halt(self):
97 if self.pause:
98 return
99 self.cond.lock()
100 self.pause= True
101 self.cond.unlock()
102 self.stop.lock()
103 self.stoppedCond.wait(self.stop)
104 self.stop.unlock()
105#
106# restart paused thread
107#
108 def resume(self):
109 if not self.pause:
110 return
111 self.cond.lock()
112 self.pause= False
113 self.cond.unlock()
114 self.pauseCond.wakeAll()
115#
116# finish thread
117#
118 def finish(self):
119 if not self.running:
120 return
121 if self.pause:
122 self.terminate()
123 else:
124 self.cond.lock()
125 self.pause= True
126 self.running= False
127 self.cond.unlock()
128 self.stop.lock()
129 self.stoppedCond.wait(self.stop)
130 self.stop.unlock()
131#
132# check pause/stop conditions
133# pauses if pause condition
134# returns True if stop condition, False otherwise
135#
136 def check_pause_stop(self):
137 self.cond.lock()
138 if(self.pause):
139 self.stop.lock()
140 if not self.running:
141 self.stoppedCond.wakeAll()
142 self.stop.unlock()
143 return True
144 self.stoppedCond.wakeAll()
145 self.stop.unlock()
146 self.pauseCond.wait(self.cond)
147 self.cond.unlock()
148 return False
149#
150# enable/ disable
151#
152 def enable(self):
153 self.devices=[]
154 return
156 def disable(self):
157 if self.commobject is not None:
158 try:
159 self.commobject.close()
160 except:
161 pass
162 self.commobject= None
163 return
164#
165# register devices, make thread object known to the device object
166#
167 def register(self, obj, name):
168 self.devices.append([obj,name])
169 obj.setThreadObject(self)
170#
171# get device list
172#
173 def getDevices(self):
174 return self.devices
175#
176# increase global frame counter
177#
178 def update_framecounter(self):
179 self.framecounter+=1
180#
181# get value of global frame counter
182#
183 def get_framecounter(self):
184 return self.framecounter
185#
186# update value of addr_framecounter
187#
188 def update_addr_framecounter(self,value):
189 self.addr_framecounter=value
190#
191# get value of addr_framecounter
192#
193 def get_addr_framecounter(self):
194 return self.addr_framecounter
195#
196# run method
197#
198 def run(self):
199 sys.settrace(threading._trace_hook)
200#
201# PIL-Box communications thread over serial port
202#
203class cls_PilBoxThread(cls_pilthread_generic):
205 def __init__(self,parent):
206 super().__init__(parent)
207 self.__lasth__=0
208 self.__baudrate__=0
211 def enable(self):
212 super().enable()
213 self.send_message("Not connected to PIL-Box")
214 baud=PILCONFIG.get("pyilper",'ttyspeed')
215 ttydevice=PILCONFIG.get("pyilper",'tty')
216 idyframe=PILCONFIG.get("pyilper",'idyframe')
217 self.__lasth__=0
218 if ttydevice== "":
219 raise PilThreadError("Serial device not configured ","Run pyILPER configuration")
220 try:
221 self.commobject=cls_pilbox(ttydevice,baud,idyframe)
222 self.commobject.open()
223 except PilBoxError as e:
224 raise PilThreadError(e.msg,e.add_msg)
225 self.__baudrate__= self.commobject.getBaudRate()
226 return
227#
228# thread execution
229#
230 def run(self):
231 super().run()
232#
233 self.send_message("connected to PIL-Box at {:d} baud".format(self.__baudrate__))
234 try:
235#
236# Thread main loop
237#
238 while True:
239 if self.check_pause_stop():
240 break
241#
242# read byte from PIL-Box
243#
244 ret=self.commobject.read()
245 if ret== b'':
246 continue
247 byt=ord(ret)
248#
249# process byte read from the PIL-Box, is not a low byte
250#
251 if (byt & 0xC0) == 0x00:
252#
253# check for high byte, else ignore
254#
255 if (byt & 0x20) != 0:
256#
257# got high byte, save it
258#
259 self.__lasth__ = byt & 0xFF
260#
261# send acknowledge only at 9600 baud connection
262#
263 if self.__baudrate__ == 9600:
264 self.commobject.write(0x0d)
265 continue
266#
267# low byte, build frame
268#
269 frame= assemble_frame(self.__lasth__,byt)
270#
271# process virtual HP-IL devices
272#
273 self.update_framecounter()
274 for i in self.devices:
275 frame=i[0].process(frame)
276#
277# If received a cmd frame from the PIL-Box send RFC frame to virtual
278# HPIL-Devices
279#
280 if (frame & 0x700) == 0x400:
281 self.update_framecounter()
282 for i in self.devices:
283 i[0].process(0x500)
284#
285# disassemble into low and high byte
286#
287 hbyt, lbyt= disassemble_frame(frame)
289 if hbyt != self.__lasth__:
290#
291# send high part if different from last one and low part
292#
293 self.__lasth__ = hbyt
294 self.commobject.write(lbyt,hbyt)
295 else:
296#
297# otherwise send only low part
298#
299 self.commobject.write(lbyt)
301 except PilBoxError as e:
302 self.send_message('PIL-Box disconnected after error. '+e.msg+': '+e.add_msg)
303 self.signal_crash()
304 self.running=False
306#
307# HP-IL over TCP-IP communication thread (see http://hp.giesselink.com/hpil.htm)
308#
309class cls_PilTcpIpThread(cls_pilthread_generic):
311 def __init__(self, parent):
312 super().__init__(parent)
314 def enable(self):
315 port= PILCONFIG.get("pyilper","port")
316 remote_host=PILCONFIG.get("pyilper","remotehost")
317 remote_port=PILCONFIG.get("pyilper","remoteport")
318 self.send_message('Not connected to virtual HP-IL devices')
319 try:
320 self.commobject= cls_piltcpip(port, remote_host, remote_port)
321 self.commobject.open()
322 except TcpIpError as e:
323 self.commobject.close()
324 self.commobject=None
325 raise PilThreadError(e.msg, e.add_msg)
326 return
328 def disable(self):
329 super().disable()
330 return
331#
332# thread execution
333#
334 def run(self):
335 super().run()
336#
337 connected=False
338 try:
339#
340# Thread main loop
341#
342 while True:
343 if self.check_pause_stop():
344 break
345#
346# read frame from Network
347#
348 frame=self.commobject.read(COMTMOUTREAD)
349 if self.commobject.isConnected():
350 if not connected:
351 connected=True
352 self.send_message('connected to virtual HP-IL devices')
353 else:
354 if connected:
355 connected= False
356 self.commobject.close_outsocket()
357 self.send_message('not connected to virtual HP-IL devices')
359 if frame is None:
360 continue
361#
362# process frame and return it to loop
363#
364 self.update_framecounter()
365 for i in self.devices:
366 frame=i[0].process(frame)
367#
368# send frame
369#
370 self.commobject.write(frame)
372 except TcpIpError as e:
373 self.send_message('disconnected after error. '+e.msg+': '+e.add_msg)
374 self.send_message(e.msg)
375 self.signal_crash()
376 self.running=False
377#
378# TCP/IP socket communication thread with DOSBox or virtualbox serial port
379#
380class cls_PilSocketThread(cls_pilthread_generic):
382 def __init__(self, parent):
383 super().__init__(parent)
385 def enable(self):
386 self.send_message("Not connected to socket")
387 socket_name=PILCONFIG.get("pyilper","serverport")
388 try:
389 self.commobject= cls_pilsocket(socket_name)
390 self.commobject.open()
391 except SocketError as e:
392 self.commobject.close()
393 self.commobject=None
394 raise PilThreadError(e.msg, e.add_msg)
395 return
396#
397# thread execution
398#
399 def run(self):
400 super().run()
401#
402 try:
403#
404# Thread main loop
405#
406 while True:
407 if self.check_pause_stop():
408 break
409 if self.commobject.isConnected():
410 self.send_message('client connected')
411 else:
412 self.send_message('waiting for client')
413#
414# read byte from socket
415#
416 ret=self.commobject.read(COMTMOUTREAD)
417 if ret is None:
418 continue
420 byt=ord(ret)
421#
422# is not a low byte
423#
424 if (byt & 0xC0) == 0x00:
425#
426# check for high byte, else ignore
427#
428 if (byt & 0x20) != 0:
429#
430# got high byte, save it and continue
431#
432 self.__lasth__ = byt & 0xFF
433 continue
434#
435# low byte, assemble frame according to 7- oder 8 bit format
436#
437 frame= assemble_frame(self.__lasth__,byt)
438#
439# send acknowledge if we received a pil box command
440#
441 if frame & 0x7F4 == 0x494:
442#
443# send only original low byte as acknowledge
444#
445 lbyt = byt
446#
447# process virtual HP-IL devices
448#
449 else:
450 self.update_framecounter()
451 for i in self.devices:
452 frame=i[0].process(frame)
453#
454# disassemble answer frame
455#
456 hbyt, lbyt= disassemble_frame(frame)
458 if hbyt != self.__lasth__:
459#
460# send high part if different from last one
461#
462 self.__lasth__ = hbyt
463 self.commobject.write(hbyt)
464#
465# read acknowledge
466#
467 b= self.commobject.read(COMTMOUTACK)
468 if b is None:
469 raise PilThreadError("cannot get acknowledge: ","timeout")
470 if ord(b)!= 0x0D:
471 raise PilThreadError("cannot get acknowledge: ","unexpected value")
472#
473# otherwise send only low part
474#
475 self.commobject.write(lbyt)
477 except SocketError as e:
478 self.send_message('socket disconnected after error. '+e.msg+': '+e.add_msg)
479 self.signal_crash()
480 self.running=False