Dawn Framework 1.0
Universal data acquisition framework for embedded systems
prph_imds.cxx
1// dawn/src/proto/nimble/prph_imds.cxx
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5
6#include "dawn/proto/nimble/prph_imds.hxx"
7
8#include <cstdlib>
9#include <new>
10
11#include "dawn/io/common.hxx"
12#include "dawn/io/sdata.hxx"
13#include "dawn/proto/nimble/gatt_runtime.hxx"
14#include "host/ble_hs_mbuf.h"
15#include "os/os_mbuf.h"
16
17using namespace dawn;
18
19#ifdef CONFIG_DAWN_PROTO_NIMBLE_EXTENDED_METADATA
20static ble_uuid_any_t *imdsMakeUuid16(uint16_t value)
21{
22 ble_uuid_any_t *uuid = new (std::nothrow) ble_uuid_any_t{};
23 if (uuid == nullptr)
24 {
25 return nullptr;
26 }
27
28 uuid->u.type = BLE_UUID_TYPE_16;
29 uuid->u16.value = value;
30 return uuid;
31}
32
33static void imdsFreeUuid(const ble_uuid_t *uuid)
34{
35 if (uuid != nullptr)
36 {
37 delete reinterpret_cast<const ble_uuid_any_t *>(uuid);
38 }
39}
40#endif
41
42#ifdef CONFIG_DAWN_IO_NOTIFY
43int CProtoNimblePrphImds::notifierCb(void *priv, io_ddata_t *data)
44{
45 SPrphNotiferCb *ncb = static_cast<SPrphNotiferCb *>(priv);
46
47 if (ncb == nullptr)
48 {
49 DAWNERR("NULL ncb pointer in notifier\n");
50 return -EINVAL;
51 }
52
53 CIOCommon *io = reinterpret_cast<CIOCommon *>(ncb->io);
54
55 if (io == nullptr)
56 {
57 DAWNERR("NULL io pointer in notifier\n");
58 return -EINVAL;
59 }
60
61 // Copy data to local storage and check if value changed
62
63 std::memcpy(ncb->data->getDataPtr(), data->getDataPtr(), ncb->data->getDataSize());
64
65 // Notify nimble
66
67 ble_gatts_chr_updated(ncb->handle);
68
69 return OK;
70}
71#endif
72
73#ifdef CONFIG_DAWN_PROTO_NIMBLE_EXTENDED_METADATA
74int CProtoNimblePrphImds::descriptorCb(uint16_t conn_handle,
75 uint16_t attr_handle,
76 struct ble_gatt_access_ctxt *ctxt,
77 void *arg)
78{
79 SImdsDscCb *dcb = static_cast<SImdsDscCb *>(arg);
80 int ret;
81
82 UNUSED(conn_handle);
83 UNUSED(attr_handle);
84
85 if (dcb == nullptr || ctxt->op != BLE_GATT_ACCESS_OP_READ_DSC)
86 {
87 return BLE_ATT_ERR_UNLIKELY;
88 }
89
90 ret = os_mbuf_append(ctxt->om, dcb->data, dcb->len);
91 if (ret < 0)
92 {
93 DAWNERR("IMDS descriptor append failed %d\n", ret);
94 return BLE_ATT_ERR_UNLIKELY;
95 }
96
97 return 0;
98}
99#endif
100
101// NOTE: For digial IO - 1B data are supported For analog IO - 4B data are
102// supported
103template<typename T, size_t WriteBytes>
104int CProtoNimblePrphImds::callback(uint16_t conn_handle,
105 uint16_t attr_handle,
106 struct ble_gatt_access_ctxt *ctxt,
107 void *arg)
108{
109 SPrphNotiferCb *ncb = static_cast<SPrphNotiferCb *>(arg);
110 CIOCommon *io = reinterpret_cast<CIOCommon *>(ncb->io);
111 uint16_t uuid16 = ble_uuid_u16(ctxt->chr->uuid);
112 int ret;
113
114 switch (ctxt->op)
115 {
116 case BLE_GATT_ACCESS_OP_READ_CHR:
117 {
118 uint32_t retval;
119
120#ifdef CONFIG_DAWN_IO_NOTIFY
121 if (!io->isNotify())
122#endif
123 {
124 // Get data
125
126 ret = io->getData(*ncb->data, 1);
127 if (ret < 0)
128 {
129 return BLE_ATT_ERR_UNLIKELY;
130 }
131 }
132
133 // Convert units if this is float type
134
135 if (io->getDtype() == SObjectId::DTYPE_FLOAT)
136 {
137 float tmp;
138
139 // Convert unit
140
141 tmp = ncb->data->get<float>(0) * IProtoNimblePrphCb::charScaleGet(uuid16);
142
143 // Convert data to int16_t
144
145 retval = (T)tmp;
146 }
147 else
148 {
149 DAWNERR("IMDS data type not supported yet\n");
150 return BLE_ATT_ERR_UNLIKELY;
151 }
152
153 // Write data
154
155 ret = os_mbuf_append(ctxt->om, &retval, WriteBytes);
156 if (ret < 0)
157 {
158 DAWNERR("os_mbuf_append failed %d\n", ret);
159 }
160
161 return 0;
162 }
163
164 default:
165 {
166 DAWNERR("IMDS GATT operation not supported\n");
167 return BLE_ATT_ERR_UNLIKELY;
168 }
169 }
170}
171
172int CProtoNimblePrphImds::allocIMDS()
173{
174 // Configure service
175
176 svc.type = BLE_GATT_SVC_TYPE_PRIMARY;
177 svc.uuid = reinterpret_cast<const ble_uuid_t *>(IProtoNimblePrphCb::UUID_IMDS);
178 svc.includes = nullptr;
179
180 // One more characteristic for end of array
181
182 noChar = cb->getObjectsLen() + 1;
183
184 // Allocate memory for characterisitcs
185
186 svc.characteristics = new (std::nothrow) ble_gatt_chr_def[noChar]();
187 if (svc.characteristics == nullptr)
188 {
189 DAWNERR("Failed to allocate characteristics\n");
190 return -ENOMEM;
191 }
192
193 return OK;
194}
195
196int CProtoNimblePrphImds::createIMDS()
197{
198 const uint32_t *char_uuid = nullptr;
199 int j = 0;
200 int ret;
201
202 if (svc.characteristics == nullptr)
203 {
204 DAWNERR("Failed to allocate characteristics\n");
205 return -ENOMEM;
206 }
207
208 // Configure service
209
210 svc.type = BLE_GATT_SVC_TYPE_PRIMARY;
211 svc.uuid = reinterpret_cast<const ble_uuid_t *>(IProtoNimblePrphCb::UUID_IMDS);
212 svc.includes = nullptr;
213
214 for (auto const objid : vio)
215 {
216 CIOCommon *io = cb->getObject(objid);
217
218 // NOTE: NimBLE C API declares svc.characteristics as
219 // const ble_gatt_chr_def*, but Dawn allocates it dynamically
220 // and needs write access to populate fields at runtime.
221
222 struct ble_gatt_chr_def *chr = const_cast<struct ble_gatt_chr_def *>(&svc.characteristics[j]);
223
224 if (io == nullptr)
225 {
226 DAWNERR("IMDS IO not found\n");
227 return -EIO;
228 }
229
230 // Seekable IOs not supported by IMDS
231
232 if (io->isSeekable())
233 {
234 DAWNERR("seekable IO not supported by IMDS"
235 " (objid=0x%08" PRIx32 ")\n",
236 io->getIdV());
237 return -ENOTSUP;
238 }
239
240 SPrphNotiferCb *ncb = nimbleGattNotifierCreate(io);
241 if (ncb == nullptr)
242 {
243 DAWNERR("IMDS notifier allocation failed\n");
244 return -ENOMEM;
245 }
246
247 // Fill characteristic, access_cb is fill later
248
249 nimbleGattChrInit(*chr, ncb);
250 char_uuid = nullptr;
251
252 // Check characteristic properties
253
254#ifdef CONFIG_DAWN_IO_NOTIFY
255 ret = nimbleGattChrNotifySet(*chr, *io, *ncb, notifierCb, objid);
256 if (ret < 0)
257 {
258 DAWNERR("IMDS notifier setup failed\n");
259 }
260#endif
261
262 nimbleGattChrAccessSet(*chr, *io);
263
264 // Get IO class
265
266 switch (ioTypeMap[objid])
267 {
269 {
270 chr->access_cb = CProtoNimblePrphImds::callback<int16_t>;
272 break;
273 }
274
276 {
277 chr->access_cb = CProtoNimblePrphImds::callback<int16_t>;
279 break;
280 }
281
283 {
284 chr->access_cb = CProtoNimblePrphImds::callback<uint32_t>;
286 break;
287 }
288
290 {
291 chr->access_cb = CProtoNimblePrphImds::callback<uint8_t>;
293 break;
294 }
295
297 {
298 // Non-standard UUID: 0x272A is not assigned by the BT SIG.
299
300 chr->access_cb = CProtoNimblePrphImds::callback<int16_t>;
302 break;
303 }
304
306 {
307 // Illuminance is a 24-bit unsigned (BT SIG 0x2AFB)
308
309 chr->access_cb = CProtoNimblePrphImds::callback<uint32_t, 3>;
311 break;
312 }
313
314 default:
315 {
316 DAWNERR("unknown IMDS type %d\n", ioTypeMap[objid]);
317 nimbleGattChrNotifierFree(*chr);
318 return -EINVAL;
319 }
320 }
321
322 // Assignsome dummy value
323
324 if (char_uuid == nullptr)
325 {
326 DAWNERR("No UUID for IMDS IO\n");
327 nimbleGattChrNotifierFree(*chr);
328 return -EINVAL;
329 }
330
331 ret = nimbleGattChrUuid128Set(*chr, char_uuid);
332 if (ret != OK)
333 {
334 DAWNERR("IMDS UUID allocation failed\n");
335 nimbleGattChrNotifierFree(*chr);
336 return ret;
337 }
338
339 ret = configureDescriptors(chr, objid);
340 if (ret != OK)
341 {
342 nimbleGattChrUuidFree(*chr);
343 nimbleGattChrNotifierFree(*chr);
344 return ret;
345 }
346
347 // Next service
348
349 j++;
350 }
351
352 return OK;
353}
354
355void CProtoNimblePrphImds::deleteIMDS()
356{
357 // NOTE: svc.characteristics fields are const-qualified per NimBLE API.
358 // Dawn allocated this memory and is the rightful owner, so the cast is safe.
359
360 if (svc.characteristics)
361 {
362 for (size_t i = 0; i < noChar - 1; i++)
363 {
364 if (svc.characteristics[i].uuid)
365 {
366 nimbleGattChrUuidFree(const_cast<struct ble_gatt_chr_def &>(svc.characteristics[i]));
367 }
368
369#ifdef CONFIG_DAWN_PROTO_NIMBLE_EXTENDED_METADATA
370 if (svc.characteristics[i].descriptors)
371 {
372 struct ble_gatt_dsc_def *dsc =
373 const_cast<struct ble_gatt_dsc_def *>(svc.characteristics[i].descriptors);
374
375 for (size_t j = 0; dsc[j].uuid != nullptr; j++)
376 {
377 SImdsDscCb *dcb = static_cast<SImdsDscCb *>(dsc[j].arg);
378
379 imdsFreeUuid(dsc[j].uuid);
380 delete dcb;
381 }
382
383 delete[] dsc;
384 }
385#endif
386
387 nimbleGattChrNotifierFree(const_cast<struct ble_gatt_chr_def &>(svc.characteristics[i]));
388 }
389
390 delete[] const_cast<ble_gatt_chr_def *>(svc.characteristics);
391
392 svc.characteristics = nullptr;
393 }
394 created = false;
395}
396
397int CProtoNimblePrphImds::configureDescriptors(struct ble_gatt_chr_def *chr,
399{
400#ifndef CONFIG_DAWN_PROTO_NIMBLE_EXTENDED_METADATA
401 UNUSED(chr);
402 UNUSED(objid);
403 return OK;
404#else
405 const auto metaIt = ioMetaMap.find(objid);
406 SImdsMeta *meta;
407 struct ble_gatt_dsc_def *dsc;
408 size_t count = 0;
409 size_t idx = 0;
410
411 if (metaIt == ioMetaMap.end())
412 {
413 return OK;
414 }
415
416 meta = const_cast<SImdsMeta *>(&metaIt->second);
417
418# ifdef CONFIG_DAWN_PROTO_NIMBLE_IMDS_DESC_USER_DESCRIPTION
419 if (meta->desc & IMDS_DESC_USER_DESCRIPTION)
420 {
421 count++;
422 }
423# endif
424
425 if (count == 0)
426 {
427 return OK;
428 }
429
430 dsc = new (std::nothrow) ble_gatt_dsc_def[count + 1]();
431 if (dsc == nullptr)
432 {
433 return -ENOMEM;
434 }
435
436# ifdef CONFIG_DAWN_PROTO_NIMBLE_IMDS_DESC_USER_DESCRIPTION
437 if (meta->desc & IMDS_DESC_USER_DESCRIPTION)
438 {
439 SImdsDscCb *dcb = new (std::nothrow) SImdsDscCb{};
440 ble_uuid_any_t *uuid = imdsMakeUuid16(UUID16_CHR_USER_DESCRIPTION);
441 if (dcb == nullptr || uuid == nullptr)
442 {
443 delete dcb;
444 imdsFreeUuid(uuid ? &uuid->u : nullptr);
445 delete[] dsc;
446 return -ENOMEM;
447 }
448
449 dcb->kind = IMDS_DESC_USER_DESCRIPTION;
450 dcb->len = 0;
451 while (dcb->len < IMDS_USER_DESCRIPTION_MAX && meta->userDescription[dcb->len] != '\0')
452 {
453 dcb->data[dcb->len] = static_cast<uint8_t>(meta->userDescription[dcb->len]);
454 dcb->len++;
455 }
456
457 dsc[idx].uuid = &uuid->u;
458 dsc[idx].att_flags = BLE_ATT_F_READ;
459 dsc[idx].access_cb = descriptorCb;
460 dsc[idx].arg = dcb;
461 idx++;
462 }
463# endif
464
465 chr->descriptors = dsc;
466 return OK;
467#endif
468}
469
470void CProtoNimblePrphImds::allocObject(const SProtoNimblePrphIOBindImdsObjid &obj,
471 const uint32_t *ext)
472{
473 uint8_t type = cfgIdIOBindImdsCfgObjTypeGet(obj.cfg);
474#ifdef CONFIG_DAWN_PROTO_NIMBLE_EXTENDED_METADATA
475 SImdsMeta meta{};
476#endif
477
478 DAWNINFO("IMDS allocate type=%d object 0x%" PRIx32 "\n", type, obj.objid.v);
479
480 // Store type for later
481
482 ioTypeMap.insert_or_assign(obj.objid.v, type);
483
484#ifdef CONFIG_DAWN_PROTO_NIMBLE_EXTENDED_METADATA
485 for (size_t i = 0; i < obj.extCount; i++)
486 {
487 uint8_t kind = cfgIdIOBindImdsExtKindGet(ext[0]);
488 uint8_t size = cfgIdIOBindImdsExtSizeGet(ext[0]);
489 const uint32_t *data = &ext[1];
490
491 switch (kind)
492 {
494 {
495 size_t bytes = static_cast<size_t>(size) * sizeof(uint32_t);
496 bytes = bytes < IMDS_USER_DESCRIPTION_MAX ? bytes : IMDS_USER_DESCRIPTION_MAX;
497
498# ifdef CONFIG_DAWN_PROTO_NIMBLE_IMDS_DESC_USER_DESCRIPTION
499 meta.desc |= IMDS_DESC_USER_DESCRIPTION;
500 std::memcpy(meta.userDescription, data, bytes);
501# endif
502 break;
503 }
504
505 default:
506 {
507 DAWNERR("unknown IMDS extension kind %u\n", kind);
508 break;
509 }
510 }
511
512 ext += 1 + size;
513 }
514
515 if (meta.desc != 0)
516 {
517 ioMetaMap.insert_or_assign(obj.objid.v, meta);
518 }
519#endif
520
521 // Allocate object in map
522
523 cb->regObject(obj.objid.v);
524
525 vio.push_back(obj.objid.v);
526}
527
528// Assumption: Configuration item passed by caller is a valid type supported by
529// this class.
530void CProtoNimblePrphImds::configureDesc(const SObjectCfg::SObjectCfgItem *item)
531{
532 // Iterrate over objectid list
533
534 for (size_t k = 0; k < item->cfgid.s.size;)
535 {
536 const SProtoNimblePrphIOBindImds *tmp =
537 reinterpret_cast<const SProtoNimblePrphIOBindImds *>(&item->data[k]);
538
539 uint8_t size = cfgIdIOBindImdsCfg0SizeGet(tmp->cfg0);
540
541 size_t pos = k + sizeof(SProtoNimblePrphIOBindImds) / sizeof(uint32_t);
542
543 for (size_t i = 0; i < size; i++)
544 {
545 const SProtoNimblePrphIOBindImdsObjid *obj =
546 reinterpret_cast<const SProtoNimblePrphIOBindImdsObjid *>(&item->data[pos]);
547 const uint32_t *ext =
548 &item->data[pos + sizeof(SProtoNimblePrphIOBindImdsObjid) / sizeof(uint32_t)];
549
550 // Allocate object
551
552 allocObject(*obj, ext);
553
554 pos += sizeof(SProtoNimblePrphIOBindImdsObjid) / sizeof(uint32_t);
555 for (size_t j = 0; j < obj->extCount; j++)
556 {
557 pos += 1 + cfgIdIOBindImdsExtSizeGet(item->data[pos]);
558 }
559 }
560
561 k = pos;
562 }
563}
564
565CProtoNimblePrphImds::CProtoNimblePrphImds(const SObjectCfg::SObjectCfgItem *desc_,
567 : IProtoNimblePrphService(desc_, cb_)
568 , created(false)
569{
570 // Service ID not assigned yet
571
572 id = -1;
573 noChar = 0;
574}
575
576CProtoNimblePrphImds::~CProtoNimblePrphImds()
577{
578 deinit();
579}
580
582{
583 int ret;
584
585 // Configure object
586
587 configureDesc(desc);
588
589 // Allocate service
590
591 ret = allocIMDS();
592 if (ret != OK)
593 {
594 return ret;
595 }
596
597 // Register servie in peripheral handler
598
599 id = cb->serviceRegister(&svc);
600 if (id < 0)
601 {
602 DAWNERR("IMDS service registration failed\n");
603 return -EIO;
604 }
605
606 return OK;
607}
608
610{
611 deleteIMDS();
612 return OK;
613}
614
616{
617 int ret;
618
619 if (created)
620 {
621 return cb->startService(id);
622 }
623
624 // Create service
625
626 ret = createIMDS();
627 if (ret != OK)
628 {
629 deleteIMDS();
630 allocIMDS();
631 return ret;
632 }
633
634 created = true;
635
636 // Signal start to peripheral handler
637
638 return cb->startService(id);
639}
640
642{
643 // Signal stop to peripheral handler
644
645 return cb->stopService(id);
646}
Base class for all I/O objects.
Definition common.hxx:27
int getData(IODataCmn &data, size_t len, size_t offset=0)
Get data from I/O (public interface with stats tracking).
Definition common.hxx:353
virtual bool isNotify() const =0
Check if IO supports notifications.
virtual bool isSeekable() const
Check if IO supports partial (seekable) access.
Definition common.hxx:502
SObjectId::ObjectId getIdV() const
Get object identifier as raw 32-bit value.
Definition object.cxx:155
uint8_t getDtype() const
Get data type field.
Definition object.cxx:175
int deinit()
Deinitialize service.
int init()
Initialize service.
int stop()
Stop service.
int start()
Start service.
@ IMDS_EXT_USER_DESCRIPTION
Characteristic User Description descriptor.
Definition prph_imds.hxx:45
@ PRPH_IMDS_TYPE_LIGHT
Industrial illuminance / light sensor.
Definition prph_imds.hxx:56
@ PRPH_IMDS_TYPE_GAS
Industrial gas resistance sensor (non-standard UUID).
Definition prph_imds.hxx:54
@ PRPH_IMDS_TYPE_TEMP
Industrial temperature sensor.
Definition prph_imds.hxx:50
@ PRPH_IMDS_TYPE_UVIDX
Industrial UV index sensor.
Definition prph_imds.hxx:53
@ PRPH_IMDS_TYPE_HUM
Industrial humidity sensor.
Definition prph_imds.hxx:51
@ PRPH_IMDS_TYPE_PRESS
Industrial pressure sensor.
Definition prph_imds.hxx:52
Interface for BLE peripheral services with GATT characteristics.
Definition iprph.hxx:24
virtual int startService(int id)=0
Start a specific service.
static uint32_t UUID_UVIDX[4]
UV Index Characteristic UUID (0x2A76).
Definition iprph.hxx:197
virtual size_t getObjectsLen()=0
Get count of registered I/O objects.
static float charScaleGet(uint16_t u)
Get scaling factor for a characteristic UUID.
Definition iprph.hxx:383
virtual CIOCommon * getObject(SObjectId::ObjectId id)=0
Get protocol object by ID.
static uint32_t UUID_PRESS[4]
Pressure Characteristic UUID (0x2A6D).
Definition iprph.hxx:186
static uint32_t UUID_TEMP[4]
Temperature Characteristic UUID (0x2A6E).
Definition iprph.hxx:149
static uint32_t UUID_ILLUMINANCE[4]
Illuminance Characteristic UUID (0x2AFB).
Definition iprph.hxx:328
static uint32_t UUID_RESISTANCE[4]
Electric Resistance Characteristic UUID (0x272A).
Definition iprph.hxx:342
virtual int serviceRegister(struct ble_gatt_svc_def *svc)=0
Register a GATT service with the peripheral.
virtual void regObject(SObjectId::ObjectId id)=0
Register an I/O object for this service.
static uint32_t UUID_IMDS[4]
Industrial Measurement Device Service UUID (0x185A).
Definition iprph.hxx:89
static uint32_t UUID_HUM[4]
Humidity Characteristic UUID (0x2A6F).
Definition iprph.hxx:174
virtual int stopService(int id)=0
Stop a specific service.
Base interface for GATT services exposed by BLE peripheral.
Definition iprph.hxx:467
std::vector< SObjectId::ObjectId > vio
Vector of I/O objects exposed by this service.
Definition iprph.hxx:557
const SObjectCfg::SObjectCfgItem * desc
Configuration descriptor for this service.
Definition iprph.hxx:574
IProtoNimblePrphCb * cb
Callback interface to peripheral.
Definition iprph.hxx:566
Out-of-tree user-extension hooks for Dawn.
Definition bindable.hxx:13
Single configuration item within object.
ObjectCfgData_t data[]
Configuration data array (flexible, size from cfgid.s.size).
UObjectCfgId cfgid
Configuration ID header (type, class, id, size, rw, dtype).
@ DTYPE_FLOAT
IEEE 754 single-precision floating point (32-bit).
Definition objectid.hxx:112
uint32_t ObjectId
ObjectID type - single 32-bit value.
Definition objectid.hxx:44
Heap-allocated dynamic I/O data buffer.
Definition ddata.hxx:21
void * getDataPtr(size_t batch=0)
Get pointer to data only (skips timestamp if present).
Definition ddata.hxx:180
uint32_t size
Configuration data size in 32-bit words (bits 5-14, max 1023).
struct dawn::SObjectCfg::UObjectCfgId::@10 s
Bit-field structure for named member access.