Examples - Bluetooth Low Energy (NX32L)

Top  Previous  Next

//-----------------------------------------------------------------------------
// 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;