This document is Copyright of VIZpin Inc, 2020. The contents of this document are Confidential and intended solely for the recipient. Reproduction of, or forwarding to anyone not directly sent this document is strictly forbidden.

Installation

iOS 13.0+ is required

VIZpinSDK.Framework provides necessary functions to communicate with the VIZPin Server and the VIZpin Readers. This SDK is compatible with all VP1 series readers. A third party developer will be able to easily integrate this into their project by just dragging the framework to the project.

Under your project Build Phases, make sure VIZpinSDK.Framework is included in Link Binary with Libraries. Also add it to Embed Frameworks.

After the VIZpinSDK.Framework is added to your project. You should be able to #import VIZpinSDK.h to your source to access the methods.

Version Caveats

VIZpinSDK.Framework requires minimum SDK to be 13.0+. The sdk is tested on iPhone 5, iPhone 6 and iPhone 7.

iOS Permissions

In order to use VIZpinSDK with an iOS app, your app should include following permissions in the plist.

Privacy - Bluetooth Peripheral Usage Description >> [App Name] uses bluetooth to unlock VIZpin powered locks.

Also set Project Capabilities: Background Modes ON > Uses Bluetooth LE accessories

Constants

There are four static constants that the developer requires to acquire from VIZpin prior to the starting of the project. You will set these constants with the VIZpinSDK soon after you initialize the library.

Please contact support@vizpin.com to get the uuid values for APP_ID and APP_KEY constants. APP_VERSION and APP_NAME can be chosen by developer.

Important: The uuids should be all in capitalized hex values

Initializing SDK

VIZpinSDK.Framework contains a class VIZpinSDK. Following code snippet shows creating an object and initializing your app with the app constants. APP_VERSION should display <major.minor.patch> in numeric format.

    sdk = [[VIZpinSDK alloc] init];
    sdk.apiServer = @"<https://YOUR_SANDBOX_HERE.force.com/VIZpin/services/apexrest/sdk";>
    sdk.APP_ID = @"YOUR_APP_ID_HERE";
    sdk.APP_NAME = @"OEM AppName";
    sdk.APP_KEY = @"YOUR_APP_KEY_HERE";
    sdk.APP_KEY_VERSION = @"1.1";
    sdk.APP_VERSION = @"1.0.0";

    VPUser* user = [[VPUser alloc] init];
    user.phone = @"USER_PHONE_NUMBER";
    user.password = @"USER_PASSWORD";
    user.bluetooth = VIZpinSDK.BLE_MODE;

    [sdk initialize:user]; // Use this on an app start where you have cached user credentials
    // or
    [sdk initialize:nil]; // Use this on a fresh install where you have no cached user credentials

The apiEndPoint parameter denotes which server or sandbox to use as the end point of the SDK calls.

Note: The string passed on to apiEndPoint is of the form https://YOUR_SANDBOX_HERE.force.com/VIZPin/services/appexrest/sdk. Please contact support@vizpin.com to get the exact URL for your sandbox.

Note: You can also call long timestamp = [sdk getServerTime]; to get the SDK"s current value for the server time. This is useful for validating VPCredentials and the states and expirations.

Initializing Bluetooth

Verifying the bluetooth subsystem on your user"s phone is ON is the responsibility of the developer.

CBCentralManager* _centralManager = [[CBCentralManager alloc] initWithDelegate:nil queue:dispatch_queue_create("com.company.app", DISPATCH_QUEUE_SERIAL)     options:@{CBCentralManagerOptionShowPowerAlertKey:@YES}];

.....

if( _centralManager.state != CBCentralManagerStatePoweredOn ) { 
    // alert user that bluetooth is required
}

Note: Your app user could go into the settings and turn bluetooth on or off anytime during the operation of app. It is the developers responsibility to continue to monitor the bluetooth state and let user know to switch it back on.

Note: REQUEST_ENABLE_BT is a locally defined variable. It should be greated than 1.

Verify Connection

