Пример #1
0
    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(f"Notification not found: {notice_id}")

        if new_ack is None:
            new_ack = True

        if acknowledged_at is None:
            acknowledged_at = int(time())
        self.notifications[notice_id].ack(acknowledged_at, new_ack)
        try:
            global_invoke_all("_notification_acked_",
                              called_by=self,
                              arguments={
                                  "notification": self.notifications[notice_id],
                                  "event": {
                                    "notification_id": notice_id,
                                    },
                                  }
                              )
        except YomboHookStopProcessing:
            pass
Пример #2
0
    def delete(self, notice_id):
        """
        Deletes a provided notification.

        :param notice_id:
        :return:
        """
        notice_id = self._Hash.sha224_compact(notice_id)
        try:
            notice = self.notifications[notice_id]
        except KeyError:
            return

        try:
            global_invoke_all("_notification_delete_",
                              called_by=self,
                              arguments={
                                  "notification": notice,
                                  "event": {
                                    "notification_id": notice_id,
                                    }
                                  }
                              )
        except YomboHookStopProcessing:
            pass

        try:
            del self.notifications[notice_id]
            self._LocalDB.delete_notification(notice_id)
        except:
            pass
    def _generic_class_storage_load_to_memory(self, storage, klass, incoming,
                                              source, **kwargs):
        """
        Loads data into memory using basic hook calls.

        :param storage: Dictionary to store new data in.
        :param klass: The class to use to store the data
        :param incoming: Data to be saved
        :return:
        """

        run_phase_name, run_phase_int = self._Loader.run_phase
        if run_phase_int < 4000:  # just before 'libraries_started' is when we start processing automation triggers.
            call_hooks = False
        else:
            call_hooks = True

        # print(f"_generic_class_storage_load_to_memory: {self._FullName} - incoming: {incoming}")

        storage_id = incoming["id"]

        hook_prefix = self._Parent._class_storage_load_hook_prefix,

        if call_hooks:
            global_invoke_all(
                f"_{hook_prefix}_before_load_",
                called_by=self,
                id=storage_id,
                data=incoming,
            )
        try:
            storage[storage_id] = klass(self,
                                        incoming,
                                        source=source,
                                        **kwargs)
            yield maybeDeferred(storage[storage_id]._init_, **kwargs)

        except Exception as e:
            logger.error("Error while creating {label} instance: {e}",
                         label=self._Parent._class_storage_load_hook_prefix,
                         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(
                "--------------------------------------------------------")
            raise YomboWarning(f"Unable to create DB model: {e}")

        if call_hooks:
            global_invoke_all(
                f"_{hook_prefix}_loaded_",
                called_by=self,
                id=storage_id,
                data=storage[storage_id],
            )
        return storage[storage_id]
Пример #4
0
    def edit_node(self, node_id, node_data, source=None, authorization=None, **kwargs):
        """
        This shouldn't be used by outside calls, instead, tp update the node, simply edit
        the node attributes directly. That will cause the node to update the database and Yombo API.

        This is used by other internal libraries to update a node's data in bulk and
        optionally

        :param node_id: Node ID to bulk update.
        :param node_data: Dictionary of items to update
        :param source: Should be: local or remote. Default is local.
        :param kwargs:
        :return:
        """
        gateway_id = self.gateway_id

        if source is None:
            source = "local"

        # print("editing node: %s" % node_id)
        if isinstance(node_data, dict) is False:
            raise YomboWarning("edit_node() only accepts dictionaries for 'node_data' argument.")

        if "data" not in node_data:
            raise YomboWarning("Cannot edit node, 'data' not found")


        global_invoke_all("_node_before_update_",
                          called_by=self,
                          node_id=node_id,
                          node=node_data,
                          in_memory=node_id in self.nodes,
                          )

        if source == "local":
            api_data = deepcopy(node_data)
            node_data["data"] = data_pickle(api_data["data"], api_data["data_content_type"])
            # print("new node data: %s" % api_data)
            api_to_send = {k: v for k, v in bytes_to_unicode(api_data).items() if v}

            response = yield self.patch_node(node_id=node_id, api_data=api_to_send, authorization=authorization)

            node_data = response.content["data"]['attributes']

        # Now we have the final data, lets update the local info.
        node_id = node_data["id"]
        if node_id in self.nodes:
            self.nodes[node_id].update_attributes(node_data)  # Update existing node data.
            self.nodes[node_id].save_to_db()

        global_invoke_all("_node_updated_",
                          called_by=self,
                          node_id=node_id,
                          node=self.nodes[node_id],
                          )
        return self.nodes[node_id]
Пример #5
0
    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), False)

        # Check if any libraries or modules need certs.
        if self.local_gateway.dns_name is None:
            logger.warn(
                "Unable to generate sign ssl/tls certs, gateway has no domain name."
            )
            return

        if self._Loader.operating_mode != "run":
            return
        sslcerts = yield global_invoke_all(
            "_sslcerts_",
            called_by=self,
        )
        for component_name, ssl_certs in sslcerts.items():
            logger.debug(
                f"Adding new managed certs from hook: {component_name}")
            if isinstance(ssl_certs, tuple) is False and isinstance(
                    ssl_certs, list) is False:
                ssl_certs = [ssl_certs]
            for ssl_item in ssl_certs:
                yield self.add_sslcert(ssl_item)
Пример #6
0
    def _load_(self, **kwargs):
        """
        Start the roles library by loading the roles from the yombo.toml file. This also calls the _roles_ hook
        to see if any libraries or modules have any additional roles to add.

        :param kwargs:
        :return:
        """
        results = yield global_invoke_all("_roles_", called_by=self)
        logger.debug("_roles_ results: {results}", results=results)
        for component, roles in results.items():
            for machine_label, role_data in roles.items():
                entity_type = component._Entity_type
                if "load_source" in role_data:
                    load_source = role_data["load_source"]
                    del role_data["source"]
                elif entity_type == "yombo_module":
                    load_source = "local"
                else:
                    load_source = "library"
                role_data["machine_label"] = machine_label
                self.new(role_data, load_source=load_source)

        if self._Loader.operating_mode != "run":
            return
Пример #7
0
    def _load_(self, **kwargs):
        self._checkExpiredLoop = LoopingCall(self.check_expired)
        self._checkExpiredLoop.start(
            self._Configs.get("notifications", "check_expired", 121, False),
            False)

        yield self._class_storage_load_from_database()

        results = yield global_invoke_all(
            "_notification_get_targets_",
            called_by=self,
        )

        for component_name, data in results.items():
            logger.debug("Adding notification target: {component_name}",
                         component_name=component_name)
            if isinstance(data, dict) is False:
                continue
            for target, description in data.items():
                if target not in self.notification_targets:
                    self.notification_targets[target] = []

                self.notification_targets[target].append({
                    "description":
                    description,
                    "component":
                    component_name,
                })
Пример #8
0
    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)
Пример #9
0
    def _modules_imported_(self, **kwargs):
        """
        Call the '_amqpyombo_options_' hook to check if other modules should do something when the AMQPYombo connection
        opens and closes. It also checks if other modules have routes that can handle incoming messages.
        when t
        :param kwargs:
        :return:
        """
        results = yield global_invoke_all("_amqpyombo_options_", called_by=self)
        connected_callbacks = []  # Have to put this here since _modules_imported_ is invoked well after the initial
                                  # connection has been started.
        for component_name, data in results.items():
            if "connected" in data:
                self.amqpyombo_options["connected"].append(data["connected"])
                connected_callbacks.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)

        if self.connected is True:
            for callback in self.amqpyombo_options["connected"]:
                callback()
Пример #10
0
    def set_from_gateway_communications(self, key, data, source):
        """
        Used by the gateway coms (mqtt) system to set atom values.
        :param key:
        :param values:
        :return:
        """
        gateway_id = data["gateway_id"]
        if gateway_id == self.gateway_id:
            return
        if gateway_id not in self.atoms:
            self.atoms[gateway_id] = {}
        source_type, source_label = yombo.utils.get_yombo_instance_type(source)
        self.atoms[data["gateway_id"]][key] = {
            "gateway_id": data["gateway_id"],
            "value": data["value"],
            "value_human": data["value_human"],
            "value_type": data["value_type"],
            "source": source_label,
            "created_at": data["created_at"],
            "updated_at": data["updated_at"],
        }

        yield global_invoke_all(
            "_atoms_set_",
            called_by=self,
            key=key,
            value=data["value"],
            value_type=data["value_type"],
            value_full=self.atoms[gateway_id][key],
            gateway_id=gateway_id,
            source=source,
            source_label=source_label,
        )
Пример #11
0
    def duplicate_scene(self, scene_id):
        """
        Deletes the scene. Will disappear on next restart. This allows the user to recover it.
        This marks the node to be deleted!

        :param scene_id:
        :return:
        """
        scene = self.get(scene_id)
        label = f"{scene.label} ({_('common::copy')})"
        machine_label = f"{scene.machine_label}_{_('common::copy')}"
        if label is not None and machine_label is not None:
            self.check_duplicate_scene(label, machine_label, scene_id)
        new_data = bytes_to_unicode(msgpack.unpackb(msgpack.packb(scene.data)))  # had issues with deepcopy
        new_scene = yield self._Nodes.new(label=label,
                                          machine_label=machine_label,
                                          node_type="scene",
                                          data=new_data,
                                          data_content_type="json",
                                          gateway_id=self._gateway_id,
                                          destination="gw",
                                          status=1)
        self.scenes[new_scene.node_id] = new_scene
        yield global_invoke_all("_scene_added_",
                                called_by=self,
                                arguments={
                                    "scene_id": scene_id,
                                    "scene": scene,
                                    }
                                )
        return new_scene
Пример #12
0
 def broadcast_status(self) -> None:
     """
     Broadcasts the current device state. Typically called internally by this class.
     """
     last_history = self.history[-1]
     print(f"broadcast_status, hisytory: {last_history}")
     global_invoke_all("_device_command_status_",
                       called_by=self,
                       arguments={
                           "device_command": self,
                           "status": last_history["status"],
                           "status_id": STATUS_TEXT[last_history["status"]],
                           "status_at": last_history["time"],
                           "message": last_history["message"],
                           "gateway_id": last_history["gwid"],
                           "source": last_history["source"],
                           }
                       )
Пример #13
0
 def broadcast(self,
               source: Optional[Type["yombo.core.entity.Entity"]] = None):
     source = source if source is not None else self
     yield global_invoke_all("_configs_set_",
                             called_by=source,
                             arguments={
                                 "config": self.config,
                                 "instance": self,
                                 "value": self.value,
                             })
Пример #14
0
    def update_node_status(self, node_id, new_status, authorization=None):
        """
        Change the node's status from: enabled (1), disabled (0), or deleted (2).

        :param node_id: Node ID to alter.
        :param new_status: 0 for disabled, 1 for enabled, 2 for deleted.
        :return:
        """
        node_status_values = {
            0: "disable",
            1: "enable",
            2: "delete",
        }

        if new_status not in node_status_values:
            raise YomboWarning("new_status must be an int: 0, 1, or 2.")

        global_invoke_all(f"_node_before_{node_status_values}_",
                          called_by=self,
                          node_id=node_id,
                          node=self.nodes[node_id],
                          )
        response = yield self.patch_node(node_id=node_id, api_data={"status": new_status},
                                         authorization=authorization)

        if node_id in self.nodes:
            node = self.nodes[node_id]
            node._status = new_status
            if new_status == 2:
                yield node.delete_from_db()
                del self.nodes[node_id]
            else:
                yield node.save_to_db()

        global_invoke_all(f"_node_after_{node_status_values}_",
                          called_by=self,
                          node_id=node_id,
                          node=self.nodes[node_id],
                          )
        return response
