|
//-----------------------------------------------------------------------------
// iTag.vpl, created 2018-05-09 11:20
// The iTag button is a BLE keychain device that is typically used as tracker/key finder.
// It has a single button and a buzzer which is used for alerting.
//
// This application scans for and connects to all found iTag buttons.
// When the button is pressed on any connected iTag, all the iTags will be alerted,
// one at a time for a short moment.
//
//-----------------------------------------------------------------------------
INCLUDE rtcu.inc
// Uncomment math.inc to add math library support.
//INCLUDE math.inc
#DEFINE TAG_COUNT 8
// Tag states
#DEFINE ST_DISCON 0
#DEFINE ST_FOUND 1
#DEFINE ST_CON 2
// Service and characteristic UUID for the button service.
#DEFINE ITAG_SERVICE_UUID "0000ffe0-0000-1000-8000-00805f9b34fb"
#DEFINE ITAG_NOTIFY_UUID "0000ffe1-0000-1000-8000-00805f9b34fb"
// Service and characteristic UUID for the immediate alert service.
#DEFINE ALERT_SERVICE_UUID "00001802-0000-1000-8000-00805f9b34fb"
#DEFINE ALERT_LEVEL_UUID "00002a06-0000-1000-8000-00805f9b34fb"
// Struct block with data for a tag
STRUCT_BLOCK iTag_data;
// Address of the tag
address : STRING;
// Service and characteristic ID for the alert service
alert_s : INT;
alert_c : INT;
// Tag state
status : INT;
END_STRUCT_BLOCK;
// 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
devInfo : btDeviceInfo;
tags : ARRAY [1..TAG_COUNT] OF iTag_data;
tagLock : MUTEX;
END_VAR;
//
// Helper function that returns a string version of the error codes
//
FUNCTION getError : STRING;
VAR_INPUT
v : INT;
END_VAR;
IF v > _BT_OK THEN
getError := "";
ELSE
CASE v OF
1: getError := "OK";
0: getError := "Not supported";
-1: getError := "Not open";
-2: getError := "Alreay open";
-3: getError := "Not present";
-4: getError := "Not found";
-5: getError := "Adapter not found";
-6: getError := "Invalid adapter";
-7: getError := "No more resources";
-8: getError := "Invalid argument";
-9: getError := "No data available";
-10: getError := "Already paired";
-11: getError := "Pair";
-12: getError := "Busy";
-13: getError := "Wrong State";
-14: getError := "Connection failed";
-15: getError := "Operation not available";
-16: getError := "Op not permitted";
-17: getError := "Timeout";
-18: getError := "Auth";
-19: getError := "Not connected";
-99: getError := "Generic error";
ELSE
getError := strFormat(format:="Unknown error: \1", v1:=v);
END_CASE;
END_IF;
END_FUNCTION;
//
// This function prints information about a device and if it is a tag,
// it adds it to the "tags" array
//
FUNCTION DeviceFound;
VAR_INPUT
dev : STRING;
END_VAR;
VAR
i : INT;
j : INT;
found : BOOL := FALSE;
END_VAR;
devInfo(dev := dev);
IF devInfo.status <> _BT_OK THEN
DebugFmt(message:="devInfo() failed: \1 "+getError(v:=devInfo.status)+", " + dev, v1:=devInfo.status);
END_IF;
IF devInfo.name = "" THEN
RETURN;
END_IF;
DebugFmt(message:=" " + devInfo.name+" , Class: \4, type: \3, connected: \1, tx power: \2",
v2:=devInfo.tx_power, v4:=devInfo.clss, v3:=devInfo.addr_type, v1:=INT(devInfo.connected));
DebugFmt(message:=" status: \1, paired: \2, trusted: \3, RSSI: \4", v1:=devInfo.status,
v2:=INT(devInfo.paired), v3:=INT(devInfo.trusted), v4:=devInfo.RSSI);
DebugFmt(message:=" Profiles: \1", v1:=devInfo.profiles);
FOR i:= 1 TO 16 DO
IF devInfo.uuid[i] <> "" THEN
DebugFmt(message:=" [\1]: " + devInfo.uuid[i], v1:=i);
END_IF;
IF devInfo.UUID[i] = ITAG_SERVICE_UUID THEN
DebugMsg(message:=" Found iTAG device");
// Device is an iTag, add it to the "tags" array
mxLock(mx:=tagLock);
FOR j := 1 TO TAG_COUNT DO
IF tags[j].status = ST_DISCON THEN
tags[j].status := ST_FOUND;
tags[j].address := dev;
tags[j].alert_s := -1;
tags[j].alert_c := -1;
DebugFmt(message:=" Stored iTag in entry \1", v1:=j);
EXIT;
END_IF;
END_FOR;
mxUnlock(mx:=tagLock);
END_IF;
END_FOR;
END_FUNCTION;
//
// This function removes a tag from the "tags" array
//
FUNCTION DeviceLost;
VAR_INPUT
dev : STRING;
END_VAR;
VAR
j : INT;
END_VAR;
DebugMsg(message:="Lost device "+dev);
mxLock(mx:=tagLock);
FOR j := 1 TO TAG_COUNT DO
IF tags[j].status > ST_DISCON AND (tags[j].address = dev) THEN
tags[j].status := ST_DISCON;
DebugFmt(message:="Removed iTag from entry \1", v1:=j);
END_IF;
END_FOR;
mxUnlock(mx:=tagLock);
END_FUNCTION;
//
// Thread for handling btWaitEvent.
//
THREAD_BLOCK thBtEvent
VAR
rc : INT;
i : INT;
address : STRING;
ch : SINT;
port : SINT;
service : INT;
chara : INT;
size : INT;
level : SINT := 0;
data : ARRAY [1..10] OF SINT;
END_VAR;
WHILE TRUE DO
rc := btWaitEvent(timeout:=10000, dev := address);
IF rc <> _BT_ERR_TIMEOUT THEN
DebugFmt(message:="event \1: "+address, v1:=rc);
CASE rc OF
_BT_EVENT_INCOMING:
rc := btHandleSerialPortIncomingConnection(ch := ch, port := port);
DebugFmt(message:="btHandleSerialPortIncomingConnection(\2) : \1, \3", v1:=rc, v2:=ch, v3:=port);
_BT_EVENT_DEV_FOUND:
DebugFmt(message:="Found "+address);
DeviceFound(dev := address);
_BT_EVENT_DEV_LOST:
DebugFmt(message:="Lost "+address);
DeviceLost(dev := address);
_BT_EVENT_PAIR_REQ:
DebugFmt(message:="Requested confirm for "+address);
// We do not want pairing in this example
rc := btSendPairResponse(accept:=FALSE);
DebugFmt(message:="btSendPairResponse: \1", v1:=rc);
_BT_EVENT_NOTIFY:
size := SIZEOF(data);
rc := btleHandleNotification(service := service, char := chara, size := size, data:=ADDR(data));
DebugFmt(message:="btleHandleNotification: \1, service \2, char \3, sz: \4", v1:=rc, v2:=service,
v3:=chara, v4:=size);
IF size > 0 THEN
// Button has been pushed
FOR i := 1 TO TAG_COUNT DO
// Trigger alert on all connected tags
IF tags[i].status = 2 THEN
// Start alerting
level := 2; // "High Alert"
rc := btleWriteVal(dev:=tags[i].address, service:=tags[i].alert_s, char :=tags[i].alert_c,
data :=ADDR(level) , size := 1);
IF rc <> _BT_OK THEN
DebugFmt(message:="btleWriteVal: \1", v1:=rc);
END_IF;
Sleep (delay:=250);
// Stop alerting
level := 0;// "No Alert"
rc := btleWriteVal(dev:=tags[i].address, service:=tags[i].alert_s, char :=tags[i].alert_c,
data :=ADDR(level) , size := 1);
DebugFmt(message:="btleWriteVal: \1", v1:=rc);
IF rc = _BT_ERR_NOT_CONNECTED THEN
// Device has disappeared, disconnect from it and remove it.
// The _BT_EVENT_DEV_LOST event is triggered to update the state
rc := btleDisconnect(dev := tags[i].address);
DebugFmt(message:="btleDisconnect: \1 ", v1:=rc);
rc := btDeviceRemove(dev := tags[i].address);
DebugFmt(message:="btDeviceRemove: \1 ", v1:=rc);
END_IF;
// Pause before triggering the next tag
Sleep(delay:=500);
END_IF;
END_FOR;
END_IF;
END_CASE;
rc := btEventDone();
DebugFmt(message:="btEventDone: \1 "+getError(v:=rc), v1:=rc);
END_IF;
END_WHILE;
END_THREAD_BLOCK;
PROGRAM iTag;
// These are the local variables of the program block
VAR
rc : INT;
thEvent : thBtEvent;
i : INT;
service : INT;
s : INT;
s_rc : INT;
sUUID : STRING;
primary : BOOL;
flags : DINT;
ch : INT;
c : INT;
c_rc : INT;
cUUID : STRING;
END_VAR;
// The next code will only be executed once after the program starts
tagLock := mxInit();
rc := btPower();
DebugFmt(message:="btPower: \1 "+getError(v:=rc)+"", v1:=rc);
thEvent();
rc := btDeviceRemove();
DebugFmt(message:="btDeviceRemove: \1 ", v1:=rc);
rc := btScanStart(transport := 2);
DebugFmt(message:="btScanStart: \1 "+getError(v:=rc), v1:=rc);
BEGIN
// Code from this point until END will be executed repeatedly
DebugFmt(message:="Devices:");
FOR i := 1 TO TAG_COUNT DO
// Check tag status
mxLock(mx:=tagLock);
DebugFmt(message:="Dev \1: status \2", v1:=i, v2:=tags[i].status);
IF tags[i].status = ST_FOUND THEN
// Connect to the device to scan the services and characteristics
rc := btleConnect (dev:=tags[i].address);
DebugFmt(message:="btleConnect: \1", v1:=rc);
IF rc = _BT_OK THEN
// Scan services
s := 1;
REPEAT
s_rc := btleServiceGet(dev:=tags[i].address, idx:=s, service:=service, primary:=primary, UUID:=sUUID);
IF s_rc = _BT_OK THEN
DebugFmt(message:=" Service: \1, primary: \2, UUID: "+sUUID, v1:=service, v2:=INT(primary));
c := 1;
REPEAT
// Scan characteristics
c_rc := btleCharGet(dev:=tags[i].address, idx:=c, service:=service, char:=ch, UUID:=cUUID, flags := flags);
IF c_rc = _BT_OK THEN
DebugFmt(message:=" Service: \1, Char: \2, flags: \4, UUID: "+cUUID, v1:=service, v2:=ch, v4:=flags);
// check for alert service UUID
IF sUUID = ALERT_SERVICE_UUID AND cUUID = ALERT_LEVEL_UUID THEN
tags[i].alert_s := service;
tags[i].alert_c := ch;
END_IF;
// Check for Button service UUID
IF sUUID = ITAG_SERVICE_UUID AND cUUID = ITAG_NOTIFY_UUID THEN
// Register notifications on the button service
rc := btleNotifyStart(dev := tags[i].address, service := service, char := ch);
DebugFmt(message:="btleNotifyStart "+tags[i].address+": \1", v1:=rc);
END_IF;
ELSE
// print return code on error
DebugFmt(message:=" btleCharGet "+tags[i].address+": \1", v1:=c_rc);
END_IF;
c := c + 1;
UNTIL c_rc <> _BT_OK END_REPEAT;
ELSE
// print return code on error
DebugFmt(message:=" btleServiceGet "+tags[i].address+": \1", v1:=s_rc);
END_IF;
s := s + 1;
UNTIL s_rc <> _BT_OK END_REPEAT;
// update status
tags[i].status := ST_CON;
END_IF;
END_IF;
mxUnlock(mx:=tagLock);
END_FOR;
DebugFmt(message:="");
Sleep(delay:=5000);
END;
END_PROGRAM;
|