Coverage for pyilper/pildevbase.py: 87%
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.1 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) 2015 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# HP-IL virtual device base object class ---------------------------------------
26#
27# Derived fom IldevBase.cpp from Christoph Gießelink
28#
29# Changelog
30#
31# 16.02.2016 jsi:
32# - merged new Ildev base class of Christoph Gießelink
33# 01.03.2016 cg:
34# - removed EOT response from __outdata__ to implement "no response" feature
35# 05.03.2016 jsi:
36# - wrong variable name __ptdsi__ corrected
37# - improved getstatus, use accesss lock and return ilstate as text
38# 02.08.2017 jsi:
39# - class variable for length of status information introduced, alter
40# statement to clear service request bit to handle multiple byte status data
41# changed the order of status bytes output (least significant byte first)
42# - reset service request bit, if DCL odr SDC
43# 07.08.2017 jsi:
44# - register functions for callbacks removed
45# 10.08.2017 jsi
46# - check if self.__did__ != "" and not != None
47# 05.10.2017 jsi:
48# - reset device address if device was reactivated an an HP-IL addressing
49# operation happened in the meantime
50# 18.12.2017 jsi:
51# - check whether the sent and the incoming data byte are identical if a
52# virtual device is talker. Send ETE if not.
53# - fix a bug in handling the SRQ bit in DOE frames
54# Both changes are courtesy of Christoph Gießelink
55# 16.01.2018 jsi:
56# - adapted to new local config parameters for color scheme and terminal width
59import threading
61class cls_pildevbase:
63 def __init__(self):
65 self.__aid__ = 0x00 # accessory id
66 self.__defaddr__ = 0 # default address alter AAU
67 self.__did__ = "" # device id
68 self.__status__ = 0 # HP-IL status
69 self.__addr__ = 0 # HP-IL primary address (by TAD,LAD)
70 # bits 0-5=AAD or AEP, bit 7=1 means
71 # auto address taken
72 self.__addr2nd__ = 0 # HP-IL secondary address (by SAD)
73 # bits 0-5=AES, bit 7 means auto addr taken
74 self.__ilstate__ =0 # state machine flag
75 # bit 7, bit 6, bit5, bit 4
76 # 0 0 0 0 idle
77 # 0 0 1 0 addressed listener in
78 # second. add. mode
79 # 0 0 0 1 addressed talker in
80 # second. add. mode
81 # 1 0 0 0 addressed listener
82 # 0 1 0 0 addressed talker
83 # bit 0 or bit 1 set active takler
84 # bit 1: SDA, SDI
85 # bit 0: SST, SDI, SAI, NRD
86 self.__talker_frame__=0 # frame sent as talker
87 self.__ptsdi__ = 0 # output pointer for device id
88 self.__status_len__=1 # length of device status in bytes
89 self.__ptssi__ = 0 # output pointer for hp-il status
90 self.__isactive__= False # device active in loop
91 self.__addr_framecounter__=0 # framecounter when device got an address
92 self.__threadobject__=None # reference to the thread object
93 self.__status_lock__= threading.Lock()
94 self.__islocked__= False
95 self.__access_lock__= threading.Lock()
96#
97# --- public functions ---
98#
99# set device active/inactive. If the device becomes active check if an AAU,
100# AAD, AEP or AES happened in the mean time. In this case reset the device
101# address
102#
103 def setactive(self, active):
104 if not self.__isactive__ and active:
105 if self.__addr_framecounter__ != self.__threadobject__.get_addr_framecounter():
106 self.__addr__=0
107 self.__addr2nd__=0
108 self.__isactive__= active
109#
110# set object reference to thread object
111#
112 def setThreadObject(self,obj):
113 self.__threadobject__= obj
114#
115# set local and update globel addr_framecounter
116#
117 def update_addr_framecounter(self):
118 self.__addr_framecounter__= self.__threadobject__.get_framecounter()
119 self.__threadobject__.update_addr_framecounter(self.__addr_framecounter__)
120#
121# return device status
122#
123 def getstatus(self):
124 self.__access_lock__.acquire()
125 status="idle"
126 if self.__ilstate__ & 0x03:
127 status="act. talker"
128 else:
129 if self.__ilstate__ & 0xA0:
130 status="addr. listener"
131 elif self.__ilstate__ & 0x50:
132 status="addr. talker"
134 ret= [self.__isactive__, self.__did__, self.__aid__, self.__addr__, self.__addr2nd__, status]
135 self.__access_lock__.release()
136 return ret
138#
139# lock device, all output is disabled
140#
141 def setlocked(self,locked):
142 self.__access_lock__.acquire()
143 self.__islocked__= locked
144 self.__access_lock__.release()
146#
147# Process device
148#
149 def process(self,frame):
151#
152# if device is not active, return
153#
154 if not self.__isactive__:
155 return(frame)
156#
157# process frames
158#
159 if (frame & 0x400) == 0:
160 frame= self.__do_doe__(frame)
161 elif (frame & 0x700) == 0x400:
162 frame= self.__do_cmd__(frame)
163 elif (frame & 0x700) == 0x500:
164 frame= self.__do_rdy__(frame)
165#
166# set service request bit if data available status bit set
167#
168 if self.__getstatus__() & 0x40:
169 if (frame & 0x700) == 0x000: # data 00x xxxx xxxx -> 001 xxxx xxxx
170 frame= frame | 0x100
171 if (frame & 0x700) == 0x200: # end 01x xxxx xxxx -> 011 xxxx xxxx
172 frame= frame | 0x100
173 if (frame & 0x700) == 0x600: # idy 11x xxxx xxxx -> 111 xxxx xxxx
174 frame= frame | 0x100
175 return(frame)
177#
178# --- private ---
179#
180#
181# get status byte from device thread safe
182#
183 def __getstatus__(self):
184 self.__status_lock__.acquire()
185 status= self.__status__
186 self.__status_lock__.release()
187 return(status)
188#
189# set status byte of device thread safe
190#
191 def __setstatus__(self,status):
192 self.__status_lock__.acquire()
193 self.__status__= status
194 self.__status_lock__.release()
195#
196# output data stub
197#
198 def __outdata__(self,frame):
199 return frame
201#
202# input data stub
203#
204 def __indata__(self,frame):
205 return
207#
208# device clear stub
209#
210 def __clear_device__(self):
211 # reset service request bit
212 s= self.__getstatus__()
213 s &= ~ 0x40
214 self.__setstatus__(s)
215 return
216#
217# stub for extended sad commands
218#
219 def __cmd_sad_ext__(self,frame):
220 return frame
222#
223# stub for extended commands
224#
225 def __cmd_ext__(self,frame):
226 return frame
228#
229# manage HPIL data frames, returns the returned frame
230#
231 def __do_doe__(self,frame):
232 talker_error= False
234 if (self.__ilstate__ & 0xC0) == 0x40: # addressed talker?
236 if (self.__ilstate__ & 0x03) != 0: # active talker?
238# compare last talker frame with actual frame without SRQ bit
239 talker_error = ((frame & 0x6FF) != (self.__talker_frame__ &0x6FF))
241# data (SDA) status (SST) or accessory ID (SDI)
242 if (not talker_error) and (self.__ilstate__ & 0x02 !=0): # talker
244# save current SRQ bit
245 SrqBit= frame & 0x100
247# status (SST) or accessory ID (SDI)
248 if (self.__ilstate__ & 0x01) != 0:
250# 0x43: active talker (multibyte status)
251 if self.__ptssi__ > 0: # SST
252 self.__ptssi__= self.__ptssi__-1
253 if self.__ptssi__ > 0:
254 frame= (self.__getstatus__() >> (( self.__status_len__-self.__ptssi__ ) * 8)) & 0xFF
255 if self.__ptsdi__ > 0: # SDI
256 if self.__ptsdi__ == len(self.__did__):
257 frame=0
258 self.__ptsdi__=0
259 else: # SDI
260 frame= ord(self.__did__ [self.__ptsdi__])
261 self.__ptsdi__= self.__ptsdi__+1
262 if self.__ptssi__ == 0 and self.__ptsdi__ == 0 :
263# EOT for SST and SDI
264 frame= 0x540
265 else: # 0x42 active talker (data)
266 frame=self.__outdata__(frame) # SDA
267# a set SRQ bit doesn't matter on ready class frames
268 frame |= SrqBit
269 else: # 0x41 active talker (single byte status)
270 frame= 0x540 # end of SAI or NRD or talker error
272 if frame == 0x540: # EOT
273# check for error and set ETE frame
274 if talker_error:
275 frame= 0x541 # ETE
276 self.__ilstate__ &= ~ 0x03 # delete active talker
277 self.__talker_frame__= frame
279 if (self.__ilstate__ & 0xC0)==0x80: # listener
280 self.__indata__(frame)
281 return(frame)
282#
283# manage HP-IL command frames
284#
285 def __do_cmd__(self,frame):
287 n= frame & 0xff
288 t= n >> 5
289 if t == 0:
290 if n == 4: # SDC
291 if (self.__ilstate__ & 0x80) != 0: # listener
292 self.__clear_device__()
293 elif n == 20: # DCL
294 self.__clear_device__()
295 elif t == 1: # LAD
296 n= n & 31
297 if n == 31: # UNL, if not talker then go to idle state
298 if (self.__ilstate__ & 0xA0) != 0: # not talker ?
299 self.__ilstate__ &= 0x50 # idle
300 else: # if MLA go to listen state
301 if (self.__ilstate__ &0x80) ==0 and n == (self.__addr__ & 31):
302 if (self.__addr2nd__ & 0x80) == 0:
303 self.__ilstate__ = 0x80 # set addressed listener
304 else:
305 self.__ilstate__ = 0x20 # set addressed listener second. add mode
306 elif t == 2: # TAD
307 n= n & 31
308 if n == (self.__addr__ & 31):
309 if (self.__addr2nd__ & 0x80) == 0: # if MTA go to talker state
310 self.__ilstate__= 0x40 # set addressed talker
311 else:
312 self.__ilstate__= 0x10 # set addressed talker, second. add. mode
313 else:
314 if ( self.__ilstate__ & 0x50) != 0: # addressed talker?
315 self.__ilstate__ &= 0xA0 # idle
316 elif t == 3: # SAD
317 if (self.__ilstate__ & 0x30) !=0: # addressed talker or listener 2nd addr mode?
318 n = n & 31
319 if n == (self.__addr2nd__ & 31):
320 self.__ilstate__<<=2 # switch to addressed listener/talker
321 else:
322 self.__ilstate__=0 # idle
323 else:
324 frame= self.__cmd_sad_ext__(frame)
325 elif t == 4:
326 n= n & 31
327 if n == 16: # IFC
328 self.__ilstate__= 0x0 # idle
329 elif n == 26: # AAU
330 self.update_addr_framecounter()
331 self.__addr__= self.__defaddr__
332 self.__addr2nd__= 0
333 else:
334 frame= self.__cmd_ext__(frame)
335 return(frame)
337#
338# HP-IL ready frames
339#
340 def __do_rdy__(self,frame):
342 n= frame & 0xFF
343 if n <= 127:
344 if ( self.__ilstate__ & 0x40) !=0: # SOT, addressed talker?
345 if n == 66: #NRD
346 self.__ptsdi__ = 0
347 self.__ptssi__ = 0
348 self.__ilstate__ = 0x41 # abort transfer, clear SDA/SDI
350 elif n == 96: # SDA
351 frame=self.__outdata__(frame)
352 if frame != 0x560: # not sda received data
353 self.__ilstate__= 0x42 # active talker, SDA/SDI
354 self.__talker_frame__= frame # last talker frame
355 elif n == 97: # SST
356 # reset service request bit
357 s= self.__getstatus__()
358 s &= ~ 0x40
359 self.__setstatus__(s)
360 # update IL status and return no. of status bytes
361 self.__ptssi__ = self.__status_len__
362 if self.__ptssi__ > 0: # response to status request
363 frame = (self.__getstatus__() >> ((self.__status_len__-self.__ptssi__) * 8)) & 0xFF
364 self.__ilstate__= 0x43 # active talker
365 self.__talker_frame__= frame # last talker frame
366 elif n == 98: # SDI
367 if self.__did__ != "":
368 frame= ord(self.__did__[0])
369 self.__ptsdi__ = 1 # other 2
370 self.__ilstate__= 0x43 # active talker, SDA/SDI, SST/SDI/SAI
371 self.__talker_frame__= frame
372 elif n == 99: # SAI
373 frame= self.__aid__ & 0xFF
374 self.__ilstate__= 0x41 # active talker, SST/SDI/SAI
375 self.__talker_frame__= frame
376 else:
377 if n < 0x80 +31: # AAD
378 if ((self.__addr__ & 0x80) == 0 and self.__addr2nd__ ==0):
379 # AAD, if not already an assigned address, take it
380 self.update_addr_framecounter()
381 self.__addr__ = n
382 frame=frame+1
383 elif (n >= 0xA0 and n < 0xA0 + 31): # AEP
384 # AEP, if not already an assigned address and got an AES frame,
385 if ((self.__addr__ & 0x80) == 0 and (self.__addr2nd__ & 0x80) != 0):
386 # take it
387 self.update_addr_framecounter()
388 self.__addr__= n & 0x9F
389 elif (n >= 0xC0 and n < 0xC0 +31): # AES
390 if (self.__addr__ & 0x80) == 0:
391 self.update_addr_framecounter()
392 self.__addr2nd__= n & 0x9F
393 frame=frame + 1
394 return (frame)