Skip to content

How to add new type of transmitter

Johan Degraeve edited this page Aug 5, 2023 · 16 revisions

This page needs to be updated - bluetooth management has been changed since this page was first written

Summary

BluetoothTransmitter.swift defines the class BluetoothTransmitter, which implements the bluetooth protocol applicable to any type of peripheral and that works with only a receive and a transmit characteristic. The class handles the scanning, connect, services discovery, characteristics discover, subscribing to characteristic, connect and reconnect, connect after app launch (app needs to connect at least once, then it will remember the address and reconnect automatically at next launch) It will also cancel the connection when there's no more reference to an instance of the class (deinit is called)

If necessary, each of the functions in the protocols CBCentralManagerDelegate and CBPeripheralDelegate can be overriden by the inheriting class. This is necessary for instance of the transmitter uses more than just a receive and a transmit characteristic, like for example Dexcom

The protocol BluetoothTransmitterDelegate defines functions that allow to pass bluetooth activity information from the BluetoothTransmitter class to a specific transmitter class. Example when a disconnect occurs, the BlueToothTransmitter class handles the reconnect but the delegate class can for instance show the connection status to the user. It will be informed about the connection status via the function centralManagerDidConnect in the BluetoothTransmitterDelegate.

The CGMTransmitter protocol defines functions that CGM transmitter classes need to implement.

The CGM transmitter communicates back to the caller via the CGMTransmitterDelegate protocol.
Needs to be conformed to, for instance by a view controller, or manager, .. whatever
This protocol allows passing information like new readings, sensor detected, and also connect/disconnect, bluetooth status change

Following specific transmitter classes exist:

  • CGMMiaoMiaoTransmitter

  • CGMG4xDripTransmitter

  • CGMG5Transmitter

  • CGMG6Transmitter

  • CGMGNSEntryTransmitter

  • CGMBlueReaderTransmitter

  • CGMDroplet1Transmitter

  • CGMBubbleTransmitter

  • CGMBluconTransmitter

Steps to adding new (CGM) transmitter types

For every new type of bluetoothtransmitter you need to

  • extend BluetoothTransmitter
  • update BluetoothPeripheralType

If it's a CGM transmitter (it could also be a bloodglucose meter that transmits data over bluetooth)

  • conform to the protocol CGMTransmitter
  • update CGMTransmitter.swift and add a new type

If there's a new category (eg CGM is a category, M5Stack is a category)

  • update BluetoothPeripheralCategory and add a new category

update CGMTransmitter.swift

The file is xdrip/Transmitter/CGMBluetoothTransmitter/CGMTransmitter.swift

This file has an enum CGMTransmitterType with a case per type.

Add a new case for the new CGM transmittertype.

The enum is of type String, the type of transmitter needs to be in the string value. This value will be used on the UI, eg for selecting the type of transmitter.

You also need to update the function sensorType, canDetectNewSensor, allowManualSensorStart, defaultBatteryAlertLevel, batteryUnit

conform to protocol BluetoothTransmitterDelegate

The protocol is used to pass back information from the BluetoothTransmitter class to the specific Transmitter class.

conform to protocol CGMTransmitter

extend class BluetoothTransmitter

A new transmitter class needs to extend BluetoothTransmitter and

initialize the super class BluetoothTransmitter

the signature of the initializer of the super class BluetoothTransmitter is