Пример #15
0
    def _start_(self, **kwargs):
        """
        Calls libraries and modules to check if any additional scene types should be defined.

        For an example, see the states library.

        **Hooks called**:

        * _scene_action_list_ : Expects a list of dictionaries containing additional scene types.

        **Usage**:

        .. code-block:: python

           def _scene_types_list_(self, **kwargs):
               '''
               Adds additional scene types.
               '''
               return [
                   {
                       "platform": "state",
                       "webroutes": "f{self._Atoms.get("app_dir")}yombo/lib/webinterface/routes/scenes/states.py",
                       "add_url": "/scenes/{scene_id}/add_state",
                       "note": "Change a state value",
                       "render_table_column_callback": self.scene_render_table_column,  # Show summary line in a table.
                       "scene_action_update_callback": self.scene_item_update,  # Return a dictionary to store as the item.
                       "handle_trigger_callback": self.scene_item_triggered,  # Do item activity
                   }
               ]

        """
        # Collect a list of automation source platforms.
        scene_types_extra = yield global_invoke_all("_scene_types_list_",
                                                    called_by=self)
        logger.debug("scene_types_extra: {scene_types_extra}",
                     scene_types_extra=scene_types_extra)
        for component_name, data in scene_types_extra.items():
            for scene_action in data:
                if not all(action_key in scene_action
                           for action_key in REQUIRED_ACTION_KEYS):
                    logger.info(
                        "Scene platform doesn't have required fields, skipping: {required}",
                        required=REQUIRED_ACTION_KEYS)
                    continue
                action = dict_filter(scene_action, REQUIRED_ACTION_KEYS)
                action["platform_source"] = component_name
                self.scene_types_urls[action["platform"]] = {
                    "add_url": action["add_url"],
                    "note": action["note"],
                }
                self.scene_types_extra[action["platform"].lower()] = action
Пример #16
0
    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
Пример #17
0
    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.",
                               },
                               "encrypt": True
                           },
                           "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
Пример #18
0
 def _load_(self, **kwargs):
     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)
Пример #19
0
    def _load_(self, **kwargs):
        data = yield global_invoke_all("_storage_backends_", called_by=self)

        for component, backends in data.items():
            if isinstance(backends, dict) is False:
                logger.warn("'_storage_backends_' must return a dictionary of storage backend types.")
                continue
            for scheme, attrs in backends.items():
                if scheme in self.storage:
                    logger.warn(f"Storage backends already has scheme type '{scheme}', skipping.")
                    continue
                self.storage[scheme] = attrs

        yield self.purge_expired()
        self._CronTab.new(self.purge_expired, min=0, hour=4, label="Delete expired storage files.", source="lib.storage")
Пример #20
0
    def set_from_gateway_communications(self, key, data, source):
        """
        Used by the gateway coms (mqtt) system to set state values.
        :param key:
        :param data:
        :return:
        """
        gateway_id = data["gateway_id"]
        if gateway_id == self.gateway_id:
            return
        if gateway_id not in self.states:
            self.states[gateway_id] = {}
        source_type, source_label = get_yombo_instance_type(source)

        self.states[data["gateway_id"]][key] = {
            "gateway_id": data["gateway_id"],
            "value": data["value"],
            "value_human": data["value_human"],
            "value_type": data["value_type"],
            "live": False,
            "source": source_label,
            "created_at": data["created_at"],
        }

        self._Automation.trigger_monitor(
            "state",
            key=key,
            value=data["value"],
            value_type=data["value_type"],
            value_full=self.states[gateway_id][key],
            action="set",
            gateway_id=gateway_id,
            source=source,
            source_label=source_label,
        )

        # Call any hooks
        yield global_invoke_all(
            "_states_set_",
            called_by=self,
            key=key,
            value=data["value"],
            value_type=data["value_type"],
            value_full=self.states[gateway_id][key],
            gateway_id=gateway_id,
            source=source,
            source_label=source_label,
        )
