robotengine.serial_io

serial_io 是 robotengine 控制硬件串口的节点。

  1"""
  2
  3serial_io 是 robotengine 控制硬件串口的节点。
  4
  5"""
  6
  7from .node import Node
  8import serial.tools.list_ports
  9import serial
 10from enum import Enum
 11import random
 12from robotengine.tools import hex2str, warning, error
 13
 14class DeviceType(Enum):
 15    """ 设备类型枚举 """
 16    STM32F407 = 0
 17    """ STM32F407 设备类型 """
 18    ARDUINO_MEGA2560 = 1
 19    """ Arduino Mega2560 设备类型 """
 20
 21class CheckSumType(Enum):
 22    """ 校验和类型枚举 """
 23    NONE = 0
 24    """ 无校验和 """
 25    SUM8 = 1
 26    """ SUM8 校验和 """
 27    SUM16 = 2
 28    """ SUM16 校验和 """
 29    XOR8 = 3
 30    """ XOR8 校验和 """
 31    XOR16 = 4
 32    """ XOR16 校验和 """
 33    CRC8 = 5
 34    """ CRC8 校验和 """
 35    CRC16 = 6
 36    """ CRC16 校验和 """
 37
 38checksum_length_map = {
 39        CheckSumType.SUM8: 1,
 40        CheckSumType.SUM16: 2,
 41        CheckSumType.XOR8: 1,
 42        CheckSumType.XOR16: 2,
 43        CheckSumType.CRC8: 1,
 44        CheckSumType.CRC16: 2
 45    }
 46""" 校验和长度映射表 """
 47
 48class SerialIO(Node):
 49    """ 串口节点 """
 50    def __init__(self, name="SerialIO", device_type=DeviceType.STM32F407, checksum_type=CheckSumType.NONE, header=[], baudrate=115200, timeout=1.0, warn=True):
 51        super().__init__(name)
 52        self._device_type = device_type
 53        self._checksum_type = checksum_type
 54        self._header = header
 55        self._device = None
 56        self._serial: serial.Serial = None
 57        self._baudrate = baudrate
 58        self._timeout = timeout
 59
 60        self._warn = warn
 61        self._receive_data = bytes()
 62
 63        self._initialize()
 64        if self._device is None:
 65            if self._warn:
 66                warning(f"节点 {self.name} 初始化时未检测到 {self.device_type} 设备,将在内部更新中继续尝试")
 67
 68    def _update(self, delta) -> None:
 69        if self._device is None:
 70            self._initialize()
 71            return
 72        
 73    def _initialize(self):
 74        self._device = self._find_device()
 75        if self.device:
 76            print(f"节点 {self.name} 初始化时检测到 {self._device_type} 设备,串口为 {self._device},波特率为 {self._baudrate}")
 77            self._serial = serial.Serial(self.device, self.baudrate, timeout=self.timeout)
 78            # 清空串口缓冲区
 79            self._serial.reset_input_buffer()
 80            self._serial.reset_output_buffer()
 81            print(f"节点 {self.name} 初始化时清空串口缓冲区")
 82
 83    def _find_device(self):
 84        if self._device_type == DeviceType.STM32F407:
 85            target_vid = 0x1A86
 86            target_pid = 0x7523
 87        elif self._device_type == DeviceType.ARDUINO_MEGA2560:
 88            target_vid = 0x2341
 89            target_pid = 0x0043
 90
 91        ports = serial.tools.list_ports.comports()
 92        for port in ports:
 93            if port.vid == target_vid and port.pid == target_pid:
 94                return port.device
 95        return None
 96    
 97    def _get_check_sum(self, data: bytes) -> bytes:
 98        if self._checksum_type == CheckSumType.SUM8:
 99            check_sum = sum(data) & 0xFF
