1

I am writing a code in swift to get data from a BLE device (Omron BP7000). I am able to pair with it and get the other service and read the data. But I am unable to get the data of the blood pressure, in didUpdateValueFor characteristic:. Other delegate of CoreBluetooth have been used and are working as expected. Just when it comes to read/write data to Current time and read data from the Blood pressure characteristic I get the issue. If anyone can post some suggest of how should I proceed will be helpful.

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
      switch characteristic.uuid {
    
      case batteryLevelCBUUID:
          print("Battery level: \(characteristic.value![0])")
          let valueBattery = characteristic.value?[0]
          batteryLabel.text = "Battery Level =" + " " + String(describing: characteristic.value!      [0]) + "%"
          centerView.isHidden = false
      case currentTimeCBUUID:
          peripheral.readValue(for: characteristic)
          let currentDate = Date()
          //           Write the date and time to the characteristic
          writeCurrentTime(to: characteristic, in: peripheral)
          //          print("\(characteristic.value as Any) gives Current Time String  ")
          if let dateTime = extractDateTime(from: characteristic) {
              print("Date and Time:", dateTime)
          } else {
              print("Unable to extract date and time from the characteristic.")
          }
       case bloodPressureMeasurementCBUUID:
          decodeBloodPressureMeasurementData(from: characteristic)
          print("\(characteristic.value as Any) gives 2A35 ")
       default:
          print("")

}
}

    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            // Handle write error
            print("Error writing value: \(error.localizedDescription)")
        } else {
            // Handle successful write
            print("Value written successfully")

            // Check if the characteristic supports notifications
            if characteristic.properties.contains(.notify) {
                peripheral.setNotifyValue(true, for: characteristic)
            }
        }
    }


    func writeCurrentTime(to characteristic: CBCharacteristic, in peripheral: CBPeripheral) {
        // Get the current date and time
        let currentDate = Date()
        let calendar = Calendar.current
        var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .weekday, .nanosecond], from: currentDate)
        
        // Create the value to be written
        var value = Data()
        value.append(Data(bytes: &components.year!, count: 2)) // Year (2 bytes)
        value.append(Data(bytes: "", count: 0))
        value.append(UInt8(components.month!)) // Month (1 byte)
        value.append(UInt8(components.day!)) // Day (1 byte)
        value.append(UInt8(components.hour!)) // Hours (1 byte)
        value.append(UInt8(components.minute!)) // Minutes (1 byte)
        value.append(UInt8(components.second!)) // Seconds (1 byte)
        value.append(UInt8(components.weekday!)) // Day of Week (1 byte)
        value.append(contentsOf: components.nanosecond!.bytes(2)) // Fractions of a second (2 bytes)
        
        // Write the value to the Current Time characteristic
        peripheral.writeValue(value, for: characteristic, type: .withResponse)
    }

    
    func decodeBloodPressureMeasurementData(from characteristic: CBCharacteristic) {
        guard let data = characteristic.value else {
            print("No data available for decoding.")
            return
        }

        let flags = data[0] // Flags byte
        let unitFlags = (flags & 0b00000001) // Bit 0 indicates the unit of blood pressure values
        let timestampFlags = (flags & 0b00000010) // Bit 1 indicates the presence of timestamp

        // Decode systolic, diastolic, and mean arterial pressure values
        let systolicPressure = (data[1] + (data[2] << 8))
        let diastolicPressure = (data[3] + (data[4] << 8))
        let meanArterialPressure = (data[5] + (data[6] << 8))

        // Decode pulse rate value
        let pulseRate = (data[7] + (data[8] << 8))


        // Decode unit of blood pressure values
        let unit: String = (unitFlags != 0) ? "kPa" : "mmHg"

        // Process and use the decoded values as needed
        print("Systolic Pressure: \(systolicPressure) \(unit)")
        print("Diastolic Pressure: \(diastolicPressure) \(unit)")
        print("Mean Arterial Pressure: \(meanArterialPressure) \(unit)")
        print("Pulse Rate: \(pulseRate) bpm")
    }
    


  [1]: https://i.stack.imgur.com/fYaxj.png
