Examples - BLE Beacon (LX)

Top  Previous  Next

//-----------------------------------------------------------------------------
// beacon.vpl
// This example scans for BLE devices until it finds one matching the wanted UUID in the iBeacon data.
// It then adds it to the accept list and switches to only receiving from devices on the acccept list.
// The example is configured to look for an BlueUp SafeX beacon, see https://www.blueupbeacons.com/index.php?page=safex
// The beacon is configured to use Safety Packets which include the status of the button and other sensors.
//
// By sending an SMS with the text "connect" to the device, it will connect to the beacon,
// show the battery level and disconnect again.
//
//-----------------------------------------------------------------------------
INCLUDE rtcu.inc
// Uncomment math.inc to add math library support.
//INCLUDE math.inc
 
//  Input variables that can be configured via the configuration dialog (These are global)
VAR_INPUT
 
END_VAR;
 
//  Output variables that can be configured via the configuration dialog (These are global)
VAR_OUTPUT
 
END_VAR;
 
//  These are the global variables of the program
VAR
  target_uuid : STRING := "A565CC84-1E0E-4B5A-B2D3-17C98FF5410A";
  target_MAC  : STRING := "";
 
  scanning    : BOOL := TRUE;
END_VAR;
 
 
// Read Big endian 16-bit integer from pointer
FUNCTION readIntBE:INT;
VAR_INPUT
  p   : PTR;
END_VAR;
VAR
  tmp : INT;
END_VAR;
  memcpy(dst:=ADDR(tmp)+1, src:=p, len:=1);
  memcpy(dst:=ADDR(tmp), src:=p+1, len:=1);
  readIntBE := tmp;
END_FUNCTION;
 
// Extract UUID from pointer, using the format used in the beacon.
FUNCTION parseProxUuid:STRING;
VAR_INPUT
  p     : PTR;
END_VAR;
VAR
  str   : STRING;
  data  : ARRAY[0..16] OF USINT;
  i     : INT;
END_VAR;
  memcpy(dst:=ADDR(data), src:=p, len:=16);
  str := sintToHex(v:=data[0])+sintToHex(v:=data[1])+sintToHex(v:=data[2])+sintToHex(v:=data[3])
     +"-"+sintToHex(v:=data[4])+sintToHex(v:=data[5])+"-"+sintToHex(v:=data[6])+sintToHex(v:=data[7])
     +"-"+sintToHex(v:=data[8])+sintToHex(v:=data[9])+"-"+sintToHex(v:=data[10])+sintToHex(v:=data[11])
     +sintToHex(v:=data[12])+sintToHex(v:=data[13])+sintToHex(v:=data[14])+sintToHex(v:=data[15]);
  parseProxUuid := str;
END_FUNCTION;
 
 
// Parse proximity beacon.
// Returns 1 if target_uuid is found, otherwise 0.
FUNCTION ParseProx : INT;
VAR_INPUT
  mac         : STRING;
  p           : PTR;
  len         : INT;
END_VAR;
VAR
  uuid        : STRING;
  major       : UINT;
  minor       : UINT;
  meas_power  : SINT;
END_VAR;
  ParseProx := 0;
  // Extract values:
  uuid := parseProxUuid(p:=p);
  major := readIntBE(p:=p+16);
  minor := readIntBE(p:=p+18);
  memcpy(dst:=ADDR(meas_power), src:=p+20, len:=1);
  // show values
  DebugFmt(message:=" "+mac);
  DebugFmt(message:="   "+uuid);
  DebugFmt(message:="   Major: "+intToHex(v:=major));
  DebugFmt(message:="   Minor: "+intToHex(v:=minor));
  DebugFmt(message:="   Measured power: \1 dB", v1:=meas_power );
  IF uuid = target_uuid THEN
    // SafeX beacon found: Show the status for it.
    DebugFmt(message:="    Button short pushed: \1", v1:=(minor AND 1));
    DebugFmt(message:="    Button long pushed: \1", v1:=(minor AND 2));
    DebugFmt(message:="    Movement: \1", v1:=(minor AND 4));
    DebugFmt(message:="    Free Fall: \1", v1:=(minor AND 8));
    ParseProx := 1;
  END_IF;
END_FUNCTION;
 
// Parses beacon
// Returns 1 if target_uuid is found, otherwise 0.
FUNCTION ParseBeacon : INT;
VAR_INPUT
  mac         : STRING;
  p           : PTR;
  len         : INT;
