├── basic_gui_sc.png ├── LICENSE ├── README.md ├── data └── index.html └── ESP32_CANViewer.ino /basic_gui_sc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cellgalvano/ESP32_CANViewer/HEAD/basic_gui_sc.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Felix 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 CANViewer 2 | A ESP32 based CAN interface for your webbrowser 3 | 4 | ### Super basic webclient 5 | ![](basic_gui_sc.png) 6 | 7 | ## Requirements 8 | ### Libraries 9 | - [Arduino CAN (Adafruit fork)](https://github.com/adafruit/arduino-CAN) or [Arduino CAN](https://github.com/sandeepmistry/arduino-CAN) 10 | - [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) 11 | - [arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets) 12 | - [ArduinoJson](https://arduinojson.org/) 13 | 14 | ### Hardware 15 | - ESP32 16 | - 3.3V CAN Transceiver e.g. SN65HVD230 17 | 18 | ## Setup 19 | 1. Download the required libraries 20 | 2. Compile sketch and upload 21 | 3. Use the [ESP32 Sketch Data Upload Plugin](https://github.com/me-no-dev/arduino-esp32fs-plugin) to upload the data folder 22 | 23 | ## What should work? 24 | ### Server 25 | The ESP32 tries to connect to your specified WIFI network. If no connection is possible it will start an access point. 26 | On port 80 a simple webserver is available that serves the index.html. 27 | The communication works via websockets on port 1337. 28 | 29 | The websockets accepts JSON strings. 30 | Currently the following commands are implemented on the server side: 31 | - SEND: `{"cmd":"SEND", "id":1, "dlc":4, "data":[4,5,6,7]}` 32 | creates a CAN packet with ID 0x1 and the four data bytes 33 | - SETFILTER: `{"cmd":"SETFILTER", "id":8, "mask":2047}` 34 | sets the CAN acceptance filter to ID 0x8 and the MASK to 0x7FF (I don't know how to reset the filter so you need to reset the ESP in order to clear it) 35 | - SETSPEED: `{"cmd":"SETSPEED", "speed":125000}` 36 | resets the CAN library and configures the desired speed (there are no plausibilty checks included from my side, use a valid speed) 37 | 38 | The server broadcasts a STATUS message every second: 39 | `{"ts":1234,"type":"STATUS","baudrate":500000,"filterId":-1,"filterMask":-1}` 40 | ts: timestamp (Arduino millis) 41 | type: message type = STATUS 42 | baudrate: current baudrate 43 | filterId: currently set filter id (-1 means no filter set) 44 | filterMask: currently set filter mask (-1 means no filter set) 45 | 46 | Whenever a CAN packet gets received a message gets broadcasted: 47 | `{"ts":1234,"type":PACKETTYPE,"dlc":LENGTH,"data":[1,2,3,4,5,6,7,8]}` 48 | ts: timestamp (Arduino millis) 49 | type: message type 50 | - STD: Standard CAN Frame 51 | - EXT: Extended CAN Frame 52 | - RTR: Remote Transmission Request 53 | - ERTR: Extended Remote Transmission Request 54 | 55 | dlc: Data Length Code = 1-8 Bytes 56 | data: 0-8 Bytes of payload data, only in STD and EXT Frames 57 | 58 | ### Client 59 | The client is currently just a super simple first try. It just creates a HTML table with all received packets and is pretty ugly. 60 | In javascript there are more functions available than what the GUI represents: 61 | - `setSpeed(speed)` 62 | e.g. setSpeed(125000); sets a baudrate of 125KBit/s 63 | - `setFilter(id, mask)` 64 | e.g. setFilter(0x8, 0x7FF); sets the filter to only receive packets with id 0x8 65 | - `sendPacket(id, dataArray)` 66 | e.g. sendPacket(0x8, [1,2,3,4,5]); sends a five byte CAN packet with id 0x8 and the content 1,2,3,4,5 67 | -------------------------------------------------------------------------------- /data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ESP32 CANViewer 5 | 6 | 7 | 8 | 14 | 15 | 16 |
17 |
18 |

