1#! python 2# 3# This is a codec to create and decode hexdumps with spaces between characters. used by miniterm. 4# 5# This file is part of pySerial. https://github.com/pyserial/pyserial 6# (C) 2015-2016 Chris Liechti <[email protected]> 7# 8# SPDX-License-Identifier: BSD-3-Clause 9"""\ 10Python 'hex' Codec - 2-digit hex with spaces content transfer encoding. 11 12Encode and decode may be a bit missleading at first sight... 13 14The textual representation is a hex dump: e.g. "40 41" 15The "encoded" data of this is the binary form, e.g. b"@A" 16 17Therefore decoding is binary to text and thus converting binary data to hex dump. 18 19""" 20 21from __future__ import absolute_import 22 23import codecs 24import serial 25 26 27try: 28 unicode 29except (NameError, AttributeError): 30 unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name 31 32 33HEXDIGITS = '0123456789ABCDEF' 34 35 36# Codec APIs 37 38def hex_encode(data, errors='strict'): 39 """'40 41 42' -> b'@ab'""" 40 return (serial.to_bytes([int(h, 16) for h in data.split()]), len(data)) 41 42 43def hex_decode(data, errors='strict'): 44 """b'@ab' -> '40 41 42'""" 45 return (unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))), len(data)) 46 47 48class Codec(codecs.Codec): 49 def encode(self, data, errors='strict'): 50 """'40 41 42' -> b'@ab'""" 51 return serial.to_bytes([int(h, 16) for h in data.split()]) 52 53 def decode(self, data, errors='strict'): 54 """b'@ab' -> '40 41 42'""" 55 return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))) 56 57 58class IncrementalEncoder(codecs.IncrementalEncoder): 59 """Incremental hex encoder""" 60 61 def __init__(self, errors='strict'): 62 self.errors = errors 63 self.state = 0 64 65 def reset(self): 66 self.state = 0 67 68 def getstate(self): 69 return self.state 70 71 def setstate(self, state): 72 self.state = state 73 74 def encode(self, data, final=False): 75 """\ 76 Incremental encode, keep track of digits and emit a byte when a pair 77 of hex digits is found. The space is optional unless the error 78 handling is defined to be 'strict'. 79 """ 80 state = self.state 81 encoded = [] 82 for c in data.upper(): 83 if c in HEXDIGITS: 84 z = HEXDIGITS.index(c) 85 if state: 86 encoded.append(z + (state & 0xf0)) 87 state = 0 88 else: 89 state = 0x100 + (z << 4) 90 elif c == ' ': # allow spaces to separate values 91 if state and self.errors == 'strict': 92 raise UnicodeError('odd number of hex digits') 93 state = 0 94 else: 95 if self.errors == 'strict': 96 raise UnicodeError('non-hex digit found: {!r}'.format(c)) 97 self.state = state 98 return serial.to_bytes(encoded) 99 100 101class IncrementalDecoder(codecs.IncrementalDecoder): 102 """Incremental decoder""" 103 def decode(self, data, final=False): 104 return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))) 105 106 107class StreamWriter(Codec, codecs.StreamWriter): 108 """Combination of hexlify codec and StreamWriter""" 109 110 111class StreamReader(Codec, codecs.StreamReader): 112 """Combination of hexlify codec and StreamReader""" 113 114 115def getregentry(): 116 """encodings module API""" 117 return codecs.CodecInfo( 118 name='hexlify', 119 encode=hex_encode, 120 decode=hex_decode, 121 incrementalencoder=IncrementalEncoder, 122 incrementaldecoder=IncrementalDecoder, 123 streamwriter=StreamWriter, 124 streamreader=StreamReader, 125 #~ _is_text_encoding=True, 126 ) 127