def __init__(self, config: ConfigHelper) -> None: super().__init__(config) self.mqtt: MQTTClient = self.server.load_component(config, 'mqtt') self.eventloop = self.server.get_event_loop() self.cmd_topic: str = config.get('command_topic') self.cmd_payload: JinjaTemplate = config.gettemplate('command_payload') self.retain_cmd_state = config.getboolean('retain_command_state', False) self.query_topic: Optional[str] = config.get('query_topic', None) self.query_payload = config.gettemplate('query_payload', None) self.must_query = config.getboolean('query_after_command', False) if self.query_topic is not None: self.must_query = False self.state_topic: str = config.get('state_topic') self.state_timeout = config.getfloat('state_timeout', 2.) self.state_response = config.load_template('state_response_template', "{payload}") self.qos: Optional[int] = config.getint('qos', None, minval=0, maxval=2) self.mqtt.subscribe_topic(self.state_topic, self._on_state_update, self.qos) self.query_response: Optional[asyncio.Future] = None self.request_mutex = asyncio.Lock() self.server.register_event_handler("mqtt:connected", self._on_mqtt_connected) self.server.register_event_handler("mqtt:disconnected", self._on_mqtt_disconnected)
def __init__(self, config: ConfigHelper) -> None: self.config = config name_parts = config.get_name().split(maxsplit=1) if len(name_parts) != 2: raise config.error(f"Invalid Section Name: {config.get_name()}") self.server = config.get_server() self.name = name_parts[1] self.apprise = apprise.Apprise() self.warned = False self.attach_requires_file_system_check = True self.attach = config.get("attach", None) if self.attach is None or \ (self.attach.startswith("http://") or self.attach.startswith("https://")): self.attach_requires_file_system_check = False url_template = config.gettemplate('url') self.url = url_template.render() if len(self.url) < 2: raise config.error(f"Invalid url for: {config.get_name()}") self.title = config.gettemplate('title', None) self.body = config.gettemplate("body", None) self.events: List[str] = config.getlist("events", separator=",") self.apprise.add(self.url)
def test_get_template_deprecate(self, test_config: ConfigHelper): server = test_config.get_server() test_config.gettemplate("test_template", deprecate=True) expected = ( f"[test_options]: Option 'test_template' is " "deprecated, see the configuration documention " "at https://moonraker.readthedocs.io/en/latest/configuration") assert expected in server.warnings
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.ldap_host = config.get('ldap_host') self.ldap_port = config.getint("ldap_port", None) self.ldap_secure = config.getboolean("ldap_secure", False) base_dn_template = config.gettemplate('base_dn') self.base_dn = base_dn_template.render() self.group_dn: Optional[str] = None group_dn_template = config.gettemplate("group_dn", None) if group_dn_template is not None: self.group_dn = group_dn_template.render() self.active_directory = config.getboolean('is_active_directory', False) self.bind_dn: Optional[str] = None self.bind_password: Optional[str] = None bind_dn_template = config.gettemplate('bind_dn', None) bind_pass_template = config.gettemplate('bind_password', None) if bind_dn_template is not None: self.bind_dn = bind_dn_template.render() if bind_pass_template is None: raise config.error("Section [ldap]: Option 'bind_password' is " "required when 'bind_dn' is provided") self.bind_password = bind_pass_template.render() self.lock = asyncio.Lock()
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.eventloop = self.server.get_event_loop() self.name = config.get_name().split()[-1] self.itransport: ITransport = self.server.lookup_component( 'internal_transport') self.mutex = asyncio.Lock() gpio: GpioFactory = self.server.load_component(config, 'gpio') self.gpio_event = gpio.register_gpio_event(config.get('pin'), self._on_gpio_event) min_event_time = config.getfloat('minimum_event_time', .05, minval=.010) self.gpio_event.setup_debounce(min_event_time, self._on_gpio_error) self.press_template = config.gettemplate("on_press", None, is_async=True) self.release_template = config.gettemplate("on_release", None, is_async=True) if (self.press_template is None and self.release_template is None): raise config.error( f"[{config.get_name()}]: No template option configured") self.notification_sent: bool = False self.user_data: Dict[str, Any] = {} self.context: Dict[str, Any] = { 'call_method': self.itransport.call_method, 'send_notification': self._send_notification, 'event': { 'elapsed_time': 0., 'received_time': 0., 'render_time': 0., 'pressed': False, }, 'user_data': self.user_data }
def test_get_template_default(self, test_config: ConfigHelper): assert test_config.gettemplate("invalid_option", None) is None
def test_get_template_render_fail(self, test_config: ConfigHelper): with pytest.raises(ServerError): test_config.gettemplate("test_template", is_async=True).render()
def test_get_template_fail(self, test_config: ConfigHelper): with pytest.raises(ConfigError): test_config.gettemplate("invalid_option")
def test_get_template_plain(self, test_config: ConfigHelper): val = test_config.gettemplate("test_string").render() assert val == "Hello World"
async def test_get_template_async(self, test_config: ConfigHelper): templ = test_config.gettemplate("test_template", is_async=True) val = await templ.render_async() assert val == "mqttuser"
def test_get_template_exists(self, test_config: ConfigHelper): val = test_config.gettemplate("test_template").render() assert val == "mqttuser"
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.event_loop = self.server.get_event_loop() self.address: str = config.get('address') self.port: int = config.getint('port', 1883) user = config.gettemplate('username', None) self.user_name: Optional[str] = None if user: self.user_name = user.render() pw_file_path = config.get('password_file', None, deprecate=True) pw_template = config.gettemplate('password', None) self.password: Optional[str] = None if pw_file_path is not None: pw_file = pathlib.Path(pw_file_path).expanduser().absolute() if not pw_file.exists(): raise config.error( f"Password file '{pw_file}' does not exist") self.password = pw_file.read_text().strip() if pw_template is not None: self.password = pw_template.render() protocol = config.get('mqtt_protocol', "v3.1.1") self.protocol = MQTT_PROTOCOLS.get(protocol, None) if self.protocol is None: raise config.error( f"Invalid value '{protocol}' for option 'mqtt_protocol' " "in section [mqtt]. Must be one of " f"{MQTT_PROTOCOLS.values()}") self.instance_name = config.get('instance_name', socket.gethostname()) if '+' in self.instance_name or '#' in self.instance_name: raise config.error( "Option 'instance_name' in section [mqtt] cannot " "contain a wildcard.") self.qos = config.getint("default_qos", 0) if self.qos > 2 or self.qos < 0: raise config.error( "Option 'default_qos' in section [mqtt] must be " "between 0 and 2") self.client = paho_mqtt.Client(protocol=self.protocol) self.client.on_connect = self._on_connect self.client.on_message = self._on_message self.client.on_disconnect = self._on_disconnect self.client.on_publish = self._on_publish self.client.on_subscribe = self._on_subscribe self.client.on_unsubscribe = self._on_unsubscribe self.connect_evt: asyncio.Event = asyncio.Event() self.disconnect_evt: Optional[asyncio.Event] = None self.reconnect_task: Optional[asyncio.Task] = None self.subscribed_topics: SubscribedDict = {} self.pending_responses: List[asyncio.Future] = [] self.pending_acks: Dict[int, asyncio.Future] = {} self.server.register_endpoint( "/server/mqtt/publish", ["POST"], self._handle_publish_request, transports=["http", "websocket", "internal"]) self.server.register_endpoint( "/server/mqtt/subscribe", ["POST"], self._handle_subscription_request, transports=["http", "websocket", "internal"]) # Subscribe to API requests self.json_rpc = JsonRPC(transport="MQTT") self.api_request_topic = f"{self.instance_name}/moonraker/api/request" self.api_resp_topic = f"{self.instance_name}/moonraker/api/response" self.klipper_status_topic = f"{self.instance_name}/klipper/status" self.moonraker_status_topic = f"{self.instance_name}/moonraker/status" status_cfg: Dict[str, Any] = config.getdict("status_objects", {}, allow_empty_fields=True) self.status_objs: Dict[str, Any] = {} for key, val in status_cfg.items(): if val is not None: self.status_objs[key] = [v.strip() for v in val.split(',') if v.strip()] else: self.status_objs[key] = None if status_cfg: logging.debug(f"MQTT: Status Objects Set: {self.status_objs}") self.server.register_event_handler("server:klippy_identified", self._handle_klippy_identified) self.timestamp_deque: Deque = deque(maxlen=20) self.api_qos = config.getint('api_qos', self.qos) if config.getboolean("enable_moonraker_api", True): api_cache = self.server.register_api_transport("mqtt", self) for api_def in api_cache.values(): if "mqtt" in api_def.supported_transports: self.register_api_handler(api_def) self.subscribe_topic(self.api_request_topic, self._process_api_request, self.api_qos) self.server.register_remote_method("publish_mqtt_topic", self._publish_from_klipper) logging.info( f"\nReserved MQTT topics:\n" f"API Request: {self.api_request_topic}\n" f"API Response: {self.api_resp_topic}\n" f"Moonraker Status: {self.moonraker_status_topic}\n" f"Klipper Status: {self.klipper_status_topic}")
def __init__(self, config: ConfigHelper) -> None: super().__init__(config, default_port=8123) self.device: str = config.get("device") self.token: str = config.gettemplate("token").render() self.domain: str = config.get("domain", "switch") self.status_delay: float = config.getfloat("status_delay", 1.)