- void when_data_written(const GattWriteCallbackParams *e)
- {
- printf("data written:\r\n");
- printf("\tconnection handle: %u\r\n", e->connHandle);
- printf("\tattribute handle: %u", e->handle);
- if (e->handle == _my_char.getValueHandle())
- {
- printf(" (second characteristic)\r\n");
- }
- else
- {
- printf("\r\n");
- }
- printf("\twrite operation: %u\r\n", e->writeOp);
- printf("\toffset: %u\r\n", e->offset);
- printf("\tlength: %u\r\n", e->len);
- printf("\t data: ");
- for (size_t i = 0; i < e->len; ++i)
- {
- printf("%02X", e->data[i]);
- if(e->data[0]=='1')
- {
- led2.write(1);
- printf("\t led on");
- }
- if(e->data[0]=='2')
- {
- led2.write(0);
- printf("\t led off");
- }
- }
- printf("\r\n");
- }
里面可以添加用户业务逻辑,本设计这里接收到字符1就点亮led2,收到字符2就熄灭led2。通过定时调用将数据以通知的形式发送到蓝牙客户端:
- void update_data(void)
- {
- uint8_t second[11] = {0};
- ble_error_t err;
- sprintf((char*)second, "%02x%04x%04x", bat,CO2,TVOC);
- err = _my_char.set(*_server, second,10);
- if (err)
- {
- printf("write of the second value returned error %u\r\n", err);
- return;
- }
- }
调用特征值_my_char对象set方法将数据发送出去。
传感器数据采集线程里面首先初始化传感器,然后周期性的读取传感器数据保存到全局变量里面。
- void sgp30_work_thread()
- {
- int ret;
- SGP30 *sgp30 = new SGP30(I2C_SCL, I2C_SDA);
- DigitalOut led1(LED1);
- uint8_t ID[6] = {0};
- while (sgp30->sgp30_init() < 0)
- {
- printf(" sgp30 init fail\r\n");
- wait_ms(1000);
- }
- if (sgp30->sgp30_get_serial_id(ID) < 0)
- {
- printf(" sgp30 read serial id failed\r\n");
- }
- else
- {
- printf("SGP30 Serial number: ");
- for (int i = 0; i < 6; i++)
- printf("%02X", ID[i]);
- printf("\r\n");
- }
- printf("sgp30 wait air for init");
- fflush(stdout);
- do
- {
- ret = sgp30->sgp30_read(&CO2, &TVOC);
- if (ret < 0)
- {
- printf("SGP30 read failed,ret=%d\r\n", ret);
- }
- else
- {
- printf("-");
- fflush(stdout);
- }
- } while (TVOC == 0 && CO2 == 400);
- printf("\r\n");
- while (true)
- {
- ret = sgp30->sgp30_read(&CO2, &TVOC);
- if (ret < 0)
- {
- printf(" sgp30 read fail,ret=%d\r\n", ret);
- }
- else
- {
- printf("CO2:%5dppm TVOC:%5dppb\r\n", CO2, TVOC);
- }
- led1 = !led1;
- ThisThread::sleep_for(1000);
- }
- }
main.cpp完整代码如下:
- #include <cstdint>
- #include <cstring>
- #include <stdio.h>
- #include "events/EventQueue.h"
- #include "platform/Callback.h"
- #include "platform/NonCopyable.h"
- #include "BLEProcess.h"
- #include "ble/BLE.h"
- #include "ble/Gap.h"
- #include "ble/GapAdvertisingData.h"
- #include "ble/GapAdvertisingParams.h"
- #include "ble/GattClient.h"
- #include "ble/GattServer.h"
- #include "mbed.h"
- #include "SGP30.h"
- using mbed::callback;
- uint8_t bat = 0;
- uint16_t CO2 = 0;
- uint16_t TVOC = 0;
- DigitalOut led2(LED2);
- class MyService {
- typedef MyService Self;
- public:
- MyService()
- : _my_service("51311102-030e-485f-b122-f8f381aa84ed",_my_characteristics,sizeof(_my_characteristics) / sizeof(_my_characteristics[0])),
- _my_char("8dd6a1b7-bc75-4741-8a26-264af75807de"),
- _server(NULL), _event_queue(NULL) {
- // update internal pointers (value, descriptors and characteristics array)
- _my_characteristics[0] = &_my_char;
- // setup authorization handlers
- //_my_char.setWriteAuthorizationCallback(this,&Self::authorize_client_write);
- }
- void start(BLE &ble_interface, events::EventQueue &event_queue)
- {
- if (_event_queue)
- {
- return;
- }
- _server = &ble_interface.gattServer();
- _event_queue = &event_queue;
- // register the service
- printf("Adding demo service\r\n");
- ble_error_t err = _server->addService(_my_service);
- if (err)
- {
- printf("Error %u during demo service registration.\r\n", err);
- return;
- }
- // read write handler
- _server->onDataSent(as_cb(&Self::when_data_sent));
- _server->onDataWritten(as_cb(&Self::when_data_written));
- _server->onDataRead(as_cb(&Self::when_data_read));
- // updates subscribtion handlers
- _server->onUpdatesEnabled(as_cb(&Self::when_update_enabled));
- _server->onUpdatesDisabled(as_cb(&Self::when_update_disabled));
- _server->onConfirmationReceived(as_cb(&Self::when_confirmation_received));
- // print the handles
- printf("my service registered\r\n");
- printf("service handle: %u\r\n", _my_service.getHandle());
- printf("\tmy characteristic value handle %u\r\n",_my_char.getValueHandle());
- _event_queue->call_every(3000,callback(this, &Self::update_data));
- }
- private:
- /**
- * Handler called when a notification or an indication has been sent.
- */
- void when_data_sent(unsigned count)
- {
- //printf("sent %u updates\r\n", count);
- }
- /**
- * Handler called after an attribute has been written.
- */
- void when_data_written(const GattWriteCallbackParams *e)
- {
- printf("data written:\r\n");
- printf("\tconnection handle: %u\r\n", e->connHandle);
- printf("\tattribute handle: %u", e->handle);
- if (e->handle == _my_char.getValueHandle())
- {
- printf(" (second characteristic)\r\n");
- }
- else
- {
- printf("\r\n");
- }
- printf("\twrite operation: %u\r\n", e->writeOp);
- printf("\toffset: %u\r\n", e->offset);
- printf("\tlength: %u\r\n", e->len);
- printf("\t data: ");
- for (size_t i = 0; i < e->len; ++i)
- {
- printf("%02X", e->data[i]);
- if(e->data[0]=='1')
- {
- led2.write(1);
- printf("\t led on");
- }
- if(e->data[0]=='2')
- {
- led2.write(0);
- printf("\t led off");
- }
- }
- printf("\r\n");
- }
- /**
- * Handler called after an attribute has been read.
- */
- void when_data_read(const GattReadCallbackParams *e)
- {
- printf("data read:\r\n");
- printf("\tconnection handle: %u\r\n", e->connHandle);
- printf("\tattribute handle: %u", e->handle);
- if (e->handle == _my_char.getValueHandle())
- {
- printf(" (my characteristic)\r\n");
- }
- else
- {
- printf("\r\n");
- }
- }
- /**
- * Handler called after a client has subscribed to notification or indication.
- *
- * @param handle Handle of the characteristic value affected by the change.
- */
- void when_update_enabled(GattAttribute::Handle_t handle)
- {
- printf("update enabled on handle %d\r\n", handle);
- }
- /**
- * Handler called after a client has cancelled his subscription from notification or indication.
- *
- * @param handle Handle of the characteristic value affected by the change.
- */
- void when_update_disabled(GattAttribute::Handle_t handle)
- {
- printf("update disabled on handle %d\r\n", handle);
- }
- /**
- * Handler called when an indication confirmation has been received.
- *
- * @param handle Handle of the characteristic value that has emitted the indication.
- */
- void when_confirmation_received(GattAttribute::Handle_t handle)
- {
- printf("confirmation received on handle %d\r\n", handle);
- }
- /**
- * Handler called when a write request is received.
- *
- * This handler verify that the value submitted by the client is valid before authorizing the operation.
- */
- void authorize_client_write(GattWriteAuthCallbackParams *e)
- {
- printf("characteristic %u write authorization\r\n", e->handle);
- if (e->offset != 0)
- {
- printf("Error invalid offset\r\n");
- e->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
- return;
- }
- if (e->len != 1)
- {
- printf("Error invalid len\r\n");
- e->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
- return;
- }
- /*if ((e->data[0] >= 60) ||
- ((e->data[0] >= 24) && (e->handle == _hour_char.getValueHandle()))) {
- printf("Error invalid data\r\n");
- e->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED;
- return;
- }*/
- e->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
- }
- /**
- * update the data to notify.
- */
- void update_data(void)
- {
- uint8_t second[11] = {0};
- ble_error_t err;
- /*err = _my_char.get(*_server, second);
- if (err)
- {
- printf("read of the second value returned error %u\r\n", err);
- return;
- }*/
- sprintf((char*)second, "%02x%04x%04x", bat,CO2,TVOC);
- err = _my_char.set(*_server, second,10);
- if (err)
- {
- printf("write of the second value returned error %u\r\n", err);
- return;
- }
- else
- {
- //printf("data:%s\r\n",second);
- }
- }
- private:
- /**
- * Helper that construct an event handler from a member function of this instance.
- */
- template <typename Arg>
- FunctionPointerWithContext<Arg> as_cb(void (Self::*member)(Arg))
- {
- return makeFunctionPointer(this, member);
- }
- /**
- * Read, Write, Notify, Indicate Characteristic declaration helper.
- *
- * @tparam T type of data held by the characteristic.
- */
- template <typename T>
- class ReadWriteNotifyIndicateCharacteristic : public GattCharacteristic {
- public:
- /**
- * Construct a characteristic that can be read or written and emit notification or indication.
- *
- * @param[in] uuid The UUID of the characteristic.
- * @param[in] initial_value Initial value contained by the characteristic.
- */
- ReadWriteNotifyIndicateCharacteristic(const UUID &uuid)
- : GattCharacteristic(
- /* UUID */ uuid,
- /* Initial value */ _value,
- /* Value size */ sizeof(_value),
- /* Value capacity */ sizeof(_value),
- /* Properties */
- GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ |
- GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE |
- GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY,
- /* Descriptors */ NULL,
- /* Num descriptors */ 0,
- /* variable len */ true){}
- /**
- * Get the value of this characteristic.
- *
- * @param[in] server GattServer instance that contain the characteristic
- * value.
- * @param[in] dst Variable that will receive the characteristic value.
- *
- * [url=home.php?mod=space&uid=266161]@return[/url] BLE_ERROR_NONE in case of success or an appropriate error code.
- */
- ble_error_t get(GattServer &server, T &dst) const
- {
- uint16_t value_length = sizeof(dst);
- return server.read(getValueHandle(), &dst, &value_length);
- }
- /**
- * Assign a new value to this characteristic.
- *
- * @param[in] server GattServer instance that will receive the new value.
- * @param[in] value The new value to set.
- * @param[in] local_only Flag that determine if the change should be kept
- * locally or forwarded to subscribed clients.
- */
- ble_error_t set(GattServer &server, const uint8_t value[],int len,bool local_only = false) const
- {
- return server.write(getValueHandle(), value, len, local_only);
- }
- private:
- uint8_t _value[10];
- };
- ReadWriteNotifyIndicateCharacteristic<uint8_t> _my_char;
- // list of the characteristics of the clock service
- GattCharacteristic *_my_characteristics[1];
- // demo service
- GattService _my_service;
- GattServer *_server;
- events::EventQueue *_event_queue;
- };
- Thread thread1;
- Thread thread2;
- Thread thread3;
- Thread thread4;
- void sgp30_work_thread()
- {
- int ret;
- SGP30 *sgp30 = new SGP30(I2C_SCL, I2C_SDA);
- DigitalOut led1(LED1);
- uint8_t ID[6] = {0};
- while (sgp30->sgp30_init() < 0)
- {
- printf(" sgp30 init fail\r\n");
- wait_ms(1000);
- }
- if (sgp30->sgp30_get_serial_id(ID) < 0)
- {
- printf(" sgp30 read serial id failed\r\n");
- }
- else
- {
- printf("SGP30 Serial number: ");
- for (int i = 0; i < 6; i++)
- printf("%02X", ID[i]);
- printf("\r\n");
- }
- printf("sgp30 wait air for init");
- fflush(stdout);
- do
- {
- ret = sgp30->sgp30_read(&CO2, &TVOC);
- if (ret < 0)
- {
- printf("SGP30 read failed,ret=%d\r\n", ret);
- }
- else
- {
- printf("-");
- fflush(stdout);
- }
- } while (TVOC == 0 && CO2 == 400);
- printf("\r\n");
- while (true)
- {
- ret = sgp30->sgp30_read(&CO2, &TVOC);
- if (ret < 0)
- {
- printf(" sgp30 read fail,ret=%d\r\n", ret);
- }
- else
- {
- printf("CO2:%5dppm TVOC:%5dppb\r\n", CO2, TVOC);
- }
- led1 = !led1;
- ThisThread::sleep_for(1000);
- }
- }
- void led_thread()
- {
- DigitalOut *led2 = new DigitalOut(LED2);
- //DigitalOut led3(LED3);
- while (true)
- {
- led2->write(led2->read() == 1 ? 0 : 1);
- // ThisThread::sleep_for(200);
- // led3 = !led3;
- wait_ms(200);
- }
- }
- void key_thread()
- {
- DigitalIn sw1(PC_4, PullUp);
- DigitalIn sw2(PD_0, PullUp);
- DigitalIn sw3(PD_1, PullUp);
- while (true)
- {
- if (sw1.read() == 0)
- {
- printf("sw1\r\n");
- }
- if (sw2.read() == 0)
- {
- printf("sw2\r\n");
- }
- if (sw3.read() == 0)
- {
- printf("sw3\r\n");
- }
- ThisThread::sleep_for(20);
- }
- }
- void get_bat_thread()
- {
- AnalogIn ain(A0);
- float bat_f;
- while (true)
- {
- bat_f = ain.read();
- bat=(uint8_t)(bat_f*100);
- //printf("ain: %f - %d\n", bat_f,bat);
- ThisThread::sleep_for(2000);
- //wait_ms(500);
- }
- }
- int main()
- {
- thread1.start(sgp30_work_thread);
- //thread2.start(led_thread);
- thread3.start(get_bat_thread);
- //thread4.start(key_thread);
- BLE &ble_interface = BLE::Instance();
- events::EventQueue event_queue;
- MyService demo_service;
- BLEProcess ble_process(event_queue, ble_interface);
- ble_process.on_init(callback(&demo_service, &MyService::start));
- // bind the event queue to the ble interface, initialize the interface and start advertising
- ble_process.start();
- // Process the event queue.
- event_queue.dispatch_forever();
- return 0;
- }
2.2.2微信小程序
微信小程序用于搜索蓝牙设备、建立连接,订阅通知,显示接收数据。连接蓝牙设备成功后将查询服务和该服务下的特征值列表,根据MCU中定义的特征值,订阅该特征值的通知属性,这样就可以接收到蓝牙设备发送的数据了。
本设计定义了2个页面,设置页面和设备信息显示页面,不同页面间通过event库实现通信:
- //接收别的页面传过来的数据
- event.on('EnvMonitorSendData2Device', this, function(data) {
- //另外一个页面传过来的data是16进制字符串形式
- console.log("要发送给蓝牙设备的数据:"+data);
- var buffer=that.stringToBytes(data);
- var dataView = new Uint8Array(buffer)
- dataView = data;
- wx.writeBLECharacteristicValue({
- deviceId: app.globalData._deviceId,//蓝牙设备 id
- serviceId: app.globalData._serviceId,//蓝牙特征值对应服务的 uuid
- characteristicId: app.globalData._writeCharacteristicId,//蓝牙特征值的 uuid
- value: buffer,//ArrayBuffer 蓝牙设备特征值对应的二进制值
- success: function (res) {//接口调用成功的回调函数
- console.log('发送成功')
- },
- fail: function(res) {//接口调用失败的回调函数
- //发送蓝牙数据失败
- console.log('发送失败')
- }
- }
- )
- })
接收到通知数据后,进行转换后送到页面显示:
- //接收别的页面传过来的数据
- event.on('environmetDataChanged', this, function(data) {
- //另外一个页面传过来的data是16进制字符串形式
- console.log("接收到蓝牙设备发来的数据:"+data)
- //bat 1byte
- var aa=parseInt(data[1]+data[3],16);
- //co2 4byte
- var f=parseInt(data[5]+data[7],16);
- var g=parseInt(data[9]+data[11],16);
- var bb=f*256+g;
- //tvoc 4byte
- var i=parseInt(data[13]+data[15],16);
- var j=parseInt(data[17]+data[19],16);
- var cc=i*256+j;
- //实时修改显示值
- var up0 = "charts[" + 0 + "].data";
- var up1 = "charts[" + 1 + "].data";
- var up2 = "charts[" + 2 + "].data";
- this.setData({
- [up0]:aa,
- [up1]:bb,
- [up2]:cc,
- });
- })
- },
三、调试与DIY展示
连接好线路,首先使用蓝牙调试助手进行设备连接、数据收发的简单测试,
点击接收通知按钮,就可以看到接收的通知数据了:
点击write按钮,发送字符1,可以看到收到了数据且led2点亮了:
四、总结
初次接触ST家的无线MCU产品,通过厂家的培训PPT资料能够快速上手STM32WB55的开发,丰富的开发生态给不懂蓝牙底层协议的开发者解决了很多难题。通过本次DIY活动,对ST家的无线MCU产品有了初步的认识,通过傻瓜化的开发生态能够快速实现创意,但是对于蓝牙等无线协议底层知识还是缺乏的,如果想把创意产品化还是需要多多学习蓝牙底层知识。
小程序工程源码:
stm32wb55rg工程源码: