mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-07 22:52:06 +08:00
38faf7f8a4
30c7ca8a5 bump version to 1.5.3 9403dbebe Need to fix wifi test before re-enabling. 0812362b5 GPS UART fix until boardd is refactored (#294) ffbdb87a8 python2 -> 3 fixes to pedal flasher (#292) 78b75ef59 Added build type to release version strings 736c2cbf7 Fixed sending of bytes over PandaSerial 0894b28f1 Fixed USB power mode on black (#291) 4b3358c92 patch to be able to switch from EON to PC with a Panda that has EON b… (#290) a95c44a71 Made setting of NOOUTPUT on no heartbeat more efficient (#287) 948683688 UART instability fix with high interrupt load (#283) 9a9e9d47b Fix usb_power_mode missing initialization (#289) af0960ad3 DFU fix (#288) 70219d7bb match safety enum in cereal (#285) a338d3932 Fix build for jenkins test 78ef4a6eb Stop charge (#284) 5266a4028 Fix typo (#286) f4787ec5a Revert "turn on CDP when ignition switches on (#281)" d37daee97 Revert "NONE and CLIENT should be the same thing in white/grey pandas" e97b283e7 NONE and CLIENT should be the same thing in white/grey pandas 8c1df559f turn on CDP when ignition switches on (#281) 847a35d42 Fix bullet points fac027716 Misra update (#280) 5a04df6b1 Added description of regression tests to README c4aabae59 Fixed some python3 bugs in the test scripts and PandaSerial 9af0cb353 Bump version c4ac3d63b Disable GPS load switching on black pandas 078ee588c This is the correct table, actually 578b95ee3 Misra table of coverage added d383a2625 bump panda b98ca010d fix sdk build in python3 env (#279) 63d3dc7d3 Set python3 env before runnign get_sdk, so we know if it fails e951d79c0 legacy code we don't control can remain python2 11b715118 Merge pull request #276 from commaai/python3 9893a842a Merge pull request #277 from zorrobyte/patch-1 d3268690c Revert "revert back esptool to python2 and force to build esptools with python2" 875e76012 revert back esptool to python2 and force to build esptools with python2 9c40e6240 needed to install python3 ed2ac87cf Also moved safety tests to python3 6842b2d2c move esptool sdk installation before python3 env is set. Kind of a cheat b5a2cabcd this hopefully fixes build test 628050955 Fixes safety replay 2c220b623 this fixes language regr test fdbe789b8 use python 3 in Docker container ee1ae4f86 Better hash print 0de9ef73c Revert "Final 2to3 on the whole repo" c92fd3bc9 Final 2to3 on the whole repo 5f2bc4460 better b2a30fdbd make works! b74005d10 fix sign.py fe727706b read file as byte and no tab before sleep 32a344ef6 Update README.md 2dc34096a 2to3 applied ffa68ef71 undo unnecessary brackets for print dbc248027 Fix all the prints with 2to3, some need to be undo 5a7aeba0f xrange is gone 982c4c928 one more python3 env 1e2412a29 env python -> env python3 git-subtree-dir: panda git-subtree-split: 30c7ca8a53a3adb05d23d7cfe64fb716a656ef1a
1579 lines
50 KiB
C
1579 lines
50 KiB
C
#include "ets_sys.h"
|
|
#include "osapi.h"
|
|
#include "gpio.h"
|
|
#include "os_type.h"
|
|
#include "user_interface.h"
|
|
#include "espconn.h"
|
|
#include "mem.h"
|
|
|
|
#include "driver/uart.h"
|
|
|
|
//#define ELM_DEBUG
|
|
|
|
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
|
#define MAX(a,b) ((a) > (b) ? (a) : (b))
|
|
int ICACHE_FLASH_ATTR spi_comm(char *dat, int len, uint32_t *recvData, int recvDataLen);
|
|
|
|
#define ELM_PORT 35000
|
|
|
|
//Version 1.5 is an invalid version used by many pirate clones
|
|
//that only partially support 1.0.
|
|
#define IDENT_MSG "ELM327 v1.5\r\r"
|
|
#define DEVICE_DESC "Panda\n\n"
|
|
|
|
#define SHOW_CONNECTION(msg, p_conn) os_printf("%s %p, proto %p, %d.%d.%d.%d:%d disconnect\r\n", \
|
|
msg, p_conn, (p_conn)->proto.tcp, (p_conn)->proto.tcp->remote_ip[0], \
|
|
(p_conn)->proto.tcp->remote_ip[1], (p_conn)->proto.tcp->remote_ip[2], \
|
|
(p_conn)->proto.tcp->remote_ip[3], (p_conn)->proto.tcp->remote_port)
|
|
|
|
const static char hex_lookup[] = {'0', '1', '2', '3', '4', '5', '6', '7',
|
|
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
bool tx : 1;
|
|
bool : 1;
|
|
bool ext : 1;
|
|
uint32_t addr : 29;
|
|
|
|
uint8_t len : 4;
|
|
uint8_t bus : 8;
|
|
uint8_t : 4; //unused
|
|
uint16_t ts : 16;
|
|
uint8_t data[8];
|
|
} panda_can_msg_t;
|
|
|
|
//TODO: Masking is likely unnecessary for these bit fields. Check.
|
|
#define panda_get_can_addr(recv) (((recv)->ext) ? ((recv)->addr & 0x1FFFFFFF) :\
|
|
(((recv)->addr >> 18) & 0x7FF))
|
|
|
|
#define PANDA_CAN_FLAG_TRANSMIT 1
|
|
#define PANDA_CAN_FLAG_EXTENDED 4
|
|
|
|
#define PANDA_USB_CAN_WRITE_BUS_NUM 3
|
|
#define PANDA_USB_LIN_WRITE_BUS_NUM 2
|
|
|
|
#define SAFETY_ELM327 3U
|
|
|
|
typedef struct _elm_tcp_conn {
|
|
struct espconn *conn;
|
|
struct _elm_tcp_conn *next;
|
|
} elm_tcp_conn_t;
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
uint8_t len;
|
|
uint8_t dat[7]; //mode and data
|
|
} elm_can_obd_msg;
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
uint8_t priority;
|
|
uint8_t receiver;
|
|
uint8_t sender;
|
|
|
|
uint8_t dat[8]; //mode, data, and checksum
|
|
} elm_lin_obd_msg;
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
uint16_t usb_ep_num;
|
|
uint16_t payload_len;
|
|
|
|
uint8_t serial_port;
|
|
//uint8_t msg[8+3];
|
|
elm_lin_obd_msg msg;
|
|
} elm_lin_usb_msg;
|
|
|
|
static struct espconn elm_conn;
|
|
static esp_tcp elm_proto;
|
|
static elm_tcp_conn_t *connection_list = NULL;
|
|
|
|
static char stripped_msg[0x100];
|
|
static uint16 stripped_msg_len = 0;
|
|
|
|
static char in_msg[0x100];
|
|
static uint16 in_msg_len = 0;
|
|
|
|
static char rsp_buff[536]; //TCP min MTU
|
|
static uint16 rsp_buff_len = 0;
|
|
|
|
static uint8_t pandaSendData[0x14] = {0};
|
|
static uint32_t pandaRecvData[0x40] = {0};
|
|
static uint32_t pandaRecvDataDummy[0x40] = {0}; // Used for CAN write operations (no received data)
|
|
|
|
#define ELM_MODE_SELECTED_PROTOCOL_DEFAULT 6
|
|
#define ELM_MODE_TIMEOUT_DEFAULT 20;
|
|
#define ELM_MODE_KEEPALIVE_PERIOD_DEFAULT (0x92*20)
|
|
|
|
static bool elm_mode_echo = true;
|
|
static bool elm_mode_linefeed = false;
|
|
static bool elm_mode_additional_headers = false;
|
|
static bool elm_mode_auto_protocol = true;
|
|
static uint8_t elm_selected_protocol = ELM_MODE_SELECTED_PROTOCOL_DEFAULT;
|
|
static bool elm_mode_print_spaces = true;
|
|
static uint8_t elm_mode_adaptive_timing = 1;
|
|
static bool elm_mode_allow_long = false;
|
|
static uint16_t elm_mode_timeout = ELM_MODE_TIMEOUT_DEFAULT;
|
|
static uint16_t elm_mode_keepalive_period = ELM_MODE_KEEPALIVE_PERIOD_DEFAULT;
|
|
|
|
bool lin_bus_initialized = false;
|
|
|
|
/***********************************************
|
|
*** ELM CLI response functions ***
|
|
*** (for sending data back to the terminal) ***
|
|
***********************************************/
|
|
|
|
// All ELM operations are global, so send data out to all connections
|
|
void ICACHE_FLASH_ATTR elm_tcp_tx_flush() {
|
|
if(!rsp_buff_len) return; // Was causing small error messages
|
|
|
|
for(elm_tcp_conn_t *iter = connection_list; iter != NULL; iter = iter->next){
|
|
int8_t err = espconn_send(iter->conn, rsp_buff, rsp_buff_len);
|
|
if(err){
|
|
os_printf(" Wifi %p TX error code %d\n", iter->conn, err);
|
|
if(err == ESPCONN_ARG) {
|
|
if(iter == connection_list) {
|
|
connection_list = iter->next;
|
|
} else {
|
|
for(elm_tcp_conn_t *iter2 = connection_list; iter2 != NULL; iter2 = iter2->next)
|
|
if(iter2->next == iter) {
|
|
iter2->next = iter->next;
|
|
break;
|
|
}
|
|
}
|
|
os_printf(" deleting orphaned connection. iter: %p; conn: %p\n", iter, iter->conn);
|
|
os_free(iter);
|
|
}
|
|
}
|
|
}
|
|
rsp_buff_len = 0;
|
|
}
|
|
|
|
static void ICACHE_FLASH_ATTR elm_append_rsp(const char *data, uint16_t len) {
|
|
uint16_t overflow_len = 0;
|
|
if(rsp_buff_len + len > sizeof(rsp_buff)) {
|
|
overflow_len = rsp_buff_len + len - sizeof(rsp_buff);
|
|
len = sizeof(rsp_buff) - rsp_buff_len;
|
|
}
|
|
if(!elm_mode_linefeed) {
|
|
memcpy(rsp_buff + rsp_buff_len, data, len);
|
|
rsp_buff_len += len;
|
|
} else {
|
|
for(int i=0; i < len && rsp_buff_len < sizeof(rsp_buff); i++){
|
|
rsp_buff[rsp_buff_len++] = data[i];
|
|
if(data[i] == '\r' && rsp_buff_len < sizeof(rsp_buff))
|
|
rsp_buff[rsp_buff_len++] = '\n';
|
|
}
|
|
}
|
|
if(overflow_len) {
|
|
os_printf("Packet full, sending\n");
|
|
elm_tcp_tx_flush();
|
|
elm_append_rsp(data + len, overflow_len);
|
|
}
|
|
}
|
|
|
|
#define elm_append_rsp_const(str) elm_append_rsp(str, sizeof(str)-1)
|
|
|
|
static void ICACHE_FLASH_ATTR elm_append_rsp_hex_byte(uint8_t num) {
|
|
elm_append_rsp(&hex_lookup[num >> 4], 1);
|
|
elm_append_rsp(&hex_lookup[num & 0xF], 1);
|
|
if(elm_mode_print_spaces) elm_append_rsp_const(" ");
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR elm_append_rsp_can_msg_addr(const panda_can_msg_t *recv) {
|
|
//Show address
|
|
uint32_t addr = panda_get_can_addr(recv);
|
|
if(recv->ext){
|
|
elm_append_rsp_hex_byte(addr>>24);
|
|
elm_append_rsp_hex_byte(addr>>16);
|
|
elm_append_rsp_hex_byte(addr>>8);
|
|
elm_append_rsp_hex_byte(addr);
|
|
} else {
|
|
elm_append_rsp(&hex_lookup[addr>>8], 1);
|
|
elm_append_rsp_hex_byte(addr);
|
|
}
|
|
}
|
|
|
|
/***************************************
|
|
*** Panda communication functions ***
|
|
*** (for controlling the Panda MCU) ***
|
|
***************************************/
|
|
|
|
static int ICACHE_FLASH_ATTR panda_usbemu_ctrl_write(uint8_t request_type, uint8_t request,
|
|
uint16_t value, uint16_t index, uint16_t length) {
|
|
//self.sock.send(struct.pack("HHBBHHH", 0, 0, request_type, request, value, index, length));
|
|
*(uint16_t*)(pandaSendData) = 0;
|
|
*(uint16_t*)(pandaSendData+2) = 0;
|
|
pandaSendData[4] = request_type;
|
|
pandaSendData[5] = request;
|
|
*(uint16_t*)(pandaSendData+6) = value;
|
|
*(uint16_t*)(pandaSendData+8) = index;
|
|
*(uint16_t*)(pandaSendData+10) = length;
|
|
|
|
int returned_count = spi_comm(pandaSendData, 0x10, pandaRecvData, 0x40);
|
|
if(returned_count > 0x40 || returned_count < 0)
|
|
return -1;
|
|
return returned_count;
|
|
}
|
|
|
|
#define panda_set_can0_cbaud(cbps) panda_usbemu_ctrl_write(0x40, 0xde, 0, cbps, 0)
|
|
#define panda_set_can0_kbaud(kbps) panda_usbemu_ctrl_write(0x40, 0xde, 0, kbps*10, 0)
|
|
#define panda_set_safety_mode(mode) panda_usbemu_ctrl_write(0x40, 0xdc, mode, 0, 0)
|
|
#define panda_kline_wakeup_pulse() panda_usbemu_ctrl_write(0x40, 0xf0, 0, 0, 0)
|
|
#define panda_clear_can_rx() panda_usbemu_ctrl_write(0x40, 0xf1, 0xFFFF, 0, 0)
|
|
#define panda_clear_lin_txrx() panda_usbemu_ctrl_write(0x40, 0xf2, 2, 0, 0)
|
|
|
|
static int ICACHE_FLASH_ATTR panda_usbemu_can_read(panda_can_msg_t** can_msgs) {
|
|
int returned_count = spi_comm((uint8_t *)((const uint16 []){1,0}), 4, pandaRecvData, 0x40);
|
|
if(returned_count > 0x40 || returned_count < 0){
|
|
os_printf("CAN read got invalid length\n");
|
|
return -1;
|
|
}
|
|
*can_msgs = (panda_can_msg_t*)(pandaRecvData+1);
|
|
return returned_count/sizeof(panda_can_msg_t);
|
|
}
|
|
|
|
static int ICACHE_FLASH_ATTR panda_usbemu_can_write(bool ext, uint32_t addr,
|
|
char *candata, uint8_t canlen) {
|
|
uint32_t rir;
|
|
|
|
if(canlen > 8) return 0;
|
|
|
|
if(ext || addr >= 0x800){
|
|
rir = (addr << 3) | PANDA_CAN_FLAG_TRANSMIT | PANDA_CAN_FLAG_EXTENDED;
|
|
}else{
|
|
rir = (addr << 21) | PANDA_CAN_FLAG_TRANSMIT;
|
|
}
|
|
|
|
#define MAX_CAN_LEN 8
|
|
|
|
//Wifi USB Wrapper
|
|
*(uint16_t*)(pandaSendData) = PANDA_USB_CAN_WRITE_BUS_NUM; //USB Bulk Endpoint ID.
|
|
*(uint16_t*)(pandaSendData+2) = MAX_CAN_LEN;
|
|
//BULK MESSAGE
|
|
*(uint32_t*)(pandaSendData+4) = rir;
|
|
*(uint32_t*)(pandaSendData+8) = MAX_CAN_LEN | (0 << 4); //0 is CAN bus number.
|
|
//CAN DATA
|
|
memcpy(pandaSendData+12, candata, canlen);
|
|
memset(pandaSendData+12+canlen, 0, MAX_CAN_LEN-canlen);
|
|
for(int i = 12+canlen; i < 20; i++) pandaSendData[i] = 0; //Zero the rest
|
|
|
|
/* spi_comm will erase data in the recv buffer even if you are only
|
|
* interested in sending data that gets no response (like writing
|
|
* can data). This behavior becomes problematic when trying to send
|
|
* a can message while processsing received can messages. A dummy
|
|
* recv buffer is used here so received data is not overwritten. */
|
|
int returned_count = spi_comm(pandaSendData, 0x14, pandaRecvDataDummy, 0x40);
|
|
if(returned_count)
|
|
os_printf("ELM Can send expected 0 bytes back from panda. Got %d bytes instead\n", returned_count);
|
|
if(returned_count > 0x40) return 0;
|
|
return returned_count;
|
|
}
|
|
|
|
elm_lin_obd_msg lin_last_sent_msg;
|
|
uint16_t lin_last_sent_msg_len = 0;
|
|
bool lin_await_msg_echo = false;
|
|
|
|
static int ICACHE_FLASH_ATTR panda_usbemu_kline_read(uint16_t len) {
|
|
int returned_count = panda_usbemu_ctrl_write(0xC0, 0xE0, 2, 0, len);
|
|
if(returned_count > len || returned_count < 0){
|
|
os_printf("LIN read got invalid length\n");
|
|
return -1;
|
|
}
|
|
|
|
#ifdef ELM_DEBUG
|
|
if(returned_count) {
|
|
os_printf("LIN Received %d bytes\n", returned_count);
|
|
os_printf(" Data: ");
|
|
for(int i = 0; i < returned_count; i++)
|
|
os_printf("%02x ", ((char*)(pandaRecvData+1))[i]);
|
|
os_printf("\n");
|
|
}
|
|
#endif
|
|
return returned_count;
|
|
}
|
|
|
|
static int ICACHE_FLASH_ATTR panda_usbemu_kline_write(elm_lin_obd_msg *msg) {
|
|
elm_lin_usb_msg usb_msg = {};
|
|
|
|
usb_msg.usb_ep_num = PANDA_USB_LIN_WRITE_BUS_NUM; //USB Bulk Endpoint ID.
|
|
usb_msg.payload_len = (msg->priority & 0x07) + 4 + 1; //The +1 is for serial_port
|
|
usb_msg.serial_port = 2;
|
|
memcpy(&usb_msg.msg, msg, sizeof(elm_lin_obd_msg));
|
|
|
|
/* spi_comm will erase data in the recv buffer even if you are only
|
|
* interested in sending data that gets no response (like writing
|
|
* can data). This behavior becomes problematic when trying to send
|
|
* a can message while processsing received can messages. A dummy
|
|
* recv buffer is used here so received data is not overwritten. */
|
|
int returned_count = spi_comm((char*)&usb_msg, sizeof(elm_lin_usb_msg), pandaRecvDataDummy, 0x40);
|
|
|
|
if(returned_count)
|
|
os_printf("ELM LIN send expected 0 bytes back from panda. Got %d bytes instead\n", returned_count);
|
|
if(returned_count > 0x40) return 0;
|
|
|
|
return returned_count;
|
|
}
|
|
|
|
/****************************************
|
|
*** Ringbuffer ***
|
|
****************************************/
|
|
|
|
//LIN data is delivered in chunks of arbitrary size. Using a
|
|
//ringbuffer to handle it.
|
|
uint8_t lin_ringbuff[0x20];
|
|
uint8_t lin_ringbuff_start = 0;
|
|
uint8_t lin_ringbuff_end = 0;
|
|
#define lin_ringbuff_len \
|
|
(((sizeof(lin_ringbuff) + lin_ringbuff_end) - lin_ringbuff_start)% sizeof(lin_ringbuff))
|
|
#define lin_ringbuff_get(index) (lin_ringbuff[(lin_ringbuff_start + index) % sizeof(lin_ringbuff)])
|
|
#define lin_ringbuff_consume(len) lin_ringbuff_start = ((lin_ringbuff_start + len) % sizeof(lin_ringbuff))
|
|
#define lin_ringbuff_clear()\
|
|
{lin_ringbuff_start = 0; \
|
|
lin_ringbuff_end = 0;}
|
|
|
|
int ICACHE_FLASH_ATTR elm_LIN_ringbuff_memcmp(uint8_t *data, uint16_t len) {
|
|
if(len > lin_ringbuff_len) return 1;
|
|
for(int i = 0; i < len; i++)
|
|
if(lin_ringbuff_get(i) != data[i]) return 1;
|
|
return 0; // Going with memcpy ret format where 0 means 'equal'
|
|
}
|
|
|
|
uint16_t ICACHE_FLASH_ATTR elm_LIN_read_into_ringbuff() {
|
|
int bytelen = panda_usbemu_kline_read((sizeof(lin_ringbuff) - lin_ringbuff_len) - 1);
|
|
if(bytelen < 0) return 0;
|
|
for(int j = 0; j < bytelen; j++) {
|
|
lin_ringbuff[lin_ringbuff_end % sizeof(lin_ringbuff)] = ((char*)(pandaRecvData+1))[j];
|
|
lin_ringbuff_end = (lin_ringbuff_end + 1) % sizeof(lin_ringbuff);
|
|
if(lin_ringbuff_start == lin_ringbuff_end) lin_ringbuff_start++;
|
|
}
|
|
|
|
#ifdef ELM_DEBUG
|
|
if(bytelen){
|
|
os_printf(" RB Data (%d %d %d): ", lin_ringbuff_start, lin_ringbuff_end, lin_ringbuff_len);
|
|
for(int i = 0; i < sizeof(lin_ringbuff); i++)
|
|
os_printf("%02x ", lin_ringbuff[i]);
|
|
os_printf("\n");
|
|
}
|
|
#endif
|
|
|
|
return bytelen;
|
|
}
|
|
|
|
/****************************************
|
|
*** String parsing utility functions ***
|
|
****************************************/
|
|
|
|
static int8_t ICACHE_FLASH_ATTR elm_decode_hex_char(char b){
|
|
if(b >= '0' && b <= '9') return b - '0';
|
|
if(b >= 'A' && b <= 'F') return (b - 'A') + 10;
|
|
if(b >= 'a' && b <= 'f') return (b - 'a') + 10;
|
|
return -1;
|
|
}
|
|
|
|
static uint8_t ICACHE_FLASH_ATTR elm_decode_hex_byte(const char* data) {
|
|
return (elm_decode_hex_char(data[0]) << 4) | elm_decode_hex_char(data[1]);
|
|
}
|
|
|
|
static bool ICACHE_FLASH_ATTR elm_check_valid_hex_chars(const char* data, uint8_t len) {
|
|
for(int i = 0; i < len; i++){
|
|
char b = data[i];
|
|
if(!((b >= '0' && b <= '9') || (b >= 'A' && b <= 'F') || (b >= 'a' && b <= 'f')))
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static uint16_t ICACHE_FLASH_ATTR elm_strip(const char *data, uint16_t lenin,
|
|
char *outbuff, uint16_t outbufflen) {
|
|
uint16_t count = 0;
|
|
for(uint16_t i = 0; i < lenin; i++) {
|
|
if(count >= outbufflen) break;
|
|
if(data[i] == ' ') continue;
|
|
if(data[i] >= 'a' && data[i] <= 'z'){
|
|
outbuff[count++] = data[i] - ('a' - 'A');
|
|
} else {
|
|
outbuff[count++] = data[i];
|
|
}
|
|
if(data[i] == '\r') break;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static int ICACHE_FLASH_ATTR elm_msg_find_cr_or_eos(char *data, uint16_t len){
|
|
uint16_t i;
|
|
for(i = 0; i < len; i++)
|
|
if(data[i] == '\r') {
|
|
i++;
|
|
break;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/*****************************************************
|
|
*** ELM protocol specification and implementation ***
|
|
*****************************************************/
|
|
|
|
typedef enum {
|
|
AUTO, LIN, CAN11, CAN29, NA
|
|
} elm_proto_type_t;
|
|
|
|
typedef struct elm_protocol {
|
|
bool supported;
|
|
elm_proto_type_t type;
|
|
uint16_t cbaud; //Centibaud (cbaud * 10 = kbaud)
|
|
void (*process_obd)(const struct elm_protocol*, const char*, uint16_t);
|
|
//init is used to init and de-init a protocol. Init functions should
|
|
//not do things that would leave a new protocol in an invalid state
|
|
//after the new protocol's init is called (e.g. No arming timers).
|
|
void (*init)(const struct elm_protocol*);
|
|
char* name;
|
|
} elm_protocol_t;
|
|
|
|
static const elm_protocol_t* ICACHE_FLASH_ATTR elm_current_proto();
|
|
void ICACHE_FLASH_ATTR elm_reset_aux_timer();
|
|
static void ICACHE_FLASH_ATTR elm_autodetect_cb(bool);
|
|
|
|
static const elm_protocol_t elm_protocols[];
|
|
//(sizeof(elm_protocols)/sizeof(elm_protocol_t))
|
|
#define ELM_PROTOCOL_COUNT 13
|
|
|
|
#define LOOPCOUNT_FULL 4
|
|
static int loopcount = 0;
|
|
static volatile os_timer_t elm_timeout;
|
|
static volatile os_timer_t elm_proto_aux_timeout;
|
|
|
|
static bool is_auto_detecting = false;
|
|
|
|
// Used only by elm_timer_cb, so not volatile
|
|
static bool did_multimessage = false;
|
|
static bool got_msg_this_run = false;
|
|
static bool can_tx_worked = false;
|
|
static uint8_t elm_msg_mode_ret_filter;
|
|
static uint8_t elm_msg_pid_ret_filter;
|
|
|
|
/*****************************************************
|
|
*** ELM protocol specification and implementation ***
|
|
*** -> SAE J1850 implementation (Unsupported) ***
|
|
*****************************************************/
|
|
|
|
static void ICACHE_FLASH_ATTR elm_process_obd_cmd_J1850(const elm_protocol_t* proto,
|
|
const char *cmd, uint16_t len) {
|
|
elm_append_rsp_const("NO DATA\r\r>");
|
|
}
|
|
|
|
/*****************************************************
|
|
*** ELM protocol specification and implementation ***
|
|
*** -> ISO 14230-4 implementation ***
|
|
*****************************************************/
|
|
|
|
const char *lin_cmd_backup = NULL; //Holds msg while bus init is done
|
|
uint16_t lin_cmd_backup_len = 0;
|
|
bool lin_waiting_keepalive_echo = false;
|
|
|
|
static void ICACHE_FLASH_ATTR elm_process_obd_cmd_LIN5baud(const elm_protocol_t* proto,
|
|
const char *cmd, uint16_t len) {
|
|
elm_append_rsp_const("BUS INIT: ...ERROR\r\r>");
|
|
}
|
|
|
|
bool ICACHE_FLASH_ATTR elm_lin_keepalive_echo() {
|
|
if(lin_waiting_keepalive_echo) {
|
|
for(int pass = 0; pass < 4 && lin_ringbuff_len < 5; pass++) {
|
|
elm_LIN_read_into_ringbuff();
|
|
}
|
|
|
|
lin_waiting_keepalive_echo = false;
|
|
//keepalive Echo should always come before other message echo.
|
|
if(lin_ringbuff_len >= 5 && !elm_LIN_ringbuff_memcmp("\xc1\x33\xf1\x3e\x23", 5)){
|
|
lin_ringbuff_consume(5);
|
|
return true;
|
|
} else {
|
|
os_printf("Keep alive echo failed\n");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR elm_LINFast_keepalive_timer_cb(void *arg) {
|
|
if(!lin_bus_initialized) {
|
|
os_printf("WARNING! Elm LIN keepalive timer running while bus is not initialized\n");
|
|
return;
|
|
}
|
|
if(loopcount) {
|
|
os_printf("WARNING! Elm LIN keepalive timer during a tx/rx loop!\n");
|
|
return;
|
|
}
|
|
if(lin_ringbuff_len) {
|
|
os_printf("WARNING! lin_ringbuff_len should be 0 when a keepalive echo is processed.\n");
|
|
return;
|
|
}
|
|
|
|
if(!elm_lin_keepalive_echo()) {
|
|
lin_bus_initialized = false;
|
|
return;
|
|
}
|
|
|
|
elm_lin_obd_msg msg = {};
|
|
|
|
msg.priority = 0xC0 | 1;
|
|
msg.receiver = 0x33;
|
|
msg.sender = 0xF1;
|
|
msg.dat[0] = 0x3E;
|
|
msg.dat[1] = msg.dat[0] + msg.priority + msg.receiver + msg.sender; // checksum
|
|
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Sending LIN KEEPALIVE: Priority: %02x; RecvAddr: %02x; SendAddr: %02x; (%02x); ",
|
|
msg.priority, msg.receiver, msg.sender, 1);
|
|
for(int i = 0; i < 2; i++) os_printf("%02x ", msg.dat[i]);
|
|
os_printf("\n");
|
|
#endif
|
|
|
|
lin_waiting_keepalive_echo = true;
|
|
|
|
panda_usbemu_kline_write(&msg);
|
|
elm_reset_aux_timer();
|
|
}
|
|
|
|
static void ICACHE_FLASH_ATTR elm_init_LINFast(const elm_protocol_t* proto){
|
|
os_timer_disarm(&elm_proto_aux_timeout);
|
|
os_timer_setfn(&elm_proto_aux_timeout, (os_timer_func_t *)elm_LINFast_keepalive_timer_cb, proto);
|
|
|
|
lin_bus_initialized = false;
|
|
lin_await_msg_echo = false;
|
|
lin_waiting_keepalive_echo = false;
|
|
|
|
lin_cmd_backup = NULL;
|
|
lin_cmd_backup_len = 0;
|
|
|
|
lin_ringbuff_clear();
|
|
panda_clear_lin_txrx();
|
|
}
|
|
|
|
int ICACHE_FLASH_ATTR elm_LINFast_process_echo() {
|
|
if(!elm_lin_keepalive_echo()) {
|
|
os_printf("Keepalive echo not detected.\n");
|
|
lin_ringbuff_clear();
|
|
return -1;
|
|
}
|
|
|
|
if(!lin_await_msg_echo) {
|
|
os_printf("Echo abort. Nothing waiting echo\n");
|
|
return 1;
|
|
}
|
|
|
|
for(int i = 0; i < 4; i++){
|
|
if(lin_ringbuff_len < lin_last_sent_msg_len) elm_LIN_read_into_ringbuff();
|
|
|
|
if(lin_ringbuff_len >= lin_last_sent_msg_len){
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Got enough data %d\n", lin_last_sent_msg_len);
|
|
#endif
|
|
if(!elm_LIN_ringbuff_memcmp((uint8_t*)&lin_last_sent_msg, lin_last_sent_msg_len)) {
|
|
#ifdef ELM_DEBUG
|
|
os_printf("LIN data was sent successfully.\n");
|
|
#endif
|
|
lin_ringbuff_consume(lin_last_sent_msg_len);
|
|
lin_await_msg_echo = false;
|
|
return 1;
|
|
} else {
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Echo not correct.\n");
|
|
os_printf(" RB Data (%d %d %d): ", lin_ringbuff_start, lin_ringbuff_end, lin_ringbuff_len);
|
|
for(int i = 0; i < sizeof(lin_ringbuff); i++)
|
|
os_printf("%02x ", lin_ringbuff[i]);
|
|
os_printf("\n");
|
|
os_printf(" MSG Data (%d): ", lin_last_sent_msg_len);
|
|
for(int i = 0; i < lin_last_sent_msg_len; i++)
|
|
os_printf("%02x ", ((uint8_t*)&lin_last_sent_msg)[i]);
|
|
os_printf("\n");
|
|
#endif
|
|
|
|
if(lin_bus_initialized || loopcount == 0 && i == 4) {
|
|
lin_ringbuff_clear();
|
|
return -1;
|
|
} else {
|
|
os_printf("Lin init echo misaligned? Consuming byte (%02x). Retry.\n", lin_ringbuff_get(0));
|
|
lin_ringbuff_consume(1);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return !lin_await_msg_echo; //true if echo handled
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR elm_LINFast_timer_cb(void *arg){
|
|
const elm_protocol_t* proto = (const elm_protocol_t*) arg;
|
|
loopcount--;
|
|
#ifdef ELM_DEBUG
|
|
os_printf("LIN CB call\n");
|
|
#endif
|
|
|
|
if(!lin_bus_initialized) {
|
|
os_printf("WARNING: LIN CB called without bus initialized!");
|
|
return; // TODO: shoulnd't ever happen. Handle?
|
|
}
|
|
|
|
int echo_result = elm_LINFast_process_echo();
|
|
|
|
if(echo_result == -1 || (echo_result == 0 && loopcount == 0)) {
|
|
if(!is_auto_detecting){
|
|
elm_append_rsp_const("BUS ERROR\r\r>");
|
|
elm_tcp_tx_flush();
|
|
}
|
|
loopcount = 0;
|
|
lin_bus_initialized = false;
|
|
return;
|
|
}
|
|
|
|
if(echo_result == 0) {
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Not ready to process\n");
|
|
#endif
|
|
os_timer_arm(&elm_timeout, 30, 0);
|
|
return; // Not ready to go on
|
|
}
|
|
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Processing ELM %d\n", lin_ringbuff_len);
|
|
#endif
|
|
|
|
if(loopcount>0) {
|
|
for(int pass = 0; pass < 16 && loopcount; pass++){
|
|
elm_LIN_read_into_ringbuff();
|
|
|
|
while(lin_ringbuff_len > 0){
|
|
//if(lin_ringbuff_len > 0){
|
|
if(lin_ringbuff_get(0) & 0x80 != 0x80){
|
|
os_printf("Resetting LIN bus due to bad first byte.\n");
|
|
loopcount = 0;
|
|
lin_bus_initialized = false;
|
|
lin_ringbuff_clear();
|
|
|
|
if(!is_auto_detecting){
|
|
elm_append_rsp_const("ERROR\r\r>");
|
|
elm_tcp_tx_flush();
|
|
}
|
|
return;
|
|
}
|
|
|
|
uint8_t newmsg_len = 4 + (lin_ringbuff_get(0) & 0x7);
|
|
if(lin_ringbuff_len >= newmsg_len) {
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Processing LIN MSG. BuffLen %d; expect %d. Dat: ", lin_ringbuff_len, newmsg_len);
|
|
for(int i = 0; i < newmsg_len; i++) os_printf("%02x ", lin_ringbuff_get(i));
|
|
os_printf("\n");
|
|
#endif
|
|
got_msg_this_run = true;
|
|
loopcount = LOOPCOUNT_FULL;
|
|
|
|
if(!is_auto_detecting){
|
|
if(elm_mode_additional_headers){
|
|
for(int i = 0; i < newmsg_len; i++) elm_append_rsp_hex_byte(lin_ringbuff_get(i));
|
|
} else {
|
|
for(int i = 3; i < newmsg_len - 1; i++) elm_append_rsp_hex_byte(lin_ringbuff_get(i));
|
|
}
|
|
elm_append_rsp_const("\r");
|
|
}
|
|
|
|
lin_ringbuff_consume(newmsg_len);
|
|
//elm_reset_aux_timer();
|
|
} else {
|
|
break; //Stop consuming data if there is not enough data for the next msg.
|
|
}
|
|
}
|
|
}
|
|
os_timer_arm(&elm_timeout, 50, 0);
|
|
} else {
|
|
bool got_msg_this_run_backup = got_msg_this_run;
|
|
if(!got_msg_this_run) {
|
|
#ifdef ELM_DEBUG
|
|
os_printf(" No data collected\n");
|
|
#endif
|
|
if(!is_auto_detecting) {
|
|
elm_append_rsp_const("NO DATA\r");
|
|
}
|
|
}
|
|
got_msg_this_run = false;
|
|
|
|
if(!is_auto_detecting) {
|
|
elm_append_rsp_const("\r>");
|
|
elm_tcp_tx_flush();
|
|
} else {
|
|
elm_autodetect_cb(got_msg_this_run_backup);
|
|
}
|
|
|
|
//TX RX over, resume Keepalive timer
|
|
elm_reset_aux_timer();
|
|
}
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR elm_LINFast_businit_timer_cb(void *arg){
|
|
const elm_protocol_t* proto = (const elm_protocol_t*) arg;
|
|
loopcount--;
|
|
#ifdef ELM_DEBUG
|
|
os_printf("LIN INIT CB call\n");
|
|
#endif
|
|
|
|
int echo_result = elm_LINFast_process_echo();
|
|
|
|
if(echo_result == -1 || (echo_result == 0 && loopcount == 0)) {
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Init failed with echo test\n");
|
|
#endif
|
|
|
|
loopcount = 0;
|
|
lin_bus_initialized = 0;
|
|
|
|
if(!is_auto_detecting){
|
|
if(echo_result == -1)
|
|
elm_append_rsp_const("BUS ERROR\r\r>");
|
|
else
|
|
elm_append_rsp_const("ERROR\r\r>");
|
|
elm_tcp_tx_flush();
|
|
} else {
|
|
elm_autodetect_cb(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(echo_result == 0) {
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Not ready to process\n");
|
|
#endif
|
|
os_timer_arm(&elm_timeout, elm_mode_timeout, 0);
|
|
return; // Not ready to go on
|
|
}
|
|
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Bus init ready to process %d bytes\n", lin_ringbuff_len);
|
|
#endif
|
|
|
|
if(lin_bus_initialized) return; // TODO: shoulnd't ever happen. Handle?
|
|
|
|
if(loopcount>0) {
|
|
//Keep waiting for response
|
|
for(int i = 0; i < 4; i++){
|
|
elm_LIN_read_into_ringbuff();
|
|
|
|
if(lin_ringbuff_len > 0){
|
|
if(lin_ringbuff_get(0) & 0x80 != 0x80){
|
|
os_printf("Resetting LIN bus due to bad first byte.\n");
|
|
loopcount = 0;
|
|
lin_ringbuff_clear();
|
|
|
|
if(!is_auto_detecting){
|
|
elm_append_rsp_const("ERROR\r\r>");
|
|
elm_tcp_tx_flush();
|
|
} else {
|
|
elm_autodetect_cb(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
uint8_t newmsg_len = 4 + (lin_ringbuff_get(0) & 0x7);
|
|
if(lin_ringbuff_len < newmsg_len) {
|
|
os_printf("Resetting LIN because returned init data was wrong.\n");
|
|
loopcount = 0;
|
|
lin_ringbuff_clear();
|
|
|
|
if(!is_auto_detecting){
|
|
elm_append_rsp_const("ERROR\r\r>");
|
|
elm_tcp_tx_flush();
|
|
} else {
|
|
elm_autodetect_cb(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(!elm_LIN_ringbuff_memcmp("\x83\xF1\x10\xC1\x8F\xE9\xBD", 7)) {
|
|
lin_ringbuff_consume(7);
|
|
lin_bus_initialized = true;
|
|
//lin_ringbuff_clear();
|
|
|
|
os_printf("BUS INITIALIZED\n");
|
|
|
|
elm_reset_aux_timer();
|
|
|
|
if(!is_auto_detecting) {
|
|
elm_append_rsp_const("OK\r");
|
|
|
|
//Do the send that was delayed
|
|
if(lin_cmd_backup_len) {
|
|
elm_tcp_tx_flush();
|
|
proto->process_obd(proto, lin_cmd_backup, lin_cmd_backup_len);
|
|
} else {
|
|
elm_append_rsp_const("\r>");
|
|
elm_tcp_tx_flush();
|
|
}
|
|
} else {
|
|
#ifdef ELM_DEBUG
|
|
os_printf("LIN success. Silent because in autodetect.\n");
|
|
#endif
|
|
elm_autodetect_cb(true);
|
|
// TODO: Since bus init is good, is it ok to skip sending the '0100' msg?
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
os_timer_arm(&elm_timeout, elm_mode_timeout, 0);
|
|
} else {
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Fall through on bus init\n");
|
|
#endif
|
|
if(!is_auto_detecting){
|
|
elm_append_rsp_const("ERROR\r\r>");
|
|
elm_tcp_tx_flush();
|
|
} else {
|
|
elm_autodetect_cb(false);
|
|
}
|
|
elm_reset_aux_timer();
|
|
}
|
|
}
|
|
|
|
static void ICACHE_FLASH_ATTR elm_process_obd_cmd_LINFast(const elm_protocol_t* proto,
|
|
const char *cmd, uint16_t len) {
|
|
elm_lin_obd_msg msg = {};
|
|
uint8_t bytelen = (len-1)/2;
|
|
if((bytelen > 7 && !elm_mode_allow_long) || bytelen > 8) {
|
|
elm_append_rsp_const("?\r\r>");
|
|
return;
|
|
}
|
|
|
|
os_timer_disarm(&elm_proto_aux_timeout);
|
|
|
|
if(!lin_bus_initialized) {
|
|
panda_clear_lin_txrx();
|
|
|
|
if(!is_auto_detecting)
|
|
elm_append_rsp_const("BUS INIT: ");
|
|
|
|
lin_cmd_backup = cmd;
|
|
lin_cmd_backup_len = len;
|
|
|
|
bytelen = 1;
|
|
msg.dat[0] = 0x81;
|
|
msg.dat[1] = 0x81; // checksum
|
|
|
|
panda_kline_wakeup_pulse();
|
|
} else {
|
|
bytelen = MIN(bytelen, 7);
|
|
for(int i = 0; i < bytelen; i++){
|
|
msg.dat[i] = elm_decode_hex_byte(&cmd[i*2]);
|
|
msg.dat[bytelen] += msg.dat[i];
|
|
}
|
|
|
|
elm_msg_mode_ret_filter = msg.dat[0];
|
|
elm_msg_pid_ret_filter = msg.dat[1];
|
|
}
|
|
|
|
msg.priority = 0xC0 | bytelen;
|
|
msg.receiver = 0x33;
|
|
msg.sender = 0xF1;
|
|
msg.dat[bytelen] += msg.priority + msg.receiver + msg.sender; // checksum
|
|
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Sending LIN OBD: Priority: %02x; RecvAddr: %02x; SendAddr: %02x; (%02x); ",
|
|
msg.priority, msg.receiver, msg.sender, bytelen);
|
|
for(int i = 0; i < 8; i++) os_printf("%02x ", msg.dat[i]);
|
|
os_printf("\n");
|
|
#endif
|
|
|
|
lin_last_sent_msg_len = (msg.priority & 0x07) + 4;
|
|
memcpy(&lin_last_sent_msg, &msg, lin_last_sent_msg_len);
|
|
lin_await_msg_echo = true;
|
|
panda_usbemu_kline_write(&msg);
|
|
|
|
loopcount = LOOPCOUNT_FULL + 1;
|
|
os_timer_disarm(&elm_timeout);
|
|
|
|
if(lin_bus_initialized) {
|
|
os_timer_setfn(&elm_timeout, (os_timer_func_t *)elm_LINFast_timer_cb, proto);
|
|
elm_LINFast_timer_cb((void*)proto);
|
|
} else {
|
|
os_timer_setfn(&elm_timeout, (os_timer_func_t *)elm_LINFast_businit_timer_cb, proto);
|
|
elm_LINFast_businit_timer_cb((void*)proto);
|
|
}
|
|
}
|
|
|
|
/*****************************************************
|
|
*** ELM protocol specification and implementation ***
|
|
*** -> ISO 15765-4 implementation ***
|
|
*****************************************************/
|
|
|
|
void ICACHE_FLASH_ATTR elm_ISO15765_timer_cb(void *arg){
|
|
const elm_protocol_t* proto = (const elm_protocol_t*) arg;
|
|
loopcount--;
|
|
if(loopcount>0) {
|
|
for(int pass = 0; pass < 16 && loopcount; pass++){
|
|
panda_can_msg_t *can_msgs;
|
|
int num_can_msgs = panda_usbemu_can_read(&can_msgs);
|
|
|
|
#ifdef ELM_DEBUG
|
|
if(num_can_msgs) os_printf(" Received %d can messages\n", num_can_msgs);
|
|
#endif
|
|
|
|
if(num_can_msgs < 0) continue;
|
|
if(!num_can_msgs) break;
|
|
|
|
for(int i = 0; i < num_can_msgs; i++){
|
|
|
|
panda_can_msg_t *recv = &can_msgs[i];
|
|
|
|
#ifdef ELM_DEBUG
|
|
os_printf(" RECV: Bus: %d; Addr: %08x; ext: %d; tx: %d; Len: %d; ",
|
|
recv->bus, panda_get_can_addr(recv), recv->ext, recv->tx, recv->len);
|
|
for(int j = 0; j < recv->len; j++) os_printf("%02x ", recv->data[j]);
|
|
os_printf("Ts: %d\n", recv->ts);
|
|
#endif
|
|
|
|
if (recv->bus==0 && recv->len == 8 &&
|
|
(
|
|
(proto->type == CAN11 && !recv->ext && (panda_get_can_addr(recv) & 0x7F8) == 0x7E8) ||
|
|
(proto->type == CAN29 && recv->ext && (panda_get_can_addr(recv) & 0x1FFFFF00) == 0x18DAF100)
|
|
)
|
|
) {
|
|
if(recv->data[0] <= 7 &&
|
|
recv->data[1] == (0x40|elm_msg_mode_ret_filter) &&
|
|
recv->data[2] == elm_msg_pid_ret_filter) {
|
|
got_msg_this_run = true;
|
|
loopcount = LOOPCOUNT_FULL;
|
|
|
|
#ifdef ELM_DEBUG
|
|
os_printf(" CAN msg response, index: %d\n", i);
|
|
#endif
|
|
|
|
if(!is_auto_detecting){
|
|
if(elm_mode_additional_headers){
|
|
elm_append_rsp_can_msg_addr(recv);
|
|
for(int j = 0; j < recv->data[0]+1; j++) elm_append_rsp_hex_byte(recv->data[j]);
|
|
} else {
|
|
for(int j = 1; j < recv->data[0]+1; j++) elm_append_rsp_hex_byte(recv->data[j]);
|
|
}
|
|
|
|
elm_append_rsp_const("\r");
|
|
elm_tcp_tx_flush();
|
|
}
|
|
|
|
} else if((recv->data[0] & 0xF0) == 0x10 &&
|
|
recv->data[2] == (0x40|elm_msg_mode_ret_filter) &&
|
|
recv->data[3] == elm_msg_pid_ret_filter) {
|
|
got_msg_this_run = true;
|
|
loopcount = LOOPCOUNT_FULL;
|
|
panda_usbemu_can_write(0,
|
|
(proto->type==CAN11) ?
|
|
0x7E0 | (panda_get_can_addr(recv)&0x7) :
|
|
(0x18DA00F1 | (((panda_get_can_addr(recv))&0xFF)<<8)),
|
|
"\x30\x00\x00", 3);
|
|
|
|
did_multimessage = true;
|
|
|
|
#ifdef ELM_DEBUG
|
|
os_printf(" CAN multimsg start response, index: %d, len %d\n", i,
|
|
((recv->data[0]&0xF)<<8) | recv->data[1]);
|
|
#endif
|
|
|
|
if(!is_auto_detecting){
|
|
if(!elm_mode_additional_headers) {
|
|
elm_append_rsp(&hex_lookup[recv->data[0]&0xF], 1);
|
|
elm_append_rsp_hex_byte(recv->data[1]);
|
|
elm_append_rsp_const("\r0:");
|
|
if(elm_mode_print_spaces) elm_append_rsp_const(" ");
|
|
for(int j = 2; j < 8; j++) elm_append_rsp_hex_byte(recv->data[j]);
|
|
} else {
|
|
elm_append_rsp_can_msg_addr(recv);
|
|
for(int j = 0; j < 8; j++) elm_append_rsp_hex_byte(recv->data[j]);
|
|
}
|
|
|
|
elm_append_rsp_const("\r");
|
|
elm_tcp_tx_flush();
|
|
}
|
|
|
|
} else if (did_multimessage && (recv->data[0] & 0xF0) == 0x20) {
|
|
got_msg_this_run = true;
|
|
loopcount = LOOPCOUNT_FULL;
|
|
#ifdef ELM_DEBUG
|
|
os_printf(" CAN multimsg data response, index: %d\n", i);
|
|
#endif
|
|
|
|
if(!is_auto_detecting){
|
|
if(!elm_mode_additional_headers) {
|
|
elm_append_rsp(&hex_lookup[recv->data[0] & 0xF], 1);
|
|
elm_append_rsp_const(":");
|
|
if(elm_mode_print_spaces) elm_append_rsp_const(" ");
|
|
for(int j = 1; j < 8; j++) elm_append_rsp_hex_byte(recv->data[j]);
|
|
} else {
|
|
elm_append_rsp_can_msg_addr(recv);
|
|
for(int j = 0; j < 8; j++) elm_append_rsp_hex_byte(recv->data[j]);
|
|
}
|
|
elm_append_rsp_const("\r");
|
|
}
|
|
}
|
|
} else if (recv->bus == 0x80 && recv->len == 8 &&
|
|
(panda_get_can_addr(recv) == ((proto->type==CAN11) ? 0x7DF : 0x18DB33F1))
|
|
) {
|
|
//Can send receipt
|
|
#ifdef ELM_DEBUG
|
|
os_printf(" Got CAN tx receipt\n");
|
|
#endif
|
|
can_tx_worked = true;
|
|
}
|
|
}
|
|
}
|
|
os_timer_arm(&elm_timeout, elm_mode_timeout, 0);
|
|
} else {
|
|
bool got_msg_this_run_backup = got_msg_this_run;
|
|
if(did_multimessage) {
|
|
os_printf(" End of multi message\n");
|
|
} else if(!got_msg_this_run) {
|
|
os_printf(" No data collected\n");
|
|
if(!is_auto_detecting) {
|
|
if(can_tx_worked) {
|
|
elm_append_rsp_const("NO DATA\r");
|
|
} else {
|
|
elm_append_rsp_const("CAN ERROR\r");
|
|
}
|
|
}
|
|
}
|
|
did_multimessage = false;
|
|
got_msg_this_run = false;
|
|
can_tx_worked = false;
|
|
|
|
if(!is_auto_detecting) {
|
|
elm_append_rsp_const("\r>");
|
|
elm_tcp_tx_flush();
|
|
} else {
|
|
elm_autodetect_cb(got_msg_this_run_backup);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ICACHE_FLASH_ATTR elm_init_ISO15765(const elm_protocol_t* proto){
|
|
panda_set_can0_cbaud(proto->cbaud);
|
|
}
|
|
|
|
static void ICACHE_FLASH_ATTR elm_process_obd_cmd_ISO15765(const elm_protocol_t* proto,
|
|
const char *cmd, uint16_t len) {
|
|
elm_can_obd_msg msg = {};
|
|
msg.len = (len-1)/2;
|
|
if((msg.len > 7 && !elm_mode_allow_long) || msg.len > 8) {
|
|
elm_append_rsp_const("?\r\r>");
|
|
return;
|
|
}
|
|
|
|
msg.len = MIN(msg.len, 7);
|
|
|
|
for(int i = 0; i < msg.len; i++)
|
|
msg.dat[i] = elm_decode_hex_byte(&cmd[i*2]);
|
|
|
|
elm_msg_mode_ret_filter = msg.dat[0];
|
|
elm_msg_pid_ret_filter = msg.dat[1];
|
|
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Sending CAN OBD: %02x; ", msg.len);
|
|
for(int i = 0; i < 7; i++)
|
|
os_printf("%02x ", msg.dat[i]);
|
|
os_printf("\n");
|
|
#endif
|
|
|
|
panda_clear_can_rx();
|
|
|
|
panda_usbemu_can_write(0, (proto->type==CAN11) ? 0x7DF : 0x18DB33F1,
|
|
(uint8_t*)&msg, msg.len+1);
|
|
|
|
#ifdef ELM_DEBUG
|
|
os_printf("Starting up timer\n");
|
|
#endif
|
|
|
|
loopcount = LOOPCOUNT_FULL;
|
|
os_timer_disarm(&elm_timeout);
|
|
os_timer_setfn(&elm_timeout, (os_timer_func_t *)elm_ISO15765_timer_cb, proto);
|
|
os_timer_arm(&elm_timeout, elm_mode_timeout, 0);
|
|
}
|
|
|
|
/*****************************************************
|
|
*** ELM protocol specification and implementation ***
|
|
*** -> Stuf for unsupported CAN protocols ***
|
|
*****************************************************/
|
|
|
|
static void ICACHE_FLASH_ATTR elm_process_obd_cmd_CANGen(const elm_protocol_t* proto,
|
|
const char *cmd, uint16_t len) {
|
|
elm_append_rsp_const("NO DATA\r\r>");
|
|
}
|
|
|
|
/*****************************************************
|
|
*** ELM protocol specification and implementation ***
|
|
*** -> AUTO Detect implementation ***
|
|
*****************************************************/
|
|
|
|
static int elm_autodetect_proto_iter;
|
|
static uint16_t elm_staged_auto_msg_len;
|
|
static const char* elm_staged_auto_msg;
|
|
|
|
static void ICACHE_FLASH_ATTR elm_autodetect_cb(bool proto_worked){
|
|
if(proto_worked) {
|
|
os_printf("Autodetect proto success\n");
|
|
is_auto_detecting = false;
|
|
elm_selected_protocol = elm_autodetect_proto_iter;
|
|
elm_current_proto()->process_obd(elm_current_proto(),
|
|
elm_staged_auto_msg, elm_staged_auto_msg_len);
|
|
} else {
|
|
for(elm_autodetect_proto_iter++; elm_autodetect_proto_iter < ELM_PROTOCOL_COUNT;
|
|
elm_autodetect_proto_iter++){
|
|
const elm_protocol_t *proto = &elm_protocols[elm_autodetect_proto_iter];
|
|
if(proto->supported && proto->type != AUTO) {
|
|
os_printf("*** AUTO trying '%s'\n", proto->name);
|
|
proto->init(proto);
|
|
proto->process_obd(proto, "0100\r", 5); // Try sending on the bus
|
|
return;
|
|
}
|
|
}
|
|
|
|
//if(elm_autodetect_main()) return;
|
|
is_auto_detecting = false;
|
|
os_printf("Autodetect failed\n");
|
|
elm_append_rsp_const("UNABLE TO CONNECT\r\r>");
|
|
elm_tcp_tx_flush();
|
|
}
|
|
}
|
|
|
|
static void ICACHE_FLASH_ATTR elm_process_obd_cmd_AUTO(const elm_protocol_t* proto,
|
|
const char *cmd, uint16_t len) {
|
|
elm_append_rsp_const("SEARCHING...\r");
|
|
elm_staged_auto_msg_len = len;
|
|
elm_staged_auto_msg = cmd;
|
|
is_auto_detecting = true;
|
|
|
|
elm_autodetect_proto_iter = 0;
|
|
elm_autodetect_cb(false);
|
|
}
|
|
|
|
/*****************************************************
|
|
*** ELM protocol specification and implementation ***
|
|
*** -> Protocol Registry and related functions. ***
|
|
*****************************************************/
|
|
|
|
static const elm_protocol_t elm_protocols[] = {
|
|
{true, AUTO, 0, elm_process_obd_cmd_AUTO, NULL, "AUTO", },
|
|
{false, NA, 416, elm_process_obd_cmd_J1850, NULL, "SAE J1850 PWM", },
|
|
{false, NA, 104, elm_process_obd_cmd_J1850, NULL, "SAE J1850 VPW", },
|
|
{false, LIN, 104, elm_process_obd_cmd_LIN5baud, NULL, "ISO 9141-2", },
|
|
{false, LIN, 104, elm_process_obd_cmd_LIN5baud, NULL, "ISO 14230-4 (KWP 5BAUD)", },
|
|
{true, LIN, 104, elm_process_obd_cmd_LINFast, NULL, "ISO 14230-4 (KWP FAST)", },
|
|
{true, CAN11, 5000, elm_process_obd_cmd_ISO15765, elm_init_ISO15765, "ISO 15765-4 (CAN 11/500)",},
|
|
{true, CAN29, 5000, elm_process_obd_cmd_ISO15765, elm_init_ISO15765, "ISO 15765-4 (CAN 29/500)",},
|
|
{true, CAN11, 2500, elm_process_obd_cmd_ISO15765, elm_init_ISO15765, "ISO 15765-4 (CAN 11/250)",},
|
|
{true, CAN29, 2500, elm_process_obd_cmd_ISO15765, elm_init_ISO15765, "ISO 15765-4 (CAN 29/250)",},
|
|
{false, CAN29, 2500, elm_process_obd_cmd_CANGen, NULL, "SAE J1939 (CAN 29/250)", },
|
|
{false, CAN11, 1250, elm_process_obd_cmd_CANGen, NULL, "USER1 (CAN 11/125)", },
|
|
{false, CAN11, 500, elm_process_obd_cmd_CANGen, NULL, "USER2 (CAN 11/50)", },
|
|
};
|
|
|
|
static const elm_protocol_t* ICACHE_FLASH_ATTR elm_current_proto() {
|
|
return &elm_protocols[elm_selected_protocol];
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR elm_reset_aux_timer() {
|
|
os_timer_disarm(&elm_proto_aux_timeout);
|
|
if(elm_mode_keepalive_period)
|
|
os_timer_arm(&elm_proto_aux_timeout, elm_mode_keepalive_period, 0);
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR elm_proto_reinit(const elm_protocol_t *proto) {
|
|
if(proto->init) proto->init(proto);
|
|
}
|
|
|
|
/*******************************************
|
|
*** ELM AT command parsing and handling ***
|
|
*******************************************/
|
|
|
|
enum at_cmd_ids_t { // FULL ELM 1.0 list
|
|
AT_INVALID, //Fake
|
|
|
|
AT_AMP1,
|
|
AT_AL,
|
|
AT_AT0, AT_AT1, AT_AT2, // Added ELM 1.2, expected by Torque
|
|
AT_BD,
|
|
AT_BI,
|
|
AT_CAF0, AT_CAF1,
|
|
AT_CF_8, AT_CF_3,
|
|
AT_CFC0, AT_CFC1,
|
|
AT_CM_8, AT_CM_3,
|
|
AT_CP,
|
|
AT_CS,
|
|
AT_CV,
|
|
AT_D,
|
|
AT_DP, AT_DPN,
|
|
AT_E0, AT_E1,
|
|
AT_H0, AT_H1,
|
|
AT_I,
|
|
AT_IB10,
|
|
AT_IB96,
|
|
AT_L0, AT_L1,
|
|
AT_M0, AT_M1, AT_MA,
|
|
AT_MR,
|
|
AT_MT,
|
|
AT_NL,
|
|
AT_PC,
|
|
AT_R0, AT_R1,
|
|
AT_RV,
|
|
AT_S0, AT_S1, // Added ELM 1.3, expected by Torque
|
|
AT_SH_6, AT_SH_3,
|
|
AT_SPA, AT_SP,
|
|
AT_ST,
|
|
AT_SW,
|
|
AT_TPA, AT_TP,
|
|
AT_WM_XYZA, AT_WM_XYZAB, AT_WM_XYZABC,
|
|
AT_WS,
|
|
AT_Z,
|
|
};
|
|
|
|
typedef struct {
|
|
char* name;
|
|
uint8_t name_len;
|
|
uint8_t cmd_len;
|
|
enum at_cmd_ids_t id;
|
|
} at_cmd_reg_t;
|
|
|
|
static const at_cmd_reg_t at_cmd_reg[] = {
|
|
{"@1", 2, 2, AT_AMP1},
|
|
{"AL", 2, 2, AT_AL},
|
|
{"AT0", 3, 3, AT_AT0}, // Added ELM 1.2, expected by Torque
|
|
{"AT1", 3, 3, AT_AT1}, // Added ELM 1.2, expected by Torque
|
|
{"AT2", 3, 3, AT_AT2}, // Added ELM 1.2, expected by Torque
|
|
{"DP", 2, 2, AT_DP},
|
|
{"DPN", 3, 3, AT_DPN},
|
|
{"E0", 2, 2, AT_E0},
|
|
{"E1", 2, 2, AT_E1},
|
|
{"H0", 2, 2, AT_H0},
|
|
{"H1", 2, 2, AT_H1},
|
|
{"I", 1, 1, AT_I},
|
|
{"L0", 2, 2, AT_L0},
|
|
{"L1", 2, 2, AT_L1},
|
|
{"M0", 2, 2, AT_M0},
|
|
//{"M1", 2, 2, AT_M1},
|
|
{"NL", 2, 2, AT_NL},
|
|
{"PC", 2, 2, AT_PC},
|
|
{"S0", 2, 2, AT_S0}, // Added ELM 1.3, expected by Torque
|
|
{"S1", 2, 2, AT_S1}, // Added ELM 1.3, expected by Torque
|
|
{"SP", 2, 3, AT_SP},
|
|
{"SPA", 3, 4, AT_SPA},
|
|
{"ST", 2, 4, AT_ST},
|
|
{"SW", 2, 4, AT_SW},
|
|
{"Z", 1, 1, AT_Z},
|
|
};
|
|
#define AT_CMD_REG_LEN (sizeof(at_cmd_reg)/sizeof(at_cmd_reg_t))
|
|
|
|
static enum at_cmd_ids_t ICACHE_FLASH_ATTR elm_parse_at_cmd(char *cmd, uint16_t len){
|
|
int i;
|
|
for(i=0; i<AT_CMD_REG_LEN; i++){
|
|
at_cmd_reg_t candidate = at_cmd_reg[i];
|
|
if(candidate.cmd_len == len-1 && !memcmp(cmd, candidate.name, candidate.name_len))
|
|
return candidate.id;
|
|
}
|
|
return AT_INVALID;
|
|
}
|
|
|
|
static void ICACHE_FLASH_ATTR elm_process_at_cmd(char *cmd, uint16_t len) {
|
|
uint8_t tmp;
|
|
|
|
os_printf("AT COMMAND ");
|
|
for(int i = 0; i < len; i++) os_printf("%c", cmd[i]);
|
|
os_printf("\r\n");
|
|
|
|
switch(elm_parse_at_cmd(cmd, len)){
|
|
case AT_AMP1: //RETURN DEVICE DESCRIPTION
|
|
elm_append_rsp_const(DEVICE_DESC);
|
|
return;
|
|
case AT_AL: //DISABLE LONG MESSAGE SUPPORT (>7 BYTES)
|
|
elm_mode_allow_long = true;
|
|
break;
|
|
case AT_AT0: //DISABLE ADAPTIVE TIMING
|
|
elm_mode_adaptive_timing = 0;
|
|
break;
|
|
case AT_AT1: //SET ADAPTIVE TIMING TO AUTO1
|
|
elm_mode_adaptive_timing = 1;
|
|
break;
|
|
case AT_AT2: //SET ADAPTIVE TIMING TO AUTO2
|
|
elm_mode_adaptive_timing = 2;
|
|
break;
|
|
case AT_DP: //DESCRIBE THE PROTOCOL BY NAME
|
|
if(elm_mode_auto_protocol && elm_selected_protocol != 0)
|
|
elm_append_rsp_const("AUTO, ");
|
|
elm_append_rsp(elm_current_proto()->name,
|
|
strlen(elm_current_proto()->name));
|
|
elm_append_rsp_const("\r\r");
|
|
return;
|
|
case AT_DPN: //DESCRIBE THE PROTOCOL BY NUMBER
|
|
//TODO: Required. Report currently selected protocol
|
|
if(elm_mode_auto_protocol)
|
|
elm_append_rsp_const("A");
|
|
elm_append_rsp(&hex_lookup[elm_selected_protocol], 1);
|
|
elm_append_rsp_const("\r\r");
|
|
return; // Don't display 'OK'
|
|
case AT_E0: //ECHO OFF
|
|
elm_mode_echo = false;
|
|
break;
|
|
case AT_E1: //ECHO ON
|
|
elm_mode_echo = true;
|
|
break;
|
|
case AT_H0: //SHOW FULL CAN HEADERS OFF
|
|
elm_mode_additional_headers = false;
|
|
break;
|
|
case AT_H1: //SHOW FULL CAN HEADERS ON
|
|
elm_mode_additional_headers = true;
|
|
break;
|
|
case AT_I: //IDENTIFY SELF
|
|
elm_append_rsp_const(IDENT_MSG);
|
|
return;
|
|
case AT_L0: //LINEFEED OFF
|
|
elm_mode_linefeed = false;
|
|
break;
|
|
case AT_L1: //LINEFEED ON
|
|
elm_mode_linefeed = true;
|
|
break;
|
|
case AT_M0: //DISABLE NONVOLATILE STORAGE
|
|
//Memory storage is likely unnecessary
|
|
break;
|
|
case AT_NL: //DISABLE LONG MESSAGE SUPPORT (>7 BYTES)
|
|
elm_mode_allow_long = false;
|
|
break;
|
|
case AT_PC: //PROTOCOL CANCEL (Stop timers and stuff)
|
|
{
|
|
//Init functions should idenpotently prepare the protocol to be used.
|
|
//Thus, the init function can be used as a protocol cancel function
|
|
elm_proto_reinit(elm_current_proto());
|
|
break;
|
|
}
|
|
case AT_S0: //DISABLE PRINTING SPACES IN ECU RESPONSES
|
|
elm_mode_print_spaces = false;
|
|
break;
|
|
case AT_S1: //ENABLE PRINTING SPACES IN ECU RESPONSES
|
|
elm_mode_print_spaces = true;
|
|
break;
|
|
case AT_SP: //SET PROTOCOL
|
|
tmp = elm_decode_hex_char(cmd[2]);
|
|
if(tmp == -1 || tmp >= ELM_PROTOCOL_COUNT) {
|
|
elm_append_rsp_const("?\r\r");
|
|
return;
|
|
}
|
|
|
|
//De-Init previous protocol
|
|
elm_proto_reinit(elm_current_proto());
|
|
|
|
elm_selected_protocol = tmp;
|
|
elm_mode_auto_protocol = (tmp == 0);
|
|
|
|
//Init new protocol
|
|
elm_proto_reinit(elm_current_proto());
|
|
break;
|
|
case AT_SPA: //SET PROTOCOL WITH AUTO FALLBACK
|
|
tmp = elm_decode_hex_char(cmd[3]);
|
|
if(tmp == -1 || tmp >= ELM_PROTOCOL_COUNT) {
|
|
elm_append_rsp_const("?\r\r");
|
|
return;
|
|
}
|
|
|
|
//De-Init previous protocol
|
|
elm_proto_reinit(elm_current_proto());
|
|
|
|
elm_selected_protocol = tmp;
|
|
elm_mode_auto_protocol = true;
|
|
|
|
//Init new protocol
|
|
elm_proto_reinit(elm_current_proto());
|
|
break;
|
|
case AT_ST: //SET TIMEOUT
|
|
if(!elm_check_valid_hex_chars(&cmd[2], 2)) {
|
|
elm_append_rsp_const("?\r\r");
|
|
return;
|
|
}
|
|
|
|
tmp = elm_decode_hex_byte(&cmd[2]);
|
|
//20 for CAN, 4 for LIN
|
|
elm_mode_timeout = tmp ? tmp*20 : ELM_MODE_TIMEOUT_DEFAULT;
|
|
break;
|
|
case AT_SW: //SET WAKEUP TIME INTERVAL
|
|
if(!elm_check_valid_hex_chars(&cmd[2], 2)) {
|
|
elm_append_rsp_const("?\r\r");
|
|
return;
|
|
}
|
|
|
|
tmp = elm_decode_hex_byte(&cmd[2]);
|
|
elm_mode_keepalive_period = tmp ? MAX(tmp, 0x20) * 20 : 0;
|
|
|
|
if(lin_bus_initialized){
|
|
os_timer_disarm(&elm_proto_aux_timeout);
|
|
if(elm_mode_keepalive_period)
|
|
os_timer_arm(&elm_proto_aux_timeout, elm_mode_keepalive_period, 0);
|
|
}
|
|
break;
|
|
case AT_Z: //RESET
|
|
elm_mode_echo = true;
|
|
elm_mode_linefeed = false;
|
|
elm_mode_additional_headers = false;
|
|
elm_mode_auto_protocol = true;
|
|
elm_selected_protocol = ELM_MODE_SELECTED_PROTOCOL_DEFAULT;
|
|
elm_mode_print_spaces = true;
|
|
elm_mode_adaptive_timing = 1;
|
|
elm_mode_allow_long = false;
|
|
elm_mode_timeout = ELM_MODE_TIMEOUT_DEFAULT;
|
|
elm_mode_keepalive_period = ELM_MODE_KEEPALIVE_PERIOD_DEFAULT;
|
|
|
|
elm_append_rsp_const("\r\r");
|
|
elm_append_rsp_const(IDENT_MSG);
|
|
panda_set_safety_mode(SAFETY_ELM327);
|
|
|
|
elm_proto_reinit(elm_current_proto());
|
|
return;
|
|
default:
|
|
elm_append_rsp_const("?\r\r");
|
|
return;
|
|
}
|
|
|
|
elm_append_rsp_const("OK\r\r");
|
|
}
|
|
|
|
/*************************************
|
|
*** Connection and cli management ***
|
|
*************************************/
|
|
|
|
static void ICACHE_FLASH_ATTR elm_append_in_msg(char *data, uint16_t len) {
|
|
if(in_msg_len + len > sizeof(in_msg))
|
|
len = sizeof(in_msg) - in_msg_len;
|
|
memcpy(in_msg + in_msg_len, data, len);
|
|
in_msg_len += len;
|
|
}
|
|
|
|
static int ICACHE_FLASH_ATTR elm_msg_is_at_cmd(char *data, uint16_t len){
|
|
return len >= 4 && data[0] == 'A' && data[1] == 'T';
|
|
}
|
|
|
|
static void ICACHE_FLASH_ATTR elm_rx_cb(void *arg, char *data, uint16_t len) {
|
|
#ifdef ELM_DEBUG
|
|
//os_printf("\nGot ELM Data In: '%s'\n", data);
|
|
#endif
|
|
|
|
rsp_buff_len = 0;
|
|
len = elm_msg_find_cr_or_eos(data, len);
|
|
|
|
if(loopcount){
|
|
os_timer_disarm(&elm_timeout);
|
|
loopcount = 0;
|
|
got_msg_this_run = false;
|
|
can_tx_worked = false;
|
|
did_multimessage = false;
|
|
|
|
os_printf("Interrupting operation, stopping timer. msg len: %d\n", len);
|
|
elm_append_rsp_const("STOPPED\r\r>");
|
|
if(len == 1 && data[0] == '\r') {
|
|
os_printf("Empty msg source of interrupt.\n");
|
|
elm_tcp_tx_flush();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(!(len == 1 && data[0] == '\r') && in_msg_len && in_msg[in_msg_len-1] == '\r'){
|
|
in_msg_len = 0;
|
|
}
|
|
|
|
if(!(len == 1 && data[0] == '\r' && in_msg_len && in_msg[in_msg_len-1] == '\r')) {
|
|
// Not Repeating last message
|
|
elm_append_in_msg(data, len); //Aim to remove this memcpy
|
|
}
|
|
if(elm_mode_echo)
|
|
elm_append_rsp(in_msg, in_msg_len);
|
|
|
|
if(in_msg_len > 0 && in_msg[in_msg_len-1] == '\r') { //Got a full line
|
|
stripped_msg_len = elm_strip(in_msg, in_msg_len, stripped_msg, sizeof(stripped_msg));
|
|
|
|
if(elm_msg_is_at_cmd(stripped_msg, stripped_msg_len)) {
|
|
elm_process_at_cmd(stripped_msg+2, stripped_msg_len-2);
|
|
elm_append_rsp_const(">");
|
|
} else if(elm_check_valid_hex_chars(stripped_msg, stripped_msg_len - 1)) {
|
|
elm_current_proto()->process_obd(elm_current_proto(), stripped_msg, stripped_msg_len);
|
|
} else {
|
|
elm_append_rsp_const("?\r\r>");
|
|
}
|
|
}
|
|
|
|
elm_tcp_tx_flush();
|
|
|
|
//Just clear the buffer if full with no termination
|
|
if(in_msg_len == sizeof(in_msg) && in_msg[in_msg_len-1] != '\r')
|
|
in_msg_len = 0;
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR elm_tcp_disconnect_cb(void *arg){
|
|
struct espconn *pesp_conn = (struct espconn *)arg;
|
|
|
|
elm_tcp_conn_t * prev = NULL;
|
|
for(elm_tcp_conn_t *iter = connection_list; iter != NULL; iter=iter->next){
|
|
struct espconn *conn = iter->conn;
|
|
//SHOW_CONNECTION("Considering Disconnecting", conn);
|
|
if(!memcmp(pesp_conn->proto.tcp->remote_ip, conn->proto.tcp->remote_ip, 4) &&
|
|
pesp_conn->proto.tcp->remote_port == conn->proto.tcp->remote_port){
|
|
os_printf("Deleting ELM Connection!\n");
|
|
if(prev){
|
|
prev->next = iter->next;
|
|
} else {
|
|
connection_list = iter->next;
|
|
}
|
|
os_free(iter);
|
|
break;
|
|
}
|
|
|
|
prev = iter;
|
|
}
|
|
|
|
if(connection_list == NULL) {
|
|
//If all clients are disconnected, reset the protocol (cancels
|
|
//keep alive timers). This will not detect inproperly killed
|
|
//connections. In this case, periodic events associated with the
|
|
//current protocol will continue until a new client attaches, a
|
|
//command is sent generating a response (ELM will try to responde
|
|
//to the dead connection, and remove it upon error), and finally,
|
|
//the new client disconnects. OFC a power cycle is also an option.
|
|
elm_proto_reinit(elm_current_proto());
|
|
}
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR elm_tcp_connect_cb(void *arg) {
|
|
struct espconn *pesp_conn = (struct espconn *)arg;
|
|
//SHOW_CONNECTION("New connection", pesp_conn);
|
|
espconn_set_opt(&elm_conn, ESPCONN_NODELAY);
|
|
espconn_regist_recvcb(pesp_conn, elm_rx_cb);
|
|
//Allow several sends to be queued at a time.
|
|
espconn_tcp_set_buf_count(pesp_conn, 3);
|
|
|
|
bool connection_address_already_there = false;
|
|
for(elm_tcp_conn_t *iter2 = connection_list; iter2 != NULL; iter2 = iter2->next)
|
|
if(iter2->conn == pesp_conn){connection_address_already_there = true; break;}
|
|
|
|
if(connection_address_already_there) {
|
|
os_printf("ELM WIFI: Memory reuse of recently killed connection\n");
|
|
} else {
|
|
os_printf("ELM WIFI: Adding connection\n");
|
|
elm_tcp_conn_t *newconn = os_malloc(sizeof(elm_tcp_conn_t));
|
|
if(!newconn) {
|
|
os_printf("Failed to allocate place for connection\n");
|
|
} else {
|
|
newconn->next = connection_list;
|
|
newconn->conn = pesp_conn;
|
|
connection_list = newconn;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR elm327_init() {
|
|
// control listener
|
|
elm_proto.local_port = ELM_PORT;
|
|
elm_conn.type = ESPCONN_TCP;
|
|
elm_conn.state = ESPCONN_NONE;
|
|
elm_conn.proto.tcp = &elm_proto;
|
|
espconn_regist_connectcb(&elm_conn, elm_tcp_connect_cb);
|
|
espconn_regist_disconcb(&elm_conn, elm_tcp_disconnect_cb);
|
|
espconn_accept(&elm_conn);
|
|
espconn_regist_time(&elm_conn, 0, 0); // 60s timeout for all connections
|
|
}
|