def __init__(self, domain=None, username=None, **kwargs): reg = ComponentRegistry() conf = ConfigManager() if domain is None: domain = conf.get('cloud:server') self.api = Api(domain=domain, **kwargs) self._domain = self.api.domain try: token = reg.get_config('arch:cloud_token') token_type = reg.get_config('arch:cloud_token_type', default='jwt') self.api.set_token(token, token_type=token_type) except ArgumentError: # If we are interactive, try to get the user to login for a single # session rather than making them call link_cloud to store a cloud token if type_system.interactive: username, password = self._prompt_user_pass(username, domain) ok_resp = self.api.login(email=username, password=password) if not ok_resp: raise ExternalError("Could not login to %s as user %s" % (domain, username)) else: raise ExternalError( "No stored iotile cloud authentication information", suggestion= 'Call iotile config link_cloud with your username and password' ) self.token = self.api.token self.token_type = self.api.token_type
def get_whitelist(self, device_id): """ Returns the whitelist associated with the given device_id if any""" api = self.api slug = device_id_to_slug(device_id) try: fleets = api.fleet.get(device=slug)['results'] except HttpNotFoundError: raise ExternalError( "Could not find the right URL. Are fleets enabled ?") if not fleets: # This is to be expected for devices set to take data from all project, or any device. raise ExternalError("The device isn't in any network !") networks = [ self.get_fleet(fleet['id']) for fleet in fleets if fleet.get('is_network', False) is True ] networks_to_manage = [ x for x in networks if x.get(slug, {}).get('is_access_point', False) is True ] out = {} for network in networks_to_manage: out.update(network) # Remove ourselves from the whitelist that we are supposed to manage if slug in out: del out[slug] if not out: raise ExternalError("No device to manage in these fleets !") return out
def _load_providers(self): """Load all config_variables providers using pkg_resources """ reg = ComponentRegistry() for name, provider in reg.load_extensions('iotile.config_variables'): try: prefix, conf_vars = provider() except (ValueError, TypeError) as exc: raise ExternalError("Error loading config variables", package=name, error=str(exc)) for var in conf_vars: if len(var) != 3 and len(var) != 4: raise ExternalError( "Error loading config variable, invalid length", data=var, package=name) name = prefix + ':' + var[0] if len(var) == 3: var_obj = ConfigVariable(var[0], var[1], var[2], MISSING) else: var_obj = ConfigVariable(name, var[1], var[2], var[3]) if name in self._known_variables: raise ExternalError( "The same config variable was defined twice", name=name) self._known_variables[name] = var_obj
def __init__(self, args): cert = args.get('certificate', None) key = args.get('private_key', None) root = args.get('root_certificate', None) endpoint = args.get('endpoint', None) iamkey = args.get('iam_key', None) iamsecret = args.get('iam_secret', None) iamsession = args.get('iam_session', None) use_websockets = args.get('use_websockets', False) try: if not use_websockets: if cert is None: raise ExternalError("Certificate for AWS IOT not passed in certificate key") elif key is None: raise ExternalError("Private key for certificate not passed in private_key key") else: if iamkey is None or iamsecret is None: raise ExternalError("IAM Credentials need to be provided for websockets auth") except ExternalError: # If the correct information is not passed in, try and see if we get it from our environment # try to pull in root certs, endpoint name and iam or cognito session information reg = ComponentRegistry() if endpoint is None: endpoint = reg.get_config('awsiot-endpoint', default=None) if root is None: root = reg.get_config('awsiot-rootcert', default=None) iamkey = reg.get_config('awsiot-iamkey', default=None) iamsecret = reg.get_config('awsiot-iamtoken', default=None) iamsession = reg.get_config('awsiot-session', default=None) if iamkey is None or iamsecret is None: raise use_websockets = True if root is None: raise ExternalError("Root of certificate chain not passed in root_certificate key (and not in registry)") elif endpoint is None: raise ExternalError("AWS IOT endpoint not passed in endpoint key (and not in registry)") self.websockets = use_websockets self.iam_key = iamkey self.iam_secret = iamsecret self.iam_session = iamsession self.cert = cert self.key = key self.root = root self.endpoint = endpoint self.client = None self.sequencer = TopicSequencer() self.queues = {} self.wildcard_queues = [] self._logger = logging.getLogger(__name__)
def _ensure_package_loaded(path, component): """Ensure that the given module is loaded as a submodule. Returns: str: The name that the module should be imported as. """ logger = logging.getLogger(__name__) packages = component.find_products('support_package') if len(packages) == 0: return None elif len(packages) > 1: raise ExternalError( "Component had multiple products declared as 'support_package", products=packages) if len(path) > 2 and ':' in path[2:]: # Don't flag windows C: type paths path, _, _ = path.rpartition(":") package_base = packages[0] relative_path = os.path.normpath(os.path.relpath(path, start=package_base)) if relative_path.startswith('..'): raise ExternalError( "Component had python product output of support_package", package=package_base, product=path, relative_path=relative_path) if not relative_path.endswith('.py'): raise ExternalError("Python product did not end with .py", path=path) relative_path = relative_path[:-3] if os.pathsep in relative_path: raise ExternalError( "Python support wheels with multiple subpackages not yet supported", relative_path=relative_path) support_distro = component.support_distribution if support_distro not in sys.modules: logger.debug("Creating dynamic support wheel package: %s", support_distro) spec = importlib.util.spec_from_file_location( os.path.basename(package_base), package_base) if spec is None: raise ExternalError("importlib cannot find module at this path", path=package_base) module = importlib.util.module_from_spec(spec) sys.modules[support_distro] = module spec.loader.exec_module(module) return "{}.{}".format(support_distro, relative_path)
def wait_reports(self, num_reports, timeout=2.0): """Wait for a fixed number of reports to be received Args: num_reports (int): The number of reports to wait for timeout (float): The maximum number of seconds to wait without receiving another report. """ if self._stream_queue is None: raise ExternalError( "You have to enable streaming before you can wait for reports") reports = [] for i in range(0, num_reports): try: report = self._stream_queue.get(timeout=timeout) reports.append(report) except Empty: raise TimeoutExpiredError("Timeout waiting for a report", expected_number=num_reports, received_number=i, received_reports=reports) return reports
def highest_acknowledged(self, device_id, streamer): """Get the highest acknowledged reading for a given streamer. Args: device_id (int): The device whose streamer we are querying streamer (int): The streamer on the device that we want info about. Returns: int: The highest reading id that has been acknowledged by the cloud """ slug = self._build_streamer_slug(device_id, streamer) try: data = self.api.streamer(slug).get() except RestHttpBaseException as exc: raise ArgumentError("Could not get information for streamer", device_id=device_id, streamer_id=streamer, slug=slug, err=str(exc)) if 'last_id' not in data: raise ExternalError( "Response fom the cloud did not have last_id set", response=data) return data['last_id']
def FindByName(cls, name): """Find an installed VirtualTile by name. This function searches for installed virtual tiles using the pkg_resources entry_point `iotile.virtual_tile`. If name is a path ending in .py, it is assumed to point to a module on disk and loaded directly rather than using pkg_resources. Args: name (str): The name of the tile to search for. Returns: VirtualTile class: A virtual tile subclass that can be instantiated to create a virtual tile. """ if name.endswith('.py'): return cls.LoadFromFile(name) for entry in pkg_resources.iter_entry_points("iotile.virtual_tile", name): obj = entry.load() if not issubclass(obj, VirtualTile): raise ExternalError( "External virtual tile could not be loaded because it does not inherit from VirtualTile" ) return obj raise ArgumentError("VirtualTile could not be found by name", name=name)
def impersonate_device(self, device_id): """Convert our token to a permanent device token. This function is most useful for creating virtual IOTile devices whose access to iotile.cloud is based on their device id, not any particular user's account. There are a few differences between device tokens and user tokens: - Device tokens never expire and don't need to be refreshed - Device tokens are more restricted in what they can access in IOTile.cloud than user tokens Args: device_id (int): The id of the device that we want to get a token for. """ slug = device_id_to_slug(device_id) token_type = IOTileCloud.DEVICE_TOKEN_TYPE try: resp = self.api.device(slug).key.get( type=IOTileCloud.DEVICE_TOKEN_TYPE) token = resp['key'] except RestHttpBaseException as exc: raise ExternalError("Error calling method on iotile.cloud", exception=exc, response=exc.response.status_code) self.api.set_token(token, token_type=token_type) self.token = token self.token_type = token_type reg = ComponentRegistry() reg.set_config('arch:cloud_token', self.token) reg.set_config('arch:cloud_token_type', self.token_type) reg.set_config('arch:cloud_device', slug)
def __init__(self, folder): self.folder = folder self.filter_prods = False modfile = os.path.join(self.folder, 'module_settings.json') try: with open(modfile, "r") as infile: settings = json.load(infile) except IOError: raise ExternalError( "Could not load module_settings.json file, make sure this directory is an IOTile component", path=self.folder) file_format = settings.get('file_format', IOTile.V1_FORMAT) if file_format == IOTile.V1_FORMAT: info = self._find_v1_settings(settings) elif file_format == IOTile.V2_FORMAT: info = self._find_v2_settings(settings) else: raise DataError("Unknown file format in module_settings.json", format=file_format, path=modfile) self._load_settings(info)
def refresh_token(self): """Attempt to refresh out cloud token with iotile.cloud.""" if self.token_type != 'jwt': raise DataError( "Attempting to refresh a token that does not need to be refreshed", token_type=self.token_type) conf = ConfigManager() domain = conf.get('cloud:server') url = '{}/api/v1/auth/api-jwt-refresh/'.format(domain) resp = self.api.session.post(url, json={'token': self.token}) if resp.status_code != 200: raise ExternalError("Could not refresh token", error_code=resp.status_code) data = resp.json() # Save token that we just refreshed to the registry and update our own token self.token = data['token'] reg = ComponentRegistry() reg.set_config('arch:cloud_token', self.token)
def run(self, refresh_interval=0.05): """Set up the loop, check that the tool is installed""" try: from asciimatics.screen import Screen except ImportError: raise ExternalError("You must have asciimatics installed to use LinebufferUI", suggestion="pip install iotilecore[ui]") Screen.wrapper(self._run_loop, arguments=[refresh_interval])
def unfreeze_extensions(self): """Remove a previously frozen list of extensions.""" output_path = os.path.join(_registry_folder(), 'frozen_extensions.json') if not os.path.isfile(output_path): raise ExternalError("There is no frozen extension list") os.remove(output_path) ComponentRegistry._frozen_extensions = None
def set_sensorgraph(self, device_id, new_sg, app_tag=None): """The the cloud's sensor graph id that informs what kind of device this is. Is app_tag is passed, verify that the sensorgraph explicitly matches the expected app_tag by making another API call. Args: device_id (int): The id of the device that we want to change the sensorgraph for. new_sg (string): Name of a valid sensorgraph that you wish to set the device to app_tag (int): Optional. The intended app_tag of the sensorgraph will be set. If the app_tag passed into this function does not match the app_tag of the sensorgraph in iotile.cloud, raise an error. """ try: sg = self.api.sg(new_sg).get() except RestHttpBaseException as exc: raise ExternalError("Error calling method on iotile.cloud", exception=exc, response=exc.response.status_code) if app_tag is not None: if sg.get('app_tag', None) != app_tag: raise ArgumentError( "Cloud sensorgraph record does not match app tag", value=new_sg, cloud_sg_app_tag=sg.get('app_tag', None), app_tag_set=app_tag) slug = device_id_to_slug(device_id) patch = {'sg': new_sg} try: self.api.device(slug).patch(patch) except RestHttpBaseException as exc: if exc.response.status_code == 400: raise ArgumentError( "Error setting sensor graph, invalid value", value=new_sg, error_code=exc.response.status_code) else: raise ExternalError("Error calling method on iotile.cloud", exception=exc, response=exc.response.status_code)
def _raise_error(self, command, response): exc_name = response.get('exception_class') reason = response.get('reason') exc_class = self._allowed_exceptions.get(exc_name) if exc_class is not None: raise exc_class(reason) raise ExternalError("Command {} failed".format(command), reason=response.get('reason'))
def _prepare(self): self.slug = self._build_device_slug(self.iotile_id) self.client = OrderedAWSIOTClient(self._args) try: self.client.connect(self.slug) except Exception as exc: raise ExternalError("Could not connect to AWS IOT", error=str(exc)) self.topics = MQTTTopicValidator(self.prefix + 'devices/{}'.format(self.slug)) self._bind_topics()
def set_device_template(self, device_id, new_template, os_tag=None): """Sets the device template for the given device in iotile.cloud. Is os_tag is passed, verify that the device template explicitly matches the expected os_tag by making another API call. Args: device_id (int): The id of the device that we want to change the device template for. new_template (string): Name of a valid device template that you wish to set the device to os_tag (int): Optional. If the os_tag passed into this function does not match the os_tag of the device_template in iotile.cloud, raise an error. """ try: dt = self.api.dt(new_template).get() except RestHttpBaseException as exc: raise ExternalError("Error calling method on iotile.cloud", exception=exc, response=exc.response.status_code) if os_tag is not None: if dt.get('os_tag', None) != os_tag: raise ArgumentError( "Cloud device template record does not match os tag", value=new_template, cloud_sg_os_tag=dt.get('os_tag', None), os_tag_set=os_tag) slug = device_id_to_slug(device_id) patch = {'template': new_template} try: self.api.device(slug).patch(patch, staff=1) except RestHttpBaseException as exc: if exc.response.status_code == 400: raise ArgumentError( "Error setting device template, invalid value", value=new_template, error_code=exc.response.status_code) else: raise ExternalError("Error calling method on iotile.cloud", exception=exc, response=exc.response.status_code)
def _find_available_bled112(logger): devices = _find_bled112_devices(logger) if len(devices) == 0: raise ExternalError( "Could not find any BLED112 adapters connected to this computer") for port in devices: try: dev = serial.Serial(port, _BAUD_RATE, timeout=0.01, rtscts=True, exclusive=True) logger.info("Using first available BLED112 adapter at %s", port) return dev except serial.serialutil.SerialException: logger.debug("Can't use BLED112 device %s because it's locked", port) raise ExternalError("There were %d BLED112 adapters but all were in use." % len(devices))
def unclaim(self, device_id, clean=True): """Unclaim a device that may have previously been claimed.""" slug = device_id_to_slug(device_id) payload = {'clean_streams': clean} try: self.api.device(slug).unclaim.post(payload) except RestHttpBaseException as exc: raise ExternalError("Error calling method on iotile.cloud", exception=exc, response=exc.response.status_code)
async def send_command(self, command, args, validator, timeout=10.0): """Send a command and synchronously wait for a single response. Args: command (string): The command name args (dict): Optional arguments. validator (Verifier): A SchemaVerifier to verify the response payload. timeout (float): The maximum time to wait for a response. Defaults to 10 seconds. Returns: dict: The response payload Raises: ExternalError: If the server is not connected or the command fails. asyncio.TimeoutError: If the command times out. ValidationError: If the response payload does not match the given validator. """ if not self._implementation.connected: raise ExternalError("No websock connection established") cmd_uuid = str(uuid.uuid4()) msg = dict(type='command', operation=command, uuid=cmd_uuid, payload=args) packed = pack(msg) # Note: register future before sending to avoid race conditions response_future = self._manager.wait_for(type="response", uuid=cmd_uuid, timeout=timeout) await self._implementation.send(packed) response = await response_future if response.get('success') is False: self._raise_error(command, response) if validator is None: return response.get('payload') return validator.verify(response.get('payload'))
def resolve(self, depinfo, destdir): from iotile.core.dev.registry import ComponentRegistry reg = ComponentRegistry() try: comp = reg.find_component(depinfo['name']) except ArgumentError: return {'found': False} # Make sure the tile we found in the registry has the required version reqver = depinfo['required_version'] if not reqver.check(comp.parsed_version): return {'found': False} # If the component is in the local registry but hasn't been built, # raise an error. if not os.path.exists(comp.output_folder): raise ExternalError( "Component found in registry but has not been built", path=comp.folder, name=comp.name, suggestion="Run iotile build on this component first") try: IOTile(comp.output_folder) except IOTileException: raise ExternalError( "Component found in registry but its build/output folder is not valid", path=comp.folder, name=comp.name, suggestion="Cleanly rebuild the component") self._copy_folder_contents(comp.output_folder, destdir) return {'found': True}
def _load_functions(self): """Load all config functions that should be bound to this ConfigManager Config functions allow you to add functions that will appear under ConfigManager but call your specified function. This is useful for adding complex configuration behavior that is callable from the iotile command line tool """ reg = ComponentRegistry() for _, conf_func in reg.load_extensions('iotile.config_function'): try: name = conf_func.__name__ self.add_function(name, conf_func) except (ValueError, TypeError) as exc: raise ExternalError("Error loading config function", name=name, error=str(exc))
def KeyTypeToStreamType(cls, key_type): """Converts key type into the type of signed report that can be encrypted with this key Args: int: a type of the key, see AuthProvider Returns: ReportSignatureFlags: a type of the stream report """ if key_type == AuthProvider.NoKey: return ReportSignatureFlags.SIGNED_WITH_HASH elif key_type == AuthProvider.UserKey: return ReportSignatureFlags.SIGNED_WITH_USER_KEY elif key_type == AuthProvider.DeviceKey: return ReportSignatureFlags.SIGNED_WITH_DEVICE_KEY else: raise ExternalError("Unsupported key type {}".format(key_type))
def _load_functions(self): """Load all config functions that should be bound to this ConfigManager Config functions allow you to add functions that will appear under ConfigManager but call your specified function. This is useful for adding complex configuration behavior that is callable from the iotile command line tool """ for entry in pkg_resources.iter_entry_points('iotile.config_function'): try: conf_func = entry.load() name = conf_func.__name__ self.add_function(name, conf_func) except (ValueError, TypeError) as exc: raise ExternalError("Error loading config function", name=name, error=str(exc))
def StreamTypeToKeyType(cls, stream_type): """Converts the stream type into the key type that was used to encryped this stream report Args: ReportSignatureFlags: a type of the stream report Returns: int: type of key, see AuthProvider """ if stream_type == ReportSignatureFlags.SIGNED_WITH_HASH: return AuthProvider.NoKey elif stream_type == ReportSignatureFlags.SIGNED_WITH_USER_KEY: return AuthProvider.UserKey elif stream_type == ReportSignatureFlags.SIGNED_WITH_DEVICE_KEY: return AuthProvider.DeviceKey else: raise ExternalError( "Unsupported stream signature type {}".format(stream_type))
def _process_elf(cls, in_path): tmp = tempfile.NamedTemporaryFile(delete=False) tmp.close() try: err = subprocess.call( ['arm-none-eabi-objcopy', '-O', 'ihex', in_path, tmp.name]) if err != 0: raise ExternalError( "Cannot convert elf to binary file", error_code=err, suggestion= "Make sure arm-none-eabi-gcc is installed and in your PATH" ) return cls._process_hex(tmp.name) finally: if os.path.isfile(tmp.name): os.remove(tmp.name)
def __init__(self, args=None): super(ChainedAuthProvider, self).__init__(args) # FIXME: Allow overwriting default providers via args self._load_installed_providers() reg = ComponentRegistry() sub_providers = [] for _, (priority, provider, provider_args ) in reg.load_extensions('iotile.default_auth_providers'): if provider not in self._auth_factories: raise ExternalError( "Default authentication provider list references unknown auth provider", provider_name=provider, known_providers=self._auth_factories.keys()) configured = self._auth_factories[provider](provider_args) sub_providers.append((priority, configured)) sub_providers.sort(key=lambda x: x[0]) self.providers = sub_providers
def __init__(self, args=None): super(ChainedAuthProvider, self).__init__(args) #FIXME: Allow overwriting default providers via args self._load_installed_providers() sub_providers = [] for entry in pkg_resources.iter_entry_points( 'iotile.default_auth_providers'): priority, provider, args = entry.load() if provider not in self._auth_factories: raise ExternalError( "Default authentication provider list references unknown auth provider", provider_name=provider, known_providers=self._auth_factories.keys()) configured = self._auth_factories[provider](args) sub_providers.append((priority, configured)) sub_providers.sort(key=lambda x: x[0]) self.providers = sub_providers
def __init__(self, args): super(NativeBLEVirtualInterface, self).__init__() # Create logger self._logger = logging.getLogger(__name__) self._logger.addHandler(logging.NullHandler()) # Create the baBLE interface to interact with BLE controllers self.bable = bable_interface.BaBLEInterface() # Get the list of BLE controllers self.bable.start(on_error=self._on_ble_error) controllers = self._find_ble_controllers() self.bable.stop() if len(controllers) == 0: raise ExternalError( "Could not find any BLE controller connected to this computer") # Parse args port = None if 'port' in args: port = args['port'] if port is None or port == '<auto>': self.controller_id = controllers[0].id else: self.controller_id = int(port) if not any(controller.id == self.controller_id for controller in controllers): raise ExternalError( "Could not find a BLE controller with the given ID, controller_id=%s" .format(self.controller_id)) if 'voltage' in args: self.voltage = float(args['voltage']) else: self.voltage = 3.8 # Restart baBLE with the selected controller id to prevent conflicts if multiple controllers self.bable.start(on_error=self._on_ble_error, exit_on_sigint=False, controller_id=self.controller_id) # Register the callback function into baBLE self.bable.on_write_request(self._on_write_request) self.bable.on_connected(self._on_connected) self.bable.on_disconnected(self._on_disconnected) # Initialize state self.connected = False self._connection_handle = 0 self.payload_notif = False self.header_notif = False self.streaming = False self.tracing = False # Keep track of whether we've launched our state machine # to stream or trace data so that when we find more data available # in process() we know not to restart the streaming/tracing process self._stream_sm_running = False self._trace_sm_running = False self.rpc_payload = bytearray(20) self.rpc_header = bytearray(20) try: self._initialize_system_sync() except Exception: self.stop_sync() raise
def __init__(self, port, on_scan=None, on_disconnect=None, active_scan=None, **kwargs): super(NativeBLEDeviceAdapter, self).__init__() # Create logger self._logger = logging.getLogger(__name__) self._logger.addHandler(logging.NullHandler()) # Register configuration self.set_config( 'minimum_scan_time', 2.0) # Time to accumulate device advertising packets first self.set_config('default_timeout', 10.0) # Time before timeout an operation self.set_config('expiration_time', 60.0) # Time before a scanned device expired self.set_config( 'maximum_connections', 3) # Maximum number of simultaneous connections per controller # Create the baBLE interface to interact with BLE controllers self.bable = bable_interface.BaBLEInterface() # Get the list of BLE controllers self.bable.start(on_error=self._on_ble_error) controllers = self._find_ble_controllers() self.bable.stop() if len(controllers) == 0: raise ExternalError( "Could not find any BLE controller connected to this computer") # Parse port and check if it exists if port is None or port == '<auto>': self.controller_id = controllers[0].id else: self.controller_id = int(port) if not any(controller.id == self.controller_id for controller in controllers): raise ExternalError( "Could not find a BLE controller with the given ID, controller_id=%s" .format(self.controller_id)) # Restart baBLE with the selected controller id to prevent conflicts if multiple controllers self.bable.start(on_error=self._on_ble_error, exit_on_sigint=False, controller_id=self.controller_id) # Register callbacks if on_scan is not None: self.add_callback('on_scan', on_scan) if on_disconnect is not None: self.add_callback('on_disconnect', on_disconnect) self.scanning = False self.stopped = False if active_scan is not None: self._active_scan = active_scan else: config = ConfigManager() self._active_scan = config.get('ble:active-scan') # To register advertising packets waiting for a scan response (only if active scan) self.partial_scan_responses = {} # To manage multiple connections self.connections = ConnectionManager(self.id) self.connections.start() # Notification callbacks self.notification_callbacks_lock = threading.Lock() self.notification_callbacks = {} try: self._initialize_system_sync() self.start_scan(active=self._active_scan) except Exception: self.stop_sync() raise