├── docs ├── DuCanBus.f3d ├── doit_open.jpg ├── sensor_data.png ├── sensor_found.png ├── sensor_info.png ├── DuCanBus_DevKit.f3d ├── U2_transceiver.jpeg ├── d1mini32_closed.jpg ├── d1mini32_open.jpg ├── U3_buck_adapter.jpeg ├── U4_4pin_adapter.jpeg ├── U5_switch_small.jpeg ├── using │ ├── hlt_engine.png │ ├── hlt_throttle.png │ └── hlt_temperature.png ├── Schematic_DuCanBus_2021-02-25.png └── config_customperipheral_name.png ├── .gitignore ├── LICENSE ├── lib └── README ├── include ├── canbusble_pinout.h └── README ├── platformio.ini ├── src ├── DucatiPanigaleCanbus.lua └── canbusble.cpp ├── scripts └── DucatiPanigaleCanbus.hscrl └── README.md /docs/DuCanBus.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/DuCanBus.f3d -------------------------------------------------------------------------------- /docs/doit_open.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/doit_open.jpg -------------------------------------------------------------------------------- /docs/sensor_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/sensor_data.png -------------------------------------------------------------------------------- /docs/sensor_found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/sensor_found.png -------------------------------------------------------------------------------- /docs/sensor_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/sensor_info.png -------------------------------------------------------------------------------- /docs/DuCanBus_DevKit.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/DuCanBus_DevKit.f3d -------------------------------------------------------------------------------- /docs/U2_transceiver.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/U2_transceiver.jpeg -------------------------------------------------------------------------------- /docs/d1mini32_closed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/d1mini32_closed.jpg -------------------------------------------------------------------------------- /docs/d1mini32_open.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/d1mini32_open.jpg -------------------------------------------------------------------------------- /docs/U3_buck_adapter.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/U3_buck_adapter.jpeg -------------------------------------------------------------------------------- /docs/U4_4pin_adapter.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/U4_4pin_adapter.jpeg -------------------------------------------------------------------------------- /docs/U5_switch_small.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/U5_switch_small.jpeg -------------------------------------------------------------------------------- /docs/using/hlt_engine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/using/hlt_engine.png -------------------------------------------------------------------------------- /docs/using/hlt_throttle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/using/hlt_throttle.png -------------------------------------------------------------------------------- /docs/using/hlt_temperature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/using/hlt_temperature.png -------------------------------------------------------------------------------- /docs/Schematic_DuCanBus_2021-02-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/Schematic_DuCanBus_2021-02-25.png -------------------------------------------------------------------------------- /docs/config_customperipheral_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatobo/DucatiPanigaleCanBus/HEAD/docs/config_customperipheral_name.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | .vscode/settings.json 7 | .vscode/extensions.json 8 | /temp/ 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Renato Bonomini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /include/canbusble_pinout.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | 3 | DucatiPanigaleCanBus: monitor CANBUS messages and report them to HLT 4 | More info at https://github.com/renatobo/DucatiPanigaleCanBus 5 | Renato Bonomini https://github.com/renatobo 6 | 7 | ******************************************************************************/ 8 | 9 | // Define PINS used for interfaces here 10 | 11 | /* ------- Status LED --------- */ 12 | // Define LED_STATUS to the pin used to show status of the system: 13 | // - 1 sec frequency on/off: regular activity 14 | // - fading light: CAN connection error, waiting to retry 15 | // Standard configuration: we flip status of LED_BUILTIN to show the main loop is active 16 | // You can decide to have the 17 | #ifdef ARDUINO_LOLIN_D32_PRO 18 | #define LED_STATUS GPIO_NUM_5 19 | #else 20 | #define LED_STATUS GPIO_NUM_2 21 | #endif 22 | 23 | /* ------- CANBUS transceiver RX and TX pins --------- */ 24 | // You can use any PIN not otherwise available, there is no dedicated TWAI set of pins 25 | // cfr https://arduino.stackexchange.com/questions/81209/what-are-the-correct-pins-for-twai-can-on-esp32 26 | // Override custom PINS from command line if needed via -DTX_GPIO_NUM and -DRX_GPIO_NUM 27 | #ifndef TX_GPIO_NUM 28 | #define TX_GPIO_NUM GPIO_NUM_21 29 | #endif 30 | #ifndef RX_GPIO_NUM 31 | #define RX_GPIO_NUM GPIO_NUM_22 32 | #endif 33 | 34 | /* end of file */ -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = doit_simulateCAN 13 | 14 | [env] 15 | platform = espressif32 16 | framework = arduino 17 | monitor_speed = 115200 18 | lib_deps = 19 | arkhipenko/TaskScheduler@^3.8.5 20 | h2zero/NimBLE-Arduino@^2.2.1 21 | https://github.com/timurrrr/arduino-CAN 22 | ; khoih-prog/ESP_DoubleResetDetector @ ^1.3.1 23 | lib_ldf_mode = chain+ 24 | ; board_build.f_flash = 80000000L 25 | ; board_build.flash_mode = qio 26 | 27 | [env:doit] 28 | board = esp32doit-devkit-v1 29 | build_type = debug 30 | build_flags = 31 | -DLOG_LOCAL_LEVEL=ESP_LOG_INFO 32 | -DCORE_DEBUG_LEVEL=3 33 | -DLAZYWATCHDOG 34 | 35 | [env:doit_prod] 36 | board = esp32doit-devkit-v1 37 | build_flags = 38 | -DLOG_LOCAL_LEVEL=ESP_LOG_NONE 39 | -DCORE_DEBUG_LEVEL=0 40 | 41 | [env:doit_simulateCAN] 42 | board = esp32doit-devkit-v1 43 | build_type = debug 44 | build_flags = 45 | -DCORE_DEBUG_LEVEL=3 46 | -DCAN_DATA_SIMULATOR_MODE 47 | 48 | [env:doit_simulateCAN_ota] 49 | board = esp32doit-devkit-v1 50 | build_type = debug 51 | lib_deps = 52 | ${env.lib_deps} 53 | khoih-prog/ESP_DoubleResetDetector @ ^1.3.1 54 | build_flags = 55 | -DCORE_DEBUG_LEVEL=3 56 | -DCAN_DATA_SIMULATOR_MODE 57 | -DENABLE_OTA_WITH_DRD 58 | upload_port = ducan.local 59 | upload_protocol = espota 60 | 61 | [env:lolin] 62 | board = lolin_d32_pro 63 | build_type = debug 64 | build_flags = 65 | -DCORE_DEBUG_LEVEL=3 66 | -DBOARD_HAS_PSRAM 67 | -mfix-esp32-psram-cache-issue 68 | -DLAZYWATCHDOG 69 | 70 | [env:lolin_simulateCAN] 71 | board = lolin_d32_pro 72 | build_type = debug 73 | build_flags = 74 | -DCORE_DEBUG_LEVEL=3 75 | -DBOARD_HAS_PSRAM 76 | -mfix-esp32-psram-cache-issue 77 | -DCAN_DATA_SIMULATOR_MODE 78 | 79 | ; pinout for wemos_d1_mini32 at https://github.com/r0oland/ESP32_mini_KiCad_Library 80 | [env:wemos_d1_mini32] 81 | board = wemos_d1_mini32 82 | build_type = debug 83 | build_flags = 84 | -DCORE_DEBUG_LEVEL=3 85 | 86 | [env:wemos_d1_mini32_simulateCAN] 87 | board = wemos_d1_mini32 88 | build_type = debug 89 | build_flags = 90 | -DCORE_DEBUG_LEVEL=3 91 | -DCAN_DATA_SIMULATOR_MODE 92 | 93 | [env:wemos_d1_mini32_prod] 94 | board = wemos_d1_mini32 95 | build_flags = 96 | -DLOG_LOCAL_LEVEL=ESP_LOG_NONE 97 | -DCORE_DEBUG_LEVEL=0 98 | 99 | [env:wemos_d1_mini32_ota] 100 | board = wemos_d1_mini32 101 | build_flags = 102 | -DLOG_LOCAL_LEVEL=ESP_LOG_NONE 103 | -DCORE_DEBUG_LEVEL=0 104 | -DENABLE_OTA_WITH_DRD 105 | upload_protocol = espota 106 | upload_port = ducan.local 107 | -------------------------------------------------------------------------------- /src/DucatiPanigaleCanbus.lua: -------------------------------------------------------------------------------- 1 | -- todo: add engine.wheelspeed 2 | 3 | local errorMsg = false 4 | local sensorname = "Ducati-Panigale" 5 | local sensordevicepattern = "DuCan-...." 6 | local bleservice = "6E400001-59f2-4a41-9acd-cd56fb435d64" 7 | -- local blecharacteristicstatic = "6E400010-59f2-4a41-9acd-cd56fb435d64" 8 | local blecharacteristicslow = "6E400011-59f2-4a41-9acd-cd56fb435d64" 9 | local blecharacteristicfast = "6E400012-59f2-4a41-9acd-cd56fb435d64" 10 | local blecharacteristicwrite = "6E400012-59f2-4a41-9acd-cd56fb435000" 11 | 12 | -- sensor.oninit() is mandatory to define for any sensor purpose 13 | -- it sets basic parameters and sets up communication 14 | 15 | function sensor.oninit() 16 | ----tracecall(connect, "sensor.oninit()") 17 | 18 | -- set sensor parameters 19 | sensor.channelsets = { engine } 20 | 21 | sensor.normupdaterate = 20 -- this is the expected update rate, used to generate warnings 22 | sensor.connectiontype = btle -- one of btle, bt, wifi, mfi 23 | 24 | -- set BTLE peripheral name pattern and service / characteristics we are interested in 25 | -- if Expert settings contain an override, use it - otherwise the default of this module is in sensordevicepattern 26 | local customsensordevicepattern = preferences.getstring("kCustomOBDBTLEPeripheral") 27 | -- regular expression, dot means any character. Taken from Expert settings 28 | 29 | if string.len(customsensordevicepattern) > 0 then 30 | sensor.btle.peripheralnamepattern = customsensordevicepattern 31 | sensor.nameprefix = customsensordevicepattern -- shown in Sensor List 32 | else 33 | sensor.btle.peripheralnamepattern = sensordevicepattern 34 | sensor.nameprefix = sensorname -- shown in Sensor List 35 | end 36 | 37 | -- set our read charactersitics, we will use the tag returned later 38 | -- not impletemented as it requires writing on bus - VIN 39 | -- no notification needed 40 | -- readcharacteristicstatic = sensor.btle.addcharacteristic(bleservice, blecharacteristicstatic, false) 41 | -- rpm, tps, gear 42 | readcharacteristicfast = sensor.btle.addcharacteristic(bleservice, blecharacteristicfast, true) 43 | -- enginetemperature, ambienttemperature, battery 44 | readcharacteristicslow = sensor.btle.addcharacteristic(bleservice, blecharacteristicslow, true) 45 | -- for write to configurations (used right now to restart the unit) 46 | writecharacteristicrestart = sensor.btle.addcharacteristic(bleservice, blecharacteristicwrite, false) 47 | 48 | -- fetch additional info from service such as battery or firmware 49 | sensor.btle.deviceinformation = true 50 | 51 | -- Engine channel specific settings 52 | sensor.engine.elm327 = false -- this is an important one: setting it to true will use predefined parsing 53 | sensor.engine.obdonly = false -- qualifies to support more than ODBII / needed? 54 | ----tracereturn(connect, "sensor.oninit()") 55 | 56 | sensor.configurationdefinitions = 57 | { 58 | reboot = { label = "Restart", type = "boolean", default = false } 59 | } 60 | 61 | end 62 | 63 | function sensor.onconfigurationschanged() 64 | 65 | if sensor.configurations.reboot then 66 | sensor.btle.writevalue(writecharacteristicrestart, "1") 67 | trace(adhoc, "Reconfig set sensor.btle.onconfigurationschanged") 68 | else 69 | -- since there are no other cases, throw an error 70 | trace(adhoc, "FAILED Reconfig, we should not be here") 71 | end 72 | 73 | end 74 | 75 | -- sensor.onconnect () is optional and called by the framework once the sensor 76 | -- is connected to the app; this hook can be used to run custom initialization 77 | 78 | function sensor.onconnect () 79 | ----trace(connect, "Ducati Panigale connected...") 80 | 81 | errorMsg = false 82 | 83 | -- queue the channel sets in sensor.onconnect(), send 1 and max value to normalize % counters 84 | -- http://forum.gps-laptimer.de/viewtopic.php?f=47&t=4608 85 | 86 | enginechannelset = {} 87 | enginechannelset.tps=1 88 | sensor.queuechannelset(enginechannelset, engine) 89 | 90 | enginechannelset = {} 91 | enginechannelset.tps=100 92 | sensor.queuechannelset(enginechannelset, engine) 93 | 94 | -- read static characteristic, once 95 | -- sensor.btle.readvalue(readcharacteristicstatic) 96 | 97 | -- reset the reboot flag 98 | sensor.changeconfigurations({ reboot = false}) 99 | 100 | end 101 | 102 | -- sensor.ondisconnect () is optional and called by the framework after the sensor 103 | -- has been disconnected from the app 104 | 105 | function sensor.ondisconnect () 106 | ----trace(connect, "Ducati Panigale disconnected...") 107 | end 108 | 109 | -- sensor.btle.onvaluechanged () is mandatory for btle sensor 110 | -- it needs to redirect incoming data either to custom processing, 111 | -- or dispatch it to one of the standard parsers; characteristic is a tag returned 112 | -- by sensor.btle.addcharacteristic (), value is a byte sequence 113 | 114 | function sensor.btle.onvaluechanged (characteristic, value) 115 | ----tracecall(btle, "sensor.btle.onvaluechanged(" .. characteristic .. ", " .. ----tracebytes(value) .. ")") 116 | if characteristic == readcharacteristicfast then 117 | sensor.fastbytesread (value) 118 | elseif characteristic == readcharacteristicslow then 119 | sensor.slowbytesread (value) 120 | -- elseif characteristic == readcharacteristicstatic then 121 | -- sensor.staticbytesread (value) 122 | end 123 | ----tracereturn(btle, "sensor.btle.onvaluechanged()") 124 | end 125 | 126 | function sensor.fastbytesread(message) 127 | -- rpm, tps, gear 128 | ----tracecall(gnss, "sensor.bytesread(" .. ----tracebytes(message) .. ")") 129 | if #message > 1 then 130 | 131 | local messagetype 132 | messagetype = string.unpack("I1", message) 133 | 134 | if messagetype == 1 then 135 | 136 | -- Create a new and empty set of channels 137 | enginechannelset = {} 138 | 139 | -- Parsing RPM, 2 bytes 140 | -- engine.rpm: engine rounds per minute (RPM); integer 0..16383; mandatory 141 | -- wheelspeed 2 bytes 142 | -- engine.wheelspeed: wheel speed in km/h; integer; optional; 143 | -- Parsing APS, 1 byte 144 | -- engine.tps: throttle or pedal position; double 0..100 percent; mandatory; engine.throttle is a valid synonym 145 | -- Parsing gear, 1 byte 146 | -- engine.gear [v23]: gear with -1 rear, and 0 neutral; integer; optional; usually derived from speed, rpms, gear 147 | -- and drive ratios, this channel can be used to feed in a gear measured 148 | 149 | -- we use pos=2 to skip the message byte we just read 150 | enginechannelset.rpm, enginechannelset.wheelspeed, enginechannelset.tps, enginechannelset.gear = string.unpack("I2I2I1i1", message,2) 151 | -- Pass result to app 152 | sensor.queuechannelset(enginechannelset, engine) 153 | sensor.rawupdatedforsensortype(engine) -- Rate Update 154 | else 155 | if errorMsg == false then 156 | error("Unrecognized Fast message version: " .. messagetype .. ". Update your HLT script") 157 | errorMsg = true 158 | end 159 | sensor.queuechannelset(nil, engine) 160 | end 161 | else 162 | -- Signal app we have received an invalid fix 163 | if errorMsg == false then 164 | error("Unexpected Fast BLE message size: " .. #message .. " bytes. Update your HLT script") 165 | errorMsg = true 166 | end 167 | sensor.queuechannelset(nil, engine) 168 | end 169 | --tracereturn(engine, "sensor.fastbytesread()") 170 | end 171 | 172 | function sensor.slowbytesread(message) 173 | -- enginetemperature, ambientemperature, battery 174 | 175 | ----tracecall(gnss, "sensor.slowbytesread(" .. ----tracebytes(message) .. ")") 176 | if #message > 1 then 177 | local messagetype 178 | messagetype = string.unpack("I1", message) 179 | 180 | if messagetype == 2 then 181 | 182 | -- Create a new and empty set of channels 183 | enginechannelset = {} 184 | -- Parsing engine temp, byte 1 185 | -- engine.enginetemp: coolant temperature; integer -40..215 degree Celsius; optional 186 | -- Parsing environmental temp, byte 2 187 | -- engine.iat: intake air temperature; integer -40..215 degree Celsius; optional 188 | -- engine.battery: voltage of board battery; double, 1.0 = 1V; optional; not stored permanently 189 | 190 | local battery 191 | -- we use pos=2 to skip the message byte we just read 192 | enginechannelset.enginetemp, enginechannelset.iat, battery = string.unpack("I1I1I1", message,2) 193 | -- battery is read as unsigned int (0..256) as in the original source so we divide by ten 194 | enginechannelset.battery = battery / 10 195 | -- Pass result to app 196 | sensor.queuechannelset(enginechannelset, engine) 197 | -- sensor.rawupdatedforsensortype(engine) -- Rate Update, not updated to keep only the highest frequency of the other messages 198 | else 199 | if errorMsg == false then 200 | error("Unrecognized Slow message version: " .. messagetype .. ". Update your HLT script") 201 | errorMsg = true 202 | end 203 | sensor.queuechannelset(nil, engine) 204 | end 205 | else 206 | -- Signal app we have received an invalid fix 207 | if errorMsg == false then 208 | error("Unexpected Slow BLE message size: " .. #message .. " bytes. Update your HLT script") 209 | errorMsg = true 210 | end 211 | sensor.queuechannelset(nil, engine) 212 | end 213 | --tracereturn(engine, "sensor.slowbytesread()") 214 | end 215 | 216 | -- function sensor.staticbytesread(message) 217 | -- -- VIN 218 | -- 219 | -- ----tracecall(gnss, "sensor.staticbytesread(" .. ----tracebytes(message) .. ")") 220 | -- if #message > 16 then 221 | -- -- Create a new and empty set of channels 222 | -- enginechannelset = {} 223 | -- -- no Parsing for VIN, we can simply read the message 224 | -- enginechannelset.vin = message 225 | -- -- Pass result to app 226 | -- sensor.queuechannelset(enginechannelset, engine) 227 | -- else 228 | -- -- Signal app we have received an invalid VIN 229 | -- if errorMsg == false then 230 | -- error("Invalid VIN BLE message size: " .. #message .. " bytes") 231 | -- errorMsg = true 232 | -- end 233 | -- sensor.queuechannelset(nil, engine) 234 | -- end 235 | -- --tracereturn(engine, "sensor.staticbytesread()") 236 | -- end -------------------------------------------------------------------------------- /scripts/DucatiPanigaleCanbus.hscrl: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | Renato Bonomini 13 | 0.3 14 | sensor.btle 15 | false 16 | 254 | 255 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DucatiPanigaleCanBus 2 | 3 | - [DucatiPanigaleCanBus](#ducatipanigalecanbus) 4 | - [What is DucatiPanigaleCanBus?](#what-is-ducatipanigalecanbus) 5 | - [What data is collected?](#what-data-is-collected) 6 | - [Transmitted at 20Hz or 25Hz](#transmitted-at-20hz-or-25hz) 7 | - [Transmitted at 1Hz](#transmitted-at-1hz) 8 | - [Setup of Harry Lap Timer](#setup-of-harry-lap-timer) 9 | - [Connect to a specific unit](#connect-to-a-specific-unit) 10 | - [Building the software in Platform IO and Arduino IDE](#building-the-software-in-platform-io-and-arduino-ide) 11 | - [Simulating CANbus values to troubleshoot BLE](#simulating-canbus-values-to-troubleshoot-ble) 12 | - [OTA updates with DoubleResetDetector](#ota-updates-with-doubleresetdetector) 13 | - [Schematics](#schematics) 14 | - [Hardware](#hardware) 15 | - [An example based on common DevKit ESP32 module](#an-example-based-on-common-devkit-esp32-module) 16 | - [An example of a compact unit based on Wemos D1mini 32](#an-example-of-a-compact-unit-based-on-wemos-d1mini-32) 17 | - [Credits](#credits) 18 | 19 | ## What is DucatiPanigaleCanBus? 20 | 21 | If you are a motorcycle rider/racer, you might want to log data from your bike to improve your riding style. 22 | This project connects a small device based on ESP32 to the CAN Bus on a Ducati Panigale (or other Ducati bikes with similar ECU), it monitors and extracts RPM/speed/gear/temperature, and it broadcasts the collected data via Bluetooth Low Energy to Harry Lap Timer 'Engine sensor'. Other apps can be supported (for example, [NBP in TrackAddict](https://racerender.com/TrackAddict/docs/NBP%20Specification.pdf)), not available today. 23 | 24 | This code is specialized for messages generated by the Mitsubishi ECU used in some models like 25 | 26 | - Panigale (899/959/1199/1199S/1199R/1299/1299S/R and Superleggera) 27 | - Multistrada 1200 (2010 to 2014) 28 | - Diavel, Diavel Strada, Diavel DS 29 | 30 | Right now this is compatible only with BLE which is the preferred option if you are using an iOS device with Harry Lap Timer: extending to regular bluetooth or WiFi/TCP-IP is not complicated, yet not on the priority list right now - please log a request if you are really interested and willing to help in the extension. 31 | 32 | ## What data is collected? 33 | 34 | The choice of data is a combination of what could be useful and what is supported by HLT channels, right now: 35 | 36 | ### Transmitted at 20Hz or 25Hz 37 | 38 | - Wheel speed 39 | - Engine RPM 40 | - Gear 41 | - Throttle position 42 | 43 | Define the frequency with the macro `FAST_MESSAGES_FREQUENCY_HZ` 44 | 45 | ### Transmitted at 1Hz 46 | 47 | - Engine coolant temperature 48 | - Air temperature 49 | - Battery voltage 50 | 51 | Measurements are mapped to the channels built in HLT, so you can inspect data directly there and export it in other formats for analysis outside of the app (think VBO format for example, or CSV). 52 | 53 | As of Jun 2021, Gear collected from the engine is not exported by HLT even if collected. 54 | 55 | ![Engine](docs/using/hlt_engine.png) 56 | ![Throttle](docs/using/hlt_throttle.png) 57 | ![Temperature](docs/using/hlt_temperature.png) 58 | 59 | ## Setup of Harry Lap Timer 60 | 61 | At this time, the script running on Harry Lap Timer needs to be made available with instructions [that are described in the HLT developer forum](http://forum.gps-laptimer.de/index.php). 62 | 63 | There is no further configuration required on HLT: once the script is available and your ESP32 flash and connected, HLT will discover the device (based on the device ID naming convention, the BLE Service, and the Characteristics IDs defined in the ESP32 code and in the LUA script) 64 | 65 | To pair the device to your phone (so that other phones with the same setup don't randomly pick up your sensor), use the pairing code defined in `BLE_SECURITY_PASS`, e.g. 66 | 67 | ```C 68 | // Define the PIN requested by Bluetooth Pairing for bonding 69 | #define BLE_SECURITY_PASS 123456 70 | ``` 71 | 72 | ![Device found](docs/sensor_found.png) 73 | ![Sensor info](docs/sensor_info.png) 74 | ![Sensor live data](docs/sensor_data.png) 75 | 76 | ### Connect to a specific unit 77 | 78 | HLT will connect to any nearby device matching a name of "DuCan-...." where the 4 dots match an id unique to your device. 79 | 80 | If you need to connect to a specific device 81 | 82 | - Open Administration 83 | - go to Settings 84 | - scroll down to Expert Settings 85 | - scroll down to Custom BTLE ODB Adapter 86 | - in 'Peripheral Name' add the full name of your device, e.g. `DuCan-A499` or `DuCan-8409`. You can once again use a single `.` to specify *any character* 87 | 88 | ![Expert Settings > Custom peripheral name](docs/config_customperipheral_name.png) 89 | 90 | In this way, if other riders have this same unit nearby your phone will only connect to the device you specify. 91 | 92 | ## Building the software in Platform IO and Arduino IDE 93 | 94 | This project is best handled with Platform.io. Nonetheless, it uses the Arduino framework so you can use the Arduino IDE as well. 95 | 96 | Steps required to compile this in the Arduino IDE 97 | 98 | - copy content of `src` and `include` into your sketch folder 99 | - perform a `git clone https://github.com/timurrrr/arduino-CAN` in the sketch folder, or copy a zip file from github directly 100 | - move the content of `arduino-CAN/src` in the main folder of your sketch 101 | 102 | ### Simulating CANbus values to troubleshoot BLE 103 | 104 | Having the device connected to a running bike to generate data while troubleshooting BLE connectivity from the console log is quite inconvenient: you can enable a "data simulator" that will linearly increase data in a loop without a live CANbus connection. 105 | 106 | Define `CAN_DATA_SIMULATOR_MODE` or add it to the build command line (as in the example *doit_simulateCAN* environment). 107 | 108 | With logging enable, your serial will show 109 | 110 | ```plaintext 111 | [ 364][I][canbusble.cpp:314] report_msg_counters(): Counters [18: 0, 80: 0, 100: 0], values RPM: 0, Speed: 0, APS: 0, Gear: 0, ETemp: 0, ATemp 0, Battery: 0. SLOW [00000000], FAST [00000000000000] 112 | [ 1364][I][canbusble.cpp:314] report_msg_counters(): Counters [18: 14, 80: 14, 100: 14], values RPM: 2140, Speed: 9, APS: 2, Gear: 1, ETemp: 70, ATemp 16, Battery: 120. SLOW [00000000], FAST [00000000000000] 113 | [ 2364][I][canbusble.cpp:314] report_msg_counters(): Counters [18: 24, 80: 24, 100: 24], values RPM: 2240, Speed: 12, APS: 3, Gear: 1, ETemp: 70, ATemp 16, Battery: 120. SLOW [00000000], FAST [00000000000000] 114 | [ 3364][I][canbusble.cpp:314] report_msg_counters(): Counters [18: 34, 80: 34, 100: 34], values RPM: 2340, Speed: 15, APS: 4, Gear: 1, ETemp: 71, ATemp 16, Battery: 120. SLOW [00000000], FAST [00000000000000] 115 | [ 4364][I][canbusble.cpp:314] report_msg_counters(): Counters [18: 44, 80: 44, 100: 44], values RPM: 2440, Speed: 17, APS: 5, Gear: 1, ETemp: 71, ATemp 17, Battery: 120. SLOW [00000000], FAST [00000000000000] 116 | ``` 117 | 118 | and with a BLE client attached 119 | 120 | ```plaintext 121 | [312367][I][canbusble.cpp:314] report_msg_counters(): Counters [18: 3124, 80: 3124, 100: 3124], values RPM: 3180, Speed: 39, APS: 12, Gear: 1, ETemp: 74, ATemp 18, Battery: 122. SLOW [024A127A], FAST [016C0C27000C01] 122 | [313367][I][canbusble.cpp:314] report_msg_counters(): Counters [18: 3134, 80: 3134, 100: 3134], values RPM: 3280, Speed: 42, APS: 13, Gear: 1, ETemp: 75, ATemp 19, Battery: 122. SLOW [024A127A], FAST [01D00C2A000D01] 123 | [314367][I][canbusble.cpp:314] report_msg_counters(): Counters [18: 3144, 80: 3144, 100: 3144], values RPM: 3380, Speed: 45, APS: 14, Gear: 1, ETemp: 75, ATemp 19, Battery: 122. SLOW [024B137A], FAST [01340D2D000E01] 124 | [315367][I][canbusble.cpp:314] report_msg_counters(): Counters [18: 3154, 80: 3154, 100: 3154], values RPM: 3480, Speed: 48, APS: 15, Gear: 1, ETemp: 75, ATemp 19, Battery: 122. SLOW [024B137A], FAST [01980D30000F01] 125 | ``` 126 | 127 | ### OTA updates with DoubleResetDetector 128 | 129 | When the unit is tucked away, updates via USB are inconvenient. The "Double Reset Detector" library will be triggered after a double "reset" activity within 5 seconds (configurable), with these series of actions 130 | 131 | - start WiFi in Access Point mode 132 | - start OTA in listening mode 133 | 134 | The AP name is based on the `DEVICE_ID` macro, and it includes the 4 digits identifying the unit. At that point, mDNS starts and after connecting to the ESP32 Access Point you can upload to the `ducan.local` device. 135 | 136 | - To activate the OTA functionality, define the compiler marco `ENABLE_OTA_WITH_DRD` 137 | - Define the Access Point password with macro `WIFI_PWD` (default `123456789`) 138 | - Press Reset once, wait 1 second, press Reset again to enable 139 | 140 | **Note**: the double reset needs about 1 second between each action for the bootstrap procedure to correclty identify the reset. 141 | 142 | ## Schematics 143 | 144 | ![schematics](docs/Schematic_DuCanBus_2021-02-25.png) 145 | 146 | The most complicated and hard to source part is the 4 pin adapter to the DDA port (check [monocilindro.com for a nice article on connections](https://www.monocilindro.com/2018/08/26/ducati-monster-797-obd2-dda-diagnostic-connector-and-communication/) 147 | 148 | - pin1: +12 149 | - pin2: Ground 150 | - pin3: CAN high 151 | - pin4: CAN low 152 | 153 | You can also buy an ODBII adapter and use th ODBII pin schema convention. 154 | 155 | In a nutshell: 156 | 157 | - Pin1 (+12) and Pin2 (ground) connect to the buck converter to power the ESP32 158 | - Pin3 and Pin4 carry the CANBUS signal going to the transceiver 159 | 160 | Since you can use any pin for TWAI, there are 2 options 161 | 162 | - use predefined options in [include/canbusble_pinout.h](include/canbusble_pinout.h) 163 | - define via compile time definitions of `TX_GPIO_NUM` and `RX_GPIO_NUM` 164 | 165 | ## Hardware 166 | 167 | Parts: `U` refers to the schematic reported above. Links to sources are examples, these are very generic components. 168 | 169 | - U1: A generic ESP32 170 | - U2: [A CANBUS transceiver, such as one based on SN65HVD230](https://www.amazon.com/gp/product/B07ZT7LLSK). Be careful to source a genuine part 171 | - U3: [Buck converter 12V to 5V](https://www.amazon.com/gp/product/B076P4C42B) to power ESP32 from the CANBUS directly 172 | - U4: [4 PINs adapter for Ducati DDA port (male)](https://www.aliexpress.com/item/4001007307044.html) 173 | 174 | Misc parts from your HW bin 175 | 176 | - U5: small switch (to cut power when not needed) inline with the Input power 177 | - heat shrink tube to protect the cable from the 4 PINs adapter 178 | 179 | ![U2](docs/U2_transceiver.jpeg) 180 | ![U3](docs/U3_buck_adapter.jpeg) 181 | ![U4](docs/U4_4pin_adapter.jpeg) 182 | ![U5](docs/U5_switch_small.jpeg) 183 | 184 | ### An example based on common DevKit ESP32 module 185 | 186 | The most common ESP32 is the DevKit: the box is 3d printed, [here the source files for DevKit](docs/DuCanBus_DevKit.f3d) of which you should print *box* and *lid*. You will need 4 M1.7 screws 4 to 6 mm long, and a lot of patience: 187 | 188 | - hot glue to hold pieces together in a firm location, to avoid issues with vibrations, is recommended 189 | - the transceiver is designed to be locked in position by the two columns: you can melt some plastic to ensure it does not move around 190 | 191 | ![open](docs/doit_open.jpg) 192 | 193 | ### An example of a compact unit based on Wemos D1mini 32 194 | 195 | The smallest esp32 readily available is the D1 mini ESP32: fitting everything in a box that can go in the tail section of the Panigale has been tediously meticolous, but the result works: the box is 3d printed, [here the source files for D1 Mini](docs/DuCanBus.f3d) of which you should print *box* and *lid*. You will need a few M1.7 screws 4 to 5 mm long, and a lot of patience: 196 | 197 | - hot glue to hold pieces together in a firm location, to avoid issues with vibrations, is recommended 198 | - the transceiver is designed to be locked in position by the two columns: you can melt some plastic to ensure it does not move around 199 | 200 | ![open](docs/d1mini32_open.jpg) 201 | 202 | Once closed 203 | 204 | ![closed](docs/d1mini32_closed.jpg) 205 | 206 | ## Credits 207 | 208 | Other projects that inspired this work are 209 | 210 | - The excellent work performed by MrCanBus [in this thread](https://www.ducati.ms/threads/canbus-data-on-you-android-device-via-bluetooth.337705/) and his [CanBus sniffer](https://github.com/MrCanBus/MTS1200-CANBUS) 211 | - Collective decoding of Ducati CANBUS messages ([spreadsheet](https://docs.google.com/spreadsheets/d/1-NJ9OlGQYTGMzBzwDPYn-aI_7_ign9SCiscKZufx3Uw/edit?pli=1#gid=1950998351)) 212 | - Field testing and motivation by [@dookie454](https://github.com/dookie454), including porting to car PIDs and TFT display of data 213 | -------------------------------------------------------------------------------- /src/canbusble.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | 3 | DucatiPanigaleCanBus 4 | 5 | Monitor CANBUS messages and report them to HLT: this code is specialized for 6 | messages generated by the Mitsubishi ECU used in some models like 7 | - Panigale (899/959/1199/1199S/1199R/1299/1299S/R and Superleggera) 8 | - Multistrada 1200 (2010 to 2014) 9 | - Diavel, Diavel Strada, Diavel DS 10 | 11 | Testing is done on a Panigale 899 right now, please report testing on other 12 | models at https://github.com/renatobo/DucatiPanigaleCanBus/issues 13 | 14 | More info at https://github.com/renatobo/DucatiPanigaleCanBus 15 | Renato Bonomini https://github.com/renatobo 16 | 17 | ******************************************************************************/ 18 | 19 | #include 20 | // Define the connections used by your ESP32 in the pinout file 21 | #include "canbusble_pinout.h" 22 | 23 | // Name used as prefix or name for BLE, WiFi, OTA hostname 24 | #define DEVICE_ID "DuCan" 25 | 26 | // Define the PIN requested by Bluetooth Pairing for bonding 27 | #define BLE_SECURITY_PASS 123456 28 | 29 | // Fast update frequency 30 | #define FAST_MESSAGES_FREQUENCY_HZ 25 31 | 32 | // Troubleshooting options 33 | // 34 | // Define the macro CAN_DATA_SIMULATOR_MODE to generate linear looping values for counters instead of reading from CANBUS 35 | // The better option is to use a compile time define (see platformio.ini for an example) 36 | // #define CAN_DATA_SIMULATOR_MODE 37 | 38 | // Define this macro to enable the ability to perform OTA upgrades after a double reset 39 | // #define ENABLE_OTA_WITH_DRD 40 | 41 | #define WIFI_PWD "123456789" // password for OTA Access Point via WiFi 42 | #define OTA_TIMEOUT 300 // 5 minutes to wait for OTA before restarting 43 | #define DRD_TIMEOUT 3 // 5 seconds to wait for a double reset to start OTA 44 | 45 | // Scheduler for periodic tasks, from arkhipenko/TaskScheduler 46 | #include 47 | Scheduler ts; 48 | 49 | /* ----------- CANBUS definitions ------------- */ 50 | #include "CAN.h" 51 | #define RX_TASK_PRIO 9 52 | #define LOGTAG "CAN Listen Only" 53 | 54 | // In this code we look at 3 messages, this is the list of CANBUS ID's 55 | #define ID_FRAME80 0x80 56 | #define ID_FRAME100 0x100 57 | #define ID_FRAME18 0x18 58 | 59 | /* ------ counters for informational and debug purposes ----- */ 60 | uint32_t counter_18; 61 | uint32_t counter_80; 62 | uint32_t counter_100; 63 | 64 | // Structure to hold "fast frequency" messages type 1 (rpm, wheelspeed, tps, gear) 65 | #define BLE_FASTTASK_INTERVAL 1000/FAST_MESSAGES_FREQUENCY_HZ 66 | #define CANBUS_FASTMSG_TYPE 1 67 | #define CANBUS_MSGTYPE1_SIZE 7 68 | #define CANBUS_FAST_SIZE CANBUS_MSGTYPE1_SIZE 69 | typedef struct 70 | { 71 | const uint8_t msgtype = CANBUS_FASTMSG_TYPE; 72 | uint16_t rpm; 73 | uint16_t rearwheelspeed; 74 | uint8_t aps; 75 | int8_t gear; 76 | } canbus_msgtype1_t; 77 | 78 | canbus_msgtype1_t message_fast; 79 | uint8_t ble_msg_fast[CANBUS_FAST_SIZE]; 80 | 81 | // Structure to hold "slow frequency" messages type 2 (enginetemperature, ambientemperature, battery) 82 | #define BLE_SLOWTASK_INTERVAL 1000 83 | #define CANBUS_SLOWMSG_TYPE 2 84 | #define CANBUS_SLOW_SIZE CANBUS_MSGTYPE2_SIZE 85 | #define CANBUS_MSGTYPE2_SIZE 4 86 | typedef struct 87 | { 88 | const uint8_t msgtype = CANBUS_SLOWMSG_TYPE; 89 | uint8_t enginetemp; 90 | uint8_t ambientemp; 91 | uint8_t battery; 92 | } canbus_msgtype2_t; 93 | 94 | canbus_msgtype2_t message_slow; 95 | uint8_t ble_msg_slow[CANBUS_SLOW_SIZE]; 96 | 97 | /* --------------------- Utilities ------------------ */ 98 | bool ledstatus; 99 | void status_led_flip() 100 | { 101 | ledstatus = !ledstatus; 102 | digitalWrite(LED_STATUS, (ledstatus ? HIGH : LOW)); // set pin to the opposite state 103 | } 104 | 105 | /* --------------------- BLE Definitions ------------------ */ 106 | #include 107 | 108 | // Random UUID's, matched in the LUA Script 109 | #define BLE_ENGINEDATA_SERVICE_UUID "6E400001-59f2-4a41-9acd-cd56fb435d64" 110 | #define BLE_SLOW_ENGINEDATA_CHARACTERISTIC_UUID "6E400011-59f2-4a41-9acd-cd56fb435d64" 111 | #define BLE_FAST_ENGINEDATA_CHARACTERISTIC_UUID "6E400012-59f2-4a41-9acd-cd56fb435d64" 112 | #define BLE_REBOOT_CHARACTERISTIC_UUID "6E400012-59f2-4a41-9acd-cd56fb435000" 113 | #define BLE_DEVICE_ID_PREFIX DEVICE_ID 114 | 115 | static NimBLEServer *pServer; 116 | NimBLECharacteristic *pFastCharacteristic = NULL; 117 | NimBLECharacteristic *pSlowCharacteristic = NULL; 118 | NimBLECharacteristic *pRebootCharacteristic = NULL; 119 | bool BLEdeviceConnected = false; 120 | 121 | // notify a fast frequency message 122 | void handle_ble_notify_msg_fast() 123 | { 124 | if (BLEdeviceConnected) 125 | { 126 | memcpy(ble_msg_fast + sizeof message_fast.msgtype, &message_fast.rpm, sizeof message_fast.rpm); 127 | memcpy(ble_msg_fast + sizeof message_fast.msgtype + sizeof message_fast.rpm, &message_fast.rearwheelspeed, sizeof message_fast.rearwheelspeed); 128 | memcpy(ble_msg_fast + sizeof message_fast.msgtype + sizeof message_fast.rpm + sizeof message_fast.rearwheelspeed, &message_fast.aps, sizeof message_fast.aps); 129 | memcpy(ble_msg_fast + sizeof message_fast.msgtype + sizeof message_fast.rpm + sizeof message_fast.rearwheelspeed + sizeof message_fast.aps, &message_fast.gear, sizeof message_fast.gear); 130 | 131 | pFastCharacteristic->setValue(ble_msg_fast, CANBUS_FAST_SIZE); 132 | pFastCharacteristic->notify(); 133 | } 134 | } 135 | Task tNotify_fast(BLE_FASTTASK_INTERVAL, -1, &handle_ble_notify_msg_fast, &ts, false); 136 | 137 | // notify a slow frequency message 138 | void handle_ble_notify_msg_slow() 139 | { 140 | if (BLEdeviceConnected) 141 | { 142 | memcpy(ble_msg_slow + sizeof message_slow.msgtype, &message_slow.enginetemp, sizeof message_slow.enginetemp); 143 | memcpy(ble_msg_slow + sizeof message_slow.msgtype + sizeof message_slow.enginetemp, &message_slow.ambientemp, sizeof message_slow.ambientemp); 144 | memcpy(ble_msg_slow + sizeof message_slow.msgtype + sizeof message_slow.enginetemp + sizeof message_slow.ambientemp, &message_slow.battery, sizeof message_slow.battery); 145 | 146 | pSlowCharacteristic->setValue(ble_msg_slow, CANBUS_SLOW_SIZE); 147 | pSlowCharacteristic->notify(); 148 | } 149 | } 150 | Task tNotify_slow(BLE_SLOWTASK_INTERVAL, -1, &handle_ble_notify_msg_slow, &ts, false); 151 | 152 | // BLE CallBacks 153 | class ServerCallbacks : public NimBLEServerCallbacks 154 | { 155 | void onConnect(NimBLEServer *pServer) 156 | { 157 | BLEdeviceConnected = true; 158 | log_i("Client connected"); 159 | // NimBLEDevice::startAdvertising(); 160 | }; 161 | 162 | void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override 163 | { 164 | BLEdeviceConnected = true; 165 | log_i("Client address: %s", connInfo.getAddress().toString().c_str()); 166 | // NimBLEDevice::stopAdvertising(); 167 | } 168 | 169 | void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override 170 | { 171 | BLEdeviceConnected = false; 172 | tNotify_fast.disable(); 173 | tNotify_slow.disable(); 174 | log_i("Client disconnected - start advertising"); 175 | NimBLEDevice::startAdvertising(); 176 | } 177 | } serverCallbacks; 178 | class CharacteristicCallbacks : public NimBLECharacteristicCallbacks 179 | { 180 | void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override 181 | { 182 | #if (CORE_DEBUG_LEVEL > 0) 183 | // Be verbose on client subscription activity when debug level is not null 184 | String logmessage = "Client ID: "; 185 | logmessage += connInfo.getConnHandle(); 186 | logmessage += " Address: "; 187 | logmessage += connInfo.getAddress().toString().c_str(); 188 | if (subValue == 0) 189 | { 190 | logmessage += " Unsubscribed to "; 191 | } 192 | else if (subValue == 1) 193 | { 194 | logmessage += " Subscribed to notifications for "; 195 | } 196 | else if (subValue == 2) 197 | { 198 | logmessage += " Subscribed to indications for "; 199 | } 200 | else if (subValue == 3) 201 | { 202 | logmessage += " Subscribed to notifications and indications for "; 203 | } 204 | logmessage += std::string(pCharacteristic->getUUID()).c_str(); 205 | log_i("New sub: %s", logmessage); 206 | #endif 207 | // Start notifications when a client subscribes 208 | 209 | log_i("Initialize message version variables: FAST %d, SLOW %d", message_fast.msgtype, message_slow.msgtype); 210 | // Initialize the message type version, once. 211 | memcpy(ble_msg_fast, &message_fast.msgtype, sizeof message_fast.msgtype); 212 | memcpy(ble_msg_slow, &message_slow.msgtype, sizeof message_slow.msgtype); 213 | 214 | // HLT normalizes APS input, so we trigger a MAX-MIN sequence to let us send 0-1 values later 215 | // Initialize APS to 1 216 | log_i("Prime APS normalization to 1-100"); 217 | uint8_t aps_init = 1; 218 | memcpy(ble_msg_fast + sizeof message_fast.msgtype + sizeof message_fast.rpm + sizeof message_fast.rearwheelspeed, &aps_init, sizeof message_fast.aps); 219 | pFastCharacteristic->setValue(ble_msg_fast, CANBUS_FAST_SIZE); 220 | pFastCharacteristic->notify(); 221 | delay(5); 222 | aps_init = 100; 223 | memcpy(ble_msg_fast + sizeof message_fast.msgtype + sizeof message_fast.rpm + sizeof message_fast.rearwheelspeed, &aps_init, sizeof message_fast.aps); 224 | pFastCharacteristic->setValue(ble_msg_fast, CANBUS_FAST_SIZE); 225 | pFastCharacteristic->notify(); 226 | delay(5); 227 | 228 | tNotify_fast.enable(); 229 | tNotify_slow.enable(); 230 | }; 231 | 232 | void onWrite(NimBLECharacteristic *pCharacteristic,ble_gap_conn_desc * desc ) { 233 | log_i("Characteristic %s, written value: %d",pCharacteristic->getUUID().toString().c_str(), pCharacteristic->getValue().c_str()); 234 | if (pCharacteristic->getUUID() == pRebootCharacteristic->getUUID()) 235 | { 236 | log_e("reboot"); 237 | //TODO: this should be handled gently, not simply stop and reboot 238 | NimBLEDevice::getServer()->disconnect(desc->conn_handle); 239 | ESP.restart(); 240 | } 241 | }; 242 | } chrCallbacks; 243 | 244 | /* --------------------- CanBus Functions ------------------ */ 245 | #if !defined(CAN_DATA_SIMULATOR_MODE) 246 | void onReceive(int packetSize) 247 | { 248 | // received a packet 249 | long identifier = CAN.packetId(); 250 | uint8_t message[packetSize]; 251 | 252 | if (identifier == ID_FRAME80) 253 | { 254 | counter_80++; 255 | CAN.readBytes(message, packetSize); 256 | message_fast.aps = message[4] / 2; 257 | message_fast.rpm = ((uint16_t)message[5]) * 256 + (uint16_t)message[6]; 258 | } 259 | else if (identifier == ID_FRAME18) 260 | { 261 | counter_18++; 262 | CAN.readBytes(message, packetSize); 263 | message_fast.rearwheelspeed = (((uint16_t)(message[4] & 0b00011111)) << 4) + (message[5] >> 4); 264 | message_fast.gear = message[4] / 32; 265 | } 266 | else if (identifier == ID_FRAME100) 267 | { 268 | counter_100++; 269 | CAN.readBytes(message, packetSize); 270 | message_slow.enginetemp = message[3] - 40; 271 | message_slow.ambientemp = message[5] - 40; 272 | message_slow.battery = message[4]; 273 | } 274 | } 275 | #endif // #if !defined(CAN_DATA_SIMULATOR_MODE) 276 | 277 | #if (CORE_DEBUG_LEVEL > 0) 278 | // Pretty print a CANBUS message 279 | bool to_hex(char *dest, size_t dest_len, const uint8_t *values, size_t val_len) 280 | { 281 | // check that dest is large enough 282 | if (dest_len < (val_len * 2 + 1)) 283 | return false; 284 | // in case val_len==0 285 | *dest = '\0'; 286 | while (val_len--) 287 | { 288 | // sprintf directly to where dest points 289 | sprintf(dest, "%02X", *values); 290 | dest += 2; 291 | ++values; 292 | } 293 | return true; 294 | } 295 | #endif 296 | 297 | uint8_t nodata_watchdog=0; 298 | // amount in seconds to wait until a restart happens if no data is seen on the bus 299 | // use a longer period if LAZYWATCHDOG defined 300 | #ifndef LAZYWATCHDOG 301 | #define NODATA_WATCHDOG_TIMER (uint8_t) 10 302 | #else 303 | #define NODATA_WATCHDOG_TIMER (uint8_t) 120 304 | #endif 305 | 306 | void report_msg_counters() 307 | { 308 | #if (CORE_DEBUG_LEVEL > 0) 309 | char slowmessagehex[sizeof(ble_msg_slow) * 2 + 1]; 310 | to_hex(slowmessagehex, sizeof(slowmessagehex), ble_msg_slow, sizeof(ble_msg_slow)); 311 | char fastmessagehex[sizeof(ble_msg_fast) * 2 + 1]; 312 | to_hex(fastmessagehex, sizeof(fastmessagehex), ble_msg_fast, sizeof(ble_msg_fast)); 313 | 314 | log_i("Counters [18: %d, 80: %d, 100: %d], values RPM: %d, Speed: %d, APS: %d, Gear: %d, ETemp: %d, ATemp %d, Battery: %d. SLOW [%s], FAST [%s]", 315 | counter_18, counter_80, counter_100, 316 | message_fast.rpm, message_fast.rearwheelspeed, message_fast.aps, message_fast.gear, 317 | message_slow.enginetemp, message_slow.ambientemp, message_slow.battery, 318 | slowmessagehex, fastmessagehex); 319 | #endif 320 | status_led_flip(); 321 | if (counter_18+counter_80+counter_100 == 0 && nodata_watchdog++>NODATA_WATCHDOG_TIMER) { 322 | log_e("no data observed - watchdog 'no data' rebooting device"); 323 | // TODO: the BLE connection should be handled gently, not simply stop and reboot 324 | ESP.restart(); 325 | } 326 | } 327 | Task tReport(1000, -1, &report_msg_counters, &ts, true); 328 | 329 | /* --------------------- Simulation Functions ------------------ */ 330 | #if defined(CAN_DATA_SIMULATOR_MODE) 331 | // this goes from 0 to 100 then it resets 332 | uint base_counter = 1; 333 | const uint base_cycles = 1000; 334 | 335 | void handle_dm_increase() 336 | { 337 | // complete update in 100 cycles 338 | message_fast.rearwheelspeed = 5 + base_counter * (300 - 5) / base_cycles; 339 | message_fast.rpm = 2000 + base_counter * (12000 - 2000) / base_cycles; 340 | message_fast.aps = 1 + base_counter * 99 / base_cycles; 341 | message_fast.gear = 1 + base_counter * 5 / base_cycles; 342 | message_slow.enginetemp = 70 + base_counter * (110 - 70) / base_cycles; 343 | message_slow.ambientemp = 16 + base_counter * (40 - 16) / base_cycles; 344 | message_slow.battery = 120 + base_counter * (140 - 120) / base_cycles; 345 | if (base_counter++ > base_cycles) 346 | base_counter = 0; 347 | counter_18++; 348 | counter_80++; 349 | counter_100++; 350 | } 351 | Task tIncreaseDM(100, -1, &handle_dm_increase, &ts, true); 352 | 353 | #endif 354 | 355 | /* --------------------- Double Reset to start OTA via WiFi ------------------ */ 356 | #if defined(ENABLE_OTA_WITH_DRD) 357 | #if defined(ESP32) 358 | // #define USE_SPIFFS true 359 | #define ESP_DRD_USE_EEPROM true 360 | #define ESP_DRD_USE_LITTLEFS false 361 | #define ESP_DRD_USE_SPIFFS false 362 | #define DOUBLERESETDETECTOR_DEBUG true 363 | #else 364 | #error This code is intended to run on the ESP32 platform! Please check your Tools->Board setting. 365 | #endif 366 | 367 | #define DRD_ADDRESS 0 368 | #include //https://github.com/khoih-prog/ESP_DoubleResetDetector 369 | DoubleResetDetector *drd; 370 | 371 | // for OTA via TCP/IP 372 | #include 373 | #include 374 | #include 375 | const char *password = WIFI_PWD; 376 | #endif // #if defined(ENABLE_OTA_WITH_DRD) 377 | 378 | /* --------------------- Setup Loop ------------------ */ 379 | void setup() 380 | { 381 | // hold the device id to be used in broadcasting unit identifier strings 382 | const uint16_t chip = (uint16_t)((uint64_t)ESP.getEfuseMac() >> 32); 383 | // Generate device name based on mac address 384 | char ble_device_id[12]; 385 | sprintf(ble_device_id, "%s-%04X", BLE_DEVICE_ID_PREFIX, chip); 386 | 387 | // Detect if a double reset was used to start OTA updates 388 | // Display reason for last restart 389 | esp_reset_reason_t last_reset_reason; 390 | last_reset_reason = esp_reset_reason(); 391 | // enter_reconfig only if there was a valid reason 392 | bool enter_reconfig = false; 393 | switch (last_reset_reason) 394 | { 395 | case ESP_RST_UNKNOWN: 396 | log_w("Restarted because ESP_RST_UNKNOWN"); 397 | break; 398 | case ESP_RST_POWERON: 399 | log_i("Restarted because ESP_RST_POWERON"); 400 | enter_reconfig = true; 401 | break; 402 | case ESP_RST_SW: 403 | log_i("Restarted because ESP_RST_SW"); 404 | break; 405 | case ESP_RST_PANIC: 406 | log_e("Restarted because ESP_RST_PANIC"); 407 | break; 408 | case ESP_RST_INT_WDT: 409 | enter_reconfig = true; 410 | log_w("Restarted because ESP_RST_INT_WDT"); 411 | break; 412 | case ESP_RST_TASK_WDT: 413 | enter_reconfig = true; 414 | log_w("Restarted because ESP_RST_TASK_WDT"); 415 | break; 416 | case ESP_RST_WDT: 417 | enter_reconfig = true; 418 | log_w("Restarted because ESP_RST_WDT"); 419 | break; 420 | case ESP_RST_DEEPSLEEP: 421 | log_i("Restarted because ESP_RST_DEEPSLEEP"); 422 | break; 423 | case ESP_RST_BROWNOUT: 424 | log_w("Restarted because ESP_RST_BROWNOUT"); 425 | delay(500); 426 | break; 427 | case ESP_RST_SDIO: 428 | log_i("Restarted because ESP_RST_SDIO"); 429 | break; 430 | default: 431 | log_e("Restarted because "); 432 | break; 433 | } 434 | 435 | #if defined(ENABLE_OTA_WITH_DRD) 436 | drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS); 437 | if (drd->detectDoubleReset() && enter_reconfig) 438 | { 439 | log_i("Double Reset detected -> starting OTA mode"); 440 | WiFi.softAP(ble_device_id, password); 441 | ArduinoOTA.setHostname(BLE_DEVICE_ID_PREFIX); 442 | ArduinoOTA.begin(); 443 | unsigned long start = millis(); 444 | 445 | // fading led to suggest status 446 | // Fade the activity led to suggest something is not right 447 | const int freq = 5000; 448 | const int ledChannel = 0; 449 | const int resolution = 8; 450 | ledcSetup(ledChannel, freq, resolution); 451 | ledcAttachPin(LED_STATUS, ledChannel); 452 | unsigned int dutyCycle = 0; 453 | 454 | // Wait OTA_TIMEOUT seconds, then restart 455 | while (millis() - start < (OTA_TIMEOUT * 1000)) 456 | { 457 | ArduinoOTA.handle(); 458 | drd->loop(); 459 | ledcWrite(ledChannel, dutyCycle++); 460 | delay(2); 461 | if (dutyCycle > 255) 462 | { 463 | dutyCycle = 0; 464 | } 465 | } 466 | ESP.restart(); 467 | } 468 | delay(1000); 469 | #endif // #if defined(ENABLE_OTA_WITH_DRD) 470 | 471 | // Create the BLE Device 472 | NimBLEDevice::init(ble_device_id); 473 | // NimBLEDevice::setOwnAddrType(BLE_OWN_ADDR_RANDOM); 474 | // Optional: set the transmit power, default is 3db 475 | NimBLEDevice::setPower(9); /** +9db */ 476 | // NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_ADV); /** +9db */ 477 | // NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_CONN_HDL0); /** +9db */ 478 | // NimBLEDevice::setMTU(185); 479 | // Adding bonding, so that we avoid picking up sensors from other nearby bikes unknownigly 480 | NimBLEDevice::setSecurityAuth(true, true, true); 481 | // NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ (uint8_t)BLE_SM_PAIR_AUTHREQ_SC); 482 | NimBLEDevice::setSecurityPasskey(BLE_SECURITY_PASS); 483 | NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); 484 | 485 | // Create the BLE Server 486 | pServer = NimBLEDevice::createServer(); 487 | pServer->setCallbacks(&serverCallbacks); 488 | pServer->advertiseOnDisconnect(true); 489 | 490 | // Create the BLE Service 491 | NimBLEService *pService = pServer->createService(BLE_ENGINEDATA_SERVICE_UUID); 492 | 493 | // Create a BLE Characteristic - fast frequency messages 494 | pFastCharacteristic = pService->createCharacteristic(BLE_FAST_ENGINEDATA_CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::NOTIFY); 495 | pFastCharacteristic->setCallbacks(&chrCallbacks); 496 | 497 | // Create a BLE Characteristic - slow frequency messages 498 | pSlowCharacteristic = pService->createCharacteristic(BLE_SLOW_ENGINEDATA_CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::NOTIFY); 499 | pSlowCharacteristic->setCallbacks(&chrCallbacks); 500 | 501 | // Create a BLE Characteristic - write a true value to reboot the component 502 | pRebootCharacteristic = pService->createCharacteristic(BLE_REBOOT_CHARACTERISTIC_UUID, NIMBLE_PROPERTY::WRITE_NR); 503 | pRebootCharacteristic->setCallbacks(&chrCallbacks); 504 | 505 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml 506 | 507 | // Start the service 508 | pService->start(); 509 | 510 | // Start advertising 511 | NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); 512 | // define appearance, from https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.gap.appearance.xml 513 | // TODO appearance: 1344-18 = 1362 sensor - multisensor https://specificationrefs.bluetooth.com/assigned-values/Appearance%20Values.pdf 514 | pAdvertising->setAppearance(1344); // this might crash esp32 ble stack 515 | pAdvertising->addServiceUUID(pService->getUUID()); 516 | pAdvertising->enableScanResponse(true); 517 | // pAdvertising->setScanResponse(false); 518 | // pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue -> find out more at https://github.com/h2zero/NimBLE-Arduino/issues/129 519 | // pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter 520 | 521 | pAdvertising->start(); 522 | log_i("Waiting a client connection to start notifications..."); 523 | 524 | gpio_reset_pin(LED_STATUS); 525 | // Set the GPIO as a push/pull output 526 | gpio_set_direction(LED_STATUS, GPIO_MODE_OUTPUT); 527 | gpio_set_level(LED_STATUS, LOW); 528 | 529 | #if !defined(CAN_DATA_SIMULATOR_MODE) 530 | log_i("Setting up CANBUS"); 531 | CAN.setPins(RX_GPIO_NUM, TX_GPIO_NUM); 532 | CAN.observe(); 533 | // start the CAN bus at 500 kbps 534 | if (!CAN.begin(500E3)) 535 | { 536 | log_e("Starting CAN failed!"); 537 | // Fade the activity led to suggest something is not right 538 | int freq = 5000; 539 | int ledChannel = 0; 540 | int resolution = 8; 541 | ledcSetup(ledChannel, freq, resolution); 542 | ledcAttachPin(LED_STATUS, ledChannel); 543 | unsigned long InTenSeconds = millis() + 10000; 544 | 545 | while (millis() < InTenSeconds) 546 | { 547 | for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) 548 | { 549 | ledcWrite(ledChannel, dutyCycle); 550 | delay(2); 551 | } 552 | 553 | for (int dutyCycle = 255; dutyCycle >= 0; dutyCycle--) 554 | { 555 | ledcWrite(ledChannel, dutyCycle); 556 | delay(2); 557 | } 558 | } 559 | // 10 seconds have passed, restart ESP32 and hope for better luck 560 | 561 | ESP.restart(); 562 | } 563 | else 564 | { 565 | log_i("Connected to CANBUS"); 566 | } 567 | CAN.onReceive(onReceive); 568 | #else 569 | log_i("Running in simulation of CAN messages"); 570 | #endif // #if !defined(CAN_DATA_SIMULATOR_MODE) 571 | log_i("Starting main loop routine"); 572 | tReport.enable(); 573 | } 574 | 575 | // In the main loop we really don't do anything right now 576 | void loop() 577 | { 578 | ts.execute(); 579 | #if defined(ENABLE_OTA_WITH_DRD) 580 | drd->loop(); 581 | #endif // #if defined(ENABLE_OTA_WITH_DRD) 582 | } 583 | 584 | /* end of file */ --------------------------------------------------------------------------------