init(addressAndName:BluetoothTransmitter.DeviceAddressAndName, CBUUID_Advertisement:String, CBUUID_Service:String, CBUUID_ReceiveCharacteristic:String, CBUUID_WriteCharacteristic:String) {

BluetoothTransmitter.DeviceAddressAndName

If the app never connected to the device, then we don't know it's name and address as the device itself is going to send.
Possibly we have an expected device name. Not all devices have a predefined device name (example xdrip/xbridge have different names).
Usually, if there's no expected device name, there will be a CBUUID_Advertisement

If the app connected before, then we have the address (should be stored in the settings or somewhere) which needs to be set during initialization

DeviceAddressAndName is an enum with two cases :

  • alreadyConnectedBefore in which case we add the address and name as stored in the settings or database. The app will only connected to a device if it has the same address.

  • notYetConnected if we have an expected name, then it's added, if we don't then we pass nil
    The app will connected to a device if the name starts with the expected name, or in case the expected name is nil, it will connect

CBUUID_Advertisement

optional
If not nil then the app will scan for devices that advertise with this specific UUID. The advantage of scanning with advertisement UUID is that the app can also scan while in the background.

CBUUID_Service

The service UUID

CBUUID_ReceiveCharacteristic

receive characteristic UUID, the BlueToothTransmitter class will take care of subscribing to it

CBUUID_WriteCharacteristic

write characteristic UUID

add a property of type CGMTransmitterDelegate

The new transmitter class needs to store a property of type CGMTransmitterDelegate
This is used to pass back information to the controller

Define it as a weak var

Functions in CGMTransmitterDelegate:

cgmTransmitterDidConnect

When the transmitter is connected
This will typically be called in centralManagerDidConnect, however it could be that the class decides to call this at a later stage, for example when subscribing to receive characteristic is done.

cgmTransmitterDidDisconnect

When the transmitter is disconnected
This will typically be called in centralManagerDidDisConnect

didUpdateBluetoothState

When the bluetooth status changes
This will typically be called in centralManagerDidUpdateState

newSensorDetected

When a new sensor is detected, only applicable to transmitters that have this functionality

sensorNotDetected

When a sensor is not detected, only applicable to transmitters that have this functionality

cgmTransmitterInfoReceived

This is the most important function, it passes new readings to the delegate

also battery info, sensor state, firmware & hardware info if applicable

transmitterNeedsPairing

The transmitter needs pairing, app should give warning to user to keep the app in the foreground

Implement the transmitter protocol

The specific protocol needs to be implemented.

See as example already existing cgm transmitter classes.

All functions in the parent class can be overriden.

Following additional functions can be used :

Functions and properties available in transmitter classes

Functions in BluetoothTransmitter classes

These functions should only be used by classes that extend the BluetoothTransmitter class.

disconnect

will call centralManager.cancelPeripheralConnection

startScanning

Will scan for the device.
This should only be used the first time the app connects to a specific device and should not be done for transmittertypes that start scanning at initialization

writeDataToPeripheral(data:Data, type:CBCharacteristicWriteType) -> Bool

calls peripheral.writeValue for characteristic CBUUID_WriteCharacteristic

Make sure that the parent class has initialized the parameter writeCharacteristic.
This may not be the case of the deriving transmitter class has overriden the method func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)

writeDataToPeripheral(data:Data, characteristicToWriteTo:CBCharacteristic, type:CBCharacteristicWriteType) -> Bool

calls peripheral.writeValue for characteristic that is given as argment : characteristicToWriteTo

setNotifyValue(_ enabled: Bool, for characteristic: CBCharacteristic)

calls setNotifyValue for characteristic with value enabled

Available CGM transmitter classes

MiaoMiao

MiaoMiao transmitter is fully implemented
class CGMMiaoMiaoTransmitter

xDripG4

xDripG4 transmitter is fully implemented
class CGMG4xDripTransmitter

Dexcom G5

class CGMG5Transmitter

  • no backfilling

Dexcom G6

class CGMG6Transmitter

  • same functionality as Dexcom G5

Bubble

Droplet-1

Use a Transmitter class

This is done in RootViewController and can be ignored for new transmitter types

  • conform to protocol CGMTransmitterDelegate

  • implement the functions in CGMTransmitterDelegate

    • cgmTransmitterDidConnect : Called when the transmitter has connected. The function gives the name and address.
      Store the address in permanent storage, next time the transmitter is initialized, give it the address value
      It will ease the initial connect

    • cgmTransmitterDidDisconnect : To give info to the user that the transmitter has disconnected, if needed to give that info.

    • deviceDidUpdateBluetoothState : Gives the status of Bluetooth (on/off ..)
      Specifically useful if first scan still needs to occur. May not be useful at all for instance transmitter class CGMG5Transmitter will start scanning by itself, there's no need for the controller to call startScanning, but it's allowed.

    • newSensorDetected : Typically for MiaoMiao, can be used to automatically start the sensor, no need to ask the user to do this.

    • sensorNotDetected : Typically for MiaoMiao

    • func cgmTransmitterInfoReceived(glucoseData:inout [RawGlucoseData], transmitterBatteryInfo:TransmitterBatteryInfo?, sensorState:SensorState?, sensorTimeInMinutes:Int?, firmware:String?, hardware:String?) Can contain lots of data, like new readings (raw readings), ...

    • cgmTransmitterNeedsPairing : Warn the user that app should be opened

    • successfullyPaired : Inform the user that the transmitter successfully paired

    • pairingFailed : Warn user that pairing failed

    • reset(successful: Bool) : give result of transmitter reset

  • Define a variable of type CGMTransmitter and initialize it with any of the transmitter classes

    • In intializer, give it the address if known, or nil if not. If needed also transmitter id and pass it the CGMTransmitterDelegate
    • If necessary start scanning. When ?
      • Not before didUpdateBluetoothState is called with status .poweredOn
      • When user choses to start scanning
      • Example Dexcom G5 : there's no need to start scanning, it's done by the class itself. This is possible because in case of Dexcom G5, there's an advertising ID (can scan while app is in the background) and we know the expected devicename.
      • Example MiaoMiao and xDripG4 : first time (ie never connected to the device before, eg after app install or when user changes/selects type of transmitter) - after user choses to start scanning, via UI button
      • In any case, for all type of transmitters, as soon as cgmTransmitterDidConnect is called, read the device address, store it permenant, and next type the app launches, read the address from permanent storage, and pass it in the Transmitter initializer. There's no need to start scanning for this case. The app will try to connect to the a device with the known address
Clone this wiki locally