Пример #21
0
    def delete(self, scene_id, session=None):
        """
        Deletes the scene. Will disappear on next restart. This allows the user to recover it.
        This marks the node to be deleted!

        :param scene_id:
        :return:
        """
        scene = self.get(scene_id)
        results = yield self._Nodes.delete_node(scene.scene_id, session=session)
        yield global_invoke_all("_scene_deleted_",
                                called_by=self,
                                arguments={
                                    "scene_id": scene_id,
                                    "scene": scene,
                                    }
                                )
        return results
Пример #22
0
    def delete(self, scene_id, session=None):
        """
        Deletes the scene. Will disappear on next restart. This allows the user to recover it.
        This marks the node to be deleted!

        :param scene_id:
        :return:
        """
        scene = self.get(scene_id)
        data = scene.data
        data["config"]["enabled"] = False
        results = yield self._Nodes.delete_node(scene.scene_id,
                                                session=session)
        yield global_invoke_all(
            "_scene_deleted_",
            called_by=self,
            scene_id=scene_id,
            scene=scene,
        )
        return results
Пример #23
0
    def _load_(self, **kwargs):
        """
        Asks libraries and modules if they have any additional event types. This calls
        the modules after they have been imported, but before their init is called.

        :return:
        """
        event_types = yield global_invoke_all("_event_types_",
                                              called_by=self,
                                              )
        for component, options in event_types.items():
            for event_type, event_data in options.items():
                if event_type not in self.event_types:
                    self.event_types[event_type] = {}
                for event_subtype, event_subdata in event_data.items():
                    if event_subtype in self.event_types:
                        logger.warn("Cannot add event type, already exists: {event_type}:{event_subtype}",
                                    event_type=event_type, event_subtype=event_subtype)
                        continue
                    self.event_types[event_type][event_subtype] = event_subdata
Пример #24
0
    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_delete_",
                    called_by=self,
                    section=section,
                    option=option,
                    value=None,
                )
Пример #25
0
    def _start_device_command(self) -> None:
        """
        Performs the actual sending of a device command. This calls the hook "_device_command_". Any modules that
        have implemented this hook can monitor or act on the hook.

        When a device changes state, whatever module changes the state of a device, or is responsible for reporting
        those changes, it *must* call "self._Devices["devicename/deviceid"].set_state()

        **Hooks called**:

        * _devices_command_ : Sends kwargs: *device*, the device object and *command*. This receiver will be
          responsible for obtaining whatever information it needs to complete the action being requested.

        :return:
        """
        print("_start_device_command")
        items = {
            DEVICE_COMMAND_COMMAND: self.command,
            DEVICE_COMMAND_COMMAND_ID: self.command.command_id,
            DEVICE_COMMAND_DEVICE: self.device,
            DEVICE_COMMAND_DEVICE_ID: self.device_id,
            DEVICE_COMMAND_INPUTS: self.inputs,
            DEVICE_COMMAND_DEVICE_COMMAND_ID: self.device_command_id,
            DEVICE_COMMAND_DEVICE_COMMAND: self,
            DEVICE_COMMAND_PIN: self.pin,
            DEVICE_COMMAND_GATEWAY_ID: self.gateway_id,
            DEVICE_COMMAND_REQUEST_BY: self.request_by,
            DEVICE_COMMAND_REQUEST_BY_TYPE: self.request_by_type,
            DEVICE_COMMAND_REQUEST_CONTEXT: self.request_context,
        }
        # logger.debug("calling _device_command_, device_command_id: {device_command_id}", device_command_id=device_command.device_command_id)
        self.set_broadcast()
        print("_start_device_command: _device_command_")
        print(items)
        results = yield global_invoke_all("_device_command_", called_by=self, arguments=items,
                                          _force_debug=True)
        for component, result in results.items():
            if result is True:
                self.set_received(message=f"Received by: {component}")
        self._Parent._Statistics.increment("lib.devices.commands_sent", anon=True)