END_VAR;
VAR
  b_type      : UINT;
END_VAR;
  ParseBeacon := 0;
  // Read beacon type
  memcpy(dst:=ADDR(b_type), src:=p, len:=2);
 
  IF b_type = 16#1502 THEN
    // proximity beacon
    ParseBeacon := ParseProx(mac:= mac, p:=p+2, len:=len-2);
  END_IF;
 
END_FUNCTION;
 
 
 
// Callback that parses manufacturer specific data.
FUNCTION CALLBACK cbAdvMan;
VAR_INPUT
  mac      : STRING;
  ev_type  : UINT;
  adv_type : USINT;
  data     : PTR;
  len      : INT;
  rssi     : SINT;
  arg      : DINT;
END_VAR;
VAR
  cid     : UINT;
  rc       : INT;
END_VAR;
  IF adv_type = 16#FF THEN
    // Extract Company Identifier
    memcpy(dst:=ADDR(cid), src:=data, len:=2);
     
    IF cid = 16#004c THEN //Apple => iBeacon
       
        // Parse beacon, searching for the wanted UUID
        rc := ParseBeacon(mac:=mac, p:=data+2, len := len-2);
        IF rc = 1 AND scanning THEN
          // Device found, restart scan to use accept filter that only accepts this device.
          target_MAC := MAC;
          // Stop scan
          rc := bleObserverStop();
          DebugFmt(message:="bleObserverStop: \1", v1:=rc);
          scanning := FALSE;
          // Add MAC to accept list
          rc := bleAcceptFilterAdd(mac:=target_MAC);
          DebugFmt(message:="bleAcceptFilterAdd: \1", v1:=rc);
           
          // Restart scan with new filter policy
          rc := bleObserverStart(
              scan_type:=1,// Active
              filter_policy := 1//Accept only from accept list
              );
          DebugFmt(message:="bleObserverStart: \1", v1:=rc);
        END_IF;
    END_IF;
  END_IF;
END_FUNCTION;
 
 
 
// Thread for connecting to a device, reading the battery status and disconnecting.
THREAD_BLOCK beaconThread;
VAR
  rc, rc2      : INT;
  i, j         : INT;
  s, c         : UINT;
  uuid         : STRING;
  primary      : BOOL;
  flags        : DINT;
  dev          : SYSHANDLE;
  bat_level_id : UINT;
  bat_level    : USINT;
  size         : INT;
 
  state        : INT := 0;
  old_state    : INT:= -1;
  running      : BOOL;
