castlebbs commited on
Commit
f4d9469
·
1 Parent(s): 81c59a7

Initial commit

Browse files
Files changed (3) hide show
  1. .gitignore +217 -0
  2. app.py +562 -0
  3. requirements.txt +2 -0
.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
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)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio
2
+ psutil