def import_node(self, node, source=None, test_node=False): """ Imports a new node. This should only be called by this library on during startup or from "add_node" function. **Hooks called**: * _node_before_import_ : If added, sends node dictionary as 'node' * _node_before_update_ : If updated, sends node dictionary as 'node' * _node_imported_ : If added, send the node instance as 'node' * _node_updated_ : If updated, send the node instance as 'node' :param node: A dictionary of items required to either setup a new node or update an existing one. :type input: dict :param test_node: Used for unit testing. :type test_node: bool :returns: Pointer to new input. Only used during unittest """ logger.debug("node: {node}", node=node) global_invoke_all('_nodes_before_import_', called_by=self, **{'node': node}) node_id = node["id"] if node_id not in self.nodes: global_invoke_all('_node_before_load_', called_by=self, **{'node': node}) self.nodes[node_id] = Node(self, node) global_invoke_all('_node_loaded_', called_by=self, **{'node': self.nodes[node_id]}) elif node_id not in self.nodes: global_invoke_all('_node_before_update_', called_by=self, **{'node': node}) self.nodes[node_id].update_attributes(node, source) global_invoke_all('_node_updated_', called_by=self, **{'node': self.nodes[node_id]})
def ack(self, notice_id, acknowledged_at=None, new_ack=None): """ Acknowledge a notice id. :param notice_id: :return: """ if notice_id not in self.notifications: raise KeyError('Notification not found: %s' % notice_id) if new_ack is None: new_ack = True if acknowledged_at is None: acknowledged_at = time() self.notifications[notice_id].set_ack(acknowledged_at, new_ack) try: global_invoke_all('_notification_acked_', called_by=self, notification=self.notifications[notice_id], event={ 'notification_id': notice_id, }) except YomboHookStopProcessing: pass
def _import_input_type(self, input_type, klass, test_input_type=False): """ Add a new input types to memory or update an existing input types. **Hooks called**: * _input_type_before_load_ : If added, sends input type dictionary as 'input_type' * _input_type_before_update_ : If updated, sends input type dictionary as 'input_type' * _input_type_loaded_ : If added, send the input type instance as 'input_type' * _input_type_updated_ : If updated, send the input type instance as 'input_type' :param input_type: A dictionary of items required to either setup a new input type or update an existing one. :type input: dict :param test_input_type: Used for unit testing. :type test_input_type: bool :returns: Pointer to new input. Only used during unittest """ input_type_id = input_type["id"] if input_type_id not in self.input_types: # print("importing path: %s" % validator_data) self.input_types[input_type_id] = klass(self, input_type) # global_invoke_all('_input_type_loaded_', # **{'input_type': self.input_types[input_type_id]}) elif input_type_id not in self.input_types: global_invoke_all('_input_type_before_update_', called_by=self, **{'input_type': input_type}) self.input_types[input_type_id].update_attributes(input_type) global_invoke_all( '_input_type_updated_', called_by=self, **{'input_type': self.input_types[input_type_id]})
def import_command(self, command, test_command=False): """ Add a new command to memory or update an existing command. **Hooks called**: * _command_before_load_ : If added, sends command dictionary as 'command' * _command_before_update_ : If updated, sends command dictionary as 'command' * _command_loaded_ : If added, send the command instance as 'command' * _command_updated_ : If updated, send the command instance as 'command' :param device: A dictionary of items required to either setup a new command or update an existing one. :type device: dict :param test_command: Used for unit testing. :type test_command: bool :returns: Pointer to new device. Only used during unittest """ logger.debug("command: {command}", command=command) try: global_invoke_all('_command_before_import_', called_by=self, **{'command': command}) except Exception as e: pass command_id = command["id"] if command_id not in self.commands: try: global_invoke_all('_command_before_load_', called_by=self, **{'command': command}) except Exception as e: pass self.commands[command_id] = Command(command) try: global_invoke_all('_command_loaded_', called_by=self, **{'command': self.commands[command_id]}) except Exception as e: pass elif command_id not in self.commands: try: global_invoke_all('_command_before_update_', called_by=self, **{'command': command}) except Exception as e: pass self.commands[command_id].update_attributes(command) try: global_invoke_all('_command_updated_', called_by=self, **{'command': self.commands[command_id]}) except Exception as e: pass if command['voice_cmd'] is not None: self.__yombocommandsByVoice[ command['voice_cmd']] = self.commands[command_id]
def edit_node(self, node_id, api_data, source=None, **kwargs): """ Edit a node at the Yombo server level, not at the local gateway level. :param data: :param kwargs: :return: """ results = None node = self.nodes[node_id] for key, value in api_data.items(): setattr(node, key, value) if source != 'amqp': input_data = api_data['data'].copy() print("input_data- %s " % type(input_data)) if 'data_content_type' not in api_data: api_data['data_content_type'] = node.data_content_type if api_data['data_content_type'] == 'json': try: api_data['data'] = json.dumps(api_data['data']) except: pass elif api_data['data_content_type'] == 'msgpack_base85': try: api_data['data'] = base64.b85encode(msgpack.dumps(api_data['data'])) except: pass node_results = yield self._YomboAPI.request('PATCH', '/v1/node/%s' % (node_id), api_data) api_data['data'] = input_data if node_results['code'] > 299: results = { 'status': 'failed', 'msg': "Couldn't edit node", 'data': None, 'node_id': node_id, 'apimsg': node_results['content']['message'], 'apimsghtml': node_results['content']['html_message'], } return results node = self.nodes[node_id] if source != 'node': node.update_attributes(api_data, source='parent') node.save_to_db() global_invoke_all('_node_edited_', called_by=self, **{'node': node}) results = { 'status': 'success', 'msg': "Node edited.", 'node_id': node_id, 'data': node.dump(), 'apimsg': "Node edited.", 'apimsghtml': "Node edited.", } return results
def set(self, key, value, value_type=None, function=None, arguments=None): """ Set the value of a given state (key). **Hooks called**: * _states_set_ : Sends kwargs: *key* - The name of the state being set. *value* - The new value to set. :param key: Name of state to set. :param value: Value to set state to. Can be string, list, or dictionary. :param value_type: If set, allows a human filter to be applied for display. :param function: If this a living state, provide a function to be called to get value. Value will be used to set the initial value. :param arguments: kwarg (arguments) to send to function. :return: Value of state """ if key in self.__States: # If state is already set to value, we don't do anything. if self.__States[key]['value'] == value: return self._Statistics.increment("lib.states.set.update", bucket_time=60, anon=True) self.__States[key]['created'] = int(round(time())) else: self.__States[key] = { 'created': int(time()), } self._Statistics.increment("lib.states.set.new", bucket_time=60, anon=True) # Call any hooks try: state_changes = global_invoke_all('_states_preset_', **{'called_by': self,'key': key, 'value': value}) except YomboHookStopProcessing as e: logger.warning("Not saving state '{state}'. Resource '{resource}' raised' YomboHookStopProcessing exception.", state=key, resource=e.by_who) return self.__States[key]['value'] = value self.__States[key]['function'] = function self.__States[key]['arguments'] = arguments self.__States[key]['value_type'] = value_type self.__States[key]['value_human'] = self.convert_to_human(value, value_type) # Call any hooks try: state_changes = global_invoke_all('_states_set_', **{'called_by': self,'key': key, 'value': value}) except YomboHookStopProcessing: pass live = False if function is not None: live = True self._LocalDB.save_state(key, value, value_type, live) self.check_trigger(key, value) # Check if any automation items need to fire!
def import_location(self, location, test_location=False): """ Add a new locations to memory or update an existing locations. **Hooks called**: * _location_before_load_ : If added, sends location dictionary as 'location' * _location_before_update_ : If updated, sends location dictionary as 'location' * _location_loaded_ : If added, send the location instance as 'location' * _location_updated_ : If updated, send the location instance as 'location' :param location: A dictionary of items required to either setup a new location or update an existing one. :type input: dict :param test_location: Used for unit testing. :type test_location: bool :returns: Pointer to new input. Only used during unittest """ # logger.debug("location: {location}", location=location) location_id = location["id"] global_invoke_all( '_locations_before_import_', called_by=self, location_id=location_id, location=location, ) if location_id not in self.locations: global_invoke_all( '_location_before_load_', called_by=self, location_id=location_id, location=location, ) self.locations[location_id] = Location(self, location) global_invoke_all( '_location_loaded_', called_by=self, location_id=location_id, location=self.locations[location_id], ) elif location_id not in self.locations: global_invoke_all( '_location_before_update_', called_by=self, location_id=location_id, location=self.locations[location_id], ) self.locations[location_id].update_attributes(location) global_invoke_all( '_location_updated_', called_by=self, location_id=location_id, location=self.locations[location_id], )
def _modules_loaded_(self, **kwargs): """ Implements the _modules_loaded_ and is called after _load_ is called for all the modules. Expects a list of events to subscribe to. **Hooks called**: * _voicecmds_add_ : Expects a list of message subscription events to subscrib to. **Usage**: .. code-block:: python def ModuleName_voice_cmds_load(self, **kwargs): return ['status'] """ voicecommands_to_add = yield global_invoke_all('_voicecmds_add_', called_by=self) # logger.info("voicecommands_to_add: {voice_cmds}", voice_cmds=voicecommands_to_add) for componentName, voice_cmds in voicecommands_to_add.items(): if voice_cmds is None: continue for list in voice_cmds: logger.debug( "For module '{fullName}', adding voice_cmd: {voice_cmd}, order: {order}", voice_cmd=list['voice_cmd'], fullName=componentName, order=list['order']) self.add_by_string(list['voice_cmd'], list['call_back'], list['device'], list['order'])
def _module_prestart_(self, **kwargs): """ Implements the _module_prestart_ and is called after _load_ is called for all the modules. Expects a list of events to subscribe to. **Hooks called**: * _voicecmds_add_ : Expects a list of message subscription events to subscrib to. **Usage**: .. code-block:: python def ModuleName_voice_cmds_load(self, **kwargs): return ['status'] """ voicecommands_to_add = global_invoke_all('_voicecmds_add_') # logger.info("voicecommands_to_add: {voice_cmds}", voice_cmds=voicecommands_to_add) for componentName, voice_cmds in voicecommands_to_add.iteritems(): if voice_cmds is None: continue for list in voice_cmds: logger.debug("For module '{fullName}', adding voice_cmd: {voice_cmd}, order: {order}", voice_cmd=list['voice_cmd'], fullName=componentName, order=list['order']) self.add_by_string(list['voice_cmd'], list['call_back'], list['device'], list['order'])
def set_from_gateway_communications(self, key, values): """ Used by the gateway coms (mqtt) system to set state values. :param key: :param values: :return: """ gateway_id = values['gateway_id'] if gateway_id == self.gateway_id: return if gateway_id not in self.__States: self.__States[gateway_id] = {} self.__States[values['gateway_id']][key] = { 'gateway_id': values['gateway_id'], 'value': values['value'], 'value_human': values['value_human'], 'value_type': values['value_type'], 'live': False, 'created_at': values['created_at'], 'updated_at': values['updated_at'], } # Call any hooks try: yield global_invoke_all('_states_set_', **{'called_by': self, 'key': key, 'value': values['value'], 'value_full': self.__States[gateway_id][key], 'gateway_id': gateway_id, } ) except YomboHookStopProcessing: pass
def set_from_gateway_communications(self, key, values): """ Used by the gateway coms (mqtt) system to set state values. :param key: :param values: :return: """ gateway_id = values['gateway_id'] if gateway_id == self.gateway_id: return if gateway_id not in self.__States: self.__States[gateway_id] = {} self.__States[values['gateway_id']][key] = { 'gateway_id': values['gateway_id'], 'value': values['value'], 'value_human': values['value_human'], 'value_type': values['value_type'], 'live': False, 'created_at': values['created_at'], 'updated_at': values['updated_at'], } # Call any hooks yield global_invoke_all( '_states_set_', called_by=self, key=key, value=values['value'], value_full=self.__States[gateway_id][key], gateway_id=gateway_id, )
def _modules_prestarted_(self, **kwargs): """ This function is called before the _start_ function of all modules is called. This implements the hook: _statistics_lifetimes_. The hook should return a dictionary with the following possible keys - not all keys are required, only ones should override the default. How these values work: If your module is saving stats every 30 seconds, this can consume a lot of data. After a while this data might be useless (or important). Periodically, the statistics are merged and save space. The following default values: {'full':60, '5m':90, '15m':90, '60m':365, '6hr':730, '24h':1825} that the system will keep full statistics for 60 days. Then, it'll collapse this down to 15 minute averages. It will keep this for an additional 90 days. After that, it will keep 15 minute averages for 90 days. At this point, we have 195 days worth of statistics. The remaining consolidations should be self explanatory. *Usage**: .. code-block:: python def _statistics_lifetimes_(**kwargs): return {'modules.mymodulename.#': {'size': 300, 'lifetime': 0}} """ if self.enabled is not True: return stat_lifetimes = yield global_invoke_all( '_statistics_lifetimes_', called_by=self, ) for moduleName, item in stat_lifetimes.items(): if isinstance(item, dict): for bucket_name, lifetime in item.items(): self.add_bucket_lifetime(bucket_name, lifetime)
def _load_(self, **kwargs): """ Sets up the module to start processng X10 commands. After this function is complete, the X10 API module will be ready to accept commands. **Hooks implemented**: * hook_x10api_interfaces : Expects a dictionary back with "priority" and "callback" for any modules that can send X10 commands to the power lines. Since only one module can perform this task, the module with the highest priority (highest number) will be used. *callback* is the function to call to perform this request. :param kwargs: :return: """ results = yield global_invoke_all('x10api_interfaces', called_by=self) temp = {} # logger.debug("message: automation_sources: {automation_sources}", automation_sources=automation_sources) for component_name, data in results.items(): temp[data['priority']] = { 'name': component_name, 'callback': data['callback'] } interfaces = OrderedDict(sorted(temp.items())) self.interface_callback = None if len(interfaces) == 0: logger.warn( "X10 API - No X10 Interface module found, disabling X10 support." ) else: self.interface_found = True key = list(interfaces.keys())[-1] self.interface_callback = temp[key][ 'callback'] # we can only have one interface, highest priority wins!! # logger.warn("X10 interface: {interface}", interface=self.interface_callback) if self.interface_callback is not None: self.x10_devices.clear() # print "x10api init1!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!114" # print "x10api devices: %s" % devices module_devices = yield self._module_devices() for device_id, device in module_devices.items(): try: device = self._Devices[device_id] # logger.debug("devicevariables: {vars}", vars=device.device_variables_cached) house = device.device_variables_cached['house']['values'][ 0].upper() unit = int(device.device_variables_cached['unit_code'] ['values'][0]) except: continue if house not in self.x10_devices: self.x10_devices[house] = {} self.x10_devices[house][unit] = device item = "%s%s" % (house, str(unit)) self.x10_devices[item] = device
def import_device_types(self, device_type, test_device_type=False): """ Add a new device types to memory or update an existing device types. **Hooks called**: * _device_type_before_load_ : If added, sends device type dictionary as 'device_type' * _device_type_before_update_ : If updated, sends device type dictionary as 'device_type' * _device_type_loaded_ : If added, send the device type instance as 'device_type' * _device_type_updated_ : If updated, send the device type instance as 'device_type' :param device_type: A dictionary of items required to either setup a new device type or update an existing one. :type device: dict :param test_device_type: Used for unit testing. :type test_device_type: bool :returns: Pointer to new device. Only used during unittest """ logger.debug("device_type: {device_type}", device_type=device_type) global_invoke_all('_device_types_before_import_', called_by=self, **{'device_type': device_type}) device_type_id = device_type["id"] if device_type_id not in self.device_types: global_invoke_all('_device_type_before_load_', called_by=self, **{'device_type': device_type}) self.device_types[device_type_id] = DeviceType(device_type, self) yield self.device_types[device_type_id]._init_() global_invoke_all( '_device_type_loaded_', called_by=self, **{'device_type': self.device_types[device_type_id]}) elif device_type_id not in self.device_types: global_invoke_all('_device_type_before_update_', called_by=self, **{'device_type': device_type}) self.device_types[device_type_id].update_attributes(device_type) yield self.device_types[device_type_id]._init_() global_invoke_all( '_device_type_updated_', called_by=self, **{'device_type': self.device_types[device_type_id]})
def enable_node(self, node_id, source=None, **kwargs): """ Enable a node at the Yombo server level :param node_id: The node ID to enable. :param kwargs: :return: """ results = None api_data = { 'status': 1, } if source != 'amqp': node_results = yield self._YomboAPI.request('PATCH', '/v1/node/%s' % node_id, api_data) if node_results['code'] > 299: results = { 'status': 'failed', 'msg': "Couldn't enable node", 'node_id': node_id, 'data': None, 'apimsg': node_results['content']['message'], 'apimsghtml': node_results['content']['html_message'], } return results node = self.nodes[node_id] if source != 'node': node.update_attributes(api_data, source='parent') node.save_to_db() global_invoke_all('_node_enabled_', called_by=self, **{'node': node}) results = { 'status': 'success', 'msg': "Node enabled.", 'node_id': node_id, 'data': node.dump(), 'apimsg': "Node enabled.", 'apimsghtml': "Node enabled.", } return results
def _load_(self, **kwargs): """ Loads device types from the database and imports them. :return: """ yield self._load_device_types_from_database() self.load_platforms(BASE_DEVICE_TYPE_PLATFORMS) platforms = yield global_invoke_all('_device_platforms_', called_by=self) for component, item in platforms.items(): self.load_platforms(item)
def delete_node(self, node_id, source=None, **kwargs): """ Delete a node at the Yombo server level, not at the local gateway level. :param node_id: The node ID to delete. :param kwargs: :return: """ results = None if source != 'amqp': node_results = yield self._YomboAPI.request('DELETE', '/v1/node/%s' % node_id) if node_results['code'] > 299: results = { 'status': 'failed', 'msg': "Couldn't delete node", 'node_id': node_id, 'data': None, 'apimsg': node_results['content']['message'], 'apimsghtml': node_results['content']['html_message'], } return results api_data = { 'status': 2, } node = self.nodes[node_id] if source != 'node': node.update_attributes(api_data, source='parent') node.save_to_db() global_invoke_all('_node_deleted_', called_by=self, **{'node': node}) results = { 'status': 'success', 'msg': "Node deleted.", 'node_id': node_id, 'data': node.dump(), 'apimsg': "Node deleted.", 'apimsghtml': "Node deleted.", } return results
def delete(self, notice_id): """ Deletes a provided notification. :param notice_id: :return: """ # Call any hooks try: global_invoke_all( '_notification_add_', **{ 'called_by': self, 'notification': self.notifications[notice_id], }) except YomboHookStopProcessing: pass try: del self.notifications[notice_id] self._LocalDB.delete_notification(notice_id) except: pass
def delete(self, notice_id): """ Deletes a provided notification. :param notice_id: :return: """ # Call any hooks try: global_invoke_all('_notification_delete_', called_by=self, notification=self.notifications[notice_id], event={ 'notification_id': notice_id, }) except YomboHookStopProcessing: pass try: del self.notifications[notice_id] self._LocalDB.delete_notification(notice_id) except: pass
def _modules_loaded_(self, **kwargs): """ Called after _load_ is called for all the modules. Get's a list of configuration items all library or modules define or use. Note: This complies with i18n translations for future use. **Hooks called**: * _configuration_details_ : Gets various details about a configuration item. Do not implement, not set in stone. Might migrate to i18n library. **Usage**: .. code-block:: python def _configuration_details_(self, **kwargs): return [{'webinterface': { 'enabled': { 'description': { 'en': 'Enables/disables the web interface.', } }, 'port': { 'description': { 'en': 'Port number for the web interface to listen on.' } } }, }] """ config_details = yield global_invoke_all('_configuration_details_', called_by=self) for component, details in config_details.items(): if details is None: continue for list in details: # logger.warn("For module {component}, adding details: {list}", component=component, list=list) self.configs_details = dict_merge(self.configs_details, list) for section, options in self.configs.items(): for option, keys in options.items(): try: self.configs[section][option][ 'details'] = self.configs_details[section][option] except: pass
def send_event_hook(self, event_msg): """ Generate an "event" message of status type being the time event name. **Hooks called**: * _time_event_ : Sends kwargs: *key* - The name of the state being set. *value* - The new value to set. """ try: state_changes = global_invoke_all('_time_event_', **{'value': event_msg}) except YomboHookStopProcessing: logger.warning("Stopping processing 'send_event_hook' due to YomboHookStopProcessing exception.") return
def _module_prestart_(self, **kwargs): """ This function is called before the _start_ function of all modules is called. This implements the hook: _statistics_lifetimes_. The hook should return a dictionary with the following possible keys - not all keys are required, only ones should override the default. How these values work: If your module is saving stats every 30 seconds, this can consume a lot of data. After a while this data might be useless (or important). Periodically, the statistics are merged and save space. The following default values: {'full':60, '5m':90, '15m':90, '60m':365, '6hr':730, '24h':1825} that the system will keep full statistics for 60 days. Then, it'll collapse this down to 15 minute averages. It will keep this for an additional 90 days. After that, it will keep 15 minute averages for 90 days. At this point, we have 195 days worth of statistics. The remaining consolidations should be self explanatory. *Usage**: .. code-block:: python def _statistics_lifetimes_(**kwargs): return {'modules.mymodulename': {'full':180, '5m':180}} This will create statistics save duration with the following values: {'full':180, '5m':180, '15m':90, '60m':365, '6hr':730, '24h':1825} Notes: set any value to 0 and it will keep that level of data forever! See :py:mod:`Atoms Library <yombo.lib.automationhelpers>` for demo. """ # first, set some generic defaults. The filter matcher when processing will always use the most specific. # full, 5m, 15m, 60m self.bucket_lifetimes_default = {"full": 60, "5m": 90, "15m": 90, "60m": 365, "6hr": 730, "24h": 1825} self.add_bucket_lifetime("#", self.bucket_lifetimes_default) self.add_bucket_lifetime("lib.#", {"full": 15, "5m": 30, "15m": 60, "60m": 90, "6hr": 180, "24h": 1000}) self.add_bucket_lifetime("lib.device.#", {"full": 60, "5m": 90, "15m": 90, "60m": 90, "6hr": 180, "24h": 1000}) self.add_bucket_lifetime( "lib.amqpyombo.amqp.#", {"full": 5, "5m": 10, "15m": 15, "60m": 15, "6hr": 15, "24h": 90} ) self.add_bucket_lifetime("lib.#", {"full": 10, "5m": 15, "15m": 15, "60m": 15, "6hr": 180, "24h": 720}) self.add_bucket_lifetime("modules.#", {"full": 30, "5m": 15, "15m": 15, "60m": 15, "6hr": 180, "24h": 1000}) stat_lifetimes = global_invoke_all("_statistics_lifetimes_", called_by=self) # print "################## %s " % stat_lifetimes # logger.debug("message: automation_sources: {automation_sources}", automation_sources=automation_sources) for moduleName, item in stat_lifetimes.iteritems(): if isinstance(item, dict): for bucket, values in item.iteritems(): # print "hook....bucket: %s" % bucket # print "hook....bucket: %s" % values self.add_bucket_lifetime(bucket, values)
def _load_(self, **kwargs): # print("################# about to process_amqpyombo_options") results = yield global_invoke_all('_amqpyombo_options_', called_by=self) for component_name, data in results.items(): if 'connected' in data: self.amqpyombo_options['connected'].append(data['connected']) if 'disconnected' in data: self.amqpyombo_options['disconnected'].append( data['disconnected']) if 'routing' in data: for key, the_callback in data['gateway_routing'].items(): if key not in self.amqpyombo_options['gateway_routing']: self.amqpyombo_options['gateway_routing'][key] = [] self.amqpyombo_options['gateway_routing'][key].append( the_callback)
def _load_(self, **kwargs): """ Starts the loop to check if any certs need to be updated. :return: """ self.check_if_certs_need_update_loop = LoopingCall( self.check_if_certs_need_update) self.check_if_certs_need_update_loop.start( self._Configs.get('sqldict', 'save_interval', random_int(60 * 60 * 24, .1), False)) # Check if any libraries or modules need certs. sslcerts = yield global_invoke_all('_sslcerts_', called_by=self) # print("about to add sslcerts") yield self._add_sslcerts(sslcerts)
def delete(self, section, option): """ Delete a section/option value from configs (yombo.ini). :param section: The configuration section to use. :type section: string :param option: The option (key) to delete. :type option: string """ if section in self.configs: if option in self.configs[section]: self.configs_dirty = True del self.configs[section][option] yield global_invoke_all('_configuration_set_', called_by=self, **{ 'section': section, 'option': option, 'value': None, 'action': 'delete' })
def _load_(self, **kwargs): results = yield global_invoke_all('insteonapi_interfaces', called_by=self) temp = {} for component_name, data in results.items(): temp[data['priority']] = { 'name': component_name, 'module': data['module'] } interfaces = OrderedDict(sorted(temp.items())) self.interface_module = None if len(interfaces) == 0: logger.error( "Insteon API - No Insteon interface module found, disabling Insteon support." ) else: key = list(interfaces.keys())[-1] self.interface_module = temp[key][ 'module'] # we can only have one interface, highest priority wins!! self.interface_module.insteonapi_init( self) # tell the interface module about us.
def _load_(self, **kwargs): """ Starts the loop to check if any certs need to be updated. :return: """ self.check_if_certs_need_update_loop = LoopingCall( self.check_if_certs_need_update) self.check_if_certs_need_update_loop.start( self._Configs.get('sqldict', 'save_interval', random_int(60 * 60 * 2, .2), False), False) # Check if any libraries or modules need certs. fqdn = self._Configs.get('dns', 'fqdn', None, False) if fqdn is None: logger.warn( "Unable to create webinterface SSL cert: DNS not set properly." ) return sslcerts = yield global_invoke_all( '_sslcerts_', called_by=self, ) yield self._add_sslcerts(sslcerts)
def add(self, notice, from_db=None, create_event=None): """ Add a new notice. :param notice: A dictionary containing notification details. :type record: dict :returns: Pointer to new notice. Only used during unittest """ if 'title' not in notice: raise YomboWarning("New notification requires a title.") if 'message' not in notice: raise YomboWarning("New notification requires a message.") if 'id' not in notice: notice['id'] = random_string(length=16) else: if notice['id'] in self.notifications: self.notifications[notice['id']].update(notice) return notice['id'] if 'type' not in notice: notice['type'] = 'notice' if 'gateway_id' not in notice: notice['gateway_id'] = self.gateway_id if 'priority' not in notice: notice['priority'] = 'normal' if 'source' not in notice: notice['source'] = '' if 'always_show' not in notice: notice['always_show'] = False else: notice['always_show'] = is_true_false(notice['always_show']) if 'always_show_allow_clear' not in notice: notice['always_show_allow_clear'] = True else: notice['always_show_allow_clear'] = is_true_false( notice['always_show_allow_clear']) if 'persist' not in notice: notice['persist'] = False if 'meta' not in notice: notice['meta'] = {} if 'user' not in notice: notice['user'] = None if 'local' not in notice: notice['local'] = False if notice['persist'] is True and 'always_show_allow_clear' is True: YomboWarning( "New notification cannot have both 'persist' and 'always_show_allow_clear' set to true." ) if 'expire_at' not in notice: if 'timeout' in notice: notice['expire_at'] = time() + notice['timeout'] else: notice['expire_at'] = time( ) + 60 * 60 * 24 * 30 # keep persistent notifications for 30 days. else: if notice['expire_at'] == None: if notice['persist'] == True: YomboWarning("Cannot persist a non-expiring notification") elif notice['expire_at'] > time(): YomboWarning( "New notification is set to expire before current time.") if 'created_at' not in notice: notice['created_at'] = time() if 'acknowledged' not in notice: notice['acknowledged'] = False else: if notice['acknowledged'] not in (True, False): YomboWarning( "New notification 'acknowledged' must be either True or False." ) if 'acknowledged_at' not in notice: notice['acknowledged_at'] = None logger.debug("notice: {notice}", notice=notice) if from_db is None and notice['persist'] is True: self._LocalDB.add_notification(notice) self.notifications.prepend(notice['id'], Notification(self, notice)) # Call any hooks try: global_invoke_all( '_notification_add_', **{ 'called_by': self, 'notification': self.notifications[notice['id']], }) except YomboHookStopProcessing: pass return notice['id']
def set(self, section, option, value, **kwargs): """ Set value of configuration option for a given section. The option length **cannot exceed 1000 characters**. The value cannot exceed 5000 bytes. Section and option will be converted to lowercase, rending the set/get function case insenstive. **Usage**: .. code-block:: python gatewayUUID = self._Config.set("section_name", "myoption", "New Value") :raises InvalidArgumentError: When an argument is invalid or illegal. :raises KeyError: When the requested section and option are not found. :param section: The configuration section to use. :type section: string :param option: The option (key) to use. :type option: string :param value: What to return if no result is found, default = None. :type value: int or string """ # print("cfgs set: %s:%s = %s" % (section, option, value)) if len(section) > self.MAX_SECTION_LENGTH: self._Statistics.increment("lib.configuration.set.invalid_length", bucket_size=15, anon=True) raise InvalidArgumentError("section cannot be more than %d chars" % self.MAX_OPTION_LENGTH) if len(option) > self.MAX_OPTION_LENGTH: self._Statistics.increment("lib.configuration.set.invalid_length", bucket_size=15, anon=True) raise InvalidArgumentError("option cannot be more than %d chars" % self.MAX_OPTION_LENGTH) # Can't set value! if section == 'yombo': self._Statistics.increment( "lib.configuration.set.no_setting_yombo", bucket_size=15, anon=True) raise InvalidArgumentError("Not allowed to set value") if isinstance(value, str): if len(value) > self.MAX_VALUE_LENGTH: self._Statistics.increment( "lib.configuration.set.value_too_long", bucket_size=15, anon=True) raise InvalidArgumentError( "value cannot be more than %d chars" % self.MAX_VALUE) section = section.lower() option = option.lower() if section not in self.configs: self.configs[section] = {} if option not in self.configs[section]: self.configs[section][option] = { 'created_at': int(time()), 'reads': 0, 'writes': 0, } self._Statistics.increment("lib.configuration.set.new", bucket_size=15, anon=True) else: # already have a value. If it's the same, we won't set it. if self.configs[section][option]['value'] == value: self._Statistics.increment( "lib.configuration.set.skipped_same_value", bucket_size=15, anon=True) return self._Statistics.increment("lib.configuration.set.update", bucket_size=15, anon=True) self.configs[section][option] = dict_merge( self.configs[section][option], { 'updated_at': int(time()), 'value': value, 'hash': hashlib.sha224(str(value).encode('utf-8')).hexdigest(), }) self.configs_dirty = True if self.loading_yombo_ini is False: self.configs[section][option]['writes'] += 1 yield global_invoke_all('_configuration_set_', called_by=self, **{ 'section': section, 'option': option, 'value': value, 'action': 'set' })
def add_node(self, api_data, source=None, **kwargs): """ Add a new node. Updates Yombo servers and creates a new entry locally. :param api_data: :param kwargs: :return: """ results = None new_node = None if 'gateway_id' not in api_data: api_data['gateway_id'] = self.gateway_id() if 'data_content_type' not in api_data: api_data['data_content_type'] = 'json' if source != 'amqp': input_data = api_data['data'].copy() if api_data['data_content_type'] == 'json': try: api_data['data'] = json.dumps(api_data['data']) except: pass elif api_data['data_content_type'] == 'msgpack_base85': try: api_data['data'] = base64.b85encode(msgpack.dumps(api_data['data'])) except: pass node_results = yield self._YomboAPI.request('POST', '/v1/node', api_data) print("added node results: %s" % node_results) if node_results['code'] > 299: results = { 'status': 'failed', 'msg': "Couldn't add node", 'data': None, 'node_id': None, 'apimsg': node_results['content']['message'], 'apimsghtml': node_results['content']['html_message'], } return results node_id = node_results['data']['id'] new_node = node_results['data'] new_node['data'] = input_data else: node_id = api_data['id'] new_node = api_data self.import_node(new_node, source) self.nodes[node_id].add_to_db() global_invoke_all('_node_added_', called_by=self, **{'node': self.nodes[node_id]}) results = { 'status': 'success', 'msg': "Node edited.", 'node_id': node_id, 'data': self.nodes[node_id].dump(), 'apimsg': "Node edited.", 'apimsghtml': "Node edited.", } return results
def page_configs_basic_post(webinterface, request, session): session.has_access("system_setting", "*", "edit") valid_submit = True # more checks to come, just doing basic for now. try: submitted_core_label = request.args.get("core_label")[0] webinterface._Configs.set("core", "label", submitted_core_label) except: valid_submit = False webinterface.add_alert("Invalid Gateway Label.") try: submitted_master_gateway_id = request.args.get( "master_gateway_id")[0] except: valid_submit = False webinterface.add_alert("Master gateway not selected.") try: master_gateways_results = yield webinterface._YomboAPI.request( "GET", "/v1/gateway?_filters[is_master]=1", session=session["yomboapi_session"]) except YomboWarning as e: webinterface.add_alert(e.html_message, "warning") return webinterface.redirect(request, "/") new_master = None if submitted_master_gateway_id == webinterface.gateway_id: new_master = True else: for gateway in master_gateways_results["data"]: if gateway['id'] == submitted_master_gateway_id: new_master = True break if new_master is None: valid_submit = False webinterface.add_alert( "Invalid master gateway selection, selection doesn't exist." ) else: webinterface._Notifications.add({ "title": "Restart Required", "message": 'Master gateway has been changed. A system <strong>' '<a class="confirm-restart" href="#" title="Restart Yombo Gateway">restart is required</a>' '</strong> to take affect.', "source": "Web Interface", "persist": False, "priority": "high", "always_show": True, "always_show_allow_clear": False, "id": "reboot_required", "local": True, }) try: submitted_core_description = request.args.get( "core_description")[0] webinterface._Configs.set("core", "description", submitted_core_description) except: valid_submit = False webinterface.add_alert("Invalid Gateway Description.") if valid_submit: try: if submitted_master_gateway_id == webinterface.gateway_id: is_master = True else: is_master = False data = { "label": submitted_core_label, "description": submitted_core_description, "master_gateway": submitted_master_gateway_id, # must be master_gateway to API "is_master": is_master, } # print("data: %s" % data) results = yield webinterface._YomboAPI.request( "PATCH", f"/v1/gateway/{webinterface.gateway_id}", data, session=session["yomboapi_session"]) # print("api results: %s" % results) previous_master_gateway_id = webinterface._Configs.get( "core", "master_gateway_id", None, False) if previous_master_gateway_id != submitted_master_gateway_id: webinterface._Configs.set("core", "is_master", is_master) webinterface._Configs.set("core", "master_gateway_id", submitted_master_gateway_id) webinterface._Notifications.add({ "title": "Restart Required", "message": "A critical configuration change has occured and requires a restart: The master gateway has been changed.", "source": "Web Interface", "persist": False, "priority": "high", "always_show": True, "always_show_allow_clear": False, "id": "reboot_required", "local": True, }) except YomboWarning as e: webinterface.add_alert(e.html_message, "warning") return webinterface.redirect(request, "/configs/basic") try: submitted_location_searchtext = request.args.get( "location_searchtext")[0] webinterface._Configs.set("location", "searchbox", submitted_location_searchtext) except: valid_submit = False webinterface.add_alert( "Invalid Gateway Location Search Entry.") try: submitted_location_latitude = request.args.get( "location_latitude")[0] webinterface._Configs.set("location", "latitude", submitted_location_latitude) except: valid_submit = False webinterface.add_alert("Invalid Gateway Lattitude.") try: submitted_location_longitude = request.args.get( "location_longitude")[0] webinterface._Configs.set("location", "longitude", submitted_location_longitude) except: valid_submit = False webinterface.add_alert("Invalid Gateway Longitude.") try: submitted_location_elevation = request.args.get( "location_elevation")[0] webinterface._Configs.set("location", "elevation", submitted_location_elevation) except: valid_submit = False webinterface.add_alert("Invalid Gateway Elevation.") try: submitted_area_id = request.args.get("area_id")[0] webinterface._Configs.set("location", "area_id", submitted_area_id) except: valid_submit = False webinterface.add_alert("Invalid Gateway Location Area.") try: submitted_location_id = request.args.get("location_id")[0] webinterface._Configs.set("location", "location_id", submitted_location_id) except: valid_submit = False webinterface.add_alert("Invalid Gateway Location.") yield global_invoke_all("_refresh_jinja2_globals_", called_by=webinterface) try: submitted_webinterface_enabled = request.args.get( "webinterface_enabled")[0] webinterface._Configs.set("webinterface", "enabled", submitted_webinterface_enabled) except: valid_submit = False webinterface.add_alert( "Invalid Webinterface Enabled/Disabled value.") try: submitted_webinterface_localhost_only = request.args.get( "webinterface_localhost_only")[0] webinterface._Configs.set( "webinterface", "localhost_only", submitted_webinterface_localhost_only) except: valid_submit = False webinterface.add_alert( "Invalid Webinterface Localhost Only Selection.") try: new_port = int( request.args.get("webinterface_nonsecure_port")[0]) if new_port == 0: submitted_webinterface_nonsecure_port = new_port webinterface._Configs.set( "webinterface", "nonsecure_port", submitted_webinterface_nonsecure_port) elif new_port == webinterface._Configs.get( "webinterface", "nonsecure_port"): submitted_webinterface_nonsecure_port = new_port webinterface._Configs.set( "webinterface", "nonsecure_port", submitted_webinterface_nonsecure_port) else: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.bind(("127.0.0.1", new_port)) except socket.error as e: if e.errno == 98: webinterface.add_alert( "Invalid webinterface non_secure port, appears to be in use already." ) else: # something else raised the socket.error exception webinterface.add_alert( f"Invalid webinterface non_secure port, unable to access: {e}" ) valid_submit = False else: webinterface._Configs.set("webinterface", "nonsecure_port", new_port) except Exception as e: valid_submit = False webinterface.add_alert( f"Invalid webinterface non_secure port: {e}") try: new_port = int(request.args.get("webinterface_secure_port")[0]) if new_port == 0: webinterface._Configs.set("webinterface", "secure_port", new_port) elif new_port == new_port: webinterface._Configs.set("webinterface", "secure_port", new_port) else: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.bind(("127.0.0.1", new_port)) except socket.error as e: if e.errno == 98: webinterface.add_alert( "Invalid webinterface secure port, appears to be in use already." ) else: # something else raised the socket.error exception webinterface.add_alert( f"Invalid webinterface secure port, unable to access: {e}" ) valid_submit = False else: webinterface._Configs.set("webinterface", "secure_port", new_port) except Exception as e: valid_submit = False webinterface.add_alert( f"Invalid webinterface secure port: {e}") try: webinterface._Configs.set( "localization", "degrees", request.args.get("localization_degrees")[0]) except: print("can't save degrees") pass configs = webinterface._Configs.get("*", "*") page = webinterface.get_template( request, webinterface.wi_dir + "/pages/configs/basic.html") return page.render( alerts=webinterface.get_alerts(), config=configs, )
def route_devices(webapp): with webapp.subroute("/devices") as webapp: @webapp.route("/") @require_auth() def page_devices(webinterface, request, session): return webinterface.redirect(request, "/devices/index") @webapp.route("/index") @require_auth() def page_devices_index(webinterface, request, session): page = webinterface.get_template(request, webinterface._dir + "pages/devices/index.html") return page.render( alerts=webinterface.get_alerts(), devices=webinterface._Libraries["devices"]._devicesByUUID, devicetypes=webinterface._DeviceTypes.device_types_by_id, ) @webapp.route("/add") @require_auth() def page_devices_add_select_device_type_get(webinterface, request, session): # session['add_device'] = { # 'start': time(), # 'id': random_string(length=10), # 'stage': 0, # } # webinterface.temp_data[session['add_device']['id']] = {} page = webinterface.get_template(request, webinterface._dir + "pages/devices/add_select_device_type.html") return page.render( alerts=webinterface.get_alerts(), device_types=webinterface._DeviceTypes.device_types_by_id ) @webapp.route("/add_details", methods=["POST"]) @require_auth() @inlineCallbacks def page_devices_add_post(webinterface, request, session): page = webinterface.get_template(request, webinterface._dir + "pages/devices/add_details.html") device_type_id = request.args.get("device_type_id")[0] try: device_type = webinterface._DeviceTypes[device_type_id] except Exception, e: webinterface.add_alert("Device Type ID was not found: %s" % device_type_id, "warning") returnValue(webinterface.redirect(request, "/devices/add")) json_output = None try: json_output = json.loads(request.args.get("json_output")[0]) start_percent = request.args.get("start_percent") status = request.args.get("status")[0] if status == "disabled": status = 0 elif status == "enabled": status = 1 elif status == "deleted": status = 2 else: webinterface.add_alert("Device status was set to an illegal value.", "warning") returnValue(webinterface.redirect(request, "/devices")) pin_required = request.args.get("pin_required")[0] if pin_required == "disabled": pin_required = 0 elif pin_required == "enabled": pin_required = 1 if request.args.get("pin_code")[0] == "": webinterface.add_alert("Device requires a pin code, but none was set.", "warning") returnValue(webinterface.redirect(request, "/devices")) else: webinterface.add_alert("Device pin required was set to an illegal value.", "warning") returnValue(webinterface.redirect(request, "/devices")) energy_usage = request.args.get("energy_usage") energy_map = {} for idx, percent in enumerate(start_percent): try: energy_map[float(float(percent) / 100)] = energy_usage[idx] except: pass energy_map = OrderedDict(sorted(energy_map.items(), key=lambda (x, y): float(x))) device = { "device_id": json_output["device_id"], "label": json_output["label"], "description": json_output["description"], "status": status, "statistic_label": json_output["statistic_label"], "device_type_id": json_output["device_type_id"], "pin_required": pin_required, "pin_code": json_output["pin_code"], "pin_timeout": json_output["pin_timeout"], "energy_type": json_output["energy_type"], "energy_map": energy_map, "variable_data": json_output["vars"], } except: device = { "device_id": "", "label": "", "description": "", "status": 1, "statistic_label": "", "device_type_id": device_type_id, "pin_required": 0, "pin_code": "", "pin_timeout": 0, "energy_type": "calc", "energy_map": {0: 0, 1: 0}, } if json_output is not None: try: global_invoke_all("_device_before_add_", **{"called_by": webinterface, "device": device}) except YomboHookStopProcessing as e: webinterface.add_alert("Adding device was halted by '%s', reason: %s" % (e.name, e.message)) returnValue(webinterface.redirect(request, "/devices")) results = yield webinterface._Devices.add_device(device) if results["status"] == "success": msg = {"header": "Device Added", "label": "Device added successfully", "description": ""} webinterface._Notifications.add( { "title": "Restart Required", "message": 'Device added. A system <strong><a class="confirm-restart" href="#" title="Restart Yombo Gateway">restart is required</a></strong> to take affect.', "source": "Web Interface", "persist": False, "priority": "high", "always_show": True, "always_show_allow_clear": False, "id": "reboot_required", } ) page = webinterface.get_template(request, webinterface._dir + "pages/reboot_needed.html") returnValue(page.render(alerts=webinterface.get_alerts(), msg=msg)) else: webinterface.add_alert("%s: %s" % (results["msg"], results["apimsghtml"])) device["device_id"] = results["device_id"] var_groups = yield webinterface._Libraries["localdb"].get_variable_groups( "device_type", device["device_type_id"] ) var_groups_final = [] for group in var_groups: group = group.__dict__ fields = yield webinterface._Libraries["localdb"].get_variable_fields_by_group(group["id"]) fields_final = [] for field in fields: field = field.__dict__ fields_final.append(field) group["fields"] = fields_final var_groups_final.append(group) # print "final groups: %s" % var_groups_final returnValue( page.render( alerts=webinterface.get_alerts(), device=device, dev_variables=var_groups_final, commands=webinterface._Commands, ) ) @webapp.route("/delayed_commands") @require_auth() def page_devices_delayed_commands(webinterface, request, session): page = webinterface.get_template(request, webinterface._dir + "pages/devices/delayed_commands.html") print "delayed queue active: %s" % webinterface._Devices.delay_queue_active return page.render( alerts=webinterface.get_alerts(), delayed_commands=webinterface._Devices.delay_queue_active ) @webapp.route("/details/<string:device_id>") @require_auth() def page_devices_details(webinterface, request, session, device_id): try: device = webinterface._Devices[device_id] except Exception, e: webinterface.add_alert("Device ID was not found. %s" % e, "warning") return webinterface.redirect(request, "/devices/index") page = webinterface.get_template(request, webinterface._dir + "pages/devices/details.html") return page.render( alerts=webinterface.get_alerts(), device=device, devicetypes=webinterface._DeviceTypes, commands=webinterface._Commands, )
def set(self, key, value, value_type=None, function=None, arguments=None, gateway_id=None): """ Set the value of a given state (key). **Hooks called**: * _states_set_ : Sends kwargs: *key* - The name of the state being set. *value* - The new value to set. :param key: Name of state to set. :param value: Value to set state to. Can be string, list, or dictionary. :param value_type: If set, allows a human filter to be applied for display. :param function: If this a living state, provide a function to be called to get value. Value will be used to set the initial value. :param arguments: kwarg (arguments) to send to function. :return: Value of state """ # logger.debug("Saving state: {key} = {value}", key=key, value=value) if gateway_id is None: gateway_id = self.gateway_id search_chars = ['#', '+'] if any(s in key for s in search_chars): raise YomboWarning("state keys cannot have # or + in them, reserved for searching.") if gateway_id not in self.__States: self.__States[gateway_id] = {} if key in self.__States[gateway_id]: is_new = False # If state is already set to value, we don't do anything. print("stats key exists for gateway... %s" % key) self.__States[gateway_id][key]['updated_at'] = int(round(time())) print("old (%s) == new (%s)" % (self.__States[gateway_id][key]['value'], value)) if self.__States[gateway_id][key]['value'] == value: print("not setting state...it matches....") return self._Statistics.increment("lib.states.set.update", bucket_size=60, anon=True) else: print("New state: %s=%s" % (key, value)) # logger.debug("Saving state: {key} = {value}", key=key, value=value) is_new = True self.__States[gateway_id][key] = { 'gateway_id': gateway_id, 'created_at': int(time()), 'updated_at': int(time()), 'live': False, } self._Statistics.increment("lib.states.set.new", bucket_size=60, anon=True) # Call any hooks try: state_changes = yield global_invoke_all('_states_preset_', **{'called_by': self, 'key': key, 'value': value, 'gateway_id': gateway_id } ) except YomboHookStopProcessing as e: logger.warning("Not saving state '{state}'. Resource '{resource}' raised' YomboHookStopProcessing exception.", state=key, resource=e.by_who) returnValue(None) self.__States[gateway_id][key]['value'] = value self.__States[gateway_id][key]['function'] = function self.__States[gateway_id][key]['arguments'] = arguments if is_new is True or value_type is not None: self.__States[gateway_id][key]['value_type'] = value_type if is_new is True or function is not None: if function is None: self.__States[gateway_id][key]['live'] = False else: self.__States[gateway_id][key]['live'] = True self.__States[gateway_id][key]['value_human'] = self.convert_to_human(value, value_type) # Call any hooks try: state_changes = yield global_invoke_all('_states_set_', **{'called_by': self, 'key': key, 'value': value, 'value_full': self.__States[gateway_id][key], 'gateway_id': gateway_id}) except YomboHookStopProcessing: pass if gateway_id == self.gateway_id: self.db_save_states_data.append((key, self.__States[gateway_id][key])) self.check_trigger(gateway_id, key, value) # Check if any automation items need to fire!