first commit

This commit is contained in:
2026-06-12 21:45:31 +02:00
parent c19de04051
commit 75c15c71ec
27 changed files with 3104 additions and 2 deletions

8
.clang-format Normal file
View File

@@ -0,0 +1,8 @@
# We'll use defaults from the LLVM style, but with some modifications so that it's close to the CDT K&R style.
BasedOnStyle: LLVM
UseTab: Always
IndentWidth: 4
TabWidth: 4
BreakConstructorInitializers: AfterColon
IndentAccessModifiers: false
AccessModifierOffset: -4

3
.clangd Normal file
View File

@@ -0,0 +1,3 @@
CompileFlags:
CompilationDatabase: build
Remove: [-m*, -f*]

17
.cproject Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="org.eclipse.cdt.core.default.config.583059789">
<storageModule buildSystemId="org.eclipse.cdt.core.defaultConfigDataProvider" id="org.eclipse.cdt.core.default.config.583059789" moduleId="org.eclipse.cdt.core.settings" name="Configuration">
<externalSettings/>
<extensions/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.pathentry">
<pathentry kind="src" path=""/>
<pathentry excluding="**/CMakeFiles/**" kind="out" path="build"/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
</cproject>

1
.gitignore vendored
View File

@@ -86,3 +86,4 @@ dkms.conf
*.out
*.app
/build/

20
.project Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>uvr67_multical603_wifi_logger</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.cdt.core.cBuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.core.ccnature</nature>
<nature>com.espressif.idf.core.idfNature</nature>
</natures>
</projectDescription>

6
CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
# 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(app-template)

5
LICENSE Normal file
View File

@@ -0,0 +1,5 @@
Code in this repository 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.

View File

