|
//-----------------------------------------------------------------------------
// 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;
// Battery level characteristic
rc := bleCharFind(dev:=dev, uuid:="00002a19-0000-1000-8000-00805f9b34fb", char:=c, flags := flags);
DebugFmt(message:="bleCharFind: \1, Char: \2, flags: \4", v1:=rc, v2:=c, v4:=flags);
IF rc = _BLE_OK THEN
bat_level_id := c;
state := 4;
END_IF;
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;
|