├── main ├── CMakeLists.txt ├── component.mk ├── idf_component.yml ├── twai.h ├── Kconfig.projbuild ├── twai.c ├── http_post.c ├── http_server.c └── main.c ├── sdkconfig.defaults ├── partitions.csv ├── csv └── can2http.csv ├── CMakeLists.txt ├── flask ├── templates │ └── index.html └── can.py ├── tornado ├── templates │ └── index.html └── can.py ├── LICENSE └── README.md /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCS "main.c" "http_post.c" "http_server.c" "twai.c") 2 | set(COMPONENT_ADD_INCLUDEDIRS ".") 3 | 4 | register_component() 5 | -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Main Makefile. This is basically the same as a component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # 2 | # Partition Table 3 | # 4 | CONFIG_PARTITION_TABLE_CUSTOM=y 5 | CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" 6 | CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" 7 | 8 | # 9 | # HTTP Server 10 | # 11 | CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 12 | -------------------------------------------------------------------------------- /main/idf_component.yml: -------------------------------------------------------------------------------- 1 | ## IDF Component Manager Manifest File 2 | dependencies: 3 | espressif/mdns: 4 | version: "^1.0.3" 5 | rules: 6 | - if: "idf_version >=5.0" 7 | espressif/cjson: 8 | version: "^1.7.0" 9 | rules: 10 | - if: "idf_version >=6.0" 11 | -------------------------------------------------------------------------------- /partitions.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild 3 | nvs, data, nvs, 0x9000, 0x6000, 4 | phy_init, data, phy, 0xf000, 0x1000, 5 | factory, app, factory, 0x10000, 0x120000, 6 | storage, data, spiffs, , 0xD0000, 7 | -------------------------------------------------------------------------------- /csv/can2http.csv: -------------------------------------------------------------------------------- 1 | #The file can2http.csv has three columns. 2 | #In the first column you need to specify the CAN Frame type. 3 | #The CAN frame type is either S(Standard frame) or E(Extended frame). 4 | #In the second column you have to specify the CAN-ID as a __hexdecimal number__. 5 | #In the last column you have to specify the PATH of external web server. 6 | #Each CAN-ID is allowed to appear only once in the whole file. 7 | 8 | S,101,/post 9 | E,101,/post 10 | S,103,/post 11 | E,103,/post 12 | -------------------------------------------------------------------------------- /main/twai.h: -------------------------------------------------------------------------------- 1 | #define STANDARD_FRAME 0 2 | #define EXTENDED_FRAME 1 3 | 4 | #define DATA_FRAME 0 5 | #define REMOTE_FRAME 1 6 | 7 | #define CMD_RECEIVE 100 8 | #define CMD_SEND 200 9 | 10 | typedef struct { 11 | int16_t command; 12 | char topic[64]; 13 | int16_t topic_len; 14 | int32_t canid; 15 | int16_t ext; 16 | int16_t rtr; 17 | int16_t data_len; 18 | char data[8]; 19 | } FRAME_t; 20 | 21 | typedef struct { 22 | uint16_t frame; 23 | uint32_t canid; 24 | char * topic; 25 | int16_t topic_len; 26 | } TOPIC_t; 27 | 28 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's CMakeLists 2 | # in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(can2http) 7 | 8 | # Create a SPIFFS image from the contents of the 'font' directory 9 | # that fits the partition named 'storage'. FLASH_IN_PROJECT indicates that 10 | # the generated image should be flashed when the entire project is flashed to 11 | # the target with 'idf.py -p PORT flash 12 | spiffs_create_partition_image(storage csv FLASH_IN_PROJECT) 13 | -------------------------------------------------------------------------------- /flask/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CAN DATA 6 | 7 | 8 | 9 | Display the latest {{ lines }} records.
10 | If you want to see more, specify the number of lines at startup.
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for i in items %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 | 32 |
DateTimeIDFrameValue
{{ i.date }}{{ i.time }}{{ i.id }}{{ i.frame }}{{ i.value }}
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tornado/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CAN DATA 6 | 7 | 8 | 9 | Display the latest {{ lines }} records.
10 | If you want to see more, specify the number of lines at startup.
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for i in items %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% end %} 31 | 32 |
DateTimeIDFrameValue
{{ i['date'] }}{{ i['time'] }}{{ i['id'] }}{{ i['frame'] }}{{ i['value'] }}
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 nopnop2002 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 | -------------------------------------------------------------------------------- /flask/can.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Simple REST Server 4 | 5 | import os 6 | import sys 7 | import json 8 | import datetime 9 | import argparse 10 | from flask import Flask, request, render_template 11 | app = Flask(__name__) 12 | 13 | database = [] 14 | 15 | @app.route("/") 16 | def root(): 17 | global database 18 | items = [] 19 | for data in database: 20 | #print("{}".format(data)) 21 | datetime = data[0].split() 22 | #print("data={} datetime={}".format(data[0], datetime)) 23 | 24 | items.append({ 25 | "date": datetime[0], 26 | "time": datetime[1], 27 | "id": data[1], 28 | "frame": data[2], 29 | "value": data[3] 30 | }) 31 | #print("items={}".format(items)) 32 | return render_template("index.html", lines=app.config['lines'], items=items) 33 | 34 | #curl -X POST -H "Content-Type: application/json" -d '{"canid":101, "frame":"standard" , "data": [1,2,3,4,5,6,7,8]}' http://192.168.10.43:8000/post 35 | @app.route("/post", methods=["POST"]) 36 | def post(): 37 | function = sys._getframe().f_code.co_name 38 | #print("{}: request={}".format(function, request)) 39 | #print("{}: request.data={}".format(function, request.data)) 40 | dict = json.loads(request.data) 41 | print("{} dict={}".format(function, dict)) 42 | 43 | items = [] 44 | now = datetime.datetime.now() 45 | items.append(now.strftime("%Y/%m/%d %H:%M:%S")) 46 | if ("canid" in dict): 47 | print("{} canid=0x{:x}".format(function, dict['canid'])) 48 | items.append(dict['canid']) 49 | 50 | if ("frame" in dict): 51 | print("{} frame={}".format(function, dict['frame'])) 52 | items.append(dict['frame']) 53 | 54 | if ("data" in dict): 55 | print("{} data length={}".format(function, len(dict['data']))) 56 | print("{} data={}".format(function, dict['data'])) 57 | items.append(dict['data']) 58 | 59 | global database 60 | #print("{} {} {}".format(type(database), len(database), database)) 61 | print("lines={}".format(app.config['lines'])) 62 | #if(len(database) >= 20): 63 | if(len(database) >= app.config['lines']): 64 | database.pop(0) 65 | database.append(items) 66 | 67 | data = json.dumps(['result', 'ok']) 68 | return data 69 | 70 | if __name__ == "__main__": 71 | parser = argparse.ArgumentParser() 72 | parser.add_argument('-port', type=int, default=8000) 73 | parser.add_argument('-lines', type=int, default=20) 74 | args = parser.parse_args() 75 | print("port={}".format(args.port)) 76 | print("lines={}".format(args.lines)) 77 | app.config['lines'] = args.lines 78 | #app.run() 79 | app.run(host='0.0.0.0', port=args.port, debug=True) 80 | 81 | -------------------------------------------------------------------------------- /tornado/can.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Simple REST Server 4 | 5 | import os 6 | import sys 7 | import json 8 | import datetime 9 | import werkzeug 10 | import tornado.httpserver 11 | import tornado.ioloop 12 | import tornado.options 13 | import tornado.web 14 | 15 | from tornado.options import define, options 16 | define("port", default=8000, help="run on the given port", type=int) 17 | define("lines", default=20, help="number of lines displayed", type=int) 18 | 19 | database = [] 20 | 21 | class IndexHandler(tornado.web.RequestHandler): 22 | def get(self): 23 | global database 24 | items = [] 25 | for data in database: 26 | #print("{}".format(data)) 27 | datetime = data[0].split() 28 | #print("data={} datetime={}".format(data[0], datetime)) 29 | 30 | items.append({ 31 | "date": datetime[0], 32 | "time": datetime[1], 33 | "id": data[1], 34 | "frame": data[2], 35 | "value": data[3] 36 | }) 37 | #print("items={}".format(items)) 38 | self.render("index.html", lines=options.lines, items=items) 39 | 40 | #curl -X POST -H "Content-Type: application/json" -d '{"canid":101, "frame":"standard" , "data": [1,2,3,4,5,6,7,8]}' http://192.168.10.43:8000/post 41 | class PostHandler(tornado.web.RequestHandler): 42 | def post(self): 43 | #print("{}".format(self.request.body)) 44 | function = sys._getframe().f_code.co_name 45 | dict = tornado.escape.json_decode(self.request.body) 46 | print("{} dict={}".format(function, dict)) 47 | 48 | items = [] 49 | now = datetime.datetime.now() 50 | items.append(now.strftime("%Y/%m/%d %H:%M:%S")) 51 | if ("canid" in dict): 52 | print("{} canid=0x{:x}".format(function, dict['canid'])) 53 | items.append(dict['canid']) 54 | 55 | if ("frame" in dict): 56 | print("{} frame={}".format(function, dict['frame'])) 57 | items.append(dict['frame']) 58 | 59 | if ("data" in dict): 60 | print("{} data length={}".format(function, len(dict['data']))) 61 | print("{} data={}".format(function, dict['data'])) 62 | items.append(dict['data']) 63 | 64 | global database 65 | #print("{} {} {}".format(type(database), len(database), database)) 66 | print("lines={}".format(options.lines)) 67 | #if(len(database) >= 20): 68 | if(len(database) >= options.lines): 69 | database.pop(0) 70 | database.append(items) 71 | 72 | self.write(json.dumps({"result":"ok"})) 73 | 74 | if __name__ == "__main__": 75 | tornado.options.parse_command_line() 76 | print("port={}".format(options.port)) 77 | print("lines={}".format(options.lines)) 78 | app = tornado.web.Application( 79 | handlers=[ 80 | (r"/", IndexHandler), 81 | (r"/post", PostHandler), 82 | ], 83 | template_path=os.path.join(os.getcwd(), "templates"), 84 | debug=True 85 | ) 86 | http_server = tornado.httpserver.HTTPServer(app) 87 | http_server.listen(options.port) 88 | tornado.ioloop.IOLoop.current().start() 89 | -------------------------------------------------------------------------------- /main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "Application Configuration" 2 | 3 | config GPIO_RANGE_MAX 4 | int 5 | default 33 if IDF_TARGET_ESP32 6 | default 46 if IDF_TARGET_ESP32S2 7 | default 48 if IDF_TARGET_ESP32S3 8 | default 19 if IDF_TARGET_ESP32C3 9 | default 30 if IDF_TARGET_ESP32C6 10 | 11 | menu "CAN Setting" 12 | 13 | choice CAN_BITRATE 14 | prompt "CAN Bitrate" 15 | default CAN_BITRATE_500 16 | help 17 | Select the CAN bitrate for the example. 18 | config CAN_BITRATE_25 19 | bool "BITRATE_25" 20 | help 21 | CAN bitrate is 25 Kbit/s. 22 | config CAN_BITRATE_50 23 | bool "BITRATE_50" 24 | help 25 | CAN bitrate is 50 Kbit/s. 26 | config CAN_BITRATE_100 27 | bool "BITRATE_100" 28 | help 29 | CAN bitrate is 100 Kbit/s. 30 | config CAN_BITRATE_125 31 | bool "BITRATE_125" 32 | help 33 | CAN bitrate is 125 Kbit/s. 34 | config CAN_BITRATE_250 35 | bool "BITRATE_250" 36 | help 37 | CAN bitrate is 250 Kbit/s. 38 | config CAN_BITRATE_500 39 | bool "BITRATE_500" 40 | help 41 | CAN bitrate is 500 Kbit/s. 42 | config CAN_BITRATE_800 43 | bool "BITRATE_800" 44 | help 45 | CAN bitrate is 800 Kbit/s. 46 | config CAN_BITRATE_1000 47 | bool "BITRATE_1000" 48 | help 49 | CAN bitrate is 1 Mbit/s. 50 | endchoice 51 | 52 | config CTX_GPIO 53 | int "CTX GPIO number" 54 | range 0 GPIO_RANGE_MAX 55 | default 21 if IDF_TARGET_ESP32 56 | default 17 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 57 | default 0 # C3 and others 58 | help 59 | GPIO number (IOxx) to CTX. 60 | Some GPIOs are used for other purposes (flash connections, etc.). 61 | GPIOs 35-39 are input-only so cannot be used as outputs. 62 | 63 | config CRX_GPIO 64 | int "CRX GPIO number" 65 | range 0 GPIO_RANGE_MAX 66 | default 22 if IDF_TARGET_ESP32 67 | default 18 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 68 | default 1 # C3 and others 69 | help 70 | GPIO number (IOxx) to CRX. 71 | Some GPIOs are used for other purposes (flash connections, etc.). 72 | GPIOs 35-39 are input-only so cannot be used as outputs. 73 | 74 | config ENABLE_PRINT 75 | bool "Output the received CAN FRAME to STDOUT" 76 | default y 77 | help 78 | Output the received CAN FRAME to STDOUT. 79 | 80 | endmenu 81 | 82 | menu "WiFi Setting" 83 | 84 | config ESP_WIFI_SSID 85 | string "WiFi SSID" 86 | default "myssid" 87 | help 88 | SSID (network name) to connect to. 89 | 90 | config ESP_WIFI_PASSWORD 91 | string "WiFi Password" 92 | default "mypassword" 93 | help 94 | WiFi password (WPA or WPA2) to connect to. 95 | 96 | config ESP_MAXIMUM_RETRY 97 | int "Maximum retry" 98 | default 5 99 | help 100 | Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent. 101 | 102 | config MDNS_HOSTNAME 103 | string "mDNS Hostname" 104 | default "esp32-server" 105 | help 106 | The mDNS host name used by the ESP32. 107 | 108 | config STATIC_IP 109 | bool "Enable Static IP Address" 110 | default false 111 | help 112 | Enable Static IP Address. 113 | 114 | config STATIC_IP_ADDRESS 115 | depends on STATIC_IP 116 | string "Static IP Address" 117 | default "192.168.10.100" 118 | help 119 | Static IP Address for Station. 120 | 121 | config STATIC_GW_ADDRESS 122 | depends on STATIC_IP 123 | string "Static GW Address" 124 | default "192.168.10.1" 125 | help 126 | Static GW Address for Station. 127 | 128 | config STATIC_NM_ADDRESS 129 | depends on STATIC_IP 130 | string "Static Netmask" 131 | default "255.255.255.0" 132 | help 133 | Static Netmask for Station. 134 | 135 | endmenu 136 | 137 | menu "HTTP Server Setting" 138 | 139 | config WEB_SERVER 140 | string "HTTP Server IP or mDNS" 141 | default "http-server.local" 142 | help 143 | The host name or IP address of the HTTP server to use. 144 | 145 | config WEB_PORT 146 | int "HTTP Server Port" 147 | default 8000 148 | help 149 | HTTP server port to use. 150 | 151 | endmenu 152 | endmenu 153 | -------------------------------------------------------------------------------- /main/twai.c: -------------------------------------------------------------------------------- 1 | /* TWAI Network Example 2 | 3 | This example code is in the Public Domain (or CC0 licensed, at your option.) 4 | 5 | Unless required by applicable law or agreed to in writing, this 6 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 7 | CONDITIONS OF ANY KIND, either express or implied. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "freertos/FreeRTOS.h" 15 | #include "freertos/task.h" 16 | #include "freertos/queue.h" 17 | #include "freertos/semphr.h" 18 | #include "esp_err.h" 19 | #include "esp_log.h" 20 | #include "driver/twai.h" // Update from V4.2 21 | 22 | #include "twai.h" 23 | 24 | static const char *TAG = "TWAI"; 25 | 26 | extern QueueHandle_t xQueue_http_client; 27 | extern QueueHandle_t xQueue_twai_tx; 28 | 29 | extern TOPIC_t *publish; 30 | extern int16_t npublish; 31 | 32 | void dump_table(TOPIC_t *topics, int16_t ntopic); 33 | 34 | void twai_task(void *pvParameters) 35 | { 36 | ESP_LOGI(TAG,"task start"); 37 | dump_table(publish, npublish); 38 | 39 | twai_message_t rx_msg; 40 | twai_message_t tx_msg; 41 | FRAME_t frameBuf; 42 | while (1) { 43 | esp_err_t ret = twai_receive(&rx_msg, pdMS_TO_TICKS(10)); 44 | if (ret == ESP_OK) { 45 | ESP_LOGD(TAG,"twai_receive identifier=0x%"PRIx32" flags=0x%"PRIx32" data_length_code=%d", 46 | rx_msg.identifier, rx_msg.flags, rx_msg.data_length_code); 47 | 48 | int ext = rx_msg.flags & 0x01; 49 | int rtr = rx_msg.flags & 0x02; 50 | ESP_LOGD(TAG, "ext=%x rtr=%x", ext, rtr); 51 | 52 | #if CONFIG_ENABLE_PRINT 53 | if (ext == STANDARD_FRAME) { 54 | printf("Standard ID: 0x%03"PRIx32" ", rx_msg.identifier); 55 | } else { 56 | printf("Extended ID: 0x%08"PRIx32, rx_msg.identifier); 57 | } 58 | printf(" DLC: %d Data: ", rx_msg.data_length_code); 59 | 60 | if (rtr == 0) { 61 | for (int i = 0; i < rx_msg.data_length_code; i++) { 62 | printf("0x%02x ", rx_msg.data[i]); 63 | } 64 | } else { 65 | printf("REMOTE REQUEST FRAME"); 66 | 67 | } 68 | printf("\n"); 69 | #endif 70 | 71 | for(int index=0;index 11 | #include 12 | #include 13 | #include 14 | #include "freertos/FreeRTOS.h" 15 | #include "freertos/task.h" 16 | #include "esp_log.h" 17 | #include "esp_system.h" 18 | #include "esp_tls.h" 19 | #include "esp_http_client.h" 20 | 21 | #include "cJSON.h" 22 | 23 | #include "twai.h" 24 | 25 | static const char *TAG = "HTTP"; 26 | 27 | extern QueueHandle_t xQueue_http_client; 28 | 29 | esp_err_t _http_event_handler(esp_http_client_event_t *evt) 30 | { 31 | static char *output_buffer; // Buffer to store response of http request from event handler 32 | static int output_len; // Stores number of bytes read 33 | switch(evt->event_id) { 34 | case HTTP_EVENT_ERROR: 35 | ESP_LOGD(TAG, "HTTP_EVENT_ERROR"); 36 | break; 37 | case HTTP_EVENT_ON_CONNECTED: 38 | ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED"); 39 | break; 40 | case HTTP_EVENT_HEADER_SENT: 41 | ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT"); 42 | break; 43 | case HTTP_EVENT_ON_HEADER: 44 | ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value); 45 | break; 46 | case HTTP_EVENT_ON_DATA: 47 | ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len); 48 | /* 49 | * Check for chunked encoding is added as the URL for chunked encoding used in this example returns binary data. 50 | * However, event handler can also be used in case chunked encoding is used. 51 | */ 52 | if (!esp_http_client_is_chunked_response(evt->client)) { 53 | // If user_data buffer is configured, copy the response into the buffer 54 | if (evt->user_data) { 55 | memcpy(evt->user_data + output_len, evt->data, evt->data_len); 56 | } else { 57 | if (output_buffer == NULL) { 58 | output_buffer = (char *) malloc(esp_http_client_get_content_length(evt->client)); 59 | output_len = 0; 60 | if (output_buffer == NULL) { 61 | ESP_LOGE(TAG, "Failed to allocate memory for output buffer"); 62 | return ESP_FAIL; 63 | } 64 | } 65 | memcpy(output_buffer + output_len, evt->data, evt->data_len); 66 | } 67 | output_len += evt->data_len; 68 | } 69 | 70 | break; 71 | case HTTP_EVENT_ON_FINISH: 72 | ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH"); 73 | if (output_buffer != NULL) { 74 | // Response is accumulated in output_buffer. Uncomment the below line to print the accumulated response 75 | // ESP_LOG_BUFFER_HEX(TAG, output_buffer, output_len); 76 | free(output_buffer); 77 | output_buffer = NULL; 78 | } 79 | output_len = 0; 80 | break; 81 | case HTTP_EVENT_DISCONNECTED: 82 | ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED"); 83 | int mbedtls_err = 0; 84 | esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL); 85 | if (err != 0) { 86 | if (output_buffer != NULL) { 87 | free(output_buffer); 88 | output_buffer = NULL; 89 | } 90 | output_len = 0; 91 | ESP_LOGI(TAG, "Last esp error code: 0x%x", err); 92 | ESP_LOGI(TAG, "Last mbedtls failure: 0x%x", mbedtls_err); 93 | } 94 | break; 95 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) 96 | case HTTP_EVENT_REDIRECT: 97 | ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT"); 98 | esp_http_client_set_header(evt->client, "From", "user@example.com"); 99 | esp_http_client_set_header(evt->client, "Accept", "text/html"); 100 | break; 101 | #endif 102 | } 103 | return ESP_OK; 104 | } 105 | 106 | #define MAX_HTTP_OUTPUT_BUFFER 2048 107 | 108 | static void http_rest_with_url(char * path, char * post_data) 109 | { 110 | ESP_LOGI(TAG, "path=[%s]", path); 111 | char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0}; 112 | /** 113 | * NOTE: All the configuration parameters for http_client must be spefied either in URL or as host and path parameters. 114 | * If host and path parameters are not set, query parameter will be ignored. In such cases, 115 | * query parameter should be specified in URL. 116 | * 117 | * If URL as well as host and path parameters are specified, values of host and path will be considered. 118 | */ 119 | #if 1 120 | esp_http_client_config_t config = { 121 | .host = CONFIG_WEB_SERVER, 122 | .port = CONFIG_WEB_PORT, 123 | .path = path, 124 | .event_handler = _http_event_handler, 125 | .user_data = local_response_buffer, // Pass address of local buffer to get response 126 | .disable_auto_redirect = true, 127 | }; 128 | #else 129 | // Same as above 130 | esp_http_client_config_t config = { 131 | .url = "http://http-server.local:8000/post", 132 | .event_handler = _http_event_handler, 133 | .user_data = local_response_buffer, // Pass address of local buffer to get response 134 | .disable_auto_redirect = true, 135 | }; 136 | #endif 137 | esp_http_client_handle_t client = esp_http_client_init(&config); 138 | 139 | // POST 140 | // no need to change url 141 | //esp_http_client_set_url(client, "http://192.168.10.43:8000/post"); 142 | esp_http_client_set_method(client, HTTP_METHOD_POST); 143 | esp_http_client_set_header(client, "Content-Type", "application/json"); 144 | esp_http_client_set_post_field(client, post_data, strlen(post_data)); 145 | esp_err_t err = esp_http_client_perform(client); 146 | if (err == ESP_OK) { 147 | ESP_LOGI(TAG, "HTTP POST Status = %d, content_length = %lld", 148 | esp_http_client_get_status_code(client), 149 | (int64_t)esp_http_client_get_content_length(client)); 150 | ESP_LOGI(TAG, "local_response_buffer=[%s]", local_response_buffer); 151 | } else { 152 | ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err)); 153 | } 154 | 155 | esp_http_client_cleanup(client); 156 | } 157 | 158 | 159 | void http_client_task(void *pvParameters) 160 | { 161 | ESP_LOGI(TAG, "Start HTTP Client: connect to http://%s:%d", CONFIG_WEB_SERVER, CONFIG_WEB_PORT); 162 | FRAME_t frameBuf; 163 | while (1) { 164 | xQueueReceive(xQueue_http_client, &frameBuf, portMAX_DELAY); 165 | ESP_LOGI(TAG, "canid=%"PRIx32" ext=%d topic=[%s]", frameBuf.canid, frameBuf.ext, frameBuf.topic); 166 | for(int i=0;i 11 | #include 12 | #include 13 | 14 | #include "freertos/FreeRTOS.h" 15 | #include "freertos/task.h" 16 | #include "freertos/queue.h" 17 | #include "freertos/semphr.h" 18 | #include "esp_log.h" 19 | #include "esp_err.h" 20 | #include "esp_vfs.h" 21 | #include "esp_spiffs.h" 22 | #include "esp_http_server.h" 23 | #include "esp_chip_info.h" 24 | #include "cJSON.h" 25 | #include "driver/twai.h" 26 | 27 | #include "twai.h" 28 | 29 | static const char *TAG = "SERVER"; 30 | //static SemaphoreHandle_t ctrl_task_sem; 31 | 32 | extern QueueHandle_t xQueue_twai_tx; 33 | 34 | #define SCRATCH_BUFSIZE (1024) 35 | 36 | typedef struct rest_server_context { 37 | char base_path[ESP_VFS_PATH_MAX + 1]; // Not used in this project 38 | char scratch[SCRATCH_BUFSIZE]; 39 | } rest_server_context_t; 40 | 41 | 42 | /* Handler for roor get handler */ 43 | static esp_err_t root_get_handler(httpd_req_t *req) 44 | { 45 | ESP_LOGI(TAG, "root_get_handler req->uri=[%s]", req->uri); 46 | 47 | /* Send empty chunk to signal HTTP response completion */ 48 | httpd_resp_sendstr_chunk(req, NULL); 49 | 50 | 51 | return ESP_OK; 52 | } 53 | 54 | /* Handler for getting system information handler */ 55 | // curl 'http://esp32-server.local:8000/api/system/info' | python -m json.tool 56 | static esp_err_t system_info_get_handler(httpd_req_t *req) 57 | { 58 | ESP_LOGI(TAG, "system_info_get_handler req->uri=[%s]", req->uri); 59 | httpd_resp_set_type(req, "application/json"); 60 | cJSON *root = cJSON_CreateObject(); 61 | esp_chip_info_t chip_info; 62 | esp_chip_info(&chip_info); 63 | cJSON_AddStringToObject(root, "version", IDF_VER); 64 | cJSON_AddNumberToObject(root, "cores", chip_info.cores); 65 | //const char *sys_info = cJSON_Print(root); 66 | char *sys_info = cJSON_Print(root); 67 | httpd_resp_sendstr(req, sys_info); 68 | // Buffers returned by cJSON_Print must be freed by the caller. 69 | // Please use the proper API (cJSON_free) rather than directly calling stdlib free. 70 | cJSON_free(sys_info); 71 | cJSON_Delete(root); 72 | return ESP_OK; 73 | } 74 | 75 | 76 | // Create array 77 | cJSON *Create_array_of_anything(cJSON **objects,int array_num) 78 | { 79 | cJSON *prev = 0; 80 | cJSON *root; 81 | root = cJSON_CreateArray(); 82 | for (int i=0;ichild=objects[i]; 85 | } else { 86 | prev->next=objects[i]; 87 | objects[i]->prev=prev; 88 | } 89 | prev=objects[i]; 90 | } 91 | return root; 92 | } 93 | 94 | char *JSON_Types(int type) { 95 | if (type == cJSON_Invalid) return ("cJSON_Invalid"); 96 | if (type == cJSON_False) return ("cJSON_False"); 97 | if (type == cJSON_True) return ("cJSON_True"); 98 | if (type == cJSON_NULL) return ("cJSON_NULL"); 99 | if (type == cJSON_Number) return ("cJSON_Number"); 100 | if (type == cJSON_String) return ("cJSON_String"); 101 | if (type == cJSON_Array) return ("cJSON_Array"); 102 | if (type == cJSON_Object) return ("cJSON_Object"); 103 | if (type == cJSON_Raw) return ("cJSON_Raw"); 104 | return NULL; 105 | } 106 | 107 | /* Handler for twai send handler */ 108 | // curl -X POST -H "Content-Type: application/json" -d '{"canid": 513, "frame": "standard", "data": [11, 12, 13, 14]}' http://esp32-server.local:8000/api/twai/send 109 | static esp_err_t twai_send_handler(httpd_req_t *req) 110 | { 111 | ESP_LOGI(TAG, "twai_send_handler req->uri=[%s]", req->uri); 112 | int total_len = req->content_len; 113 | int cur_len = 0; 114 | char *buf = ((rest_server_context_t *)(req->user_ctx))->scratch; 115 | int received = 0; 116 | if (total_len >= SCRATCH_BUFSIZE) { 117 | /* Respond with 500 Internal Server Error */ 118 | httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long"); 119 | return ESP_FAIL; 120 | } 121 | while (cur_len < total_len) { 122 | received = httpd_req_recv(req, buf + cur_len, total_len); 123 | if (received <= 0) { 124 | /* Respond with 500 Internal Server Error */ 125 | httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to post control value"); 126 | return ESP_FAIL; 127 | } 128 | cur_len += received; 129 | } 130 | buf[total_len] = '\0'; 131 | ESP_LOGI(TAG, "buf=[%s]", buf); 132 | 133 | 134 | bool parse = true; 135 | cJSON *root = cJSON_Parse(buf); 136 | 137 | // Search canid item 138 | int32_t canid = 0; 139 | cJSON* state = cJSON_GetObjectItem(root, "canid"); 140 | if (state) { 141 | canid = cJSON_GetObjectItem(root, "canid")->valueint; 142 | ESP_LOGI(TAG, "canid=%"PRIx32, canid); 143 | } else { 144 | ESP_LOGE(TAG, "canid item not found"); 145 | parse = false; 146 | } 147 | 148 | // Search frame item 149 | char frameStr[12]; 150 | uint16_t frame = 0; 151 | state = cJSON_GetObjectItem(root, "frame"); 152 | if (state) { 153 | strcpy(frameStr, cJSON_GetObjectItem(root,"frame")->valuestring); 154 | ESP_LOGI(TAG, "frameStr=[%s]", frameStr); 155 | if (strcmp(frameStr, "standard") != 0 && strcmp(frameStr, "extended") != 0 ) { 156 | ESP_LOGE(TAG, "frame item not correct"); 157 | parse = false; 158 | } else { 159 | if (strcmp(frameStr, "standard") == 0) frame = STANDARD_FRAME; 160 | if (strcmp(frameStr, "extended") == 0) frame = EXTENDED_FRAME; 161 | } 162 | } else { 163 | ESP_LOGE(TAG, "frame item not found"); 164 | parse = false; 165 | } 166 | 167 | // Search data item 168 | int16_t data_len; 169 | char data_value[8]; 170 | state = cJSON_GetObjectItem(root, "data"); 171 | if (state) { 172 | cJSON *data_array = cJSON_GetObjectItem(root,"data"); 173 | ESP_LOGI(TAG, "data_array->type=%s", JSON_Types(data_array->type)); 174 | if (data_array->type == cJSON_Array) { 175 | int data_array_size = cJSON_GetArraySize(data_array); 176 | ESP_LOGI(TAG, "data_array_size=%d", data_array_size); 177 | bool data_valid = true; 178 | data_len = data_array_size; 179 | if (data_array_size > 8) { 180 | ESP_LOGW(TAG, "Too many data arrays : %d", data_array_size); 181 | data_len = 8; 182 | } 183 | for (int i=0;itype=%s", JSON_Types(array->type)); 186 | uint16_t data_int = array->valueint; 187 | ESP_LOGI(TAG, "data_int[%d]=%x", i, data_int); 188 | if (data_int <= 0xff) { 189 | data_value[i] = data_int; 190 | } else { 191 | ESP_LOGE(TAG, "Too large data value : %x", data_int); 192 | data_valid = false; 193 | } 194 | } // end for 195 | if (data_valid == false) { 196 | parse = false; 197 | } 198 | } else { 199 | ESP_LOGE(TAG, "data item not array"); 200 | parse = false; 201 | } // end if 202 | 203 | 204 | } else { 205 | ESP_LOGE(TAG, "data item not found"); 206 | parse = false; 207 | } 208 | 209 | cJSON_Delete(root); 210 | 211 | // JSON parse success. Send twai data. 212 | if (parse) { 213 | ESP_LOGI(TAG, "twai_send_handler frame=%d canid=%"PRIx32" data_len=%d", frame, canid, data_len); 214 | ESP_LOG_BUFFER_HEX(TAG, data_value, data_len); 215 | twai_message_t tx_msg; 216 | tx_msg.extd = frame; 217 | tx_msg.ss = 1; 218 | tx_msg.self = 0; 219 | tx_msg.dlc_non_comp = 0; 220 | tx_msg.identifier = canid; 221 | tx_msg.data_length_code = data_len; 222 | for (int i=0;i 10 | #include 11 | #include 12 | #include 13 | 14 | #include "freertos/FreeRTOS.h" 15 | #include "freertos/task.h" 16 | #include "freertos/queue.h" 17 | #include "freertos/event_groups.h" 18 | #include "freertos/semphr.h" 19 | #include "esp_system.h" 20 | #include "esp_wifi.h" 21 | #include "esp_event.h" 22 | #include "esp_vfs.h" 23 | #include "nvs_flash.h" 24 | #include "esp_err.h" 25 | #include "esp_log.h" 26 | #include "esp_spiffs.h" 27 | #include "mdns.h" 28 | #include "lwip/dns.h" 29 | #include "driver/twai.h" // Update from V4.2 30 | 31 | #include "twai.h" 32 | 33 | static const char *TAG = "MAIN"; 34 | 35 | static const twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); 36 | 37 | #if CONFIG_CAN_BITRATE_25 38 | static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_25KBITS(); 39 | #define BITRATE "Bitrate is 25 Kbit/s" 40 | #elif CONFIG_CAN_BITRATE_50 41 | static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_50KBITS(); 42 | #define BITRATE "Bitrate is 50 Kbit/s" 43 | #elif CONFIG_CAN_BITRATE_100 44 | static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_100KBITS(); 45 | #define BITRATE "Bitrate is 100 Kbit/s" 46 | #elif CONFIG_CAN_BITRATE_125 47 | static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_125KBITS(); 48 | #define BITRATE "Bitrate is 125 Kbit/s" 49 | #elif CONFIG_CAN_BITRATE_250 50 | static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS(); 51 | #define BITRATE "Bitrate is 250 Kbit/s" 52 | #elif CONFIG_CAN_BITRATE_500 53 | static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS(); 54 | #define BITRATE "Bitrate is 500 Kbit/s" 55 | #elif CONFIG_CAN_BITRATE_800 56 | static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_800KBITS(); 57 | #define BITRATE "Bitrate is 800 Kbit/s" 58 | #elif CONFIG_CAN_BITRATE_1000 59 | static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_1MBITS(); 60 | #define BITRATE "Bitrate is 1 Mbit/s" 61 | #endif 62 | 63 | /* FreeRTOS event group to signal when we are connected*/ 64 | static EventGroupHandle_t s_wifi_event_group; 65 | 66 | /* The event group allows multiple bits for each event, but we only care about two events: 67 | * - we are connected to the AP with an IP 68 | * - we failed to connect after the maximum amount of retries */ 69 | #define WIFI_CONNECTED_BIT BIT0 70 | #define WIFI_FAIL_BIT BIT1 71 | 72 | static int s_retry_num = 0; 73 | 74 | QueueHandle_t xQueue_http_client; 75 | QueueHandle_t xQueue_twai_tx; 76 | 77 | TOPIC_t *publish; 78 | int16_t npublish; 79 | 80 | static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) 81 | { 82 | if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { 83 | esp_wifi_connect(); 84 | } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { 85 | if (s_retry_num < CONFIG_ESP_MAXIMUM_RETRY) { 86 | esp_wifi_connect(); 87 | s_retry_num++; 88 | ESP_LOGI(TAG, "retry to connect to the AP"); 89 | } else { 90 | xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); 91 | } 92 | ESP_LOGI(TAG,"connect to the AP fail"); 93 | } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { 94 | ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; 95 | ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); 96 | s_retry_num = 0; 97 | xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); 98 | } 99 | } 100 | 101 | esp_err_t wifi_init_sta() 102 | { 103 | s_wifi_event_group = xEventGroupCreate(); 104 | 105 | ESP_ERROR_CHECK(esp_netif_init()); 106 | ESP_ERROR_CHECK(esp_event_loop_create_default()); 107 | esp_netif_t *netif = esp_netif_create_default_wifi_sta(); 108 | assert(netif); 109 | 110 | #if CONFIG_STATIC_IP 111 | 112 | ESP_LOGI(TAG, "CONFIG_STATIC_IP_ADDRESS=[%s]",CONFIG_STATIC_IP_ADDRESS); 113 | ESP_LOGI(TAG, "CONFIG_STATIC_GW_ADDRESS=[%s]",CONFIG_STATIC_GW_ADDRESS); 114 | ESP_LOGI(TAG, "CONFIG_STATIC_NM_ADDRESS=[%s]",CONFIG_STATIC_NM_ADDRESS); 115 | 116 | /* Stop DHCP client */ 117 | ESP_ERROR_CHECK(esp_netif_dhcpc_stop(netif)); 118 | ESP_LOGI(TAG, "Stop DHCP Services"); 119 | 120 | /* Set STATIC IP Address */ 121 | esp_netif_ip_info_t ip_info; 122 | memset(&ip_info, 0 , sizeof(esp_netif_ip_info_t)); 123 | ip_info.ip.addr = ipaddr_addr(CONFIG_STATIC_IP_ADDRESS); 124 | ip_info.netmask.addr = ipaddr_addr(CONFIG_STATIC_NM_ADDRESS); 125 | ip_info.gw.addr = ipaddr_addr(CONFIG_STATIC_GW_ADDRESS);; 126 | esp_netif_set_ip_info(netif, &ip_info); 127 | 128 | /* 129 | I referred from here. 130 | https://www.esp32.com/viewtopic.php?t=5380 131 | if we should not be using DHCP (for example we are using static IP addresses), 132 | then we need to instruct the ESP32 of the locations of the DNS servers manually. 133 | Google publicly makes available two name servers with the addresses of 8.8.8.8 and 8.8.4.4. 134 | */ 135 | 136 | ip_addr_t d; 137 | d.type = IPADDR_TYPE_V4; 138 | d.u_addr.ip4.addr = 0x08080808; //8.8.8.8 dns 139 | dns_setserver(0, &d); 140 | d.u_addr.ip4.addr = 0x08080404; //8.8.4.4 dns 141 | dns_setserver(1, &d); 142 | 143 | #endif // CONFIG_STATIC_IP 144 | 145 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 146 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 147 | 148 | esp_event_handler_instance_t instance_any_id; 149 | esp_event_handler_instance_t instance_got_ip; 150 | ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, 151 | ESP_EVENT_ANY_ID, 152 | &event_handler, 153 | NULL, 154 | &instance_any_id)); 155 | ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, 156 | IP_EVENT_STA_GOT_IP, 157 | &event_handler, 158 | NULL, 159 | &instance_got_ip)); 160 | 161 | wifi_config_t wifi_config = { 162 | .sta = { 163 | .ssid = CONFIG_ESP_WIFI_SSID, 164 | .password = CONFIG_ESP_WIFI_PASSWORD, 165 | /* Setting a password implies station will connect to all security modes including WEP/WPA. 166 | * However these modes are deprecated and not advisable to be used. Incase your Access point 167 | * doesn't support WPA2, these mode can be enabled by commenting below line */ 168 | .threshold.authmode = WIFI_AUTH_WPA2_PSK, 169 | 170 | .pmf_cfg = { 171 | .capable = true, 172 | .required = false 173 | }, 174 | }, 175 | }; 176 | ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); 177 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); 178 | ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); 179 | ESP_ERROR_CHECK(esp_wifi_start()); 180 | 181 | /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum 182 | * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */ 183 | esp_err_t ret_value = ESP_OK; 184 | EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, 185 | WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, 186 | pdFALSE, 187 | pdFALSE, 188 | portMAX_DELAY); 189 | 190 | /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually 191 | * happened. */ 192 | if (bits & WIFI_CONNECTED_BIT) { 193 | ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD); 194 | } else if (bits & WIFI_FAIL_BIT) { 195 | ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD); 196 | ret_value = ESP_FAIL; 197 | } else { 198 | ESP_LOGE(TAG, "UNEXPECTED EVENT"); 199 | ret_value = ESP_FAIL; 200 | } 201 | 202 | /* The event will not be processed after unregister */ 203 | ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip)); 204 | ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id)); 205 | vEventGroupDelete(s_wifi_event_group); 206 | return ret_value; 207 | } 208 | 209 | void initialise_mdns(void) 210 | { 211 | //initialize mDNS 212 | ESP_ERROR_CHECK( mdns_init() ); 213 | //set mDNS hostname (required if you want to advertise services) 214 | ESP_ERROR_CHECK( mdns_hostname_set(CONFIG_MDNS_HOSTNAME) ); 215 | ESP_LOGI(TAG, "mdns hostname set to: [%s]", CONFIG_MDNS_HOSTNAME); 216 | 217 | #if 0 218 | //set default mDNS instance name 219 | ESP_ERROR_CHECK( mdns_instance_name_set("ESP32 with mDNS") ); 220 | #endif 221 | } 222 | 223 | esp_err_t mountSPIFFS(char * partition_label, char * base_path) { 224 | ESP_LOGI(TAG, "Initializing SPIFFS file system"); 225 | 226 | esp_vfs_spiffs_conf_t conf = { 227 | .base_path = base_path, 228 | .partition_label = partition_label, 229 | .max_files = 5, 230 | .format_if_mount_failed = true 231 | }; 232 | 233 | // Use settings defined above to initialize and mount SPIFFS filesystem. 234 | // Note: esp_vfs_spiffs_register is an all-in-one convenience function. 235 | esp_err_t ret = esp_vfs_spiffs_register(&conf); 236 | 237 | if (ret != ESP_OK) { 238 | if (ret == ESP_FAIL) { 239 | ESP_LOGE(TAG, "Failed to mount or format filesystem"); 240 | } else if (ret == ESP_ERR_NOT_FOUND) { 241 | ESP_LOGE(TAG, "Failed to find SPIFFS partition"); 242 | } else { 243 | ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); 244 | } 245 | return ret; 246 | } 247 | 248 | size_t total = 0, used = 0; 249 | ret = esp_spiffs_info(partition_label, &total, &used); 250 | if (ret != ESP_OK) { 251 | ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret)); 252 | } else { 253 | ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); 254 | DIR* dir = opendir(base_path); 255 | assert(dir != NULL); 256 | while (true) { 257 | struct dirent*pe = readdir(dir); 258 | if (!pe) break; 259 | ESP_LOGI(TAG, "d_name=%s d_ino=%d d_type=%x", pe->d_name,pe->d_ino, pe->d_type); 260 | } 261 | closedir(dir); 262 | } 263 | ESP_LOGI(TAG, "Mount SPIFFS filesystem"); 264 | return ret; 265 | } 266 | 267 | esp_err_t build_table(TOPIC_t **topics, char *file, int16_t *ntopic) 268 | { 269 | ESP_LOGI(TAG, "build_table file=%s", file); 270 | char line[128]; 271 | int _ntopic = 0; 272 | 273 | FILE* f = fopen(file, "r"); 274 | if (f == NULL) { 275 | ESP_LOGE(TAG, "Failed to open file for reading"); 276 | return ESP_FAIL; 277 | } 278 | while (1){ 279 | if ( fgets(line, sizeof(line) ,f) == 0 ) break; 280 | // strip newline 281 | char* pos = strchr(line, '\n'); 282 | if (pos) { 283 | *pos = '\0'; 284 | } 285 | ESP_LOGD(TAG, "line=[%s]", line); 286 | if (strlen(line) == 0) continue; 287 | if (line[0] == '#') continue; 288 | _ntopic++; 289 | } 290 | fclose(f); 291 | ESP_LOGI(TAG, "build_table _ntopic=%d", _ntopic); 292 | 293 | *topics = calloc(_ntopic, sizeof(TOPIC_t)); 294 | if (*topics == NULL) { 295 | ESP_LOGE(TAG, "Error allocating memory for topic"); 296 | return ESP_ERR_NO_MEM; 297 | } 298 | 299 | f = fopen(file, "r"); 300 | if (f == NULL) { 301 | ESP_LOGE(TAG, "Failed to open file for reading"); 302 | return ESP_FAIL; 303 | } 304 | 305 | char *ptr; 306 | int index = 0; 307 | while (1){ 308 | if ( fgets(line, sizeof(line) ,f) == 0 ) break; 309 | // strip newline 310 | char* pos = strchr(line, '\n'); 311 | if (pos) { 312 | *pos = '\0'; 313 | } 314 | ESP_LOGD(TAG, "line=[%s]", line); 315 | if (strlen(line) == 0) continue; 316 | if (line[0] == '#') continue; 317 | 318 | // Frame type 319 | ptr = strtok(line, ","); 320 | ESP_LOGD(TAG, "ptr=%s", ptr); 321 | if (strcmp(ptr, "S") == 0) { 322 | (*topics+index)->frame = 0; 323 | } else if (strcmp(ptr, "E") == 0) { 324 | (*topics+index)->frame = 1; 325 | } else { 326 | ESP_LOGE(TAG, "This line is invalid [%s]", line); 327 | continue; 328 | } 329 | 330 | // CAN ID 331 | uint32_t canid; 332 | ptr = strtok(NULL, ","); 333 | if(ptr == NULL) continue; 334 | ESP_LOGD(TAG, "ptr=%s", ptr); 335 | canid = strtol(ptr, NULL, 16); 336 | if (canid == 0) { 337 | ESP_LOGE(TAG, "This line is invalid [%s]", line); 338 | continue; 339 | } 340 | (*topics+index)->canid = canid; 341 | 342 | // mqtt topic 343 | char *sp; 344 | ptr = strtok(NULL, ","); 345 | if(ptr == NULL) { 346 | ESP_LOGE(TAG, "This line is invalid [%s]", line); 347 | continue; 348 | } 349 | ESP_LOGD(TAG, "ptr=[%s] strlen=%d", ptr, strlen(ptr)); 350 | sp = strstr(ptr,"#"); 351 | if (sp != NULL) { 352 | ESP_LOGE(TAG, "This line is invalid [%s]", line); 353 | continue; 354 | } 355 | sp = strstr(ptr,"+"); 356 | if (sp != NULL) { 357 | ESP_LOGE(TAG, "This line is invalid [%s]", line); 358 | continue; 359 | } 360 | (*topics+index)->topic = (char *)malloc(strlen(ptr)+1); 361 | strcpy((*topics+index)->topic, ptr); 362 | (*topics+index)->topic_len = strlen(ptr); 363 | index++; 364 | } 365 | fclose(f); 366 | *ntopic = index; 367 | return ESP_OK; 368 | } 369 | 370 | void dump_table(TOPIC_t *topics, int16_t ntopic) 371 | { 372 | for(int i=0;iframe, (topics+i)->canid, (topics+i)->topic, (topics+i)->topic_len); 375 | } 376 | 377 | } 378 | 379 | void http_client_task(void *pvParameters); 380 | void http_server_task(void *pvParameters); 381 | void twai_task(void *pvParameters); 382 | 383 | void app_main() 384 | { 385 | // Initialize NVS 386 | esp_err_t ret = nvs_flash_init(); 387 | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { 388 | ESP_ERROR_CHECK(nvs_flash_erase()); 389 | ret = nvs_flash_init(); 390 | } 391 | ESP_ERROR_CHECK(ret); 392 | 393 | // Initialize WiFi 394 | ESP_ERROR_CHECK(wifi_init_sta()); 395 | 396 | // Initialize mDNS 397 | initialise_mdns(); 398 | 399 | // Install and start TWAI driver 400 | ESP_LOGI(TAG, "%s",BITRATE); 401 | ESP_LOGI(TAG, "CTX_GPIO=%d",CONFIG_CTX_GPIO); 402 | ESP_LOGI(TAG, "CRX_GPIO=%d",CONFIG_CRX_GPIO); 403 | 404 | static const twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(CONFIG_CTX_GPIO, CONFIG_CRX_GPIO, TWAI_MODE_NORMAL); 405 | ESP_ERROR_CHECK(twai_driver_install(&g_config, &t_config, &f_config)); 406 | ESP_LOGI(TAG, "Driver installed"); 407 | ESP_ERROR_CHECK(twai_start()); 408 | ESP_LOGI(TAG, "Driver started"); 409 | 410 | // Mount SPIFFS 411 | char *partition_label = "storage"; 412 | char *base_path = "/spiffs"; 413 | ESP_ERROR_CHECK(mountSPIFFS(partition_label, base_path)); 414 | 415 | // Create Queue 416 | xQueue_http_client = xQueueCreate( 10, sizeof(FRAME_t) ); 417 | configASSERT( xQueue_http_client ); 418 | xQueue_twai_tx = xQueueCreate( 10, sizeof(twai_message_t) ); 419 | configASSERT( xQueue_twai_tx ); 420 | 421 | // build publish table 422 | ret = build_table(&publish, "/spiffs/can2http.csv", &npublish); 423 | if (ret != ESP_OK) { 424 | ESP_LOGE(TAG, "build publish table fail"); 425 | while(1) { vTaskDelay(1); } 426 | } 427 | dump_table(publish, npublish); 428 | 429 | /* Get the local IP address */ 430 | esp_netif_ip_info_t ip_info; 431 | ESP_ERROR_CHECK(esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info)); 432 | 433 | char cparam0[64]; 434 | sprintf(cparam0, IPSTR, IP2STR(&ip_info.ip)); 435 | xTaskCreate(http_server_task, "server", 1024*6, (void *)cparam0, 2, NULL); 436 | 437 | xTaskCreate(http_client_task, "client", 1024*6, NULL, 2, NULL); 438 | xTaskCreate(twai_task, "twai_rx", 1024*6, NULL, 2, NULL); 439 | } 440 | --------------------------------------------------------------------------------