@@ -1,3 +1,11 @@
# uvr67_multical603_wifi_logger
ESP-IDF template app
====================
Logger for UVR67 Solar Collector controller and Kamstrup Multical603 over Modbus
This is a template application to be used with [Espressif IoT Development Framework](https://github.com/espressif/esp-idf).
Please check [ESP-IDF docs](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for getting started instructions.
*Code in this repository 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.*

24
main/CMakeLists.txt Normal file
View File

@@ -0,0 +1,24 @@
# See the build system documentation in IDF programming guide
# for more information about component CMakeLists.txt files.
idf_component_register(
SRCS main.c # list the source files of this component
# Canbus components
canbus_service/canbus_service.c
# Wifi components
wifi_service/wifi_service.c
wifi_service/http_client.c
# MQTT components
mqtt_service/mqtt_service.c
mqtt_service/mqtt_driver.c
mqtt_service/mqtt_protocol.c
INCLUDE_DIRS # optional, add here public include directories
PRIV_INCLUDE_DIRS # optional, add here private include directories
REQUIRES # optional, list the public requirements (component names)
PRIV_REQUIRES # optional, list the private requirements
)

14
main/Kconfig.projbuild Normal file
View File

@@ -0,0 +1,14 @@
# put here your custom config value
menu "Example Configuration"
config ESP_WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config ESP_WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
endmenu

View File

@@ -0,0 +1,96 @@
/*
* board_defines.h
*
* Created on: 23 Apr 2026
* Author: Christian Lind Vie Madsen
*/
#ifndef BOARD_DEFINES_H_
#define BOARD_DEFINES_H_
#include "driver/gpio.h"
/*
* I2C config and addresses
* */
#define I2C_CONFIG_SPEED 400000
#define I2C_ADDR_IOEXP_PCF8574 0x40
#define I2C_ADDR_ADC_ADS1015 (0x48 << 1)
/*
* General IO
* */
// Buttons
#define GPIO_BTN_ENT GPIO_NUM_6
#define GPIO_BTN_ESC GPIO_NUM_7
#define GPIO_BTN_DOWN GPIO_NUM_15
#define GPIO_BTN_UP GPIO_NUM_16
// LCD Display Specific
#define GPIO_LCD_CS GPIO_NUM_40
#define GPIO_LCD_RS GPIO_NUM_39
#define GPIO_LCD_RST GPIO_NUM_38
#define GPIO_LCD_BACKLIGHT GPIO_NUM_41
// PWM/Outputs
#define GPIO_OUTPUT0 GPIO_NUM_45
#define GPIO_OUTPUT1 GPIO_NUM_35
#define GPIO_OUTPUT2 GPIO_NUM_42
#define GPIO_LEDG GPIO_NUM_14
#define GPIO_LEDR GPIO_NUM_21
// DIP switch Input
#define GPIO_DIPSW0 GPIO_NUM_48
#define GPIO_DIPSW1 GPIO_NUM_47
// HW Revision
#define GPIO_HW_REVISION GPIO_NUM_9
/*
* Communication
* */
// I2C Internal
#define GPIO_I2C_INT_SDA GPIO_NUM_1
#define GPIO_I2C_INT_SCL GPIO_NUM_2
// I2C External (Expansion port)
#define GPIO_I2C_EXT_SDA GPIO_NUM_10
#define GPIO_I2C_EXT_SCL GPIO_NUM_11
// CAN-Bus
#define GPIO_CANBUS_TX GPIO_NUM_4
#define GPIO_CANBUS_RX GPIO_NUM_5
// MODBUS
#define GPIO_MODBUS_RX GPIO_NUM_17
#define GPIO_MODBUS_TX GPIO_NUM_18
#define GPIO_MODBUS_DE GPIO_NUM_8
// UART (GSM/GPS module)
#define GPIO_GSMGPS_RX GPIO_NUM_12
#define GPIO_GSMGPS_TX GPIO_NUM_13
// USB
#define GPIO_USB_N GPIO_NUM_19
#define GPIO_USB_P GPIO_NUM_20
// SPI (LCD)
#define GPIO_SPI_MOSI GPIO_NUM_37
#define GPIO_SPI_SCK GPIO_NUM_36
/*
* LCD Specs
* */
#define LCD_SPEC_HEIGHT (128) // Pixels
#define LCD_SPEC_WIDTH (160) // Pixels
#define MENU_SPACING 4
#endif /* BOARD_DEFINES_H_ */

View File

@@ -0,0 +1,170 @@
/*
* canbus_service.c
*
* Created on: 28 Apr 2026
* Author: Christian Lind Vie Madsen
*/
#include "canbus_service.h"
#include <stdio.h>
#include "../board_defines/board_defines.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "../mqtt_service/mqtt_driver.h"
#include "esp_log.h"
#define TAG "canbus_service.c"
#define RX_TASK_STACK 4096
#define RX_TASK_PRIORITY 9
#define TX_QUEUE_LEN 32
#define RX_QUEUE_LEN 32
#define MAX_RETRIES 3
#define TX_TIMEOUT_MS 10
// ---------------- STATE ----------------
static QueueHandle_t tx_queue = NULL;
//static can_rx_callback_t rx_callback = NULL;
// ---------------- DRIVER INIT ----------------
static esp_err_t can_driver_init(void)
{
twai_general_config_t g_config =
TWAI_GENERAL_CONFIG_DEFAULT(GPIO_CANBUS_TX, GPIO_CANBUS_RX, TWAI_MODE_NORMAL);
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_50KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
g_config.rx_queue_len = RX_QUEUE_LEN;
g_config.tx_queue_len = TX_QUEUE_LEN;
ESP_ERROR_CHECK(twai_driver_install(&g_config, &t_config, &f_config));
ESP_ERROR_CHECK(twai_start());
ESP_LOGI(TAG, "TWAI initialized");
return ESP_OK;
}
// ---------------- TX FUNCTION ----------------
static esp_err_t can_transmit_with_retry(const twai_message_t *msg)
{
int retries = 0;
while (retries < MAX_RETRIES) {
esp_err_t err = twai_transmit(msg, pdMS_TO_TICKS(TX_TIMEOUT_MS));
if (err == ESP_OK) {
return ESP_OK;
}
if (err == ESP_ERR_TIMEOUT) {
retries++;
vTaskDelay(pdMS_TO_TICKS(5));
} else {
ESP_LOGE(TAG, "TX failed: %s", esp_err_to_name(err));
return err;
}
}
ESP_LOGW(TAG, "TX dropped after retries");
return ESP_FAIL;
}
void publish_can_message(const twai_message_t *msg)
{
if (!msg) return;
char payload[160];
int pos = 0;
// header
pos += snprintf(payload + pos, sizeof(payload) - pos,
"{\"ts\":%lu,\"id\":%u,\"extd\":%d,\"rtr\":%d,\"dlc\":%d,\"data\":[",
(unsigned long)esp_log_timestamp(),
msg->identifier,
msg->extd,
msg->rtr,
msg->data_length_code
);
// data bytes (ONLY valid DLC bytes)
for (int i = 0; i < msg->data_length_code; i++) {
pos += snprintf(payload + pos, sizeof(payload) - pos,
"%u%s",
msg->data[i],
(i < msg->data_length_code - 1) ? "," : ""
);
}
// close JSON
snprintf(payload + pos, sizeof(payload) - pos, "]}");
mqtt_driver_publish("can/raw", payload);
ESP_LOGD(TAG, "CAN ID 0x%X DLC %d",
msg->identifier,
msg->data_length_code);
}
// ---------------- RX TASK ----------------
static void can_rx_task(void *arg)
{
twai_message_t msg;
while (1) {
if (twai_receive(&msg, portMAX_DELAY) == ESP_OK) {
// Pass to application
publish_can_message(&msg);
//futedo_can_decoder(&futedo_conf, &msg.data[0], msg.data_length_code, msg.identifier);
// Print data:
ESP_LOGI(TAG, "ID: 0x%x; [0]: 0x%x, [1]: 0x%x, [2]: 0x%x,[3]: 0x%x, [4]: 0x%x, [5]: 0x%x,",msg.identifier,msg.data[0],msg.data[1],msg.data[2],msg.data[3],msg.data[4],msg.data[5]);
}
}
}
// ---------------- TX TASK ----------------
static void can_tx_task(void *arg)
{
twai_message_t msg;
while (1) {
if (xQueueReceive(tx_queue, &msg, portMAX_DELAY) == pdTRUE) {
can_transmit_with_retry(&msg);
}
}
}
// ---------------- PUBLIC API ----------------
// Init service
void canbus_service_init()
{
can_driver_init();
tx_queue = xQueueCreate(TX_QUEUE_LEN, sizeof(twai_message_t));
if (!tx_queue) {
ESP_LOGE(TAG, "Failed to create TX queue");
return;
}
xTaskCreate(can_rx_task, "can_rx_task", RX_TASK_STACK, NULL, RX_TASK_PRIORITY, NULL);
xTaskCreate(can_tx_task, "can_tx_task", 2048, NULL, RX_TASK_PRIORITY - 1, NULL);
}
// Send message (non-blocking)
esp_err_t canbus_send(const twai_message_t *msg)
{
if (xQueueSend(tx_queue, msg, 0) != pdTRUE) {
ESP_LOGW(TAG, "TX queue full");
return ESP_FAIL;
}
return ESP_OK;
}

View File

@@ -0,0 +1,18 @@
/*
* canbus_service.h
*
* Created on: 28 Apr 2026
* Author: Christian Lind Vie Madsen
*/
#ifndef MAIN_CANBUS_SERVICE_CANBUS_SERVICE_H_
#define MAIN_CANBUS_SERVICE_CANBUS_SERVICE_H_
#include "driver/twai.h"
// ---------------- TYPES ----------------
typedef void (*can_rx_callback_t)(const twai_message_t *msg);
esp_err_t canbus_send(const twai_message_t *msg);
void canbus_service_init();
#endif /* MAIN_CANBUS_SERVICE_CANBUS_SERVICE_H_ */

29
main/main.c Normal file
View File

@@ -0,0 +1,29 @@
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include "canbus_service/canbus_service.h"
#include "wifi_service/wifi_service.c"
#include "mqtt_service/mqtt_driver.h"
#include "mqtt_service/mqtt_service.h"
void app_main(void)
{
canbus_service_init();
wifi_init_start();
// For now.. hang until we connect to wifi..
while(!wifi_IsConnected()){vTaskDelay(1);}
// MQTT init
mqtt_service_init();
uint32_t idx = 0;
while (true) {
//mqtt_driver_publish("cma/heartbeat", "Hej med dig!");
//printf("Hello from app_main!\n");
sleep(1);
}
}

478
main/mqtt_service/jsmn.h Normal file
View File

@@ -0,0 +1,478 @@
/*
* MIT License
*
* Copyright (c) 2010 Serge Zaitsev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef JSMN_H
#define JSMN_H
#include <stddef.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef JSMN_STATIC
#define JSMN_API static
#else
#define JSMN_API extern
#endif
/**
* JSON type identifier. Basic types are:
* o Object
* o Array
* o String
* o Other primitive: number, boolean (true/false) or null
*/
typedef enum {
JSMN_UNDEFINED = 0,
JSMN_OBJECT = 1 << 0,
JSMN_ARRAY = 1 << 1,
JSMN_STRING = 1 << 2,
JSMN_PRIMITIVE = 1 << 3
} jsmntype_t;
enum jsmnerr {
/* Not enough tokens were provided */
JSMN_ERROR_NOMEM = -1,
/* Invalid character inside JSON string */
JSMN_ERROR_INVAL = -2,
/* The string is not a full JSON packet, more bytes expected */
JSMN_ERROR_PART = -3
};
/**
* JSON token description.
* type type (object, array, string etc.)
* start start position in JSON data string
* end end position in JSON data string
*/
typedef struct jsmntok {
jsmntype_t type;
int start;
int end;
int size;
#ifdef JSMN_PARENT_LINKS
int parent;
#endif
} jsmntok_t;
/**
* JSON parser. Contains an array of token blocks available. Also stores
* the string being parsed now and current position in that string.
*/
typedef struct jsmn_parser {
unsigned int pos; /* offset in the JSON string */
unsigned int toknext; /* next token to allocate */
int toksuper; /* superior token node, e.g. parent object or array */
} jsmn_parser;
/**
* Create JSON parser over an array of tokens
*/
JSMN_API void jsmn_init(jsmn_parser *parser);
/**
* Run JSON parser. It parses a JSON data string into and array of tokens, each
* describing
* a single JSON object.
*/
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
jsmntok_t *tokens, const unsigned int num_tokens);
#ifndef JSMN_HEADER
/**
* Allocates a fresh unused token from the token pool.
*/
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *tok;
if (parser->toknext >= num_tokens) {
return NULL;
}
tok = &tokens[parser->toknext++];
tok->start = tok->end = -1;
tok->size = 0;
#ifdef JSMN_PARENT_LINKS
tok->parent = -1;
#endif
return tok;
}
/**
* Fills token type and boundaries.
*/
static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
const int start, const int end) {
token->type = type;
token->start = start;
token->end = end;
token->size = 0;
}
/**
* Fills next available token with JSON primitive.
*/
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
const size_t len, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *token;
int start;
start = parser->pos;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
switch (js[parser->pos]) {
#ifndef JSMN_STRICT
/* In strict mode primitive must be followed by "," or "}" or "]" */
case ':':
#endif
case '\t':
case '\r':
case '\n':
case ' ':
case ',':
case ']':
case '}':
goto found;
default:
/* to quiet a warning from gcc*/
break;
}
if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
#ifdef JSMN_STRICT
/* In strict mode primitive must be followed by a comma/object/array */
parser->pos = start;
return JSMN_ERROR_PART;
#endif
found:
if (tokens == NULL) {
parser->pos--;
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
parser->pos--;
return 0;
}
/**
* Fills next token with JSON string.
*/
static int jsmn_parse_string(jsmn_parser *parser, const char *js,
const size_t len, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *token;
int start = parser->pos;
/* Skip starting quote */
parser->pos++;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c = js[parser->pos];
/* Quote: end of string */
if (c == '\"') {
if (tokens == NULL) {
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
return 0;
}
/* Backslash: Quoted symbol expected */
if (c == '\\' && parser->pos + 1 < len) {
int i;
parser->pos++;
switch (js[parser->pos]) {
/* Allowed escaped symbols */
case '\"':
case '/':
case '\\':
case 'b':
case 'f':
case 'r':
case 'n':
case 't':
break;
/* Allows escaped symbol \uXXXX */
case 'u':
parser->pos++;
for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
i++) {
/* If it isn't a hex character we have an error */
if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
(js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
(js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
parser->pos = start;
return JSMN_ERROR_INVAL;
}
parser->pos++;
}
parser->pos--;
break;
/* Unexpected symbol */
default:
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
}
parser->pos = start;
return JSMN_ERROR_PART;
}
static int jsoneq(const char *json, jsmntok_t *tok, const char *s) {
if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
return 0;
}
return -1;
}
/**
* Parse JSON string and fill tokens.
*/
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
jsmntok_t *tokens, const unsigned int num_tokens) {
int r;
int i;
jsmntok_t *token;
int count = parser->toknext;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c;
jsmntype_t type;
c = js[parser->pos];
switch (c) {
case '{':
case '[':
count++;
if (tokens == NULL) {
break;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
return JSMN_ERROR_NOMEM;
}
if (parser->toksuper != -1) {
jsmntok_t *t = &tokens[parser->toksuper];
#ifdef JSMN_STRICT
/* In strict mode an object or array can't become a key */
if (t->type == JSMN_OBJECT) {
return JSMN_ERROR_INVAL;
}
#endif
t->size++;
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
}
token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
token->start = parser->pos;
parser->toksuper = parser->toknext - 1;
break;
case '}':
case ']':
if (tokens == NULL) {
break;
}
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
if (parser->toknext < 1) {
return JSMN_ERROR_INVAL;
}
token = &tokens[parser->toknext - 1];
for (;;) {
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
token->end = parser->pos + 1;
parser->toksuper = token->parent;
break;
}
if (token->parent == -1) {
if (token->type != type || parser->toksuper == -1) {
return JSMN_ERROR_INVAL;
}
break;
}
token = &tokens[token->parent];
}
#else
for (i = parser->toknext - 1; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
parser->toksuper = -1;
token->end = parser->pos + 1;
break;
}
}
/* Error if unmatched closing bracket */
if (i == -1) {
return JSMN_ERROR_INVAL;
}
for (; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
parser->toksuper = i;
break;
}
}
#endif
break;
case '\"':
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
if (r < 0) {
return r;
}
count++;
if (parser->toksuper != -1 && tokens != NULL) {
tokens[parser->toksuper].size++;
}
break;
case '\t':
case '\r':
case '\n':
case ' ':
break;
case ':':
parser->toksuper = parser->toknext - 1;
break;
case ',':
if (tokens != NULL && parser->toksuper != -1 &&
tokens[parser->toksuper].type != JSMN_ARRAY &&
tokens[parser->toksuper].type != JSMN_OBJECT) {
#ifdef JSMN_PARENT_LINKS
parser->toksuper = tokens[parser->toksuper].parent;
#else
for (i = parser->toknext - 1; i >= 0; i--) {
if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
if (tokens[i].start != -1 && tokens[i].end == -1) {
parser->toksuper = i;
break;
}
}
}
#endif
}
break;
#ifdef JSMN_STRICT
/* In strict mode primitives are: numbers and booleans */
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 't':
case 'f':
case 'n':
/* And they must not be keys of the object */
if (tokens != NULL && parser->toksuper != -1) {
const jsmntok_t *t = &tokens[parser->toksuper];
if (t->type == JSMN_OBJECT ||
(t->type == JSMN_STRING && t->size != 0)) {
return JSMN_ERROR_INVAL;
}
}
#else
/* In non-strict mode every unquoted value is a primitive */
default:
#endif
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
if (r < 0) {
return r;
}
count++;
if (parser->toksuper != -1 && tokens != NULL) {
tokens[parser->toksuper].size++;
}
break;
#ifdef JSMN_STRICT
/* Unexpected char in strict mode */
default:
return JSMN_ERROR_INVAL;
#endif
}
}
if (tokens != NULL) {
for (i = parser->toknext - 1; i >= 0; i--) {
/* Unmatched opened object or array */
if (tokens[i].start != -1 && tokens[i].end == -1) {
return JSMN_ERROR_PART;
}
}
}
return count;
}
/**
* Creates a new parser based over a given buffer with an array of tokens
* available.
*/
JSMN_API void jsmn_init(jsmn_parser *parser) {
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
}
#endif /* JSMN_HEADER */
#ifdef __cplusplus
}
#endif
#endif /* JSMN_H */