This function can be used to verify that your connection is valid and your credentials are correct. It also returns the current server time via timestamp. Like all functions there will be a VPError return object and a function specific callback to receive the results. Below is a sample use of this function:

VPError* ret = [sdk.verifyConnection:YES withCompletion: ^ (VPError *error) {
    if (error.status == VIZpinSDK.SDK_SUCCESS) {
      // do something else, the SDK is set up and the SDK time is updated
    }
}];

if( ret != VIZpinSDK.RET_SDK_SUCCESS ) {
    // show error message or determine issue with parameters
}

Add User

New system Users are added to the server via the addUser function. This method takes only one parameter and that is a VPUser object. All properties of the VPUser except sms must be set for this function. As you can see below, all calls return a VPError object and you also specify a function specific callback object to handle the results of this function. The returned VPError object will only be non null when there is an issue with the parameters or the SDK configuration. Any errors that are encountered connecting to the server will be returned in the callback. The VPError returned in all callbacks will indicate success or failure. Each of the function specific callbacks will also echo back any VP**** objects that were passed in as parameters. Below is a sample use of this function:

VPUser* user = [[VPUser alloc] init];
user.first = @"first";
user.last  = @"last";
user.country = @"USA"; 
user.phone = @"+17775551212";
user.password = @"secret123";
user.bluetooth = VIZpinSDK.BLE_MODE;

VPError* ret = [sdk addUser:user withCompletion: ^ (VPError *error, VPUser *user) {
    if (error.status == VIZpinSDK.SDK_SUCCESS) {
      // do something else, usually the next step is `verifyUser` to enter the SMS verification code they should have received
    }
}];

if( ret != VIZpinSDK.RET_SDK_SUCCESS ) {
  // show error message or determine issue with parameters
}

Note: The country property of VPUser is in the [three letter iso code format] (https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3). The phone property of VPUser is the country code and the phone number concatenated together.

Verify User

Verification is used to have the User received an SMS to their phone number to prove they own this device. Only the phone, password and sms properties of the VPUser need set for this function. If you want to reset a users password, simply send a different password that the server has and it will be udpated. Like all functions there will be a VPError return object and a function specific callback to receive the results. Below is a sample use of this function:

VPUser* user = [[VPUser alloc] init];
user.phone = @"+17775551212";
user.password = @"secret123";
user.sms = @"123456";

VPError* ret = [sdk verifyUser:user withCompletion: ^ (VPError *error, VPUser *user) {
    if (error.status == VIZpinSDK.SDK_SUCCESS) {
      // do something else, the User is all setup and can now be granted VIZpins on the server
    }
}];

if( ret != VIZpinSDK.RET_SDK_SUCCESS ) {
    // show error message or determine issue with parameters
}

Authenticate User

Authentication logs this User into the server to be able to perform other funtions. Once you have successfullly set everything up you can authenticate manually at startup or pass the same VPuser into the initialize function and the SDK will authenticate for you as needed. Only the phone and password properties of the VPUser need set for this function. Like all functions there will be a VPError return object and a function specific callback to receive the results. Below is a sample use of this function:

VPUser* user = [[VPUser alloc] init];
user.phone = @"+17775551212";
user.password = @"secret123";

VPError* ret = [sdk authenticateUser:user withCompletion: ^ (VPError *error, VPUser *user) {
    if (error.status == VIZpinSDK.SDK_SUCCESS) {
      // do something else, the User is all setup and can now request credentials from the server
    }
}];

if( ret != VIZpinSDK.RET_SDK_SUCCESS ) {
    // show error message or determine issue with parameters
}

Note: If a mismatch of properties is detected, you may need to call resetUser to send a new SMS verification code to the User and then call verifyUser to verify it.

Reset User

Sometimes a User may get a new device or have a mismatch between the values in the SDK and the server. Calling resetUser will cause an SMS to be sent to this User"s phone so that their device information may be verified. This can also be used to resend an SMS if the User had trouble receiving it. This is also how you can initiate a password reset. Only the phone property of the VPUser needs set for this function. Like all functions there will be a VPError return object and a function specific callback to receive the results. Below is a sample use of this function:

