First Release

This commit is contained in:
nopnop2002
2021-06-30 06:52:43 +09:00
parent bbfdb0da2e
commit 0e9809c8b7
15 changed files with 1172 additions and 1 deletions
+12
View File
@@ -0,0 +1,12 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(can2http)
# Create a SPIFFS image from the contents of the 'font' directory
# that fits the partition named 'storage'. FLASH_IN_PROJECT indicates that
# the generated image should be flashed when the entire project is flashed to
# the target with 'idf.py -p PORT flash
spiffs_create_partition_image(storage csv FLASH_IN_PROJECT)
+16
View File
@@ -0,0 +1,16 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := can2mqtt
include $(IDF_PATH)/make/project.mk
# Create a SPIFFS image from the contents of the 'spiffs_image' directory
# that fits the partition named 'storage'. FLASH_IN_PROJECT indicates that
# the generated image should be flashed when the entire project is flashed to
# the target with 'make flash'.
SPIFFS_IMAGE_FLASH_IN_PROJECT := 1
$(eval $(call spiffs_create_partition_image,storage,csv))
+173 -1
View File
@@ -1,2 +1,174 @@
# esp-idf-can2http
CANbus to http bridge using esp32
CANbus to http bridge using esp32.
It's purpose is to be a bridge between a CAN-Bus and a HTTP-Server.
I inspired from [here](https://github.com/c3re/can2mqtt).
![esp-idf-can2http](https://user-images.githubusercontent.com/6020549/123870583-773bc300-d96d-11eb-8c07-27747faf7bb4.jpg)
# Software requirement
esp-idf v4.2-dev-2243 or later.
Use twai(Two-Wire Automotive Interface) driver instead of can driver.
# Hardware requirements
1. SN65HVD23x CAN-BUS Transceiver
|SN65HVD23x||ESP32|ESP32-S2||
|:-:|:-:|:-:|:-:|:-:|
|D(CTX)|--|GPIO21|GPIO20|(*1)|
|GND|--|GND|GND||
|Vcc|--|3.3V|3.3V||
|R(CRX)|--|GPIO22|GPIO21|(*1)|
|Vref|--|N/C|N/C||
|CANL|--|||To CAN Bus|
|CANH|--|||To CAN Bus|
|RS|--|GND|GND|(*2)|
(*1) You can change using menuconfig.
(*2) N/C for SN65HVD232
2. Termination resistance
I used 150 ohms.
# Test Circuit
```
+-----------+ +-----------+ +-----------+
| Atmega328 | | Atmega328 | | ESP32 |
| | | | | |
| Transmit | | Receive | | 21 22 |
+-----------+ +-----------+ +-----------+
| | | | | |
+-----------+ +-----------+ | |
| | | | | |
| MCP2515 | | MCP2515 | | |
| | | | | |
+-----------+ +-----------+ | |
| | | | | |
+-----------+ +-----------+ +-----------+
| | | | | D R |
| MCP2551 | | MCP2551 | | VP230 |
| H L | | H L | | H L |
+-----------+ +-----------+ +-----------+
| | | | | |
+--^^^--+ | | +--^^^--+
| R1 | | | | R2 |
|---+-------|-----+-------|-----+-------|---| BackBorn H
| | |
| | |
| | |
|-----------+-------------+-------------+---| BackBorn L
+--^^^--+:Terminaror register
R1:120 ohms
R2:150 ohms(Not working at 120 ohms)
```
__NOTE__
3V CAN Trasnceviers like VP230 are fully interoperable with 5V CAN trasnceviers like MCP2551.
Check [here](http://www.ti.com/lit/an/slla337/slla337.pdf).
# Installation for ESP32
```
git clone https://github.com/nopnop2002/esp-idf-can2http
cd esp-idf-can2htp
idf.py set-target esp32
idf.py menuconfig
idf.py flash
```
# Installation for ESP32-S2
```
git clone https://github.com/nopnop2002/esp-idf-can2http
cd esp-idf-can2http
idf.py set-target esp32s2
idf.py menuconfig
idf.py flash
```
# Configuration
![config-main](https://user-images.githubusercontent.com/6020549/123870635-92a6ce00-d96d-11eb-9b67-7b6a26e95fbd.jpg)
![config-app](https://user-images.githubusercontent.com/6020549/123870638-94709180-d96d-11eb-94da-b4860148be6a.jpg)
## CAN Setting
![config-can](https://user-images.githubusercontent.com/6020549/123870665-a05c5380-d96d-11eb-89b1-78a274bfd957.jpg)
## WiFi Setting
![config-wifi](https://user-images.githubusercontent.com/6020549/123870681-a81bf800-d96d-11eb-8637-66295408b055.jpg)
## HTTP Server Setting
![config-http](https://user-images.githubusercontent.com/6020549/123870716-b702aa80-d96d-11eb-8954-6ca365e78639.jpg)
# Definition from CANbus to HTTP
When CANbus data is received, it is sent by HTTP according to csv/can2http.csv.
The file can2http.csv has three columns.
In the first column you need to specify the CAN Frame type.
The CAN frame type is either S(Standard frame) or E(Extended frame).
In the second column you have to specify the CAN-ID as a __hexdecimal number__.
In the last column you have to specify the HTTP-Path.
Each CAN-ID and each HTTP-Path is allowed to appear only once in the whole file.
```
S,101,/post
E,101,/post
S,103,/post
E,103,/post
```
When a Standard CAN frame with ID 0x101 is received, it is POST by PATH of "/post".
When a Extended CAN frame with ID 0x101 is received, it is POST by PATH of "/post".
http://{HTTP-Server-IP}:{HTTP-Server-Port}/post/{CAN-Data}
# HTTP Server Using Tornado
```
sudo apt install python3-pip python3-setuptools python3-magic
python -m pip install -U pip
python -m pip install -U wheel
python -m pip install tornado
cd tornado
python can.py
```
![can2http-tornado](https://user-images.githubusercontent.com/6020549/123871778-18774900-d96f-11eb-95b6-df9713047c30.jpg)
# HTTP Server Using Flask
```
sudo apt install python3-pip python3-setuptools python3-magic
python -m pip install -U pip
python -m pip install -U wheel
python -m pip install -U Werkzeug
python -m pip install flask
cd flask
python can.py
```
![can2http-flask](https://user-images.githubusercontent.com/6020549/123871850-35ac1780-d96f-11eb-9d03-c1a0b547e9c8.jpg)
# List data Using Brouser
Open your browser and put the IP address in the address bar.
![can2http-browser](https://user-images.githubusercontent.com/6020549/123872025-71df7800-d96f-11eb-8832-8d9e1169c993.jpg)
# Troubleshooting
There is a module of SN65HVD230 like this.
![SN65HVD230-1](https://user-images.githubusercontent.com/6020549/80897499-4d204e00-8d34-11ea-80c9-3dc41b1addab.JPG)
There is a __120 ohms__ terminating resistor on the left side.
![SN65HVD230-22](https://user-images.githubusercontent.com/6020549/89281044-74185400-d684-11ea-9f55-830e0e9e6424.JPG)
I have removed the terminating resistor.
And I used a external resistance of __150 ohms__.
A transmission fail is fixed.
![SN65HVD230-33](https://user-images.githubusercontent.com/6020549/89280710-f7857580-d683-11ea-9b36-12e36910e7d9.JPG)
If the transmission fails, these are the possible causes.
- There is no receiving app on CanBus.
- The speed does not match the receiver.
- There is no terminating resistor on the CanBus.
- There are three terminating resistors on the CanBus.
- The resistance value of the terminating resistor is incorrect.
- Stub length in CAN bus is too long. See [here](https://e2e.ti.com/support/interface-group/interface/f/interface-forum/378932/iso1050-can-bus-stub-length).
+11
View File
@@ -0,0 +1,11 @@
#The file can2http.csv has three columns.
#In the first column you need to specify the CAN Frame type.
#The CAN frame type is either S(Standard frame) or E(Extended frame).
#In the second column you have to specify the CAN-ID as a __hexdecimal number__.
#In the last column you have to specify the HTTP-Path.
#Each CAN-ID and each HTTP-Path is allowed to appear only once in the whole file.
S,101,/post
E,101,/post
S,103,/post
E,103,/post
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 HTTP-Path.
6 #Each CAN-ID and each HTTP-Path is allowed to appear only once in the whole file.
7 S,101,/post
8 E,101,/post
9 S,103,/post
10 E,103,/post
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Simple REST Server
import sys
import json
import datetime
from flask import Flask, request
app = Flask(__name__)
database = []
@app.route("/")
def root():
global database
res = ""
for data in database:
print("{}".format(data))
#print("{}".format(len(data[2])))
res = res + "{} {:x} {} {}".format(data[0],data[1],data[2],data[3]) + "<br>"
#return "Hello World!"
return res
#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
@app.route("/post", methods=["POST"])
def post():
function = sys._getframe().f_code.co_name
#print("{}: request={}".format(function, request))
#print("{}: request.data={}".format(function, request.data))
dict = json.loads(request.data)
print("{} dict={}".format(function, dict))
items = []
now = datetime.datetime.now()
items.append(now.strftime("%Y/%m/%d %H:%M:%S"))
if ("canid" in dict):
print("{} canid=0x{:x}".format(function, dict['canid']))
items.append(dict['canid'])
if ("frame" in dict):
print("{} frame={}".format(function, dict['frame']))
items.append(dict['frame'])
if ("data" in dict):
print("{} data length={}".format(function, len(dict['data'])))
print("{} data={}".format(function, dict['data']))
items.append(dict['data'])
global database
#print("{} {} {}".format(type(database), len(database), database))
if(len(database) >= 20):
database.pop(0)
database.append(items)
data = json.dumps(['result', 'ok'])
return data
if __name__ == "__main__":
#app.run()
app.run(host='0.0.0.0', port=8000, debug=True)
+4
View File
@@ -0,0 +1,4 @@
set(COMPONENT_SRCS "main.c" "http_post.c" "twai.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()
+110
View File
@@ -0,0 +1,110 @@
menu "Application Configuration"
menu "CAN Setting"
choice CAN_BITRATE
prompt "CAN Bitrate"
default CAN_BITRATE_500
help
Select the CAN bitrate for the example.
config CAN_BITRATE_25
bool "BITRATE_25"
help
CAN bitrate is 25 Kbit/s.
config CAN_BITRATE_50
bool "BITRATE_50"
help
CAN bitrate is 50 Kbit/s.
config CAN_BITRATE_100
bool "BITRATE_100"
help
CAN bitrate is 100 Kbit/s.
config CAN_BITRATE_125
bool "BITRATE_125"
help
CAN bitrate is 125 Kbit/s.
config CAN_BITRATE_250
bool "BITRATE_250"
help
CAN bitrate is 250 Kbit/s.
config CAN_BITRATE_500
bool "BITRATE_500"
help
CAN bitrate is 500 Kbit/s.
config CAN_BITRATE_800
bool "BITRATE_800"
help
CAN bitrate is 800 Kbit/s.
config CAN_BITRATE_1000
bool "BITRATE_1000"
help
CAN bitrate is 1 Mbit/s.
endchoice
config CTX_GPIO
int "CTX GPIO number"
range 0 34
default 2 if IDF_TARGET_ESP32C3
default 20 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 21 if IDF_TARGET_ESP32
help
GPIO number (IOxx) to CTX.
Some GPIOs are used for other purposes (flash connections, etc.).
GPIOs 35-39 are input-only so cannot be used as outputs.
config CRX_GPIO
int "CRX GPIO number"
range 0 34
default 3 if IDF_TARGET_ESP32C3
default 21 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 22 if IDF_TARGET_ESP32
help
GPIO number (IOxx) to CRX.
Some GPIOs are used for other purposes (flash connections, etc.).
GPIOs 35-39 are input-only so cannot be used as outputs.
config ENABLE_PRINT
bool "Output the received CAN FRAME to STDOUT"
help
Output the received CAN FRAME to STDOUT.
endmenu
menu "WiFi Setting"
config ESP_WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) to connect to.
config ESP_WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) to connect to.
config ESP_MAXIMUM_RETRY
int "Maximum retry"
default 5
help
Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
endmenu
menu "HTTP Server Setting"
config WEB_SERVER
string "HTTP Server"
default "myhttpserver"
help
The host name or IP address of the HTTP server to use.
config WEB_PORT
int "HTTP Port"
default 8000
help
HTTP server port to use.
endmenu
endmenu
+4
View File
@@ -0,0 +1,4 @@
#
# Main Makefile. This is basically the same as a component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+188
View File
@@ -0,0 +1,188 @@
/* ESP HTTP Client Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_tls.h"
#include "esp_http_client.h"
#include "cJSON.h"
#include "twai.h"
static const char *TAG = "HTTP";
extern QueueHandle_t xQueue_http;
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
static char *output_buffer; // Buffer to store response of http request from event handler
static int output_len; // Stores number of bytes read
switch(evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
/*
* Check for chunked encoding is added as the URL for chunked encoding used in this example returns binary data.
* However, event handler can also be used in case chunked encoding is used.
*/
if (!esp_http_client_is_chunked_response(evt->client)) {
// If user_data buffer is configured, copy the response into the buffer
if (evt->user_data) {
memcpy(evt->user_data + output_len, evt->data, evt->data_len);
} else {
if (output_buffer == NULL) {
output_buffer = (char *) malloc(esp_http_client_get_content_length(evt->client));
output_len = 0;
if (output_buffer == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for output buffer");
return ESP_FAIL;
}
}
memcpy(output_buffer + output_len, evt->data, evt->data_len);
}
output_len += evt->data_len;
}
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
if (output_buffer != NULL) {
// Response is accumulated in output_buffer. Uncomment the below line to print the accumulated response
// ESP_LOG_BUFFER_HEX(TAG, output_buffer, output_len);
free(output_buffer);
output_buffer = NULL;
}
output_len = 0;
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
int mbedtls_err = 0;
esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL);
if (err != 0) {
if (output_buffer != NULL) {
free(output_buffer);
output_buffer = NULL;
}
output_len = 0;
ESP_LOGI(TAG, "Last esp error code: 0x%x", err);
ESP_LOGI(TAG, "Last mbedtls failure: 0x%x", mbedtls_err);
}
break;
}
return ESP_OK;
}
#define MAX_HTTP_OUTPUT_BUFFER 2048
static void http_rest_with_url(char * path, char * post_data)
{
char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
/**
* NOTE: All the configuration parameters for http_client must be spefied either in URL or as host and path parameters.
* If host and path parameters are not set, query parameter will be ignored. In such cases,
* query parameter should be specified in URL.
*
* If URL as well as host and path parameters are specified, values of host and path will be considered.
*/
esp_http_client_config_t config = {
.host = CONFIG_WEB_SERVER,
.port = CONFIG_WEB_PORT,
.path = path,
.event_handler = _http_event_handler,
.user_data = local_response_buffer, // Pass address of local buffer to get response
.disable_auto_redirect = true,
};
#if 0
// Same as above
esp_http_client_config_t config = {
.url = "http://192.168.10.43:8000/post",
.event_handler = _http_event_handler,
.user_data = local_response_buffer, // Pass address of local buffer to get response
.disable_auto_redirect = true,
};
#endif
esp_http_client_handle_t client = esp_http_client_init(&config);
// POST
// no need to change url
//esp_http_client_set_url(client, "http://192.168.10.43:8000/post");
esp_http_client_set_method(client, HTTP_METHOD_POST);
esp_http_client_set_header(client, "Content-Type", "application/json");
esp_http_client_set_post_field(client, post_data, strlen(post_data));
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP POST Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
ESP_LOGI(TAG, "local_response_buffer=[%s]", local_response_buffer);
} else {
ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
void http_client_task(void *pvParameters)
{
ESP_LOGI(TAG, "Start HTTP Client: http://%s:%d", CONFIG_WEB_SERVER, CONFIG_WEB_PORT);
FRAME_t frameBuf;
while (1) {
xQueueReceive(xQueue_http, &frameBuf, portMAX_DELAY);
ESP_LOGI(TAG, "canid=%x ext=%d topic=[%s]", frameBuf.canid, frameBuf.ext, frameBuf.topic);
for(int i=0;i<frameBuf.data_len;i++) {
ESP_LOGI(TAG, "DATA=%x", frameBuf.data[i]);
}
// build JSON string
cJSON *root;
root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "canid", frameBuf.canid);
if (frameBuf.ext == 0) {
cJSON_AddStringToObject(root, "frame", "standard");
} else {
cJSON_AddStringToObject(root, "frame", "extended");
}
cJSON *dataArray;
dataArray = cJSON_CreateArray();
cJSON_AddItemToObject(root, "data", dataArray);
for(int i=0;i<frameBuf.data_len;i++) {
cJSON *dataItem = NULL;
dataItem = cJSON_CreateNumber(frameBuf.data[i]);
cJSON_AddItemToArray(dataArray, dataItem);
}
char *json_string = cJSON_Print(root);
ESP_LOGI(TAG, "json_string\n%s",json_string);
cJSON_Delete(root);
//char *post_data = "{\"canid\":257}";
//http_rest_with_url(frameBuf.topic, post_data);
http_rest_with_url(frameBuf.topic, json_string);
} // end while
// Never reach here
vTaskDelete(NULL);
}
+380
View File
@@ -0,0 +1,380 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_vfs.h"
#include "nvs_flash.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_spiffs.h"
#include "driver/twai.h" // Update from V4.2
#include "twai.h"
#define TAG "MAIN"
static const twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
#if CONFIG_CAN_BITRATE_25
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_25KBITS();
#define BITRATE "Bitrate is 25 Kbit/s"
#elif CONFIG_CAN_BITRATE_50
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_50KBITS();
#define BITRATE "Bitrate is 50 Kbit/s"
#elif CONFIG_CAN_BITRATE_100
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_100KBITS();
#define BITRATE "Bitrate is 100 Kbit/s"
#elif CONFIG_CAN_BITRATE_125
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_125KBITS();
#define BITRATE "Bitrate is 125 Kbit/s"
#elif CONFIG_CAN_BITRATE_250
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS();
#define BITRATE "Bitrate is 250 Kbit/s"
#elif CONFIG_CAN_BITRATE_500
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
#define BITRATE "Bitrate is 500 Kbit/s"
#elif CONFIG_CAN_BITRATE_800
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_800KBITS();
#define BITRATE "Bitrate is 800 Kbit/s"
#elif CONFIG_CAN_BITRATE_1000
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_1MBITS();
#define BITRATE "Bitrate is 1 Mbit/s"
#endif
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
QueueHandle_t xQueue_http;
TOPIC_t *publish;
int16_t npublish;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < CONFIG_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
bool wifi_init_sta(void)
{
bool ret = false;
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = CONFIG_ESP_WIFI_SSID,
.password = CONFIG_ESP_WIFI_PASSWORD,
/* Setting a password implies station will connect to all security modes including WEP/WPA.
* However these modes are deprecated and not advisable to be used. Incase your Access point
* doesn't support WPA2, these mode can be enabled by commenting below line */
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD);
ret = true;
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
/* The event will not be processed after unregister */
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
vEventGroupDelete(s_wifi_event_group);
return ret;
}
esp_err_t mountSPIFFS(char * partition_label, char * base_path) {
ESP_LOGI(TAG, "Initializing SPIFFS file system");
esp_vfs_spiffs_conf_t conf = {
.base_path = base_path,
.partition_label = partition_label,
.max_files = 5,
.format_if_mount_failed = true
};
// Use settings defined above to initialize and mount SPIFFS filesystem.
// Note: esp_vfs_spiffs_register is an all-in-one convenience function.
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount or format filesystem");
} else if (ret == ESP_ERR_NOT_FOUND) {
ESP_LOGE(TAG, "Failed to find SPIFFS partition");
} else {
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
}
return ret;
}
size_t total = 0, used = 0;
ret = esp_spiffs_info(partition_label, &total, &used);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
} else {
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
DIR* dir = opendir(base_path);
assert(dir != NULL);
while (true) {
struct dirent*pe = readdir(dir);
if (!pe) break;
ESP_LOGI(TAG, "d_name=%s d_ino=%d d_type=%x", pe->d_name,pe->d_ino, pe->d_type);
}
closedir(dir);
}
ESP_LOGI(TAG, "Mount SPIFFS filesystem");
return ret;
}
esp_err_t build_table(TOPIC_t **topics, char *file, int16_t *ntopic)
{
ESP_LOGI(TAG, "build_table file=%s", file);
char line[128];
int _ntopic = 0;
FILE* f = fopen(file, "r");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for reading");
return ESP_FAIL;
}
while (1){
if ( fgets(line, sizeof(line) ,f) == 0 ) break;
// strip newline
char* pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGD(TAG, "line=[%s]", line);
if (strlen(line) == 0) continue;
if (line[0] == '#') continue;
_ntopic++;
}
fclose(f);
ESP_LOGI(TAG, "build_table _ntopic=%d", _ntopic);
*topics = calloc(_ntopic, sizeof(TOPIC_t));
if (*topics == NULL) {
ESP_LOGE(TAG, "Error allocating memory for topic");
return ESP_ERR_NO_MEM;
}
f = fopen(file, "r");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for reading");
return ESP_FAIL;
}
char *ptr;
int index = 0;
while (1){
if ( fgets(line, sizeof(line) ,f) == 0 ) break;
// strip newline
char* pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGD(TAG, "line=[%s]", line);
if (strlen(line) == 0) continue;
if (line[0] == '#') continue;
// Frame type
ptr = strtok(line, ",");
ESP_LOGD(TAG, "ptr=%s", ptr);
if (strcmp(ptr, "S") == 0) {
(*topics+index)->frame = 0;
} else if (strcmp(ptr, "E") == 0) {
(*topics+index)->frame = 1;
} else {
ESP_LOGE(TAG, "This line is invalid [%s]", line);
continue;
}
// CAN ID
uint32_t canid;
ptr = strtok(NULL, ",");
if(ptr == NULL) continue;
ESP_LOGD(TAG, "ptr=%s", ptr);
canid = strtol(ptr, NULL, 16);
if (canid == 0) {
ESP_LOGE(TAG, "This line is invalid [%s]", line);
continue;
}
(*topics+index)->canid = canid;
// mqtt topic
char *sp;
ptr = strtok(NULL, ",");
if(ptr == NULL) {
ESP_LOGE(TAG, "This line is invalid [%s]", line);
continue;
}
ESP_LOGD(TAG, "ptr=[%s] strlen=%d", ptr, strlen(ptr));
sp = strstr(ptr,"#");
if (sp != NULL) {
ESP_LOGE(TAG, "This line is invalid [%s]", line);
continue;
}
sp = strstr(ptr,"+");
if (sp != NULL) {
ESP_LOGE(TAG, "This line is invalid [%s]", line);
continue;
}
(*topics+index)->topic = (char *)malloc(strlen(ptr)+1);
strcpy((*topics+index)->topic, ptr);
(*topics+index)->topic_len = strlen(ptr);
index++;
}
fclose(f);
*ntopic = index;
return ESP_OK;
}
void dump_table(TOPIC_t *topics, int16_t ntopic)
{
for(int i=0;i<ntopic;i++) {
ESP_LOGI(pcTaskGetTaskName(0), "topics[%d] frame=%d canid=0x%x topic=[%s] topic_len=%d",
i, (topics+i)->frame, (topics+i)->canid, (topics+i)->topic, (topics+i)->topic_len);
}
}
void http_client_task(void *pvParameters);
void twai_task(void *pvParameters);
void app_main()
{
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// WiFi initialize
if (wifi_init_sta() == false) {
while(1) vTaskDelay(10);
}
// Install and start TWAI driver
ESP_LOGI(TAG, "%s",BITRATE);
ESP_LOGI(TAG, "CTX_GPIO=%d",CONFIG_CTX_GPIO);
ESP_LOGI(TAG, "CRX_GPIO=%d",CONFIG_CRX_GPIO);
static const twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(CONFIG_CTX_GPIO, CONFIG_CRX_GPIO, TWAI_MODE_NORMAL);
ESP_ERROR_CHECK(twai_driver_install(&g_config, &t_config, &f_config));
ESP_LOGI(TAG, "Driver installed");
ESP_ERROR_CHECK(twai_start());
ESP_LOGI(TAG, "Driver started");
// Mount SPIFFS
char *partition_label = "storage";
char *base_path = "/spiffs";
ret = mountSPIFFS(partition_label, base_path);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "mountSPIFFS fail");
while(1) { vTaskDelay(1); }
}
// Create Queue
xQueue_http = xQueueCreate( 10, sizeof(FRAME_t) );
configASSERT( xQueue_http );
// build publish table
ret = build_table(&publish, "/spiffs/can2http.csv", &npublish);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "build publish table fail");
while(1) { vTaskDelay(1); }
}
dump_table(publish, npublish);
xTaskCreate(http_client_task, "http", 1024*6, NULL, 2, NULL);
xTaskCreate(twai_task, "twai_rx", 1024*6, NULL, 2, NULL);
}
+103
View File
@@ -0,0 +1,103 @@
/* TWAI Network Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/twai.h" // Update from V4.2
#include "twai.h"
static const char *TAG = "TWAI";
extern QueueHandle_t xQueue_http;
//extern QueueHandle_t xQueue_twai_tx;
extern TOPIC_t *publish;
extern int16_t npublish;
void dump_table(TOPIC_t *topics, int16_t ntopic);
void twai_task(void *pvParameters)
{
ESP_LOGI(TAG,"task start");
dump_table(publish, npublish);
twai_message_t rx_msg;
FRAME_t frameBuf;
while (1) {
esp_err_t ret = twai_receive(&rx_msg, pdMS_TO_TICKS(1));
if (ret == ESP_OK) {
ESP_LOGD(TAG,"twai_receive identifier=0x%x flags=0x%x data_length_code=%d",
rx_msg.identifier, rx_msg.flags, rx_msg.data_length_code);
int ext = rx_msg.flags & 0x01;
int rtr = rx_msg.flags & 0x02;
ESP_LOGD(TAG, "ext=%x rtr=%x", ext, rtr);
#if CONFIG_ENABLE_PRINT
if (ext == 0) {
printf("Standard ID: 0x%03x ", rx_msg.identifier);
} else {
printf("Extended ID: 0x%08x", rx_msg.identifier);
}
printf(" DLC: %d Data: ", rx_msg.data_length_code);
if (rtr == 0) {
for (int i = 0; i < rx_msg.data_length_code; i++) {
printf("0x%02x ", rx_msg.data[i]);
}
} else {
printf("REMOTE REQUEST FRAME");
}
printf("\n");
#endif
for(int index=0;index<npublish;index++) {
if (publish[index].frame != ext) continue;
if (publish[index].canid == rx_msg.identifier) {
ESP_LOGI(TAG, "publish[%d] frame=%d canid=0x%x topic=[%s] topic_len=%d",
index, publish[index].frame, publish[index].canid, publish[index].topic, publish[index].topic_len);
strcpy(frameBuf.topic, publish[index].topic);
frameBuf.topic_len = publish[index].topic_len;
frameBuf.canid = rx_msg.identifier;
frameBuf.ext = ext;
frameBuf.rtr = rtr;
if (rtr == 0) {
frameBuf.data_len = rx_msg.data_length_code;
} else {
frameBuf.data_len = 0;
}
for(int i=0;i<frameBuf.data_len;i++) {
frameBuf.data[i] = rx_msg.data[i];
}
if (xQueueSend(xQueue_http, &frameBuf, portMAX_DELAY) != pdPASS) {
ESP_LOGE(TAG, "xQueueSend Fail");
}
}
}
} else if (ret == ESP_ERR_TIMEOUT) {
} else {
ESP_LOGE(TAG, "twai_receive Fail %s", esp_err_to_name(ret));
}
}
// never reach
vTaskDelete(NULL);
}
+17
View File
@@ -0,0 +1,17 @@
typedef struct {
char topic[64];
int16_t topic_len;
int32_t canid;
int16_t ext;
int16_t rtr;
int16_t data_len;
char data[8];
} FRAME_t;
typedef struct {
uint16_t frame;
uint32_t canid;
char * topic;
int16_t topic_len;
} TOPIC_t;
+6
View File
@@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
storage, data, spiffs, , 0xF0000,
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, 1M,
6 storage, data, spiffs, , 0xF0000,
+17
View File
@@ -0,0 +1,17 @@
#
# Serial flasher config
#
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
#
# Partition Table
#
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
#
# ESP32-specific
#
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Simple REST Server
import json
import sys
import datetime
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
database = []
class IndexHandler(tornado.web.RequestHandler):
def get(self):
global database
res = ""
for data in database:
print("{}".format(data))
#print("{}".format(len(data[2])))
res = res + "{} {:x} {} {}".format(data[0],data[1],data[2],data[3]) + "<br>"
self.write(res)
#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
class PostHandler(tornado.web.RequestHandler):
def post(self):
#print("{}".format(self.request.body))
function = sys._getframe().f_code.co_name
dict = tornado.escape.json_decode(self.request.body)
print("{} dict={}".format(function, dict))
items = []
now = datetime.datetime.now()
items.append(now.strftime("%Y/%m/%d %H:%M:%S"))
if ("canid" in dict):
print("{} canid=0x{:x}".format(function, dict['canid']))
items.append(dict['canid'])
if ("frame" in dict):
print("{} frame={}".format(function, dict['frame']))
items.append(dict['frame'])
if ("data" in dict):
print("{} data length={}".format(function, len(dict['data'])))
print("{} data={}".format(function, dict['data']))
items.append(dict['data'])
global database
#print("{} {} {}".format(type(database), len(database), database))
if(len(database) >= 20):
database.pop(0)
database.append(items)
self.write(json.dumps({"result":"ok"}))
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[
(r"/", IndexHandler),
(r"/post", PostHandler),
],debug=True
)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.current().start()