100            return bytes([check_sum])
101        elif self._checksum_type == CheckSumType.SUM16:
102            check_sum = sum(data) & 0xFFFF
103            return check_sum.to_bytes(2, byteorder='big')
104        elif self._checksum_type == CheckSumType.XOR8:
105            check_sum = 0
106            for byte in data:
107                check_sum ^= byte
108            return bytes([check_sum])
109        elif self._checksum_type == CheckSumType.XOR16:
110            check_sum = 0
111            for byte in data:
112                check_sum ^= byte
113            return check_sum.to_bytes(2, byteorder='big')
114        elif self._checksum_type == CheckSumType.CRC8:
115            crc = 0x00
116            polynomial = 0x07
117            for byte in data:
118                crc ^= byte
119                for _ in range(8):
120                    if crc & 0x80:
121                        crc = (crc << 1) ^ polynomial
122                    else:
123                        crc <<= 1
124                    crc &= 0xFF
125            return bytes([crc])
126        elif self._checksum_type == CheckSumType.CRC16:
127            crc = 0xFFFF
128            polynomial = 0x8005
129            for byte in data:
130                crc ^= byte
131                for _ in range(8):
132                    if crc & 0x0001:
133                        crc = (crc >> 1) ^ polynomial
134                    else:
135                        crc >>= 1  # 否则仅右移
136            return crc.to_bytes(2, byteorder='big')
137        else:
138            raise ValueError("无效的校验和类型")
139            
140    def _add_header(self, data: bytes) -> bytes:
141        return bytes(self._header) + data
142    
143    def random_bytes(self, length: int) -> bytes:
144        """ 生成随机字节 """
145        return bytes([random.randint(0, 255) for _ in range(length)])
146    
147    def fixed_bytes(self, byte: int, length: int) -> bytes:
148        """ 生成固定字节 """
149        return bytes([byte for _ in range(length)])
150    
151    def transmit(self, data: bytes) -> bytes:
152        """ 发送串口数据 """
153        if self._serial is None:
154            if self._warn:
155                warning(f"节点 {self.name} 串口未初始化,无法发送数据")
156            return
157        if self._checksum_type !=CheckSumType.NONE:
158            data += self._get_check_sum(data)
159        if self._header:
160            data = self._add_header(data)
161        self._serial.write(data)
162        return data
163            
164    def receive(self, len: int) -> bytes:
165        """ 接收串口数据 """
166        if self._serial is None:
167            if self._warn:
168                warning(f"节点 {self.name} 串口未初始化,无法接收数据")
169            return
170        if self._serial.in_waiting >= len:
171            return self._serial.read(len)
172        else:
173            return None
174        
175    def check_sum(self, data: bytes) -> bool:
176        """ 校验串口数据 """
177        if self._checksum_type == CheckSumType.NONE:
178            return True
179        checksum_length = checksum_length_map.get(self._checksum_type)
180        if checksum_length is None:
181            raise ValueError("无效的校验和类型,无法进行校验")
182
183        data_to_check = data[len(self._header):-checksum_length]
184        expected_checksum = data[-checksum_length:]
185        calculated_checksum = self._get_check_sum(data_to_check)
186
187        return calculated_checksum == expected_checksum
188
189    def __del__(self):
190        if self._serial:
191            self._serial.close()
class DeviceType(enum.Enum):
15class DeviceType(Enum):
16    """ 设备类型枚举 """
17    STM32F407 = 0
18    """ STM32F407 设备类型 """
19    ARDUINO_MEGA2560 = 1
20    """ Arduino Mega2560 设备类型 """

设备类型枚举

STM32F407 = <DeviceType.STM32F407: 0>

STM32F407 设备类型

ARDUINO_MEGA2560 = <DeviceType.ARDUINO_MEGA2560: 1>

Arduino Mega2560 设备类型

Inherited Members
enum.Enum
name
value
class CheckSumType(enum.Enum):
22class CheckSumType(Enum):
23    """ 校验和类型枚举 """
24    NONE = 0
25    """ 无校验和 """
26    SUM8 = 1
27    """ SUM8 校验和 """
28    SUM16 = 2
29    """ SUM16 校验和 """
30    XOR8 = 3
31    """ XOR8 校验和 """
32    XOR16 = 4
33    """ XOR16 校验和 """
34    CRC8 = 5
35    """ CRC8 校验和 """
36    CRC16 = 6
37    """ CRC16 校验和 """

校验和类型枚举

NONE = <CheckSumType.NONE: 0>

无校验和

SUM8 = <CheckSumType.SUM8: 1>

SUM8 校验和

SUM16 = <CheckSumType.SUM16: 2>

SUM16 校验和

XOR8 = <CheckSumType.XOR8: 3>

XOR8 校验和

XOR16 = <CheckSumType.XOR16: 4>

XOR16 校验和

CRC8 = <CheckSumType.CRC8: 5>

CRC8 校验和

CRC16 = <CheckSumType.CRC16: 6>

CRC16 校验和

