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