VPUser* user = [[VPUser alloc] init];
user.phone = @"+17775551212";

VPError* ret = [sdk resetUser:user withCompletion: ^ (VPError *error, VPUser *user) {
    if (error.status == VIZpinSDK.SDK_SUCCESS) {
      // do something else, the User is in a pending verification state waiting for `verifyUser`
    }
}];

if( ret != VIZpinSDK.RET_SDK_SUCCESS ) {
    // show error message or determine issue with parameters
}

Update User

Occassionally a User may need to change their bluetooth mode (BTC vs BLE) or User name. The User must be in an authenticated state and not in a pending verification state. Only the phone property of the VPUser must be set and the bluetooth, first and last properties are optional for this function. Like all functions there will be a VPError return object and a function specific callback to receive the results. Below is a sample use of this function:

VPUser* user = [[VPUser alloc] init];
user.phone = @"+17775551212";
user.bluetooth = VIZpinSDK.BLE_MODE;

VPError* ret = sdk.updateUser:user withCompletion: ^ (VPError *error, VPUser *user) {
    if (error.status == VIZpinSDK.SDK_SUCCESS) {
      // do something else, the User is in a good state. If you changed bluetooth modes, you will need to force a refresh of credentials
    }
}];

if( ret != VIZpinSDK.RET_SDK_SUCCESS ) {
    // show error message or determine issue with parameters
}

Request Access

A User must complete registration before they can be granted access. This function provides a way to allow a User to notify the Account manager that they have finished registration and want access. Each Account on the server is given a unique seven character code that can be given to future Users to be entered when they finish. The User must be in an authenticated state and not in a pending verification state. This function only takes the location ID for this specific Account. Like all functions there will be a VPError return object and a function specific callback to receive the results. Below is a sample use of this function:

VPError* ret = [sdk requestAccess:@"ABC-123" withCompletion: ^ (VPError *error, VPUser *user) {
    if (error.status == VIZpinSDK.SDK_SUCCESS) {
      // do something else, the manager of this account will be notified of the User"s request
    }
}];

if( ret != VIZpinSDK.RET_SDK_SUCCESS ) {
    // show error message or determine issue with parameters
}

Get Credentials

We generally refer to our VPCredentials as VIZpins on the server but the SDK is designed to abstract that slightly. Each VPCredential has a private version that is stored in the SDK. This includes the encrypted token that allow for this VPCredential to unlock one of our VP1 or EZ1 Readers. This private version may also contain confiugration messages (called LINK messages) that will be sent automatically to a Reader during the unlock process. The VPCredentials you will receive from this function contain a lot of metadata describing this VPCredential. This will include address and contact information (entered in the Account on the server previously) and the name given to the Reader. It will also contain a number of timestamps which describe the current access window start/stop times, when it expires, the local time at the Reader, timezone of the Reader, etc. These are all integer seconds since January 1, 1970 and in UMT. Depending on the Account type on the server, some VPCredentials will have varying expiration and others may need reverified from the server every 4 hours. The getCredentials function will handle this for you automatically using local cache when it can and connecting to the server as needed but you can override this behavior. You have the ability to force retrieval from local cache; this is useful at startup or when the device has no connection. You also have the ability to force a refresh from the server; this is useful on a User requested refresh or if you want to force the download of LINK messages to send to the Reader.

VPCredential new field: keyStatus
VPCredential Status of the Key values can be: ACTIVE, INACTIVE or EXPIRED.

Periodically call getCredentials from your app to get the updated keys and status of the keys. Make sure that every time you do this, the user interface gets updated. Recommended frequency of call to getCredentials from the app is every minute.

Like all functions there will be a VPError return object and a function specific callback to receive the results.

Below is a sample use of this function:

