#!/usr/bin/env python3 import logging import sys import serial import threading from threading import Thread import time import binascii from base64 import b64decode, b64encode from PyCRC.CRC32 import CRC32 import struct import messages_pb2 as messages from google.protobuf.message import Message log = logging.getLogger("ugv") class UGVComms: MAX_WRITE_RETRY = 5 RETRY_TIME = 1.5 def __init__(self, serial_port: serial.Serial, on_msg_received=None): self.ser = serial_port self.on_msg_received = on_msg_received self.msg_acks = [] self.ack_cv = threading.Condition() self.next_command_id = 1 self.last_status = None self.last_status_time = None self.rx_thread = None self.is_running = False self.log_file = None def write_base64(self, data: bytes): crc = CRC32().calculate(data) data_with_checksum = bytearray(data) data_with_checksum.extend(struct.pack(' 0: if cmdid in self.msg_acks: self.msg_acks.remove(cmdid) log.debug("received ack for command") return time_left = time.time() - last_write_time if time_left >= self.RETRY_TIME: log.warning("retry writing command") self.write_message(gmsg) last_write_time = time.time() tries -= 1 self.ack_cv.wait(timeout=time_left) raise TimeoutError("Timeout waiting for command ack") def read_message(self): data = self.ser.read_until(terminator=b'\n') if len(data) is 0: return None try: decoded = b64decode(data, validate=True) except binascii.Error: log.warning("read bad data: %s", data) self.ser.flush() return None if len(decoded) < 4: log.warning('Message too short ({} bytes)'.format(len(decoded))) return None msgcrc, = struct.unpack('= 2: ser_url = sys.argv[1] else: ser_url = "hwgrep://" ser = serial.serial_for_url(ser_url, baudrate=9600, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=0.5) ugv = UGVComms(ser) ugv.start() time.sleep(0.2) try: cmd = messages.GroundCommand() cmd.type = messages.CMD_SET_TARGET cmd.target_location.latitude = 34.068415 cmd.target_location.longitude = -118.443217 # ugv.write_command(cmd) cmd.type = messages.CMD_SET_CONFIG cmd.config.angle_pid.kp = 0.10 cmd.config.angle_pid.ki = 0 # .00005 cmd.config.angle_pid.kd = 0.4 cmd.config.angle_pid.max_output = 0.5 cmd.config.angle_pid.max_i_error = 15.0 cmd.config.min_target_dist = 10.0 cmd.config.min_flip_pitch = 90.0 ugv.write_command(cmd) while True: if ugv.last_status is None or ugv.last_status.state is not messages.STATE_DRIVE_HEADING: cmd = messages.GroundCommand() cmd.type = messages.CMD_DRIVE_HEADING cmd.drive_heading.heading = -115.0 - 180 cmd.drive_heading.power = 0.3 ugv.write_command(cmd) time.sleep(2.0) except KeyboardInterrupt: ugv.write_command(messages.CMD_DISABLE) log.info("exiting...") finally: ugv.ser.flush() ugv.ser.close() ugv.stop() if __name__ == "__main__": logging.basicConfig(format='%(asctime)s [%(name)s] %(levelname)s: %(message)s', datefmt='%Y-%b-%d %H:%M:%S') log.setLevel(logging.DEBUG) main()