├── LICENSE ├── README.md ├── images ├── battery.png ├── face-angry.png ├── face-cool.png ├── face-sad.png ├── face-surprise.png ├── obd-architecture.odg ├── obd-gui-comms.png ├── obd-gui-dtc-tab.png ├── obd-gui-ss1.png ├── obd-gui-ss2.png ├── obd-gui-ss3.png ├── obd-gui-ss4.png ├── obd-monitor-gui.png ├── package_settings.png ├── preferences-system-network.png └── troubleshoot_red_icon.png ├── resources ├── bleachbit.svg ├── cairo_text_extents.c ├── gnome-monitor.svg ├── gtk+-3.0.pc ├── gtk-utf8-string-error.png ├── obd-gui-windows.jpg ├── oil-pressure.txt ├── pacman-commands.txt ├── pkg-config-gtk3.txt ├── setroubleshoot_icon.svg └── setroubleshoot_red_icon.svg └── src ├── Makefile ├── Makefile.win ├── config.c ├── config.h ├── dtc_hash_map.c ├── dtc_hash_map.h ├── ecu_simulator.c ├── gui_dialogs.c ├── gui_dialogs.h ├── gui_gauges.c ├── gui_gauges.h ├── gui_gauges_aux.c ├── gui_gauges_aux.h ├── log.c ├── obd_gui_log.txt ├── obd_monitor.h ├── obd_monitor_gui.c ├── obd_monitor_server.c ├── obd_server_log.txt ├── pid_hash_map.c ├── pid_hash_map.h ├── protocols.c ├── protocols.h ├── rs232.c ├── rs232.h ├── sockets.c ├── test_serial_rxtx.c ├── test_server.c ├── tinyexpr.c ├── tinyexpr.h ├── unit_test.c ├── unit_tests_log.txt ├── uthash.h ├── util.c └── winsockets.c /README.md: -------------------------------------------------------------------------------- 1 | # OBD-Monitor 2 | Tools for interfacing with vehicle engine control units using the OBD-II protocol. 3 | 4 | 5 | ## STATUS: Experimental - Do NOT use if your life depends on it. 6 | 7 | ## 1. Introduction 8 | 9 | The On-Board Diagnostics II (OBD-II) standard is a mandatory requirement for 10 | all vehicles manufactured since 1997 in many countries. It specifies a number 11 | of protocols that manufacturers must implement for diagnostic scan tools to 12 | communicate with engine control units (ECU). These protocols are used for 13 | monitoring and troubleshooting vehicle drive trains, chassis and electrical 14 | systems. Implementing a hardware interface is relatively easy with a basic 15 | knowledge of electronics and one of the OBD interpreter kits available from 16 | popular electronics stores (See section 5). 17 | 18 | 19 | ## 2. Design 20 | 21 | Implemented with two processes, a graphical user interface and a communications server process. 22 | 23 | 1. GUI: GTK+3.0 user interface. 24 | 25 | 2. OBD Communications: a process that implements RS232 comms with the OBD-II interface and TCP/IP comms with the GUI. 26 | 27 | 28 | ## 3. User Interface 29 | 30 | 31 | 1. Main Gauge Panel with OBD-II protocol drop down list, nine gauges, PID information and DTC code windows. 32 | 33 | 2. Auxilliary Gauge Panel. 34 | 35 | 3. PID Information and Search Panel. 36 | 37 | 4. Communications Log Panel. 38 | 39 | 40 | ### 3.1 Screenshots 41 | 42 | !["GUI"](https://github.com/dchad/OBD-Monitor/blob/master/images/obd-gui-ss4.png "GUI Prototype") 43 | 44 | #### Screenshot 1: Main Gauges. 45 | 46 | 47 | 48 | 49 | !["GUI"](https://github.com/dchad/OBD-Monitor/blob/master/images/obd-gui-ss3.png "GUI Prototype") 50 | 51 | #### Screenshot 2: User Defined Gauges. 52 | 53 | 54 | 55 | 56 | !["GUI"](https://github.com/dchad/OBD-Monitor/blob/master/images/obd-gui-ss2.png "GUI Prototype") 57 | 58 | #### Screenshot 3: PID Database. 59 | 60 | 61 | 62 | 63 | !["GUI"](https://github.com/dchad/OBD-Monitor/blob/master/images/obd-gui-ss1.png "GUI Prototype") 64 | 65 | #### Screenshot 4: Communications Log. 66 | 67 | 68 | 69 | 70 | !["GUI"](https://github.com/dchad/OBD-Monitor/blob/master/resources/obd-gui-windows.jpg "Windows GUI Prototype") 71 | 72 | #### Screenshot 5: GUI running on Windows 8.1 73 | 74 | 75 | 76 | ## 4. On-Board Diagnostics 77 | 78 | ### 4.1 OBD Standards 79 | 80 | 1 - OBD-II as defined by the CARB 81 | 82 | 2 - OBD as defined by the EPA 83 | 84 | 3 - OBD and OBD-II 85 | 86 | 4 - OBD-I 87 | 88 | 5 - Not OBD compliant 89 | 90 | 6 - EOBD (Europe) 91 | 92 | 7 - EOBD and OBD-II 93 | 94 | 8 - EOBD and OBD 95 | 96 | 9 - EOBD, OBD and OBD II 97 | 98 | 10 - JOBD (Japan) 99 | 100 | 11 - JOBD and OBD II 101 | 102 | 12 - JOBD and EOBD 103 | 104 | 13 - JOBD, EOBD, and OBD II 105 | 106 | 14 - Reserved 107 | 108 | 15 - Reserved 109 | 110 | 16 - Reserved 111 | 112 | 17 - Engine Manufacturer Diagnostics (EMD) 113 | 114 | 18 - Engine Manufacturer Diagnostics Enhanced (EMD+) 115 | 116 | 19 - Heavy Duty On-Board Diagnostics (Child/Partial) (HD OBD-C) 117 | 118 | 20 - Heavy Duty On-Board Diagnostics (HD OBD) 119 | 120 | 21 - World Wide Harmonized OBD (WWH OBD) 121 | 122 | 22 - Reserved 123 | 124 | 23 - Heavy Duty Euro OBD Stage I without NOx control (HD EOBD-I) 125 | 126 | 24 - Heavy Duty Euro OBD Stage I with NOx control (HD EOBD-I N) 127 | 128 | 25 - Heavy Duty Euro OBD Stage II without NOx control (HD EOBD-II) 129 | 130 | 26 - Heavy Duty Euro OBD Stage II with NOx control (HD EOBD-II N) 131 | 132 | 27 - Reserved 133 | 134 | 28 - Brazil OBD Phase 1 (OBDBr-1) 135 | 136 | 29 - Brazil OBD Phase 2 (OBDBr-2) 137 | 138 | 30 - Korean OBD (KOBD) 139 | 140 | 31 - India OBD I (IOBD I) 141 | 142 | 32 - India OBD II (IOBD II) 143 | 144 | 33 - Heavy Duty Euro OBD Stage VI (HD EOBD-IV) 145 | 146 | 34-250 - Reserved 147 | 148 | 251-255 - Not available for assignment (SAE J1939 special meaning) 149 | 150 | 151 | ### 4.2 OSI and SAE Protocols 152 | 153 | 1 - SAE J1850 PWM (41.6 kbaud)(Ford) 154 | 155 | 2 - SAE J1850 VPW (10.4 kbaud)(GM, Isuzu) 156 | 157 | 3 - IS0 9141-2 (5 baud init, 10.4 kbaud)(Chrysler) 158 | 159 | 4 - ISO 14230-4 KWP2000 (5-baud init.) 160 | 161 | 5 - IS0 14230-4 KWP2000 (Fast init.) 162 | 163 | 6 - IS0 15765-4 CAN (11 bit ID, 500 kbaud) 164 | 165 | 7 - IS0 15765-4 CAN (29 bit ID, 500 kbaud) 166 | 167 | 8 - IS0 15765-4 CAN (11 bit ID, 250 kbaud) 168 | 169 | 9 - IS0 15765-4 CAN (29 bit ID, 250 kbaud) 170 | 171 | A - SAE J1939 CAN (29 bit ID, 250 kbaud) 172 | 173 | B - USER1 CAN (11 bit ID, 125 kbaud) 174 | 175 | C - USER2 CAN (11 bit ID, 50 kbaud) 176 | 177 | Note: From 2008 all vehicles must support Controller Area Network (CAN-Bus) protocols. 178 | 179 | 180 | ### 4.3 Diagnostic Test Modes 181 | 182 | The SAE J1979 standard currently defines ten possible diagnostic test modes: 183 | 184 | 01 - show current data 185 | 186 | 02 - show freeze frame data 187 | 188 | 03 - show diagnostic trouble codes 189 | 190 | 04 - clear trouble codes and stored values 191 | 192 | 05 - test results, oxygen sensors 193 | 194 | 06 - test results, non-continuously monitored 195 | 196 | 07 - show pending trouble codes 197 | 198 | 08 - special control mode 199 | 200 | 09 - request vehicle information 201 | 202 | 0A - request permanent trouble codes 203 | 204 | 205 | ### 4.4 Diagnostic Trouble Codes (DTC) 206 | 207 | TODO: 208 | 209 | ### 4.5 Parameter Identifiers (PID) 210 | 211 | Parameter identifiers represent values maintained by the ECU, most are sensor 212 | values obtained by monitoring the numerous drive train, chassis and body 213 | sensors on the vehicle. The PIDs are hexadecimal values encoded as ASCII 214 | characters for communication between ELM327 based OBD interpreters and the 215 | laptop/tablet/smartphone client device. 216 | 217 | #### 4.5.1 Selected ECU Mode 01 Parameters: 218 | 219 | [PID] [Data Bytes] [Min Value] [Max Value] [Formula] [Description] 220 | 221 | 01 4 Bit Encoded (Monitor status since DTCs cleared) 222 | 223 | 04 1 0 100 100 / 255 * A (Calculated Engine Load) 224 | 225 | 05 1 -40 215 A - 40 (ECT Centigrade) 226 | 227 | 0A 1 0 765 3 * A (Fuel Pressure kPa) 228 | 229 | 0B 1 0 255 A (MAP Pressure kPa) 230 | 231 | 0C 2 0 16,383.75 (256 * A + B) / 4 (Engine RPM) 232 | 233 | 0D 1 0 255 A (Vehicle Speed) 234 | 235 | 0E 1 -64 63.5 (A / 2) - 64 (Timing Advance: degrees before TDC) 236 | 237 | 0F 1 -40 215 A - 40 (IAT Centigrade) 238 | 239 | 11 1 0 100 100 / 255 * A (Throttle Position %) 240 | 241 | 22 2 0 5177.265 0.079(256*A + B) (Fuel Rail Pressure) 242 | 243 | 23 2 0 655,350 10(256*A + B) (Fuel Rail Gauge Pressure) 244 | 245 | 2F 1 0 100 100 / 255 * A (Fuel Tank Level %) 246 | 247 | 45 1 0 100 100 / 255 * A (Relative Throttle Position %) 248 | 249 | 59 2 0 655,350 10(256*A + B) (Fuel Rail Absolute Pressure) 250 | 251 | 5A 1 0 100 100 / 255 * A (Relative Accelerator Pedal Position %) 252 | 253 | 5C 1 -40 215 A - 40 (Oil Temperature) 254 | 255 | 5E 2 0 3276.75 (256 * A + B) / 20 (Fuel Flow Rate L/h) 256 | 257 | (Oil Pressure - manufacturer proprietary codes.) (Mode 22 PID 115C - GM) 258 | 259 | 260 | 261 | 262 | #### 4.5.2 Selected ECU Mode 09 Parameters: 263 | 264 | [PID] [Data Bytes] [Description] 265 | 266 | 02 17 VIN - Vehicle Identification Number 267 | 268 | 0A 20 ECU Name 269 | 270 | 271 | ## 5. Hardware Interfaces 272 | 273 | ### 5.1 Integrated Circuits and Projects 274 | 275 | 1. ELM327 OBD Interpreter - https://www.elmelectronics.com/wp-content/uploads/2016/07/ELM327DS.pdf 276 | 2. STN1110 OBD Interpreter - http://www.obdsol.com/solutions/chips/stn1110/ 277 | 3. Sparkfun Car Diagnostics Kit - https://www.sparkfun.com/products/10769 278 | 4. Altronics OBD Interpreter Kit - http://www.altronics.com.au/p/k4065-obdii-automotive-interpreter-kit/ 279 | 5. Silicon Chip Magazine Feb 2010 Issue: https://www.siliconchip.com.au/ 280 | 281 | ### 5.2 Pics 282 | 283 | TODO: 284 | 285 | ## 6. Acknowledgements 286 | 287 | 1. Lewis Van Winkle, TinyExpr Math Evaluator: https://github.com/codeplea/tinyexpr.git 288 | 2. Teunis van Beelen, RS232 Serial Communications: https://www.teuniz.net/RS-232/ 289 | 3. Troy D. Hanson, UTHASH List/Map Data Structures: http://troydhanson.github.com/uthash/ 290 | 4. Jan Bodnar, GTK+ Tutorial and Example Code: http://zetcode.com/ 291 | 292 | ## 7. Building and Troubleshooting Notes 293 | 294 | ### 7.1 Building on Linux. 295 | 296 | apt install libgtk-3-dev 297 | 298 | git clone https://github.com/dchad/OBD-Monitor.git 299 | 300 | cd OBD-Monitor/src 301 | 302 | make all 303 | 304 | 305 | 306 | Make targets: 307 | 308 | make all 309 | 310 | make gui 311 | 312 | make server 313 | 314 | make simulator (for development and testing the GUI, simulates the server and ECU) 315 | 316 | make utests (unit tests) 317 | 318 | make ftests (server functional tests and logging when connected to an ECU, replaces GUI) 319 | 320 | make stests (USB-Serial Port interface testing with a loopback cable) 321 | 322 | 323 | ### 7.2 Building on Windows 324 | 325 | To build with MSYS2 on Microsoft Windows download and install MSYS2 (www.msys2.org). 326 | 327 | Open an MSYS2 terminal and install GTK+ as per the instructions here (www.gtk.org/download/windows.php). 328 | 329 | Check that Git is installed and pull the OBD-Monitor source code. 330 | 331 | Build with the command: make -f Makefile.win 332 | 333 | 334 | #### 7.2.1 Includes for GTK applications (avoid using pkg-config it is another nightmare dependency): 335 | 336 | INCLUDES=-I/mingw64/include/gtk-3.0 -I/mingw64/include/dbus-1.0 -I/mingw64/lib/dbus-1.0/include \ 337 | -I/mingw64/include/gio-win32-2.0/ -I/mingw64/include/cairo -I/mingw64/include/pango-1.0 \ 338 | -I/mingw64/include/harfbuzz -I/mingw64/include/pixman-1 -I/mingw64/include/freetype2 \ 339 | -I/mingw64/include/libpng16 -I/mingw64/include/gdk-pixbuf-2.0 -I/mingw64/include/glib-2.0 \ 340 | -I/mingw64/lib/glib-2.0/include -I/mingw64/include/atk-1.0 341 | 342 | 343 | #### 7.2.2 Libraries for GTK applications (avoid using pkg-config it is another nightmare dependency): 344 | 345 | LIBS=-lm -lgtk-3.dll -lgdk-3.dll -lpangocairo-1.0.dll -lpango-1.0.dll -latk-1.0.dll -lcairo-gobject.dll \ 346 | -lcairo.dll -lgdk_pixbuf-2.0.dll -lgio-2.0.dll -lgobject-2.0.dll -lglib-2.0.dll -lwsock32 347 | 348 | LIBDIRS=-L/mingw64/lib -L/mingw64/lib/gtk-3.0 -L/mingw64/lib/glib-2.0 349 | 350 | 351 | #### 7.2.3 MSYS2 Environment Path 352 | 353 | Add /mingw64/bin to the PATH or gcc will do nothing and exit without reporting any errors! 354 | 355 | 356 | #### 7.2.4 Get a list of dependencies for distribution with the application: 357 | 358 | ldd gtkapp.exe 359 | 360 | 361 | #### 7.2.5 List dependencies and copy to the current directory: 362 | 363 | ldd gtkapp.exe | grep '\/mingw.*\.dll' -o | xargs -I{} cp "{}" . 364 | 365 | This command copies the dlls to the current directory. 366 | 367 | 368 | ### 7.3 Troubleshooting USB-RS232 Converter Modules on Linux 369 | 370 | Use the following procedure if problems occur with USB-RS232 371 | interfaces such as the FTDI232 module on Linux: 372 | 373 | Device Driver Downloads: 374 | 375 | - FTDI232 devices: (https://www.ftdichip.com/FTDrivers.htm). 376 | - CP2102 devices: (https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers). 377 | 378 | 1. cd OBD-Monitor/src 379 | 380 | make stests 381 | 382 | 2. Connect a wire between the USB-RS232 converter module transmit and 383 | receive pins, then insert the module into a USB port. 384 | 385 | 3. Check the kernel module is loaded, for example: 386 | 387 | dmesg | grep "FTDI" 388 | 389 | 4. Check for user read/write permission on the device. 390 | 391 | ls -la /dev/ttyUSB0 392 | 393 | sudo chmod a+rw /dev/ttyUSB0 394 | 395 | or 396 | 397 | chmod +s serial_test (may not work) 398 | 399 | or 400 | 401 | usermod -G dialout "user-name" (may not work) 402 | 403 | 5. Run the serial loopback test with an optional device name: 404 | 405 | ./serial_test ttyUSB0 406 | 407 | or 408 | 409 | ./serial_test ttyACM0 410 | 411 | or 412 | 413 | ./serial_test 414 | 415 | 6. If serial communications still not functioning then the 416 | converter module may be faulty. Try swapping out the module, 417 | Silicon Chip Magazine sell them for $5.00AUD plus postage. 418 | (http://www.siliconchip.com.au/Shop/7/3437) 419 | 420 | 421 | ### 7.4 Troubleshooting OBD Interface (RS232 comms) In Vehicle 422 | 423 | If serial interface confirmed working, but no communication with 424 | the OBD interpreter module: 425 | 426 | 1. Turn vehicle ignition OFF. 427 | 428 | 2. Connect the interpreter module to vehicle OBD socket. 429 | 430 | 3. Connect serial cable from the OBD interpreter module to laptop. 431 | 432 | 4. Turn vehicle ignition ON, but do not start vehicle. 433 | 434 | 5. Ensure interpreter module power LED indicators go ON. 435 | 436 | 6. On laptop: 437 | 438 | cd OBD-Monitor/src 439 | 440 | make server 441 | 442 | make ftests 443 | 444 | chmod +s obd_monitor_server 445 | 446 | ./obd_monitor_server 8989 447 | 448 | ./server_test 449 | 450 | 7. Output from server_test should indicate correct OBD protocol for 451 | the vehicle. 452 | 453 | 8. If "NO DATA" returned then the wrong OBD protocol has probably 454 | been selected by the interpreter auto search function. 455 | 456 | 9. Set the correct OBD protocol manually with: 457 | 458 | ./server_test protocol-number 459 | 460 | Where protocol number is between 1 and C hexadecimal (see Section 4.2). 461 | 462 | 10. If still no data returned: 463 | 464 | Open a serial terminal program such as moserial. 465 | 466 | Select the serial port and default comms parameters. 467 | 468 | Example: ttyUSB0, 9600 baud, 8 data bits, no parity, 1 stop bit. 469 | 470 | Ensure line terminator option is set to carriage return "\r" character or new line and carriage return "\n\r". 471 | 472 | Type the following command into the terminal input and press enter. 473 | 474 | "ATDP" 475 | 476 | This should return the currently set OBD protocol. If "NO DATA" is 477 | 478 | returned then either the OBD interface or the ECU are faulty. 479 | 480 | Other ELM327 or compatible interpreter commands to try: 481 | 482 | "ATZ" - reset the OBD interpreter IC. 483 | 484 | "ATI" - get OBD interpreter version ID. 485 | 486 | "ATRV" - vehicle battery voltage. 487 | 488 | "ATTP n" - try protocol number "n". 489 | 490 | "09 02" - get vehicle VIN number. 491 | 492 | "09 0A" - get ECU name. 493 | 494 | "01 01" - get MIL (check engine light) status and number of diagnostic trouble codes set. 495 | 496 | "03" - get a list of diagnostic trouble codes currently set. 497 | 498 | 499 | 500 | 501 | -------------------------------------------------------------------------------- /images/battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/battery.png -------------------------------------------------------------------------------- /images/face-angry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/face-angry.png -------------------------------------------------------------------------------- /images/face-cool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/face-cool.png -------------------------------------------------------------------------------- /images/face-sad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/face-sad.png -------------------------------------------------------------------------------- /images/face-surprise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/face-surprise.png -------------------------------------------------------------------------------- /images/obd-architecture.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/obd-architecture.odg -------------------------------------------------------------------------------- /images/obd-gui-comms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/obd-gui-comms.png -------------------------------------------------------------------------------- /images/obd-gui-dtc-tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/obd-gui-dtc-tab.png -------------------------------------------------------------------------------- /images/obd-gui-ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/obd-gui-ss1.png -------------------------------------------------------------------------------- /images/obd-gui-ss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/obd-gui-ss2.png -------------------------------------------------------------------------------- /images/obd-gui-ss3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/obd-gui-ss3.png -------------------------------------------------------------------------------- /images/obd-gui-ss4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/obd-gui-ss4.png -------------------------------------------------------------------------------- /images/obd-monitor-gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/obd-monitor-gui.png -------------------------------------------------------------------------------- /images/package_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/package_settings.png -------------------------------------------------------------------------------- /images/preferences-system-network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/preferences-system-network.png -------------------------------------------------------------------------------- /images/troubleshoot_red_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/images/troubleshoot_red_icon.png -------------------------------------------------------------------------------- /resources/cairo_text_extents.c: -------------------------------------------------------------------------------- 1 | cairo_text_extents_t extents; 2 | 3 | const char *utf8 = "cairo"; 4 | double x,y; 5 | 6 | cairo_select_font_face (cr, "Sans", 7 | CAIRO_FONT_SLANT_NORMAL, 8 | CAIRO_FONT_WEIGHT_NORMAL); 9 | 10 | cairo_set_font_size (cr, 100.0); 11 | cairo_text_extents (cr, utf8, &extents); 12 | 13 | x=25.0; 14 | y=150.0; 15 | 16 | cairo_move_to (cr, x,y); 17 | cairo_show_text (cr, utf8); 18 | 19 | /* draw helping lines */ 20 | cairo_set_source_rgba (cr, 1, 0.2, 0.2, 0.6); 21 | cairo_set_line_width (cr, 6.0); 22 | cairo_arc (cr, x, y, 10.0, 0, 2*M_PI); 23 | cairo_fill (cr); 24 | cairo_move_to (cr, x,y); 25 | cairo_rel_line_to (cr, 0, -extents.height); 26 | cairo_rel_line_to (cr, extents.width, 0); 27 | cairo_rel_line_to (cr, extents.x_bearing, -extents.y_bearing); 28 | cairo_stroke (cr); 29 | 30 | 31 | 32 | 33 | cairo_text_extents_t extents; 34 | 35 | const char *utf8 = "cairo"; 36 | double x,y; 37 | 38 | cairo_select_font_face (cr, "Sans", 39 | CAIRO_FONT_SLANT_NORMAL, 40 | CAIRO_FONT_WEIGHT_NORMAL); 41 | 42 | cairo_set_font_size (cr, 52.0); 43 | cairo_text_extents (cr, utf8, &extents); 44 | x = 128.0-(extents.width/2 + extents.x_bearing); 45 | y = 128.0-(extents.height/2 + extents.y_bearing); 46 | 47 | cairo_move_to (cr, x, y); 48 | cairo_show_text (cr, utf8); 49 | 50 | /* draw helping lines */ 51 | cairo_set_source_rgba (cr, 1, 0.2, 0.2, 0.6); 52 | cairo_set_line_width (cr, 6.0); 53 | cairo_arc (cr, x, y, 10.0, 0, 2*M_PI); 54 | cairo_fill (cr); 55 | cairo_move_to (cr, 128.0, 0); 56 | cairo_rel_line_to (cr, 0, 256); 57 | cairo_move_to (cr, 0, 128.0); 58 | cairo_rel_line_to (cr, 256, 0); 59 | cairo_stroke (cr); 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /resources/gtk+-3.0.pc: -------------------------------------------------------------------------------- 1 | prefix=/usr 2 | exec_prefix=${prefix} 3 | libdir=/usr/lib/x86_64-linux-gnu 4 | includedir=${prefix}/include 5 | targets=x11 broadway wayland mir 6 | 7 | gtk_binary_version=3.0.0 8 | gtk_host=x86_64-pc-linux-gnu 9 | 10 | Name: GTK+ 11 | Description: GTK+ Graphical UI Library 12 | Version: 3.18.9 13 | Requires: gdk-3.0 atk >= 2.15.1 cairo >= 1.14.0 cairo-gobject >= 1.14.0 gdk-pixbuf-2.0 >= 2.30.0 gio-2.0 >= 2.45.8 14 | Requires.private: atk atk-bridge-2.0 wayland-client >= 1.5.91 xkbcommon >= 0.2.0 wayland-cursor >= 1.5.91 wayland-egl mirclient >= 0.11.0 mircookie >= 0.17.0 epoxy >= 1.0 pangoft2 gio-unix-2.0 >= 2.45.8 15 | Libs: -L${libdir} -lgtk-3 16 | Cflags: -I${includedir}/gtk-3.0 17 | -------------------------------------------------------------------------------- /resources/gtk-utf8-string-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/resources/gtk-utf8-string-error.png -------------------------------------------------------------------------------- /resources/obd-gui-windows.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dchad/OBD-Monitor/7369a8ff8ef12646fa5e839f16a32aac489fc724/resources/obd-gui-windows.jpg -------------------------------------------------------------------------------- /resources/oil-pressure.txt: -------------------------------------------------------------------------------- 1 | GM Oil Press: This was a toughie. Set up custom PID 22115c, unit PSI, max/min 100/0, equation (A*.065)-17.5. This is not part of the extended PID set, you have to create it from scratch. It's a little jumpy on my application -- while DIC says steady 42 PSI, for example, Torque will show it jumping between, say, 41-43. Dunno what's up with that. 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /resources/pacman-commands.txt: -------------------------------------------------------------------------------- 1 | Which Applications Are Installed On Your Computer 2 | 3 | You can view a list of all the packages installed on your system using the following command: 4 | 5 | pacman -Q 6 | 7 | This will return a list of all the applications on your computer and their version numbers. 8 | Viewing The Change Log For An Installed Application 9 | 10 | You can retrieve more information about a package or indeed packages by supplying various query options as follows: 11 | 12 | pacman -Q -c octopi 13 | 14 | View Packages Installed As Dependencies For Other Packages 15 | 16 | The above command will show me the changelog for octopi if it exists. If it doesn't exist a message will be displayed telling you that no changelog is available. 17 | 18 | pacman -Q -d 19 | 20 | The above command shows you all the files that are installed as dependencies to other packages. 21 | 22 | pacman -Q -d -t 23 | 24 | This will show you all the orphaned dependencies installed on your computer. 25 | View Explicitly Installed Packages 26 | 27 | If you want to see all of the explicitly installed packages use the following command: 28 | 29 | pacman -Q -e 30 | 31 | An explicit package is one that you actually chose to install as opposed to a package that was installed as a dependency to other packages. 32 | 33 | You can see which explicit packages have no dependencies by using the following command: 34 | 35 | pacman -Q -e -t 36 | 37 | View All The Packages In A Group 38 | 39 | To see which groups packages belong to you can use the following command: 40 | 41 | pacman -Q -g 42 | 43 | This will list the name of the group followed by the name of the package. 44 | 45 | If you want to see all the packages in a particular group you can specify the group name: 46 | 47 | pacman -Q -g base 48 | 49 | Return Information About Installed Packages 50 | 51 | If you want to know the name, description and all other manner of details about a package use the following command: 52 | 53 | pacman -Q -i packagename 54 | 55 | The output includes: 56 | 57 | name 58 | version 59 | description 60 | architecture 61 | URL to package's website 62 | License 63 | Groups 64 | Provides 65 | Depends On 66 | Optional Deps 67 | Required By 68 | Optional For 69 | Conflicts With 70 | Replaces 71 | Installed Size 72 | Name Of Packager 73 | Build Date 74 | Install Date 75 | Install Reason 76 | Install Script 77 | Validated 78 | 79 | Check The Health Of An Installed Package 80 | 81 | To check the health of a particular package you can use the following command: 82 | 83 | pacman -Q -k packagename 84 | 85 | This will return output similar to the following: 86 | 87 | scratch: 1208 total files, 0 missing files 88 | 89 | You can run this command against all the installed packages: 90 | 91 | pacman -Q -k 92 | 93 | Find All Files Owned By A Package 94 | 95 | You can find all the files that are owned by a specific package using the following command: 96 | 97 | pacman -Q -l packagename 98 | 99 | This returns the package name and the path to files that it owns. You can specify multiple packages after the -l. 100 | Find Packages Not Found In The Sync Databases (i.e. Installed Manually) 101 | 102 | You can find manually installed packages using the following command: 103 | 104 | pacman -Q -m 105 | 106 | Packages installed using yaourt such as Google Chrome will be listed using this command. 107 | Find Packages Only Available In The Sync Databases 108 | 109 | This is the inverse to the previous command and only shows packages installed via the sync databases. 110 | 111 | pacman -Q -n 112 | 113 | Find Out Of Date Packages 114 | 115 | To find packages that need to be updated use the following command: 116 | 117 | pacman -Q -u 118 | 119 | This will return a list of packages, their version numbers, and the latest version numbers. 120 | How To Install A Package Using Pacman 121 | 122 | To install a package use the following command: 123 | 124 | pacman -S packagename 125 | 126 | You may need to use the sudo command to elevate your permissions for this command to run. Alternatively, switch to a user with elevated permissions using the su command. 127 | 128 | When a package is available in multiple repositories you can choose which repository to use by specifying it in the command as follows: 129 | 130 | pacman -S repositoryname/packagename 131 | 132 | Installing a package with pacman will automatically download and install any dependencies. 133 | 134 | You can also install a group of packages such as a desktop environment like XFCE. 135 | 136 | When you specify a group name the output will be along the lines of: 137 | 138 | There are 17 members in group xfce4 139 | 140 | 141 | Repository extra 142 | 1) exo 2) garcon 3) gtk-xfce-engine 143 | 144 | You can choose to install all the packages in the group by pressing return. Alternatively, you can install individual packages by providing a comma-separated list of numbers (i.e. 1,2,3,4,5). If you want to install all the packages between 1 and 10 you can also use a hyphen (i.e. 1-10). 145 | How To Upgrade Out Of Date Packages 146 | 147 | To upgrade all of the out-of-date packages use the following command: 148 | 149 | pacman -S -u 150 | 151 | Sometimes you want to upgrade the packages but for one particular package, you want it to stay at an older version (because you know the newer version has removed a feature or is broken). You can use the following command for this: 152 | 153 | pacman -S -u --ignore packagename 154 | 155 | Show A List Of Available Packages 156 | 157 | You can view a list of the available packages in the sync database with the following command: 158 | 159 | pacman -S -l 160 | 161 | Display Information About A Package In The Sync Database 162 | 163 | You can find detailed information about a package in the sync database using the following command: 164 | 165 | pacman -S -i packagename 166 | 167 | Search For A Package In The Sync Database 168 | 169 | If you just want to search for a package in the sync database use the following command: 170 | 171 | pacman -S -s packagename 172 | 173 | The results will be a list of all available packages matching the search criteria. 174 | Refresh The Sync Database 175 | 176 | You can make sure the sync database is up to date using the following command: 177 | 178 | pacman -S -y 179 | 180 | This should be used prior to running the upgrade command. It is also useful to run this if you haven't done it in a while so that when you search you are getting the latest results. 181 | A Note About Switches 182 | 183 | Throughout this guide, you will have noticed that I have specified each switch on its own. For example: 184 | 185 | pacman -S -u 186 | 187 | You can, of course, combine switches: 188 | 189 | pacman -Su 190 | 191 | 192 | -------------------------------------------------------------------------------- /resources/pkg-config-gtk3.txt: -------------------------------------------------------------------------------- 1 | pkg-config --libs --cflags gtk+-3.0 2 | -pthread -I/usr/include/gtk-3.0 -I/usr/include/at-spi2-atk/2.0 -I/usr/include/at-spi-2.0 -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -I/usr/include/gtk-3.0 -I/usr/include/gio-unix-2.0/ -I/usr/include/mirclient -I/usr/include/mircore -I/usr/include/mircookie -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/harfbuzz -I/usr/include/pango-1.0 -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/freetype2 -I/usr/include/libpng12 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/libpng12 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -lgtk-3 -lgdk-3 -lpangocairo-1.0 -lpango-1.0 -latk-1.0 -lcairo-gobject -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 3 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # Project: OBD Monitor 2 | # Author: Derek Chadwick 3 | # Date: 01/12/2017 4 | 5 | 6 | # Compiler flags 7 | 8 | CC=gcc 9 | CFLAGS=-Wall 10 | 11 | # Linker flags 12 | 13 | LDFLAGS=-static 14 | LIBS=-lm 15 | LIBDIRS=-L../../libs 16 | 17 | # Sources 18 | 19 | GUI_SOURCES=obd_monitor_gui.c protocols.c sockets.c gui_dialogs.c gui_gauges.c log.c util.c gui_gauges_aux.c config.c pid_hash_map.c 20 | SERVER_SOURCES=obd_monitor_server.c rs232.c log.c util.c 21 | SIMULATOR_SOURCES=ecu_simulator.c rs232.c log.c util.c 22 | UNIT_TEST_SOURCES=unit_test.c util.c log.c rs232.c pid_hash_map.c dtc_hash_map.c config.c 23 | FUNCTION_TEST_SOURCES=test_server.c sockets.c util.c log.c 24 | SERIAL_TEST_SOURCES=test_serial_rxtx.c rs232.c 25 | 26 | # Objects 27 | 28 | GUI_EXECUTABLE=obd_gui 29 | SERVER_EXECUTABLE=obd_server 30 | SIMULATOR_EXECUTABLE=ecu_sim 31 | UNIT_TEST_EXECUTABLE=unit_test 32 | FUNCTION_TEST_EXECUTABLE=server_test 33 | SERIAL_TEST_EXECUTABLE=serial_test 34 | 35 | # Includes 36 | 37 | INCPREFIX=../../libs/ 38 | INCLUDES=-I$(INCPREFIX)/include 39 | 40 | # Target Rules 41 | 42 | all: gui server simulator utests ftests stests 43 | 44 | gui: obd_monitor_gui.c obd_monitor.h protocols.h 45 | $(CC) $(GUI_SOURCES) $(LIBS) `pkg-config --libs --cflags gtk+-3.0` -o $(GUI_EXECUTABLE) 46 | 47 | server: obd_monitor_server.c obd_monitor.h 48 | $(CC) $(CFLAGS) $(SERVER_SOURCES) -o $(SERVER_EXECUTABLE) 49 | 50 | simulator: ecu_simulator.c obd_monitor.h 51 | $(CC) $(CFLAGS) $(SIMULATOR_SOURCES) -o $(SIMULATOR_EXECUTABLE) 52 | 53 | utests: unit_test.c obd_monitor.h 54 | $(CC) $(CFLAGS) $(UNIT_TEST_SOURCES) -o $(UNIT_TEST_EXECUTABLE) 55 | 56 | ftests: test_server.c obd_monitor.h 57 | $(CC) $(CFLAGS) $(FUNCTION_TEST_SOURCES) -o $(FUNCTION_TEST_EXECUTABLE) 58 | 59 | stests: test_serial_rxtx.c 60 | $(CC) $(CFLAGS) $(SERIAL_TEST_SOURCES) -o $(SERIAL_TEST_EXECUTABLE) 61 | 62 | strip: 63 | strip $(SERVER_EXECUTABLE) $(GUI_EXECUTABLE) 64 | 65 | clean: 66 | rm $(SERVER_EXECUTABLE) $(GUI_EXECUTABLE) $(SIMULATOR_EXECUTABLE) $(UNIT_TEST_EXECUTABLE) $(FUNCTION_TEST_EXECUTABLE) $(SERIAL_TEST_EXECUTABLE) 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/Makefile.win: -------------------------------------------------------------------------------- 1 | # Project: OBD Monitor 2 | # Author: Derek Chadwick 3 | # Date: 18/11/2018 4 | # Windows version built with the MSYS2 environment on Windows 8.1 5 | 6 | 7 | # Compiler flags 8 | 9 | CC=/mingw64/bin/gcc 10 | CFLAGS=-Wall -pthread 11 | 12 | # Linker flags 13 | 14 | LDFLAGS=-static 15 | LIBS=-lm -lgtk-3.dll -lgdk-3.dll -lpangocairo-1.0.dll -lpango-1.0.dll -latk-1.0.dll -lcairo-gobject.dll -lcairo.dll -lgdk_pixbuf-2.0.dll -lgio-2.0.dll -lgobject-2.0.dll -lglib-2.0.dll -lwsock32 16 | LIBDIRS=-L/mingw64/lib -L/mingw64/lib/gtk-3.0 -L/mingw64/lib/glib-2.0 17 | 18 | # Sources 19 | 20 | GUI_SOURCES=obd_monitor_gui.c protocols.c winsockets.c gui_dialogs.c gui_gauges.c log.c util.c gui_gauges_aux.c config.c pid_hash_map.c 21 | SERVER_SOURCES=obd_monitor_server.c rs232.c log.c util.c winsockets.c 22 | SIMULATOR_SOURCES=ecu_simulator.c rs232.c log.c util.c 23 | UNIT_TEST_SOURCES=unit_test.c util.c log.c rs232.c pid_hash_map.c dtc_hash_map.c config.c 24 | FUNCTION_TEST_SOURCES=test_server.c winsockets.c util.c log.c 25 | SERIAL_TEST_SOURCES=test_serial_rxtx.c rs232.c 26 | 27 | # Objects 28 | 29 | GUI_EXECUTABLE=obd_gui 30 | SERVER_EXECUTABLE=obd_server 31 | SIMULATOR_EXECUTABLE=ecu_sim 32 | UNIT_TEST_EXECUTABLE=unit_test 33 | FUNCTION_TEST_EXECUTABLE=server_test 34 | SERIAL_TEST_EXECUTABLE=serial_test 35 | 36 | # Includes 37 | 38 | INCPREFIX=../../libs/ 39 | INCLUDES=-I$(INCPREFIX)/include -I/mingw64/include/gtk-3.0 -I/mingw64/include/dbus-1.0 -I/mingw64/lib/dbus-1.0/include \ 40 | -I/mingw64/include/gio-win32-2.0/ -I/mingw64/include/cairo -I/mingw64/include/pango-1.0 -I/mingw64/include/harfbuzz \ 41 | -I/mingw64/include/pixman-1 -I/mingw64/include/freetype2 -I/mingw64/include/libpng16 -I/mingw64/include/gdk-pixbuf-2.0 \ 42 | -I/mingw64/include/glib-2.0 -I/mingw64/lib/glib-2.0/include -I/mingw64/include/atk-1.0 43 | 44 | 45 | # Target Rules 46 | 47 | all: $(GUI_SOURCES) $(SERVER_SOURCES) 48 | 49 | gui: obd_monitor_gui.c obd_monitor.h protocols.h 50 | $(CC) -o $(GUI_EXECUTABLE) $(GUI_SOURCES) $(CFLAGS) $(INCLUDES) $(LIBDIRS) $(LIBS) -D_WINSOCK 51 | 52 | server: obd_monitor_server.c obd_monitor.h 53 | $(CC) -o $(SERVER_EXECUTABLE) $(CFLAGS) $(SERVER_SOURCES) -lwsock32 -D_WINSOCK 54 | 55 | simulator: ecu_simulator.c obd_monitor.h 56 | $(CC) $(CFLAGS) $(SIMULATOR_SOURCES) -o $(SIMULATOR_EXECUTABLE) -D_WINSOCK 57 | 58 | utests: unit_test.c obd_monitor.h 59 | $(CC) $(CFLAGS) $(UNIT_TEST_SOURCES) -o $(UNIT_TEST_EXECUTABLE) -D_WINSOCK 60 | 61 | ftests: test_server.c obd_monitor.h 62 | $(CC) $(CFLAGS) $(FUNCTION_TEST_SOURCES) -o $(FUNCTION_TEST_EXECUTABLE) -D_WINSOCK 63 | 64 | stests: test_serial_rxtx.c 65 | $(CC) $(CFLAGS) $(SERIAL_TEST_SOURCES) -o $(SERIAL_TEST_EXECUTABLE) 66 | 67 | strip: 68 | strip $(SERVER_EXECUTABLE) $(GUI_EXECUTABLE) 69 | 70 | clean: 71 | rm $(SERVER_EXECUTABLE) $(GUI_EXECUTABLE) $(SIMULATOR_EXECUTABLE) $(UNIT_TEST_EXECUTABLE) $(FUNCTION_TEST_EXECUTABLE) $(SERIAL_TEST_EXECUTABLE) 72 | 73 | test: 74 | $(CC) -o ex ex.c $(CFLAGS) $(INCLUDES) $(LIBDIRS) $(LIBS) 75 | 76 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | config.c 3 | 4 | Project: OBD-II Monitor (On-Board Diagnostics) 5 | 6 | Author: Derek Chadwick 7 | 8 | Description: Configuration functions. 9 | 10 | PID Data Format: 11 | 12 | Manufacturer_PID_List { 13 | 14 | PID_Entry { 15 | 16 | ID_Number: "String" 17 | Name: "String" 18 | Description: "String" 19 | Formula: "String" 20 | Minumum_Value: "String" 21 | Maximum_Value: "String" 22 | 23 | } 24 | 25 | . 26 | . 27 | . 28 | 29 | } 30 | 31 | DTC Data Format: 32 | 33 | Manufacturer_DTC_List { 34 | 35 | DTC_Entry { 36 | 37 | ID_Number: "String" 38 | Name: "String" 39 | Description: "String" 40 | 41 | } 42 | 43 | . 44 | . 45 | . 46 | 47 | } 48 | 49 | Date: 18/12/2017 50 | 51 | */ 52 | #include 53 | #include 54 | 55 | #include "obd_monitor.h" 56 | #include "protocols.h" 57 | #include "pid_hash_map.h" 58 | #include "dtc_hash_map.h" 59 | 60 | FILE *config_file; 61 | 62 | int load_configuration_file(char *config_file) 63 | { 64 | /* TODO: Define an XML style markup for configuration items. */ 65 | 66 | return(0); 67 | } 68 | 69 | int load_custom_pid_list() 70 | { 71 | int ii; 72 | 73 | /* TODO: Define a custom PID file format using XML style markup. */ 74 | 75 | for(ii = 0; ii < 10; ii++) 76 | { 77 | PID_Parameters *pid = (PID_Parameters *) xmalloc(sizeof(PID_Parameters)); 78 | sprintf(pid->pid_code, "%.4x", ii); 79 | sprintf(pid->pid_description, "MODE 01: %.4x", ii); 80 | add_pid(pid); 81 | } 82 | print_pid_map(); 83 | 84 | return(0); 85 | } 86 | 87 | int get_custom_pid(int pid_num) 88 | { 89 | return(0); 90 | } 91 | 92 | 93 | int get_config_item() 94 | { 95 | return(0); 96 | } 97 | 98 | int set_config_item() 99 | { 100 | return(0); 101 | } 102 | 103 | int save_configuration_file(char *config_file) 104 | { 105 | 106 | 107 | return(0); 108 | } 109 | 110 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | config.h 3 | 4 | Project: OBD-II Monitor (On-Board Diagnostics) 5 | 6 | Author: Derek Chadwick 7 | 8 | Description: Configuration management functions. 9 | 10 | 11 | Date: 18/12/2017 12 | 13 | */ 14 | 15 | #ifndef OBD_CONFIG_INCLUDED 16 | #define OBD_CONFIG_INCLUDED 17 | 18 | 19 | struct _ConfigOptions { 20 | char log_file_name[MAX_PATH_LEN]; 21 | char custom_pid_file_name[MAX_PATH_LEN]; 22 | char config_file_name[MAX_PATH_LEN]; 23 | int ecu_auto_connect; /* 0,1 or delay in seconds. */ 24 | }; 25 | 26 | typedef struct _ConfigOptions ConfigOptions; 27 | 28 | int load_configuration_file(char *config_file); 29 | int load_custom_pid_list(); 30 | int get_custom_pid(int pid_num); 31 | 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/dtc_hash_map.c: -------------------------------------------------------------------------------- 1 | 2 | /* Copyright 2017 Derek Chadwick 3 | 4 | This file is part of the OBD Monitor project. 5 | 6 | Fineline is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Fineline is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Fineline. If not, see . 18 | */ 19 | 20 | /* 21 | dtc_hash_map.c 22 | 23 | Title : OBD Monitor 24 | Author: Derek Chadwick 25 | Date : 06/07/2017 26 | 27 | Purpose: A hashmap wrapper for uthash, used to store DTC parameters. 28 | 29 | */ 30 | 31 | #include 32 | #include 33 | 34 | #include "obd_monitor.h" 35 | #include "dtc_hash_map.h" 36 | 37 | DTC_Parameters *dtc_map = NULL; /* the hash map head record */ 38 | 39 | void add_dtc(DTC_Parameters *fldtc) 40 | { 41 | DTC_Parameters *s; 42 | 43 | HASH_FIND_STR(dtc_map, fldtc->dtc_code , s); /* id already in the hash? */ 44 | if (s == NULL) 45 | { 46 | HASH_ADD_STR(dtc_map, dtc_code, fldtc); /* id: name of key field */ 47 | } 48 | 49 | } 50 | 51 | DTC_Parameters *find_dtc(char *lookup_string) 52 | { 53 | DTC_Parameters *s; 54 | 55 | HASH_FIND_STR(dtc_map, lookup_string, s); /* s: output pointer */ 56 | return s; 57 | } 58 | 59 | DTC_Parameters *get_first_dtc_record() 60 | { 61 | return(dtc_map); 62 | } 63 | 64 | DTC_Parameters *get_last_dtc_record() 65 | { 66 | DTC_Parameters *s = get_first_dtc_record(); 67 | if (s != NULL) 68 | return((DTC_Parameters *)s->hh.prev); 69 | return(NULL); 70 | } 71 | 72 | void delete_dtc(DTC_Parameters *dtc_record) 73 | { 74 | HASH_DEL(dtc_map, dtc_record); /* event: pointer to deletee */ 75 | free(dtc_record); 76 | } 77 | 78 | void delete_all_dtcs() 79 | { 80 | DTC_Parameters *current_dtc, *tmp; 81 | 82 | HASH_ITER(hh, dtc_map, current_dtc, tmp) 83 | { 84 | HASH_DEL(dtc_map,current_dtc); /* delete it (dtc_map advances to next) */ 85 | free(current_dtc); /* free it */ 86 | } 87 | } 88 | 89 | void write_dtc_map(FILE *outfile) 90 | { 91 | DTC_Parameters *s; 92 | 93 | for(s=dtc_map; s != NULL; s=(DTC_Parameters *)(s->hh.next)) 94 | { 95 | fputs(s->dtc_code, outfile); 96 | } 97 | } 98 | 99 | void send_dtc_map() 100 | { 101 | DTC_Parameters *s; 102 | 103 | for(s=dtc_map; s != NULL; s=(DTC_Parameters *)(s->hh.next)) 104 | { 105 | /* TODO: send_event(s->dtc_code); */ 106 | } 107 | } 108 | 109 | void print_dtc_map() 110 | { 111 | DTC_Parameters *s; 112 | 113 | for(s=dtc_map; s != NULL; s=(DTC_Parameters *)(s->hh.next)) 114 | { 115 | printf("<%s> %s\n", s->dtc_code, s->dtc_description); 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /src/dtc_hash_map.h: -------------------------------------------------------------------------------- 1 | /* 2 | dtc_hash_map.h 3 | 4 | Title : OBD Monitor 5 | Author: Derek Chadwick 6 | Date : 06/07/2017 7 | 8 | Purpose: A wrapper for uthash, used to store DTC parameters. 9 | 10 | */ 11 | 12 | #ifndef DTC_MAP_INCLUDED 13 | #define DTC_MAP_INCLUDED 14 | 15 | #include "protocols.h" 16 | 17 | /* dtc_hash_map.c */ 18 | void add_dtc(DTC_Parameters *fldtc); 19 | DTC_Parameters *find_dtc(char *lookup_string); 20 | DTC_Parameters *get_first_dtc_record(); 21 | DTC_Parameters *get_last_dtc_record(); 22 | void delete_dtc(DTC_Parameters *dtc_record); 23 | void delete_all_dtcs(); 24 | void write_dtc_map(FILE *outfile); 25 | void print_dtc_map(); 26 | 27 | #endif 28 | 29 | -------------------------------------------------------------------------------- /src/ecu_simulator.c: -------------------------------------------------------------------------------- 1 | /* 2 | Project: OBD-II Monitor (On-Board Diagnostics) 3 | 4 | Author: Derek Chadwick 5 | 6 | Description: A UDP server that simulates an engine control unit 7 | and an ELM372 OBD interface for functional and unit 8 | testing without any hardware or vehicles. Default 9 | UDP port is 8989. 10 | 11 | Usage: ./ecu_sim [udp port] 12 | 13 | Selected ECU Mode 01 Parameters: 14 | 15 | [PID] [Data Bytes] [Min Value] [Max Value] [Formula] [Description] 16 | 05 1 -40 215 A - 40 (ECT Centigrade) 17 | 0B 1 0 255 A (MAP Pressure kPa) 18 | 0C 2 0 16,383.75 (256 * A + B) / 4 (Engine RPM) 19 | 0D 1 0 255 A (Vehicle Speed) 20 | 0F 1 -40 215 A - 40 (IAT Centigrade) 21 | 11 1 0 100 100 / 256 * A (Throttle Position %) 22 | 5C 1 -40 215 A - 40 (Oil Temperature) 23 | 5E 2 0 3276.75 (256 * A + B) / 20 (Fuel Flow Rate L/h) 24 | 25 | (Oil Pressure?) 26 | (EGR Pressure?) 27 | (Accelerator Position) 28 | 29 | Selected ECU Mode 09 Parameters: 30 | 31 | [PID] [Data Bytes] [Description] 32 | 02 17 VIN - Vehicle Identification Number 33 | 0A 20 ECU Name 34 | 35 | 36 | Date: 30/11/2017 37 | 38 | */ 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #include "obd_monitor.h" 52 | #include "protocols.h" 53 | #include "rs232.h" 54 | 55 | #define BUFFER_LEN 512 56 | 57 | ECU_Parameters simulator_ecu; 58 | OBD_Interface simulator_obd; 59 | 60 | unsigned int tick_count; 61 | 62 | int sock, length, n, serial_port; 63 | socklen_t from_len; 64 | struct sockaddr_in server; 65 | struct sockaddr_in from_client; 66 | char in_buf[MAX_BUFFER_LEN]; 67 | unsigned char ecu_msg[MAX_BUFFER_LEN]; 68 | 69 | const char *OBD_Protocol_List[] = { 70 | "OBD 0 - Automatic OBD-II Protocol Search", 71 | "OBD 1 - SAE J1850 PWM (41.6 kbaud)(Ford)", 72 | "OBD 2 - SAE J1850 VPW (10.4 kbaud)(GM, Isuzu)", 73 | "OBD 3 - IS0 9141-2 (5 baud init, 10.4 kbaud)(Chrysler)", 74 | "OBD 4 - ISO 14230-4 KWP2000 (5-baud init.)", 75 | "OBD 5 - IS0 14230-4 KWP2000 (Fast init.)", 76 | "OBD 6 - IS0 15765-4 CAN (11 bit ID, 500 kbaud)", 77 | "OBD 7 - IS0 15765-4 CAN (29 bit ID, 500 kbaud)", 78 | "OBD 8 - IS0 15765-4 CAN (11 bit ID, 250 kbaud)", 79 | "OBD 9 - IS0 15765-4 CAN (29 bit ID, 250 kbaud)", 80 | "OBD A - SAE J1939 CAN (29 bit ID, 250 kbaud)", 81 | "OBD B - USER1 CAN (11 bit ID, 125 kbaud)", 82 | "OBD C - USER2 CAN (11 bit ID, 50 kbaud)" 83 | }; 84 | 85 | /* Example VINs, first is non CAN protocol, second is CAN protocol 86 | for the same vehicle identification number. 87 | Example: 1 D 4 G P 0 0 R 5 5 B 1 2 3 4 5 6 88 | */ 89 | const char *ecu_vin[] = { 90 | "49 02 43 52 41 50 50 3A 43 41 52 3A 43 4F 4D 50 41 4E 59", 91 | "49 02 31 44 34 47 50 30 30 52 35 35 42 31 32 33 34 35 36", 92 | "49 02 01 31 44 34 47 50 30 30 52 35 35 42 31 32 33 34 35 36" 93 | }; 94 | 95 | /* 96 | Example ECU names - 20 ASCII characters. 97 | */ 98 | const char *ecu_name[] = { 99 | "49 0A 43 52 41 50 54 45 43 48 3A 53 59 53 54 45 4D 53 3A 30 31 32 33 34", 100 | "49 0A 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 47 48 49 4A 4B 4C", 101 | "49 0A 01 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 31 32 33 34 35 36 37 38" 102 | }; 103 | 104 | 105 | void fatal_error(const char *error_msg) 106 | { 107 | perror(error_msg); 108 | exit(0); 109 | } 110 | 111 | 112 | void set_simulator_ecu_parameters() 113 | { 114 | /* TODO: set all the ecu parameters. */ 115 | simulator_ecu.ecu_engine_rpm = 10.0 * (double)tick_count; 116 | simulator_ecu.ecu_vehicle_speed = (double)tick_count; 117 | simulator_ecu.ecu_coolant_temperature = (double)tick_count; 118 | simulator_ecu.ecu_intake_air_temperature = 2.0 * (double)tick_count; 119 | simulator_ecu.ecu_manifold_air_pressure = 3.0 * (double)tick_count; 120 | /* simulator_ecu.ecu_oil_pressure = 5.0 * (double)tick_count; 121 | simulator_ecu.ecu_egr_pressure = 5.0 * (double)tick_count; */ 122 | simulator_ecu.ecu_battery_voltage = 12.5; 123 | simulator_ecu.ecu_throttle_position = (double)tick_count; 124 | simulator_ecu.ecu_oil_temperature = 2.0 * (double)tick_count; 125 | simulator_ecu.ecu_accelerator_position = (double)tick_count; 126 | simulator_ecu.ecu_fuel_pressure = 3.0 * (double)tick_count; 127 | simulator_ecu.ecu_fuel_flow_rate = 2.0 * (double)tick_count; 128 | simulator_ecu.ecu_fuel_tank_level = (double)tick_count; 129 | 130 | simulator_ecu.ecu_mil_status = 1; 131 | simulator_ecu.ecu_dtc_count = 1; 132 | 133 | return; 134 | } 135 | 136 | void get_simulator_ecu_parameters(ECU_Parameters *ecupout) 137 | { 138 | /* TODO: */ 139 | } 140 | 141 | void log_simulator_ecu_parameters() 142 | { 143 | /* TODO: log ecu parameters on a 60 second timer. */ 144 | } 145 | 146 | int send_engine_rpm() 147 | { 148 | char reply_buf[BUFFER_LEN]; 149 | unsigned int rpm_temp, rpm_A, rpm_B; 150 | int n; 151 | 152 | memset(reply_buf, 0, 256); 153 | 154 | rpm_temp = (unsigned int)simulator_ecu.ecu_engine_rpm * 4; 155 | rpm_A = rpm_temp / 256; 156 | rpm_B = rpm_temp % 256; 157 | 158 | sprintf(reply_buf, "41 0C %.2x %.2x\n", rpm_A, rpm_B); 159 | /* TODO: log simulator msg. */ 160 | printf("send_engine_rpm(): Simulator RPM Msg: %s", reply_buf); 161 | 162 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 163 | 164 | return(n); 165 | } 166 | 167 | 168 | int send_coolant_temperature() 169 | { 170 | char reply_buf[BUFFER_LEN]; 171 | unsigned int ect_A; 172 | int n; 173 | 174 | memset(reply_buf, 0, 256); 175 | 176 | ect_A = (unsigned int)simulator_ecu.ecu_coolant_temperature + 40; 177 | 178 | sprintf(reply_buf, "41 05 %.2x\n", ect_A); 179 | /* TODO: log simulator msg. */ 180 | printf("send_coolant_temperature(): Simulator ECT Msg: %s", reply_buf); 181 | 182 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 183 | 184 | return(n); 185 | } 186 | 187 | 188 | void send_manifold_pressure() 189 | { 190 | char reply_buf[BUFFER_LEN]; 191 | unsigned int map_A; 192 | int n; 193 | 194 | memset(reply_buf, 0, 256); 195 | 196 | map_A = (unsigned int)simulator_ecu.ecu_manifold_air_pressure; 197 | 198 | sprintf(reply_buf, "41 0B %.2x\n", map_A); 199 | 200 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 201 | 202 | printf("send_manifold_pressure(): Simulator MAP Msg: %i bytes %s", n, reply_buf); 203 | 204 | return; 205 | } 206 | 207 | 208 | int send_intake_air_temperature() 209 | { 210 | char reply_buf[BUFFER_LEN]; 211 | unsigned int iat_A; 212 | int n; 213 | 214 | memset(reply_buf, 0, 256); 215 | 216 | iat_A = (unsigned int)simulator_ecu.ecu_intake_air_temperature + 40; 217 | 218 | sprintf(reply_buf, "41 0F %.2x\n", iat_A); 219 | /* TODO: log simulator msg. */ 220 | printf("send_intake_air_temperature(): Simulator IAT Msg: %s", reply_buf); 221 | 222 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 223 | 224 | return(n); 225 | } 226 | 227 | 228 | int send_vehicle_speed() 229 | { 230 | char reply_buf[BUFFER_LEN]; 231 | unsigned int vs_A; 232 | int n; 233 | 234 | memset(reply_buf, 0, 256); 235 | 236 | vs_A = (unsigned int)simulator_ecu.ecu_vehicle_speed; 237 | 238 | sprintf(reply_buf, "41 0D %.2x\n", vs_A); 239 | printf("send_vehicle_speed(): Simulator VS Msg: %s", reply_buf); 240 | 241 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 242 | 243 | return(n); 244 | } 245 | 246 | 247 | void send_egr_pressure() 248 | { 249 | /* Has to be calculated. */ 250 | return; 251 | } 252 | 253 | 254 | int send_throttle_position() 255 | { 256 | char reply_buf[BUFFER_LEN]; 257 | unsigned int tp_A; 258 | int n; 259 | 260 | memset(reply_buf, 0, 256); 261 | 262 | tp_A = (unsigned int)simulator_ecu.ecu_throttle_position / 0.392; 263 | 264 | sprintf(reply_buf, "41 11 %.2x\n", tp_A); 265 | /* TODO: log simulator msg. */ 266 | printf("send_throttle_position(): Simulator Throttle Position Msg: %s", reply_buf); 267 | 268 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 269 | 270 | return(n); 271 | } 272 | 273 | 274 | int send_oil_temperature() 275 | { 276 | char reply_buf[BUFFER_LEN]; 277 | unsigned int ot_A; 278 | int n; 279 | 280 | memset(reply_buf, 0, 256); 281 | 282 | ot_A = (unsigned int)simulator_ecu.ecu_oil_temperature + 40; 283 | 284 | sprintf(reply_buf, "41 5C %.2x\n", ot_A); 285 | /* TODO: log simulator msg. */ 286 | printf("send_oil_temperature(): Simulator OT Msg: %s", reply_buf); 287 | 288 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 289 | 290 | return(n); 291 | } 292 | 293 | 294 | void send_oil_pressure() 295 | { 296 | /* TODO: use oil pressure switch parameter. */ 297 | return; 298 | } 299 | 300 | 301 | int send_mode_1_supported_pid_list_1_32() 302 | { 303 | char reply_buf[BUFFER_LEN]; 304 | unsigned int spidl_A, spidl_B, spidl_C, spidl_D; 305 | int n; 306 | 307 | memset(reply_buf, 0, 256); 308 | 309 | spidl_A = 0b11110000; /* Just some random test values. */ 310 | spidl_B = 0b01010101; 311 | spidl_C = 0b11001100; 312 | spidl_D = 0b00001111; 313 | 314 | sprintf(reply_buf, "41 00 %.2x %.2x %.2x %.2x\n", spidl_A, spidl_B, spidl_C, spidl_D); 315 | 316 | printf("send_mode_1_supported_pid_list_1_32(): Supported PID Msg: %s", reply_buf); 317 | 318 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 319 | 320 | return(n); 321 | } 322 | 323 | int send_mode_9_supported_pid_list_1_32() 324 | { 325 | char reply_buf[BUFFER_LEN]; 326 | unsigned int spidl_A, spidl_B, spidl_C, spidl_D; 327 | int n; 328 | 329 | memset(reply_buf, 0, 256); 330 | 331 | spidl_A = 0b11110000; /* Just some random test values. */ 332 | spidl_B = 0b01010101; 333 | spidl_C = 0b00000000; 334 | spidl_D = 0b00000000; 335 | 336 | sprintf(reply_buf, "49 00 %.2x %.2x %.2x %.2x\n", spidl_A, spidl_B, spidl_C, spidl_D); 337 | /* TODO: log simulator msg. */ 338 | printf("send_mode_9_supported_pid_list_1_32(): Supported PID Msg: %s", reply_buf); 339 | 340 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 341 | 342 | return(n); 343 | } 344 | 345 | void send_timing_advance() 346 | { 347 | return; 348 | } 349 | 350 | 351 | 352 | int send_fuel_tank_level() 353 | { 354 | char reply_buf[BUFFER_LEN]; 355 | unsigned int ftl_A; 356 | int n; 357 | 358 | memset(reply_buf, 0, 256); 359 | 360 | ftl_A = (unsigned int)simulator_ecu.ecu_fuel_tank_level / 0.392; 361 | 362 | sprintf(reply_buf, "41 2F %.2x\n", ftl_A); 363 | /* TODO: log simulator msg. */ 364 | printf("send_fuel_tank_level(): Simulator Fuel Tank Level Msg: %s", reply_buf); 365 | 366 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 367 | 368 | return(n); 369 | } 370 | 371 | 372 | int send_fuel_flow_rate() 373 | { 374 | char reply_buf[BUFFER_LEN]; 375 | unsigned int ffr_temp, ffr_A, ffr_B; 376 | int n; 377 | 378 | memset(reply_buf, 0, 256); 379 | 380 | ffr_temp = (unsigned int)simulator_ecu.ecu_fuel_flow_rate * 20; 381 | ffr_A = ffr_temp / 256; 382 | ffr_B = ffr_temp % 256; 383 | 384 | sprintf(reply_buf, "41 5E %.2x %.2x\n", ffr_A, ffr_B); 385 | /* TODO: log simulator msg. */ 386 | printf("send_fuel_flow_rate(): Simulator Fuel Flow Rate Msg: %s", reply_buf); 387 | 388 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 389 | 390 | return(n); 391 | } 392 | 393 | int send_fuel_pressure() 394 | { 395 | char reply_buf[BUFFER_LEN]; 396 | unsigned int fp_A; 397 | int n; 398 | 399 | memset(reply_buf, 0, 256); 400 | 401 | fp_A = (unsigned int)simulator_ecu.ecu_fuel_pressure / 3.0; 402 | 403 | sprintf(reply_buf, "41 0A %.2x\n", fp_A); 404 | /* TODO: log simulator msg. */ 405 | printf("send_fuel_pressure(): Simulator Fuel Pressure Msg: %s", reply_buf); 406 | 407 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 408 | 409 | return(n); 410 | } 411 | 412 | int send_accelerator_position() 413 | { 414 | char reply_buf[BUFFER_LEN]; 415 | unsigned int ap_A; 416 | int n; 417 | 418 | memset(reply_buf, 0, 256); 419 | 420 | ap_A = (unsigned int)simulator_ecu.ecu_accelerator_position / 0.392; 421 | 422 | sprintf(reply_buf, "41 5A %.2x\n", ap_A); 423 | /* TODO: log simulator msg. */ 424 | printf("send_accelerator_position(): Simulator Accelerator Position Msg: %s", reply_buf); 425 | 426 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 427 | 428 | return(n); 429 | } 430 | 431 | /* OBD Interface Messages. */ 432 | 433 | void send_battery_voltage() 434 | { 435 | char reply_buf[BUFFER_LEN]; 436 | int n; 437 | 438 | memset(reply_buf, 0, 256); 439 | sprintf(reply_buf, "ATRV %.2f\n", simulator_ecu.ecu_battery_voltage); 440 | 441 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 442 | 443 | printf("send_battery_voltage(): Simulator ATRV Msg: %i bytes %s", n, reply_buf); 444 | 445 | return; 446 | } 447 | 448 | void send_interface_information() 449 | { 450 | char reply_buf[BUFFER_LEN]; 451 | int n; 452 | 453 | memset(reply_buf, 0, 256); 454 | /* sprintf(reply_buf, "ATI ELM327\nOK\n>\n"); */ 455 | 456 | sprintf(reply_buf, "ATI ELM327\n"); 457 | 458 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 459 | 460 | printf("send_interface_information(): Simulator ATI Msg: %i bytes %s", n, reply_buf); 461 | 462 | return; 463 | } 464 | 465 | void send_obd_protocol_name(char *obd_msg) 466 | { 467 | char reply_buf[BUFFER_LEN]; 468 | int pnum, n; 469 | 470 | memset(reply_buf, 0, 256); 471 | 472 | if (strncmp(obd_msg, "ATDP", 4) == 0) 473 | { 474 | sprintf(reply_buf, "ATDP %s\n", simulator_obd.obd_protocol_name); 475 | } 476 | else if (sscanf(obd_msg, "ATTP %x", &pnum) == 1) 477 | { 478 | if (pnum > 12) 479 | { 480 | pnum = 0; 481 | } 482 | simulator_obd.obd_protocol_number = pnum; 483 | memset(simulator_obd.obd_protocol_name, 0, 256); 484 | strcpy(simulator_obd.obd_protocol_name, OBD_Protocol_List[pnum]); 485 | sprintf(reply_buf, "ATTP %s\n", simulator_obd.obd_protocol_name); 486 | } 487 | else if (sscanf(obd_msg, "ATSP %x", &pnum) == 1) 488 | { 489 | if (pnum > 12) 490 | { 491 | pnum = 0; 492 | } 493 | simulator_obd.obd_protocol_number = pnum; 494 | memset(simulator_obd.obd_protocol_name, 0, 256); 495 | strcpy(simulator_obd.obd_protocol_name, OBD_Protocol_List[pnum]); 496 | sprintf(reply_buf, "ATSP %s\n", simulator_obd.obd_protocol_name); 497 | } 498 | else 499 | { 500 | simulator_obd.obd_protocol_number = 0; 501 | memset(simulator_obd.obd_protocol_name, 0, 256); 502 | strcpy(simulator_obd.obd_protocol_name, "Unknown OBD protocol.\n"); 503 | printf("send_obd_protocol_name(): %s", obd_msg); 504 | } 505 | 506 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 507 | printf("send_obd_protocol_name() : %i bytes %s", n, reply_buf); 508 | 509 | return; 510 | } 511 | 512 | void send_vin_msg() 513 | { 514 | char reply_buf[BUFFER_LEN]; 515 | int n; 516 | 517 | memset(reply_buf, 0, 256); 518 | 519 | sprintf(reply_buf, "%s\n", ecu_vin[0]); /* TODO: switch between CAN and non-CAN formats. */ 520 | 521 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 522 | 523 | printf("send_vin_msg(): VIN Msg: %i bytes %s", n, reply_buf); 524 | 525 | return; 526 | } 527 | 528 | void send_ecu_name() 529 | { 530 | char reply_buf[BUFFER_LEN]; 531 | int n; 532 | 533 | memset(reply_buf, 0, 256); 534 | 535 | sprintf(reply_buf, "%s\n", ecu_name[0]); 536 | 537 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 538 | 539 | printf("send_ecu_name(): ECU Name: %i bytes %s", n, reply_buf); 540 | 541 | return; 542 | } 543 | 544 | void send_mil_status() 545 | { 546 | char reply_buf[BUFFER_LEN]; 547 | int n, mil_status; 548 | 549 | memset(reply_buf, 0, 256); 550 | 551 | mil_status = simulator_ecu.ecu_mil_status * 128 + simulator_ecu.ecu_dtc_count; 552 | 553 | sprintf(reply_buf, "41 01 %.2x 01 02 03\n", mil_status); /* Msg = (41 01 81 XX XX XX) if MIL on and 1 DTC. */ 554 | 555 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 556 | 557 | printf("send_mil_status(): MIL Msg: %i bytes %s", n, reply_buf); 558 | 559 | return; 560 | } 561 | 562 | void reply_mode_01_msg(char *obd_msg) 563 | { 564 | /* Send back an ECU parameter message. */ 565 | unsigned int pid, pmode; 566 | int n; 567 | 568 | n = sscanf(obd_msg, "%x %x", &pmode, &pid); 569 | if (n == 2) 570 | { 571 | printf("reply_mode_01_msg(): %d %d\n", pmode, pid); 572 | switch(pid) 573 | { 574 | case 0: send_mode_1_supported_pid_list_1_32(); break; /* TODO: Supported PIDs. */ 575 | case 1: send_mil_status(); break; /* MIL on/off and DTC count. */ 576 | case 5: send_coolant_temperature(); break; /* */ 577 | case 10: send_fuel_pressure(); break; 578 | case 11: send_manifold_pressure(); break; /* Throttle Position. */ 579 | case 12: send_engine_rpm(); break; 580 | case 13: send_vehicle_speed(); break; 581 | case 14: send_timing_advance(); break; 582 | case 15: send_intake_air_temperature(); break; 583 | case 17: send_throttle_position(); break; 584 | case 47: send_fuel_tank_level(); break; /* Fuel Tank Level. */ 585 | case 90: send_accelerator_position(); break; 586 | case 92: send_oil_temperature(); break; 587 | case 94: send_fuel_flow_rate(); break; 588 | default: printf("reply_mode_01_msg(): Unknown PID %i\n", pid); break; 589 | } 590 | } 591 | else 592 | { 593 | printf("reply_mode_01_msg() : Unknown OBD message.\n"); 594 | } 595 | return; 596 | } 597 | 598 | void reply_mode_03_msg(char *obd_msg) 599 | { 600 | /* TODO: Send back a DTC message. */ 601 | char reply_buf[BUFFER_LEN]; 602 | int n; 603 | 604 | memset(reply_buf, 0, 256); 605 | 606 | /* TODO: send multiple DTCs. */ 607 | strcpy(reply_buf, "43 01 33 00 00 00 00\n"); /* DTC = P0133. */ 608 | 609 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 610 | printf("---------------------------------------------------------\n"); 611 | printf("reply_mode_03_msg(): DTC Msg: %i bytes %s", n, reply_buf); 612 | printf("---------------------------------------------------------\n"); 613 | 614 | return; 615 | 616 | } 617 | 618 | void reply_mode_09_msg(char *obd_msg) 619 | { 620 | /* Send back ECU and vehicle information message. */ 621 | int n; 622 | unsigned int pid, pmode; 623 | 624 | n = sscanf(obd_msg, "%x %x", &pmode, &pid); 625 | if (n == 2) 626 | { 627 | printf("reply_mode_09_msg(): %d %d\n", pmode, pid); 628 | switch(pid) 629 | { 630 | case 0: send_mode_9_supported_pid_list_1_32(); break; /* TODO: Supported PIDs. */ 631 | case 1: break; /* TODO: Send ???. */ 632 | case 2: send_vin_msg(); break; /* Send VIN number. */ 633 | case 10: send_ecu_name(); break; /* ECU Name. */ 634 | } 635 | } 636 | return; 637 | } 638 | 639 | void send_no_data() 640 | { 641 | char reply_buf[BUFFER_LEN]; 642 | int n; 643 | 644 | memset(reply_buf, 0, 256); 645 | 646 | sprintf(reply_buf, "NO DATA"); 647 | 648 | n = sendto(sock, reply_buf, strlen(reply_buf), 0, (struct sockaddr *)&from_client, from_len); 649 | 650 | printf("send_no_data(): %i bytes %s", n, reply_buf); 651 | 652 | return; 653 | } 654 | 655 | int parse_gui_message() 656 | { 657 | int n; 658 | int msg_len; 659 | 660 | msg_len = strlen(in_buf); 661 | n = 0; 662 | 663 | if (msg_len > 0) /* All messages must terminate with a \r. */ 664 | { 665 | /* Parse the message. */ 666 | if (in_buf[0] == 'A') /* ELM327 interface messages all start with 'AT'. */ 667 | { 668 | if (strncmp(in_buf, "ATRV", 4) == 0) 669 | { 670 | send_battery_voltage(); 671 | } 672 | else if (strncmp(in_buf, "ATI", 3) == 0) /* Get interface name. */ 673 | { 674 | send_interface_information(); 675 | } 676 | else if (strncmp(in_buf, "ATDP", 4) == 0) /* Get OBD Protocol. */ 677 | { 678 | send_obd_protocol_name(in_buf); 679 | } 680 | else if (strncmp(in_buf, "ATSP", 4) == 0) /* Set OBD Protocol. */ 681 | { 682 | send_obd_protocol_name(in_buf); 683 | } 684 | else if (strncmp(in_buf, "ATTP", 4) == 0) /* Try OBD Protocol. */ 685 | { 686 | send_obd_protocol_name(in_buf); 687 | } 688 | } 689 | else 690 | { /* ECU request messages always start with the mode '01'...'0A' */ 691 | if (in_buf[0] == '0') /* This is an OBD request message from the GUI. */ 692 | { 693 | switch(in_buf[1]) 694 | { 695 | case '0': break; /* Invalid mode. */ 696 | case '1': reply_mode_01_msg(in_buf); break; /* Mode 01 message, ECU parameter update. */ 697 | case '2': break; 698 | case '3': reply_mode_03_msg(in_buf); break; /* Mode 03 message, diagnostic trouble codes. */ 699 | case '4': break; 700 | case '5': break; 701 | case '6': break; 702 | case '7': break; 703 | case '8': break; 704 | case '9': reply_mode_09_msg(in_buf); break; /* Mode 09 message, ECU information. */ 705 | case 'A': break; 706 | default: send_no_data(); 707 | } 708 | } 709 | else /* This is an AT message response for the user interface. */ 710 | { 711 | /* TODO: Save configuration info from the OBD interface. */ 712 | } 713 | } 714 | 715 | } 716 | else 717 | { 718 | /* TODO: Invalid message, log an error. */ 719 | n = -1; 720 | } 721 | 722 | return(n); 723 | } 724 | 725 | int init_serial_comms(char *interface_name) 726 | { 727 | int cport_nr=0; /* /dev/ttyS0 (COM1 on windows) */ 728 | int bdrate=9600; /* 9600 baud */ 729 | char mode[]={'8','N','1',0}; 730 | 731 | cport_nr = RS232_GetPortnr(interface_name); 732 | if (cport_nr == -1) 733 | { 734 | printf("ERROR: Can not get com port number.\n"); 735 | exit(-1); 736 | } 737 | 738 | printf("init_serial_comms(): Serial port number: %i\n",cport_nr); 739 | 740 | if(RS232_OpenComport(cport_nr, bdrate, mode)) 741 | { 742 | printf("init_serial_comms() : Can not open comport!\n"); 743 | exit(-1); 744 | } 745 | 746 | return(cport_nr); 747 | } 748 | 749 | int send_ecu_query(int serial_port, char *ecu_query) 750 | { 751 | int out_msg_len = 0; 752 | 753 | out_msg_len = strlen(ecu_query); 754 | if ((out_msg_len < 1) || (out_msg_len > MAX_BUFFER_LEN)) 755 | { 756 | printf("send_ecu_query() : Bad message length!\n"); 757 | return(0); 758 | } 759 | 760 | RS232_cputs(serial_port, ecu_query); 761 | 762 | printf("send_ecu_query(): TXD %i bytes: %s", out_msg_len, ecu_query); 763 | 764 | usleep(100000); /* sleep for 100 milliseconds */ 765 | 766 | return(out_msg_len); 767 | } 768 | 769 | int recv_ecu_reply(int serial_port, unsigned char *ecu_query) 770 | { 771 | int in_msg_len = 0; 772 | 773 | while((in_msg_len = RS232_PollComport(serial_port, ecu_query, MAX_BUFFER_LEN)) > 0) 774 | { 775 | int idx; 776 | 777 | ecu_query[in_msg_len] = 0; /* always put a "null" at the end of a string! */ 778 | 779 | for(idx = 0; idx < in_msg_len; idx++) 780 | { 781 | if(ecu_query[idx] < 32) /* replace unreadable control-codes by dots */ 782 | { 783 | ecu_query[idx] = '.'; 784 | } 785 | } 786 | 787 | printf("recv_ecu_reply(): RXD %i bytes: %s", in_msg_len, ecu_query); 788 | 789 | usleep(100000); /* sleep for 100 milliSeconds */ 790 | } 791 | 792 | return(in_msg_len); 793 | } 794 | 795 | int main(int argc, char *argv[]) 796 | { 797 | char udp_port[16]; 798 | 799 | memset(udp_port, 0, 16); 800 | 801 | if (argc < 2) 802 | { 803 | strcpy(udp_port, "8989"); 804 | } 805 | else 806 | { 807 | strncpy(udp_port, argv[1], strlen(argv[1])); 808 | } 809 | 810 | tick_count = 0; 811 | set_simulator_ecu_parameters(); 812 | memset(simulator_obd.obd_protocol_name, 0, 256); 813 | strncpy(simulator_obd.obd_protocol_name, OBD_Protocol_List[0], strlen(OBD_Protocol_List[0])); 814 | 815 | /* TODO: make serial port configurable. */ 816 | 817 | /* TODO: make this configurable for RS232 unit tests. 818 | serial_port = init_serial_comms("ttyUSB0"); 819 | */ 820 | 821 | sock = socket(AF_INET, SOCK_DGRAM, 0); 822 | 823 | if (sock < 0) 824 | fatal_error("Opening socket"); 825 | 826 | length = sizeof(server); 827 | memset(&server, 0, length); 828 | 829 | server.sin_family=AF_INET; 830 | server.sin_addr.s_addr=INADDR_ANY; 831 | server.sin_port=htons(atoi(udp_port)); 832 | 833 | if (bind(sock, (struct sockaddr *)&server, length) < 0) 834 | fatal_error("binding"); 835 | 836 | from_len = sizeof(struct sockaddr_in); 837 | 838 | memset(in_buf, 0, MAX_BUFFER_LEN); 839 | 840 | while (1) 841 | { 842 | n = recvfrom(sock, in_buf, MAX_BUFFER_LEN, 0, (struct sockaddr *)&from_client, &from_len); 843 | 844 | if (n < 0) fatal_error("recvfrom"); 845 | 846 | printf("main(): RXD ECU Query: %s", in_buf); 847 | 848 | n = parse_gui_message(); 849 | 850 | if (n < 0) 851 | printf("main() :Message parsing failed.\n"); 852 | 853 | set_simulator_ecu_parameters(); 854 | 855 | tick_count += 1; 856 | if (tick_count == 100) 857 | { 858 | tick_count = 0; 859 | } 860 | 861 | /* Now send the query to the ECU interface and get a response. 862 | n = send_ecu_query(serial_port, in_buf); 863 | n = recv_ecu_reply(serial_port, ecu_msg); 864 | */ 865 | /* TODO: log ECU query and reply. */ 866 | 867 | 868 | /* TODO: Clear the buffers!!! */ 869 | memset(in_buf, 0, MAX_BUFFER_LEN); 870 | } 871 | 872 | return 0; 873 | } 874 | 875 | -------------------------------------------------------------------------------- /src/gui_dialogs.c: -------------------------------------------------------------------------------- 1 | /* gui_dialogs.c 2 | 3 | 4 | Project: OBD-II Monitor (On-Board Diagnostics) 5 | 6 | Author: Derek Chadwick 7 | 8 | Description: A GUI for communication with vehicle engine control units via 9 | an OBD-II interface to obtain and display engine status 10 | and fault codes. 11 | 12 | 13 | Date: 30/11/2017 14 | 15 | */ 16 | 17 | #include 18 | #include 19 | #include "obd_monitor.h" 20 | #include "protocols.h" 21 | 22 | 23 | /* Status, error and warning message buffers. */ 24 | char current_info_msg[256]; 25 | char current_error_msg[256]; 26 | char current_status_msg[256]; 27 | char current_warning_msg[256]; 28 | char current_question_msg[256]; 29 | 30 | /* Buffers for Engine Control Unit messages. */ 31 | char ECU_PID_Request[256]; 32 | char ECU_PID_Reply[256]; 33 | 34 | /* Buffers for OBD Interface messages. */ 35 | char ODB_Request[256]; 36 | char OBD_Reply[256]; 37 | 38 | 39 | void set_info_msg(char *msg) 40 | { 41 | strncpy(current_info_msg, msg, 256); 42 | return; 43 | } 44 | 45 | void set_error_msg(char *msg) 46 | { 47 | memset(current_error_msg, 0, 256); 48 | strncpy(current_error_msg, msg, 256); 49 | return; 50 | } 51 | 52 | void set_status_msg(char *msg) 53 | { 54 | memset(current_status_msg, 0, 256); 55 | strncpy(current_status_msg, msg, 256); 56 | return; 57 | } 58 | 59 | void get_status_msg(char *msg) 60 | { 61 | memset(msg, 0, 256); 62 | strncpy(msg, current_status_msg, 256); 63 | return; 64 | } 65 | 66 | void set_warning_msg(char *msg) 67 | { 68 | memset(current_warning_msg, 0, 256); 69 | strncpy(current_warning_msg, msg, 256); 70 | return; 71 | } 72 | 73 | void set_question_msg(char *msg) 74 | { 75 | 76 | memset(current_question_msg, 0, 256); 77 | strncpy(current_question_msg, msg, 256); 78 | return; 79 | } 80 | 81 | void init_dialogs() 82 | { 83 | 84 | return; 85 | } 86 | 87 | 88 | void send_custom_pid_query(GtkWidget *widget, gpointer window) 89 | { 90 | char buffer[256]; 91 | /* TODO: this is for custom PID dialog box send button. */ 92 | memset(buffer, 0, 256); 93 | memset(ECU_PID_Request, 0, 16); 94 | sprintf(ECU_PID_Request,"0100"); 95 | send_ecu_msg(ECU_PID_Request); 96 | usleep(OBD_WAIT_TIMEOUT); 97 | recv_ecu_msg(buffer); 98 | 99 | return; 100 | } 101 | 102 | void show_info_dialog(GtkWidget *widget, gpointer window) 103 | { 104 | 105 | GtkWidget *dialog; 106 | dialog = gtk_message_dialog_new(GTK_WINDOW(window), 107 | GTK_DIALOG_DESTROY_WITH_PARENT, 108 | GTK_MESSAGE_INFO, 109 | GTK_BUTTONS_OK, 110 | "%s", 111 | current_info_msg); 112 | gtk_window_set_title(GTK_WINDOW(dialog), "Information"); 113 | gtk_dialog_run(GTK_DIALOG(dialog)); 114 | gtk_widget_destroy(dialog); 115 | 116 | return; 117 | } 118 | 119 | void show_error_dialog(GtkWidget *widget, gpointer window) 120 | { 121 | 122 | GtkWidget *dialog; 123 | dialog = gtk_message_dialog_new(GTK_WINDOW(window), 124 | GTK_DIALOG_DESTROY_WITH_PARENT, 125 | GTK_MESSAGE_ERROR, 126 | GTK_BUTTONS_OK, 127 | "%s", 128 | current_error_msg); 129 | gtk_window_set_title(GTK_WINDOW(dialog), "Error"); 130 | gtk_dialog_run(GTK_DIALOG(dialog)); 131 | gtk_widget_destroy(dialog); 132 | 133 | return; 134 | } 135 | 136 | 137 | void show_question_dialog(GtkWidget *widget, gpointer window) 138 | { 139 | 140 | GtkWidget *dialog; 141 | dialog = gtk_message_dialog_new(GTK_WINDOW(window), 142 | GTK_DIALOG_DESTROY_WITH_PARENT, 143 | GTK_MESSAGE_QUESTION, 144 | GTK_BUTTONS_YES_NO, 145 | "Are you sure to quit?"); 146 | gtk_window_set_title(GTK_WINDOW(dialog), "Question"); 147 | gtk_dialog_run(GTK_DIALOG(dialog)); 148 | gtk_widget_destroy(dialog); 149 | 150 | return; 151 | } 152 | 153 | void show_warning_dialog(GtkWidget *widget, gpointer window) 154 | { 155 | 156 | GtkWidget *dialog; 157 | dialog = gtk_message_dialog_new(GTK_WINDOW(window), 158 | GTK_DIALOG_DESTROY_WITH_PARENT, 159 | GTK_MESSAGE_WARNING, 160 | GTK_BUTTONS_OK, 161 | "%s", 162 | current_warning_msg); 163 | gtk_window_set_title(GTK_WINDOW(dialog), "Warning"); 164 | gtk_dialog_run(GTK_DIALOG(dialog)); 165 | gtk_widget_destroy(dialog); 166 | 167 | return; 168 | } 169 | 170 | 171 | -------------------------------------------------------------------------------- /src/gui_dialogs.h: -------------------------------------------------------------------------------- 1 | /* gui_dialogs.h 2 | 3 | 4 | Project: OBD-II Monitor (On-Board Diagnostics) 5 | 6 | Author: Derek Chadwick 7 | 8 | Description: A GUI for communication with vehicle engine control units via 9 | an OBD-II interface to obtain and display engine status 10 | and fault codes. 11 | 12 | 13 | Date: 30/11/2017 14 | 15 | */ 16 | 17 | #ifndef OBD_DIALOGS_INCLUDED 18 | #define OBD_DIALOGS_INCLUDED 19 | 20 | void init_dialogs(); 21 | void open_custom_pid_dialog(); 22 | void send_custom_pid_query(GtkWidget *widget, gpointer window); 23 | 24 | /* Dialog Functions. */ 25 | void show_info_dialog(GtkWidget *widget, gpointer window); 26 | void show_error_dialog(GtkWidget *widget, gpointer window); 27 | void show_status_dialog(GtkWidget *widget, gpointer window); 28 | void show_warning_dialog(GtkWidget *widget, gpointer window); 29 | void show_question_dialog(GtkWidget *widget, gpointer window); 30 | 31 | /* Message Setting Functions. */ 32 | void set_info_msg(char *msg); 33 | void set_error_msg(char *msg); 34 | void set_status_msg(char *msg); 35 | void set_warning_msg(char *msg); 36 | void set_question_msg(char *msg); 37 | 38 | void get_status_msg(char *msg); 39 | 40 | #endif 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/gui_gauges.h: -------------------------------------------------------------------------------- 1 | /* 2 | Project: OBD-II Monitor (On-Board Diagnostics) 3 | 4 | Author: Derek Chadwick 5 | 6 | Description: A GUI for communication with vehicle engine control units via 7 | an OBD-II interface to obtain and display engine status 8 | and fault codes. 9 | 10 | 11 | Date: 30/11/2017 12 | 13 | */ 14 | 15 | #ifndef OBD_GAUGES_INCLUDED 16 | #define OBD_GAUGES_INCLUDED 17 | 18 | #include 19 | 20 | gboolean draw_rpm_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 21 | gboolean draw_speed_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 22 | gboolean draw_ect_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 23 | gboolean draw_iat_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 24 | gboolean draw_map_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 25 | gboolean draw_egr_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 26 | gboolean draw_oil_pressure_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 27 | gboolean draw_oil_temp_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 28 | gboolean draw_fuel_flow_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 29 | gboolean draw_fuel_pressure_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 30 | gboolean draw_fuel_tank_level_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 31 | gboolean draw_pid_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 32 | gboolean draw_dtc_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 33 | gboolean draw_battery_voltage_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 34 | gboolean draw_notification_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 35 | gboolean draw_timing_advance_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 36 | 37 | /* Draw dial labels and values. */ 38 | void draw_dial_text(cairo_t *cr, char *gauge_label, char *gauge_numerals, char *gauge_units); 39 | 40 | /* Draw dial backgrounds. */ 41 | void draw_small_dial_background(cairo_t *cr); 42 | void draw_large_dial_background(cairo_t *cr); 43 | void draw_dial_background(cairo_t *cr, double width, double height); 44 | void draw_dial_tick_gauge(cairo_t *cr, double width, double height, double lower, double upper, double radius, double angle); 45 | 46 | 47 | #endif 48 | 49 | -------------------------------------------------------------------------------- /src/gui_gauges_aux.c: -------------------------------------------------------------------------------- 1 | /* 2 | Project: OBD-II Monitor (On-Board Diagnostics) 3 | 4 | Author: Derek Chadwick 5 | 6 | Description: A GUI for communication with vehicle engine control units via 7 | an OBD-II interface to obtain and display engine status 8 | and fault codes. 9 | 10 | 11 | Date: 01/01/2018 12 | 13 | */ 14 | 15 | 16 | #include 17 | #include 18 | #include "obd_monitor.h" 19 | #include "protocols.h" 20 | #include "gui_gauges.h" 21 | 22 | /* Constant Definitions. */ 23 | 24 | #define DIAL_X_CENTER 100.0 25 | #define DIAL_Y_CENTER 75.0 26 | #define GAUGE_RADIUS 50.0 27 | #define GAUGE_START_ANGLE 0.167 * NUM_PI /* 30 degrees */ 28 | #define GAUGE_END_ANGLE -1.167 * NUM_PI /* 210 degrees */ 29 | #define RPM_SCALE_FACTOR 38.2 /* 8000 / (radius * 2 * PI * ((30 + 210) / 360)) = 8000 / 209.5 */ 30 | #define IAT_SCALE_FACTOR 0.764 /* 160 / (radius * (1.167 * PI + 0.167 * PI)) = 160 / 209.5 */ 31 | #define ECT_SCALE_FACTOR 0.764 /* 160C / (radius * (1.167 * PI + 0.167 * PI)) = 160 / 209.5 = 0.764 */ 32 | #define MAP_SCALE_FACTOR 1.217 /* 255 / (radius * (1.167 * PI + 0.167 * PI)) = 255 / 209.5 = 1.217 */ 33 | #define FUEL_FLOW_SCALE_FACTOR 0.783 /* 164.0 / (radius * (1.167 * PI + 0.167 * PI)) */ 34 | #define GAUGE_ARC_LENGTH 209.5 /* Cairo user space dial arc length for 200 x 150 drawing area. */ 35 | 36 | char info_msg[] = "AUXILLIARY GAUGES."; 37 | 38 | void open_aux_gauge_window(GtkWidget *widget, gpointer window) 39 | { 40 | 41 | GtkWidget *dialog; 42 | dialog = gtk_message_dialog_new(GTK_WINDOW(window), 43 | GTK_DIALOG_DESTROY_WITH_PARENT, 44 | GTK_MESSAGE_INFO, 45 | GTK_BUTTONS_OK, 46 | "%s", 47 | info_msg); 48 | gtk_window_set_title(GTK_WINDOW(dialog), "Auxilliary Gauges"); 49 | gtk_dialog_run(GTK_DIALOG(dialog)); 50 | gtk_widget_destroy(dialog); 51 | 52 | return; 53 | } 54 | 55 | 56 | void load_aux_pid_list() 57 | { 58 | 59 | return; 60 | } 61 | 62 | gboolean draw_aux_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data) 63 | { 64 | double xc = 100.0; 65 | double yc = 75.0; 66 | double radius = 50.0; 67 | double gauge_start_angle = 0.167 * NUM_PI; /* 30 degrees */ 68 | double gauge_end_angle = -1.167 * NUM_PI; /* 210 degrees */ 69 | double pid_scale_factor = 1.0; /* 200km/h / (radius * (1.167 * PI + 0.167 * PI)) = 200 / 209.5 = 0.954 */ 70 | double pid_value = 0; /* TODO: Get pid value from protocol module. */ 71 | double gauge_value = pid_value / pid_scale_factor; /* this is the gauge arc length for the needle. */ 72 | double needle_angle = (-1.167 * NUM_PI) + (gauge_value / radius); /* Angle in radians. */ 73 | cairo_text_extents_t ctext; 74 | double cpx; 75 | double cpy; 76 | char gauge_numerals[16]; 77 | char gauge_name[256]; 78 | 79 | 80 | memset(gauge_name, 0, 256); 81 | strcpy(gauge_name, (char *)user_data); 82 | 83 | /* Draw gauge background and arc. */ 84 | draw_dial_background(cr, 190, 140); 85 | 86 | cairo_arc_negative(cr, xc, yc, radius, gauge_start_angle, gauge_end_angle); 87 | cairo_stroke(cr); 88 | 89 | /* Draw gauge indicator arc and dot. */ 90 | cairo_set_source_rgb(cr, 0.9, 0.9, 0.9); 91 | cairo_set_line_width(cr, 3.0); 92 | cairo_arc(cr, xc, yc, radius, gauge_end_angle, needle_angle); 93 | cairo_get_current_point(cr, &cpx, &cpy); 94 | cairo_stroke(cr); 95 | 96 | cairo_set_source_rgb(cr, 0.634, 0.0, 0.0); 97 | cairo_arc (cr, cpx, cpy, 5.0, 0.0, 2*NUM_PI); 98 | cairo_fill(cr); 99 | 100 | /* Draw gauge text. */ 101 | sprintf(gauge_numerals, "%.0f", pid_value); 102 | draw_dial_text(cr, gauge_name, gauge_numerals, "?"); 103 | 104 | return TRUE; 105 | } 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /src/gui_gauges_aux.h: -------------------------------------------------------------------------------- 1 | /* 2 | Project: OBD-II Monitor (On-Board Diagnostics) 3 | 4 | Author: Derek Chadwick 5 | 6 | Description: A GUI for communication with vehicle engine control units via 7 | an OBD-II interface to obtain and display engine status 8 | and fault codes. 9 | 10 | 11 | Date: 01/01/2018 12 | 13 | */ 14 | 15 | #ifndef AUX_GAUGES_INCLUDED 16 | #define AUX_GAUGES_INCLUDED 17 | 18 | #include 19 | 20 | void open_aux_gauge_window(GtkWidget *widget, gpointer window); 21 | gboolean draw_aux_dial(GtkWidget *widget, cairo_t *cr, gpointer user_data); 22 | 23 | 24 | #endif 25 | 26 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | /* 2 | log.c 3 | 4 | Project: OBD-II Monitor 5 | 6 | Author : Derek Chadwick 7 | 8 | Date : 24/12/2017 9 | 10 | Purpose: Logging, reporting and debug functions. 11 | 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "obd_monitor.h" 23 | 24 | FILE *log_file = NULL; 25 | 26 | /* 27 | Function: open_log_file() 28 | 29 | Purpose : Changes the current working directory to the location of the fsic binary 30 | : then opens the log file. 31 | Input : Start file path. 32 | Output : Returns 1 or -1. 33 | */ 34 | int open_log_file(char *startup_path, char *log_file_name) 35 | { 36 | char *temp_path = NULL; 37 | int ret_val = 1; 38 | 39 | 40 | if (startup_path[0] != '.') /* if started from some other directory then change to the home directory */ 41 | { 42 | temp_path = dirname(startup_path); 43 | if (chdir(temp_path) != 0) 44 | { 45 | printf("open_log_file() : Could not change directory: %s\n", temp_path); 46 | } 47 | } 48 | 49 | log_file = fopen(log_file_name, "a"); 50 | if (log_file == NULL) 51 | { 52 | printf("open_log_file() : could not open logfile: %s\n", log_file_name); 53 | ret_val = -1; 54 | } 55 | 56 | 57 | return(ret_val); 58 | } 59 | 60 | 61 | /* 62 | Function: close_log_file() 63 | 64 | Purpose : Closes the log file. 65 | 66 | */ 67 | void close_log_file() 68 | { 69 | print_log_entry("------------------------"); 70 | print_log_entry(">>> Closing log session."); 71 | print_log_entry("------------------------"); 72 | 73 | fclose(log_file); 74 | 75 | return; 76 | } 77 | 78 | 79 | /* 80 | Function: print_log_entry() 81 | 82 | Purpose : Creates a log entry and prints to the log file and stdin. 83 | : 84 | Input : Log string and log file. 85 | Output : Timestamped log entry. 86 | */ 87 | int print_log_entry(char *estr) 88 | { 89 | time_t curtime; 90 | struct tm *loctime; 91 | int slen = strlen(estr); 92 | char *log_entry = xcalloc(slen + 256); 93 | 94 | /* Get the current time. */ 95 | curtime = time (NULL); 96 | loctime = localtime (&curtime); 97 | char *time_str = asctime(loctime); 98 | strncpy(log_entry, time_str, strlen(time_str) - 1); 99 | strcat(log_entry, " "); 100 | strncat(log_entry, estr, slen); 101 | strcat(log_entry, "\n"); 102 | 103 | if (log_file != NULL) 104 | { 105 | fputs (log_entry, log_file); 106 | } 107 | else 108 | { 109 | printf("print_log_entry(): %s\n", log_entry); 110 | } 111 | 112 | xfree(log_entry, slen + 256); 113 | 114 | return(0); 115 | } 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/obd_monitor.h: -------------------------------------------------------------------------------- 1 | /* 2 | Project: OBD-II Monitor (On-Board Diagnostics) 3 | 4 | Author: Derek Chadwick 5 | 6 | Description: A UDP server and GUI that communicates with vehicle 7 | engine control units via an OBD-II interface to obtain 8 | engine status and fault codes. 9 | 10 | Implements two functions: 11 | 12 | 1. A UDP datagram server that receives requests for vehicle 13 | status information from a client application (GUI) and 14 | returns the requested information to the client. 15 | 16 | 2. Serial communications to request vehicle status 17 | information and fault codes from the engine control unit using 18 | the OBD-II protocol. 19 | 20 | Date: 30/11/2017 21 | 22 | */ 23 | 24 | #ifndef OBD_MONITOR_INCLUDED 25 | #define OBD_MONITOR_INCLUDED 26 | 27 | 28 | #include 29 | #include 30 | 31 | /* Constant Definitions. */ 32 | 33 | #define MAX_ECU_QUERY_LEN 16 34 | #define MAX_BUFFER_LEN 4096 35 | #define MAX_PATH_LEN 4096 36 | #define MAX_COPY_LEN 4088 37 | #define MAX_SERIAL_BUF_LEN 256 38 | #define OBD_WAIT_TIMEOUT 100000 39 | #define NUM_PI 3.1415926535897932384626433832795028841971693993751 40 | #define LOG_FILE "./obd-mon-data.log" 41 | 42 | /* TODO: PID Message Codes. */ 43 | 44 | #define PID_SUPPORTED_01 "01 00\n" 45 | #define PID_DTC_COUNT "01 01\n" 46 | #define PID_ECT "01 05\n" 47 | 48 | #define PID_DTC_CODE "03\n" 49 | 50 | #define PID_VIN "09 02\n" 51 | #define PID_ECU "09 0A\n" 52 | 53 | 54 | 55 | /* Type Definitions. */ 56 | 57 | struct _DialPoint { 58 | double x; 59 | double y; 60 | }; 61 | 62 | typedef struct _DialPoint DialPoint; 63 | 64 | /* Function Prototypes. */ 65 | 66 | /* obd_monitor_gui.c */ 67 | void set_interface_on(); 68 | void set_interface_off(); 69 | int get_interface_status(); 70 | void update_comms_log_view(char *msg); 71 | void set_status_bar_msg(char *msg); 72 | void get_status_bar_msg(char *msg); 73 | 74 | /* util.c */ 75 | int xfatal(char *str); 76 | void *xcalloc (size_t size); 77 | void *xmalloc (size_t size); 78 | void *xrealloc (void *ptr, size_t size); 79 | int xfree(char *buf, int len); 80 | char* xitoa(int value, char* result, int len, int base); 81 | int xstrcpy(char *out_buf, char *in_buf, int start, int end); 82 | int xhextoascii(char *out_buf, char *in_buf); 83 | int print_help(); 84 | int get_time_string(char *tstr, int slen); 85 | /* int get_ip_address(char *interface, char *ip_addr); */ 86 | int validate_ipv4_address(char *ipv4_addr); 87 | int validate_ipv6_address(char *ipv6_addr); 88 | char *ltrim(char *s); 89 | char *rtrim(char *s); 90 | char *trim(char *s); 91 | void uppercase(char *s); 92 | int replacechar(char *str, char orig, char rep); 93 | 94 | 95 | /* log.c */ 96 | int open_log_file(char *startup_path, char *log_file_name); 97 | int print_log_entry(char *estr); 98 | void close_log_file(); 99 | 100 | /* sockets.c */ 101 | int init_client_socket(char *server, char *port); 102 | int init_server_socket(char *port); 103 | int send_ecu_msg(char *query); 104 | int recv_ecu_msg(char *msg); 105 | int init_obd_comms(char *obd_msg); 106 | int server_connect(); 107 | int get_ecu_connected(); 108 | void set_ecu_connected(int cstatus); 109 | 110 | /* Unit Test Functions */ 111 | int unit_tests(FILE *log_file); 112 | 113 | #endif 114 | 115 | -------------------------------------------------------------------------------- /src/obd_monitor_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | Project: OBD-II Monitor (On-Board Diagnostics) 3 | 4 | Author: Derek Chadwick 5 | 6 | Description: A UDP server that communicates with vehicle 7 | engine control units via an OBD-II interface to obtain 8 | engine status and fault codes. 9 | 10 | Implements two functions: 11 | 12 | 1. A UDP datagram server that receives requests for vehicle 13 | status information from a client application (GUI) and 14 | returns the requested information to the client. 15 | 16 | 2. Serial communications to request vehicle status 17 | information and fault codes from the engine control unit using 18 | the OBD-II protocol. 19 | 20 | Date: 30/11/2017 21 | 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifdef _WINSOCK 33 | 34 | #include 35 | 36 | #else 37 | 38 | #include 39 | #include 40 | #include 41 | 42 | #endif 43 | 44 | 45 | 46 | 47 | #include "obd_monitor.h" 48 | 49 | #include "rs232.h" 50 | 51 | 52 | #define DEFAULT_UDP_PORT 8989 53 | 54 | 55 | void fatal_error(const char *error_msg) 56 | { 57 | /* TODO: log error message. */ 58 | perror(error_msg); 59 | exit(0); 60 | } 61 | 62 | int init_serial_comms(char *interface_name) 63 | { 64 | int cport_nr=0; /* /dev/ttyS0 (COM1 on windows) */ 65 | int bdrate=9600; /* 9600 baud */ 66 | char mode[]={'8','N','1',0}; 67 | 68 | cport_nr = RS232_GetPortnr(interface_name); 69 | if (cport_nr == -1) 70 | { 71 | printf("init_serial_comms() ERROR: Cannot get com port number.\n"); 72 | exit(-1); 73 | } 74 | 75 | printf("init_serial_comms() Serial port number: %i\n",cport_nr); 76 | 77 | if(RS232_OpenComport(cport_nr, bdrate, mode)) 78 | { 79 | printf("innit_serial_comms() ERROR: Cannot open comport!\n"); 80 | exit(-1); 81 | } 82 | 83 | return(cport_nr); 84 | } 85 | 86 | int send_ecu_query(int serial_port, char *ecu_query) 87 | { 88 | int out_msg_len = 0; 89 | /* struct timespec reqtime; 90 | reqtime.tv_sec = 0; 91 | reqtime.tv_nsec = 1000000; */ 92 | 93 | out_msg_len = strlen(ecu_query); 94 | 95 | if ((out_msg_len < 1) || (out_msg_len > MAX_BUFFER_LEN)) 96 | { 97 | printf("send_ecu_query() ERROR: Bad message length!\n"); 98 | return(0); 99 | } 100 | 101 | RS232_SendBuf(serial_port, (unsigned char *)ecu_query, out_msg_len); 102 | 103 | printf("send_ecu_query() TXD %i bytes: %s\n", out_msg_len, ecu_query); 104 | 105 | /* nanosleep(100000); sleep for 1 millisecond */ 106 | RS232_flushTX(serial_port); 107 | 108 | return(out_msg_len); 109 | } 110 | 111 | int recv_ecu_reply(int serial_port, char *ecu_reply) 112 | { 113 | int in_msg_len; 114 | unsigned char in_buf[MAX_SERIAL_BUF_LEN]; 115 | int interpreter_ready_status = 0; 116 | int msg_idx = 0; 117 | 118 | while (interpreter_ready_status == 0) /* TODO: need a timeout in case lose comms with interpreter. */ 119 | { 120 | int buf_idx; 121 | 122 | memset(in_buf, 0, MAX_SERIAL_BUF_LEN); 123 | 124 | if ((in_msg_len = RS232_PollComport(serial_port, in_buf, MAX_SERIAL_BUF_LEN)) > 0) 125 | { 126 | /* printf("recv_ecu_reply(): RXD00 buf %i bytes: %s\n", in_msg_len, in_buf); */ 127 | 128 | if (in_buf[0] == '>') 129 | { 130 | /* TODO: something wrong here!!! */ 131 | /* ELM327 is ready to receive another request, so exit. */ 132 | /* See ELM327 datasheet for vague details of protocol. */ 133 | interpreter_ready_status = 1; 134 | printf("recv_ecu_reply(): RXD01 > Interpreter Ready.\n"); 135 | } 136 | else 137 | { 138 | 139 | for (buf_idx = 0; buf_idx < in_msg_len; buf_idx++) 140 | { 141 | if (in_buf[buf_idx] < 32) /* Ignore unreadable control-codes except 0x0D message delimiter. */ 142 | { 143 | /* if (in_buf[buf_idx] == '\r') End of message ASCII value 0x0D == \r not ASCII value 0x0A == \n */ 144 | 145 | ecu_reply[msg_idx] = '!'; /* Delimiter between request and response. */ 146 | msg_idx++; 147 | 148 | } 149 | else 150 | { 151 | 152 | if (in_buf[buf_idx] == '>') 153 | { 154 | /* ELM327 is ready to receive another request, so exit. */ 155 | /* See ELM327 datasheet for vague details of protocol. */ 156 | interpreter_ready_status = 1; 157 | /* DEBUG: view raw messages. */ 158 | printf("recv_ecu_reply(): RXD02 > Interpreter Ready.\n"); 159 | } 160 | else 161 | { 162 | ecu_reply[msg_idx] = in_buf[buf_idx]; /* Add character to the reply message buffer. */ 163 | msg_idx++; 164 | } 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | RS232_flushRX(serial_port); 172 | 173 | /* printf("recv_ecu_reply(): RXD buf %i bytes: %s\n", in_msg_len, in_buf); */ 174 | printf("recv_ecu_reply(): RXD msg %i bytes: %s\n", msg_idx, ecu_reply); 175 | 176 | return(msg_idx); 177 | } 178 | 179 | 180 | /* TODO: Temp protocol test function, move to functional test module. */ 181 | void interface_check(int serial_port) 182 | { 183 | char recv_msg[MAX_SERIAL_BUF_LEN]; 184 | /* struct timespec reqtime; 185 | reqtime.tv_sec = 1; 186 | reqtime.tv_nsec = 0; */ 187 | 188 | memset(recv_msg, 0, 256); 189 | 190 | send_ecu_query(serial_port, "ATZ\r\0"); /* Reset the ELM327 OBD interpreter. */ 191 | recv_ecu_reply(serial_port, recv_msg); 192 | printf("ATZ: %s\n", recv_msg); 193 | print_log_entry((char *)recv_msg); 194 | memset(recv_msg, 0, 256); 195 | 196 | send_ecu_query(serial_port, "ATRV\r\0"); /* Get battery voltage from interface. */ 197 | recv_ecu_reply(serial_port, recv_msg); 198 | printf("ATRV: %s\n", recv_msg); 199 | print_log_entry((char *)recv_msg); 200 | memset(recv_msg, 0, 256); 201 | 202 | send_ecu_query(serial_port, "ATDP\r\0"); /* Get OBD protocol name from interface. */ 203 | recv_ecu_reply(serial_port, recv_msg); 204 | printf("ATDP: %s\n", recv_msg); 205 | print_log_entry((char *)recv_msg); 206 | memset(recv_msg, 0, 256); 207 | 208 | send_ecu_query(serial_port, "ATI\r\0"); /* Get interpreter version ID. */ 209 | recv_ecu_reply(serial_port, recv_msg); 210 | printf("ATI: %s\n", recv_msg); 211 | print_log_entry((char *)recv_msg); 212 | memset(recv_msg, 0, 256); 213 | 214 | send_ecu_query(serial_port, "09 02\r\0"); /* Get vehicle VIN number. */ 215 | recv_ecu_reply(serial_port, recv_msg); 216 | printf("VIN: %s\n", recv_msg); 217 | print_log_entry((char *)recv_msg); 218 | memset(recv_msg, 0, 256); 219 | 220 | send_ecu_query(serial_port, "09 0A\r\0"); /* Get ECU name. */ 221 | recv_ecu_reply(serial_port, recv_msg); 222 | printf("ECUName: %s\n", recv_msg); 223 | print_log_entry((char *)recv_msg); 224 | memset(recv_msg, 0, 256); 225 | 226 | send_ecu_query(serial_port, "01 01\r\0"); /* Get DTC Count and MIL status. */ 227 | recv_ecu_reply(serial_port, recv_msg); 228 | printf("MIL: %s\n", recv_msg); 229 | print_log_entry((char *)recv_msg); 230 | memset(recv_msg, 0, 256); 231 | 232 | send_ecu_query(serial_port, "01 00\r\0"); /* Get supported PIDs 1 - 32 for MODE 1. */ 233 | recv_ecu_reply(serial_port, recv_msg); 234 | printf("PID01: %s\n", recv_msg); 235 | print_log_entry((char *)recv_msg); 236 | memset(recv_msg, 0, 256); 237 | 238 | send_ecu_query(serial_port, "09 00\r\0"); /* Get supported PIDs 1 - 32 for MODE 9. */ 239 | recv_ecu_reply(serial_port, recv_msg); 240 | printf("PID09: %s\n", recv_msg); 241 | print_log_entry((char *)recv_msg); 242 | memset(recv_msg, 0, 256); 243 | 244 | send_ecu_query(serial_port, "03\r\0"); /* Get DTCs that are set. */ 245 | recv_ecu_reply(serial_port, recv_msg); 246 | printf("DTC: %s\n", recv_msg); 247 | print_log_entry((char *)recv_msg); 248 | memset(recv_msg, 0, 256); 249 | 250 | /* nanosleep(&reqtime, NULL); Sleep for 1 Second. */ 251 | 252 | return; 253 | } 254 | 255 | 256 | int main(int argc, char *argv[]) 257 | { 258 | int sock, length, n, udp_port, serial_port; 259 | socklen_t from_len; 260 | struct sockaddr_in server; 261 | struct sockaddr_in from_client; 262 | char in_buf[MAX_BUFFER_LEN]; 263 | char log_buf[MAX_BUFFER_LEN+64]; 264 | char ecu_msg[MAX_BUFFER_LEN]; 265 | char *pch; 266 | 267 | /* 268 | struct timespec reqtime; 269 | reqtime.tv_sec = 1; 270 | reqtime.tv_nsec = 0; 271 | */ 272 | 273 | if (argc < 2) 274 | { 275 | udp_port = DEFAULT_UDP_PORT; 276 | } 277 | else 278 | { 279 | udp_port = atoi(argv[1]); 280 | } 281 | 282 | 283 | 284 | open_log_file("./", "obd_server_log.txt"); 285 | 286 | /* TODO: make serial port configurable, ttyUSB0 is an FTDI232 USB-RS232 Converter Module. */ 287 | serial_port = init_serial_comms("ttyUSB0"); 288 | 289 | interface_check(serial_port); 290 | 291 | 292 | 293 | 294 | #ifdef _WINSOCK 295 | 296 | WSADATA wsaData; 297 | int iResult; 298 | unsigned long iMode = 1; 299 | iResult = WSAStartup(MAKEWORD(2,2), &wsaData); 300 | if (iResult != NO_ERROR) 301 | printf("init_server_comms() : WSAStartup() failed with error: %ld\n", iResult); 302 | 303 | #endif 304 | 305 | 306 | 307 | sock = socket(AF_INET, SOCK_DGRAM, 0); 308 | 309 | if (sock < 0) 310 | fatal_error("Opening socket"); 311 | 312 | length = sizeof(server); 313 | memset(&server, 0, length); 314 | 315 | server.sin_family=AF_INET; 316 | server.sin_addr.s_addr=INADDR_ANY; 317 | server.sin_port=htons(udp_port); 318 | 319 | if (bind(sock, (struct sockaddr *)&server, length) < 0) 320 | fatal_error("binding"); 321 | 322 | from_len = sizeof(struct sockaddr_in); 323 | 324 | while (1) 325 | { 326 | /* Clear the buffers!!! */ 327 | memset(in_buf, 0, MAX_BUFFER_LEN); 328 | memset(ecu_msg, 0, MAX_BUFFER_LEN); 329 | 330 | n = recvfrom(sock, in_buf, MAX_BUFFER_LEN, 0, (struct sockaddr *)&from_client, &from_len); 331 | 332 | if (n < 0) 333 | fatal_error("recvfrom"); 334 | 335 | /* TODO: do some message vaidation here. 336 | printf("RXD ECU Query: %s\n", in_buf); */ 337 | 338 | /* Now send the query to the interpreter and get a response. */ 339 | n = send_ecu_query(serial_port, in_buf); 340 | if (n > 0) 341 | { 342 | sprintf(log_buf, "main(): TXD - %s", in_buf); 343 | print_log_entry(log_buf); 344 | } 345 | 346 | /* nanosleep(&reqtime, NULL); */ 347 | 348 | n = recv_ecu_reply(serial_port, ecu_msg); 349 | if (n > 3) 350 | { 351 | /* Reformat messages before sending to the GUI. 352 | ELM327 returns the request message plus the ECU response, 353 | so break off the request header and only send the ECU response 354 | to the GUI. 355 | */ 356 | if (strstr(ecu_msg, "ERROR") != 0) /* Interpreter sent a data error message, so ignore it. */ 357 | { 358 | sprintf(log_buf, "main(): DATA ERROR - %s\n", ecu_msg); 359 | print_log_entry(log_buf); 360 | 361 | } 362 | else if (ecu_msg[0] == 'A') /* TODO: or 'a' Interpreter AT response message. */ 363 | { 364 | /* Replace ! with space. */ 365 | replacechar((char *)ecu_msg, '!', ' '); 366 | sprintf(log_buf, "main(): RXD AT MSG: %s", ecu_msg); 367 | print_log_entry(log_buf); 368 | 369 | /* Send interpreter reply to GUI. */ 370 | n = sendto(sock, ecu_msg, n, 0, (struct sockaddr *)&from_client, from_len); 371 | 372 | if (n < 0) 373 | fatal_error("sendto"); 374 | } 375 | else if (ecu_msg[0] == '0') /* ELM327 sends the request header plus the ECU response message. */ 376 | { 377 | /* replacechar((char *)ecu_msg, '!', ' '); */ 378 | sprintf(log_buf, "main(): RXD ECU MSG: %s", ecu_msg); 379 | print_log_entry(log_buf); 380 | 381 | pch = strtok((char *)ecu_msg,"!"); /* Cut off the header and delimiters. */ 382 | pch = strtok(NULL,"!"); 383 | if (pch != NULL) 384 | { 385 | 386 | /* Send ECU reply to GUI. */ 387 | n = sendto(sock, pch, strlen(pch), 0, (struct sockaddr *)&from_client, from_len); 388 | 389 | if (n < 0) 390 | fatal_error("sendto"); 391 | 392 | printf("main(): Sent ECU msg to GUI: %s\n", pch); 393 | } 394 | 395 | } 396 | else 397 | { 398 | /* Log an error message. */ 399 | sprintf(log_buf, "main(): RXD Unknown ECU Message: %s", ecu_msg); 400 | print_log_entry(log_buf); 401 | } 402 | } 403 | 404 | 405 | } 406 | 407 | close_log_file(); 408 | 409 | return(0); 410 | } 411 | 412 | -------------------------------------------------------------------------------- /src/pid_hash_map.c: -------------------------------------------------------------------------------- 1 | 2 | /* Copyright 2017 Derek Chadwick 3 | 4 | This file is part of the OBD Monitor project. 5 | 6 | Fineline is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Fineline is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Fineline. If not, see . 18 | */ 19 | 20 | /* 21 | pid_hash_map.c 22 | 23 | Title : OBD Monitor 24 | Author: Derek Chadwick 25 | Date : 06/07/2017 26 | 27 | Purpose: A hashmap wrapper for uthash, used to store PID parameters. 28 | 29 | */ 30 | 31 | #include 32 | #include 33 | 34 | #include "obd_monitor.h" 35 | #include "pid_hash_map.h" 36 | 37 | PID_Parameters *pid_map = NULL; /* the hash map head record */ 38 | 39 | void add_pid(PID_Parameters *flpid) 40 | { 41 | PID_Parameters *s; 42 | 43 | HASH_FIND_STR(pid_map, flpid->pid_code , s); /* id already in the hash? */ 44 | if (s == NULL) 45 | { 46 | HASH_ADD_STR(pid_map, pid_code, flpid); /* id: name of key field */ 47 | } 48 | 49 | } 50 | 51 | PID_Parameters *find_pid(char *lookup_string) 52 | { 53 | PID_Parameters *s; 54 | 55 | HASH_FIND_STR(pid_map, lookup_string, s); /* s: output pointer */ 56 | return s; 57 | } 58 | 59 | PID_Parameters *get_first_pid_record() 60 | { 61 | return(pid_map); 62 | } 63 | 64 | PID_Parameters *get_last_pid_record() 65 | { 66 | PID_Parameters *s = get_first_pid_record(); 67 | if (s != NULL) 68 | return((PID_Parameters *)s->hh.prev); 69 | return(NULL); 70 | } 71 | 72 | void delete_pid(PID_Parameters *pid_record) 73 | { 74 | HASH_DEL(pid_map, pid_record); /* event: pointer to deletee */ 75 | free(pid_record); 76 | } 77 | 78 | void delete_all_pids() 79 | { 80 | PID_Parameters *current_pid, *tmp; 81 | 82 | HASH_ITER(hh, pid_map, current_pid, tmp) 83 | { 84 | HASH_DEL(pid_map,current_pid); /* delete it (pid_map advances to next) */ 85 | free(current_pid); /* free it */ 86 | } 87 | } 88 | 89 | void write_pid_map(FILE *outfile) 90 | { 91 | PID_Parameters *s; 92 | 93 | for(s=pid_map; s != NULL; s=(PID_Parameters *)(s->hh.next)) 94 | { 95 | fputs(s->pid_code, outfile); 96 | } 97 | } 98 | 99 | void send_pid_map() 100 | { 101 | PID_Parameters *s; 102 | 103 | for(s=pid_map; s != NULL; s=(PID_Parameters *)(s->hh.next)) 104 | { 105 | /* TODO: send_event(s->pid_code); */ 106 | } 107 | } 108 | 109 | void print_pid_map() 110 | { 111 | PID_Parameters *s; 112 | 113 | for(s=pid_map; s != NULL; s=(PID_Parameters *)(s->hh.next)) 114 | { 115 | printf("<%s> %s\n", s->pid_code, s->pid_description); 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /src/pid_hash_map.h: -------------------------------------------------------------------------------- 1 | /* 2 | pid_hash_map.h 3 | 4 | Title : OBD Monitor 5 | Author: Derek Chadwick 6 | Date : 06/07/2017 7 | 8 | Purpose: A wrapper for uthash, used to store PID parameters. 9 | 10 | */ 11 | 12 | #ifndef PID_MAP_INCLUDED 13 | #define PID_MAP_INCLUDED 14 | 15 | #include "protocols.h" 16 | 17 | /* pid_hash_map.c */ 18 | void add_pid(PID_Parameters *flpid); 19 | PID_Parameters *find_pid(char *lookup_string); 20 | PID_Parameters *get_first_pid_record(); 21 | PID_Parameters *get_last_pid_record(); 22 | void delete_pid(PID_Parameters *pid_record); 23 | void delete_all_pids(); 24 | void write_pid_map(FILE *outfile); 25 | void print_pid_map(); 26 | 27 | #endif 28 | 29 | -------------------------------------------------------------------------------- /src/protocols.h: -------------------------------------------------------------------------------- 1 | /* 2 | Project: OBD-II Monitor (On-Board Diagnostics) 3 | 4 | Author: Derek Chadwick 5 | 6 | Description: OBD and AT message crackers. 7 | If the message starts with ASCII hex digits '40'...'49' then 8 | the message is from the ECU. Otherwise it is a message from 9 | the OBD interface IC or is invalid. 10 | 11 | 12 | Selected ECU Mode 01 Parameters: 13 | 14 | [PID] [Data Bytes] [Min Value] [Max Value] [Formula] [Description] 15 | 05 1 -40 215 A - 40 (ECT Centigrade) 16 | 0A 1 0 765 3 * A (Fuel Pressure kPa) 17 | 0B 1 0 255 A (MAP Pressure kPa) 18 | 0C 2 0 16,383.75 (256 * A + B) / 4 (Engine RPM) 19 | 0D 1 0 255 A (Vehicle Speed) 20 | 0E 1 -64 63.5 (A / 2) - 64 (Timing Advance: degrees before TDC) 21 | 0F 1 -40 215 A - 40 (IAT Centigrade) 22 | 11 1 0 100 100 / 255 * A (Throttle Position %) 23 | 2F 1 0 100 100 / 255 * A (Fuel Tank Level %) 24 | 5A 1 0 100 100 / 255 * A (Relative Accelerator Pedal Position %) 25 | 5C 1 -40 215 A - 40 (Oil Temperature) 26 | 5E 2 0 3276.75 (256 * A + B) / 20 (Fuel Flow Rate L/h) 27 | 28 | 29 | 30 | Selected ECU Mode 09 Parameters: 31 | 32 | [PID] [Data Bytes] [Description] 33 | 02 17 VIN - Vehicle Identification Number 34 | 0A 20 ECU Name 35 | 36 | 37 | Selected ECU Mode 21 Parameters (Toyota): 38 | 39 | 40 | 41 | Selected ECU Mode 22 Parameters (GM/Isuzu): 42 | 43 | (Oil Pressure? Mode: 22 PID: 115C) 44 | 45 | 46 | 47 | Date: 7/11/2017 48 | 49 | */ 50 | 51 | #ifndef OBD_PROTOCOLS_INCLUDED 52 | #define OBD_PROTOCOLS_INCLUDED 53 | 54 | #include "uthash.h" 55 | 56 | /* Constant Definitions. */ 57 | 58 | #define ECU_ECT_TEMPERATURE_MAX 215.0 59 | #define ECU_ECT_TEMPERATURE_IN -40.0 60 | #define ECU_VEHICLE_SPEED_MAX 200.0 61 | #define ECU_VEHICLE_SPEED_MIN 0.0 62 | #define ECU_IAT_TEMPERATURE_MAX 215.0 63 | #define ECU_IAT_TEMPERATURE_MIN -40.0 64 | #define ECU_ENGINE_RPM_MAX 8000.0 65 | #define ECU_ENGINE_RPM_MIN 0.0 66 | #define ECU_MAP_PRESSURE_MAX 255.0 67 | #define ECU_MAP_PRESSURE_MIN 0.0 68 | 69 | /* OBD Message Types. */ 70 | #define OBD_MSG_MODE01_PARAMETER 1 71 | #define OBD_MSG_VOLTAGE 2 72 | #define OBD_MSG_INTERFACE 3 73 | #define OBD_MSG_PROTOCOL 4 74 | #define OBD_MSG_MODE03_PARAMETER 5 75 | #define OBD_MSG_MODE09_PARAMETER 6 76 | 77 | /* Type Definitions. */ 78 | 79 | struct _ECU_Parameters { 80 | double ecu_engine_rpm; 81 | double ecu_vehicle_speed; 82 | double ecu_coolant_temperature; 83 | double ecu_intake_air_temperature; 84 | double ecu_manifold_air_pressure; 85 | double ecu_oil_pressure; 86 | double ecu_egr_pressure; 87 | double ecu_battery_voltage; 88 | double ecu_throttle_position; 89 | double ecu_oil_temperature; 90 | double ecu_accelerator_position; 91 | double ecu_fuel_pressure; 92 | double ecu_fuel_flow_rate; 93 | double ecu_fuel_tank_level; 94 | double ecu_timing_advance; 95 | int ecu_mil_status; 96 | int ecu_dtc_count; 97 | char ecu_last_dtc_code[16]; 98 | char ecu_vin[256]; 99 | char ecu_name[256]; 100 | char ecu_manufacturer[256]; 101 | char battery_voltage[256]; 102 | }; 103 | 104 | typedef struct _ECU_Parameters ECU_Parameters; 105 | 106 | struct _OBD_Interface { 107 | unsigned int obd_interface_status; 108 | unsigned int obd_rs232_baud; 109 | unsigned int obd_stop_bits; 110 | unsigned int obd_data_bits; 111 | unsigned int obd_parity_bits; 112 | int obd_protocol_number; 113 | char obd_protocol_name[256]; 114 | char obd_interface_name[256]; 115 | }; 116 | 117 | typedef struct _OBD_Interface OBD_Interface; 118 | 119 | struct _PID_Parameters { 120 | char pid_code[16]; 121 | unsigned int pid_num; 122 | unsigned int pid_supported; 123 | unsigned int pid_data_bytes; 124 | unsigned int pid_scale_factor; 125 | char pid_formula[256]; 126 | char pid_description[256]; 127 | char pid_gauge_label[32]; 128 | UT_hash_handle hh; 129 | }; 130 | 131 | typedef struct _PID_Parameters PID_Parameters; 132 | 133 | struct _DTC_Parameters { 134 | char dtc_code[16]; 135 | unsigned int dtc_number; 136 | unsigned int dtc_set; 137 | char dtc_description[256]; 138 | char dtc_date_time[256]; 139 | UT_hash_handle hh; 140 | }; 141 | 142 | typedef struct _DTC_Parameters DTC_Parameters; 143 | 144 | /* Function Declarations. */ 145 | 146 | /* OBD Interface Status. */ 147 | void set_interface_on(); 148 | void set_interface_off(); 149 | int get_interface_status(); 150 | void set_obd_protocol_number(int obdpnum); 151 | int get_obd_protocol_number(); 152 | void set_obd_protocol_name(char *obdname); 153 | void get_obd_protocol_name(char *info); 154 | void set_interface_information(char *ii_msg); 155 | void get_interface_information(char *info); 156 | void get_vehicle_vin(char *vin); 157 | void get_ecu_name(char *ecu); 158 | 159 | /* ECU Parameter Get/Set Functions. */ 160 | 161 | void set_ecu_parameters(ECU_Parameters *ecup); 162 | void get_ecu_parameters(ECU_Parameters *ecup); 163 | 164 | double get_engine_rpm(); 165 | double get_coolant_temperature(); 166 | double get_manifold_pressure(); 167 | double get_intake_air_temperature(); 168 | double get_battery_voltage(); 169 | double get_vehicle_speed(); 170 | double get_egr_pressure(); 171 | double get_oil_temperature(); 172 | double get_oil_pressure(); 173 | double get_throttle_position(); 174 | double get_fuel_tank_level(); 175 | double get_fuel_flow_rate(); 176 | double get_fuel_pressure(); 177 | double get_accelerator_position(); 178 | double get_timing_advance(); 179 | 180 | int get_mil_status(); 181 | int get_dtc_count(); 182 | void get_last_dtc_code(char *code_buf); 183 | 184 | /* Message Parsers. */ 185 | int parse_obd_msg(char *obd_msg); 186 | 187 | 188 | #endif 189 | 190 | -------------------------------------------------------------------------------- /src/rs232.c: -------------------------------------------------------------------------------- 1 | /* 2 | *************************************************************************** 3 | * 4 | * Author: Teunis van Beelen 5 | * 6 | * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Teunis van Beelen 7 | * 8 | * Email: teuniz@gmail.com 9 | * 10 | *************************************************************************** 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | *************************************************************************** 26 | */ 27 | 28 | 29 | /* Last revision: November 22, 2017 */ 30 | 31 | /* For more info and how to use this library, visit: http://www.teuniz.net/RS-232/ */ 32 | 33 | 34 | #include "rs232.h" 35 | 36 | 37 | #if defined(__linux__) || defined(__FreeBSD__) /* Linux & FreeBSD */ 38 | 39 | #define RS232_PORTNR 38 40 | 41 | 42 | int Cport[RS232_PORTNR], 43 | error; 44 | 45 | struct termios new_port_settings, 46 | old_port_settings[RS232_PORTNR]; 47 | 48 | char *comports[RS232_PORTNR]={"/dev/ttyS0","/dev/ttyS1","/dev/ttyS2","/dev/ttyS3","/dev/ttyS4","/dev/ttyS5", 49 | "/dev/ttyS6","/dev/ttyS7","/dev/ttyS8","/dev/ttyS9","/dev/ttyS10","/dev/ttyS11", 50 | "/dev/ttyS12","/dev/ttyS13","/dev/ttyS14","/dev/ttyS15","/dev/ttyUSB0", 51 | "/dev/ttyUSB1","/dev/ttyUSB2","/dev/ttyUSB3","/dev/ttyUSB4","/dev/ttyUSB5", 52 | "/dev/ttyAMA0","/dev/ttyAMA1","/dev/ttyACM0","/dev/ttyACM1", 53 | "/dev/rfcomm0","/dev/rfcomm1","/dev/ircomm0","/dev/ircomm1", 54 | "/dev/cuau0","/dev/cuau1","/dev/cuau2","/dev/cuau3", 55 | "/dev/cuaU0","/dev/cuaU1","/dev/cuaU2","/dev/cuaU3"}; 56 | 57 | int RS232_OpenComport(int comport_number, int baudrate, const char *mode) 58 | { 59 | int baudr, 60 | status; 61 | 62 | if((comport_number>=RS232_PORTNR)||(comport_number<0)) 63 | { 64 | printf("illegal comport number\n"); 65 | return(1); 66 | } 67 | 68 | switch(baudrate) 69 | { 70 | case 50 : baudr = B50; 71 | break; 72 | case 75 : baudr = B75; 73 | break; 74 | case 110 : baudr = B110; 75 | break; 76 | case 134 : baudr = B134; 77 | break; 78 | case 150 : baudr = B150; 79 | break; 80 | case 200 : baudr = B200; 81 | break; 82 | case 300 : baudr = B300; 83 | break; 84 | case 600 : baudr = B600; 85 | break; 86 | case 1200 : baudr = B1200; 87 | break; 88 | case 1800 : baudr = B1800; 89 | break; 90 | case 2400 : baudr = B2400; 91 | break; 92 | case 4800 : baudr = B4800; 93 | break; 94 | case 9600 : baudr = B9600; 95 | break; 96 | case 19200 : baudr = B19200; 97 | break; 98 | case 38400 : baudr = B38400; 99 | break; 100 | case 57600 : baudr = B57600; 101 | break; 102 | case 115200 : baudr = B115200; 103 | break; 104 | case 230400 : baudr = B230400; 105 | break; 106 | case 460800 : baudr = B460800; 107 | break; 108 | case 500000 : baudr = B500000; 109 | break; 110 | case 576000 : baudr = B576000; 111 | break; 112 | case 921600 : baudr = B921600; 113 | break; 114 | case 1000000 : baudr = B1000000; 115 | break; 116 | case 1152000 : baudr = B1152000; 117 | break; 118 | case 1500000 : baudr = B1500000; 119 | break; 120 | case 2000000 : baudr = B2000000; 121 | break; 122 | case 2500000 : baudr = B2500000; 123 | break; 124 | case 3000000 : baudr = B3000000; 125 | break; 126 | case 3500000 : baudr = B3500000; 127 | break; 128 | case 4000000 : baudr = B4000000; 129 | break; 130 | default : printf("invalid baudrate\n"); 131 | return(1); 132 | break; 133 | } 134 | 135 | int cbits=CS8, 136 | cpar=0, 137 | ipar=IGNPAR, 138 | bstop=0; 139 | 140 | if(strlen(mode) != 3) 141 | { 142 | printf("invalid mode \"%s\"\n", mode); 143 | return(1); 144 | } 145 | 146 | switch(mode[0]) 147 | { 148 | case '8': cbits = CS8; 149 | break; 150 | case '7': cbits = CS7; 151 | break; 152 | case '6': cbits = CS6; 153 | break; 154 | case '5': cbits = CS5; 155 | break; 156 | default : printf("invalid number of data-bits '%c'\n", mode[0]); 157 | return(1); 158 | break; 159 | } 160 | 161 | switch(mode[1]) 162 | { 163 | case 'N': 164 | case 'n': cpar = 0; 165 | ipar = IGNPAR; 166 | break; 167 | case 'E': 168 | case 'e': cpar = PARENB; 169 | ipar = INPCK; 170 | break; 171 | case 'O': 172 | case 'o': cpar = (PARENB | PARODD); 173 | ipar = INPCK; 174 | break; 175 | default : printf("invalid parity '%c'\n", mode[1]); 176 | return(1); 177 | break; 178 | } 179 | 180 | switch(mode[2]) 181 | { 182 | case '1': bstop = 0; 183 | break; 184 | case '2': bstop = CSTOPB; 185 | break; 186 | default : printf("invalid number of stop bits '%c'\n", mode[2]); 187 | return(1); 188 | break; 189 | } 190 | 191 | /* 192 | http://pubs.opengroup.org/onlinepubs/7908799/xsh/termios.h.html 193 | 194 | http://man7.org/linux/man-pages/man3/termios.3.html 195 | */ 196 | 197 | Cport[comport_number] = open(comports[comport_number], O_RDWR | O_NOCTTY | O_NDELAY); 198 | if(Cport[comport_number]==-1) 199 | { 200 | perror("unable to open comport "); 201 | return(1); 202 | } 203 | 204 | /* lock access so that another process can't also use the port */ 205 | if(flock(Cport[comport_number], LOCK_EX | LOCK_NB) != 0) 206 | { 207 | close(Cport[comport_number]); 208 | perror("Another process has locked the comport."); 209 | return(1); 210 | } 211 | 212 | error = tcgetattr(Cport[comport_number], old_port_settings + comport_number); 213 | if(error==-1) 214 | { 215 | close(Cport[comport_number]); 216 | flock(Cport[comport_number], LOCK_UN); /* free the port so that others can use it. */ 217 | perror("unable to read portsettings "); 218 | return(1); 219 | } 220 | memset(&new_port_settings, 0, sizeof(new_port_settings)); /* clear the new struct */ 221 | 222 | new_port_settings.c_cflag = cbits | cpar | bstop | CLOCAL | CREAD; 223 | new_port_settings.c_iflag = ipar; 224 | new_port_settings.c_oflag = 0; 225 | new_port_settings.c_lflag = 0; 226 | new_port_settings.c_cc[VMIN] = 0; /* block untill n bytes are received */ 227 | new_port_settings.c_cc[VTIME] = 0; /* block untill a timer expires (n * 100 mSec.) */ 228 | 229 | cfsetispeed(&new_port_settings, baudr); 230 | cfsetospeed(&new_port_settings, baudr); 231 | 232 | error = tcsetattr(Cport[comport_number], TCSANOW, &new_port_settings); 233 | if(error==-1) 234 | { 235 | tcsetattr(Cport[comport_number], TCSANOW, old_port_settings + comport_number); 236 | close(Cport[comport_number]); 237 | flock(Cport[comport_number], LOCK_UN); /* free the port so that others can use it. */ 238 | perror("unable to adjust portsettings "); 239 | return(1); 240 | } 241 | 242 | /* http://man7.org/linux/man-pages/man4/tty_ioctl.4.html */ 243 | 244 | if(ioctl(Cport[comport_number], TIOCMGET, &status) == -1) 245 | { 246 | tcsetattr(Cport[comport_number], TCSANOW, old_port_settings + comport_number); 247 | flock(Cport[comport_number], LOCK_UN); /* free the port so that others can use it. */ 248 | perror("unable to get portstatus"); 249 | return(1); 250 | } 251 | 252 | status |= TIOCM_DTR; /* turn on DTR */ 253 | status |= TIOCM_RTS; /* turn on RTS */ 254 | 255 | if(ioctl(Cport[comport_number], TIOCMSET, &status) == -1) 256 | { 257 | tcsetattr(Cport[comport_number], TCSANOW, old_port_settings + comport_number); 258 | flock(Cport[comport_number], LOCK_UN); /* free the port so that others can use it. */ 259 | perror("unable to set portstatus"); 260 | return(1); 261 | } 262 | 263 | return(0); 264 | } 265 | 266 | 267 | int RS232_PollComport(int comport_number, unsigned char *buf, int size) 268 | { 269 | int n; 270 | 271 | n = read(Cport[comport_number], buf, size); 272 | 273 | if(n < 0) 274 | { 275 | if(errno == EAGAIN) return 0; 276 | } 277 | 278 | return(n); 279 | } 280 | 281 | 282 | int RS232_SendByte(int comport_number, unsigned char byte) 283 | { 284 | int n = write(Cport[comport_number], &byte, 1); 285 | if(n < 0) 286 | { 287 | if(errno == EAGAIN) 288 | { 289 | return 0; 290 | } 291 | else 292 | { 293 | return 1; 294 | } 295 | } 296 | 297 | return(0); 298 | } 299 | 300 | 301 | int RS232_SendBuf(int comport_number, unsigned char *buf, int size) 302 | { 303 | int n = write(Cport[comport_number], buf, size); 304 | if(n < 0) 305 | { 306 | if(errno == EAGAIN) 307 | { 308 | return 0; 309 | } 310 | else 311 | { 312 | return -1; 313 | } 314 | } 315 | 316 | return(n); 317 | } 318 | 319 | 320 | void RS232_CloseComport(int comport_number) 321 | { 322 | int status; 323 | 324 | if(ioctl(Cport[comport_number], TIOCMGET, &status) == -1) 325 | { 326 | perror("unable to get portstatus"); 327 | } 328 | 329 | status &= ~TIOCM_DTR; /* turn off DTR */ 330 | status &= ~TIOCM_RTS; /* turn off RTS */ 331 | 332 | if(ioctl(Cport[comport_number], TIOCMSET, &status) == -1) 333 | { 334 | perror("unable to set portstatus"); 335 | } 336 | 337 | tcsetattr(Cport[comport_number], TCSANOW, old_port_settings + comport_number); 338 | close(Cport[comport_number]); 339 | 340 | flock(Cport[comport_number], LOCK_UN); /* free the port so that others can use it. */ 341 | } 342 | 343 | /* 344 | Constant Description 345 | TIOCM_LE DSR (data set ready/line enable) 346 | TIOCM_DTR DTR (data terminal ready) 347 | TIOCM_RTS RTS (request to send) 348 | TIOCM_ST Secondary TXD (transmit) 349 | TIOCM_SR Secondary RXD (receive) 350 | TIOCM_CTS CTS (clear to send) 351 | TIOCM_CAR DCD (data carrier detect) 352 | TIOCM_CD see TIOCM_CAR 353 | TIOCM_RNG RNG (ring) 354 | TIOCM_RI see TIOCM_RNG 355 | TIOCM_DSR DSR (data set ready) 356 | 357 | http://man7.org/linux/man-pages/man4/tty_ioctl.4.html 358 | */ 359 | 360 | int RS232_IsDCDEnabled(int comport_number) 361 | { 362 | int status; 363 | 364 | ioctl(Cport[comport_number], TIOCMGET, &status); 365 | 366 | if(status&TIOCM_CAR) return(1); 367 | else return(0); 368 | } 369 | 370 | 371 | int RS232_IsCTSEnabled(int comport_number) 372 | { 373 | int status; 374 | 375 | ioctl(Cport[comport_number], TIOCMGET, &status); 376 | 377 | if(status&TIOCM_CTS) return(1); 378 | else return(0); 379 | } 380 | 381 | 382 | int RS232_IsDSREnabled(int comport_number) 383 | { 384 | int status; 385 | 386 | ioctl(Cport[comport_number], TIOCMGET, &status); 387 | 388 | if(status&TIOCM_DSR) return(1); 389 | else return(0); 390 | } 391 | 392 | 393 | void RS232_enableDTR(int comport_number) 394 | { 395 | int status; 396 | 397 | if(ioctl(Cport[comport_number], TIOCMGET, &status) == -1) 398 | { 399 | perror("unable to get portstatus"); 400 | } 401 | 402 | status |= TIOCM_DTR; /* turn on DTR */ 403 | 404 | if(ioctl(Cport[comport_number], TIOCMSET, &status) == -1) 405 | { 406 | perror("unable to set portstatus"); 407 | } 408 | } 409 | 410 | 411 | void RS232_disableDTR(int comport_number) 412 | { 413 | int status; 414 | 415 | if(ioctl(Cport[comport_number], TIOCMGET, &status) == -1) 416 | { 417 | perror("unable to get portstatus"); 418 | } 419 | 420 | status &= ~TIOCM_DTR; /* turn off DTR */ 421 | 422 | if(ioctl(Cport[comport_number], TIOCMSET, &status) == -1) 423 | { 424 | perror("unable to set portstatus"); 425 | } 426 | } 427 | 428 | 429 | void RS232_enableRTS(int comport_number) 430 | { 431 | int status; 432 | 433 | if(ioctl(Cport[comport_number], TIOCMGET, &status) == -1) 434 | { 435 | perror("unable to get portstatus"); 436 | } 437 | 438 | status |= TIOCM_RTS; /* turn on RTS */ 439 | 440 | if(ioctl(Cport[comport_number], TIOCMSET, &status) == -1) 441 | { 442 | perror("unable to set portstatus"); 443 | } 444 | } 445 | 446 | 447 | void RS232_disableRTS(int comport_number) 448 | { 449 | int status; 450 | 451 | if(ioctl(Cport[comport_number], TIOCMGET, &status) == -1) 452 | { 453 | perror("unable to get portstatus"); 454 | } 455 | 456 | status &= ~TIOCM_RTS; /* turn off RTS */ 457 | 458 | if(ioctl(Cport[comport_number], TIOCMSET, &status) == -1) 459 | { 460 | perror("unable to set portstatus"); 461 | } 462 | } 463 | 464 | 465 | void RS232_flushRX(int comport_number) 466 | { 467 | tcflush(Cport[comport_number], TCIFLUSH); 468 | } 469 | 470 | 471 | void RS232_flushTX(int comport_number) 472 | { 473 | tcflush(Cport[comport_number], TCOFLUSH); 474 | } 475 | 476 | 477 | void RS232_flushRXTX(int comport_number) 478 | { 479 | tcflush(Cport[comport_number], TCIOFLUSH); 480 | } 481 | 482 | 483 | #else /* windows */ 484 | 485 | #define RS232_PORTNR 16 486 | 487 | HANDLE Cport[RS232_PORTNR]; 488 | 489 | 490 | char *comports[RS232_PORTNR]={"\\\\.\\COM1", "\\\\.\\COM2", "\\\\.\\COM3", "\\\\.\\COM4", 491 | "\\\\.\\COM5", "\\\\.\\COM6", "\\\\.\\COM7", "\\\\.\\COM8", 492 | "\\\\.\\COM9", "\\\\.\\COM10", "\\\\.\\COM11", "\\\\.\\COM12", 493 | "\\\\.\\COM13", "\\\\.\\COM14", "\\\\.\\COM15", "\\\\.\\COM16"}; 494 | 495 | char mode_str[128]; 496 | 497 | 498 | int RS232_OpenComport(int comport_number, int baudrate, const char *mode) 499 | { 500 | if((comport_number>=RS232_PORTNR)||(comport_number<0)) 501 | { 502 | printf("illegal comport number\n"); 503 | return(1); 504 | } 505 | 506 | switch(baudrate) 507 | { 508 | case 110 : strcpy(mode_str, "baud=110"); 509 | break; 510 | case 300 : strcpy(mode_str, "baud=300"); 511 | break; 512 | case 600 : strcpy(mode_str, "baud=600"); 513 | break; 514 | case 1200 : strcpy(mode_str, "baud=1200"); 515 | break; 516 | case 2400 : strcpy(mode_str, "baud=2400"); 517 | break; 518 | case 4800 : strcpy(mode_str, "baud=4800"); 519 | break; 520 | case 9600 : strcpy(mode_str, "baud=9600"); 521 | break; 522 | case 19200 : strcpy(mode_str, "baud=19200"); 523 | break; 524 | case 38400 : strcpy(mode_str, "baud=38400"); 525 | break; 526 | case 57600 : strcpy(mode_str, "baud=57600"); 527 | break; 528 | case 115200 : strcpy(mode_str, "baud=115200"); 529 | break; 530 | case 128000 : strcpy(mode_str, "baud=128000"); 531 | break; 532 | case 256000 : strcpy(mode_str, "baud=256000"); 533 | break; 534 | case 500000 : strcpy(mode_str, "baud=500000"); 535 | break; 536 | case 1000000 : strcpy(mode_str, "baud=1000000"); 537 | break; 538 | default : printf("invalid baudrate\n"); 539 | return(1); 540 | break; 541 | } 542 | 543 | if(strlen(mode) != 3) 544 | { 545 | printf("invalid mode \"%s\"\n", mode); 546 | return(1); 547 | } 548 | 549 | switch(mode[0]) 550 | { 551 | case '8': strcat(mode_str, " data=8"); 552 | break; 553 | case '7': strcat(mode_str, " data=7"); 554 | break; 555 | case '6': strcat(mode_str, " data=6"); 556 | break; 557 | case '5': strcat(mode_str, " data=5"); 558 | break; 559 | default : printf("invalid number of data-bits '%c'\n", mode[0]); 560 | return(1); 561 | break; 562 | } 563 | 564 | switch(mode[1]) 565 | { 566 | case 'N': 567 | case 'n': strcat(mode_str, " parity=n"); 568 | break; 569 | case 'E': 570 | case 'e': strcat(mode_str, " parity=e"); 571 | break; 572 | case 'O': 573 | case 'o': strcat(mode_str, " parity=o"); 574 | break; 575 | default : printf("invalid parity '%c'\n", mode[1]); 576 | return(1); 577 | break; 578 | } 579 | 580 | switch(mode[2]) 581 | { 582 | case '1': strcat(mode_str, " stop=1"); 583 | break; 584 | case '2': strcat(mode_str, " stop=2"); 585 | break; 586 | default : printf("invalid number of stop bits '%c'\n", mode[2]); 587 | return(1); 588 | break; 589 | } 590 | 591 | strcat(mode_str, " dtr=on rts=on"); 592 | 593 | /* 594 | http://msdn.microsoft.com/en-us/library/windows/desktop/aa363145%28v=vs.85%29.aspx 595 | 596 | http://technet.microsoft.com/en-us/library/cc732236.aspx 597 | */ 598 | 599 | Cport[comport_number] = CreateFileA(comports[comport_number], 600 | GENERIC_READ|GENERIC_WRITE, 601 | 0, /* no share */ 602 | NULL, /* no security */ 603 | OPEN_EXISTING, 604 | 0, /* no threads */ 605 | NULL); /* no templates */ 606 | 607 | if(Cport[comport_number]==INVALID_HANDLE_VALUE) 608 | { 609 | printf("unable to open comport\n"); 610 | return(1); 611 | } 612 | 613 | DCB port_settings; 614 | memset(&port_settings, 0, sizeof(port_settings)); /* clear the new struct */ 615 | port_settings.DCBlength = sizeof(port_settings); 616 | 617 | if(!BuildCommDCBA(mode_str, &port_settings)) 618 | { 619 | printf("unable to set comport dcb settings\n"); 620 | CloseHandle(Cport[comport_number]); 621 | return(1); 622 | } 623 | 624 | if(!SetCommState(Cport[comport_number], &port_settings)) 625 | { 626 | printf("unable to set comport cfg settings\n"); 627 | CloseHandle(Cport[comport_number]); 628 | return(1); 629 | } 630 | 631 | COMMTIMEOUTS Cptimeouts; 632 | 633 | Cptimeouts.ReadIntervalTimeout = MAXDWORD; 634 | Cptimeouts.ReadTotalTimeoutMultiplier = 0; 635 | Cptimeouts.ReadTotalTimeoutConstant = 0; 636 | Cptimeouts.WriteTotalTimeoutMultiplier = 0; 637 | Cptimeouts.WriteTotalTimeoutConstant = 0; 638 | 639 | if(!SetCommTimeouts(Cport[comport_number], &Cptimeouts)) 640 | { 641 | printf("unable to set comport time-out settings\n"); 642 | CloseHandle(Cport[comport_number]); 643 | return(1); 644 | } 645 | 646 | return(0); 647 | } 648 | 649 | 650 | int RS232_PollComport(int comport_number, unsigned char *buf, int size) 651 | { 652 | int n; 653 | 654 | /* added the void pointer cast, otherwise gcc will complain about */ 655 | /* "warning: dereferencing type-punned pointer will break strict aliasing rules" */ 656 | 657 | ReadFile(Cport[comport_number], buf, size, (LPDWORD)((void *)&n), NULL); 658 | 659 | return(n); 660 | } 661 | 662 | 663 | int RS232_SendByte(int comport_number, unsigned char byte) 664 | { 665 | int n; 666 | 667 | WriteFile(Cport[comport_number], &byte, 1, (LPDWORD)((void *)&n), NULL); 668 | 669 | if(n<0) return(1); 670 | 671 | return(0); 672 | } 673 | 674 | 675 | int RS232_SendBuf(int comport_number, unsigned char *buf, int size) 676 | { 677 | int n; 678 | 679 | if(WriteFile(Cport[comport_number], buf, size, (LPDWORD)((void *)&n), NULL)) 680 | { 681 | return(n); 682 | } 683 | 684 | return(-1); 685 | } 686 | 687 | 688 | void RS232_CloseComport(int comport_number) 689 | { 690 | CloseHandle(Cport[comport_number]); 691 | } 692 | 693 | /* 694 | http://msdn.microsoft.com/en-us/library/windows/desktop/aa363258%28v=vs.85%29.aspx 695 | */ 696 | 697 | int RS232_IsDCDEnabled(int comport_number) 698 | { 699 | int status; 700 | 701 | GetCommModemStatus(Cport[comport_number], (LPDWORD)((void *)&status)); 702 | 703 | if(status&MS_RLSD_ON) return(1); 704 | else return(0); 705 | } 706 | 707 | 708 | int RS232_IsCTSEnabled(int comport_number) 709 | { 710 | int status; 711 | 712 | GetCommModemStatus(Cport[comport_number], (LPDWORD)((void *)&status)); 713 | 714 | if(status&MS_CTS_ON) return(1); 715 | else return(0); 716 | } 717 | 718 | 719 | int RS232_IsDSREnabled(int comport_number) 720 | { 721 | int status; 722 | 723 | GetCommModemStatus(Cport[comport_number], (LPDWORD)((void *)&status)); 724 | 725 | if(status&MS_DSR_ON) return(1); 726 | else return(0); 727 | } 728 | 729 | 730 | void RS232_enableDTR(int comport_number) 731 | { 732 | EscapeCommFunction(Cport[comport_number], SETDTR); 733 | } 734 | 735 | 736 | void RS232_disableDTR(int comport_number) 737 | { 738 | EscapeCommFunction(Cport[comport_number], CLRDTR); 739 | } 740 | 741 | 742 | void RS232_enableRTS(int comport_number) 743 | { 744 | EscapeCommFunction(Cport[comport_number], SETRTS); 745 | } 746 | 747 | 748 | void RS232_disableRTS(int comport_number) 749 | { 750 | EscapeCommFunction(Cport[comport_number], CLRRTS); 751 | } 752 | 753 | /* 754 | https://msdn.microsoft.com/en-us/library/windows/desktop/aa363428%28v=vs.85%29.aspx 755 | */ 756 | 757 | void RS232_flushRX(int comport_number) 758 | { 759 | PurgeComm(Cport[comport_number], PURGE_RXCLEAR | PURGE_RXABORT); 760 | } 761 | 762 | 763 | void RS232_flushTX(int comport_number) 764 | { 765 | PurgeComm(Cport[comport_number], PURGE_TXCLEAR | PURGE_TXABORT); 766 | } 767 | 768 | 769 | void RS232_flushRXTX(int comport_number) 770 | { 771 | PurgeComm(Cport[comport_number], PURGE_RXCLEAR | PURGE_RXABORT); 772 | PurgeComm(Cport[comport_number], PURGE_TXCLEAR | PURGE_TXABORT); 773 | } 774 | 775 | 776 | #endif 777 | 778 | 779 | void RS232_cputs(int comport_number, const char *text) /* sends a string to serial port */ 780 | { 781 | while(*text != 0) RS232_SendByte(comport_number, *(text++)); 782 | } 783 | 784 | 785 | /* return index in comports matching to device name or -1 if not found */ 786 | int RS232_GetPortnr(const char *devname) 787 | { 788 | int i; 789 | 790 | char str[32]; 791 | 792 | #if defined(__linux__) || defined(__FreeBSD__) /* Linux & FreeBSD */ 793 | strcpy(str, "/dev/"); 794 | #else /* windows */ 795 | strcpy(str, "\\\\.\\"); 796 | #endif 797 | strncat(str, devname, 16); 798 | str[31] = 0; 799 | 800 | for(i=0; i. 24 | * 25 | *************************************************************************** 26 | */ 27 | 28 | /* Last revision: August 5, 2017 */ 29 | 30 | /* For more info and how to use this library, visit: http://www.teuniz.net/RS-232/ */ 31 | 32 | 33 | #ifndef rs232_INCLUDED 34 | #define rs232_INCLUDED 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif 39 | 40 | #include 41 | #include 42 | 43 | 44 | 45 | #if defined(__linux__) || defined(__FreeBSD__) 46 | 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | #else 58 | 59 | #include 60 | 61 | #endif 62 | 63 | int RS232_OpenComport(int, int, const char *); 64 | int RS232_PollComport(int, unsigned char *, int); 65 | int RS232_SendByte(int, unsigned char); 66 | int RS232_SendBuf(int, unsigned char *, int); 67 | void RS232_CloseComport(int); 68 | void RS232_cputs(int, const char *); 69 | int RS232_IsDCDEnabled(int); 70 | int RS232_IsCTSEnabled(int); 71 | int RS232_IsDSREnabled(int); 72 | void RS232_enableDTR(int); 73 | void RS232_disableDTR(int); 74 | void RS232_enableRTS(int); 75 | void RS232_disableRTS(int); 76 | void RS232_flushRX(int); 77 | void RS232_flushTX(int); 78 | void RS232_flushRXTX(int); 79 | int RS232_GetPortnr(const char *); 80 | 81 | #ifdef __cplusplus 82 | } /* extern "C" */ 83 | #endif 84 | 85 | #endif 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/sockets.c: -------------------------------------------------------------------------------- 1 | /* 2 | sockets.c 3 | 4 | Project: OBD-II Monitor (On-Board Diagnostics) 5 | 6 | Author: Derek Chadwick 7 | 8 | Description: UDP server and client functions. 9 | 10 | 11 | Date: 18/12/2017 12 | 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #ifdef _WINSOCK 25 | 26 | #include 27 | 28 | #else 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #endif 35 | 36 | 37 | #include "obd_monitor.h" 38 | 39 | int c_sock, s_sock; 40 | unsigned int length; 41 | struct sockaddr_in obd_server, from; 42 | struct hostent *hp; 43 | int ecu_connected; 44 | int ecu_auto_connect; 45 | 46 | 47 | #ifdef _WINSOCK 48 | 49 | WSADATA wsaData; 50 | int iResult; 51 | unsigned long iMode = 1; 52 | 53 | int init_client_socket(char *server, char *port) 54 | { 55 | iResult = WSAStartup(MAKEWORD(2,2), &wsaData); 56 | if (iResult != NO_ERROR) 57 | printf("init_client_socket() : WSAStartup() failed with error: %ld\n", iResult); 58 | 59 | sock = socket(AF_INET, SOCK_DGRAM, 0); 60 | if (sock < 0) 61 | printf("init_client_socket() : socket creation failed.\n"); 62 | 63 | iResult = ioctlsocket(sock, FIONBIO, &iMode); 64 | if (iResult != NO_ERROR) 65 | printf("init_client_socket() : ioctlsocket failed with error: %ld\n", iResult); 66 | 67 | obd_server.sin_family = AF_INET; 68 | hp = gethostbyname(server); 69 | if (hp == 0) 70 | printf("init_client_socket() : Unknown host -> %s.\n", server); 71 | 72 | memcpy((char *)&obd_server.sin_addr, (char *)hp->h_addr, hp->h_length); 73 | obd_server.sin_port = htons(atoi(port)); 74 | length = sizeof(struct sockaddr_in); 75 | 76 | return(sock); 77 | } 78 | 79 | int init_server_socket(char *port) 80 | { 81 | /* TODO: */ 82 | 83 | return(sock); 84 | } 85 | 86 | 87 | #else 88 | 89 | /* NOT WINSOCK */ 90 | 91 | int init_client_socket(char *server, char *port) 92 | { 93 | c_sock = socket(AF_INET, SOCK_DGRAM, 0); 94 | if (c_sock < 0) 95 | printf("init_client_socket() : socket creation failed.\n"); 96 | 97 | obd_server.sin_family = AF_INET; 98 | hp = gethostbyname(server); 99 | if (hp == 0) 100 | printf("init_client_socket() : Unknown host -> %s.\n", server); 101 | 102 | memcpy((char *)&obd_server.sin_addr, (char *)hp->h_addr, hp->h_length); 103 | obd_server.sin_port = htons(atoi(port)); 104 | length = sizeof(struct sockaddr_in); 105 | 106 | return(c_sock); 107 | } 108 | 109 | int init_server_socket(char *port) 110 | { 111 | struct sockaddr_in server; 112 | 113 | s_sock = socket(AF_INET, SOCK_DGRAM, 0); 114 | 115 | if (s_sock < 0) 116 | printf("init_server_socket() : Opening socket"); 117 | 118 | length = sizeof(struct sockaddr_in); 119 | memset(&server, 0, length); 120 | 121 | server.sin_family=AF_INET; 122 | server.sin_addr.s_addr=INADDR_ANY; 123 | server.sin_port=htons(atoi(port)); 124 | 125 | if (bind(s_sock, (struct sockaddr *)&server, length) < 0) 126 | printf("init_server_socket() : binding"); 127 | 128 | return(s_sock); 129 | } 130 | 131 | 132 | 133 | #endif 134 | 135 | 136 | int send_ecu_msg(char *query) 137 | { 138 | int n; 139 | 140 | n = sendto(c_sock,query,strlen(query),0,(const struct sockaddr *)&obd_server,length); 141 | if (n < 0) 142 | { 143 | printf("send_ecu_msg() : Sendto failed.\n"); 144 | } 145 | else 146 | { 147 | /* TODO: Write message to log file. */ 148 | /* printf("send_ecu_msg() - SENT ECU Message: %s", buffer); */ 149 | } 150 | 151 | return n; 152 | } 153 | 154 | int recv_ecu_msg(char *msg) 155 | { 156 | int n; 157 | 158 | memset(msg,0,256); 159 | 160 | n = recvfrom(c_sock,msg,256,MSG_DONTWAIT,(struct sockaddr *)&from,&length); 161 | /* We are not blocking on recv now. */ 162 | 163 | /* 164 | if (n > 0) 165 | { 166 | printf("recv_ecu_msg() - RECV ECU Message: %s", buffer); 167 | strncpy(msg, buffer, n); 168 | } 169 | */ 170 | 171 | return(n); 172 | } 173 | 174 | 175 | int init_obd_comms(char *obd_msg) 176 | { 177 | int n; 178 | 179 | n = sendto(c_sock,obd_msg,strlen(obd_msg),0,(const struct sockaddr *)&obd_server,length); 180 | 181 | if (n <= 0) 182 | { 183 | printf("init_obd_comms() - : Sendto failed.\n"); 184 | } 185 | else 186 | { 187 | printf("init_obd_comms() - SENT OBD Message: %s", obd_msg); 188 | } 189 | 190 | 191 | return(n); 192 | } 193 | 194 | 195 | int server_connect() 196 | { 197 | int result; 198 | 199 | /* First set up UDP communication with the server process 200 | and check connection to the OBD interface. */ 201 | result = init_client_socket("127.0.0.1", "8989"); 202 | 203 | if (result <= 0) 204 | { 205 | printf("ecu_connect() : Failed to connect to OBD server.\n"); 206 | ecu_connected = 0; 207 | } 208 | else 209 | { 210 | printf("ecu_connect() : Connected to OBD server.\n"); 211 | ecu_connected = 1; 212 | } 213 | 214 | /* Do this in the GUI/Client process. 215 | else 216 | { 217 | result = init_obd_comms("ATI\n", rcv_msg_buf); 218 | if (result <= 0) 219 | { 220 | printf("ecu_connect() : Failed to connect to OBD interface.\n"); 221 | } 222 | else 223 | { 224 | ecu_connected = 1; 225 | if (strlen(protocol_req) > 5) 226 | { 227 | send_ecu_msg(protocol_req); 228 | } 229 | } 230 | } 231 | */ 232 | 233 | return(result); 234 | } 235 | 236 | int get_ecu_connected() 237 | { 238 | return(ecu_connected); 239 | } 240 | 241 | void set_ecu_connected(int cstatus) 242 | { 243 | ecu_connected = cstatus; 244 | return; 245 | } 246 | -------------------------------------------------------------------------------- /src/test_serial_rxtx.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Project: OBD-II Monitor (On-Board Diagnostics) 4 | 5 | File: test_serial_rxtx.c 6 | 7 | Author: Derek Chadwick 8 | 9 | Description: Serial communications loopback test function for USB to RS232 10 | converter modules such as the FTDI232. 11 | 12 | Usage: 13 | 1. Connect a wire between the converter module transmit and 14 | receive pins, then insert the module into a USB port. 15 | 16 | 2. Check the kernel module is loaded, for example: 17 | 18 | dmesg | grep "FTDI" 19 | 20 | 3. Check for user read/write permission on the device. 21 | 22 | ls -la /dev/ttyUSB0 23 | 24 | sudo chmod a+rw /dev/ttyUSB0 25 | 26 | or 27 | 28 | chmod +s serial_test 29 | 30 | or 31 | 32 | usermod -G dialout "user-name" 33 | 34 | 4. Run the serial loopback test with an optional device name: 35 | 36 | ./test_serial ttyUSB0 37 | 38 | or 39 | 40 | ./test_serial ttyACM0 41 | 42 | or 43 | 44 | ./test_serial 45 | 46 | 47 | 48 | Date: 30/11/2017 49 | 50 | */ 51 | 52 | 53 | 54 | 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | 65 | #include "rs232.h" 66 | 67 | char tx_msgs[2][512] = { 68 | "Serial Port Message ONE!!!\r\n", 69 | "Serial Port Message TWO!!!\r\n" 70 | }; 71 | 72 | int main(int argc, char *argv[]) 73 | { 74 | int ii, msg_num=0, n_bytes=0, 75 | cport_nr=0, /* /dev/ttyS0 (COM1 on windows) */ 76 | bdrate=38400; /* 9600 baud */ 77 | /* Make baud rate configurable. */ 78 | 79 | char serial_device[16]; 80 | 81 | char mode[]={'8','N','1',0}; 82 | unsigned char buf[4096]; 83 | 84 | int len = strlen(tx_msgs[0]); 85 | 86 | memset(serial_device, 0, 16); 87 | 88 | /* cport_nr = RS232_GetPortnr("ttyACM0"); */ 89 | if (argc > 1) 90 | { 91 | strncpy(serial_device, argv[1], 16); 92 | } 93 | else 94 | { 95 | strcpy(serial_device, "ttyUSB0"); 96 | } 97 | 98 | cport_nr = RS232_GetPortnr(serial_device); 99 | if (cport_nr == -1) 100 | { 101 | printf("ERROR: Can not get com port number.\n"); 102 | exit(-1); 103 | } 104 | 105 | if (RS232_OpenComport(cport_nr, bdrate, mode)) 106 | { 107 | printf("ERROR: Can not open comport!\n"); 108 | return(0); 109 | } 110 | 111 | printf("Serial port number: %i\n", cport_nr); 112 | 113 | for (ii = 0; ii < 10; ii++) 114 | { 115 | /* RS232_cputs(cport_nr, tx_msgs[msg_num]); */ 116 | 117 | RS232_SendBuf(cport_nr, (unsigned char*)tx_msgs[msg_num], len); 118 | 119 | printf("TXD %i bytes: %s", (int)strlen(tx_msgs[msg_num]), tx_msgs[msg_num]); 120 | 121 | usleep(100000); /* sleep for 1 Second */ 122 | 123 | msg_num++; 124 | 125 | msg_num %= 2; 126 | 127 | if ((n_bytes = RS232_PollComport(cport_nr, buf, 4095)) > 0) 128 | { 129 | int idx; 130 | 131 | buf[n_bytes] = 0; /* always put a "null" at the end of a string! */ 132 | 133 | for(idx = 0; idx < n_bytes; idx++) 134 | { 135 | if(buf[idx] < 32) /* replace unreadable control-codes by dots */ 136 | { 137 | buf[idx] = '.'; 138 | } 139 | } 140 | 141 | printf("RXD %i bytes: %s\n\n", n_bytes, (char *)buf); 142 | 143 | } 144 | } 145 | 146 | return(0); 147 | } 148 | 149 | -------------------------------------------------------------------------------- /src/test_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | Project: OBD-II Monitor (On-Board Diagnostics) 3 | 4 | 5 | File: test_server.c 6 | 7 | 8 | Author: Derek Chadwick 9 | 10 | Description: A test set for the UDP server that communicates with vehicle 11 | engine control units via an OBD-II interface to obtain 12 | engine status and fault codes. Can also be used as an ECU 13 | communications logging function when the GUI is not required. 14 | 15 | 16 | Date: 03/01/2018 17 | 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "obd_monitor.h" 31 | #include "rs232.h" 32 | 33 | const char *OBD_Protocol_List[] = { 34 | "OBD 0 - Automatic OBD-II Protocol Search", 35 | "OBD 1 - SAE J1850 PWM (41.6 kbaud)(Ford)", 36 | "OBD 2 - SAE J1850 VPW (10.4 kbaud)(GM, Isuzu)", 37 | "OBD 3 - IS0 9141-2 (5 baud init, 10.4 kbaud)(Chrysler)", 38 | "OBD 4 - ISO 14230-4 KWP2000 (5-baud init.)", 39 | "OBD 5 - IS0 14230-4 KWP2000 (Fast init.)", 40 | "OBD 6 - IS0 15765-4 CAN (11 bit ID, 500 kbaud)", 41 | "OBD 7 - IS0 15765-4 CAN (29 bit ID, 500 kbaud)", 42 | "OBD 8 - IS0 15765-4 CAN (11 bit ID, 250 kbaud)", 43 | "OBD 9 - IS0 15765-4 CAN (29 bit ID, 250 kbaud)", 44 | "OBD A - SAE J1939 CAN (29 bit ID, 250 kbaud)", 45 | "OBD B - USER1 CAN (11 bit ID, 125 kbaud)", 46 | "OBD C - USER2 CAN (11 bit ID, 50 kbaud)" 47 | }; 48 | 49 | int main(int argc, char *argv[]) 50 | { 51 | struct timespec reqtime; 52 | char protocol_req[256]; 53 | char recv_msg[256]; 54 | int ii, jj; 55 | 56 | 57 | memset(recv_msg, 0, 256); 58 | memset(protocol_req, 0, 256); 59 | reqtime.tv_sec = 1; 60 | reqtime.tv_nsec = 0; 61 | 62 | 63 | if (argc < 2) /* Get protocol number from command line. */ 64 | { 65 | strcpy(protocol_req, "ATTP 0\r"); 66 | } 67 | else 68 | { 69 | strncpy(protocol_req, "ATTP %c\r", argv[1][0]); 70 | } 71 | 72 | if (server_connect() > 0) /* Sockets Module Connect Function. */ 73 | { 74 | 75 | send_ecu_msg("ATRV\r"); 76 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 77 | recv_ecu_msg(recv_msg); 78 | printf("ATRV: %s\n", recv_msg); 79 | memset(recv_msg, 0, 256); 80 | 81 | send_ecu_msg("ATDP\r"); /* Get OBD protocol name from interface. */ 82 | nanosleep(&reqtime, NULL); 83 | recv_ecu_msg(recv_msg); 84 | printf("ATDP: %s\n", recv_msg); 85 | memset(recv_msg, 0, 256); 86 | 87 | send_ecu_msg("ATRV\r"); /* Get battery voltage from interface. */ 88 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 89 | recv_ecu_msg(recv_msg); 90 | printf("ATRV: %s\n", recv_msg); 91 | memset(recv_msg, 0, 256); 92 | 93 | send_ecu_msg("09 02\r"); /* Get vehicle VIN number. */ 94 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 95 | recv_ecu_msg(recv_msg); 96 | printf("VIN: %s\n", recv_msg); 97 | memset(recv_msg, 0, 256); 98 | 99 | send_ecu_msg("09 0A\r"); /* Get ECU name. */ 100 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 101 | recv_ecu_msg(recv_msg); 102 | printf("ECUName: %s\n", recv_msg); 103 | memset(recv_msg, 0, 256); 104 | 105 | send_ecu_msg("01 01\r"); /* Get DTC Count and MIL status. */ 106 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 107 | recv_ecu_msg(recv_msg); 108 | printf("MIL: %s\n", recv_msg); 109 | memset(recv_msg, 0, 256); 110 | 111 | send_ecu_msg("01 00\r"); /* Get supported PIDs 1 - 32 for MODE 1. */ 112 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 113 | recv_ecu_msg(recv_msg); 114 | printf("PID01: %s\n", recv_msg); 115 | memset(recv_msg, 0, 256); 116 | 117 | send_ecu_msg("09 00\r"); /* Get supported PIDs 1 - 32 for MODE 9. */ 118 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 119 | recv_ecu_msg(recv_msg); 120 | printf("PID09: %s\n", recv_msg); 121 | memset(recv_msg, 0, 256); 122 | 123 | send_ecu_msg("03\r"); /* Get DTCs that are set. */ 124 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 125 | recv_ecu_msg(recv_msg); 126 | printf("DTC: %s\n", recv_msg); 127 | memset(recv_msg, 0, 256); 128 | 129 | 130 | send_ecu_msg("01 0C\r"); /* Engine RPM */ 131 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 132 | recv_ecu_msg(recv_msg); 133 | printf("RPM: %s\n", recv_msg); 134 | memset(recv_msg, 0, 256); 135 | 136 | send_ecu_msg("01 0D\r"); /* Vehicle Speed */ 137 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 138 | recv_ecu_msg(recv_msg); 139 | printf("VS: %s\n", recv_msg); 140 | memset(recv_msg, 0, 256); 141 | 142 | send_ecu_msg("01 0A\r"); /* Fuel Pressure */ 143 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 144 | recv_ecu_msg(recv_msg); 145 | printf("FP: %s\n", recv_msg); 146 | memset(recv_msg, 0, 256); 147 | 148 | send_ecu_msg("01 0B\r"); /* MAP Pressure */ 149 | nanosleep(&reqtime, NULL); /* Sleep for 100 milliSecond. */ 150 | recv_ecu_msg(recv_msg); 151 | printf("MAP: %s\n", recv_msg); 152 | memset(recv_msg, 0, 256); 153 | 154 | send_ecu_msg("01 5E\r"); /* Fuel Flow Rate */ 155 | nanosleep(&reqtime, NULL); /* Sleep for 1 Second. */ 156 | recv_ecu_msg(recv_msg); 157 | printf("FFR: %s\n", recv_msg); 158 | memset(recv_msg, 0, 256); 159 | 160 | send_ecu_msg("01 05\r"); /* Coolant Temperature */ 161 | nanosleep(&reqtime, NULL); /* Sleep for 100 milliSecond. */ 162 | recv_ecu_msg(recv_msg); 163 | printf("ECT: %s\n", recv_msg); 164 | memset(recv_msg, 0, 256); 165 | 166 | send_ecu_msg("01 2F\r"); /* Fuel Tank Level */ 167 | nanosleep(&reqtime, NULL); /* Sleep for 100 milliSecond. */ 168 | recv_ecu_msg(recv_msg); 169 | printf("FTL: %s\n", recv_msg); 170 | memset(recv_msg, 0, 256); 171 | 172 | send_ecu_msg("01 0F\r"); /* Intake Air Temperature */ 173 | nanosleep(&reqtime, NULL); /* Sleep for 100 milliSecond. */ 174 | recv_ecu_msg(recv_msg); 175 | printf("IAT: %s\n", recv_msg); 176 | memset(recv_msg, 0, 256); 177 | 178 | send_ecu_msg("01 5C\r"); /* Oil Temperature */ 179 | nanosleep(&reqtime, NULL); /* Sleep for 100 milliSecond. */ 180 | recv_ecu_msg(recv_msg); 181 | printf("OILT: %s\n", recv_msg); 182 | memset(recv_msg, 0, 256); 183 | 184 | 185 | send_ecu_msg("02 02\r"); /* Freeze Frame DTC. */ 186 | nanosleep(&reqtime, NULL); 187 | recv_ecu_msg(recv_msg); 188 | printf("Freeze Frame DTC: %s\n", recv_msg); 189 | memset(recv_msg, 0, 256); 190 | 191 | 192 | for (ii = 0; ii < 64; ii++) 193 | { 194 | /* TODO: send a bunch of PID request messages to non-standard mode numbers. */ 195 | for (jj = 0; jj < 64; jj++) 196 | { 197 | memset(protocol_req, 0, 256); 198 | sprintf(protocol_req, "%.2x %.2x\r", ii, jj); 199 | printf("Sending supported PID request: %s\n", protocol_req); 200 | send_ecu_msg(protocol_req); 201 | nanosleep(&reqtime, NULL); 202 | while (recv_ecu_msg(recv_msg) > 0) 203 | { 204 | printf("ECU: <%d> %s\n", ii, recv_msg); 205 | memset(recv_msg, 0, 256); 206 | } 207 | } 208 | } 209 | 210 | } 211 | else 212 | { 213 | printf("Connection to ECU failed.\n"); 214 | exit(-1); 215 | } 216 | 217 | printf("Server Test Exiting...\n"); 218 | 219 | return(0); 220 | } 221 | 222 | -------------------------------------------------------------------------------- /src/tinyexpr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * TINYEXPR - Tiny recursive descent parser and evaluation engine in C 3 | * 4 | * Copyright (c) 2015, 2016 Lewis Van Winkle 5 | * 6 | * http://CodePlea.com 7 | * 8 | * This software is provided 'as-is', without any express or implied 9 | * warranty. In no event will the authors be held liable for any damages 10 | * arising from the use of this software. 11 | * 12 | * Permission is granted to anyone to use this software for any purpose, 13 | * including commercial applications, and to alter it and redistribute it 14 | * freely, subject to the following restrictions: 15 | * 16 | * 1. The origin of this software must not be misrepresented; you must not 17 | * claim that you wrote the original software. If you use this software 18 | * in a product, an acknowledgement in the product documentation would be 19 | * appreciated but is not required. 20 | * 2. Altered source versions must be plainly marked as such, and must not be 21 | * misrepresented as being the original software. 22 | * 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | /* COMPILE TIME OPTIONS */ 26 | 27 | /* Exponentiation associativity: 28 | For a^b^c = (a^b)^c and -a^b = (-a)^b do nothing. 29 | For a^b^c = a^(b^c) and -a^b = -(a^b) uncomment the next line.*/ 30 | /* #define TE_POW_FROM_RIGHT */ 31 | 32 | /* Logarithms 33 | For log = base 10 log do nothing 34 | For log = natural log uncomment the next line. */ 35 | /* #define TE_NAT_LOG */ 36 | 37 | #include "tinyexpr.h" 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #ifndef NAN 45 | #define NAN (0.0/0.0) 46 | #endif 47 | 48 | #ifndef INFINITY 49 | #define INFINITY (1.0/0.0) 50 | #endif 51 | 52 | 53 | typedef double (*te_fun2)(double, double); 54 | 55 | enum { 56 | TOK_NULL = TE_CLOSURE7+1, TOK_ERROR, TOK_END, TOK_SEP, 57 | TOK_OPEN, TOK_CLOSE, TOK_NUMBER, TOK_VARIABLE, TOK_INFIX 58 | }; 59 | 60 | 61 | enum {TE_CONSTANT = 1}; 62 | 63 | 64 | typedef struct state { 65 | const char *start; 66 | const char *next; 67 | int type; 68 | union {double value; const double *bound; const void *function;}; 69 | void *context; 70 | 71 | const te_variable *lookup; 72 | int lookup_len; 73 | } state; 74 | 75 | 76 | #define TYPE_MASK(TYPE) ((TYPE)&0x0000001F) 77 | 78 | #define IS_PURE(TYPE) (((TYPE) & TE_FLAG_PURE) != 0) 79 | #define IS_FUNCTION(TYPE) (((TYPE) & TE_FUNCTION0) != 0) 80 | #define IS_CLOSURE(TYPE) (((TYPE) & TE_CLOSURE0) != 0) 81 | #define ARITY(TYPE) ( ((TYPE) & (TE_FUNCTION0 | TE_CLOSURE0)) ? ((TYPE) & 0x00000007) : 0 ) 82 | #define NEW_EXPR(type, ...) new_expr((type), (const te_expr*[]){__VA_ARGS__}) 83 | 84 | static te_expr *new_expr(const int type, const te_expr *parameters[]) { 85 | const int arity = ARITY(type); 86 | const int psize = sizeof(void*) * arity; 87 | const int size = (sizeof(te_expr) - sizeof(void*)) + psize + (IS_CLOSURE(type) ? sizeof(void*) : 0); 88 | te_expr *ret = malloc(size); 89 | memset(ret, 0, size); 90 | if (arity && parameters) { 91 | memcpy(ret->parameters, parameters, psize); 92 | } 93 | ret->type = type; 94 | ret->bound = 0; 95 | return ret; 96 | } 97 | 98 | 99 | void te_free_parameters(te_expr *n) { 100 | if (!n) return; 101 | switch (TYPE_MASK(n->type)) { 102 | case TE_FUNCTION7: case TE_CLOSURE7: te_free(n->parameters[6]); 103 | case TE_FUNCTION6: case TE_CLOSURE6: te_free(n->parameters[5]); 104 | case TE_FUNCTION5: case TE_CLOSURE5: te_free(n->parameters[4]); 105 | case TE_FUNCTION4: case TE_CLOSURE4: te_free(n->parameters[3]); 106 | case TE_FUNCTION3: case TE_CLOSURE3: te_free(n->parameters[2]); 107 | case TE_FUNCTION2: case TE_CLOSURE2: te_free(n->parameters[1]); 108 | case TE_FUNCTION1: case TE_CLOSURE1: te_free(n->parameters[0]); 109 | } 110 | } 111 | 112 | 113 | void te_free(te_expr *n) { 114 | if (!n) return; 115 | te_free_parameters(n); 116 | free(n); 117 | } 118 | 119 | 120 | static double pi() {return 3.14159265358979323846;} 121 | static double e() {return 2.71828182845904523536;} 122 | static double fac(double a) {/* simplest version of fac */ 123 | if (a < 0.0) 124 | return NAN; 125 | if (a > UINT_MAX) 126 | return INFINITY; 127 | unsigned int ua = (unsigned int)(a); 128 | unsigned long int result = 1, i; 129 | for (i = 1; i <= ua; i++) { 130 | if (i > ULONG_MAX / result) 131 | return INFINITY; 132 | result *= i; 133 | } 134 | return (double)result; 135 | } 136 | static double ncr(double n, double r) { 137 | if (n < 0.0 || r < 0.0 || n < r) return NAN; 138 | if (n > UINT_MAX || r > UINT_MAX) return INFINITY; 139 | unsigned long int un = (unsigned int)(n), ur = (unsigned int)(r), i; 140 | unsigned long int result = 1; 141 | if (ur > un / 2) ur = un - ur; 142 | for (i = 1; i <= ur; i++) { 143 | if (result > ULONG_MAX / (un - ur + i)) 144 | return INFINITY; 145 | result *= un - ur + i; 146 | result /= i; 147 | } 148 | return result; 149 | } 150 | static double npr(double n, double r) {return ncr(n, r) * fac(r);} 151 | 152 | static const te_variable functions[] = { 153 | /* must be in alphabetical order */ 154 | {"abs", fabs, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 155 | {"acos", acos, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 156 | {"asin", asin, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 157 | {"atan", atan, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 158 | {"atan2", atan2, TE_FUNCTION2 | TE_FLAG_PURE, 0}, 159 | {"ceil", ceil, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 160 | {"cos", cos, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 161 | {"cosh", cosh, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 162 | {"e", e, TE_FUNCTION0 | TE_FLAG_PURE, 0}, 163 | {"exp", exp, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 164 | {"fac", fac, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 165 | {"floor", floor, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 166 | {"ln", log, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 167 | #ifdef TE_NAT_LOG 168 | {"log", log, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 169 | #else 170 | {"log", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 171 | #endif 172 | {"log10", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 173 | {"ncr", ncr, TE_FUNCTION2 | TE_FLAG_PURE, 0}, 174 | {"npr", npr, TE_FUNCTION2 | TE_FLAG_PURE, 0}, 175 | {"pi", pi, TE_FUNCTION0 | TE_FLAG_PURE, 0}, 176 | {"pow", pow, TE_FUNCTION2 | TE_FLAG_PURE, 0}, 177 | {"sin", sin, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 178 | {"sinh", sinh, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 179 | {"sqrt", sqrt, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 180 | {"tan", tan, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 181 | {"tanh", tanh, TE_FUNCTION1 | TE_FLAG_PURE, 0}, 182 | {0, 0, 0, 0} 183 | }; 184 | 185 | static const te_variable *find_builtin(const char *name, int len) { 186 | int imin = 0; 187 | int imax = sizeof(functions) / sizeof(te_variable) - 2; 188 | 189 | /*Binary search.*/ 190 | while (imax >= imin) { 191 | const int i = (imin + ((imax-imin)/2)); 192 | int c = strncmp(name, functions[i].name, len); 193 | if (!c) c = '\0' - functions[i].name[len]; 194 | if (c == 0) { 195 | return functions + i; 196 | } else if (c > 0) { 197 | imin = i + 1; 198 | } else { 199 | imax = i - 1; 200 | } 201 | } 202 | 203 | return 0; 204 | } 205 | 206 | static const te_variable *find_lookup(const state *s, const char *name, int len) { 207 | int iters; 208 | const te_variable *var; 209 | if (!s->lookup) return 0; 210 | 211 | for (var = s->lookup, iters = s->lookup_len; iters; ++var, --iters) { 212 | if (strncmp(name, var->name, len) == 0 && var->name[len] == '\0') { 213 | return var; 214 | } 215 | } 216 | return 0; 217 | } 218 | 219 | 220 | 221 | static double add(double a, double b) {return a + b;} 222 | static double sub(double a, double b) {return a - b;} 223 | static double mul(double a, double b) {return a * b;} 224 | static double divide(double a, double b) {return a / b;} 225 | static double negate(double a) {return -a;} 226 | static double comma(double a, double b) {(void)a; return b;} 227 | 228 | 229 | void next_token(state *s) { 230 | s->type = TOK_NULL; 231 | 232 | do { 233 | 234 | if (!*s->next){ 235 | s->type = TOK_END; 236 | return; 237 | } 238 | 239 | /* Try reading a number. */ 240 | if ((s->next[0] >= '0' && s->next[0] <= '9') || s->next[0] == '.') { 241 | s->value = strtod(s->next, (char**)&s->next); 242 | s->type = TOK_NUMBER; 243 | } else { 244 | /* Look for a variable or builtin function call. */ 245 | if (s->next[0] >= 'a' && s->next[0] <= 'z') { 246 | const char *start; 247 | start = s->next; 248 | while ((s->next[0] >= 'a' && s->next[0] <= 'z') || (s->next[0] >= '0' && s->next[0] <= '9') || (s->next[0] == '_')) s->next++; 249 | 250 | const te_variable *var = find_lookup(s, start, s->next - start); 251 | if (!var) var = find_builtin(start, s->next - start); 252 | 253 | if (!var) { 254 | s->type = TOK_ERROR; 255 | } else { 256 | switch(TYPE_MASK(var->type)) 257 | { 258 | case TE_VARIABLE: 259 | s->type = TOK_VARIABLE; 260 | s->bound = var->address; 261 | break; 262 | 263 | case TE_CLOSURE0: case TE_CLOSURE1: case TE_CLOSURE2: case TE_CLOSURE3: 264 | case TE_CLOSURE4: case TE_CLOSURE5: case TE_CLOSURE6: case TE_CLOSURE7: 265 | s->context = var->context; 266 | 267 | case TE_FUNCTION0: case TE_FUNCTION1: case TE_FUNCTION2: case TE_FUNCTION3: 268 | case TE_FUNCTION4: case TE_FUNCTION5: case TE_FUNCTION6: case TE_FUNCTION7: 269 | s->type = var->type; 270 | s->function = var->address; 271 | break; 272 | } 273 | } 274 | 275 | } else { 276 | /* Look for an operator or special character. */ 277 | switch (s->next++[0]) { 278 | case '+': s->type = TOK_INFIX; s->function = add; break; 279 | case '-': s->type = TOK_INFIX; s->function = sub; break; 280 | case '*': s->type = TOK_INFIX; s->function = mul; break; 281 | case '/': s->type = TOK_INFIX; s->function = divide; break; 282 | case '^': s->type = TOK_INFIX; s->function = pow; break; 283 | case '%': s->type = TOK_INFIX; s->function = fmod; break; 284 | case '(': s->type = TOK_OPEN; break; 285 | case ')': s->type = TOK_CLOSE; break; 286 | case ',': s->type = TOK_SEP; break; 287 | case ' ': case '\t': case '\n': case '\r': break; 288 | default: s->type = TOK_ERROR; break; 289 | } 290 | } 291 | } 292 | } while (s->type == TOK_NULL); 293 | } 294 | 295 | 296 | static te_expr *list(state *s); 297 | static te_expr *expr(state *s); 298 | static te_expr *power(state *s); 299 | 300 | static te_expr *base(state *s) { 301 | /* = | | {"(" ")"} | | "(" {"," } ")" | "(" ")" */ 302 | te_expr *ret; 303 | int arity; 304 | 305 | switch (TYPE_MASK(s->type)) { 306 | case TOK_NUMBER: 307 | ret = new_expr(TE_CONSTANT, 0); 308 | ret->value = s->value; 309 | next_token(s); 310 | break; 311 | 312 | case TOK_VARIABLE: 313 | ret = new_expr(TE_VARIABLE, 0); 314 | ret->bound = s->bound; 315 | next_token(s); 316 | break; 317 | 318 | case TE_FUNCTION0: 319 | case TE_CLOSURE0: 320 | ret = new_expr(s->type, 0); 321 | ret->function = s->function; 322 | if (IS_CLOSURE(s->type)) ret->parameters[0] = s->context; 323 | next_token(s); 324 | if (s->type == TOK_OPEN) { 325 | next_token(s); 326 | if (s->type != TOK_CLOSE) { 327 | s->type = TOK_ERROR; 328 | } else { 329 | next_token(s); 330 | } 331 | } 332 | break; 333 | 334 | case TE_FUNCTION1: 335 | case TE_CLOSURE1: 336 | ret = new_expr(s->type, 0); 337 | ret->function = s->function; 338 | if (IS_CLOSURE(s->type)) ret->parameters[1] = s->context; 339 | next_token(s); 340 | ret->parameters[0] = power(s); 341 | break; 342 | 343 | case TE_FUNCTION2: case TE_FUNCTION3: case TE_FUNCTION4: 344 | case TE_FUNCTION5: case TE_FUNCTION6: case TE_FUNCTION7: 345 | case TE_CLOSURE2: case TE_CLOSURE3: case TE_CLOSURE4: 346 | case TE_CLOSURE5: case TE_CLOSURE6: case TE_CLOSURE7: 347 | arity = ARITY(s->type); 348 | 349 | ret = new_expr(s->type, 0); 350 | ret->function = s->function; 351 | if (IS_CLOSURE(s->type)) ret->parameters[arity] = s->context; 352 | next_token(s); 353 | 354 | if (s->type != TOK_OPEN) { 355 | s->type = TOK_ERROR; 356 | } else { 357 | int i; 358 | for(i = 0; i < arity; i++) { 359 | next_token(s); 360 | ret->parameters[i] = expr(s); 361 | if(s->type != TOK_SEP) { 362 | break; 363 | } 364 | } 365 | if(s->type != TOK_CLOSE || i != arity - 1) { 366 | s->type = TOK_ERROR; 367 | } else { 368 | next_token(s); 369 | } 370 | } 371 | 372 | break; 373 | 374 | case TOK_OPEN: 375 | next_token(s); 376 | ret = list(s); 377 | if (s->type != TOK_CLOSE) { 378 | s->type = TOK_ERROR; 379 | } else { 380 | next_token(s); 381 | } 382 | break; 383 | 384 | default: 385 | ret = new_expr(0, 0); 386 | s->type = TOK_ERROR; 387 | ret->value = NAN; 388 | break; 389 | } 390 | 391 | return ret; 392 | } 393 | 394 | 395 | static te_expr *power(state *s) { 396 | /* = {("-" | "+")} */ 397 | int sign = 1; 398 | while (s->type == TOK_INFIX && (s->function == add || s->function == sub)) { 399 | if (s->function == sub) sign = -sign; 400 | next_token(s); 401 | } 402 | 403 | te_expr *ret; 404 | 405 | if (sign == 1) { 406 | ret = base(s); 407 | } else { 408 | ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, base(s)); 409 | ret->function = negate; 410 | } 411 | 412 | return ret; 413 | } 414 | 415 | #ifdef TE_POW_FROM_RIGHT 416 | static te_expr *factor(state *s) { 417 | /* = {"^" } */ 418 | te_expr *ret = power(s); 419 | 420 | int neg = 0; 421 | te_expr *insertion = 0; 422 | 423 | if (ret->type == (TE_FUNCTION1 | TE_FLAG_PURE) && ret->function == negate) { 424 | te_expr *se = ret->parameters[0]; 425 | free(ret); 426 | ret = se; 427 | neg = 1; 428 | } 429 | 430 | while (s->type == TOK_INFIX && (s->function == pow)) { 431 | te_fun2 t = s->function; 432 | next_token(s); 433 | 434 | if (insertion) { 435 | /* Make exponentiation go right-to-left. */ 436 | te_expr *insert = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, insertion->parameters[1], power(s)); 437 | insert->function = t; 438 | insertion->parameters[1] = insert; 439 | insertion = insert; 440 | } else { 441 | ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s)); 442 | ret->function = t; 443 | insertion = ret; 444 | } 445 | } 446 | 447 | if (neg) { 448 | ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, ret); 449 | ret->function = negate; 450 | } 451 | 452 | return ret; 453 | } 454 | #else 455 | static te_expr *factor(state *s) { 456 | /* = {"^" } */ 457 | te_expr *ret = power(s); 458 | 459 | while (s->type == TOK_INFIX && (s->function == pow)) { 460 | te_fun2 t = s->function; 461 | next_token(s); 462 | ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s)); 463 | ret->function = t; 464 | } 465 | 466 | return ret; 467 | } 468 | #endif 469 | 470 | 471 | 472 | static te_expr *term(state *s) { 473 | /* = {("*" | "/" | "%") } */ 474 | te_expr *ret = factor(s); 475 | 476 | while (s->type == TOK_INFIX && (s->function == mul || s->function == divide || s->function == fmod)) { 477 | te_fun2 t = s->function; 478 | next_token(s); 479 | ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, factor(s)); 480 | ret->function = t; 481 | } 482 | 483 | return ret; 484 | } 485 | 486 | 487 | static te_expr *expr(state *s) { 488 | /* = {("+" | "-") } */ 489 | te_expr *ret = term(s); 490 | 491 | while (s->type == TOK_INFIX && (s->function == add || s->function == sub)) { 492 | te_fun2 t = s->function; 493 | next_token(s); 494 | ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, term(s)); 495 | ret->function = t; 496 | } 497 | 498 | return ret; 499 | } 500 | 501 | 502 | static te_expr *list(state *s) { 503 | /* = {"," } */ 504 | te_expr *ret = expr(s); 505 | 506 | while (s->type == TOK_SEP) { 507 | next_token(s); 508 | ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, expr(s)); 509 | ret->function = comma; 510 | } 511 | 512 | return ret; 513 | } 514 | 515 | 516 | #define TE_FUN(...) ((double(*)(__VA_ARGS__))n->function) 517 | #define M(e) te_eval(n->parameters[e]) 518 | 519 | 520 | double te_eval(const te_expr *n) { 521 | if (!n) return NAN; 522 | 523 | switch(TYPE_MASK(n->type)) { 524 | case TE_CONSTANT: return n->value; 525 | case TE_VARIABLE: return *n->bound; 526 | 527 | case TE_FUNCTION0: case TE_FUNCTION1: case TE_FUNCTION2: case TE_FUNCTION3: 528 | case TE_FUNCTION4: case TE_FUNCTION5: case TE_FUNCTION6: case TE_FUNCTION7: 529 | switch(ARITY(n->type)) { 530 | case 0: return TE_FUN(void)(); 531 | case 1: return TE_FUN(double)(M(0)); 532 | case 2: return TE_FUN(double, double)(M(0), M(1)); 533 | case 3: return TE_FUN(double, double, double)(M(0), M(1), M(2)); 534 | case 4: return TE_FUN(double, double, double, double)(M(0), M(1), M(2), M(3)); 535 | case 5: return TE_FUN(double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4)); 536 | case 6: return TE_FUN(double, double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4), M(5)); 537 | case 7: return TE_FUN(double, double, double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4), M(5), M(6)); 538 | default: return NAN; 539 | } 540 | 541 | case TE_CLOSURE0: case TE_CLOSURE1: case TE_CLOSURE2: case TE_CLOSURE3: 542 | case TE_CLOSURE4: case TE_CLOSURE5: case TE_CLOSURE6: case TE_CLOSURE7: 543 | switch(ARITY(n->type)) { 544 | case 0: return TE_FUN(void*)(n->parameters[0]); 545 | case 1: return TE_FUN(void*, double)(n->parameters[1], M(0)); 546 | case 2: return TE_FUN(void*, double, double)(n->parameters[2], M(0), M(1)); 547 | case 3: return TE_FUN(void*, double, double, double)(n->parameters[3], M(0), M(1), M(2)); 548 | case 4: return TE_FUN(void*, double, double, double, double)(n->parameters[4], M(0), M(1), M(2), M(3)); 549 | case 5: return TE_FUN(void*, double, double, double, double, double)(n->parameters[5], M(0), M(1), M(2), M(3), M(4)); 550 | case 6: return TE_FUN(void*, double, double, double, double, double, double)(n->parameters[6], M(0), M(1), M(2), M(3), M(4), M(5)); 551 | case 7: return TE_FUN(void*, double, double, double, double, double, double, double)(n->parameters[7], M(0), M(1), M(2), M(3), M(4), M(5), M(6)); 552 | default: return NAN; 553 | } 554 | 555 | default: return NAN; 556 | } 557 | 558 | } 559 | 560 | #undef TE_FUN 561 | #undef M 562 | 563 | static void optimize(te_expr *n) { 564 | /* Evaluates as much as possible. */ 565 | if (n->type == TE_CONSTANT) return; 566 | if (n->type == TE_VARIABLE) return; 567 | 568 | /* Only optimize out functions flagged as pure. */ 569 | if (IS_PURE(n->type)) { 570 | const int arity = ARITY(n->type); 571 | int known = 1; 572 | int i; 573 | for (i = 0; i < arity; ++i) { 574 | optimize(n->parameters[i]); 575 | if (((te_expr*)(n->parameters[i]))->type != TE_CONSTANT) { 576 | known = 0; 577 | } 578 | } 579 | if (known) { 580 | const double value = te_eval(n); 581 | te_free_parameters(n); 582 | n->type = TE_CONSTANT; 583 | n->value = value; 584 | } 585 | } 586 | } 587 | 588 | 589 | te_expr *te_compile(const char *expression, const te_variable *variables, int var_count, int *error) { 590 | state s; 591 | s.start = s.next = expression; 592 | s.lookup = variables; 593 | s.lookup_len = var_count; 594 | 595 | next_token(&s); 596 | te_expr *root = list(&s); 597 | 598 | if (s.type != TOK_END) { 599 | te_free(root); 600 | if (error) { 601 | *error = (s.next - s.start); 602 | if (*error == 0) *error = 1; 603 | } 604 | return 0; 605 | } else { 606 | optimize(root); 607 | if (error) *error = 0; 608 | return root; 609 | } 610 | } 611 | 612 | 613 | double te_interp(const char *expression, int *error) { 614 | te_expr *n = te_compile(expression, 0, 0, error); 615 | double ret; 616 | if (n) { 617 | ret = te_eval(n); 618 | te_free(n); 619 | } else { 620 | ret = NAN; 621 | } 622 | return ret; 623 | } 624 | 625 | static void pn (const te_expr *n, int depth) { 626 | int i, arity; 627 | printf("%*s", depth, ""); 628 | 629 | switch(TYPE_MASK(n->type)) { 630 | case TE_CONSTANT: printf("%f\n", n->value); break; 631 | case TE_VARIABLE: printf("bound %p\n", n->bound); break; 632 | 633 | case TE_FUNCTION0: case TE_FUNCTION1: case TE_FUNCTION2: case TE_FUNCTION3: 634 | case TE_FUNCTION4: case TE_FUNCTION5: case TE_FUNCTION6: case TE_FUNCTION7: 635 | case TE_CLOSURE0: case TE_CLOSURE1: case TE_CLOSURE2: case TE_CLOSURE3: 636 | case TE_CLOSURE4: case TE_CLOSURE5: case TE_CLOSURE6: case TE_CLOSURE7: 637 | arity = ARITY(n->type); 638 | printf("f%d", arity); 639 | for(i = 0; i < arity; i++) { 640 | printf(" %p", n->parameters[i]); 641 | } 642 | printf("\n"); 643 | for(i = 0; i < arity; i++) { 644 | pn(n->parameters[i], depth + 1); 645 | } 646 | break; 647 | } 648 | } 649 | 650 | 651 | void te_print(const te_expr *n) { 652 | pn(n, 0); 653 | } 654 | -------------------------------------------------------------------------------- /src/tinyexpr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * TINYEXPR - Tiny recursive descent parser and evaluation engine in C 3 | * 4 | * Copyright (c) 2015, 2016 Lewis Van Winkle 5 | * 6 | * http://CodePlea.com 7 | * 8 | * This software is provided 'as-is', without any express or implied 9 | * warranty. In no event will the authors be held liable for any damages 10 | * arising from the use of this software. 11 | * 12 | * Permission is granted to anyone to use this software for any purpose, 13 | * including commercial applications, and to alter it and redistribute it 14 | * freely, subject to the following restrictions: 15 | * 16 | * 1. The origin of this software must not be misrepresented; you must not 17 | * claim that you wrote the original software. If you use this software 18 | * in a product, an acknowledgement in the product documentation would be 19 | * appreciated but is not required. 20 | * 2. Altered source versions must be plainly marked as such, and must not be 21 | * misrepresented as being the original software. 22 | * 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | #ifndef __TINYEXPR_H__ 26 | #define __TINYEXPR_H__ 27 | 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | 34 | 35 | typedef struct te_expr { 36 | int type; 37 | union {double value; const double *bound; const void *function;}; 38 | void *parameters[1]; 39 | } te_expr; 40 | 41 | 42 | enum { 43 | TE_VARIABLE = 0, 44 | 45 | TE_FUNCTION0 = 8, TE_FUNCTION1, TE_FUNCTION2, TE_FUNCTION3, 46 | TE_FUNCTION4, TE_FUNCTION5, TE_FUNCTION6, TE_FUNCTION7, 47 | 48 | TE_CLOSURE0 = 16, TE_CLOSURE1, TE_CLOSURE2, TE_CLOSURE3, 49 | TE_CLOSURE4, TE_CLOSURE5, TE_CLOSURE6, TE_CLOSURE7, 50 | 51 | TE_FLAG_PURE = 32 52 | }; 53 | 54 | typedef struct te_variable { 55 | const char *name; 56 | const void *address; 57 | int type; 58 | void *context; 59 | } te_variable; 60 | 61 | 62 | 63 | /* Parses the input expression, evaluates it, and frees it. */ 64 | /* Returns NaN on error. */ 65 | double te_interp(const char *expression, int *error); 66 | 67 | /* Parses the input expression and binds variables. */ 68 | /* Returns NULL on error. */ 69 | te_expr *te_compile(const char *expression, const te_variable *variables, int var_count, int *error); 70 | 71 | /* Evaluates the expression. */ 72 | double te_eval(const te_expr *n); 73 | 74 | /* Prints debugging information on the syntax tree. */ 75 | void te_print(const te_expr *n); 76 | 77 | /* Frees the expression. */ 78 | /* This is safe to call on NULL pointers. */ 79 | void te_free(te_expr *n); 80 | 81 | 82 | #ifdef __cplusplus 83 | } 84 | #endif 85 | 86 | #endif /*__TINYEXPR_H__*/ 87 | -------------------------------------------------------------------------------- /src/unit_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "obd_monitor.h" 17 | #include "pid_hash_map.h" 18 | #include "dtc_hash_map.h" 19 | 20 | const char *OBD_Protocol_List[] = { 21 | "OBD 0 - Automatic OBD-II Protocol Search", 22 | "OBD 1 - SAE J1850 PWM (41.6 kbaud)(Ford)", 23 | "OBD 2 - SAE J1850 VPW (10.4 kbaud)(GM, Isuzu)", 24 | "OBD 3 - IS0 9141-2 (5 baud init, 10.4 kbaud)(Chrysler)", 25 | "OBD 4 - ISO 14230-4 KWP2000 (5-baud init.)", 26 | "OBD 5 - IS0 14230-4 KWP2000 (Fast init.)", 27 | "OBD 6 - IS0 15765-4 CAN (11 bit ID, 500 kbaud)", 28 | "OBD 7 - IS0 15765-4 CAN (29 bit ID, 500 kbaud)", 29 | "OBD 8 - IS0 15765-4 CAN (11 bit ID, 250 kbaud)", 30 | "OBD 9 - IS0 15765-4 CAN (29 bit ID, 250 kbaud)", 31 | "OBD A - SAE J1939 CAN (29 bit ID, 250 kbaud)", 32 | "OBD B - USER1 CAN (11 bit ID, 125 kbaud)", 33 | "OBD C - USER2 CAN (11 bit ID, 50 kbaud)" 34 | }; 35 | 36 | /* Example VINs, first is non CAN protocol, second is CAN protocol 37 | for the same vehicle identification number. */ 38 | const char *ecu_vin[] = { 39 | "30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A", 40 | "49 02 31 44 34 47 50 30 30 52 35 35 42 31 32 33 34 35 36", 41 | "49 02 01 31 44 34 47 50 30 30 52 35 35 42 31 32 33 34 35 36" 42 | }; 43 | 44 | const char *ecu_name[] = { 45 | "49 0A 43 52 41 50 54 45 43 48 3A 53 59 53 54 45 4D 53 3A 30 31 32 33 34", 46 | "49 0A 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 47 48 49 4A 4B 4C", 47 | "49 0A 01 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 31 32 33 34 35 36 37 38" 48 | }; 49 | 50 | 51 | const char *test_strings[] = { 52 | "this is test STRING one.", 53 | "THIS is test STRING TWO.", 54 | "1234.....4321.....898911", 55 | "127.0.0.1", 56 | "999.999.999.999", 57 | "255.255.255.255", 58 | "01 A1 B0 30 08 01", 59 | "41 00 K0 04 C9 FF", 60 | "ATZ!ELM327 v2.1!" 61 | }; 62 | 63 | void generate_dtc_lookup_table() 64 | { 65 | return; 66 | } 67 | 68 | 69 | int main(int argc, char *argv[]) 70 | { 71 | char temp_buf[256]; 72 | char obd_msg[256]; 73 | int len, ii; 74 | 75 | memset(temp_buf, 0, 256); 76 | memset(obd_msg, 0, 256); 77 | 78 | if (argc < 2) 79 | { 80 | /* fprintf(stderr, "main() : no port provided.\n"); 81 | exit(0); */ 82 | } 83 | 84 | if (open_log_file("./", "unit_tests_log.txt") == -1) 85 | { 86 | printf("unit_test : Could not open log file.\n"); 87 | } 88 | 89 | /* 90 | ---------------------------------------------- 91 | Generate DTC Lookup Table. 92 | ---------------------------------------------- 93 | */ 94 | /* generate_dtc_lookup_table(); */ 95 | 96 | /* 97 | ---------------------------------------------- 98 | Function tests util.c 99 | ---------------------------------------------- 100 | */ 101 | for (ii = 0; ii < 3; ii++) 102 | { 103 | strcpy(obd_msg, ecu_vin[ii]); 104 | 105 | len = xhextoascii(temp_buf, obd_msg); 106 | if (len > 0) 107 | { 108 | print_log_entry(temp_buf); 109 | } 110 | else 111 | { 112 | strcpy(temp_buf, "Unknown VIN Message."); 113 | } 114 | printf("VIN: %s\n", temp_buf); 115 | } 116 | 117 | for (ii = 0; ii < 3; ii++) 118 | { 119 | strcpy(obd_msg, ecu_name[ii]); 120 | 121 | len = xhextoascii(temp_buf, obd_msg); 122 | if (len > 0) 123 | { 124 | print_log_entry(temp_buf); 125 | } 126 | else 127 | { 128 | strcpy(temp_buf, "Unknown ECU Message."); 129 | } 130 | printf("ECU: %s\n", temp_buf); 131 | } 132 | 133 | for (ii = 0; ii < 2; ii++) 134 | { 135 | strcpy(temp_buf, test_strings[ii]); 136 | 137 | uppercase(temp_buf); 138 | 139 | print_log_entry(temp_buf); 140 | 141 | printf("uppercase(): %s\n", temp_buf); 142 | } 143 | 144 | for (ii = 0; ii < 9; ii++) 145 | { 146 | strcpy(temp_buf, test_strings[ii]); 147 | 148 | replacechar(temp_buf, '.', ' '); 149 | 150 | print_log_entry(temp_buf); 151 | 152 | printf("replacechar(): %s\n", temp_buf); 153 | } 154 | 155 | /* 156 | ---------------------------------------------- 157 | Hashmap tests. 158 | ---------------------------------------------- 159 | */ 160 | 161 | for(ii = 0; ii < 10; ii++) 162 | { 163 | PID_Parameters *pid = (PID_Parameters *) xmalloc(sizeof(PID_Parameters)); 164 | sprintf(pid->pid_code, "%.4x", ii); 165 | sprintf(pid->pid_description, "MODE 01: %.4x", ii); 166 | add_pid(pid); 167 | } 168 | print_pid_map(); 169 | 170 | for(ii = 0; ii < 10; ii++) 171 | { 172 | DTC_Parameters *dtc = (DTC_Parameters *) xmalloc(sizeof(DTC_Parameters)); 173 | sprintf(dtc->dtc_code, "%.4x", ii); 174 | sprintf(dtc->dtc_description, "Powertrain: %.4x", ii); 175 | add_dtc(dtc); 176 | } 177 | print_dtc_map(); 178 | 179 | 180 | close_log_file(); 181 | 182 | exit(0); 183 | } 184 | -------------------------------------------------------------------------------- /src/unit_tests_log.txt: -------------------------------------------------------------------------------- 1 | Tue Apr 13 23:15:11 2021 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 2 | Tue Apr 13 23:15:11 2021 I 1 D 4 G P 0 0 R 5 5 B 1 2 3 4 5 6 3 | Tue Apr 13 23:15:11 2021 I 1 D 4 G P 0 0 R 5 5 B 1 2 3 4 5 6 4 | Tue Apr 13 23:15:11 2021 I C R A P T E C H : S Y S T E M S : 0 1 2 3 4 5 | Tue Apr 13 23:15:11 2021 I 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L 6 | Tue Apr 13 23:15:11 2021 I M N O P Q R S T U V W X Y Z 1 2 3 4 5 6 7 8 7 | Tue Apr 13 23:15:11 2021 THIS IS TEST STRING ONE. 8 | Tue Apr 13 23:15:11 2021 THIS IS TEST STRING TWO. 9 | Tue Apr 13 23:15:11 2021 this is test STRING one 10 | Tue Apr 13 23:15:11 2021 THIS is test STRING TWO 11 | Tue Apr 13 23:15:11 2021 1234 4321 898911 12 | Tue Apr 13 23:15:11 2021 127 0 0 1 13 | Tue Apr 13 23:15:11 2021 999 999 999 999 14 | Tue Apr 13 23:15:11 2021 255 255 255 255 15 | Tue Apr 13 23:15:11 2021 01 A1 B0 30 08 01 16 | Tue Apr 13 23:15:11 2021 41 00 K0 04 C9 FF 17 | Tue Apr 13 23:15:11 2021 ATZ!ELM327 v2 1! 18 | Tue Apr 13 23:15:11 2021 ------------------------ 19 | Tue Apr 13 23:15:11 2021 >>> Closing log session. 20 | Tue Apr 13 23:15:11 2021 ------------------------ 21 | Tue Apr 13 23:45:20 2021 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 22 | Tue Apr 13 23:45:20 2021 I 1 D 4 G P 0 0 R 5 5 B 1 2 3 4 5 6 23 | Tue Apr 13 23:45:20 2021 I 1 D 4 G P 0 0 R 5 5 B 1 2 3 4 5 6 24 | Tue Apr 13 23:45:20 2021 I C R A P T E C H : S Y S T E M S : 0 1 2 3 4 25 | Tue Apr 13 23:45:20 2021 I 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L 26 | Tue Apr 13 23:45:20 2021 I M N O P Q R S T U V W X Y Z 1 2 3 4 5 6 7 8 27 | Tue Apr 13 23:45:20 2021 THIS IS TEST STRING ONE. 28 | Tue Apr 13 23:45:20 2021 THIS IS TEST STRING TWO. 29 | Tue Apr 13 23:45:20 2021 this is test STRING one 30 | Tue Apr 13 23:45:20 2021 THIS is test STRING TWO 31 | Tue Apr 13 23:45:20 2021 1234 4321 898911 32 | Tue Apr 13 23:45:20 2021 127 0 0 1 33 | Tue Apr 13 23:45:20 2021 999 999 999 999 34 | Tue Apr 13 23:45:20 2021 255 255 255 255 35 | Tue Apr 13 23:45:20 2021 01 A1 B0 30 08 01 36 | Tue Apr 13 23:45:20 2021 41 00 K0 04 C9 FF 37 | Tue Apr 13 23:45:20 2021 ATZ!ELM327 v2 1! 38 | Tue Apr 13 23:45:20 2021 ------------------------ 39 | Tue Apr 13 23:45:20 2021 >>> Closing log session. 40 | Tue Apr 13 23:45:20 2021 ------------------------ 41 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | util.c 3 | 4 | Project: OBD-II Monitor 5 | Author : Derek Chadwick 18910502 6 | Date : 24/09/2017 7 | 8 | Purpose: Wrapper functions for various standard C lib functions to 9 | make them safer!!!. 10 | 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | /* #include 18 | #include 19 | #include */ 20 | #include 21 | #include 22 | #include 23 | 24 | #include "obd_monitor.h" 25 | 26 | /* Redefine malloc with a fatal exit. */ 27 | void *xmalloc (size_t size) 28 | { 29 | register void *value = malloc (size); 30 | if (value == 0) 31 | { 32 | xfatal("xmalloc() Virtual Memory Exhausted!!!"); 33 | } 34 | return value; 35 | } 36 | 37 | /* Redefine calloc with a fatal exit. */ 38 | void *xcalloc (size_t size) 39 | { 40 | register void *value = calloc (size, 1); 41 | if (value == 0) 42 | { 43 | xfatal("xmalloc() Virtual Memory Exhausted!!!"); 44 | } 45 | return value; 46 | } 47 | 48 | /* Redefine realloc with a fatal exit. */ 49 | void *xrealloc (void *ptr, size_t size) 50 | { 51 | register void *value = realloc (ptr, size); 52 | if (value == 0) 53 | { 54 | xfatal ("xmalloc() Virtual Memory Exhausted"); 55 | } 56 | return value; 57 | } 58 | 59 | /* Redefine free with buffer zeroing. */ 60 | int xfree(char *buf, int len) 61 | { 62 | memset(buf, 0, len); 63 | free(buf); 64 | return(0); 65 | } 66 | 67 | /* 68 | Copy a string segment specified by start and end indices. 69 | Start and end values must be 0...strlen()-1, with start 70 | being less than the end value. 71 | */ 72 | int xstrcpy(char *out_buf, char *in_buf, int start, int end) 73 | { 74 | int ii, len, result, ij; 75 | 76 | result = -1; 77 | len = strlen(in_buf); 78 | if ((start >= 0) && (end > start) && (end < len)) 79 | { 80 | ij = 0; 81 | for (ii = start; ii <= end; ii++) 82 | { 83 | out_buf[ij] = in_buf[ii]; 84 | ij++; 85 | } 86 | result = ij; 87 | } 88 | return(result); 89 | } 90 | 91 | /* 92 | Converts a string of hexadecimal values encoded as ascii characters to 93 | the equivalent ascii string. 94 | 95 | Example hexadecimal string: 96 | 97 | "30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A" 98 | 99 | converts to ascii string: 100 | 101 | "0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z" 102 | 103 | */ 104 | int xhextoascii(char *out_buf, char *in_buf) 105 | { 106 | int ii; 107 | long lnum; 108 | char temp_buf[256]; 109 | char *token; 110 | char vin_char[1]; 111 | 112 | ii = 0; 113 | memset(out_buf, 0, 256); 114 | memset(temp_buf, 0, 256); 115 | 116 | token = strtok(in_buf, " "); 117 | 118 | /* Parse the tokens. */ 119 | while( token != NULL ) 120 | { 121 | /* printf("xhextoascii() : %s\n", token); */ 122 | lnum = strtol(token, 0, 16); 123 | if ((lnum > 31) && (lnum < 124)) /* Only printable characters. */ 124 | { 125 | vin_char[0] = (char)lnum; 126 | strncat(out_buf, vin_char, 1); 127 | strcat(out_buf, " "); 128 | } 129 | 130 | token = strtok(NULL, " "); 131 | ii++; 132 | } 133 | 134 | return(ii); 135 | } 136 | 137 | /* Bail Out */ 138 | int xfatal(char *str) 139 | { 140 | printf("%s\n", str); 141 | exit(1); 142 | } 143 | 144 | /* help */ 145 | int print_help() 146 | { 147 | printf("\nOBD Monitor Version 1.0\n\n"); 148 | printf("Command: obd_gui \n\n"); 149 | printf("Log Filename : -l log.txt\n"); 150 | printf("Server UDP Port : -p 8989\n"); 151 | printf("Server IP Address: -s 127.0.0.1 \n"); 152 | printf("OBD Protocol : [0..C]\n"); 153 | printf("\n"); 154 | 155 | return(0); 156 | } 157 | 158 | /** 159 | * Modified version of char* style "itoa" with buffer length check. 160 | * (Original by Kernighan and Ritchie) 161 | * Just try to understand it, I dare you. 162 | */ 163 | char *xitoa(int value, char* result, int len, int base) 164 | { 165 | 166 | if (base < 2 || base > 36) { *result = '\0'; return result; } 167 | 168 | char* ptr = result, *ptr1 = result, tmp_char; 169 | int tmp_value; 170 | int i = 0; 171 | 172 | do { 173 | tmp_value = value; 174 | value /= base; 175 | *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)]; 176 | i++; 177 | } while ((i < len) && value ); 178 | 179 | if (tmp_value < 0) *ptr++ = '-'; 180 | *ptr-- = '\0'; 181 | while(ptr1 < ptr) 182 | { 183 | tmp_char = *ptr; 184 | *ptr--= *ptr1; 185 | *ptr1++ = tmp_char; 186 | } 187 | return result; 188 | } 189 | 190 | /* 191 | Function: get_time_string() 192 | 193 | Purpose : Gets current date and time in a string. 194 | : 195 | Input : String for date and time. 196 | Output : Formatted date and time string. 197 | */ 198 | int get_time_string(char *tstr, int slen) 199 | { 200 | time_t curtime; 201 | struct tm *loctime; 202 | int len; 203 | 204 | if ((tstr == NULL) || (slen < 15)) 205 | { 206 | printf("get_time_string() Invalid string or length.\n"); 207 | return(0); 208 | } 209 | /* Get the current time. */ 210 | 211 | curtime = time (NULL); 212 | loctime = localtime (&curtime); 213 | if ((len = strftime(tstr, slen - 1, "-%Y%m%d-%H%M%S", loctime)) < 1) 214 | { 215 | printf("get_time_string() Indeterminate time string: %s\n", tstr); 216 | } 217 | 218 | return(len); 219 | } 220 | 221 | 222 | int validate_ipv4_address(char *ipv4_addr) 223 | { 224 | /* TODO: a regex would be nice = m/\d+\.\d+\.\d+\.\d+/ */ 225 | 226 | return(0); 227 | } 228 | 229 | int validate_ipv6_address(char *ipv6_addr) 230 | { 231 | /* TODO: definitely need a regex for this one */ 232 | 233 | return(0); 234 | } 235 | 236 | char *ltrim(char *s) 237 | { 238 | while(isspace(*s)) s++; 239 | return s; 240 | } 241 | 242 | char *rtrim(char *s) 243 | { 244 | char* back = s + strlen(s); 245 | while(isspace(*--back)); 246 | *(back+1) = '\0'; 247 | return s; 248 | } 249 | 250 | char *trim(char *s) 251 | { 252 | return rtrim(ltrim(s)); 253 | } 254 | 255 | void uppercase(char *s) 256 | { 257 | while ( *s != '\0' ) 258 | { 259 | *s = toupper((unsigned char) *s); 260 | ++s; 261 | } 262 | 263 | return; 264 | } 265 | 266 | int replacechar(char *str, char orig, char rep) 267 | { 268 | char *ix = str; 269 | int n = 0; 270 | while((ix = strchr(ix, orig)) != NULL) 271 | { 272 | *ix++ = rep; 273 | n++; 274 | } 275 | return(n); 276 | } 277 | 278 | 279 | -------------------------------------------------------------------------------- /src/winsockets.c: -------------------------------------------------------------------------------- 1 | /* 2 | sockets.c 3 | 4 | Project: OBD-II Monitor (On-Board Diagnostics) 5 | 6 | Author: Derek Chadwick 7 | 8 | Description: Winsock version of UDP server and client functions. 9 | 10 | 11 | Date: 18/12/2017 12 | 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | /* #include */ 20 | #include 21 | /* #include */ 22 | #include 23 | #include 24 | 25 | #include "obd_monitor.h" 26 | 27 | int sock; 28 | unsigned int length; 29 | struct sockaddr_in obd_server, from; 30 | struct hostent *hp; 31 | int ecu_connected; 32 | int ecu_auto_connect; 33 | WSADATA wsaData; 34 | int iResult; 35 | unsigned long iMode = 1; 36 | 37 | int init_client_socket(char *server, char *port) 38 | { 39 | iResult = WSAStartup(MAKEWORD(2,2), &wsaData); 40 | if (iResult != NO_ERROR) 41 | printf("init_server_comms() : WSAStartup() failed with error: %ld\n", iResult); 42 | 43 | sock = socket(AF_INET, SOCK_DGRAM, 0); 44 | if (sock < 0) 45 | printf("init_server_comms() : socket creation failed.\n"); 46 | 47 | iResult = ioctlsocket(sock, FIONBIO, &iMode); 48 | if (iResult != NO_ERROR) 49 | printf("init_server_comms() : ioctlsocket failed with error: %ld\n", iResult); 50 | 51 | obd_server.sin_family = AF_INET; 52 | hp = gethostbyname(server); 53 | if (hp == 0) 54 | printf("init_server_comms() : Unknown host -> %s.\n", server); 55 | 56 | bcopy((char *)hp->h_addr, (char *)&obd_server.sin_addr, hp->h_length); 57 | obd_server.sin_port = htons(atoi(port)); 58 | length = sizeof(struct sockaddr_in); 59 | 60 | return(sock); 61 | } 62 | 63 | int init_server_socket(char *port) 64 | { 65 | /* TODO: */ 66 | 67 | return(sock); 68 | } 69 | 70 | int send_ecu_msg(char *query) 71 | { 72 | int n; 73 | 74 | n = sendto(sock,query,strlen(query),0,(const struct sockaddr *)&obd_server,length); 75 | if (n < 0) 76 | { 77 | printf("send_ecu_msg() : Sendto failed.\n"); 78 | } 79 | else 80 | { 81 | /* TODO: Write message to log file. */ 82 | /* printf("send_ecu_msg() - SENT ECU Message: %s", buffer); */ 83 | } 84 | 85 | return n; 86 | } 87 | 88 | int recv_ecu_msg(char *msg) 89 | { 90 | int n; 91 | 92 | memset(msg,0,256); 93 | 94 | /* n = recvfrom(sock,msg,256,MSG_DONTWAIT,(struct sockaddr *)&from,&length); 95 | We are not blocking on recv now. */ 96 | n = recvfrom(sock,msg,256,0,(struct sockaddr *)&from,&length); 97 | 98 | /* 99 | if (n > 0) 100 | { 101 | printf("recv_ecu_msg() - RECV ECU Message: %s", buffer); 102 | strncpy(msg, buffer, n); 103 | } 104 | */ 105 | 106 | return(n); 107 | } 108 | 109 | 110 | int init_obd_comms(char *obd_msg) 111 | { 112 | int n; 113 | 114 | n = sendto(sock,obd_msg,strlen(obd_msg),0,(const struct sockaddr *)&obd_server,length); 115 | 116 | if (n <= 0) 117 | { 118 | printf("init_obd_comms() - : Sendto failed.\n"); 119 | } 120 | else 121 | { 122 | printf("init_obd_comms() - SENT OBD Message: %s", obd_msg); 123 | } 124 | 125 | 126 | return(n); 127 | } 128 | 129 | 130 | int server_connect() 131 | { 132 | int result; 133 | 134 | /* First set up UDP communication with the server process 135 | and check connection to the OBD interface. */ 136 | result = init_client_socket("127.0.0.1", "8989"); /* TODO: get server ip address and port from config file. */ 137 | if (result <= 0) 138 | { 139 | printf("ecu_connect() : Failed to connect to OBD server.\n"); 140 | ecu_connected = 0; 141 | } 142 | else 143 | { 144 | printf("ecu_connect() : Connected to OBD server.\n"); 145 | ecu_connected = 1; 146 | } 147 | 148 | /* Do this in the GUI/Client process. 149 | else 150 | { 151 | result = init_obd_comms("ATI\n", rcv_msg_buf); 152 | if (result <= 0) 153 | { 154 | printf("ecu_connect() : Failed to connect to OBD interface.\n"); 155 | } 156 | else 157 | { 158 | ecu_connected = 1; 159 | if (strlen(protocol_req) > 5) 160 | { 161 | send_ecu_msg(protocol_req); 162 | } 163 | } 164 | } 165 | */ 166 | 167 | return(result); 168 | } 169 | 170 | int get_ecu_connected() 171 | { 172 | return(ecu_connected); 173 | } 174 | 175 | void set_ecu_connected(int cstatus) 176 | { 177 | ecu_connected = cstatus; 178 | return; 179 | } 180 | --------------------------------------------------------------------------------