VPError* ret = [sdk getCredentials:NO withRefresh:YES withCompletion: ^ (VPError *error, NSDictionary *credentials) {
    if (error.status == VIZpinSDK.SDK_SUCCESS) {
      // do something else, you now have a list of the Credentials this User can use to unlock our Readers
    }
}];

if( ret != VIZpinSDK.RET_SDK_SUCCESS ) {
    // show error message or determine issue with parameters
}

Note: Each entry in the HashMap is for a different Reader and the key is their serial number (e.g. 8000001 or 4000001). The list that is the value for this entry will contain all the VPCredentials that this User has for this Reader. It most cases this will be a list of one item but certain modes allow for multiple VPCredentials per Reader.

Note: The VPCredential object has a function called getStatus that will interpret the timestamps concerning access for you. It takes a single parameter which is the time the SDK has that matches the server. You can get this time from the SDK via the getServerTime function. This function will return one of three statuses: VPCredential.EXPIRED (usually happens if you forceCache, requires a forceRefresh update call to the getCredentials function), VPCredential.INACTIVE (you have a valid VPCredential but are outside of its access window, this may be a 9-5 access but it is midnight), VPCredential.ACTIVE (you have valid VPCredential and can use it in a call to unlockReader)

Note: As the time is constantly changing, it is generally advised to set a timer for once a minute to call getCredentials with both parameters false to make sure you have the most up to date Credentials and statuses

Detect Readers

The SDK has the ability to scan continuosly for VP1 and EZ1 Readers while your app is running. This helpful as you can know what Readers are in range and available for unlocking and also to hide or disable VPCredentials in your UI for Readers that are not available. This function will start and also stop the scan. In general, you should expect the callback function to be called approximately every two seconds with the current list of VPReaders that are detected. The VPReader contains its serial number and other status information obtained from the bluetooth advertisements and has an indicator of signal strength will can be used for sorting, etc. Using this function is not a prerequsite for calling unlockReader but it makes for a better UX and will speed up your unlocks. You must supply the same callback object for the stop invocation that you sent for the start invocation. Like all functions there will be a VPError return object and a function specific callback to receive the results. Below is a sample use of this function:

VPError* ret = [sdk detectReaders:YES withCompletion: ^ (VPError *error, NSArray *readers) {
    if (error.status == VIZpinSDK.SDK_SUCCESS) {
      // do something else, you now have a list of the VPReaders nearby, you can update your UI to show this visually
    }
}];

if( ret != VIZpinSDK.RET_SDK_SUCCESS ) {
    // show error message or determine issue with parameters
}

Note: You must call detectReader with false to stop the scan when your application goes to the background. You can call it again with true when you application becomes active. You should not expect to receive continuous updates on the callback while your application is not active. The operating system will turn off our bluetooth access in the background as this is very costly to battery life.

Unlock Reader

Unlocking a Reader is one of the key functions of the SDK. You must supply a VPCredential that has the serial number of the VPReader that you want to unlock. If you have previously called detectReaders, the serial number of the reader you want to unlock must be in local cache or you will receive an error. If you haven"t called detectReaders, then the SDK will do a several second scan to try to detect the VPReader that you are trying to unlock. If it isn"t found in that time, you will receive a failure status in the callback. When the unlock has happened and the Reader object has informed us of success, you will receive the status in the callback. You can NOT unlock two VPReaders at the same time. You must wait for the one to complete before unlocking another one. You will receive an error in another unlock is still in progress. Keep in mind that part of the unlock process is to send configuration LINK messages to the Reader and to pull audits from the Reader which are then sent to the server.

UnlockReader now checks the status of the key, and cancels the unlock if the status of the key is EXPIRED or INACTIVE.

Like all functions there will be a VPError return object and a function specific callback to receive the results. Below is a sample use of this function:

VPError* ret = sdk.unlockReader:credential withCompletion: ^ (VPError *error, VPCredential *credential) {
    if (error.status == VIZpinSDK.SDK_SUCCESS) {
      // do something else, you now have a status of whether your unlock operation was a success or failure
    }
}];

