def validate_scene(self, data: Optional[dict] = None): """ Validates the scene (node.data). Removes attributes that are not allowed. """ validate_only = False if data is None: data = self.data validate_only = True for field in SCENE_DATA_COMPONENTS: if field not in data or isinstance(data[field], dict) is False: data[field] = {} if "allow_intents" not in data["configs"]: data["configs"]["allow_intents"] = True if isinstance(data["configs"]["allow_intents"], bool) is False: data["configs"]["allow_intents"] = is_true_false( data["configs"]["allow_intents"]) if "description" not in data["configs"]: data["configs"]["description"] = self.label if "run_on_start" not in data["configs"]: data["configs"]["run_on_start"] = False if isinstance(data["configs"]["run_on_start"], bool) is False: data["configs"]["run_on_start"] = is_true_false( data["configs"]["run_on_start"]) if "run_on_start_forced" not in data["configs"]: data["configs"]["run_on_start_forced"] = False if isinstance(data["configs"]["run_on_start_forced"], bool) is False: data["configs"]["run_on_start_forced"] = is_true_false( data["configs"]["run_on_start_forced"]) for action_id, action in data["actions"].items(): self.validate_action(action_id, action, validate_only=validate_only) return self.sort_actions(data)
def is_direct_controllable(self): """ Return true if this device can be directly controlled. This is usally True, except for instances like relays that control garage doors, etc. :return: """ if self.has_device_feature(FEATURE_CONTROLLABLE) and \ self.has_device_feature(FEATURE_ALLOW_DIRECT_CONTROL) and \ is_true_false(self.allow_direct_control) and \ is_true_false(self.controllable): return True return False
def load_attribute_values_pre_process(self, incoming: dict) -> None: """ Setup basic class attributes based on incoming data. """ self.__dict__["callbacks"] = { "broadcast": [], "accepted": [], "sent": [], "received": [], "pending": [], "failed": [], "canceled": [], "done": [], } # Default values self.__dict__["started"] = False self.__dict__["status"] = DEVICE_COMMAND_STATUS_NEW self.__dict__["created_at"] = time() self.__dict__["call_later"] = None self.__dict__["started"] = incoming.get("started", False) self.__dict__["idempotence"] = incoming.get("idempotence", None) self.__dict__["history"] = [] self.__dict__["started"] = False self.__dict__["uploadable"] = True # Todo: Make this settable. if "uploaded" in incoming: self.__dict__["uploaded"] = is_true_false(incoming["uploaded"], only_bool=True) else: self.__dict__["uploaded"] = False self.gateway_id = incoming.get("gateway_id", self._gateway_id) if self.gateway_id is None: self.gateway_id = self._gateway_id self.update_attributes_pre_process(incoming)
def page_automation_add_get(webinterface, request, session): session.has_access("automation", "*", "add", raise_error=True) data = { "label": webinterface.request_get_default(request, "label", ""), "machine_label": webinterface.request_get_default(request, "machine_label", ""), "description": webinterface.request_get_default(request, "description", ""), "run_on_start": webinterface.request_get_default(request, "run_on_start", True), "status": int(webinterface.request_get_default(request, "status", 1)), } try: data["run_on_start"] = is_true_false(data["run_on_start"]) except: webinterface.add_alert( "Cannot add automation rule. run_on_start must be either true or false.", "warning") return page_automation_form( webinterface, request, session, "add", data, "Add Automation Rule", ) root_breadcrumb(webinterface, request) webinterface.add_breadcrumb(request, "/automation/add", "Add") return page_automation_form(webinterface, request, session, "add", data, "Add Automation Rule")
def add(self, label, machine_label, description, status): """ Add new scene. :param label: :param machine_label: :return: """ self.check_duplicate_scene(label, machine_label) data = { "actions": {}, "config": { "description": description, "enabled": is_true_false(status), }, } new_scene = yield self._Nodes.create(label=label, machine_label=machine_label, node_type="scene", data=data, data_content_type="json", gateway_id=self.gateway_id, destination="gw", status=1) self.patch_scene(new_scene) self.scenes[new_scene.node_id] = new_scene reactor.callLater( 0.001, global_invoke_all, "_scene_added_", called_by=self, scene_id=new_scene.node_id, scene=new_scene, ) return new_scene
def is_controllable(self): """ Returns True if the device can be controlled. This will be False for input type devices, like motion sensors. :return: """ if self.has_device_feature(FEATURE_ALLOW_DIRECT_CONTROL) and \ is_true_false(self.controllable): return True return False
def set_fake_data(self, value): """ If data is fake, we won't sync to anywhere. """ if isinstance(is_true_false(value), bool): self.__dict__["_fake_data"] = value if value is True: self.stop_data_sync() else: self.start_data_sync() return True
def basic_values_run_filter_callback(self, rule, portion, new_value, **kwargs): """ A callback to check if a provided condition is valid before being added as a possible condition. :param rule: The rule. We don't use this here. :param kwargs: None :return: """ filter_value = portion['filter']['value'] # logger.debug("basic_values_run_filter_callback rule name: {name}", name=rule['name']) # logger.debug("Checking new = filter: {new_value} = {filter_value}", # new_value=new_value, # filter_value=filter_value) # logger.debug("Checking as bools: {new_value} = {filter_value}", # new_value=is_true_false(new_value), # filter_value=is_true_false(filter_value)) if 'operator' in portion['filter']: op_func = ops[portion['filter']['operator']] else: op_func = ops['=='] if op_func(new_value, filter_value): return True else: if new_value == filter_value: return True else: try: logger.debug( "basic_values_run_filter_callback - checking if values match as a bool" ) logger.debug( "basic_values_run_filter_callback as bool: {result}", result=op_func(is_true_false(new_value), is_true_false(filter_value))) return op_func(is_true_false(new_value), is_true_false(filter_value)) except: return False return False
def page_automation_edit_rule_post(webinterface, request, session, rule_id): session.has_access("automation", rule_id, "edit", raise_error=True) data = { "label": webinterface.request_get_default(request, "label", ""), "machine_label": webinterface.request_get_default(request, "machine_label", ""), "description": webinterface.request_get_default(request, "description", ""), "run_on_start": webinterface.request_get_default(request, "run_on_start", True), "status": int(webinterface.request_get_default(request, "status", 1)), "rule_id": rule_id, } try: data["run_on_start"] = is_true_false(data["run_on_start"]) except: webinterface.add_alert( "Cannot add automation rule. run_on_start must be either true or false.", "warning") return page_automation_form( webinterface, request, session, "add", data, "Add Automation rule", ) try: rule = webinterface._Automation.edit(rule_id, data["label"], data["machine_label"], data["description"], data["status"], data["run_on_start"]) except YomboWarning as e: webinterface.add_alert( f"Cannot edit automation rule. {e.message}", "warning") root_breadcrumb(webinterface, request) webinterface.add_breadcrumb( request, f"/automation/{rule.rule_id}/details", rule.label) webinterface.add_breadcrumb( request, f"/automation/{rule.rule_id}/edit", "Edit") return page_automation_form( webinterface, request, session, "edit", data, "Edit Automation rule: {rule.label}") webinterface.add_alert(f"Automation rule '{rule.label}' edited.") return webinterface.redirect( request, f"/automation/{rule.rule_id}/details")
def convert_to_human(self, value, value_type): if value_type == 'bool': results = is_true_false(value) if results is not None: return results else: return value elif value_type == 'epoch': return epoch_to_string(value) else: return value
def convert_to_human(self, value, value_type): if value_type == "bool": results = is_true_false(value) if results is not None: return results else: return value elif value_type == "epoch": return epoch_to_string(value) else: return value
def page_automation_add_post(webinterface, request, session): session.has_access("automation", "*", "add", raise_error=True) data = { "label": webinterface.request_get_default(request, "label", ""), "machine_label": webinterface.request_get_default(request, "machine_label", ""), "description": webinterface.request_get_default(request, "description", ""), "run_on_start": webinterface.request_get_default(request, "run_on_start", "true"), "status": int(webinterface.request_get_default(request, "status", 1)), } try: data["run_on_start"] = is_true_false(data["run_on_start"]) except: webinterface.add_alert( "Cannot add automation rule. run_on_start must be either true or false.", "warning") return page_automation_form( webinterface, request, session, "add", data, "Add Automation rule", ) try: rule = yield webinterface._Automation.add( data["label"], data["machine_label"], data["description"], data["status"], data["run_on_start"]) except YomboWarning as e: webinterface.add_alert( f"Cannot add automation rule. {e.message}", "warning") return page_automation_form( webinterface, request, session, "add", data, "Add Automation rule", ) webinterface.add_alert( f"New automation rule '{rule.label}' added.") return webinterface.redirect( request, f"/automation/{rule.rule_id}/details")
def field_remap(self, data, config_data): new_data = {} table_meta = self._LocalDB.db_model[config_data["table"]] key = None value = None try: for key, value in data.items( ): # we must re-map AMQP names to local names. Removes ones without a DB column too. if key in config_data["map"]: # Convert ints and floats. if value is None: pass elif table_meta[config_data["map"] [key]]["type"] == "INTEGER": value = int(value) elif table_meta[config_data["map"][key]]["type"] == "REAL": value = float(value) elif table_meta[config_data["map"] [key]]["type"] == "BOOLEAN": value = is_true_false(value) if key == "energy_map": if isinstance(value, dict): try: value = json.dumps(value) except Exception as e: value = '{"0.0":0,"1.0":0}' new_data[config_data["map"][key]] = value else: new_data[key] = value return new_data except Exception as e: print(f"error in field remap. Last key: {key}") print(f"input value: {value}") print(f"field remap - config_data = {config_data}") print(f"field remap - data = {data}") print(f"tablemeta: {table_meta}") logger.error("--------==(Error: {e})==--------", e=e) logger.error( "--------------------------------------------------------") logger.error("{error}", error=sys.exc_info()) logger.error( "---------------==(Traceback)==--------------------------") logger.error("{trace}", trace=traceback.print_exc(file=sys.stdout)) logger.error( "--------------------------------------------------------")
def convert_to_human(self, value, value_type): """ Convert various value types to a more human friendly display. :param value: :param value_type: :return: """ if value_type == "bool": results = is_true_false(value) if results is not None: return results else: return value elif value_type == "epoch": return converters.epoch_to_string(value) else: return value
def edit(self, label=None, machine_label=None, description=None, status=None, allow_intents=None): """ Edit various scene attributes. This anti-pythonic due to syncing back to to yombo api and the database. :param scene_id: :param label: :param machine_label: :param description: :param status: :return: """ if label is not None and machine_label is not None: self._Scenes.check_duplicate_scene(label, machine_label, self.node_id) updates = {} if label is not None: updates["label"] = label if machine_label is not None: updates["machine_label"] = label if description is not None: self.data["configs"]["description"] = description if status is not None: updates["status"] = 1 if is_true_false(status, only_bool=True) else 0 if allow_intents is not None: self.data["configs"]["allow_intents"] = allow_intents self.update(updates) reactor.callLater(0.001, global_invoke_all, "_scene_edited_", called_by=self, arguments={ "scene_id": self.node_id, "scene": self, })
def edit(self, scene_id, label=None, machine_label=None, description=None, status=None, allow_intents=None): """ Edit a scene label and machine_label. :param scene_id: :param label: :param machine_label: :param description: :param status: :return: """ if label is not None and machine_label is not None: self.check_duplicate_scene(label, machine_label, scene_id) scene = self.get(scene_id) if label is not None: scene.label = label if machine_label is not None: scene.machine_label = machine_label if description is not None: scene.data["config"]["description"] = description if status is not None: scene.status = is_true_false(status) scene.data["config"]["enabled"] = scene.status if allow_intents is not None: scene.data["config"]["allow_intents"] = allow_intents reactor.callLater( 0.001, global_invoke_all, "_scene_edited_", called_by=self, scene_id=scene_id, scene=scene, ) return scene
def coerce_value(value, value_type): """ Convert a value to it's intended type. Typically used when loading data from databases. :param value:2502jXYQR1fcSyPc :param value_type: one of - string, bool, int, float, epoch. Unknowns will be converted to strings. :return: """ if value_type is None: return value if value_type.lower() in ("str", "string"): return str(value) elif value_type.lower() in ("int", "integer", "epoch", "number"): return int(value) elif value_type.lower() == "float": return float(value) elif value_type.lower() in ("bool", "boolean"): return is_true_false(value) else: return value
def coerce_value(value, value_type): """ Convert a value to it's intended type. Typically used when loading data from databases. :param value:2502jXYQR1fcSyPc :param value_type: one of - string, bool, int, float, epoch. Unknowns will be converted to strings. :return: """ if value_type is None: return value if value_type.lower() in ('str', 'string'): return str(value) elif value_type.lower() in ('int', 'integer', 'epoch', 'number'): return int(value) elif value_type.lower() == 'float': return float(value) elif value_type.lower() == 'bool': return is_true_false(value) else: return value
def page_setup_wizard_6_post(webinterface, request, session): """ Last step is to handle the GPG key. One of: create a new one, import one, or select an existing one. :param webinterface: :param request: :param session: :return: """ result_output = "" if session.get('setup_wizard_last_step', 1) not in (5, 6): webinterface.add_alert( "Invalid wizard state. Please don't use the browser forward or back buttons." ) return webinterface.redirect(request, '/setup_wizard/1') session.set('setup_wizard_6_post', 1) try: submitted_gpg_action = request.args.get('gpg_action')[ 0] # underscore here due to jquery except: webinterface.add_alert( "Please select an appropriate GPG/PGP Key action.") return webinterface.redirect(request, '/setup_wizard/5') if session['setup_wizard_gateway_id'] == 'new': data = { 'machine_label': session['setup_wizard_gateway_machine_label'], 'label': session['setup_wizard_gateway_label'], 'description': session['setup_wizard_gateway_description'], } try: results = yield webinterface._YomboAPI.request( 'POST', '/v1/gateway', data, session['yomboapi_session']) except YomboWarning as e: webinterface.add_alert(e.html_message, 'warning') return webinterface.redirect(request, '/setup_wizard/3') session['setup_wizard_gateway_id'] = results['data']['id'] else: data = { 'label': session['setup_wizard_gateway_label'], 'description': session['setup_wizard_gateway_description'], } results = yield webinterface._YomboAPI.request( 'PATCH', '/v1/gateway/%s' % session['setup_wizard_gateway_id']) if results['code'] > 299: webinterface.add_alert(results['content']['html_message'], 'warning') return webinterface.redirect(request, '/setup_wizard/5') results = yield webinterface._YomboAPI.request( 'GET', '/v1/gateway/%s/new_hash' % session['setup_wizard_gateway_id']) if results['code'] > 299: webinterface.add_alert(results['content']['html_message'], 'warning') return webinterface.redirect(request, '/setup_wizard/5') # webinterface._Configs.set('core', 'updated', results['data']['updated_at']) # webinterface._Configs.set('core', 'created', results['data']['created_at']) # print("new gwid: %s" % results['data']['id']) # print("got gwid before set: %s" % webinterface._Configs.get('core', 'gwid')) webinterface._Configs.set('core', 'gwid', results['data']['id']) # print("got gwid after set: %s" % webinterface._Configs.get('core', 'gwid')) webinterface._Configs.set('core', 'gwuuid', results['data']['uuid']) webinterface._Configs.set( 'core', 'machine_label', session['setup_wizard_gateway_machine_label']) webinterface._Configs.set('core', 'label', session['setup_wizard_gateway_label']) webinterface._Configs.set( 'core', 'description', session['setup_wizard_gateway_description']) webinterface._Configs.set('core', 'gwhash', results['data']['hash']) webinterface._Configs.set( 'core', 'is_master', is_true_false(session['setup_wizard_gateway_is_master'])) webinterface._Configs.set( 'core', 'master_gateway', session['setup_wizard_gateway_master_gateway']) webinterface._Configs.set('security', 'amqpsendstatus', session['setup_wizard_security_status']) webinterface._Configs.set( 'security', 'amqpsendgpsstatus', session['setup_wizard_security_gps_status']) webinterface._Configs.set( 'security', 'amqpsendprivatestats', session['setup_wizard_security_send_private_stats']) webinterface._Configs.set( 'security', 'amqpsendanonstats', session['setup_wizard_security_send_anon_stats']) webinterface._Configs.set('location', 'latitude', session['setup_wizard_gateway_latitude']) webinterface._Configs.set( 'location', 'longitude', session['setup_wizard_gateway_longitude']) webinterface._Configs.set( 'location', 'elevation', session['setup_wizard_gateway_elevation']) webinterface._Configs.set('core', 'first_run', False) # Remove wizard settings... for session_key in list(session.keys()): if session_key.startswith('setup_wizard_'): del session[session_key] session['setup_wizard_done'] = True session['setup_wizard_last_step'] = 7 print("gf 1") if submitted_gpg_action == 'new': # make GPG keys! print("gf 2") logger.info("New gpg key will be generated on next restart.") # reactor.callLater(0.0001, webinterface._GPG.generate_key) # yield webinterface._GPG.generate_key() elif submitted_gpg_action == 'import': # make GPG keys! try: submitted_gpg_private = request.args.get( 'gpg-private-key')[0] except: webinterface.add_alert( "When importing, must have a valid private GPG/PGP key." ) return webinterface.redirect(request, '/setup_wizard/5') try: submitted_gpg_public = request.args.get( 'gpg-public-key')[0] except: webinterface.add_alert( "When importing, must have a valid public GPG/PGP key." ) return webinterface.redirect(request, '/setup_wizard/5') else: gpg_existing = yield webinterface._LocalDB.get_gpg_key() if submitted_gpg_action in gpg_existing: key_ascii = webinterface._GPG.get_key(submitted_gpg_action) webinterface._Configs.set('gpg', 'keyid', submitted_gpg_action) webinterface._Configs.set('gpg', 'keyascii', key_ascii) else: webinterface.add_alert("Existing GPG/PGP key not fount.") return webinterface.redirect(request, '/setup_wizard/5') print("gj 1") session['gpg_selected'] = submitted_gpg_action session['setup_wizard_last_step'] = 6 print("gj 4") results = yield form_setup_wizard_6(webinterface, request, session) print("gj 5") return results
def new(self, machine_label: str, label: str, description: str, preserve_key: Optional[bool] = None, status: Optional[int] = None, roles: Optional[List[str]] = None, request_by: Optional[str] = None, request_by_type: Optional[str] = None, request_context: Optional[str] = None, authentication: Optional[Type[AuthMixin]] = None, last_access_at: Optional[Union[int, float]] = None, auth_key_id: Optional[str] = None, auth_key_id_full: Optional[str] = None, load_source: Optional[str] = None, **kwargs) -> AuthKey: """ Create a new auth_key. To track how the authkey was created, either request_by and request_by_type or an authentication item can be anything with the authmixin, such as a user, websession, or authkey. :param machine_label: Authkey machine_label :param label: Authkey human label. :param description: Authkey description. :param preserve_key: If true, the original auth_id will be available from auth_key :param status: 0 - disabled, 1 - enabled, 2 - deleted :param roles: Assign authkey to a list of roles. :param request_by: Who created the Authkey. "alexamodule" :param request_by_type: What type of item created it: "module" :param request_context: Some additional information about where the request comes from. :param authentication: An auth item such as a websession or authkey. :param last_access_at: When auth was last used. :param auth_key_id: Authkey id to use, not normally set. :param auth_key_id_full: Full auth key id to use, not normally set. :param load_source: How the authkey was loaded. :return: """ preserve_key = is_true_false(preserve_key) or True if request_context is None: request_context = caller_string() # get the module/class/function name of caller try: results = self.get(machine_label) raise YomboWarning( { "id": results.auth_key_id, "title": "Duplicate entry", "something": "hahahaha", "detail": "An authkey with that machine_label already exists." }) except KeyError as e: pass logger.debug("authkey new: about to load a new item....: {machine_label}", machine_label=machine_label) results = yield self.load_an_item_to_memory( { "id": auth_key_id, "auth_key_id_full": auth_key_id_full, "machine_label": machine_label, "label": label, "description": description, "preserve_key": preserve_key, "status": status, "roles": roles, "request_by": request_by, "request_by_type": request_by_type, "request_context": request_context, "last_access_at": last_access_at, }, authentication=authentication, load_source=load_source) return results
def new(self, title: str, message: str, gateway_id: Optional[str] = None, priority: Optional[str] = None, persist: Optional[bool] = None, timeout: Optional[Union[int, float]] = None, expire_at: Optional[Union[int, float]] = None, always_show: Optional[bool] = None, always_show_allow_clear: Optional[bool] = None, notice_type: Optional[str] = None, notice_id: Optional[str] = None, local: Optional[bool] = None, targets: Optional[List[str]] = None, request_by: Optional[str] = None, request_by_type: Optional[str] = None, request_context: Optional[str] = None, meta: Optional[dict] = None, acknowledged: Optional[bool] = None, acknowledged_at: Optional[int] = None, created_at: Optional[Union[int, float]] = None, # gateway_id: Optional[str] = None, create_event: Optional[bool] = None): """ Add a new notice. :param title: Title, or label, for the not :returns: Pointer to new notice. Only used during unittest """ if gateway_id is None: gateway_id = self._gateway_id if priority not in ("low", "normal", "high", "urgent"): priority = "normal" if persist is None: persist = False if always_show is None: always_show = False else: always_show = is_true_false(always_show) if isinstance(always_show, bool) is False: raise YomboWarning(f"always_show must be True or False, got: {always_show}") if always_show_allow_clear is None: always_show_allow_clear = False else: always_show_allow_clear = is_true_false(always_show_allow_clear) if isinstance(always_show_allow_clear, bool) is False: raise YomboWarning("always_show must be True or False.") if notice_type is None: notice_type = "notice" if notice_type not in ("notice"): raise YomboWarning("Invalid notification type.") if notice_id is None: notice_id = random_string(length=50) notice_id = self._Hash.sha224_compact(notice_id) if local is None: local = True else: local = is_true_false(local) if persist is True and always_show_allow_clear is False: raise YomboWarning(f"New notification cannot be 'persist'=True and 'always_show_allow_clear'=False..{title}") if isinstance(expire_at, int) or isinstance(expire_at, float): expire_at = time() + expire_at elif isinstance(timeout, int) or isinstance(timeout, float): expire_at = time() + timeout elif expire_at is not None: raise YomboWarning("expire_at must be int or float.") elif timeout is not None: raise YomboWarning("timeout must be int or float.") if persist is True and expire_at is None: expire_at = time() + 60*60*24*30 # keep persistent notifications for 30 days. if targets is not None: # tags on where to send notifications if isinstance(targets, list) is False: if isinstance(targets, str): targets = [targets] else: raise YomboWarning("targets argument must be a list of strings.") for target in targets: if isinstance(target, str) is False: raise YomboWarning("targets argument must be a list of strings.") if created_at is None: created_at = time() if acknowledged is None: acknowledged = False else: acknowledged = is_true_false(acknowledged) if isinstance(acknowledged_at, float): acknowledged_at = int(acknowledged_at) if acknowledged is True and acknowledged_at is None: acknowledged_at = int(time) notice = { "id": notice_id, "title": title, "message": message, "gateway_id": gateway_id, "priority": priority, "persist": persist, "always_show": always_show, "always_show_allow_clear": always_show_allow_clear, "type": notice_type, "local": local, "targets": targets, "request_by": request_by, "request_by_type": request_by_type, "request_context": request_context, "meta": meta, "acknowledged": acknowledged, "acknowledged_at": acknowledged_at, "expire_at": expire_at, "created_at": created_at, } if notice_id in self.notifications: del notice["id"] self.notifications[notice_id].update(notice) return self.notifications[notice_id] logger.debug("notice: {notice}", notice=notice) notification = yield self.load_an_item_to_memory(notice, load_source="local") reactor.callLater(.0001, global_invoke_all, "_notification_new_", called_by=self, arguments={ "notification": notification, "target": targets, "event": notification.to_dict(), } ) return notification
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 "targets" not in notice: # tags on where to send notifications notice["targets"] = [] if isinstance(notice["targets"], str): notice["targets"] = [notice["targets"]] 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) self.notifications.prepend(notice["id"], Notification(self, notice)) for target in notice["targets"]: reactor.callLater(.0001, global_invoke_all, "_notification_target_", called_by=self, notification=self.notifications[notice["id"]], target=target, event=self.notifications[notice["id"]].asdict()) return notice["id"]
def __init__(self, data, parent, start=None): """ Get the instance setup. :param data: Basic details about the device command to get started. :param parent: A pointer to the device types instance. """ # print("new device_comamnd: %s" % data) self._status = 0 self._Parent = parent self.source_gateway_id = data.get('source_gateway_id', self._Parent.gateway_id) self.local_gateway_id = self._Parent.gateway_id self.request_id = data['request_id'] if 'device' in data: self.device = data['device'] elif 'device_id' in data: self.device = parent.get(data['device_id']) else: raise ValueError("Must have either device reference, or device_id") if 'command' in data: self.command = data['command'] elif 'command_id' in data: self.command = parent._Commands.get(data['command_id']) else: raise ValueError( "Must have either command reference, or command_id") self.inputs = data.get('inputs', None) if 'history' in data: self.history = data['history'] else: self.history = [] self.requested_by = data['requested_by'] self.status = data.get('status', 'new') self.command_status_received = is_true_false( data.get( 'command_status_received', False)) # if a status has been reported against this request self.persistent_request_id = data.get('persistent_request_id', None) self.broadcast_at = data.get( 'broadcast_at', None) # time when command was sent through hooks. self.accepted_at = data.get( 'accepted_at', None) # when a module accepts the device command for processing self.sent_at = data.get( 'sent_at', None ) # when a module or receiver sent the command to final end-point self.received_at = data.get( 'received_at', None) # when the command was received by the final end-point self.pending_at = data.get( 'pending_at', None ) # if command takes a while to process time, this is the timestamp of last update self.finished_at = data.get( 'finished_at', None ) # when the command is finished and end-point has changed state self.not_before_at = data.get('not_before_at', None) self.not_after_at = data.get('not_after_at', None) self.pin = data.get('pin', None) self.call_later = None self.created_at = data.get('created_at', time()) self._dirty = is_true_false(data.get('dirty', True)) self._source = data.get('_source', None) self.started = data.get('started', False) if self._source == 'database': self._dirty = False self._in_db = True elif self._source == 'gateway_coms': self._dirty = False self._in_db = False reactor.callLater(1, self.check_if_device_command_in_database) else: self.history.append((self.created_at, self.status, 'Created.', self.local_gateway_id)) self._in_db = False if self.device.gateway_id == self.local_gateway_id: # print("I should start....") self.started = False start = True if start is None or start is True: reactor.callLater(0.001, self.start)
def _init_(self, **kwargs): """ Gets various configuration items and determines if the system is running for the first time, needs configuration setup, or should run as normal. It also validates that the system has a valid API login and API session. This is used to interact with the Yombo API cloud. :param kwargs: :return: """ self.configs_needed = [] self.configs_needed_human = [] self.gwid = self._Configs.get("core.gwid", "local", False) self.gwhash = self._Configs.get("core.gwhash", None, False) self.has_valid_gw_auth = False if self._Loader.operating_mode == "first_run" or self._Configs.get( "core.first_run", False, False): self._Loader.operating_mode = "first_run" self.configs_needed = ['gwid', 'gwhash'] return if self.gwid is None or self.gwid == "": self.configs_needed_human.append( "Gateway ID is missing. Please complete the setup wizard again." ) self._Loader.operating_mode = "first_run" self.configs_needed = ['gwid'] return if self.gwhash is None or self.gwhash == "": print("setting to config mode ... 11") self._Loader.operating_mode = "config" self.configs_needed = ['gwhash'] self.configs_needed_human.append("Gateway password is missing.") return if len(self.configs_needed_human) == 0: has_valid_credentials = self._YomboAPI.gateway_credentials_is_valid if has_valid_credentials is False: print("setting to config mode ... 22") self._Loader.operating_mode = "config" self.configs_needed_human.append( "Gateway ID is invalid or has invalid authentication info." ) return else: # If we have a valid gateway, download it's details. try: response = yield self._YomboAPI.request( "GET", f"/v1/gateways/{self.gwid}") except YomboWarning as e: logger.warn("Unable to get gateway details:{e}", e=e) self._Loader.operating_mode = "config" self.configs_needed = ['gwhash'] self.configs_needed_human.append( "Gateway password is missing.") return gateway = response.content["data"]["attributes"] self._Configs.set("core.is_master", is_true_false(gateway["is_master"])) self._Configs.set("core.master_gateway_id", gateway["master_gateway_id"]) self._Configs.set("core.created_at", gateway["created_at"]) self._Configs.set("core.updated_at", gateway["updated_at"]) self._Configs.set("core.machine_label", gateway["label"]) self._Configs.set("core.label", gateway["label"]) self._Configs.set("core.description", gateway["description"]) self._Configs.set("core.owner_id", gateway["user_id"]) self._Configs.set("dns.fqdn", gateway["dns_name"]) if gateway["dns_name"] is not None: try: response = yield self._YomboAPI.request( "GET", f"/v1/gateways/{self.gwid}/dns") except YomboWarning as e: logger.warn("Unable to get gateway dns details:{e}", e=e) self._Loader.operating_mode = "config" self.configs_needed = ['gwhash'] self.configs_needed_human.append( "Gateway password is missing.") return gateway_dns = response.content["data"]["attributes"] self._Configs.set("dns.domain_id", gateway_dns["dns_domain_id"]) self._Configs.set("dns.name", gateway_dns["name"]) self._Configs.set("dns.allow_change_at", gateway_dns["allow_change_at"]) self._Configs.set("dns.domain", gateway_dns["domain"]) self._Configs.set( "dns.fqdn", f"{gateway_dns['name']}.{gateway_dns['domain']}") else: self._Configs.set("dns.domain_id", None) self._Configs.set("dns.name", None) self._Configs.set("dns.allow_change_at", None) self._Configs.set("dns.domain", None) self._Configs.set("dns.fqdn", None) is_master = self._Configs.get("core.is_master", True) if is_master is False: master_gateway_id = self._Configs.get("core.master_gateway_id", None, False) if master_gateway_id is None or master_gateway_id == "": self.configs_needed_human.append( "Gateway is marked as slave, but no master gateway set.") if len(self.configs_needed_human) > 0: needed_text = "</li><li>".join(self.configs_needed_human) yield self._Notifications.new( title="Need configurations", message= f"System has been placed into configuration mode. The following " f"configurations are needed:<p><ul><li>{needed_text}</li></ul>", request_context=self._FullName, persist=False, priority="high", always_show=True, always_show_allow_clear=True) self._Loader.operating_mode = "config" return self._Loader.operating_mode = "run"
def update_attributes_postprocess(self, incoming): try: if "device" in incoming: self.device_id = incoming["device"].device_id elif "device_id" in incoming: self.device = self._Devices.get(incoming["device_id"]) else: raise YomboWarning( "Device command must have either a device instance or device_id." ) except: raise YomboWarning( "Device command is unable to find a matching device for the provided device command." ) try: if "command" in incoming: self.command_id = incoming["command"].command_id elif "command_id" in incoming: self.command = self._Commands.get(incoming["command_id"]) else: raise YomboWarning( "Device command must have either a command instance or command_id." ) except: raise YomboWarning( "Device command is unable to find a matching command for the provided device command." ) if self.history is None: self.history = [] # TODO: Check to make sure there's some from of auth_id - system or user. # if "auth_id" in incoming: # self.auth_id = incoming["auth_id"] if self.status is None: self.status = "new" if self.created_at is None: self.created_at = time() if isinstance(self.command_status_received, bool) is False: self.command_status_received = is_true_false( self.command_status_received) self.call_later = None self.started = incoming.get("started", False) self.idempotence = incoming.get("idempotence", None) if len(self.history) == 0: self.history.append( self.history_dict(self.created_at, self.status, "Created.", self.gateway_id)) if self.device.gateway_id == self.gateway_id: self.started = False start = True # Allows various callbacks to be called when status changes. if "callbacks" in incoming and incoming["callbacks"] is not None: for cb_status, cb_callback in incoming["callbacks"].items(): self.add_callback(cb_status, cb_callback) if start is None or start is True: reactor.callLater(0.0001, self.start)
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 page_setup_wizard_dns_post(webinterface, request, session): """ Last step is to handle the DNS hostname. :param webinterface: :param request: :param session: :return: """ if session.get("setup_wizard_last_step", 1) not in ("advanced_settings", "dns", "finished"): session.add_alert( "Invalid wizard state. Please don't use the browser forward or back buttons." ) return webinterface.redirect(request, "/setup_wizard/select_gateway") valid_submit = True try: submitted_gateway_master_gateway_id = request.args.get( "master-gateway-id")[0] if submitted_gateway_master_gateway_id == "local": submitted_gateway_is_master = 1 submitted_gateway_master_gateway_id = None else: submitted_gateway_is_master = 0 except: valid_submit = False session.add_alert("Invalid Master Gateway.") try: submitted_security_status = request.args.get( "security-status")[0] except: valid_submit = False session.add_alert("Invalid Gateway Device Send Status.") if valid_submit is False: return webinterface.redirect( request, "/setup_wizard/advanced_settings") try: submitted_security_send_private_stats = request.args.get( "security-send-private-stats")[0] except: valid_submit = False session.add_alert("Invalid send private stats.") if valid_submit is False: return webinterface.redirect( request, "/setup_wizard/advanced_settings") try: submitted_security_send_anon_stats = request.args.get( "security-send-anon-stats")[0] except: valid_submit = False session.add_alert("Invalid send anonymous statistics.") if valid_submit is False: return webinterface.redirect( request, "/setup_wizard/advanced_settings") session[ "setup_wizard_gateway_is_master"] = submitted_gateway_is_master session[ "setup_wizard_gateway_master_gateway_id"] = submitted_gateway_master_gateway_id session["setup_wizard_security_status"] = submitted_security_status session[ "setup_wizard_security_send_private_stats"] = submitted_security_send_private_stats session[ "setup_wizard_security_send_anon_stats"] = submitted_security_send_anon_stats auth_header = yield session.authorization_header(request) if session["setup_wizard_gateway_id"] == "new": data = { "machine_label": session["setup_wizard_gateway_machine_label"], "label": session["setup_wizard_gateway_label"], "description": session["setup_wizard_gateway_description"], "is_master": session["setup_wizard_gateway_is_master"], "status": 1 } if session[ "setup_wizard_gateway_master_gateway_id"] is not None: data["master_gateway"] = session[ "setup_wizard_gateway_master_gateway_id"], try: response = yield webinterface._YomboAPI.request( "POST", "/v1/gateways", data, authorization_header=auth_header) except YomboWarning as e: for error in e.errors: session.add_alert( f"Unable to add gateway, Yombo API responded with: ({error['code']}) {error['title']} - " f"{error['detail']}", "warning") return webinterface.redirect( request, "/setup_wizard/basic_settings") # print(f"response.content: {response.content}") session["setup_wizard_gateway_id"] = response.content["data"][ "attributes"] else: data = { "label": session["setup_wizard_gateway_label"], "description": session["setup_wizard_gateway_description"], } try: response = yield webinterface._YomboAPI.request( "PATCH", f"/v1/gateways/{session['setup_wizard_gateway_id']}", data, authorization_header=auth_header) except YomboWarning as e: for error in e.errors: session.add_alert( f"Unable to setup gateway, Yombo API responded with: ({error['code']}) {error['title']} -" f" {error['detail']}", "warning") return webinterface.redirect(request, "/setup_wizard/dns") try: response = yield webinterface._YomboAPI.request( "GET", f"/v1/gateways/{session['setup_wizard_gateway_id']}/reset_authentication", authorization_header=auth_header) except YomboWarning as e: for error in e.errors: session.add_alert( f"Unable to setup gateway, Yombo API responded with: ({error['code']}) {error['title']} -" f" {error['detail']}", "warning") return webinterface.redirect(request, "/setup_wizard/dns") new_auth = response.content["data"]["attributes"] print(f"new_auth: {new_auth}") webinterface._Configs.set("core", "gwid", new_auth["id"]) webinterface._Configs.set("core", "gwuuid", new_auth["uuid"]) webinterface._Configs.set( "core", "machine_label", session["setup_wizard_gateway_machine_label"]) webinterface._Configs.set("core", "label", session["setup_wizard_gateway_label"]) webinterface._Configs.set( "core", "description", session["setup_wizard_gateway_description"]) webinterface._Configs.set("core", "gwhash", new_auth["hash"]) webinterface._Configs.set( "core", "is_master", is_true_false(session["setup_wizard_gateway_is_master"])) webinterface._Configs.set( "core", "master_gateway_id", session["setup_wizard_gateway_master_gateway_id"]) webinterface._Configs.set("security", "amqpsenddevicestatus", session["setup_wizard_security_status"]) webinterface._Configs.set( "security", "amqpsendprivatestats", session["setup_wizard_security_send_private_stats"]) webinterface._Configs.set( "security", "amqpsendanonstats", session["setup_wizard_security_send_anon_stats"]) webinterface._Configs.set("location", "latitude", session["setup_wizard_gateway_latitude"]) webinterface._Configs.set( "location", "longitude", session["setup_wizard_gateway_longitude"]) webinterface._Configs.set( "location", "elevation", session["setup_wizard_gateway_elevation"]) webinterface._Configs.set("core", "first_run", False) # Remove wizard settings... for session_key in list(session.keys()): if session_key.startswith("setup_wizard_"): del session[session_key] session["setup_wizard_done"] = True session["setup_wizard_last_step"] = "finished" logger.info("New gpg key will be generated on next restart.") session["setup_wizard_last_step"] = "dns" results = yield form_setup_wizard_dns(webinterface, request, session) return results