Kshitij876
  • 11
  • 4
  • Did you check the properties of your attribute to confirm that it supports write with response? – Paulw11 Jun 23 '23 at 09:56
  • yes @Paulw11 , for Current Time = 0x2A2B, it specifically supports write with response. When trying for write without response I get error in the console log. – Kshitij876 Jun 23 '23 at 10:01
  • Why are you using `&components` for year? Check the data you are writing in the debugger. Does it look correct? Have you accounted for MSB/LSB correctly? – Paulw11 Jun 23 '23 at 10:33
  • & components is used to for data to directly access and modify component.year value. The MSB/LSB thing is new to me, if you can tell me what to search for it will be helpful for me. But I have tried to send the byte data according to the offset(index) as provided by the documentations. @Paulw11 – Kshitij876 Jun 23 '23 at 10:45
  • 1
    the year data should be sent In UInt16 format and rest of the part is supposed to be send in UInt8 format. @Paulw11 – Kshitij876 Jun 23 '23 at 10:53
  • @Paulw11 , as per your first comment I tried to change it to without response and here is there error message that I got in the console , " WARNING: Characteristic does not specify the "Write Without Response" property - ignoring response-less write" – Kshitij876 Jun 23 '23 at 12:55

2 Answers2

0

I'm assuming that your Current Time is part of the Current Time Service. Error code 128 (0x80) indicates "Data field ignored." So you're either sending a field the device doesn't support, or you're sending something out of range. The docs also explain that all values are little-endian.

The specs note that Current Time may optionally be written (with response) and is not permitted to be written without response. So you're correct to write with response. Since you're getting 0x80 rather than a general write failure, I will assume the characteristic is writable on this device.

So, now turn to the GSS v9, section 3.62 for details on the Current Time characteristic. The structure is 10 octets, an Exact Time 256 (9 octets) followed by an Adjust Reason (1 octet).

Now, turn to Section 3.90 that defines Exact Time 256. It is a Day Date Time struct (section 3.71) followed by a Factions256 uint8.

You get the idea. Go step by step through the spec to build up the format. Do not guess. Never assume it's obvious. Follow the spec. I'll skip over some steps and assemble the whole format:

year        : 16 (little-endian)
month       : 8
day         : 8
hour        : 8
minute      : 8
second      : 8
dayOfWeek   : 8 (1=Monday, 7=Sunday, 0=Unknown)
fractions256: 8 (factional part of seconds in units of 1/256)
adjustReason: 8 (0=manual, 1=reference update, 2=TZ change, 3=DST)

For convenience, I often use this extension on FixedWidthIntegers:

enum ByteOrder {
    case littleEndian, bigEndian
}

extension FixedWidthInteger {
    func data(byteOrder: ByteOrder) -> Data {
        switch byteOrder {
        case .littleEndian: return withUnsafeBytes(of: self.littleEndian) { Data($0) }
        case .bigEndian: return withUnsafeBytes(of: self.bigEndian) { Data($0) }
        }
    }
}

And the Bluetooth day-of-week is different than Foundation's:

extension DateComponents {
    // Monday=1 to Sunday=7, 0=unknown
    var btDayOfWeek: UInt8 {
        guard let dow = self.weekday else { return 0 }
        return dow == 1 ? 7 : UInt8(dow - 1)
    }
}

With those, I believe this would be the correct packet:

var data = Data()
data.append(UInt16(c.year!).data(byteOrder: .littleEndian))
data.append(UInt8(c.month!))
data.append(UInt8(c.day!))
data.append(UInt8(c.hour!))
data.append(UInt8(c.minute!))
data.append(UInt8(c.second!))
data.append(c.btDayOfWeek)
data.append(UInt8(c.nanosecond! * 256 / Int(NSEC_PER_SEC)))
data.append(0) // Adjust reason. 0 == Manual Time Update

So then you're decoding blood pressure. It looks like you're treating them as integers, when they're medfloat16, and you're ignoring the documented scaling factors. Start with section 2.3 of the GSS, which explains how to interpret values generally, and particularly 2.3.2 about scalars. Then see section 3.31 that explains the Blood Pressure Measurement characteristic in detail. You may find the Personal Health Devices Transcoding white paper helpful. "medfloat16" is often referred to as SFLOAT, so you can read section 2.2.2 of the whitepaper for details and an example encoding.

