15
15
import traceback
16
16
import subprocess
17
17
from glob import glob
18
- from atom .api import Atom , List , Str , Instance
18
+ from atom .api import Atom , List , Str , Instance , Value
19
19
from inkcut .core .api import Plugin , log
20
+ from inkcut .device .plugin import DeviceTransport
21
+ from twisted .internet import abstract , fdesc
22
+ import twisted .internet
20
23
21
- from inkcut .device .transports .raw .plugin import (
22
- RawFdTransport , RawFdProtocol , RawFdConfig
23
- )
24
+ from inkcut .device .transports .raw .plugin import RawFdProtocol , RawFdConfig
24
25
25
26
26
27
class ParallelPortDescriptor (Atom ):
@@ -32,7 +33,7 @@ def __str__(self):
32
33
33
34
34
35
def find_dev_name (dev ):
35
- """ Use udevadm to lookup info on a device
36
+ """Use udevadm to lookup info on a device
36
37
37
38
Parameters
38
39
----------
@@ -46,33 +47,33 @@ def find_dev_name(dev):
46
47
47
48
"""
48
49
try :
49
- cmd = ' udevadm info -a %s' % dev
50
+ cmd = " udevadm info -a %s" % dev
50
51
manufacturer = ""
51
52
product = ""
52
53
53
54
output = subprocess .check_output (cmd .split ())
54
55
if sys .version_info .major > 2 :
55
56
output = output .decode ()
56
- for line in output .split (' \n ' ):
57
+ for line in output .split (" \n " ):
57
58
log .debug (line )
58
59
m = re .search (r'ATTRS{(.+)}=="(.+)"' , line )
59
60
if m :
60
61
k , v = m .groups ()
61
- if k == ' manufacturer' :
62
+ if k == " manufacturer" :
62
63
manufacturer = v .strip ()
63
- elif k == ' product' :
64
+ elif k == " product" :
64
65
product = v .strip ()
65
66
if manufacturer and product :
66
- return ' {} {}' .format (manufacturer , product )
67
- log .warning (' Could not lookup device info for %s' % dev )
67
+ return " {} {}" .format (manufacturer , product )
68
+ log .warning (" Could not lookup device info for %s" % dev )
68
69
except Exception as e :
69
70
tb = traceback .format_exc ()
70
- log .warning (' Could not lookup device info for %s %s' % (dev , tb ))
71
- return ' usb%s' % dev .split ('/' )[- 1 ]
71
+ log .warning (" Could not lookup device info for %s %s" % (dev , tb ))
72
+ return " usb%s" % dev .split ("/" )[- 1 ]
72
73
73
74
74
75
def find_ports ():
75
- """ Lookup ports from known locations on the system
76
+ """Lookup ports from known locations on the system
76
77
77
78
Returns
78
79
-------
@@ -81,22 +82,22 @@ def find_ports():
81
82
82
83
"""
83
84
ports = []
84
- if ' win32' in sys .platform :
85
+ if " win32" in sys .platform :
85
86
pass # TODO
86
- elif ' darwin' in sys .platform :
87
+ elif " darwin" in sys .platform :
87
88
pass # TODO
88
89
else :
89
- for p in glob (' /dev/lp*' ):
90
+ for p in glob (" /dev/lp*" ):
90
91
# TODO: Get friendly device name
91
- name = p .split ('/' )[- 1 ]
92
+ name = p .split ("/" )[- 1 ]
92
93
ports .append (ParallelPortDescriptor (device = p , name = name ))
93
94
94
- for p in glob (' /dev/parport*' ):
95
+ for p in glob (" /dev/parport*" ):
95
96
# TODO: Get friendly device name
96
- name = p .split ('/' )[- 1 ]
97
+ name = p .split ("/" )[- 1 ]
97
98
ports .append (ParallelPortDescriptor (device = p , name = name ))
98
99
99
- for p in glob (' /dev/usb/lp*' ):
100
+ for p in glob (" /dev/usb/lp*" ):
100
101
name = find_dev_name (p )
101
102
ports .append (ParallelPortDescriptor (device = p , name = name ))
102
103
@@ -122,15 +123,122 @@ def refresh(self):
122
123
self .ports = self ._default_ports ()
123
124
124
125
125
- class ParallelTransport (RawFdTransport ):
126
- """ This is just a wrapper for the RawFdTransport
126
+ class ParallelTwistedTransport (abstract .FileDescriptor ):
127
+ connected = 1
128
+
129
+ def __init__ (
130
+ self ,
131
+ port_name : str ,
132
+ protocol : RawFdProtocol ,
133
+ reactor = None ,
134
+ ):
135
+ abstract .FileDescriptor .__init__ (self , reactor )
136
+ self .fd = open (port_name , "r+b" )
137
+ fdesc .setNonBlocking (self .fileno ())
138
+ self .protocol = protocol
139
+ self .written_something = False
140
+ self .protocol .makeConnection (self )
141
+ self .startReading ()
142
+
143
+ def fileno (self ):
144
+ return self .fd .fileno ()
145
+
146
+ def writeSomeData (self , data ):
147
+ res = fdesc .writeToFD (self .fileno (), data )
148
+ self .written_something = self .written_something or res > 0
149
+ return res
150
+
151
+ def doRead (self ):
152
+ return fdesc .readFromFD (self .fileno (), self .protocol .dataReceived )
153
+
154
+ def doWrite (self ):
155
+ self .written_something = False
156
+ return abstract .FileDescriptor .doWrite (self )
157
+
158
+ def stopWriting (self ):
159
+ abstract .FileDescriptor .stopWriting (self )
160
+
161
+ def connectionLost (self , reason ):
162
+ if (
163
+ self .written_something
164
+ and "linux" in sys .platform
165
+ and isinstance (reason .value , twisted .internet .error .ConnectionDone )
166
+ ):
167
+
168
+ # Queue up one more iteration in select loop to wait until write is really done.
169
+ # Linux usblp driver has a documented quirk where closing it in nonblocking mode can cause dropping
170
+ # pending data.
171
+ # https://github.com/torvalds/linux/blob/df87d843c6eb4dad31b7bf63614549dd3521fe71/drivers/usb/class/usblp.c#L893C1-L898C1
172
+ # https://github.com/karliss/inkcut/issues/56
173
+ self .startWriting ()
174
+ return
175
+
176
+ abstract .FileDescriptor .connectionLost (self , reason )
177
+ self .fd .close ()
178
+ self .protocol .connectionLost (reason )
179
+ log .debug ("Closed {}" .format (self .fd .name ))
180
+
181
+
182
+ class ParallelTransport (DeviceTransport ):
183
+ """This is just a wrapper for the RawFdTransport"""
127
184
128
- """
129
185
#: Default config
130
186
config = Instance (ParallelConfig , ()).tag (config = True )
131
187
188
+ #: Current path
189
+ device_path = Str ()
190
+ #: Wrapper which forwards twisted protocol to inkcut
191
+ _wrapper_protocol = Instance (RawFdProtocol )
192
+
193
+ #: A raw device connection
194
+ connection = Instance (ParallelTwistedTransport )
195
+
196
+ def connect (self ):
197
+ config = self .config
198
+ device_path = self .device_path = config .device_path
199
+ try :
200
+ log .debug ("-- {} | opened" .format (device_path ))
201
+ self ._wrapper_protocol = RawFdProtocol (self , self .protocol )
202
+ self .connection = ParallelTwistedTransport (
203
+ device_path , self ._wrapper_protocol
204
+ )
205
+ except Exception as e :
206
+ #: Make sure to log any issues as these tracebacks can get
207
+ #: squashed by twisted
208
+ log .error (
209
+ "Parallel port open failed {} | {}" .format (
210
+ device_path , traceback .format_exc ()
211
+ )
212
+ )
213
+ raise
214
+
215
+ def write (self , data ):
216
+ if not self .connection :
217
+ raise IOError ("{} is not opened" .format (self .device_path ))
218
+ log .debug ("-> {} | {}" .format (self .device_path , data ))
219
+ if hasattr (data , "encode" ):
220
+ # TODO: cleanup str/byte handling transport should always receive bytes not strings
221
+ data = data .encode ()
222
+ self .last_write = data
223
+ self .connection .write (data )
224
+
225
+ def disconnect (self ):
226
+ if self .connection :
227
+ log .debug ("-- {} | closed by request" .format (self .device_path ))
228
+ self .connection .loseConnection ()
229
+ self .connection = None
230
+
231
+ def __repr__ (self ):
232
+ return self .device_path
233
+
234
+ @property
235
+ def always_disconnect_after_job (self ) -> bool :
236
+ return False
237
+
238
+ @property
239
+ def auto_disconnect_after_job (self ) -> bool :
240
+ return self .config .close_after_job
132
241
133
- class ParallelPlugin (Plugin ):
134
- """ Plugin for handling parallel port communication
135
242
136
- """
243
+ class ParallelPlugin (Plugin ):
244
+ """Plugin for handling parallel port communication"""
0 commit comments