// SPDX-License-Identifier: Apache-2.0 // Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest #include "evSerial.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "hi2lo.pb.h" #include "lo2hi.pb.h" evSerial::evSerial() { fd = 0; baud = 0; reset_done_flag = false; forced_reset = false; cobsDecodeReset(); } evSerial::~evSerial() { if (fd) close(fd); } bool evSerial::openDevice(const char* device, int _baud) { fd = open(device, O_RDWR | O_NOCTTY | O_SYNC); if (fd < 0) { printf("Serial: error %d opening %s: %s\n", errno, device, strerror(errno)); return false; } // else printf ("Serial: opened %s as %i\n", device, fd); cobsDecodeReset(); switch (_baud) { case 9600: baud = B9600; break; case 19200: baud = B19200; break; case 38400: baud = B38400; break; case 57600: baud = B57600; break; case 115200: baud = B115200; break; case 230400: baud = B230400; break; default: baud = 0; return false; } return setSerialAttributes(); } bool evSerial::setSerialAttributes() { struct termios tty; if (tcgetattr(fd, &tty) != 0) { printf("Serial: error %d from tcgetattr\n", errno); return false; } cfsetospeed(&tty, baud); cfsetispeed(&tty, baud); tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars // disable IGNBRK for mismatched speed tests; otherwise receive break // as \000 chars tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY); tty.c_lflag = 0; // no signaling chars, no echo, // no canonical processing tty.c_oflag = 0; // no remapping, no delays tty.c_cc[VMIN] = 0; // read blocks tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout tty.c_cflag |= (CLOCAL | CREAD); // ignore modem controls, // enable reading tty.c_cflag &= ~(PARENB | PARODD); // shut off parity tty.c_cflag &= ~CSTOPB; tty.c_cflag &= ~CRTSCTS; if (tcsetattr(fd, TCSANOW, &tty) != 0) { printf("Serial: error %d from tcsetattr\n", errno); return false; } // printf ("Success setting tcsetattr\n"); return true; } void evSerial::cobsDecodeReset() { code = 0xff; block = 0; decode = msg; } uint32_t evSerial::crc32(uint8_t* buf, int len) { int i, j; uint32_t b, crc, msk; i = 0; crc = 0xFFFFFFFF; while (i < len) { b = buf[i]; crc = crc ^ b; for (j = 7; j >= 0; j--) { msk = -(crc & 1); crc = (crc >> 1) ^ (0xEDB88320 & msk); } i = i + 1; } // printf("%X",crc); return crc; } void evSerial::handlePacket(uint8_t* buf, int len) { // printf ("packet received len %u\n", len); // Check CRC32 (last 4 bytes) // uint32_t crc = calculateCrc(rx_packet_buf, rx_packet_len); if (crc32(buf, len)) { printf("CRC mismatch\n"); return; } len -= 4; LoToHi msg_in; pb_istream_t istream = pb_istream_from_buffer(buf, len); if (pb_decode(&istream, LoToHi_fields, &msg_in)) switch (msg_in.which_payload) { case LoToHi_keep_alive_tag: // printf("Received keep_alive_lo\n"); signalKeepAliveLo(msg_in.payload.keep_alive); // detect connection timeout if keep_alive packets stop coming... last_keep_alive_lo_timestamp = date::utc_clock::now(); break; case LoToHi_event_tag: // printf("Received event %i\n",msg_in.payload.event); signalEvent(msg_in.payload.event); break; case LoToHi_measurements_tag: // printf("Received event %i\n",msg_in.payload.event); signalMeasurements(msg_in.payload.measurements); break; case LoToHi_reset_done_tag: // printf("Received reset_done\n"); reset_done_flag = true; if (!forced_reset) signalSpuriousReset(); break; } } void evSerial::cobsDecode(uint8_t* buf, int len) { for (int i = 0; i < len; i++) cobsDecodeByte(buf[i]); } void evSerial::cobsDecodeByte(uint8_t byte) { // check max length if ((decode - msg == 2048 - 1) && byte != 0x00) { printf("cobsDecode: Buffer overflow\n"); cobsDecodeReset(); } if (block) { // we're currently decoding and should not get a 0 if (byte == 0x00) { // probably found some garbage -> reset printf("cobsDecode: Garbage detected\n"); cobsDecodeReset(); return; } *decode++ = byte; } else { if (code != 0xff) { *decode++ = 0; } block = code = byte; if (code == 0x00) { // we're finished, reset everything and commit if (decode == msg) { // we received nothing, just a 0x00 printf("cobsDecode: Received nothing\n"); } else { // set back decode with one, as it gets post-incremented handlePacket(msg, decode - 1 - msg); } cobsDecodeReset(); return; // need to return here, because of block-- } } block--; } void evSerial::run() { readThreadHandle = std::thread(&evSerial::readThread, this); timeoutDetectionThreadHandle = std::thread(&evSerial::timeoutDetectionThread, this); } void evSerial::timeoutDetectionThread() { while (true) { sleep(1); if (timeoutDetectionThreadHandle.shouldExit()) break; if (serial_timed_out()) signalConnectionTimeout(); } } void evSerial::readThread() { uint8_t buf[2048]; int n; cobsDecodeReset(); while (true) { if (readThreadHandle.shouldExit()) break; if (fd > 0) { n = read(fd, buf, sizeof buf); cobsDecode(buf, n); } } } bool evSerial::linkWrite(HiToLo* m) { if (fd <= 0) { return false; } uint8_t tx_packet_buf[1024]; uint8_t encode_buf[1500]; pb_ostream_t ostream = pb_ostream_from_buffer(tx_packet_buf, sizeof(tx_packet_buf) - 4); bool status = pb_encode(&ostream, HiToLo_fields, m); if (!status) { // couldn't encode return false; } size_t tx_payload_len = ostream.bytes_written; // add crc32 (CRC-32/JAMCRC) uint32_t crc = crc32(tx_packet_buf, tx_payload_len); for (int byte_pos = 0; byte_pos < 4; ++byte_pos) { tx_packet_buf[tx_payload_len] = (uint8_t)crc & 0xFF; crc = crc >> 8; tx_payload_len++; } size_t tx_encode_len = cobsEncode(tx_packet_buf, tx_payload_len, encode_buf); // std::cout << "Write "<(now - last_keep_alive_lo_timestamp).count(); if (timeSinceLastKeepAlive >= 5000) return true; return false; } void evSerial::allowPowerOn(bool p) { HiToLo msg_out = HiToLo_init_default; msg_out.which_payload = HiToLo_allow_power_on_tag; msg_out.payload.allow_power_on.p = p; linkWrite(&msg_out); } void evSerial::setBCDE(uint32_t mode) { HiToLo msg_out = HiToLo_init_default; msg_out.which_payload = HiToLo_set_bcde_tag; msg_out.payload.set_bcde.mode = mode; linkWrite(&msg_out); } void evSerial::enable() { HiToLo msg_out = HiToLo_init_default; msg_out.which_payload = HiToLo_enable_tag; linkWrite(&msg_out); } void evSerial::disable() { HiToLo msg_out = HiToLo_init_default; msg_out.which_payload = HiToLo_disable_tag; linkWrite(&msg_out); } bool evSerial::reset(const int reset_pin) { reset_done_flag = false; forced_reset = true; if (reset_pin > 0) { // Try to hardware reset Yeti controller to be in a known state char cmd[100]; sprintf(cmd, "echo %i >/sys/class/gpio/export", reset_pin); system(cmd); sprintf(cmd, "echo out > /sys/class/gpio/gpio%i/direction", reset_pin); system(cmd); sprintf(cmd, "echo 0 > /sys/class/gpio/gpio%i/value", reset_pin); system(cmd); sprintf(cmd, "echo 1 > /sys/class/gpio/gpio%i/value", reset_pin); system(cmd); } else { // Try to soft reset Yeti controller to be in a known state HiToLo msg_out = HiToLo_init_default; msg_out.which_payload = HiToLo_reset_tag; linkWrite(&msg_out); } bool success = false; // Wait for reset done message from uC for (int i = 0; i < 20; i++) { if (reset_done_flag) { success = true; break; } std::this_thread::sleep_for(std::chrono::milliseconds(50)); } // Reset flag to detect run time spurious resets of uC from now on reset_done_flag = false; forced_reset = false; // send some dummy packets to resync COBS etc. keepAlive(); keepAlive(); keepAlive(); return success; } void evSerial::firmwareUpdate(bool rom) { HiToLo msg_out = HiToLo_init_default; msg_out.which_payload = HiToLo_firmware_update_tag; msg_out.payload.firmware_update.invoke_rom_bootloader = rom; linkWrite(&msg_out); } void evSerial::keepAlive() { HiToLo msg_out = HiToLo_init_default; msg_out.which_payload = HiToLo_keep_alive_tag; msg_out.payload.keep_alive.time_stamp = 0; msg_out.payload.keep_alive.hw_type = 0; msg_out.payload.keep_alive.hw_revision = 0; msg_out.payload.keep_alive.protocol_version_major = 0; msg_out.payload.keep_alive.protocol_version_minor = 1; strcpy(msg_out.payload.keep_alive.sw_version_string, "n/a"); linkWrite(&msg_out); }