InvenTree 插件开发:基于 MachineDriverMixin 注册自定义机器驱动与机器类型
InvenTree 插件开发基于 MachineDriverMixin 注册自定义机器驱动与机器类型【免费下载链接】InvenTreeOpen Source Inventory Management System项目地址: https://gitcode.com/GitHub_Trending/in/InvenTree导读InvenTree 内置了一套「机器注册表」Machine Registry允许以插件的形式接入外部硬件设备如标签打印机。本篇指南以MachineDriverMixin为核心讲解如何通过插件实现get_machine_drivers与get_machine_types两个钩子方法从而注册自定义机器驱动与自定义机器类型。读完本文你将掌握机器驱动Driver与机器类型Machine Type的完整开发流程并能参照官方示例插件编写出可直接运行的自定义驱动同时理解其背后的注册表初始化原理、多进程缓存约束与状态上报机制。什么是 MachineDriverMixinMachineDriverMixin是 InvenTree 插件体系中用于「注册机器驱动与机器类型」的混合类Mixin。它的源码位于 MachineMixin.py其类注释明确说明了两种用途get_machine_types注册一个自定义的机器类型返回BaseMachineType子类列表。get_machine_drivers为已有机器类型注册自定义机器驱动返回BaseDriver子类列表。InvenTree 通过插件提供设备驱动driver来集成外部机器机器驱动负责在物理设备与 InvenTree 之间建立连接层。围绕机器的架构体系在 机器概述文档 中有完整阐述机器注册表是核心组件服务器启动时初始化并统一管理所有已配置的机器。从源码看该 Mixin 在__init__中通过self.add_mixin(PluginMixinEnum.MACHINE, True, __class__)向插件注册自己MIXIN_NAME为MachineDriver。这意味着只要插件类继承了MachineDriverMixin插件系统就能识别并加载它声明的机器相关能力。class MachineDriverMixin: Mixin class for registering machine driver types. class MixinMeta: MIXIN_NAME MachineDriver def __init__(self): super().__init__() self.add_mixin(PluginMixinEnum.MACHINE, True, __class__) def get_machine_types(self) - list[BaseMachineType]: Register custom machine types. return [] def get_machine_drivers(self) - list[BaseDriver]: Register custom machine drivers. return []注意这两个方法的默认实现均返回空列表即插件若不覆盖它们则不会注册任何机器驱动或机器类型。钩子方法一get_machine_drivers要注册自定义机器驱动必须实现get_machine_drivers方法返回值是插件支持的机器驱动类列表list[BaseDriver]。驱动的发现流程与注册表的三阶段初始化过程直接相关见 overview.md阶段 1 —— 发现机器类型查找所有继承BaseMachineType的类阶段 2 —— 发现驱动查找所有继承BaseDriver且未被引用为任何机器类型基础驱动的类阶段 3 —— 机器加载为数据库中的每条MachineConfig实例化对应的MachineType类依次调用driver.init_driver、machine.initialize后者内部会为每台机器调用driver.init_machine最后将machine.initialized置为true。从 registry.py 的源码可以看到注册表内部维护了四类状态machine_types、drivers、driver_instances与machines。其中同一驱动类在注册表中只维护一个实例driver_instances: dict[str, BaseDriver]机器类型实例化时按需传入该共享驱动实例。一个最基本的驱动注册示例如下选自 overview.mdfrom plugin.mixins import MachineDriverMixin from plugin import InvenTreePlugin from plugin.machine.machine_types import ABCBaseDriver class XYZDriver(ABCBaseDriver): SLUG my-xyz-driver NAME My XYZ driver DESCRIPTION This is an awesome XYZ driver for a ABC machine class MyXyzAbcDriverPlugin(MachineDriverMixin, InvenTreePlugin): NAME XyzAbcDriver SLUG xyz-driver TITLE Xyz Abc Driver # ... def get_machine_drivers(self): Return a list of machine drivers for this plugin. return [XYZDriver]驱动类只需声明SLUG、NAME、DESCRIPTION等基础属性其余能力由对应的机器类型基础驱动如ABCBaseDriver提供。驱动会被插件系统发现的前提是该插件已安装并激活。钩子方法二get_machine_types要注册自定义机器类型必须实现get_machine_types方法返回值是插件支持的机器类型类列表list[BaseMachineType]。机器类型定义了 InvenTree 与物理机器之间的「连接功能类型」。InvenTree 目前内置的机器类型是 Label printer直接为各类物料打印标签。若要创建全新机器类型可参考machines/machine_types/*.py中已有实现其导出入口在 plugin/machine/machine_types.py。一个名为abc的机器类型定义示例如下选自 overview.mdfrom django.utils.translation import gettext_lazy as _ from generic.states import ColorEnum from plugin.machine import BaseDriver, BaseMachineType, MachineStatus class ABCBaseDriver(BaseDriver): Base xyz driver. machine_type abc def my_custom_required_method(self): This function must be overridden. raise NotImplementedError(The my_custom_required_method function must be overridden!) def my_custom_method(self): This function can be overridden. raise NotImplementedError(The my_custom_method function can be overridden!) required_overrides [my_custom_required_method] class ABCMachine(BaseMachineType): SLUG abc NAME _(ABC) DESCRIPTION _(This is an awesome machine type for ABC.) base_driver ABCBaseDriver class ABCStatus(MachineStatus): CONNECTED 100, _(Connected), ColorEnum.success STANDBY 101, _(Standby), ColorEnum.success PRINTING 110, _(Printing), ColorEnum.primary MACHINE_STATUS ABCStatus default_machine_status ABCStatus.DISCONNECTED要点解读base_driver指定该机器类型的基础驱动类所有该类型的第三方驱动都必须继承它MACHINE_STATUS声明该类型可用的状态码枚举default_machine_status指定默认状态机器类型类会在服务器启动时为每台机器实例化一次实例引用被保存在注册表中因此machine.NAME指机器类型名而machine.name指向用户为机器实例定义的名字见 overview.md。BaseMachineType还提供了一组实例 APImachine_config、name、active、initialize、update、restart、handle_error、clear_errors、get_setting、set_setting、check_setting、set_status、set_status_text、set_properties驱动代码中均可直接调用。驱动生命周期钩子BaseDriver 的完整 API驱动继承BaseDriver后可根据需要覆盖以下生命周期方法方法签名与文档注释见 machine/machine_type.py方法触发时机说明init_driver()所有机器创建完成后用于初始化驱动之后会为每台关联机器调用init_machineinit_machine(machine)每台激活机器初始化时若抛出异常异常会被记录到machine.errorsupdate_machine(old_machine_state, machine)每次机器更新时入参为旧状态字典与携带新状态的机器实例restart_machine(machine)管理员中心手动重启机器时手动重启触发ping_machines()后台任务周期性调用当全局设置MACHINE_PING_ENABLED开启时周期性探测机器在线状态get_machines(**kwargs)任意时刻返回使用该驱动的机器列表默认仅返回已初始化机器handle_error(error)出错时统一处理驱动错误驱动配置MACHINE_SETTINGS 与 required 校验每台机器可以拥有不同的配置。机器设置machine settings属于机器类型驱动设置driver settings属于驱动但两者都可以为每台机器单独指定。定义方式是在驱动类或机器类型类上添加MACHINE_SETTINGS字典属性其格式与普通插件SettingsMixin的SETTINGS完全一致参考 SettingsMixin。class MyXYZDriver(ABCBaseDriver): MACHINE_SETTINGS { SERVER: { name: _(Server), description: _(IP/Hostname to connect to the cups server), default: localhost, required: True, } }特别地设置项可以标记required: True这会在机器启动前阻止未配置该设置的机器运行。源码中对应的是 machine_type.py 的get_setting与设置校验逻辑机器启动前会检查所有必需的设置项是否已定义缺失则拒绝启动。官方示例插件SamplePrinterDriver还展示了units单位、validator校验器等扩展字段的用法见 sample_printer.pyMACHINE_SETTINGS { CONNECTION: { name: Connection String, description: Custom string for connecting to the printer, default: 123-xx123:8000, }, DELAY: { name: Print Delay, description: Delay (in seconds) before printing, default: 0, units: seconds, validator: int, }, }机器状态状态码、自由文本与错误处理机器状态用于向用户报告设备当前状况由驱动为每台机器设置但会在服务器重启后丢失。每台机器都必须有默认状态码机器类型通过MACHINE_STATUS定义状态码集合overview.mdfrom plugin.machine import MachineStatus, BaseMachineType class XYZStatus(MachineStatus): CONNECTED 100, _(Connected), success STANDBY 101, _(Standby), success DISCONNECTED 400, _(Disconnected), danger class XYZMachineType(BaseMachineType): # ... MACHINE_STATUS XYZStatus default_machine_status XYZStatus.DISCONNECTED驱动在初始化或运行过程中通过machine.set_status(...)设置状态码class MyXYZDriver(ABCBaseDriver): # ... def init_machine(self, machine): # ... do some init stuff here machine.set_status(XYZMachineType.MACHINE_STATUS.CONNECTED)set_status的底层实现machine_type.py会将状态值写入机器的共享状态set_shared_state(status, status.value)。除了结构化状态码还可以设置任意自由文本状态例如machine.set_status_text(Paper missing)用于补充人类可读的设备提示信息。机器属性set_properties 上报设备信息机器属性如设备型号、固件版本、累计打印页数会展示在机器详情抽屉中为用户提供相关设备信息。实现方式是调用machine.set_properties设置属性并可结合周期任务如ping_machines保持信息实时更新overview.mdfrom plugin.machine import MachineProperty class MyXYZDriver(ABCBaseDriver): # ... def ping_machines(self): for machine in self.get_machines(): # ... fetch machine info props: list[MachineProperty] [ { key: Model, value: ABC }, ] machine.set_properties(props)官方示例驱动在init_machine中同样使用了该接口并展示了type: progress这一带类型修饰的属性sample_printer.pydef init_machine(self, machine: BaseMachineType) - None: Machine initialization hook. machine.set_properties([ {key: Model, value: Sample Printer 3000}, {key: Battery, value: 42, type: progress}, ])官方示例SamplePrinterMachine 全解析文档指向的示例插件类SamplePrinterMachine位于 plugin/samples/machines/sample_printer.py它是一个实现标签打印驱动的完整最小样例。它同时继承了MachineDriverMixin与SettingsMixin并通过get_machine_drivers注册了SamplePrinterDriverclass SamplePrinterMachine(MachineDriverMixin, SettingsMixin, InvenTreePlugin): A very simple example of a printer machine plugin. NAME SamplePrinterMachine SLUG sample-printer-machine-plugin TITLE Sample dummy plugin for printing labels VERSION 0.1 def get_machine_drivers(self) - list[BaseDriver]: Return a list of drivers registered by this plugin. return [SamplePrinterDriver]其驱动SamplePrinterDriver继承自标签打印机的LabelPrinterBaseDriver实现了init_machine与print_label后者从机器设置中读取DELAY并模拟打印行为class SamplePrinterDriver(LabelPrinterBaseDriver): SLUG sample-printer-driver NAME Sample Label Printer Driver DESCRIPTION Sample label printing driver for InvenTree # MACHINE_SETTINGS 见上文 def print_label(self, machine, label, item, **kwargs): print_delay machine.get_setting(DELAY, D) print(MOCK LABEL PRINTING:) if print_delay 0: print(f - Delaying for {print_delay} seconds...) time.sleep(print_delay) print(- machine:, machine) print(- label:, label) print(- item:, item)这里machine.get_setting(DELAY, D)的第二个参数D表示读取的是驱动Driver设置而非机器类型设置机器类型为M与get_setting的签名get_setting(self, key, config_type_str: Literal[M, D], cacheFalse)一一对应。与 LabelPrintingMixin 的差异标签打印机器替代了传统的LabelPrintingMixin插件见 label_printer.md。相比传统方式机器方案的核心优势在于同一驱动可以通过不同设置创建多台机器从而让同品牌的多个标签打印机同时接入 InvenTree。编写自定义标签打印驱动时插件需实现MachineDriverMixin并在get_machine_drivers中返回标签打印驱动列表驱动需实现print_label或print_labels函数并可选用get_printers、PrintingOptionsSerializer、render_to_pdf、render_to_pdf_data、render_to_html、render_to_png等LabelPrintingDriver API能力。标签打印机预定义了一套状态码默认状态为UNKNOWN驱动可随时变更。生产环境约束共享 Redis 缓存是硬性要求使用机器功能时有两个重要的部署前提见 overview.md生产环境多 worker下必须配置共享 Redis 缓存。由于 Python 在不同进程间不共享状态连接 worker 后每个 worker 线程与主线程都会各自存在一份机器注册表实例机器与驱动会被多次实例化__init__多次调用。但初始化函数与更新钩子如init_machine只会从主进程调用一次。注册表、驱动与机器状态状态码、错误等都存储在缓存中因此需要支持跨进程共享的 Redis 缓存默认的本地内存缓存不具备跨进程能力。部署时请参考 进程与缓存服务器配置。小结围绕MachineDriverMixin可以总结出清晰的插件开发路径插件类继承MachineDriverMixin按需叠加SettingsMixin等并实现get_machine_drivers若要定义全新设备品类再实现get_machine_types返回自定义BaseMachineType子类驱动类继承对应机器类型的基础驱动声明SLUG/NAME/DESCRIPTION与MACHINE_SETTINGS并按需覆盖init_driver、init_machine、update_machine、ping_machines等钩子通过machine.set_status/set_status_text/set_properties/handle_error上报设备状态与属性生产部署时确保配置共享 Redis 缓存。官方样例插件sample_printer.py与注册表实现registry.py可作为进一步研究的最佳起点机器相关自动化测试见 machine/tests.py 与 machine/test_api.py。【免费下载链接】InvenTreeOpen Source Inventory Management System项目地址: https://gitcode.com/GitHub_Trending/in/InvenTree创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考