Skip to content

Commit

Permalink
Initial driver implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
wolflu05 committed Feb 9, 2024
1 parent 92baff2 commit 078ed62
Show file tree
Hide file tree
Showing 10 changed files with 711 additions and 1 deletion.
60 changes: 60 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: CI

on:
push:
pull_request:
release:
types: [published]

jobs:
style-python:
name: "💄 Style: python"
if: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }}
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # [email protected]

- name: Setup python
uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # [email protected]
with:
python-version: "3.10"

- name: Install style check dependencies
run: |
pip install flake8==7.0.0
pip install pep8-naming==0.13.3
- name: Check style
run: |
flake8 .
publish:
if: github.event_name == 'release' && github.event.action == 'published'
needs: [style-python]
name: 📦 Publish to PyPi
runs-on: ubuntu-latest
environment:
name: release
url: https://pypi.org/p/inventree-dymo-plugin
permissions:
id-token: write

steps:
- name: Checkout
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # [email protected]

- name: Setup python
uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # [email protected]
with:
python-version: "3.10"

- name: Install build dependencies
run: pip install --upgrade wheel setuptools twine build

- name: Build pip package
run: python3 -m build

- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@f8c70e705ffc13c3b4d1221169b84f12a75d6ca8 # [email protected]
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,8 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/


inventree_dymo/testing.py
inventree_dymo/*.bin
.vscode/
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,34 @@
# inventree-dymo-plugin
Dymo Label printer driver plugin for InvenTree.

[![License: ](https://img.shields.io/badge/License-GPLv3-yellow.svg)](https://opensource.org/licenses/MIT)
![CI](https://github.com/wolflu05/inventree-dymo-plugin/actions/workflows/ci.yml/badge.svg)

A label printer driver plugin for [InvenTree](https://inventree.org/), which provides support for Dymo Label Writer® printers.

## Compatibility

The following printers are already supported by the driver:

- DYMO Label Writer 450
- DYMO Label Writer 450 Duo (Tape is not supported currently)
- DYMO Label Writer 450 Turbo
- DYMO Label Writer 450 Twin Turbo

## Requirements

Currently only printing over network is supported, so an RAW network socket server needs to be connected to the printer. A raspberry pi zero w is just enough for that job.

The easiest way to set this up, is using cups and configure a RAW printer device in combination with `xinetd` like described in this [blog post](https://nerdig.es/labelwriter-im-netz-teil1/).

## Installation

> [!IMPORTANT]
> This plugin is only compatible with InvenTree>=0.14 because this uses the new label printer driver interface.
Goto "Admin Center > Plugins > Install Plugin" and enter `inventree-dymo-plugin` as package name.

Then goto "Admin Center > Machines" and create a new machine using this driver.

## Technical working

This driver implements the RAW Dymo LabelWriter® 450 Series commands like described in the [technical reference manual](https://download.dymo.com/dymo/technical-data-sheets/LW%20450%20Series%20Technical%20Reference.pdf) to send the label data to the printer.
128 changes: 128 additions & 0 deletions inventree_dymo/InvenTreeDymoPlugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import socket
from django.db.models.query import QuerySet
from rest_framework.request import Request
from django.utils.translation import gettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator

from plugin import InvenTreePlugin
from plugin.base.label.mixins import LabelItemType
from plugin.machine.machine_types import LabelPrinterBaseDriver, LabelPrinterMachine
from label.models import LabelTemplate

from .version import DYMO_PLUGIN_VERSION
from .dymo import DymoLabel, RoleSelect, PrintDensity


class InvenTreeDymoPlugin(InvenTreePlugin):
AUTHOR = "wolflu05"
DESCRIPTION = "InvenTree Dymo plugin"
VERSION = DYMO_PLUGIN_VERSION

# Machine driver registry is only available in InvenTree 0.14.0 and later
MIN_VERSION = "0.14.0"

TITLE = "InvenTree Dymo Plugin"
SLUG = "inventree-dymo-plugin"
NAME = "InvenTreeDymoPlugin"


class DymoLabelPrinterDriver(LabelPrinterBaseDriver):
"""Label printer driver for Dymo printers."""

SLUG = "dymo-driver"
NAME = "Dymo Driver"
DESCRIPTION = "Dymo label printing drive for InvenTree"

def __init__(self, *args, **kwargs):
self.MACHINE_SETTINGS = {
'SERVER': {
'name': _('Server'),
'description': _('IP/Hostname of the Dymo print server'),
'default': 'localhost',
'required': True,
},
'PORT': {
'name': _('Port'),
'description': _('Port number of the Dymo print server'),
'validator': int,
'default': 9100,
'required': True,
},
'SELECT_ROLL': {
'name': _('Select Roll'),
'description': _('Select the roll to use for printing'),
'choices': [(a.name, a.label) for a in RoleSelect],
'default': 'AUTOMATIC',
'required': True,
},
'DENSITY': {
'name': _('Print Density'),
'description': _('Set the print density'),
'choices': [(a.name, a.label) for a in PrintDensity],
'default': 'NORMAL',
'required': True,
},
'PRINT_MODE': {
'name': _('Print Mode'),
'description': _('Set the print mode'),
'choices': [('TEXT', _('Text (300x300dpi)')), ('GRAPHIC', _('Graphic (300x600dpi)'))],
'default': 'TEXT',
'required': True,
},
'LABEL_LENGTH': {
'name': _('Label Length'),
'description': _('Set the label length in dots between the holes + 10mm tolerance (e.g: (<distance> mm + 10mm)/25.4inch*300dpi)'),
'validator': int,
'default': 3058,
'required': True,
},
'THRESHOLD': {
'name': _('Threshold'),
'description': _('Set the threshold for converting grayscale to BW (0-255)'),
'validator': [int, MinValueValidator(0), MaxValueValidator(255)],
'default': 200,
'required': True,
},
'ROTATE': {
'name': _('Rotate'),
'description': _('Rotate the label'),
'choices': [(f"{a}", f"{a}°") for a in [0, 90, 180, 270]],
'default': "0",
'required': False,
},
}

super().__init__(*args, **kwargs)

def print_labels(self, machine: LabelPrinterMachine, label: LabelTemplate, items: QuerySet[LabelItemType], request: Request, **kwargs):
"""Print labels using a Dymo label printer."""
printing_options = kwargs.get('printing_options', {})

dymo_label = DymoLabel(
label_length=machine.get_setting('LABEL_LENGTH', 'D'),
mode=machine.get_setting('PRINT_MODE', 'D'),
density=PrintDensity[machine.get_setting('DENSITY', 'D')],
role_select=RoleSelect[machine.get_setting('SELECT_ROLL', 'D')],
rotate=int(machine.get_setting('ROTATE', 'D')),
threshold=machine.get_setting('THRESHOLD', 'D'),
)

for item in items:
dpi = {"TEXT": 300, "GRAPHIC": 600}[dymo_label.mode]
png = self.render_to_png(label, item, request, dpi=dpi)

for _copies in range(printing_options.get('copies', 1)):
dymo_label.add_label(png)

data = dymo_label.get_data()

ip_addr = machine.get_setting('SERVER', 'D')
port = machine.get_setting('PORT', 'D')

try:
print_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print_socket.connect((ip_addr, port))
print_socket.send(data)
print_socket.close()
except Exception as e:
raise ConnectionError(f"Error connection to network printer: {e}")
Empty file added inventree_dymo/__init__.py
Empty file.
Loading

0 comments on commit 078ed62

Please sign in to comment.