Пример #26
0
    def _load_(self, **kwargs):
        results = yield global_invoke_all("_auth_platforms_", called_by=self)
        logger.debug("_auth_platforms_ results: {results}", results=results)
        for component, platforms in results.items():
            for machine_label, platform_data in platforms.items():
                if "actions" not in platform_data:
                    logger.warn("Unable to add auth platform, actions is missing from: {component} - {machine_label}",
                                component=component, machine_label=machine_label)
                    continue
                if "possible" not in platform_data["actions"]:
                    logger.warn("Unable to add auth platform, 'possible' actions are missing from:"
                                " {component} - {machine_label}",
                                component=component, machine_label=machine_label)
                    continue
                if "user" not in platform_data["actions"]:
                    logger.info("'user' default allowed actions is missing from {component} - {machine_label},"
                                " setting to none.",
                                component=component, machine_label=machine_label)
                    platform_data["actions"]["user"] = []
                self.auth_platforms[machine_label] = platform_data

        yield self.setup_system_permissions()
    def update_attributes(self,
                          incoming,
                          source=None,
                          session=None,
                          broadcast=None):
        """
        Use to set attributes for the instance. This can used to edit the attributes as well.

        This is 100% anti-pythonic. Here's why:
        If attributes are set internally, it's assumed that these come internally and are 100% pythonic. This is fine.
        There are times when things don't need to be synced to other other places:
        * If from AQMP/YomboAPI, then we just need to update the memory and database.
        * If from database (loading), then we just need to update memory and not Yombo API.

        :param incoming: a dictionary containing key/value pairs to update.
        :param source:
        :return:
        """
        if hasattr(self, "_sync_init_complete") is False:
            for name, value in incoming.items():
                self.__dict__[name] = value
            return
        # print(f"library db child: {self._Entity_type}, update attrs start")

        if hasattr(self, "_can_have_fake_data") and "_fake_data" in incoming:
            self._set_fake_data(incoming['_fake_data'])

        # print(f"LibraryDBChildMixin update_attributes __dict__ : {self.__dict__}")
        # print(f"LibraryDBChildMixin update_attributes __dict__ : {LibraryDBChildMixin.__dict__}")

        hook_prefix = self._Parent._class_storage_load_hook_prefix
        storage_id = getattr(self, self._primary_column)

        try:
            self.update_attributes_preprocess(incoming)
        except YomboWarning as e:
            logger.info("Skipping update attributes for {item_type}",
                        item_type=self._Parent._class_storage_load_hook_prefix)
            return

        if broadcast in (None, True) and self._Loader.run_phase[1] >= 6500:
            # print(f"calling hook: {hook_prefix}")
            global_invoke_all(
                f"_{hook_prefix}_before_update2_",
                called_by=self,
                id=storage_id,
                data=self,
            )

        if hasattr(super(), 'update_attributes'):
            super().update_attributes(incoming, source=source, session=session)
        else:
            for name, value in incoming.items():
                self.__dict__[name] = value

        if broadcast in (None, True) and self._Loader.run_phase[1] >= 6500:
            global_invoke_all(
                f"_{hook_prefix}_updated2_",
                called_by=self,
                id=storage_id,
                data=self,
            )

        try:
            self.update_attributes_preprocess(incoming)
        except YomboWarning as e:
            logger.info("Skipping update attributes for {item_type}",
                        item_type=self._Parent._class_storage_load_hook_prefix)
            return