View File

@@ -0,0 +1,91 @@
/*
* mqtt_driver.c
*
* Created on: 26 May 2026
* Author: Christian Lind Vie Madsen
*/
#include "mqtt_driver.h"
#include "mqtt_client.h"
// Not important libs..
#include "esp_log.h"
static esp_mqtt_client_handle_t client;
uint8_t valid = 0;
static void mqtt_event_handler(void *handler_args,
esp_event_base_t base,
int32_t event_id,
void *event_data)
{
if (event_id == MQTT_EVENT_CONNECTED) {
ESP_LOGI("MQTT", "Connected!");
valid = 1;
}
}
int mqtt_driver_publish(const char *topic, const char *data){
if(!valid)return 1;
esp_mqtt_client_publish(
client,
topic, // topic
data, // payload
0, // length (0 = auto strlen)
1, // QoS (0,1,2)
0 // retain flag
);
return 0;
}
const char emqx_ca_cert[] = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----
)EOF";
void mqtt_start(void)
{
esp_mqtt_client_config_t cfg = {
.uri = "mqtts://s960411f.ala.eu-central-1.emqxsl.com:8883",
.username = "cma",
.password = "testcma",
.cert_pem = emqx_ca_cert,
};
client = esp_mqtt_client_init(&cfg);
esp_mqtt_client_register_event(
client,
ESP_EVENT_ANY_ID,
mqtt_event_handler,
NULL
);
esp_mqtt_client_start(client);
}

