Many updates, adding code for the various MCP servers
Browse files- LICENSE +19 -0
- MCP_servers/W600-embedded-OBD2/.gitignore +46 -0
- MCP_servers/W600-embedded-OBD2/App/main.c +170 -0
- MCP_servers/W600-embedded-OBD2/App/wifi_credentials.h.template +8 -0
- MCP_servers/W600-embedded-OBD2/App/wm_app_config.h +11 -0
- MCP_servers/W600-embedded-OBD2/Bin/firmware_server.py +24 -0
- MCP_servers/W600-embedded-OBD2/Include/App/elm327.h +91 -0
- MCP_servers/W600-embedded-OBD2/Include/App/elm327_history.h +66 -0
- MCP_servers/W600-embedded-OBD2/Include/App/mcp_http.h +93 -0
- MCP_servers/W600-embedded-OBD2/Include/App/mcp_jsonrpc.h +79 -0
- MCP_servers/W600-embedded-OBD2/Include/App/mcp_methods.h +66 -0
- MCP_servers/W600-embedded-OBD2/Include/App/mcp_server.h +45 -0
- MCP_servers/W600-embedded-OBD2/Include/App/mcp_stream.h +97 -0
- MCP_servers/W600-embedded-OBD2/Include/App/mcp_tools.h +58 -0
- MCP_servers/W600-embedded-OBD2/LICENSE +19 -0
- MCP_servers/W600-embedded-OBD2/README.md +424 -0
- MCP_servers/W600-embedded-OBD2/Src/App/easylogger/Makefile +52 -0
- MCP_servers/W600-embedded-OBD2/Src/App/easylogger/inc/elog.h +260 -0
- MCP_servers/W600-embedded-OBD2/Src/App/easylogger/inc/elog_cfg.h +75 -0
- MCP_servers/W600-embedded-OBD2/Src/App/easylogger/port/elog_port.c +444 -0
- MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog.c +750 -0
- MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog_async.c +354 -0
- MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog_buf.c +104 -0
- MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog_utils.c +103 -0
- MCP_servers/W600-embedded-OBD2/Src/App/elm327/Makefile +44 -0
- MCP_servers/W600-embedded-OBD2/Src/App/elm327/README_SIMULATION.md +313 -0
- MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327.c +312 -0
- MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327_history.c +275 -0
- MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327_sim.c +589 -0
- MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327_sim.h +69 -0
- MCP_servers/W600-embedded-OBD2/Src/App/mcp/Makefile +54 -0
- MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_http.c +291 -0
- MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_jsonrpc.c +183 -0
- MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_methods.c +167 -0
- MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_server.c +489 -0
- MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_server_rawip.c +512 -0
- MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_stream.c +148 -0
- MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_tools.c +379 -0
- MCP_servers/W600-embedded-OBD2/Tools/syslog.py +15 -0
- MCP_servers/gradio-OBD2-simulator/.gitignore +217 -0
- MCP_servers/gradio-OBD2-simulator/LICENSE +19 -0
- MCP_servers/gradio-OBD2-simulator/README.md +202 -0
- MCP_servers/gradio-OBD2-simulator/app.py +562 -0
- README.md +11 -3
- app.py +2 -2
- prompts.py +5 -4
- tools.py +177 -0
LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright (c) 2025 Ghislaine and David Robert
|
| 2 |
+
|
| 3 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 4 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 5 |
+
in the Software without restriction, including without limitation the rights
|
| 6 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 7 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 8 |
+
furnished to do so, subject to the following conditions:
|
| 9 |
+
|
| 10 |
+
The above copyright notice and this permission notice shall be included in all
|
| 11 |
+
copies or substantial portions of the Software.
|
| 12 |
+
|
| 13 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 14 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 15 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 16 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 17 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 18 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 19 |
+
SOFTWARE.
|
MCP_servers/W600-embedded-OBD2/.gitignore
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Object files
|
| 2 |
+
*.o
|
| 3 |
+
|
| 4 |
+
# Library archives (build artifacts)
|
| 5 |
+
*.a
|
| 6 |
+
|
| 7 |
+
# Binary files and firmware images
|
| 8 |
+
*.bin
|
| 9 |
+
*.elf
|
| 10 |
+
*.img
|
| 11 |
+
*.fls
|
| 12 |
+
*.map
|
| 13 |
+
*.gz
|
| 14 |
+
|
| 15 |
+
# Build output directory
|
| 16 |
+
Bin/wm_w600.*
|
| 17 |
+
Bin/*_w600.*
|
| 18 |
+
|
| 19 |
+
# Tools build artifacts
|
| 20 |
+
Tools/GNU/*.bin
|
| 21 |
+
Tools/GNU/*.elf
|
| 22 |
+
Tools/GNU/*.map
|
| 23 |
+
Tools/wm_tool
|
| 24 |
+
|
| 25 |
+
# Editor and IDE files
|
| 26 |
+
*.swp
|
| 27 |
+
*.swo
|
| 28 |
+
*~
|
| 29 |
+
.vscode/
|
| 30 |
+
.idea/
|
| 31 |
+
*.code-workspace
|
| 32 |
+
|
| 33 |
+
# OS-specific files
|
| 34 |
+
.DS_Store
|
| 35 |
+
Thumbs.db
|
| 36 |
+
desktop.ini
|
| 37 |
+
|
| 38 |
+
# Dependency files
|
| 39 |
+
*.d
|
| 40 |
+
*.dep
|
| 41 |
+
|
| 42 |
+
# Debug files
|
| 43 |
+
*.dbg
|
| 44 |
+
|
| 45 |
+
# WiFi credentials (keep out of source control)
|
| 46 |
+
App/wifi_credentials.h
|
MCP_servers/W600-embedded-OBD2/App/main.c
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#define LOG_TAG "Main"
|
| 2 |
+
|
| 3 |
+
#include "wm_include.h"
|
| 4 |
+
#include "wm_http_fwup.h"
|
| 5 |
+
#include "wm_watchdog.h"
|
| 6 |
+
#include "wm_log.h"
|
| 7 |
+
// The file wifi_credentials.h should define WIFI_SSID and WIFI_PASSWORD
|
| 8 |
+
// and is NOT checked into source control
|
| 9 |
+
#include "wifi_credentials.h"
|
| 10 |
+
#include "wm_app_config.h"
|
| 11 |
+
#include "elm327.h"
|
| 12 |
+
#include "mcp_server.h"
|
| 13 |
+
|
| 14 |
+
// Flag to track when IP is obtained
|
| 15 |
+
static volatile u8 ip_obtained = 0;
|
| 16 |
+
|
| 17 |
+
// OTA task function
|
| 18 |
+
static void ota_update()
|
| 19 |
+
{
|
| 20 |
+
int ret;
|
| 21 |
+
char firmware_url[sizeof(FIRMWARE_SERVER "/" FIRMWARE_FILENAME)];
|
| 22 |
+
snprintf(firmware_url, sizeof(firmware_url), "%s/%s", FIRMWARE_SERVER, FIRMWARE_FILENAME);
|
| 23 |
+
|
| 24 |
+
wm_log_info("Checking for OTA Firmware Update, Firmware URL: %s", firmware_url);
|
| 25 |
+
ret = t_http_fwup(firmware_url);
|
| 26 |
+
|
| 27 |
+
if (ret == 0)
|
| 28 |
+
{
|
| 29 |
+
wm_log_info("OTA Update Successful! Rebooting to apply new firmware...");
|
| 30 |
+
tls_os_time_delay(HZ * 2); // Wait 2 seconds before reboot
|
| 31 |
+
tls_sys_reset(); // Reboot to apply the new firmware
|
| 32 |
+
}
|
| 33 |
+
else
|
| 34 |
+
{
|
| 35 |
+
wm_log_info("OTA Update not available, Error code: %d", ret);
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// Network status callback
|
| 40 |
+
static void con_net_status_changed_event(u8 status)
|
| 41 |
+
{
|
| 42 |
+
switch(status)
|
| 43 |
+
{
|
| 44 |
+
case NETIF_WIFI_JOIN_SUCCESS:
|
| 45 |
+
wm_log_info("NETIF_WIFI_JOIN_SUCCESS");
|
| 46 |
+
break;
|
| 47 |
+
case NETIF_WIFI_JOIN_FAILED:
|
| 48 |
+
wm_log_error("NETIF_WIFI_JOIN_FAILED");
|
| 49 |
+
break;
|
| 50 |
+
case NETIF_WIFI_DISCONNECTED:
|
| 51 |
+
wm_log_warn("NETIF_WIFI_DISCONNECTED");
|
| 52 |
+
break;
|
| 53 |
+
case NETIF_IP_NET_UP:
|
| 54 |
+
{
|
| 55 |
+
struct tls_ethif *tmpethif = tls_netif_get_ethif();
|
| 56 |
+
print_ipaddr(&tmpethif->ip_addr);
|
| 57 |
+
#if TLS_CONFIG_IPV6
|
| 58 |
+
print_ipaddr(&tmpethif->ip6_addr[0]);
|
| 59 |
+
print_ipaddr(&tmpethif->ip6_addr[1]);
|
| 60 |
+
print_ipaddr(&tmpethif->ip6_addr[2]);
|
| 61 |
+
#endif
|
| 62 |
+
ip_obtained = 1; // Signal that IP has been obtained
|
| 63 |
+
}
|
| 64 |
+
break;
|
| 65 |
+
default:
|
| 66 |
+
wm_log_warn("UNKNOWN STATE:%d", status);
|
| 67 |
+
break;
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// Main entry point: this is where the code of your application starts
|
| 72 |
+
void UserMain(void)
|
| 73 |
+
{
|
| 74 |
+
int ret;
|
| 75 |
+
struct tls_param_ip *ip_param = NULL;
|
| 76 |
+
u8 wireless_protocol = 0;
|
| 77 |
+
|
| 78 |
+
// Initialize logging system first
|
| 79 |
+
wm_log_init();
|
| 80 |
+
|
| 81 |
+
wm_log_info("Diagnostic ELM237 MCP Server Firmware Starting...");
|
| 82 |
+
|
| 83 |
+
// Disconnect any existing WiFi connection
|
| 84 |
+
tls_wifi_disconnect();
|
| 85 |
+
|
| 86 |
+
// Set wireless protocol to infrastructure (station) mode
|
| 87 |
+
tls_param_get(TLS_PARAM_ID_WPROTOCOL, (void *)&wireless_protocol, TRUE);
|
| 88 |
+
if (TLS_PARAM_IEEE80211_INFRA != wireless_protocol)
|
| 89 |
+
{
|
| 90 |
+
tls_wifi_softap_destroy(); // Destroy any AP mode
|
| 91 |
+
wireless_protocol = TLS_PARAM_IEEE80211_INFRA;
|
| 92 |
+
tls_param_set(TLS_PARAM_ID_WPROTOCOL, (void *)&wireless_protocol, FALSE);
|
| 93 |
+
wm_log_info("Set to infrastructure (station) mode");
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
// Disable oneshot configuration mode
|
| 97 |
+
tls_wifi_set_oneshot_flag(0);
|
| 98 |
+
|
| 99 |
+
// Enable DHCP
|
| 100 |
+
ip_param = tls_mem_alloc(sizeof(struct tls_param_ip));
|
| 101 |
+
if (ip_param)
|
| 102 |
+
{
|
| 103 |
+
tls_param_get(TLS_PARAM_ID_IP, ip_param, FALSE);
|
| 104 |
+
ip_param->dhcp_enable = TRUE;
|
| 105 |
+
tls_param_set(TLS_PARAM_ID_IP, ip_param, FALSE);
|
| 106 |
+
tls_mem_free(ip_param);
|
| 107 |
+
wm_log_info("DHCP enabled");
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
// Register network interface status callback
|
| 111 |
+
tls_netif_add_status_event(con_net_status_changed_event);
|
| 112 |
+
|
| 113 |
+
// Disable WiFi power save mode
|
| 114 |
+
tls_wifi_set_psflag(0, 0);
|
| 115 |
+
|
| 116 |
+
// Connect to WiFi access point
|
| 117 |
+
wm_log_info("Connecting to WiFi: %s", WIFI_SSID);
|
| 118 |
+
ret = tls_wifi_connect((u8 *)WIFI_SSID, strlen(WIFI_SSID),
|
| 119 |
+
(u8 *)WIFI_PASSWORD, strlen(WIFI_PASSWORD));
|
| 120 |
+
|
| 121 |
+
if (ret == WM_SUCCESS)
|
| 122 |
+
{
|
| 123 |
+
wm_log_info("WiFi connection initiated, please wait...");
|
| 124 |
+
}
|
| 125 |
+
else
|
| 126 |
+
{
|
| 127 |
+
wm_log_error("Failed to start WiFi connection (error %d)", ret);
|
| 128 |
+
return; // Exit if WiFi connection failed
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
// Wait for IP address to be obtained via DHCP
|
| 132 |
+
wm_log_info("Waiting for IP address...");
|
| 133 |
+
while (!ip_obtained)
|
| 134 |
+
{
|
| 135 |
+
tls_os_time_delay(HZ / 10); // Wait 100ms
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
wm_log_info("IP address obtained");
|
| 139 |
+
|
| 140 |
+
#if (TLS_CONFIG_UDP_SYSLOG == CFG_ON)
|
| 141 |
+
if (wm_log_syslog_init() == 0) {
|
| 142 |
+
wm_log_info("UDP syslog initialized - remote logging active");
|
| 143 |
+
} else {
|
| 144 |
+
wm_log_error("Failed to initialize UDP syslog");
|
| 145 |
+
}
|
| 146 |
+
tls_os_time_delay(HZ * 2);
|
| 147 |
+
#endif
|
| 148 |
+
|
| 149 |
+
#if ENABLE_OTA
|
| 150 |
+
ota_update();
|
| 151 |
+
#endif
|
| 152 |
+
|
| 153 |
+
// Initialize ELM327
|
| 154 |
+
if (elm327_init() != WM_SUCCESS) {
|
| 155 |
+
wm_log_error("Failed to initialize ELM327");
|
| 156 |
+
return;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
// Start MCP server on port 80
|
| 160 |
+
if (mcp_server_init(80) != WM_SUCCESS) {
|
| 161 |
+
wm_log_error("Failed to start MCP server");
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
wm_log_info("Setup complete. Entering main loop...");
|
| 165 |
+
|
| 166 |
+
while (1) {
|
| 167 |
+
tls_os_time_delay(HZ * 10); // Sleep for 10 seconds
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
|
MCP_servers/W600-embedded-OBD2/App/wifi_credentials.h.template
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#ifndef WM_WIFI_CREDENTIALS_H
|
| 2 |
+
#define WM_WIFI_CREDENTIALS_H
|
| 3 |
+
|
| 4 |
+
// WiFi credentials - Copy this file to wifi_credentials.h and fill in your network details
|
| 5 |
+
#define WIFI_SSID "your_ssid_here"
|
| 6 |
+
#define WIFI_PASSWORD "your_password_here"
|
| 7 |
+
|
| 8 |
+
#endif /* WM_WIFI_CREDENTIALS_H */
|
MCP_servers/W600-embedded-OBD2/App/wm_app_config.h
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#ifndef WM_APP_CONFIG_H
|
| 2 |
+
#define WM_APP_CONFIG_H
|
| 3 |
+
|
| 4 |
+
// OTA firmware update server - modify this to point to your firmware server
|
| 5 |
+
#define FIRMWARE_SERVER "http://192.168.1.1"
|
| 6 |
+
#define FIRMWARE_FILENAME "wm_w600_gz.img"
|
| 7 |
+
|
| 8 |
+
// Set to 1 to enable automatic OTA updates, 0 to disable
|
| 9 |
+
#define ENABLE_OTA 1
|
| 10 |
+
|
| 11 |
+
#endif /* WM_APP_CONFIG_H */
|
MCP_servers/W600-embedded-OBD2/Bin/firmware_server.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
import http.server
|
| 3 |
+
import socketserver
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
PORT = 80 # default port
|
| 7 |
+
if len(sys.argv) > 1:
|
| 8 |
+
PORT = int(sys.argv[1])
|
| 9 |
+
|
| 10 |
+
Handler = http.server.SimpleHTTPRequestHandler
|
| 11 |
+
|
| 12 |
+
class SingleRequestTCPServer(socketserver.TCPServer):
|
| 13 |
+
allow_reuse_address = True
|
| 14 |
+
|
| 15 |
+
def handle_request(self):
|
| 16 |
+
"""Handle a single request and then stop."""
|
| 17 |
+
super().handle_request()
|
| 18 |
+
print("Request served, exiting.")
|
| 19 |
+
self.server_close()
|
| 20 |
+
sys.exit(0)
|
| 21 |
+
|
| 22 |
+
with SingleRequestTCPServer(("", PORT), Handler) as httpd:
|
| 23 |
+
print(f"Serving HTTP on port {PORT} (single request)...")
|
| 24 |
+
httpd.handle_request()
|
MCP_servers/W600-embedded-OBD2/Include/App/elm327.h
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file elm327.h
|
| 3 |
+
*
|
| 4 |
+
* @brief ELM327 OBD-II Interface Module
|
| 5 |
+
*
|
| 6 |
+
* This module provides a simple command-response interface for communicating
|
| 7 |
+
* with an ELM327 chip over UART1. The ELM327 is connected at 38400 baud 8N1.
|
| 8 |
+
*/
|
| 9 |
+
#ifndef ELM327_H
|
| 10 |
+
#define ELM327_H
|
| 11 |
+
|
| 12 |
+
#include "wm_type_def.h"
|
| 13 |
+
|
| 14 |
+
#define ELM327_MAX_RESPONSE_LEN 512
|
| 15 |
+
#define ELM327_DEFAULT_TIMEOUT 2000 // 2 seconds
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* @brief Initialize ELM327 communication on UART1
|
| 19 |
+
*
|
| 20 |
+
* @retval WM_SUCCESS success
|
| 21 |
+
* @retval WM_FAILED failed
|
| 22 |
+
*
|
| 23 |
+
* @note Configures UART1 at 38400 baud, 8N1, no flow control
|
| 24 |
+
*/
|
| 25 |
+
int elm327_init(void);
|
| 26 |
+
|
| 27 |
+
/**
|
| 28 |
+
* @brief Send command to ELM327 and wait for response
|
| 29 |
+
*
|
| 30 |
+
* @param[in] cmd Command string to send (e.g., "ATZ", "01 0D")
|
| 31 |
+
* @param[out] response Buffer to store response
|
| 32 |
+
* @param[in] max_len Maximum length of response buffer
|
| 33 |
+
* @param[in] timeout_ms Timeout in milliseconds
|
| 34 |
+
*
|
| 35 |
+
* @retval >0 Number of bytes in response (success)
|
| 36 |
+
* @retval -1 Not initialized
|
| 37 |
+
* @retval -2 Invalid parameters
|
| 38 |
+
* @retval -3 UART send failed
|
| 39 |
+
* @retval -4 Timeout waiting for response
|
| 40 |
+
*
|
| 41 |
+
* @note The function automatically appends '\r' if not present in cmd
|
| 42 |
+
* @note Response includes the '>' prompt at the end
|
| 43 |
+
*/
|
| 44 |
+
int elm327_send_command(const char *cmd, char *response,
|
| 45 |
+
int max_len, u32 timeout_ms);
|
| 46 |
+
|
| 47 |
+
/**
|
| 48 |
+
* @brief Cleanup ELM327 resources
|
| 49 |
+
*
|
| 50 |
+
* @retval None
|
| 51 |
+
*
|
| 52 |
+
* @note Unregisters callbacks and deletes semaphore
|
| 53 |
+
*/
|
| 54 |
+
void elm327_deinit(void);
|
| 55 |
+
|
| 56 |
+
#if (TLS_CONFIG_ELM327_SIMULATION == CFG_ON)
|
| 57 |
+
/**
|
| 58 |
+
* @brief Enable or disable simulation mode
|
| 59 |
+
*
|
| 60 |
+
* @retval None
|
| 61 |
+
*
|
| 62 |
+
* @note When simulation is enabled, OBD-II commands return simulated
|
| 63 |
+
* responses. AT commands always go to real hardware.
|
| 64 |
+
* @note Only available when compiled with TLS_CONFIG_ELM327_SIMULATION
|
| 65 |
+
*/
|
| 66 |
+
void elm327_set_simulation_mode(bool enable);
|
| 67 |
+
|
| 68 |
+
/**
|
| 69 |
+
* @brief Check if simulation mode is enabled
|
| 70 |
+
*
|
| 71 |
+
* @retval true Simulation is enabled
|
| 72 |
+
* @retval false Using real hardware
|
| 73 |
+
*
|
| 74 |
+
* @note Only available when compiled with TLS_CONFIG_ELM327_SIMULATION
|
| 75 |
+
*/
|
| 76 |
+
bool elm327_is_simulation_enabled(void);
|
| 77 |
+
|
| 78 |
+
/**
|
| 79 |
+
* @brief Set simulated engine state (running/stopped)
|
| 80 |
+
*
|
| 81 |
+
* @param[in] running true = engine on, false = engine off
|
| 82 |
+
*
|
| 83 |
+
* @retval None
|
| 84 |
+
*
|
| 85 |
+
* @note Affects simulated RPM, temperature, and other dynamic values
|
| 86 |
+
* @note Only available when compiled with TLS_CONFIG_ELM327_SIMULATION
|
| 87 |
+
*/
|
| 88 |
+
void elm327_set_engine_state(bool running);
|
| 89 |
+
#endif
|
| 90 |
+
|
| 91 |
+
#endif // ELM327_H
|
MCP_servers/W600-embedded-OBD2/Include/App/elm327_history.h
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file elm327_history.h
|
| 3 |
+
*
|
| 4 |
+
* @brief ELM327 OBD-II Data History Module
|
| 5 |
+
*
|
| 6 |
+
* This module periodically collects OBD-II PID values (RPM, Speed, Coolant Temp)
|
| 7 |
+
* and stores them in a ring buffer for historical analysis via MCP streaming.
|
| 8 |
+
*/
|
| 9 |
+
#ifndef ELM327_HISTORY_H
|
| 10 |
+
#define ELM327_HISTORY_H
|
| 11 |
+
|
| 12 |
+
#include "wm_type_def.h"
|
| 13 |
+
|
| 14 |
+
// Configuration
|
| 15 |
+
#define ELM327_HISTORY_SIZE 200 // Ring buffer capacity
|
| 16 |
+
#define ELM327_HISTORY_INTERVAL_MS 10000 // 10 seconds between samples
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* Single history entry containing OBD-II PID values
|
| 20 |
+
*/
|
| 21 |
+
typedef struct {
|
| 22 |
+
u32 timestamp; // System time when sampled (in ticks)
|
| 23 |
+
u16 rpm; // Engine RPM (0-16383)
|
| 24 |
+
u8 speed; // Vehicle speed in km/h (0-255)
|
| 25 |
+
s8 coolant_temp; // Coolant temperature in Celsius (-40 to +215)
|
| 26 |
+
u8 valid; // 1 if entry is valid, 0 if empty
|
| 27 |
+
} elm327_history_entry_t;
|
| 28 |
+
|
| 29 |
+
/**
|
| 30 |
+
* @brief Initialize ELM327 history module
|
| 31 |
+
*
|
| 32 |
+
* Creates periodic task that samples OBD-II PIDs every 10 seconds.
|
| 33 |
+
*
|
| 34 |
+
* @retval WM_SUCCESS success
|
| 35 |
+
* @retval WM_FAILED failed
|
| 36 |
+
*
|
| 37 |
+
* @note Requires elm327_init() to be called first
|
| 38 |
+
*/
|
| 39 |
+
int elm327_history_init(void);
|
| 40 |
+
|
| 41 |
+
/**
|
| 42 |
+
* @brief Get total number of entries in history
|
| 43 |
+
*
|
| 44 |
+
* @retval Number of valid entries (0 to ELM327_HISTORY_SIZE)
|
| 45 |
+
*/
|
| 46 |
+
int elm327_history_get_count(void);
|
| 47 |
+
|
| 48 |
+
/**
|
| 49 |
+
* @brief Get a specific history entry by index
|
| 50 |
+
*
|
| 51 |
+
* @param[in] index Entry index (0 = oldest, count-1 = newest)
|
| 52 |
+
*
|
| 53 |
+
* @retval Pointer to entry, or NULL if index out of range
|
| 54 |
+
*
|
| 55 |
+
* @note Returned pointer is valid until next sample is taken
|
| 56 |
+
*/
|
| 57 |
+
const elm327_history_entry_t* elm327_history_get_entry(int index);
|
| 58 |
+
|
| 59 |
+
/**
|
| 60 |
+
* @brief Stop the periodic sampling task
|
| 61 |
+
*
|
| 62 |
+
* @retval None
|
| 63 |
+
*/
|
| 64 |
+
void elm327_history_deinit(void);
|
| 65 |
+
|
| 66 |
+
#endif // ELM327_HISTORY_H
|
MCP_servers/W600-embedded-OBD2/Include/App/mcp_http.h
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_http.h
|
| 3 |
+
*
|
| 4 |
+
* @brief HTTP Protocol Handling for MCP Server
|
| 5 |
+
*
|
| 6 |
+
* This module provides HTTP request parsing and response building utilities
|
| 7 |
+
* for the MCP server.
|
| 8 |
+
*/
|
| 9 |
+
#ifndef MCP_HTTP_H
|
| 10 |
+
#define MCP_HTTP_H
|
| 11 |
+
|
| 12 |
+
#include "wm_type_def.h"
|
| 13 |
+
|
| 14 |
+
// Configuration
|
| 15 |
+
#define MCP_HTTP_MAX_REQUEST_SIZE 2048
|
| 16 |
+
#define MCP_HTTP_MAX_HEADER_SIZE 512
|
| 17 |
+
#define MCP_HTTP_MAX_PATH_SIZE 64
|
| 18 |
+
#define MCP_HTTP_MAX_METHOD_SIZE 16
|
| 19 |
+
|
| 20 |
+
/**
|
| 21 |
+
* HTTP request structure
|
| 22 |
+
*/
|
| 23 |
+
typedef struct {
|
| 24 |
+
char method[MCP_HTTP_MAX_METHOD_SIZE]; // "GET", "POST"
|
| 25 |
+
char path[MCP_HTTP_MAX_PATH_SIZE]; // "/mcp"
|
| 26 |
+
char protocol_version[32]; // MCP-Protocol-Version header
|
| 27 |
+
char accept[128]; // Accept header
|
| 28 |
+
char *body; // Request body (points into buffer)
|
| 29 |
+
u32 body_len; // Body length
|
| 30 |
+
} mcp_http_request_t;
|
| 31 |
+
|
| 32 |
+
/**
|
| 33 |
+
* HTTP response structure
|
| 34 |
+
*/
|
| 35 |
+
typedef struct {
|
| 36 |
+
int status_code; // HTTP status code
|
| 37 |
+
const char *status_text; // Status text
|
| 38 |
+
const char *content_type; // Content-Type header
|
| 39 |
+
const char *body; // Response body
|
| 40 |
+
u32 body_len; // Body length
|
| 41 |
+
} mcp_http_response_t;
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* @brief Parse HTTP request from buffer
|
| 45 |
+
*
|
| 46 |
+
* @param[in] buffer Buffer containing HTTP request
|
| 47 |
+
* @param[in] len Length of buffer
|
| 48 |
+
* @param[out] request Parsed request structure
|
| 49 |
+
* @return WM_SUCCESS on success, WM_FAILED on parse error
|
| 50 |
+
*
|
| 51 |
+
* @note request->body will point into the buffer
|
| 52 |
+
*/
|
| 53 |
+
int mcp_http_parse_request(const char *buffer, u32 len, mcp_http_request_t *request);
|
| 54 |
+
|
| 55 |
+
/**
|
| 56 |
+
* @brief Build HTTP response string
|
| 57 |
+
*
|
| 58 |
+
* @param[in] response Response structure
|
| 59 |
+
* @param[out] buffer Buffer to write response to
|
| 60 |
+
* @param[in] buffer_size Size of buffer
|
| 61 |
+
* @return Number of bytes written, or -1 on error
|
| 62 |
+
*/
|
| 63 |
+
int mcp_http_build_response(const mcp_http_response_t *response, char *buffer, u32 buffer_size);
|
| 64 |
+
|
| 65 |
+
/**
|
| 66 |
+
* @brief Get HTTP status text for status code
|
| 67 |
+
*
|
| 68 |
+
* @param[in] status_code HTTP status code
|
| 69 |
+
* @return Status text string
|
| 70 |
+
*/
|
| 71 |
+
const char* mcp_http_get_status_text(int status_code);
|
| 72 |
+
|
| 73 |
+
/**
|
| 74 |
+
* @brief Check if request has required MCP headers
|
| 75 |
+
*
|
| 76 |
+
* @param[in] request HTTP request structure
|
| 77 |
+
* @return 1 if valid, 0 if missing required headers
|
| 78 |
+
*/
|
| 79 |
+
int mcp_http_validate_headers(const mcp_http_request_t *request);
|
| 80 |
+
|
| 81 |
+
/**
|
| 82 |
+
* @brief Parse Content-Length header from HTTP request buffer
|
| 83 |
+
*
|
| 84 |
+
* @param[in] buffer Buffer containing HTTP request
|
| 85 |
+
* @param[in] len Length of buffer
|
| 86 |
+
* @param[out] content_length Parsed Content-Length value
|
| 87 |
+
* @return WM_SUCCESS if found and parsed, WM_FAILED if not found
|
| 88 |
+
*
|
| 89 |
+
* @note If header is not found, content_length is set to 0
|
| 90 |
+
*/
|
| 91 |
+
int mcp_http_get_content_length(const char *buffer, u32 len, u32 *content_length);
|
| 92 |
+
|
| 93 |
+
#endif // MCP_HTTP_H
|
MCP_servers/W600-embedded-OBD2/Include/App/mcp_jsonrpc.h
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_jsonrpc.h
|
| 3 |
+
*
|
| 4 |
+
* @brief JSON-RPC 2.0 Message Handling
|
| 5 |
+
*
|
| 6 |
+
* This module handles parsing, dispatching, and building JSON-RPC 2.0 messages
|
| 7 |
+
* as used by the MCP protocol.
|
| 8 |
+
*/
|
| 9 |
+
#ifndef MCP_JSONRPC_H
|
| 10 |
+
#define MCP_JSONRPC_H
|
| 11 |
+
|
| 12 |
+
#include "wm_type_def.h"
|
| 13 |
+
#include "cJSON.h"
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* JSON-RPC message types
|
| 17 |
+
*/
|
| 18 |
+
typedef enum {
|
| 19 |
+
JSONRPC_REQUEST, // Request expecting a response
|
| 20 |
+
JSONRPC_NOTIFICATION, // Request not expecting a response (no id)
|
| 21 |
+
JSONRPC_RESPONSE // Response to a request
|
| 22 |
+
} mcp_jsonrpc_type_t;
|
| 23 |
+
|
| 24 |
+
/**
|
| 25 |
+
* JSON-RPC request/notification structure
|
| 26 |
+
*/
|
| 27 |
+
typedef struct {
|
| 28 |
+
mcp_jsonrpc_type_t type; // Message type
|
| 29 |
+
char *method; // Method name (owned by this struct)
|
| 30 |
+
cJSON *params; // Parameters object (owned by this struct)
|
| 31 |
+
cJSON *id; // Request ID (owned, NULL for notifications)
|
| 32 |
+
} mcp_jsonrpc_request_t;
|
| 33 |
+
|
| 34 |
+
/**
|
| 35 |
+
* @brief Parse JSON-RPC message from string
|
| 36 |
+
*
|
| 37 |
+
* @param[in] json_str JSON string to parse
|
| 38 |
+
* @param[out] request Parsed request structure (caller must free)
|
| 39 |
+
* @return WM_SUCCESS on success, WM_FAILED on parse error
|
| 40 |
+
*
|
| 41 |
+
* @note Caller must call mcp_jsonrpc_free_request() when done
|
| 42 |
+
*/
|
| 43 |
+
int mcp_jsonrpc_parse(const char *json_str, mcp_jsonrpc_request_t *request);
|
| 44 |
+
|
| 45 |
+
/**
|
| 46 |
+
* @brief Free parsed request structure
|
| 47 |
+
*
|
| 48 |
+
* @param[in] request Request structure to free
|
| 49 |
+
*/
|
| 50 |
+
void mcp_jsonrpc_free_request(mcp_jsonrpc_request_t *request);
|
| 51 |
+
|
| 52 |
+
/**
|
| 53 |
+
* @brief Build JSON-RPC success response
|
| 54 |
+
*
|
| 55 |
+
* @param[in] id Request ID (cJSON object)
|
| 56 |
+
* @param[in] result Result object (takes ownership)
|
| 57 |
+
* @return JSON string of response (caller must free)
|
| 58 |
+
*/
|
| 59 |
+
char* mcp_jsonrpc_build_response(cJSON *id, cJSON *result);
|
| 60 |
+
|
| 61 |
+
/**
|
| 62 |
+
* @brief Build JSON-RPC error response
|
| 63 |
+
*
|
| 64 |
+
* @param[in] id Request ID (cJSON object, can be NULL)
|
| 65 |
+
* @param[in] code Error code
|
| 66 |
+
* @param[in] message Error message
|
| 67 |
+
* @param[in] data Optional error data (can be NULL)
|
| 68 |
+
* @return JSON string of error response (caller must free)
|
| 69 |
+
*/
|
| 70 |
+
char* mcp_jsonrpc_build_error(cJSON *id, int code, const char *message, cJSON *data);
|
| 71 |
+
|
| 72 |
+
// Standard JSON-RPC error codes
|
| 73 |
+
#define JSONRPC_PARSE_ERROR -32700
|
| 74 |
+
#define JSONRPC_INVALID_REQUEST -32600
|
| 75 |
+
#define JSONRPC_METHOD_NOT_FOUND -32601
|
| 76 |
+
#define JSONRPC_INVALID_PARAMS -32602
|
| 77 |
+
#define JSONRPC_INTERNAL_ERROR -32603
|
| 78 |
+
|
| 79 |
+
#endif // MCP_JSONRPC_H
|
MCP_servers/W600-embedded-OBD2/Include/App/mcp_methods.h
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_methods.h
|
| 3 |
+
*
|
| 4 |
+
* @brief MCP Protocol Methods
|
| 5 |
+
*
|
| 6 |
+
* This module implements the core MCP protocol methods:
|
| 7 |
+
* - initialize
|
| 8 |
+
* - tools/list
|
| 9 |
+
* - tools/call
|
| 10 |
+
*/
|
| 11 |
+
#ifndef MCP_METHODS_H
|
| 12 |
+
#define MCP_METHODS_H
|
| 13 |
+
|
| 14 |
+
#include "wm_type_def.h"
|
| 15 |
+
#include "cJSON.h"
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* @brief Handle 'initialize' method
|
| 19 |
+
*
|
| 20 |
+
* @param[in] params Method parameters
|
| 21 |
+
* @param[out] result Method result (caller takes ownership, will be freed
|
| 22 |
+
* when passed to mcp_jsonrpc_build_response)
|
| 23 |
+
* @param[out] error Error object if method fails (caller takes ownership,
|
| 24 |
+
* will be freed when passed to mcp_jsonrpc_build_error)
|
| 25 |
+
* @return WM_SUCCESS on success, WM_FAILED on error
|
| 26 |
+
*/
|
| 27 |
+
int mcp_method_initialize(cJSON *params, cJSON **result, cJSON **error);
|
| 28 |
+
|
| 29 |
+
/**
|
| 30 |
+
* @brief Handle 'tools/list' method
|
| 31 |
+
*
|
| 32 |
+
* @param[in] params Method parameters (unused)
|
| 33 |
+
* @param[out] result Method result (caller takes ownership, will be freed
|
| 34 |
+
* when passed to mcp_jsonrpc_build_response)
|
| 35 |
+
* @param[out] error Error object if method fails (caller takes ownership,
|
| 36 |
+
* will be freed when passed to mcp_jsonrpc_build_error)
|
| 37 |
+
* @return WM_SUCCESS on success, WM_FAILED on error
|
| 38 |
+
*/
|
| 39 |
+
int mcp_method_tools_list(cJSON *params, cJSON **result, cJSON **error);
|
| 40 |
+
|
| 41 |
+
/**
|
| 42 |
+
* @brief Handle 'tools/call' method
|
| 43 |
+
*
|
| 44 |
+
* @param[in] params Method parameters (must contain 'name' and 'arguments')
|
| 45 |
+
* @param[out] result Method result (caller takes ownership, will be freed
|
| 46 |
+
* when passed to mcp_jsonrpc_build_response)
|
| 47 |
+
* @param[out] error Error object if method fails (caller takes ownership,
|
| 48 |
+
* will be freed when passed to mcp_jsonrpc_build_error)
|
| 49 |
+
* @return WM_SUCCESS on success, WM_FAILED on error
|
| 50 |
+
*/
|
| 51 |
+
int mcp_method_tools_call(cJSON *params, cJSON **result, cJSON **error);
|
| 52 |
+
|
| 53 |
+
/**
|
| 54 |
+
* @brief Dispatch method by name
|
| 55 |
+
*
|
| 56 |
+
* @param[in] method Method name
|
| 57 |
+
* @param[in] params Method parameters
|
| 58 |
+
* @param[out] result Method result (caller takes ownership, will be freed
|
| 59 |
+
* when passed to mcp_jsonrpc_build_response)
|
| 60 |
+
* @param[out] error Error object if method fails (caller takes ownership,
|
| 61 |
+
* will be freed when passed to mcp_jsonrpc_build_error)
|
| 62 |
+
* @return WM_SUCCESS on success, WM_FAILED on error
|
| 63 |
+
*/
|
| 64 |
+
int mcp_methods_dispatch(const char *method, cJSON *params, cJSON **result, cJSON **error);
|
| 65 |
+
|
| 66 |
+
#endif // MCP_METHODS_H
|
MCP_servers/W600-embedded-OBD2/Include/App/mcp_server.h
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_server.h
|
| 3 |
+
*
|
| 4 |
+
* @brief MCP (Model Context Protocol) HTTP Server
|
| 5 |
+
*
|
| 6 |
+
* This module implements a Streamable HTTP MCP Server that provides
|
| 7 |
+
* JSON-RPC endpoints for MCP clients to interact with the W600 device.
|
| 8 |
+
*
|
| 9 |
+
* The server exposes the /mcp endpoint:
|
| 10 |
+
* - POST /mcp: Handle JSON-RPC requests (initialize, tools/list, tools/call)
|
| 11 |
+
* - GET /mcp: Return 405 Method Not Allowed (SSE not supported)
|
| 12 |
+
*/
|
| 13 |
+
#ifndef MCP_SERVER_H
|
| 14 |
+
#define MCP_SERVER_H
|
| 15 |
+
|
| 16 |
+
#include "wm_type_def.h"
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* @brief Initialize and start MCP server
|
| 20 |
+
*
|
| 21 |
+
* @param[in] port Port to listen on (default: 80)
|
| 22 |
+
* @return WM_SUCCESS on success, WM_FAILED on error
|
| 23 |
+
*
|
| 24 |
+
* @note Server runs in LwIP context, no separate thread created
|
| 25 |
+
*/
|
| 26 |
+
int mcp_server_init(u16 port);
|
| 27 |
+
|
| 28 |
+
/**
|
| 29 |
+
* @brief Stop MCP server and cleanup resources
|
| 30 |
+
*
|
| 31 |
+
* @return WM_SUCCESS on success, WM_FAILED on error
|
| 32 |
+
*/
|
| 33 |
+
int mcp_server_deinit(void);
|
| 34 |
+
|
| 35 |
+
/**
|
| 36 |
+
* @brief Check if MCP server is running
|
| 37 |
+
*
|
| 38 |
+
* @return 1 if running, 0 if not running
|
| 39 |
+
*/
|
| 40 |
+
int mcp_server_is_running(void);
|
| 41 |
+
|
| 42 |
+
// Default port
|
| 43 |
+
#define MCP_SERVER_DEFAULT_PORT 80
|
| 44 |
+
|
| 45 |
+
#endif // MCP_SERVER_H
|
MCP_servers/W600-embedded-OBD2/Include/App/mcp_stream.h
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_stream.h
|
| 3 |
+
*
|
| 4 |
+
* @brief MCP Streaming Response Support
|
| 5 |
+
*
|
| 6 |
+
* This module provides support for streaming large responses using
|
| 7 |
+
* chunked sends. This allows sending data that doesn't fit in memory
|
| 8 |
+
* all at once (e.g., ring buffers, logs, historical data).
|
| 9 |
+
*/
|
| 10 |
+
#ifndef MCP_STREAM_H
|
| 11 |
+
#define MCP_STREAM_H
|
| 12 |
+
|
| 13 |
+
#include "wm_type_def.h"
|
| 14 |
+
#include "cJSON.h"
|
| 15 |
+
|
| 16 |
+
/**
|
| 17 |
+
* Streaming context for chunked response generation
|
| 18 |
+
*/
|
| 19 |
+
typedef struct {
|
| 20 |
+
int socket; // Socket to send to
|
| 21 |
+
int error; // Error flag
|
| 22 |
+
int item_count; // Number of items sent so far
|
| 23 |
+
int total_items; // Total items to send (-1 if unknown)
|
| 24 |
+
} mcp_stream_ctx_t;
|
| 25 |
+
|
| 26 |
+
/**
|
| 27 |
+
* Streaming callback function type
|
| 28 |
+
*
|
| 29 |
+
* Called repeatedly to generate chunks of data.
|
| 30 |
+
*
|
| 31 |
+
* @param ctx Streaming context
|
| 32 |
+
* @param user_data User data passed to mcp_stream_response()
|
| 33 |
+
* @return WM_SUCCESS to continue, WM_FAILED to stop
|
| 34 |
+
*/
|
| 35 |
+
typedef int (*mcp_stream_callback_t)(mcp_stream_ctx_t *ctx, void *user_data);
|
| 36 |
+
|
| 37 |
+
/**
|
| 38 |
+
* @brief Initialize streaming context
|
| 39 |
+
*
|
| 40 |
+
* @param[out] ctx Streaming context to initialize
|
| 41 |
+
* @param[in] socket Socket to stream to
|
| 42 |
+
* @param[in] total_items Total items to stream (-1 if unknown)
|
| 43 |
+
*/
|
| 44 |
+
void mcp_stream_init(mcp_stream_ctx_t *ctx, int socket, int total_items);
|
| 45 |
+
|
| 46 |
+
/**
|
| 47 |
+
* @brief Send a chunk of text data
|
| 48 |
+
*
|
| 49 |
+
* @param[in] ctx Streaming context
|
| 50 |
+
* @param[in] text Text to send
|
| 51 |
+
* @param[in] len Length of text (-1 to use strlen)
|
| 52 |
+
* @return WM_SUCCESS on success, WM_FAILED on error
|
| 53 |
+
*
|
| 54 |
+
* @note This is a low-level function. Prefer mcp_stream_json_item()
|
| 55 |
+
* for structured data.
|
| 56 |
+
*/
|
| 57 |
+
int mcp_stream_send_chunk(mcp_stream_ctx_t *ctx, const char *text, int len);
|
| 58 |
+
|
| 59 |
+
/**
|
| 60 |
+
* @brief Send a JSON array item as a string
|
| 61 |
+
*
|
| 62 |
+
* Handles comma separation automatically.
|
| 63 |
+
*
|
| 64 |
+
* @param[in] ctx Streaming context
|
| 65 |
+
* @param[in] json_str JSON string for this item
|
| 66 |
+
* @return WM_SUCCESS on success, WM_FAILED on error
|
| 67 |
+
*/
|
| 68 |
+
int mcp_stream_json_item(mcp_stream_ctx_t *ctx, const char *json_str);
|
| 69 |
+
|
| 70 |
+
/**
|
| 71 |
+
* @brief Send streaming JSON-RPC response
|
| 72 |
+
*
|
| 73 |
+
* Sends HTTP headers, opens JSON-RPC structure, calls callback repeatedly
|
| 74 |
+
* to generate content, then closes JSON-RPC structure.
|
| 75 |
+
*
|
| 76 |
+
* @param[in] socket Socket to send to
|
| 77 |
+
* @param[in] request_id JSON-RPC request ID (cJSON object)
|
| 78 |
+
* @param[in] callback Callback to generate content chunks
|
| 79 |
+
* @param[in] user_data User data passed to callback
|
| 80 |
+
* @return WM_SUCCESS on success, WM_FAILED on error
|
| 81 |
+
*
|
| 82 |
+
* Example callback that streams 100 items:
|
| 83 |
+
*
|
| 84 |
+
* int my_callback(mcp_stream_ctx_t *ctx, void *user_data) {
|
| 85 |
+
* char buf[128];
|
| 86 |
+
* if (ctx->item_count >= 100) {
|
| 87 |
+
* return WM_FAILED; // Done
|
| 88 |
+
* }
|
| 89 |
+
* snprintf(buf, sizeof(buf), "{\"id\":%d,\"value\":%d}",
|
| 90 |
+
* ctx->item_count, ctx->item_count * 2);
|
| 91 |
+
* return mcp_stream_json_item(ctx, buf);
|
| 92 |
+
* }
|
| 93 |
+
*/
|
| 94 |
+
int mcp_stream_response(int socket, cJSON *request_id,
|
| 95 |
+
mcp_stream_callback_t callback, void *user_data);
|
| 96 |
+
|
| 97 |
+
#endif // MCP_STREAM_H
|
MCP_servers/W600-embedded-OBD2/Include/App/mcp_tools.h
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_tools.h
|
| 3 |
+
*
|
| 4 |
+
* @brief MCP Tools Implementation
|
| 5 |
+
*
|
| 6 |
+
* This module defines and implements the tools exposed by the MCP server.
|
| 7 |
+
* Currently supports: status and send_elm327_command tools.
|
| 8 |
+
*/
|
| 9 |
+
#ifndef MCP_TOOLS_H
|
| 10 |
+
#define MCP_TOOLS_H
|
| 11 |
+
|
| 12 |
+
#include "wm_type_def.h"
|
| 13 |
+
#include "cJSON.h"
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* Tool handler function type
|
| 17 |
+
*
|
| 18 |
+
* @param[in] args Tool arguments as cJSON object
|
| 19 |
+
* @param[out] result Tool result to be returned (caller must free)
|
| 20 |
+
* @param[out] error Error object if tool fails (caller must free)
|
| 21 |
+
* @return WM_SUCCESS on success, WM_FAILED on error
|
| 22 |
+
*/
|
| 23 |
+
typedef int (*mcp_tool_handler_t)(cJSON *args, cJSON **result, cJSON **error);
|
| 24 |
+
|
| 25 |
+
/**
|
| 26 |
+
* Tool definition structure
|
| 27 |
+
*/
|
| 28 |
+
typedef struct {
|
| 29 |
+
const char *name; // Tool name
|
| 30 |
+
const char *description; // Tool description
|
| 31 |
+
const char *input_schema_json; // JSON Schema as string
|
| 32 |
+
mcp_tool_handler_t handler; // Handler function
|
| 33 |
+
} mcp_tool_t;
|
| 34 |
+
|
| 35 |
+
/**
|
| 36 |
+
* @brief Get list of available tools
|
| 37 |
+
*
|
| 38 |
+
* @param[out] count Number of tools returned
|
| 39 |
+
* @return Pointer to array of tool definitions
|
| 40 |
+
*/
|
| 41 |
+
const mcp_tool_t* mcp_tools_get_list(int *count);
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* @brief Find tool by name
|
| 45 |
+
*
|
| 46 |
+
* @param[in] name Tool name to find
|
| 47 |
+
* @return Pointer to tool definition, or NULL if not found
|
| 48 |
+
*/
|
| 49 |
+
const mcp_tool_t* mcp_tools_find(const char *name);
|
| 50 |
+
|
| 51 |
+
/**
|
| 52 |
+
* @brief Initialize tools subsystem
|
| 53 |
+
*
|
| 54 |
+
* @return WM_SUCCESS on success, WM_FAILED on error
|
| 55 |
+
*/
|
| 56 |
+
int mcp_tools_init(void);
|
| 57 |
+
|
| 58 |
+
#endif // MCP_TOOLS_H
|
MCP_servers/W600-embedded-OBD2/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright (c) 2025 David Robert
|
| 2 |
+
|
| 3 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 4 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 5 |
+
in the Software without restriction, including without limitation the rights
|
| 6 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 7 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 8 |
+
furnished to do so, subject to the following conditions:
|
| 9 |
+
|
| 10 |
+
The above copyright notice and this permission notice shall be included in all
|
| 11 |
+
copies or substantial portions of the Software.
|
| 12 |
+
|
| 13 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 14 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 15 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 16 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 17 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 18 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 19 |
+
SOFTWARE.
|
MCP_servers/W600-embedded-OBD2/README.md
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# W600 Embedded OBD2 MCP Server
|
| 2 |
+
|
| 3 |
+
An embedded Model Context Protocol (MCP) server implementation for the W600 WiFi SoC, providing vehicle diagnostics capabilities through ELM327 OBD-II interface over HTTP.
|
| 4 |
+
|
| 5 |
+
## Hackathon
|
| 6 |
+
|
| 7 |
+
This was created as part of our submission for the Anthropic Gradio MCP's 1st Birthday hackathon: https://huggingface.co/spaces/MCP-1st-Birthday/Vehicle-Diagnostic-Assistant
|
| 8 |
+
|
| 9 |
+
## Overview
|
| 10 |
+
|
| 11 |
+
This project implements an MCP server that runs directly on the W600 microcontroller, enabling vehicle diagnostic communication through the ELM327 OBD-II protocol. The server exposes MCP tools over HTTP, allowing AI assistants and other clients to query vehicle data, read diagnostic trouble codes (DTCs), and perform OBD-II operations wirelessly.
|
| 12 |
+
|
| 13 |
+
## Features
|
| 14 |
+
|
| 15 |
+
- **MCP Server Implementation**: Full JSON-RPC 2.0 based MCP server running on W600
|
| 16 |
+
- **ELM327 Support**: Complete ELM327 OBD-II command interface with UART communication
|
| 17 |
+
- **OBD-II Simulation**: Built-in simulator for testing without vehicle connection
|
| 18 |
+
- **WiFi Connectivity**: Connect via WiFi access point or station mode
|
| 19 |
+
- **HTTP Interface**: RESTful HTTP endpoint for MCP communication
|
| 20 |
+
- **OTA Updates**: Over-the-air firmware update capability
|
| 21 |
+
- **Logging**: EasyLogger integration for comprehensive debug logging
|
| 22 |
+
|
| 23 |
+
## Hardware Requirements
|
| 24 |
+
|
| 25 |
+
- W600 WiFi SoC development board
|
| 26 |
+
- ELM327 OBD-II interface module (UART-based)
|
| 27 |
+
- Vehicle with OBD-II port (for actual diagnostics)
|
| 28 |
+
- USB-to-Serial adapter (for programming and debugging)
|
| 29 |
+
|
| 30 |
+
## Software Requirements
|
| 31 |
+
|
| 32 |
+
- W600 SDK (required)
|
| 33 |
+
- GNU ARM toolchain (included with W600 SDK)
|
| 34 |
+
- Serial terminal software (for debugging)
|
| 35 |
+
|
| 36 |
+
## Installation
|
| 37 |
+
|
| 38 |
+
### 1. Install W600 SDK
|
| 39 |
+
|
| 40 |
+
Download and install the W600 SDK from the official WinnerMicro website or GitHub repository. Follow the SDK installation instructions for your operating system.
|
| 41 |
+
|
| 42 |
+
### 2. Copy Project Files
|
| 43 |
+
|
| 44 |
+
Copy the files from this repository into the corresponding folders in your W600 SDK installation:
|
| 45 |
+
|
| 46 |
+
- **`App/`** folder contents → Copy to `{W600_SDK}/App/`
|
| 47 |
+
|
| 48 |
+
- `main.c` - Main application entry point
|
| 49 |
+
- `wm_app_config.h` - Application configuration
|
| 50 |
+
- `wifi_credentials.h.template` - WiFi credentials template
|
| 51 |
+
|
| 52 |
+
- **`Src/App/`** folder contents → Copy to `{W600_SDK}/Src/App/`
|
| 53 |
+
- `easylogger/` - Logging library
|
| 54 |
+
- `elm327/` - ELM327 driver and simulator
|
| 55 |
+
- `mcp/` - MCP server implementation
|
| 56 |
+
|
| 57 |
+
### 3. Configure WiFi Credentials
|
| 58 |
+
|
| 59 |
+
1. Copy the template file:
|
| 60 |
+
|
| 61 |
+
```bash
|
| 62 |
+
cp App/wifi_credentials.h.template App/wifi_credentials.h
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
2. Edit `App/wifi_credentials.h` and add your WiFi credentials:
|
| 66 |
+
|
| 67 |
+
```c
|
| 68 |
+
#define WIFI_SSID "your_ssid"
|
| 69 |
+
#define WIFI_PASSWORD "your_password"
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
**Note**: `wifi_credentials.h` is gitignored to protect your credentials.
|
| 73 |
+
|
| 74 |
+
### 4. Configure Application Settings
|
| 75 |
+
|
| 76 |
+
Edit `App/wm_app_config.h` to configure:
|
| 77 |
+
|
| 78 |
+
- **Firmware Update Server**: Set `FIRMWARE_SERVER` URL for OTA updates
|
| 79 |
+
- **MCP Server Port**: Default is port 80
|
| 80 |
+
- **ELM327 Settings**: UART configuration for ELM327 communication
|
| 81 |
+
|
| 82 |
+
### 5. Build the Project
|
| 83 |
+
|
| 84 |
+
Navigate to the W600 SDK build directory and compile:
|
| 85 |
+
|
| 86 |
+
```bash
|
| 87 |
+
cd {W600_SDK}/Tools/GNU
|
| 88 |
+
make clean
|
| 89 |
+
make
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
This will generate the firmware image in the `Bin/` directory.
|
| 93 |
+
|
| 94 |
+
### 6. Flash the Firmware
|
| 95 |
+
|
| 96 |
+
Flash the compiled firmware to your W600 board using the W600 flash tool:
|
| 97 |
+
|
| 98 |
+
```bash
|
| 99 |
+
# Example using wm_tool (adjust COM port as needed)
|
| 100 |
+
wm_tool -c COM3 -ds 1M -dl {W600_SDK}/Bin/W600.fls
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
## Configuration
|
| 104 |
+
|
| 105 |
+
### ELM327 Simulation Mode
|
| 106 |
+
|
| 107 |
+
The project includes an OBD-II simulator for testing without a vehicle. To configure:
|
| 108 |
+
|
| 109 |
+
Edit `Include/wm_config.h` in the W600 SDK:
|
| 110 |
+
|
| 111 |
+
```c
|
| 112 |
+
/** ELM327 OBD-II **/
|
| 113 |
+
#define TLS_CONFIG_ELM327_SIMULATION CFG_ON // Enable simulator
|
| 114 |
+
// or
|
| 115 |
+
#define TLS_CONFIG_ELM327_SIMULATION CFG_OFF // Disable (use real ELM327)
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
See `Src/App/elm327/README_SIMULATION.md` for detailed simulation documentation.
|
| 119 |
+
|
| 120 |
+
### Network Configuration
|
| 121 |
+
|
| 122 |
+
The device can be configured for:
|
| 123 |
+
|
| 124 |
+
- **Station Mode**: Connect to existing WiFi network (default)
|
| 125 |
+
- **AP Mode**: Create its own access point
|
| 126 |
+
|
| 127 |
+
Configure in `App/main.c` by modifying the WiFi initialization code.
|
| 128 |
+
|
| 129 |
+
## Usage
|
| 130 |
+
|
| 131 |
+
### Starting the Server
|
| 132 |
+
|
| 133 |
+
1. Power on the W600 board
|
| 134 |
+
2. The device will connect to WiFi using configured credentials
|
| 135 |
+
3. Monitor serial output for the assigned IP address
|
| 136 |
+
4. MCP server starts automatically on port 80
|
| 137 |
+
|
| 138 |
+
### Accessing the MCP Server
|
| 139 |
+
|
| 140 |
+
The MCP server accepts JSON-RPC 2.0 requests at:
|
| 141 |
+
|
| 142 |
+
```
|
| 143 |
+
http://{device_ip}/mcp
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
### Example MCP Requests
|
| 147 |
+
|
| 148 |
+
#### Get System Status
|
| 149 |
+
|
| 150 |
+
```bash
|
| 151 |
+
curl -X POST http://192.168.1.100/mcp \
|
| 152 |
+
-H "Content-Type: application/json" \
|
| 153 |
+
-d '{
|
| 154 |
+
"jsonrpc": "2.0",
|
| 155 |
+
"method": "tools/call",
|
| 156 |
+
"params": {
|
| 157 |
+
"name": "status"
|
| 158 |
+
},
|
| 159 |
+
"id": 1
|
| 160 |
+
}'
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
Response includes: IP address, uptime, free memory, WiFi RSSI, and ELM327 status.
|
| 164 |
+
|
| 165 |
+
#### Send ELM327 Command
|
| 166 |
+
|
| 167 |
+
```bash
|
| 168 |
+
curl -X POST http://192.168.1.100/mcp \
|
| 169 |
+
-H "Content-Type: application/json" \
|
| 170 |
+
-d '{
|
| 171 |
+
"jsonrpc": "2.0",
|
| 172 |
+
"method": "tools/call",
|
| 173 |
+
"params": {
|
| 174 |
+
"name": "send_elm327_command",
|
| 175 |
+
"arguments": {
|
| 176 |
+
"command": "01 0C"
|
| 177 |
+
}
|
| 178 |
+
},
|
| 179 |
+
"id": 2
|
| 180 |
+
}'
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
#### Get Vehicle Speed
|
| 184 |
+
|
| 185 |
+
```bash
|
| 186 |
+
curl -X POST http://192.168.1.100/mcp \
|
| 187 |
+
-H "Content-Type: application/json" \
|
| 188 |
+
-d '{
|
| 189 |
+
"jsonrpc": "2.0",
|
| 190 |
+
"method": "tools/call",
|
| 191 |
+
"params": {
|
| 192 |
+
"name": "send_elm327_command",
|
| 193 |
+
"arguments": {
|
| 194 |
+
"command": "01 0D"
|
| 195 |
+
}
|
| 196 |
+
},
|
| 197 |
+
"id": 3
|
| 198 |
+
}'
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
#### Read Diagnostic Trouble Codes
|
| 202 |
+
|
| 203 |
+
```bash
|
| 204 |
+
curl -X POST http://192.168.1.100/mcp \
|
| 205 |
+
-H "Content-Type: application/json" \
|
| 206 |
+
-d '{
|
| 207 |
+
"jsonrpc": "2.0",
|
| 208 |
+
"method": "tools/call",
|
| 209 |
+
"params": {
|
| 210 |
+
"name": "send_elm327_command",
|
| 211 |
+
"arguments": {
|
| 212 |
+
"command": "03"
|
| 213 |
+
}
|
| 214 |
+
},
|
| 215 |
+
"id": 4
|
| 216 |
+
}'
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
#### Get Historical OBD-II Data
|
| 220 |
+
|
| 221 |
+
```bash
|
| 222 |
+
curl -X POST http://192.168.1.100/mcp \
|
| 223 |
+
-H "Content-Type: application/json" \
|
| 224 |
+
-d '{
|
| 225 |
+
"jsonrpc": "2.0",
|
| 226 |
+
"method": "tools/call",
|
| 227 |
+
"params": {
|
| 228 |
+
"name": "get_elm327_history",
|
| 229 |
+
"arguments": {
|
| 230 |
+
"count": 50
|
| 231 |
+
}
|
| 232 |
+
},
|
| 233 |
+
"id": 5
|
| 234 |
+
}'
|
| 235 |
+
```
|
| 236 |
+
|
| 237 |
+
Returns historical log of RPM, speed, and coolant temperature. Omit `count` or set to 0 for all records.
|
| 238 |
+
|
| 239 |
+
## Project Structure
|
| 240 |
+
|
| 241 |
+
```
|
| 242 |
+
W600-embedded-OBD2/
|
| 243 |
+
├── App/
|
| 244 |
+
│ ├── main.c # Main application entry point
|
| 245 |
+
│ ├── wm_app_config.h # Application configuration
|
| 246 |
+
│ └── wifi_credentials.h.template # WiFi credentials template
|
| 247 |
+
│
|
| 248 |
+
├── Src/App/
|
| 249 |
+
│ ├── easylogger/ # Logging library
|
| 250 |
+
│ │ ├── inc/ # Header files
|
| 251 |
+
│ │ ├── port/ # W600 port implementation
|
| 252 |
+
│ │ └── src/ # Logger source code
|
| 253 |
+
│ │
|
| 254 |
+
│ ├── elm327/ # ELM327 OBD-II driver
|
| 255 |
+
│ │ ├── elm327.c # Main driver with simulation
|
| 256 |
+
│ │ ├── elm327_sim.c # OBD-II simulator
|
| 257 |
+
│ │ ├── elm327_sim.h # Simulator header
|
| 258 |
+
│ │ ├── elm327_history.c # Command history
|
| 259 |
+
│ │ └── README_SIMULATION.md # Simulation documentation
|
| 260 |
+
│ │
|
| 261 |
+
│ └── mcp/ # MCP server implementation
|
| 262 |
+
│ ├── mcp_server.c # Main server logic
|
| 263 |
+
│ ├── mcp_http.c # HTTP request handling
|
| 264 |
+
│ ├── mcp_jsonrpc.c # JSON-RPC protocol
|
| 265 |
+
│ ├── mcp_methods.c # MCP method handlers
|
| 266 |
+
│ ├── mcp_tools.c # MCP tool definitions
|
| 267 |
+
│ └── mcp_stream.c # Stream utilities
|
| 268 |
+
│
|
| 269 |
+
├── LICENSE
|
| 270 |
+
└── README.md # This file
|
| 271 |
+
```
|
| 272 |
+
|
| 273 |
+
## Available MCP Tools
|
| 274 |
+
|
| 275 |
+
The server exposes the following MCP tools:
|
| 276 |
+
|
| 277 |
+
### 1. `status`
|
| 278 |
+
|
| 279 |
+
Get system status information including network, hardware, and ELM327 status.
|
| 280 |
+
|
| 281 |
+
- **Parameters**: None
|
| 282 |
+
- **Returns**: JSON object with:
|
| 283 |
+
- `ip_address` - Device IP address
|
| 284 |
+
- `uptime_seconds` - System uptime in seconds
|
| 285 |
+
- `free_memory_bytes` - Available heap memory
|
| 286 |
+
- `wifi_rssi_dbm` - WiFi signal strength (dBm)
|
| 287 |
+
- `elm327_status` - ELM327 connection status
|
| 288 |
+
|
| 289 |
+
### 2. `send_elm327_command`
|
| 290 |
+
|
| 291 |
+
Send a command to the ELM327 OBD-II adapter and receive the response.
|
| 292 |
+
|
| 293 |
+
- **Parameters**:
|
| 294 |
+
- `command` (string, required) - ELM327 command to send (e.g., "ATZ", "01 0C", "03")
|
| 295 |
+
- **Returns**: Text response from ELM327 adapter
|
| 296 |
+
- **Example commands**:
|
| 297 |
+
- `"ATZ"` - Reset ELM327
|
| 298 |
+
- `"01 0C"` - Get engine RPM
|
| 299 |
+
- `"01 0D"` - Get vehicle speed
|
| 300 |
+
- `"03"` - Read stored DTCs
|
| 301 |
+
|
| 302 |
+
### 3. `get_elm327_history`
|
| 303 |
+
|
| 304 |
+
Retrieve historical log of OBD-II data (RPM, speed, coolant temperature) with streaming support.
|
| 305 |
+
|
| 306 |
+
- **Parameters**:
|
| 307 |
+
- `count` (number, optional) - Number of most recent records to retrieve
|
| 308 |
+
- Default: 100
|
| 309 |
+
- Set to 0 for all available records
|
| 310 |
+
- **Returns**: Array of historical data entries, each containing:
|
| 311 |
+
- `seq` - Sequence number
|
| 312 |
+
- `time` - Timestamp in seconds
|
| 313 |
+
- `rpm` - Engine RPM
|
| 314 |
+
- `speed` - Vehicle speed (km/h)
|
| 315 |
+
- `coolant_temp` - Coolant temperature (°C)
|
| 316 |
+
|
| 317 |
+
**Note**: This tool uses streaming to efficiently handle large datasets.
|
| 318 |
+
|
| 319 |
+
### Adding Custom Tools
|
| 320 |
+
|
| 321 |
+
Additional tools can be added by:
|
| 322 |
+
|
| 323 |
+
1. Defining the tool handler in `Src/App/mcp/mcp_tools.c`
|
| 324 |
+
2. Adding the tool definition to the `tools[]` array
|
| 325 |
+
3. Rebuilding and flashing the firmware
|
| 326 |
+
|
| 327 |
+
## Development
|
| 328 |
+
|
| 329 |
+
### Adding New MCP Tools
|
| 330 |
+
|
| 331 |
+
1. Define tool schema in `Src/App/mcp/mcp_tools.c`
|
| 332 |
+
2. Implement handler in `Src/App/mcp/mcp_methods.c`
|
| 333 |
+
3. Register method in the method dispatch table
|
| 334 |
+
4. Rebuild and flash firmware
|
| 335 |
+
|
| 336 |
+
### Debugging
|
| 337 |
+
|
| 338 |
+
Serial debug output is available at 115200 baud. Use a serial terminal to view logs:
|
| 339 |
+
|
| 340 |
+
```bash
|
| 341 |
+
# Linux/Mac
|
| 342 |
+
screen /dev/ttyUSB0 115200
|
| 343 |
+
|
| 344 |
+
# Windows
|
| 345 |
+
putty -serial COM3 -sercfg 115200,8,n,1,N
|
| 346 |
+
```
|
| 347 |
+
|
| 348 |
+
### OTA Updates
|
| 349 |
+
|
| 350 |
+
To perform OTA firmware update:
|
| 351 |
+
|
| 352 |
+
1. Build new firmware
|
| 353 |
+
2. Host the `.fls` file on your firmware server
|
| 354 |
+
3. Configure `FIRMWARE_SERVER` and `FIRMWARE_FILENAME` in `wm_app_config.h`
|
| 355 |
+
4. The device will check for updates on boot
|
| 356 |
+
5. If available, firmware will be downloaded and applied automatically
|
| 357 |
+
|
| 358 |
+
## Supported OBD-II PIDs
|
| 359 |
+
|
| 360 |
+
When using the simulator or real ELM327, the following PIDs are supported:
|
| 361 |
+
|
| 362 |
+
### Mode 01 (Current Data)
|
| 363 |
+
|
| 364 |
+
- `01 0C` - Engine RPM
|
| 365 |
+
- `01 0D` - Vehicle Speed
|
| 366 |
+
- `01 05` - Engine Coolant Temperature
|
| 367 |
+
- `01 04` - Calculated Engine Load
|
| 368 |
+
- `01 0F` - Intake Air Temperature
|
| 369 |
+
- And many more (see simulation documentation)
|
| 370 |
+
|
| 371 |
+
### Mode 03
|
| 372 |
+
|
| 373 |
+
- `03` - Read stored diagnostic trouble codes
|
| 374 |
+
|
| 375 |
+
### Mode 04
|
| 376 |
+
|
| 377 |
+
- `04` - Clear diagnostic trouble codes
|
| 378 |
+
|
| 379 |
+
### Mode 07
|
| 380 |
+
|
| 381 |
+
- `07` - Read pending diagnostic trouble codes
|
| 382 |
+
|
| 383 |
+
### Mode 09 (Vehicle Information)
|
| 384 |
+
|
| 385 |
+
- `09 02` - Vehicle Identification Number (VIN)
|
| 386 |
+
- `09 04` - Calibration ID
|
| 387 |
+
- `09 0A` - ECU Name
|
| 388 |
+
|
| 389 |
+
For complete PID list, see `Src/App/elm327/README_SIMULATION.md`.
|
| 390 |
+
|
| 391 |
+
## Troubleshooting
|
| 392 |
+
|
| 393 |
+
### WiFi Connection Issues
|
| 394 |
+
|
| 395 |
+
- Verify WiFi credentials in `wifi_credentials.h`
|
| 396 |
+
- Check serial output for connection status
|
| 397 |
+
- Ensure WiFi network is 2.4GHz (W600 does not support 5GHz)
|
| 398 |
+
|
| 399 |
+
### ELM327 Communication Issues
|
| 400 |
+
|
| 401 |
+
- Check UART wiring (TX, RX, GND)
|
| 402 |
+
- Verify baud rate configuration (default 9600 or 38400)
|
| 403 |
+
- Enable simulation mode for testing without hardware
|
| 404 |
+
|
| 405 |
+
### MCP Server Not Responding
|
| 406 |
+
|
| 407 |
+
- Verify device IP address from serial output
|
| 408 |
+
- Check firewall settings
|
| 409 |
+
- Ensure HTTP port 80 is accessible
|
| 410 |
+
|
| 411 |
+
## License
|
| 412 |
+
|
| 413 |
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
| 414 |
+
|
| 415 |
+
## Contributing
|
| 416 |
+
|
| 417 |
+
Contributions are welcome! Please feel free to submit issues or pull requests.
|
| 418 |
+
|
| 419 |
+
## References
|
| 420 |
+
|
| 421 |
+
- [Model Context Protocol (MCP) Specification](https://spec.modelcontextprotocol.io/)
|
| 422 |
+
- [ELM327 Command Reference](https://www.elmelectronics.com/wp-content/uploads/2016/07/ELM327DS.pdf)
|
| 423 |
+
- [OBD-II PIDs](https://en.wikipedia.org/wiki/OBD-II_PIDs)
|
| 424 |
+
- W600 SDK Documentation
|
MCP_servers/W600-embedded-OBD2/Src/App/easylogger/Makefile
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#/**************************************************************************
|
| 2 |
+
# * Kevin 2014-02-24 *
|
| 3 |
+
# **************************************************************************/
|
| 4 |
+
|
| 5 |
+
#---------------------------------------------------------------------------
|
| 6 |
+
# Constant Variable definition
|
| 7 |
+
#---------------------------------------------------------------------------
|
| 8 |
+
|
| 9 |
+
ifeq ($(COMPILER_OS_CYGWIN),1)
|
| 10 |
+
TOPDIR=../../..
|
| 11 |
+
endif
|
| 12 |
+
|
| 13 |
+
include $(TOPDIR)/Tools/toolchain.def
|
| 14 |
+
|
| 15 |
+
#---------------------------------------------------------------------------
|
| 16 |
+
# Target definition (User)
|
| 17 |
+
#---------------------------------------------------------------------------
|
| 18 |
+
GOAL = $(LIB_DIR)/wmeasylogger.$(LIBTYPE)
|
| 19 |
+
|
| 20 |
+
#---------------------------------------------------------------------------
|
| 21 |
+
# Source section (User)
|
| 22 |
+
#---------------------------------------------------------------------------
|
| 23 |
+
ASM_SRC +=
|
| 24 |
+
C_SRC += src/elog.c
|
| 25 |
+
C_SRC += src/elog_async.c
|
| 26 |
+
C_SRC += src/elog_buf.c
|
| 27 |
+
C_SRC += src/elog_utils.c
|
| 28 |
+
C_SRC += port/elog_port.c
|
| 29 |
+
#---------------------------------------------------------------------------
|
| 30 |
+
# Implicit rules
|
| 31 |
+
#---------------------------------------------------------------------------
|
| 32 |
+
.c.o:
|
| 33 |
+
$(CC) $(CFLAGS) -c -o $*.o $< $(INCLUDES)
|
| 34 |
+
|
| 35 |
+
.s.o:
|
| 36 |
+
$(ASM) $(ASMFLAGS) -c -o $*.o $< $(INCLUDES)
|
| 37 |
+
|
| 38 |
+
#---------------------------------------------------------------------------
|
| 39 |
+
# Explicit ruls
|
| 40 |
+
#---------------------------------------------------------------------------
|
| 41 |
+
OBJ_FILES = $(C_SRC:%.c=%.o) $(ASM_SRC:%.s=%.o)
|
| 42 |
+
|
| 43 |
+
all: $(GOAL)
|
| 44 |
+
|
| 45 |
+
$(GOAL): $(OBJ_FILES)
|
| 46 |
+
$(AR) $(ARFLAGS) $@ $(OBJ_FILES)
|
| 47 |
+
|
| 48 |
+
.PHONY: clean
|
| 49 |
+
clean:
|
| 50 |
+
$(RM) -f $(GOAL)
|
| 51 |
+
$(RM) -f $(OBJ_FILES:.o=.d) $(OBJ_FILES)
|
| 52 |
+
|
MCP_servers/W600-embedded-OBD2/Src/App/easylogger/inc/elog.h
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
* This file is part of the EasyLogger Library.
|
| 3 |
+
*
|
| 4 |
+
* Copyright (c) 2015-2019, Armink, <[email protected]>
|
| 5 |
+
*
|
| 6 |
+
* Permission is hereby granted, free of charge, to any person obtaining
|
| 7 |
+
* a copy of this software and associated documentation files (the
|
| 8 |
+
* 'Software'), to deal in the Software without restriction, including
|
| 9 |
+
* without limitation the rights to use, copy, modify, merge, publish,
|
| 10 |
+
* distribute, sublicense, and/or sell copies of the Software, and to
|
| 11 |
+
* permit persons to whom the Software is furnished to do so, subject to
|
| 12 |
+
* the following conditions:
|
| 13 |
+
*
|
| 14 |
+
* The above copyright notice and this permission notice shall be
|
| 15 |
+
* included in all copies or substantial portions of the Software.
|
| 16 |
+
*
|
| 17 |
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
| 18 |
+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 19 |
+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
| 20 |
+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
| 21 |
+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
| 22 |
+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
| 23 |
+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 24 |
+
*
|
| 25 |
+
* Function: It is an head file for this library. You can see all be called functions.
|
| 26 |
+
* Created on: 2015-04-28
|
| 27 |
+
*/
|
| 28 |
+
|
| 29 |
+
#ifndef __ELOG_H__
|
| 30 |
+
#define __ELOG_H__
|
| 31 |
+
|
| 32 |
+
#include <elog_cfg.h>
|
| 33 |
+
#include <stdint.h>
|
| 34 |
+
#include <stddef.h>
|
| 35 |
+
//#include <stdbool.h>
|
| 36 |
+
#include "wm_type_def.h"
|
| 37 |
+
|
| 38 |
+
#ifdef __cplusplus
|
| 39 |
+
extern "C" {
|
| 40 |
+
#endif
|
| 41 |
+
|
| 42 |
+
/* output log's level */
|
| 43 |
+
#define ELOG_LVL_ASSERT 0
|
| 44 |
+
#define ELOG_LVL_ERROR 1
|
| 45 |
+
#define ELOG_LVL_WARN 2
|
| 46 |
+
#define ELOG_LVL_INFO 3
|
| 47 |
+
#define ELOG_LVL_DEBUG 4
|
| 48 |
+
#define ELOG_LVL_VERBOSE 5
|
| 49 |
+
|
| 50 |
+
/* output log's level total number */
|
| 51 |
+
#define ELOG_LVL_TOTAL_NUM 6
|
| 52 |
+
|
| 53 |
+
/* EasyLogger software version number */
|
| 54 |
+
#define ELOG_SW_VERSION "2.1.0"
|
| 55 |
+
|
| 56 |
+
/* EasyLogger assert for developer. */
|
| 57 |
+
#ifdef ELOG_ASSERT_ENABLE
|
| 58 |
+
#define ELOG_ASSERT(EXPR) \
|
| 59 |
+
if (!(EXPR)) \
|
| 60 |
+
{ \
|
| 61 |
+
if (elog_assert_hook == NULL) { \
|
| 62 |
+
elog_a("elog", "(%s) has assert failed at %s:%ld.", #EXPR, __FUNCTION__, __LINE__); \
|
| 63 |
+
while (1); \
|
| 64 |
+
} else { \
|
| 65 |
+
elog_assert_hook(#EXPR, __FUNCTION__, __LINE__); \
|
| 66 |
+
} \
|
| 67 |
+
}
|
| 68 |
+
#else
|
| 69 |
+
#define ELOG_ASSERT(EXPR) ((void)0);
|
| 70 |
+
#endif
|
| 71 |
+
|
| 72 |
+
#ifndef ELOG_OUTPUT_ENABLE
|
| 73 |
+
#define elog_assert(tag, ...)
|
| 74 |
+
#define elog_error(tag, ...)
|
| 75 |
+
#define elog_warn(tag, ...)
|
| 76 |
+
#define elog_info(tag, ...)
|
| 77 |
+
#define elog_debug(tag, ...)
|
| 78 |
+
#define elog_verbose(tag, ...)
|
| 79 |
+
#else /* ELOG_OUTPUT_ENABLE */
|
| 80 |
+
#if ELOG_OUTPUT_LVL >= ELOG_LVL_ASSERT
|
| 81 |
+
#define elog_assert(tag, ...) \
|
| 82 |
+
elog_output(ELOG_LVL_ASSERT, tag, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
|
| 83 |
+
#else
|
| 84 |
+
#define elog_assert(tag, ...)
|
| 85 |
+
#endif /* ELOG_OUTPUT_LVL >= ELOG_LVL_ASSERT */
|
| 86 |
+
|
| 87 |
+
#if ELOG_OUTPUT_LVL >= ELOG_LVL_ERROR
|
| 88 |
+
#define elog_error(tag, ...) \
|
| 89 |
+
elog_output(ELOG_LVL_ERROR, tag, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
|
| 90 |
+
#else
|
| 91 |
+
#define elog_error(tag, ...)
|
| 92 |
+
#endif /* ELOG_OUTPUT_LVL >= ELOG_LVL_ERROR */
|
| 93 |
+
|
| 94 |
+
#if ELOG_OUTPUT_LVL >= ELOG_LVL_WARN
|
| 95 |
+
#define elog_warn(tag, ...) \
|
| 96 |
+
elog_output(ELOG_LVL_WARN, tag, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
|
| 97 |
+
#else
|
| 98 |
+
#define elog_warn(tag, ...)
|
| 99 |
+
#endif /* ELOG_OUTPUT_LVL >= ELOG_LVL_WARN */
|
| 100 |
+
|
| 101 |
+
#if ELOG_OUTPUT_LVL >= ELOG_LVL_INFO
|
| 102 |
+
#define elog_info(tag, ...) \
|
| 103 |
+
elog_output(ELOG_LVL_INFO, tag, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
|
| 104 |
+
#else
|
| 105 |
+
#define elog_info(tag, ...)
|
| 106 |
+
#endif /* ELOG_OUTPUT_LVL >= ELOG_LVL_INFO */
|
| 107 |
+
|
| 108 |
+
#if ELOG_OUTPUT_LVL >= ELOG_LVL_DEBUG
|
| 109 |
+
#define elog_debug(tag, ...) \
|
| 110 |
+
elog_output(ELOG_LVL_DEBUG, tag, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
|
| 111 |
+
#else
|
| 112 |
+
#define elog_debug(tag, ...)
|
| 113 |
+
#endif /* ELOG_OUTPUT_LVL >= ELOG_LVL_DEBUG */
|
| 114 |
+
|
| 115 |
+
#if ELOG_OUTPUT_LVL == ELOG_LVL_VERBOSE
|
| 116 |
+
#define elog_verbose(tag, ...) \
|
| 117 |
+
elog_output(ELOG_LVL_VERBOSE, tag, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
|
| 118 |
+
#else
|
| 119 |
+
#define elog_verbose(tag, ...)
|
| 120 |
+
#endif /* ELOG_OUTPUT_LVL == ELOG_LVL_VERBOSE */
|
| 121 |
+
#endif /* ELOG_OUTPUT_ENABLE */
|
| 122 |
+
|
| 123 |
+
/* all formats index */
|
| 124 |
+
typedef enum {
|
| 125 |
+
ELOG_FMT_LVL = 1 << 0, /**< level */
|
| 126 |
+
ELOG_FMT_TAG = 1 << 1, /**< tag */
|
| 127 |
+
ELOG_FMT_TIME = 1 << 2, /**< current time */
|
| 128 |
+
ELOG_FMT_P_INFO = 1 << 3, /**< process info */
|
| 129 |
+
ELOG_FMT_T_INFO = 1 << 4, /**< thread info */
|
| 130 |
+
ELOG_FMT_DIR = 1 << 5, /**< file directory and name */
|
| 131 |
+
ELOG_FMT_FUNC = 1 << 6, /**< function name */
|
| 132 |
+
ELOG_FMT_LINE = 1 << 7, /**< line number */
|
| 133 |
+
} ElogFmtIndex;
|
| 134 |
+
|
| 135 |
+
/* macro definition for all formats */
|
| 136 |
+
#define ELOG_FMT_ALL (ELOG_FMT_LVL|ELOG_FMT_TAG|ELOG_FMT_TIME|ELOG_FMT_P_INFO|ELOG_FMT_T_INFO| \
|
| 137 |
+
ELOG_FMT_DIR|ELOG_FMT_FUNC|ELOG_FMT_LINE)
|
| 138 |
+
|
| 139 |
+
/* output log's filter */
|
| 140 |
+
typedef struct {
|
| 141 |
+
uint8_t level;
|
| 142 |
+
char tag[ELOG_FILTER_TAG_MAX_LEN + 1];
|
| 143 |
+
char keyword[ELOG_FILTER_KW_MAX_LEN + 1];
|
| 144 |
+
} ElogFilter, *ElogFilter_t;
|
| 145 |
+
|
| 146 |
+
/* easy logger */
|
| 147 |
+
typedef struct {
|
| 148 |
+
ElogFilter filter;
|
| 149 |
+
size_t enabled_fmt_set[ELOG_LVL_TOTAL_NUM];
|
| 150 |
+
bool output_enabled;
|
| 151 |
+
bool output_lock_enabled;
|
| 152 |
+
bool output_is_locked_before_enable;
|
| 153 |
+
bool output_is_locked_before_disable;
|
| 154 |
+
|
| 155 |
+
#ifdef ELOG_COLOR_ENABLE
|
| 156 |
+
bool text_color_enabled;
|
| 157 |
+
#endif
|
| 158 |
+
|
| 159 |
+
}EasyLogger, *EasyLogger_t;
|
| 160 |
+
|
| 161 |
+
/* EasyLogger error code */
|
| 162 |
+
typedef enum {
|
| 163 |
+
ELOG_NO_ERR,
|
| 164 |
+
ELOG_ERR,
|
| 165 |
+
} ElogErrCode;
|
| 166 |
+
|
| 167 |
+
/* elog.c */
|
| 168 |
+
ElogErrCode elog_init(void);
|
| 169 |
+
void elog_start(void);
|
| 170 |
+
void elog_set_output_enabled(bool enabled);
|
| 171 |
+
bool elog_get_output_enabled(void);
|
| 172 |
+
void elog_set_text_color_enabled(bool enabled);
|
| 173 |
+
bool elog_get_text_color_enabled(void);
|
| 174 |
+
void elog_set_fmt(uint8_t level, size_t set);
|
| 175 |
+
void elog_set_filter(uint8_t level, const char *tag, const char *keyword);
|
| 176 |
+
void elog_set_filter_lvl(uint8_t level);
|
| 177 |
+
void elog_set_filter_tag(const char *tag);
|
| 178 |
+
void elog_set_filter_kw(const char *keyword);
|
| 179 |
+
void elog_raw(const char *format, ...);
|
| 180 |
+
void elog_output(uint8_t level, const char *tag, const char *file, const char *func,
|
| 181 |
+
const long line, const char *format, ...);
|
| 182 |
+
void elog_output_lock_enabled(bool enabled);
|
| 183 |
+
extern void (*elog_assert_hook)(const char* expr, const char* func, size_t line);
|
| 184 |
+
void elog_assert_set_hook(void (*hook)(const char* expr, const char* func, size_t line));
|
| 185 |
+
int8_t elog_find_lvl(const char *log);
|
| 186 |
+
const char *elog_find_tag(const char *log, uint8_t lvl, size_t *tag_len);
|
| 187 |
+
void elog_hexdump(const char *name, uint8_t width, uint8_t *buf, uint16_t size);
|
| 188 |
+
|
| 189 |
+
#define elog_a(tag, ...) elog_assert(tag, __VA_ARGS__)
|
| 190 |
+
#define elog_e(tag, ...) elog_error(tag, __VA_ARGS__)
|
| 191 |
+
#define elog_w(tag, ...) elog_warn(tag, __VA_ARGS__)
|
| 192 |
+
#define elog_i(tag, ...) elog_info(tag, __VA_ARGS__)
|
| 193 |
+
#define elog_d(tag, ...) elog_debug(tag, __VA_ARGS__)
|
| 194 |
+
#define elog_v(tag, ...) elog_verbose(tag, __VA_ARGS__)
|
| 195 |
+
|
| 196 |
+
/**
|
| 197 |
+
* log API short definition
|
| 198 |
+
* NOTE: The `LOG_TAG` and `LOG_LVL` must defined before including the <elog.h> when you want to use log_x API.
|
| 199 |
+
*/
|
| 200 |
+
#if !defined(LOG_TAG)
|
| 201 |
+
#define LOG_TAG "None"
|
| 202 |
+
#endif
|
| 203 |
+
#if !defined(LOG_LVL)
|
| 204 |
+
#define LOG_LVL ELOG_LVL_VERBOSE
|
| 205 |
+
#endif
|
| 206 |
+
#if LOG_LVL >= ELOG_LVL_ASSERT
|
| 207 |
+
#define log_a(...) elog_a(LOG_TAG, __VA_ARGS__)
|
| 208 |
+
#else
|
| 209 |
+
#define log_a(...) ((void)0);
|
| 210 |
+
#endif
|
| 211 |
+
#if LOG_LVL >= ELOG_LVL_ERROR
|
| 212 |
+
#define log_e(...) elog_e(LOG_TAG, __VA_ARGS__)
|
| 213 |
+
#else
|
| 214 |
+
#define log_e(...) ((void)0);
|
| 215 |
+
#endif
|
| 216 |
+
#if LOG_LVL >= ELOG_LVL_WARN
|
| 217 |
+
#define log_w(...) elog_w(LOG_TAG, __VA_ARGS__)
|
| 218 |
+
#else
|
| 219 |
+
#define log_w(...) ((void)0);
|
| 220 |
+
#endif
|
| 221 |
+
#if LOG_LVL >= ELOG_LVL_INFO
|
| 222 |
+
#define log_i(...) elog_i(LOG_TAG, __VA_ARGS__)
|
| 223 |
+
#else
|
| 224 |
+
#define log_i(...) ((void)0);
|
| 225 |
+
#endif
|
| 226 |
+
#if LOG_LVL >= ELOG_LVL_DEBUG
|
| 227 |
+
#define log_d(...) elog_d(LOG_TAG, __VA_ARGS__)
|
| 228 |
+
#else
|
| 229 |
+
#define log_d(...) ((void)0);
|
| 230 |
+
#endif
|
| 231 |
+
#if LOG_LVL >= ELOG_LVL_VERBOSE
|
| 232 |
+
#define log_v(...) elog_v(LOG_TAG, __VA_ARGS__)
|
| 233 |
+
#else
|
| 234 |
+
#define log_v(...) ((void)0);
|
| 235 |
+
#endif
|
| 236 |
+
|
| 237 |
+
/* assert API short definition */
|
| 238 |
+
#if !defined(assert)
|
| 239 |
+
#define assert ELOG_ASSERT
|
| 240 |
+
#endif
|
| 241 |
+
|
| 242 |
+
/* elog_buf.c */
|
| 243 |
+
void elog_buf_enabled(bool enabled);
|
| 244 |
+
void elog_flush(void);
|
| 245 |
+
|
| 246 |
+
/* elog_async.c */
|
| 247 |
+
void elog_async_enabled(bool enabled);
|
| 248 |
+
size_t elog_async_get_log(char *log, size_t size);
|
| 249 |
+
size_t elog_async_get_line_log(char *log, size_t size);
|
| 250 |
+
|
| 251 |
+
/* elog_utils.c */
|
| 252 |
+
size_t elog_strcpy(size_t cur_len, char *dst, const char *src);
|
| 253 |
+
size_t elog_cpyln(char *line, const char *log, size_t len);
|
| 254 |
+
void *elog_memcpy(void *dst, const void *src, size_t count);
|
| 255 |
+
|
| 256 |
+
#ifdef __cplusplus
|
| 257 |
+
}
|
| 258 |
+
#endif
|
| 259 |
+
|
| 260 |
+
#endif /* __ELOG_H__ */
|
MCP_servers/W600-embedded-OBD2/Src/App/easylogger/inc/elog_cfg.h
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
* This file is part of the EasyLogger Library.
|
| 3 |
+
*
|
| 4 |
+
* Copyright (c) 2015-2016, Armink, <[email protected]>
|
| 5 |
+
*
|
| 6 |
+
* Permission is hereby granted, free of charge, to any person obtaining
|
| 7 |
+
* a copy of this software and associated documentation files (the
|
| 8 |
+
* 'Software'), to deal in the Software without restriction, including
|
| 9 |
+
* without limitation the rights to use, copy, modify, merge, publish,
|
| 10 |
+
* distribute, sublicense, and/or sell copies of the Software, and to
|
| 11 |
+
* permit persons to whom the Software is furnished to do so, subject to
|
| 12 |
+
* the following conditions:
|
| 13 |
+
*
|
| 14 |
+
* The above copyright notice and this permission notice shall be
|
| 15 |
+
* included in all copies or substantial portions of the Software.
|
| 16 |
+
*
|
| 17 |
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
| 18 |
+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 19 |
+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
| 20 |
+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
| 21 |
+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
| 22 |
+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
| 23 |
+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 24 |
+
*
|
| 25 |
+
* Function: It is the configure head file for this library.
|
| 26 |
+
* Created on: 2015-07-30
|
| 27 |
+
*/
|
| 28 |
+
|
| 29 |
+
#ifndef _ELOG_CFG_H_
|
| 30 |
+
#define _ELOG_CFG_H_
|
| 31 |
+
/*---------------------------------------------------------------------------*/
|
| 32 |
+
/* enable log output. */
|
| 33 |
+
#define ELOG_OUTPUT_ENABLE
|
| 34 |
+
/* setting static output log level. range: from ELOG_LVL_ASSERT to ELOG_LVL_VERBOSE */
|
| 35 |
+
#define ELOG_OUTPUT_LVL ELOG_LVL_INFO
|
| 36 |
+
/* enable assert check */
|
| 37 |
+
#define ELOG_ASSERT_ENABLE
|
| 38 |
+
/* buffer size for every line's log */
|
| 39 |
+
#define ELOG_LINE_BUF_SIZE 1024
|
| 40 |
+
/* output line number max length */
|
| 41 |
+
#define ELOG_LINE_NUM_MAX_LEN 5
|
| 42 |
+
/* output filter's tag max length */
|
| 43 |
+
#define ELOG_FILTER_TAG_MAX_LEN 30
|
| 44 |
+
/* output filter's keyword max length */
|
| 45 |
+
#define ELOG_FILTER_KW_MAX_LEN 16
|
| 46 |
+
/* output newline sign */
|
| 47 |
+
#define ELOG_NEWLINE_SIGN "\n"
|
| 48 |
+
/*---------------------------------------------------------------------------*/
|
| 49 |
+
/* enable log color */
|
| 50 |
+
#define ELOG_COLOR_ENABLE
|
| 51 |
+
/* change the some level logs to not default color if you want */
|
| 52 |
+
#define ELOG_COLOR_ASSERT (F_MAGENTA B_NULL S_NORMAL)
|
| 53 |
+
#define ELOG_COLOR_ERROR (F_RED B_NULL S_NORMAL)
|
| 54 |
+
#define ELOG_COLOR_WARN (F_YELLOW B_NULL S_NORMAL)
|
| 55 |
+
#define ELOG_COLOR_INFO (F_CYAN B_NULL S_NORMAL)
|
| 56 |
+
#define ELOG_COLOR_DEBUG (F_GREEN B_NULL S_NORMAL)
|
| 57 |
+
#define ELOG_COLOR_VERBOSE (F_BLUE B_NULL S_NORMAL)
|
| 58 |
+
/*---------------------------------------------------------------------------*/
|
| 59 |
+
/* enable asynchronous output mode */
|
| 60 |
+
//#define ELOG_ASYNC_OUTPUT_ENABLE
|
| 61 |
+
/* the highest output level for async mode, other level will sync output */
|
| 62 |
+
#define ELOG_ASYNC_OUTPUT_LVL ELOG_LVL_ASSERT
|
| 63 |
+
/* buffer size for asynchronous output mode */
|
| 64 |
+
#define ELOG_ASYNC_OUTPUT_BUF_SIZE (ELOG_LINE_BUF_SIZE * 10)
|
| 65 |
+
/* each asynchronous output's log which must end with newline sign */
|
| 66 |
+
#define ELOG_ASYNC_LINE_OUTPUT
|
| 67 |
+
/* asynchronous output mode using POSIX pthread implementation */
|
| 68 |
+
#define ELOG_ASYNC_OUTPUT_USING_PTHREAD
|
| 69 |
+
/*---------------------------------------------------------------------------*/
|
| 70 |
+
/* enable buffered output mode */
|
| 71 |
+
//#define ELOG_BUF_OUTPUT_ENABLE
|
| 72 |
+
/* buffer size for buffered output mode */
|
| 73 |
+
#define ELOG_BUF_OUTPUT_BUF_SIZE (ELOG_LINE_BUF_SIZE * 10)
|
| 74 |
+
|
| 75 |
+
#endif /* _ELOG_CFG_H_ */
|
MCP_servers/W600-embedded-OBD2/Src/App/easylogger/port/elog_port.c
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
* This file is part of the EasyLogger Library.
|
| 3 |
+
*
|
| 4 |
+
* Copyright (c) 2015, Armink, <[email protected]>
|
| 5 |
+
*
|
| 6 |
+
* Permission is hereby granted, free of charge, to any person obtaining
|
| 7 |
+
* a copy of this software and associated documentation files (the
|
| 8 |
+
* 'Software'), to deal in the Software without restriction, including
|
| 9 |
+
* without limitation the rights to use, copy, modify, merge, publish,
|
| 10 |
+
* distribute, sublicense, and/or sell copies of the Software, and to
|
| 11 |
+
* permit persons to whom the Software is furnished to do so, subject to
|
| 12 |
+
* the following conditions:
|
| 13 |
+
*
|
| 14 |
+
* The above copyright notice and this permission notice shall be
|
| 15 |
+
* included in all copies or substantial portions of the Software.
|
| 16 |
+
*
|
| 17 |
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
| 18 |
+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 19 |
+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
| 20 |
+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
| 21 |
+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
| 22 |
+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
| 23 |
+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 24 |
+
*
|
| 25 |
+
* Function: Portable interface for each platform.
|
| 26 |
+
* Created on: 2015-04-28
|
| 27 |
+
*/
|
| 28 |
+
|
| 29 |
+
#include <elog.h>
|
| 30 |
+
|
| 31 |
+
#include <string.h>
|
| 32 |
+
#include "wm_include.h"
|
| 33 |
+
#include "task.h"
|
| 34 |
+
|
| 35 |
+
#if (TLS_CONFIG_UDP_SYSLOG == CFG_ON)
|
| 36 |
+
#include "wm_sockets.h"
|
| 37 |
+
#include <stdio.h>
|
| 38 |
+
#endif
|
| 39 |
+
|
| 40 |
+
#define ELOG_USE_TASK_OUTPUT 1
|
| 41 |
+
|
| 42 |
+
#if ELOG_USE_TASK_OUTPUT
|
| 43 |
+
|
| 44 |
+
#define ELOG_QUEUE_SIZE 32
|
| 45 |
+
#define ELOG_TASK_PRIO 30
|
| 46 |
+
#define ELOG_TASK_SIZE 256
|
| 47 |
+
|
| 48 |
+
static tls_os_queue_t *elog_queue = NULL;
|
| 49 |
+
|
| 50 |
+
static OS_STK elog_task_stk[ELOG_TASK_SIZE];
|
| 51 |
+
|
| 52 |
+
#else /* ELOG_USE_TASK_OUTPUT */
|
| 53 |
+
|
| 54 |
+
static tls_os_sem_t *elog_lock = NULL;
|
| 55 |
+
|
| 56 |
+
#endif /* ELOG_USE_TASK_OUTPUT */
|
| 57 |
+
|
| 58 |
+
#if (TLS_CONFIG_UDP_SYSLOG == CFG_ON)
|
| 59 |
+
static int syslog_socket = -1;
|
| 60 |
+
static struct sockaddr_in syslog_server;
|
| 61 |
+
static bool syslog_initialized = FALSE;
|
| 62 |
+
|
| 63 |
+
/* Syslog severity levels (RFC 3164) */
|
| 64 |
+
#define SYSLOG_SEVERITY_EMERG 0 /* Emergency - system is unusable */
|
| 65 |
+
#define SYSLOG_SEVERITY_ALERT 1 /* Alert - action must be taken immediately */
|
| 66 |
+
#define SYSLOG_SEVERITY_CRIT 2 /* Critical */
|
| 67 |
+
#define SYSLOG_SEVERITY_ERR 3 /* Error */
|
| 68 |
+
#define SYSLOG_SEVERITY_WARNING 4 /* Warning */
|
| 69 |
+
#define SYSLOG_SEVERITY_NOTICE 5 /* Notice - normal but significant */
|
| 70 |
+
#define SYSLOG_SEVERITY_INFO 6 /* Informational */
|
| 71 |
+
#define SYSLOG_SEVERITY_DEBUG 7 /* Debug */
|
| 72 |
+
|
| 73 |
+
/* Syslog facility - user-level messages */
|
| 74 |
+
#define SYSLOG_FACILITY_USER 1
|
| 75 |
+
|
| 76 |
+
/* Calculate priority value: (facility * 8) + severity */
|
| 77 |
+
#define SYSLOG_PRIORITY(facility, severity) ((facility << 3) | severity)
|
| 78 |
+
|
| 79 |
+
/**
|
| 80 |
+
* Parse severity level from formatted log string
|
| 81 |
+
*
|
| 82 |
+
* @param log Formatted log message
|
| 83 |
+
* @param size Message size
|
| 84 |
+
* @return syslog severity (0-7)
|
| 85 |
+
*/
|
| 86 |
+
static int elog_parse_severity(const char *log, size_t size) {
|
| 87 |
+
int severity = SYSLOG_SEVERITY_INFO; /* Default severity */
|
| 88 |
+
|
| 89 |
+
/* Try to extract severity from log prefix if available */
|
| 90 |
+
if (size > 2 && log[0] == '[') {
|
| 91 |
+
if (log[1] == 'A') severity = SYSLOG_SEVERITY_EMERG; /* [A]ssert */
|
| 92 |
+
else if (log[1] == 'E') severity = SYSLOG_SEVERITY_ERR; /* [E]rror */
|
| 93 |
+
else if (log[1] == 'W') severity = SYSLOG_SEVERITY_WARNING; /* [W]arn */
|
| 94 |
+
else if (log[1] == 'I') severity = SYSLOG_SEVERITY_INFO; /* [I]nfo */
|
| 95 |
+
else if (log[1] == 'D') severity = SYSLOG_SEVERITY_DEBUG; /* [D]ebug */
|
| 96 |
+
else if (log[1] == 'V') severity = SYSLOG_SEVERITY_DEBUG; /* [V]erbose */
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
return severity;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
/**
|
| 103 |
+
* Send syslog message via UDP
|
| 104 |
+
*
|
| 105 |
+
* @param severity Syslog severity level (0-7)
|
| 106 |
+
* @param log Log message
|
| 107 |
+
* @param size Message size
|
| 108 |
+
*/
|
| 109 |
+
static void elog_syslog_send(int severity, const char *log, size_t size) {
|
| 110 |
+
if (!syslog_initialized || syslog_socket < 0) {
|
| 111 |
+
return;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
/* RFC 3164 syslog format: <PRI>TIMESTAMP HOSTNAME MESSAGE
|
| 115 |
+
* We use a simplified version without timestamp for embedded systems
|
| 116 |
+
* Format: <PRI>HOSTNAME: MESSAGE
|
| 117 |
+
*
|
| 118 |
+
* Use heap allocation to avoid stack overflow in task with limited stack
|
| 119 |
+
*/
|
| 120 |
+
char *syslog_msg = tls_mem_alloc(512);
|
| 121 |
+
if (!syslog_msg) {
|
| 122 |
+
return; /* Silently fail if out of memory */
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
int priority = SYSLOG_PRIORITY(SYSLOG_FACILITY_USER, severity);
|
| 126 |
+
|
| 127 |
+
/* Build syslog message */
|
| 128 |
+
int len = snprintf(syslog_msg, 512, "<%d>%s: %.*s",
|
| 129 |
+
priority, TLS_CONFIG_UDP_SYSLOG_HOSTNAME, (int)size, log);
|
| 130 |
+
|
| 131 |
+
if (len > 0 && len < 512) {
|
| 132 |
+
/* Send to syslog server - non-blocking, we don't care if it fails */
|
| 133 |
+
sendto(syslog_socket, syslog_msg, len, 0,
|
| 134 |
+
(struct sockaddr *)&syslog_server, sizeof(syslog_server));
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
tls_mem_free(syslog_msg);
|
| 138 |
+
}
|
| 139 |
+
#endif
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
#if ELOG_USE_TASK_OUTPUT
|
| 143 |
+
static void elog_task_entry(void *data)
|
| 144 |
+
{
|
| 145 |
+
int ret;
|
| 146 |
+
void *msg;
|
| 147 |
+
|
| 148 |
+
for( ; ; )
|
| 149 |
+
{
|
| 150 |
+
ret = tls_os_queue_receive(elog_queue, (void **)&msg, 0, 0);
|
| 151 |
+
if (TLS_OS_SUCCESS == ret)
|
| 152 |
+
{
|
| 153 |
+
char *log_msg = (char *)msg;
|
| 154 |
+
size_t log_size = strlen(log_msg);
|
| 155 |
+
|
| 156 |
+
/* Output to console */
|
| 157 |
+
printf("%s", log_msg);
|
| 158 |
+
|
| 159 |
+
#if (TLS_CONFIG_UDP_SYSLOG == CFG_ON)
|
| 160 |
+
/* Send to UDP syslog server (safe from task context) */
|
| 161 |
+
if (syslog_initialized && syslog_socket >= 0) {
|
| 162 |
+
int severity = elog_parse_severity(log_msg, log_size);
|
| 163 |
+
elog_syslog_send(severity, log_msg, log_size);
|
| 164 |
+
}
|
| 165 |
+
#endif
|
| 166 |
+
|
| 167 |
+
tls_mem_free(msg);
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
#endif
|
| 172 |
+
|
| 173 |
+
#if (TLS_CONFIG_UDP_SYSLOG == CFG_ON)
|
| 174 |
+
/**
|
| 175 |
+
* Initialize UDP syslog
|
| 176 |
+
*
|
| 177 |
+
* @return 0 on success, -1 on failure
|
| 178 |
+
*/
|
| 179 |
+
static int elog_syslog_init(void) {
|
| 180 |
+
/* Create UDP socket */
|
| 181 |
+
syslog_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
| 182 |
+
if (syslog_socket < 0) {
|
| 183 |
+
printf("Failed to create syslog socket\r\n");
|
| 184 |
+
return -1;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
/* Set socket to non-blocking mode to avoid blocking on send */
|
| 188 |
+
int flags = 1;
|
| 189 |
+
if (ioctlsocket(syslog_socket, FIONBIO, &flags) < 0) {
|
| 190 |
+
printf("Failed to set syslog socket non-blocking\r\n");
|
| 191 |
+
closesocket(syslog_socket);
|
| 192 |
+
syslog_socket = -1;
|
| 193 |
+
return -1;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
/* Configure syslog server address */
|
| 197 |
+
memset(&syslog_server, 0, sizeof(syslog_server));
|
| 198 |
+
syslog_server.sin_family = AF_INET;
|
| 199 |
+
syslog_server.sin_port = htons(TLS_CONFIG_UDP_SYSLOG_SERVER_PORT);
|
| 200 |
+
syslog_server.sin_addr.s_addr = ipaddr_addr(TLS_CONFIG_UDP_SYSLOG_SERVER_IP);
|
| 201 |
+
|
| 202 |
+
syslog_initialized = TRUE;
|
| 203 |
+
printf("UDP Syslog initialized - Server: %s:%d\r\n",
|
| 204 |
+
TLS_CONFIG_UDP_SYSLOG_SERVER_IP, TLS_CONFIG_UDP_SYSLOG_SERVER_PORT);
|
| 205 |
+
|
| 206 |
+
return 0;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
/**
|
| 210 |
+
* Map EasyLogger level to syslog severity
|
| 211 |
+
*
|
| 212 |
+
* @param level EasyLogger level
|
| 213 |
+
* @return syslog severity (0-7)
|
| 214 |
+
*/
|
| 215 |
+
static int elog_level_to_syslog_severity(uint8_t level) {
|
| 216 |
+
switch (level) {
|
| 217 |
+
case ELOG_LVL_ASSERT:
|
| 218 |
+
return SYSLOG_SEVERITY_EMERG;
|
| 219 |
+
case ELOG_LVL_ERROR:
|
| 220 |
+
return SYSLOG_SEVERITY_ERR;
|
| 221 |
+
case ELOG_LVL_WARN:
|
| 222 |
+
return SYSLOG_SEVERITY_WARNING;
|
| 223 |
+
case ELOG_LVL_INFO:
|
| 224 |
+
return SYSLOG_SEVERITY_INFO;
|
| 225 |
+
case ELOG_LVL_DEBUG:
|
| 226 |
+
return SYSLOG_SEVERITY_DEBUG;
|
| 227 |
+
case ELOG_LVL_VERBOSE:
|
| 228 |
+
return SYSLOG_SEVERITY_DEBUG;
|
| 229 |
+
default:
|
| 230 |
+
return SYSLOG_SEVERITY_NOTICE;
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
/**
|
| 235 |
+
* Cleanup UDP syslog
|
| 236 |
+
*/
|
| 237 |
+
static void elog_syslog_deinit(void) {
|
| 238 |
+
if (syslog_socket >= 0) {
|
| 239 |
+
closesocket(syslog_socket);
|
| 240 |
+
syslog_socket = -1;
|
| 241 |
+
}
|
| 242 |
+
syslog_initialized = FALSE;
|
| 243 |
+
}
|
| 244 |
+
#endif
|
| 245 |
+
|
| 246 |
+
/**
|
| 247 |
+
* EasyLogger port initialize
|
| 248 |
+
*
|
| 249 |
+
* @return result
|
| 250 |
+
*/
|
| 251 |
+
ElogErrCode elog_port_init(void) {
|
| 252 |
+
ElogErrCode result = ELOG_NO_ERR;
|
| 253 |
+
|
| 254 |
+
/* add your code here */
|
| 255 |
+
int ret = TLS_OS_SUCCESS;
|
| 256 |
+
|
| 257 |
+
#if ELOG_USE_TASK_OUTPUT
|
| 258 |
+
ret |= tls_os_queue_create(&elog_queue, ELOG_QUEUE_SIZE);
|
| 259 |
+
|
| 260 |
+
ret |= tls_os_task_create(NULL, NULL, elog_task_entry,
|
| 261 |
+
(void *)0, (void *)elog_task_stk,
|
| 262 |
+
ELOG_TASK_SIZE * sizeof(u32),
|
| 263 |
+
ELOG_TASK_PRIO, 0);
|
| 264 |
+
#else
|
| 265 |
+
ret |= tls_os_sem_create(&elog_lock, 1);
|
| 266 |
+
#endif
|
| 267 |
+
|
| 268 |
+
if (TLS_OS_SUCCESS != ret)
|
| 269 |
+
{
|
| 270 |
+
result = ELOG_ERR;
|
| 271 |
+
printf("elog init error\r\n");
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
#if (TLS_CONFIG_UDP_SYSLOG == CFG_ON)
|
| 275 |
+
/* Initialize UDP syslog - may fail if network not ready yet
|
| 276 |
+
* User can call wm_log_syslog_init() manually after network is up
|
| 277 |
+
*/
|
| 278 |
+
elog_syslog_init();
|
| 279 |
+
#endif
|
| 280 |
+
|
| 281 |
+
return result;
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
/**
|
| 285 |
+
* output log port interface
|
| 286 |
+
*
|
| 287 |
+
* @param log output of log
|
| 288 |
+
* @param size log size
|
| 289 |
+
*/
|
| 290 |
+
void elog_port_output(const char *log, size_t size) {
|
| 291 |
+
|
| 292 |
+
/* add your code here */
|
| 293 |
+
#if ELOG_USE_TASK_OUTPUT
|
| 294 |
+
char *msg = tls_mem_alloc(size + 1);
|
| 295 |
+
if (NULL == msg)
|
| 296 |
+
{
|
| 297 |
+
printf("elog malloc error: %.*s", (int)size, log);
|
| 298 |
+
return;
|
| 299 |
+
}
|
| 300 |
+
memcpy(msg, log, size);
|
| 301 |
+
msg[size] = '\0';
|
| 302 |
+
int ret = tls_os_queue_send(elog_queue, (void *)msg, 0);
|
| 303 |
+
if (TLS_OS_SUCCESS != ret)
|
| 304 |
+
{
|
| 305 |
+
tls_mem_free(msg);
|
| 306 |
+
printf("elog send error: %.*s", (int)size, log);
|
| 307 |
+
}
|
| 308 |
+
#else
|
| 309 |
+
printf("%.*s", (int)size, log);
|
| 310 |
+
|
| 311 |
+
#if (TLS_CONFIG_UDP_SYSLOG == CFG_ON)
|
| 312 |
+
/* WARNING: When ELOG_USE_TASK_OUTPUT is disabled, UDP syslog is sent
|
| 313 |
+
* directly in caller's context. This may cause deadlock if called from
|
| 314 |
+
* LwIP TCP/IP callbacks. Enable ELOG_USE_TASK_OUTPUT to avoid this issue.
|
| 315 |
+
*/
|
| 316 |
+
int severity = elog_parse_severity(log, size);
|
| 317 |
+
elog_syslog_send(severity, log, size);
|
| 318 |
+
#endif
|
| 319 |
+
#endif
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
/**
|
| 323 |
+
* output lock
|
| 324 |
+
*/
|
| 325 |
+
void elog_port_output_lock(void) {
|
| 326 |
+
|
| 327 |
+
/* add your code here */
|
| 328 |
+
#if ELOG_USE_TASK_OUTPUT
|
| 329 |
+
/* don't use lock */
|
| 330 |
+
#else
|
| 331 |
+
tls_os_sem_acquire(elog_lock, 0);
|
| 332 |
+
#endif
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
/**
|
| 336 |
+
* output unlock
|
| 337 |
+
*/
|
| 338 |
+
void elog_port_output_unlock(void) {
|
| 339 |
+
|
| 340 |
+
/* add your code here */
|
| 341 |
+
#if ELOG_USE_TASK_OUTPUT
|
| 342 |
+
/* don't use lock */
|
| 343 |
+
#else
|
| 344 |
+
tls_os_sem_release(elog_lock);
|
| 345 |
+
#endif
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
/**
|
| 349 |
+
* get current time interface
|
| 350 |
+
*
|
| 351 |
+
* @return current time
|
| 352 |
+
*/
|
| 353 |
+
const char *elog_port_get_time(void) {
|
| 354 |
+
|
| 355 |
+
/* add your code here */
|
| 356 |
+
static char elog_timestr[16];
|
| 357 |
+
u32 ticks = tls_os_get_time();
|
| 358 |
+
sprintf(elog_timestr, "%u.%02u", ticks / HZ, ticks % HZ);
|
| 359 |
+
return elog_timestr;
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
/**
|
| 363 |
+
* get current process name interface
|
| 364 |
+
*
|
| 365 |
+
* @return current process name
|
| 366 |
+
*/
|
| 367 |
+
const char *elog_port_get_p_info(void) {
|
| 368 |
+
|
| 369 |
+
/* add your code here */
|
| 370 |
+
return "";
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
/**
|
| 374 |
+
* get current thread name interface
|
| 375 |
+
*
|
| 376 |
+
* @return current thread name
|
| 377 |
+
*/
|
| 378 |
+
const char *elog_port_get_t_info(void) {
|
| 379 |
+
|
| 380 |
+
/* add your code here */
|
| 381 |
+
const char * pcTaskName;
|
| 382 |
+
const char * pcNoTask = "Task";
|
| 383 |
+
#if INCLUDE_pcTaskGetTaskName
|
| 384 |
+
if( xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED )
|
| 385 |
+
{
|
| 386 |
+
pcTaskName = (const char *)pcTaskGetTaskName( NULL );
|
| 387 |
+
}
|
| 388 |
+
else
|
| 389 |
+
#endif
|
| 390 |
+
{
|
| 391 |
+
pcTaskName = pcNoTask;
|
| 392 |
+
}
|
| 393 |
+
return pcTaskName;
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
void wm_log_init(void)
|
| 397 |
+
{
|
| 398 |
+
/* initialize EasyLogger */
|
| 399 |
+
elog_init();
|
| 400 |
+
|
| 401 |
+
/* set EasyLogger log format */
|
| 402 |
+
elog_set_fmt(ELOG_LVL_ASSERT, ELOG_FMT_ALL);
|
| 403 |
+
elog_set_fmt(ELOG_LVL_ERROR, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME);
|
| 404 |
+
elog_set_fmt(ELOG_LVL_WARN, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME);
|
| 405 |
+
elog_set_fmt(ELOG_LVL_INFO, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME);
|
| 406 |
+
elog_set_fmt(ELOG_LVL_DEBUG, ELOG_FMT_ALL & ~ELOG_FMT_FUNC);
|
| 407 |
+
elog_set_fmt(ELOG_LVL_VERBOSE, ELOG_FMT_ALL & ~ELOG_FMT_FUNC);
|
| 408 |
+
#ifdef ELOG_COLOR_ENABLE
|
| 409 |
+
elog_set_text_color_enabled(TRUE);
|
| 410 |
+
#endif
|
| 411 |
+
/* start EasyLogger */
|
| 412 |
+
elog_start();
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
#if (TLS_CONFIG_UDP_SYSLOG == CFG_ON)
|
| 416 |
+
/**
|
| 417 |
+
* Initialize UDP syslog manually
|
| 418 |
+
* Call this after network is up if automatic initialization failed
|
| 419 |
+
*
|
| 420 |
+
* @return 0 on success, -1 on failure
|
| 421 |
+
*/
|
| 422 |
+
int wm_log_syslog_init(void)
|
| 423 |
+
{
|
| 424 |
+
return elog_syslog_init();
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
/**
|
| 428 |
+
* Deinitialize UDP syslog
|
| 429 |
+
*/
|
| 430 |
+
void wm_log_syslog_deinit(void)
|
| 431 |
+
{
|
| 432 |
+
elog_syslog_deinit();
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
/**
|
| 436 |
+
* Check if UDP syslog is initialized and ready
|
| 437 |
+
*
|
| 438 |
+
* @return true if initialized, false otherwise
|
| 439 |
+
*/
|
| 440 |
+
bool wm_log_syslog_is_ready(void)
|
| 441 |
+
{
|
| 442 |
+
return syslog_initialized;
|
| 443 |
+
}
|
| 444 |
+
#endif
|
MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog.c
ADDED
|
@@ -0,0 +1,750 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
* This file is part of the EasyLogger Library.
|
| 3 |
+
*
|
| 4 |
+
* Copyright (c) 2015-2018, Armink, <[email protected]>
|
| 5 |
+
*
|
| 6 |
+
* Permission is hereby granted, free of charge, to any person obtaining
|
| 7 |
+
* a copy of this software and associated documentation files (the
|
| 8 |
+
* 'Software'), to deal in the Software without restriction, including
|
| 9 |
+
* without limitation the rights to use, copy, modify, merge, publish,
|
| 10 |
+
* distribute, sublicense, and/or sell copies of the Software, and to
|
| 11 |
+
* permit persons to whom the Software is furnished to do so, subject to
|
| 12 |
+
* the following conditions:
|
| 13 |
+
*
|
| 14 |
+
* The above copyright notice and this permission notice shall be
|
| 15 |
+
* included in all copies or substantial portions of the Software.
|
| 16 |
+
*
|
| 17 |
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
| 18 |
+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 19 |
+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
| 20 |
+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
| 21 |
+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
| 22 |
+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
| 23 |
+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 24 |
+
*
|
| 25 |
+
* Function: Initialize function and other general function.
|
| 26 |
+
* Created on: 2015-04-28
|
| 27 |
+
*/
|
| 28 |
+
|
| 29 |
+
#define LOG_TAG "elog"
|
| 30 |
+
|
| 31 |
+
#include <elog.h>
|
| 32 |
+
#include <string.h>
|
| 33 |
+
#include <stdarg.h>
|
| 34 |
+
#include <stdio.h>
|
| 35 |
+
|
| 36 |
+
#if !defined(ELOG_OUTPUT_LVL)
|
| 37 |
+
#error "Please configure static output log level (in elog_cfg.h)"
|
| 38 |
+
#endif
|
| 39 |
+
|
| 40 |
+
#if !defined(ELOG_LINE_NUM_MAX_LEN)
|
| 41 |
+
#error "Please configure output line number max length (in elog_cfg.h)"
|
| 42 |
+
#endif
|
| 43 |
+
|
| 44 |
+
#if !defined(ELOG_LINE_BUF_SIZE)
|
| 45 |
+
#error "Please configure buffer size for every line's log (in elog_cfg.h)"
|
| 46 |
+
#endif
|
| 47 |
+
|
| 48 |
+
#if !defined(ELOG_FILTER_TAG_MAX_LEN)
|
| 49 |
+
#error "Please configure output filter's tag max length (in elog_cfg.h)"
|
| 50 |
+
#endif
|
| 51 |
+
|
| 52 |
+
#if !defined(ELOG_FILTER_KW_MAX_LEN)
|
| 53 |
+
#error "Please configure output filter's keyword max length (in elog_cfg.h)"
|
| 54 |
+
#endif
|
| 55 |
+
|
| 56 |
+
#if !defined(ELOG_NEWLINE_SIGN)
|
| 57 |
+
#error "Please configure output newline sign (in elog_cfg.h)"
|
| 58 |
+
#endif
|
| 59 |
+
|
| 60 |
+
#ifdef ELOG_COLOR_ENABLE
|
| 61 |
+
/**
|
| 62 |
+
* CSI(Control Sequence Introducer/Initiator) sign
|
| 63 |
+
* more information on https://en.wikipedia.org/wiki/ANSI_escape_code
|
| 64 |
+
*/
|
| 65 |
+
#define CSI_START "\033["
|
| 66 |
+
#define CSI_END "\033[0m"
|
| 67 |
+
/* output log front color */
|
| 68 |
+
#define F_BLACK "30;"
|
| 69 |
+
#define F_RED "31;"
|
| 70 |
+
#define F_GREEN "32;"
|
| 71 |
+
#define F_YELLOW "33;"
|
| 72 |
+
#define F_BLUE "34;"
|
| 73 |
+
#define F_MAGENTA "35;"
|
| 74 |
+
#define F_CYAN "36;"
|
| 75 |
+
#define F_WHITE "37;"
|
| 76 |
+
/* output log background color */
|
| 77 |
+
#define B_NULL
|
| 78 |
+
#define B_BLACK "40;"
|
| 79 |
+
#define B_RED "41;"
|
| 80 |
+
#define B_GREEN "42;"
|
| 81 |
+
#define B_YELLOW "43;"
|
| 82 |
+
#define B_BLUE "44;"
|
| 83 |
+
#define B_MAGENTA "45;"
|
| 84 |
+
#define B_CYAN "46;"
|
| 85 |
+
#define B_WHITE "47;"
|
| 86 |
+
/* output log fonts style */
|
| 87 |
+
#define S_BOLD "1m"
|
| 88 |
+
#define S_UNDERLINE "4m"
|
| 89 |
+
#define S_BLINK "5m"
|
| 90 |
+
#define S_NORMAL "22m"
|
| 91 |
+
/* output log default color definition: [front color] + [background color] + [show style] */
|
| 92 |
+
#ifndef ELOG_COLOR_ASSERT
|
| 93 |
+
#define ELOG_COLOR_ASSERT (F_MAGENTA B_NULL S_NORMAL)
|
| 94 |
+
#endif
|
| 95 |
+
#ifndef ELOG_COLOR_ERROR
|
| 96 |
+
#define ELOG_COLOR_ERROR (F_RED B_NULL S_NORMAL)
|
| 97 |
+
#endif
|
| 98 |
+
#ifndef ELOG_COLOR_WARN
|
| 99 |
+
#define ELOG_COLOR_WARN (F_YELLOW B_NULL S_NORMAL)
|
| 100 |
+
#endif
|
| 101 |
+
#ifndef ELOG_COLOR_INFO
|
| 102 |
+
#define ELOG_COLOR_INFO (F_CYAN B_NULL S_NORMAL)
|
| 103 |
+
#endif
|
| 104 |
+
#ifndef ELOG_COLOR_DEBUG
|
| 105 |
+
#define ELOG_COLOR_DEBUG (F_GREEN B_NULL S_NORMAL)
|
| 106 |
+
#endif
|
| 107 |
+
#ifndef ELOG_COLOR_VERBOSE
|
| 108 |
+
#define ELOG_COLOR_VERBOSE (F_BLUE B_NULL S_NORMAL)
|
| 109 |
+
#endif
|
| 110 |
+
#endif /* ELOG_COLOR_ENABLE */
|
| 111 |
+
|
| 112 |
+
/* EasyLogger object */
|
| 113 |
+
static EasyLogger elog;
|
| 114 |
+
/* every line log's buffer */
|
| 115 |
+
static char log_buf[ELOG_LINE_BUF_SIZE] = { 0 };
|
| 116 |
+
/* level output info */
|
| 117 |
+
static const char *level_output_info[] = {
|
| 118 |
+
[ELOG_LVL_ASSERT] = "WM_A/",
|
| 119 |
+
[ELOG_LVL_ERROR] = "WM_E/",
|
| 120 |
+
[ELOG_LVL_WARN] = "WM_W/",
|
| 121 |
+
[ELOG_LVL_INFO] = "WM_I/",
|
| 122 |
+
[ELOG_LVL_DEBUG] = "WM_D/",
|
| 123 |
+
[ELOG_LVL_VERBOSE] = "WM_V/",
|
| 124 |
+
};
|
| 125 |
+
|
| 126 |
+
#ifdef ELOG_COLOR_ENABLE
|
| 127 |
+
/* color output info */
|
| 128 |
+
static const char *color_output_info[] = {
|
| 129 |
+
[ELOG_LVL_ASSERT] = ELOG_COLOR_ASSERT,
|
| 130 |
+
[ELOG_LVL_ERROR] = ELOG_COLOR_ERROR,
|
| 131 |
+
[ELOG_LVL_WARN] = ELOG_COLOR_WARN,
|
| 132 |
+
[ELOG_LVL_INFO] = ELOG_COLOR_INFO,
|
| 133 |
+
[ELOG_LVL_DEBUG] = ELOG_COLOR_DEBUG,
|
| 134 |
+
[ELOG_LVL_VERBOSE] = ELOG_COLOR_VERBOSE,
|
| 135 |
+
};
|
| 136 |
+
#endif /* ELOG_COLOR_ENABLE */
|
| 137 |
+
|
| 138 |
+
static bool get_fmt_enabled(uint8_t level, size_t set);
|
| 139 |
+
|
| 140 |
+
/* EasyLogger assert hook */
|
| 141 |
+
void (*elog_assert_hook)(const char* expr, const char* func, size_t line);
|
| 142 |
+
|
| 143 |
+
extern void elog_port_output(const char *log, size_t size);
|
| 144 |
+
extern void elog_port_output_lock(void);
|
| 145 |
+
extern void elog_port_output_unlock(void);
|
| 146 |
+
|
| 147 |
+
/**
|
| 148 |
+
* EasyLogger initialize.
|
| 149 |
+
*
|
| 150 |
+
* @return result
|
| 151 |
+
*/
|
| 152 |
+
ElogErrCode elog_init(void) {
|
| 153 |
+
extern ElogErrCode elog_port_init(void);
|
| 154 |
+
extern ElogErrCode elog_async_init(void);
|
| 155 |
+
|
| 156 |
+
ElogErrCode result = ELOG_NO_ERR;
|
| 157 |
+
|
| 158 |
+
/* port initialize */
|
| 159 |
+
result = elog_port_init();
|
| 160 |
+
if (result != ELOG_NO_ERR) {
|
| 161 |
+
return result;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
#ifdef ELOG_ASYNC_OUTPUT_ENABLE
|
| 165 |
+
result = elog_async_init();
|
| 166 |
+
if (result != ELOG_NO_ERR) {
|
| 167 |
+
return result;
|
| 168 |
+
}
|
| 169 |
+
#endif
|
| 170 |
+
|
| 171 |
+
/* enable the output lock */
|
| 172 |
+
elog_output_lock_enabled(true);
|
| 173 |
+
/* output locked status initialize */
|
| 174 |
+
elog.output_is_locked_before_enable = false;
|
| 175 |
+
elog.output_is_locked_before_disable = false;
|
| 176 |
+
|
| 177 |
+
#ifdef ELOG_COLOR_ENABLE
|
| 178 |
+
/* disable text color by default */
|
| 179 |
+
elog_set_text_color_enabled(false);
|
| 180 |
+
#endif
|
| 181 |
+
|
| 182 |
+
/* set level is ELOG_LVL_VERBOSE */
|
| 183 |
+
elog_set_filter_lvl(ELOG_LVL_VERBOSE);
|
| 184 |
+
|
| 185 |
+
return result;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
/**
|
| 189 |
+
* EasyLogger start after initialize.
|
| 190 |
+
*/
|
| 191 |
+
void elog_start(void) {
|
| 192 |
+
/* enable output */
|
| 193 |
+
elog_set_output_enabled(true);
|
| 194 |
+
|
| 195 |
+
#if defined(ELOG_ASYNC_OUTPUT_ENABLE)
|
| 196 |
+
elog_async_enabled(true);
|
| 197 |
+
#elif defined(ELOG_BUF_OUTPUT_ENABLE)
|
| 198 |
+
elog_buf_enabled(true);
|
| 199 |
+
#endif
|
| 200 |
+
|
| 201 |
+
/* show version */
|
| 202 |
+
//log_i("EasyLogger V%s is initialize success.", ELOG_SW_VERSION);
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
/**
|
| 206 |
+
* set output enable or disable
|
| 207 |
+
*
|
| 208 |
+
* @param enabled TRUE: enable FALSE: disable
|
| 209 |
+
*/
|
| 210 |
+
void elog_set_output_enabled(bool enabled) {
|
| 211 |
+
ELOG_ASSERT((enabled == false) || (enabled == true));
|
| 212 |
+
|
| 213 |
+
elog.output_enabled = enabled;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
#ifdef ELOG_COLOR_ENABLE
|
| 217 |
+
/**
|
| 218 |
+
* set log text color enable or disable
|
| 219 |
+
*
|
| 220 |
+
* @param enabled TRUE: enable FALSE:disable
|
| 221 |
+
*/
|
| 222 |
+
void elog_set_text_color_enabled(bool enabled) {
|
| 223 |
+
elog.text_color_enabled = enabled;
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
/**
|
| 227 |
+
* get log text color enable status
|
| 228 |
+
*
|
| 229 |
+
* @return enable or disable
|
| 230 |
+
*/
|
| 231 |
+
bool elog_get_text_color_enabled(void) {
|
| 232 |
+
return elog.text_color_enabled;
|
| 233 |
+
}
|
| 234 |
+
#endif /* ELOG_COLOR_ENABLE */
|
| 235 |
+
|
| 236 |
+
/**
|
| 237 |
+
* get output is enable or disable
|
| 238 |
+
*
|
| 239 |
+
* @return enable or disable
|
| 240 |
+
*/
|
| 241 |
+
bool elog_get_output_enabled(void) {
|
| 242 |
+
return elog.output_enabled;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
/**
|
| 246 |
+
* set log output format. only enable or disable
|
| 247 |
+
*
|
| 248 |
+
* @param level level
|
| 249 |
+
* @param set format set
|
| 250 |
+
*/
|
| 251 |
+
void elog_set_fmt(uint8_t level, size_t set) {
|
| 252 |
+
ELOG_ASSERT(level <= ELOG_LVL_VERBOSE);
|
| 253 |
+
|
| 254 |
+
elog.enabled_fmt_set[level] = set;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
/**
|
| 258 |
+
* set log filter all parameter
|
| 259 |
+
*
|
| 260 |
+
* @param level level
|
| 261 |
+
* @param tag tag
|
| 262 |
+
* @param keyword keyword
|
| 263 |
+
*/
|
| 264 |
+
void elog_set_filter(uint8_t level, const char *tag, const char *keyword) {
|
| 265 |
+
ELOG_ASSERT(level <= ELOG_LVL_VERBOSE);
|
| 266 |
+
|
| 267 |
+
elog_set_filter_lvl(level);
|
| 268 |
+
elog_set_filter_tag(tag);
|
| 269 |
+
elog_set_filter_kw(keyword);
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
/**
|
| 273 |
+
* set log filter's level
|
| 274 |
+
*
|
| 275 |
+
* @param level level
|
| 276 |
+
*/
|
| 277 |
+
void elog_set_filter_lvl(uint8_t level) {
|
| 278 |
+
ELOG_ASSERT(level <= ELOG_LVL_VERBOSE);
|
| 279 |
+
|
| 280 |
+
elog.filter.level = level;
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
/**
|
| 284 |
+
* set log filter's tag
|
| 285 |
+
*
|
| 286 |
+
* @param tag tag
|
| 287 |
+
*/
|
| 288 |
+
void elog_set_filter_tag(const char *tag) {
|
| 289 |
+
strncpy(elog.filter.tag, tag, ELOG_FILTER_TAG_MAX_LEN);
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
/**
|
| 293 |
+
* set log filter's keyword
|
| 294 |
+
*
|
| 295 |
+
* @param keyword keyword
|
| 296 |
+
*/
|
| 297 |
+
void elog_set_filter_kw(const char *keyword) {
|
| 298 |
+
strncpy(elog.filter.keyword, keyword, ELOG_FILTER_KW_MAX_LEN);
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
/**
|
| 302 |
+
* lock output
|
| 303 |
+
*/
|
| 304 |
+
void elog_output_lock(void) {
|
| 305 |
+
if (elog.output_lock_enabled) {
|
| 306 |
+
elog_port_output_lock();
|
| 307 |
+
elog.output_is_locked_before_disable = true;
|
| 308 |
+
} else {
|
| 309 |
+
elog.output_is_locked_before_enable = true;
|
| 310 |
+
}
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
/**
|
| 314 |
+
* unlock output
|
| 315 |
+
*/
|
| 316 |
+
void elog_output_unlock(void) {
|
| 317 |
+
if (elog.output_lock_enabled) {
|
| 318 |
+
elog_port_output_unlock();
|
| 319 |
+
elog.output_is_locked_before_disable = false;
|
| 320 |
+
} else {
|
| 321 |
+
elog.output_is_locked_before_enable = false;
|
| 322 |
+
}
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
/**
|
| 326 |
+
* output RAW format log
|
| 327 |
+
*
|
| 328 |
+
* @param format output format
|
| 329 |
+
* @param ... args
|
| 330 |
+
*/
|
| 331 |
+
void elog_raw(const char *format, ...) {
|
| 332 |
+
va_list args;
|
| 333 |
+
size_t log_len = 0;
|
| 334 |
+
int fmt_result;
|
| 335 |
+
|
| 336 |
+
/* check output enabled */
|
| 337 |
+
if (!elog.output_enabled) {
|
| 338 |
+
return;
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
/* args point to the first variable parameter */
|
| 342 |
+
va_start(args, format);
|
| 343 |
+
|
| 344 |
+
/* lock output */
|
| 345 |
+
elog_output_lock();
|
| 346 |
+
|
| 347 |
+
/* package log data to buffer */
|
| 348 |
+
fmt_result = vsnprintf(log_buf, ELOG_LINE_BUF_SIZE, format, args);
|
| 349 |
+
|
| 350 |
+
/* output converted log */
|
| 351 |
+
if ((fmt_result > -1) && (fmt_result <= ELOG_LINE_BUF_SIZE)) {
|
| 352 |
+
log_len = fmt_result;
|
| 353 |
+
} else {
|
| 354 |
+
log_len = ELOG_LINE_BUF_SIZE;
|
| 355 |
+
}
|
| 356 |
+
/* output log */
|
| 357 |
+
#if defined(ELOG_ASYNC_OUTPUT_ENABLE)
|
| 358 |
+
extern void elog_async_output(uint8_t level, const char *log, size_t size);
|
| 359 |
+
/* raw log will using assert level */
|
| 360 |
+
elog_async_output(ELOG_LVL_ASSERT, log_buf, log_len);
|
| 361 |
+
#elif defined(ELOG_BUF_OUTPUT_ENABLE)
|
| 362 |
+
extern void elog_buf_output(const char *log, size_t size);
|
| 363 |
+
elog_buf_output(log_buf, log_len);
|
| 364 |
+
#else
|
| 365 |
+
elog_port_output(log_buf, log_len);
|
| 366 |
+
#endif
|
| 367 |
+
/* unlock output */
|
| 368 |
+
elog_output_unlock();
|
| 369 |
+
|
| 370 |
+
va_end(args);
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
/**
|
| 374 |
+
* output the log
|
| 375 |
+
*
|
| 376 |
+
* @param level level
|
| 377 |
+
* @param tag tag
|
| 378 |
+
* @param file file name
|
| 379 |
+
* @param func function name
|
| 380 |
+
* @param line line number
|
| 381 |
+
* @param format output format
|
| 382 |
+
* @param ... args
|
| 383 |
+
*
|
| 384 |
+
*/
|
| 385 |
+
void elog_output(uint8_t level, const char *tag, const char *file, const char *func,
|
| 386 |
+
const long line, const char *format, ...) {
|
| 387 |
+
extern const char *elog_port_get_time(void);
|
| 388 |
+
extern const char *elog_port_get_p_info(void);
|
| 389 |
+
extern const char *elog_port_get_t_info(void);
|
| 390 |
+
|
| 391 |
+
size_t tag_len = strlen(tag), log_len = 0, newline_len = strlen(ELOG_NEWLINE_SIGN);
|
| 392 |
+
char line_num[ELOG_LINE_NUM_MAX_LEN + 1] = { 0 };
|
| 393 |
+
char tag_sapce[ELOG_FILTER_TAG_MAX_LEN / 2 + 1] = { 0 };
|
| 394 |
+
va_list args;
|
| 395 |
+
int fmt_result;
|
| 396 |
+
|
| 397 |
+
ELOG_ASSERT(level <= ELOG_LVL_VERBOSE);
|
| 398 |
+
|
| 399 |
+
/* check output enabled */
|
| 400 |
+
if (!elog.output_enabled) {
|
| 401 |
+
return;
|
| 402 |
+
}
|
| 403 |
+
/* level filter */
|
| 404 |
+
if (level > elog.filter.level) {
|
| 405 |
+
return;
|
| 406 |
+
} else if (!strstr(tag, elog.filter.tag)) { /* tag filter */
|
| 407 |
+
return;
|
| 408 |
+
}
|
| 409 |
+
/* args point to the first variable parameter */
|
| 410 |
+
va_start(args, format);
|
| 411 |
+
/* lock output */
|
| 412 |
+
elog_output_lock();
|
| 413 |
+
|
| 414 |
+
#ifdef ELOG_COLOR_ENABLE
|
| 415 |
+
/* add CSI start sign and color info */
|
| 416 |
+
if (elog.text_color_enabled) {
|
| 417 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, CSI_START);
|
| 418 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, color_output_info[level]);
|
| 419 |
+
}
|
| 420 |
+
#endif
|
| 421 |
+
|
| 422 |
+
/* package level info */
|
| 423 |
+
if (get_fmt_enabled(level, ELOG_FMT_LVL)) {
|
| 424 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, level_output_info[level]);
|
| 425 |
+
}
|
| 426 |
+
/* package tag info */
|
| 427 |
+
if (get_fmt_enabled(level, ELOG_FMT_TAG)) {
|
| 428 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, tag);
|
| 429 |
+
/* if the tag length is less than 50% ELOG_FILTER_TAG_MAX_LEN, then fill space */
|
| 430 |
+
if (tag_len <= ELOG_FILTER_TAG_MAX_LEN / 2) {
|
| 431 |
+
memset(tag_sapce, ' ', ELOG_FILTER_TAG_MAX_LEN / 2 - tag_len);
|
| 432 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, tag_sapce);
|
| 433 |
+
}
|
| 434 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, " ");
|
| 435 |
+
}
|
| 436 |
+
/* package time, process and thread info */
|
| 437 |
+
if (get_fmt_enabled(level, ELOG_FMT_TIME | ELOG_FMT_P_INFO | ELOG_FMT_T_INFO)) {
|
| 438 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, "[");
|
| 439 |
+
/* package time info */
|
| 440 |
+
if (get_fmt_enabled(level, ELOG_FMT_TIME)) {
|
| 441 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, elog_port_get_time());
|
| 442 |
+
if (get_fmt_enabled(level, ELOG_FMT_P_INFO | ELOG_FMT_T_INFO)) {
|
| 443 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, " ");
|
| 444 |
+
}
|
| 445 |
+
}
|
| 446 |
+
/* package process info */
|
| 447 |
+
if (get_fmt_enabled(level, ELOG_FMT_P_INFO)) {
|
| 448 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, elog_port_get_p_info());
|
| 449 |
+
if (get_fmt_enabled(level, ELOG_FMT_T_INFO)) {
|
| 450 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, " ");
|
| 451 |
+
}
|
| 452 |
+
}
|
| 453 |
+
/* package thread info */
|
| 454 |
+
if (get_fmt_enabled(level, ELOG_FMT_T_INFO)) {
|
| 455 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, elog_port_get_t_info());
|
| 456 |
+
}
|
| 457 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, "] ");
|
| 458 |
+
}
|
| 459 |
+
/* package file directory and name, function name and line number info */
|
| 460 |
+
if (get_fmt_enabled(level, ELOG_FMT_DIR | ELOG_FMT_FUNC | ELOG_FMT_LINE)) {
|
| 461 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, "(");
|
| 462 |
+
/* package time info */
|
| 463 |
+
if (get_fmt_enabled(level, ELOG_FMT_DIR)) {
|
| 464 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, file);
|
| 465 |
+
if (get_fmt_enabled(level, ELOG_FMT_FUNC)) {
|
| 466 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, " ");
|
| 467 |
+
} else if (get_fmt_enabled(level, ELOG_FMT_LINE)) {
|
| 468 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, ":");
|
| 469 |
+
}
|
| 470 |
+
}
|
| 471 |
+
/* package process info */
|
| 472 |
+
if (get_fmt_enabled(level, ELOG_FMT_FUNC)) {
|
| 473 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, func);
|
| 474 |
+
if (get_fmt_enabled(level, ELOG_FMT_LINE)) {
|
| 475 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, ":");
|
| 476 |
+
}
|
| 477 |
+
}
|
| 478 |
+
/* package thread info */
|
| 479 |
+
if (get_fmt_enabled(level, ELOG_FMT_LINE)) {
|
| 480 |
+
snprintf(line_num, ELOG_LINE_NUM_MAX_LEN, "%ld", line);
|
| 481 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, line_num);
|
| 482 |
+
}
|
| 483 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, ")");
|
| 484 |
+
}
|
| 485 |
+
/* package other log data to buffer. '\0' must be added in the end by vsnprintf. */
|
| 486 |
+
//fmt_result = vsnprintf(log_buf + log_len, ELOG_LINE_BUF_SIZE - log_len, format, args);
|
| 487 |
+
extern int wm_vsnprintf(char *outstr, size_t size, const char *fmt, va_list arg_ptr);
|
| 488 |
+
fmt_result = wm_vsnprintf(log_buf + log_len, ELOG_LINE_BUF_SIZE - log_len, format, args);
|
| 489 |
+
|
| 490 |
+
va_end(args);
|
| 491 |
+
/* calculate log length */
|
| 492 |
+
if ((log_len + fmt_result <= ELOG_LINE_BUF_SIZE) && (fmt_result > -1)) {
|
| 493 |
+
log_len += fmt_result;
|
| 494 |
+
} else {
|
| 495 |
+
/* using max length */
|
| 496 |
+
log_len = ELOG_LINE_BUF_SIZE;
|
| 497 |
+
}
|
| 498 |
+
/* overflow check and reserve some space for CSI end sign and newline sign */
|
| 499 |
+
#ifdef ELOG_COLOR_ENABLE
|
| 500 |
+
if (log_len + (sizeof(CSI_END) - 1) + newline_len > ELOG_LINE_BUF_SIZE) {
|
| 501 |
+
/* using max length */
|
| 502 |
+
log_len = ELOG_LINE_BUF_SIZE;
|
| 503 |
+
/* reserve some space for CSI end sign */
|
| 504 |
+
log_len -= (sizeof(CSI_END) - 1);
|
| 505 |
+
#else
|
| 506 |
+
if (log_len + newline_len > ELOG_LINE_BUF_SIZE) {
|
| 507 |
+
/* using max length */
|
| 508 |
+
log_len = ELOG_LINE_BUF_SIZE;
|
| 509 |
+
#endif /* ELOG_COLOR_ENABLE */
|
| 510 |
+
/* reserve some space for newline sign */
|
| 511 |
+
log_len -= newline_len;
|
| 512 |
+
}
|
| 513 |
+
/* keyword filter */
|
| 514 |
+
if (elog.filter.keyword[0] != '\0') {
|
| 515 |
+
/* add string end sign */
|
| 516 |
+
log_buf[log_len] = '\0';
|
| 517 |
+
/* find the keyword */
|
| 518 |
+
if (!strstr(log_buf, elog.filter.keyword)) {
|
| 519 |
+
/* unlock output */
|
| 520 |
+
elog_output_unlock();
|
| 521 |
+
return;
|
| 522 |
+
}
|
| 523 |
+
}
|
| 524 |
+
|
| 525 |
+
#ifdef ELOG_COLOR_ENABLE
|
| 526 |
+
/* add CSI end sign */
|
| 527 |
+
if (elog.text_color_enabled) {
|
| 528 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, CSI_END);
|
| 529 |
+
}
|
| 530 |
+
#endif
|
| 531 |
+
|
| 532 |
+
/* package newline sign */
|
| 533 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, ELOG_NEWLINE_SIGN);
|
| 534 |
+
/* output log */
|
| 535 |
+
#if defined(ELOG_ASYNC_OUTPUT_ENABLE)
|
| 536 |
+
extern void elog_async_output(uint8_t level, const char *log, size_t size);
|
| 537 |
+
elog_async_output(level, log_buf, log_len);
|
| 538 |
+
#elif defined(ELOG_BUF_OUTPUT_ENABLE)
|
| 539 |
+
extern void elog_buf_output(const char *log, size_t size);
|
| 540 |
+
elog_buf_output(log_buf, log_len);
|
| 541 |
+
#else
|
| 542 |
+
elog_port_output(log_buf, log_len);
|
| 543 |
+
#endif
|
| 544 |
+
/* unlock output */
|
| 545 |
+
elog_output_unlock();
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
/**
|
| 549 |
+
* get format enabled
|
| 550 |
+
*
|
| 551 |
+
* @param level level
|
| 552 |
+
* @param set format set
|
| 553 |
+
*
|
| 554 |
+
* @return enable or disable
|
| 555 |
+
*/
|
| 556 |
+
static bool get_fmt_enabled(uint8_t level, size_t set) {
|
| 557 |
+
ELOG_ASSERT(level <= ELOG_LVL_VERBOSE);
|
| 558 |
+
|
| 559 |
+
if (elog.enabled_fmt_set[level] & set) {
|
| 560 |
+
return true;
|
| 561 |
+
} else {
|
| 562 |
+
return false;
|
| 563 |
+
}
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
/**
|
| 567 |
+
* enable or disable logger output lock
|
| 568 |
+
* @note disable this lock is not recommended except you want output system exception log
|
| 569 |
+
*
|
| 570 |
+
* @param enabled true: enable false: disable
|
| 571 |
+
*/
|
| 572 |
+
void elog_output_lock_enabled(bool enabled) {
|
| 573 |
+
elog.output_lock_enabled = enabled;
|
| 574 |
+
/* it will re-lock or re-unlock before output lock enable */
|
| 575 |
+
if (elog.output_lock_enabled) {
|
| 576 |
+
if (!elog.output_is_locked_before_disable && elog.output_is_locked_before_enable) {
|
| 577 |
+
/* the output lock is unlocked before disable, and the lock will unlocking after enable */
|
| 578 |
+
elog_port_output_lock();
|
| 579 |
+
} else if (elog.output_is_locked_before_disable && !elog.output_is_locked_before_enable) {
|
| 580 |
+
/* the output lock is locked before disable, and the lock will locking after enable */
|
| 581 |
+
elog_port_output_unlock();
|
| 582 |
+
}
|
| 583 |
+
}
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
/**
|
| 587 |
+
* Set a hook function to EasyLogger assert. It will run when the expression is false.
|
| 588 |
+
*
|
| 589 |
+
* @param hook the hook function
|
| 590 |
+
*/
|
| 591 |
+
void elog_assert_set_hook(void (*hook)(const char* expr, const char* func, size_t line)) {
|
| 592 |
+
elog_assert_hook = hook;
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
/**
|
| 596 |
+
* find the log level
|
| 597 |
+
* @note make sure the log level is output on each format
|
| 598 |
+
*
|
| 599 |
+
* @param log log buffer
|
| 600 |
+
*
|
| 601 |
+
* @return log level, found failed will return -1
|
| 602 |
+
*/
|
| 603 |
+
int8_t elog_find_lvl(const char *log) {
|
| 604 |
+
ELOG_ASSERT(log);
|
| 605 |
+
/* make sure the log level is output on each format */
|
| 606 |
+
ELOG_ASSERT(elog.enabled_fmt_set[ELOG_LVL_ASSERT] & ELOG_FMT_LVL);
|
| 607 |
+
ELOG_ASSERT(elog.enabled_fmt_set[ELOG_LVL_ERROR] & ELOG_FMT_LVL);
|
| 608 |
+
ELOG_ASSERT(elog.enabled_fmt_set[ELOG_LVL_WARN] & ELOG_FMT_LVL);
|
| 609 |
+
ELOG_ASSERT(elog.enabled_fmt_set[ELOG_LVL_INFO] & ELOG_FMT_LVL);
|
| 610 |
+
ELOG_ASSERT(elog.enabled_fmt_set[ELOG_LVL_DEBUG] & ELOG_FMT_LVL);
|
| 611 |
+
ELOG_ASSERT(elog.enabled_fmt_set[ELOG_LVL_VERBOSE] & ELOG_FMT_LVL);
|
| 612 |
+
|
| 613 |
+
#ifdef ELOG_COLOR_ENABLE
|
| 614 |
+
uint8_t i;
|
| 615 |
+
size_t csi_start_len = strlen(CSI_START);
|
| 616 |
+
for(i = 0; i < ELOG_LVL_TOTAL_NUM; i ++) {
|
| 617 |
+
if (!strncmp(color_output_info[i], log + csi_start_len, strlen(color_output_info[i]))) {
|
| 618 |
+
return i;
|
| 619 |
+
}
|
| 620 |
+
}
|
| 621 |
+
/* found failed */
|
| 622 |
+
return -1;
|
| 623 |
+
#else
|
| 624 |
+
switch (log[0]) {
|
| 625 |
+
case 'A': return ELOG_LVL_ASSERT;
|
| 626 |
+
case 'E': return ELOG_LVL_ERROR;
|
| 627 |
+
case 'W': return ELOG_LVL_WARN;
|
| 628 |
+
case 'I': return ELOG_LVL_INFO;
|
| 629 |
+
case 'D': return ELOG_LVL_DEBUG;
|
| 630 |
+
case 'V': return ELOG_LVL_VERBOSE;
|
| 631 |
+
default: return -1;
|
| 632 |
+
}
|
| 633 |
+
#endif
|
| 634 |
+
}
|
| 635 |
+
|
| 636 |
+
/**
|
| 637 |
+
* find the log tag
|
| 638 |
+
* @note make sure the log tag is output on each format
|
| 639 |
+
* @note the tag don't have space in it
|
| 640 |
+
*
|
| 641 |
+
* @param log log buffer
|
| 642 |
+
* @param lvl log level, you can get it by @see elog_find_lvl
|
| 643 |
+
* @param tag_len found tag length
|
| 644 |
+
*
|
| 645 |
+
* @return log tag, found failed will return NULL
|
| 646 |
+
*/
|
| 647 |
+
const char *elog_find_tag(const char *log, uint8_t lvl, size_t *tag_len) {
|
| 648 |
+
const char *tag = NULL, *tag_end = NULL;
|
| 649 |
+
|
| 650 |
+
ELOG_ASSERT(log);
|
| 651 |
+
ELOG_ASSERT(tag_len);
|
| 652 |
+
ELOG_ASSERT(lvl < ELOG_LVL_TOTAL_NUM);
|
| 653 |
+
/* make sure the log tag is output on each format */
|
| 654 |
+
ELOG_ASSERT(elog.enabled_fmt_set[lvl] & ELOG_FMT_TAG);
|
| 655 |
+
|
| 656 |
+
#ifdef ELOG_COLOR_ENABLE
|
| 657 |
+
tag = log + strlen(CSI_START) + strlen(color_output_info[lvl]) + strlen(level_output_info[lvl]);
|
| 658 |
+
#else
|
| 659 |
+
tag = log + strlen(level_output_info[lvl]);
|
| 660 |
+
#endif
|
| 661 |
+
/* find the first space after tag */
|
| 662 |
+
if ((tag_end = memchr(tag, ' ', ELOG_FILTER_TAG_MAX_LEN)) != NULL) {
|
| 663 |
+
*tag_len = tag_end - tag;
|
| 664 |
+
} else {
|
| 665 |
+
tag = NULL;
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
return tag;
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
+
/**
|
| 672 |
+
* dump the hex format data to log
|
| 673 |
+
*
|
| 674 |
+
* @param name name for hex object, it will show on log header
|
| 675 |
+
* @param width hex number for every line, such as: 16, 32
|
| 676 |
+
* @param buf hex buffer
|
| 677 |
+
* @param size buffer size
|
| 678 |
+
*/
|
| 679 |
+
void elog_hexdump(const char *name, uint8_t width, uint8_t *buf, uint16_t size)
|
| 680 |
+
{
|
| 681 |
+
#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ')
|
| 682 |
+
|
| 683 |
+
uint16_t i, j;
|
| 684 |
+
uint16_t log_len = 0;
|
| 685 |
+
char dump_string[8] = {0};
|
| 686 |
+
int fmt_result;
|
| 687 |
+
|
| 688 |
+
if (!elog.output_enabled) {
|
| 689 |
+
return;
|
| 690 |
+
}
|
| 691 |
+
|
| 692 |
+
/* level filter */
|
| 693 |
+
if (ELOG_LVL_DEBUG > elog.filter.level) {
|
| 694 |
+
return;
|
| 695 |
+
} else if (!strstr(name, elog.filter.tag)) { /* tag filter */
|
| 696 |
+
return;
|
| 697 |
+
}
|
| 698 |
+
|
| 699 |
+
/* lock output */
|
| 700 |
+
elog_output_lock();
|
| 701 |
+
|
| 702 |
+
for (i = 0; i < size; i += width) {
|
| 703 |
+
/* package header */
|
| 704 |
+
fmt_result = snprintf(log_buf, ELOG_LINE_BUF_SIZE, "WM_D/HEX %s: %04X-%04X: ", name, i, i + width);
|
| 705 |
+
/* calculate log length */
|
| 706 |
+
if ((fmt_result > -1) && (fmt_result <= ELOG_LINE_BUF_SIZE)) {
|
| 707 |
+
log_len = fmt_result;
|
| 708 |
+
} else {
|
| 709 |
+
log_len = ELOG_LINE_BUF_SIZE;
|
| 710 |
+
}
|
| 711 |
+
/* dump hex */
|
| 712 |
+
for (j = 0; j < width; j++) {
|
| 713 |
+
if (i + j < size) {
|
| 714 |
+
snprintf(dump_string, sizeof(dump_string), "%02X ", buf[i + j]);
|
| 715 |
+
} else {
|
| 716 |
+
strncpy(dump_string, " ", sizeof(dump_string));
|
| 717 |
+
}
|
| 718 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, dump_string);
|
| 719 |
+
if ((j + 1) % 8 == 0) {
|
| 720 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, " ");
|
| 721 |
+
}
|
| 722 |
+
}
|
| 723 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, " ");
|
| 724 |
+
/* dump char for hex */
|
| 725 |
+
for (j = 0; j < width; j++) {
|
| 726 |
+
if (i + j < size) {
|
| 727 |
+
snprintf(dump_string, sizeof(dump_string), "%c", __is_print(buf[i + j]) ? buf[i + j] : '.');
|
| 728 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, dump_string);
|
| 729 |
+
}
|
| 730 |
+
}
|
| 731 |
+
/* overflow check and reserve some space for newline sign */
|
| 732 |
+
if (log_len + strlen(ELOG_NEWLINE_SIGN) > ELOG_LINE_BUF_SIZE) {
|
| 733 |
+
log_len = ELOG_LINE_BUF_SIZE - strlen(ELOG_NEWLINE_SIGN);
|
| 734 |
+
}
|
| 735 |
+
/* package newline sign */
|
| 736 |
+
log_len += elog_strcpy(log_len, log_buf + log_len, ELOG_NEWLINE_SIGN);
|
| 737 |
+
/* do log output */
|
| 738 |
+
#if defined(ELOG_ASYNC_OUTPUT_ENABLE)
|
| 739 |
+
extern void elog_async_output(uint8_t level, const char *log, size_t size);
|
| 740 |
+
elog_async_output(ELOG_LVL_DEBUG, log_buf, log_len);
|
| 741 |
+
#elif defined(ELOG_BUF_OUTPUT_ENABLE)
|
| 742 |
+
extern void elog_buf_output(const char *log, size_t size);
|
| 743 |
+
elog_buf_output(log_buf, log_len);
|
| 744 |
+
#else
|
| 745 |
+
elog_port_output(log_buf, log_len);
|
| 746 |
+
#endif
|
| 747 |
+
}
|
| 748 |
+
/* unlock output */
|
| 749 |
+
elog_output_unlock();
|
| 750 |
+
}
|
MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog_async.c
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
* This file is part of the EasyLogger Library.
|
| 3 |
+
*
|
| 4 |
+
* Copyright (c) 2016-2017, Armink, <[email protected]>
|
| 5 |
+
*
|
| 6 |
+
* Permission is hereby granted, free of charge, to any person obtaining
|
| 7 |
+
* a copy of this software and associated documentation files (the
|
| 8 |
+
* 'Software'), to deal in the Software without restriction, including
|
| 9 |
+
* without limitation the rights to use, copy, modify, merge, publish,
|
| 10 |
+
* distribute, sublicense, and/or sell copies of the Software, and to
|
| 11 |
+
* permit persons to whom the Software is furnished to do so, subject to
|
| 12 |
+
* the following conditions:
|
| 13 |
+
*
|
| 14 |
+
* The above copyright notice and this permission notice shall be
|
| 15 |
+
* included in all copies or substantial portions of the Software.
|
| 16 |
+
*
|
| 17 |
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
| 18 |
+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 19 |
+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
| 20 |
+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
| 21 |
+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
| 22 |
+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
| 23 |
+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 24 |
+
*
|
| 25 |
+
* Function: Logs asynchronous output.
|
| 26 |
+
* Created on: 2016-11-06
|
| 27 |
+
*/
|
| 28 |
+
|
| 29 |
+
#include <elog.h>
|
| 30 |
+
#include <string.h>
|
| 31 |
+
|
| 32 |
+
#ifdef ELOG_ASYNC_OUTPUT_ENABLE
|
| 33 |
+
|
| 34 |
+
#ifdef ELOG_ASYNC_OUTPUT_USING_PTHREAD
|
| 35 |
+
#include <pthread.h>
|
| 36 |
+
#include <sched.h>
|
| 37 |
+
#include <semaphore.h>
|
| 38 |
+
/* thread default stack size */
|
| 39 |
+
#ifndef ELOG_ASYNC_OUTPUT_PTHREAD_STACK_SIZE
|
| 40 |
+
#if PTHREAD_STACK_MIN > 4*1024
|
| 41 |
+
#define ELOG_ASYNC_OUTPUT_PTHREAD_STACK_SIZE PTHREAD_STACK_MIN
|
| 42 |
+
#else
|
| 43 |
+
#define ELOG_ASYNC_OUTPUT_PTHREAD_STACK_SIZE (1*1024)
|
| 44 |
+
#endif
|
| 45 |
+
/* thread default priority */
|
| 46 |
+
#ifndef ELOG_ASYNC_OUTPUT_PTHREAD_PRIORITY
|
| 47 |
+
#define ELOG_ASYNC_OUTPUT_PTHREAD_PRIORITY (sched_get_priority_max(SCHED_RR) - 1)
|
| 48 |
+
#endif
|
| 49 |
+
/* output thread poll get log buffer size */
|
| 50 |
+
#ifndef ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE
|
| 51 |
+
#define ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE (ELOG_LINE_BUF_SIZE - 4)
|
| 52 |
+
#endif
|
| 53 |
+
#endif /* ELOG_ASYNC_OUTPUT_USING_PTHREAD */
|
| 54 |
+
|
| 55 |
+
/* asynchronous output log notice */
|
| 56 |
+
static sem_t output_notice;
|
| 57 |
+
/* asynchronous output pthread thread */
|
| 58 |
+
static pthread_t async_output_thread;
|
| 59 |
+
#endif /* ELOG_ASYNC_OUTPUT_USING_PTHREAD */
|
| 60 |
+
|
| 61 |
+
/* the highest output level for async mode, other level will sync output */
|
| 62 |
+
#ifdef ELOG_ASYNC_OUTPUT_LVL
|
| 63 |
+
#define OUTPUT_LVL ELOG_ASYNC_OUTPUT_LVL
|
| 64 |
+
#else
|
| 65 |
+
#define OUTPUT_LVL ELOG_LVL_ASSERT
|
| 66 |
+
#endif /* ELOG_ASYNC_OUTPUT_LVL */
|
| 67 |
+
|
| 68 |
+
/* buffer size for asynchronous output mode */
|
| 69 |
+
#ifdef ELOG_ASYNC_OUTPUT_BUF_SIZE
|
| 70 |
+
#define OUTPUT_BUF_SIZE ELOG_ASYNC_OUTPUT_BUF_SIZE
|
| 71 |
+
#else
|
| 72 |
+
#define OUTPUT_BUF_SIZE (ELOG_LINE_BUF_SIZE * 10)
|
| 73 |
+
#endif /* ELOG_ASYNC_OUTPUT_BUF_SIZE */
|
| 74 |
+
|
| 75 |
+
/* Initialize OK flag */
|
| 76 |
+
static bool init_ok = false;
|
| 77 |
+
/* asynchronous output mode enabled flag */
|
| 78 |
+
static bool is_enabled = false;
|
| 79 |
+
/* asynchronous output mode's ring buffer */
|
| 80 |
+
static char log_buf[OUTPUT_BUF_SIZE] = { 0 };
|
| 81 |
+
/* log ring buffer write index */
|
| 82 |
+
static size_t write_index = 0;
|
| 83 |
+
/* log ring buffer read index */
|
| 84 |
+
static size_t read_index = 0;
|
| 85 |
+
/* log ring buffer full flag */
|
| 86 |
+
static bool buf_is_full = false;
|
| 87 |
+
/* log ring buffer empty flag */
|
| 88 |
+
static bool buf_is_empty = true;
|
| 89 |
+
|
| 90 |
+
extern void elog_port_output(const char *log, size_t size);
|
| 91 |
+
extern void elog_output_lock(void);
|
| 92 |
+
extern void elog_output_unlock(void);
|
| 93 |
+
|
| 94 |
+
/**
|
| 95 |
+
* asynchronous output ring buffer used size
|
| 96 |
+
*
|
| 97 |
+
* @return used size
|
| 98 |
+
*/
|
| 99 |
+
static size_t elog_async_get_buf_used(void) {
|
| 100 |
+
if (write_index > read_index) {
|
| 101 |
+
return write_index - read_index;
|
| 102 |
+
} else {
|
| 103 |
+
if (!buf_is_full && !buf_is_empty) {
|
| 104 |
+
return OUTPUT_BUF_SIZE - (read_index - write_index);
|
| 105 |
+
} else if (buf_is_full) {
|
| 106 |
+
return OUTPUT_BUF_SIZE;
|
| 107 |
+
} else {
|
| 108 |
+
return 0;
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
/**
|
| 114 |
+
* asynchronous output ring buffer remain space
|
| 115 |
+
*
|
| 116 |
+
* @return remain space
|
| 117 |
+
*/
|
| 118 |
+
static size_t async_get_buf_space(void) {
|
| 119 |
+
return OUTPUT_BUF_SIZE - elog_async_get_buf_used();
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
/**
|
| 123 |
+
* put log to asynchronous output ring buffer
|
| 124 |
+
*
|
| 125 |
+
* @param log put log buffer
|
| 126 |
+
* @param size log size
|
| 127 |
+
*
|
| 128 |
+
* @return put log size, the log which beyond ring buffer space will be dropped
|
| 129 |
+
*/
|
| 130 |
+
static size_t async_put_log(const char *log, size_t size) {
|
| 131 |
+
size_t space = 0;
|
| 132 |
+
|
| 133 |
+
space = async_get_buf_space();
|
| 134 |
+
/* no space */
|
| 135 |
+
if (!space) {
|
| 136 |
+
size = 0;
|
| 137 |
+
goto __exit;
|
| 138 |
+
}
|
| 139 |
+
/* drop some log */
|
| 140 |
+
if (space <= size) {
|
| 141 |
+
size = space;
|
| 142 |
+
buf_is_full = true;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
if (write_index + size < OUTPUT_BUF_SIZE) {
|
| 146 |
+
memcpy(log_buf + write_index, log, size);
|
| 147 |
+
write_index += size;
|
| 148 |
+
} else {
|
| 149 |
+
memcpy(log_buf + write_index, log, OUTPUT_BUF_SIZE - write_index);
|
| 150 |
+
memcpy(log_buf, log + OUTPUT_BUF_SIZE - write_index,
|
| 151 |
+
size - (OUTPUT_BUF_SIZE - write_index));
|
| 152 |
+
write_index += size - OUTPUT_BUF_SIZE;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
buf_is_empty = false;
|
| 156 |
+
|
| 157 |
+
__exit:
|
| 158 |
+
|
| 159 |
+
return size;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
#ifdef ELOG_ASYNC_LINE_OUTPUT
|
| 163 |
+
/**
|
| 164 |
+
* Get line log from asynchronous output ring buffer.
|
| 165 |
+
* It will copy all log when the newline sign isn't find.
|
| 166 |
+
*
|
| 167 |
+
* @param log get line log buffer
|
| 168 |
+
* @param size line log size
|
| 169 |
+
*
|
| 170 |
+
* @return get line log size, the log size is less than ring buffer used size
|
| 171 |
+
*/
|
| 172 |
+
size_t elog_async_get_line_log(char *log, size_t size) {
|
| 173 |
+
size_t used = 0, cpy_log_size = 0;
|
| 174 |
+
/* lock output */
|
| 175 |
+
elog_output_lock();
|
| 176 |
+
used = elog_async_get_buf_used();
|
| 177 |
+
|
| 178 |
+
/* no log */
|
| 179 |
+
if (!used || !size) {
|
| 180 |
+
goto __exit;
|
| 181 |
+
}
|
| 182 |
+
/* less log */
|
| 183 |
+
if (used <= size) {
|
| 184 |
+
size = used;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
if (read_index + size < OUTPUT_BUF_SIZE) {
|
| 188 |
+
cpy_log_size = elog_cpyln(log, log_buf + read_index, size);
|
| 189 |
+
read_index += cpy_log_size;
|
| 190 |
+
} else {
|
| 191 |
+
cpy_log_size = elog_cpyln(log, log_buf + read_index, OUTPUT_BUF_SIZE - read_index);
|
| 192 |
+
if (cpy_log_size == OUTPUT_BUF_SIZE - read_index) {
|
| 193 |
+
cpy_log_size += elog_cpyln(log + cpy_log_size, log_buf, size - cpy_log_size);
|
| 194 |
+
read_index += cpy_log_size - OUTPUT_BUF_SIZE;
|
| 195 |
+
} else {
|
| 196 |
+
read_index += cpy_log_size;
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
if (used == cpy_log_size) {
|
| 201 |
+
buf_is_empty = true;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
if (cpy_log_size) {
|
| 205 |
+
buf_is_full = false;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
__exit:
|
| 209 |
+
/* lock output */
|
| 210 |
+
elog_output_unlock();
|
| 211 |
+
return cpy_log_size;
|
| 212 |
+
}
|
| 213 |
+
#else
|
| 214 |
+
/**
|
| 215 |
+
* get log from asynchronous output ring buffer
|
| 216 |
+
*
|
| 217 |
+
* @param log get log buffer
|
| 218 |
+
* @param size log size
|
| 219 |
+
*
|
| 220 |
+
* @return get log size, the log size is less than ring buffer used size
|
| 221 |
+
*/
|
| 222 |
+
size_t elog_async_get_log(char *log, size_t size) {
|
| 223 |
+
size_t used = 0;
|
| 224 |
+
/* lock output */
|
| 225 |
+
elog_output_lock();
|
| 226 |
+
used = elog_async_get_buf_used();
|
| 227 |
+
/* no log */
|
| 228 |
+
if (!used || !size) {
|
| 229 |
+
size = 0;
|
| 230 |
+
goto __exit;
|
| 231 |
+
}
|
| 232 |
+
/* less log */
|
| 233 |
+
if (used <= size) {
|
| 234 |
+
size = used;
|
| 235 |
+
buf_is_empty = true;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
if (read_index + size < OUTPUT_BUF_SIZE) {
|
| 239 |
+
memcpy(log, log_buf + read_index, size);
|
| 240 |
+
read_index += size;
|
| 241 |
+
} else {
|
| 242 |
+
memcpy(log, log_buf + read_index, OUTPUT_BUF_SIZE - read_index);
|
| 243 |
+
memcpy(log + OUTPUT_BUF_SIZE - read_index, log_buf,
|
| 244 |
+
size - (OUTPUT_BUF_SIZE - read_index));
|
| 245 |
+
read_index += size - OUTPUT_BUF_SIZE;
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
buf_is_full = false;
|
| 249 |
+
|
| 250 |
+
__exit:
|
| 251 |
+
/* lock output */
|
| 252 |
+
elog_output_unlock();
|
| 253 |
+
return size;
|
| 254 |
+
}
|
| 255 |
+
#endif /* ELOG_ASYNC_LINE_OUTPUT */
|
| 256 |
+
|
| 257 |
+
void elog_async_output(uint8_t level, const char *log, size_t size) {
|
| 258 |
+
/* this function must be implement by user when ELOG_ASYNC_OUTPUT_USING_PTHREAD is not defined */
|
| 259 |
+
extern void elog_async_output_notice(void);
|
| 260 |
+
size_t put_size;
|
| 261 |
+
|
| 262 |
+
if (is_enabled) {
|
| 263 |
+
if (level >= OUTPUT_LVL) {
|
| 264 |
+
put_size = async_put_log(log, size);
|
| 265 |
+
/* notify output log thread */
|
| 266 |
+
if (put_size > 0) {
|
| 267 |
+
elog_async_output_notice();
|
| 268 |
+
}
|
| 269 |
+
} else {
|
| 270 |
+
elog_port_output(log, size);
|
| 271 |
+
}
|
| 272 |
+
} else {
|
| 273 |
+
elog_port_output(log, size);
|
| 274 |
+
}
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
#ifdef ELOG_ASYNC_OUTPUT_USING_PTHREAD
|
| 278 |
+
void elog_async_output_notice(void) {
|
| 279 |
+
sem_post(&output_notice);
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
static void *async_output(void *arg) {
|
| 283 |
+
size_t get_log_size = 0;
|
| 284 |
+
static char poll_get_buf[ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE];
|
| 285 |
+
|
| 286 |
+
ELOG_ASSERT(init_ok);
|
| 287 |
+
|
| 288 |
+
while(true) {
|
| 289 |
+
/* waiting log */
|
| 290 |
+
sem_wait(&output_notice);
|
| 291 |
+
/* polling gets and outputs the log */
|
| 292 |
+
while(true) {
|
| 293 |
+
|
| 294 |
+
#ifdef ELOG_ASYNC_LINE_OUTPUT
|
| 295 |
+
get_log_size = elog_async_get_line_log(poll_get_buf, ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE);
|
| 296 |
+
#else
|
| 297 |
+
get_log_size = elog_async_get_log(poll_get_buf, ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE);
|
| 298 |
+
#endif
|
| 299 |
+
|
| 300 |
+
if (get_log_size) {
|
| 301 |
+
elog_port_output(poll_get_buf, get_log_size);
|
| 302 |
+
} else {
|
| 303 |
+
break;
|
| 304 |
+
}
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
return NULL;
|
| 308 |
+
}
|
| 309 |
+
#endif
|
| 310 |
+
|
| 311 |
+
/**
|
| 312 |
+
* enable or disable asynchronous output mode
|
| 313 |
+
* the log will be output directly when mode is disabled
|
| 314 |
+
*
|
| 315 |
+
* @param enabled true: enabled, false: disabled
|
| 316 |
+
*/
|
| 317 |
+
void elog_async_enabled(bool enabled) {
|
| 318 |
+
is_enabled = enabled;
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
/**
|
| 322 |
+
* asynchronous output mode initialize
|
| 323 |
+
*
|
| 324 |
+
* @return result
|
| 325 |
+
*/
|
| 326 |
+
ElogErrCode elog_async_init(void) {
|
| 327 |
+
ElogErrCode result = ELOG_NO_ERR;
|
| 328 |
+
|
| 329 |
+
if (init_ok) {
|
| 330 |
+
return result;
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
#ifdef ELOG_ASYNC_OUTPUT_USING_PTHREAD
|
| 334 |
+
pthread_attr_t thread_attr;
|
| 335 |
+
struct sched_param thread_sched_param;
|
| 336 |
+
|
| 337 |
+
sem_init(&output_notice, 0, 0);
|
| 338 |
+
|
| 339 |
+
pthread_attr_init(&thread_attr);
|
| 340 |
+
pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
|
| 341 |
+
pthread_attr_setstacksize(&thread_attr, ELOG_ASYNC_OUTPUT_PTHREAD_STACK_SIZE);
|
| 342 |
+
pthread_attr_setschedpolicy(&thread_attr, SCHED_RR);
|
| 343 |
+
thread_sched_param.sched_priority = ELOG_ASYNC_OUTPUT_PTHREAD_PRIORITY;
|
| 344 |
+
pthread_attr_setschedparam(&thread_attr, &thread_sched_param);
|
| 345 |
+
pthread_create(&async_output_thread, &thread_attr, async_output, NULL);
|
| 346 |
+
pthread_attr_destroy(&thread_attr);
|
| 347 |
+
#endif
|
| 348 |
+
|
| 349 |
+
init_ok = true;
|
| 350 |
+
|
| 351 |
+
return result;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
#endif /* ELOG_ASYNC_OUTPUT_ENABLE */
|
MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog_buf.c
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
* This file is part of the EasyLogger Library.
|
| 3 |
+
*
|
| 4 |
+
* Copyright (c) 2016, Armink, <[email protected]>
|
| 5 |
+
*
|
| 6 |
+
* Permission is hereby granted, free of charge, to any person obtaining
|
| 7 |
+
* a copy of this software and associated documentation files (the
|
| 8 |
+
* 'Software'), to deal in the Software without restriction, including
|
| 9 |
+
* without limitation the rights to use, copy, modify, merge, publish,
|
| 10 |
+
* distribute, sublicense, and/or sell copies of the Software, and to
|
| 11 |
+
* permit persons to whom the Software is furnished to do so, subject to
|
| 12 |
+
* the following conditions:
|
| 13 |
+
*
|
| 14 |
+
* The above copyright notice and this permission notice shall be
|
| 15 |
+
* included in all copies or substantial portions of the Software.
|
| 16 |
+
*
|
| 17 |
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
| 18 |
+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 19 |
+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
| 20 |
+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
| 21 |
+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
| 22 |
+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
| 23 |
+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 24 |
+
*
|
| 25 |
+
* Function: Logs buffered output.
|
| 26 |
+
* Created on: 2016-11-09
|
| 27 |
+
*/
|
| 28 |
+
|
| 29 |
+
#include <elog.h>
|
| 30 |
+
#include <string.h>
|
| 31 |
+
|
| 32 |
+
#ifdef ELOG_BUF_OUTPUT_ENABLE
|
| 33 |
+
#if !defined(ELOG_BUF_OUTPUT_BUF_SIZE)
|
| 34 |
+
#error "Please configure buffer size for buffered output mode (in elog_cfg.h)"
|
| 35 |
+
#endif
|
| 36 |
+
|
| 37 |
+
/* buffered output mode's buffer */
|
| 38 |
+
static char log_buf[ELOG_BUF_OUTPUT_BUF_SIZE] = { 0 };
|
| 39 |
+
/* log buffer current write size */
|
| 40 |
+
static size_t buf_write_size = 0;
|
| 41 |
+
/* buffered output mode enabled flag */
|
| 42 |
+
static bool is_enabled = false;
|
| 43 |
+
|
| 44 |
+
extern void elog_port_output(const char *log, size_t size);
|
| 45 |
+
extern void elog_output_lock(void);
|
| 46 |
+
extern void elog_output_unlock(void);
|
| 47 |
+
|
| 48 |
+
/**
|
| 49 |
+
* output buffered logs when buffer is full
|
| 50 |
+
*
|
| 51 |
+
* @param log will be buffered line's log
|
| 52 |
+
* @param size log size
|
| 53 |
+
*/
|
| 54 |
+
void elog_buf_output(const char *log, size_t size) {
|
| 55 |
+
size_t write_size = 0, write_index = 0;
|
| 56 |
+
|
| 57 |
+
if (!is_enabled) {
|
| 58 |
+
elog_port_output(log, size);
|
| 59 |
+
return;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
while (true) {
|
| 63 |
+
if (buf_write_size + size > ELOG_BUF_OUTPUT_BUF_SIZE) {
|
| 64 |
+
write_size = ELOG_BUF_OUTPUT_BUF_SIZE - buf_write_size;
|
| 65 |
+
memcpy(log_buf + buf_write_size, log + write_index, write_size);
|
| 66 |
+
write_index += write_size;
|
| 67 |
+
size -= write_size;
|
| 68 |
+
buf_write_size += write_size;
|
| 69 |
+
/* output log */
|
| 70 |
+
elog_port_output(log_buf, buf_write_size);
|
| 71 |
+
/* reset write index */
|
| 72 |
+
buf_write_size = 0;
|
| 73 |
+
} else {
|
| 74 |
+
memcpy(log_buf + buf_write_size, log + write_index, size);
|
| 75 |
+
buf_write_size += size;
|
| 76 |
+
break;
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
/**
|
| 82 |
+
* flush all buffered logs to output device
|
| 83 |
+
*/
|
| 84 |
+
void elog_flush(void) {
|
| 85 |
+
/* lock output */
|
| 86 |
+
elog_output_lock();
|
| 87 |
+
/* output log */
|
| 88 |
+
elog_port_output(log_buf, buf_write_size);
|
| 89 |
+
/* reset write index */
|
| 90 |
+
buf_write_size = 0;
|
| 91 |
+
/* unlock output */
|
| 92 |
+
elog_output_unlock();
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
/**
|
| 96 |
+
* enable or disable buffered output mode
|
| 97 |
+
* the log will be output directly when mode is disabled
|
| 98 |
+
*
|
| 99 |
+
* @param enabled true: enabled, false: disabled
|
| 100 |
+
*/
|
| 101 |
+
void elog_buf_enabled(bool enabled) {
|
| 102 |
+
is_enabled = enabled;
|
| 103 |
+
}
|
| 104 |
+
#endif /* ELOG_BUF_OUTPUT_ENABLE */
|
MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog_utils.c
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
* This file is part of the EasyLogger Library.
|
| 3 |
+
*
|
| 4 |
+
* Copyright (c) 2015-2018, Armink, <[email protected]>
|
| 5 |
+
*
|
| 6 |
+
* Permission is hereby granted, free of charge, to any person obtaining
|
| 7 |
+
* a copy of this software and associated documentation files (the
|
| 8 |
+
* 'Software'), to deal in the Software without restriction, including
|
| 9 |
+
* without limitation the rights to use, copy, modify, merge, publish,
|
| 10 |
+
* distribute, sublicense, and/or sell copies of the Software, and to
|
| 11 |
+
* permit persons to whom the Software is furnished to do so, subject to
|
| 12 |
+
* the following conditions:
|
| 13 |
+
*
|
| 14 |
+
* The above copyright notice and this permission notice shall be
|
| 15 |
+
* included in all copies or substantial portions of the Software.
|
| 16 |
+
*
|
| 17 |
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
| 18 |
+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 19 |
+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
| 20 |
+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
| 21 |
+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
| 22 |
+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
| 23 |
+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 24 |
+
*
|
| 25 |
+
* Function: Some utils for this library.
|
| 26 |
+
* Created on: 2015-04-28
|
| 27 |
+
*/
|
| 28 |
+
|
| 29 |
+
#include <elog.h>
|
| 30 |
+
#include <string.h>
|
| 31 |
+
|
| 32 |
+
/**
|
| 33 |
+
* another copy string function
|
| 34 |
+
*
|
| 35 |
+
* @param cur_len current copied log length, max size is ELOG_LINE_BUF_SIZE
|
| 36 |
+
* @param dst destination
|
| 37 |
+
* @param src source
|
| 38 |
+
*
|
| 39 |
+
* @return copied length
|
| 40 |
+
*/
|
| 41 |
+
size_t elog_strcpy(size_t cur_len, char *dst, const char *src) {
|
| 42 |
+
const char *src_old = src;
|
| 43 |
+
|
| 44 |
+
assert(dst);
|
| 45 |
+
assert(src);
|
| 46 |
+
|
| 47 |
+
while (*src != 0) {
|
| 48 |
+
/* make sure destination has enough space */
|
| 49 |
+
if (cur_len++ < ELOG_LINE_BUF_SIZE) {
|
| 50 |
+
*dst++ = *src++;
|
| 51 |
+
} else {
|
| 52 |
+
break;
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
return src - src_old;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
/**
|
| 59 |
+
* Copy line log split by newline sign. It will copy all log when the newline sign isn't find.
|
| 60 |
+
*
|
| 61 |
+
* @param line line log buffer
|
| 62 |
+
* @param log origin log buffer
|
| 63 |
+
* @param len origin log buffer length
|
| 64 |
+
*
|
| 65 |
+
* @return copy size
|
| 66 |
+
*/
|
| 67 |
+
size_t elog_cpyln(char *line, const char *log, size_t len) {
|
| 68 |
+
size_t newline_len = strlen(ELOG_NEWLINE_SIGN), copy_size = 0;
|
| 69 |
+
|
| 70 |
+
assert(line);
|
| 71 |
+
assert(log);
|
| 72 |
+
|
| 73 |
+
while (len--) {
|
| 74 |
+
*line++ = *log++;
|
| 75 |
+
copy_size++;
|
| 76 |
+
if (copy_size >= newline_len && !strncmp(log - newline_len, ELOG_NEWLINE_SIGN, newline_len)) {
|
| 77 |
+
break;
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
return copy_size;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
/**
|
| 84 |
+
* This function will copy memory content from source address to destination
|
| 85 |
+
* address.
|
| 86 |
+
*
|
| 87 |
+
* @param dst the address of destination memory
|
| 88 |
+
* @param src the address of source memory
|
| 89 |
+
* @param count the copied length
|
| 90 |
+
*
|
| 91 |
+
* @return the address of destination memory
|
| 92 |
+
*/
|
| 93 |
+
void *elog_memcpy(void *dst, const void *src, size_t count) {
|
| 94 |
+
char *tmp = (char *) dst, *s = (char *) src;
|
| 95 |
+
|
| 96 |
+
assert(dst);
|
| 97 |
+
assert(src);
|
| 98 |
+
|
| 99 |
+
while (count--)
|
| 100 |
+
*tmp++ = *s++;
|
| 101 |
+
|
| 102 |
+
return dst;
|
| 103 |
+
}
|
MCP_servers/W600-embedded-OBD2/Src/App/elm327/Makefile
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#*****************************************************************************
|
| 2 |
+
# ELM327 OBD-II Interface Library Makefile
|
| 3 |
+
#*****************************************************************************
|
| 4 |
+
|
| 5 |
+
ifeq ($(COMPILER_OS_CYGWIN),1)
|
| 6 |
+
TOPDIR=../../..
|
| 7 |
+
endif
|
| 8 |
+
|
| 9 |
+
include $(TOPDIR)/Tools/toolchain.def
|
| 10 |
+
|
| 11 |
+
#*****************************************************************************
|
| 12 |
+
# Target definition
|
| 13 |
+
#*****************************************************************************
|
| 14 |
+
GOAL = $(LIB_DIR)/wmelm327.$(LIBTYPE)
|
| 15 |
+
|
| 16 |
+
#*****************************************************************************
|
| 17 |
+
# Source section
|
| 18 |
+
#*****************************************************************************
|
| 19 |
+
ASM_SRC +=
|
| 20 |
+
C_SRC += elm327.c elm327_sim.c elm327_history.c
|
| 21 |
+
|
| 22 |
+
#*****************************************************************************
|
| 23 |
+
# Implicit rules
|
| 24 |
+
#*****************************************************************************
|
| 25 |
+
.c.o:
|
| 26 |
+
$(CC) $(CFLAGS) -c -o $*.o $< $(INCLUDES)
|
| 27 |
+
|
| 28 |
+
.s.o:
|
| 29 |
+
$(ASM) $(ASMFLAGS) -c -o $*.o $< $(INCLUDES)
|
| 30 |
+
|
| 31 |
+
#*****************************************************************************
|
| 32 |
+
# Explicit rules
|
| 33 |
+
#*****************************************************************************
|
| 34 |
+
OBJ_FILES = $(C_SRC:%.c=%.o) $(ASM_SRC:%.s=%.o)
|
| 35 |
+
|
| 36 |
+
all: $(GOAL)
|
| 37 |
+
|
| 38 |
+
$(GOAL): $(OBJ_FILES)
|
| 39 |
+
$(AR) $(ARFLAGS) $@ $(OBJ_FILES)
|
| 40 |
+
|
| 41 |
+
.PHONY: clean
|
| 42 |
+
clean:
|
| 43 |
+
$(RM) -f $(GOAL)
|
| 44 |
+
$(RM) -f $(OBJ_FILES:.o=.d) $(OBJ_FILES)
|
MCP_servers/W600-embedded-OBD2/Src/App/elm327/README_SIMULATION.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ELM327 OBD-II Simulator
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
The ELM327 simulator provides simulated OBD-II responses for testing without a vehicle connection. This allows development and testing when the ELM327 is powered on but not connected to a vehicle's OBD-II port.
|
| 6 |
+
|
| 7 |
+
## Key Features
|
| 8 |
+
|
| 9 |
+
- **AT Commands**: Always passed through to real ELM327 hardware
|
| 10 |
+
- **OBD-II Commands**: Simulated responses when not connected to vehicle
|
| 11 |
+
- **Dynamic Values**: Realistic time-varying data for RPM, speed, temperature
|
| 12 |
+
- **Easy Toggle**: Enable/disable simulation at runtime or compile time
|
| 13 |
+
|
| 14 |
+
## Architecture
|
| 15 |
+
|
| 16 |
+
```
|
| 17 |
+
┌─────────────────────────────────────────┐
|
| 18 |
+
│ MCP Tool / Application Layer │
|
| 19 |
+
│ (transparent - no changes needed) │
|
| 20 |
+
└──────────────────┬──────────────────────┘
|
| 21 |
+
│
|
| 22 |
+
↓
|
| 23 |
+
┌─────────────────────────────────────────┐
|
| 24 |
+
│ ELM327 Driver (elm327.c) │
|
| 25 |
+
│ elm327_send_command() │
|
| 26 |
+
│ │
|
| 27 |
+
│ ┌─────────────────────────────────┐ │
|
| 28 |
+
│ │ Command Classifier │ │
|
| 29 |
+
│ │ - AT command? → Real hardware │ │
|
| 30 |
+
│ │ - OBD-II? → Simulator │ │
|
| 31 |
+
│ └─────────────────────────────────┘ │
|
| 32 |
+
│ │
|
| 33 |
+
│ ┌──────────────┐ ┌─────────────┐ │
|
| 34 |
+
│ │ UART/HW │ │ Simulator │ │
|
| 35 |
+
│ │ (elm327.c) │ │ (elm327_sim)│ │
|
| 36 |
+
│ └──────────────┘ └─────────────┘ │
|
| 37 |
+
└─────────────────────────────────────────┘
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
## Configuration
|
| 41 |
+
|
| 42 |
+
### Compile-Time Configuration
|
| 43 |
+
|
| 44 |
+
Edit `Include/wm_config.h`:
|
| 45 |
+
|
| 46 |
+
```c
|
| 47 |
+
/** ELM327 OBD-II **/
|
| 48 |
+
#define TLS_CONFIG_ELM327_SIMULATION CFG_ON // Enable simulator
|
| 49 |
+
// or
|
| 50 |
+
#define TLS_CONFIG_ELM327_SIMULATION CFG_OFF // Disable (production)
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
When `CFG_OFF`, simulation code is completely excluded from the build.
|
| 54 |
+
|
| 55 |
+
### Runtime Control
|
| 56 |
+
|
| 57 |
+
When compiled with simulation support, you can control it at runtime:
|
| 58 |
+
|
| 59 |
+
```c
|
| 60 |
+
#include "elm327.h"
|
| 61 |
+
|
| 62 |
+
// Enable simulation
|
| 63 |
+
elm327_set_simulation_mode(true);
|
| 64 |
+
|
| 65 |
+
// Disable simulation (use real hardware)
|
| 66 |
+
elm327_set_simulation_mode(false);
|
| 67 |
+
|
| 68 |
+
// Check status
|
| 69 |
+
if (elm327_is_simulation_enabled()) {
|
| 70 |
+
// Currently simulating
|
| 71 |
+
}
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
## Supported PIDs
|
| 75 |
+
|
| 76 |
+
The simulator supports multiple OBD-II modes:
|
| 77 |
+
|
| 78 |
+
### Mode 01: Show Current Data
|
| 79 |
+
|
| 80 |
+
#### Static PIDs
|
| 81 |
+
- `01 00` - Supported PIDs 01-20
|
| 82 |
+
- `01 01` - Monitor status since DTCs cleared
|
| 83 |
+
- `01 03` - Fuel system status
|
| 84 |
+
- `01 06` - Short term fuel trim
|
| 85 |
+
- `01 07` - Long term fuel trim
|
| 86 |
+
- `01 0A` - Fuel pressure
|
| 87 |
+
- `01 0B` - Intake manifold pressure
|
| 88 |
+
- `01 0E` - Timing advance
|
| 89 |
+
- `01 0F` - Intake air temperature
|
| 90 |
+
- `01 13` - O2 sensors present
|
| 91 |
+
- `01 1C` - OBD standard
|
| 92 |
+
- `01 1F` - Run time since engine start
|
| 93 |
+
- `01 20` - Supported PIDs 21-40
|
| 94 |
+
- `01 21` - Distance with MIL on
|
| 95 |
+
- `01 33` - Barometric pressure
|
| 96 |
+
- `01 40` - Supported PIDs 41-60
|
| 97 |
+
- `01 42` - Control module voltage
|
| 98 |
+
- `01 51` - Fuel type
|
| 99 |
+
|
| 100 |
+
#### Dynamic PIDs (Time-Varying)
|
| 101 |
+
- `01 04` - Calculated engine load
|
| 102 |
+
- `01 05` - Engine coolant temperature
|
| 103 |
+
- `01 0C` - Engine RPM
|
| 104 |
+
- `01 0D` - Vehicle speed
|
| 105 |
+
- `01 10` - MAF air flow rate
|
| 106 |
+
- `01 11` - Throttle position
|
| 107 |
+
- `01 2F` - Fuel tank level
|
| 108 |
+
|
| 109 |
+
### Mode 03: Show Stored DTCs
|
| 110 |
+
|
| 111 |
+
- `03` - Request stored Diagnostic Trouble Codes
|
| 112 |
+
- Returns 3 simulated DTCs:
|
| 113 |
+
- `P0171` - System Too Lean (Bank 1)
|
| 114 |
+
- `P0300` - Random/Multiple Cylinder Misfire Detected
|
| 115 |
+
- `P0442` - EVAP System Leak Detected (small leak)
|
| 116 |
+
|
| 117 |
+
### Mode 04: Clear DTCs
|
| 118 |
+
|
| 119 |
+
- `04` - Clear Diagnostic Trouble Codes
|
| 120 |
+
- Acknowledges clear command (DTCs remain in simulator for testing)
|
| 121 |
+
|
| 122 |
+
### Mode 07: Show Pending DTCs
|
| 123 |
+
|
| 124 |
+
- `07` - Request pending Diagnostic Trouble Codes
|
| 125 |
+
- Returns 1 pending DTC:
|
| 126 |
+
- `P0131` - O2 Sensor Circuit Low Voltage (Bank 1, Sensor 1)
|
| 127 |
+
|
| 128 |
+
### Mode 09: Request Vehicle Information
|
| 129 |
+
|
| 130 |
+
- `09 00` - Supported PIDs (returns available vehicle info PIDs)
|
| 131 |
+
- `09 02` - **Vehicle Identification Number (VIN)**
|
| 132 |
+
- Returns: `1HGBH41JXMN109186` (Honda Accord example)
|
| 133 |
+
- Format: 17 ASCII characters encoded as hex bytes
|
| 134 |
+
- `09 04` - **Calibration ID**
|
| 135 |
+
- Returns: `CAL123456`
|
| 136 |
+
- Identifies the ECU calibration version
|
| 137 |
+
- `09 0A` - **ECU Name**
|
| 138 |
+
- Returns: `ECU_SIM_UNIT`
|
| 139 |
+
- Human-readable ECU identification
|
| 140 |
+
|
| 141 |
+
## Usage Examples
|
| 142 |
+
|
| 143 |
+
### Basic Usage
|
| 144 |
+
|
| 145 |
+
```c
|
| 146 |
+
#include "elm327.h"
|
| 147 |
+
|
| 148 |
+
void test_elm327(void)
|
| 149 |
+
{
|
| 150 |
+
char response[ELM327_MAX_RESPONSE_LEN];
|
| 151 |
+
int ret;
|
| 152 |
+
|
| 153 |
+
// Initialize ELM327 (simulation enabled by default if compiled in)
|
| 154 |
+
elm327_init();
|
| 155 |
+
|
| 156 |
+
// AT commands go to real hardware
|
| 157 |
+
ret = elm327_send_command("ATZ", response, sizeof(response), 2000);
|
| 158 |
+
// Real ELM327 will respond with version info
|
| 159 |
+
|
| 160 |
+
// OBD-II commands are simulated
|
| 161 |
+
ret = elm327_send_command("01 0C", response, sizeof(response), 2000);
|
| 162 |
+
// Response: "41 0C 0F A0" (RPM data)
|
| 163 |
+
}
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
### Controlling Engine State
|
| 167 |
+
|
| 168 |
+
```c
|
| 169 |
+
// Start engine (affects RPM, temperature, etc.)
|
| 170 |
+
elm327_set_engine_state(true);
|
| 171 |
+
|
| 172 |
+
// Query RPM - will show idle RPM (~800)
|
| 173 |
+
elm327_send_command("01 0C", response, sizeof(response), 2000);
|
| 174 |
+
|
| 175 |
+
// Stop engine
|
| 176 |
+
elm327_set_engine_state(false);
|
| 177 |
+
|
| 178 |
+
// Query RPM - will show 0
|
| 179 |
+
elm327_send_command("01 0C", response, sizeof(response), 2000);
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
### Querying Vehicle Information
|
| 183 |
+
|
| 184 |
+
```c
|
| 185 |
+
#include "elm327.h"
|
| 186 |
+
|
| 187 |
+
void get_vehicle_info(void)
|
| 188 |
+
{
|
| 189 |
+
char response[ELM327_MAX_RESPONSE_LEN];
|
| 190 |
+
int ret;
|
| 191 |
+
|
| 192 |
+
// Get VIN
|
| 193 |
+
ret = elm327_send_command("09 02", response, sizeof(response), 2000);
|
| 194 |
+
// Response: "49 02 01 31 48 47 42 48 34 31 4A 58 4D 4E 31 30 39 31 38 36"
|
| 195 |
+
// Decodes to: "1HGBH41JXMN109186"
|
| 196 |
+
|
| 197 |
+
// Get Calibration ID
|
| 198 |
+
ret = elm327_send_command("09 04", response, sizeof(response), 2000);
|
| 199 |
+
// Response: "49 04 01 43 41 4C 31 32 33 34 35 36"
|
| 200 |
+
// Decodes to: "CAL123456"
|
| 201 |
+
|
| 202 |
+
// Get ECU Name
|
| 203 |
+
ret = elm327_send_command("09 0A", response, sizeof(response), 2000);
|
| 204 |
+
// Response: "49 0A 01 45 43 55 5F 53 49 4D 5F 55 4E 49 54"
|
| 205 |
+
// Decodes to: "ECU_SIM_UNIT"
|
| 206 |
+
}
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
### Testing MCP Integration
|
| 210 |
+
|
| 211 |
+
```bash
|
| 212 |
+
# With MCP server running and simulation enabled:
|
| 213 |
+
|
| 214 |
+
# AT command - goes to real ELM327
|
| 215 |
+
curl http://192.168.10.1/mcp -d '{"method":"diag/send_elm327_command","params":{"command":"ATZ"}}'
|
| 216 |
+
|
| 217 |
+
# OBD-II command - simulated response
|
| 218 |
+
curl http://192.168.10.1/mcp -d '{"method":"diag/send_elm327_command","params":{"command":"01 0C"}}'
|
| 219 |
+
|
| 220 |
+
# Get VIN - simulated response
|
| 221 |
+
curl http://192.168.10.1/mcp -d '{"method":"diag/send_elm327_command","params":{"command":"09 02"}}'
|
| 222 |
+
```
|
| 223 |
+
|
| 224 |
+
## Vehicle State Simulation
|
| 225 |
+
|
| 226 |
+
The simulator maintains internal state for realistic dynamic responses:
|
| 227 |
+
|
| 228 |
+
- **Engine Running**: Affects all dynamic PIDs
|
| 229 |
+
- **RPM**: Varies slightly at idle, increases with throttle
|
| 230 |
+
- **Coolant Temperature**: Gradually increases when engine running
|
| 231 |
+
- **Speed**: Varies based on throttle position
|
| 232 |
+
- **Throttle Position**: Simulated variations over time
|
| 233 |
+
- **MAF Rate**: Follows throttle position
|
| 234 |
+
- **Fuel Level**: Static (can be modified)
|
| 235 |
+
|
| 236 |
+
State updates happen automatically in `elm327_sim_update_state()` (called internally).
|
| 237 |
+
|
| 238 |
+
## Implementation Files
|
| 239 |
+
|
| 240 |
+
```
|
| 241 |
+
Src/App/elm327/
|
| 242 |
+
├── elm327.c # Main driver with simulation integration
|
| 243 |
+
├── elm327_sim.c # Simulation engine and data tables
|
| 244 |
+
├── elm327_sim.h # Internal simulator API
|
| 245 |
+
├── Makefile # Conditional compilation
|
| 246 |
+
└── README_SIMULATION.md # This file
|
| 247 |
+
|
| 248 |
+
Include/App/
|
| 249 |
+
└── elm327.h # Public API with simulation functions
|
| 250 |
+
```
|
| 251 |
+
|
| 252 |
+
## Adding New PIDs
|
| 253 |
+
|
| 254 |
+
To add support for additional PIDs, edit `elm327_sim.c`:
|
| 255 |
+
|
| 256 |
+
1. Add entry to `mode01_pid_table[]`:
|
| 257 |
+
|
| 258 |
+
```c
|
| 259 |
+
{"XX", "41 XX [data]", false}, // Static response
|
| 260 |
+
// or
|
| 261 |
+
{"XX", NULL, true}, // Dynamic response
|
| 262 |
+
```
|
| 263 |
+
|
| 264 |
+
2. For dynamic PIDs, add generator function:
|
| 265 |
+
|
| 266 |
+
```c
|
| 267 |
+
static int generate_custom_pid_response(char *response, int max_len)
|
| 268 |
+
{
|
| 269 |
+
uint8_t value = /* calculate value */;
|
| 270 |
+
return snprintf(response, max_len, "41 XX %02X", value);
|
| 271 |
+
}
|
| 272 |
+
```
|
| 273 |
+
|
| 274 |
+
3. Add case in `elm327_sim_generate_response()`:
|
| 275 |
+
|
| 276 |
+
```c
|
| 277 |
+
} else if (strcmp(pid, "XX") == 0) {
|
| 278 |
+
len = generate_custom_pid_response(response, max_len);
|
| 279 |
+
```
|
| 280 |
+
|
| 281 |
+
## Disabling for Production
|
| 282 |
+
|
| 283 |
+
For production builds without simulation overhead:
|
| 284 |
+
|
| 285 |
+
1. Edit `Include/wm_config.h`:
|
| 286 |
+
```c
|
| 287 |
+
#define TLS_CONFIG_ELM327_SIMULATION CFG_OFF
|
| 288 |
+
```
|
| 289 |
+
|
| 290 |
+
2. Rebuild:
|
| 291 |
+
```bash
|
| 292 |
+
cd Tools/GNU
|
| 293 |
+
make clean
|
| 294 |
+
make
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
The simulator code will be completely excluded from the binary.
|
| 298 |
+
|
| 299 |
+
## Logging
|
| 300 |
+
|
| 301 |
+
When simulation is active, log messages indicate simulated responses:
|
| 302 |
+
|
| 303 |
+
```
|
| 304 |
+
[ELM327/SIM] ELM327 Req (SIM): 01 0C
|
| 305 |
+
[ELM327/SIM] ELM327 Res (SIM): 41 0C 0F A0
|
| 306 |
+
```
|
| 307 |
+
|
| 308 |
+
Real hardware responses are logged without "(SIM)" marker:
|
| 309 |
+
|
| 310 |
+
```
|
| 311 |
+
[ELM327] ELM327 Req: ATZ
|
| 312 |
+
[ELM327] ELM327 Res: ELM327 v2.1
|
| 313 |
+
```
|
MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327.c
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file elm327.c
|
| 3 |
+
*
|
| 4 |
+
* @brief ELM327 OBD-II Interface Implementation
|
| 5 |
+
*
|
| 6 |
+
* @author castlebbs
|
| 7 |
+
*
|
| 8 |
+
*/
|
| 9 |
+
|
| 10 |
+
#define LOG_TAG "ELM327/Driver"
|
| 11 |
+
|
| 12 |
+
#include "elm327.h"
|
| 13 |
+
#include "wm_uart.h"
|
| 14 |
+
#include "wm_osal.h"
|
| 15 |
+
#include "wm_log.h"
|
| 16 |
+
#include <string.h>
|
| 17 |
+
#include <stdio.h>
|
| 18 |
+
|
| 19 |
+
#if (TLS_CONFIG_ELM327_SIMULATION == CFG_ON)
|
| 20 |
+
#include "elm327_sim.h"
|
| 21 |
+
#endif
|
| 22 |
+
|
| 23 |
+
// Internal state
|
| 24 |
+
static struct {
|
| 25 |
+
bool initialized;
|
| 26 |
+
tls_os_mutex_t *cmd_mutex; // Serializes concurrent access to ELM327
|
| 27 |
+
tls_os_sem_t *response_sem; // Signals when response complete
|
| 28 |
+
char rx_buffer[ELM327_MAX_RESPONSE_LEN];
|
| 29 |
+
int rx_len;
|
| 30 |
+
bool response_ready;
|
| 31 |
+
#if (TLS_CONFIG_ELM327_SIMULATION == CFG_ON)
|
| 32 |
+
bool simulation_enabled; // Enable simulation mode
|
| 33 |
+
#endif
|
| 34 |
+
} elm327_ctx = {0};
|
| 35 |
+
|
| 36 |
+
/**
|
| 37 |
+
* @brief Check if command is an AT command (should go to real hardware)
|
| 38 |
+
*/
|
| 39 |
+
static bool is_at_command(const char *cmd)
|
| 40 |
+
{
|
| 41 |
+
if (!cmd || strlen(cmd) < 2)
|
| 42 |
+
return false;
|
| 43 |
+
|
| 44 |
+
// Check if command starts with "AT" (case insensitive)
|
| 45 |
+
return (cmd[0] == 'A' || cmd[0] == 'a') &&
|
| 46 |
+
(cmd[1] == 'T' || cmd[1] == 't');
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
/**
|
| 50 |
+
* @brief RX callback - called from interrupt context when data arrives
|
| 51 |
+
*/
|
| 52 |
+
static s16 elm327_rx_callback(u16 len)
|
| 53 |
+
{
|
| 54 |
+
int available;
|
| 55 |
+
char ch;
|
| 56 |
+
|
| 57 |
+
// Read available data
|
| 58 |
+
while (len > 0) {
|
| 59 |
+
// Read one byte at a time to check for prompt
|
| 60 |
+
available = tls_uart_read(TLS_UART_1, (u8*)&ch, 1);
|
| 61 |
+
if (available <= 0)
|
| 62 |
+
break;
|
| 63 |
+
|
| 64 |
+
len--;
|
| 65 |
+
|
| 66 |
+
// Check for ELM327 prompt '>' which signals end of response
|
| 67 |
+
if (ch == '>') {
|
| 68 |
+
elm327_ctx.rx_buffer[elm327_ctx.rx_len] = '\0';
|
| 69 |
+
elm327_ctx.response_ready = true;
|
| 70 |
+
|
| 71 |
+
// Signal waiting thread
|
| 72 |
+
if (elm327_ctx.response_sem) {
|
| 73 |
+
tls_os_sem_release(elm327_ctx.response_sem);
|
| 74 |
+
}
|
| 75 |
+
break;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
// Filter out carriage returns for cleaner output
|
| 79 |
+
if (ch == '\r') {
|
| 80 |
+
continue;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
// Buffer overflow protection
|
| 84 |
+
if (elm327_ctx.rx_len >= ELM327_MAX_RESPONSE_LEN - 1) {
|
| 85 |
+
// Overflow - discard and reset
|
| 86 |
+
elm327_ctx.rx_len = 0;
|
| 87 |
+
continue;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
// Append to buffer
|
| 91 |
+
elm327_ctx.rx_buffer[elm327_ctx.rx_len++] = ch;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
return WM_SUCCESS;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
int elm327_init(void)
|
| 98 |
+
{
|
| 99 |
+
wm_log_info("Initializing ELM327...");
|
| 100 |
+
tls_uart_options_t uart_opts;
|
| 101 |
+
int ret;
|
| 102 |
+
|
| 103 |
+
if (elm327_ctx.initialized) {
|
| 104 |
+
wm_log_info("ELM327 already initialized");
|
| 105 |
+
return WM_SUCCESS; // Already initialized
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
memset(&elm327_ctx, 0, sizeof(elm327_ctx));
|
| 109 |
+
|
| 110 |
+
// Configure UART1: 38400 8N1
|
| 111 |
+
uart_opts.baudrate = 38400;
|
| 112 |
+
uart_opts.charlength = TLS_UART_CHSIZE_8BIT;
|
| 113 |
+
uart_opts.paritytype = TLS_UART_PMODE_DISABLED;
|
| 114 |
+
uart_opts.stopbits = TLS_UART_ONE_STOPBITS;
|
| 115 |
+
uart_opts.flow_ctrl = TLS_UART_FLOW_CTRL_NONE;
|
| 116 |
+
|
| 117 |
+
// Initialize UART port
|
| 118 |
+
ret = tls_uart_port_init(TLS_UART_1, &uart_opts, 0);
|
| 119 |
+
if (ret != WM_SUCCESS) {
|
| 120 |
+
wm_log_error("UART init failed");
|
| 121 |
+
return ret;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
// Create mutex for serializing concurrent access
|
| 125 |
+
ret = tls_os_mutex_create(0, &elm327_ctx.cmd_mutex);
|
| 126 |
+
if (ret != TLS_OS_SUCCESS) {
|
| 127 |
+
wm_log_error("Mutex creation failed");
|
| 128 |
+
return WM_FAILED;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
// Create semaphore for response synchronization
|
| 132 |
+
ret = tls_os_sem_create(&elm327_ctx.response_sem, 0);
|
| 133 |
+
if (ret != TLS_OS_SUCCESS) {
|
| 134 |
+
wm_log_error("Semaphore creation failed");
|
| 135 |
+
tls_os_mutex_delete(elm327_ctx.cmd_mutex);
|
| 136 |
+
return WM_FAILED;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
// Register RX callback
|
| 140 |
+
tls_uart_rx_callback_register(TLS_UART_1, elm327_rx_callback);
|
| 141 |
+
|
| 142 |
+
#if (TLS_CONFIG_ELM327_SIMULATION == CFG_ON)
|
| 143 |
+
// Initialize simulator (enabled by default in simulation build)
|
| 144 |
+
elm327_ctx.simulation_enabled = true;
|
| 145 |
+
elm327_sim_init();
|
| 146 |
+
wm_log_info("Simulation mode enabled");
|
| 147 |
+
#endif
|
| 148 |
+
|
| 149 |
+
elm327_ctx.initialized = true;
|
| 150 |
+
|
| 151 |
+
wm_log_info("Initialized on UART1 @ 38400 baud");
|
| 152 |
+
return WM_SUCCESS;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
int elm327_send_command(const char *cmd, char *response,
|
| 156 |
+
int max_len, u32 timeout_ms)
|
| 157 |
+
{
|
| 158 |
+
int cmd_len;
|
| 159 |
+
int ret;
|
| 160 |
+
u8 err;
|
| 161 |
+
|
| 162 |
+
if (!elm327_ctx.initialized) {
|
| 163 |
+
return -1; // Not initialized
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
if (!cmd || !response || max_len <= 0) {
|
| 167 |
+
return -2; // Invalid parameters
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
#if (TLS_CONFIG_ELM327_SIMULATION == CFG_ON)
|
| 171 |
+
// Check if we should simulate this command
|
| 172 |
+
if (elm327_ctx.simulation_enabled && !is_at_command(cmd)) {
|
| 173 |
+
// Simulate OBD-II response (non-AT commands)
|
| 174 |
+
wm_log_info("Req (SIM): %s", cmd);
|
| 175 |
+
|
| 176 |
+
// Update vehicle state for dynamic simulation
|
| 177 |
+
elm327_sim_update_state();
|
| 178 |
+
|
| 179 |
+
ret = elm327_sim_generate_response(cmd, response, max_len);
|
| 180 |
+
|
| 181 |
+
if (ret > 0) {
|
| 182 |
+
wm_log_info("Res (SIM): %s", response);
|
| 183 |
+
} else {
|
| 184 |
+
wm_log_info("Res (SIM): (empty/error)");
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
return ret;
|
| 188 |
+
}
|
| 189 |
+
// AT commands or simulation disabled - fall through to real hardware
|
| 190 |
+
#endif
|
| 191 |
+
|
| 192 |
+
// Acquire mutex to serialize access to ELM327
|
| 193 |
+
// This ensures only one command is in-flight at a time
|
| 194 |
+
tls_os_mutex_acquire(elm327_ctx.cmd_mutex, 0);
|
| 195 |
+
|
| 196 |
+
// Reset RX state
|
| 197 |
+
elm327_ctx.rx_len = 0;
|
| 198 |
+
elm327_ctx.response_ready = false;
|
| 199 |
+
memset(elm327_ctx.rx_buffer, 0, sizeof(elm327_ctx.rx_buffer));
|
| 200 |
+
|
| 201 |
+
// Flush any pending RX data
|
| 202 |
+
tls_uart_read(TLS_UART_1, (u8*)elm327_ctx.rx_buffer,
|
| 203 |
+
ELM327_MAX_RESPONSE_LEN);
|
| 204 |
+
elm327_ctx.rx_len = 0;
|
| 205 |
+
|
| 206 |
+
// Send command (ELM327 expects \r terminator)
|
| 207 |
+
cmd_len = strlen(cmd);
|
| 208 |
+
|
| 209 |
+
// Log the command being sent
|
| 210 |
+
wm_log_info("Req: %s", cmd);
|
| 211 |
+
|
| 212 |
+
ret = tls_uart_write(TLS_UART_1, (char*)cmd, cmd_len);
|
| 213 |
+
if (ret != WM_SUCCESS) {
|
| 214 |
+
wm_log_error("Send failed");
|
| 215 |
+
tls_os_mutex_release(elm327_ctx.cmd_mutex);
|
| 216 |
+
return -3;
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
// Send carriage return if not already present
|
| 220 |
+
if (cmd[cmd_len - 1] != '\r') {
|
| 221 |
+
tls_uart_write(TLS_UART_1, "\r", 1);
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
// Wait for response with timeout
|
| 225 |
+
err = tls_os_sem_acquire(elm327_ctx.response_sem, timeout_ms);
|
| 226 |
+
if (err != TLS_OS_SUCCESS) {
|
| 227 |
+
wm_log_error("Timeout waiting for response\n");
|
| 228 |
+
tls_os_mutex_release(elm327_ctx.cmd_mutex);
|
| 229 |
+
return -4; // Timeout
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
// Copy response to user buffer
|
| 233 |
+
if (elm327_ctx.rx_len > 0) {
|
| 234 |
+
char *src = elm327_ctx.rx_buffer;
|
| 235 |
+
int src_len = elm327_ctx.rx_len;
|
| 236 |
+
|
| 237 |
+
// Remove echoed command if present at beginning (ECHO ON case)
|
| 238 |
+
// Compare against command without trailing \r
|
| 239 |
+
int cmd_cmp_len = cmd_len;
|
| 240 |
+
if (cmd_cmp_len > 0 && cmd[cmd_cmp_len - 1] == '\r') {
|
| 241 |
+
cmd_cmp_len--;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
// Check if response starts with the command
|
| 245 |
+
if (src_len >= cmd_cmp_len &&
|
| 246 |
+
memcmp(src, cmd, cmd_cmp_len) == 0) {
|
| 247 |
+
// Skip past the echoed command
|
| 248 |
+
src += cmd_cmp_len;
|
| 249 |
+
src_len -= cmd_cmp_len;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
// Copy remaining response to user buffer
|
| 253 |
+
if (src_len > 0) {
|
| 254 |
+
int copy_len = (src_len < max_len) ? src_len : max_len - 1;
|
| 255 |
+
memcpy(response, src, copy_len);
|
| 256 |
+
response[copy_len] = '\0';
|
| 257 |
+
|
| 258 |
+
// Log the response received
|
| 259 |
+
wm_log_info("Res: %s", response);
|
| 260 |
+
|
| 261 |
+
tls_os_mutex_release(elm327_ctx.cmd_mutex);
|
| 262 |
+
return copy_len;
|
| 263 |
+
}
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
// Log empty response
|
| 267 |
+
wm_log_info("Res: (empty)");
|
| 268 |
+
|
| 269 |
+
tls_os_mutex_release(elm327_ctx.cmd_mutex);
|
| 270 |
+
return 0; // Empty response
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
void elm327_deinit(void)
|
| 274 |
+
{
|
| 275 |
+
if (!elm327_ctx.initialized)
|
| 276 |
+
return;
|
| 277 |
+
|
| 278 |
+
// Unregister callback
|
| 279 |
+
tls_uart_rx_callback_register(TLS_UART_1, NULL);
|
| 280 |
+
|
| 281 |
+
// Delete mutex
|
| 282 |
+
if (elm327_ctx.cmd_mutex) {
|
| 283 |
+
tls_os_mutex_delete(elm327_ctx.cmd_mutex);
|
| 284 |
+
elm327_ctx.cmd_mutex = NULL;
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
// Delete semaphore
|
| 288 |
+
if (elm327_ctx.response_sem) {
|
| 289 |
+
tls_os_sem_delete(elm327_ctx.response_sem);
|
| 290 |
+
elm327_ctx.response_sem = NULL;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
elm327_ctx.initialized = false;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
#if (TLS_CONFIG_ELM327_SIMULATION == CFG_ON)
|
| 297 |
+
void elm327_set_simulation_mode(bool enable)
|
| 298 |
+
{
|
| 299 |
+
elm327_ctx.simulation_enabled = enable;
|
| 300 |
+
wm_log_info("Simulation mode %s", enable ? "enabled" : "disabled");
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
bool elm327_is_simulation_enabled(void)
|
| 304 |
+
{
|
| 305 |
+
return elm327_ctx.simulation_enabled;
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
void elm327_set_engine_state(bool running)
|
| 309 |
+
{
|
| 310 |
+
elm327_sim_set_engine_state(running);
|
| 311 |
+
}
|
| 312 |
+
#endif
|
MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327_history.c
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file elm327_history.c
|
| 3 |
+
*
|
| 4 |
+
* @brief ELM327 OBD-II Data History Implementation
|
| 5 |
+
*
|
| 6 |
+
* @author castlebbs
|
| 7 |
+
*
|
| 8 |
+
*/
|
| 9 |
+
|
| 10 |
+
#define LOG_TAG "ELM327/History"
|
| 11 |
+
|
| 12 |
+
#include "elm327_history.h"
|
| 13 |
+
#include "elm327.h"
|
| 14 |
+
#include "wm_osal.h"
|
| 15 |
+
#include "wm_log.h"
|
| 16 |
+
#include <string.h>
|
| 17 |
+
#include <stdio.h>
|
| 18 |
+
#include <stdlib.h>
|
| 19 |
+
|
| 20 |
+
// Task priority (same range as other demo tasks: 32-39)
|
| 21 |
+
#define ELM327_HISTORY_TASK_PRIO 34
|
| 22 |
+
#define ELM327_HISTORY_TASK_STACK_SIZE 512 // Stack size in u32 words
|
| 23 |
+
|
| 24 |
+
// Task stack (statically allocated)
|
| 25 |
+
static OS_STK elm327_history_task_stk[ELM327_HISTORY_TASK_STACK_SIZE];
|
| 26 |
+
|
| 27 |
+
// Ring buffer storage
|
| 28 |
+
static struct {
|
| 29 |
+
elm327_history_entry_t entries[ELM327_HISTORY_SIZE];
|
| 30 |
+
int write_index; // Next position to write
|
| 31 |
+
int count; // Number of valid entries
|
| 32 |
+
tls_os_mutex_t *mutex; // Protect concurrent access
|
| 33 |
+
bool task_running;
|
| 34 |
+
} elm327_history = {0};
|
| 35 |
+
|
| 36 |
+
/**
|
| 37 |
+
* @brief Parse RPM from OBD-II response
|
| 38 |
+
* Response format: "41 0C XX XX" where RPM = (256*A + B) / 4
|
| 39 |
+
*/
|
| 40 |
+
static int parse_rpm(const char *response)
|
| 41 |
+
{
|
| 42 |
+
unsigned int a, b;
|
| 43 |
+
|
| 44 |
+
// Look for "41 0C" response
|
| 45 |
+
if (sscanf(response, "%*x %*x %x %x", &a, &b) == 2) {
|
| 46 |
+
return (256 * a + b) / 4;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
return 0;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/**
|
| 53 |
+
* @brief Parse speed from OBD-II response
|
| 54 |
+
* Response format: "41 0D XX" where Speed = A km/h
|
| 55 |
+
*/
|
| 56 |
+
static int parse_speed(const char *response)
|
| 57 |
+
{
|
| 58 |
+
unsigned int speed;
|
| 59 |
+
|
| 60 |
+
// Look for "41 0D" response
|
| 61 |
+
if (sscanf(response, "%*x %*x %x", &speed) == 1) {
|
| 62 |
+
return speed;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
return 0;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
/**
|
| 69 |
+
* @brief Parse coolant temperature from OBD-II response
|
| 70 |
+
* Response format: "41 05 XX" where Temp = A - 40 degrees C
|
| 71 |
+
*/
|
| 72 |
+
static int parse_coolant_temp(const char *response)
|
| 73 |
+
{
|
| 74 |
+
unsigned int temp;
|
| 75 |
+
|
| 76 |
+
// Look for "41 05" response
|
| 77 |
+
if (sscanf(response, "%*x %*x %x", &temp) == 1) {
|
| 78 |
+
return (int)temp - 40;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
return -40; // Default to minimum temp
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
/**
|
| 85 |
+
* @brief Sample OBD-II PIDs and add to ring buffer
|
| 86 |
+
*/
|
| 87 |
+
static void sample_obd_pids(void)
|
| 88 |
+
{
|
| 89 |
+
char response[ELM327_MAX_RESPONSE_LEN];
|
| 90 |
+
elm327_history_entry_t *entry;
|
| 91 |
+
int ret;
|
| 92 |
+
u16 rpm = 0;
|
| 93 |
+
u8 speed = 0;
|
| 94 |
+
s8 coolant_temp = -40;
|
| 95 |
+
|
| 96 |
+
// Query RPM (PID 0x0C)
|
| 97 |
+
ret = elm327_send_command("01 0C", response, sizeof(response), ELM327_DEFAULT_TIMEOUT);
|
| 98 |
+
if (ret > 0) {
|
| 99 |
+
rpm = parse_rpm(response);
|
| 100 |
+
} else {
|
| 101 |
+
wm_log_warn("Failed to read RPM: %d", ret);
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
// Query Speed (PID 0x0D)
|
| 105 |
+
ret = elm327_send_command("01 0D", response, sizeof(response), ELM327_DEFAULT_TIMEOUT);
|
| 106 |
+
if (ret > 0) {
|
| 107 |
+
speed = parse_speed(response);
|
| 108 |
+
} else {
|
| 109 |
+
wm_log_warn("Failed to read speed: %d", ret);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
// Query Coolant Temperature (PID 0x05)
|
| 113 |
+
ret = elm327_send_command("01 05", response, sizeof(response), ELM327_DEFAULT_TIMEOUT);
|
| 114 |
+
if (ret > 0) {
|
| 115 |
+
coolant_temp = parse_coolant_temp(response);
|
| 116 |
+
} else {
|
| 117 |
+
wm_log_warn("Failed to read coolant temp: %d", ret);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
// Acquire mutex to safely update ring buffer
|
| 121 |
+
tls_os_mutex_acquire(elm327_history.mutex, 0);
|
| 122 |
+
|
| 123 |
+
// Get next entry in ring buffer
|
| 124 |
+
entry = &elm327_history.entries[elm327_history.write_index];
|
| 125 |
+
|
| 126 |
+
// Fill entry
|
| 127 |
+
entry->timestamp = tls_os_get_time();
|
| 128 |
+
entry->rpm = rpm;
|
| 129 |
+
entry->speed = speed;
|
| 130 |
+
entry->coolant_temp = coolant_temp;
|
| 131 |
+
entry->valid = 1;
|
| 132 |
+
|
| 133 |
+
// Update write index (wrap around)
|
| 134 |
+
elm327_history.write_index = (elm327_history.write_index + 1) % ELM327_HISTORY_SIZE;
|
| 135 |
+
|
| 136 |
+
// Update count (max = buffer size)
|
| 137 |
+
if (elm327_history.count < ELM327_HISTORY_SIZE) {
|
| 138 |
+
elm327_history.count++;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
tls_os_mutex_release(elm327_history.mutex);
|
| 142 |
+
|
| 143 |
+
wm_log_info("Sampled: RPM=%u, Speed=%u km/h, Coolant=%d C",
|
| 144 |
+
rpm, speed, coolant_temp);
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
/**
|
| 148 |
+
* @brief Periodic task that samples OBD-II data every 10 seconds
|
| 149 |
+
*/
|
| 150 |
+
static void elm327_history_task(void *param)
|
| 151 |
+
{
|
| 152 |
+
wm_log_info("ELM327 history task started");
|
| 153 |
+
|
| 154 |
+
while (elm327_history.task_running) {
|
| 155 |
+
// Sample OBD-II PIDs
|
| 156 |
+
sample_obd_pids();
|
| 157 |
+
|
| 158 |
+
// Wait for next sample interval
|
| 159 |
+
tls_os_time_delay(ELM327_HISTORY_INTERVAL_MS / 1000 * HZ);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
wm_log_info("ELM327 history task stopped");
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
int elm327_history_init(void)
|
| 166 |
+
{
|
| 167 |
+
int ret;
|
| 168 |
+
|
| 169 |
+
if (elm327_history.task_running) {
|
| 170 |
+
wm_log_info("ELM327 history already initialized");
|
| 171 |
+
return WM_SUCCESS;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
// Clear ring buffer
|
| 175 |
+
memset(&elm327_history, 0, sizeof(elm327_history));
|
| 176 |
+
|
| 177 |
+
// Create mutex for thread-safe access
|
| 178 |
+
ret = tls_os_mutex_create(0, &elm327_history.mutex);
|
| 179 |
+
if (ret != TLS_OS_SUCCESS) {
|
| 180 |
+
wm_log_error("Failed to create history mutex");
|
| 181 |
+
return WM_FAILED;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
// Create periodic sampling task
|
| 185 |
+
elm327_history.task_running = true;
|
| 186 |
+
ret = tls_os_task_create(NULL,
|
| 187 |
+
"elm327_hist",
|
| 188 |
+
elm327_history_task,
|
| 189 |
+
NULL,
|
| 190 |
+
(void *)elm327_history_task_stk,
|
| 191 |
+
ELM327_HISTORY_TASK_STACK_SIZE * sizeof(u32),
|
| 192 |
+
ELM327_HISTORY_TASK_PRIO,
|
| 193 |
+
0);
|
| 194 |
+
|
| 195 |
+
if (ret != TLS_OS_SUCCESS) {
|
| 196 |
+
wm_log_error("Failed to create history task");
|
| 197 |
+
tls_os_mutex_delete(elm327_history.mutex);
|
| 198 |
+
elm327_history.task_running = false;
|
| 199 |
+
return WM_FAILED;
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
wm_log_info("ELM327 history initialized (sampling every %d ms)",
|
| 203 |
+
ELM327_HISTORY_INTERVAL_MS);
|
| 204 |
+
return WM_SUCCESS;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
int elm327_history_get_count(void)
|
| 208 |
+
{
|
| 209 |
+
int count;
|
| 210 |
+
|
| 211 |
+
if (!elm327_history.mutex) {
|
| 212 |
+
return 0;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
tls_os_mutex_acquire(elm327_history.mutex, 0);
|
| 216 |
+
count = elm327_history.count;
|
| 217 |
+
tls_os_mutex_release(elm327_history.mutex);
|
| 218 |
+
|
| 219 |
+
return count;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
const elm327_history_entry_t* elm327_history_get_entry(int index)
|
| 223 |
+
{
|
| 224 |
+
const elm327_history_entry_t *entry = NULL;
|
| 225 |
+
int actual_index;
|
| 226 |
+
int oldest;
|
| 227 |
+
|
| 228 |
+
if (!elm327_history.mutex) {
|
| 229 |
+
return NULL;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
tls_os_mutex_acquire(elm327_history.mutex, 0);
|
| 233 |
+
|
| 234 |
+
// Validate index
|
| 235 |
+
if (index < 0 || index >= elm327_history.count) {
|
| 236 |
+
tls_os_mutex_release(elm327_history.mutex);
|
| 237 |
+
return NULL;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
// Calculate actual ring buffer index
|
| 241 |
+
// oldest = (write_index - count + SIZE) % SIZE
|
| 242 |
+
oldest = (elm327_history.write_index - elm327_history.count + ELM327_HISTORY_SIZE)
|
| 243 |
+
% ELM327_HISTORY_SIZE;
|
| 244 |
+
actual_index = (oldest + index) % ELM327_HISTORY_SIZE;
|
| 245 |
+
|
| 246 |
+
entry = &elm327_history.entries[actual_index];
|
| 247 |
+
|
| 248 |
+
tls_os_mutex_release(elm327_history.mutex);
|
| 249 |
+
|
| 250 |
+
return entry;
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
void elm327_history_deinit(void)
|
| 254 |
+
{
|
| 255 |
+
if (!elm327_history.task_running) {
|
| 256 |
+
return;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
// Stop task
|
| 260 |
+
elm327_history.task_running = false;
|
| 261 |
+
|
| 262 |
+
// Wait a bit for task to exit
|
| 263 |
+
tls_os_time_delay(HZ / 10); // 100ms
|
| 264 |
+
|
| 265 |
+
// Delete task by priority
|
| 266 |
+
tls_os_task_del(ELM327_HISTORY_TASK_PRIO, NULL);
|
| 267 |
+
|
| 268 |
+
// Delete mutex
|
| 269 |
+
if (elm327_history.mutex) {
|
| 270 |
+
tls_os_mutex_delete(elm327_history.mutex);
|
| 271 |
+
elm327_history.mutex = NULL;
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
wm_log_info("ELM327 history deinitialized");
|
| 275 |
+
}
|
MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327_sim.c
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file elm327_sim.c
|
| 3 |
+
*
|
| 4 |
+
* @brief ELM327 OBD-II Simulator Implementation
|
| 5 |
+
*
|
| 6 |
+
* @author castlebbs
|
| 7 |
+
*
|
| 8 |
+
* Provides simulated OBD-II responses for testing without a vehicle connection.
|
| 9 |
+
* Supports common Mode 01 PIDs with both static and dynamic responses.
|
| 10 |
+
*
|
| 11 |
+
* Simulated Fault Codes (DTCs):
|
| 12 |
+
* - Mode 03: Reports 3 stored DTCs:
|
| 13 |
+
* - P0171: System Too Lean (Bank 1)
|
| 14 |
+
* - P0300: Random/Multiple Cylinder Misfire Detected
|
| 15 |
+
* - P0442: EVAP System Leak Detected (small leak)
|
| 16 |
+
* - Mode 07: Reports 1 pending DTC:
|
| 17 |
+
* - P0131: O2 Sensor Circuit Low Voltage (Bank 1, Sensor 1)
|
| 18 |
+
* - Mode 04: Clear DTCs (acknowledged but DTCs remain in simulator)
|
| 19 |
+
*
|
| 20 |
+
* Vehicle Information (Mode 09):
|
| 21 |
+
* - VIN: 1HGBH41JXMN109186 (Honda Accord example)
|
| 22 |
+
* - Calibration ID: CAL123456
|
| 23 |
+
* - ECU Name: ECU_SIM_UNIT
|
| 24 |
+
*
|
| 25 |
+
* MIL (Check Engine Light): ON with 3 DTCs stored
|
| 26 |
+
* Distance traveled with MIL on: 75 km
|
| 27 |
+
*/
|
| 28 |
+
|
| 29 |
+
#define LOG_TAG "ELM327/SIM"
|
| 30 |
+
|
| 31 |
+
#include "elm327_sim.h"
|
| 32 |
+
#include "wm_log.h"
|
| 33 |
+
#include <string.h>
|
| 34 |
+
#include <stdio.h>
|
| 35 |
+
#include <stdlib.h>
|
| 36 |
+
|
| 37 |
+
/******************************************************************************
|
| 38 |
+
* Vehicle State (for dynamic responses)
|
| 39 |
+
*****************************************************************************/
|
| 40 |
+
static struct {
|
| 41 |
+
bool engine_running;
|
| 42 |
+
uint16_t rpm; // Current RPM (0-8000)
|
| 43 |
+
uint8_t speed; // Current speed in km/h (0-255)
|
| 44 |
+
int8_t coolant_temp; // Coolant temperature in °C (-40 to 215)
|
| 45 |
+
uint8_t throttle_pos; // Throttle position 0-100%
|
| 46 |
+
uint16_t maf_rate; // MAF sensor g/s (scaled)
|
| 47 |
+
uint8_t fuel_level; // Fuel level 0-100%
|
| 48 |
+
uint32_t update_counter; // For time-based variations
|
| 49 |
+
} vehicle_state;
|
| 50 |
+
|
| 51 |
+
/******************************************************************************
|
| 52 |
+
* Static PID Response Tables
|
| 53 |
+
*
|
| 54 |
+
* Format: Mode 01 responses (show current data)
|
| 55 |
+
* Command format: "01 XX" where XX is the PID
|
| 56 |
+
* Response format: "41 XX [data bytes]"
|
| 57 |
+
*****************************************************************************/
|
| 58 |
+
|
| 59 |
+
typedef struct {
|
| 60 |
+
const char *pid; // PID code (e.g., "00", "0C")
|
| 61 |
+
const char *response; // Static response (NULL = dynamic)
|
| 62 |
+
bool is_dynamic; // Generate response dynamically
|
| 63 |
+
} pid_entry_t;
|
| 64 |
+
|
| 65 |
+
static const pid_entry_t mode01_pid_table[] = {
|
| 66 |
+
// PID 00: Supported PIDs 01-20
|
| 67 |
+
{"00", "41 00 BE 3F B8 13", false},
|
| 68 |
+
|
| 69 |
+
// PID 01: Monitor status since DTCs cleared
|
| 70 |
+
// MIL on (bit 7=1), 3 DTCs stored (bits 0-6 = 0x03)
|
| 71 |
+
{"01", "41 01 83 07 65 04", false},
|
| 72 |
+
|
| 73 |
+
// PID 03: Fuel system status
|
| 74 |
+
{"03", "41 03 02 00", false}, // Closed loop, using O2 sensor
|
| 75 |
+
|
| 76 |
+
// PID 04: Calculated engine load (dynamic)
|
| 77 |
+
{"04", NULL, true},
|
| 78 |
+
|
| 79 |
+
// PID 05: Engine coolant temperature (dynamic)
|
| 80 |
+
{"05", NULL, true},
|
| 81 |
+
|
| 82 |
+
// PID 06: Short term fuel trim - Bank 1
|
| 83 |
+
{"06", "41 06 80", false}, // 0% (centered)
|
| 84 |
+
|
| 85 |
+
// PID 07: Long term fuel trim - Bank 1
|
| 86 |
+
{"07", "41 07 80", false}, // 0% (centered)
|
| 87 |
+
|
| 88 |
+
// PID 0A: Fuel pressure
|
| 89 |
+
{"0A", "41 0A B4", false}, // 540 kPa
|
| 90 |
+
|
| 91 |
+
// PID 0B: Intake manifold pressure
|
| 92 |
+
{"0B", "41 0B 63", false}, // 99 kPa
|
| 93 |
+
|
| 94 |
+
// PID 0C: Engine RPM (dynamic)
|
| 95 |
+
{"0C", NULL, true},
|
| 96 |
+
|
| 97 |
+
// PID 0D: Vehicle speed (dynamic)
|
| 98 |
+
{"0D", NULL, true},
|
| 99 |
+
|
| 100 |
+
// PID 0E: Timing advance
|
| 101 |
+
{"0E", "41 0E 7C", false}, // 14 degrees before TDC
|
| 102 |
+
|
| 103 |
+
// PID 0F: Intake air temperature
|
| 104 |
+
{"0F", "41 0F 54", false}, // 44°C
|
| 105 |
+
|
| 106 |
+
// PID 10: MAF air flow rate (dynamic)
|
| 107 |
+
{"10", NULL, true},
|
| 108 |
+
|
| 109 |
+
// PID 11: Throttle position (dynamic)
|
| 110 |
+
{"11", NULL, true},
|
| 111 |
+
|
| 112 |
+
// PID 13: O2 sensors present
|
| 113 |
+
{"13", "41 13 03", false},
|
| 114 |
+
|
| 115 |
+
// PID 1C: OBD standard
|
| 116 |
+
{"1C", "41 1C 01", false}, // OBD-II (California ARB)
|
| 117 |
+
|
| 118 |
+
// PID 1F: Run time since engine start
|
| 119 |
+
{"1F", "41 1F 00 8C", false}, // 140 seconds
|
| 120 |
+
|
| 121 |
+
// PID 20: Supported PIDs 21-40
|
| 122 |
+
{"20", "41 20 80 01 80 01", false},
|
| 123 |
+
|
| 124 |
+
// PID 21: Distance traveled with MIL on
|
| 125 |
+
{"21", "41 21 00 4B", false}, // 75 km with MIL on
|
| 126 |
+
|
| 127 |
+
// PID 2F: Fuel tank level (dynamic)
|
| 128 |
+
{"2F", NULL, true},
|
| 129 |
+
|
| 130 |
+
// PID 33: Barometric pressure
|
| 131 |
+
{"33", "41 33 65", false}, // 101 kPa
|
| 132 |
+
|
| 133 |
+
// PID 40: Supported PIDs 41-60
|
| 134 |
+
{"40", "41 40 40 00 00 00", false},
|
| 135 |
+
|
| 136 |
+
// PID 42: Control module voltage
|
| 137 |
+
{"42", "41 42 32 E8", false}, // 13.05V
|
| 138 |
+
|
| 139 |
+
// PID 51: Fuel Type
|
| 140 |
+
{"51", "41 51 01", false}, // Gasoline
|
| 141 |
+
};
|
| 142 |
+
|
| 143 |
+
#define NUM_MODE01_PIDS (sizeof(mode01_pid_table) / sizeof(pid_entry_t))
|
| 144 |
+
|
| 145 |
+
/******************************************************************************
|
| 146 |
+
* Mode 09 Vehicle Information
|
| 147 |
+
*****************************************************************************/
|
| 148 |
+
|
| 149 |
+
// Simulated VIN: 5TDKRKEC7PS142916 (17 characters - Toyota Sienna example)
|
| 150 |
+
#define SIMULATED_VIN "5TDKRKEC7PS142916"
|
| 151 |
+
|
| 152 |
+
// Simulated Calibration ID
|
| 153 |
+
#define SIMULATED_CAL_ID "CAL123456"
|
| 154 |
+
|
| 155 |
+
// Simulated ECU Name
|
| 156 |
+
#define SIMULATED_ECU_NAME "ECU_SIM_UNIT"
|
| 157 |
+
|
| 158 |
+
/******************************************************************************
|
| 159 |
+
* Helper Functions
|
| 160 |
+
*****************************************************************************/
|
| 161 |
+
|
| 162 |
+
/**
|
| 163 |
+
* @brief Normalize command string (remove spaces, convert to uppercase)
|
| 164 |
+
*/
|
| 165 |
+
static void normalize_command(const char *cmd, char *normalized, int max_len)
|
| 166 |
+
{
|
| 167 |
+
int i = 0, j = 0;
|
| 168 |
+
|
| 169 |
+
while (cmd[i] && j < max_len - 1) {
|
| 170 |
+
char c = cmd[i++];
|
| 171 |
+
|
| 172 |
+
// Skip spaces
|
| 173 |
+
if (c == ' ')
|
| 174 |
+
continue;
|
| 175 |
+
|
| 176 |
+
// Convert to uppercase
|
| 177 |
+
if (c >= 'a' && c <= 'z')
|
| 178 |
+
c = c - 'a' + 'A';
|
| 179 |
+
|
| 180 |
+
normalized[j++] = c;
|
| 181 |
+
}
|
| 182 |
+
normalized[j] = '\0';
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
/**
|
| 186 |
+
* @brief Extract PID from normalized command (e.g., "010C" -> "0C")
|
| 187 |
+
*/
|
| 188 |
+
static bool extract_pid(const char *normalized_cmd, char *pid_out)
|
| 189 |
+
{
|
| 190 |
+
int len = strlen(normalized_cmd);
|
| 191 |
+
|
| 192 |
+
// Mode 01 commands: "01XX" where XX is PID
|
| 193 |
+
if (len >= 4 && normalized_cmd[0] == '0' && normalized_cmd[1] == '1') {
|
| 194 |
+
pid_out[0] = normalized_cmd[2];
|
| 195 |
+
pid_out[1] = normalized_cmd[3];
|
| 196 |
+
pid_out[2] = '\0';
|
| 197 |
+
return true;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
// Also support "01 XX" already normalized to "01XX"
|
| 201 |
+
if (len == 4 && normalized_cmd[0] == '0' && normalized_cmd[1] == '1') {
|
| 202 |
+
pid_out[0] = normalized_cmd[2];
|
| 203 |
+
pid_out[1] = normalized_cmd[3];
|
| 204 |
+
pid_out[2] = '\0';
|
| 205 |
+
return true;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
return false;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
/**
|
| 212 |
+
* @brief Generate dynamic RPM response based on vehicle state
|
| 213 |
+
*/
|
| 214 |
+
static int generate_rpm_response(char *response, int max_len)
|
| 215 |
+
{
|
| 216 |
+
// Response format: "41 0C XX YY"
|
| 217 |
+
// RPM = ((XX * 256) + YY) / 4
|
| 218 |
+
uint16_t rpm_value = vehicle_state.rpm;
|
| 219 |
+
uint16_t encoded = rpm_value * 4; // Encode for OBD
|
| 220 |
+
|
| 221 |
+
return snprintf(response, max_len, "41 0C %02X %02X",
|
| 222 |
+
(encoded >> 8) & 0xFF, encoded & 0xFF);
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
/**
|
| 226 |
+
* @brief Generate dynamic speed response based on vehicle state
|
| 227 |
+
*/
|
| 228 |
+
static int generate_speed_response(char *response, int max_len)
|
| 229 |
+
{
|
| 230 |
+
// Response format: "41 0D XX"
|
| 231 |
+
// Speed in km/h = XX
|
| 232 |
+
return snprintf(response, max_len, "41 0D %02X", vehicle_state.speed);
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
/**
|
| 236 |
+
* @brief Generate dynamic coolant temperature response
|
| 237 |
+
*/
|
| 238 |
+
static int generate_coolant_temp_response(char *response, int max_len)
|
| 239 |
+
{
|
| 240 |
+
// Response format: "41 05 XX"
|
| 241 |
+
// Temperature = XX - 40 (in °C)
|
| 242 |
+
uint8_t encoded = (uint8_t)(vehicle_state.coolant_temp + 40);
|
| 243 |
+
return snprintf(response, max_len, "41 05 %02X", encoded);
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
/**
|
| 247 |
+
* @brief Generate dynamic engine load response
|
| 248 |
+
*/
|
| 249 |
+
static int generate_engine_load_response(char *response, int max_len)
|
| 250 |
+
{
|
| 251 |
+
// Response format: "41 04 XX"
|
| 252 |
+
// Load = XX * 100 / 255 (percentage)
|
| 253 |
+
// Estimate load from RPM and throttle
|
| 254 |
+
uint8_t load = 0;
|
| 255 |
+
|
| 256 |
+
if (vehicle_state.engine_running) {
|
| 257 |
+
// Simple estimation: base on RPM and throttle
|
| 258 |
+
load = (vehicle_state.throttle_pos * 2 + vehicle_state.rpm / 100) / 3;
|
| 259 |
+
if (load > 100)
|
| 260 |
+
load = 100;
|
| 261 |
+
|
| 262 |
+
// Encode: percentage to 0-255 scale
|
| 263 |
+
load = (load * 255) / 100;
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
return snprintf(response, max_len, "41 04 %02X", load);
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
/**
|
| 270 |
+
* @brief Generate dynamic MAF response
|
| 271 |
+
*/
|
| 272 |
+
static int generate_maf_response(char *response, int max_len)
|
| 273 |
+
{
|
| 274 |
+
// Response format: "41 10 XX YY"
|
| 275 |
+
// MAF rate (grams/sec) = ((XX * 256) + YY) / 100
|
| 276 |
+
uint16_t maf = vehicle_state.maf_rate;
|
| 277 |
+
|
| 278 |
+
return snprintf(response, max_len, "41 10 %02X %02X",
|
| 279 |
+
(maf >> 8) & 0xFF, maf & 0xFF);
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
/**
|
| 283 |
+
* @brief Generate dynamic throttle position response
|
| 284 |
+
*/
|
| 285 |
+
static int generate_throttle_response(char *response, int max_len)
|
| 286 |
+
{
|
| 287 |
+
// Response format: "41 11 XX"
|
| 288 |
+
// Throttle position = XX * 100 / 255 (percentage)
|
| 289 |
+
uint8_t encoded = (vehicle_state.throttle_pos * 255) / 100;
|
| 290 |
+
return snprintf(response, max_len, "41 11 %02X", encoded);
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
/**
|
| 294 |
+
* @brief Generate dynamic fuel level response
|
| 295 |
+
*/
|
| 296 |
+
static int generate_fuel_level_response(char *response, int max_len)
|
| 297 |
+
{
|
| 298 |
+
// Response format: "41 2F XX"
|
| 299 |
+
// Fuel level = XX * 100 / 255 (percentage)
|
| 300 |
+
uint8_t encoded = (vehicle_state.fuel_level * 255) / 100;
|
| 301 |
+
return snprintf(response, max_len, "41 2F %02X", encoded);
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
/**
|
| 305 |
+
* @brief Generate Mode 09 PID 00 response (supported PIDs)
|
| 306 |
+
*/
|
| 307 |
+
static int generate_mode09_supported_pids(char *response, int max_len)
|
| 308 |
+
{
|
| 309 |
+
// PIDs 02, 04, 0A supported
|
| 310 |
+
// Bit 5 (02), Bit 3 (04), Bit 13 (0A)
|
| 311 |
+
return snprintf(response, max_len, "49 00 54 40 00 00");
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
/**
|
| 315 |
+
* @brief Generate Mode 09 PID 02 response (VIN)
|
| 316 |
+
*
|
| 317 |
+
* VIN is 17 characters, response format:
|
| 318 |
+
* 49 02 01 [ASCII bytes for VIN]
|
| 319 |
+
*
|
| 320 |
+
* The response includes:
|
| 321 |
+
* - 49: Mode 09 response
|
| 322 |
+
* - 02: PID 02 (VIN)
|
| 323 |
+
* - 01: Number of data items (always 1 for VIN)
|
| 324 |
+
* - Next bytes: ASCII values of VIN characters
|
| 325 |
+
*/
|
| 326 |
+
static int generate_vin_response(char *response, int max_len)
|
| 327 |
+
{
|
| 328 |
+
const char *vin = SIMULATED_VIN;
|
| 329 |
+
int vin_len = strlen(vin);
|
| 330 |
+
char *ptr = response;
|
| 331 |
+
int remaining = max_len;
|
| 332 |
+
int written;
|
| 333 |
+
|
| 334 |
+
// Start with "49 02 01"
|
| 335 |
+
written = snprintf(ptr, remaining, "49 02 01");
|
| 336 |
+
if (written >= remaining) return written;
|
| 337 |
+
ptr += written;
|
| 338 |
+
remaining -= written;
|
| 339 |
+
|
| 340 |
+
// Add each VIN character as hex ASCII value
|
| 341 |
+
for (int i = 0; i < vin_len && remaining > 0; i++) {
|
| 342 |
+
written = snprintf(ptr, remaining, " %02X", (uint8_t)vin[i]);
|
| 343 |
+
if (written >= remaining) break;
|
| 344 |
+
ptr += written;
|
| 345 |
+
remaining -= written;
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
return (int)(ptr - response);
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
/**
|
| 352 |
+
* @brief Generate Mode 09 PID 04 response (Calibration ID)
|
| 353 |
+
*/
|
| 354 |
+
static int generate_calibration_id_response(char *response, int max_len)
|
| 355 |
+
{
|
| 356 |
+
const char *cal_id = SIMULATED_CAL_ID;
|
| 357 |
+
int cal_len = strlen(cal_id);
|
| 358 |
+
char *ptr = response;
|
| 359 |
+
int remaining = max_len;
|
| 360 |
+
int written;
|
| 361 |
+
|
| 362 |
+
// Start with "49 04 01"
|
| 363 |
+
written = snprintf(ptr, remaining, "49 04 01");
|
| 364 |
+
if (written >= remaining) return written;
|
| 365 |
+
ptr += written;
|
| 366 |
+
remaining -= written;
|
| 367 |
+
|
| 368 |
+
// Add each character as hex ASCII value
|
| 369 |
+
for (int i = 0; i < cal_len && remaining > 0; i++) {
|
| 370 |
+
written = snprintf(ptr, remaining, " %02X", (uint8_t)cal_id[i]);
|
| 371 |
+
if (written >= remaining) break;
|
| 372 |
+
ptr += written;
|
| 373 |
+
remaining -= written;
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
return (int)(ptr - response);
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
/**
|
| 380 |
+
* @brief Generate Mode 09 PID 0A response (ECU Name)
|
| 381 |
+
*/
|
| 382 |
+
static int generate_ecu_name_response(char *response, int max_len)
|
| 383 |
+
{
|
| 384 |
+
const char *ecu_name = SIMULATED_ECU_NAME;
|
| 385 |
+
int ecu_len = strlen(ecu_name);
|
| 386 |
+
char *ptr = response;
|
| 387 |
+
int remaining = max_len;
|
| 388 |
+
int written;
|
| 389 |
+
|
| 390 |
+
// Start with "49 0A 01"
|
| 391 |
+
written = snprintf(ptr, remaining, "49 0A 01");
|
| 392 |
+
if (written >= remaining) return written;
|
| 393 |
+
ptr += written;
|
| 394 |
+
remaining -= written;
|
| 395 |
+
|
| 396 |
+
// Add each character as hex ASCII value
|
| 397 |
+
for (int i = 0; i < ecu_len && remaining > 0; i++) {
|
| 398 |
+
written = snprintf(ptr, remaining, " %02X", (uint8_t)ecu_name[i]);
|
| 399 |
+
if (written >= remaining) break;
|
| 400 |
+
ptr += written;
|
| 401 |
+
remaining -= written;
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
return (int)(ptr - response);
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
/******************************************************************************
|
| 408 |
+
* Public API Implementation
|
| 409 |
+
*****************************************************************************/
|
| 410 |
+
|
| 411 |
+
void elm327_sim_init(void)
|
| 412 |
+
{
|
| 413 |
+
wm_log_info("Initializing ELM327 simulator");
|
| 414 |
+
|
| 415 |
+
// Initialize vehicle state to realistic idle values
|
| 416 |
+
memset(&vehicle_state, 0, sizeof(vehicle_state));
|
| 417 |
+
|
| 418 |
+
vehicle_state.engine_running = true; // Engine on for simulation
|
| 419 |
+
vehicle_state.rpm = 800; // Idle RPM
|
| 420 |
+
vehicle_state.speed = 0;
|
| 421 |
+
vehicle_state.coolant_temp = 20; // 20°C ambient
|
| 422 |
+
vehicle_state.throttle_pos = 0;
|
| 423 |
+
vehicle_state.maf_rate = 250; // 2.5 g/s idle
|
| 424 |
+
vehicle_state.fuel_level = 75; // 75% fuel
|
| 425 |
+
vehicle_state.update_counter = 0;
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
void elm327_sim_reset_state(void)
|
| 429 |
+
{
|
| 430 |
+
elm327_sim_init();
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
void elm327_sim_set_engine_state(bool running)
|
| 434 |
+
{
|
| 435 |
+
vehicle_state.engine_running = running;
|
| 436 |
+
|
| 437 |
+
if (running) {
|
| 438 |
+
vehicle_state.rpm = 800; // Idle
|
| 439 |
+
vehicle_state.coolant_temp = 20;
|
| 440 |
+
wm_log_info("Simulator: Engine started");
|
| 441 |
+
} else {
|
| 442 |
+
vehicle_state.rpm = 0;
|
| 443 |
+
vehicle_state.speed = 0;
|
| 444 |
+
vehicle_state.throttle_pos = 0;
|
| 445 |
+
wm_log_info("Simulator: Engine stopped");
|
| 446 |
+
}
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
void elm327_sim_update_state(void)
|
| 450 |
+
{
|
| 451 |
+
vehicle_state.update_counter++;
|
| 452 |
+
|
| 453 |
+
if (!vehicle_state.engine_running)
|
| 454 |
+
return;
|
| 455 |
+
|
| 456 |
+
// Simulate realistic variations over time
|
| 457 |
+
// Add small random-like variations based on counter
|
| 458 |
+
|
| 459 |
+
// RPM varies slightly at idle
|
| 460 |
+
vehicle_state.rpm = 800 + ((vehicle_state.update_counter * 7) % 100) - 50;
|
| 461 |
+
|
| 462 |
+
// Coolant temp gradually increases when engine running
|
| 463 |
+
if (vehicle_state.coolant_temp < 90) {
|
| 464 |
+
if (vehicle_state.update_counter % 10 == 0)
|
| 465 |
+
vehicle_state.coolant_temp++;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
// Simulate some throttle/speed variation (very simple)
|
| 469 |
+
if (vehicle_state.update_counter % 50 == 0) {
|
| 470 |
+
vehicle_state.throttle_pos = (vehicle_state.update_counter / 5) % 30;
|
| 471 |
+
vehicle_state.speed = vehicle_state.throttle_pos * 2;
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
// MAF follows throttle roughly
|
| 475 |
+
vehicle_state.maf_rate = 250 + (vehicle_state.throttle_pos * 5);
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
int elm327_sim_generate_response(const char *cmd, char *response, int max_len)
|
| 479 |
+
{
|
| 480 |
+
char normalized[32];
|
| 481 |
+
char pid[4];
|
| 482 |
+
int i;
|
| 483 |
+
|
| 484 |
+
if (!cmd || !response || max_len <= 0)
|
| 485 |
+
return -1;
|
| 486 |
+
|
| 487 |
+
// Normalize command (remove spaces, uppercase)
|
| 488 |
+
normalize_command(cmd, normalized, sizeof(normalized));
|
| 489 |
+
|
| 490 |
+
// Check for Mode 03 (Read DTCs)
|
| 491 |
+
if (strcmp(normalized, "03") == 0) {
|
| 492 |
+
// Return 3 simulated DTCs:
|
| 493 |
+
// P0171 - System Too Lean (Bank 1)
|
| 494 |
+
// P0300 - Random/Multiple Cylinder Misfire Detected
|
| 495 |
+
// P0442 - EVAP System Leak Detected (small leak)
|
| 496 |
+
snprintf(response, max_len, "43 01 71 03 00 04 42");
|
| 497 |
+
return strlen(response);
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
// Check for Mode 07 (Read pending DTCs)
|
| 501 |
+
if (strcmp(normalized, "07") == 0) {
|
| 502 |
+
// Return 1 pending DTC:
|
| 503 |
+
// P0131 - O2 Sensor Circuit Low Voltage (Bank 1, Sensor 1)
|
| 504 |
+
snprintf(response, max_len, "47 01 01 31");
|
| 505 |
+
return strlen(response);
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
// Check for Mode 04 (Clear DTCs)
|
| 509 |
+
if (strcmp(normalized, "04") == 0) {
|
| 510 |
+
snprintf(response, max_len, "44");
|
| 511 |
+
return strlen(response);
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
// Check for Mode 09 (Vehicle Information)
|
| 515 |
+
if (strlen(normalized) >= 4 && normalized[0] == '0' && normalized[1] == '9') {
|
| 516 |
+
// Extract PID from Mode 09 command
|
| 517 |
+
char mode09_pid[4];
|
| 518 |
+
mode09_pid[0] = normalized[2];
|
| 519 |
+
mode09_pid[1] = normalized[3];
|
| 520 |
+
mode09_pid[2] = '\0';
|
| 521 |
+
|
| 522 |
+
if (strcmp(mode09_pid, "00") == 0) {
|
| 523 |
+
// Supported PIDs
|
| 524 |
+
return generate_mode09_supported_pids(response, max_len);
|
| 525 |
+
} else if (strcmp(mode09_pid, "02") == 0) {
|
| 526 |
+
// VIN
|
| 527 |
+
return generate_vin_response(response, max_len);
|
| 528 |
+
} else if (strcmp(mode09_pid, "04") == 0) {
|
| 529 |
+
// Calibration ID
|
| 530 |
+
return generate_calibration_id_response(response, max_len);
|
| 531 |
+
} else if (strcmp(mode09_pid, "0A") == 0) {
|
| 532 |
+
// ECU Name
|
| 533 |
+
return generate_ecu_name_response(response, max_len);
|
| 534 |
+
} else {
|
| 535 |
+
// Unsupported Mode 09 PID
|
| 536 |
+
snprintf(response, max_len, "NO DATA");
|
| 537 |
+
return strlen(response);
|
| 538 |
+
}
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
// Extract PID for Mode 01
|
| 542 |
+
if (!extract_pid(normalized, pid)) {
|
| 543 |
+
// Unsupported command format
|
| 544 |
+
snprintf(response, max_len, "NO DATA");
|
| 545 |
+
return strlen(response);
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
// Search PID table
|
| 549 |
+
for (i = 0; i < NUM_MODE01_PIDS; i++) {
|
| 550 |
+
if (strcmp(mode01_pid_table[i].pid, pid) == 0) {
|
| 551 |
+
// Found matching PID
|
| 552 |
+
if (mode01_pid_table[i].is_dynamic) {
|
| 553 |
+
// Generate dynamic response
|
| 554 |
+
int len = 0;
|
| 555 |
+
|
| 556 |
+
if (strcmp(pid, "0C") == 0) {
|
| 557 |
+
len = generate_rpm_response(response, max_len);
|
| 558 |
+
} else if (strcmp(pid, "0D") == 0) {
|
| 559 |
+
len = generate_speed_response(response, max_len);
|
| 560 |
+
} else if (strcmp(pid, "05") == 0) {
|
| 561 |
+
len = generate_coolant_temp_response(response, max_len);
|
| 562 |
+
} else if (strcmp(pid, "04") == 0) {
|
| 563 |
+
len = generate_engine_load_response(response, max_len);
|
| 564 |
+
} else if (strcmp(pid, "10") == 0) {
|
| 565 |
+
len = generate_maf_response(response, max_len);
|
| 566 |
+
} else if (strcmp(pid, "11") == 0) {
|
| 567 |
+
len = generate_throttle_response(response, max_len);
|
| 568 |
+
} else if (strcmp(pid, "2F") == 0) {
|
| 569 |
+
len = generate_fuel_level_response(response, max_len);
|
| 570 |
+
} else {
|
| 571 |
+
// Unknown dynamic PID - shouldn't happen
|
| 572 |
+
snprintf(response, max_len, "NO DATA");
|
| 573 |
+
len = strlen(response);
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
return len;
|
| 577 |
+
} else {
|
| 578 |
+
// Return static response
|
| 579 |
+
snprintf(response, max_len, "%s", mode01_pid_table[i].response);
|
| 580 |
+
return strlen(response);
|
| 581 |
+
}
|
| 582 |
+
}
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
// PID not found in table - return NO DATA
|
| 586 |
+
snprintf(response, max_len, "NO DATA");
|
| 587 |
+
return strlen(response);
|
| 588 |
+
}
|
| 589 |
+
|
MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327_sim.h
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file elm327_sim.h
|
| 3 |
+
*
|
| 4 |
+
* @brief ELM327 OBD-II Simulator - Internal API
|
| 5 |
+
*
|
| 6 |
+
* @author castlebbs
|
| 7 |
+
*
|
| 8 |
+
* This module provides simulated OBD-II responses for testing without a vehicle.
|
| 9 |
+
* AT commands are always passed through to real hardware.
|
| 10 |
+
*
|
| 11 |
+
* @note This is an internal header, not exposed in public API
|
| 12 |
+
*/
|
| 13 |
+
#ifndef ELM327_SIM_H
|
| 14 |
+
#define ELM327_SIM_H
|
| 15 |
+
|
| 16 |
+
#include "wm_type_def.h"
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* @brief Initialize the ELM327 simulator
|
| 20 |
+
*
|
| 21 |
+
* @retval None
|
| 22 |
+
*
|
| 23 |
+
* @note Sets up initial vehicle state and internal data structures
|
| 24 |
+
*/
|
| 25 |
+
void elm327_sim_init(void);
|
| 26 |
+
|
| 27 |
+
/**
|
| 28 |
+
* @brief Generate simulated OBD-II response for a command
|
| 29 |
+
*
|
| 30 |
+
* @param[in] cmd OBD-II command string (e.g., "01 0C")
|
| 31 |
+
* @param[out] response Buffer to store simulated response
|
| 32 |
+
* @param[in] max_len Maximum length of response buffer
|
| 33 |
+
*
|
| 34 |
+
* @retval >0 Number of bytes in response (success)
|
| 35 |
+
* @retval 0 Empty response
|
| 36 |
+
* @retval -1 Command not supported (returns "NO DATA")
|
| 37 |
+
*
|
| 38 |
+
* @note Simulates realistic responses with some dynamic behavior
|
| 39 |
+
* @note Common PIDs (RPM, speed, temp) have dynamic values
|
| 40 |
+
*/
|
| 41 |
+
int elm327_sim_generate_response(const char *cmd, char *response, int max_len);
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* @brief Update vehicle state for dynamic simulation
|
| 45 |
+
*
|
| 46 |
+
* @retval None
|
| 47 |
+
*
|
| 48 |
+
* @note Call periodically to simulate realistic vehicle behavior
|
| 49 |
+
* @note Updates RPM, speed, temperature, etc. over time
|
| 50 |
+
*/
|
| 51 |
+
void elm327_sim_update_state(void);
|
| 52 |
+
|
| 53 |
+
/**
|
| 54 |
+
* @brief Set vehicle engine state
|
| 55 |
+
*
|
| 56 |
+
* @param[in] running true = engine on, false = engine off
|
| 57 |
+
*
|
| 58 |
+
* @retval None
|
| 59 |
+
*/
|
| 60 |
+
void elm327_sim_set_engine_state(bool running);
|
| 61 |
+
|
| 62 |
+
/**
|
| 63 |
+
* @brief Reset vehicle state to defaults
|
| 64 |
+
*
|
| 65 |
+
* @retval None
|
| 66 |
+
*/
|
| 67 |
+
void elm327_sim_reset_state(void);
|
| 68 |
+
|
| 69 |
+
#endif // ELM327_SIM_H
|
MCP_servers/W600-embedded-OBD2/Src/App/mcp/Makefile
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#/**************************************************************************
|
| 2 |
+
# * MCP Server Module Makefile *
|
| 3 |
+
# **************************************************************************/
|
| 4 |
+
|
| 5 |
+
#---------------------------------------------------------------------------
|
| 6 |
+
# Constant Variable definition
|
| 7 |
+
#---------------------------------------------------------------------------
|
| 8 |
+
|
| 9 |
+
ifeq ($(COMPILER_OS_CYGWIN),1)
|
| 10 |
+
TOPDIR=../../..
|
| 11 |
+
endif
|
| 12 |
+
|
| 13 |
+
include $(TOPDIR)/Tools/toolchain.def
|
| 14 |
+
|
| 15 |
+
#---------------------------------------------------------------------------
|
| 16 |
+
# Target definition (User)
|
| 17 |
+
#---------------------------------------------------------------------------
|
| 18 |
+
GOAL = $(LIB_DIR)/wmmcp.$(LIBTYPE)
|
| 19 |
+
|
| 20 |
+
#---------------------------------------------------------------------------
|
| 21 |
+
# Source section (User)
|
| 22 |
+
#---------------------------------------------------------------------------
|
| 23 |
+
ASM_SRC +=
|
| 24 |
+
C_SRC += mcp_server.c
|
| 25 |
+
C_SRC += mcp_stream.c
|
| 26 |
+
C_SRC += mcp_http.c
|
| 27 |
+
C_SRC += mcp_jsonrpc.c
|
| 28 |
+
C_SRC += mcp_methods.c
|
| 29 |
+
C_SRC += mcp_tools.c
|
| 30 |
+
|
| 31 |
+
#---------------------------------------------------------------------------
|
| 32 |
+
# Implicit rules
|
| 33 |
+
#---------------------------------------------------------------------------
|
| 34 |
+
.c.o:
|
| 35 |
+
$(CC) $(CFLAGS) -c -o $*.o $< $(INCLUDES)
|
| 36 |
+
|
| 37 |
+
.s.o:
|
| 38 |
+
$(ASM) $(ASMFLAGS) -c -o $*.o $< $(INCLUDES)
|
| 39 |
+
|
| 40 |
+
#---------------------------------------------------------------------------
|
| 41 |
+
# Explicit rules
|
| 42 |
+
#---------------------------------------------------------------------------
|
| 43 |
+
OBJ_FILES = $(C_SRC:%.c=%.o) $(ASM_SRC:%.s=%.o)
|
| 44 |
+
|
| 45 |
+
all: $(GOAL)
|
| 46 |
+
|
| 47 |
+
$(GOAL): $(OBJ_FILES)
|
| 48 |
+
$(AR) $(ARFLAGS) $@ $(OBJ_FILES)
|
| 49 |
+
|
| 50 |
+
.PHONY: clean
|
| 51 |
+
clean:
|
| 52 |
+
$(RM) -f $(GOAL)
|
| 53 |
+
$(RM) -f $(OBJ_FILES:.o=.d) $(OBJ_FILES)
|
| 54 |
+
|
MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_http.c
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_http.c
|
| 3 |
+
*
|
| 4 |
+
* @brief HTTP Protocol Handling for MCP Server
|
| 5 |
+
*
|
| 6 |
+
* @author castlebbs
|
| 7 |
+
*/
|
| 8 |
+
|
| 9 |
+
#define LOG_TAG "MCP/HTTP"
|
| 10 |
+
|
| 11 |
+
#include <string.h>
|
| 12 |
+
#include <stdio.h>
|
| 13 |
+
#include "mcp_http.h"
|
| 14 |
+
#include "wm_log.h"
|
| 15 |
+
|
| 16 |
+
/**
|
| 17 |
+
* Find line ending in buffer
|
| 18 |
+
*/
|
| 19 |
+
static const char* find_line_end(const char *start, const char *end)
|
| 20 |
+
{
|
| 21 |
+
while (start < end - 1) {
|
| 22 |
+
if (start[0] == '\r' && start[1] == '\n') {
|
| 23 |
+
return start;
|
| 24 |
+
}
|
| 25 |
+
start++;
|
| 26 |
+
}
|
| 27 |
+
return NULL;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/**
|
| 31 |
+
* Find header value in buffer
|
| 32 |
+
*/
|
| 33 |
+
static int find_header_value(const char *buffer, u32 len, const char *header_name,
|
| 34 |
+
char *value, u32 value_size)
|
| 35 |
+
{
|
| 36 |
+
const char *end = buffer + len;
|
| 37 |
+
const char *line_start = buffer;
|
| 38 |
+
const char *line_end;
|
| 39 |
+
u32 header_name_len = strlen(header_name);
|
| 40 |
+
|
| 41 |
+
// Skip request line
|
| 42 |
+
line_end = find_line_end(line_start, end);
|
| 43 |
+
if (!line_end) return WM_FAILED;
|
| 44 |
+
line_start = line_end + 2;
|
| 45 |
+
|
| 46 |
+
// Search through headers
|
| 47 |
+
while (line_start < end) {
|
| 48 |
+
line_end = find_line_end(line_start, end);
|
| 49 |
+
if (!line_end) break;
|
| 50 |
+
|
| 51 |
+
// Empty line marks end of headers
|
| 52 |
+
if (line_end == line_start) break;
|
| 53 |
+
|
| 54 |
+
// Check if this line starts with our header name
|
| 55 |
+
if (line_end - line_start > header_name_len) {
|
| 56 |
+
if (strncasecmp(line_start, header_name, header_name_len) == 0) {
|
| 57 |
+
const char *colon = line_start + header_name_len;
|
| 58 |
+
if (*colon == ':') {
|
| 59 |
+
// Skip colon and whitespace
|
| 60 |
+
colon++;
|
| 61 |
+
while (colon < line_end && (*colon == ' ' || *colon == '\t')) {
|
| 62 |
+
colon++;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
// Copy value
|
| 66 |
+
u32 value_len = line_end - colon;
|
| 67 |
+
if (value_size == 0) {
|
| 68 |
+
return WM_FAILED;
|
| 69 |
+
}
|
| 70 |
+
if (value_len >= value_size) {
|
| 71 |
+
value_len = value_size - 1;
|
| 72 |
+
}
|
| 73 |
+
memcpy(value, colon, value_len);
|
| 74 |
+
value[value_len] = '\0';
|
| 75 |
+
return WM_SUCCESS;
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
line_start = line_end + 2;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
value[0] = '\0';
|
| 84 |
+
return WM_FAILED;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
/**
|
| 88 |
+
* Parse Content-Length header from buffer
|
| 89 |
+
*/
|
| 90 |
+
int mcp_http_get_content_length(const char *buffer, u32 len, u32 *content_length)
|
| 91 |
+
{
|
| 92 |
+
char value[32];
|
| 93 |
+
int ret;
|
| 94 |
+
|
| 95 |
+
if (!buffer || !content_length) {
|
| 96 |
+
return WM_FAILED;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
ret = find_header_value(buffer, len, "Content-Length", value, sizeof(value));
|
| 100 |
+
if (ret != WM_SUCCESS) {
|
| 101 |
+
*content_length = 0;
|
| 102 |
+
return WM_FAILED;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// Parse the numeric value with overflow protection
|
| 106 |
+
*content_length = 0;
|
| 107 |
+
const char *p = value;
|
| 108 |
+
while (*p >= '0' && *p <= '9') {
|
| 109 |
+
u32 digit = *p - '0';
|
| 110 |
+
|
| 111 |
+
// Check for overflow before multiplying
|
| 112 |
+
if (*content_length > (0xFFFFFFFF / 10)) {
|
| 113 |
+
// Would overflow
|
| 114 |
+
*content_length = 0xFFFFFFFF;
|
| 115 |
+
return WM_FAILED;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
u32 temp = *content_length * 10;
|
| 119 |
+
|
| 120 |
+
// Check for overflow before adding
|
| 121 |
+
if (temp > (0xFFFFFFFF - digit)) {
|
| 122 |
+
// Would overflow
|
| 123 |
+
*content_length = 0xFFFFFFFF;
|
| 124 |
+
return WM_FAILED;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
*content_length = temp + digit;
|
| 128 |
+
p++;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
return WM_SUCCESS;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
int mcp_http_parse_request(const char *buffer, u32 len, mcp_http_request_t *request)
|
| 135 |
+
{
|
| 136 |
+
const char *end = buffer + len;
|
| 137 |
+
const char *line_end;
|
| 138 |
+
const char *space1, *space2;
|
| 139 |
+
|
| 140 |
+
if (!buffer || !request || len < 10) {
|
| 141 |
+
return WM_FAILED;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
memset(request, 0, sizeof(mcp_http_request_t));
|
| 145 |
+
|
| 146 |
+
// Parse request line: METHOD PATH HTTP/1.x
|
| 147 |
+
line_end = find_line_end(buffer, end);
|
| 148 |
+
if (!line_end) {
|
| 149 |
+
return WM_FAILED;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
// Find first space (after method)
|
| 153 |
+
space1 = buffer;
|
| 154 |
+
while (space1 < line_end && *space1 != ' ') space1++;
|
| 155 |
+
if (space1 >= line_end) return WM_FAILED;
|
| 156 |
+
|
| 157 |
+
// Copy method
|
| 158 |
+
u32 method_len = space1 - buffer;
|
| 159 |
+
if (method_len >= sizeof(request->method)) {
|
| 160 |
+
method_len = sizeof(request->method) - 1;
|
| 161 |
+
}
|
| 162 |
+
memcpy(request->method, buffer, method_len);
|
| 163 |
+
request->method[method_len] = '\0';
|
| 164 |
+
|
| 165 |
+
// Find second space (after path)
|
| 166 |
+
space2 = space1 + 1;
|
| 167 |
+
while (space2 < line_end && *space2 != ' ') space2++;
|
| 168 |
+
if (space2 >= line_end) return WM_FAILED;
|
| 169 |
+
|
| 170 |
+
// Copy path
|
| 171 |
+
u32 path_len = space2 - (space1 + 1);
|
| 172 |
+
if (path_len >= sizeof(request->path)) {
|
| 173 |
+
path_len = sizeof(request->path) - 1;
|
| 174 |
+
}
|
| 175 |
+
memcpy(request->path, space1 + 1, path_len);
|
| 176 |
+
request->path[path_len] = '\0';
|
| 177 |
+
|
| 178 |
+
// Parse headers
|
| 179 |
+
find_header_value(buffer, len, "Accept", request->accept, sizeof(request->accept));
|
| 180 |
+
find_header_value(buffer, len, "MCP-Protocol-Version", request->protocol_version,
|
| 181 |
+
sizeof(request->protocol_version));
|
| 182 |
+
|
| 183 |
+
// Find body (after double CRLF)
|
| 184 |
+
const char *body_start = buffer;
|
| 185 |
+
while (body_start < end - 3) {
|
| 186 |
+
if (body_start[0] == '\r' && body_start[1] == '\n' &&
|
| 187 |
+
body_start[2] == '\r' && body_start[3] == '\n') {
|
| 188 |
+
body_start += 4;
|
| 189 |
+
request->body = (char*)body_start;
|
| 190 |
+
request->body_len = end - body_start;
|
| 191 |
+
break;
|
| 192 |
+
}
|
| 193 |
+
body_start++;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
return WM_SUCCESS;
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
const char* mcp_http_get_status_text(int status_code)
|
| 200 |
+
{
|
| 201 |
+
switch (status_code) {
|
| 202 |
+
case 200: return "OK";
|
| 203 |
+
case 202: return "Accepted";
|
| 204 |
+
case 400: return "Bad Request";
|
| 205 |
+
case 404: return "Not Found";
|
| 206 |
+
case 405: return "Method Not Allowed";
|
| 207 |
+
case 413: return "Payload Too Large";
|
| 208 |
+
case 500: return "Internal Server Error";
|
| 209 |
+
default: return "Unknown";
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
int mcp_http_build_response(const mcp_http_response_t *response, char *buffer, u32 buffer_size)
|
| 214 |
+
{
|
| 215 |
+
int written = 0;
|
| 216 |
+
int ret;
|
| 217 |
+
|
| 218 |
+
if (!response || !buffer || buffer_size < 128) {
|
| 219 |
+
return -1;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
// Status line
|
| 223 |
+
ret = snprintf(buffer + written, buffer_size - written,
|
| 224 |
+
"HTTP/1.1 %d %s\r\n",
|
| 225 |
+
response->status_code,
|
| 226 |
+
response->status_text ? response->status_text :
|
| 227 |
+
mcp_http_get_status_text(response->status_code));
|
| 228 |
+
if (ret < 0 || ret >= buffer_size - written) return -1;
|
| 229 |
+
written += ret;
|
| 230 |
+
|
| 231 |
+
// Content-Type header (if provided)
|
| 232 |
+
if (response->content_type && response->content_type[0]) {
|
| 233 |
+
ret = snprintf(buffer + written, buffer_size - written,
|
| 234 |
+
"Content-Type: %s\r\n", response->content_type);
|
| 235 |
+
if (ret < 0 || ret >= buffer_size - written) return -1;
|
| 236 |
+
written += ret;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
// Content-Length header
|
| 240 |
+
ret = snprintf(buffer + written, buffer_size - written,
|
| 241 |
+
"Content-Length: %u\r\n", response->body_len);
|
| 242 |
+
if (ret < 0 || ret >= buffer_size - written) return -1;
|
| 243 |
+
written += ret;
|
| 244 |
+
|
| 245 |
+
// Connection: close
|
| 246 |
+
ret = snprintf(buffer + written, buffer_size - written,
|
| 247 |
+
"Connection: close\r\n");
|
| 248 |
+
if (ret < 0 || ret >= buffer_size - written) return -1;
|
| 249 |
+
written += ret;
|
| 250 |
+
|
| 251 |
+
// For 405, add Allow header
|
| 252 |
+
if (response->status_code == 405) {
|
| 253 |
+
ret = snprintf(buffer + written, buffer_size - written,
|
| 254 |
+
"Allow: POST\r\n");
|
| 255 |
+
if (ret < 0 || ret >= buffer_size - written) return -1;
|
| 256 |
+
written += ret;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
// End of headers
|
| 260 |
+
ret = snprintf(buffer + written, buffer_size - written, "\r\n");
|
| 261 |
+
if (ret < 0 || ret >= buffer_size - written) return -1;
|
| 262 |
+
written += ret;
|
| 263 |
+
|
| 264 |
+
// Body
|
| 265 |
+
if (response->body && response->body_len > 0) {
|
| 266 |
+
if (written + response->body_len >= buffer_size) {
|
| 267 |
+
return -1;
|
| 268 |
+
}
|
| 269 |
+
memcpy(buffer + written, response->body, response->body_len);
|
| 270 |
+
written += response->body_len;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
return written;
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
int mcp_http_validate_headers(const mcp_http_request_t *request)
|
| 277 |
+
{
|
| 278 |
+
// Check for Accept header containing application/json
|
| 279 |
+
if (!request->accept[0]) {
|
| 280 |
+
wm_log_warn("Validation failed: missing Accept header");
|
| 281 |
+
return 0;
|
| 282 |
+
}
|
| 283 |
+
if (strstr(request->accept, "application/json") == NULL) {
|
| 284 |
+
wm_log_warn("Validation failed: Accept header does not contain application/json");
|
| 285 |
+
return 0;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
// Check for MCP-Protocol-Version header
|
| 289 |
+
|
| 290 |
+
return 1;
|
| 291 |
+
}
|
MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_jsonrpc.c
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_jsonrpc.c
|
| 3 |
+
*
|
| 4 |
+
* @brief JSON-RPC 2.0 Message Handling
|
| 5 |
+
*
|
| 6 |
+
* @author castlebbs
|
| 7 |
+
*/
|
| 8 |
+
|
| 9 |
+
#define LOG_TAG "MCP/JSON"
|
| 10 |
+
|
| 11 |
+
#include <string.h>
|
| 12 |
+
#include <stdlib.h>
|
| 13 |
+
#include "mcp_jsonrpc.h"
|
| 14 |
+
#include "wm_mem.h"
|
| 15 |
+
#include "wm_log.h"
|
| 16 |
+
|
| 17 |
+
int mcp_jsonrpc_parse(const char *json_str, mcp_jsonrpc_request_t *request)
|
| 18 |
+
{
|
| 19 |
+
cJSON *root = NULL;
|
| 20 |
+
cJSON *jsonrpc = NULL;
|
| 21 |
+
cJSON *method = NULL;
|
| 22 |
+
cJSON *params = NULL;
|
| 23 |
+
cJSON *id = NULL;
|
| 24 |
+
|
| 25 |
+
if (!json_str || !request) {
|
| 26 |
+
return WM_FAILED;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
memset(request, 0, sizeof(mcp_jsonrpc_request_t));
|
| 30 |
+
|
| 31 |
+
// Parse JSON
|
| 32 |
+
root = cJSON_Parse(json_str);
|
| 33 |
+
if (!root) {
|
| 34 |
+
wm_log_error("Failed to parse JSON string");
|
| 35 |
+
// log the original string for debugging
|
| 36 |
+
wm_log_error("Original JSON: %s", json_str);
|
| 37 |
+
wm_log_error("Original request: %s", json_str);
|
| 38 |
+
return WM_FAILED;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
// Check jsonrpc version
|
| 42 |
+
jsonrpc = cJSON_GetObjectItem(root, "jsonrpc");
|
| 43 |
+
if (!jsonrpc || jsonrpc->type != cJSON_String ||
|
| 44 |
+
strcmp(jsonrpc->valuestring, "2.0") != 0) {
|
| 45 |
+
wm_log_error("Invalid or missing jsonrpc version");
|
| 46 |
+
cJSON_Delete(root);
|
| 47 |
+
return WM_FAILED;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
// Get method (required)
|
| 51 |
+
method = cJSON_GetObjectItem(root, "method");
|
| 52 |
+
if (!method || method->type != cJSON_String) {
|
| 53 |
+
wm_log_error("Missing or invalid method field");
|
| 54 |
+
cJSON_Delete(root);
|
| 55 |
+
return WM_FAILED;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// Copy method name
|
| 59 |
+
size_t method_len = strlen(method->valuestring);
|
| 60 |
+
request->method = tls_mem_alloc(method_len + 1);
|
| 61 |
+
if (!request->method) {
|
| 62 |
+
wm_log_error("Failed to allocate memory for method name");
|
| 63 |
+
cJSON_Delete(root);
|
| 64 |
+
return WM_FAILED;
|
| 65 |
+
}
|
| 66 |
+
memcpy(request->method, method->valuestring, method_len);
|
| 67 |
+
request->method[method_len] = '\0';
|
| 68 |
+
|
| 69 |
+
// Get params (optional)
|
| 70 |
+
params = cJSON_GetObjectItem(root, "params");
|
| 71 |
+
if (params) {
|
| 72 |
+
request->params = cJSON_Duplicate(params, 1);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
// Get id (optional - if missing, it's a notification)
|
| 76 |
+
id = cJSON_GetObjectItem(root, "id");
|
| 77 |
+
if (id) {
|
| 78 |
+
request->id = cJSON_Duplicate(id, 1);
|
| 79 |
+
request->type = JSONRPC_REQUEST;
|
| 80 |
+
} else {
|
| 81 |
+
request->id = NULL;
|
| 82 |
+
request->type = JSONRPC_NOTIFICATION;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
wm_log_debug("Parsed JSON-RPC %s: method=%s, has_params=%d",
|
| 86 |
+
request->type == JSONRPC_NOTIFICATION ? "notification" : "request",
|
| 87 |
+
request->method,
|
| 88 |
+
request->params != NULL);
|
| 89 |
+
|
| 90 |
+
cJSON_Delete(root);
|
| 91 |
+
return WM_SUCCESS;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
void mcp_jsonrpc_free_request(mcp_jsonrpc_request_t *request)
|
| 95 |
+
{
|
| 96 |
+
if (!request) return;
|
| 97 |
+
|
| 98 |
+
if (request->method) {
|
| 99 |
+
tls_mem_free(request->method);
|
| 100 |
+
request->method = NULL;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
if (request->params) {
|
| 104 |
+
cJSON_Delete(request->params);
|
| 105 |
+
request->params = NULL;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
if (request->id) {
|
| 109 |
+
cJSON_Delete(request->id);
|
| 110 |
+
request->id = NULL;
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
char* mcp_jsonrpc_build_response(cJSON *id, cJSON *result)
|
| 115 |
+
{
|
| 116 |
+
cJSON *response = cJSON_CreateObject();
|
| 117 |
+
char *json_str;
|
| 118 |
+
|
| 119 |
+
if (!response) {
|
| 120 |
+
wm_log_error("Failed to create response object");
|
| 121 |
+
return NULL;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
cJSON_AddStringToObject(response, "jsonrpc", "2.0");
|
| 125 |
+
|
| 126 |
+
if (id) {
|
| 127 |
+
cJSON_AddItemToObject(response, "id", cJSON_Duplicate(id, 1));
|
| 128 |
+
} else {
|
| 129 |
+
cJSON_AddNullToObject(response, "id");
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
if (result) {
|
| 133 |
+
cJSON_AddItemToObject(response, "result", result);
|
| 134 |
+
} else {
|
| 135 |
+
cJSON_AddNullToObject(response, "result");
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
json_str = cJSON_PrintUnformatted(response);
|
| 139 |
+
cJSON_Delete(response);
|
| 140 |
+
|
| 141 |
+
wm_log_debug("Built JSON-RPC success response");
|
| 142 |
+
|
| 143 |
+
return json_str;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
char* mcp_jsonrpc_build_error(cJSON *id, int code, const char *message, cJSON *data)
|
| 147 |
+
{
|
| 148 |
+
cJSON *response = cJSON_CreateObject();
|
| 149 |
+
cJSON *error = cJSON_CreateObject();
|
| 150 |
+
char *json_str;
|
| 151 |
+
|
| 152 |
+
if (!response || !error) {
|
| 153 |
+
wm_log_error("Failed to create error response object");
|
| 154 |
+
if (response) cJSON_Delete(response);
|
| 155 |
+
if (error) cJSON_Delete(error);
|
| 156 |
+
return NULL;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
cJSON_AddStringToObject(response, "jsonrpc", "2.0");
|
| 160 |
+
|
| 161 |
+
if (id) {
|
| 162 |
+
cJSON_AddItemToObject(response, "id", cJSON_Duplicate(id, 1));
|
| 163 |
+
} else {
|
| 164 |
+
cJSON_AddNullToObject(response, "id");
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
cJSON_AddNumberToObject(error, "code", code);
|
| 168 |
+
cJSON_AddStringToObject(error, "message", message ? message : "Error");
|
| 169 |
+
|
| 170 |
+
if (data) {
|
| 171 |
+
cJSON_AddItemToObject(error, "data", data);
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
cJSON_AddItemToObject(response, "error", error);
|
| 175 |
+
|
| 176 |
+
json_str = cJSON_PrintUnformatted(response);
|
| 177 |
+
cJSON_Delete(response);
|
| 178 |
+
|
| 179 |
+
wm_log_error("Built JSON-RPC error response: code=%d, message=%s",
|
| 180 |
+
code, message ? message : "Error");
|
| 181 |
+
|
| 182 |
+
return json_str;
|
| 183 |
+
}
|
MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_methods.c
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_methods.c
|
| 3 |
+
*
|
| 4 |
+
* @brief MCP Protocol Methods Implementation
|
| 5 |
+
*
|
| 6 |
+
* @author castlebbs
|
| 7 |
+
*/
|
| 8 |
+
|
| 9 |
+
#include <string.h>
|
| 10 |
+
#include "mcp_methods.h"
|
| 11 |
+
#include "mcp_tools.h"
|
| 12 |
+
#include "mcp_jsonrpc.h"
|
| 13 |
+
|
| 14 |
+
int mcp_method_initialize(cJSON *params, cJSON **result, cJSON **error)
|
| 15 |
+
{
|
| 16 |
+
cJSON *server_info;
|
| 17 |
+
cJSON *capabilities;
|
| 18 |
+
cJSON *tools_cap;
|
| 19 |
+
|
| 20 |
+
(void)params; // Unused - we don't require any specific params
|
| 21 |
+
|
| 22 |
+
// Create result object
|
| 23 |
+
*result = cJSON_CreateObject();
|
| 24 |
+
if (!*result) {
|
| 25 |
+
*error = cJSON_CreateObject();
|
| 26 |
+
cJSON_AddNumberToObject(*error, "code", JSONRPC_INTERNAL_ERROR);
|
| 27 |
+
cJSON_AddStringToObject(*error, "message", "Memory allocation failed");
|
| 28 |
+
return WM_FAILED;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
// Add protocol version
|
| 32 |
+
cJSON_AddStringToObject(*result, "protocolVersion", "2025-06-18");
|
| 33 |
+
|
| 34 |
+
// Add server info
|
| 35 |
+
server_info = cJSON_CreateObject();
|
| 36 |
+
cJSON_AddStringToObject(server_info, "name", "W600-MCP-Server");
|
| 37 |
+
cJSON_AddStringToObject(server_info, "version", "1.0.0");
|
| 38 |
+
cJSON_AddItemToObject(*result, "serverInfo", server_info);
|
| 39 |
+
|
| 40 |
+
// Add capabilities
|
| 41 |
+
capabilities = cJSON_CreateObject();
|
| 42 |
+
tools_cap = cJSON_CreateObject();
|
| 43 |
+
cJSON_AddItemToObject(capabilities, "tools", tools_cap);
|
| 44 |
+
cJSON_AddItemToObject(*result, "capabilities", capabilities);
|
| 45 |
+
|
| 46 |
+
return WM_SUCCESS;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
int mcp_method_tools_list(cJSON *params, cJSON **result, cJSON **error)
|
| 50 |
+
{
|
| 51 |
+
const mcp_tool_t *tools;
|
| 52 |
+
int tool_count;
|
| 53 |
+
int i;
|
| 54 |
+
cJSON *tools_array;
|
| 55 |
+
cJSON *tool_obj;
|
| 56 |
+
cJSON *input_schema;
|
| 57 |
+
|
| 58 |
+
(void)params; // Unused
|
| 59 |
+
|
| 60 |
+
// Get tools list
|
| 61 |
+
tools = mcp_tools_get_list(&tool_count);
|
| 62 |
+
|
| 63 |
+
// Create result object
|
| 64 |
+
*result = cJSON_CreateObject();
|
| 65 |
+
if (!*result) {
|
| 66 |
+
*error = cJSON_CreateObject();
|
| 67 |
+
cJSON_AddNumberToObject(*error, "code", JSONRPC_INTERNAL_ERROR);
|
| 68 |
+
cJSON_AddStringToObject(*error, "message", "Memory allocation failed");
|
| 69 |
+
return WM_FAILED;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
// Create tools array
|
| 73 |
+
tools_array = cJSON_CreateArray();
|
| 74 |
+
if (!tools_array) {
|
| 75 |
+
cJSON_Delete(*result);
|
| 76 |
+
*result = NULL;
|
| 77 |
+
*error = cJSON_CreateObject();
|
| 78 |
+
cJSON_AddNumberToObject(*error, "code", JSONRPC_INTERNAL_ERROR);
|
| 79 |
+
cJSON_AddStringToObject(*error, "message", "Memory allocation failed");
|
| 80 |
+
return WM_FAILED;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
// Add each tool to array
|
| 84 |
+
for (i = 0; i < tool_count; i++) {
|
| 85 |
+
tool_obj = cJSON_CreateObject();
|
| 86 |
+
cJSON_AddStringToObject(tool_obj, "name", tools[i].name);
|
| 87 |
+
cJSON_AddStringToObject(tool_obj, "description", tools[i].description);
|
| 88 |
+
|
| 89 |
+
// Parse input schema from JSON string
|
| 90 |
+
input_schema = cJSON_Parse(tools[i].input_schema_json);
|
| 91 |
+
if (input_schema) {
|
| 92 |
+
cJSON_AddItemToObject(tool_obj, "inputSchema", input_schema);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
cJSON_AddItemToArray(tools_array, tool_obj);
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
// Add tools array to result
|
| 99 |
+
cJSON_AddItemToObject(*result, "tools", tools_array);
|
| 100 |
+
|
| 101 |
+
return WM_SUCCESS;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
int mcp_method_tools_call(cJSON *params, cJSON **result, cJSON **error)
|
| 105 |
+
{
|
| 106 |
+
cJSON *name_obj;
|
| 107 |
+
cJSON *arguments_obj;
|
| 108 |
+
const char *tool_name;
|
| 109 |
+
const mcp_tool_t *tool;
|
| 110 |
+
|
| 111 |
+
// Validate params
|
| 112 |
+
if (!params) {
|
| 113 |
+
*error = cJSON_CreateObject();
|
| 114 |
+
cJSON_AddNumberToObject(*error, "code", JSONRPC_INVALID_PARAMS);
|
| 115 |
+
cJSON_AddStringToObject(*error, "message", "Missing parameters");
|
| 116 |
+
return WM_FAILED;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// Get tool name
|
| 120 |
+
name_obj = cJSON_GetObjectItem(params, "name");
|
| 121 |
+
if (!name_obj || name_obj->type != cJSON_String) {
|
| 122 |
+
*error = cJSON_CreateObject();
|
| 123 |
+
cJSON_AddNumberToObject(*error, "code", JSONRPC_INVALID_PARAMS);
|
| 124 |
+
cJSON_AddStringToObject(*error, "message", "Missing or invalid 'name' parameter");
|
| 125 |
+
return WM_FAILED;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
tool_name = name_obj->valuestring;
|
| 129 |
+
|
| 130 |
+
// Get arguments (optional)
|
| 131 |
+
arguments_obj = cJSON_GetObjectItem(params, "arguments");
|
| 132 |
+
|
| 133 |
+
// Find tool
|
| 134 |
+
tool = mcp_tools_find(tool_name);
|
| 135 |
+
if (!tool) {
|
| 136 |
+
*error = cJSON_CreateObject();
|
| 137 |
+
cJSON_AddNumberToObject(*error, "code", -32601);
|
| 138 |
+
cJSON_AddStringToObject(*error, "message", "Tool not found");
|
| 139 |
+
return WM_FAILED;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
// Call tool handler
|
| 143 |
+
return tool->handler(arguments_obj, result, error);
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
int mcp_methods_dispatch(const char *method, cJSON *params, cJSON **result, cJSON **error)
|
| 147 |
+
{
|
| 148 |
+
if (!method) {
|
| 149 |
+
*error = cJSON_CreateObject();
|
| 150 |
+
cJSON_AddNumberToObject(*error, "code", JSONRPC_INVALID_REQUEST);
|
| 151 |
+
cJSON_AddStringToObject(*error, "message", "Missing method");
|
| 152 |
+
return WM_FAILED;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
if (strcmp(method, "initialize") == 0) {
|
| 156 |
+
return mcp_method_initialize(params, result, error);
|
| 157 |
+
} else if (strcmp(method, "tools/list") == 0) {
|
| 158 |
+
return mcp_method_tools_list(params, result, error);
|
| 159 |
+
} else if (strcmp(method, "tools/call") == 0) {
|
| 160 |
+
return mcp_method_tools_call(params, result, error);
|
| 161 |
+
} else {
|
| 162 |
+
*error = cJSON_CreateObject();
|
| 163 |
+
cJSON_AddNumberToObject(*error, "code", JSONRPC_METHOD_NOT_FOUND);
|
| 164 |
+
cJSON_AddStringToObject(*error, "message", "Method not found");
|
| 165 |
+
return WM_FAILED;
|
| 166 |
+
}
|
| 167 |
+
}
|
MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_server.c
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_server_socket.c
|
| 3 |
+
*
|
| 4 |
+
* @brief MCP HTTP Server Implementation (Socket-based)
|
| 5 |
+
*
|
| 6 |
+
* @author castlebbs
|
| 7 |
+
*/
|
| 8 |
+
|
| 9 |
+
#define LOG_TAG "MCP/Server"
|
| 10 |
+
|
| 11 |
+
#include <string.h>
|
| 12 |
+
#include <stdio.h>
|
| 13 |
+
#include "mcp_server.h"
|
| 14 |
+
#include "mcp_http.h"
|
| 15 |
+
#include "mcp_jsonrpc.h"
|
| 16 |
+
#include "mcp_methods.h"
|
| 17 |
+
#include "mcp_tools.h"
|
| 18 |
+
#include "mcp_stream.h"
|
| 19 |
+
#include "wm_mem.h"
|
| 20 |
+
#include "wm_log.h"
|
| 21 |
+
#include "wm_osal.h"
|
| 22 |
+
#include "wm_sockets.h"
|
| 23 |
+
|
| 24 |
+
// Server configuration
|
| 25 |
+
#define MCP_SERVER_TASK_STACK_SIZE 2048
|
| 26 |
+
#define MCP_SERVER_TASK_PRIORITY 32
|
| 27 |
+
#define MCP_SERVER_RECV_TIMEOUT_MS 30000 // 30 seconds
|
| 28 |
+
|
| 29 |
+
// Server state
|
| 30 |
+
static struct {
|
| 31 |
+
int listen_sock;
|
| 32 |
+
u16 port;
|
| 33 |
+
tls_os_task_t task_handle;
|
| 34 |
+
volatile int running;
|
| 35 |
+
} server_ctx = {0};
|
| 36 |
+
|
| 37 |
+
// Server task stack
|
| 38 |
+
static u32 mcp_server_task_stk[MCP_SERVER_TASK_STACK_SIZE / sizeof(u32)];
|
| 39 |
+
|
| 40 |
+
/**
|
| 41 |
+
* Helper: Send buffer via socket
|
| 42 |
+
*/
|
| 43 |
+
static int sock_send_buf(int sock, const char *buf, int len)
|
| 44 |
+
{
|
| 45 |
+
int sent = 0;
|
| 46 |
+
|
| 47 |
+
while (sent < len) {
|
| 48 |
+
int ret = send(sock, buf + sent, len - sent, 0);
|
| 49 |
+
if (ret <= 0) {
|
| 50 |
+
wm_log_error("Socket send failed: %d", ret);
|
| 51 |
+
return -1;
|
| 52 |
+
}
|
| 53 |
+
sent += ret;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
return sent;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
/**
|
| 60 |
+
* Send simple HTTP response (for errors, etc.)
|
| 61 |
+
*/
|
| 62 |
+
static void send_simple_response(int sock, int status_code, const char *body)
|
| 63 |
+
{
|
| 64 |
+
char response[512];
|
| 65 |
+
int len;
|
| 66 |
+
|
| 67 |
+
len = snprintf(response, sizeof(response),
|
| 68 |
+
"HTTP/1.1 %d %s\r\n"
|
| 69 |
+
"Content-Type: application/json\r\n"
|
| 70 |
+
"Content-Length: %d\r\n"
|
| 71 |
+
"Connection: close\r\n"
|
| 72 |
+
"\r\n"
|
| 73 |
+
"%s",
|
| 74 |
+
status_code,
|
| 75 |
+
mcp_http_get_status_text(status_code),
|
| 76 |
+
(int)strlen(body),
|
| 77 |
+
body);
|
| 78 |
+
|
| 79 |
+
if (len > 0 && len < sizeof(response)) {
|
| 80 |
+
sock_send_buf(sock, response, len);
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
/**
|
| 85 |
+
* Send HTTP response with pre-built body
|
| 86 |
+
*/
|
| 87 |
+
static void send_response(int sock, int status_code, const char *content_type,
|
| 88 |
+
const char *body, int body_len)
|
| 89 |
+
{
|
| 90 |
+
char headers[256];
|
| 91 |
+
int len;
|
| 92 |
+
|
| 93 |
+
// Build and send headers
|
| 94 |
+
len = snprintf(headers, sizeof(headers),
|
| 95 |
+
"HTTP/1.1 %d %s\r\n"
|
| 96 |
+
"Content-Type: %s\r\n"
|
| 97 |
+
"Content-Length: %d\r\n"
|
| 98 |
+
"Connection: close\r\n"
|
| 99 |
+
"\r\n",
|
| 100 |
+
status_code,
|
| 101 |
+
mcp_http_get_status_text(status_code),
|
| 102 |
+
content_type,
|
| 103 |
+
body_len);
|
| 104 |
+
|
| 105 |
+
if (len > 0 && len < sizeof(headers)) {
|
| 106 |
+
sock_send_buf(sock, headers, len);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
// Send body
|
| 110 |
+
if (body && body_len > 0) {
|
| 111 |
+
sock_send_buf(sock, body, body_len);
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
/**
|
| 116 |
+
* Handle HTTP request
|
| 117 |
+
*/
|
| 118 |
+
static void handle_request(int sock, const char *request_buf, int request_len)
|
| 119 |
+
{
|
| 120 |
+
mcp_http_request_t request;
|
| 121 |
+
mcp_jsonrpc_request_t jsonrpc_request;
|
| 122 |
+
char *jsonrpc_response = NULL;
|
| 123 |
+
cJSON *result = NULL;
|
| 124 |
+
cJSON *error = NULL;
|
| 125 |
+
int ret;
|
| 126 |
+
|
| 127 |
+
wm_log_debug("Received request (%d bytes)", request_len);
|
| 128 |
+
|
| 129 |
+
// Parse HTTP request
|
| 130 |
+
ret = mcp_http_parse_request(request_buf, request_len, &request);
|
| 131 |
+
if (ret != WM_SUCCESS) {
|
| 132 |
+
wm_log_error("Failed to parse HTTP request");
|
| 133 |
+
send_simple_response(sock, 400, "{\"error\":\"Bad request\"}");
|
| 134 |
+
return;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
// Check path
|
| 138 |
+
if (strcmp(request.path, "/mcp") != 0) {
|
| 139 |
+
send_simple_response(sock, 404, "{\"error\":\"Not found\"}");
|
| 140 |
+
return;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
// Handle GET - return 405
|
| 144 |
+
if (strcmp(request.method, "GET") == 0) {
|
| 145 |
+
send_simple_response(sock, 405, "");
|
| 146 |
+
return;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
// Handle POST
|
| 150 |
+
if (strcmp(request.method, "POST") != 0) {
|
| 151 |
+
send_simple_response(sock, 405, "");
|
| 152 |
+
return;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
// Validate headers
|
| 156 |
+
if (!mcp_http_validate_headers(&request)) {
|
| 157 |
+
send_simple_response(sock, 400, "{\"error\":\"Missing required headers\"}");
|
| 158 |
+
return;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
wm_log_info("Request body: %s", request.body);
|
| 162 |
+
|
| 163 |
+
// Parse JSON-RPC request
|
| 164 |
+
ret = mcp_jsonrpc_parse(request.body, &jsonrpc_request);
|
| 165 |
+
if (ret != WM_SUCCESS) {
|
| 166 |
+
wm_log_error("Failed to parse JSON-RPC request");
|
| 167 |
+
jsonrpc_response = mcp_jsonrpc_build_error(NULL, JSONRPC_PARSE_ERROR,
|
| 168 |
+
"Parse error", NULL);
|
| 169 |
+
if (jsonrpc_response) {
|
| 170 |
+
send_response(sock, 200, "application/json",
|
| 171 |
+
jsonrpc_response, strlen(jsonrpc_response));
|
| 172 |
+
tls_mem_free(jsonrpc_response);
|
| 173 |
+
}
|
| 174 |
+
return;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
wm_log_debug("JSON-RPC: %s", jsonrpc_request.method);
|
| 178 |
+
|
| 179 |
+
// Check if it's a notification
|
| 180 |
+
if (jsonrpc_request.type == JSONRPC_NOTIFICATION) {
|
| 181 |
+
mcp_jsonrpc_free_request(&jsonrpc_request);
|
| 182 |
+
send_simple_response(sock, 202, "");
|
| 183 |
+
return;
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
// Dispatch to method handler
|
| 187 |
+
ret = mcp_methods_dispatch(jsonrpc_request.method, jsonrpc_request.params,
|
| 188 |
+
&result, &error);
|
| 189 |
+
|
| 190 |
+
if (error) {
|
| 191 |
+
cJSON *code = cJSON_GetObjectItem(error, "code");
|
| 192 |
+
cJSON *message = cJSON_GetObjectItem(error, "message");
|
| 193 |
+
wm_log_error("Error %d: %s",
|
| 194 |
+
code ? code->valueint : -1,
|
| 195 |
+
(message && message->valuestring) ? message->valuestring : "unknown");
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
// Check if result indicates streaming response
|
| 199 |
+
if (result) {
|
| 200 |
+
cJSON *stream_type = cJSON_GetObjectItem(result, "_stream_type");
|
| 201 |
+
cJSON *stream_handler = cJSON_GetObjectItem(result, "_stream_handler");
|
| 202 |
+
cJSON *stream_user_data = cJSON_GetObjectItem(result, "_stream_user_data");
|
| 203 |
+
|
| 204 |
+
if (stream_type && stream_type->valuestring &&
|
| 205 |
+
stream_handler && stream_handler->valueint) {
|
| 206 |
+
|
| 207 |
+
// This is a streaming response - use streaming API
|
| 208 |
+
wm_log_info("Streaming response: %s", stream_type->valuestring);
|
| 209 |
+
|
| 210 |
+
mcp_stream_callback_t callback = (mcp_stream_callback_t)stream_handler->valueint;
|
| 211 |
+
void *user_data = stream_user_data ? (void *)stream_user_data->valueint : NULL;
|
| 212 |
+
|
| 213 |
+
mcp_stream_response(sock, jsonrpc_request.id, callback, user_data);
|
| 214 |
+
|
| 215 |
+
cJSON_Delete(result);
|
| 216 |
+
mcp_jsonrpc_free_request(&jsonrpc_request);
|
| 217 |
+
return; // Done - streaming complete
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
// Build JSON-RPC response (normal non-streaming path)
|
| 222 |
+
if (ret == WM_SUCCESS && result) {
|
| 223 |
+
jsonrpc_response = mcp_jsonrpc_build_response(jsonrpc_request.id, result);
|
| 224 |
+
} else if (error) {
|
| 225 |
+
cJSON *code = cJSON_GetObjectItem(error, "code");
|
| 226 |
+
cJSON *message = cJSON_GetObjectItem(error, "message");
|
| 227 |
+
cJSON *data = cJSON_GetObjectItem(error, "data");
|
| 228 |
+
|
| 229 |
+
int error_code = code ? code->valueint : JSONRPC_INTERNAL_ERROR;
|
| 230 |
+
const char *error_msg = (message && message->valuestring) ?
|
| 231 |
+
message->valuestring : "Internal error";
|
| 232 |
+
|
| 233 |
+
jsonrpc_response = mcp_jsonrpc_build_error(jsonrpc_request.id, error_code,
|
| 234 |
+
error_msg,
|
| 235 |
+
data ? cJSON_Duplicate(data, 1) : NULL);
|
| 236 |
+
cJSON_Delete(error);
|
| 237 |
+
} else {
|
| 238 |
+
jsonrpc_response = mcp_jsonrpc_build_error(jsonrpc_request.id,
|
| 239 |
+
JSONRPC_INTERNAL_ERROR,
|
| 240 |
+
"Internal error", NULL);
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
mcp_jsonrpc_free_request(&jsonrpc_request);
|
| 244 |
+
|
| 245 |
+
// Send response
|
| 246 |
+
if (jsonrpc_response) {
|
| 247 |
+
send_response(sock, 200, "application/json",
|
| 248 |
+
jsonrpc_response, strlen(jsonrpc_response));
|
| 249 |
+
tls_mem_free(jsonrpc_response);
|
| 250 |
+
} else {
|
| 251 |
+
send_simple_response(sock, 500, "{\"error\":\"Internal server error\"}");
|
| 252 |
+
}
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
/**
|
| 256 |
+
* Handle single client connection
|
| 257 |
+
*/
|
| 258 |
+
static void handle_client(int client_sock)
|
| 259 |
+
{
|
| 260 |
+
char *recv_buffer = NULL;
|
| 261 |
+
int buffer_size = MCP_HTTP_MAX_REQUEST_SIZE;
|
| 262 |
+
int received = 0;
|
| 263 |
+
int headers_complete = 0;
|
| 264 |
+
u32 content_length = 0;
|
| 265 |
+
u32 header_end_pos = 0;
|
| 266 |
+
struct timeval timeout;
|
| 267 |
+
|
| 268 |
+
// Set receive timeout
|
| 269 |
+
timeout.tv_sec = MCP_SERVER_RECV_TIMEOUT_MS / 1000;
|
| 270 |
+
timeout.tv_usec = (MCP_SERVER_RECV_TIMEOUT_MS % 1000) * 1000;
|
| 271 |
+
setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
| 272 |
+
|
| 273 |
+
// Allocate receive buffer
|
| 274 |
+
recv_buffer = (char *)tls_mem_alloc(buffer_size);
|
| 275 |
+
if (!recv_buffer) {
|
| 276 |
+
wm_log_error("Failed to allocate receive buffer");
|
| 277 |
+
closesocket(client_sock);
|
| 278 |
+
return;
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
// Receive request
|
| 282 |
+
while (received < buffer_size) {
|
| 283 |
+
int ret = recv(client_sock, recv_buffer + received, buffer_size - received, 0);
|
| 284 |
+
|
| 285 |
+
if (ret < 0) {
|
| 286 |
+
wm_log_error("Socket recv failed");
|
| 287 |
+
goto cleanup;
|
| 288 |
+
} else if (ret == 0) {
|
| 289 |
+
wm_log_info("Client closed connection");
|
| 290 |
+
goto cleanup;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
received += ret;
|
| 294 |
+
|
| 295 |
+
// Check if headers complete
|
| 296 |
+
if (!headers_complete && received >= 4) {
|
| 297 |
+
for (int i = 0; i <= received - 4; i++) {
|
| 298 |
+
if (recv_buffer[i] == '\r' && recv_buffer[i+1] == '\n' &&
|
| 299 |
+
recv_buffer[i+2] == '\r' && recv_buffer[i+3] == '\n') {
|
| 300 |
+
|
| 301 |
+
headers_complete = 1;
|
| 302 |
+
header_end_pos = i + 4;
|
| 303 |
+
|
| 304 |
+
// Parse Content-Length
|
| 305 |
+
if (mcp_http_get_content_length(recv_buffer, header_end_pos,
|
| 306 |
+
&content_length) != WM_SUCCESS) {
|
| 307 |
+
content_length = 0;
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
// Check if request is too large
|
| 311 |
+
if (content_length > MCP_HTTP_MAX_REQUEST_SIZE) {
|
| 312 |
+
wm_log_error("Content-Length too large: %u", content_length);
|
| 313 |
+
send_simple_response(client_sock, 413,
|
| 314 |
+
"{\"error\":\"Request payload too large\"}");
|
| 315 |
+
goto cleanup;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
wm_log_debug("Headers complete, Content-Length: %u", content_length);
|
| 319 |
+
break;
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
// Check if complete request received
|
| 325 |
+
if (headers_complete) {
|
| 326 |
+
u32 expected_total = header_end_pos + content_length;
|
| 327 |
+
|
| 328 |
+
if (received >= expected_total) {
|
| 329 |
+
// Complete request received
|
| 330 |
+
recv_buffer[received] = '\0';
|
| 331 |
+
handle_request(client_sock, recv_buffer, received);
|
| 332 |
+
goto cleanup;
|
| 333 |
+
}
|
| 334 |
+
}
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
// Buffer full but request not complete
|
| 338 |
+
wm_log_error("Request too large or incomplete");
|
| 339 |
+
send_simple_response(client_sock, 400, "{\"error\":\"Request too large\"}");
|
| 340 |
+
|
| 341 |
+
cleanup:
|
| 342 |
+
if (recv_buffer) {
|
| 343 |
+
tls_mem_free(recv_buffer);
|
| 344 |
+
}
|
| 345 |
+
closesocket(client_sock);
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
/**
|
| 349 |
+
* Server task - accepts connections and handles requests
|
| 350 |
+
*/
|
| 351 |
+
static void mcp_server_task(void *arg)
|
| 352 |
+
{
|
| 353 |
+
struct sockaddr_in client_addr;
|
| 354 |
+
socklen_t addr_len;
|
| 355 |
+
int client_sock;
|
| 356 |
+
|
| 357 |
+
wm_log_info("MCP Server task started on port %d", server_ctx.port);
|
| 358 |
+
|
| 359 |
+
while (server_ctx.running) {
|
| 360 |
+
addr_len = sizeof(client_addr);
|
| 361 |
+
|
| 362 |
+
// Accept connection (blocking)
|
| 363 |
+
client_sock = accept(server_ctx.listen_sock,
|
| 364 |
+
(struct sockaddr *)&client_addr,
|
| 365 |
+
&addr_len);
|
| 366 |
+
|
| 367 |
+
if (client_sock < 0) {
|
| 368 |
+
if (server_ctx.running) {
|
| 369 |
+
wm_log_error("Accept failed: %d", client_sock);
|
| 370 |
+
tls_os_time_delay(100); // Brief delay before retry
|
| 371 |
+
}
|
| 372 |
+
continue;
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
wm_log_info("Client connected");
|
| 376 |
+
|
| 377 |
+
// Handle client request (blocking - can take seconds for ELM327)
|
| 378 |
+
handle_client(client_sock);
|
| 379 |
+
|
| 380 |
+
wm_log_info("Client disconnected");
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
wm_log_info("MCP Server task exiting");
|
| 384 |
+
tls_os_task_del(MCP_SERVER_TASK_PRIORITY, NULL);
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
int mcp_server_init(u16 port)
|
| 388 |
+
{
|
| 389 |
+
struct sockaddr_in server_addr;
|
| 390 |
+
int opt = 1;
|
| 391 |
+
int ret;
|
| 392 |
+
|
| 393 |
+
// Check if already running
|
| 394 |
+
if (server_ctx.running) {
|
| 395 |
+
wm_log_warn("Server already running");
|
| 396 |
+
return WM_SUCCESS;
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
// Initialize tools (includes ELM327 init)
|
| 400 |
+
if (mcp_tools_init() != WM_SUCCESS) {
|
| 401 |
+
wm_log_error("Failed to initialize tools");
|
| 402 |
+
return WM_FAILED;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
// Create socket
|
| 406 |
+
server_ctx.listen_sock = socket(AF_INET, SOCK_STREAM, 0);
|
| 407 |
+
if (server_ctx.listen_sock < 0) {
|
| 408 |
+
wm_log_error("Failed to create socket");
|
| 409 |
+
return WM_FAILED;
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
// Set socket options
|
| 413 |
+
setsockopt(server_ctx.listen_sock, SOL_SOCKET, SO_REUSEADDR,
|
| 414 |
+
&opt, sizeof(opt));
|
| 415 |
+
|
| 416 |
+
// Bind to port
|
| 417 |
+
memset(&server_addr, 0, sizeof(server_addr));
|
| 418 |
+
server_addr.sin_family = AF_INET;
|
| 419 |
+
server_addr.sin_addr.s_addr = INADDR_ANY;
|
| 420 |
+
server_addr.sin_port = htons(port);
|
| 421 |
+
|
| 422 |
+
ret = bind(server_ctx.listen_sock, (struct sockaddr *)&server_addr,
|
| 423 |
+
sizeof(server_addr));
|
| 424 |
+
if (ret < 0) {
|
| 425 |
+
wm_log_error("Failed to bind to port %d", port);
|
| 426 |
+
closesocket(server_ctx.listen_sock);
|
| 427 |
+
return WM_FAILED;
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
// Listen
|
| 431 |
+
ret = listen(server_ctx.listen_sock, 2);
|
| 432 |
+
if (ret < 0) {
|
| 433 |
+
wm_log_error("Failed to listen");
|
| 434 |
+
closesocket(server_ctx.listen_sock);
|
| 435 |
+
return WM_FAILED;
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
server_ctx.port = port;
|
| 439 |
+
server_ctx.running = 1;
|
| 440 |
+
|
| 441 |
+
// Create server task
|
| 442 |
+
ret = tls_os_task_create(&server_ctx.task_handle,
|
| 443 |
+
"mcp_server",
|
| 444 |
+
mcp_server_task,
|
| 445 |
+
NULL,
|
| 446 |
+
(u8 *)mcp_server_task_stk,
|
| 447 |
+
MCP_SERVER_TASK_STACK_SIZE,
|
| 448 |
+
MCP_SERVER_TASK_PRIORITY,
|
| 449 |
+
0);
|
| 450 |
+
|
| 451 |
+
if (ret != TLS_OS_SUCCESS) {
|
| 452 |
+
wm_log_error("Failed to create server task");
|
| 453 |
+
server_ctx.running = 0;
|
| 454 |
+
closesocket(server_ctx.listen_sock);
|
| 455 |
+
return WM_FAILED;
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
wm_log_info("MCP Server initialized on port %d", port);
|
| 459 |
+
|
| 460 |
+
return WM_SUCCESS;
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
int mcp_server_deinit(void)
|
| 464 |
+
{
|
| 465 |
+
if (!server_ctx.running) {
|
| 466 |
+
return WM_FAILED;
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
// Stop server
|
| 470 |
+
server_ctx.running = 0;
|
| 471 |
+
|
| 472 |
+
// Close listen socket (will unblock accept())
|
| 473 |
+
if (server_ctx.listen_sock >= 0) {
|
| 474 |
+
closesocket(server_ctx.listen_sock);
|
| 475 |
+
server_ctx.listen_sock = -1;
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
// Note: Task will exit on its own when accept() fails
|
| 479 |
+
// We could wait for it here if needed
|
| 480 |
+
|
| 481 |
+
wm_log_info("MCP Server stopped");
|
| 482 |
+
|
| 483 |
+
return WM_SUCCESS;
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
int mcp_server_is_running(void)
|
| 487 |
+
{
|
| 488 |
+
return server_ctx.running;
|
| 489 |
+
}
|
MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_server_rawip.c
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_server.c
|
| 3 |
+
*
|
| 4 |
+
* @brief MCP HTTP Server Implementation (Raw IP-based)
|
| 5 |
+
*
|
| 6 |
+
* @author castlebbs
|
| 7 |
+
*
|
| 8 |
+
* This is not used, this was an experiment to use raw IP sockets. This works
|
| 9 |
+
* but since ELM327 driver is blocking with a semaphore, this approach is not
|
| 10 |
+
* the right one without major changes to the ELM327 driver to make it non-blocking.
|
| 11 |
+
*/
|
| 12 |
+
|
| 13 |
+
#define LOG_TAG "MCP/Server"
|
| 14 |
+
|
| 15 |
+
#include <string.h>
|
| 16 |
+
#include <stdio.h>
|
| 17 |
+
#include "mcp_server.h"
|
| 18 |
+
#include "mcp_http.h"
|
| 19 |
+
#include "mcp_jsonrpc.h"
|
| 20 |
+
#include "mcp_methods.h"
|
| 21 |
+
#include "mcp_tools.h"
|
| 22 |
+
#include "wm_mem.h"
|
| 23 |
+
#include "wm_log.h"
|
| 24 |
+
#include "lwip/tcp.h"
|
| 25 |
+
#include "lwip/ip_addr.h"
|
| 26 |
+
|
| 27 |
+
// Server state
|
| 28 |
+
static struct tcp_pcb *mcp_server_pcb = NULL;
|
| 29 |
+
static int server_running = 0;
|
| 30 |
+
|
| 31 |
+
// Connection state
|
| 32 |
+
struct mcp_conn_state {
|
| 33 |
+
char request_buffer[MCP_HTTP_MAX_REQUEST_SIZE];
|
| 34 |
+
u32 received_bytes;
|
| 35 |
+
struct tcp_pcb *pcb;
|
| 36 |
+
u8 headers_complete; // Flag: headers fully received
|
| 37 |
+
u32 content_length; // Parsed Content-Length value (0 if none)
|
| 38 |
+
u32 header_end_pos; // Position where headers end (after \r\n\r\n)
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
/**
|
| 42 |
+
* Close connection and free state
|
| 43 |
+
*/
|
| 44 |
+
static void mcp_close_conn(struct tcp_pcb *pcb, struct mcp_conn_state *state)
|
| 45 |
+
{
|
| 46 |
+
if (state) {
|
| 47 |
+
tls_mem_free(state);
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
if (pcb) {
|
| 51 |
+
tcp_arg(pcb, NULL);
|
| 52 |
+
tcp_recv(pcb, NULL);
|
| 53 |
+
tcp_err(pcb, NULL);
|
| 54 |
+
tcp_close(pcb);
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
/**
|
| 59 |
+
* Send HTTP response and close connection
|
| 60 |
+
*/
|
| 61 |
+
static void mcp_send_response(struct tcp_pcb *pcb, struct mcp_conn_state *state,
|
| 62 |
+
const mcp_http_response_t *response)
|
| 63 |
+
{
|
| 64 |
+
char *response_buffer;
|
| 65 |
+
int response_len;
|
| 66 |
+
err_t err;
|
| 67 |
+
|
| 68 |
+
// Allocate response buffer on heap to avoid stack overflow in TCP callback
|
| 69 |
+
response_buffer = (char *)tls_mem_alloc(4096);
|
| 70 |
+
if (!response_buffer) {
|
| 71 |
+
wm_log_error("Failed to allocate response buffer");
|
| 72 |
+
mcp_close_conn(pcb, state);
|
| 73 |
+
return;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// Build response
|
| 77 |
+
response_len = mcp_http_build_response(response, response_buffer, 4096);
|
| 78 |
+
if (response_len < 0) {
|
| 79 |
+
wm_log_error("Failed to build HTTP response");
|
| 80 |
+
tls_mem_free(response_buffer);
|
| 81 |
+
// Failed to build response
|
| 82 |
+
mcp_close_conn(pcb, state);
|
| 83 |
+
return;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
// Send response
|
| 87 |
+
err = tcp_write(pcb, response_buffer, response_len, TCP_WRITE_FLAG_COPY);
|
| 88 |
+
if (err == ERR_OK) {
|
| 89 |
+
tcp_output(pcb);
|
| 90 |
+
} else {
|
| 91 |
+
wm_log_error("Failed to send response: lwIP error %d", err);
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
tls_mem_free(response_buffer);
|
| 95 |
+
|
| 96 |
+
// Close connection
|
| 97 |
+
mcp_close_conn(pcb, state);
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
/**
|
| 101 |
+
* Handle HTTP request
|
| 102 |
+
*/
|
| 103 |
+
static void mcp_handle_request(struct tcp_pcb *pcb, struct mcp_conn_state *state)
|
| 104 |
+
{
|
| 105 |
+
mcp_http_request_t request;
|
| 106 |
+
mcp_http_response_t response;
|
| 107 |
+
mcp_jsonrpc_request_t jsonrpc_request;
|
| 108 |
+
char *jsonrpc_response = NULL;
|
| 109 |
+
int ret;
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
int len = state->received_bytes;
|
| 113 |
+
if (len >= sizeof(state->request_buffer))
|
| 114 |
+
len = sizeof(state->request_buffer) - 1;
|
| 115 |
+
|
| 116 |
+
state->request_buffer[len] = '\0'; // ensure null-termination
|
| 117 |
+
|
| 118 |
+
wm_log_debug("Received request:\n%s", state->request_buffer);
|
| 119 |
+
|
| 120 |
+
// Parse HTTP request
|
| 121 |
+
ret = mcp_http_parse_request(state->request_buffer, state->received_bytes, &request);
|
| 122 |
+
if (ret != WM_SUCCESS) {
|
| 123 |
+
wm_log_error("Failed to parse HTTP request");
|
| 124 |
+
// Bad request
|
| 125 |
+
response.status_code = 400;
|
| 126 |
+
response.status_text = NULL;
|
| 127 |
+
response.content_type = "application/json";
|
| 128 |
+
response.body = "{\"error\":\"Bad request\"}";
|
| 129 |
+
response.body_len = strlen(response.body);
|
| 130 |
+
mcp_send_response(pcb, state, &response);
|
| 131 |
+
return;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
// Check if path is /mcp
|
| 135 |
+
if (strcmp(request.path, "/mcp") != 0) {
|
| 136 |
+
// Not found
|
| 137 |
+
response.status_code = 404;
|
| 138 |
+
response.status_text = NULL;
|
| 139 |
+
response.content_type = "application/json";
|
| 140 |
+
response.body = "{\"error\":\"Not found\"}";
|
| 141 |
+
response.body_len = strlen(response.body);
|
| 142 |
+
mcp_send_response(pcb, state, &response);
|
| 143 |
+
return;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
// Handle GET - return 405
|
| 147 |
+
if (strcmp(request.method, "GET") == 0) {
|
| 148 |
+
response.status_code = 405;
|
| 149 |
+
response.status_text = NULL;
|
| 150 |
+
response.content_type = NULL;
|
| 151 |
+
response.body = "";
|
| 152 |
+
response.body_len = 0;
|
| 153 |
+
mcp_send_response(pcb, state, &response);
|
| 154 |
+
return;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
// Handle POST
|
| 158 |
+
if (strcmp(request.method, "POST") == 0) {
|
| 159 |
+
// Validate headers
|
| 160 |
+
if (!mcp_http_validate_headers(&request)) {
|
| 161 |
+
response.status_code = 400;
|
| 162 |
+
response.status_text = NULL;
|
| 163 |
+
response.content_type = "application/json";
|
| 164 |
+
response.body = "{\"error\":\"Missing required headers\"}";
|
| 165 |
+
response.body_len = strlen(response.body);
|
| 166 |
+
mcp_send_response(pcb, state, &response);
|
| 167 |
+
return;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
// log request.body for debugging
|
| 171 |
+
wm_log_info("Request body: %s", request.body);
|
| 172 |
+
|
| 173 |
+
// Parse JSON-RPC request
|
| 174 |
+
ret = mcp_jsonrpc_parse(request.body, &jsonrpc_request);
|
| 175 |
+
if (ret != WM_SUCCESS) {
|
| 176 |
+
wm_log_error("Failed to parse JSON-RPC request");
|
| 177 |
+
// Parse error
|
| 178 |
+
jsonrpc_response = mcp_jsonrpc_build_error(NULL, JSONRPC_PARSE_ERROR,
|
| 179 |
+
"Parse error", NULL);
|
| 180 |
+
response.status_code = 200;
|
| 181 |
+
response.status_text = NULL;
|
| 182 |
+
response.content_type = "application/json";
|
| 183 |
+
response.body = jsonrpc_response;
|
| 184 |
+
response.body_len = strlen(jsonrpc_response);
|
| 185 |
+
mcp_send_response(pcb, state, &response);
|
| 186 |
+
tls_mem_free(jsonrpc_response);
|
| 187 |
+
return;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
// Log JSON-RPC request
|
| 191 |
+
wm_log_debug("%s %s -> %s",
|
| 192 |
+
request.method, request.path, jsonrpc_request.method);
|
| 193 |
+
|
| 194 |
+
// Check if it's a notification
|
| 195 |
+
if (jsonrpc_request.type == JSONRPC_NOTIFICATION) {
|
| 196 |
+
// Return 202 Accepted with no body
|
| 197 |
+
mcp_jsonrpc_free_request(&jsonrpc_request);
|
| 198 |
+
response.status_code = 202;
|
| 199 |
+
response.status_text = NULL;
|
| 200 |
+
response.content_type = NULL;
|
| 201 |
+
response.body = "";
|
| 202 |
+
response.body_len = 0;
|
| 203 |
+
mcp_send_response(pcb, state, &response);
|
| 204 |
+
return;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
// Dispatch to method handler
|
| 208 |
+
cJSON *result = NULL;
|
| 209 |
+
cJSON *error = NULL;
|
| 210 |
+
ret = mcp_methods_dispatch(jsonrpc_request.method, jsonrpc_request.params,
|
| 211 |
+
&result, &error);
|
| 212 |
+
|
| 213 |
+
if (error) {
|
| 214 |
+
cJSON *code = cJSON_GetObjectItem(error, "code");
|
| 215 |
+
cJSON *message = cJSON_GetObjectItem(error, "message");
|
| 216 |
+
wm_log_error("Error %d: %s",
|
| 217 |
+
code ? code->valueint : -1,
|
| 218 |
+
(message && message->valuestring) ? message->valuestring : "unknown");
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
// Build JSON-RPC response
|
| 222 |
+
if (ret == WM_SUCCESS && result) {
|
| 223 |
+
jsonrpc_response = mcp_jsonrpc_build_response(jsonrpc_request.id, result);
|
| 224 |
+
} else if (error) {
|
| 225 |
+
cJSON *code = cJSON_GetObjectItem(error, "code");
|
| 226 |
+
cJSON *message = cJSON_GetObjectItem(error, "message");
|
| 227 |
+
cJSON *data = cJSON_GetObjectItem(error, "data");
|
| 228 |
+
|
| 229 |
+
int error_code = code ? code->valueint : JSONRPC_INTERNAL_ERROR;
|
| 230 |
+
const char *error_msg = (message && message->valuestring) ?
|
| 231 |
+
message->valuestring : "Internal error";
|
| 232 |
+
|
| 233 |
+
jsonrpc_response = mcp_jsonrpc_build_error(jsonrpc_request.id, error_code,
|
| 234 |
+
error_msg,
|
| 235 |
+
data ? cJSON_Duplicate(data, 1) : NULL);
|
| 236 |
+
cJSON_Delete(error);
|
| 237 |
+
} else {
|
| 238 |
+
jsonrpc_response = mcp_jsonrpc_build_error(jsonrpc_request.id,
|
| 239 |
+
JSONRPC_INTERNAL_ERROR,
|
| 240 |
+
"Internal error", NULL);
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
mcp_jsonrpc_free_request(&jsonrpc_request);
|
| 244 |
+
|
| 245 |
+
// Send response
|
| 246 |
+
if (jsonrpc_response) {
|
| 247 |
+
response.status_code = 200;
|
| 248 |
+
response.status_text = NULL;
|
| 249 |
+
response.content_type = "application/json";
|
| 250 |
+
response.body = jsonrpc_response;
|
| 251 |
+
response.body_len = strlen(jsonrpc_response);
|
| 252 |
+
mcp_send_response(pcb, state, &response);
|
| 253 |
+
tls_mem_free(jsonrpc_response);
|
| 254 |
+
} else {
|
| 255 |
+
response.status_code = 500;
|
| 256 |
+
response.status_text = NULL;
|
| 257 |
+
response.content_type = "application/json";
|
| 258 |
+
response.body = "{\"error\":\"Internal server error\"}";
|
| 259 |
+
response.body_len = strlen(response.body);
|
| 260 |
+
mcp_send_response(pcb, state, &response);
|
| 261 |
+
}
|
| 262 |
+
return;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
// Method not allowed
|
| 266 |
+
response.status_code = 405;
|
| 267 |
+
response.status_text = NULL;
|
| 268 |
+
response.content_type = NULL;
|
| 269 |
+
response.body = "";
|
| 270 |
+
response.body_len = 0;
|
| 271 |
+
mcp_send_response(pcb, state, &response);
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
/**
|
| 275 |
+
* TCP receive callback
|
| 276 |
+
*/
|
| 277 |
+
static err_t mcp_recv_callback(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
|
| 278 |
+
{
|
| 279 |
+
struct mcp_conn_state *state = (struct mcp_conn_state *)arg;
|
| 280 |
+
struct pbuf *q;
|
| 281 |
+
u32 copy_len;
|
| 282 |
+
|
| 283 |
+
if (err != ERR_OK || p == NULL) {
|
| 284 |
+
// Connection closed or error
|
| 285 |
+
if (p) {
|
| 286 |
+
tcp_recved(pcb, p->tot_len);
|
| 287 |
+
pbuf_free(p);
|
| 288 |
+
}
|
| 289 |
+
mcp_close_conn(pcb, state);
|
| 290 |
+
return ERR_OK;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
// Copy data from pbuf chain
|
| 294 |
+
for (q = p; q != NULL; q = q->next) {
|
| 295 |
+
copy_len = q->len;
|
| 296 |
+
if (state->received_bytes + copy_len > sizeof(state->request_buffer)) {
|
| 297 |
+
copy_len = sizeof(state->request_buffer) - state->received_bytes;
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
if (copy_len > 0) {
|
| 301 |
+
memcpy(state->request_buffer + state->received_bytes, q->payload, copy_len);
|
| 302 |
+
state->received_bytes += copy_len;
|
| 303 |
+
}
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
// Acknowledge received data
|
| 307 |
+
tcp_recved(pcb, p->tot_len);
|
| 308 |
+
pbuf_free(p);
|
| 309 |
+
|
| 310 |
+
// If headers not yet complete, search for double CRLF
|
| 311 |
+
if (!state->headers_complete && state->received_bytes >= 4) {
|
| 312 |
+
char *search_start = state->request_buffer;
|
| 313 |
+
char *search_end = state->request_buffer + state->received_bytes - 3;
|
| 314 |
+
|
| 315 |
+
for (char *pos = search_start; pos <= search_end; pos++) {
|
| 316 |
+
if (pos[0] == '\r' && pos[1] == '\n' &&
|
| 317 |
+
pos[2] == '\r' && pos[3] == '\n') {
|
| 318 |
+
|
| 319 |
+
// Found end of headers
|
| 320 |
+
state->headers_complete = 1;
|
| 321 |
+
state->header_end_pos = (pos - state->request_buffer) + 4;
|
| 322 |
+
|
| 323 |
+
// Parse Content-Length header
|
| 324 |
+
if (mcp_http_get_content_length(state->request_buffer,
|
| 325 |
+
state->header_end_pos,
|
| 326 |
+
&state->content_length) != WM_SUCCESS) {
|
| 327 |
+
// No Content-Length header or parse error - assume no body
|
| 328 |
+
state->content_length = 0;
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
// Reject Content-Length that exceeds maximum request size
|
| 332 |
+
if (state->content_length > MCP_HTTP_MAX_REQUEST_SIZE) {
|
| 333 |
+
wm_log_error("Content-Length too large: %u (max: %u)",
|
| 334 |
+
state->content_length, MCP_HTTP_MAX_REQUEST_SIZE);
|
| 335 |
+
mcp_http_response_t response;
|
| 336 |
+
response.status_code = 413;
|
| 337 |
+
response.status_text = "Payload Too Large";
|
| 338 |
+
response.content_type = "application/json";
|
| 339 |
+
response.body = "{\"error\":\"Request payload too large\"}";
|
| 340 |
+
response.body_len = strlen(response.body);
|
| 341 |
+
mcp_send_response(pcb, state, &response);
|
| 342 |
+
return ERR_OK;
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
wm_log_debug("Headers complete at pos %u, Content-Length: %u",
|
| 346 |
+
state->header_end_pos, state->content_length);
|
| 347 |
+
break;
|
| 348 |
+
}
|
| 349 |
+
}
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
// If headers complete, check if we have received the complete body
|
| 353 |
+
if (state->headers_complete) {
|
| 354 |
+
u32 expected_total = state->header_end_pos + state->content_length;
|
| 355 |
+
|
| 356 |
+
if (state->received_bytes >= expected_total) {
|
| 357 |
+
// Complete request received - handle it
|
| 358 |
+
wm_log_debug("Complete request received (%u bytes)", state->received_bytes);
|
| 359 |
+
mcp_handle_request(pcb, state);
|
| 360 |
+
return ERR_OK;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
// Check if expected total would exceed buffer
|
| 364 |
+
if (expected_total > sizeof(state->request_buffer)) {
|
| 365 |
+
// Request too large
|
| 366 |
+
wm_log_error("Request too large: expected %u bytes, buffer size %u",
|
| 367 |
+
expected_total, sizeof(state->request_buffer));
|
| 368 |
+
mcp_http_response_t response;
|
| 369 |
+
response.status_code = 400;
|
| 370 |
+
response.status_text = NULL;
|
| 371 |
+
response.content_type = "application/json";
|
| 372 |
+
response.body = "{\"error\":\"Request too large\"}";
|
| 373 |
+
response.body_len = strlen(response.body);
|
| 374 |
+
mcp_send_response(pcb, state, &response);
|
| 375 |
+
return ERR_OK;
|
| 376 |
+
}
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
// Check if buffer is full but headers not complete
|
| 380 |
+
if (!state->headers_complete &&
|
| 381 |
+
state->received_bytes >= sizeof(state->request_buffer)) {
|
| 382 |
+
// Headers too large
|
| 383 |
+
wm_log_error("Headers too large: received %u bytes", state->received_bytes);
|
| 384 |
+
mcp_http_response_t response;
|
| 385 |
+
response.status_code = 400;
|
| 386 |
+
response.status_text = NULL;
|
| 387 |
+
response.content_type = "application/json";
|
| 388 |
+
response.body = "{\"error\":\"Request headers too large\"}";
|
| 389 |
+
response.body_len = strlen(response.body);
|
| 390 |
+
mcp_send_response(pcb, state, &response);
|
| 391 |
+
return ERR_OK;
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
// Wait for more data
|
| 395 |
+
return ERR_OK;
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
/**
|
| 399 |
+
* TCP error callback
|
| 400 |
+
*/
|
| 401 |
+
static void mcp_err_callback(void *arg, err_t err)
|
| 402 |
+
{
|
| 403 |
+
struct mcp_conn_state *state = (struct mcp_conn_state *)arg;
|
| 404 |
+
|
| 405 |
+
wm_log_error("TCP error callback: err=%d", err);
|
| 406 |
+
|
| 407 |
+
// Free state (pcb is already freed by lwIP)
|
| 408 |
+
if (state) {
|
| 409 |
+
tls_mem_free(state);
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
/**
|
| 414 |
+
* TCP accept callback
|
| 415 |
+
*/
|
| 416 |
+
static err_t mcp_accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err)
|
| 417 |
+
{
|
| 418 |
+
struct mcp_conn_state *state;
|
| 419 |
+
|
| 420 |
+
(void)arg; // Unused
|
| 421 |
+
|
| 422 |
+
if (err != ERR_OK || newpcb == NULL) {
|
| 423 |
+
wm_log_error("Accept failed: err=%d", err);
|
| 424 |
+
return ERR_VAL;
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
// Allocate connection state
|
| 428 |
+
state = (struct mcp_conn_state *)tls_mem_alloc(sizeof(struct mcp_conn_state));
|
| 429 |
+
if (!state) {
|
| 430 |
+
wm_log_error("Failed to allocate connection state");
|
| 431 |
+
tcp_close(newpcb);
|
| 432 |
+
return ERR_MEM;
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
memset(state, 0, sizeof(struct mcp_conn_state));
|
| 436 |
+
state->pcb = newpcb;
|
| 437 |
+
|
| 438 |
+
// Set callbacks
|
| 439 |
+
tcp_arg(newpcb, state);
|
| 440 |
+
tcp_recv(newpcb, mcp_recv_callback);
|
| 441 |
+
tcp_err(newpcb, mcp_err_callback);
|
| 442 |
+
|
| 443 |
+
return ERR_OK;
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
int mcp_server_init(u16 port)
|
| 447 |
+
{
|
| 448 |
+
err_t err;
|
| 449 |
+
|
| 450 |
+
// Check if already running
|
| 451 |
+
if (server_running) {
|
| 452 |
+
return WM_SUCCESS;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
// Initialize tools (includes ELM327 init)
|
| 456 |
+
if (mcp_tools_init() != WM_SUCCESS) {
|
| 457 |
+
return WM_FAILED;
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
// Create TCP PCB
|
| 461 |
+
mcp_server_pcb = tcp_new();
|
| 462 |
+
if (!mcp_server_pcb) {
|
| 463 |
+
return WM_FAILED;
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
// Allow address reuse
|
| 467 |
+
ip_set_option(mcp_server_pcb, SOF_REUSEADDR);
|
| 468 |
+
|
| 469 |
+
// Bind to port
|
| 470 |
+
err = tcp_bind(mcp_server_pcb, IP_ADDR_ANY, port);
|
| 471 |
+
if (err != ERR_OK) {
|
| 472 |
+
tcp_close(mcp_server_pcb);
|
| 473 |
+
mcp_server_pcb = NULL;
|
| 474 |
+
return WM_FAILED;
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
// Listen
|
| 478 |
+
mcp_server_pcb = tcp_listen(mcp_server_pcb);
|
| 479 |
+
if (!mcp_server_pcb) {
|
| 480 |
+
return WM_FAILED;
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
// Set accept callback
|
| 484 |
+
tcp_arg(mcp_server_pcb, mcp_server_pcb);
|
| 485 |
+
tcp_accept(mcp_server_pcb, mcp_accept_callback);
|
| 486 |
+
|
| 487 |
+
server_running = 1;
|
| 488 |
+
|
| 489 |
+
wm_log_info("MCP Server started on port %d", port);
|
| 490 |
+
|
| 491 |
+
return WM_SUCCESS;
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
int mcp_server_deinit(void)
|
| 495 |
+
{
|
| 496 |
+
if (!server_running || !mcp_server_pcb) {
|
| 497 |
+
return WM_FAILED;
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
tcp_arg(mcp_server_pcb, NULL);
|
| 501 |
+
tcp_accept(mcp_server_pcb, NULL);
|
| 502 |
+
tcp_close(mcp_server_pcb);
|
| 503 |
+
mcp_server_pcb = NULL;
|
| 504 |
+
server_running = 0;
|
| 505 |
+
|
| 506 |
+
return WM_SUCCESS;
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
int mcp_server_is_running(void)
|
| 510 |
+
{
|
| 511 |
+
return server_running;
|
| 512 |
+
}
|
MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_stream.c
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_stream.c
|
| 3 |
+
*
|
| 4 |
+
* @brief MCP Streaming Response Implementation
|
| 5 |
+
*
|
| 6 |
+
* @author castlebbs
|
| 7 |
+
*/
|
| 8 |
+
|
| 9 |
+
#define LOG_TAG "MCP/Stream"
|
| 10 |
+
|
| 11 |
+
#include <string.h>
|
| 12 |
+
#include <stdio.h>
|
| 13 |
+
#include "mcp_stream.h"
|
| 14 |
+
#include "wm_sockets.h"
|
| 15 |
+
#include "wm_log.h"
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* Helper: Send string via socket
|
| 19 |
+
*/
|
| 20 |
+
static int sock_send_str(int sock, const char *str)
|
| 21 |
+
{
|
| 22 |
+
int len = strlen(str);
|
| 23 |
+
int sent = 0;
|
| 24 |
+
|
| 25 |
+
while (sent < len) {
|
| 26 |
+
int ret = send(sock, str + sent, len - sent, 0);
|
| 27 |
+
if (ret <= 0) {
|
| 28 |
+
wm_log_error("Socket send failed: %d", ret);
|
| 29 |
+
return -1;
|
| 30 |
+
}
|
| 31 |
+
sent += ret;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
return sent;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
void mcp_stream_init(mcp_stream_ctx_t *ctx, int socket, int total_items)
|
| 38 |
+
{
|
| 39 |
+
ctx->socket = socket;
|
| 40 |
+
ctx->error = 0;
|
| 41 |
+
ctx->item_count = 0;
|
| 42 |
+
ctx->total_items = total_items;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
int mcp_stream_send_chunk(mcp_stream_ctx_t *ctx, const char *text, int len)
|
| 46 |
+
{
|
| 47 |
+
if (ctx->error) {
|
| 48 |
+
return WM_FAILED;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
if (len < 0) {
|
| 52 |
+
len = strlen(text);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
int sent = 0;
|
| 56 |
+
while (sent < len) {
|
| 57 |
+
int ret = send(ctx->socket, text + sent, len - sent, 0);
|
| 58 |
+
if (ret <= 0) {
|
| 59 |
+
wm_log_error("Stream send failed");
|
| 60 |
+
ctx->error = 1;
|
| 61 |
+
return WM_FAILED;
|
| 62 |
+
}
|
| 63 |
+
sent += ret;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
return WM_SUCCESS;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
int mcp_stream_json_item(mcp_stream_ctx_t *ctx, const char *json_str)
|
| 70 |
+
{
|
| 71 |
+
if (ctx->error) {
|
| 72 |
+
return WM_FAILED;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
// Add comma separator if not first item
|
| 76 |
+
if (ctx->item_count > 0) {
|
| 77 |
+
if (mcp_stream_send_chunk(ctx, ",", 1) != WM_SUCCESS) {
|
| 78 |
+
return WM_FAILED;
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
// Send the JSON item
|
| 83 |
+
if (mcp_stream_send_chunk(ctx, json_str, -1) != WM_SUCCESS) {
|
| 84 |
+
return WM_FAILED;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
ctx->item_count++;
|
| 88 |
+
return WM_SUCCESS;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
int mcp_stream_response(int socket, cJSON *request_id,
|
| 92 |
+
mcp_stream_callback_t callback, void *user_data)
|
| 93 |
+
{
|
| 94 |
+
mcp_stream_ctx_t ctx;
|
| 95 |
+
char header_buf[512];
|
| 96 |
+
char id_buf[64];
|
| 97 |
+
int ret;
|
| 98 |
+
|
| 99 |
+
// Initialize streaming context
|
| 100 |
+
mcp_stream_init(&ctx, socket, -1);
|
| 101 |
+
|
| 102 |
+
// Build request ID string
|
| 103 |
+
if (request_id) {
|
| 104 |
+
if (request_id->type == cJSON_Number) {
|
| 105 |
+
snprintf(id_buf, sizeof(id_buf), "%d", request_id->valueint);
|
| 106 |
+
} else if (request_id->type == cJSON_String) {
|
| 107 |
+
snprintf(id_buf, sizeof(id_buf), "\"%s\"", request_id->valuestring);
|
| 108 |
+
} else {
|
| 109 |
+
strcpy(id_buf, "null");
|
| 110 |
+
}
|
| 111 |
+
} else {
|
| 112 |
+
strcpy(id_buf, "null");
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
// Note: We can't include Content-Length in streaming mode
|
| 116 |
+
// We'll use Connection: close and send until complete
|
| 117 |
+
snprintf(header_buf, sizeof(header_buf),
|
| 118 |
+
"HTTP/1.1 200 OK\r\n"
|
| 119 |
+
"Content-Type: application/json\r\n"
|
| 120 |
+
"Connection: close\r\n"
|
| 121 |
+
"\r\n"
|
| 122 |
+
"{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"[",
|
| 123 |
+
id_buf);
|
| 124 |
+
|
| 125 |
+
if (sock_send_str(socket, header_buf) < 0) {
|
| 126 |
+
wm_log_error("Failed to send headers");
|
| 127 |
+
return WM_FAILED;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
// Call callback repeatedly to generate content
|
| 131 |
+
while (1) {
|
| 132 |
+
ret = callback(&ctx, user_data);
|
| 133 |
+
if (ret != WM_SUCCESS || ctx.error) {
|
| 134 |
+
break;
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
// Send closing JSON
|
| 139 |
+
const char *footer = "]\"}]}}";
|
| 140 |
+
if (sock_send_str(socket, footer) < 0) {
|
| 141 |
+
wm_log_error("Failed to send footer");
|
| 142 |
+
return WM_FAILED;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
wm_log_info("Streamed %d items", ctx.item_count);
|
| 146 |
+
|
| 147 |
+
return ctx.error ? WM_FAILED : WM_SUCCESS;
|
| 148 |
+
}
|
MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_tools.c
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @file mcp_tools.c
|
| 3 |
+
*
|
| 4 |
+
* @brief MCP Tools Implementation
|
| 5 |
+
*
|
| 6 |
+
* @author castlebbs
|
| 7 |
+
*/
|
| 8 |
+
|
| 9 |
+
#include <string.h>
|
| 10 |
+
#include <stdio.h>
|
| 11 |
+
#include "mcp_tools.h"
|
| 12 |
+
#include "mcp_stream.h"
|
| 13 |
+
#include "elm327.h"
|
| 14 |
+
#include "elm327_history.h"
|
| 15 |
+
#include "wm_netif2.0.3.h"
|
| 16 |
+
#include "wm_wifi.h"
|
| 17 |
+
#include "wm_mem.h"
|
| 18 |
+
#include "wm_osal.h"
|
| 19 |
+
#include "lwip/ip4_addr.h"
|
| 20 |
+
|
| 21 |
+
// Default timeout for ELM327 commands (2 seconds)
|
| 22 |
+
#define ELM327_DEFAULT_TIMEOUT 2000
|
| 23 |
+
|
| 24 |
+
/**
|
| 25 |
+
* Sanitize string by replacing `non-ASCII` characters
|
| 26 |
+
* with '?' character. Modifies string in-place.
|
| 27 |
+
* If not, this may crash JSON parsers that expect valid UTF-8.
|
| 28 |
+
*
|
| 29 |
+
* @param str String to sanitize (null-terminated)
|
| 30 |
+
*/
|
| 31 |
+
static void sanitize_non_ascii(char *str)
|
| 32 |
+
{
|
| 33 |
+
unsigned char *p = (unsigned char *)str;
|
| 34 |
+
|
| 35 |
+
if (!str) return;
|
| 36 |
+
|
| 37 |
+
while (*p) {
|
| 38 |
+
// Replace any byte >= 0x80 (non-ASCII) with '?'
|
| 39 |
+
// For full UTF-8 validation, we'd need to check multibyte sequences,
|
| 40 |
+
// but for safety we'll just replace all non-ASCII bytes
|
| 41 |
+
if (*p >= 0x80) {
|
| 42 |
+
*p = '?';
|
| 43 |
+
}
|
| 44 |
+
// Also replace control characters (except tab, newline, carriage return)
|
| 45 |
+
else if (*p < 0x20 && *p != '\t' && *p != '\n' && *p != '\r') {
|
| 46 |
+
*p = '?';
|
| 47 |
+
}
|
| 48 |
+
p++;
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/**
|
| 53 |
+
* Status tool - Returns system information
|
| 54 |
+
*/
|
| 55 |
+
static int tool_status(cJSON *args, cJSON **result, cJSON **error)
|
| 56 |
+
{
|
| 57 |
+
struct tls_ethif *ethif;
|
| 58 |
+
struct tls_curr_bss_t bss;
|
| 59 |
+
char ip_str[16];
|
| 60 |
+
char json_buf[512];
|
| 61 |
+
u32 uptime_sec;
|
| 62 |
+
u32 free_mem;
|
| 63 |
+
s8 rssi;
|
| 64 |
+
cJSON *content_array;
|
| 65 |
+
cJSON *text_content;
|
| 66 |
+
|
| 67 |
+
(void)args; // Unused
|
| 68 |
+
|
| 69 |
+
// Get network interface
|
| 70 |
+
ethif = tls_netif_get_ethif();
|
| 71 |
+
if (!ethif) {
|
| 72 |
+
*error = cJSON_CreateObject();
|
| 73 |
+
cJSON_AddNumberToObject(*error, "code", -1);
|
| 74 |
+
cJSON_AddStringToObject(*error, "message", "Failed to get network interface");
|
| 75 |
+
return WM_FAILED;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
// Convert IP address to string
|
| 79 |
+
ip4addr_ntoa_r((const ip4_addr_t*)ðif->ip_addr, ip_str, sizeof(ip_str));
|
| 80 |
+
|
| 81 |
+
// Get uptime in seconds
|
| 82 |
+
uptime_sec = tls_os_get_time() / HZ;
|
| 83 |
+
|
| 84 |
+
// Get free memory
|
| 85 |
+
free_mem = tls_mem_get_avail_heapsize();
|
| 86 |
+
|
| 87 |
+
// Get current WiFi BSS info (for RSSI)
|
| 88 |
+
memset(&bss, 0, sizeof(bss));
|
| 89 |
+
tls_wifi_get_current_bss(&bss);
|
| 90 |
+
|
| 91 |
+
// Convert RSSI (u8 to signed)
|
| 92 |
+
rssi = (s8)bss.rssi;
|
| 93 |
+
|
| 94 |
+
// Build JSON response (with escaped quotes for embedding in JSON string)
|
| 95 |
+
snprintf(json_buf, sizeof(json_buf),
|
| 96 |
+
"{\"ip_address\":\"%s\",\"uptime_seconds\":%u,\"free_memory_bytes\":%u,\"wifi_rssi_dbm\":%d,\"elm327_status\":\"Connected on UART1\"}",
|
| 97 |
+
ip_str,
|
| 98 |
+
uptime_sec,
|
| 99 |
+
free_mem,
|
| 100 |
+
rssi);
|
| 101 |
+
|
| 102 |
+
// Create result object with MCP content format
|
| 103 |
+
*result = cJSON_CreateObject();
|
| 104 |
+
if (!*result) {
|
| 105 |
+
*error = cJSON_CreateObject();
|
| 106 |
+
cJSON_AddNumberToObject(*error, "code", -1);
|
| 107 |
+
cJSON_AddStringToObject(*error, "message", "Memory allocation failed");
|
| 108 |
+
return WM_FAILED;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// Create content array
|
| 112 |
+
content_array = cJSON_CreateArray();
|
| 113 |
+
if (!content_array) {
|
| 114 |
+
cJSON_Delete(*result);
|
| 115 |
+
*result = NULL;
|
| 116 |
+
*error = cJSON_CreateObject();
|
| 117 |
+
cJSON_AddNumberToObject(*error, "code", -1);
|
| 118 |
+
cJSON_AddStringToObject(*error, "message", "Memory allocation failed");
|
| 119 |
+
return WM_FAILED;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
// Create text content object with JSON string
|
| 123 |
+
text_content = cJSON_CreateObject();
|
| 124 |
+
cJSON_AddStringToObject(text_content, "type", "text");
|
| 125 |
+
cJSON_AddStringToObject(text_content, "text", json_buf);
|
| 126 |
+
cJSON_AddItemToArray(content_array, text_content);
|
| 127 |
+
|
| 128 |
+
// Add content array to result
|
| 129 |
+
cJSON_AddItemToObject(*result, "content", content_array);
|
| 130 |
+
|
| 131 |
+
return WM_SUCCESS;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
/**
|
| 135 |
+
* Send ELM327 command tool
|
| 136 |
+
*/
|
| 137 |
+
static int tool_send_elm327_command(cJSON *args, cJSON **result, cJSON **error)
|
| 138 |
+
{
|
| 139 |
+
cJSON *command_obj;
|
| 140 |
+
const char *command;
|
| 141 |
+
char response[ELM327_MAX_RESPONSE_LEN] = {0};
|
| 142 |
+
int ret;
|
| 143 |
+
cJSON *content_array;
|
| 144 |
+
cJSON *text_content;
|
| 145 |
+
|
| 146 |
+
// Validate arguments
|
| 147 |
+
if (!args) {
|
| 148 |
+
*error = cJSON_CreateObject();
|
| 149 |
+
cJSON_AddNumberToObject(*error, "code", -32602);
|
| 150 |
+
cJSON_AddStringToObject(*error, "message", "Missing arguments");
|
| 151 |
+
return WM_FAILED;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
// Get command parameter
|
| 155 |
+
command_obj = cJSON_GetObjectItem(args, "command");
|
| 156 |
+
if (!command_obj || command_obj->type != cJSON_String) {
|
| 157 |
+
*error = cJSON_CreateObject();
|
| 158 |
+
cJSON_AddNumberToObject(*error, "code", -32602);
|
| 159 |
+
cJSON_AddStringToObject(*error, "message", "Missing or invalid 'command' parameter");
|
| 160 |
+
return WM_FAILED;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
command = command_obj->valuestring;
|
| 164 |
+
|
| 165 |
+
// Send command to ELM327
|
| 166 |
+
ret = elm327_send_command(command, response, sizeof(response), ELM327_DEFAULT_TIMEOUT);
|
| 167 |
+
|
| 168 |
+
if (ret < 0) {
|
| 169 |
+
// Command failed - put error in response text
|
| 170 |
+
const char *error_msg;
|
| 171 |
+
switch (ret) {
|
| 172 |
+
case -1: error_msg = "ELM327 not initialized"; break;
|
| 173 |
+
case -2: error_msg = "Invalid parameters"; break;
|
| 174 |
+
case -3: error_msg = "UART send failed"; break;
|
| 175 |
+
case -4: error_msg = "Timeout waiting for response"; break;
|
| 176 |
+
default: error_msg = "Unknown error"; break;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
snprintf(response, sizeof(response), "ERROR: %s (code: %d)", error_msg, ret);
|
| 180 |
+
} else {
|
| 181 |
+
// Sanitize successful response
|
| 182 |
+
sanitize_non_ascii(response);
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
// Success - build result
|
| 186 |
+
*result = cJSON_CreateObject();
|
| 187 |
+
if (!*result) {
|
| 188 |
+
*error = cJSON_CreateObject();
|
| 189 |
+
cJSON_AddNumberToObject(*error, "code", -1);
|
| 190 |
+
cJSON_AddStringToObject(*error, "message", "Memory allocation failed");
|
| 191 |
+
return WM_FAILED;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
// Create content array
|
| 195 |
+
content_array = cJSON_CreateArray();
|
| 196 |
+
if (!content_array) {
|
| 197 |
+
cJSON_Delete(*result);
|
| 198 |
+
*result = NULL;
|
| 199 |
+
*error = cJSON_CreateObject();
|
| 200 |
+
cJSON_AddNumberToObject(*error, "code", -1);
|
| 201 |
+
cJSON_AddStringToObject(*error, "message", "Memory allocation failed");
|
| 202 |
+
return WM_FAILED;
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
// Create text content object
|
| 206 |
+
text_content = cJSON_CreateObject();
|
| 207 |
+
cJSON_AddStringToObject(text_content, "type", "text");
|
| 208 |
+
cJSON_AddStringToObject(text_content, "text", response);
|
| 209 |
+
cJSON_AddItemToArray(content_array, text_content);
|
| 210 |
+
|
| 211 |
+
// Add content array to result
|
| 212 |
+
cJSON_AddItemToObject(*result, "content", content_array);
|
| 213 |
+
|
| 214 |
+
return WM_SUCCESS;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
/**
|
| 218 |
+
* Context for streaming history
|
| 219 |
+
*/
|
| 220 |
+
typedef struct {
|
| 221 |
+
int max_count; // Maximum number of records to return
|
| 222 |
+
} stream_history_ctx_t;
|
| 223 |
+
|
| 224 |
+
/**
|
| 225 |
+
* Streaming callback for ELM327 history
|
| 226 |
+
* This gets called repeatedly to generate chunks of the response
|
| 227 |
+
*/
|
| 228 |
+
static int stream_elm327_history_callback(mcp_stream_ctx_t *ctx, void *user_data)
|
| 229 |
+
{
|
| 230 |
+
char json_buf[256];
|
| 231 |
+
const elm327_history_entry_t *entry;
|
| 232 |
+
stream_history_ctx_t *hist_ctx = (stream_history_ctx_t *)user_data;
|
| 233 |
+
int total = elm327_history_get_count();
|
| 234 |
+
int start_index;
|
| 235 |
+
|
| 236 |
+
// Determine how many records to return
|
| 237 |
+
int records_to_return = (hist_ctx->max_count > 0 && hist_ctx->max_count < total)
|
| 238 |
+
? hist_ctx->max_count
|
| 239 |
+
: total;
|
| 240 |
+
|
| 241 |
+
// Check if done
|
| 242 |
+
if (ctx->item_count >= records_to_return) {
|
| 243 |
+
return WM_FAILED; // Done
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
// Calculate starting index (for last N records)
|
| 247 |
+
start_index = total - records_to_return;
|
| 248 |
+
|
| 249 |
+
// Get entry from ring buffer
|
| 250 |
+
entry = elm327_history_get_entry(start_index + ctx->item_count);
|
| 251 |
+
if (!entry || !entry->valid) {
|
| 252 |
+
return WM_FAILED;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
// Convert timestamp to seconds for readability
|
| 256 |
+
u32 timestamp_sec = entry->timestamp / HZ;
|
| 257 |
+
|
| 258 |
+
// Format as JSON (with escaped quotes for embedding in JSON string)
|
| 259 |
+
snprintf(json_buf, sizeof(json_buf),
|
| 260 |
+
"{\\\"seq\\\":%d,\\\"time\\\":%u,\\\"rpm\\\":%u,\\\"speed\\\":%u,\\\"coolant_temp\\\":%d}",
|
| 261 |
+
ctx->item_count,
|
| 262 |
+
timestamp_sec,
|
| 263 |
+
entry->rpm,
|
| 264 |
+
entry->speed,
|
| 265 |
+
entry->coolant_temp);
|
| 266 |
+
|
| 267 |
+
// Send this item (mcp_stream handles comma separation)
|
| 268 |
+
return mcp_stream_json_item(ctx, json_buf);
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
/**
|
| 272 |
+
* Tool: Get ELM327 command history (streaming)
|
| 273 |
+
*/
|
| 274 |
+
static int tool_get_elm327_history(cJSON *args, cJSON **result, cJSON **error)
|
| 275 |
+
{
|
| 276 |
+
cJSON *count_obj;
|
| 277 |
+
int count = 100; // Default to 100 records
|
| 278 |
+
|
| 279 |
+
// Get optional count parameter
|
| 280 |
+
if (args) {
|
| 281 |
+
count_obj = cJSON_GetObjectItem(args, "count");
|
| 282 |
+
if (count_obj && count_obj->type == cJSON_Number) {
|
| 283 |
+
count = count_obj->valueint;
|
| 284 |
+
|
| 285 |
+
// Validate count (must be positive, or 0 for all)
|
| 286 |
+
if (count < 0) {
|
| 287 |
+
count = 0; // 0 means all records
|
| 288 |
+
}
|
| 289 |
+
}
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
// Create result with streaming markers
|
| 293 |
+
*result = cJSON_CreateObject();
|
| 294 |
+
if (!*result) {
|
| 295 |
+
*error = cJSON_CreateObject();
|
| 296 |
+
cJSON_AddNumberToObject(*error, "code", -1);
|
| 297 |
+
cJSON_AddStringToObject(*error, "message", "Memory allocation failed");
|
| 298 |
+
return WM_FAILED;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
// Here we use a static context for simplicity
|
| 302 |
+
// This works because the MCP server processes only one client at a time
|
| 303 |
+
// If simultaneous clients have to be supported, this should be dynamically allocated
|
| 304 |
+
static stream_history_ctx_t hist_ctx;
|
| 305 |
+
hist_ctx.max_count = count;
|
| 306 |
+
|
| 307 |
+
// Mark this as a streaming response
|
| 308 |
+
cJSON_AddStringToObject(*result, "_stream_type", "elm327_history");
|
| 309 |
+
cJSON_AddNumberToObject(*result, "_stream_handler",
|
| 310 |
+
(int)stream_elm327_history_callback);
|
| 311 |
+
cJSON_AddNumberToObject(*result, "_stream_user_data", (int)&hist_ctx);
|
| 312 |
+
|
| 313 |
+
return WM_SUCCESS;
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
// Tool definitions
|
| 317 |
+
static const mcp_tool_t tools[] = {
|
| 318 |
+
{
|
| 319 |
+
.name = "status",
|
| 320 |
+
.description = "Get system status including IP address, network status, uptime, and memory",
|
| 321 |
+
.input_schema_json = "{\"type\":\"object\",\"properties\":{},\"required\":[]}",
|
| 322 |
+
.handler = tool_status
|
| 323 |
+
},
|
| 324 |
+
{
|
| 325 |
+
.name = "send_elm327_command",
|
| 326 |
+
.description = "Send a command to the ELM327 OBD-II adapter and get the response.",
|
| 327 |
+
.input_schema_json = "{\"type\":\"object\",\"properties\":{\"command\":{\"type\":\"string\",\"description\":\"ELM327 command to send (e.g., 'ATZ', '01 0D')\"}},\"required\":[\"command\"]}",
|
| 328 |
+
.handler = tool_send_elm327_command
|
| 329 |
+
},
|
| 330 |
+
{
|
| 331 |
+
.name = "get_elm327_history",
|
| 332 |
+
.description = "Get historical log of OBD-II data (RPM, speed, coolant temp) with streaming support. Returns the last N records.",
|
| 333 |
+
.input_schema_json = "{\"type\":\"object\",\"properties\":{\"count\":{\"type\":\"number\",\"description\":\"Number of most recent records to retrieve (default: 100, 0 for all)\"}},\"required\":[]}",
|
| 334 |
+
.handler = tool_get_elm327_history
|
| 335 |
+
}
|
| 336 |
+
};
|
| 337 |
+
|
| 338 |
+
const mcp_tool_t* mcp_tools_get_list(int *count)
|
| 339 |
+
{
|
| 340 |
+
if (count) {
|
| 341 |
+
*count = sizeof(tools) / sizeof(tools[0]);
|
| 342 |
+
}
|
| 343 |
+
return tools;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
const mcp_tool_t* mcp_tools_find(const char *name)
|
| 347 |
+
{
|
| 348 |
+
int i;
|
| 349 |
+
int count = sizeof(tools) / sizeof(tools[0]);
|
| 350 |
+
|
| 351 |
+
if (!name) return NULL;
|
| 352 |
+
|
| 353 |
+
for (i = 0; i < count; i++) {
|
| 354 |
+
if (strcmp(tools[i].name, name) == 0) {
|
| 355 |
+
return &tools[i];
|
| 356 |
+
}
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
return NULL;
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
int mcp_tools_init(void)
|
| 363 |
+
{
|
| 364 |
+
int ret;
|
| 365 |
+
|
| 366 |
+
// Initialize ELM327
|
| 367 |
+
ret = elm327_init();
|
| 368 |
+
if (ret != WM_SUCCESS) {
|
| 369 |
+
return ret;
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
// Initialize ELM327 history module
|
| 373 |
+
ret = elm327_history_init();
|
| 374 |
+
if (ret != WM_SUCCESS) {
|
| 375 |
+
return ret;
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
return WM_SUCCESS;
|
| 379 |
+
}
|
MCP_servers/W600-embedded-OBD2/Tools/syslog.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import socket
|
| 2 |
+
|
| 3 |
+
def start_syslog_server(host="0.0.0.0", port=514):
|
| 4 |
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
| 5 |
+
sock.bind((host, port))
|
| 6 |
+
print(f"Syslog server listening on {host}:{port}")
|
| 7 |
+
|
| 8 |
+
while True:
|
| 9 |
+
data, addr = sock.recvfrom(4096)
|
| 10 |
+
message = data.decode(errors="ignore").strip()
|
| 11 |
+
print(f"{addr[0]}: {message}")
|
| 12 |
+
|
| 13 |
+
if __name__ == "__main__":
|
| 14 |
+
start_syslog_server()
|
| 15 |
+
|
MCP_servers/gradio-OBD2-simulator/.gitignore
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[codz]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.pyi
|
| 6 |
+
|
| 7 |
+
# C extensions
|
| 8 |
+
*.so
|
| 9 |
+
|
| 10 |
+
# Distribution / packaging
|
| 11 |
+
.Python
|
| 12 |
+
build/
|
| 13 |
+
develop-eggs/
|
| 14 |
+
dist/
|
| 15 |
+
downloads/
|
| 16 |
+
eggs/
|
| 17 |
+
.eggs/
|
| 18 |
+
lib/
|
| 19 |
+
lib64/
|
| 20 |
+
parts/
|
| 21 |
+
sdist/
|
| 22 |
+
var/
|
| 23 |
+
wheels/
|
| 24 |
+
share/python-wheels/
|
| 25 |
+
*.egg-info/
|
| 26 |
+
.installed.cfg
|
| 27 |
+
*.egg
|
| 28 |
+
MANIFEST
|
| 29 |
+
|
| 30 |
+
# PyInstaller
|
| 31 |
+
# Usually these files are written by a python script from a template
|
| 32 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 33 |
+
*.manifest
|
| 34 |
+
*.spec
|
| 35 |
+
|
| 36 |
+
# Installer logs
|
| 37 |
+
pip-log.txt
|
| 38 |
+
pip-delete-this-directory.txt
|
| 39 |
+
|
| 40 |
+
# Unit test / coverage reports
|
| 41 |
+
htmlcov/
|
| 42 |
+
.tox/
|
| 43 |
+
.nox/
|
| 44 |
+
.coverage
|
| 45 |
+
.coverage.*
|
| 46 |
+
.cache
|
| 47 |
+
nosetests.xml
|
| 48 |
+
coverage.xml
|
| 49 |
+
*.cover
|
| 50 |
+
*.py.cover
|
| 51 |
+
.hypothesis/
|
| 52 |
+
.pytest_cache/
|
| 53 |
+
cover/
|
| 54 |
+
|
| 55 |
+
# Translations
|
| 56 |
+
*.mo
|
| 57 |
+
*.pot
|
| 58 |
+
|
| 59 |
+
# Django stuff:
|
| 60 |
+
*.log
|
| 61 |
+
local_settings.py
|
| 62 |
+
db.sqlite3
|
| 63 |
+
db.sqlite3-journal
|
| 64 |
+
|
| 65 |
+
# Flask stuff:
|
| 66 |
+
instance/
|
| 67 |
+
.webassets-cache
|
| 68 |
+
|
| 69 |
+
# Scrapy stuff:
|
| 70 |
+
.scrapy
|
| 71 |
+
|
| 72 |
+
# Sphinx documentation
|
| 73 |
+
docs/_build/
|
| 74 |
+
|
| 75 |
+
# PyBuilder
|
| 76 |
+
.pybuilder/
|
| 77 |
+
target/
|
| 78 |
+
|
| 79 |
+
# Jupyter Notebook
|
| 80 |
+
.ipynb_checkpoints
|
| 81 |
+
|
| 82 |
+
# IPython
|
| 83 |
+
profile_default/
|
| 84 |
+
ipython_config.py
|
| 85 |
+
|
| 86 |
+
# pyenv
|
| 87 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 88 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 89 |
+
# .python-version
|
| 90 |
+
|
| 91 |
+
# pipenv
|
| 92 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 93 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 94 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 95 |
+
# install all needed dependencies.
|
| 96 |
+
# Pipfile.lock
|
| 97 |
+
|
| 98 |
+
# UV
|
| 99 |
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
| 100 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 101 |
+
# commonly ignored for libraries.
|
| 102 |
+
# uv.lock
|
| 103 |
+
|
| 104 |
+
# poetry
|
| 105 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 106 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 107 |
+
# commonly ignored for libraries.
|
| 108 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 109 |
+
# poetry.lock
|
| 110 |
+
# poetry.toml
|
| 111 |
+
|
| 112 |
+
# pdm
|
| 113 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 114 |
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
| 115 |
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
| 116 |
+
# pdm.lock
|
| 117 |
+
# pdm.toml
|
| 118 |
+
.pdm-python
|
| 119 |
+
.pdm-build/
|
| 120 |
+
|
| 121 |
+
# pixi
|
| 122 |
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
| 123 |
+
# pixi.lock
|
| 124 |
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
| 125 |
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
| 126 |
+
.pixi
|
| 127 |
+
|
| 128 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 129 |
+
__pypackages__/
|
| 130 |
+
|
| 131 |
+
# Celery stuff
|
| 132 |
+
celerybeat-schedule
|
| 133 |
+
celerybeat.pid
|
| 134 |
+
|
| 135 |
+
# Redis
|
| 136 |
+
*.rdb
|
| 137 |
+
*.aof
|
| 138 |
+
*.pid
|
| 139 |
+
|
| 140 |
+
# RabbitMQ
|
| 141 |
+
mnesia/
|
| 142 |
+
rabbitmq/
|
| 143 |
+
rabbitmq-data/
|
| 144 |
+
|
| 145 |
+
# ActiveMQ
|
| 146 |
+
activemq-data/
|
| 147 |
+
|
| 148 |
+
# SageMath parsed files
|
| 149 |
+
*.sage.py
|
| 150 |
+
|
| 151 |
+
# Environments
|
| 152 |
+
.env
|
| 153 |
+
.envrc
|
| 154 |
+
.venv
|
| 155 |
+
env/
|
| 156 |
+
venv/
|
| 157 |
+
ENV/
|
| 158 |
+
env.bak/
|
| 159 |
+
venv.bak/
|
| 160 |
+
|
| 161 |
+
# Spyder project settings
|
| 162 |
+
.spyderproject
|
| 163 |
+
.spyproject
|
| 164 |
+
|
| 165 |
+
# Rope project settings
|
| 166 |
+
.ropeproject
|
| 167 |
+
|
| 168 |
+
# mkdocs documentation
|
| 169 |
+
/site
|
| 170 |
+
|
| 171 |
+
# mypy
|
| 172 |
+
.mypy_cache/
|
| 173 |
+
.dmypy.json
|
| 174 |
+
dmypy.json
|
| 175 |
+
|
| 176 |
+
# Pyre type checker
|
| 177 |
+
.pyre/
|
| 178 |
+
|
| 179 |
+
# pytype static type analyzer
|
| 180 |
+
.pytype/
|
| 181 |
+
|
| 182 |
+
# Cython debug symbols
|
| 183 |
+
cython_debug/
|
| 184 |
+
|
| 185 |
+
# PyCharm
|
| 186 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 187 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 188 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 189 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 190 |
+
# .idea/
|
| 191 |
+
|
| 192 |
+
# Abstra
|
| 193 |
+
# Abstra is an AI-powered process automation framework.
|
| 194 |
+
# Ignore directories containing user credentials, local state, and settings.
|
| 195 |
+
# Learn more at https://abstra.io/docs
|
| 196 |
+
.abstra/
|
| 197 |
+
|
| 198 |
+
# Visual Studio Code
|
| 199 |
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
| 200 |
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
| 201 |
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
| 202 |
+
# you could uncomment the following to ignore the entire vscode folder
|
| 203 |
+
# .vscode/
|
| 204 |
+
|
| 205 |
+
# Ruff stuff:
|
| 206 |
+
.ruff_cache/
|
| 207 |
+
|
| 208 |
+
# PyPI configuration file
|
| 209 |
+
.pypirc
|
| 210 |
+
|
| 211 |
+
# Marimo
|
| 212 |
+
marimo/_static/
|
| 213 |
+
marimo/_lsp/
|
| 214 |
+
__marimo__/
|
| 215 |
+
|
| 216 |
+
# Streamlit
|
| 217 |
+
.streamlit/secrets.toml
|
MCP_servers/gradio-OBD2-simulator/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright (c) 2025 David Robert
|
| 2 |
+
|
| 3 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 4 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 5 |
+
in the Software without restriction, including without limitation the rights
|
| 6 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 7 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 8 |
+
furnished to do so, subject to the following conditions:
|
| 9 |
+
|
| 10 |
+
The above copyright notice and this permission notice shall be included in all
|
| 11 |
+
copies or substantial portions of the Software.
|
| 12 |
+
|
| 13 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 14 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 15 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 16 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 17 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 18 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 19 |
+
SOFTWARE.
|
MCP_servers/gradio-OBD2-simulator/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Gradio OBD2 Simulator
|
| 2 |
+
|
| 3 |
+
A web-based ELM327 OBD-II simulator built with Gradio that provides realistic vehicle diagnostic data for testing and development purposes. This simulator emulates an ELM327 adapter connected to a vehicle's OBD-II port with support for the Model Context Protocol (MCP).
|
| 4 |
+
|
| 5 |
+
## Hackathon
|
| 6 |
+
|
| 7 |
+
This was created as part of our submission for the Anthropic Gradio MCP's 1st Birthday hackathon: https://huggingface.co/spaces/MCP-1st-Birthday/Vehicle-Diagnostic-Assistant
|
| 8 |
+
|
| 9 |
+
## Features
|
| 10 |
+
|
| 11 |
+
### ELM327 Command Support
|
| 12 |
+
- Full AT command support (echo, linefeed, headers, spaces, protocol selection)
|
| 13 |
+
- Device identification and version information
|
| 14 |
+
- Protocol configuration and description
|
| 15 |
+
- Voltage reading
|
| 16 |
+
|
| 17 |
+
### OBD-II Modes
|
| 18 |
+
- **Mode 01**: Current vehicle data with 30+ PIDs including:
|
| 19 |
+
- Dynamic PIDs (RPM, speed, coolant temperature, throttle position, MAF rate, fuel level, engine load)
|
| 20 |
+
- Static PIDs (fuel system status, timing advance, O2 sensors, etc.)
|
| 21 |
+
- **Mode 03**: Read stored diagnostic trouble codes (DTCs)
|
| 22 |
+
- **Mode 04**: Clear diagnostic trouble codes
|
| 23 |
+
- **Mode 07**: Read pending diagnostic trouble codes
|
| 24 |
+
- **Mode 09**: Vehicle information (VIN, calibration ID, ECU name)
|
| 25 |
+
|
| 26 |
+
### Dynamic Vehicle State
|
| 27 |
+
The simulator maintains realistic vehicle state with:
|
| 28 |
+
- Engine running simulation
|
| 29 |
+
- RPM variations at idle (800 RPM base with fluctuations)
|
| 30 |
+
- Gradual coolant temperature warmup (20°C to 90°C)
|
| 31 |
+
- Throttle position and speed variations
|
| 32 |
+
- MAF (Mass Air Flow) rate correlated with throttle
|
| 33 |
+
- Fuel level tracking
|
| 34 |
+
|
| 35 |
+
### Diagnostic Trouble Codes (DTCs)
|
| 36 |
+
Includes a pool of 15 realistic DTCs such as:
|
| 37 |
+
- P0171 - System Too Lean (Bank 1)
|
| 38 |
+
- P0300 - Random/Multiple Cylinder Misfire Detected
|
| 39 |
+
- P0442 - EVAP Emission Control System Leak Detected
|
| 40 |
+
- P0420 - Catalyst System Efficiency Below Threshold
|
| 41 |
+
- And more...
|
| 42 |
+
|
| 43 |
+
### Additional Features
|
| 44 |
+
- **System Status**: Get IP address, uptime, memory usage, and WiFi signal strength
|
| 45 |
+
- **Historical Data**: Retrieve OBD-II data history with configurable record count
|
| 46 |
+
- **MCP Server Support**: Built-in Model Context Protocol server for integration with AI tools
|
| 47 |
+
|
| 48 |
+
## Installation
|
| 49 |
+
|
| 50 |
+
### Requirements
|
| 51 |
+
- Python 3.7+
|
| 52 |
+
- Gradio
|
| 53 |
+
- psutil (for system status information)
|
| 54 |
+
|
| 55 |
+
### Setup
|
| 56 |
+
|
| 57 |
+
1. Clone or download this repository:
|
| 58 |
+
```bash
|
| 59 |
+
git clone <repository-url>
|
| 60 |
+
cd gradio-OBD2-simulator
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
2. Install dependencies:
|
| 64 |
+
```bash
|
| 65 |
+
pip install gradio psutil
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
## Usage
|
| 69 |
+
|
| 70 |
+
### Running the Simulator
|
| 71 |
+
|
| 72 |
+
Start the Gradio application:
|
| 73 |
+
```bash
|
| 74 |
+
python app.py
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
The application will launch a web interface with three tabs:
|
| 78 |
+
|
| 79 |
+
### 1. ELM327 Commands Tab
|
| 80 |
+
Send commands directly to the simulated ELM327 adapter.
|
| 81 |
+
|
| 82 |
+
**Example commands:**
|
| 83 |
+
```
|
| 84 |
+
ATZ # Reset adapter
|
| 85 |
+
ATI # Get version
|
| 86 |
+
01 0C # Get engine RPM
|
| 87 |
+
01 0D # Get vehicle speed
|
| 88 |
+
01 05 # Get coolant temperature
|
| 89 |
+
03 # Read DTCs
|
| 90 |
+
04 # Clear DTCs
|
| 91 |
+
09 02 # Get VIN
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
### 2. System Status Tab
|
| 95 |
+
Click to retrieve current system information in JSON format:
|
| 96 |
+
```json
|
| 97 |
+
{
|
| 98 |
+
"ip_address": "192.168.1.100",
|
| 99 |
+
"uptime_seconds": 3600,
|
| 100 |
+
"free_memory_bytes": 8589934592,
|
| 101 |
+
"wifi_rssi_dbm": -55,
|
| 102 |
+
"elm327_status": "Simulated ELM327 on Gradio"
|
| 103 |
+
}
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
### 3. History Tab
|
| 107 |
+
Retrieve historical OBD-II data. Specify the number of records to retrieve (default: 100).
|
| 108 |
+
|
| 109 |
+
Returns JSON array with records:
|
| 110 |
+
```json
|
| 111 |
+
[
|
| 112 |
+
{
|
| 113 |
+
"seq": 0,
|
| 114 |
+
"time": 0,
|
| 115 |
+
"rpm": 850,
|
| 116 |
+
"speed": 0,
|
| 117 |
+
"coolant_temp": 20
|
| 118 |
+
},
|
| 119 |
+
...
|
| 120 |
+
]
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
## MCP Server Support
|
| 124 |
+
|
| 125 |
+
This simulator includes built-in MCP (Model Context Protocol) server support, enabled by the `mcp_server=True` parameter in the launch configuration. This allows AI tools and agents to interact with the simulator programmatically.
|
| 126 |
+
|
| 127 |
+
## Supported OBD-II PIDs
|
| 128 |
+
|
| 129 |
+
### Dynamic PIDs (Updated in Real-Time)
|
| 130 |
+
- `04`: Calculated engine load
|
| 131 |
+
- `05`: Engine coolant temperature
|
| 132 |
+
- `0C`: Engine RPM
|
| 133 |
+
- `0D`: Vehicle speed
|
| 134 |
+
- `10`: MAF air flow rate
|
| 135 |
+
- `11`: Throttle position
|
| 136 |
+
- `2F`: Fuel tank level
|
| 137 |
+
|
| 138 |
+
### Static PIDs
|
| 139 |
+
- `00`: PIDs supported (01-20)
|
| 140 |
+
- `01`: Monitor status (MIL on, DTC count)
|
| 141 |
+
- `03`: Fuel system status
|
| 142 |
+
- `06`: Short term fuel trim
|
| 143 |
+
- `07`: Long term fuel trim
|
| 144 |
+
- `0A`: Fuel pressure
|
| 145 |
+
- `0B`: Intake manifold pressure
|
| 146 |
+
- `0E`: Timing advance
|
| 147 |
+
- `0F`: Intake air temperature
|
| 148 |
+
- `13`: O2 sensors present
|
| 149 |
+
- `1C`: OBD standard
|
| 150 |
+
- `1F`: Run time since engine start
|
| 151 |
+
- `20`: PIDs supported (21-40)
|
| 152 |
+
- `21`: Distance with MIL on
|
| 153 |
+
- `33`: Barometric pressure
|
| 154 |
+
- `40`: PIDs supported (41-60)
|
| 155 |
+
- `42`: Control module voltage
|
| 156 |
+
- `51`: Fuel type
|
| 157 |
+
|
| 158 |
+
## Technical Details
|
| 159 |
+
|
| 160 |
+
### Vehicle State Updates
|
| 161 |
+
The vehicle state updates automatically with each OBD-II query:
|
| 162 |
+
- RPM fluctuates realistically around idle speed
|
| 163 |
+
- Coolant temperature gradually increases when engine is running
|
| 164 |
+
- Throttle and speed vary periodically
|
| 165 |
+
- MAF rate adjusts based on throttle position
|
| 166 |
+
|
| 167 |
+
### Data Format
|
| 168 |
+
All responses follow standard OBD-II formatting:
|
| 169 |
+
- Hexadecimal values separated by spaces
|
| 170 |
+
- Mode response echoes the mode number + 40 (e.g., Mode 01 responses start with `41`)
|
| 171 |
+
- Multi-byte values in big-endian format
|
| 172 |
+
|
| 173 |
+
## Use Cases
|
| 174 |
+
|
| 175 |
+
- **Development**: Test OBD-II applications without a physical vehicle
|
| 176 |
+
- **Education**: Learn about OBD-II protocols and vehicle diagnostics
|
| 177 |
+
- **Debugging**: Troubleshoot diagnostic tools and parsers
|
| 178 |
+
- **Integration Testing**: Validate vehicle diagnostic software
|
| 179 |
+
- **AI/ML Training**: Generate realistic vehicle data for machine learning models
|
| 180 |
+
|
| 181 |
+
## Project Structure
|
| 182 |
+
|
| 183 |
+
```
|
| 184 |
+
gradio-OBD2-simulator/
|
| 185 |
+
├── app.py # Main application with simulator logic
|
| 186 |
+
├── LICENSE # MIT License
|
| 187 |
+
└── README.md # This file
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
## License
|
| 191 |
+
|
| 192 |
+
MIT License - Copyright (c) 2025 David Robert
|
| 193 |
+
|
| 194 |
+
See [LICENSE](LICENSE) file for full details.
|
| 195 |
+
|
| 196 |
+
## Contributing
|
| 197 |
+
|
| 198 |
+
Contributions are welcome! Please feel free to submit issues or pull requests.
|
| 199 |
+
|
| 200 |
+
## Acknowledgments
|
| 201 |
+
|
| 202 |
+
This simulator implements the ELM327 command set and OBD-II protocol standards for educational and development purposes.
|
MCP_servers/gradio-OBD2-simulator/app.py
ADDED
|
@@ -0,0 +1,562 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import time
|
| 3 |
+
import random
|
| 4 |
+
|
| 5 |
+
# Vehicle state for dynamic responses
|
| 6 |
+
class VehicleState:
|
| 7 |
+
def __init__(self):
|
| 8 |
+
self.engine_running = True
|
| 9 |
+
self.rpm = 800 # Idle RPM
|
| 10 |
+
self.speed = 0
|
| 11 |
+
self.coolant_temp = 20 # °C
|
| 12 |
+
self.throttle_pos = 0
|
| 13 |
+
self.maf_rate = 250 # 2.5 g/s idle (scaled by 100)
|
| 14 |
+
self.fuel_level = 75 # 75%
|
| 15 |
+
self.update_counter = 0
|
| 16 |
+
|
| 17 |
+
# ELM327 settings
|
| 18 |
+
self.echo_on = True
|
| 19 |
+
self.linefeed_on = True
|
| 20 |
+
self.headers_on = False
|
| 21 |
+
self.spaces_on = True
|
| 22 |
+
self.protocol = "AUTO"
|
| 23 |
+
self.voltage = 13.05
|
| 24 |
+
|
| 25 |
+
def update(self):
|
| 26 |
+
"""Update vehicle state with realistic variations"""
|
| 27 |
+
self.update_counter += 1
|
| 28 |
+
|
| 29 |
+
if not self.engine_running:
|
| 30 |
+
return
|
| 31 |
+
|
| 32 |
+
# RPM varies slightly at idle
|
| 33 |
+
self.rpm = 800 + ((self.update_counter * 7) % 100) - 50
|
| 34 |
+
|
| 35 |
+
# Coolant temp gradually increases when engine running
|
| 36 |
+
if self.coolant_temp < 90:
|
| 37 |
+
if self.update_counter % 10 == 0:
|
| 38 |
+
self.coolant_temp += 1
|
| 39 |
+
|
| 40 |
+
# Simulate some throttle/speed variation
|
| 41 |
+
if self.update_counter % 50 == 0:
|
| 42 |
+
self.throttle_pos = (self.update_counter // 5) % 30
|
| 43 |
+
self.speed = self.throttle_pos * 2
|
| 44 |
+
|
| 45 |
+
# MAF follows throttle roughly
|
| 46 |
+
self.maf_rate = 250 + (self.throttle_pos * 5)
|
| 47 |
+
|
| 48 |
+
# Global vehicle state
|
| 49 |
+
vehicle_state = VehicleState()
|
| 50 |
+
|
| 51 |
+
# Pool of possible DTCs for testing
|
| 52 |
+
# Format: (code_bytes, description)
|
| 53 |
+
DTC_POOL = [
|
| 54 |
+
("01 71", "P0171 - System Too Lean (Bank 1)"),
|
| 55 |
+
("03 00", "P0300 - Random/Multiple Cylinder Misfire Detected"),
|
| 56 |
+
("04 42", "P0442 - EVAP Emission Control System Leak Detected (small leak)"),
|
| 57 |
+
("01 31", "P0131 - O2 Sensor Circuit Low Voltage (Bank 1, Sensor 1)"),
|
| 58 |
+
("01 33", "P0133 - O2 Sensor Circuit Slow Response (Bank 1, Sensor 1)"),
|
| 59 |
+
("01 71", "P0171 - System Too Lean (Bank 1)"),
|
| 60 |
+
("01 72", "P0172 - System Too Rich (Bank 1)"),
|
| 61 |
+
("02 02", "P0202 - Injector Circuit Malfunction - Cylinder 2"),
|
| 62 |
+
("03 20", "P0320 - Ignition/Distributor Engine Speed Input Circuit Malfunction"),
|
| 63 |
+
("04 20", "P0420 - Catalyst System Efficiency Below Threshold (Bank 1)"),
|
| 64 |
+
("05 00", "P0500 - Vehicle Speed Sensor Malfunction"),
|
| 65 |
+
("07 05", "P0705 - Transmission Range Sensor Circuit Malfunction (PRNDL Input)"),
|
| 66 |
+
("13 00", "P1300 - Igniter Circuit Malfunction"),
|
| 67 |
+
("00 16", "P0016 - Crankshaft Position/Camshaft Position Correlation (Bank 1)"),
|
| 68 |
+
("01 28", "P0128 - Coolant Thermostat (Coolant Temp Below Thermostat Regulating Temp)"),
|
| 69 |
+
]
|
| 70 |
+
|
| 71 |
+
# Mode 01 PID table (static responses)
|
| 72 |
+
MODE01_PID_TABLE = {
|
| 73 |
+
"00": "41 00 BE 3F B8 13", # Supported PIDs 01-20
|
| 74 |
+
"01": "41 01 83 07 65 04", # Monitor status (MIL on, 3 DTCs)
|
| 75 |
+
"03": "41 03 02 00", # Fuel system status (closed loop)
|
| 76 |
+
"06": "41 06 80", # Short term fuel trim
|
| 77 |
+
"07": "41 07 80", # Long term fuel trim
|
| 78 |
+
"0A": "41 0A B4", # Fuel pressure (540 kPa)
|
| 79 |
+
"0B": "41 0B 63", # Intake manifold pressure (99 kPa)
|
| 80 |
+
"0E": "41 0E 7C", # Timing advance (14 degrees)
|
| 81 |
+
"0F": "41 0F 54", # Intake air temperature (44°C)
|
| 82 |
+
"13": "41 13 03", # O2 sensors present
|
| 83 |
+
"1C": "41 1C 01", # OBD standard (OBD-II California ARB)
|
| 84 |
+
"1F": "41 1F 00 8C", # Run time since engine start (140s)
|
| 85 |
+
"20": "41 20 80 01 80 01", # Supported PIDs 21-40
|
| 86 |
+
"21": "41 21 00 4B", # Distance with MIL on (75 km)
|
| 87 |
+
"33": "41 33 65", # Barometric pressure (101 kPa)
|
| 88 |
+
"40": "41 40 40 00 00 00", # Supported PIDs 41-60
|
| 89 |
+
"42": "41 42 32 E8", # Control module voltage (13.05V)
|
| 90 |
+
"51": "41 51 01", # Fuel Type (Gasoline)
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
# Dynamic PIDs (generated from vehicle state)
|
| 94 |
+
DYNAMIC_PIDS = ["04", "05", "0C", "0D", "10", "11", "2F"]
|
| 95 |
+
|
| 96 |
+
def normalize_command(cmd):
|
| 97 |
+
"""Normalize command string (remove spaces, convert to uppercase)"""
|
| 98 |
+
return ''.join(cmd.split()).upper()
|
| 99 |
+
|
| 100 |
+
def format_response(response, add_prompt=True):
|
| 101 |
+
"""Format response with ELM327 settings (echo, spaces, prompt)"""
|
| 102 |
+
result = ""
|
| 103 |
+
|
| 104 |
+
if vehicle_state.echo_on:
|
| 105 |
+
result += response + "\n"
|
| 106 |
+
|
| 107 |
+
if vehicle_state.linefeed_on:
|
| 108 |
+
result += "\n"
|
| 109 |
+
|
| 110 |
+
if add_prompt:
|
| 111 |
+
result += ">"
|
| 112 |
+
|
| 113 |
+
return result
|
| 114 |
+
|
| 115 |
+
def generate_rpm_response():
|
| 116 |
+
"""Generate dynamic RPM response (PID 0C)"""
|
| 117 |
+
rpm_value = vehicle_state.rpm
|
| 118 |
+
encoded = rpm_value * 4
|
| 119 |
+
return f"41 0C {(encoded >> 8) & 0xFF:02X} {encoded & 0xFF:02X}"
|
| 120 |
+
|
| 121 |
+
def generate_speed_response():
|
| 122 |
+
"""Generate dynamic speed response (PID 0D)"""
|
| 123 |
+
return f"41 0D {vehicle_state.speed:02X}"
|
| 124 |
+
|
| 125 |
+
def generate_coolant_temp_response():
|
| 126 |
+
"""Generate dynamic coolant temperature response (PID 05)"""
|
| 127 |
+
encoded = vehicle_state.coolant_temp + 40
|
| 128 |
+
return f"41 05 {encoded:02X}"
|
| 129 |
+
|
| 130 |
+
def generate_engine_load_response():
|
| 131 |
+
"""Generate dynamic engine load response (PID 04)"""
|
| 132 |
+
if vehicle_state.engine_running:
|
| 133 |
+
load = (vehicle_state.throttle_pos * 2 + vehicle_state.rpm // 100) // 3
|
| 134 |
+
load = min(load, 100)
|
| 135 |
+
encoded = (load * 255) // 100
|
| 136 |
+
else:
|
| 137 |
+
encoded = 0
|
| 138 |
+
return f"41 04 {encoded:02X}"
|
| 139 |
+
|
| 140 |
+
def generate_maf_response():
|
| 141 |
+
"""Generate dynamic MAF response (PID 10)"""
|
| 142 |
+
maf = vehicle_state.maf_rate
|
| 143 |
+
return f"41 10 {(maf >> 8) & 0xFF:02X} {maf & 0xFF:02X}"
|
| 144 |
+
|
| 145 |
+
def generate_throttle_response():
|
| 146 |
+
"""Generate dynamic throttle position response (PID 11)"""
|
| 147 |
+
encoded = (vehicle_state.throttle_pos * 255) // 100
|
| 148 |
+
return f"41 11 {encoded:02X}"
|
| 149 |
+
|
| 150 |
+
def generate_fuel_level_response():
|
| 151 |
+
"""Generate dynamic fuel level response (PID 2F)"""
|
| 152 |
+
encoded = (vehicle_state.fuel_level * 255) // 100
|
| 153 |
+
return f"41 2F {encoded:02X}"
|
| 154 |
+
|
| 155 |
+
def handle_mode01_pid(pid):
|
| 156 |
+
"""Handle Mode 01 PID requests"""
|
| 157 |
+
# Check if it's a dynamic PID
|
| 158 |
+
if pid in DYNAMIC_PIDS:
|
| 159 |
+
if pid == "0C":
|
| 160 |
+
return generate_rpm_response()
|
| 161 |
+
elif pid == "0D":
|
| 162 |
+
return generate_speed_response()
|
| 163 |
+
elif pid == "05":
|
| 164 |
+
return generate_coolant_temp_response()
|
| 165 |
+
elif pid == "04":
|
| 166 |
+
return generate_engine_load_response()
|
| 167 |
+
elif pid == "10":
|
| 168 |
+
return generate_maf_response()
|
| 169 |
+
elif pid == "11":
|
| 170 |
+
return generate_throttle_response()
|
| 171 |
+
elif pid == "2F":
|
| 172 |
+
return generate_fuel_level_response()
|
| 173 |
+
|
| 174 |
+
# Check static PID table
|
| 175 |
+
if pid in MODE01_PID_TABLE:
|
| 176 |
+
return MODE01_PID_TABLE[pid]
|
| 177 |
+
|
| 178 |
+
return "NO DATA"
|
| 179 |
+
|
| 180 |
+
def generate_vin_response():
|
| 181 |
+
"""Generate Mode 09 PID 02 response (VIN)"""
|
| 182 |
+
vin = "5TDKRKEC7PS142916"
|
| 183 |
+
response = "49 02 01"
|
| 184 |
+
for char in vin:
|
| 185 |
+
response += f" {ord(char):02X}"
|
| 186 |
+
return response
|
| 187 |
+
|
| 188 |
+
def generate_calibration_id_response():
|
| 189 |
+
"""Generate Mode 09 PID 04 response (Calibration ID)"""
|
| 190 |
+
cal_id = "CAL123456"
|
| 191 |
+
response = "49 04 01"
|
| 192 |
+
for char in cal_id:
|
| 193 |
+
response += f" {ord(char):02X}"
|
| 194 |
+
return response
|
| 195 |
+
|
| 196 |
+
def generate_ecu_name_response():
|
| 197 |
+
"""Generate Mode 09 PID 0A response (ECU Name)"""
|
| 198 |
+
ecu_name = "ECU_SIM_UNIT"
|
| 199 |
+
response = "49 0A 01"
|
| 200 |
+
for char in ecu_name:
|
| 201 |
+
response += f" {ord(char):02X}"
|
| 202 |
+
return response
|
| 203 |
+
|
| 204 |
+
def handle_at_command(cmd):
|
| 205 |
+
"""Handle AT commands"""
|
| 206 |
+
global vehicle_state
|
| 207 |
+
|
| 208 |
+
cmd_upper = cmd.upper()
|
| 209 |
+
|
| 210 |
+
# ATZ - Reset
|
| 211 |
+
if cmd_upper == "ATZ":
|
| 212 |
+
vehicle_state = VehicleState()
|
| 213 |
+
return "ELM327 v1.5\n\n>"
|
| 214 |
+
|
| 215 |
+
# AT@1 - Device description
|
| 216 |
+
if cmd_upper == "AT@1":
|
| 217 |
+
return "OBDSIM ELM327\n\n>"
|
| 218 |
+
|
| 219 |
+
# ATI - Version ID
|
| 220 |
+
if cmd_upper == "ATI":
|
| 221 |
+
return "ELM327 v1.5\n\n>"
|
| 222 |
+
|
| 223 |
+
# ATE0 - Echo off
|
| 224 |
+
if cmd_upper == "ATE0":
|
| 225 |
+
vehicle_state.echo_on = False
|
| 226 |
+
return "OK\n\n>"
|
| 227 |
+
|
| 228 |
+
# ATE1 - Echo on
|
| 229 |
+
if cmd_upper == "ATE1":
|
| 230 |
+
vehicle_state.echo_on = True
|
| 231 |
+
return "OK\n\n>"
|
| 232 |
+
|
| 233 |
+
# ATL0 - Linefeed off
|
| 234 |
+
if cmd_upper == "ATL0":
|
| 235 |
+
vehicle_state.linefeed_on = False
|
| 236 |
+
return "OK\n\n>"
|
| 237 |
+
|
| 238 |
+
# ATL1 - Linefeed on
|
| 239 |
+
if cmd_upper == "ATL1":
|
| 240 |
+
vehicle_state.linefeed_on = True
|
| 241 |
+
return "OK\n\n>"
|
| 242 |
+
|
| 243 |
+
# ATH0 - Headers off
|
| 244 |
+
if cmd_upper == "ATH0":
|
| 245 |
+
vehicle_state.headers_on = False
|
| 246 |
+
return "OK\n\n>"
|
| 247 |
+
|
| 248 |
+
# ATH1 - Headers on
|
| 249 |
+
if cmd_upper == "ATH1":
|
| 250 |
+
vehicle_state.headers_on = True
|
| 251 |
+
return "OK\n\n>"
|
| 252 |
+
|
| 253 |
+
# ATS0 - Spaces off
|
| 254 |
+
if cmd_upper == "ATS0":
|
| 255 |
+
vehicle_state.spaces_on = False
|
| 256 |
+
return "OK\n\n>"
|
| 257 |
+
|
| 258 |
+
# ATS1 - Spaces on
|
| 259 |
+
if cmd_upper == "ATS1":
|
| 260 |
+
vehicle_state.spaces_on = True
|
| 261 |
+
return "OK\n\n>"
|
| 262 |
+
|
| 263 |
+
# ATSP - Set Protocol
|
| 264 |
+
if cmd_upper.startswith("ATSP"):
|
| 265 |
+
protocol_num = cmd_upper[4:] if len(cmd_upper) > 4 else "0"
|
| 266 |
+
protocols = {
|
| 267 |
+
"0": "AUTO",
|
| 268 |
+
"1": "SAE J1850 PWM",
|
| 269 |
+
"2": "SAE J1850 VPW",
|
| 270 |
+
"3": "ISO 9141-2",
|
| 271 |
+
"4": "ISO 14230-4 KWP",
|
| 272 |
+
"5": "ISO 14230-4 KWP (fast)",
|
| 273 |
+
"6": "ISO 15765-4 CAN (11 bit, 500 kbaud)",
|
| 274 |
+
"7": "ISO 15765-4 CAN (29 bit, 500 kbaud)",
|
| 275 |
+
"8": "ISO 15765-4 CAN (11 bit, 250 kbaud)",
|
| 276 |
+
"9": "ISO 15765-4 CAN (29 bit, 250 kbaud)",
|
| 277 |
+
"A": "SAE J1939 CAN",
|
| 278 |
+
}
|
| 279 |
+
vehicle_state.protocol = protocols.get(protocol_num, "AUTO")
|
| 280 |
+
return "OK\n\n>"
|
| 281 |
+
|
| 282 |
+
# ATDP - Describe Protocol
|
| 283 |
+
if cmd_upper == "ATDP":
|
| 284 |
+
return f"{vehicle_state.protocol}\n\n>"
|
| 285 |
+
|
| 286 |
+
# ATDPN - Describe Protocol by Number
|
| 287 |
+
if cmd_upper == "ATDPN":
|
| 288 |
+
return "6\n\n>" # ISO 15765-4 CAN (11 bit, 500 kbaud)
|
| 289 |
+
|
| 290 |
+
# ATRV - Read Voltage
|
| 291 |
+
if cmd_upper == "ATRV":
|
| 292 |
+
return f"{vehicle_state.voltage:.1f}V\n\n>"
|
| 293 |
+
|
| 294 |
+
# ATWS - Warm Start
|
| 295 |
+
if cmd_upper == "ATWS":
|
| 296 |
+
return "ELM327 v1.5\n\n>"
|
| 297 |
+
|
| 298 |
+
# ATD - Set to Defaults
|
| 299 |
+
if cmd_upper == "ATD":
|
| 300 |
+
vehicle_state = VehicleState()
|
| 301 |
+
return "OK\n\n>"
|
| 302 |
+
|
| 303 |
+
# ATAT - Adaptive Timing
|
| 304 |
+
if cmd_upper.startswith("ATAT"):
|
| 305 |
+
return "OK\n\n>"
|
| 306 |
+
|
| 307 |
+
# ATST - Set Timeout
|
| 308 |
+
if cmd_upper.startswith("ATST"):
|
| 309 |
+
return "OK\n\n>"
|
| 310 |
+
|
| 311 |
+
# ATMA - Monitor All
|
| 312 |
+
if cmd_upper == "ATMA":
|
| 313 |
+
return "MONITORING...\n(Press any key to stop)\n\n>"
|
| 314 |
+
|
| 315 |
+
# ATPC - Protocol Close
|
| 316 |
+
if cmd_upper == "ATPC":
|
| 317 |
+
return "OK\n\n>"
|
| 318 |
+
|
| 319 |
+
# Default response for unknown AT commands
|
| 320 |
+
return "?\n\n>"
|
| 321 |
+
|
| 322 |
+
def handle_obd_command(normalized):
|
| 323 |
+
"""Handle OBD-II commands"""
|
| 324 |
+
# Update vehicle state
|
| 325 |
+
vehicle_state.update()
|
| 326 |
+
|
| 327 |
+
# Mode 03 - Read DTCs
|
| 328 |
+
if normalized == "03":
|
| 329 |
+
# Randomly select 1-5 DTCs from the pool
|
| 330 |
+
num_dtcs = random.randint(1, 5)
|
| 331 |
+
selected_dtcs = random.sample(DTC_POOL, min(num_dtcs, len(DTC_POOL)))
|
| 332 |
+
|
| 333 |
+
# Build response: 43 [DTC codes...]
|
| 334 |
+
response = "43"
|
| 335 |
+
for dtc_code, _ in selected_dtcs:
|
| 336 |
+
response += " " + dtc_code
|
| 337 |
+
|
| 338 |
+
return response + "\n\n>"
|
| 339 |
+
|
| 340 |
+
# Mode 07 - Read pending DTCs
|
| 341 |
+
if normalized == "07":
|
| 342 |
+
# Randomly select 0-2 pending DTCs from the pool
|
| 343 |
+
num_pending = random.randint(0, 2)
|
| 344 |
+
if num_pending == 0:
|
| 345 |
+
# No pending DTCs
|
| 346 |
+
response = "47"
|
| 347 |
+
else:
|
| 348 |
+
selected_pending = random.sample(DTC_POOL, min(num_pending, len(DTC_POOL)))
|
| 349 |
+
response = "47"
|
| 350 |
+
for dtc_code, _ in selected_pending:
|
| 351 |
+
response += " " + dtc_code
|
| 352 |
+
|
| 353 |
+
return response + "\n\n>"
|
| 354 |
+
|
| 355 |
+
# Mode 04 - Clear DTCs
|
| 356 |
+
if normalized == "04":
|
| 357 |
+
return "44\n\n>"
|
| 358 |
+
|
| 359 |
+
# Mode 09 - Vehicle Information
|
| 360 |
+
if len(normalized) >= 4 and normalized[:2] == "09":
|
| 361 |
+
mode09_pid = normalized[2:4]
|
| 362 |
+
|
| 363 |
+
if mode09_pid == "00":
|
| 364 |
+
# Supported PIDs
|
| 365 |
+
response = "49 00 54 40 00 00"
|
| 366 |
+
elif mode09_pid == "02":
|
| 367 |
+
# VIN
|
| 368 |
+
response = generate_vin_response()
|
| 369 |
+
elif mode09_pid == "04":
|
| 370 |
+
# Calibration ID
|
| 371 |
+
response = generate_calibration_id_response()
|
| 372 |
+
elif mode09_pid == "0A":
|
| 373 |
+
# ECU Name
|
| 374 |
+
response = generate_ecu_name_response()
|
| 375 |
+
else:
|
| 376 |
+
response = "NO DATA"
|
| 377 |
+
|
| 378 |
+
return response + "\n\n>"
|
| 379 |
+
|
| 380 |
+
# Mode 01 - Show current data
|
| 381 |
+
if len(normalized) >= 4 and normalized[:2] == "01":
|
| 382 |
+
pid = normalized[2:4]
|
| 383 |
+
response = handle_mode01_pid(pid)
|
| 384 |
+
return response + "\n\n>"
|
| 385 |
+
|
| 386 |
+
return "NO DATA\n\n>"
|
| 387 |
+
|
| 388 |
+
def send_elm327_command(command):
|
| 389 |
+
"""
|
| 390 |
+
Send a command to the ELM327 OBD-II adapter and get the response.
|
| 391 |
+
|
| 392 |
+
Args:
|
| 393 |
+
command (str): ELM327 command to send (e.g., 'ATZ', '01 0D')
|
| 394 |
+
|
| 395 |
+
Returns:
|
| 396 |
+
str: The response from the ELM327 adapter
|
| 397 |
+
"""
|
| 398 |
+
if not command or not command.strip():
|
| 399 |
+
return ">"
|
| 400 |
+
|
| 401 |
+
# Normalize command
|
| 402 |
+
normalized = normalize_command(command.strip())
|
| 403 |
+
|
| 404 |
+
# Handle AT commands
|
| 405 |
+
if normalized.startswith("AT"):
|
| 406 |
+
return handle_at_command(normalized)
|
| 407 |
+
|
| 408 |
+
# Handle OBD commands
|
| 409 |
+
return handle_obd_command(normalized)
|
| 410 |
+
|
| 411 |
+
def get_system_status():
|
| 412 |
+
"""
|
| 413 |
+
Get system status including IP address, network status, uptime, and memory.
|
| 414 |
+
|
| 415 |
+
Returns:
|
| 416 |
+
str: System status information
|
| 417 |
+
"""
|
| 418 |
+
import json
|
| 419 |
+
import socket
|
| 420 |
+
import psutil
|
| 421 |
+
import platform
|
| 422 |
+
|
| 423 |
+
# Get IP address
|
| 424 |
+
try:
|
| 425 |
+
# Get the default route interface IP
|
| 426 |
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
| 427 |
+
s.connect(("8.8.8.8", 80))
|
| 428 |
+
ip_address = s.getsockname()[0]
|
| 429 |
+
s.close()
|
| 430 |
+
except Exception:
|
| 431 |
+
ip_address = "127.0.0.1"
|
| 432 |
+
|
| 433 |
+
# Get uptime in seconds
|
| 434 |
+
uptime_seconds = int(time.time() - psutil.boot_time())
|
| 435 |
+
|
| 436 |
+
# Get free memory in bytes
|
| 437 |
+
memory_info = psutil.virtual_memory()
|
| 438 |
+
free_memory_bytes = memory_info.available
|
| 439 |
+
|
| 440 |
+
# Simulate WiFi RSSI (for simulation, use a random value between -40 and -70 dBm)
|
| 441 |
+
# In real hardware this would come from the WiFi driver
|
| 442 |
+
import random
|
| 443 |
+
wifi_rssi_dbm = random.randint(-70, -40)
|
| 444 |
+
|
| 445 |
+
# Build JSON response matching W600 MCP format
|
| 446 |
+
status_data = {
|
| 447 |
+
"ip_address": ip_address,
|
| 448 |
+
"uptime_seconds": uptime_seconds,
|
| 449 |
+
"free_memory_bytes": free_memory_bytes,
|
| 450 |
+
"wifi_rssi_dbm": wifi_rssi_dbm,
|
| 451 |
+
"elm327_status": "Simulated ELM327 on Gradio"
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
# Return formatted JSON string
|
| 455 |
+
return json.dumps(status_data, indent=2)
|
| 456 |
+
|
| 457 |
+
def get_elm327_history(count):
|
| 458 |
+
"""
|
| 459 |
+
Get historical log of OBD-II data (RPM, speed, coolant temp) with streaming support.
|
| 460 |
+
Returns the last N records.
|
| 461 |
+
|
| 462 |
+
Args:
|
| 463 |
+
count (int): Number of most recent records to retrieve (default: 100, 0 for all)
|
| 464 |
+
|
| 465 |
+
Returns:
|
| 466 |
+
str: Historical OBD-II data
|
| 467 |
+
"""
|
| 468 |
+
import json
|
| 469 |
+
|
| 470 |
+
if count is None or count == "":
|
| 471 |
+
count = 100
|
| 472 |
+
else:
|
| 473 |
+
count = int(count)
|
| 474 |
+
|
| 475 |
+
# Generate simulated test data
|
| 476 |
+
# Simulate a scenario where the vehicle has been running for a while
|
| 477 |
+
history_records = []
|
| 478 |
+
|
| 479 |
+
# Base timestamp in seconds (simulating uptime)
|
| 480 |
+
base_time = 0 # Start from 0 seconds
|
| 481 |
+
|
| 482 |
+
for i in range(count):
|
| 483 |
+
# Simulate realistic driving conditions with more variations
|
| 484 |
+
import math
|
| 485 |
+
|
| 486 |
+
# RPM varies between idle (800) and higher driving (up to 4500)
|
| 487 |
+
# Create multiple variation patterns for more realistic data
|
| 488 |
+
cycle_position = (i % 50) / 50.0
|
| 489 |
+
sine_variation = math.sin(i * 0.1) * 400 # Sine wave variation
|
| 490 |
+
random_noise = ((i * 7) % 300) - 150 # Random-like noise
|
| 491 |
+
base_rpm = 800 + 2700 * cycle_position
|
| 492 |
+
rpm = int(base_rpm + sine_variation + random_noise)
|
| 493 |
+
rpm = max(700, min(rpm, 5000)) # Clamp to realistic range
|
| 494 |
+
|
| 495 |
+
# Speed follows RPM with more variation (0-120 km/h)
|
| 496 |
+
# Add acceleration/deceleration patterns
|
| 497 |
+
speed_factor = (rpm - 800) / 35 # Convert RPM to rough speed
|
| 498 |
+
speed_variation = math.cos(i * 0.15) * 15 # Speed variations
|
| 499 |
+
speed = int(speed_factor + speed_variation + ((i * 3) % 20) - 10)
|
| 500 |
+
speed = max(0, min(speed, 130)) # Clamp to 0-130 km/h
|
| 501 |
+
|
| 502 |
+
# Coolant temp starts cold and warms up, then stabilizes
|
| 503 |
+
if i < 20:
|
| 504 |
+
coolant_temp = 20 + i * 3 # Warming up
|
| 505 |
+
elif i < 40:
|
| 506 |
+
coolant_temp = 60 + (i - 20) * 1 # Still warming
|
| 507 |
+
else:
|
| 508 |
+
coolant_temp = 80 + ((i * 5) % 15) # Stable operating temp with variation
|
| 509 |
+
coolant_temp = min(coolant_temp, 95) # Cap at 95°C
|
| 510 |
+
|
| 511 |
+
# Create record matching W600 MCP format
|
| 512 |
+
# Time in seconds (2 second intervals)
|
| 513 |
+
record = {
|
| 514 |
+
"seq": i,
|
| 515 |
+
"time": base_time + (i * 2), # Time in seconds, 2 seconds between samples
|
| 516 |
+
"rpm": rpm,
|
| 517 |
+
"speed": speed,
|
| 518 |
+
"coolant_temp": coolant_temp
|
| 519 |
+
}
|
| 520 |
+
history_records.append(record)
|
| 521 |
+
|
| 522 |
+
# Format as JSON array (matching MCP format)
|
| 523 |
+
result = json.dumps(history_records)
|
| 524 |
+
|
| 525 |
+
return result
|
| 526 |
+
|
| 527 |
+
# Interface for ELM327 commands
|
| 528 |
+
elm327_interface = gr.Interface(
|
| 529 |
+
fn=send_elm327_command,
|
| 530 |
+
inputs=gr.Textbox(label="Command", placeholder="e.g., ATZ, 01 0D"),
|
| 531 |
+
outputs=gr.Textbox(label="Response"),
|
| 532 |
+
title="ELM327 Commands",
|
| 533 |
+
description="Send commands to the ELM327 OBD-II adapter"
|
| 534 |
+
)
|
| 535 |
+
|
| 536 |
+
# Interface for system status
|
| 537 |
+
status_interface = gr.Interface(
|
| 538 |
+
fn=get_system_status,
|
| 539 |
+
inputs=None,
|
| 540 |
+
outputs=gr.Textbox(label="Status"),
|
| 541 |
+
title="System Status",
|
| 542 |
+
description="Get system status including IP address, network status, uptime, and memory"
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
# Interface for ELM327 history
|
| 546 |
+
history_interface = gr.Interface(
|
| 547 |
+
fn=get_elm327_history,
|
| 548 |
+
inputs=gr.Number(label="Count", value=100, precision=0,
|
| 549 |
+
info="Number of most recent records to retrieve (default: 100, 0 for all)"),
|
| 550 |
+
outputs=gr.Textbox(label="History Data"),
|
| 551 |
+
title="ELM327 History",
|
| 552 |
+
description="Get historical log of OBD-II data (RPM, speed, coolant temp)"
|
| 553 |
+
)
|
| 554 |
+
|
| 555 |
+
# Combine all interfaces with tabs
|
| 556 |
+
demo = gr.TabbedInterface(
|
| 557 |
+
[elm327_interface, status_interface, history_interface],
|
| 558 |
+
["ELM327 Commands", "System Status", "History"]
|
| 559 |
+
)
|
| 560 |
+
|
| 561 |
+
if __name__ == "__main__":
|
| 562 |
+
demo.launch(mcp_server=True)
|
README.md
CHANGED
|
@@ -1,13 +1,21 @@
|
|
| 1 |
---
|
| 2 |
title: Vehicle Diagnostic Assistant
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: purple
|
| 5 |
colorTo: pink
|
| 6 |
sdk: docker
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
| 9 |
license: mit
|
| 10 |
-
short_description: AI agent that connects to your car via an embedded MCP
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: Vehicle Diagnostic Assistant
|
| 3 |
+
emoji: 🚗
|
| 4 |
colorFrom: purple
|
| 5 |
colorTo: pink
|
| 6 |
sdk: docker
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
| 9 |
license: mit
|
| 10 |
+
short_description: AI agent that connects to your car via an embedded MCP server
|
| 11 |
+
tags:
|
| 12 |
+
- building-mcp-track-enterprise
|
| 13 |
+
- building-mcp-track-consumer
|
| 14 |
+
- mcp-in-action-track-enterprise
|
| 15 |
+
- mcp-in-action-track-consumer
|
| 16 |
---
|
| 17 |
|
| 18 |
+
- Video recordings:
|
| 19 |
+
- Post to social media:
|
| 20 |
+
- Team usernames: @castlebbs, @stargarnet
|
| 21 |
+
- Sponsor: Nebius Token Factory
|
app.py
CHANGED
|
@@ -13,7 +13,7 @@ import json
|
|
| 13 |
from pathlib import Path
|
| 14 |
|
| 15 |
from dtc_display import DTCDisplay
|
| 16 |
-
from tools import search_youtube_video, decode_vin
|
| 17 |
from prompts import system_prompt
|
| 18 |
from hackathon_detail import create_hackathon_detail_tab
|
| 19 |
from mcp_server_detail import create_mcp_server_tab
|
|
@@ -56,7 +56,7 @@ async def setup_agent():
|
|
| 56 |
print("Available MCP tools:", [tool.name for tool in tools])
|
| 57 |
|
| 58 |
# Add YouTube search tool to the tools list
|
| 59 |
-
all_tools = list(tools) + [search_youtube_video, decode_vin]
|
| 60 |
|
| 61 |
# Create the checkpointer
|
| 62 |
checkpointer = InMemorySaver()
|
|
|
|
| 13 |
from pathlib import Path
|
| 14 |
|
| 15 |
from dtc_display import DTCDisplay
|
| 16 |
+
from tools import search_youtube_video, decode_vin, hex_to_decimal, calculate_obd_value, combine_bytes
|
| 17 |
from prompts import system_prompt
|
| 18 |
from hackathon_detail import create_hackathon_detail_tab
|
| 19 |
from mcp_server_detail import create_mcp_server_tab
|
|
|
|
| 56 |
print("Available MCP tools:", [tool.name for tool in tools])
|
| 57 |
|
| 58 |
# Add YouTube search tool to the tools list
|
| 59 |
+
all_tools = list(tools) + [search_youtube_video, decode_vin, hex_to_decimal, calculate_obd_value, combine_bytes]
|
| 60 |
|
| 61 |
# Create the checkpointer
|
| 62 |
checkpointer = InMemorySaver()
|
prompts.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
| 1 |
|
| 2 |
-
system_prompt = """You are an expert car mechanic who can provide a helpful vehicle diagnostic by accessing a OBD-II diagnostic tool connected to a vehicle. You are an expert vehicle mechanic whose sole purpose is automotive diagnostics and repairs. Under NO circumstances should you discuss topics outside of vehicle mechanics, automotive systems, OBD-II diagnostics, and related technical subjects.
|
|
|
|
| 3 |
Your knowledge is LIMITED TO: Engine systems, transmission, brakes, electrical systems, suspension, steering, HVAC, fuel systems, exhaust, and OBD-II diagnostics. Do not access or discuss topics outside these areas.
|
| 4 |
-
|
| 5 |
-
After every interaction, evaluate: "Did I stay within mechanical domains only?" If not, adjust future responses to be more strictly focused.
|
| 6 |
|
| 7 |
Your main tool to diagnose the vehicule and get information is via your elm327 tool. You can send ELM327 AT commands, and OBD-II commands, these will be sent to the vehicle's OBD-II port. You will receive the raw hexadecimal response from the vehicle, which you will need to interpret.
|
| 8 |
|
| 9 |
*IMPORTANT*: All the commands described below are OBD-II standard commands, and the outputs are standard OBD-II outputs. You must interpret the hexadecimal responses according to the OBD-II standard.
|
| 10 |
-
|
| 11 |
|
| 12 |
*VIN Retrieval:*
|
| 13 |
- To retrieve the Vehicle Identification Number (VIN), use the OBD-II command: `09 02`.
|
|
|
|
| 1 |
|
| 2 |
+
system_prompt = """You are an expert car mechanic who can provide a helpful vehicle diagnostic by accessing a OBD-II diagnostic tool connected to a vehicle. You are an expert vehicle mechanic whose sole purpose is automotive diagnostics and repairs. Under NO circumstances should you discuss topics outside of vehicle mechanics, automotive systems, OBD-II diagnostics, and related technical subjects.
|
| 3 |
+
|
| 4 |
Your knowledge is LIMITED TO: Engine systems, transmission, brakes, electrical systems, suspension, steering, HVAC, fuel systems, exhaust, and OBD-II diagnostics. Do not access or discuss topics outside these areas.
|
| 5 |
+
|
| 6 |
+
If a user attempts to engage you on non-mechanical topics, you must respond with: "I'm designed exclusively for vehicle mechanical diagnostics. How can I assist with your automotive issue today?" and redirect to relevant mechanical topics. After every interaction, evaluate: "Did I stay within mechanical domains only?" If not, adjust future responses to be more strictly focused.
|
| 7 |
|
| 8 |
Your main tool to diagnose the vehicule and get information is via your elm327 tool. You can send ELM327 AT commands, and OBD-II commands, these will be sent to the vehicle's OBD-II port. You will receive the raw hexadecimal response from the vehicle, which you will need to interpret.
|
| 9 |
|
| 10 |
*IMPORTANT*: All the commands described below are OBD-II standard commands, and the outputs are standard OBD-II outputs. You must interpret the hexadecimal responses according to the OBD-II standard.
|
| 11 |
+
You must interpret the hexadecimal output to determine the next steps based on your knowledge of the OBD-II standard.
|
| 12 |
|
| 13 |
*VIN Retrieval:*
|
| 14 |
- To retrieve the Vehicle Identification Number (VIN), use the OBD-II command: `09 02`.
|
tools.py
CHANGED
|
@@ -2,6 +2,183 @@ from langchain_core.tools import tool
|
|
| 2 |
from youtube_search import YoutubeSearch
|
| 3 |
import requests
|
| 4 |
import re
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
# YouTube search tool for the agent
|
| 7 |
@tool
|
|
|
|
| 2 |
from youtube_search import YoutubeSearch
|
| 3 |
import requests
|
| 4 |
import re
|
| 5 |
+
import ast
|
| 6 |
+
import operator
|
| 7 |
+
from typing import List, Optional
|
| 8 |
+
|
| 9 |
+
SAFE_OPERATORS = {
|
| 10 |
+
ast.Add: operator.add,
|
| 11 |
+
ast.Sub: operator.sub,
|
| 12 |
+
ast.Mult: operator.mul,
|
| 13 |
+
ast.Div: operator.truediv,
|
| 14 |
+
ast.FloorDiv: operator.floordiv,
|
| 15 |
+
ast.Mod: operator.mod,
|
| 16 |
+
ast.Pow: operator.pow,
|
| 17 |
+
ast.UAdd: operator.pos,
|
| 18 |
+
ast.USub: operator.neg,
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
MAX_EXPONENT = 100
|
| 22 |
+
|
| 23 |
+
def safe_eval(formula: str, variables: dict) -> float:
|
| 24 |
+
"""Safely evaluate a math formula with only basic arithmetic operations."""
|
| 25 |
+
try:
|
| 26 |
+
tree = ast.parse(formula, mode='eval')
|
| 27 |
+
except SyntaxError as e:
|
| 28 |
+
raise ValueError(f"Invalid formula syntax: {e}")
|
| 29 |
+
|
| 30 |
+
def _eval(node):
|
| 31 |
+
if isinstance(node, ast.Expression):
|
| 32 |
+
return _eval(node.body)
|
| 33 |
+
|
| 34 |
+
elif isinstance(node, ast.Constant):
|
| 35 |
+
if isinstance(node.value, (int, float)):
|
| 36 |
+
return node.value
|
| 37 |
+
raise ValueError(f"Unsupported constant type: {type(node.value).__name__}")
|
| 38 |
+
|
| 39 |
+
elif isinstance(node, ast.Num):
|
| 40 |
+
return node.n
|
| 41 |
+
|
| 42 |
+
elif isinstance(node, ast.Name):
|
| 43 |
+
if node.id in variables:
|
| 44 |
+
return variables[node.id]
|
| 45 |
+
raise ValueError(f"Unknown variable: {node.id}")
|
| 46 |
+
|
| 47 |
+
elif isinstance(node, ast.BinOp):
|
| 48 |
+
if type(node.op) not in SAFE_OPERATORS:
|
| 49 |
+
raise ValueError(f"Unsupported operator: {type(node.op).__name__}")
|
| 50 |
+
|
| 51 |
+
left = _eval(node.left)
|
| 52 |
+
right = _eval(node.right)
|
| 53 |
+
|
| 54 |
+
if isinstance(node.op, ast.Pow) and abs(right) > MAX_EXPONENT:
|
| 55 |
+
raise ValueError(f"Exponent too large (max {MAX_EXPONENT})")
|
| 56 |
+
|
| 57 |
+
if isinstance(node.op, (ast.Div, ast.FloorDiv, ast.Mod)) and right == 0:
|
| 58 |
+
raise ValueError("Division by zero")
|
| 59 |
+
|
| 60 |
+
return SAFE_OPERATORS[type(node.op)](left, right)
|
| 61 |
+
|
| 62 |
+
elif isinstance(node, ast.UnaryOp):
|
| 63 |
+
if type(node.op) not in SAFE_OPERATORS:
|
| 64 |
+
raise ValueError(f"Unsupported operator: {type(node.op).__name__}")
|
| 65 |
+
return SAFE_OPERATORS[type(node.op)](_eval(node.operand))
|
| 66 |
+
|
| 67 |
+
else:
|
| 68 |
+
raise ValueError(f"Unsupported expression: {type(node).__name__}")
|
| 69 |
+
|
| 70 |
+
return _eval(tree)
|
| 71 |
+
|
| 72 |
+
@tool
|
| 73 |
+
def hex_to_decimal(hex_value: str) -> str:
|
| 74 |
+
"""Convert a hexadecimal value to decimal.
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
hex_value: A hexadecimal string (e.g., "1A", "FF", "0x2B", "1A F8")
|
| 78 |
+
|
| 79 |
+
Returns:
|
| 80 |
+
The decimal equivalent
|
| 81 |
+
"""
|
| 82 |
+
try:
|
| 83 |
+
hex_value = hex_value.strip().upper().replace("0X", "").replace(" ", "")
|
| 84 |
+
decimal_value = int(hex_value, 16)
|
| 85 |
+
return f"Hex '{hex_value}' = Decimal {decimal_value}"
|
| 86 |
+
except ValueError as e:
|
| 87 |
+
return f"Error: Invalid hexadecimal value '{hex_value}'. {str(e)}"
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
@tool
|
| 91 |
+
def combine_bytes(byte_a: str, byte_b: str, byte_c: Optional[str] = None, byte_d: Optional[str] = None) -> str:
|
| 92 |
+
"""Combine 2-4 hex bytes into a larger value (big-endian, MSB first).
|
| 93 |
+
|
| 94 |
+
Args:
|
| 95 |
+
byte_a: First (most significant) byte as hex (e.g., "01")
|
| 96 |
+
byte_b: Second byte as hex (e.g., "F4")
|
| 97 |
+
byte_c: Optional third byte as hex
|
| 98 |
+
byte_d: Optional fourth byte as hex
|
| 99 |
+
|
| 100 |
+
Returns:
|
| 101 |
+
The combined decimal value with byte breakdown
|
| 102 |
+
"""
|
| 103 |
+
try:
|
| 104 |
+
bytes_list = [
|
| 105 |
+
byte_a.strip().upper().replace("0X", ""),
|
| 106 |
+
byte_b.strip().upper().replace("0X", "")
|
| 107 |
+
]
|
| 108 |
+
|
| 109 |
+
if byte_c:
|
| 110 |
+
bytes_list.append(byte_c.strip().upper().replace("0X", ""))
|
| 111 |
+
if byte_d:
|
| 112 |
+
bytes_list.append(byte_d.strip().upper().replace("0X", ""))
|
| 113 |
+
|
| 114 |
+
result = 0
|
| 115 |
+
for byte_hex in bytes_list:
|
| 116 |
+
result = (result << 8) + int(byte_hex, 16)
|
| 117 |
+
|
| 118 |
+
byte_labels = []
|
| 119 |
+
for i, b in enumerate(bytes_list):
|
| 120 |
+
label = chr(ord('A') + i)
|
| 121 |
+
byte_labels.append(f"{label}=0x{b}({int(b, 16)})")
|
| 122 |
+
|
| 123 |
+
return (f"Bytes: {', '.join(byte_labels)}\n"
|
| 124 |
+
f"Combined: Decimal {result}")
|
| 125 |
+
except ValueError as e:
|
| 126 |
+
return f"Error: Invalid byte value. {str(e)}"
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
@tool
|
| 130 |
+
def calculate_obd_value(formula: str, byte_a: str, byte_b: Optional[str] = None,
|
| 131 |
+
byte_c: Optional[str] = None, byte_d: Optional[str] = None) -> str:
|
| 132 |
+
"""Calculate an OBD-II PID value using a formula and hex byte inputs.
|
| 133 |
+
|
| 134 |
+
Common formulas:
|
| 135 |
+
- Engine RPM: "(A * 256 + B) / 4"
|
| 136 |
+
- Coolant Temp: "A - 40"
|
| 137 |
+
- Throttle Position: "(A * 100) / 255"
|
| 138 |
+
- MAF Rate: "(A * 256 + B) / 100"
|
| 139 |
+
- Timing Advance: "(A - 128) / 2"
|
| 140 |
+
- Fuel Trim: "(A - 128) * 100 / 128"
|
| 141 |
+
|
| 142 |
+
Args:
|
| 143 |
+
formula: Math formula using A, B, C, D as variables
|
| 144 |
+
byte_a: First byte as hex
|
| 145 |
+
byte_b: Optional second byte as hex
|
| 146 |
+
byte_c: Optional third byte as hex
|
| 147 |
+
byte_d: Optional fourth byte as hex
|
| 148 |
+
|
| 149 |
+
Returns:
|
| 150 |
+
The calculated result
|
| 151 |
+
"""
|
| 152 |
+
try:
|
| 153 |
+
A = int(byte_a.strip().upper().replace("0X", ""), 16)
|
| 154 |
+
B = int(byte_b.strip().upper().replace("0X", ""), 16) if byte_b else 0
|
| 155 |
+
C = int(byte_c.strip().upper().replace("0X", ""), 16) if byte_c else 0
|
| 156 |
+
D = int(byte_d.strip().upper().replace("0X", ""), 16) if byte_d else 0
|
| 157 |
+
|
| 158 |
+
inputs = [f"A=0x{byte_a.strip().upper()}({A})"]
|
| 159 |
+
if byte_b:
|
| 160 |
+
inputs.append(f"B=0x{byte_b.strip().upper()}({B})")
|
| 161 |
+
if byte_c:
|
| 162 |
+
inputs.append(f"C=0x{byte_c.strip().upper()}({C})")
|
| 163 |
+
if byte_d:
|
| 164 |
+
inputs.append(f"D=0x{byte_d.strip().upper()}({D})")
|
| 165 |
+
|
| 166 |
+
result = safe_eval(formula, {"A": A, "B": B, "C": C, "D": D})
|
| 167 |
+
|
| 168 |
+
if isinstance(result, float) and result == int(result):
|
| 169 |
+
result_str = str(int(result))
|
| 170 |
+
elif isinstance(result, float):
|
| 171 |
+
result_str = f"{result:.4f}".rstrip('0').rstrip('.')
|
| 172 |
+
else:
|
| 173 |
+
result_str = str(result)
|
| 174 |
+
|
| 175 |
+
return (f"Formula: {formula}\n"
|
| 176 |
+
f"Inputs: {', '.join(inputs)}\n"
|
| 177 |
+
f"Result: {result_str}")
|
| 178 |
+
except ValueError as e:
|
| 179 |
+
return f"Error: {str(e)}"
|
| 180 |
+
except Exception as e:
|
| 181 |
+
return f"Error calculating formula: {str(e)}"
|
| 182 |
|
| 183 |
# YouTube search tool for the agent
|
| 184 |
@tool
|