castlebbs commited on
Commit
85f345d
·
1 Parent(s): 79b15fc

Many updates, adding code for the various MCP servers

Browse files
Files changed (47) hide show
  1. LICENSE +19 -0
  2. MCP_servers/W600-embedded-OBD2/.gitignore +46 -0
  3. MCP_servers/W600-embedded-OBD2/App/main.c +170 -0
  4. MCP_servers/W600-embedded-OBD2/App/wifi_credentials.h.template +8 -0
  5. MCP_servers/W600-embedded-OBD2/App/wm_app_config.h +11 -0
  6. MCP_servers/W600-embedded-OBD2/Bin/firmware_server.py +24 -0
  7. MCP_servers/W600-embedded-OBD2/Include/App/elm327.h +91 -0
  8. MCP_servers/W600-embedded-OBD2/Include/App/elm327_history.h +66 -0
  9. MCP_servers/W600-embedded-OBD2/Include/App/mcp_http.h +93 -0
  10. MCP_servers/W600-embedded-OBD2/Include/App/mcp_jsonrpc.h +79 -0
  11. MCP_servers/W600-embedded-OBD2/Include/App/mcp_methods.h +66 -0
  12. MCP_servers/W600-embedded-OBD2/Include/App/mcp_server.h +45 -0
  13. MCP_servers/W600-embedded-OBD2/Include/App/mcp_stream.h +97 -0
  14. MCP_servers/W600-embedded-OBD2/Include/App/mcp_tools.h +58 -0
  15. MCP_servers/W600-embedded-OBD2/LICENSE +19 -0
  16. MCP_servers/W600-embedded-OBD2/README.md +424 -0
  17. MCP_servers/W600-embedded-OBD2/Src/App/easylogger/Makefile +52 -0
  18. MCP_servers/W600-embedded-OBD2/Src/App/easylogger/inc/elog.h +260 -0
  19. MCP_servers/W600-embedded-OBD2/Src/App/easylogger/inc/elog_cfg.h +75 -0
  20. MCP_servers/W600-embedded-OBD2/Src/App/easylogger/port/elog_port.c +444 -0
  21. MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog.c +750 -0
  22. MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog_async.c +354 -0
  23. MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog_buf.c +104 -0
  24. MCP_servers/W600-embedded-OBD2/Src/App/easylogger/src/elog_utils.c +103 -0
  25. MCP_servers/W600-embedded-OBD2/Src/App/elm327/Makefile +44 -0
  26. MCP_servers/W600-embedded-OBD2/Src/App/elm327/README_SIMULATION.md +313 -0
  27. MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327.c +312 -0
  28. MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327_history.c +275 -0
  29. MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327_sim.c +589 -0
  30. MCP_servers/W600-embedded-OBD2/Src/App/elm327/elm327_sim.h +69 -0
  31. MCP_servers/W600-embedded-OBD2/Src/App/mcp/Makefile +54 -0
  32. MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_http.c +291 -0
  33. MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_jsonrpc.c +183 -0
  34. MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_methods.c +167 -0
  35. MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_server.c +489 -0
  36. MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_server_rawip.c +512 -0
  37. MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_stream.c +148 -0
  38. MCP_servers/W600-embedded-OBD2/Src/App/mcp/mcp_tools.c +379 -0
  39. MCP_servers/W600-embedded-OBD2/Tools/syslog.py +15 -0
  40. MCP_servers/gradio-OBD2-simulator/.gitignore +217 -0
  41. MCP_servers/gradio-OBD2-simulator/LICENSE +19 -0
  42. MCP_servers/gradio-OBD2-simulator/README.md +202 -0
  43. MCP_servers/gradio-OBD2-simulator/app.py +562 -0
  44. README.md +11 -3
  45. app.py +2 -2
  46. prompts.py +5 -4
  47. 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*)&ethif->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 serve
 
 
 
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
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
- 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.
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
- DO NOT retry a command unless you have an error. Having hexadecimal output is NOT an error, it means the command was successful, even if there are no fault codes stored. You must interpret the hexadecimal output to determine the next steps based on your knowledge of the OBD-II standard.
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