View File

@@ -0,0 +1,15 @@
/*
* mqtt_driver.h
*
* Created on: 26 May 2026
* Author: Christian Lind Vie Madsen
*/
#ifndef MAIN_MQTT_SERVICE_MQTT_DRIVER_H_
#define MAIN_MQTT_SERVICE_MQTT_DRIVER_H_
void mqtt_start(void);
int mqtt_driver_publish(const char *topic, const char *data);
#endif /* MAIN_MQTT_SERVICE_MQTT_DRIVER_H_ */

View File

@@ -0,0 +1,42 @@
/*
* mqtt_protocol.c
*
* Created on: 28 May 2026
* Author: Christian Lind Vie Madsen
*/
#include "mqtt_protocol.h"
char *topics[5] = {
};
void mqtt_protocol_GenerateTopic(char *buf, size_t size, mqtt_protocol_msg_t msg_type){
snprintf(buf, size,"SV/v1/1234/%d",msg_type+1);
}
void mqtt_protocol_heartbeat(char *buf,
size_t size,
const char *trailerId,
const char *timestamp,
const char *lat,
const char *lon)
{
snprintf(buf, size,
"{"
"\"timestamp\":\"%s\","
"\"location\":{"
"\"latitude\":\"%s\","
"\"longitude\":\"%s\""
"}"
"}",
timestamp,
lat,
lon
);
}

