├── .gitattributes ├── .gitignore ├── A0RET.h ├── A0RET.ino ├── ELM327_Emulator.cpp ├── ELM327_Emulator.h ├── LICENSE ├── Logger.cpp ├── Logger.h ├── README.md ├── SerialConsole.cpp ├── SerialConsole.h ├── can_manager.cpp ├── can_manager.h ├── config.h ├── gvret_comm.cpp ├── gvret_comm.h ├── lawicel.cpp ├── lawicel.h ├── sys_io.cpp ├── sys_io.h ├── utility.h ├── wifi_manager.cpp └── wifi_manager.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files we want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.c text 7 | *.h text 8 | *.cpp text 9 | *.ino text 10 | *.txt text 11 | *.brd text 12 | *.sch text 13 | 14 | # Declare files that will always have CRLF line endings on checkout. 15 | *.sln text eol=crlf 16 | *.vcxproj eol=crlf 17 | 18 | # Denote all files that are truly binary and should not be modified. 19 | *.png binary 20 | *.jpg binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | -------------------------------------------------------------------------------- /A0RET.h: -------------------------------------------------------------------------------- 1 | /* 2 | * A0RET.h 3 | * 4 | Copyright (c) 2013-2020 Collin Kidder, Michael Neuweiler 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include "esp32_can.h" 31 | #include "sys_io.h" 32 | 33 | void loadSettings(); 34 | void processDigToggleFrame(CAN_FRAME &frame); 35 | void sendDigToggleMsg(); 36 | void sendMarkTriggered(int which); 37 | -------------------------------------------------------------------------------- /A0RET.ino: -------------------------------------------------------------------------------- 1 | // December 2024 updated for arduino-esp32 core 2.0.x (Tested on 2.0.16 & 2.0.17) 2 | // Use partition scheme: Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS) 3 | // Set CAN TX/RX pins in config.h, lines 39 & 40 4 | 5 | /* 6 | A0RET.ino 7 | 8 | Created: June 1, 2020 9 | Author: Collin Kidder 10 | 11 | Copyright (c) 2014-2020 Collin Kidder, Michael Neuweiler 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining 14 | a copy of this software and associated documentation files (the 15 | "Software"), to deal in the Software without restriction, including 16 | without limitation the rights to use, copy, modify, merge, publish, 17 | distribute, sublicense, and/or sell copies of the Software, and to 18 | permit persons to whom the Software is furnished to do so, subject to 19 | the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included 22 | in all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | /* 34 | This code is now all specialized for the A0 and its ESP32 WRover-B which has 4MB flash and 35 | 8MB of PSRAM. It also only has a single CAN port. 36 | */ 37 | #include "config.h" 38 | #include 39 | #include 40 | #include 41 | #include "ELM327_Emulator.h" 42 | #include "SerialConsole.h" 43 | #include "wifi_manager.h" 44 | #include "gvret_comm.h" 45 | #include "can_manager.h" 46 | #include "lawicel.h" 47 | 48 | byte i = 0; 49 | 50 | uint32_t lastFlushMicros = 0; 51 | 52 | bool markToggle[6]; 53 | uint32_t lastMarkTrigger = 0; 54 | 55 | EEPROMSettings settings; 56 | SystemSettings SysSettings; 57 | Preferences nvPrefs; 58 | 59 | ELM327Emu elmEmulator; 60 | 61 | WiFiManager wifiManager; 62 | 63 | GVRET_Comm_Handler serialGVRET; //gvret protocol over the serial to USB connection 64 | GVRET_Comm_Handler wifiGVRET; //GVRET over the wifi telnet port 65 | CANManager canManager; //keeps track of bus load and abstracts away some details of how things are done 66 | LAWICELHandler lawicel; 67 | 68 | SerialConsole console; 69 | 70 | //initializes all the system EEPROM values. Chances are this should be broken out a bit but 71 | //there is only one checksum check for all of them so it's simple to do it all here. 72 | void loadSettings() 73 | { 74 | Logger::console("Loading settings...."); 75 | 76 | nvPrefs.begin(PREF_NAME, false); 77 | 78 | settings.CAN0Speed = nvPrefs.getUInt("can0speed", 500000); 79 | settings.CAN0_Enabled = nvPrefs.getBool("can0_en", true); 80 | settings.CAN0ListenOnly = nvPrefs.getBool("can0-listenonly", false); 81 | settings.useBinarySerialComm = nvPrefs.getBool("binarycomm", false); 82 | settings.logLevel = nvPrefs.getUChar("loglevel", 1); //info 83 | settings.wifiMode = nvPrefs.getUChar("wifiMode", 2); //Wifi defaults to creating an AP 84 | settings.enableBT = nvPrefs.getBool("enable-bt", false); 85 | settings.enableLawicel = nvPrefs.getBool("enableLawicel", true); 86 | settings.systemType = nvPrefs.getUChar("systype", 0); 87 | settings.CAN1Speed = nvPrefs.getUInt("can1speed", 500000); 88 | settings.CAN1_Enabled = nvPrefs.getBool("can1_en", false); 89 | settings.CAN1ListenOnly = nvPrefs.getBool("can1-listenonly", false); 90 | 91 | if (nvPrefs.getString("SSID", settings.SSID, 32) == 0) 92 | { 93 | strcpy(settings.SSID, "ESP32SSID"); 94 | } 95 | 96 | if (nvPrefs.getString("wpa2Key", settings.WPA2Key, 64) == 0) 97 | { 98 | strcpy(settings.WPA2Key, "aBigSecret"); 99 | } 100 | if (nvPrefs.getString("btname", settings.btName, 32) == 0) 101 | { 102 | strcpy(settings.btName, "ELM327-A0"); 103 | } 104 | 105 | nvPrefs.end(); 106 | 107 | Logger::setLoglevel((Logger::LogLevel)settings.logLevel); 108 | 109 | if (settings.systemType == 0) 110 | { 111 | Logger::console("Running on Macchina A0"); 112 | SysSettings.LED_CANTX = 255; 113 | SysSettings.LED_CANRX = 13; 114 | SysSettings.LED_LOGGING = 255; 115 | SysSettings.logToggle = false; 116 | SysSettings.txToggle = true; 117 | SysSettings.rxToggle = true; 118 | SysSettings.lawicelAutoPoll = false; 119 | SysSettings.lawicelMode = false; 120 | SysSettings.lawicellExtendedMode = false; 121 | SysSettings.lawicelTimestamping = false; 122 | SysSettings.numBuses = 1; //Currently we support CAN0 123 | SysSettings.isWifiActive = false; 124 | SysSettings.isWifiConnected = false; 125 | } 126 | 127 | if (settings.systemType == 1) 128 | { 129 | Logger::console("Running on EVTV ESP32 Board"); 130 | SysSettings.LED_CANTX = 255; 131 | SysSettings.LED_CANRX = 255; 132 | SysSettings.LED_LOGGING = 255; 133 | SysSettings.logToggle = false; 134 | SysSettings.txToggle = true; 135 | SysSettings.rxToggle = true; 136 | SysSettings.lawicelAutoPoll = false; 137 | SysSettings.lawicelMode = false; 138 | SysSettings.lawicellExtendedMode = false; 139 | SysSettings.lawicelTimestamping = false; 140 | SysSettings.numBuses = 2; 141 | SysSettings.isWifiActive = false; 142 | SysSettings.isWifiConnected = false; 143 | } 144 | 145 | for (int rx = 0; rx < NUM_BUSES; rx++) SysSettings.lawicelBusReception[rx] = true; //default to showing messages on RX 146 | //set pin mode for all LEDS 147 | } 148 | 149 | void setup() 150 | { 151 | //delay(5000); //just for testing. Don't use in production 152 | 153 | Serial.begin(1000000); //for production 154 | //Serial.begin(115200); //for testing 155 | 156 | SysSettings.isWifiConnected = false; 157 | 158 | loadSettings(); 159 | 160 | //If you enable PSRAM then BluetoothSerial will kill everything via a heap error. These calls 161 | //try to debug that. But, no dice yet. :( 162 | //heap_caps_print_heap_info(MALLOC_CAP_8BIT); 163 | 164 | wifiManager.setup(); 165 | 166 | //heap_caps_print_heap_info(MALLOC_CAP_8BIT); 167 | 168 | if (settings.enableBT) 169 | { 170 | Serial.println("Starting bluetooth"); 171 | elmEmulator.setup(); 172 | } 173 | 174 | //heap_caps_print_heap_info(MALLOC_CAP_8BIT); 175 | 176 | Serial.print("Build number: "); 177 | Serial.println(CFG_BUILD_NUM); 178 | 179 | canManager.setup(); 180 | 181 | SysSettings.lawicelMode = false; 182 | SysSettings.lawicelAutoPoll = false; 183 | SysSettings.lawicelTimestamping = false; 184 | SysSettings.lawicelPollCounter = 0; 185 | 186 | //elmEmulator.setup(); 187 | 188 | Serial.print("Done with init\n"); 189 | } 190 | 191 | /* 192 | Send a fake frame out USB and maybe to file to show where the mark was triggered at. The fake frame has bits 31 through 3 193 | set which can never happen in reality since frames are either 11 or 29 bit IDs. So, this is a sign that it is a mark frame 194 | and not a real frame. The bottom three bits specify which mark triggered. 195 | */ 196 | void sendMarkTriggered(int which) 197 | { 198 | CAN_FRAME frame; 199 | frame.id = 0xFFFFFFF8ull + which; 200 | frame.extended = true; 201 | frame.length = 0; 202 | frame.rtr = 0; 203 | canManager.displayFrame(frame, 0); 204 | } 205 | 206 | /* 207 | Loop executes as often as possible all the while interrupts fire in the background. 208 | The serial comm protocol is as follows: 209 | All commands start with 0xF1 this helps to synchronize if there were comm issues 210 | Then the next byte specifies which command this is. 211 | Then the command data bytes which are specific to the command 212 | Lastly, there is a checksum byte just to be sure there are no missed or duped bytes 213 | Any bytes between checksum and 0xF1 are thrown away 214 | 215 | Yes, this should probably have been done more neatly but this way is likely to be the 216 | fastest and safest with limited function calls 217 | */ 218 | void loop() 219 | { 220 | //uint32_t temp32; 221 | bool isConnected = false; 222 | int serialCnt; 223 | uint8_t in_byte; 224 | 225 | /*if (Serial)*/ isConnected = true; 226 | 227 | if (SysSettings.lawicelPollCounter > 0) SysSettings.lawicelPollCounter--; 228 | //} 229 | 230 | canManager.loop(); 231 | wifiManager.loop(); 232 | 233 | size_t wifiLength = wifiGVRET.numAvailableBytes(); 234 | size_t serialLength = serialGVRET.numAvailableBytes(); 235 | size_t maxLength = (wifiLength>serialLength)?wifiLength:serialLength; 236 | 237 | //If the max time has passed or the buffer is almost filled then send buffered data out 238 | if ((micros() - lastFlushMicros > SER_BUFF_FLUSH_INTERVAL) || (maxLength > (WIFI_BUFF_SIZE - 40)) ) { 239 | lastFlushMicros = micros(); 240 | if (serialLength > 0) { 241 | Serial.write(serialGVRET.getBufferedBytes(), serialLength); 242 | serialGVRET.clearBufferedBytes(); 243 | } 244 | if (wifiLength > 0) 245 | { 246 | wifiManager.sendBufferedData(); 247 | } 248 | } 249 | 250 | serialCnt = 0; 251 | while ( (Serial.available() > 0) && serialCnt < 128 ) { 252 | serialCnt++; 253 | in_byte = Serial.read(); 254 | serialGVRET.processIncomingByte(in_byte); 255 | } 256 | 257 | if (settings.enableBT) elmEmulator.loop(); 258 | } 259 | -------------------------------------------------------------------------------- /ELM327_Emulator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ELM327_Emu.cpp 3 | * 4 | * Class emulates the serial comm of an ELM327 chip - Used to create an OBDII interface 5 | * 6 | * Created: 3/23/2017 7 | * Author: Collin Kidder 8 | */ 9 | 10 | /* 11 | Copyright (c) 2017 Collin Kidder 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining 14 | a copy of this software and associated documentation files (the 15 | "Software"), to deal in the Software without restriction, including 16 | without limitation the rights to use, copy, modify, merge, publish, 17 | distribute, sublicense, and/or sell copies of the Software, and to 18 | permit persons to whom the Software is furnished to do so, subject to 19 | the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included 22 | in all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | #include "ELM327_Emulator.h" 34 | #include "BluetoothSerial.h" 35 | #include "config.h" 36 | #include "Logger.h" 37 | #include "utility.h" 38 | #include "esp32_can.h" 39 | #include "can_manager.h" 40 | 41 | /* 42 | * Constructor. Nothing at the moment 43 | */ 44 | ELM327Emu::ELM327Emu() 45 | { 46 | 47 | } 48 | 49 | /* 50 | * Initialization of hardware and parameters 51 | */ 52 | void ELM327Emu::setup() { 53 | 54 | tickCounter = 0; 55 | ibWritePtr = 0; 56 | ecuAddress = 0x7E0; 57 | serialBT.begin(settings.btName); 58 | } 59 | 60 | /* 61 | * Send a command to ichip. The "AT+i" part will be added. 62 | */ 63 | void ELM327Emu::sendCmd(String cmd) { 64 | serialBT.print("AT"); 65 | serialBT.print(cmd); 66 | serialBT.write(13); 67 | loop(); // parse the response 68 | } 69 | 70 | /* 71 | * Called in the main loop (hopefully) in order to process serial input waiting for us 72 | * from the wifi module. It should always terminate its answers with 13 so buffer 73 | * until we get 13 (CR) and then process it. 74 | * But, for now just echo stuff to our serial port for debugging 75 | */ 76 | 77 | void ELM327Emu::loop() { 78 | int incoming; 79 | while (serialBT.available()) { 80 | incoming = serialBT.read(); 81 | if (incoming != -1) { //and there is no reason it should be -1 82 | if (incoming == 13 || ibWritePtr > 126) { // on CR or full buffer, process the line 83 | incomingBuffer[ibWritePtr] = 0; //null terminate the string 84 | ibWritePtr = 0; //reset the write pointer 85 | 86 | if (Logger::isDebug()) 87 | Logger::debug(incomingBuffer); 88 | processCmd(); 89 | 90 | } else { // add more characters 91 | if (incoming != 10 && incoming != ' ') // don't add a LF character or spaces. Strip them right out 92 | incomingBuffer[ibWritePtr++] = (char)tolower(incoming); //force lowercase to make processing easier 93 | } 94 | } else 95 | return; 96 | } 97 | } 98 | 99 | /* 100 | * There is no need to pass the string in here because it is local to the class so this function can grab it by default 101 | * But, for reference, this cmd processes the command in incomingBuffer 102 | */ 103 | void ELM327Emu::processCmd() { 104 | String retString = processELMCmd(incomingBuffer); 105 | 106 | serialBT.print(retString); 107 | if (Logger::isDebug()) { 108 | char buff[30]; 109 | retString.toCharArray(buff, 30); 110 | Logger::debug(buff); 111 | } 112 | 113 | } 114 | 115 | String ELM327Emu::processELMCmd(char *cmd) 116 | { 117 | String retString = String(); 118 | String lineEnding; 119 | if (bLineFeed) lineEnding = String("\r\n"); 120 | else lineEnding = String("\r"); 121 | 122 | if (!strncmp(cmd, "at", 2)) 123 | { 124 | 125 | if (!strcmp(cmd, "atz")) 126 | { //reset hardware 127 | retString.concat(lineEnding); 128 | retString.concat("ELM327 v1.3a"); 129 | } 130 | else if (!strncmp(cmd, "atsh",4)) //set header address (address we send queries to) 131 | { 132 | size_t idSize = strlen(cmd+4); 133 | ecuAddress = Utility::parseHexString(cmd+4, idSize); 134 | Logger::debug("New ECU address: %x", ecuAddress); 135 | retString.concat("OK"); 136 | } 137 | else if (!strncmp(cmd, "ate",3)) 138 | { //turn echo on/off 139 | //could support echo but I don't see the need, just ignore this 140 | retString.concat("OK"); 141 | } 142 | else if (!strncmp(cmd, "ath",3)) 143 | { //turn headers on/off 144 | if (cmd[3] == '1') bHeader = true; 145 | else bHeader = false; 146 | retString.concat("OK"); 147 | } 148 | else if (!strncmp(cmd, "atl",3)) 149 | { //turn linefeeds on/off 150 | if (cmd[3] == '1') bLineFeed = true; 151 | else bLineFeed = false; 152 | retString.concat("OK"); 153 | } 154 | else if (!strcmp(cmd, "at@1")) 155 | { //send device description 156 | retString.concat("ELM327 Emulator"); 157 | } 158 | else if (!strcmp(cmd, "ati")) 159 | { //send chip ID 160 | retString.concat("ELM327 v1.3a"); 161 | } 162 | else if (!strncmp(cmd, "atat",4)) 163 | { //set adaptive timing 164 | //don't intend to support adaptive timing at all 165 | retString.concat("OK"); 166 | } 167 | else if (!strncmp(cmd, "atsp",4)) 168 | { //set protocol 169 | //theoretically we can ignore this 170 | retString.concat("OK"); 171 | } 172 | else if (!strcmp(cmd, "atdp")) 173 | { //show description of protocol 174 | retString.concat("can11/500"); 175 | } 176 | else if (!strcmp(cmd, "atdpn")) 177 | { //show protocol number (same as passed to sp) 178 | retString.concat("6"); 179 | } 180 | else if (!strcmp(cmd, "atd")) 181 | { //set to defaults 182 | retString.concat("OK"); 183 | } 184 | else if (!strncmp(cmd, "atm", 3)) 185 | { //turn memory on/off 186 | retString.concat("OK"); 187 | } 188 | else if (!strcmp(cmd, "atrv")) 189 | { //show 12v rail voltage 190 | //TODO: the system should actually have this value so it wouldn't hurt to 191 | //look it up and report the real value. 192 | retString.concat("14.2V"); 193 | } 194 | else 195 | { //by default respond to anything not specifically handled by just saying OK and pretending. 196 | retString.concat("OK"); 197 | } 198 | } 199 | else 200 | { //if no AT then assume it is a PID request. This takes the form of four bytes which form the alpha hex digit encoding for two bytes 201 | //there should be four or six characters here forming the ascii representation of the PID request. Easiest for now is to turn the ascii into 202 | //a 16 bit number and mask off to get the bytes 203 | CAN_FRAME outFrame; 204 | outFrame.id = ecuAddress; 205 | outFrame.extended = false; 206 | outFrame.length = 8; 207 | outFrame.rtr = 0; 208 | outFrame.data.byte[3] = 0xAA; outFrame.data.byte[4] = 0xAA; 209 | outFrame.data.byte[5] = 0xAA; outFrame.data.byte[6] = 0xAA; 210 | outFrame.data.byte[7] = 0xAA; 211 | size_t cmdSize = strlen(cmd); 212 | if (cmdSize == 4) //generic OBDII codes 213 | { 214 | uint32_t valu = strtol((char *) cmd, NULL, 16); //the pid format is always in hex 215 | uint8_t pidnum = (uint8_t)(valu & 0xFF); 216 | uint8_t mode = (uint8_t)((valu >> 8) & 0xFF); 217 | Logger::debug("Mode: %i, PID: %i", mode, pidnum); 218 | outFrame.data.byte[0] = 2; 219 | outFrame.data.byte[1] = mode; 220 | outFrame.data.byte[2] = pidnum; 221 | } 222 | if (cmdSize == 6) //custom PIDs for specific vehicles 223 | { 224 | uint32_t valu = strtol((char *) cmd, NULL, 16); //the pid format is always in hex 225 | uint16_t pidnum = (uint8_t)(valu & 0xFFFF); 226 | uint8_t mode = (uint8_t)((valu >> 16) & 0xFF); 227 | Logger::debug("Mode: %i, PID: %i", mode, pidnum); 228 | outFrame.data.byte[0] = 3; 229 | outFrame.data.byte[1] = mode; 230 | outFrame.data.byte[2] = pidnum >> 8; 231 | outFrame.data.byte[3] = pidnum & 0xFF; 232 | } 233 | 234 | canManager.sendFrame(&CAN0, outFrame); 235 | } 236 | 237 | retString.concat(lineEnding); 238 | retString.concat(">"); //prompt to show we're ready to receive again 239 | 240 | return retString; 241 | } 242 | 243 | void ELM327Emu::processCANReply(CAN_FRAME &frame) 244 | { 245 | //at the moment assume anything sent here is a legit reply to something we sent. Package it up properly 246 | //and send it down the line 247 | char buff[8]; 248 | if (bHeader) 249 | { 250 | sprintf(buff, "%03X", frame.id); 251 | serialBT.print(buff); 252 | } 253 | for (int i = 0; i < frame.data.byte[0]; i++) 254 | { 255 | sprintf(buff, "%02X", frame.data.byte[1+i]); 256 | serialBT.print(buff); 257 | } 258 | } -------------------------------------------------------------------------------- /ELM327_Emulator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ELM327_Emu.h 3 | * 4 | * Class emulates the serial comm of an ELM327 chip - Used to create an OBDII interface 5 | * 6 | * Created: 3/23/2017 7 | * Author: Collin Kidder 8 | */ 9 | 10 | /* 11 | Copyright (c) 2017 Collin Kidder 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining 14 | a copy of this software and associated documentation files (the 15 | "Software"), to deal in the Software without restriction, including 16 | without limitation the rights to use, copy, modify, merge, publish, 17 | distribute, sublicense, and/or sell copies of the Software, and to 18 | permit persons to whom the Software is furnished to do so, subject to 19 | the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included 22 | in all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | /* 34 | List of AT commands to support: 35 | AT E0 (turn echo off) 36 | AT H (0/1) - Turn headers on or off - headers are used to determine how many ECU√≠s present (hint: only send one response to 0100 and emulate a single ECU system to save time coding) 37 | AT L0 (Turn linefeeds off - just use CR) 38 | AT Z (reset) 39 | AT SH - Set header address - seems to set the ECU address to send to (though you may be able to ignore this if you wish) 40 | AT @1 - Display device description - ELM327 returns: Designed by Andy Honecker 2011 41 | AT I - Cause chip to output its ID: ELM327 says: ELM327 v1.3a 42 | AT AT (0/1/2) - Set adaptive timing (though you can ignore this) 43 | AT SP (set protocol) - you can ignore this 44 | AT DP (get protocol by name) - (always return can11/500) 45 | AT DPN (get protocol by number) - (always return 6) 46 | AT RV (adapter voltage) - Send something like 14.4V 47 | */ 48 | 49 | 50 | #ifndef ELM327_H_ 51 | #define ELM327_H_ 52 | 53 | #include 54 | #include "BluetoothSerial.h" 55 | 56 | class CAN_FRAME; 57 | 58 | class ELM327Emu { 59 | public: 60 | 61 | ELM327Emu(); 62 | void setup(); //initialization on start up 63 | void handleTick(); //periodic processes 64 | void loop(); 65 | void sendCmd(String cmd); 66 | void processCANReply(CAN_FRAME &frame); 67 | 68 | private: 69 | BluetoothSerial serialBT; 70 | char incomingBuffer[128]; //storage for one incoming line 71 | char buffer[30]; // a buffer for various string conversions 72 | bool bLineFeed; //should we use line feeds? 73 | bool bHeader; //should we produce a header? 74 | uint32_t ecuAddress; 75 | int tickCounter; 76 | int ibWritePtr; 77 | int currReply; 78 | 79 | void processCmd(); 80 | String processELMCmd(char *cmd); 81 | }; 82 | 83 | #endif 84 | 85 | 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Collin Kidder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Logger.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Logger.cpp 3 | * 4 | Copyright (c) 2013 Collin Kidder, Michael Neuweiler, Charles Galpin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | #include "Logger.h" 28 | #include "config.h" 29 | #include "sys_io.h" 30 | #include "EEPROM.h" 31 | 32 | Logger::LogLevel Logger::logLevel = Logger::Info; 33 | uint32_t Logger::lastLogTime = 0; 34 | 35 | /* 36 | * Output a debug message with a variable amount of parameters. 37 | * printf() style, see Logger::log() 38 | * 39 | */ 40 | void Logger::debug(const char *message, ...) 41 | { 42 | if (logLevel > Debug) { 43 | return; 44 | } 45 | 46 | va_list args; 47 | va_start(args, message); 48 | Logger::log(Debug, message, args); 49 | va_end(args); 50 | } 51 | 52 | /* 53 | * Output a info message with a variable amount of parameters 54 | * printf() style, see Logger::log() 55 | */ 56 | void Logger::info(const char *message, ...) 57 | { 58 | if (logLevel > Info) { 59 | return; 60 | } 61 | 62 | va_list args; 63 | va_start(args, message); 64 | Logger::log(Info, message, args); 65 | va_end(args); 66 | } 67 | 68 | /* 69 | * Output a warning message with a variable amount of parameters 70 | * printf() style, see Logger::log() 71 | */ 72 | void Logger::warn(const char *message, ...) 73 | { 74 | if (logLevel > Warn) { 75 | return; 76 | } 77 | 78 | va_list args; 79 | va_start(args, message); 80 | Logger::log(Warn, message, args); 81 | va_end(args); 82 | } 83 | 84 | /* 85 | * Output a error message with a variable amount of parameters 86 | * printf() style, see Logger::log() 87 | */ 88 | void Logger::error(const char *message, ...) 89 | { 90 | if (logLevel > Error) { 91 | return; 92 | } 93 | 94 | va_list args; 95 | va_start(args, message); 96 | Logger::log(Error, message, args); 97 | va_end(args); 98 | } 99 | 100 | /* 101 | * Output a console message with a variable amount of parameters 102 | * printf() style, see Logger::logMessage() 103 | */ 104 | void Logger::console(const char *message, ...) 105 | { 106 | va_list args; 107 | va_start(args, message); 108 | Logger::logMessage(message, args); 109 | va_end(args); 110 | } 111 | 112 | /* 113 | * Set the log level. Any output below the specified log level will be omitted. 114 | */ 115 | void Logger::setLoglevel(LogLevel level) 116 | { 117 | logLevel = level; 118 | } 119 | 120 | /* 121 | * Retrieve the current log level. 122 | */ 123 | Logger::LogLevel Logger::getLogLevel() 124 | { 125 | return logLevel; 126 | } 127 | 128 | /* 129 | * Return a timestamp when the last log entry was made. 130 | */ 131 | uint32_t Logger::getLastLogTime() 132 | { 133 | return lastLogTime; 134 | } 135 | 136 | /* 137 | * Returns if debug log level is enabled. This can be used in time critical 138 | * situations to prevent unnecessary string concatenation (if the message won't 139 | * be logged in the end). 140 | * 141 | * Example: 142 | * if (Logger::isDebug()) { 143 | * Logger::debug("current time: %d", millis()); 144 | * } 145 | */ 146 | boolean Logger::isDebug() 147 | { 148 | return logLevel == Debug; 149 | } 150 | 151 | /* 152 | * Output a log message (called by debug(), info(), warn(), error(), console()) 153 | * 154 | * Supports printf() like syntax: 155 | * 156 | * %% - outputs a '%' character 157 | * %s - prints the next parameter as string 158 | * %d - prints the next parameter as decimal 159 | * %f - prints the next parameter as double float 160 | * %x - prints the next parameter as hex value 161 | * %X - prints the next parameter as hex value with '0x' added before 162 | * %b - prints the next parameter as binary value 163 | * %B - prints the next parameter as binary value with '0b' added before 164 | * %l - prints the next parameter as long 165 | * %c - prints the next parameter as a character 166 | * %t - prints the next parameter as boolean ('T' or 'F') 167 | * %T - prints the next parameter as boolean ('true' or 'false') 168 | */ 169 | void Logger::log(LogLevel level, const char *format, va_list args) 170 | { 171 | lastLogTime = millis(); 172 | Serial.print(lastLogTime); 173 | Serial.print(" - "); 174 | 175 | switch (level) { 176 | case Debug: 177 | Serial.print("DEBUG"); 178 | break; 179 | 180 | case Info: 181 | Serial.print("INFO"); 182 | break; 183 | 184 | case Warn: 185 | Serial.print("WARNING"); 186 | break; 187 | 188 | case Error: 189 | Serial.print("ERROR"); 190 | break; 191 | } 192 | 193 | Serial.print(": "); 194 | 195 | logMessage(format, args); 196 | } 197 | 198 | /* 199 | * Output a log message (called by log(), console()) 200 | * 201 | * Supports printf() like syntax: 202 | * 203 | * %% - outputs a '%' character 204 | * %s - prints the next parameter as string 205 | * %d - prints the next parameter as decimal 206 | * %f - prints the next parameter as double float 207 | * %x - prints the next parameter as hex value 208 | * %X - prints the next parameter as hex value with '0x' added before 209 | * %l - prints the next parameter as long 210 | * %c - prints the next parameter as a character 211 | * %t - prints the next parameter as boolean ('T' or 'F') 212 | * %T - prints the next parameter as boolean ('true' or 'false') 213 | */ 214 | void Logger::logMessage(const char *format, va_list args) 215 | { 216 | uint8_t buffer[200]; 217 | uint8_t buffLen = 0; 218 | uint8_t writeLen; 219 | for (; *format != 0; ++format) { 220 | if (*format == '%') { 221 | ++format; 222 | 223 | if (*format == '\0') { 224 | break; 225 | } 226 | 227 | if (*format == '%') { 228 | buffer[buffLen++] = *format; 229 | continue; 230 | } 231 | 232 | if (*format == 's') { 233 | register char *s = (char *) va_arg(args, int); 234 | writeLen = sprintf((char*)&buffer[buffLen], "%s", s); 235 | buffLen += writeLen; 236 | continue; 237 | } 238 | 239 | if (*format == 'd' || *format == 'i') { 240 | writeLen = sprintf((char*)&buffer[buffLen], "%i", va_arg(args, int)); 241 | buffLen += writeLen; 242 | continue; 243 | } 244 | 245 | if (*format == 'f') { 246 | writeLen = sprintf((char*)&buffer[buffLen], "%.2f", va_arg(args, double)); 247 | buffLen += writeLen; 248 | continue; 249 | } 250 | 251 | if (*format == 'x') { 252 | writeLen = sprintf((char*)&buffer[buffLen], "%X", va_arg(args, int)); 253 | buffLen += writeLen; 254 | continue; 255 | } 256 | 257 | if (*format == 'X') { 258 | writeLen = sprintf((char*)&buffer[buffLen], "0x%X", va_arg(args, int)); 259 | buffLen += writeLen; 260 | continue; 261 | } 262 | 263 | if (*format == 'l') { 264 | writeLen = sprintf((char*)&buffer[buffLen], "%l", va_arg(args, long)); 265 | buffLen += writeLen; 266 | continue; 267 | } 268 | 269 | if (*format == 'c') { 270 | writeLen = sprintf((char*)&buffer[buffLen], "%c", va_arg(args, int)); 271 | buffLen += writeLen; 272 | continue; 273 | } 274 | 275 | if (*format == 't') { 276 | if (va_arg(args, int) == 1) { 277 | buffer[buffLen++] = 'T'; 278 | } else { 279 | buffer[buffLen++] = 'F'; 280 | } 281 | continue; 282 | } 283 | 284 | if (*format == 'T') { 285 | if (va_arg(args, int) == 1) { 286 | writeLen = sprintf((char*)&buffer[buffLen], "TRUE"); 287 | buffLen += writeLen; 288 | } else { 289 | writeLen = sprintf((char*)&buffer[buffLen], "FALSE"); 290 | buffLen += writeLen; 291 | } 292 | continue; 293 | } 294 | } 295 | else buffer[buffLen++] = *format; 296 | } 297 | buffer[buffLen++] = '\r'; 298 | buffer[buffLen++] = '\n'; 299 | Serial.write(buffer, buffLen); 300 | //If wifi has connected nodes then send to them too. 301 | for(int i = 0; i < MAX_CLIENTS; i++){ 302 | if (SysSettings.clientNodes[i] && SysSettings.clientNodes[i].connected()){ 303 | SysSettings.clientNodes[i].write(buffer, buffLen); 304 | } 305 | } 306 | } 307 | 308 | 309 | -------------------------------------------------------------------------------- /Logger.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Logger.h 3 | * 4 | Copyright (c) 2013 Collin Kidder, Michael Neuweiler, Charles Galpin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | #ifndef LOGGER_H_ 28 | #define LOGGER_H_ 29 | 30 | #include 31 | #include "config.h" 32 | 33 | 34 | class Logger { 35 | public: 36 | enum LogLevel { 37 | Debug = 0, Info = 1, Warn = 2, Error = 3, Off = 4 38 | }; 39 | static void debug(const char *, ...); 40 | static void info(const char *, ...); 41 | static void warn(const char *, ...); 42 | static void error(const char *, ...); 43 | static void console(const char *, ...); 44 | static void setLoglevel(LogLevel); 45 | static LogLevel getLogLevel(); 46 | static uint32_t getLastLogTime(); 47 | static boolean isDebug(); 48 | static void loop(); 49 | private: 50 | static LogLevel logLevel; 51 | static uint32_t lastLogTime; 52 | 53 | static void log(LogLevel, const char *format, va_list); 54 | static void logMessage(const char *format, va_list args); 55 | }; 56 | 57 | #endif /* LOGGER_H_ */ 58 | 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A0RET 2 | ======= 3 | 4 | Reverse Engineering Tool running on ESP32 based hardware. Originally meant for Macchina A0 but also 5 | has support for EVTV ESP32 Board (which was originally supported by ESP32RET). 6 | 7 | A fork of the ESP32RET project, itself a fork of the M2RET project, 8 | itself a fork of the GVRET project. 9 | 10 | #### Requirements: 11 | 12 | You will need the following to be able to compile the run this project: 13 | 14 | - [Arduino IDE](https://www.arduino.cc/en/software) Tested on IDE v2.3.4 with arduino-esp32 core v2.0.17 15 | - [Arduino-ESP32](https://github.com/espressif/arduino-esp32) - Allows for programming the ESP32 with the Arduino IDE 16 | - [esp32_can](https://github.com/collin80/esp32_can) - A unified CAN library that supports the built-in CAN plus MCP2515 and MCP2517FD 17 | 18 | PLEASE NOTE: The Macchina A0 uses a WRover ESP32 module which includes PSRAM. But, do NOT use the WRover 19 | board in the Arduino IDE nor try to enable PSRAM. Doing so causes a fatal crash bug. 20 | 21 | The EVTV board has no PSRAM anyway. 22 | 23 | All libraries belong in %USERPROFILE%\Documents\Arduino\hardware\esp32\libraries (Windows) or ~/Arduino/hardware/esp32/libraries (Linux/Mac). 24 | 25 | The canbus is supposed to be terminated on both ends of the bus. This should not be a problem as this firmware will be used to reverse engineer existing buses. However, do note that CAN buses should have a resistance from CAN_H to CAN_L of 60 ohms. This is affected by placing a 120 ohm resistor on both sides of the bus. If the bus resistance is not fairly close to 60 ohms then you may run into trouble. 26 | 27 | #### The firmware is a work in progress. What works: 28 | - CAN0 reading and writing 29 | - Preferences are saved and loaded 30 | - Text console is active (configuration and CAN capture display) 31 | - Can connect as a GVRET device with SavvyCAN 32 | - LAWICEL support (somewhat tested. Still experimental) 33 | - Bluetooth works to create an ELM327 compatible interface (tested with Torque app) 34 | 35 | #### What does not work: 36 | - Digital and Analog I/O 37 | 38 | #### License: 39 | 40 | This software is MIT licensed: 41 | 42 | Copyright (c) 2014-2020 Collin Kidder, Michael Neuweiler 43 | 44 | Permission is hereby granted, free of charge, to any person obtaining 45 | a copy of this software and associated documentation files (the 46 | "Software"), to deal in the Software without restriction, including 47 | without limitation the rights to use, copy, modify, merge, publish, 48 | distribute, sublicense, and/or sell copies of the Software, and to 49 | permit persons to whom the Software is furnished to do so, subject to 50 | the following conditions: 51 | 52 | The above copyright notice and this permission notice shall be included 53 | in all copies or substantial portions of the Software. 54 | 55 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 56 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 57 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 58 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 59 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 60 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 61 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 62 | 63 | -------------------------------------------------------------------------------- /SerialConsole.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SerialConsole.cpp 3 | * 4 | Copyright (c) 2014-2018 Collin Kidder 5 | 6 | Shamelessly copied from the version in GEVCU 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included 17 | in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | */ 28 | 29 | #include "SerialConsole.h" 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "config.h" 36 | #include "sys_io.h" 37 | #include "lawicel.h" 38 | 39 | extern void CANHandler(); 40 | 41 | SerialConsole::SerialConsole() 42 | { 43 | init(); 44 | } 45 | 46 | void SerialConsole::init() 47 | { 48 | //State variables for serial console 49 | ptrBuffer = 0; 50 | state = STATE_ROOT_MENU; 51 | } 52 | 53 | void SerialConsole::printMenu() 54 | { 55 | char buff[80]; 56 | //Show build # here as well in case people are using the native port and don't get to see the start up messages 57 | Serial.print("Build number: "); 58 | Serial.println(CFG_BUILD_NUM); 59 | Serial.println("System Menu:"); 60 | Serial.println(); 61 | Serial.println("Enable line endings of some sort (LF, CR, CRLF)"); 62 | Serial.println(); 63 | Serial.println("Short Commands:"); 64 | Serial.println("h = help (displays this message)"); 65 | Serial.println("R = reset to factory defaults"); 66 | Serial.println("s = Start logging to file"); 67 | Serial.println("S = Stop logging to file"); 68 | Serial.println(); 69 | Serial.println("Config Commands (enter command=newvalue). Current values shown in parenthesis:"); 70 | Serial.println(); 71 | 72 | Logger::console("SYSTYPE=%i - Set board type (0 = Macchina A0, 1 = EVTV ESP32 Board", settings.systemType); 73 | Logger::console("LOGLEVEL=%i - set log level (0=debug, 1=info, 2=warn, 3=error, 4=off)", settings.logLevel); 74 | Serial.println(); 75 | 76 | Logger::console("CAN0EN=%i - Enable/Disable CAN0 (0 = Disable, 1 = Enable)", settings.CAN0_Enabled); 77 | Logger::console("CAN0SPEED=%i - Set speed of CAN0 in baud (125000, 250000, etc)", settings.CAN0Speed); 78 | Logger::console("CAN0LISTENONLY=%i - Enable/Disable Listen Only Mode (0 = Dis, 1 = En)", settings.CAN0ListenOnly); 79 | /*for (int i = 0; i < 8; i++) { 80 | sprintf(buff, "CAN0FILTER%i=0x%%x,0x%%x,%%i,%%i (ID, Mask, Extended, Enabled)", i); 81 | Logger::console(buff, settings.CAN0Filters[i].id, settings.CAN0Filters[i].mask, 82 | settings.CAN0Filters[i].extended, settings.CAN0Filters[i].enabled); 83 | }*/ 84 | Serial.println(); 85 | 86 | if (settings.systemType != 0) 87 | { 88 | Logger::console("CAN1EN=%i - Enable/Disable CAN0 (0 = Disable, 1 = Enable)", settings.CAN1_Enabled); 89 | Logger::console("CAN1SPEED=%i - Set speed of CAN0 in baud (125000, 250000, etc)", settings.CAN1Speed); 90 | Logger::console("CAN1LISTENONLY=%i - Enable/Disable Listen Only Mode (0 = Dis, 1 = En)", settings.CAN1ListenOnly); 91 | /*for (int i = 0; i < 8; i++) { 92 | sprintf(buff, "CAN0FILTER%i=0x%%x,0x%%x,%%i,%%i (ID, Mask, Extended, Enabled)", i); 93 | Logger::console(buff, settings.CAN0Filters[i].id, settings.CAN0Filters[i].mask, 94 | settings.CAN0Filters[i].extended, settings.CAN0Filters[i].enabled); 95 | }*/ 96 | Serial.println(); 97 | } 98 | 99 | Logger::console("CAN0SEND=ID,LEN, - Ex: CAN0SEND=0x200,4,1,2,3,4"); 100 | if (settings.systemType !=0) 101 | Logger::console("CAN1SEND=ID,LEN, - Ex: CAN1SEND=0x200,4,1,2,3,4"); 102 | Logger::console("MARK= - Set a mark in the log file about what you are about to do."); 103 | Serial.println(); 104 | 105 | Logger::console("BINSERIAL=%i - Enable/Disable Binary Sending of CANBus Frames to Serial (0=Dis, 1=En)", settings.useBinarySerialComm); 106 | Serial.println(); 107 | 108 | Logger::console("BTMODE=%i - Set mode for Bluetooth (0 = Off, 1 = On)", settings.enableBT); 109 | Logger::console("BTNAME=%s - Set advertised Bluetooth name", settings.btName); 110 | Serial.println(); 111 | 112 | Logger::console("LAWICEL=%i - Set whether to accept LAWICEL commands (0 = Off, 1 = On)", settings.enableLawicel); 113 | Serial.println(); 114 | 115 | Logger::console("WIFIMODE=%i - Set mode for WiFi (0 = Wifi Off, 1 = Connect to AP, 2 = Create AP", settings.wifiMode); 116 | Logger::console("SSID=%s - Set SSID to either connect to or create", (char *)settings.SSID); 117 | Logger::console("WPA2KEY=%s - Either passphrase or actual key", (char *)settings.WPA2Key); 118 | } 119 | 120 | /* There is a help menu (press H or h or ?) 121 | This is no longer going to be a simple single character console. 122 | Now the system can handle up to 80 input characters. Commands are submitted 123 | by sending line ending (LF, CR, or both) 124 | */ 125 | void SerialConsole::rcvCharacter(uint8_t chr) 126 | { 127 | if (chr == 10 || chr == 13) { //command done. Parse it. 128 | handleConsoleCmd(); 129 | ptrBuffer = 0; //reset line counter once the line has been processed 130 | } else { 131 | cmdBuffer[ptrBuffer++] = (unsigned char) chr; 132 | if (ptrBuffer > 79) 133 | ptrBuffer = 79; 134 | } 135 | } 136 | 137 | void SerialConsole::handleConsoleCmd() 138 | { 139 | if (state == STATE_ROOT_MENU) { 140 | if (ptrBuffer == 1) { 141 | //command is a single ascii character 142 | handleShortCmd(); 143 | } else { //at least two bytes 144 | boolean equalSign = false; 145 | for (int i = 0; i < ptrBuffer; i++) if (cmdBuffer[i] == '=') equalSign = true; 146 | cmdBuffer[ptrBuffer] = 0; //make sure to null terminate 147 | if (equalSign) handleConfigCmd(); 148 | else if (settings.enableLawicel) lawicel.handleLongCmd(cmdBuffer); 149 | } 150 | ptrBuffer = 0; //reset line counter once the line has been processed 151 | } 152 | } 153 | 154 | void SerialConsole::handleShortCmd() 155 | { 156 | uint8_t val; 157 | 158 | switch (cmdBuffer[0]) { 159 | //non-lawicel commands 160 | case 'h': 161 | case '?': 162 | case 'H': 163 | printMenu(); 164 | break; 165 | case 'R': //reset to factory defaults. 166 | nvPrefs.begin(PREF_NAME, false); 167 | nvPrefs.clear(); 168 | nvPrefs.end(); 169 | Logger::console("Power cycle to reset to factory defaults"); 170 | break; 171 | default: 172 | if (settings.enableLawicel) lawicel.handleShortCmd(cmdBuffer[0]); 173 | break; 174 | } 175 | } 176 | 177 | void SerialConsole::handleConfigCmd() 178 | { 179 | int i; 180 | int newValue; 181 | char *newString; 182 | bool writeEEPROM = false; 183 | bool writeDigEE = false; 184 | char *dataTok; 185 | 186 | //Logger::debug("Cmd size: %i", ptrBuffer); 187 | if (ptrBuffer < 6) 188 | return; //4 digit command, =, value is at least 6 characters 189 | cmdBuffer[ptrBuffer] = 0; //make sure to null terminate 190 | String cmdString = String(); 191 | unsigned char whichEntry = '0'; 192 | i = 0; 193 | 194 | while (cmdBuffer[i] != '=' && i < ptrBuffer) { 195 | cmdString.concat(String(cmdBuffer[i++])); 196 | } 197 | i++; //skip the = 198 | if (i >= ptrBuffer) { 199 | Logger::console("Command needs a value..ie TORQ=3000"); 200 | Logger::console(""); 201 | return; //or, we could use this to display the parameter instead of setting 202 | } 203 | 204 | // strtol() is able to parse also hex values (e.g. a string "0xCAFE"), useful for enable/disable by device id 205 | newValue = strtol((char *) (cmdBuffer + i), NULL, 0); //try to turn the string into a number 206 | newString = (char *)(cmdBuffer + i); //leave it as a string 207 | 208 | cmdString.toUpperCase(); 209 | 210 | if (cmdString == String("CAN0EN")) { 211 | if (newValue < 0) newValue = 0; 212 | if (newValue > 1) newValue = 1; 213 | Logger::console("Setting CAN0 Enabled to %i", newValue); 214 | settings.CAN0_Enabled = newValue; 215 | if (newValue == 1) 216 | { 217 | //CAN0.enable(); 218 | CAN0.begin(settings.CAN0Speed, 255); 219 | CAN0.watchFor(); 220 | } 221 | else CAN0.disable(); 222 | writeEEPROM = true; 223 | } else if (cmdString == String("CAN0SPEED")) { 224 | if (newValue > 0 && newValue <= 1000000) { 225 | Logger::console("Setting CAN0 Baud Rate to %i", newValue); 226 | settings.CAN0Speed = newValue; 227 | if (settings.CAN0_Enabled) CAN0.begin(settings.CAN0Speed, 255); 228 | writeEEPROM = true; 229 | } else Logger::console("Invalid baud rate! Enter a value 1 - 1000000"); 230 | } else if (cmdString == String("CAN0LISTENONLY")) { 231 | if (newValue >= 0 && newValue <= 1) { 232 | Logger::console("Setting CAN0 Listen Only to %i", newValue); 233 | settings.CAN0ListenOnly = newValue; 234 | if (settings.CAN0ListenOnly) { 235 | CAN0.setListenOnlyMode(true); 236 | } else { 237 | CAN0.setListenOnlyMode(false); 238 | } 239 | writeEEPROM = true; 240 | } else Logger::console("Invalid setting! Enter a value 0 - 1"); 241 | } else if (cmdString == String("CAN1EN")) { 242 | if (newValue < 0) newValue = 0; 243 | if (newValue > 1) newValue = 1; 244 | Logger::console("Setting CAN1 Enabled to %i", newValue); 245 | settings.CAN1_Enabled = newValue; 246 | if (newValue == 1 && settings.systemType != 0) 247 | { 248 | //CAN0.enable(); 249 | CAN1.begin(settings.CAN0Speed, 255); 250 | CAN1.watchFor(); 251 | } 252 | else CAN1.disable(); 253 | writeEEPROM = true; 254 | } else if (cmdString == String("CAN1SPEED")) { 255 | if (newValue > 0 && newValue <= 1000000) { 256 | Logger::console("Setting CAN1 Baud Rate to %i", newValue); 257 | settings.CAN1Speed = newValue; 258 | if (settings.CAN1_Enabled) CAN1.begin(settings.CAN1Speed, 255); 259 | writeEEPROM = true; 260 | } else Logger::console("Invalid baud rate! Enter a value 1 - 1000000"); 261 | } else if (cmdString == String("CAN1LISTENONLY")) { 262 | if (newValue >= 0 && newValue <= 1) { 263 | Logger::console("Setting CAN1 Listen Only to %i", newValue); 264 | settings.CAN1ListenOnly = newValue; 265 | if (settings.CAN1ListenOnly) { 266 | CAN1.setListenOnlyMode(true); 267 | } else { 268 | CAN1.setListenOnlyMode(false); 269 | } 270 | writeEEPROM = true; 271 | } else Logger::console("Invalid setting! Enter a value 0 - 1"); 272 | } else if (cmdString == String("CAN0FILTER0")) { //someone should kick me in the face for this laziness... FIX THIS! 273 | handleFilterSet(0, 0, newString); 274 | } else if (cmdString == String("CAN0FILTER1")) { 275 | if (handleFilterSet(0, 1, newString)) writeEEPROM = true; 276 | } else if (cmdString == String("CAN0FILTER2")) { 277 | if (handleFilterSet(0, 2, newString)) writeEEPROM = true; 278 | } else if (cmdString == String("CAN0FILTER3")) { 279 | if (handleFilterSet(0, 3, newString)) writeEEPROM = true; 280 | } else if (cmdString == String("CAN0FILTER4")) { 281 | if (handleFilterSet(0, 4, newString)) writeEEPROM = true; 282 | } else if (cmdString == String("CAN0FILTER5")) { 283 | if (handleFilterSet(0, 5, newString)) writeEEPROM = true; 284 | } else if (cmdString == String("CAN0FILTER6")) { 285 | if (handleFilterSet(0, 6, newString)) writeEEPROM = true; 286 | } else if (cmdString == String("CAN0FILTER7")) { 287 | if (handleFilterSet(0, 7, newString)) writeEEPROM = true; 288 | } else if (cmdString == String("CAN1FILTER0")) { 289 | if (handleFilterSet(1, 0, newString)) writeEEPROM = true; 290 | } else if (cmdString == String("CAN1FILTER1")) { 291 | if (handleFilterSet(1, 1, newString)) writeEEPROM = true; 292 | } else if (cmdString == String("CAN1FILTER2")) { 293 | if (handleFilterSet(1, 2, newString)) writeEEPROM = true; 294 | } else if (cmdString == String("CAN1FILTER3")) { 295 | if (handleFilterSet(1, 3, newString)) writeEEPROM = true; 296 | } else if (cmdString == String("CAN1FILTER4")) { 297 | if (handleFilterSet(1, 4, newString)) writeEEPROM = true; 298 | } else if (cmdString == String("CAN1FILTER5")) { 299 | if (handleFilterSet(1, 5, newString)) writeEEPROM = true; 300 | } else if (cmdString == String("CAN1FILTER6")) { 301 | if (handleFilterSet(1, 6, newString)) writeEEPROM = true; 302 | } else if (cmdString == String("CAN1FILTER7")) { 303 | if (handleFilterSet(1, 7, newString)) writeEEPROM = true; 304 | } else if (cmdString == String("CAN0SEND")) { 305 | handleCANSend(CAN0, newString); 306 | } else if (cmdString == String("CAN1SEND")) { 307 | handleCANSend(CAN1, newString); 308 | } else if (cmdString == String("MARK")) { //just ascii based for now 309 | if (!settings.useBinarySerialComm) Logger::console("Mark: %s", newString); 310 | } else if (cmdString == String("BINSERIAL")) { 311 | if (newValue < 0) newValue = 0; 312 | if (newValue > 1) newValue = 1; 313 | Logger::console("Setting Serial Binary Comm to %i", newValue); 314 | settings.useBinarySerialComm = newValue; 315 | writeEEPROM = true; 316 | } else if (cmdString == String("BTMODE")) { 317 | if (newValue < 0) newValue = 0; 318 | if (newValue > 1) newValue = 1; 319 | Logger::console("Setting Bluetooth Mode to %i", newValue); 320 | settings.enableBT = newValue; 321 | writeEEPROM = true; 322 | } else if (cmdString == String("LAWICEL")) { 323 | if (newValue < 0) newValue = 0; 324 | if (newValue > 1) newValue = 1; 325 | Logger::console("Setting LAWICEL Mode to %i", newValue); 326 | settings.enableLawicel = newValue; 327 | writeEEPROM = true; 328 | } else if (cmdString == String("WIFIMODE")) { 329 | if (newValue < 0) newValue = 0; 330 | if (newValue > 2) newValue = 2; 331 | if (newValue == 0) Logger::console("Setting Wifi Mode to OFF"); 332 | if (newValue == 1) Logger::console("Setting Wifi Mode to Connect to AP"); 333 | if (newValue == 2) Logger::console("Setting Wifi Mode to Create AP"); 334 | settings.wifiMode = newValue; 335 | writeEEPROM = true; 336 | } else if (cmdString == String("BTNAME")) { 337 | Logger::console("Setting Bluetooth Name to %s", newString); 338 | strcpy((char *)settings.btName, newString); 339 | writeEEPROM = true; 340 | } else if (cmdString == String("SSID")) { 341 | Logger::console("Setting SSID to %s", newString); 342 | strcpy((char *)settings.SSID, newString); 343 | writeEEPROM = true; 344 | } else if (cmdString == String("WPA2KEY")) { 345 | Logger::console("Setting WPA2 Key to %s", newString); 346 | strcpy((char *)settings.WPA2Key, newString); 347 | writeEEPROM = true; 348 | } else if (cmdString == String("SYSTYPE")) { 349 | if (newValue < 0) newValue = 0; 350 | if (newValue > 1) newValue = 1; 351 | if (newValue == 0) Logger::console("Setting board type to Macchina A0"); 352 | if (newValue == 1) Logger::console("Setting board type to EVTV ESP32"); 353 | settings.systemType = newValue; 354 | writeEEPROM = true; 355 | } else if (cmdString == String("LOGLEVEL")) { 356 | switch (newValue) { 357 | case 0: 358 | Logger::setLoglevel(Logger::Debug); 359 | settings.logLevel = 0; 360 | Logger::console("setting loglevel to 'debug'"); 361 | writeEEPROM = true; 362 | break; 363 | case 1: 364 | Logger::setLoglevel(Logger::Info); 365 | settings.logLevel = 1; 366 | Logger::console("setting loglevel to 'info'"); 367 | writeEEPROM = true; 368 | break; 369 | case 2: 370 | Logger::console("setting loglevel to 'warning'"); 371 | settings.logLevel = 2; 372 | Logger::setLoglevel(Logger::Warn); 373 | writeEEPROM = true; 374 | break; 375 | case 3: 376 | Logger::console("setting loglevel to 'error'"); 377 | settings.logLevel = 3; 378 | Logger::setLoglevel(Logger::Error); 379 | writeEEPROM = true; 380 | break; 381 | case 4: 382 | Logger::console("setting loglevel to 'off'"); 383 | settings.logLevel = 4; 384 | Logger::setLoglevel(Logger::Off); 385 | writeEEPROM = true; 386 | break; 387 | } 388 | 389 | } else { 390 | Logger::console("Unknown command"); 391 | } 392 | if (writeEEPROM) { 393 | nvPrefs.begin(PREF_NAME, false); 394 | nvPrefs.putUInt("can0speed", settings.CAN0Speed); 395 | nvPrefs.putBool("can0_en", settings.CAN0_Enabled); 396 | nvPrefs.putBool("can0-listenonly", settings.CAN0ListenOnly); 397 | nvPrefs.putUInt("can1speed", settings.CAN1Speed); 398 | nvPrefs.putBool("can1_en", settings.CAN1_Enabled); 399 | nvPrefs.putBool("can1-listenonly", settings.CAN1ListenOnly); 400 | nvPrefs.putBool("binarycomm", settings.useBinarySerialComm); 401 | nvPrefs.putBool("enable-bt", settings.enableBT); 402 | nvPrefs.putBool("enableLawicel", settings.enableLawicel); 403 | nvPrefs.putUChar("loglevel", settings.logLevel); 404 | nvPrefs.putUChar("systype", settings.systemType); 405 | nvPrefs.putUChar("wifiMode", settings.wifiMode); 406 | nvPrefs.putString("SSID", settings.SSID); 407 | nvPrefs.putString("wpa2Key", settings.WPA2Key); 408 | nvPrefs.putString("btname", settings.btName); 409 | nvPrefs.end(); 410 | } 411 | } 412 | 413 | //CAN0FILTER%i=%%i,%%i,%%i,%%i (ID, Mask, Extended, Enabled)", i); 414 | bool SerialConsole::handleFilterSet(uint8_t bus, uint8_t filter, char *values) 415 | { 416 | if (filter < 0 || filter > 7) return false; 417 | if (bus < 0 || bus > 1) return false; 418 | 419 | //there should be four tokens 420 | char *idTok = strtok(values, ","); 421 | char *maskTok = strtok(NULL, ","); 422 | char *extTok = strtok(NULL, ","); 423 | char *enTok = strtok(NULL, ","); 424 | 425 | if (!idTok) return false; //if any of them were null then something was wrong. Abort. 426 | if (!maskTok) return false; 427 | if (!extTok) return false; 428 | if (!enTok) return false; 429 | 430 | int idVal = strtol(idTok, NULL, 0); 431 | int maskVal = strtol(maskTok, NULL, 0); 432 | int extVal = strtol(extTok, NULL, 0); 433 | int enVal = strtol(enTok, NULL, 0); 434 | 435 | Logger::console("Setting CAN%iFILTER%i to ID 0x%x Mask 0x%x Extended %i Enabled %i", bus, filter, idVal, maskVal, extVal, enVal); 436 | 437 | if (bus == 0) { 438 | //settings.CAN0Filters[filter].id = idVal; 439 | //settings.CAN0Filters[filter].mask = maskVal; 440 | //settings.CAN0Filters[filter].extended = extVal; 441 | //settings.CAN0Filters[filter].enabled = enVal; 442 | //CAN0.setRXFilter(filter, idVal, maskVal, extVal); 443 | } else if (bus == 1) { 444 | //settings.CAN1Filters[filter].id = idVal; 445 | //settings.CAN1Filters[filter].mask = maskVal; 446 | //settings.CAN1Filters[filter].extended = extVal; 447 | //settings.CAN1Filters[filter].enabled = enVal; 448 | //CAN1.setRXFilter(filter, idVal, maskVal, extVal); 449 | } 450 | 451 | return true; 452 | } 453 | 454 | bool SerialConsole::handleCANSend(CAN_COMMON &port, char *inputString) 455 | { 456 | char *idTok = strtok(inputString, ","); 457 | char *lenTok = strtok(NULL, ","); 458 | char *dataTok; 459 | CAN_FRAME frame; 460 | 461 | if (!idTok) return false; 462 | if (!lenTok) return false; 463 | 464 | int idVal = strtol(idTok, NULL, 0); 465 | int lenVal = strtol(lenTok, NULL, 0); 466 | 467 | for (int i = 0; i < lenVal; i++) { 468 | dataTok = strtok(NULL, ","); 469 | if (!dataTok) return false; 470 | frame.data.byte[i] = strtol(dataTok, NULL, 0); 471 | } 472 | 473 | //things seem good so try to send the frame. 474 | frame.id = idVal; 475 | if (idVal >= 0x7FF) frame.extended = true; 476 | else frame.extended = false; 477 | frame.rtr = 0; 478 | frame.length = lenVal; 479 | port.sendFrame(frame); 480 | 481 | Logger::console("Sending frame with id: 0x%x len: %i", frame.id, frame.length); 482 | SysSettings.txToggle = !SysSettings.txToggle; 483 | setLED(SysSettings.LED_CANTX, SysSettings.txToggle); 484 | return true; 485 | } 486 | 487 | void SerialConsole::printBusName(int bus) { 488 | switch (bus) { 489 | case 0: 490 | Serial.print("CAN0"); 491 | break; 492 | case 1: 493 | Serial.print("CAN1"); 494 | break; 495 | default: 496 | Serial.print("UNKNOWN"); 497 | break; 498 | } 499 | } 500 | -------------------------------------------------------------------------------- /SerialConsole.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SerialConsole.h 3 | * 4 | Copyright (c) 2013 Collin Kidder, Michael Neuweiler, Charles Galpin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | #ifndef SERIALCONSOLE_H_ 28 | #define SERIALCONSOLE_H_ 29 | 30 | #include "config.h" 31 | #include "sys_io.h" 32 | #include "A0RET.h" 33 | #include "esp32_can.h" 34 | 35 | class SerialConsole { 36 | public: 37 | SerialConsole(); 38 | void printMenu(); 39 | void rcvCharacter(uint8_t chr); 40 | void printBusName(int bus); 41 | 42 | protected: 43 | enum CONSOLE_STATE { 44 | STATE_ROOT_MENU 45 | }; 46 | 47 | private: 48 | char cmdBuffer[80]; 49 | int ptrBuffer; 50 | int state; 51 | 52 | void init(); 53 | void handleConsoleCmd(); 54 | void handleShortCmd(); 55 | void handleConfigCmd(); 56 | bool handleFilterSet(uint8_t bus, uint8_t filter, char *values); 57 | bool handleCANSend(CAN_COMMON &port, char *inputString); 58 | bool handleSWCANSend(char *inputString); 59 | }; 60 | 61 | #endif /* SERIALCONSOLE_H_ */ 62 | 63 | -------------------------------------------------------------------------------- /can_manager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "can_manager.h" 3 | #include "esp32_can.h" 4 | #include "config.h" 5 | #include "SerialConsole.h" 6 | #include "gvret_comm.h" 7 | #include "lawicel.h" 8 | #include "ELM327_Emulator.h" 9 | 10 | CANManager::CANManager() 11 | { 12 | 13 | } 14 | 15 | void CANManager::setup() 16 | { 17 | if (settings.CAN0_Enabled) 18 | { 19 | if (settings.systemType == 0) CAN0.setCANPins(GPIO_CAN_RX, GPIO_CAN_TX); 20 | CAN0.enable(); 21 | CAN0.begin(settings.CAN0Speed, 255); 22 | Serial.print("Enabled CAN0 with speed "); 23 | Serial.println(settings.CAN0Speed); 24 | if (settings.CAN0ListenOnly) 25 | { 26 | CAN0.setListenOnlyMode(true); 27 | } 28 | else 29 | { 30 | CAN0.setListenOnlyMode(false); 31 | } 32 | CAN0.watchFor(); 33 | } 34 | else 35 | { 36 | CAN0.disable(); 37 | } 38 | 39 | if (settings.CAN1_Enabled && settings.systemType != 0) { 40 | CAN1.enable(); 41 | CAN1.begin(settings.CAN1Speed, 255); 42 | Serial.print("Enabled CAN1 with speed "); 43 | Serial.println(settings.CAN1Speed); 44 | if (settings.CAN1ListenOnly) 45 | { 46 | CAN1.setListenOnlyMode(true); 47 | } 48 | else 49 | { 50 | CAN1.setListenOnlyMode(false); 51 | } 52 | CAN1.watchFor(); 53 | } 54 | else 55 | { 56 | CAN1.disable(); 57 | } 58 | 59 | busLoad[0].bitsPerQuarter = settings.CAN0Speed / 4; 60 | busLoad[1].bitsPerQuarter = settings.CAN1Speed / 4; 61 | 62 | for (int j = 0; j < NUM_BUSES; j++) 63 | { 64 | busLoad[j].bitsSoFar = 0; 65 | busLoad[j].busloadPercentage = 0; 66 | if (busLoad[j].bitsPerQuarter == 0) busLoad[j].bitsPerQuarter = 125000; 67 | } 68 | 69 | busLoadTimer = millis(); 70 | } 71 | 72 | void CANManager::addBits(int offset, CAN_FRAME &frame) 73 | { 74 | if (offset < 0) return; 75 | if (offset >= NUM_BUSES) return; 76 | busLoad[offset].bitsSoFar += 41 + (frame.length * 9); 77 | if (frame.extended) busLoad[offset].bitsSoFar += 18; 78 | } 79 | 80 | void CANManager::sendFrame(CAN_COMMON *bus, CAN_FRAME &frame) 81 | { 82 | int whichBus; 83 | if (bus == &CAN0) whichBus = 0; 84 | if (bus == &CAN1) whichBus = 1; 85 | bus->sendFrame(frame); 86 | addBits(whichBus, frame); 87 | } 88 | 89 | void CANManager::displayFrame(CAN_FRAME &frame, int whichBus) 90 | { 91 | if (settings.enableLawicel && SysSettings.lawicelMode) 92 | { 93 | lawicel.sendFrameToBuffer(frame, whichBus); 94 | } 95 | else 96 | { 97 | if (SysSettings.isWifiActive) wifiGVRET.sendFrameToBuffer(frame, whichBus); 98 | else serialGVRET.sendFrameToBuffer(frame, whichBus); 99 | } 100 | } 101 | 102 | void CANManager::loop() 103 | { 104 | CAN_FRAME incoming; 105 | size_t wifiLength = wifiGVRET.numAvailableBytes(); 106 | size_t serialLength = serialGVRET.numAvailableBytes(); 107 | size_t maxLength = (wifiLength>serialLength)?wifiLength:serialLength; 108 | 109 | if (millis() > (busLoadTimer + 250)) { 110 | busLoadTimer = millis(); 111 | busLoad[0].busloadPercentage = ((busLoad[0].busloadPercentage * 3) + (((busLoad[0].bitsSoFar * 1000) / busLoad[0].bitsPerQuarter) / 10)) / 4; 112 | //Force busload percentage to be at least 1% if any traffic exists at all. This forces the LED to light up for any traffic. 113 | if (busLoad[0].busloadPercentage == 0 && busLoad[0].bitsSoFar > 0) busLoad[0].busloadPercentage = 1; 114 | busLoad[0].bitsPerQuarter = settings.CAN0Speed / 4; 115 | busLoad[0].bitsSoFar = 0; 116 | if(busLoad[0].busloadPercentage > busLoad[1].busloadPercentage){ 117 | //updateBusloadLED(busLoad[0].busloadPercentage); 118 | } else{ 119 | //updateBusloadLED(busLoad[1].busloadPercentage); 120 | } 121 | } 122 | 123 | while (CAN0.available() > 0 && (maxLength < (WIFI_BUFF_SIZE - 80))) 124 | { 125 | CAN0.read(incoming); 126 | addBits(0, incoming); 127 | toggleRXLED(); 128 | displayFrame(incoming, 0); 129 | if (incoming.id > 0x7DF && incoming.id < 0x7F0) elmEmulator.processCANReply(incoming); 130 | wifiLength = wifiGVRET.numAvailableBytes(); 131 | serialLength = serialGVRET.numAvailableBytes(); 132 | maxLength = (wifiLength > serialLength) ? wifiLength:serialLength; 133 | } 134 | 135 | if (settings.systemType != 0) 136 | { 137 | while (CAN1.available() > 0 && (maxLength < (WIFI_BUFF_SIZE - 80))) 138 | { 139 | CAN1.read(incoming); 140 | addBits(1, incoming); 141 | toggleRXLED(); 142 | displayFrame(incoming, 1); 143 | if (incoming.id > 0x7DF && incoming.id < 0x7F0) elmEmulator.processCANReply(incoming); 144 | wifiLength = wifiGVRET.numAvailableBytes(); 145 | serialLength = serialGVRET.numAvailableBytes(); 146 | maxLength = (wifiLength > serialLength) ? wifiLength:serialLength; 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /can_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "config.h" 3 | 4 | typedef struct { 5 | uint32_t bitsPerQuarter; 6 | uint32_t bitsSoFar; 7 | uint8_t busloadPercentage; 8 | } BUSLOAD; 9 | 10 | class CAN_COMMON; 11 | class CAN_FRAME; 12 | 13 | class CANManager 14 | { 15 | public: 16 | CANManager(); 17 | void addBits(int offset, CAN_FRAME &frame); 18 | void sendFrame(CAN_COMMON *bus, CAN_FRAME &frame); 19 | void displayFrame(CAN_FRAME &frame, int whichBus); 20 | void loop(); 21 | void setup(); 22 | 23 | private: 24 | BUSLOAD busLoad[NUM_BUSES]; 25 | uint32_t busLoadTimer; 26 | }; 27 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * config.h 3 | * 4 | * allows the user to configure static parameters. 5 | * 6 | * Note: Make sure with all pin defintions of your hardware that each pin number is 7 | * only defined once. 8 | 9 | Copyright (c) 2013-2018 Collin Kidder, Michael Neuweiler, Charles Galpin 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining 12 | a copy of this software and associated documentation files (the 13 | "Software"), to deal in the Software without restriction, including 14 | without limitation the rights to use, copy, modify, merge, publish, 15 | distribute, sublicense, and/or sell copies of the Software, and to 16 | permit persons to whom the Software is furnished to do so, subject to 17 | the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included 20 | in all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 25 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 26 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 27 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 28 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | * Author: Michael Neuweiler 30 | */ 31 | 32 | #ifndef CONFIG_H_ 33 | #define CONFIG_H_ 34 | 35 | #include 36 | #include 37 | 38 | // Set CAN TX/RX pins 39 | #define GPIO_CAN_TX GPIO_NUM_5 40 | #define GPIO_CAN_RX GPIO_NUM_4 41 | 42 | //size to use for buffering writes to USB. On the ESP32 we're actually talking TTL serial to a TTL<->USB chip 43 | #define SER_BUFF_SIZE 1024 44 | 45 | //Buffer for CAN frames when sending over wifi. This allows us to build up a multi-frame packet that goes 46 | //over the air all at once. This is much more efficient than trying to send a new TCP/IP packet for each and every 47 | //frame. It delays frames from getting to the other side a bit but that's life. 48 | //Probably don't set this over 2048 as the default packet size for wifi is 2312 including all overhead. 49 | #define WIFI_BUFF_SIZE 2048 50 | 51 | //Number of microseconds between hard flushes of the serial buffer (if not in wifi mode) or the wifi buffer (if in wifi mode) 52 | //This keeps the latency more consistent. Otherwise the buffer could partially fill and never send. 53 | #define SER_BUFF_FLUSH_INTERVAL 20000 54 | 55 | #define CFG_BUILD_NUM 620 56 | #define CFG_VERSION "A0RET Alpha Dec 9, 2024" 57 | #define PREF_NAME "A0RET" 58 | 59 | #define MARK_LIMIT 6 //# of our analog input pins to use for marking. Defaults to all of them. Send voltage to pin to trigger it 60 | 61 | #define NUM_ANALOG NUM_ANALOG_INPUTS // we get the number of analogue inputs from variant.h 62 | #define NUM_DIGITAL 6 // Not currently using digital pins on the ESP32 63 | #define NUM_OUTPUT 6 // Ditto 64 | 65 | #define NUM_BUSES 2 //max # of buses supported by any of the supported boards 66 | 67 | //It's not even used on this hardware currently. But, slows down the blinks to make them more visible 68 | #define BLINK_SLOWNESS 100 69 | 70 | //How many devices to allow to connect to our WiFi telnet port? 71 | #define MAX_CLIENTS 1 72 | 73 | struct FILTER { //should be 10 bytes 74 | uint32_t id; 75 | uint32_t mask; 76 | boolean extended; 77 | boolean enabled; 78 | }; 79 | 80 | struct EEPROMSettings { 81 | uint32_t CAN0Speed; 82 | boolean CAN0_Enabled; 83 | boolean CAN0ListenOnly; //if true we don't allow any messing with the bus but rather just passively monitor. 84 | 85 | uint32_t CAN1Speed; 86 | boolean CAN1_Enabled; 87 | boolean CAN1ListenOnly; 88 | 89 | boolean useBinarySerialComm; //use a binary protocol on the serial link or human readable format? 90 | 91 | uint8_t logLevel; //Level of logging to output on serial line 92 | uint8_t systemType; //0 = A0RET, 1 = EVTV ESP32 Board, maybe others in the future 93 | 94 | boolean enableBT; //are we enabling bluetooth too? 95 | char btName[32]; 96 | 97 | boolean enableLawicel; 98 | 99 | //if we're using WiFi then output to serial is disabled (it's far too slow to keep up) 100 | uint8_t wifiMode; //0 = don't use wifi, 1 = connect to an AP, 2 = Create an AP 101 | char SSID[32]; //null terminated string for the SSID 102 | char WPA2Key[64]; //Null terminated string for the key. Can be a passphase or the actual key 103 | }; 104 | 105 | struct SystemSettings { 106 | uint8_t LED_CANTX; 107 | uint8_t LED_CANRX; 108 | uint8_t LED_LOGGING; 109 | boolean txToggle; //LED toggle values 110 | boolean rxToggle; 111 | boolean logToggle; 112 | boolean lawicelMode; 113 | boolean lawicellExtendedMode; 114 | boolean lawicelAutoPoll; 115 | boolean lawicelTimestamping; 116 | int lawicelPollCounter; 117 | boolean lawicelBusReception[NUM_BUSES]; //does user want to see messages from this bus? 118 | int8_t numBuses; //number of buses this hardware currently supports. 119 | WiFiClient clientNodes[MAX_CLIENTS]; 120 | boolean isWifiConnected; 121 | boolean isWifiActive; 122 | }; 123 | 124 | class GVRET_Comm_Handler; 125 | class SerialConsole; 126 | class CANManager; 127 | class LAWICELHandler; 128 | class ELM327Emu; 129 | 130 | extern EEPROMSettings settings; 131 | extern SystemSettings SysSettings; 132 | extern Preferences nvPrefs; 133 | extern GVRET_Comm_Handler serialGVRET; 134 | extern GVRET_Comm_Handler wifiGVRET; 135 | extern SerialConsole console; 136 | extern CANManager canManager; 137 | extern LAWICELHandler lawicel; 138 | extern ELM327Emu elmEmulator; 139 | 140 | #endif /* CONFIG_H_ */ 141 | -------------------------------------------------------------------------------- /gvret_comm.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Implements handling of the GVRET comm protocol, both sending and receiving 3 | */ 4 | 5 | #include "gvret_comm.h" 6 | #include "SerialConsole.h" 7 | #include "config.h" 8 | #include "can_manager.h" 9 | 10 | GVRET_Comm_Handler::GVRET_Comm_Handler() 11 | { 12 | step = 0; 13 | state = IDLE; 14 | transmitBufferLength = 0; 15 | } 16 | 17 | void GVRET_Comm_Handler::processIncomingByte(uint8_t in_byte) 18 | { 19 | uint32_t busSpeed = 0; 20 | uint32_t now = micros(); 21 | 22 | uint8_t temp8; 23 | uint16_t temp16; 24 | 25 | switch (state) { 26 | case IDLE: 27 | if(in_byte == 0xF1) 28 | { 29 | state = GET_COMMAND; 30 | } 31 | else if(in_byte == 0xE7) 32 | { 33 | settings.useBinarySerialComm = true; 34 | SysSettings.lawicelMode = false; 35 | //setPromiscuousMode(); //going into binary comm will set promisc. mode too. 36 | } 37 | else 38 | { 39 | console.rcvCharacter((uint8_t) in_byte); 40 | } 41 | break; 42 | case GET_COMMAND: 43 | switch(in_byte) 44 | { 45 | case PROTO_BUILD_CAN_FRAME: 46 | state = BUILD_CAN_FRAME; 47 | buff[0] = 0xF1; 48 | step = 0; 49 | break; 50 | case PROTO_TIME_SYNC: 51 | state = TIME_SYNC; 52 | step = 0; 53 | transmitBuffer[transmitBufferLength++] = 0xF1; 54 | transmitBuffer[transmitBufferLength++] = 1; //time sync 55 | transmitBuffer[transmitBufferLength++] = (uint8_t) (now & 0xFF); 56 | transmitBuffer[transmitBufferLength++] = (uint8_t) (now >> 8); 57 | transmitBuffer[transmitBufferLength++] = (uint8_t) (now >> 16); 58 | transmitBuffer[transmitBufferLength++] = (uint8_t) (now >> 24); 59 | break; 60 | case PROTO_DIG_INPUTS: 61 | //immediately return the data for digital inputs 62 | temp8 = 0; //getDigital(0) + (getDigital(1) << 1) + (getDigital(2) << 2) + (getDigital(3) << 3) + (getDigital(4) << 4) + (getDigital(5) << 5); 63 | transmitBuffer[transmitBufferLength++] = 0xF1; 64 | transmitBuffer[transmitBufferLength++] = 2; //digital inputs 65 | transmitBuffer[transmitBufferLength++] = temp8; 66 | temp8 = checksumCalc(buff, 2); 67 | transmitBuffer[transmitBufferLength++] = temp8; 68 | state = IDLE; 69 | break; 70 | case PROTO_ANA_INPUTS: 71 | //immediately return data on analog inputs 72 | temp16 = 0;// getAnalog(0); // Analogue input 1 73 | transmitBuffer[transmitBufferLength++] = 0xF1; 74 | transmitBuffer[transmitBufferLength++] = 3; 75 | transmitBuffer[transmitBufferLength++] = temp16 & 0xFF; 76 | transmitBuffer[transmitBufferLength++] = uint8_t(temp16 >> 8); 77 | temp16 = 0;//getAnalog(1); // Analogue input 2 78 | transmitBuffer[transmitBufferLength++] = temp16 & 0xFF; 79 | transmitBuffer[transmitBufferLength++] = uint8_t(temp16 >> 8); 80 | temp16 = 0;//getAnalog(2); // Analogue input 3 81 | transmitBuffer[transmitBufferLength++] = temp16 & 0xFF; 82 | transmitBuffer[transmitBufferLength++] = uint8_t(temp16 >> 8); 83 | temp16 = 0;//getAnalog(3); // Analogue input 4 84 | transmitBuffer[transmitBufferLength++] = temp16 & 0xFF; 85 | transmitBuffer[transmitBufferLength++] = uint8_t(temp16 >> 8); 86 | temp16 = 0;//getAnalog(4); // Analogue input 5 87 | transmitBuffer[transmitBufferLength++] = temp16 & 0xFF; 88 | transmitBuffer[transmitBufferLength++] = uint8_t(temp16 >> 8); 89 | temp16 = 0;//getAnalog(5); // Analogue input 6 90 | transmitBuffer[transmitBufferLength++] = temp16 & 0xFF; 91 | transmitBuffer[transmitBufferLength++] = uint8_t(temp16 >> 8); 92 | temp16 = 0;//getAnalog(6); // Vehicle Volts 93 | transmitBuffer[transmitBufferLength++] = temp16 & 0xFF; 94 | transmitBuffer[transmitBufferLength++] = uint8_t(temp16 >> 8); 95 | temp8 = checksumCalc(buff, 9); 96 | transmitBuffer[transmitBufferLength++] = temp8; 97 | state = IDLE; 98 | break; 99 | case PROTO_SET_DIG_OUT: 100 | state = SET_DIG_OUTPUTS; 101 | buff[0] = 0xF1; 102 | break; 103 | case PROTO_SETUP_CANBUS: 104 | state = SETUP_CANBUS; 105 | step = 0; 106 | buff[0] = 0xF1; 107 | break; 108 | case PROTO_GET_CANBUS_PARAMS: 109 | //immediately return data on canbus params 110 | transmitBuffer[transmitBufferLength++] = 0xF1; 111 | transmitBuffer[transmitBufferLength++] = 6; 112 | transmitBuffer[transmitBufferLength++] = settings.CAN0_Enabled + ((unsigned char) settings.CAN0ListenOnly << 4); 113 | transmitBuffer[transmitBufferLength++] = settings.CAN0Speed; 114 | transmitBuffer[transmitBufferLength++] = settings.CAN0Speed >> 8; 115 | transmitBuffer[transmitBufferLength++] = settings.CAN0Speed >> 16; 116 | transmitBuffer[transmitBufferLength++] = settings.CAN0Speed >> 24; 117 | transmitBuffer[transmitBufferLength++] = 0; 118 | transmitBuffer[transmitBufferLength++] = settings.CAN1Speed; 119 | transmitBuffer[transmitBufferLength++] = settings.CAN1Speed >> 8; 120 | transmitBuffer[transmitBufferLength++] = settings.CAN1Speed >> 16; 121 | transmitBuffer[transmitBufferLength++] = settings.CAN1Speed >> 24; 122 | state = IDLE; 123 | break; 124 | case PROTO_GET_DEV_INFO: 125 | //immediately return device information 126 | transmitBuffer[transmitBufferLength++] = 0xF1; 127 | transmitBuffer[transmitBufferLength++] = 7; 128 | transmitBuffer[transmitBufferLength++] = CFG_BUILD_NUM & 0xFF; 129 | transmitBuffer[transmitBufferLength++] = (CFG_BUILD_NUM >> 8); 130 | transmitBuffer[transmitBufferLength++] = 0x20; 131 | transmitBuffer[transmitBufferLength++] = 0; 132 | transmitBuffer[transmitBufferLength++] = 0; 133 | transmitBuffer[transmitBufferLength++] = 0; //was single wire mode. Should be rethought for this board. 134 | state = IDLE; 135 | break; 136 | case PROTO_SET_SW_MODE: 137 | buff[0] = 0xF1; 138 | state = SET_SINGLEWIRE_MODE; 139 | step = 0; 140 | break; 141 | case PROTO_KEEPALIVE: 142 | transmitBuffer[transmitBufferLength++] = 0xF1; 143 | transmitBuffer[transmitBufferLength++] = 0x09; 144 | transmitBuffer[transmitBufferLength++] = 0xDE; 145 | transmitBuffer[transmitBufferLength++] = 0xAD; 146 | state = IDLE; 147 | break; 148 | case PROTO_SET_SYSTYPE: 149 | buff[0] = 0xF1; 150 | state = SET_SYSTYPE; 151 | step = 0; 152 | break; 153 | case PROTO_ECHO_CAN_FRAME: 154 | state = ECHO_CAN_FRAME; 155 | buff[0] = 0xF1; 156 | step = 0; 157 | break; 158 | case PROTO_GET_NUMBUSES: 159 | transmitBuffer[transmitBufferLength++] = 0xF1; 160 | transmitBuffer[transmitBufferLength++] = 12; 161 | transmitBuffer[transmitBufferLength++] = SysSettings.numBuses; 162 | state = IDLE; 163 | break; 164 | case PROTO_GET_EXT_BUSES: 165 | transmitBuffer[transmitBufferLength++] = 0xF1; 166 | transmitBuffer[transmitBufferLength++] = 13; 167 | for (int u = 2; u < 17; u++) transmitBuffer[transmitBufferLength++] = 0; 168 | step = 0; 169 | state = IDLE; 170 | break; 171 | case PROTO_SET_EXT_BUSES: 172 | state = SETUP_EXT_BUSES; 173 | step = 0; 174 | buff[0] = 0xF1; 175 | break; 176 | } 177 | break; 178 | case BUILD_CAN_FRAME: 179 | buff[1 + step] = in_byte; 180 | switch(step) 181 | { 182 | case 0: 183 | build_out_frame.id = in_byte; 184 | break; 185 | case 1: 186 | build_out_frame.id |= in_byte << 8; 187 | break; 188 | case 2: 189 | build_out_frame.id |= in_byte << 16; 190 | break; 191 | case 3: 192 | build_out_frame.id |= in_byte << 24; 193 | if(build_out_frame.id & 1 << 31) 194 | { 195 | build_out_frame.id &= 0x7FFFFFFF; 196 | build_out_frame.extended = true; 197 | } else build_out_frame.extended = false; 198 | break; 199 | case 4: 200 | out_bus = in_byte & 3; 201 | break; 202 | case 5: 203 | build_out_frame.length = in_byte & 0xF; 204 | if(build_out_frame.length > 8) 205 | { 206 | build_out_frame.length = 8; 207 | } 208 | break; 209 | default: 210 | if(step < build_out_frame.length + 6) 211 | { 212 | build_out_frame.data.uint8[step - 6] = in_byte; 213 | } 214 | else 215 | { 216 | state = IDLE; 217 | //this would be the checksum byte. Compute and compare. 218 | //temp8 = checksumCalc(buff, step); 219 | build_out_frame.rtr = 0; 220 | if (out_bus == 0) canManager.sendFrame(&CAN0, build_out_frame); 221 | if (out_bus == 1) canManager.sendFrame(&CAN1, build_out_frame); 222 | } 223 | break; 224 | } 225 | step++; 226 | break; 227 | case TIME_SYNC: 228 | state = IDLE; 229 | break; 230 | case GET_DIG_INPUTS: 231 | // nothing to do 232 | break; 233 | case GET_ANALOG_INPUTS: 234 | // nothing to do 235 | break; 236 | case SET_DIG_OUTPUTS: //todo: validate the XOR byte 237 | buff[1] = in_byte; 238 | //temp8 = checksumCalc(buff, 2); 239 | for(int c = 0; c < 8; c++){ 240 | if(in_byte & (1 << c)) setOutput(c, true); 241 | else setOutput(c, false); 242 | } 243 | state = IDLE; 244 | break; 245 | case SETUP_CANBUS: //todo: validate checksum 246 | switch(step) 247 | { 248 | case 0: 249 | build_int = in_byte; 250 | break; 251 | case 1: 252 | build_int |= in_byte << 8; 253 | break; 254 | case 2: 255 | build_int |= in_byte << 16; 256 | break; 257 | case 3: 258 | build_int |= in_byte << 24; 259 | busSpeed = build_int & 0xFFFFF; 260 | if(busSpeed > 1000000) busSpeed = 1000000; 261 | 262 | if(build_int > 0) 263 | { 264 | if(build_int & 0x80000000ul) //signals that enabled and listen only status are also being passed 265 | { 266 | if(build_int & 0x40000000ul) 267 | { 268 | settings.CAN0_Enabled = true; 269 | } else 270 | { 271 | settings.CAN0_Enabled = false; 272 | } 273 | if(build_int & 0x20000000ul) 274 | { 275 | settings.CAN0ListenOnly = true; 276 | } else 277 | { 278 | settings.CAN0ListenOnly = false; 279 | } 280 | } else 281 | { 282 | //if not using extended status mode then just default to enabling - this was old behavior 283 | settings.CAN0_Enabled = true; 284 | } 285 | //CAN0.set_baudrate(build_int); 286 | settings.CAN0Speed = busSpeed; 287 | } else { //disable first canbus 288 | settings.CAN0_Enabled = false; 289 | } 290 | 291 | if (settings.CAN0_Enabled) 292 | { 293 | CAN0.begin(settings.CAN0Speed, 255); 294 | if (settings.CAN0ListenOnly) CAN0.setListenOnlyMode(true); 295 | else CAN0.setListenOnlyMode(false); 296 | CAN0.watchFor(); 297 | } 298 | else CAN0.disable(); 299 | break; 300 | case 4: 301 | build_int = in_byte; 302 | break; 303 | case 5: 304 | build_int |= in_byte << 8; 305 | break; 306 | case 6: 307 | build_int |= in_byte << 16; 308 | break; 309 | case 7: 310 | build_int |= in_byte << 24; 311 | busSpeed = build_int & 0xFFFFF; 312 | if(busSpeed > 1000000) busSpeed = 1000000; 313 | 314 | if(build_int > 0 && SysSettings.numBuses > 1) 315 | { 316 | if(build_int & 0x80000000ul) //signals that enabled and listen only status are also being passed 317 | { 318 | if(build_int & 0x40000000ul) 319 | { 320 | settings.CAN1_Enabled = true; 321 | } else 322 | { 323 | settings.CAN1_Enabled = false; 324 | } 325 | if(build_int & 0x20000000ul) 326 | { 327 | settings.CAN1ListenOnly = true; 328 | } else 329 | { 330 | settings.CAN1ListenOnly = false; 331 | } 332 | } else 333 | { 334 | //if not using extended status mode then just default to enabling - this was old behavior 335 | settings.CAN1_Enabled = true; 336 | } 337 | //CAN1.set_baudrate(build_int); 338 | settings.CAN1Speed = busSpeed; 339 | } else { //disable first canbus 340 | settings.CAN1_Enabled = false; 341 | } 342 | 343 | if (settings.CAN1_Enabled) 344 | { 345 | CAN1.begin(settings.CAN0Speed, 255); 346 | if (settings.CAN1ListenOnly) CAN1.setListenOnlyMode(true); 347 | else CAN1.setListenOnlyMode(false); 348 | CAN1.watchFor(); 349 | } 350 | else CAN1.disable(); 351 | 352 | state = IDLE; 353 | //now, write out the new canbus settings to EEPROM 354 | //EEPROM.writeBytes(0, &settings, sizeof(settings)); 355 | //EEPROM.commit(); 356 | //setPromiscuousMode(); 357 | break; 358 | } 359 | step++; 360 | break; 361 | case GET_CANBUS_PARAMS: 362 | // nothing to do 363 | break; 364 | case GET_DEVICE_INFO: 365 | // nothing to do 366 | break; 367 | case SET_SINGLEWIRE_MODE: 368 | if(in_byte == 0x10){ 369 | } else { 370 | } 371 | //EEPROM.writeBytes(0, &settings, sizeof(settings)); 372 | //EEPROM.commit(); 373 | state = IDLE; 374 | break; 375 | case SET_SYSTYPE: 376 | settings.systemType = in_byte; 377 | //EEPROM.writeBytes(0, &settings, sizeof(settings)); 378 | //EEPROM.commit(); 379 | //loadSettings(); 380 | state = IDLE; 381 | break; 382 | case ECHO_CAN_FRAME: 383 | buff[1 + step] = in_byte; 384 | switch(step) 385 | { 386 | case 0: 387 | build_out_frame.id = in_byte; 388 | break; 389 | case 1: 390 | build_out_frame.id |= in_byte << 8; 391 | break; 392 | case 2: 393 | build_out_frame.id |= in_byte << 16; 394 | break; 395 | case 3: 396 | build_out_frame.id |= in_byte << 24; 397 | if(build_out_frame.id & 1 << 31) { 398 | build_out_frame.id &= 0x7FFFFFFF; 399 | build_out_frame.extended = true; 400 | } else build_out_frame.extended = false; 401 | break; 402 | case 4: 403 | out_bus = in_byte & 1; 404 | break; 405 | case 5: 406 | build_out_frame.length = in_byte & 0xF; 407 | if(build_out_frame.length > 8) build_out_frame.length = 8; 408 | break; 409 | default: 410 | if(step < build_out_frame.length + 6) 411 | { 412 | build_out_frame.data.bytes[step - 6] = in_byte; 413 | } 414 | else 415 | { 416 | state = IDLE; 417 | //this would be the checksum byte. Compute and compare. 418 | //temp8 = checksumCalc(buff, step); 419 | //if (temp8 == in_byte) 420 | //{ 421 | toggleRXLED(); 422 | //if(isConnected) { 423 | canManager.displayFrame(build_out_frame, 0); 424 | //} 425 | //} 426 | } 427 | break; 428 | } 429 | step++; 430 | break; 431 | case SETUP_EXT_BUSES: //setup enable/listenonly/speed for SWCAN, Enable/Speed for LIN1, LIN2 432 | switch(step) 433 | { 434 | case 0: 435 | build_int = in_byte; 436 | break; 437 | case 1: 438 | build_int |= in_byte << 8; 439 | break; 440 | case 2: 441 | build_int |= in_byte << 16; 442 | break; 443 | case 3: 444 | build_int |= in_byte << 24; 445 | break; 446 | case 4: 447 | build_int = in_byte; 448 | break; 449 | case 5: 450 | build_int |= in_byte << 8; 451 | break; 452 | case 6: 453 | build_int |= in_byte << 16; 454 | break; 455 | case 7: 456 | build_int |= in_byte << 24; 457 | break; 458 | case 8: 459 | build_int = in_byte; 460 | break; 461 | case 9: 462 | build_int |= in_byte << 8; 463 | break; 464 | case 10: 465 | build_int |= in_byte << 16; 466 | break; 467 | case 11: 468 | build_int |= in_byte << 24; 469 | state = IDLE; 470 | //now, write out the new canbus settings to EEPROM 471 | //EEPROM.writeBytes(0, &settings, sizeof(settings)); 472 | //EEPROM.commit(); 473 | //setPromiscuousMode(); 474 | break; 475 | } 476 | step++; 477 | break; 478 | } 479 | } 480 | 481 | void GVRET_Comm_Handler::sendFrameToBuffer(CAN_FRAME &frame, int whichBus) 482 | { 483 | uint8_t temp; 484 | size_t writtenBytes; 485 | if (settings.useBinarySerialComm) { 486 | if (frame.extended) frame.id |= 1 << 31; 487 | transmitBuffer[transmitBufferLength++] = 0xF1; 488 | transmitBuffer[transmitBufferLength++] = 0; //0 = canbus frame sending 489 | uint32_t now = micros(); 490 | transmitBuffer[transmitBufferLength++] = (uint8_t)(now & 0xFF); 491 | transmitBuffer[transmitBufferLength++] = (uint8_t)(now >> 8); 492 | transmitBuffer[transmitBufferLength++] = (uint8_t)(now >> 16); 493 | transmitBuffer[transmitBufferLength++] = (uint8_t)(now >> 24); 494 | transmitBuffer[transmitBufferLength++] = (uint8_t)(frame.id & 0xFF); 495 | transmitBuffer[transmitBufferLength++] = (uint8_t)(frame.id >> 8); 496 | transmitBuffer[transmitBufferLength++] = (uint8_t)(frame.id >> 16); 497 | transmitBuffer[transmitBufferLength++] = (uint8_t)(frame.id >> 24); 498 | transmitBuffer[transmitBufferLength++] = frame.length + (uint8_t)(whichBus << 4); 499 | for (int c = 0; c < frame.length; c++) { 500 | transmitBuffer[transmitBufferLength++] = frame.data.uint8[c]; 501 | } 502 | //temp = checksumCalc(buff, 11 + frame.length); 503 | temp = 0; 504 | transmitBuffer[transmitBufferLength++] = temp; 505 | //Serial.write(buff, 12 + frame.length); 506 | } else { 507 | writtenBytes = sprintf((char *)&transmitBuffer[transmitBufferLength], "%d - %x", micros(), frame.id); 508 | transmitBufferLength += writtenBytes; 509 | if (frame.extended) sprintf((char *)&transmitBuffer[transmitBufferLength], " X "); 510 | else sprintf((char *)&transmitBuffer[transmitBufferLength], " S "); 511 | transmitBufferLength += 3; 512 | writtenBytes = sprintf((char *)&transmitBuffer[transmitBufferLength], "%i %i", whichBus, frame.length); 513 | transmitBufferLength += writtenBytes; 514 | for (int c = 0; c < frame.length; c++) { 515 | writtenBytes = sprintf((char *)&transmitBuffer[transmitBufferLength], " %x", frame.data.uint8[c]); 516 | transmitBufferLength += writtenBytes; 517 | } 518 | sprintf((char *)&transmitBuffer[transmitBufferLength], "\r\n"); 519 | transmitBufferLength += 2; 520 | } 521 | } 522 | 523 | size_t GVRET_Comm_Handler::numAvailableBytes() 524 | { 525 | return transmitBufferLength; 526 | } 527 | 528 | void GVRET_Comm_Handler::clearBufferedBytes() 529 | { 530 | transmitBufferLength = 0; 531 | } 532 | 533 | uint8_t* GVRET_Comm_Handler::getBufferedBytes() 534 | { 535 | return transmitBuffer; 536 | } 537 | 538 | //Get the value of XOR'ing all the bytes together. This creates a reasonable checksum that can be used 539 | //to make sure nothing too stupid has happened on the comm. 540 | uint8_t GVRET_Comm_Handler::checksumCalc(uint8_t *buffer, int length) 541 | { 542 | uint8_t valu = 0; 543 | for (int c = 0; c < length; c++) { 544 | valu ^= buffer[c]; 545 | } 546 | return valu; 547 | } -------------------------------------------------------------------------------- /gvret_comm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "config.h" 4 | #include "esp32_can.h" 5 | 6 | enum STATE { 7 | IDLE, 8 | GET_COMMAND, 9 | BUILD_CAN_FRAME, 10 | TIME_SYNC, 11 | GET_DIG_INPUTS, 12 | GET_ANALOG_INPUTS, 13 | SET_DIG_OUTPUTS, 14 | SETUP_CANBUS, 15 | GET_CANBUS_PARAMS, 16 | GET_DEVICE_INFO, 17 | SET_SINGLEWIRE_MODE, 18 | SET_SYSTYPE, 19 | ECHO_CAN_FRAME, 20 | SETUP_EXT_BUSES 21 | }; 22 | 23 | enum GVRET_PROTOCOL 24 | { 25 | PROTO_BUILD_CAN_FRAME = 0, 26 | PROTO_TIME_SYNC = 1, 27 | PROTO_DIG_INPUTS = 2, 28 | PROTO_ANA_INPUTS = 3, 29 | PROTO_SET_DIG_OUT = 4, 30 | PROTO_SETUP_CANBUS = 5, 31 | PROTO_GET_CANBUS_PARAMS = 6, 32 | PROTO_GET_DEV_INFO = 7, 33 | PROTO_SET_SW_MODE = 8, 34 | PROTO_KEEPALIVE = 9, 35 | PROTO_SET_SYSTYPE = 10, 36 | PROTO_ECHO_CAN_FRAME = 11, 37 | PROTO_GET_NUMBUSES = 12, 38 | PROTO_GET_EXT_BUSES = 13, 39 | PROTO_SET_EXT_BUSES = 14 40 | }; 41 | 42 | class GVRET_Comm_Handler 43 | { 44 | public: 45 | GVRET_Comm_Handler(); 46 | void processIncomingByte(uint8_t in_byte); 47 | void sendFrameToBuffer(CAN_FRAME &frame, int whichBus); 48 | size_t numAvailableBytes(); 49 | uint8_t* getBufferedBytes(); 50 | void clearBufferedBytes(); 51 | private: 52 | CAN_FRAME build_out_frame; 53 | int out_bus; 54 | uint8_t buff[20]; 55 | int step; 56 | STATE state; 57 | uint32_t build_int; 58 | byte transmitBuffer[WIFI_BUFF_SIZE]; 59 | int transmitBufferLength; //not creating a ring buffer. The buffer should be large enough to never overflow 60 | 61 | uint8_t checksumCalc(uint8_t *buffer, int length); 62 | }; -------------------------------------------------------------------------------- /lawicel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Implements the lawicel protocol. 3 | */ 4 | 5 | #include "lawicel.h" 6 | #include "config.h" 7 | #include 8 | #include "utility.h" 9 | 10 | void LAWICELHandler::handleShortCmd(char cmd) 11 | { 12 | switch (cmd) 13 | { 14 | case 'O': //LAWICEL open canbus port 15 | CAN0.setListenOnlyMode(false); 16 | CAN0.begin(settings.CAN0Speed, 255); 17 | CAN0.enable(); 18 | Serial.write(13); //send CR to mean "ok" 19 | SysSettings.lawicelMode = true; 20 | break; 21 | case 'C': //LAWICEL close canbus port (First one) 22 | CAN0.disable(); 23 | Serial.write(13); //send CR to mean "ok" 24 | break; 25 | case 'L': //LAWICEL open canbus port in listen only mode 26 | CAN0.setListenOnlyMode(true); 27 | CAN0.begin(settings.CAN0Speed, 255); 28 | CAN0.enable(); 29 | Serial.write(13); //send CR to mean "ok" 30 | SysSettings.lawicelMode = true; 31 | break; 32 | case 'P': //LAWICEL - poll for one waiting frame. Or, just CR if no frames 33 | if (CAN0.available()) SysSettings.lawicelPollCounter = 1; 34 | else Serial.write(13); //no waiting frames 35 | break; 36 | case 'A': //LAWICEL - poll for all waiting frames - CR if no frames 37 | SysSettings.lawicelPollCounter = CAN0.available(); 38 | if (SysSettings.lawicelPollCounter == 0) Serial.write(13); 39 | break; 40 | case 'F': //LAWICEL - read status bits 41 | Serial.print("F00"); //bit 0 = RX Fifo Full, 1 = TX Fifo Full, 2 = Error warning, 3 = Data overrun, 5= Error passive, 6 = Arb. Lost, 7 = Bus Error 42 | Serial.write(13); 43 | break; 44 | case 'V': //LAWICEL - get version number 45 | Serial.print("V1013\n"); 46 | SysSettings.lawicelMode = true; 47 | break; 48 | case 'N': //LAWICEL - get serial number 49 | Serial.print("ESP32RET\n"); 50 | SysSettings.lawicelMode = true; 51 | break; 52 | case 'x': 53 | SysSettings.lawicellExtendedMode = !SysSettings.lawicellExtendedMode; 54 | if (SysSettings.lawicellExtendedMode) { 55 | Serial.print("V2\n"); 56 | } 57 | else { 58 | Serial.print("LAWICEL\n"); 59 | } 60 | break; 61 | case 'B': //LAWICEL V2 - Output list of supported buses 62 | if (SysSettings.lawicellExtendedMode) { 63 | for (int i = 0; i < NUM_BUSES; i++) { 64 | printBusName(i); 65 | Serial.print("\n"); 66 | } 67 | } 68 | break; 69 | case 'X': 70 | if (SysSettings.lawicellExtendedMode) { 71 | } 72 | break; 73 | } 74 | } 75 | 76 | void LAWICELHandler::handleLongCmd(char *buffer) 77 | { 78 | CAN_FRAME outFrame; 79 | char buff[80]; 80 | int val; 81 | 82 | tokenizeCmdString(buffer); 83 | 84 | switch (buffer[0]) { 85 | case 't': //transmit standard frame 86 | outFrame.id = Utility::parseHexString(buffer + 1, 3); 87 | outFrame.length = buffer[4] - '0'; 88 | outFrame.extended = false; 89 | if (outFrame.length < 0) outFrame.length = 0; 90 | if (outFrame.length > 8) outFrame.length = 8; 91 | for (int data = 0; data < outFrame.length; data++) { 92 | outFrame.data.bytes[data] = Utility::parseHexString(buffer + 5 + (2 * data), 2); 93 | } 94 | CAN0.sendFrame(outFrame); 95 | if (SysSettings.lawicelAutoPoll) Serial.print("z"); 96 | break; 97 | case 'T': //transmit extended frame 98 | outFrame.id = Utility::parseHexString(buffer + 1, 8); 99 | outFrame.length = buffer[9] - '0'; 100 | outFrame.extended = false; 101 | if (outFrame.length < 0) outFrame.length = 0; 102 | if (outFrame.length > 8) outFrame.length = 8; 103 | for (int data = 0; data < outFrame.length; data++) { 104 | outFrame.data.bytes[data] = Utility::parseHexString(buffer + 10 + (2 * data), 2); 105 | } 106 | CAN0.sendFrame(outFrame); 107 | if (SysSettings.lawicelAutoPoll) Serial.print("Z"); 108 | break; 109 | case 'S': 110 | if (!SysSettings.lawicellExtendedMode) { 111 | //setup canbus baud via predefined speeds 112 | val = Utility::parseHexCharacter(buffer[1]); 113 | switch (val) { 114 | case 0: 115 | settings.CAN0Speed = 10000; 116 | break; 117 | case 1: 118 | settings.CAN0Speed = 20000; 119 | break; 120 | case 2: 121 | settings.CAN0Speed = 50000; 122 | break; 123 | case 3: 124 | settings.CAN0Speed = 100000; 125 | break; 126 | case 4: 127 | settings.CAN0Speed = 125000; 128 | break; 129 | case 5: 130 | settings.CAN0Speed = 250000; 131 | break; 132 | case 6: 133 | settings.CAN0Speed = 500000; 134 | break; 135 | case 7: 136 | settings.CAN0Speed = 800000; 137 | break; 138 | case 8: 139 | settings.CAN0Speed = 1000000; 140 | break; 141 | } 142 | } 143 | else { //LAWICEL V2 - Send packet out of specified bus - S <...> 144 | uint8_t bytes[8]; 145 | uint32_t id; 146 | int numBytes = 0; 147 | id = strtol(tokens[2], nullptr, 16); 148 | for (int b = 0; b < 8; b++) { 149 | if (tokens[3 + b][0] != 0) { 150 | bytes[b] = strtol(tokens[3 + b], nullptr, 16); 151 | numBytes++; 152 | } 153 | else break; //break for loop because we're obviously done. 154 | } 155 | if (!strcasecmp(tokens[1], "CAN0")) { 156 | CAN_FRAME outFrame; 157 | outFrame.id = id; 158 | outFrame.length = numBytes; 159 | outFrame.extended = false; 160 | for (int b = 0; b < numBytes; b++) outFrame.data.bytes[b] = bytes[b]; 161 | CAN0.sendFrame(outFrame); 162 | } 163 | if (!strcasecmp(tokens[1], "CAN1")) { 164 | CAN_FRAME outFrame; 165 | outFrame.id = id; 166 | outFrame.length = numBytes; 167 | outFrame.extended = false; 168 | for (int b = 0; b < numBytes; b++) outFrame.data.bytes[b] = bytes[b]; 169 | CAN1.sendFrame(outFrame); 170 | } 171 | } 172 | case 's': //setup canbus baud via register writes (we can't really do that...) 173 | //settings.CAN0Speed = 250000; 174 | break; 175 | case 'r': //send a standard RTR frame (don't really... that's so deprecated its not even funny) 176 | break; 177 | case 'R': 178 | if (SysSettings.lawicellExtendedMode) { //Lawicel V2 - Set that we want to receive traffic from the given bus - R 179 | if (!strcasecmp(tokens[1], "CAN0")) SysSettings.lawicelBusReception[0] = true; 180 | if (!strcasecmp(tokens[1], "CAN1")) SysSettings.lawicelBusReception[1] = true; 181 | } 182 | else { //Lawicel V1 - send extended RTR frame (NO! DON'T DO IT!) 183 | } 184 | break; 185 | case 'X': //Set autopoll off/on 186 | if (buffer[1] == '1') SysSettings.lawicelAutoPoll = true; 187 | else SysSettings.lawicelAutoPoll = false; 188 | break; 189 | case 'W': //Dual or single filter mode 190 | break; //don't actually support this mode 191 | case 'm': //set acceptance mask - these things seem to be odd and aren't actually implemented yet 192 | case 'M': 193 | if (SysSettings.lawicellExtendedMode) { //Lawicel V2 - Set filter mask - M 194 | int mask = strtol(tokens[2], nullptr, 16); 195 | int filt = strtol(tokens[3], nullptr, 16); 196 | if (!strcasecmp(tokens[1], "CAN0")) 197 | { 198 | if (!strcasecmp(tokens[4], "X")) CAN0.setRXFilter(0, filt, mask, true); 199 | else CAN0.setRXFilter(0, filt, mask, false); 200 | } 201 | if (!strcasecmp(tokens[1], "CAN1")) 202 | { 203 | if (!strcasecmp(tokens[4], "X")) CAN1.setRXFilter(0, filt, mask, true); 204 | else CAN1.setRXFilter(0, filt, mask, false); 205 | } 206 | } 207 | else { //Lawicel V1 - set acceptance code 208 | } 209 | break; 210 | case 'H': 211 | if (SysSettings.lawicellExtendedMode) { //Lawicel V2 - Halt reception of traffic from given bus - H 212 | if (!strcasecmp(tokens[1], "CAN0")) SysSettings.lawicelBusReception[0] = false; 213 | if (!strcasecmp(tokens[1], "CAN1")) SysSettings.lawicelBusReception[1] = false; 214 | } 215 | break; 216 | case 'U': //set uart speed. We just ignore this. You can't set a baud rate on a USB CDC port 217 | break; //also no action here 218 | case 'Z': //Turn timestamp off/on 219 | if (buffer[1] == '1') SysSettings.lawicelTimestamping = true; 220 | else SysSettings.lawicelTimestamping = false; 221 | break; 222 | case 'Q': //turn auto start up on/off - probably don't need to actually implement this at the moment. 223 | break; //no action yet or maybe ever 224 | case 'C': //Lawicel V2 - configure one of the buses - C 225 | if (SysSettings.lawicellExtendedMode) { 226 | //at least two parameters separated by spaces. First BUS ID (CAN0, CAN1, SWCAN, etc) then speed (or more params separated by #'s) 227 | int speed = atoi(tokens[2]); 228 | if (!strcasecmp(tokens[1], "CAN0")) { 229 | CAN0.begin(speed, 255); 230 | } 231 | if (!strcasecmp(tokens[1], "CAN1")) { 232 | CAN1.begin(speed, 255); 233 | } 234 | } 235 | break; 236 | } 237 | Serial.write(13); 238 | } 239 | 240 | //Tokenize cmdBuffer on space boundaries - up to 10 tokens supported 241 | void LAWICELHandler::tokenizeCmdString(char *buff) { 242 | int idx = 0; 243 | char *tok; 244 | 245 | for (int i = 0; i < 13; i++) tokens[i][0] = 0; 246 | 247 | tok = strtok(buff, " "); 248 | if (tok != nullptr) strcpy(tokens[idx], tok); 249 | else tokens[idx][0] = 0; 250 | while (tokens[idx] != nullptr && idx < 13) { 251 | idx++; 252 | tok = strtok(nullptr, " "); 253 | if (tok != nullptr) strcpy(tokens[idx], tok); 254 | else tokens[idx][0] = 0; 255 | } 256 | } 257 | 258 | void LAWICELHandler::uppercaseToken(char *token) { 259 | int idx = 0; 260 | while (token[idx] != 0 && idx < 9) { 261 | token[idx] = toupper(token[idx]); 262 | idx++; 263 | } 264 | token[idx] = 0; 265 | } 266 | 267 | void LAWICELHandler::printBusName(int bus) { 268 | switch (bus) { 269 | case 0: 270 | Serial.print("CAN0"); 271 | break; 272 | case 1: 273 | Serial.print("CAN1"); 274 | break; 275 | default: 276 | Serial.print("UNKNOWN"); 277 | break; 278 | } 279 | } 280 | 281 | //Expecting to find ID in tokens[2] then zero or more data bytes 282 | bool LAWICELHandler::parseLawicelCANCmd(CAN_FRAME &frame) { 283 | if (tokens[2] == nullptr) return false; 284 | frame.id = strtol(tokens[2], nullptr, 16); 285 | int idx = 3; 286 | int dataLen = 0; 287 | while (tokens[idx] != nullptr) { 288 | frame.data.bytes[dataLen++] = strtol(tokens[idx], nullptr, 16); 289 | idx++; 290 | } 291 | frame.length = dataLen; 292 | 293 | return true; 294 | } 295 | 296 | void LAWICELHandler::sendFrameToBuffer(CAN_FRAME &frame, int whichBus) 297 | { 298 | uint8_t buff[40]; 299 | uint8_t writtenBytes; 300 | uint8_t temp; 301 | uint32_t now = micros(); 302 | 303 | if (SysSettings.lawicellExtendedMode) 304 | { 305 | Serial.print(micros()); 306 | Serial.print(" - "); 307 | Serial.print(frame.id, HEX); 308 | if (frame.extended) Serial.print(" X "); 309 | else Serial.print(" S "); 310 | 311 | printBusName(whichBus); 312 | for (int d = 0; d < frame.length; d++) 313 | { 314 | Serial.print(" "); 315 | Serial.print(frame.data.uint8[d], HEX); 316 | } 317 | } 318 | else 319 | { 320 | if (frame.extended) 321 | { 322 | Serial.print("T"); 323 | sprintf((char *)buff, "%08x", frame.id); 324 | Serial.print((char *)buff); 325 | } 326 | else 327 | { 328 | Serial.print("t"); 329 | sprintf((char *)buff, "%03x", frame.id); 330 | Serial.print((char *)buff); 331 | } 332 | Serial.print(frame.length); 333 | for (int i = 0; i < frame.length; i++) 334 | { 335 | sprintf((char *)buff, "%02x", frame.data.uint8[i]); 336 | Serial.print((char *)buff); 337 | } 338 | if (SysSettings.lawicelTimestamping) 339 | { 340 | uint16_t timestamp = (uint16_t)millis(); 341 | sprintf((char *)buff, "%04x", timestamp); 342 | Serial.print((char *)buff); 343 | } 344 | } 345 | Serial.write(13); 346 | } -------------------------------------------------------------------------------- /lawicel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class CAN_FRAME; 4 | 5 | class LAWICELHandler 6 | { 7 | public: 8 | void handleLongCmd(char *buffer); 9 | void handleShortCmd(char cmd); 10 | void sendFrameToBuffer(CAN_FRAME &frame, int whichBus); 11 | 12 | private: 13 | char tokens[14][10]; 14 | 15 | void tokenizeCmdString(char *buff); 16 | void uppercaseToken(char *token); 17 | void printBusName(int bus); 18 | bool parseLawicelCANCmd(CAN_FRAME &frame); 19 | }; -------------------------------------------------------------------------------- /sys_io.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * sys_io.cpp 3 | * 4 | * Handles the low level details of system I/O 5 | * 6 | Copyright (c) 2013-2018 Collin Kidder, Michael Neuweiler, Charles Galpin 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included 17 | in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | some portions based on code credited as: 28 | Arduino Due ADC->DMA->USB 1MSPS 29 | by stimmer 30 | 31 | */ 32 | 33 | #include "sys_io.h" 34 | 35 | bool useRawADC = false; 36 | 37 | #undef HID_ENABLED 38 | 39 | uint8_t dig[NUM_DIGITAL]; //digital inputs. sudo/analogue inputs 40 | uint8_t adc[NUM_ANALOG][2]; // [x][0]This holds the ADC Channel number. // This is calculated in sys_early_setup() 41 | // [x][1]This holds the pointer of the ADC number position into the adc_buf[x][x] array (Now redundent). 42 | // This is calculated in sys_early_setup() 43 | uint8_t out[NUM_OUTPUT]; //digital output configuration details 44 | 45 | uint32_t Enabled_Analogue_Pins = 0; // Sum of ADC input numbers to load into ADC_CHER 46 | 47 | ADC_COMP adc_comp[NUM_ANALOG]; //holds ADC offset or gain 48 | 49 | //forces the digital I/O ports to a safe state. This is called very early in initialization. 50 | void sys_early_setup(){ 51 | uint8_t i; 52 | } 53 | 54 | /* 55 | Initialize DMA driven ADC and read in gain/offset for each channel 56 | */ 57 | void setup_sys_io() 58 | { 59 | uint8_t i; 60 | 61 | setupFastADC(); 62 | } 63 | 64 | /* 65 | Setup the system to continuously read the proper ADC channels and use DMA to place the results into RAM 66 | Testing to find a good batch of settings for how fast to do ADC readings. The relevant areas: 67 | 1. In the adc_init call it is possible to use something other than ADC_FREQ_MAX to slow down the ADC clock 68 | 2. ADC_MR has a clock divisor, start up time, settling time, tracking time, and transfer time. These can be adjusted 69 | */ 70 | void setupFastADC(){ 71 | Logger::debug("Fast ADC Mode Enabled"); 72 | } 73 | 74 | /* 75 | get value of one of the 9 analog inputs 0->(NUM_ANALOG - 1) 76 | Uses a special buffer which has smoothed and corrected ADC values. This call is very fast 77 | because the actual work is done via DMA and then a separate polled step. 78 | */ 79 | uint16_t getAnalog(uint8_t which) 80 | { 81 | 82 | if (which >= NUM_ANALOG) which = 0; 83 | if (adc[which][0] > 15) which = 0; 84 | 85 | return 0;//adc_out_vals[which]; 86 | } 87 | 88 | /* 89 | get value of one of the sudo 6 digital/Analogue inputs 0->(NUM_DIGITAL - 1) 90 | */ 91 | boolean getDigital(uint8_t which) 92 | { 93 | if((which >= NUM_OUTPUT) || (dig[which] == 255)){ 94 | return(false); 95 | } 96 | return(false); //(adc_out_vals[which] > 200) ? true : false); 97 | } 98 | 99 | //set output high or not 100 | void setOutput(uint8_t which, boolean active) 101 | { 102 | if((which >= NUM_OUTPUT) || (out[which] == 255)){ 103 | return; 104 | } 105 | if(active){ 106 | (which <= 2) ? digitalWrite(out[which], HIGH) : digitalWrite(out[which], LOW); 107 | }else{ 108 | (which <= 2) ? digitalWrite(out[which], LOW) : digitalWrite(out[which], HIGH); 109 | } 110 | } 111 | 112 | //get current value of output state (high?) 113 | boolean getOutput(uint8_t which) 114 | { 115 | if((which >= NUM_OUTPUT) || (out[which] == 255)){ 116 | return false; 117 | } 118 | return digitalRead(out[which]); 119 | } 120 | 121 | void setLED(uint8_t which, boolean hi){ 122 | if(which == 255){ 123 | return; 124 | } 125 | if(hi){ 126 | digitalWrite(which, HIGH); 127 | } else{ 128 | digitalWrite(which, LOW); 129 | } 130 | } 131 | 132 | void toggleRXLED() 133 | { 134 | static int counter = 0; 135 | counter++; 136 | if (counter >= BLINK_SLOWNESS) { 137 | counter = 0; 138 | SysSettings.rxToggle = !SysSettings.rxToggle; 139 | setLED(SysSettings.LED_CANRX, SysSettings.rxToggle); 140 | } 141 | } 142 | 143 | void toggleTXLED() 144 | { 145 | static int counter = 0; 146 | counter++; 147 | if (counter >= BLINK_SLOWNESS) { 148 | counter = 0; 149 | SysSettings.txToggle = !SysSettings.txToggle; 150 | setLED(SysSettings.LED_CANTX, SysSettings.txToggle); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /sys_io.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sys_io.h 3 | * 4 | * Handles raw interaction with system I/O 5 | * 6 | Copyright (c) 2013 Collin Kidder, Michael Neuweiler, Charles Galpin 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included 17 | in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | */ 28 | 29 | 30 | #ifndef SYS_IO_H_ 31 | #define SYS_IO_H_ 32 | 33 | #include 34 | #include "config.h" 35 | #include "Logger.h" 36 | 37 | typedef struct { 38 | uint16_t offset; 39 | uint16_t gain; 40 | } ADC_COMP; 41 | 42 | extern void ADC_Handler(); 43 | void sys_early_setup(); 44 | void setup_sys_io(); 45 | void setupFastADC(); 46 | void getADCAvg(); //CALL in loop()Take the arithmetic average of the readings in the buffer for each channel & updates adc_out_vals[x] 47 | uint16_t getAnalog(uint8_t which); //get value of one of the 9 analog inputs 48 | boolean getDigital(uint8_t which); ////get value of one of the 6 digital/sudo(Analogue) inputs 0->(NUM_DIGITAL - 1) 49 | void setOutput(uint8_t which, boolean active); //set output high or not 50 | boolean getOutput(uint8_t which); //get current value of output state (high?) 51 | void setLED(uint8_t, boolean); 52 | void toggleTXLED(); 53 | void toggleRXLED(); 54 | #endif 55 | 56 | -------------------------------------------------------------------------------- /utility.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Utility 4 | { 5 | public: 6 | static unsigned int parseHexCharacter(char chr) 7 | { 8 | unsigned int result = 0; 9 | if (chr >= '0' && chr <= '9') result = chr - '0'; 10 | else if (chr >= 'A' && chr <= 'F') result = 10 + chr - 'A'; 11 | else if (chr >= 'a' && chr <= 'f') result = 10 + chr - 'a'; 12 | 13 | return result; 14 | } 15 | 16 | static unsigned int parseHexString(char *str, int length) 17 | { 18 | unsigned int result = 0; 19 | for (int i = 0; i < length; i++) result += parseHexCharacter(str[i]) << (4 * (length - i - 1)); 20 | return result; 21 | } 22 | }; -------------------------------------------------------------------------------- /wifi_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "wifi_manager.h" 3 | #include "gvret_comm.h" 4 | #include "SerialConsole.h" 5 | 6 | static IPAddress broadcastAddr(255,255,255,255); 7 | 8 | WiFiManager::WiFiManager() 9 | { 10 | lastBroadcast = 0; 11 | } 12 | 13 | void WiFiManager::setup() 14 | { 15 | if (settings.wifiMode == 1) //connect to an AP 16 | { 17 | WiFi.mode(WIFI_STA); 18 | WiFi.setSleep(false); //sleeping could cause delays 19 | WiFi.begin((const char *)settings.SSID, (const char *)settings.WPA2Key); 20 | 21 | WiFiEventId_t eventID = WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) 22 | { 23 | Serial.print("WiFi lost connection. Reason: "); 24 | Serial.println(info.wifi_sta_disconnected.reason); 25 | SysSettings.isWifiConnected = false; 26 | if (info.wifi_sta_disconnected.reason == 202) 27 | { 28 | Serial.println("Connection failed, rebooting to fix it."); 29 | esp_sleep_enable_timer_wakeup(10); 30 | esp_deep_sleep_start(); 31 | delay(100); 32 | } 33 | }, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); 34 | } 35 | if (settings.wifiMode == 2) //BE an AP 36 | { 37 | WiFi.mode(WIFI_AP); 38 | WiFi.setSleep(false); 39 | WiFi.softAP((const char *)settings.SSID, (const char *)settings.WPA2Key); 40 | } 41 | } 42 | 43 | void WiFiManager::loop() 44 | { 45 | boolean needServerInit = false; 46 | int i; 47 | 48 | if (settings.wifiMode > 0) 49 | { 50 | if (!SysSettings.isWifiConnected) 51 | { 52 | if (WiFi.isConnected()) 53 | { 54 | WiFi.setSleep(false); 55 | Serial.print("Wifi now connected to SSID "); 56 | Serial.println((const char *)settings.SSID); 57 | Serial.print("IP address: "); 58 | Serial.println(WiFi.localIP()); 59 | Serial.print("RSSI: "); 60 | Serial.println(WiFi.RSSI()); 61 | needServerInit = true; 62 | } 63 | if (settings.wifiMode == 2) 64 | { 65 | Serial.print("Wifi setup as SSID "); 66 | Serial.println((const char *)settings.SSID); 67 | Serial.print("IP address: "); 68 | Serial.println(WiFi.softAPIP()); 69 | needServerInit = true; 70 | } 71 | if (needServerInit) 72 | { 73 | SysSettings.isWifiConnected = true; 74 | wifiServer.begin(23); //setup as a telnet server 75 | wifiServer.setNoDelay(true); 76 | ArduinoOTA.setPort(3232); 77 | ArduinoOTA.setHostname("A0RET"); 78 | // No authentication by default 79 | //ArduinoOTA.setPassword("admin"); 80 | 81 | ArduinoOTA 82 | .onStart([]() { 83 | String type; 84 | if (ArduinoOTA.getCommand() == U_FLASH) 85 | type = "sketch"; 86 | else // U_SPIFFS 87 | type = "filesystem"; 88 | 89 | // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() 90 | Serial.println("Start updating " + type); 91 | }) 92 | .onEnd([]() { 93 | Serial.println("\nEnd"); 94 | }) 95 | .onProgress([](unsigned int progress, unsigned int total) { 96 | Serial.printf("Progress: %u%%\r", (progress / (total / 100))); 97 | }) 98 | .onError([](ota_error_t error) { 99 | Serial.printf("Error[%u]: ", error); 100 | if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); 101 | else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); 102 | else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); 103 | else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); 104 | else if (error == OTA_END_ERROR) Serial.println("End Failed"); 105 | }); 106 | 107 | ArduinoOTA.begin(); 108 | } 109 | } 110 | else 111 | { 112 | if (WiFi.isConnected() || settings.wifiMode == 2) 113 | { 114 | if (wifiServer.hasClient()) 115 | { 116 | for(i = 0; i < MAX_CLIENTS; i++) 117 | { 118 | if (!SysSettings.clientNodes[i] || !SysSettings.clientNodes[i].connected()) 119 | { 120 | if (SysSettings.clientNodes[i]) SysSettings.clientNodes[i].stop(); 121 | SysSettings.clientNodes[i] = wifiServer.available(); 122 | if (!SysSettings.clientNodes[i]) Serial.println("Couldn't accept client connection!"); 123 | else 124 | { 125 | Serial.print("New client: "); 126 | Serial.print(i); Serial.print(' '); 127 | Serial.println(SysSettings.clientNodes[i].remoteIP()); 128 | } 129 | } 130 | } 131 | if (i >= MAX_CLIENTS) { 132 | //no free/disconnected spot so reject 133 | wifiServer.available().stop(); 134 | } 135 | } 136 | 137 | //check clients for data 138 | for(i = 0; i < MAX_CLIENTS; i++){ 139 | if (SysSettings.clientNodes[i] && SysSettings.clientNodes[i].connected()) 140 | { 141 | if(SysSettings.clientNodes[i].available()) 142 | { 143 | //get data from the telnet client and push it to input processing 144 | while(SysSettings.clientNodes[i].available()) 145 | { 146 | uint8_t inByt; 147 | inByt = SysSettings.clientNodes[i].read(); 148 | SysSettings.isWifiActive = true; 149 | //Serial.write(inByt); //echo to serial - just for debugging. Don't leave this on! 150 | wifiGVRET.processIncomingByte(inByt); 151 | } 152 | } 153 | } 154 | else { 155 | if (SysSettings.clientNodes[i]) { 156 | SysSettings.clientNodes[i].stop(); 157 | } 158 | } 159 | } 160 | } 161 | else 162 | { 163 | if (settings.wifiMode == 1) 164 | { 165 | Serial.println("WiFi disconnected. Bummer!"); 166 | SysSettings.isWifiConnected = false; 167 | SysSettings.isWifiActive = false; 168 | } 169 | } 170 | } 171 | } 172 | 173 | if (SysSettings.isWifiConnected && ((micros() - lastBroadcast) > 1000000ul) ) //every second send out a broadcast ping 174 | { 175 | uint8_t buff[4] = {0x1C,0xEF,0xAC,0xED}; 176 | lastBroadcast = micros(); 177 | wifiUDPServer.beginPacket(broadcastAddr, 17222); 178 | wifiUDPServer.write(buff, 4); 179 | wifiUDPServer.endPacket(); 180 | } 181 | 182 | ArduinoOTA.handle(); 183 | } 184 | 185 | void WiFiManager::sendBufferedData() 186 | { 187 | for(int i = 0; i < MAX_CLIENTS; i++) 188 | { 189 | size_t wifiLength = wifiGVRET.numAvailableBytes(); 190 | uint8_t* buff = wifiGVRET.getBufferedBytes(); 191 | if (SysSettings.clientNodes[i] && SysSettings.clientNodes[i].connected()) 192 | { 193 | SysSettings.clientNodes[i].write(buff, wifiLength); 194 | } 195 | } 196 | wifiGVRET.clearBufferedBytes(); 197 | } 198 | -------------------------------------------------------------------------------- /wifi_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | class WiFiManager 8 | { 9 | public: 10 | WiFiManager(); 11 | void setup(); 12 | void loop(); 13 | void sendBufferedData(); 14 | private: 15 | WiFiServer wifiServer; 16 | WiFiUDP wifiUDPServer; 17 | uint32_t lastBroadcast; 18 | }; 19 | --------------------------------------------------------------------------------