From 4868df6727c9af46a7796848b3d154e68671dd11 Mon Sep 17 00:00:00 2001 From: Tobias Diedrich Date: Mon, 9 Sep 2024 20:35:26 +0200 Subject: [PATCH] Implement receiving of CAN messages via MQTT This is mainly intended for debugging. --- include/BatteryCanReceiver.h | 11 +++ include/Configuration.h | 2 + include/defaults.h | 2 + src/BatteryCanReceiver.cpp | 103 ++++++++++++++++++++++++++ src/Configuration.cpp | 4 + webapp/src/locales/de.json | 6 ++ webapp/src/locales/en.json | 6 ++ webapp/src/types/BatteryConfig.ts | 2 + webapp/src/views/BatteryAdminView.vue | 39 ++++++++++ 9 files changed, 175 insertions(+) diff --git a/include/BatteryCanReceiver.h b/include/BatteryCanReceiver.h index 5f35c09aa..35b8fa52c 100644 --- a/include/BatteryCanReceiver.h +++ b/include/BatteryCanReceiver.h @@ -4,6 +4,7 @@ #include "Battery.h" #include #include +#include class BatteryCanReceiver : public BatteryProvider { public: @@ -26,4 +27,14 @@ class BatteryCanReceiver : public BatteryProvider { private: char const* _providerName = "Battery CAN"; + + enum CanInterface { + kTwai, + kMqtt, + } _canInterface; + String _canTopic; + + void postMessage(twai_message_t&& rx_message); + void onMqttMessageCAN(espMqttClientTypes::MessageProperties const& properties, + char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total); }; diff --git a/include/Configuration.h b/include/Configuration.h index 3b99c38bb..d77e0459a 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -136,12 +136,14 @@ struct BATTERY_CONFIG_T { bool Enabled; bool VerboseLogging; uint8_t Provider; + uint8_t CanInterface; uint8_t JkBmsInterface; uint8_t JkBmsPollingInterval; char MqttSocTopic[MQTT_MAX_TOPIC_STRLEN + 1]; char MqttSocJsonPath[BATTERY_JSON_MAX_PATH_STRLEN + 1]; char MqttVoltageTopic[MQTT_MAX_TOPIC_STRLEN + 1]; char MqttVoltageJsonPath[BATTERY_JSON_MAX_PATH_STRLEN + 1]; + char MqttCANTopic[MQTT_MAX_TOPIC_STRLEN + 1]; BatteryVoltageUnit MqttVoltageUnit; bool EnableDischargeCurrentLimit; float DischargeCurrentLimit; diff --git a/include/defaults.h b/include/defaults.h index 67b1d122e..f631fff8d 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -157,6 +157,8 @@ #define BATTERY_ENABLE_DISCHARGE_CURRENT_LIMIT false #define BATTERY_DISCHARGE_CURRENT_LIMIT 0 #define BATTERY_USE_BATTERY_REPORTED_DISCHARGE_CURRENT_LIMIT false +#define BATTERY_CAN_INTERFACE 0 +#define BATTERY_CAN_TOPIC "debug/battery/can/message" #define HUAWEI_ENABLED false #define HUAWEI_CAN_CONTROLLER_FREQUENCY 8000000UL diff --git a/src/BatteryCanReceiver.cpp b/src/BatteryCanReceiver.cpp index ff16fd043..fb4cd5624 100644 --- a/src/BatteryCanReceiver.cpp +++ b/src/BatteryCanReceiver.cpp @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later +#include "Configuration.h" #include "BatteryCanReceiver.h" +#include "MqttSettings.h" #include "MessageOutput.h" #include "PinMapping.h" #include @@ -12,6 +14,24 @@ bool BatteryCanReceiver::init(bool verboseLogging, char const* providerName) MessageOutput.printf("[%s] Initialize interface...\r\n", _providerName); + auto const& config = Configuration.get(); + _canTopic = config.Battery.MqttCANTopic; + _canInterface = static_cast(config.Battery.CanInterface); + if (_canInterface == kMqtt) { + MqttSettings.subscribe(_canTopic, 0/*QoS*/, + std::bind(&BatteryCanReceiver::onMqttMessageCAN, + this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4, + std::placeholders::_5, std::placeholders::_6) + ); + + if (_verboseLogging) { + MessageOutput.printf("BatteryCanReceiver: Subscribed to '%s' for CAN messages\r\n", + _canTopic.c_str()); + } + return true; + } + const PinMapping_t& pin = PinMapping.get(); MessageOutput.printf("[%s] Interface rx = %d, tx = %d\r\n", _providerName, pin.battery_rx, pin.battery_tx); @@ -82,6 +102,11 @@ bool BatteryCanReceiver::init(bool verboseLogging, char const* providerName) void BatteryCanReceiver::deinit() { + if (_canInterface == kMqtt) { + MqttSettings.unsubscribe(_canTopic); + return; + } + // Stop TWAI driver esp_err_t twaiLastResult = twai_stop(); switch (twaiLastResult) { @@ -111,6 +136,10 @@ void BatteryCanReceiver::deinit() void BatteryCanReceiver::loop() { + if (_canInterface == kMqtt) { + return; // Mqtt CAN messages are event-driven + } + // Check for messages. twai_receive is blocking when there is no data so we return if there are no frames in the buffer twai_status_info_t status_info; esp_err_t twaiLastResult = twai_get_status_info(&status_info); @@ -139,6 +168,80 @@ void BatteryCanReceiver::loop() return; } + postMessage(std::move(rx_message)); +} + + +void BatteryCanReceiver::onMqttMessageCAN(espMqttClientTypes::MessageProperties const& properties, + char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total) +{ + std::string value(reinterpret_cast(payload), len); + JsonDocument json; + + auto log = [this, topic](char const* format, auto&&... args) -> void { + MessageOutput.printf("[%s] Topic '%s': ", _providerName, topic); + MessageOutput.printf(format, args...); + MessageOutput.println(); + }; + + const DeserializationError error = deserializeJson(json, value); + if (error) { + log("cannot parse payload '%s' as JSON", value.c_str()); + return; + } + + if (json.overflowed()) { + log("payload too large to process as JSON"); + return; + } + + int canID = json["id"] | -1; + if (canID == -1) { + log("JSON is missing message id"); + return; + } + + twai_message_t rx_message = {}; + rx_message.identifier = canID; + int maxLen = sizeof(rx_message.data); + + JsonVariant canData = json["data"]; + if (canData.isNull()) { + log("JSON is missing message data"); + return; + } + + if (canData.is()) { + String strData = canData.as(); + int len = strData.length(); + if (len > maxLen) { + log("JSON data has more than %d elements", maxLen); + return; + } + + rx_message.data_length_code = len; + for (int i = 0; i < len; i++) { + rx_message.data[i] = strData[i]; + } + } else { + JsonArray arrayData = canData.as(); + int len = arrayData.size(); + if (len > maxLen) { + log("JSON data has more than %d elements", maxLen); + return; + } + + rx_message.data_length_code = len; + for (int i = 0; i < len; i++) { + rx_message.data[i] = arrayData[i]; + } + } + + postMessage(std::move(rx_message)); +} + +void BatteryCanReceiver::postMessage(twai_message_t&& rx_message) +{ if (_verboseLogging) { MessageOutput.printf("[%s] Received CAN message: 0x%04X -", _providerName, rx_message.identifier); diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 61ee7758d..3f09b5711 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -92,6 +92,8 @@ void ConfigurationClass::serializeBatteryConfig(BatteryConfig const& source, Jso target["mqtt_discharge_current_topic"] = config.Battery.MqttDischargeCurrentTopic; target["mqtt_discharge_current_json_path"] = config.Battery.MqttDischargeCurrentJsonPath; target["mqtt_amperage_unit"] = config.Battery.MqttAmperageUnit; + target["can_interface"] = config.Battery.CanInterface; + target["mqtt_can_topic"] = config.Battery.MqttCANTopic; } bool ConfigurationClass::write() @@ -380,6 +382,8 @@ void ConfigurationClass::deserializeBatteryConfig(JsonObject const& source, Batt strlcpy(target.MqttSocJsonPath, source["mqtt_soc_json_path"] | source["mqtt_json_path"] | "", sizeof(config.Battery.MqttSocJsonPath)); // mqtt_soc_json_path was previously saved as mqtt_json_path. Be nice and also try old key. strlcpy(target.MqttVoltageTopic, source["mqtt_voltage_topic"] | "", sizeof(config.Battery.MqttVoltageTopic)); strlcpy(target.MqttVoltageJsonPath, source["mqtt_voltage_json_path"] | "", sizeof(config.Battery.MqttVoltageJsonPath)); + target.CanInterface = source["can_interface"] | BATTERY_CAN_INTERFACE; + strlcpy(target.MqttCANTopic, source["mqtt_can_topic"] | BATTERY_CAN_TOPIC, sizeof(config.Battery.MqttCANTopic)); target.MqttVoltageUnit = source["mqtt_voltage_unit"] | BatteryVoltageUnit::Volts; target.EnableDischargeCurrentLimit = source["enable_discharge_current_limit"] | BATTERY_ENABLE_DISCHARGE_CURRENT_LIMIT; target.DischargeCurrentLimit = source["discharge_current_limit"] | BATTERY_DISCHARGE_CURRENT_LIMIT; diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 140b18b0d..6aa824e06 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -676,6 +676,12 @@ "batteryadmin": { "BatterySettings": "Batterie Einstellungen", "BatteryConfiguration": "Generelle Schnittstelleneinstellungen", + "CanConfiguration": "CAN Einstellungen", + "CanInterface": "Schnittstellentyp", + "CanInterfaceTwai": "CAN-Transceiver an der MCU", + "CanInterfaceMqtt": "MQTT Broker", + "CanMqttTopic": "Topic für CAN-Nachrichten", + "CanMqttTopicHint": "Nachrichten sollte im JSON-Format mit 'id' und 'data' feldern sein.", "EnableBattery": "Aktiviere Schnittstelle", "VerboseLogging": "@:base.VerboseLogging", "Provider": "Datenanbieter", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 9d1bebedc..69deee402 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -678,6 +678,12 @@ "batteryadmin": { "BatterySettings": "Battery Settings", "BatteryConfiguration": "General Interface Settings", + "CanConfiguration": "CAN Interface Settings", + "CanInterface": "Interface Type", + "CanInterfaceTwai": "CAN Transceiver on MCU", + "CanInterfaceMqtt": "MQTT Topic", + "CanMqttTopic": "CAN Message Topic", + "CanMqttTopicHint": "Messages should be JSON with 'id' and 'data' fields.", "EnableBattery": "Enable Interface", "VerboseLogging": "@:base.VerboseLogging", "Provider": "Data Provider", diff --git a/webapp/src/types/BatteryConfig.ts b/webapp/src/types/BatteryConfig.ts index a781b74a0..4134b83d7 100644 --- a/webapp/src/types/BatteryConfig.ts +++ b/webapp/src/types/BatteryConfig.ts @@ -4,6 +4,8 @@ export interface BatteryConfig { provider: number; jkbms_interface: number; jkbms_polling_interval: number; + can_interface: number; + mqtt_can_topic: string; mqtt_soc_topic: string; mqtt_soc_json_path: string; mqtt_voltage_topic: string; diff --git a/webapp/src/views/BatteryAdminView.vue b/webapp/src/views/BatteryAdminView.vue index 4a07357c7..8a58acb7b 100644 --- a/webapp/src/views/BatteryAdminView.vue +++ b/webapp/src/views/BatteryAdminView.vue @@ -33,6 +33,40 @@ + +
+ +
+ +
+
+ + + + +
+