View File

@@ -0,0 +1,29 @@
/*
* mqtt_protocol.h
*
* Created on: 28 May 2026
* Author: Christian Lind Vie Madsen
*/
#ifndef MAIN_MQTT_SERVICE_MQTT_PROTOCOL_H_
#define MAIN_MQTT_SERVICE_MQTT_PROTOCOL_H_
#include <stdio.h>
#include <stdint.h>
typedef enum{
MQTT_PROTOCOL_HEARTBEAT = 0,
}mqtt_protocol_msg_t;
void mqtt_protocol_GenerateTopic(char *buf, size_t size, mqtt_protocol_msg_t msg_type);
void mqtt_protocol_heartbeat(char *buf,
size_t size,
const char *trailerId,
const char *timestamp,
const char *lat,
const char *lon);
#endif /* MAIN_MQTT_SERVICE_MQTT_PROTOCOL_H_ */

View File

@@ -0,0 +1,127 @@
/*
* mqtt_service.c
*
* Created on: 28 May 2026
* Author: Christian Lind Vie Madsen
*/
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include "mqtt_service.h"
#include "mqtt_driver.h"
#include "mqtt_protocol.h"
// Not important libs..
#include "esp_log.h"
#define TAG "mqtt_service.c"
#define HOURSTOMS(hours) ((hours) * 60 * 60 * 1000)
TaskHandle_t mqtt_service_handle;
QueueHandle_t mqtt_queue;
typedef enum {
MSG_STAT = 0,
}mqtt_header_t;
typedef struct {
mqtt_header_t header;
}mqtt_service_msg_t;
/*
static void sRepeatedMqttSend(){
static uint32_t stimer = 0;
// Send Heartbeat
if( (stimer++ % HOURSTOMS(1)10000) == 0){
char topic[124] = {0};
char data[124] = {0};
char lat[24] = {0};
char lon[24] = {0};
mqtt_protocol_GenerateTopic(topic, sizeof(topic), MQTT_PROTOCOL_HEARTBEAT);
mqtt_protocol_heartbeat(data, sizeof(data), "1234", "12", lat, lon);
// For now.. just send it with GSM..
if(gps_gsm_mqtt_publish(topic, data)){
// If GSM is not available, then send over WIFI.
//mqtt_driver_publish(topic,data);
}
//ESP_LOGI(TAG,"Sending MQTT Heartbeat");
}
}*/
static void mqtt_task(void *pv){
// Init MQTT WIFI
mqtt_start();
mqtt_service_msg_t msg;
while(1){
vTaskDelay(1);
if (xQueueReceive(mqtt_queue,
&msg,
pdMS_TO_TICKS(1)))
{
}else{
//sRepeatedMqttSend();
}
}
}
int mqtt_service_init(){
// Create IO request queue
mqtt_queue = xQueueCreate(
10,
sizeof(mqtt_service_msg_t)
);
if (mqtt_queue == NULL)
{
ESP_LOGE(TAG, "Failed to create IO queue");
return 1;
}
// Create the mqtt task
xTaskCreate(
mqtt_task, // Task function
"mqtt_service", // Name
4096, // Stack size
NULL, // Parameters
1, // Priority
&mqtt_service_handle // Task handle
);
ESP_LOGI(TAG, "mqtt_service task started");
return 0;
}