If you have questions about blood pressure after reading the above, open a new question addressing the specific issue you're having. What is the input? What should the output be? What output are you getting instead?

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Hello @Rob,thank you the answer it helped me understand more about the work I am doing but I have tried the following suggestion and it still gives me the error same as above. The data sheet which have received from the starter kit of omron I got for the device show there is no data on the 1st index, and the total data packet size should be 10 bytes. – Kshitij876 Jun 26 '23 at 10:12
  • I don't know what you mean by "there is no data on the 1st index." If you have a data sheet that explains the format, you of course should use that. What does the data sheet say? (I'm also not quite certain what you mean by "the total data packet size should be 10 bytes." The answer here is 10 bytes. Are you suggesting you're not writing 10 bytes, or just confirming that they match?) – Rob Napier Jun 26 '23 at 13:29
  • Hello, @rob, sorry for the late response I have tried the solution that you have mentioned. But unfortunately I am getting the same error. But the solution you have give did give some insights to the BLE current time and other formats. The error is still this "Error writing value: Unknown ATT error." when I print it. And "domain: "CBATTErrorDomain" - code: 128" in the console. – Kshitij876 Jun 27 '23 at 12:18
  • mValue = new byte[10]; mValue[0] = (byte) calendar.get(Calendar.YEAR); mValue[1] = (byte) (calendar.get(Calendar.YEAR) >> 8); mValue[2] = (byte) (calendar.get(Calendar.MONTH) + 1); mValue[3] = (byte) calendar.get(Calendar.DATE); mValue[4] = (byte) calendar.get(Calendar.HOUR_OF_DAY); mValue[5] = (byte) calendar.get(Calendar.MINUTE); mValue[6] = (byte) calendar.get(Calendar.SECOND); mValue[7] = (byte) ((calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7 + 1); – Kshitij876 Jun 27 '23 at 12:49
  • mValue[8] = (byte) (calendar.get(Calendar.MILLISECOND) * 256 / 1000); mValue[9] = 1; the above comment and this comment is the data packet , that is getting accepted in android side when there is write for current time. – Kshitij876 Jun 27 '23 at 12:50
  • If you have working Android code, send the same packet it generates, and verify that your issue isn't elsewhere (print out its values; send those hardcoded). Then compare each value that Android generates to the one your iOS code generates and see which computation is different. – Rob Napier Jun 27 '23 at 13:17
  • Okay , I will try the following method – Kshitij876 Jun 27 '23 at 13:35
0

I have found the answer for this issue. I was using Data() and instead of that when I used NSMutableData() it started working and connected to the BLE device (Omron BP 7000). Following is the code for the answer I have used.

// Define the CurrentTime structure
struct DateAndTime {
    var year: UInt16
    var month: UInt8
    var day: UInt8
    var hours: UInt8
    var minutes: UInt8
    var seconds: UInt8
}

struct CurrentTime {
    var dateData : DateAndTime
    var dayOfWeek: UInt8
    var fractions256: UInt8
    var adjustReason: UInt8
}



func writeCurrentTime(to characteristic: CBCharacteristic, in peripheral: CBPeripheral){
        // Get the current date and time
        let currentDate = Date()
        let calendar = Calendar.current
        let components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .weekday, .nanosecond], from: currentDate)
        // Send the bytes to the BLE device
        let data = NSMutableData()
        
        let time = DateAndTime(year: UInt16(components.year!), month: (UInt8(components.month!) + 1), day: (UInt8(components.day!)), hours: (UInt8(components.hour!)), minutes: (UInt8(components.minute!)), seconds: (UInt8(components.second!)))
        
        
        // Create an instance of CurrentTime with current date and time
        let currentTime = CurrentTime(dateData: time, dayOfWeek: (components.btDayOfWeek), fractions256: UInt8(components.nanosecond! * 256 / Int(NSEC_PER_SEC)), adjustReason: 0)    // Replace with the actual year
        
        // Convert the CurrentTime structure to Data
        let currentTimeData = withUnsafeBytes(of: currentTime) { Data($0) }
        data.append(currentTimeData)
        // Write the value to the Current Time characteristic
        peripheral.writeValue(data as Data, for: characteristic, type: .withResponse)
    }
Kshitij876
  • 11
  • 4