ESP32 CANViewer

19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 | 156 | 157 | -------------------------------------------------------------------------------- /ESP32_CANViewer.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * ESP32 CANViewer 3 | * V0.1 4 | * Connect CRX to Pin 4, CTX to Pin 5 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | const char* ssid = "YOURNETWORK"; 15 | const char* password = "YOURPASSWORD"; 16 | 17 | AsyncWebServer server(80); 18 | WebSocketsServer webSocket = WebSocketsServer(1337); 19 | 20 | long CAN_baudRate = 500E3; 21 | long CAN_filterId = -1; 22 | long CAN_filterMask = -1; 23 | 24 | void onWebSocketEvent(uint8_t client_num, WStype_t type, uint8_t * payload, size_t length) { 25 | switch(type) { 26 | case WStype_DISCONNECTED: 27 | Serial.printf("[%u] Disconnected!\n", client_num); 28 | break; 29 | 30 | case WStype_CONNECTED: 31 | { 32 | IPAddress ip = webSocket.remoteIP(client_num); 33 | Serial.printf("[%u] Connection from ", client_num); 34 | Serial.println(ip.toString()); 35 | } 36 | break; 37 | 38 | case WStype_TEXT: 39 | { 40 | Serial.printf("[%u] Received text: %s\n", client_num, payload); 41 | 42 | StaticJsonDocument<250> jsonobj; 43 | DeserializationError err = deserializeJson(jsonobj, payload); 44 | if(err){ 45 | Serial.println("Invalid JSON!"); 46 | }else{ 47 | const char* command = jsonobj["cmd"]; 48 | 49 | Serial.print("CMD: "); 50 | Serial.println(command); 51 | 52 | // CMD: SETFILTER 53 | if(String(command).equals("SETFILTER")){ 54 | const unsigned int filter_id = jsonobj["id"]; 55 | const unsigned int filter_mask = jsonobj["mask"]; 56 | CAN.filter(filter_id, filter_mask); 57 | CAN_filterId = filter_id; 58 | CAN_filterMask = filter_mask; 59 | Serial.printf("Setting Filter ID: %x ,MASK: %x\n", filter_id, filter_mask); 60 | } 61 | 62 | // CMD: SETSPEED 63 | if(String(command).equals("SETSPEED")){ 64 | const unsigned int targetspeed = jsonobj["speed"]; 65 | CAN_baudRate = targetspeed; 66 | CAN.end(); 67 | CAN.begin(CAN_baudRate); 68 | Serial.printf("Setting Speed/Baudrate to: %u\n", targetspeed); 69 | } 70 | 71 | // CMD: SEND 72 | if(String(command).equals("SEND")){ 73 | const unsigned int send_id = jsonobj["id"]; 74 | const unsigned int send_dlc = jsonobj["dlc"]; 75 | JsonArray send_arr = jsonobj["data"].as(); 76 | if(send_dlc == send_arr.size()){ 77 | if(send_id <= 0x7FF){ 78 | CAN.beginPacket(send_id, send_dlc); 79 | }else{ 80 | CAN.beginExtendedPacket(send_id, send_dlc); 81 | } 82 | for(byte i=0; iclient()->remoteIP(); 112 | Serial.println("["+remote_ip.toString()+"] HTTP GET request of " + request->url()); 113 | request->send(SPIFFS, "/index.html", "text/html"); 114 | } 115 | 116 | void onPageNotFound(AsyncWebServerRequest *request) { 117 | IPAddress remote_ip = request->client()->remoteIP(); 118 | Serial.println("["+remote_ip.toString()+"] HTTP GET request of " + request->url()); 119 | request->send(404, "text/plain", "Not found"); 120 | } 121 | 122 | void setup() { 123 | Serial.begin(115200); 124 | while (!Serial); 125 | Serial.println(""); 126 | Serial.println("ESP32 CANViewer"); 127 | 128 | // Test SPIFFS 129 | if(!SPIFFS.begin(true)){ 130 | Serial.println("An Error has occurred while mounting SPIFFS"); 131 | return; 132 | } 133 | 134 | // Connect to WIFI 135 | WiFi.mode(WIFI_STA); 136 | WiFi.begin(ssid, password); 137 | WiFi.setHostname("ESP32CANViewer"); 138 | Serial.print("Connecting to WIFI ..."); 139 | byte wifi_counter = 0; 140 | while (WiFi.status() != WL_CONNECTED) { 141 | delay(500); 142 | Serial.print("."); 143 | wifi_counter++; 144 | if(wifi_counter > 20){ 145 | WiFi.disconnect(); 146 | WiFi.mode(WIFI_AP); 147 | WiFi.softAP("ESP32CANViewer", NULL); 148 | Serial.print("[Switching to AP Mode]"); 149 | break; 150 | } 151 | } 152 | Serial.println(" connected!"); 153 | 154 | // Show IP Address 155 | Serial.print("IP: "); 156 | Serial.println(WiFi.localIP()); 157 | 158 | // Setup Webserver 159 | server.on("/", HTTP_GET, onIndexRequest); 160 | server.onNotFound(onPageNotFound); 161 | server.begin(); 162 | 163 | // Setup Websocket Server 164 | webSocket.begin(); 165 | webSocket.onEvent(onWebSocketEvent); 166 | 167 | // Start CAN with default Baudrate 168 | if (!CAN.begin(CAN_baudRate)) { 169 | Serial.println("Starting CAN failed!"); 170 | while (1); 171 | }else{ 172 | Serial.println("CAN started!"); 173 | } 174 | 175 | } 176 | 177 | long prevMillis = 0; 178 | void loop() { 179 | webSocket.loop(); 180 | handleCAN_RX(); 181 | 182 | // Broadcast STATUS message every second 183 | if((millis() - prevMillis) > 1000){ 184 | prevMillis = millis(); 185 | //Serial.println("Sending status!"); 186 | StaticJsonDocument<250> jsonobj; 187 | String output = ""; 188 | jsonobj["ts"] = millis(); 189 | jsonobj["type"] = "STATUS"; 190 | jsonobj["baudrate"] = CAN_baudRate; 191 | jsonobj["filterId"] = CAN_filterId; 192 | jsonobj["filterMask"] = CAN_filterMask; 193 | serializeJson(jsonobj, output); 194 | webSocket.broadcastTXT(output); 195 | } 196 | } 197 | 198 | // Handle CAN Input 199 | void handleCAN_RX() { 200 | 201 | int packetSize = CAN.parsePacket(); 202 | if (packetSize) { 203 | 204 | StaticJsonDocument<250> jsonobj; 205 | String output = ""; 206 | jsonobj["ts"] = millis(); 207 | 208 | jsonobj["type"] = "STD"; 209 | 210 | if (CAN.packetExtended()) { 211 | jsonobj["type"] = "EXT"; 212 | } 213 | 214 | if (CAN.packetRtr()) { 215 | jsonobj["type"] = "RTR"; 216 | if(CAN.packetExtended()) jsonobj["type"] = "ERTR"; 217 | } 218 | 219 | jsonobj["id"] = CAN.packetId(); 220 | 221 | if (CAN.packetRtr()) { 222 | // RTR FRAME 223 | jsonobj["dlc"] = CAN.packetDlc(); 224 | } else { 225 | // NON RTR FRAME 226 | jsonobj["dlc"] = packetSize; 227 | JsonArray data = jsonobj.createNestedArray("data"); 228 | while (CAN.available()) { 229 | char temp = (char)CAN.read(); 230 | data.add(temp); 231 | //Serial.printf("%u\n", temp); 232 | } 233 | //Serial.printf("Size: %u\n", data.size()); 234 | } 235 | 236 | serializeJson(jsonobj, output); 237 | webSocket.broadcastTXT(output); // Broadcast every received packet 238 | } 239 | } 240 | --------------------------------------------------------------------------------