def on_state_change(self, state): if state == 'poweredOn': Logger.info(LOCATION, 'Bluetooth powered on. Starting advertising...') BluetoothService.start_advertising(self) else: Logger.info(LOCATION, 'Bluetooth is powered off. Stopping advertising...') BluetoothService.stop_advertising(self)
def generate_qr_code(self): Logger.info(LOCATION, 'Generating QR Code for alternative pairing...') device_information = self.configuration.get_device_information() image = qrcode.make(json.dumps(device_information), image_factory=PymagingImage) Store.save_qrcode(image) Logger.success(LOCATION, 'QR Code successfully generated')
def on_advertising_start(self, error): if error: Logger.error(LOCATION, 'Failed to start advertising.') else: Logger.info(LOCATION, 'Successfully started advertising.') if not error: bleno.setServices([blenoService], self.on_set_services)
def resume_configuration(self): device_status = self.configuration.get_device_status() Logger.info(LOCATION, 'DeviceStatus = ' + device_status.value) if device_status == DeviceStatus.NEW: self.pair() if device_status == DeviceStatus.PAIRED: self.activate()
def initialize_configuration(self, maker_id): 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 (status == 'yes'): device_status = DeviceStatus.MULTIPAIR.value print('Enter your alternativeID:') aid = input() else: device_status = DeviceStatus.NEW.value print('Enable Bluetooth (yes/no; default is yes)') bluetooth = input() if bluetooth == 'no': bluetooth_enabled = False else: bluetooth_enabled = True # Added alternative id as an argument to initializing the configuration self.configuration.initialize(maker_id, device_id, device_status, bluetooth_enabled, aid, public_key, private_key) self.configuration_store.save(self.configuration) self.generate_qr_code() Logger.success(LOCATION, 'Configuration successfully initialized.')
def onReadRequest(self, offset, callback): if not offset: Logger.info(LOCATION, 'Device Network info being read by connected device.') interfaces = netifaces.interfaces() address = '' interface_name = '' for iface in interfaces: addrs = netifaces.ifaddresses(iface) # Skip local address if not iface.startswith('lo'): try: # Filter only ipv4 address ipv4Address = addrs[netifaces.AF_INET] for ipv4 in ipv4Address: address = ipv4['addr'] interface_name = iface except Exception as exception: # Filter only ipv4 address pass data = { 'network': address, 'ip': interface_name } self.networkData.extend(map(ord, json.dumps(data))) #Return the necessary network related data through the callback callback(Characteristic.RESULT_SUCCESS, self.networkData[offset:])
def on_post(self, request, response): configuration = self.configuration_store.get() if configuration.get_device_status() is not DeviceStatus.ACTIVE: error = 'Not allowed to trigger actions when device is not activated.' Logger.error(LOCATION, error) raise falcon.HTTPForbidden(description=error) Logger.info(LOCATION, INCOMING_REQUEST + METHOD_POST + ' ' + ACTIONS_ENDPOINT) data = request.media if ACTION_ID not in data.keys(): Logger.error( LOCATION, 'Missing parameter `' + ACTION_ID + '` for ' + METHOD_POST + ' ' + ACTIONS_ENDPOINT) raise falcon.HTTPBadRequest action_id = data[ACTION_ID] value = data[VALUE_KEY] if VALUE_KEY in data.keys() else None success = self.action_service.trigger(action_id, value) if success: response.media = {'message': 'Action triggered'} else: raise falcon.HTTPServiceUnavailable
def run(self): Logger.info(LOCATION, 'Starting to pair device...') for tries in range(1, MAXIMUM_TRIES + 1): Logger.info(LOCATION, 'Pairing device, attempt: ' + str(tries)) if self.pair(): return True time.sleep(POLLING_INTERVAL_IN_SECONDS) return False
def onReadRequest(self, offset, callback): if not offset: data = {'BoT': 'Configuration Done'} Logger.info( LOCATION, 'Connected device configuration complete. ' + 'Start pairing process...') self.configureData.extend(map(ord, json.dumps(data))) PairingService().run() callback(Characteristic.RESULT_SUCCESS, self.configureData[offset:])
def initialize_configuration(self, maker_id): Logger.info(LOCATION, 'Initializing configuration...') public_key, private_key = KeyGenerator().generate_key() device_id = self.key_generator.generate_uuid() device_status = DeviceStatus.NEW.value self.configuration.initialize(maker_id, device_id, device_status, public_key, private_key) self.configuration_store.save(self.configuration) self.generate_qr_code()
def on_get(self, request, response): Logger.info(LOCATION, "Serving Activation Request...") configuration = self.configuration_store.get() if configuration.get_device_status() is DeviceStatus.ACTIVE: error = 'Device is already activated' Logger.error(LOCATION, error) raise falcon.HTTPBadRequest(description=error) else: self.configuration_service.resume_configuration()
def on_get(self, request, response): 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 falcon.HTTPForbidden(description=error) response.media = configuration.get_device_information subprocess.Popen(['make', 'pair'])
def get_actions(self): Logger.info(LOCATION, 'Retrieving actions...') try: actions = self.bot_service.get(ACTIONS_ENDPOINT) Logger.success(LOCATION, 'Successfully retrieved ' + str(len(actions)) + ' action(s) from server') Store.set_actions(actions) return actions except falcon.HTTPServiceUnavailable: Logger.warning(LOCATION, 'Unable to retrieve actions from server. Loading locally stored action(s)...') actions = self.store.get_actions() Logger.success(LOCATION, 'Successfully loaded ' + str(len(actions)) + ' cached action(s)') return actions
def onAdvertisingStart(self, error): if error: Logger.error(LOCATION, 'Failed to start advertising.') else: Logger.info(LOCATION, 'Successfully started advertising.') if not error: def on_setServiceError(error): if error: Logger.error(LOCATION, 'setServices: ', error) else: Logger.info(LOCATION, 'Successfully set services.') bleno.setServices([blenoService], on_setServiceError)
def pair(self): try: response = self.bot_service.get(RESOURCE) Logger.info(LOCATION, 'Pairing Response: ' + str(response)) # TODO : Make exception more specific except: Logger.error(LOCATION, 'Failed pairing attempt.') return False if response['status'] is True: Logger.success(LOCATION, 'Device successfully paired.') return True else: Logger.error(LOCATION, 'Failed pairing attempt.') return False
def trigger(self, action_id, value=None): Logger.info(LOCATION, 'Triggering action: ' + action_id) action = self._get_action(action_id) self._validate_frequency(action) Logger.success(LOCATION, 'Action valid') data = self.create_trigger_body(action_id, value) try: self.bot_service.post(ACTIONS_ENDPOINT, data) Logger.success(LOCATION, 'Successfully triggered action: ' + action_id) self.store.set_last_triggered(action_id, time.time()) return True except: Logger.error(LOCATION, 'Unable to trigger action: ' + action_id) return False
def onWriteRequest(self, data, offset, without_response, callback): if offset: callback(Characteristic.RESULT_ATTR_NOT_LONG) else: callback(Characteristic.RESULT_SUCCESS) if offset > len(data): callback(bleno.Characteristic.RESULT_INVALID_OFFSET) Logger.error(LOCATION, 'Error in Characteristic') else: callback(Characteristic.RESULT_SUCCESS) #decode the byte sequence sent from the client and prepare a JSON structure details = json.loads(data.decode()) #skip wifi configuration skip_wifi_config = False try: skip_wifi_config = details['Skip'] except: Logger.info(LOCATION, 'Wifi Configuration is available ..') #wifi configuration is enabled from the client if skip_wifi_config == True: Logger.info( LOCATION, 'Connected device skipped Wifi setup. ' + 'Initializing pairing process...') PairingService().run() else: wifi_details = '' #if valid SSID provided then create the wpa supplicant configuration. if details['SSID'] != '': wifi_details = 'ctrl_interface=DIR=/var/run/wpa_supplicant' + \ ' GROUP=netdev\r\n update_config=1\r\n country=GB \r\n'+ \ 'network={ \r\n ssid="' + details['SSID'] + \ '" \r\n' + \ ' psk="' + details['PWD'] + \ '" \r\n ' + \ 'key_mgmt=WPA-PSK \r\n}' Logger.info( LOCATION, 'Wifi setup complete. Initializing pairing process...') PairingService().run() time.sleep(3) subprocess.run([ 'sudo echo \'' + wifi_details + '\' > ./wpa_supplicant.conf' ], shell=True) subprocess.run([ "sudo", "cp", "./wpa_supplicant.conf", "/etc/wpa_supplicant/" ]) Logger.info( LOCATION, 'Wifi configuration done! Device reboot in progress') # run the necessary command to update the wpa supplicant file with in /etc/ subprocess.run(["sudo", "rm", "./wpa_supplicant.conf"]) subprocess.run(["sudo", "sleep", "1"]) # reboot the device after successful update of wpa suppicant configuration subprocess.run(["sudo", "reboot"])
def onWriteRequest(self, data, offset, withoutResponse, callback): if offset: callback(Characteristic.RESULT_ATTR_NOT_LONG) else: callback(Characteristic.RESULT_SUCCESS) if offset > len(data): callback(bleno.Characteristic.RESULT_INVALID_OFFSET) Logger.error(LOCATION, 'Error in Characteristic') else: callback(Characteristic.RESULT_SUCCESS, data[offset:]) details = json.loads(data) if details['Skip'] == True: Logger.info( LOCATION, 'Connected device skipped Wifi setup. ' + 'Initializing pairing process...') PairingService().run() else: wifiDetails = '' if details['SSID'] != '': wifiDetails = 'ctrl_interface=DIR=/var/run/wpa_supplicant' + \ ' GROUP=netdev\r\n update_config=1\r\n country=GB \r\n'+ \ 'network={ \r\n ssid="' + details['SSID'] + \ '" \r\n' + \ ' psk="' + details['PWD'] + \ '" \r\n ' + \ 'key_mgmt=WPA-PSK \r\n}' Logger.info( LOCATION, 'Wifi setup complete. Initializing pairing process...') PairingService().run() time.sleep(3) subprocess.run([ 'sudo echo \'' + wifiDetails + '\' > ./wpa_supplicant.conf' ], shell=True) subprocess.run([ "sudo", "cp", "./wpa_supplicant.conf", "/etc/wpa_supplicant/" ]) subprocess.run(["sudo", "rm", "./wpa_supplicant.conf"]) subprocess.run(["sudo", "sleep", "1", "&&", "reboot"])
def onReadRequest(self, offset, callback): if not offset: configuration = self.configuration_store.get() Logger.info(LOCATION, 'Device data being read by connected device.') device_status = configuration.get_device_status() device_information = configuration.get_device_information() data = { 'deviceID': device_information['deviceID'], 'makerID': device_information['makerID'], 'name': socket.gethostname(), 'publicKey' : device_information['publicKey'] } # Multipairing mode checks if(device_status == DeviceStatus.MULTIPAIR): data['multipair'] = 1 data['aid'] = device_information['aid'] self.deviceData.extend(map(ord, json.dumps(data))) Logger.info(LOCATION, json.dumps(data)) #Return through the callback the necessary data callback(Characteristic.RESULT_SUCCESS, self.deviceData[offset:])
def onReadRequest(self, offset, callback): if not offset: Logger.info(LOCATION, 'Device Info being read by connected device.') total_memory = virtual_memory() endian = sys.byteorder data = { 'platform':platform.system(), 'release':platform.release(), 'type': platform.uname().system, 'arch': platform.uname().machine, 'cpus': json.dumps(os.cpu_count()), 'hostname': socket.gethostname(), 'totalMemory': total_memory.total } # Mapping endian value to match with node sdk if endian == 'little': data['endianness'] = 'LE' else: data['endianness'] = 'BE' self.byteData.extend(map(ord, json.dumps(data))) callback(Characteristic.RESULT_SUCCESS, self.byteData[offset:])
def run(self): if not self.can_pair: return if self.device_status == DeviceStatus.MULTIPAIR: Logger.info(LOCATION, 'Multipair mode, no need to poll or delete keys...') return Logger.info(LOCATION, 'Starting to pair device...') for tries in range(1, MAXIMUM_TRIES + 1): Logger.info(LOCATION, 'Pairing device, attempt: ' + str(tries)) if self.pair(): return True time.sleep(POLLING_INTERVAL_IN_SECONDS) return False
def check_and_resume_configuration(): configuration = ConfigurationStore().get() device_status = configuration.get_device_status() system_platform = platform.system() Logger.info(LOCATION, "Detected Platform System: " + system_platform) if device_status is DeviceStatus.ACTIVE: Logger.info(LOCATION, "Device is already active, no need to further configure") Logger.info(LOCATION, "Server is waiting for requests to serve...") Logger.info( LOCATION, "Supported Endpoints: /qrcode /actions /pairing /activate" ) elif device_status is DeviceStatus.PAIRED: Logger.info(LOCATION, "Device state is PAIRED, resuming the configuration") ConfigurationService().resume_configuration() elif device_status is DeviceStatus.MULTIPAIR and PairingService().pair(): Logger.info( LOCATION, "Device is paired as MULTIPAIR, Server is waiting for requests to serve..." ) else: Logger.info( LOCATION, "Pair the device either using QRCode or Bluetooth Service through FINN Mobile App" ) if system_platform != 'Darwin' and configuration.is_bluetooth_enabled( ): from bot_python_sdk.bluetooth_service import BluetoothService # Handle BLE specific events and callbacks BluetoothService().initialize() ConfigurationService().resume_configuration()
def on_get(self, request, response): Logger.info(LOCATION, "Serving base endpoint request...") response.body = '{"message": "BoT-Python-SDK Webserver", "endpoints" : "/qrcode /actions /pairing /activate" }' response.status = falcon.HTTP_200
def generate_key(self): Logger.info('Key Generator', 'Generating KeyPair...') key = PKey() key.generate_key(TYPE_RSA, 1024) key = key.to_cryptography_key() return self._public_key(key), self._private_key(key)
def on_setServiceError(error): if error: Logger.error(LOCATION, 'setServices: ', error) else: Logger.info(LOCATION, 'Successfully set services.')
def on_get(self, request, response): Logger.info(LOCATION, "Serving QRCode Request...") stream = open(QRCODE_IMG_PATH, 'rb') content_length = os.path.getsize(QRCODE_IMG_PATH) response.content_type = "image/png" response.stream, response.content_length = stream, content_length
def on_get(self, request, response): Logger.info(LOCATION, INCOMING_REQUEST + METHOD_GET + ' ' + ACTIONS_ENDPOINT) response.media = self.action_service.get_actions()
# Function to validate the given IP Address def is_valid(ip): regex = '''^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.( 25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.( 25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.( 25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)''' return re.search(regex, ip) if not store.has_configuration(): if len(sys.argv) <= 1: exit('Please add your makerID to configure the SDK: "make server makerID=YOUR_MAKER_ID"') maker_id = sys.argv[1] # 1 -> First argument after server.py configuration_service.initialize_configuration(maker_id) # If OS is windows based, it doesn't support gunicorn so we run waitress if os.name == 'nt': subprocess.run(['waitress-serve', '--port=3001', 'bot_python_sdk.api:api']) else: cmd = subprocess.Popen(['hostname', '-I'], stdout=subprocess.PIPE) ip = cmd.communicate()[0].decode('ascii').split(' ')[0] if is_valid(ip): Logger.info(LOCATION, "Detected IP Address :" + ip) else: Logger.info(LOCATION, "Failed in detecting valid IP Address, using loop back address: 127.0.0.1") ip = '127.0.0.1' Logger.info(LOCATION, "Starting Webserver at URL: http://" + ip + ':3001/') subprocess.run(['gunicorn', '-b', ip + ':3001', 'bot_python_sdk.api:api'])