Пример #28
0
    def add_device(self, api_data, source=None, **kwargs):
        """
        Add a new device. This will also make an API request to add device at the server too.

        :param data:
        :param kwargs:
        :return:
        """
        results = None
        # logger.info("Add new device.  Data: {data}", data=data)
        if "gateway_id" not in api_data:
            api_data["gateway_id"] = self.gateway_id

        try:
            for key, value in api_data.items():
                if value == "":
                    api_data[key] = None
                elif key in ["statistic_lifetime", "pin_timeout"]:
                    if api_data[key] is None or (isinstance(value, str)
                                                 and value.lower() == "none"):
                        del api_data[key]
                    else:
                        api_data[key] = int(value)
        except Exception as e:
            return {
                "status": "failed",
                "msg": "Couldn't add device due to value mismatches.",
                "apimsg": e,
                "apimsghtml": e,
                "device_id": None,
                "data": None,
            }

        try:
            global_invoke_all(
                "_device_before_add_",
                called_by=self,
                data=api_data,
                stoponerror=True,
            )
        except YomboHookStopProcessing as e:
            raise YomboWarning(
                f"Adding device was halted by '{e.name}', reason: {e.message}")

        if source != "amqp":
            logger.debug("POSTING device. api data: {api_data}",
                         api_data=api_data)
            try:
                if "session" in kwargs:
                    session = kwargs["session"]
                else:
                    session = None

                if "variable_data" in api_data and len(
                        api_data["variable_data"]) > 0:
                    variable_data = api_data["variable_data"]
                    del api_data["variable_data"]
                else:
                    variable_data = None

                device_results = yield self._YomboAPI.request("POST",
                                                              "/v1/device",
                                                              api_data,
                                                              session=session)
            except YomboWarning as e:
                return {
                    "status": "failed",
                    f"msg": "Couldn't add device: {e.message}",
                    f"apimsg": f"Couldn't add device: {e.message}",
                    f"apimsghtml": f"Couldn't add device: {e.html_message}",
                }
            logger.info("add new device results: {device_results}",
                        device_results=device_results)
            if variable_data is not None:
                variable_results = yield self.set_device_variables(
                    device_results["data"]["id"],
                    variable_data,
                    "add",
                    source,
                    session=session)
                if variable_results["code"] > 299:
                    results = {
                        "status": "failed",
                        "msg":
                        f"Device saved, but had problems with saving variables: {variable_results['msg']}",
                        "apimsg": variable_results["apimsg"],
                        "apimsghtml": variable_results["apimsghtml"],
                        "device_id": device_results["data"]["id"],
                        "data": device_results["data"],
                    }

            device_id = device_results["data"]["id"]
            new_device = device_results["data"]
            new_device["created"] = new_device["created_at"]
            new_device["updated"] = new_device["updated_at"]
        else:
            device_id = api_data["id"]
            new_device = api_data

        logger.debug("device add results: {device_results}",
                     device_results=device_results)

        new_device = yield self._load_node_into_memory(new_device, source)

        try:
            yield global_invoke_all(
                "_device_added_",
                called_by=self,
                id=device_id,
                device=self.devices[device_id],
            )
        except Exception:
            pass

        if results is None:
            return {
                "status": "success",
                "msg": "Device added",
                "apimsg": "Device added",
                "apimsghtml": "Device added",
                "device_id": device_id,
                "data": new_device,
            }
Пример #29
0
    def _class_storage_load_db_items_to_memory(self,
                                               incoming,
                                               source=None,
                                               **kwargs):
        device_id = incoming["id"]
        if device_id not in self.devices:
            device_type = self._DeviceTypes[incoming["device_type_id"]]

            if device_type.platform is None or device_type.platform == "":
                device_type.platform = "device"
            class_names = device_type.platform.lower()

            class_names = "".join(class_names.split())  # we don't like spaces
            class_names = class_names.split(",")

            # logger.info("Loading device ({device}), platforms: {platforms}",
            #             device=device,
            #             platforms=class_names)

            klass = None
            for class_name in class_names:
                if class_name in self._DeviceTypes.platforms:
                    klass = self._DeviceTypes.platforms[class_name]
                    break

            if klass is None:
                klass = self._DeviceTypes.platforms["device"]
                logger.warn(
                    "Using base device class for device '{label}' cannot find any of these requested classes:"
                    " {class_names}",
                    label=incoming["label"],
                    class_names=class_names)

            device = yield maybeDeferred(
                self._generic_class_storage_load_to_memory,
                self.devices,
                Device,
                incoming,
                source=source)

            d = Deferred()
            d.addCallback(lambda ignored: maybeDeferred(
                self.devices[device_id]._system_init_, incoming, source=source)
                          )
            d.addErrback(self._load_node_into_memory_failure,
                         self.devices[device_id])
            d.addCallback(
                lambda ignored: maybeDeferred(self.devices[device_id]._init_))
            d.addErrback(self._load_node_into_memory_failure,
                         self.devices[device_id])
            d.addCallback(
                lambda ignored: maybeDeferred(self.devices[device_id]._load_))
            d.addErrback(self._load_node_into_memory_failure,
                         self.devices[device_id])
            d.addCallback(
                lambda ignored: maybeDeferred(self.devices[device_id]._start_))
            d.addErrback(self._load_node_into_memory_failure,
                         self.devices[device_id])
            d.callback(1)
            yield d
            try:
                global_invoke_all(
                    "_device_imported_",
                    called_by=self,
                    id=device_id,
                    device=self.devices[device_id],
                )
            except YomboHookStopProcessing as e:
                pass
        else:
            device = yield maybeDeferred(
                self._generic_class_storage_load_to_memory,
                self.devices,
                Device,
                incoming,
                source=source)
        return device
