1""" 2Asynchronous (nonblocking) serial io 3 4""" 5from __future__ import absolute_import, division, print_function 6 7import sys 8import os 9import errno 10from collections import deque 11 12# Import ioflo libs 13from ...aid.sixing import * 14from ...aid.consoling import getConsole 15 16console = getConsole() 17 18class ConsoleNb(object): 19 """ 20 Class to manage non blocking io on serial console. 21 22 Opens non blocking read file descriptor on console 23 Use instance method close to close file descriptor 24 Use instance methods getline & put to read & write to console 25 Needs os module 26 """ 27 28 def __init__(self): 29 """Initialization method for instance. 30 31 """ 32 self.fd = None #console file descriptor needs to be opened 33 34 def open(self, port='', canonical=True): 35 """ 36 Opens fd on terminal console in non blocking mode. 37 38 port is the serial port device path name 39 or if '' then use os.ctermid() which 40 returns path name of console usually '/dev/tty' 41 42 canonical sets the mode for the port. Canonical means no characters 43 available until a newline 44 45 os.O_NONBLOCK makes non blocking io 46 os.O_RDWR allows both read and write. 47 os.O_NOCTTY don't make this the controlling terminal of the process 48 O_NOCTTY is only for cross platform portability BSD never makes it the 49 controlling terminal 50 51 Don't use print at same time since it will mess up non blocking reads. 52 53 Default is canonical mode so no characters available until newline 54 need to add code to enable non canonical mode 55 56 It appears that canonical mode only applies to the console. For other 57 serial ports the characters are available immediately 58 """ 59 if not port: 60 port = os.ctermid() #default to console 61 62 try: 63 self.fd = os.open(port, os.O_NONBLOCK | os.O_RDWR | os.O_NOCTTY) 64 except OSError as ex: 65 console.terse("os.error = {0}\n".format(ex)) 66 return False 67 return True 68 69 def close(self): 70 """Closes fd. 71 72 """ 73 if self.fd: 74 os.close(self.fd) 75 self.fd = None 76 77 def getLine(self,bs = 80): 78 """Gets nonblocking line from console up to bs characters including newline. 79 80 Returns empty string if no characters available else returns line. 81 In canonical mode no chars available until newline is entered. 82 """ 83 line = '' 84 try: 85 line = os.read(self.fd, bs) 86 except OSError as ex1: #if no chars available generates exception 87 try: #need to catch correct exception 88 errno = ex1.args[0] #if args not sequence get TypeError 89 if errno == 35: 90 pass #No characters available 91 else: 92 raise #re raise exception ex1 93 except TypeError as ex2: #catch args[0] mismatch above 94 raise ex1 #ignore TypeError, re-raise exception ex1 95 96 return line 97 98 def put(self, data = '\n'): 99 """Writes data string to console. 100 101 """ 102 return(os.write(self.fd, data)) 103 104class DeviceNb(object): 105 """ 106 Class to manage non blocking IO on serial device port. 107 108 Opens non blocking read file descriptor on serial port 109 Use instance method close to close file descriptor 110 Use instance methods get & put to read & write to serial device 111 Needs os module 112 """ 113 114 def __init__(self, port=None, speed=9600, bs=1024): 115 """ 116 Initialization method for instance. 117 118 port = serial device port path string 119 speed = serial port speed in bps 120 bs = buffer size for reads 121 """ 122 self.fd = None #serial device port file descriptor, must be opened first 123 self.port = port or os.ctermid() #default to console 124 self.speed = speed or 9600 125 self.bs = bs or 1024 126 self.opened = False 127 128 def open(self, port=None, speed=None, bs=None): 129 """ 130 Opens fd on serial port in non blocking mode. 131 132 port is the serial port device path name or 133 if '' then use os.ctermid() which 134 returns path name of console usually '/dev/tty' 135 136 os.O_NONBLOCK makes non blocking io 137 os.O_RDWR allows both read and write. 138 os.O_NOCTTY don't make this the controlling terminal of the process 139 O_NOCTTY is only for cross platform portability BSD never makes it the 140 controlling terminal 141 142 Don't use print and console at same time since it will mess up non blocking reads. 143 144 Raw mode 145 146 def setraw(fd, when=TCSAFLUSH): 147 Put terminal into a raw mode. 148 mode = tcgetattr(fd) 149 mode[IFLAG] = mode[IFLAG] & ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON) 150 mode[OFLAG] = mode[OFLAG] & ~(OPOST) 151 mode[CFLAG] = mode[CFLAG] & ~(CSIZE | PARENB) 152 mode[CFLAG] = mode[CFLAG] | CS8 153 mode[LFLAG] = mode[LFLAG] & ~(ECHO | ICANON | IEXTEN | ISIG) 154 mode[CC][VMIN] = 1 155 mode[CC][VTIME] = 0 156 tcsetattr(fd, when, mode) 157 158 159 # set up raw mode / no echo / binary 160 cflag |= (TERMIOS.CLOCAL|TERMIOS.CREAD) 161 lflag &= ~(TERMIOS.ICANON|TERMIOS.ECHO|TERMIOS.ECHOE|TERMIOS.ECHOK|TERMIOS.ECHONL| 162 TERMIOS.ISIG|TERMIOS.IEXTEN) #|TERMIOS.ECHOPRT 163 for flag in ('ECHOCTL', 'ECHOKE'): # netbsd workaround for Erk 164 if hasattr(TERMIOS, flag): 165 lflag &= ~getattr(TERMIOS, flag) 166 167 oflag &= ~(TERMIOS.OPOST) 168 iflag &= ~(TERMIOS.INLCR|TERMIOS.IGNCR|TERMIOS.ICRNL|TERMIOS.IGNBRK) 169 if hasattr(TERMIOS, 'IUCLC'): 170 iflag &= ~TERMIOS.IUCLC 171 if hasattr(TERMIOS, 'PARMRK'): 172 iflag &= ~TERMIOS.PARMRK 173 174 """ 175 if port is not None: 176 self.port = port 177 if speed is not None: 178 self.speed = speed 179 if bs is not None: 180 self.bs = bs 181 182 self.fd = os.open(self.port, os.O_NONBLOCK | os.O_RDWR | os.O_NOCTTY) 183 184 system = platform.system() 185 186 if (system == 'Darwin') or (system == 'Linux'): #use termios to set values 187 import termios 188 189 iflag, oflag, cflag, lflag, ispeed, ospeed, cc = range(7) 190 191 settings = termios.tcgetattr(self.fd) 192 #print(settings) 193 194 settings[lflag] = (settings[lflag] & ~termios.ICANON) 195 196 settings[lflag] = (settings[lflag] & ~termios.ECHO) # no echo 197 198 #ignore carriage returns on input 199 #settings[iflag] = (settings[iflag] | (termios.IGNCR)) #ignore cr 200 201 # 8N1 8bit word no parity one stop bit nohardware handshake ctsrts 202 # to set size have to mask out(clear) CSIZE bits and or in size 203 settings[cflag] = ((settings[cflag] & ~termios.CSIZE) | termios.CS8) 204 # no parity clear PARENB 205 settings[cflag] = (settings[cflag] & ~termios.PARENB) 206 #one stop bit clear CSTOPB 207 settings[cflag] = (settings[cflag] & ~termios.CSTOPB) 208 #no hardware handshake clear crtscts 209 settings[cflag] = (settings[cflag] & ~termios.CRTSCTS) 210 211 # in linux the speed flag does not equal value so always set it 212 speedattr = "B{0}".format(self.speed) # convert numeric speed to attribute name string 213 speed = getattr(termios, speedattr) 214 settings[ispeed] = speed 215 settings[ospeed] = speed 216 217 termios.tcsetattr(self.fd, termios.TCSANOW, settings) 218 #print(settings) 219 220 self.opened = True 221 222 def reopen(self): 223 """ 224 Idempotently open serial device port 225 """ 226 self.close() 227 return self.open() 228 229 def close(self): 230 """Closes fd. 231 232 """ 233 if self.fd: 234 os.close(self.fd) 235 self.fd = None 236 self.opened = False 237 238 def receive(self): 239 """ 240 Reads nonblocking characters from serial device up to bs characters 241 Returns empty bytes if no characters available else returns all available. 242 In canonical mode no chars are available until newline is entered. 243 """ 244 data = b'' 245 try: 246 data = os.read(self.fd, self.bs) #if no chars available generates exception 247 except OSError as ex1: # ex1 is the target instance of the exception 248 if ex1.errno == errno.EAGAIN: #BSD 35, Linux 11 249 pass #No characters available 250 else: 251 raise #re raise exception ex1 252 253 return data 254 255 def send(self, data=b'\n'): 256 """ 257 Writes data bytes to serial device port. 258 Returns number of bytes sent 259 """ 260 try: 261 count = os.write(self.fd, data) 262 except OSError as ex1: # ex1 is the target instance of the exception 263 if ex1.errno == errno.EAGAIN: #BSD 35, Linux 11 264 count = 0 # buffer full can't write 265 else: 266 raise #re raise exception ex1 267 268 return count 269 270 271class SerialNb(object): 272 """ 273 Class to manage non blocking IO on serial device port using pyserial 274 275 Opens non blocking read file descriptor on serial port 276 Use instance method close to close file descriptor 277 Use instance methods get & put to read & write to serial device 278 Needs os module 279 """ 280 281 def __init__(self, port=None, speed=9600, bs=1024): 282 """ 283 Initialization method for instance. 284 285 port = serial device port path string 286 speed = serial port speed in bps 287 bs = buffer size for reads 288 289 290 """ 291 self.serial = None # Serial instance 292 self.port = port or os.ctermid() #default to console 293 self.speed = speed or 9600 294 self.bs = bs or 1024 295 self.opened = False 296 297 def open(self, port=None, speed=None, bs=None): 298 """ 299 Opens fd on serial port in non blocking mode. 300 301 port is the serial port device path name or 302 if None then use os.ctermid() which returns path name of console 303 usually '/dev/tty' 304 """ 305 if port is not None: 306 self.port = port 307 if speed is not None: 308 self.speed = speed 309 if bs is not None: 310 self.bs = bs 311 312 import serial # import pyserial 313 self.serial = serial.Serial(port=self.port, 314 baudrate=self.speed, 315 timeout=0, 316 writeTimeout=0) 317 #self.serial.nonblocking() 318 self.serial.reset_input_buffer() 319 self.opened = True 320 321 def reopen(self): 322 """ 323 Idempotently open serial device port 324 """ 325 self.close() 326 return self.open() 327 328 def close(self): 329 """ 330 Closes .serial 331 """ 332 if self.serial: 333 self.serial.reset_output_buffer() 334 self.serial.close() 335 self.serial = None 336 self.opened = False 337 338 def receive(self): 339 """ 340 Reads nonblocking characters from serial device up to bs characters 341 Returns empty bytes if no characters available else returns all available. 342 In canonical mode no chars are available until newline is entered. 343 """ 344 data = b'' 345 try: 346 data = self.serial.read(self.bs) #if no chars available generates exception 347 except OSError as ex1: # ex1 is the target instance of the exception 348 if ex1.errno == errno.EAGAIN: #BSD 35, Linux 11 349 pass #No characters available 350 else: 351 raise #re raise exception ex1 352 353 return data 354 355 def send(self, data=b'\n'): 356 """ 357 Writes data bytes to serial device port. 358 Returns number of bytes sent 359 """ 360 try: 361 count = self.serial.write(data) 362 except OSError as ex1: # ex1 is the target instance of the exception 363 if ex1.errno == errno.EAGAIN: #BSD 35, Linux 11 364 count = 0 # buffer full can't write 365 else: 366 raise #re raise exception ex1 367 368 return count 369 370class Driver(object): 371 """ 372 Nonblocking Serial Device Port Driver 373 """ 374 375 def __init__(self, 376 name=u'', 377 uid=0, 378 port=None, 379 speed=9600, 380 bs=1024, 381 server=None): 382 """ 383 Initialization method for instance. 384 385 Parameters: 386 name = user friendly name for driver 387 uid = unique identifier for driver 388 port = serial device port path string 389 speed = serial port speed in bps 390 canonical = canonical mode True or False 391 bs = buffer size for reads 392 server = serial port device server if any 393 394 Attributes: 395 name = user friendly name for driver 396 uid = unique identifier for driver 397 server = serial device server nonblocking 398 txes = deque of data bytes to send 399 rxbs = bytearray of data bytes received 400 401 """ 402 self.name = name 403 self.uid = uid 404 405 if not server: 406 try: 407 import serial 408 self.server = SerialNb(port=port, 409 speed=speed, 410 bs=bs) 411 412 except ImportError as ex: 413 console.terse("Error: importing pyserial\n{0}\n".format(ex)) 414 self.server = DeviceNb(port=port, 415 speed=speed, 416 bs=bs) 417 else: 418 self.server = server 419 420 self.txes = deque() # deque of data to send 421 self.rxbs = bytearray() # byte array of data received 422 423 def serviceReceives(self): 424 """ 425 Service receives until no more 426 """ 427 while self.server.opened: 428 data = self.server.receive() # bytes 429 if not data: 430 break 431 self.rxbs.extend(data) 432 433 def serviceReceiveOnce(self): 434 ''' 435 Retrieve from server only one reception 436 ''' 437 if self.server.opened: 438 data = self.server.receive() 439 if data: 440 self.rxbs.extend(data) 441 442 def clearRxbs(self): 443 """ 444 Clear .rxbs 445 """ 446 del self.rxbs[:] 447 448 def scan(self, start): 449 """ 450 Returns offset of given start byte in self.rxbs 451 Returns None if start is not given or not found 452 If strip then remove any bytes before offset 453 """ 454 offset = self.rxbs.find(start) 455 if offset < 0: 456 return None 457 return offset 458 459 def tx(self, data): 460 ''' 461 Queue data onto .txes 462 ''' 463 self.txes.append(data) 464 465 def _serviceOneTx(self): 466 """ 467 Handle one tx data 468 """ 469 data = self.txes.popleft() 470 count = self.server.send(data) 471 if count < len(data): # put back unsent portion 472 self.txes.appendleft(data[count:]) 473 return False # blocked 474 console.profuse("{0}: Sent: {1}\n".format(self.name, data)) 475 return True # send more 476 477 def serviceTxes(self): 478 """ 479 Service txes data 480 """ 481 while self.txes and self.server.opened: 482 again = self._serviceOneTx() 483 if not again: 484 break # blocked try again later 485 486 def serviceTxOnce(self): 487 ''' 488 Service one data on the .txes deque to send through device 489 ''' 490 if self.txes and self.server.opened: 491 self._serviceOneTx() 492 493