#define BAUD_RATE 115200 #include "tty.h" #include "config.h" #include "fw.h" #include "rom/crc.h" #include #include #define SOH '\001' #define STX '\002' #define ETX '\003' #define EOT '\004' #define ENQ '\005' #define ACK '\006' #define XON '\021' // DC1 #define WRST '\022' // DC2 - window = 0 #define XOFF '\023' // DC3 #define WGO '\024' // DC4 - window + 256 bytes #define NAK '\025' #define SYN '\026' #define ETB '\027' // Not in upload mode #define CAN '\030' #define EM '\031' // Packet offset too high #define FS '\034' #define GS '\035' #define RS '\036' #define US '\037' #define WGO_CHUNK 256 #define STREAMBUF_SIZE 8192 static char enq_str[] = "\026\035MAX80 v0\004\r\n"; static const char fwupload_start[] = "\034\001: /// MAX80 FW UPLOAD ~@~ $\035\r\n"; void TTY::reset() { rx.state = rx_state::normal; } TTY::TTY(Stream &port) { _port = &port; rx_sbuf = NULL; win_sem = xSemaphoreCreateMutexStatic(&win_sem_ss); reset(); } TTY::~TTY() { if (rx_sbuf) vStreamBufferDelete(rx_sbuf); } void TTY::_update_window(bool rst) { xSemaphoreTake(win_sem, portMAX_DELAY); if (rst) { port().write(WRST); tx_window = 0; } while (xStreamBufferSpacesAvailable(rx_sbuf) >= tx_window + WGO_CHUNK) { tx_window += WGO_CHUNK; port().write(WGO); // Space for one more window } xSemaphoreGive(win_sem); } int TTY::rxdata(void *buf, size_t len) { if (!rx_sbuf) return 0; size_t rcv = xStreamBufferReceive(rx_sbuf, buf, len, portMAX_DELAY); _update_window(false); return rcv; } int TTY::rxdata(token_t me, void *buf, size_t len) { TTY *tty = (TTY *)me; return tty->rxdata(buf, len); } void TTY::_upload_begin() { if (rx_sbuf) xStreamBufferReset(rx_sbuf); else rx_sbuf = xStreamBufferCreate(STREAMBUF_SIZE, 1); if (!rx_sbuf) goto nak; if (firmware_update_start((read_func_t)TTY::rxdata, (token_t)this)) goto nak; // Respond with WRST + n*WGO _update_window(true); rx.state = rx_state::hdr; return; nak: port().write(NAK); rx.state = rx_state::normal; return; } void TTY::_onerr() { if (rx.state != rx_state::normal) _update_window(true); } // Change this to be a buffer... void TTY::_onrx() { int byte; int len; while (1) { int byte = port().read(); if (byte == -1) break; // No more data switch (rx.state) { case rx_state::normal: if (byte == fwupload_start[rx.rlen]) { if (!fwupload_start[++rx.rlen]) _upload_begin(); } else { rx.rlen = 0; switch (byte) { case ENQ: port().write(enq_str); byte = ETB; break; case ETX: case EOT: case CAN: byte = ETB; // Not in file upload state break; default: // Normal echo break; } } break; case rx_state::hdr: if (rx.rlen > 0 || byte == STX) { rx.hdr_raw[rx.rlen++] = byte; byte = -1; if (rx.rlen == sizeof rx.hdr) { // Start of data packet rx.state = rx_state::data; rx.rlen = 0; } } else { switch (byte) { case ETX: case EOT: case CAN: reset(); byte = CAN; break; case ENQ: byte = SYN; // In file upload state break; default: // Normal echo break; } } break; case rx_state::data: rx_data[rx.rlen++] = byte; byte = -1; // rx.hdr.len = packet data len - 1 if (rx.rlen > rx.hdr.len) { int have = rx.rlen; uint32_t crc = ~crc32_le(0, rx_data, have); if (crc != rx.hdr.crc) { byte = NAK; } else if (rx.hdr.offs > rx.last_ack) { byte = EM; } else { int sent = 0; tx_window -= rx.rlen; if (rx.hdr.offs + rx.rlen <= rx.last_ack) { have = 0; } else { uint32_t skip = rx.last_ack - rx.hdr.offs; have -= skip; sent = xStreamBufferSend(rx_sbuf, rx_data+skip, have, 0); rx.last_ack += sent; } if (sent == have) byte = ACK; else _update_window(true); rx.state = rx_state::hdr; rx.rlen = 0; } } break; } if (byte >= 0) port().write(byte); } } void TTY::_onconnect() { port().write(XON); } void TTY::_onbreak() { reset(); } void TTY::_ondisconnect() { reset(); } static TTY *uart_tty, *usb_tty; void TTY::usb_onevent(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { switch (event_id) { case ARDUINO_USB_CDC_CONNECTED_EVENT: case ARDUINO_USB_CDC_LINE_STATE_EVENT: usb_tty->_onconnect(); break; case ARDUINO_USB_CDC_DISCONNECTED_EVENT: usb_tty->_ondisconnect(); break; case ARDUINO_USB_CDC_RX_EVENT: usb_tty->_onrx(); break; case ARDUINO_USB_CDC_RX_OVERFLOW_EVENT: usb_tty->_onerr(); break; default: // Do nothing break; } } void TTY::uart_onrx(void) { uart_tty->_onrx(); } void TTY::uart_onerr(hardwareSerial_error_t err) { switch (err) { case UART_BREAK_ERROR: uart_tty->_onbreak(); break; default: uart_tty->_onerr(); break; } } void TTY::init() { enq_str[sizeof(enq_str)-5] += max80_board_version; uart_tty = new TTY(Serial0); Serial0.begin(BAUD_RATE); Serial0.onReceive(uart_onrx, false); Serial0.onReceiveError(uart_onerr); usb_tty = new TTY(Serial); Serial.onEvent(usb_onevent); } void TTY::ping() { }