Skip to content

Commit 9ce8f45

Browse files
authored
minor cleanup of the BLE module
1 parent 1fe9fca commit 9ce8f45

File tree

1 file changed

+66
-26
lines changed

1 file changed

+66
-26
lines changed

docs/Bluetooth_MI32.md

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ The naming conventions in the product range of bluetooth sensors in XIAOMI-unive
142142
</tr>
143143
</table>
144144
passive: data is received via BLE advertisements
145-
active: data is received via bidrectional connection to the sensor
145+
active: data is received via bidirectional connection to the sensor
146146

147147
#### Devices with payload encryption
148148

@@ -324,8 +324,23 @@ We have the following methods, which are chosen to be able to replace the old co
324324
For generic BLE access we import the module:
325325
`import BLE`
326326

327-
To simplify BLE access this works in the form of state machine, where you have to set some properties of a context and then finally launch an operation. Besides we have three callback mechanisms for listening to advertisements, active sensor connections with Tasmota as a client and providing a server including advertising. All need a byte buffer in Berry for data exchange and a Berry function as the callback.
327+
BLE Function|Parameters and details
328+
:---|:---
329+
adv_cb|`(callback function:function, buffer:bytes)`<br>Will start listening to advertisements or stop it by providing `nil` as function.<br>The callback function will have arguments `service data` and `manufacturer data` as integer values, that are indices pointing to these kinds of data in the buffer or have a value of 0 if there is no such data in the advertisement.
330+
adv_watch|`(MAC:bytes[, type:int])`<br>Watch BLE address exclusively, is added to a list (MAC is a 6-byte-buffer, type is optional 0-3, default is 0).
331+
adv_block|`(MAC:bytes[, type:int])`<br>Block BLE address, is added to a list (MAC is a 6-byte-buffer, type is optional 0-3, default is 0).
332+
conn_cb|`(callback function:function, buffer:bytes)`<br>Will init Tasmota as a peripheral device, that can connect to a central device.<br>The callback function will have arguments `error`,`op code`,`16-bit uuid` and `handle`. If an UUID with more than 16 bit is accessed, the automatic conversion to 16-bit will probably give no usable result, thus the handle should be used in these cases.
333+
serv_cb|`(callback function:function, buffer:bytes)`<br>Will init Tasmota as a central device (aka server) or stop it by providing `nil` as function.<br>The callback function will have arguments `error`,`op code`,`16-bit uuid` and `handle`. If an UUID with more than 16 bit is accessed, the automatic conversion to 16-bit will probably give no usable result, thus the handle should be used in these cases.
334+
set_MAC|`(MAC:bytes[, type:int]) -> handled:bool`<br>Set MAC for for use as peripheral or central device as a 6-byte-buffer, type is optional 0-3, default is 0.
335+
set_svc|`(UUID:string[, discoverAttributes:bool]) -> handled:bool`<br>Set service UUID for for use as peripheral or central device as a 16-Bit or 128-Bit service uuid, the latter must include the dashes. Optional: Let the BLE stack discover all attributes of the service, which takes time and battery. Default is `false`.
336+
set_chr|`(UUID:string) -> handled:bool`<br>Set characteristic UUID for for use as peripheral or central device as a 16-Bit or 128-Bit service uuid, the latter must include the dashes.
337+
run|`(operation:int[, response:bool])`<br>Start a Bluetooth operation, where `operation` is a proprietary code - see sections below. `Response` is optional and defaults to `false`.
338+
loop|`()`<br>Triggers a synchronization between Bluetooth stack and Berry, thus firing callbacks, if there is new data. Will typically be called from Berrys [Fast Loop](Berry.md#fast-loop).
339+
340+
341+
To simplify BLE access this works in the form of state machine, where you have to set some properties of a context and then finally launch an operation. Besides we have three callback mechanisms for listening to advertisements, active sensor connections with Tasmota as a client and providing a server including advertising. All you need is a byte buffer in Berry for data exchange and a Berry function as the callback.
328342
The byte buffer is always organized in the format `length-data bytes`, where the first byte represents the length of the following data bytes, which results in a maximum of 255 data bytes.
343+
Because Bluetooth is inherently very asynchronous, almost every status, result or error condition is reported via callbacks.
329344

330345
#### Observer (aka Advertisement listener)
331346
To listen to advertisements inside a class (that could be a driver) we could initialize like that:
@@ -380,13 +395,10 @@ The payload is always provided completely, so every possibles AD type can be par
380395

381396
The payload can be parsed according to the BLE GAP standard. It consists of AD elements of variable size in the format length-type-data, where the length byte describes the length of the two following components in bytes, the type byte is defined in the GAP and the data parts of 'length-1' bytes is interpreted according to the type.
382397

383-
Two methods for filtering of advertisements are provided:
384-
`BLE.adv_watch(MAC,type)`: watch BLE address exclusively, is added to a list (MAC is a 6-byte-buffer, type is optional 0-3, default is 0)
385-
`BLE.adv_block(MAC,type)`: block BLE address, is added to a list(MAC is a 6-byte-buffer, type is optional 0-3, default is 0)
386-
398+
387399
!!! tip
388400

389-
The watchlist is more effective to avoid missing packets, than the blocklist in environments with high BLE traffic. Both methods work for the internal Xiaomi driver and the post processing with Berry.
401+
The watchlist is more effective to avoid missing packets than the blocklist in environments with high BLE traffic. Both methods work for the internal Xiaomi driver and the post processing with Berry, because they set properties of the underlying Bluetooth framework.
390402

391403
#### Peripheral role (aka client)
392404

@@ -739,13 +751,13 @@ Here is an implementation of the "old" MI32 commands:
739751

740752
```berry
741753
# Simple Berry driver for the BPR2S Air mouse (a cheap BLE HID controller)
742-
# TODO: handle mouse mode
743754

744755
import BLE
745756

746757
class BLE_BPR2S : Driver
747758
var buf
748-
var connecting, connected
759+
var connecting, connected, new_position
760+
var x,y
749761

750762
def init(MAC,addr_type)
751763
var cbp = tasmota.gen_cb(/e,o,u,h->self.cb(e,o,u,h))
@@ -754,12 +766,15 @@ Here is an implementation of the "old" MI32 commands:
754766
BLE.set_MAC(bytes(MAC),addr_type)
755767
print("BLE: will try to connect to BPR2S with MAC:",MAC)
756768
self.connect()
757-
tasmota.add_fast_loop(/-> BLE.loop())
769+
tasmota.add_fast_loop(/-> BLE.loop()) # needed for mouse position
758770
end
759771

760772
def connect()
761-
self.connecting = true;
762-
self.connected = false;
773+
self.connecting = true
774+
self.connected = false
775+
self.new_position = false
776+
self.x = 128
777+
self.y = 128
763778
BLE.set_svc("1812")
764779
BLE.set_chr("2a4a") # the first characteristic we have to read
765780
BLE.run(1) # read
@@ -772,22 +787,53 @@ Here is an implementation of the "old" MI32 commands:
772787
end
773788
end
774789

790+
def every_50ms()
791+
import mqtt
792+
if self.new_position == true
793+
mqtt.publish("tele/BPR2S",format('{"mouse":{"x":%s,"y":%s}}',self.x,self.y))
794+
self.new_position = false
795+
end
796+
end
797+
775798
def handle_read_CB(uuid) # uuid is the callback characteristic
776799
self.connected = true;
777800
# we just have to read these characteristics before we can finally subscribe
778801
if uuid == 0x2a4a # did receive HID info
802+
print("BLE: now connecting to BPR2S")
779803
BLE.set_chr("2a4b")
780804
BLE.run(1) # read next characteristic
781805
elif uuid == 0x2a4b # did receive HID report map
782806
BLE.set_chr("2a4d")
783807
BLE.run(1) # read to trigger notifications of the HID device
784808
elif uuid == 0x2a4d # did receive HID report
785-
print(self.buf[1..self.buf[0]])
786809
BLE.set_chr("2a4d")
787810
BLE.run(3) # subscribe
788811
end
789812
end
790813

814+
def handle_mouse_pos()
815+
var x = self.buf.getbits(12,12)
816+
if x > 2048
817+
x -= 4096
818+
end
819+
var y = self.buf.getbits(24,12)
820+
if y > 2048
821+
y -= 4096
822+
end
823+
824+
self.x += (x >> 7) # some conversion factor
825+
self.y += (y >> 7)
826+
827+
# could be mapped to hue, saturation, brightness, ...
828+
if self.x > 255 self.x = 255
829+
elif self.x < 0 self.x = 0
830+
end
831+
if self.y > 255 self.y = 255
832+
elif self.y < 0 self.y = 0
833+
end
834+
self.new_position = true
835+
end
836+
791837
def handle_HID_notification(h)
792838
import mqtt
793839
var t = "key"
@@ -827,16 +873,8 @@ Here is an implementation of the "old" MI32 commands:
827873
v = "plus"
828874
end
829875
elif h == 34
830-
t = "mouse"
831-
var x = self.buf.getbits(12,12)
832-
if x > 2048
833-
x -= 4096
834-
end
835-
var y = self.buf.getbits(24,12)
836-
if y > 2048
837-
y -= 4096
838-
end
839-
v = format('{"x":%i,"y":%i}',x,y)
876+
self.handle_mouse_pos()
877+
return
840878
end
841879
if v != ''
842880
mqtt.publish("tele/BPR2S",format('{"%s":"%s"}',t,v))
@@ -851,13 +889,15 @@ Here is an implementation of the "old" MI32 commands:
851889
# print(op,uuid)
852890
self.handle_read_CB(uuid)
853891
elif op == 3
854-
self.connecting = false;
892+
self.connecting = false
893+
self.connected = true
855894
print("BLE: init completed for BPR2S")
856895
elif op == 5
857-
self.connected = false;
858-
self.connecting = false;
896+
self.connected = false
897+
self.connecting = false
859898
print("BLE: did disconnect BPR2S ... will try to reconnect")
860899
elif op == 103 # notification OP
900+
if self.connected == false return end
861901
self.handle_HID_notification(handle)
862902
end
863903
else

0 commit comments

Comments
 (0)