Examples - BLE Key Fob Data Receiver (LX)

Top  Previous  Next

// ----------------------------------------------------------------------------
// Program: BLE Key Fob Data Receiver Demo (LX)
//
// Description:
//    This educational program demonstrates how to read BLE packets
//    sent from the BLE Key Fob.
//
// Key Concepts Demonstrated:
//    BLE communication:
//       - Initializing the BLE interface.
//       - Listening BLE packages.
//       - Receiving and Interpreting the data
//
// Hardware Setup:
//    - RTCU LX-family Device with BLE interface.
//    - RTCU BLE Key Fob.
// ----------------------------------------------------------------------------
 
INCLUDE rtcu.inc
 
#DEFINE KeyFobMAC                   "p2C:6A:6F:90:AE:10" // MAC address of the key fob to listen
 
// Data Object IDs
// The object IDs are used by the BLE key fob
#DEFINE PACKET_ID           16#00
#DEFINE BATTERY_ID           16#01
#DEFINE VOLTAGE_ID           16#0C
#DEFINE BUTTON_ID           16#3A
#DEFINE TEMPERATURE_ID       16#57
 
// ===========================================================================
// GLOBAL DATA STORAGE
// ===========================================================================
VAR
  // Global buffer to hold the raw packet for inspection
  ble_raw_data   : ARRAY[1.. 100] OF USINT;
  temp_packetID : USINT; // temporary sequence number buffer
END_VAR;
 
// ===========================================================================
// BLE KEY FOB PACKET PARSER (CALLBACK FUNCTION)
// ===========================================================================
FUNCTION CALLBACK cbBLEKeyFob;
VAR_INPUT
  mac     : STRING; // MAC address of the emitting sensor
  ev_type : UINT;   // Type of event triggering this call
  adv_type : USINT; // Advertisement type (Expecting 0x16 for Service Data)
  data     : PTR;   // Memory pointer to the raw sensor payload
  len     : INT;   // Length of the payload
  rssi     : SINT;   // Signal strength in dBm
  arg     : DINT;   // Custom user argument
END_VAR;
 
VAR
  // Local variables for extracted values
  v_packetID : USINT;
  v_btn       : ARRAY[1..4] OF SINT;
  v_batt_pct : SINT;
  v_batt_vol : INT;
  v_temp     : SINT;
  pos         : USINT;
  btn         : USINT := 1;
 
  exp_len     : SINT := 20; // expected data length