if( ret != VIZpinSDK.RET_SDK_SUCCESS ) {
    // show error message or determine issue with parameters
}

Appendix: Objects

VPError

@interface VPError : NSObject

    @property (nonatomic, readwrite) NSInteger code;
    @property (nonatomic, strong) NSString* message;
    @property (nonatomic, readwrite) NSInteger status;

    + (VPError*) errorWithCode:(NSInteger)code andMessage:(NSString*)message andStatus:(NSInteger)status;
}

VPUser

@interface VPUser : NSObject

    @property (nonatomic, strong) NSString *first;
    @property (nonatomic, strong) NSString *last;
    @property (nonatomic, strong) NSString *country;
    @property (nonatomic, strong) NSString *phone;
    @property (nonatomic, strong) NSString *password;
    @property (nonatomic) NSInteger bluetooth;
    @property (nonatomic, strong) NSString *sms;

    + (VPUser*) userWithFirst:(NSString*)first andLast:(NSString*)last andCountry:(NSString*)country andPhone:(NSString*)phone andPassword:(NSString*)password andBluetooth:(NSInteger)bluetooth andCode:(NSString*)sms;

    + (VPUser*) userWithUser:(VPUser*)user;
}

VPCredential

@interface VPCredential : NSObject

    + (int) DATE_MAX;   // 2145916800

    + (int) UNKNOWN;    // -1
    + (int) ACTIVE;     // 1
    + (int) INACTIVE;   // 2
    + (int) EXPIRED;    // 3

    @property (nonatomic, strong) NSString* reader;
    @property (nonatomic, strong) NSString* serial_number;
    @property (nonatomic, strong) NSString* identifier;
    @property (nonatomic, readwrite) NSUInteger period;

    @property (nonatomic, readwrite) NSUInteger enddate;
    @property (nonatomic, readwrite) NSUInteger endtime;
    @property (nonatomic, readwrite) NSUInteger starttime;
    @property (nonatomic, readwrite) NSUInteger expires;

    @property (nonatomic, strong) NSString* phone;
    @property (nonatomic, strong) NSString* contact;
    @property (nonatomic, readwrite) NSUInteger tzoffset;
    @property (nonatomic, strong) NSString* timezone;
    @property (nonatomic, strong) NSString* country;
    @property (nonatomic, strong) NSString* zipcode;
    @property (nonatomic, strong) NSString* state;
    @property (nonatomic, strong) NSString* city;
    @property (nonatomic, strong) NSString* street1;
    @property (nonatomic, strong) NSString* street2;
    @property (nonatomic, strong) NSString* name;
    @property (nonatomic, strong) NSString* note;

    // returns one of the states defined above
    - (int) getStatus:(long)now;
}

VPReader

@interface VPReader : NSObject

    @property (nonatomic, readwrite) NSInteger strength;
    @property (nonatomic, readwrite) NSInteger threshold;
    @property (nonatomic, strong) NSString *serial_number;
    @property (nonatomic, strong) NSString *firmware_version;
    @property (nonatomic, readwrite) NSInteger timestamp;
    @property (nonatomic, strong) NSString *rawstatus;
    @property (nonatomic, readwrite) NSInteger status;
    @property (nonatomic, readwrite) BOOL hasAudits;
    @property (nonatomic, readwrite) BOOL hasMessages;

    + (int) READER_STATUS_NO;
    + (int) READER_STATUS_YES;
    + (int) READER_STATUS_UNKNOWN;

    + (int) READER_STATUS_DOOR_CLOSED;
    + (int) READER_STATUS_DOOR_LOCKED;
    + (int) READER_STATUS_UNUSED;
    + (int) READER_STATUS_DOOR_PROPPED;
    + (int) READER_STATUS_WIEGAND_SENT;
    + (int) READER_STATUS_TRIGGER_ON;
    + (int) READER_STATUS_HAS_AUDITS;
    + (int) READER_STATUS_BAD_UNLOCK;

    + (VPReader*) readerWithSerial:(NSString*)serial andFirmware:(NSString*)firmware andSignal:(NSInteger)signal andRange:(NSInteger)range;

    - (int) getStatus:(int)mask;
}