Пример #30
0
    def add_node(self, node_data, source=None, authorization=None, **kwargs):
        """
        Used to create new nodes. Node data should be a dictionary. This will:

        1) Send the node information to Yombo cloud for persistence.
        2) Save the node information to local database.
        3) Load the node into memory for usage.

        This adds the node at Yombo, adds to the local DB store if the gateway_id matches outs,
        and loads it into memory if the gateways is ours and destination is 'gw' or 'always_load' is 1.

        Required:
        node_type
        weight (defaults to 0 if not set)
        always_load (defaults to 1 - true if not set)
        data
        data_content_type - Usually msgpack_base85 or json.
        status (defaults to 1 - enabled)

        Optional:
        gateway_id - Will not save to localdb or load into memory if not set to this gateway.
        machine_label
        label
        destination

        :param node_data:
        :param kwargs:
        :return:
        """
        print("nodes:: new_new 1")

        if source is None:
            source = "local"

        gateway_id = self.gateway_id
        if "data" not in node_data or node_data["data"] is None:
            raise YomboWarning("Node must have data!")

        if "data_content_type" not in node_data or node_data["data_content_type"] is None:
            if isinstance(node_data["data"], dict) or isinstance(node_data["data"], list):
                node_data["data_content_type"] = "json"
            elif isinstance(node_data["data"], bool):
                node_data["data_content_type"] = "bool"
            else:
                node_data["data_content_type"] = "string"

        if "parent_id" not in node_data:
            node_data["parent_id"] = None

        if "gateway_id" not in node_data:
            node_data["gateway_id"] = gateway_id

        if "destination" in node_data and node_data["destination"] == "gw" and \
                ("gateway_id" not in node_data or node_data["gateway_id"] is None):
            node_data["gateway_id"] = gateway_id

        if "always_load" not in node_data or node_data["always_load"] is None:
            node_data["always_load"] = 1

        if "weight" not in node_data or node_data["weight"] is None:
            node_data["weight"] = 0

        if "status" not in node_data or node_data["status"] is None:
            node_data["status"] = 1

        if source == "local":
            # api_data = deepcopy(node_data)
            node_data["data"] = data_pickle(node_data["data"], node_data["data_content_type"])

            api_data = {k: v for k, v in bytes_to_unicode(node_data).items() if v}

            print("nodes:: new_new 10")
            response = yield self._YomboAPI.request("POST", "/v1/node",
                                                    api_data,
                                                    authorization_header=authorization)

            # print("added node results: %s" % node_results)
            node_data = response.content["data"]['attributes']
            print(f"new node data: {node_data}")

        node_id = node_data["id"]
        if "gateway_id" in node_data and node_data["gateway_id"] == gateway_id:
            self.nodes[node_id].add_to_db()

        if "destination" in node_data and node_data["destination"] == "gw" and \
                "gateway_id" in node_data and node_data["gateway_id"] == gateway_id:
            print("Loading new node data into memory...")
            self._load_node_into_memory(node_data)
            global_invoke_all("_node_added_",
                              called_by=self,
                              node_id=node_id,
                              node=self.nodes[node_id],
                              )
        return node_id