-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy pathWeatherSensor.h
610 lines (518 loc) · 23.4 KB
/
WeatherSensor.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
///////////////////////////////////////////////////////////////////////////////////////////////////
// WeatherSensor.h
//
// Bresser 5-in-1/6-in-1/7-in-1 868 MHz Weather Sensor Radio Receiver
// based on CC1101 or SX1276/RFM95W and ESP32/ESP8266
//
// https://github.com/matthias-bs/BresserWeatherSensorReceiver
//
// NOTE: Application/hardware specific configurations should be made in WeatherSensorCfg.h!
//
// Based on:
// ---------
// Bresser5in1-CC1101 by Sean Siford (https://github.com/seaniefs/Bresser5in1-CC1101)
// RadioLib by Jan Gromeš (https://github.com/jgromes/RadioLib)
// rtl433 by Benjamin Larsson (https://github.com/merbanan/rtl_433)
// - https://github.com/merbanan/rtl_433/blob/master/src/devices/bresser_5in1.c
// - https://github.com/merbanan/rtl_433/blob/master/src/devices/bresser_6in1.c
// - https://github.com/merbanan/rtl_433/blob/master/src/devices/bresser_7in1.c
//
// created: 05/2022
//
//
// MIT License
//
// Copyright (c) 2022 Matthias Prinke
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// History:
//
// 20220523 Created from https://github.com/matthias-bs/Bresser5in1-CC1101
// 20220524 Moved code to class WeatherSensor
// 20220526 Implemented getData(), changed debug output to macros
// 20220815 Added support of multiple sensors
// Moved windspeed_ms_to_bft() to WeatherUtils.h/.cpp
// 20221207 Added SENSOR_TYPE_THERMO_HYGRO
// 20220110 Added WEATHER0_RAIN_OV/WEATHER1_RAIN_OV
// 20230228 Added Bresser 7 in 1 decoder by Jorge Navarro-Ortiz ([email protected])
// 20230328 Added MSG_BUF_SIZE
// 20230330 Added changes for Adafruit Feather 32u4 LoRa Radio
// 20230412 Added workaround for Professional Wind Gauge / Anemometer, P/N 7002531
// 20230624 Added Bresser Lightning Sensor decoder
// 20230708 Added SENSOR_TYPE_WEATHER_7IN1 and startup flag
// 20230716 Added decodeMessage() to separate decoding function from receiving function
// 20230723 Added SENSOR_TYPE_WATER
// 20230804 Added Bresser Water Leakage Sensor decoder
// 20231006 Added crc16() from https://github.com/merbanan/rtl_433/blob/master/src/util.c
// 20231024 Added SENSOR_TYPE_POOL_THERMO
// 20231025 Added Bresser Air Quality (Particulate Matter) Sensor, P/N 7009970
// Modified device type definitions
// 20231030 Refactored sensor data using a union to save memory
// 20231130 Bresser 3-in-1 Professional Wind Gauge / Anemometer, PN 7002531: Replaced workaround
// for negative temperatures by fix (6-in-1 decoder)
// 20231227 Added sleep()
// 20240116 Fixed width of Lightning.strike_count
// 20240207 Added sensors for CO2, P/N 7009977 and HCHO/VOC, P/N 7009978
// see https://github.com/merbanan/rtl_433/pull/2815
// & https://github.com/merbanan/rtl_433/pull/2817
// 20240213 Added PM1.0 to air quality (PM) sensor decoder
// 20240222 Added clearing of flags in clearSlots() to prevent mixing of old and new data
// 20240322 Added pin definitions for M5Stack Core2 with M5Stack Module LoRa868
// 20240409 Added radioReset()
// 20240417 Added sensor configuration at run time
// 20240506 Changed sensor from array to std::vector, added getSensorCfg() / setSensorCfg()
// 20240507 Added configuration of enabled decoders at run time
//
// ToDo:
// -
//
///////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef WeatherSensor_h
#define WeatherSensor_h
#include <Arduino.h>
#include <vector>
#include <string>
#include <Preferences.h>
#include <RadioLib.h>
// Sensor Types
// 0 - Weather Station 5-in-1; PN 7002510..12/7902510..12
// 1 - Weather Station 6-in-1; PN 7002585
// - Professional Wind Gauge 6-in-1; PN 7002531
// - Weather Station 7-in-1; PN 7003300
// 2 - Thermo-/Hygro-Sensor 6-in-1; PN 7009999
// 3 - Pool / Spa Thermometer 6-in-1; PN 7000073
// 4 - Soil Moisture Sensor 6-in-1; PN 7009972
// 5 - Water Leakage Sensor 6-in-1; PN 7009975
// 8 - Air Quality Sensor PM2.5/PM10 7-in-1; P/N 7009970
// 9 - Professional Rain Gauge (5-in-1 decoder)
// 9 - Lightning Sensor PN 7009976
// 10 - CO2 Sensor 7-in-1; PN 7009977
// 11 - HCHO/VCO Sensor 7-in-1; PN 7009978
#define SENSOR_TYPE_WEATHER0 0 // Weather Station
#define SENSOR_TYPE_WEATHER1 1 // Weather Station
#define SENSOR_TYPE_THERMO_HYGRO 2 // Thermo-/Hygro-Sensor
#define SENSOR_TYPE_POOL_THERMO 3 // Pool / Spa Thermometer
#define SENSOR_TYPE_SOIL 4 // Soil Temperature and Moisture (from 6-in-1 decoder)
#define SENSOR_TYPE_LEAKAGE 5 // Water Leakage
#define SENSOR_TYPE_AIR_PM 8 // Air Quality Sensor (Particle Matter)
#define SENSOR_TYPE_RAIN 9 // Professional Rain Gauge (from 5-in-1 decoder)
#define SENSOR_TYPE_LIGHTNING 9 // Lightning Sensor
#define SENSOR_TYPE_CO2 10 // CO2 Sensor
#define SENSOR_TYPE_HCHO_VOC 11 // Air Quality Sensor (HCHO and VOC)
// Sensor specific rain gauge overflow threshold (mm)
#define WEATHER0_RAIN_OV 1000
#define WEATHER1_RAIN_OV 100000
// Flags for controlling completion of reception in getData()
#define DATA_COMPLETE 0x1 // only completed slots (as opposed to partially filled)
#define DATA_TYPE 0x2 // at least one slot with specific sensor type
#define DATA_ALL_SLOTS 0x8 // all slots completed
// Flags for checking enabled decoders
#define DECODER_5IN1 0x01
#define DECODER_6IN1 0x02
#define DECODER_7IN1 0x04
#define DECODER_LIGHTNING 0x08
#define DECODER_LEAKAGE 0x10
// Message buffer size
#define MSG_BUF_SIZE 27
// Radio message decoding status
typedef enum DecodeStatus {
DECODE_INVALID, DECODE_OK, DECODE_PAR_ERR, DECODE_CHK_ERR, DECODE_DIG_ERR, DECODE_SKIP, DECODE_FULL
} DecodeStatus;
/*!
* \struct SensorMap
*
* \brief Mapping of sensor IDs to names
*/
typedef struct SensorMap {
uint32_t id; //!< ID if sensor (as transmitted in radio message)
std::string name; //!< Name of sensor (e.g. for MQTT topic)
} SensorMap;
/*!
\class WeatherSensor
\brief Receive, decode and store Bresser Weather Sensor Data
Uses CC1101 or SX1276 radio module for receiving FSK modulated signal at 868 MHz.
*/
class WeatherSensor {
private:
Preferences cfgPrefs; //!< Preferences (stored in flash memory)
std::vector<uint32_t> sensor_ids_inc;
std::vector<uint32_t> sensor_ids_exc;
static uint8_t _dummy_en_decoders;
public:
/*!
\brief Presence check and initialization of radio module.
\returns RADIOLIB_ERR_NONE on success (otherwise does never return).
*/
int16_t begin(void);
/*!
\brief Reset radio transceiver
*/
void radioReset(void);
/*!
\brief Set transceiver into sleep mode
*/
void sleep(void);
/*!
\brief Wait for reception of data or occurrance of timeout.
With BRESSER_6_IN_1, data is distributed across two different messages. Reception of entire
data is tried if 'complete' is set.
\param timeout timeout in ms.
\param flags DATA_COMPLETE / DATA_TYPE / DATA_ALL_SLOTS
\param type sensor type (combined with FLAGS==DATA_TYPE)
\param func Callback function for each loop iteration. (default: NULL)
\returns false: Timeout occurred.
true: Reception (according to parammeter 'complete') successful.
*/
bool getData(uint32_t timeout, uint8_t flags = 0, uint8_t type = 0, void (*func)() = NULL);
/*!
\brief Tries to receive radio message (non-blocking) and to decode it.
Timeout occurs after a multitude of expected time-on-air.
\returns DecodeStatus
*/
DecodeStatus getMessage(void);
/*!
\brief Decode message
Tries the available decoders until a decoding was successful.
\returns DecodeStatus
*/
DecodeStatus decodeMessage(const uint8_t *msg, uint8_t msgSize);
struct Weather {
bool temp_ok = false; //!< temperature o.k. (only 6-in-1)
bool humidity_ok = false; //!< humidity o.k.
bool light_ok = false; //!< light o.k. (only 7-in-1)
bool uv_ok = false; //!< uv radiation o.k. (only 6-in-1)
bool wind_ok = false; //!< wind speed/direction o.k. (only 6-in-1)
bool rain_ok = false; //!< rain gauge level o.k.
float temp_c = 0.0; //!< temperature in degC
float light_klx = 0.0; //!< Light KLux (only 7-in-1)
float light_lux = 0.0; //!< Light lux (only 7-in-1)
float uv = 0.0; //!< uv radiation (only 6-in-1)
float rain_mm = 0.0; //!< rain gauge level in mm
#ifdef WIND_DATA_FLOATINGPOINT
float wind_direction_deg = 0.0; //!< wind direction in deg
float wind_gust_meter_sec = 0.0; //!< wind speed (gusts) in m/s
float wind_avg_meter_sec = 0.0; //!< wind speed (avg) in m/s
#endif
#ifdef WIND_DATA_FIXEDPOINT
// For LoRa_Serialization:
// fixed point integer with 1 decimal -
// saves two bytes compared to "RawFloat"
uint16_t wind_direction_deg_fp1 = 0; //!< wind direction in deg (fixed point int w. 1 decimal)
uint16_t wind_gust_meter_sec_fp1 = 0; //!< wind speed (gusts) in m/s (fixed point int w. 1 decimal)
uint16_t wind_avg_meter_sec_fp1 = 0; //!< wind speed (avg) in m/s (fixed point int w. 1 decimal)
#endif
uint8_t humidity = 0; //!< humidity in %
};
struct Soil {
float temp_c; //!< temperature in degC
uint8_t moisture; //!< moisture in % (only 6-in-1)
};
struct Lightning {
uint8_t distance_km; //!< lightning distance in km (only lightning)
uint16_t strike_count; //!< lightning strike counter (only lightning)
uint16_t unknown1; //!< unknown part 1
uint16_t unknown2; //!< unknown part 2
};
struct Leakage {
bool alarm; //!< water leakage alarm (only water leakage)
};
struct AirPM {
uint16_t pm_1_0; //!< air quality PM1.0 in µg/m³
uint16_t pm_2_5; //!< air quality PM2.5 in µg/m³
uint16_t pm_10; //!< air quality PM10 in µg/m³
uint16_t pm_1_0_init; //!< measurement value invalid due to initialization
bool pm_2_5_init; //!< measurement value invalid due to initialization
bool pm_10_init; //!< measurement value invalid due to initialization
};
struct AirCO2 {
uint16_t co2_ppm; //!< CO2 concentration in ppm
bool co2_init; //!< measurement value invalid due to initialization
};
struct AirVOC {
uint16_t hcho_ppb; //!< formaldehyde concentrartion in ppb
uint8_t voc_level; //!< volatile organic oompounds; 1 - bad air quality .. 5 - very good air quality
bool hcho_init; //!< measurement value invalid due to initialization
bool voc_init; //!< measurement value invalid due to initialization
};
/**
* \struct Sensor
*
* \brief sensor data and status flags
*/
struct Sensor {
uint32_t sensor_id; //!< sensor ID (5-in-1: 1 byte / 6-in-1: 4 bytes / 7-in-1: 2 bytes)
float rssi; //!< received signal strength indicator in dBm
uint8_t s_type; //!< sensor type
uint8_t chan; //!< channel
bool startup = false; //!< startup after reset / battery change
bool battery_ok; //!< battery o.k.
bool valid; //!< data valid (but not necessarily complete)
bool complete; //!< data is split into two separate messages is complete (only 6-in-1 WS)
union {
struct Weather w;
struct Soil soil;
struct Lightning lgt;
struct Leakage leak;
struct AirPM pm;
struct AirCO2 co2;
struct AirVOC voc;
};
Sensor ()
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wclass-memaccess"
memset(this, 0, sizeof(*this));
#pragma GCC diagnostic pop
};
};
typedef struct Sensor sensor_t; //!< Shortcut for struct Sensor
std::vector<sensor_t> sensor; //!< sensor data array
float rssi = 0.0; //!< received signal strength indicator in dBm
uint8_t rxFlags; //!< receive flags (see getData())
uint8_t enDecoders = 0xFF; //!< enabled Decoders
/*!
\brief Generates data otherwise received and decoded from a radio message.
\returns Always true (for compatibility with getMessage())
*/
bool genMessage(int i, uint32_t id = 0xff, uint8_t s_type = 1, uint8_t channel = 0, uint8_t startup = 0);
/*!
\brief Clear sensor data
If 'type' is not specified, all slots are cleared. If 'type' is specified,
only slots containing data of the given sensor type are cleared.
\param type Sensor type
*/
void clearSlots(uint8_t type = 0xFF)
{
for (size_t i=0; i<sensor.size(); i++) {
if ((type == 0xFF) || (sensor[i].s_type == type)) {
sensor[i].valid = false;
sensor[i].complete = false;
}
if (sensor[i].s_type == SENSOR_TYPE_WEATHER1) {
sensor[i].w.temp_ok = false;
sensor[i].w.humidity_ok = false;
sensor[i].w.light_ok = false;
sensor[i].w.uv_ok = false;
sensor[i].w.wind_ok = false;
sensor[i].w.rain_ok = false;
}
}
};
/*!
* Find slot of required data set by ID
*
* \param id sensor ID
*
* \returns slot (or -1 if not found)
*/
int findId(uint32_t id);
/*!
* Find slot of required data set by type and (optionally) channel
*
* \param type sensor type
* \param channel sensor channel (0xFF: don't care)
*
* \returns slot (or -1 if not found)
*/
int findType(uint8_t type, uint8_t channel = 0xFF);
/*!
* Set sensors include list in Preferences
*
* \param bytes sensor IDs
* \param size buffer size in bytes
*/
void setSensorsInc(uint8_t *bytes, uint8_t size);
/*!
* Set sensors include list in Preferences
*
* \param bytes sensor IDs
* \param size buffer size in bytes
*/
void setSensorsExc(uint8_t *bytes, uint8_t size);
/*!
* Set maximum number of sensors and store it in Preferences
*
* \param max_sensors maximum number of sensors
* \param rx_flags receive flags (see getData())
* \param en_decoders enabled decoders
*/
void setSensorsCfg(uint8_t max_sensors, uint8_t rx_flags, uint8_t en_decoders = 0xFF);
/*!
* Get sensors include list from Preferences
*
* \param payload buffer for storing sensor IDs
*
* \returns size size in bytes
*/
uint8_t getSensorsInc(uint8_t *payload);
/*!
* Get sensors exclude list from Preferences
*
* \param payload buffer for storing sensor IDs
*
* \returns size size in bytes
*/
uint8_t getSensorsExc(uint8_t *payload);
/*!
* Get maximum number of sensors from Preferences
*
* \param max_sensors maximum number of sensors
* \param rx_flags receive flags (see getData())
* \param en_decoders enabled decoders
*/
void getSensorsCfg(uint8_t &max_sensors, uint8_t &rx_flags, uint8_t &en_decoders = _dummy_en_decoders);
private:
struct Sensor *pData; //!< pointer to slot in sensor data array
/*!
* Initialize list from Preferences or array
*
* The default list will be used if there is no entry from Preferences
*
* \param list list of sensor IDs
* \param list_def default list of sensor IDs
* \param key keyword in Preferences
*/
void initList(std::vector<uint32_t> &list, const std::vector<uint32_t> list_def, const char *key);
/*!
* \brief Find slot in sensor data array
*
* 1. The current sensor ID is checked against the exclude-list (sensor_ids_exc).
* If there is a match, the current message is skipped.
*
* 2. If the include-list (sensor_ids_inc) is not empty, the current ID is checked
* against it. If there is NO match, the current message is skipped.
*
* 3. Either an existing slot with the same ID as the current message is updated
* or a free slot (if any) is selected.
*
* \param id Sensor ID from current message
*
* \returns Pointer to slot in sensor data array or NULL if ID is not wanted or
* no free slot is available.
*/
int findSlot(uint32_t id, DecodeStatus * status);
#ifdef BRESSER_5_IN_1
/*!
\brief Decode BRESSER_5_IN_1 message.
\param msg Message buffer.
\param msgSize Message size in bytes.
\returns Decode status.
*/
DecodeStatus decodeBresser5In1Payload(const uint8_t *msg, uint8_t msgSize);
#endif
#ifdef BRESSER_6_IN_1
/*!
\brief Decode BRESSER_6_IN_1 message.
With BRESSER_6_IN_1, data is distributed across two different messages. Additionally,
the message format supports different kinds of sensors.
\param msg Message buffer.
\param msgSize Message size in bytes.
\returns Decode status.
*/
DecodeStatus decodeBresser6In1Payload(const uint8_t *msg, uint8_t msgSize);
#endif
#ifdef BRESSER_7_IN_1
/*!
\brief Decode BRESSER_7_IN_1 message.
\param msg Message buffer.
\param msgSize Message size in bytes.
\returns Decode status.
*/
DecodeStatus decodeBresser7In1Payload(const uint8_t *msg, uint8_t msgSize);
#endif
#ifdef BRESSER_LIGHTNING
/*!
\brief Decode BRESSER_LIGHTNING message. (similar to 7-in-1)
\param msg Message buffer.
\param msgSize Message size in bytes.
\returns Decode status.
*/
DecodeStatus decodeBresserLightningPayload(const uint8_t *msg, uint8_t msgSize);
#endif
#ifdef BRESSER_LEAKAGE
/*!
\brief Decode BRESSER_LEAKAGE message. (similar to 7-in-1)
\param msg Message buffer.
\param msgSize Message size in bytes.
\returns Decode status.
*/
DecodeStatus decodeBresserLeakagePayload(const uint8_t *msg, uint8_t msgSize);
#endif
protected:
/*!
\brief Linear Feedback Shift Register - Digest16 (Data integrity check).
*/
uint16_t lfsr_digest16(uint8_t const message[], unsigned bytes, uint16_t gen, uint16_t key);
/*!
\brief Calculate sum of all message bytes.
\param message Message buffer.
\param num_bytes Number of bytes.
\returns Sum of all message bytes.
*/
int add_bytes(uint8_t const message[], unsigned num_bytes);
/*!
\brief Calculate crc16 of all message bytes.
\param message Message buffer.
\param num_bytes Number of bytes.
\param polynomial Polynomial
\param initial Initial value.
\returns CRC16 of all message bytes.
*/
uint16_t crc16(uint8_t const message[], unsigned nBytes, uint16_t polynomial, uint16_t init);
#if CORE_DEBUG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG
/*!
* \brief Log message payload
*
* \param descr Description.
* \param msg Message buffer.
* \param msgSize Message size.
*
* Result (example):
* Byte #: 00 01 02 03...
* <descr>: DE AD BE EF...
*/
void log_message(const char *descr, const uint8_t *msg, uint8_t msgSize) {
char buf[128];
const char txt[] = "Byte #: ";
int offs;
int len1 = strlen(txt);
int len2 = strlen(descr) + 2; // add colon and space
int prefix_len = max(len1, len2);
memset(buf, ' ', prefix_len);
buf[prefix_len] = '\0';
offs = (len1 < len2) ? (len2 - len1) : 0;
strcpy(&buf[offs], txt);
// Print byte index
for (size_t i = 0 ; i < msgSize; i++) {
sprintf(&buf[strlen(buf)], "%02d ", i);
}
log_d("%s", buf);
memset(buf, ' ', prefix_len);
buf[prefix_len] ='\0';
offs = (len1 > len2) ? (len1 - len2) : 0;
sprintf(&buf[offs], "%s: ", descr);
for (size_t i = 0 ; i < msgSize; i++) {
sprintf(&buf[strlen(buf)], "%02X ", msg[i]);
}
log_d("%s", buf);
}
#endif
};
#endif