END_VAR;
  // STEP 1: Filter by Packet Length and Type
  // We only care about Service Data (0x16) and your specific 20-byte length.
  IF adv_type = 16#16 AND len = exp_len THEN
    // STEP 2: Copy raw pointer data to a readable array
    memcpy(dst := ADDR(ble_raw_data), src := data, len := exp_len);
     
    // STEP 3: Verify UUID (Bytes 1 & 2 must be D2 FC)
    IF ble_raw_data[1] = 16#D2 AND ble_raw_data[2] = 16#FC THEN
       
        // STEP 4: Decode the data
        pos := 4; // Decode after the device information
        WHILE pos < len DO
          IF pos >= len THEN
              EXIT;
          END_IF;
          CASE ble_raw_data[pos] OF
              PACKET_ID:
                IF pos + 2 <= len THEN
                    v_packetID := ble_raw_data[pos+1];
                    pos := pos + 2; // packet ID is 1 byte
                END_IF;
              BUTTON_ID:
                IF pos + 1 <= len THEN
                    v_btn[btn] := ble_raw_data[pos+1];
                    btn := btn + 1;
                    pos := pos + 2; // button event is 1 byte
                END_IF;
              BATTERY_ID:
                IF pos + 1 <= len THEN
                    v_batt_pct := ble_raw_data[pos+1];
                    pos := pos + 2; // battery level is 1 byte
                END_IF;
              VOLTAGE_ID:
                IF pos + 2 <= len THEN
                    v_batt_vol := shl16(in := INT(ble_raw_data[pos+2]), n := 8) OR (ble_raw_data[pos+1] AND 16#FF);
                    pos := pos + 3; // battery voltage is 2 bytes
                END_IF;
              TEMPERATURE_ID:
                IF pos + 1 <= len THEN
                    v_temp := ble_raw_data[pos+1];
                    pos := pos + 2; // temperature is 1 bytes
                END_IF;
              ELSE
                EXIT; // Unknown object ID, data is not from BLE key fob
          END_CASE;
        END_WHILE;
        btn := 1; // reset the button count
       
        // STEP 5: Debug reporting
        // Show only once per button press
        IF temp_packetID <> v_packetID THEN
           temp_packetID := v_packetID;
          IF ((v_btn[1] = 0) AND (v_btn[2] = 0) AND (v_btn[3] = 0) AND (v_btn[4] = 0)) THEN
              DebugMsg(message := "--- Status Packet Received ---");
              DebugFmt(message := "Sensor MAC : " + mac + ", RSSI: \1, Advertisement Type: \2", v1:=INT(rssi), v2:=INT(adv_type));
              DebugFmt(message := "Packet ID  : \1", v1 := v_packetID);
              DebugFmt(message := "Battery    : \1% (\2 mV)", v1 := INT(v_batt_pct), v2 := v_batt_vol);
              DebugFmt(message := "Temperature: \1 C", v1 := INT(v_temp));
          ELSE
              DebugMsg(message := "--- Button Event Packet Received ---");
              DebugFmt(message := "Sensor MAC : " + mac + ", RSSI: \1, Advertisement Type: \2", v1:=INT(rssi), v2:=INT(adv_type));
              DebugFmt(message := "Packet ID  : \1", v1 := v_packetID);
              DebugFmt(message := "Buttons    : B1=\1, B2=\2, B3=\3, B4=\4",
                       v1 := INT(v_btn[1]), v2 := INT(v_btn[2]), v3 := INT(v_btn[3]), v4 := DINT(v_btn[4]));
              IF ((v_btn[1] = 4) OR (v_btn[2] = 4) OR (v_btn[3] = 4) OR (v_btn[4] = 4)) THEN
                DebugFmt(message := "Long-press : YES");
              ELSE
                DebugFmt(message := "Long-press : NO");
              END_IF;
              DebugFmt(message := "Battery    : \1% (\2 mV)", v1 := INT(v_batt_pct), v2 := v_batt_vol);
              DebugFmt(message := "Temperature: \1 C", v1 := INT(v_temp));
          END_IF;
        END_IF;
    END_IF;
  END_IF;
 
END_FUNCTION;
 
// ===========================================================================
// MAIN PROGRAM BLOCK
// ===========================================================================
PROGRAM BLE_KEY_FOB;
 
  // PHASE 1: INITIALIZATION (Executes Once)
  DebugMsg(message := "Initializing BLE Platform...");
 
  // 1. Turn on the BLE chip power
  IF blePower(power := TRUE) = 1 THEN
    DebugMsg(message := "Success: BLE Powered ON.");
  ELSE
    DebugMsg(message := "Error: Failed to power BLE.");
  END_IF;
 
  // 2. Register the Callback for Service Data (0x16)
  // This tells the system: "If you see a 0x16 packet, run cbBLEKeyFob"
  bleRegisterAdvData(adv_type := 16#16, cb_adv := @cbBLEKeyFob);
  DebugMsg(message := "Registration: Listening for Service Data (0x16).");
 
  // 3. Filter the other BLE devices
  // Remove all MAC addresses from accept filter
  bleAcceptFilterClear();
  // Listen only on the BLE key fob
  bleAcceptFilterAdd(mac:=KeyFobMAC);
     
  // 4. Start the Bluetooth Observer (Scanning)
  // scan_interval/window at 10s = Continuous scanning
  bleObserverStart(
     scan_interval := bleTimeFromMs(v:=10000),
     scan_window := bleTimeFromMs(v:=10000),
     filter_policy := 1
  );
  DebugMsg(message := "Scanning: Started.");
 
BEGIN
  // PHASE 2: CYCLIC SCAN (Executes Infinitely)
  // Logic is handled by the callback
END;
 
END_PROGRAM;