diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b093467 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/isotp"] + path = lib/isotp + url = https://github.com/LibreSolar/isotp-c diff --git a/README.md b/README.md index 29f412f..7b73b4e 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,23 @@ The data can be accessed in the same way as described above. - Data access via HTTP JSON API - Publishing of monitoring data via WiFi to - Open Energy Monitor [Emoncms](https://emoncms.org/) - - MQTT sever (ToDo) + - MQTT server (ToDo) - Data logging on SD card (ToDo) ## Usage -### Build the Webapp +### Getting the firmware + +This firmware repository contains git submodules, so you need to clone (download) it by calling: + +``` +git clone --recursive https://github.com/LibreSolar/esp32-edge-firmware +``` + +Unfortunately, the green GitHub "Clone or download" button does not include submodules. If you cloned the repository already and want to pull the submodules, run `git submodule update --init --recursive`. + + +### Building the webapp To be able to build the esp32-edge-firmware you need to build the webapp first. To do so, go into the webapp folder @@ -59,7 +70,8 @@ and run npm install npm run build ``` -you are now able to build the firmware itself. + +You are now able to build the firmware itself. ### ESP-IDF toolchain @@ -72,7 +84,7 @@ After installation run the following commands: ### PlatformIO -You can use PlatformIO for easy bulding and flashing. Currently, ESP-IDF 4.0 support is still in beta phase, so it might not work out of the box. However, the setup in `platformio.ini` was adjusted to support the new ESP-IDF already. +You can use PlatformIO for easy building and flashing. However, the PlatformIO packages for ESP-IDF are not updated as frequently as the official repositories. ### Configuration diff --git a/lib/isotp b/lib/isotp new file mode 160000 index 0000000..9755e03 --- /dev/null +++ b/lib/isotp @@ -0,0 +1 @@ +Subproject commit 9755e0390921b18f188a01978ac7f03233af68fd diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index c6535fd..96a2330 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -10,6 +10,8 @@ set(app_sources "web_fs.c" "web_server.c" "provisioning.c" + "../lib/isotp/isotp.c" + "isotp_user.c" ) idf_component_register(SRCS ${app_sources} INCLUDE_DIRS ".") diff --git a/main/can.c b/main/can.c index 0678e27..daa37ef 100644 --- a/main/can.c +++ b/main/can.c @@ -21,11 +21,28 @@ #include "driver/can.h" #include "driver/gpio.h" +#include "../lib/isotp/isotp.h" +#include "../lib/isotp/isotp_defines.h" + +static const char *TAG = "can"; + bool update_bms_received = false; bool update_mppt_received = false; #if CONFIG_THINGSET_CAN +#define ISOTP_BUFSIZE 512 + +/* Alloc IsoTpLink statically in RAM */ +static IsoTpLink isotp_link; + +/* Alloc send and receive buffer statically in RAM */ +static uint8_t isotp_recv_buf[ISOTP_BUFSIZE]; +static uint8_t isotp_send_buf[ISOTP_BUFSIZE]; + +uint32_t can_addr_client = 0xF1; // this device +uint32_t can_addr_server = 0x14; // select MPPT or BMS + // buffer for JSON string generated from received data objects via CAN static char json_buf[500]; @@ -101,7 +118,8 @@ static int generate_json_string(char *buf, size_t len, DataObject *objs, size_t switch (objs[i].raw_data[0]) { case CAN_TS_T_TRUE: case CAN_TS_T_FALSE: - pos += snprintf(&buf[pos], len - pos, "%d", (objs[i].raw_data[0] == CAN_TS_T_TRUE) ? 1 : 0); + pos += snprintf(&buf[pos], len - pos, "%d", + (objs[i].raw_data[0] == CAN_TS_T_TRUE) ? 1 : 0); break; case CAN_TS_T_POS_INT32: value_abs = @@ -160,7 +178,6 @@ static int generate_json_string(char *buf, size_t len, DataObject *objs, size_t return pos; } - char *get_mppt_json_data() { generate_json_string(json_buf, sizeof(json_buf), @@ -184,69 +201,106 @@ void can_setup() gpio_set_level(CONFIG_GPIO_CAN_STB, 0); #endif - if (can_driver_install(&g_config, &t_config, &f_config) == ESP_OK) { - printf("CAN driver installed\n"); - } - else { - printf("Failed to install CAN driver\n"); + if (can_driver_install(&g_config, &t_config, &f_config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to install CAN driver"); return; } - if (can_start() == ESP_OK) { - printf("CAN driver started\n"); - } - else { - printf("Failed to start CAN driver\n"); + if (can_start() != ESP_OK) { + ESP_LOGE(TAG, "Failed to start CAN driver"); return; } + + /* Initialize link with the CAN ID we send with */ + isotp_init_link(&isotp_link, can_addr_server << 8 | can_addr_client | 0x1ada << 16, + isotp_send_buf, sizeof(isotp_send_buf), isotp_recv_buf, sizeof(isotp_recv_buf)); } void can_receive_task(void *arg) { can_message_t message; - //unsigned int msg_priority; // currently not used - unsigned int node_id; - unsigned int data_object_id; + unsigned int device_addr; + unsigned int data_node_id; + + uint8_t payload[500]; while (1) { if (can_receive(&message, pdMS_TO_TICKS(10000)) == ESP_OK) { - // ThingSet publication message format: https://thingset.github.io/spec/can - //msg_priority = message.identifier >> 26; - node_id = message.identifier & 0x000000FF; - data_object_id = (message.identifier >> 8) & 0x000000FF; - - if (node_id == 0) { - for (int i = 0; i < sizeof(data_obj_bms)/sizeof(DataObject); i++) { - if (data_obj_bms[i].id == data_object_id) { - memcpy(data_obj_bms[i].raw_data, message.data, message.data_length_code); - data_obj_bms[i].len = message.data_length_code; - } + /* checking for CAN ID used to receive ISO-TP frames */ + if (message.identifier == (can_addr_client << 8 | can_addr_server | 0x1ada << 16)) { + ESP_LOGI(TAG, "ISO TP msg part received"); + isotp_on_can_message(&isotp_link, message.data, message.data_length_code); + + /* process multiple frame transmissions and timeouts */ + isotp_poll(&isotp_link); + + /* extract received data */ + uint16_t out_size; + int ret = isotp_receive(&isotp_link, payload, sizeof(payload) - 1, &out_size); + if (ret == ISOTP_RET_OK) { + payload[out_size] = '\0'; + ESP_LOGI(TAG, "Received %d bytes via ISO-TP: %s", out_size, payload); + /* ToDo: handle received message */ } - update_bms_received = true; } - else if (node_id == 10) { - for (int i = 0; i < sizeof(data_obj_mppt)/sizeof(DataObject); i++) { - if (data_obj_mppt[i].id == data_object_id) { - memcpy(data_obj_mppt[i].raw_data, message.data, message.data_length_code); - data_obj_mppt[i].len = message.data_length_code; + else { + // ThingSet publication message format: https://thingset.github.io/spec/can + device_addr = message.identifier & 0x000000FF; + data_node_id = (message.identifier >> 8) & 0x0000FFFF; + + if (device_addr == 0) { + for (int i = 0; i < sizeof(data_obj_bms) / sizeof(DataObject); i++) { + if (data_obj_bms[i].id == data_node_id) { + memcpy(data_obj_bms[i].raw_data, message.data, + message.data_length_code); + data_obj_bms[i].len = message.data_length_code; + } } + update_bms_received = true; + } + else if (device_addr == 10) { + for (int i = 0; i < sizeof(data_obj_mppt) / sizeof(DataObject); i++) { + if (data_obj_mppt[i].id == data_node_id) { + memcpy(data_obj_mppt[i].raw_data, message.data, + message.data_length_code); + data_obj_mppt[i].len = message.data_length_code; + } + } + update_mppt_received = true; } - update_mppt_received = true; - } - printf("CAN msg node %u, data object 0x%.2x = 0x", - node_id, data_object_id); - if (!(message.flags & CAN_MSG_FLAG_RTR)) { - for (int i = 0; i < message.data_length_code; i++) { - printf("%.2x", message.data[i]); + printf("CAN device addr %u, data node 0x%.2x = 0x", device_addr, data_node_id); + if (!(message.flags & CAN_MSG_FLAG_RTR)) { + for (int i = 0; i < message.data_length_code; i++) { + printf("%.2x", message.data[i]); + } } + printf("\n"); } - printf("\n"); } } } +/* dummy task to send regular requests for testing */ +void isotp_task(void *arg) +{ + //uint8_t ts_request[] = { 0x01, 0x18, 0x70, 0xA0 }; + uint8_t ts_request[] = "?output"; + + while (1) { + + int ret = isotp_send(&isotp_link, ts_request, strlen((char *)ts_request)); + if (ISOTP_RET_OK == ret) { + printf("ISOTP Send OK\n"); + } else { + printf("ISOTP Send ERROR\n"); + } + + vTaskDelay(3000 / portTICK_PERIOD_MS); + } +} + #else /* not CONFIG_THINGSET_CAN */ char *get_mppt_json_data() diff --git a/main/can.h b/main/can.h index e007654..2d39af0 100644 --- a/main/can.h +++ b/main/can.h @@ -34,6 +34,11 @@ void can_setup(); */ void can_receive_task(void *arg); +/** + * Thread performing regular requests to other devices using ISO-TP + */ +void isotp_task(void *arg); + /** * Get data from MPPT connected via CAN bus and convert it to JSON * diff --git a/main/isotp_user.c b/main/isotp_user.c new file mode 100644 index 0000000..17c99f0 --- /dev/null +++ b/main/isotp_user.c @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2020 Martin Jäger / Libre Solar + */ + +#include +#include +#include + +#include "esp_system.h" +#include "esp_err.h" +#include "esp_log.h" + +#include "driver/can.h" +#include "driver/gpio.h" + +#include "../lib/isotp/isotp.h" + +/* + * required, this must send a single CAN message with the given arbitration + * ID (i.e. the CAN message ID) and data. The size will never be more than 8 + * bytes. + */ +int isotp_user_send_can(const uint32_t arbitration_id, + const uint8_t* data, const uint8_t size) +{ + can_message_t msg; + memcpy(msg.data, data, size); + msg.data_length_code = size; + msg.identifier = arbitration_id; + msg.flags = CAN_MSG_FLAG_EXTD; + return can_transmit(&msg, 0); +} + +/* + * required, return system tick, unit is millisecond + */ +uint32_t isotp_user_get_ms(void) +{ + return esp_timer_get_time() / 1000; +} + +/* + * optional, provide to receive debugging log messages + */ +void isotp_user_debug(const char* message, ...) +{ + va_list argp; + va_start(argp, message); + printf(message, argp); + va_end(argp); +} diff --git a/main/main.c b/main/main.c index 3cd1487..dd28e74 100644 --- a/main/main.c +++ b/main/main.c @@ -60,6 +60,8 @@ void app_main(void) can_setup(); xTaskCreatePinnedToCore(can_receive_task, "CAN_rx", 4096, NULL, RX_TASK_PRIO, NULL, 1); + xTaskCreatePinnedToCore(isotp_task, "CAN_isotp", 1024, + NULL, RX_TASK_PRIO, NULL, 1); #endif #if CONFIG_THINGSET_SERIAL