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

248 statements  

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 

58 

59# 

60# abstract class for communication thread 

61# 

62class cls_pilthread_generic(QtCore.QThread): 

63 

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 

155 

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): 

204 

205 def __init__(self,parent): 

206 super().__init__(parent) 

207 self.__lasth__=0 

208 self.__baudrate__=0 

209 

210 

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) 

288 

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) 

300 

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 

305 

306# 

307# HP-IL over TCP-IP communication thread (see http://hp.giesselink.com/hpil.htm) 

308# 

309class cls_PilTcpIpThread(cls_pilthread_generic): 

310 

311 def __init__(self, parent): 

312 super().__init__(parent) 

313 

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 

327 

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') 

358 

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) 

371 

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): 

381 

382 def __init__(self, parent): 

383 super().__init__(parent) 

384 

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 

419 

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) 

457 

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) 

476 

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