View File

@@ -0,0 +1,15 @@
/*
* mqtt_service.h
*
* Created on: 28 May 2026
* Author: Christian Lind Vie Madsen
*/
#ifndef MAIN_MQTT_SERVICE_MQTT_SERVICE_H_
#define MAIN_MQTT_SERVICE_MQTT_SERVICE_H_
int mqtt_service_init();
#endif /* MAIN_MQTT_SERVICE_MQTT_SERVICE_H_ */

View File

@@ -0,0 +1,103 @@
/*
* web_client.c
*
* Created on: 4. aug. 2023
* Author: OZ1CM
*/
#include "http_client.h"
#include "esp_log.h"
#define TAG "http_client"
esp_err_t _http_event_handle(esp_http_client_event_t *evt)
{
switch(evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGI(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT");
// printf("%.*s", evt->data_len, (char*)evt->data);
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER");
printf("%.*s", evt->data_len, (char*)evt->data);
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
//if (!esp_http_client_is_chunked_response(evt->client)) {
ESP_LOGI(TAG,"Response from server: %s",(char*)evt->data);
//}
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH");
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
break;
default:
break;
}
return ESP_OK;
}
int http_client_sendUrl(http_client_t *inst,char *url){
esp_http_client_config_t config = {
.url = url,
.method = HTTP_METHOD_GET,
//.event_handler = _http_event_handle,
};
inst->client = esp_http_client_init(&config);
esp_err_t err;
err = esp_http_client_perform(inst->client);
// some error logging..
if(err == ESP_OK){
int status_code = esp_http_client_get_status_code(inst->client);
switch (status_code){
case 200:
ESP_LOGI(TAG, "Message sent Successfully");
esp_http_client_cleanup(inst->client);
return 0;
break;
default:
ESP_LOGE(TAG, "Message sent Failed");
esp_http_client_cleanup(inst->client);
return 1;
break;
}
}else{
ESP_LOGE(TAG, "Message sent Failed");
esp_http_client_cleanup(inst->client);
}
// ESP_LOGI(TAG, "URL: %s", inst->client->);
//ESP_LOGI(TAG,"hejmeddig");
//esp_http_client_set_header(inst->client, "Content-Type", "application/x-www-form-urlencoded");
return 1;
}

View File

@@ -0,0 +1,24 @@
/*
* web_client.h
*
* Created on: 4. aug. 2023
* Author: OZ1CM
*/
#ifndef MAIN_INCLUDE_HTTP_CLIENT_H_
#define MAIN_INCLUDE_HTTP_CLIENT_H_
#include "esp_http_client.h"
typedef struct {
// Config
esp_http_client_config_t config;
esp_http_client_handle_t client;
}http_client_t;
int http_client_sendUrl(http_client_t *inst,char *url);
#endif /* MAIN_INCLUDE_HTTP_CLIENT_H_ */

View File

@@ -0,0 +1,185 @@
/*
* wifi_service.c
*
* Created on: 26 May 2026
* Author: Christian Lind Vie Madsen
*/
#include "wifi_service.h"
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "../board_defines/wifi_info.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#define ESP_HOSTNAME "ws_wifi_client"
#define EXAMPLE_ESP_MAXIMUM_RETRY 5
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
static int s_retry_num = 0;
/* 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
#define TAG "wifi_driver"
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 < EXAMPLE_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);
}
}
/**
* @brief Get RSSI of currently connected WiFi AP
* @return RSSI in dBm (e.g. -45), or 0 if not connected / error
*/
int wifi_get_rssi(void)
{
wifi_ap_record_t ap_info;
esp_err_t err = esp_wifi_sta_get_ap_info(&ap_info);
if (err != ESP_OK) {
return -113; // not connected or error
}
return ap_info.rssi;
}
int wifi_IsConnected(){
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) return 1;
return 0;
}
void wifi_checkConnection(){
static uint32_t runtimer = 0;
if((runtimer++ % 50) == 0){
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 ");
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID");
ESP_ERROR_CHECK(esp_wifi_stop());
vTaskDelay(1000);
ESP_ERROR_CHECK(esp_wifi_start());
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
}
}
int wifi_scan_to_buffer(char ssid[][33], int *rssi, int max_len)
{
uint16_t ap_count = 0;
wifi_ap_record_t ap_info[25];
memset(ap_info, 0, sizeof(ap_info));
ESP_ERROR_CHECK(esp_wifi_scan_start(NULL, true));
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));
if (ap_count > max_len) {
ap_count = max_len;
}
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_info));
for (int i = 0; i < ap_count; i++) {
strncpy(ssid[i], (char *)ap_info[i].ssid, 13);
ssid[i][13] = '\0'; // safety null-terminate
rssi[i] = ap_info[i].rssi;
}
return ap_count;
}
void wifi_init_start(void) {
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();
}
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *netif = esp_netif_create_default_wifi_sta();
esp_netif_set_hostname(netif, ESP_HOSTNAME);
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 = SSID,
.password = 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, }, };
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_sta finished.");
}