Inherited Members
enum.Enum
name
value
checksum_length_map = {<CheckSumType.SUM8: 1>: 1, <CheckSumType.SUM16: 2>: 2, <CheckSumType.XOR8: 3>: 1, <CheckSumType.XOR16: 4>: 2, <CheckSumType.CRC8: 5>: 1, <CheckSumType.CRC16: 6>: 2}

校验和长度映射表

class SerialIO(robotengine.node.Node):
 49class SerialIO(Node):
 50    """ 串口节点 """
 51    def __init__(self, name="SerialIO", device_type=DeviceType.STM32F407, checksum_type=CheckSumType.NONE, header=[], baudrate=115200, timeout=1.0, warn=True):
 52        super().__init__(name)
 53        self._device_type = device_type
 54        self._checksum_type = checksum_type
 55        self._header = header
 56        self._device = None
 57        self._serial: serial.Serial = None
 58        self._baudrate = baudrate
 59        self._timeout = timeout
 60
 61        self._warn = warn
 62        self._receive_data = bytes()
 63
 64        self._initialize()
 65        if self._device is None:
 66            if self._warn:
 67                warning(f"节点 {self.name} 初始化时未检测到 {self.device_type} 设备,将在内部更新中继续尝试")
 68
 69    def _update(self, delta) -> None:
 70        if self._device is None:
 71            self._initialize()
 72            return
 73        
 74    def _initialize(self):
 75        self._device = self._find_device()
 76        if self.device:
 77            print(f"节点 {self.name} 初始化时检测到 {self._device_type} 设备,串口为 {self._device},波特率为 {self._baudrate}")
 78            self._serial = serial.Serial(self.device, self.baudrate, timeout=self.timeout)
 79            # 清空串口缓冲区
 80            self._serial.reset_input_buffer()
 81            self._serial.reset_output_buffer()
 82            print(f"节点 {self.name} 初始化时清空串口缓冲区")
 83
 84    def _find_device(self):
 85        if self._device_type == DeviceType.STM32F407:
 86            target_vid = 0x1A86
 87            target_pid = 0x7523
 88        elif self._device_type == DeviceType.ARDUINO_MEGA2560:
 89            target_vid = 0x2341
 90            target_pid = 0x0043
 91
 92        ports = serial.tools.list_ports.comports()
 93        for port in ports:
 94            if port.vid == target_vid and port.pid == target_pid:
 95                return port.device
 96        return None
 97    
 98    def _get_check_sum(self, data: bytes) -> bytes:
 99        if self._checksum_type == CheckSumType.SUM8:
100            check_sum = sum(data) & 0xFF
101            return bytes([check_sum])
102        elif self._checksum_type == CheckSumType.SUM16:
103            check_sum = sum(data) & 0xFFFF
104            return check_sum.to_bytes(2, byteorder='big')
105        elif self._checksum_type == CheckSumType.XOR8:
106            check_sum = 0
107            for byte in data:
108                check_sum ^= byte
109            return bytes([check_sum])
110        elif self._checksum_type == CheckSumType.XOR16:
111            check_sum = 0
112            for byte in data:
113                check_sum ^= byte
114            return check_sum.to_bytes(2, byteorder='big')
115        elif self._checksum_type == CheckSumType.CRC8:
116            crc = 0x00
117            polynomial = 0x07
118            for byte in data:
119                crc ^= byte
120                for _ in range(8):
121                    if crc & 0x80:
122                        crc = (crc << 1) ^ polynomial
123                    else:
124                        crc <<= 1
125                    crc &= 0xFF
126            return bytes([crc])
127        elif self._checksum_type == CheckSumType.CRC16:
128            crc = 0xFFFF
129            polynomial = 0x8005
130            for byte in data:
131                crc ^= byte
132                for _ in range(8):
133                    if crc & 0x0001:
134                        crc = (crc >> 1) ^ polynomial
135                    else:
136                        crc >>= 1  # 否则仅右移
137            return crc.to_bytes(2, byteorder='big')
138        else:
139            raise ValueError("无效的校验和类型")
140            
141    def _add_header(self, data: bytes) -> bytes:
142        return bytes(self._header) + data
143    
144    def random_bytes(self, length: int) -> bytes:
145        """ 生成随机字节 """
146        return bytes([random.randint(0, 255) for _ in range(length)])
147    
148    def fixed_bytes(self, byte: int, length: int) -> bytes:
149        """ 生成固定字节 """
150        return bytes([byte for _ in range(length)])
151    
152    def transmit(self, data: bytes) -> bytes:
153        """ 发送串口数据 """
154        if self._serial is None:
155            if self._warn:
156                warning(f"节点 {self.name} 串口未初始化,无法发送数据")
157            return
158        if self._checksum_type !=CheckSumType.NONE:
159            data += self._get_check_sum(data)
160        if self._header:
161            data = self._add_header(data)
162        self._serial.write(data)
163        return data
164            
165    def receive(self, len: int) -> bytes:
166        """ 接收串口数据 """
167        if self._serial is None:
168            if self._warn:
169                warning(f"节点 {self.name} 串口未初始化,无法接收数据")
170            return
171        if self._serial.in_waiting >= len:
172            return self._serial.read(len)
173        else:
174            return None
175        
176    def check_sum(self, data: bytes) -> bool:
177        """ 校验串口数据 """
178        if self._checksum_type == CheckSumType.NONE:
179            return True
180        checksum_length = checksum_length_map.get(self._checksum_type)
181        if checksum_length is None:
182            raise ValueError("无效的校验和类型,无法进行校验")
183
184        data_to_check = data[len(self._header):-checksum_length]
185        expected_checksum = data[-checksum_length:]
186        calculated_checksum = self._get_check_sum(data_to_check)
187
188        return calculated_checksum == expected_checksum
189
190    def __del__(self):
191        if self._serial:
192            self._serial.close()