END_VAR;
  DebugMsg(message:="Starting beacon thread");
  running := TRUE;
 
  // Stop scanning so the connection can be created
  rc := bleObserverStop();
  DebugFmt(message:="bleObserverStop: \1", v1:=rc);
 
  WHILE running DO
    IF old_state <> state THEN
        // Show state changes
        DebugFmt(message:="S: \1 => \2", v1:=old_state, v2:=state);
        old_state:=state;
    END_IF;
     
    CASE state OF
    0:
        // not connected, start connection
        DebugFmt(message:="Connect...");
        rc := bleConnect(dev:=dev, mac:=target_mac);
        DebugFmt(message:="bleConnect: \1", v1:=rc);
        IF rc = _BLE_OK THEN
          state := 1;
        ELSE
          state := state+100;
        END_IF;
    1:
        // bleConnect succeeded, check status
        rc := bleDeviceStatus(dev:=dev);
        DebugFmt(message:="bleDeviceStatus: \1, "+bleDeviceMacGet(dev:=dev), v1:=rc);
        IF rc = 2 THEN
          // Connection lost, switch to disconnect state
          DebugFmt(message:="connection lost");
          state := 10;
        ELSIF rc = 5 THEN
          // Connecting
          // Try again
          state := state+100;
        ELSIF rc = 10 THEN
          // Connected, waiting to be ready, so try again
          state := state+100;
        ELSIF rc = 20 THEN
          // Ready, no cache
          state := 2;
        ELSIF rc > 20 THEN
          // ready, cache present, so skip reading cache
          state := 3;
        END_IF;
    2:
        // Ready, try reading cache
        rc := bleServiceCacheUpdate(dev:=dev);
        DebugFmt(message:="bleServiceCacheUpdate: \1", v1:=rc);
        IF rc = _BLE_OK THEN
          state := 3;
        ELSE
          state := 10;
        END_IF;
    3:
        // Find wanted characteristic
        state := state+100;
        DebugMsg(message:="-- Services: --");
        i := 0;
        REPEAT
          rc := bleServiceGet(dev:=dev, idx := i, service:=s, uuid:=uuid, primary := primary);
          IF rc = _BLE_OK THEN
              DebugFmt(message:="  Service: \1, primary: \2, UUID: "+UUID, v1:=s, v2:=INT(primary));
              j := 0;
              REPEAT
                rc2 := bleCharGet(dev:=dev, service:=s, idx := j, char:=c, uuid:=uuid, flags := flags);
                IF rc2 = _BLE_OK THEN
                    DebugFmt(message:="    Service: \1, Char: \2, flags: \4, UUID: "+UUID, v1:=s, v2:=c, v3:=j, v4:=flags);
                    // Battery level characteristic
                    IF uuid = "00002a19-0000-1000-8000-00805f9b34fb" THEN
                      bat_level_id := c;
                      state := 4;
                    END_IF;
                ELSE
                    DebugFmt(message:="    End(\2): \1", v1:=rc2, v2:=j);
                END_IF;
                j:=j+1;
              UNTIL rc2 <> _BLE_OK
              END_REPEAT;
          ELSE
              DebugFmt(message:="  End(\2): \1", v1:=rc, v2:=i);
          END_IF;
          i:=i+1;
        UNTIL rc <> _BLE_OK
        END_REPEAT;
    4:
        // Read from characteristic
        size:=1;
        rc := bleReadVal(dev:=dev, char:=bat_level_id, size := size, data := ADDR(bat_level));
        DebugFmt(message:="bleReadVal(\2): \1", v1:=rc, v2:=bat_level_id);
        IF rc = 1 THEN
          DebugFmt(message:="Battery level: \1 %", v1:=bat_level);
          state := 5;
        ELSIF rc = -19 THEN
          // Disconnected
          DebugFmt(message:="connection lost");
          state := 10;
        ELSE
          state := state+100;
        END_IF;
    5:
        // We are done, switch to disconnect.
        state := 10;
       
    10:
        // Disconnect
        DebugMsg(message:="Disconnecting...");
        rc := bleDisConnect(dev:=dev);
        DebugFmt(message:="bleDisconnect: \1", v1:=rc);
       
        IF rc = _BLE_OK THEN
          // Stop thread
          running := FALSE;
          state := 100;
        ELSE
          state := state+100;
        END_IF;
    END_CASE;
     
    IF state >= 100 THEN
        // step failed; wait 5s and try again
        state := state -100;
        Sleep(delay:=5000);
    END_IF;
     
  END_WHILE;
 
  // Start scanner again
  rc := bleObserverStart(
    scan_type:=1,// Active
    filter_policy := 1//Accept only from accept list
     );
  DebugFmt(message:="bleObserverStart: \1", v1:=rc);

 
END_THREAD_BLOCK;
 
VAR
  beaconThr:beaconThread;
END_VAR;
 
 
PROGRAM beacon;
// These are the local variables of the program block
VAR
  rc : INT;
  sms : gsmIncomingSMS;
END_VAR;
// The next code will only be executed once after the program starts
 
  // Enable BT
  rc := blePower(power:=ON);
  DebugFmt(message:="blePower: \1", v1:=rc);
 
  rc := bleAcceptFilterClear();
  DebugFmt(message:="bleAcceptFilterClear: \1", v1:=rc);
     
  // Register callback for advertising data type 16#FF, Manufacturer specific data
  rc := bleRegisterAdvData(cb_Adv:=@cbAdvMan, adv_type := 16#FF);
  DebugFmt(message:="BleRegisterAdvData: \1", v1:=rc);
 
 
 
  // Start listening
  rc := bleObserverStart(
    scan_type:=1,// Active
    filter_policy := 0//Accept all
     );
  DebugFmt(message:="bleObserverStart: \1", v1:=rc);
 
 
BEGIN
// Code from this point until END will be executed repeatedly
  // Check for command
  sms();
  IF sms.status > 0 THEN
    IF sms.message = "connect" THEN
        IF target_MAC <> "" THEN
          // Start thread to connect to the target
          beaconThr();
        ELSE
          DebugMsg(message:="Target not found");
        END_IF;
    END_IF;
  END_IF;
 
END;
 
END_PROGRAM;