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: 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.type: str = config.get('type') self.state: str = "init" self.locked_while_printing = config.getboolean('locked_while_printing', False) self.off_when_shutdown = config.getboolean('off_when_shutdown', False) self.off_when_shutdown_delay = 0. if self.off_when_shutdown: self.off_when_shutdown_delay = config.getfloat( 'off_when_shutdown_delay', 0., minval=0.) self.shutdown_timer_handle: Optional[asyncio.TimerHandle] = None self.restart_delay = 1. self.klipper_restart = config.getboolean( 'restart_klipper_when_powered', False) if self.klipper_restart: self.restart_delay = config.getfloat('restart_delay', 1.) if self.restart_delay < .000001: raise config.error("Option 'restart_delay' must be above 0.0") self.bound_service: Optional[str] = config.get('bound_service', None) self.need_scheduled_restart = False self.on_when_queued = config.getboolean('on_when_upload_queued', False)
def __init__(self: Strip, name: str, color_order: ColorOrder, cfg: ConfigHelper): self.server = cfg.get_server() self.client = AsyncHTTPClient() self.request_mutex = asyncio.Lock() self.name = name self.color_order = color_order # Read the uri information addr: str = cfg.get("address") port: int = cfg.getint("port", 80) protocol: str = cfg.get("protocol", "http") self.url = f"{protocol}://{addr}:{port}/json" self.timeout: float = cfg.getfloat("timeout", 2.) self.initial_preset: int = cfg.getint("initial_preset", -1) self.initial_red: float = cfg.getfloat("initial_red", 0.5) self.initial_green: float = cfg.getfloat("initial_green", 0.5) self.initial_blue: float = cfg.getfloat("initial_blue", 0.5) self.initial_white: float = cfg.getfloat("initial_white", 0.5) self.chain_count: int = cfg.getint("chain_count", 1) self._chain_data = bytearray(self.chain_count * color_order.Elem_Size()) self.onoff = OnOff.off self.preset = self.initial_preset
def __init__(self, config: ConfigHelper, cmd_helper: CommandHelper ) -> None: super().__init__(config, cmd_helper) self.repo = config.get('repo').strip().strip("/") self.owner = self.repo.split("/", 1)[0] self.path = pathlib.Path(config.get("path")).expanduser().resolve() self.type = config.get('type') self.channel = "stable" if self.type == "web" else "beta" self.persistent_files: List[str] = [] pfiles = config.get('persistent_files', None) if pfiles is not None: self.persistent_files = [pf.strip().strip("/") for pf in pfiles.split("\n") if pf.strip()] if ".version" in self.persistent_files: raise config.error( "Invalid value for option 'persistent_files': " "'.version' can not be persistent") self.version: str = "?" self.remote_version: str = "?" self.dl_info: Tuple[str, str, int] = ("?", "?", 0) self.refresh_evt: Optional[asyncio.Event] = None self.mutex: asyncio.Lock = asyncio.Lock() logging.info(f"\nInitializing Client Updater: '{self.name}'," f"\nChannel: {self.channel}" f"\npath: {self.path}")
def test_get_str_deprecate(self, test_config: ConfigHelper): server = test_config.get_server() test_config.get("test_string", deprecate=True) expected = ( f"[test_options]: Option 'test_string' is " "deprecated, see the configuration documention " "at https://moonraker.readthedocs.io/en/latest/configuration") assert expected in server.warnings
def __init__(self: StripHttp, name: str, cfg: ConfigHelper): super().__init__(name, cfg) # Read the uri information addr: str = cfg.get("address") port: int = cfg.getint("port", 80) protocol: str = cfg.get("protocol", "http") self.url = f"{protocol}://{addr}:{port}/json" self.timeout: float = cfg.getfloat("timeout", 2.) self.client = AsyncHTTPClient()
def __init__(self, config: ConfigHelper, default_port: int = -1, default_user: str = "", default_password: str = "", default_protocol: str = "http") -> None: super().__init__(config) self.client = AsyncHTTPClient() self.request_mutex = Lock() self.addr: str = config.get("address") self.port = config.getint("port", default_port) self.user = config.get("user", default_user) self.password = config.get("password", default_password) self.protocol = config.get("protocol", default_protocol)
def __init__(self, config: ConfigHelper) -> None: super().__init__(config) self.timer = config.get("timer", "") self.request_mutex = asyncio.Lock() addr_and_output_id = config.get("address").split('/') self.addr = addr_and_output_id[0] if (len(addr_and_output_id) > 1): self.server.add_warning( f"Power Device {self.name}: Including the output id in the" " address is deprecated, use the 'output_id' option") self.output_id: Optional[int] = int(addr_and_output_id[1]) else: self.output_id = config.getint("output_id", None) self.port = config.getint("port", 9999)
def from_config(cls, config: ConfigHelper) -> WebCam: webcam: Dict[str, Any] = {} webcam["name"] = config.get_name().split(maxsplit=1)[-1] webcam["location"] = config.get("location", "printer") webcam["service"] = config.get("service", "mjpegstreamer") webcam["target_fps"] = config.getint("target_fps", 15) webcam["stream_url"] = config.get("stream_url") webcam["snapshot_url"] = config.get("snapshot_url") webcam["flip_horizontal"] = config.getboolean("flip_horizontal", False) webcam["flip_vertical"] = config.getboolean("flip_vertical", False) webcam["rotation"] = config.getint("rotation", 0) if webcam["rotation"] not in [0, 90, 180, 270]: raise config.error("Invalid value for option 'rotation'") webcam["source"] = "config" return cls(config.get_server(), **webcam)
def __init__(self, config: confighelper.ConfigHelper) -> None: self.server = config.get_server() self.uds_address: str = config.get( 'klippy_uds_address', "/tmp/klippy_uds") self.writer: Optional[asyncio.StreamWriter] = None self.connection_mutex: asyncio.Lock = asyncio.Lock() self.event_loop = self.server.get_event_loop() self.log_no_access = True # Connection State self.connection_task: Optional[asyncio.Task] = None self.closing: bool = False self._klippy_info: Dict[str, Any] = {} self.init_list: List[str] = [] self._klipper_version: str = "" self._missing_reqs: Set[str] = set() self._peer_cred: Dict[str, int] = {} self.init_attempts: int = 0 self._state: str = "disconnected" self.subscriptions: Dict[Subscribable, Dict[str, Any]] = {} # Setup remote methods accessable to Klippy. Note that all # registered remote methods should be of the notification type, # they do not return a response to Klippy after execution self.pending_requests: Dict[int, KlippyRequest] = {} self.remote_methods: Dict[str, FlexCallback] = {} self.klippy_reg_methods: List[str] = [] self.register_remote_method( 'process_gcode_response', self._process_gcode_response, need_klippy_reg=False) self.register_remote_method( 'process_status_update', self._process_status_update, need_klippy_reg=False) self.server.register_component("klippy_connection", self)
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 __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() dist_info: Dict[str, Any] dist_info = {'name': distro.name(pretty=True)} dist_info.update(distro.info()) dist_info['release_info'] = distro.distro_release_info() self.inside_container = False self.system_info: Dict[str, Any] = { 'python': { "version": sys.version_info, "version_string": sys.version.replace("\n", " ") }, 'cpu_info': self._get_cpu_info(), 'sd_info': self._get_sdcard_info(), 'distribution': dist_info, 'virtualization': self._check_inside_container() } self._update_log_rollover(log=True) providers: Dict[str, type] = { "none": BaseProvider, "systemd_cli": SystemdCliProvider, "systemd_dbus": SystemdDbusProvider } ptype = config.get('provider', 'systemd_dbus') pclass = providers.get(ptype) if pclass is None: raise config.error(f"Invalid Provider: {ptype}") self.sys_provider: BaseProvider = pclass(config) logging.info(f"Using System Provider: {ptype}") self.server.register_endpoint("/machine/reboot", ['POST'], self._handle_machine_request) self.server.register_endpoint("/machine/shutdown", ['POST'], self._handle_machine_request) self.server.register_endpoint("/machine/services/restart", ['POST'], self._handle_service_request) self.server.register_endpoint("/machine/services/stop", ['POST'], self._handle_service_request) self.server.register_endpoint("/machine/services/start", ['POST'], self._handle_service_request) self.server.register_endpoint("/machine/system_info", ['GET'], self._handle_sysinfo_request) self.server.register_notification("machine:service_state_changed") # Register remote methods self.server.register_remote_method("shutdown_machine", self.sys_provider.shutdown) self.server.register_remote_method("reboot_machine", self.sys_provider.reboot) # IP network shell commands shell_cmd: SCMDComp = self.server.load_component( config, 'shell_command') self.addr_cmd = shell_cmd.build_shell_command("ip -json address") iwgetbin = "/sbin/iwgetid" if not pathlib.Path(iwgetbin).exists(): iwgetbin = "iwgetid" self.iwgetid_cmd = shell_cmd.build_shell_command(iwgetbin) self.init_evt = asyncio.Event()
def __init__(self, config: ConfigHelper, cmd_helper: CommandHelper, app_params: Optional[Dict[str, Any]]) -> None: super().__init__(config, cmd_helper, app_params) self.official_repo: str = "?" self.owner: str = "?" # Extract repo from origin for validation match = re.match(r"https?://(?:www\.)?github.com/([^/]+/[^.]+)", self.origin) if match is not None: self.official_repo = match.group(1) self.owner = self.official_repo.split('/')[0] else: raise config.error( "Invalid url set for 'origin' option in section " f"[{config.get_name()}]. Unable to extract owner/repo.") self.host_repo: str = config.get('host_repo', self.official_repo) self.detected_type: str = "?" self.source_checksum: str = "" self.pristine = False self.verified = False self.build_date: int = 0 self.full_version: str = "?" self.short_version: str = "?" self.commit_hash: str = "?" self.lastest_hash: str = "?" self.latest_version: str = "?" self.latest_checksum: str = "?" self.latest_build_date: int = 0 self.commit_log: List[Dict[str, Any]] = [] self.package_list: List[str] = [] self.python_pkg_list: List[str] = [] self.release_download_info: Tuple[str, str, int] = ("?", "?", 0) self.errors: List[str] = [] self.mutex: asyncio.Lock = asyncio.Lock() self.refresh_event: Optional[asyncio.Event] = None
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.namespaces: Dict[str, object] = {} self.enable_debug = config.getboolean("enable_database_debug", False) self.database_path = os.path.expanduser( config.get('database_path', "~/.moonraker_database")) if not os.path.isdir(self.database_path): os.mkdir(self.database_path) self.lmdb_env = lmdb.open(self.database_path, map_size=MAX_DB_SIZE, max_dbs=MAX_NAMESPACES) with self.lmdb_env.begin(write=True, buffers=True) as txn: # lookup existing namespaces cursor = txn.cursor() remaining = cursor.first() while remaining: key = bytes(cursor.key()) self.namespaces[key.decode()] = self.lmdb_env.open_db(key, txn) remaining = cursor.next() cursor.close() if "moonraker" not in self.namespaces: mrdb = self.lmdb_env.open_db(b"moonraker", txn) self.namespaces["moonraker"] = mrdb txn.put(b'database_version', self._encode_value(DATABASE_VERSION), db=mrdb) # Read out all namespaces to remove any invalid keys on init for ns in self.namespaces.keys(): self._get_namespace(ns) # Protected Namespaces have read-only API access. Write access can # be granted by enabling the debug option. Forbidden namespaces # have no API access. This cannot be overridden. self.protected_namespaces = set( self.get_item("moonraker", "database.protected_namespaces", ["moonraker"])) self.forbidden_namespaces = set( self.get_item("moonraker", "database.forbidden_namespaces", [])) debug_counter: int = self.get_item("moonraker", "database.debug_counter", 0) if self.enable_debug: debug_counter += 1 self.insert_item("moonraker", "database.debug_counter", debug_counter) if debug_counter: logging.info(f"Database Debug Count: {debug_counter}") # Track unsafe shutdowns unsafe_shutdowns: int = self.get_item("moonraker", "database.unsafe_shutdowns", 0) logging.info(f"Unsafe Shutdown Count: {unsafe_shutdowns}") # Increment unsafe shutdown counter. This will be reset if # moonraker is safely restarted self.insert_item("moonraker", "database.unsafe_shutdowns", unsafe_shutdowns + 1) self.server.register_endpoint("/server/database/list", ['GET'], self._handle_list_request) self.server.register_endpoint("/server/database/item", ["GET", "POST", "DELETE"], self._handle_item_request)
def __init__(self, config: ConfigHelper, cmd_helper: CommandHelper, app_params: Optional[Dict[str, Any]]) -> None: super().__init__(config, cmd_helper) self.config = config self.app_params = app_params self.debug = self.cmd_helper.is_debug_enabled() if app_params is not None: self.channel: str = app_params['channel'] self.path: pathlib.Path = pathlib.Path( app_params['path']).expanduser().resolve() executable: Optional[str] = app_params['executable'] self.type = CHANNEL_TO_TYPE[self.channel] else: self.type = config.get('type') self.channel = TYPE_TO_CHANNEL[self.type] self.path = pathlib.Path(config.get('path')).expanduser().resolve() executable = config.get('env', None) if self.channel not in CHANNEL_TO_TYPE.keys(): raise config.error(f"Invalid Channel '{self.channel}' for config " f"section [{config.get_name()}]") self._verify_path(config, 'path', self.path) self.executable: Optional[pathlib.Path] = None self.venv_args: Optional[str] = None if executable is not None: self.executable = pathlib.Path(executable).expanduser().resolve() self._verify_path(config, 'env', self.executable) self.venv_args = config.get('venv_args', None) self.is_service = config.getboolean("is_system_service", True) self.need_channel_update = False self._is_valid = False # We need to fetch all potential options for an Application. Not # all options apply to each subtype, however we can't limit the # options in children if we want to switch between channels and # satisfy the confighelper's requirements. self.origin: str = config.get('origin') self.primary_branch = config.get("primary_branch", "master") self.npm_pkg_json: Optional[pathlib.Path] = None if config.get("enable_node_updates", False): self.npm_pkg_json = self.path.joinpath("package-lock.json") self._verify_path(config, 'enable_node_updates', self.npm_pkg_json) self.python_reqs: Optional[pathlib.Path] = None if self.executable is not None: self.python_reqs = self.path.joinpath(config.get("requirements")) self._verify_path(config, 'requirements', self.python_reqs) self.install_script: Optional[pathlib.Path] = None install_script = config.get('install_script', None) if install_script is not None: self.install_script = self.path.joinpath(install_script).resolve() self._verify_path(config, 'install_script', self.install_script)
def _get_path_option(self, config: ConfigHelper, option: str) -> str: path: Optional[str] = config.get(option, None) if path is None: return "" expanded = os.path.abspath(os.path.expanduser(path)) if not os.path.exists(expanded): raise self.server.error(f"Invalid path for option '{option}', " f"{path} does not exist") return expanded
def __init__(self, config: ConfigHelper, paneldue: PanelDue) -> None: self.event_loop = config.get_server().get_event_loop() self.paneldue = paneldue self.port: str = config.get('serial') self.baud = config.getint('baud', 57600) self.partial_input: bytes = b"" self.ser: Optional[serial.Serial] = None self.fd: Optional[int] = None self.connected: bool = False self.send_busy: bool = False self.send_buffer: bytes = b"" self.attempting_connect: bool = True
def __init__(self, config: ConfigHelper, paneldue: PanelDue) -> None: self.ioloop = IOLoop.current() self.paneldue = paneldue self.port: str = config.get('serial') self.baud = config.getint('baud', 57600) self.partial_input: bytes = b"" self.ser: Optional[serial.Serial] = None self.fd: Optional[int] = None self.connected: bool = False self.send_busy: bool = False self.send_buffer: bytes = b"" self.attempting_connect: bool = True self.ioloop.spawn_callback(self._connect)
def __init__(self, config: ConfigHelper, cmd_helper: CommandHelper ) -> None: name_parts = config.get_name().split() self.name = name_parts[-1] self.server = config.get_server() self.cmd_helper = cmd_helper if name_parts == 1: self.prefix: str = "" if config.get('type', "") == "web": self.prefix = f"Web Client {self.name}: " else: self.prefix = f"Application {self.name}: "
def __init__(self, config: ConfigHelper) -> None: 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.type: str = config.get('type') self.state: str = "init" self.locked_while_printing = config.getboolean('locked_while_printing', False) self.off_when_shutdown = config.getboolean('off_when_shutdown', False) self.restart_delay = 1. self.klipper_restart = config.getboolean( 'restart_klipper_when_powered', False) if self.klipper_restart: self.restart_delay = config.getfloat('restart_delay', 1.) if self.restart_delay < .000001: raise config.error("Option 'restart_delay' must be above 0.0")
def __init__(self, config: ConfigHelper, cmd_helper: CommandHelper) -> None: super().__init__(config, cmd_helper) self.need_channel_update = self.type != "zip" self.official_repo: str = "?" self.owner: str = "?" # Extract repo from origin for validation match = re.match(r"https?://(?:www\.)?github.com/([^/]+/[^.]+)", self.origin) if match is not None: self.official_repo = match.group(1) self.owner = self.official_repo.split('/')[0] else: raise config.error( "Invalid url set for 'origin' option in section " f"[{config.get_name()}]. Unable to extract owner/repo.") self.host_repo: str = config.get('host_repo', self.official_repo) self.package_list: List[str] = [] self.python_pkg_list: List[str] = [] self.release_download_info: Tuple[str, str, int] = ("?", "?", 0)
def _parse_pin(self, config: ConfigHelper) -> Tuple[int, str, bool]: pin = cfg_pin = config.get("pin") invert = False if pin[0] == "!": pin = pin[1:] invert = True chip_id: str = "gpiochip0" pin_parts = pin.split("/") if len(pin_parts) == 2: chip_id, pin = pin_parts elif len(pin_parts) == 1: pin = pin_parts[0] # Verify pin if not chip_id.startswith("gpiochip") or \ not chip_id[-1].isdigit() or \ not pin.startswith("gpio") or \ not pin[4:].isdigit(): raise config.error(f"Invalid Power Pin configuration: {cfg_pin}") pin_id = int(pin[4:]) return pin_id, chip_id, invert
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.queued_jobs: Dict[str, QueuedJob] = {} self.lock = asyncio.Lock() self.load_on_start = config.getboolean("load_on_startup", False) self.automatic = config.getboolean("automatic_transition", False) self.queue_state: str = "ready" if self.automatic else "paused" self.job_delay = config.getfloat("job_transition_delay", 0.01) if self.job_delay <= 0.: raise config.error( "Value for option 'job_transition_delay' in section [job_queue]" " must be above 0.0") self.job_transition_gcode = config.get( "job_transition_gcode", "").strip() self.pop_queue_handle: Optional[asyncio.TimerHandle] = None self.server.register_event_handler( "server:klippy_ready", self._handle_ready) self.server.register_event_handler( "server:klippy_shutdown", self._handle_shutdown) self.server.register_event_handler( "job_state:complete", self._on_job_complete) self.server.register_event_handler( "job_state:error", self._on_job_abort) self.server.register_event_handler( "job_state:cancelled", self._on_job_abort) self.server.register_notification("job_queue:job_queue_changed") self.server.register_remote_method("pause_job_queue", self.pause_queue) self.server.register_remote_method("start_job_queue", self.start_queue) self.server.register_endpoint( "/server/job_queue/job", ['POST', 'DELETE'], self._handle_job_request) self.server.register_endpoint( "/server/job_queue/pause", ['POST'], self._handle_pause_queue) self.server.register_endpoint( "/server/job_queue/start", ['POST'], self._handle_start_queue) self.server.register_endpoint( "/server/job_queue/status", ['GET'], self._handle_queue_status)
def __init__(self, config: ConfigHelper) -> None: super().__init__(config) if self.off_when_shutdown: raise config.error( "Option 'off_when_shutdown' in section " f"[{config.get_name()}] is unsupported for 'klipper_device'") if self.klipper_restart: raise config.error( "Option 'restart_klipper_when_powered' in section " f"[{config.get_name()}] is unsupported for 'klipper_device'") if (self.bound_service is not None and self.bound_service.startswith("klipper")): # Klipper devices cannot be bound to an instance of klipper or # klipper_mcu raise config.error( f"Option 'bound_service' cannot be set to {self.bound_service}" f" for 'klipper_device' [{config.get_name()}]") self.is_shutdown: bool = False self.update_fut: Optional[asyncio.Future] = None self.request_mutex = asyncio.Lock() self.timer: Optional[float] = config.getfloat('timer', None, above=0.000001) self.timer_handle: Optional[asyncio.TimerHandle] = None self.object_name = config.get('object_name') obj_parts = self.object_name.split() self.gc_cmd = f"SET_PIN PIN={obj_parts[-1]} " if obj_parts[0] == "gcode_macro": self.gc_cmd = obj_parts[-1] elif obj_parts[0] != "output_pin": raise config.error( "Klipper object must be either 'output_pin' or 'gcode_macro' " f"for option 'object_name' in section [{config.get_name()}]") self.server.register_event_handler("server:status_update", self._status_update) self.server.register_event_handler("server:klippy_ready", self._handle_ready) self.server.register_event_handler("server:klippy_disconnect", self._handle_disconnect)
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: server = config.get_server() self.secrets_file: Optional[pathlib.Path] = None path: Optional[str] = config.get('secrets_path', None) self.type = "invalid" self.values: Dict[str, Any] = {} if path is not None: self.secrets_file = pathlib.Path(path).expanduser().resolve() if not self.secrets_file.is_file(): server.add_warning( "[secrets]: option 'secrets_path', file does not exist: " f"'{self.secrets_file}'") return data = self.secrets_file.read_text() vals = self._parse_json(data) if vals is not None: if not isinstance(vals, dict): server.add_warning( f"[secrets]: option 'secrets_path', top level item in" f" json file '{self.secrets_file}' must be an Object.") return self.values = vals self.type = "json" else: vals = self._parse_ini(data) if vals is None: server.add_warning( "[secrets]: option 'secrets_path', invalid file " f"format, must be json or ini: '{self.secrets_file}'") return self.values = vals self.type = "ini" logging.debug(f"[secrets]: Loaded {self.type} file: " f"{self.secrets_file}") else: logging.debug( "[secrets]: Option `secrets_path` not supplied")
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 __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.event_loop = self.server.get_event_loop() self.app_config = config.read_supplemental_config( SUPPLEMENTAL_CFG_PATH) auto_refresh_enabled = config.getboolean('enable_auto_refresh', False) self.channel = config.get('channel', "dev") if self.channel not in ["dev", "beta"]: raise config.error( f"Unsupported channel '{self.channel}' in section" " [update_manager]") self.cmd_helper = CommandHelper(config) self.updaters: Dict[str, BaseDeploy] = {} if config.getboolean('enable_system_updates', True): self.updaters['system'] = PackageDeploy(config, self.cmd_helper) if ( os.path.exists(KLIPPER_DEFAULT_PATH) and os.path.exists(KLIPPER_DEFAULT_EXEC) ): self.updaters['klipper'] = get_deploy_class(KLIPPER_DEFAULT_PATH)( self.app_config[f"update_manager klipper"], self.cmd_helper, { 'channel': self.channel, 'path': KLIPPER_DEFAULT_PATH, 'executable': KLIPPER_DEFAULT_EXEC }) else: self.updaters['klipper'] = BaseDeploy( self.app_config[f"update_manager klipper"], self.cmd_helper) self.updaters['moonraker'] = get_deploy_class(MOONRAKER_PATH)( self.app_config[f"update_manager moonraker"], self.cmd_helper, { 'channel': self.channel, 'path': MOONRAKER_PATH, 'executable': sys.executable }) # TODO: The below check may be removed when invalid config options # raise a config error. if ( config.get("client_repo", None) is not None or config.get('client_path', None) is not None ): raise config.error( "The deprecated 'client_repo' and 'client_path' options\n" "have been removed. See Moonraker's configuration docs\n" "for details on client configuration.") client_sections = config.get_prefix_sections("update_manager ") for section in client_sections: cfg = config[section] name = section.split()[-1] if name in self.updaters: raise config.error(f"Client repo {name} already added") client_type = cfg.get("type") if client_type in ["web", "web_beta"]: self.updaters[name] = WebClientDeploy(cfg, self.cmd_helper) elif client_type in ["git_repo", "zip", "zip_beta"]: path = os.path.expanduser(cfg.get('path')) self.updaters[name] = get_deploy_class(path)( cfg, self.cmd_helper) else: raise config.error( f"Invalid type '{client_type}' for section [{section}]") self.cmd_request_lock = asyncio.Lock() self.initialized_lock = asyncio.Event() self.klippy_identified_evt: Optional[asyncio.Event] = None # Auto Status Refresh self.last_refresh_time: float = 0 self.refresh_cb: Optional[PeriodicCallback] = None if auto_refresh_enabled: self.refresh_cb = PeriodicCallback( self._handle_auto_refresh, # type: ignore UPDATE_REFRESH_INTERVAL_MS) self.server.register_endpoint( "/machine/update/moonraker", ["POST"], self._handle_update_request) self.server.register_endpoint( "/machine/update/klipper", ["POST"], self._handle_update_request) self.server.register_endpoint( "/machine/update/system", ["POST"], self._handle_update_request) self.server.register_endpoint( "/machine/update/client", ["POST"], self._handle_update_request) self.server.register_endpoint( "/machine/update/full", ["POST"], self._handle_full_update_request) self.server.register_endpoint( "/machine/update/status", ["GET"], self._handle_status_request) self.server.register_endpoint( "/machine/update/recover", ["POST"], self._handle_repo_recovery) self.server.register_notification("update_manager:update_response") self.server.register_notification("update_manager:update_refreshed") # Register Ready Event self.server.register_event_handler( "server:klippy_identified", self._set_klipper_repo) # Initialize GitHub API Rate Limits and configured updaters self.event_loop.register_callback( self._initalize_updaters, list(self.updaters.values()))
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.event_loop = self.server.get_event_loop() self.file_manager: FMComp = \ self.server.lookup_component('file_manager') self.klippy_apis: APIComp = \ self.server.lookup_component('klippy_apis') self.kinematics: str = "none" self.machine_name = config.get('machine_name', "Klipper") self.firmware_name: str = "Repetier | Klipper" self.last_message: Optional[str] = None self.last_gcode_response: Optional[str] = None self.current_file: str = "" self.file_metadata: Dict[str, Any] = {} self.enable_checksum = config.getboolean('enable_checksum', True) self.debug_queue: Deque[str] = deque(maxlen=100) # Initialize tracked state. self.printer_state: Dict[str, Dict[str, Any]] = { 'gcode_move': {}, 'toolhead': {}, 'virtual_sdcard': {}, 'fan': {}, 'display_status': {}, 'print_stats': {}, 'idle_timeout': {}, 'gcode_macro PANELDUE_BEEP': {} } self.extruder_count: int = 0 self.heaters: List[str] = [] self.is_ready: bool = False self.is_shutdown: bool = False self.initialized: bool = False self.cq_busy: bool = False self.gq_busy: bool = False self.command_queue: List[Tuple[FlexCallback, Any, Any]] = [] self.gc_queue: List[str] = [] self.last_printer_state: str = 'O' self.last_update_time: float = 0. # Set up macros self.confirmed_gcode: str = "" self.mbox_sequence: int = 0 self.available_macros: Dict[str, str] = {} self.confirmed_macros = { "RESTART": "RESTART", "FIRMWARE_RESTART": "FIRMWARE_RESTART" } macros = config.getlist('macros', None) if macros is not None: # The macro's configuration name is the key, whereas the full # command is the value self.available_macros = {m.split()[0]: m for m in macros} conf_macros = config.getlist('confirmed_macros', None) if conf_macros is not None: # The macro's configuration name is the key, whereas the full # command is the value self.confirmed_macros = {m.split()[0]: m for m in conf_macros} self.available_macros.update(self.confirmed_macros) self.non_trivial_keys = config.getlist('non_trivial_keys', ["Klipper state"]) self.ser_conn = SerialConnection(config, self) logging.info("PanelDue Configured") # Register server events self.server.register_event_handler("server:klippy_ready", self._process_klippy_ready) self.server.register_event_handler("server:klippy_shutdown", self._process_klippy_shutdown) self.server.register_event_handler("server:klippy_disconnect", self._process_klippy_disconnect) self.server.register_event_handler("server:status_update", self.handle_status_update) self.server.register_event_handler("server:gcode_response", self.handle_gcode_response) self.server.register_remote_method("paneldue_beep", self.paneldue_beep) # These commands are directly executued on the server and do not to # make a request to Klippy self.direct_gcodes: Dict[str, FlexCallback] = { 'M20': self._run_paneldue_M20, 'M30': self._run_paneldue_M30, 'M36': self._run_paneldue_M36, 'M408': self._run_paneldue_M408 } # These gcodes require special parsing or handling prior to being # sent via Klippy's "gcode/script" api command. self.special_gcodes: Dict[str, Callable[[List[str]], str]] = { 'M0': lambda args: "CANCEL_PRINT", 'M23': self._prepare_M23, 'M24': lambda args: "RESUME", 'M25': lambda args: "PAUSE", 'M32': self._prepare_M32, 'M98': self._prepare_M98, 'M120': lambda args: "SAVE_GCODE_STATE STATE=PANELDUE", 'M121': lambda args: "RESTORE_GCODE_STATE STATE=PANELDUE", 'M290': self._prepare_M290, 'M292': self._prepare_M292, 'M999': lambda args: "FIRMWARE_RESTART" }
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.address: str = config.get('address') self.port: int = config.getint('port', 1883) self.user_name = config.get('username', None) pw_file_path = config.get('password_file', 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() 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"]) self.server.register_endpoint("/server/mqtt/subscribe", ["POST"], self._handle_subscription_request, transports=["http", "websocket"]) # 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.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) logging.info( f"Moonraker API topics - Request: {self.api_request_topic}, " f"Response: {self.api_resp_topic}") IOLoop.current().spawn_callback(self._initialize)