串口节点

SerialIO( name='SerialIO', device_type=<DeviceType.STM32F407: 0>, checksum_type=<CheckSumType.NONE: 0>, header=[], baudrate=115200, timeout=1.0, warn=True)
51    def __init__(self, name="SerialIO", device_type=DeviceType.STM32F407, checksum_type=CheckSumType.NONE, header=[], baudrate=115200, timeout=1.0, warn=True):
52        super().__init__(name)
53        self._device_type = device_type
54        self._checksum_type = checksum_type
55        self._header = header
56        self._device = None
57        self._serial: serial.Serial = None
58        self._baudrate = baudrate
59        self._timeout = timeout
60
61        self._warn = warn
62        self._receive_data = bytes()
63
64        self._initialize()
65        if self._device is None:
66            if self._warn:
67                warning(f"节点 {self.name} 初始化时未检测到 {self.device_type} 设备,将在内部更新中继续尝试")

初始化节点, 需要指定节点名称

def random_bytes(self, length: int) -> bytes:
144    def random_bytes(self, length: int) -> bytes:
145        """ 生成随机字节 """
146        return bytes([random.randint(0, 255) for _ in range(length)])

生成随机字节

def fixed_bytes(self, byte: int, length: int) -> bytes:
148    def fixed_bytes(self, byte: int, length: int) -> bytes:
149        """ 生成固定字节 """
150        return bytes([byte for _ in range(length)])

生成固定字节

def transmit(self, data: bytes) -> bytes:
152    def transmit(self, data: bytes) -> bytes:
153        """ 发送串口数据 """
154        if self._serial is None:
155            if self._warn:
156                warning(f"节点 {self.name} 串口未初始化,无法发送数据")
157            return
158        if self._checksum_type !=CheckSumType.NONE:
159            data += self._get_check_sum(data)
160        if self._header:
161            data = self._add_header(data)
162        self._serial.write(data)
163        return data

发送串口数据

def receive(self, len: int) -> bytes:
165    def receive(self, len: int) -> bytes:
166        """ 接收串口数据 """
167        if self._serial is None:
168            if self._warn:
169                warning(f"节点 {self.name} 串口未初始化,无法接收数据")
170            return
171        if self._serial.in_waiting >= len:
172            return self._serial.read(len)
173        else:
174            return None

接收串口数据

def check_sum(self, data: bytes) -> bool:
176    def check_sum(self, data: bytes) -> bool:
177        """ 校验串口数据 """
178        if self._checksum_type == CheckSumType.NONE:
179            return True
180        checksum_length = checksum_length_map.get(self._checksum_type)
181        if checksum_length is None:
182            raise ValueError("无效的校验和类型,无法进行校验")
183
184        data_to_check = data[len(self._header):-checksum_length]
185        expected_checksum = data[-checksum_length:]
186        calculated_checksum = self._get_check_sum(data_to_check)
187
188        return calculated_checksum == expected_checksum

校验串口数据