Appendix: Errors

static VPError* RET_SDK_SUCCESS = nil;
static VPError* RET_BT_SUCCESS = nil;
static int UNKNOWN_TIME = 0;

// This section is general SDK error codes
static int SDK_FAILURE              = 900;
static int SDK_SUCCESS              = 901;
static int SDK_UNKNOWN              = 902;
static int SDK_NO_CONNECTION        = 903;
static int SDK_INVALID_APP_ID       = 904;
static int SDK_INVALID_BT_MODE      = 905;
static int SDK_NO_BLE_SUPPORT       = 906;
static int SDK_INVALID_CALLBACK     = 907;
static int SDK_PARAMETERS_EMPTY     = 908;
static int SDK_INVALID_USER         = 909;
static int SDK_WRONG_CALLBACK       = 910;
static int SDK_NO_VIZPIN_FOUND      = 911;
static int SDK_READERS_FOUND        = 912;
static int SDK_UNKNOWN_ERROR        = 998;
static int SDK_UNKNOWN_EXCEPTION    = 999;

// This section is SDK error codes relating to bluetooth operations
static int BT_UNLOCK_SUCCESS    = 800;
static int BT_CONNECTION_LOST   = 801;
static int BT_INVALID_UNLOCK    = 802;
static int BT_LOCK_SUCCESS      = 803;
static int BT_INVALID_LOCK      = 804;
static int BT_READER_NOT_FOUND  = 805;
static int BT_LINK_COMPLETE     = 806;
static int BT_AUDIT_RECEIVED    = 807;
static int BT_BLUETOOTH_NOT_ON  = 808;
static int BT_BLUETOOTH_IN_USE  = 809;
static int BT_ALREADY_UNLOCKING = 810;
static int BT_ALREADY_SCANNING  = 811;
static int BT_READER_SCAN_ERROR = 812;
static int BT_WRONG_CALLBACK    = 813;
static int BT_LOCK_ERROR        = 897;
static int BT_UNLOCK_ERROR      = 898;
static int BT_UKNOWN_ERROR      = 899;

// This section is SDK error codes received from hitting the server
static int API_UNKNOWN          = -1;
static int API_ERROR            = 0;
static int API_SUCCESS          = 1;
static int API_EXISTING_PHONE   = 2;
static int API_ILLEGAL_PHONE    = 3;
static int API_BAD_PASSWORD     = 4;
static int API_BAD_COUNTRY      = 5;
static int API_BAD_PHONE        = 6;
static int API_BAD_CHALLENGE    = 7;
static int API_USER_ADDED       = 8;
static int API_USER_LOGIN       = 9;
static int API_VERIFY_FAILED    = 10;
static int API_VERIFY_SMS_SENT  = 11;
static int API_VERIFY_SUCCESS   = 12;
static int API_USER_UPDATE      = 13;
static int API_ACCESS_ERROR     = 14;
static int API_UNKNOWN_SITE     = 15;
static int API_VIZPINS_SUCCESS  = 16;
static int API_ACCESS_SUCCESS   = 17;
static int API_INVALID_AUDIT    = 18;
static int API_AUDIT_SUCCESS    = 19;
static int API_BAD_SESSION      = 20;
static int API_UNKNOWN_API      = 21;
static int API_USER_ADDED_SMS   = 22;
static int API_USER_UPDATE_SMS  = 23;
static int API_VERIFY_PENDING   = 24;
static int API_TIME_SUCCESS     = 30;    
static int API_INVALID_KEY 	    = 90;
static int API_INVALID_APP 	    = 91;
static int API_INVALID_IP   	= 92;
static int API_UNKNOWN_ERROR    = 99;

SDK Download