View File

@@ -0,0 +1,20 @@
/*
* wifi_service.h
*
* Created on: 26 May 2026
* Author: Christian Lind Vie Madsen
*/
#ifndef MAIN_WIFI_SERVICE_WIFI_SERVICE_H_
#define MAIN_WIFI_SERVICE_WIFI_SERVICE_H_
int wifi_scan_to_buffer(char ssid[][33], int *rssi, int max_len);
void wifi_init_start(void);
int wifi_IsConnected();
int wifi_get_rssi(void);
#endif /* MAIN_WIFI_SERVICE_WIFI_SERVICE_H_ */

View File

@@ -0,0 +1,144 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jun 12 21:31:53 2026
@author: chris
"""
import json
import time
from collections import defaultdict, deque
import numpy as np
import paho.mqtt.client as mqtt
import matplotlib.pyplot as plt
MQTT_BROKER = "s960411f.ala.eu-central-1.emqxsl.com"
MQTT_PORT = 8883
MQTT_USER = "cma"
MQTT_PASS = "cmatest"
TOPIC = "can/raw"
# store last N samples per CAN ID
history = defaultdict(lambda: deque(maxlen=200))
# candidate sensor signals
candidates = defaultdict(list)
# ----------------------------
# MQTT callback
# ----------------------------
def on_message(client, userdata, msg):
try:
data = json.loads(msg.payload.decode())
can_id = data["id"]
dlc = data["dlc"]
payload = data["data"]
timestamp = data.get("ts", time.time())
history[can_id].append((timestamp, payload))
except Exception as e:
print("Parse error:", e)
# ----------------------------
# Find best "sensor-like" byte index
# ----------------------------
def detect_sensor_like_signals():
sensor_map = {}
for can_id, samples in history.items():
if len(samples) < 20:
continue
arr = np.array([s[1][:8] for s in samples])
# check each byte position
for i in range(arr.shape[1]):
series = arr[:, i]
# ignore constant / noisy bytes
if np.std(series) < 0.5:
continue
# sensor heuristic:
# - slowly varying
# - not binary toggling
# - not full random noise
diff = np.abs(np.diff(series))
stability_score = np.mean(diff)
if 0.01 < stability_score < 5.0:
sensor_map.setdefault(can_id, []).append((i, stability_score))
return sensor_map
# ----------------------------
# Print likely sensors
# ----------------------------
def print_candidates():
sensors = detect_sensor_like_signals()
print("\n=== SENSOR CANDIDATES ===")
idx = 1
for can_id, signals in sensors.items():
for byte_index, score in signals:
print(f"Sensor{idx}: CAN ID {hex(can_id)} byte[{byte_index}] score={score:.3f}")
idx += 1
# ----------------------------
# Live plotting (simple)
# ----------------------------
def plot_sensor(can_id, byte_index):
samples = history[can_id]
if len(samples) < 10:
return
y = [s[1][byte_index] for s in samples]
x = list(range(len(y)))
plt.clf()
plt.title(f"CAN {hex(can_id)} byte[{byte_index}]")
plt.plot(x, y)
plt.pause(0.1)
# ----------------------------
# MQTT setup
# ----------------------------
client = mqtt.Client()
client.username_pw_set(MQTT_USER, MQTT_PASS)
client.on_message = on_message
client.connect(MQTT_BROKER, MQTT_PORT, 60)
client.subscribe(TOPIC)
client.loop_start()
print("Listening for CAN frames...\n")
# ----------------------------
# Main loop
# ----------------------------
plt.ion()
while True:
time.sleep(5)
print_candidates()
# try plotting strongest candidate (if any)
sensors = detect_sensor_like_signals()
for can_id, signals in sensors.items():
if signals:
byte_index = signals[0][0]
plot_sensor(can_id, byte_index)
break

1410
sdkconfig Normal file

File diff suppressed because it is too large Load Diff