InvenTree 插件开发:基于 MachineDriverMixin 注册自定义机器驱动与机器类型
2026/9/17 9:53:28 网站建设 项目流程

InvenTree 插件开发:基于 MachineDriverMixin 注册自定义机器驱动与机器类型

【免费下载链接】InvenTreeOpen Source Inventory Management System项目地址: https://gitcode.com/GitHub_Trending/in/InvenTree

导读

InvenTree 内置了一套「机器注册表」(Machine Registry),允许以插件的形式接入外部硬件设备(如标签打印机)。本篇指南以MachineDriverMixin为核心,讲解如何通过插件实现get_machine_driversget_machine_types两个钩子方法,从而注册自定义机器驱动与自定义机器类型。读完本文,你将掌握机器驱动(Driver)与机器类型(Machine Type)的完整开发流程,并能参照官方示例插件编写出可直接运行的自定义驱动,同时理解其背后的注册表初始化原理、多进程缓存约束与状态上报机制。

什么是 MachineDriverMixin

MachineDriverMixin是 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. 阶段 1 —— 发现机器类型:查找所有继承BaseMachineType的类;
  2. 阶段 2 —— 发现驱动:查找所有继承BaseDriver且未被引用为任何机器类型基础驱动的类;
  3. 阶段 3 —— 机器加载:为数据库中的每条MachineConfig实例化对应的MachineType类,依次调用driver.init_drivermachine.initialize(后者内部会为每台机器调用driver.init_machine),最后将machine.initialized置为true

从 registry.py 的源码可以看到注册表内部维护了四类状态:machine_typesdriversdriver_instancesmachines。其中同一驱动类在注册表中只维护一个实例driver_instances: dict[str, BaseDriver]),机器类型实例化时按需传入该共享驱动实例。

一个最基本的驱动注册示例如下(选自 overview.md):

from 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]

驱动类只需声明SLUGNAMEDESCRIPTION等基础属性,其余能力由对应的机器类型基础驱动(如ABCBaseDriver)提供。驱动会被插件系统发现的前提是该插件已安装并激活

钩子方法二:get_machine_types

要注册自定义机器类型,必须实现get_machine_types方法,返回值是插件支持的机器类型类列表(list[BaseMachineType])。

机器类型定义了 InvenTree 与物理机器之间的「连接功能类型」。InvenTree 目前内置的机器类型是 Label printer(直接为各类物料打印标签)。若要创建全新机器类型,可参考machines/machine_types/*.py中已有实现,其导出入口在 plugin/machine/machine_types.py。

一个名为abc的机器类型定义示例如下(选自 overview.md):

from 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还提供了一组实例 API:machine_confignameactiveinitializeupdaterestarthandle_errorclear_errorsget_settingset_settingcheck_settingset_statusset_status_textset_properties,驱动代码中均可直接调用。

驱动生命周期钩子:BaseDriver 的完整 API

驱动继承BaseDriver后,可根据需要覆盖以下生命周期方法(方法签名与文档注释见 machine/machine_type.py):

方法触发时机说明
init_driver()所有机器创建完成后用于初始化驱动,之后会为每台关联机器调用init_machine
init_machine(machine)每台激活机器初始化时若抛出异常,异常会被记录到machine.errors
update_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字典属性,其格式与普通插件SettingsMixinSETTINGS完全一致(参考 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.py):

MACHINE_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.md):

from 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.md):

from 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.py):

def 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,它是一个实现标签打印驱动的完整最小样例。它同时继承了MachineDriverMixinSettingsMixin,并通过get_machine_drivers注册了SamplePrinterDriver

class 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_machineprint_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'], cache=False)一一对应。

与 LabelPrintingMixin 的差异

标签打印机器替代了传统的LabelPrintingMixin插件(见 label_printer.md)。相比传统方式,机器方案的核心优势在于:同一驱动可以通过不同设置创建多台机器,从而让同品牌的多个标签打印机同时接入 InvenTree。

编写自定义标签打印驱动时,插件需实现MachineDriverMixin并在get_machine_drivers中返回标签打印驱动列表;驱动需实现print_labelprint_labels函数,并可选用get_printersPrintingOptionsSerializerrender_to_pdfrender_to_pdf_datarender_to_htmlrender_to_pngLabelPrintingDriver API能力。标签打印机预定义了一套状态码(默认状态为UNKNOWN),驱动可随时变更。

生产环境约束:共享 Redis 缓存是硬性要求

使用机器功能时有两个重要的部署前提(见 overview.md):

  1. 生产环境(多 worker)下必须配置共享 Redis 缓存。由于 Python 在不同进程间不共享状态,连接 worker 后每个 worker 线程与主线程都会各自存在一份机器注册表实例,机器与驱动会被多次实例化(__init__多次调用)。但初始化函数与更新钩子(如init_machine只会从主进程调用一次
  2. 注册表、驱动与机器状态(状态码、错误等)都存储在缓存中,因此需要支持跨进程共享的 Redis 缓存;默认的本地内存缓存不具备跨进程能力。部署时请参考 进程与缓存服务器配置。

小结

围绕MachineDriverMixin,可以总结出清晰的插件开发路径:

  1. 插件类继承MachineDriverMixin(按需叠加SettingsMixin等)并实现get_machine_drivers
  2. 若要定义全新设备品类,再实现get_machine_types返回自定义BaseMachineType子类;
  3. 驱动类继承对应机器类型的基础驱动,声明SLUG/NAME/DESCRIPTIONMACHINE_SETTINGS,并按需覆盖init_driverinit_machineupdate_machineping_machines等钩子;
  4. 通过machine.set_status/set_status_text/set_properties/handle_error上报设备状态与属性;
  5. 生产部署时确保配置共享 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询