11// /////////////////////////////////////////////////////////////////////////////
2- // BleSensors.h
2+ // BleSensors.cpp
33//
44// Wrapper class for Theeengs Decoder (https://github.com/theengs/decoder)
55//
1111//
1212// MIT License
1313//
14- // Copyright (c) 2023 Matthias Prinke
14+ // Copyright (c) 2025 Matthias Prinke
1515//
1616// Permission is hereby granted, free of charge, to any person obtaining a copy
1717// of this software and associated documentation files (the "Software"), to deal
3535// History:
3636//
3737// 20230211 Created
38- // 20240427 Added paramter activeScan to getData()
38+ // 20240427 Added parameter activeScan to getData()
3939// 20250121 Updated for NimBLE-Arduino v2.x
40+ // 20250725 Fixed potential buffer overflow
41+ // 20250728 Fixed naming collision with ATC_MiThermometer
42+ // 20250808 Added specific logging macros in scan callback to avoid WDT reset
43+ // 20250926 Moved decoding from callback to getData() to prevent WDT reset
44+ // Modified getData() to return number of known sensors found
45+ // 20251013 Added abort of scanning by touch sensor
4046//
4147// ToDo:
4248// -
4753
4854#include " BleSensors.h"
4955
56+ extern bool TouchTriggered (void );
57+
5058namespace BleSensorsCallbacks
5159{
60+ constexpr size_t JSON_SERIALIZATION_BUFFER_SIZE = 256 ; // !< Buffer size for JSON serialization
5261
62+ /* !
63+ * \brief BLE scan callback class
64+ */
5365 class ScanCallbacks : public NimBLEScanCallbacks
5466 {
5567 public:
56- std::vector<std::string> m_knownBLEAddresses; // / MAC addresses of known sensors
57- std::vector<ble_sensors_t > *m_sensorData; // / Sensor data
58- NimBLEScan *m_pBLEScan;
68+ std::vector<std::string> m_knownBLEAddresses; // !< MAC addresses of known sensors
69+ std::vector<ble_sensors_t > *m_sensorData; // !< Sensor data
70+ NimBLEScan *m_pBLEScan; // !< Pointer to the BLE scan object
5971
60- private:
61- int m_devices_found = 0 ; // / Number of known devices found
72+ // Raw JSON payloads collected during scan for later decoding.
73+ std::vector<std::string> m_rawBLEJsons;
74+ std::vector<int > m_rawIndexes; // !< corresponding known sensor index
6275
76+ int m_devices_found = 0 ; // !< Number of known devices found
77+
78+ private:
6379 void onDiscovered (const NimBLEAdvertisedDevice *advertisedDevice) override
6480 {
65- log_v (" Discovered Advertised Device: %s" , advertisedDevice->toString ().c_str ());
81+ cb_log_v (" Discovered Advertised Device: %s" , advertisedDevice->toString ().c_str ());
6682 }
6783
6884 void onResult (const NimBLEAdvertisedDevice *advertisedDevice) override
6985 {
70- TheengsDecoder decoder;
71- unsigned idx;
72- bool device_found = false ;
73- JsonDocument doc;
86+ cb_log_v (" Advertised Device Result: %s" , advertisedDevice->toString ().c_str ());
7487
75- log_v (" Advertised Device Result: %s" , advertisedDevice->toString ().c_str ());
88+ // Build JSON representation of advertised data
89+ JsonDocument doc;
7690 JsonObject BLEdata = doc.to <JsonObject>();
7791 String mac_adress = advertisedDevice->getAddress ().toString ().c_str ();
7892
7993 BLEdata[" id" ] = (char *)mac_adress.c_str ();
80- for (idx = 0 ; idx < m_knownBLEAddresses.size (); idx++)
94+
95+ int found_index = -1 ;
96+ for (unsigned idx = 0 ; idx < m_knownBLEAddresses.size (); idx++)
8197 {
8298 if (mac_adress == m_knownBLEAddresses[idx].c_str ())
8399 {
84- log_v (" BLE device found at index %d" , idx);
85- device_found = true ;
86- m_devices_found++;
100+ cb_log_v (" BLE device known at index %u" , idx);
101+ found_index = (int )idx;
87102 break ;
88103 }
89104 }
@@ -111,42 +126,37 @@ namespace BleSensorsCallbacks
111126 BLEdata[" servicedatauuid" ] = " 0x181a" ;
112127 }
113128
114- if (decoder.decodeBLEJson (BLEdata) && device_found)
129+ // If this is a known device, store the serialized JSON for decoding later
130+ if (found_index >= 0 )
115131 {
116- if (CORE_DEBUG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG)
117- {
118- char buf[512 ];
119- serializeJson (BLEdata, buf);
120- log_d (" TheengsDecoder found device: %s" , buf);
121- }
132+ // Serialize into std::string
133+ std::string serialized;
134+ serialized.reserve (JSON_SERIALIZATION_BUFFER_SIZE);
135+ serializeJson (BLEdata, serialized);
136+ m_rawBLEJsons.push_back (std::move (serialized));
137+ m_rawIndexes.push_back (found_index);
138+
139+ m_devices_found++;
140+ cb_log_v (" Known BLE device queued for decoding at index %d" , found_index);
141+ }
122142
123- // see https://stackoverflow.com/questions/5348089/passing-a-vector-between-functions-via-pointers
124- (*m_sensorData)[idx].temperature = (float )BLEdata[" tempc" ];
125- (*m_sensorData)[idx].humidity = (float )BLEdata[" hum" ];
126- (*m_sensorData)[idx].batt_level = (uint8_t )BLEdata[" batt" ];
127- (*m_sensorData)[idx].rssi = (int )BLEdata[" rssi" ];
128- (*m_sensorData)[idx].valid = ((*m_sensorData)[idx].batt_level > 0 );
129- log_i (" Temperature: %.1f°C" , (*m_sensorData)[idx].temperature );
130- log_i (" Humidity: %.1f%%" , (*m_sensorData)[idx].humidity );
131- log_i (" Battery level: %d%%" , (*m_sensorData)[idx].batt_level );
132- log_i (" RSSI: %ddBm" , (*m_sensorData)[idx].rssi = (int )BLEdata[" rssi" ]);
133- log_d (" BLE devices found: %d" , m_devices_found);
134-
135- BLEdata.remove (" manufacturerdata" );
136- BLEdata.remove (" servicedata" );
143+ // Abort scanning by touch sensor
144+ if (TouchTriggered ()) {
145+ log_i (" Touch interrupt!" );
146+ m_pBLEScan->stop ();
137147 }
138148
139149 // Abort scanning because all known devices have been found
140- if (m_devices_found == m_knownBLEAddresses.size ())
150+ if (m_devices_found == ( int ) m_knownBLEAddresses.size ())
141151 {
142- log_i (" All devices found." );
152+ cb_log_i (" All devices found." );
143153 m_pBLEScan->stop ();
144154 }
145155 }
146156
147157 void onScanEnd (const NimBLEScanResults &results, int reason) override
148158 {
149- log_v (" Scan Ended; reason = %d" , reason);
159+ cb_log_v (" Scan Ended; reason = %d" , reason);
150160 }
151161 } scanCallbacks;
152162
@@ -170,6 +180,11 @@ using namespace BleSensorsCallbacks;
170180
171181/* *
172182 * \brief Get BLE sensor data
183+ *
184+ * Note: Decoding using TheengsDecoder is performed here after scanning,
185+ * instead of during the NimBLE callback, to avoid heavy processing
186+ * in the callback context, which can lead to the watchdog being
187+ * triggered.
173188 */
174189unsigned BleSensors::getData (uint32_t scanTime, bool activeScan)
175190{
@@ -185,11 +200,74 @@ unsigned BleSensors::getData(uint32_t scanTime, bool activeScan)
185200 scanCallbacks.m_sensorData = &data;
186201 scanCallbacks.m_pBLEScan = _pBLEScan;
187202
203+ // Ensure previous scan data cleared
204+ scanCallbacks.m_rawBLEJsons .clear ();
205+ scanCallbacks.m_rawIndexes .clear ();
206+ scanCallbacks.m_devices_found = 0 ;
207+
188208 // Start scanning
189209 // Blocks until all known devices are found or scanTime is expired
190210 _pBLEScan->getResults (scanTime * 1000 , false );
191211
192- return 0 ;
212+ // Now perform decoding of the collected JSON payloads (outside callback)
213+ TheengsDecoder decoder;
214+ for (size_t i = 0 ; i < scanCallbacks.m_rawBLEJsons .size (); ++i)
215+ {
216+ const std::string &serialized = scanCallbacks.m_rawBLEJsons [i];
217+ int idx = scanCallbacks.m_rawIndexes [i];
218+ if (idx < 0 || idx >= (int )scanCallbacks.m_sensorData ->size ())
219+ continue ;
220+
221+ // Parse JSON
222+ JsonDocument doc;
223+ DeserializationError err = deserializeJson (doc, serialized);
224+ if (err)
225+ {
226+ cb_log_w (" Failed to parse stored advert JSON: %s" , err.c_str ());
227+ continue ;
228+ }
229+
230+ JsonObject BLEdata = doc.as <JsonObject>();
231+
232+ if (decoder.decodeBLEJson (BLEdata))
233+ {
234+ if (CORE_DEBUG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG) {
235+ constexpr size_t DEBUG_BUFFER_SIZE = 512 ;
236+ char buf[DEBUG_BUFFER_SIZE];
237+ serializeJson (BLEdata, buf, sizeof (buf));
238+ cb_log_d (" TheengsDecoder decoded device: %s" , buf);
239+ }
240+
241+ // Update sensor data vector
242+ (*scanCallbacks.m_sensorData )[idx].temperature = (float )BLEdata[" tempc" ];
243+ (*scanCallbacks.m_sensorData )[idx].humidity = (float )BLEdata[" hum" ];
244+ (*scanCallbacks.m_sensorData )[idx].batt_level = (uint8_t )BLEdata[" batt" ];
245+ (*scanCallbacks.m_sensorData )[idx].rssi = (int )BLEdata[" rssi" ];
246+ (*scanCallbacks.m_sensorData )[idx].valid = ((*scanCallbacks.m_sensorData )[idx].batt_level > 0 );
247+
248+ cb_log_i (" Temperature: %.1f°C" , (*scanCallbacks.m_sensorData )[idx].temperature );
249+ cb_log_i (" Humidity: %.1f%%" , (*scanCallbacks.m_sensorData )[idx].humidity );
250+ cb_log_i (" Battery level: %d%%" , (*scanCallbacks.m_sensorData )[idx].batt_level );
251+ cb_log_i (" RSSI: %ddBm" , (*scanCallbacks.m_sensorData )[idx].rssi );
252+ cb_log_d (" BLE devices found: %d" , scanCallbacks.m_devices_found );
253+
254+ // remove bulky fields from doc to keep log/output clean if needed
255+ BLEdata.remove (" manufacturerdata" );
256+ BLEdata.remove (" servicedata" );
257+ }
258+ else
259+ {
260+ cb_log_v (" TheengsDecoder could not decode stored advert for index %d" , idx);
261+ }
262+ }
263+
264+ // Clear stored raw adverts to free memory
265+ scanCallbacks.m_rawBLEJsons .clear ();
266+ scanCallbacks.m_rawIndexes .clear ();
267+ unsigned devices_found = scanCallbacks.m_devices_found ;
268+ scanCallbacks.m_devices_found = 0 ;
269+
270+ return devices_found;
193271}
194272
195273#endif // !defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) && !defined(ARDUINO_ARCH_RP2040)
0 commit comments