diff --git a/bot_python_sdk/action_service.py b/bot_python_sdk/action_service.py index 1944f00..9d827d8 100644 --- a/bot_python_sdk/action_service.py +++ b/bot_python_sdk/action_service.py @@ -39,7 +39,7 @@ def get_actions(self): Logger.success(LOCATION, 'Successfully loaded ' + str(len(actions)) + ' cached action(s)') return actions - def trigger(self, action_id, value=None, alternative_id=None): + def trigger(self, action_id, value=None, alternative_id=None, queue_id=None): Logger.info(LOCATION, 'Triggering action: ' + action_id) action = self._get_action(action_id) self._validate_frequency(action) diff --git a/bot_python_sdk/api.py b/bot_python_sdk/api.py index 5fcf71a..4b1dc1d 100644 --- a/bot_python_sdk/api.py +++ b/bot_python_sdk/api.py @@ -89,7 +89,7 @@ def __init__(self): def on_get(self): self.configuration_service.resume_configuration() - + api = application = falcon.API() api.add_route(ACTIONS_ENDPOINT, ActionsResource()) @@ -101,3 +101,4 @@ def on_get(self): #Initialize the Bluetooth service class to process #handle BLE specific envents and callbacks BluetoothService().initialize() +ConfigurationService().resume_configuration() diff --git a/bot_python_sdk/bleno/configure.py b/bot_python_sdk/bleno/configure.py index 4791333..19db8c8 100644 --- a/bot_python_sdk/bleno/configure.py +++ b/bot_python_sdk/bleno/configure.py @@ -90,5 +90,5 @@ def onReadRequest(self, offset, callback): Logger.info(LOCATION,'Connected device configuration complete. ' + 'Start pairing process...') self.configureData.extend(map(ord, json.dumps(data))) - PairingService().run() + #PairingService().run() callback(Characteristic.RESULT_SUCCESS, self.configureData[offset:]) diff --git a/bot_python_sdk/bot_api_service.py b/bot_python_sdk/bot_api_service.py new file mode 100644 index 0000000..0b244a8 --- /dev/null +++ b/bot_python_sdk/bot_api_service.py @@ -0,0 +1,219 @@ +"""@package docstring +BOT API service module provides interface for applications to interact with BOT Services. +Applications have to import the module and create object of the BotApiService class. +Then call the corresponding API's and classes to interface with BOT Service +""" +import os +import sys +import json +from bot_python_sdk.store import Store +from bot_python_sdk.action_service import ActionService +from bot_python_sdk.configuration_service import ConfigurationService +from bot_python_sdk.configuration_store import ConfigurationStore +from bot_python_sdk.device_status import DeviceStatus +from bot_python_sdk.logger import Logger + +from bot_python_sdk.bluetooth_service import BluetoothService + +## Global variables to interface with BOT Services. +LOCATION = 'Controller' +INCOMING_REQUEST = 'Incoming request: ' + +DEVICE_ID_KEY = 'deviceId' +MAKER_ID_KEY = 'makerId' +PUBLIC_KEY_KEY = 'publicKey' + +ACTION_ID = 'actionID' +VALUE_KEY = 'value' +ALTERNATIVER_ID ='alternativeID' + +METHOD_GET = 'get' +METHOD_POST = 'set' +ACTIONS_ENDPOINT = 'actions' +PAIRING_ENDPOINT = 'pairing' +ACTIVATION_ENDPOINT = 'activation' + +class BoTerror(Exception): + + """BoT API service Exception class, using which all the exceptions are created. + """ + pass + +class DeviceForbiddenError(BoTerror): + + """BoT API service Device Forbidden Exception. This shall be triggered when + the device status is not active + """ + def __init__(self, error): + """The constructor. Stores the error message which the application can access """ + self.msg = error + +class InvalidRequestError(BoTerror): + + """BoT API service Invalid Request Exception. This shall be triggered when + the actionID or alternateID is missing from the action trigger request. + """ + def __init__(self, error): + """The constructor. Stores the error message which the application can access """ + self.msg = error + +class ServiceUnavailableError(BoTerror): + + """BoT API service Service Not Available Exception. This shall be triggered when + the triggered action request is failed from the BOT service. + """ + def __init__(self, error): + """The constructor. Stores the error message which the application can access """ + self.msg = error + +class BoTinputError(BoTerror): + + """BoT API service no imput provided Exception. This shall be triggered when + a new device pairing is requested with out maker id being provided. + """ + def __init__(self, error): + """The constructor. Stores the error message which the application can access """ + self.msg = error + +class MissingInputError(BoTerror): + + """BoT API service Missing Input Exception. This shall be triggered when + either multipairing is enabled and alternative ID is 0 or invalid. + """ + def __init__(self, error): + """The constructor. Stores the error message which the application can access """ + self.msg = error + +class NetworkUnavailableError(BoTerror): + + def __init__(self, error): + """The constructor. Stores the error message which the application can access """ + self.msg = error + +class ActionsTriggerStoreError(BoTerror): + + def __init__(self, error): + """The constructor. Stores the error message which the application can access """ + self.msg = error + +class ActionsResource: + + """ Action Resource class is responsible for interfacing with Action Service and + either get the actions requested or trigger the actions. + """ + def __init__(self): + """ Constructor. Initializes/creates the action service and configuration store objects""" + """@var action_service : stores action service object""" + """@var configuration_store : stores configuration store object""" + self.action_service = ActionService() + self.configuration_store = ConfigurationStore() + + ## Gets the list of actions from the action service/makerid server. + # @param self The object pointer. + def get_actions(self): + Logger.info(LOCATION, INCOMING_REQUEST + METHOD_GET + ' ' + ACTIONS_ENDPOINT) + response = self.action_service.get_actions() + return response + + ## Triggers the actions. + # @param self The object pointer. + # @param actions_to_set actions to perform (dictionary). + def trigger_actions(self, actions_to_set): + configuration = self.configuration_store.get() + device_status = configuration.get_device_status() + + # if device status is not active/multipair, then its an ivalid state to trigger action. + if device_status is not DeviceStatus.ACTIVE and device_status is not DeviceStatus.MULTIPAIR: + error = 'Not allowed to trigger actions when device is not activated.' + Logger.error(LOCATION, error) + raise DeviceForbiddenError(error) + + Logger.info(LOCATION, INCOMING_REQUEST + METHOD_POST + ' ' + ACTIONS_ENDPOINT) + data = actions_to_set + + #If actionID is missing then throw an error + if ACTION_ID not in data.keys(): + Logger.error(LOCATION, 'Missing parameter `' + ACTION_ID + '` for ' + METHOD_POST + ' ' + ACTIONS_ENDPOINT) + raise InvalidRequestError('Missing parameter') + + if device_status is DeviceStatus.MULTIPAIR: + if ALTERNATIVER_ID not in data.keys(): + Logger.error(LOCATION, 'Missing parameter `' + ALTERNATIVER_ID + '` for ' + METHOD_POST + ' ' + ACTIONS_ENDPOINT) + raise InvalidRequestError('Missing parameter alternativeID') + + action_id = data[ACTION_ID] + value = data[VALUE_KEY] if VALUE_KEY in data.keys() else None + alternative_id = data[ALTERNATIVER_ID] if ALTERNATIVER_ID in data.keys() else None + + success = self.action_service.trigger(action_id, value, alternative_id) + response = '' + if success: + response = {'message': 'Action triggered'} + return response + else: + error = 'action service trigger failed, service not available' + raise ServiceUnavailableError(error) + + +class PairingResource: + + """ Pairing Resource class is responsible for interfacing with Pairing Service to + get the pairing status of the device with the FINN application. + """ + def __init__(self): + """ Constructor. Initializes/creates the configuration store and configuration service objects""" + """@var configuration_service : stores configuration service object""" + """@var configuration_store : stores configuration store object""" + self.configuration_store = ConfigurationStore() + self.configuration_service = ConfigurationService() + + def get_pairing_status(self): + Logger.info(LOCATION, INCOMING_REQUEST + METHOD_GET + ' ' + PAIRING_ENDPOINT) + configuration = self.configuration_store.get() + if configuration.get_device_status() is not DeviceStatus.NEW: + error = 'Device is already paired.' + Logger.error(LOCATION, error) + raise DeviceForbiddenError(error) + device_information = configuration.get_device_information() + response = json.dumps(device_information) + #subprocess.Popen(['make', 'pair']) + self.configuration_service.pair() + return response + + +class ActivationResource: + + def __init__(self): + self.configuration_service = ConfigurationService() + + def activate_service(self): + self.configuration_service.resume_configuration() + +class BoTApiService(): + + """ BOT API Service class provides interfaces for BOT SDK. Applications can + import this class and call the api's to talk to BOT service + """ + def __init__(self, makerid, multipairing='no', aid=0): + ## Constructor + self.makerID = makerid + self.multipairStatus = multipairing + self.aid = aid + + def start(self): + configuration_service = ConfigurationService() + store = Store() + + if( (self.multipairStatus == 'yes') and (self.aid == 0)): + raise MissingInputError('Alternate ID is missing') + + if not store.has_configuration(): + if (self.makerID == ''): # if no maker ID then raise an exception + error = 'Maker ID missing to configure the SDK' + raise BoTinputError(error) + #Module based or Server based. 0:Module based + configuration_service.initialize_configuration(self.makerID, 0, self.multipairStatus, self.aid) + + #Initialize the Bluetooth service class to process BLE specific events and callbacks + BluetoothService().initialize() + configuration_service.resume_configuration() \ No newline at end of file diff --git a/bot_python_sdk/configuration_service.py b/bot_python_sdk/configuration_service.py index 697cef8..5a22f5e 100644 --- a/bot_python_sdk/configuration_service.py +++ b/bot_python_sdk/configuration_service.py @@ -19,22 +19,31 @@ def __init__(self): self.configuration = self.configuration_store.get() self.key_generator = KeyGenerator() - def initialize_configuration(self, maker_id): + def initialize_configuration(self, maker_id, sever=1, multipair='no', altId=0): Logger.info(LOCATION, 'Initializing configuration...') public_key, private_key = KeyGenerator().generate_key() device_id = self.key_generator.generate_uuid() #initialize the alternative id. aid = 0 - # Option for Multi pairing - # If the option is yes, then alternative id needed - print('Enable Multi pair(yes/no)') - status = input() + #If its running as server + if (sever == 1): + # Option for Multi pairing + # If the option is yes, then alternative id needed + print('Enable Multi pair(yes/no)') + status = input() + else: + status = multipair + if(status == 'yes'): device_status = DeviceStatus.MULTIPAIR.value - print('Enter your alternativeID:') - aid = input() + if (sever == 1): + print('Enter your alternativeID:') + aid = input() + else: + aid = altId else: device_status = DeviceStatus.NEW.value + # Added alternative id as an argument to initializing the configuration self.configuration.initialize(maker_id, device_id, device_status, aid , public_key, private_key) self.configuration_store.save(self.configuration) @@ -43,7 +52,11 @@ def initialize_configuration(self, maker_id): def resume_configuration(self): device_status = self.configuration.get_device_status() - Logger.info(LOCATION, 'DeviceStatus = ' + device_status.value) + try: + Logger.info(LOCATION, 'DeviceStatus = ' + device_status.value) + except: + Logger.info(LOCATION, 'DeviceStatus = ' + device_status) + if device_status == DeviceStatus.NEW: self.pair() if device_status == DeviceStatus.PAIRED: diff --git a/examples/bot_api_access_example.py b/examples/bot_api_access_example.py new file mode 100644 index 0000000..cfd2df6 --- /dev/null +++ b/examples/bot_api_access_example.py @@ -0,0 +1,91 @@ +import time +import threading +import bot_python_sdk.bot_api_service as BotSDK +import json +from bot_python_sdk.logger import Logger + +import signal +import sys + +Component = 'BoT_Application' + +bApplicationExit = 0 +timeToSleep = 0.2 #200 ms sleep + +def handleExitEvent(sig, frame): + msg = 'User Pressed Ctrl+C! ...' + Logger.error(Component, msg) + global bApplicationExit + bApplicationExit = 1 + #sys.exit(0) + +#print('Press Ctrl+C') +#signal.pause() +signal.signal(signal.SIGINT, handleExitEvent) + +''' +def exitHanderThread(): + print("THREAD : Handle External Exit Event ... ") + global timeToSleep + global bApplicationExit + while(!bApplicationExit): + time.sleep(timeToSleep) # sleep for 200 ms + print("THREAD : Exiting the Application thread ... ") +''' +if __name__ == "__main__": + + makerid = '' + multipairingS = 'no' + aid = 0 + + if len (sys.argv) == 1: + Logger.info(Component, 'Execute w/o Maker ID ') + elif len (sys.argv) == 2: + # Maker ID provided .. + Logger.info(Component, 'Only Maker ID Provided ') + makerid = sys.argv[1] + elif len (sys.argv) < 4: + msg = 'Please provide all the parameters as \'sudo python3 bot_example01.py ' + Logger.error(Component, msg) + sys.exit(0) + else: + makerid = sys.argv[1] + multipairingS = sys.argv[2] + aid = sys.argv[3] + + + print(makerid) + print(multipairingS) + print(aid) + + #sys.exit(0) + BotSDKHndl = BotSDK.BoTApiService(makerid, multipairingS,aid) + #print("Bot Handle Created ") + Logger.info(Component, 'Bot Handle Created') + # creating thread + #tAppExit = threading.Thread(target=exitHanderThread) + + try: + BotSDKHndl.start() + #print("BotSDK API service started successfully ....") + Logger.info(Component, 'BotSDK API service started successfully ....') + except BotSDK.BoTinputError as Error: + #print('Error from BoT API Service : '+Error.msg) + Logger.error(Component, Error.msg) + sys.exit(0) + except BotSDK.MissingInputError as Error: + Logger.error(Component, Error.msg) + sys.exit(0) + + msg = 'To Exit the Test Appplication, Press Ctrl+C ...' + Logger.info(Component, msg) + + while(bApplicationExit == 0): + time.sleep(timeToSleep) # sleep for 200 ms + msg = 'Application : Exiting the Application (Ctrl+C pressed) ...' + Logger.error(Component, msg) + BotSDKHndl.start() + + time.sleep(0.5) + sys.exit(0) + diff --git a/examples/bot_trigger_example.py b/examples/bot_trigger_example.py new file mode 100644 index 0000000..f2a38f0 --- /dev/null +++ b/examples/bot_trigger_example.py @@ -0,0 +1,62 @@ +import time +import bot_python_sdk.bot_api_service as BotSDK +import json +from bot_python_sdk.logger import Logger + +Component = 'BoT_Application' + + +if __name__ == "__main__": + makerid = '' + multipairingS = 'no' + aid = 0 + BotSDKHndl = BotSDK.BoTApiService(makerid, multipairingS, aid) + #print("Bot Handle Created ") + Logger.info(Component, 'Bot Handle Created') +# try: +# BotSDKHndl.start() +# #print("BotSDK API service started successfully ....") +# Logger.info(BoT_Application, 'BotSDK API service started successfully ....') +# except BotSDKHndl.BoTinputError as Error: +# #print('Error from BoT API Service : '+Error.msg) +# Logger.error(BoT_Application, Error.msg) + + # PSOT Triggers + actionRes = BotSDK.ActionsResource() + response = actionRes.get_actions() + #print('GET Action Response : ' + json.dumps(response)) + Logger.info(Component, 'GET Action Response : ' + json.dumps(response)) + + # GET Pairing details + pairingRes = BotSDK.PairingResource() + try: + response = pairingRes.get_pairing_status() + #print('GET Pairing Response : ' + json.dumps(response)) + Logger.info(Component, 'GET Pairing Response : ' + json.dumps(response)) + except BotSDK.DeviceForbiddenError as Error: + #print('Error from BoT API Service : '+Error.msg) + Logger.error(Component, Error.msg) + + # '{"actionID":"YOUR_ACTION_ID"}' + triggerData = {"actionID":"4D047330-0066-4778-BEA9-DDC49A644583"} + #triggerData = {"actionID":"4D047330-0066-4778-BEA9-DDC49A644583","alternativeID":"123456"} + + try: + response = actionRes.trigger_actions(triggerData) + #print('GET Action Response : ' + json.dumps(response)) + Logger.info(Component, 'Actions Response : ' + json.dumps(response)) + except BotSDK.DeviceForbiddenError as Error: + #print('Error from BoT API Service : '+Error.msg) + Logger.error(Component, Error.msg) + except BotSDK.InvalidRequestError as Error: + #print('Error from BoT API Service : '+Error.msg) + Logger.error(Component, Error.msg) + except BotSDK.ServiceUnavailableError as Error: + #print('Error from BoT API Service : '+Error.msg) + Logger.error(Component, Error.msg) + + + time.sleep(10) + + + \ No newline at end of file