Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0f2f53a
started 3d printer binding
computergeek1507 May 15, 2026
5915dda
Update name
computergeek1507 May 15, 2026
1dbcc75
add job-preview image
computergeek1507 May 15, 2026
5cc45eb
missed a pom
computergeek1507 May 26, 2026
a3d12ae
Fix copilot suggestions
computergeek1507 May 29, 2026
528115a
Add to code owners
computergeek1507 Jun 16, 2026
0054e69
Attempt to fix Image preview
computergeek1507 Jul 28, 2026
c08ea0a
Fix Klipper preview not showing when item linked after fetch
computergeek1507 Aug 1, 2026
8680251
Fix same preview-not-updating bug in PrusaLink and OctoPrint handlers
computergeek1507 Aug 1, 2026
4c5b6a7
Update bundles/org.openhab.binding.threedprinter/pom.xml
computergeek1507 Aug 1, 2026
348d9de
Address PR review feedback
computergeek1507 Aug 1, 2026
ac29303
update readme info
computergeek1507 Aug 1, 2026
e99d603
Address remaining PR review feedback
computergeek1507 Aug 11, 2026
513307c
Address remaining functional review findings on Klipper and OctoPrint
computergeek1507 Aug 13, 2026
ca72e06
Fix OctoPrint /api/job null-safety and error handling
computergeek1507 Aug 18, 2026
9e55bc9
Fix command-status handling, PrusaLink job aux-request, and OctoPrint…
computergeek1507 Aug 19, 2026
a85eb83
Add multi-toolhead support for Klipper and OctoPrint
computergeek1507 Aug 19, 2026
3e56f65
Treat Moonraker's 400 as a command rejection; fix PrusaLink fan tag
computergeek1507 Aug 19, 2026
1c21f8e
Merge multi-toolhead support for Klipper and OctoPrint
computergeek1507 Aug 20, 2026
f74f1c3
Target the correct nozzle for primary setpoint commands
computergeek1507 Aug 22, 2026
ed519c0
Fix Klipper extra-extruder channel re-add and stale time-remaining
computergeek1507 Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@
/bundles/org.openhab.binding.tesla/ @kgoderis
/bundles/org.openhab.binding.teslapowerwall/ @psmedley
/bundles/org.openhab.binding.teslascope/ @psmedley
/bundles/org.openhab.binding.threedprinter/ @computergeek1507
/bundles/org.openhab.binding.tibber/ @kjoglum @weymann
/bundles/org.openhab.binding.tivo/ @mlobstein
/bundles/org.openhab.binding.touchwand/ @roieg
Expand Down
5 changes: 5 additions & 0 deletions bom/openhab-addons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2061,6 +2061,11 @@
<artifactId>org.openhab.binding.teslascope</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.threedprinter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.tibber</artifactId>
Expand Down
13 changes: 13 additions & 0 deletions bundles/org.openhab.binding.threedprinter/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.

* Project home: https://www.openhab.org

== Declared Project Licenses

This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.

== Source Code

https://github.com/openhab/openhab-addons
162 changes: 162 additions & 0 deletions bundles/org.openhab.binding.threedprinter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# 3D Printer Binding

This binding integrates FDM 3D printers into openHAB, allowing you to monitor print status, temperatures, and job progress, and to control prints (pause, resume, cancel). Klipper and OctoPrint also allow adjusting temperatures, print speed, and fan speed; PrusaLink exposes these as read-only, since Buddy firmware does not expose an API to change them (see [Channels](#channels)).

Three printer firmware/server platforms are supported:

- **PrusaLink** — Prusa printers with Buddy Board running Buddy firmware (MK4(s), Core ONE, XL, Mini+, etc.)
Comment thread
computergeek1507 marked this conversation as resolved.
- **Klipper** — Klipper firmware printers accessed via the Moonraker REST API
- **OctoPrint** — Any printer managed by an OctoPrint server

## Tested Hardware

This binding has been tested against:

- **Prusa MK4** running PrusaLink server 2.1.2 / Buddy firmware 6.5.7+12836 (`prusa-link` thing type)
- **Prusa Core One** running PrusaLink server 2.1.2 / Buddy firmware 6.5.7+12836 (`prusa-link` thing type)
- **Snapmaker U1** running Klipper via Moonraker 1.4.1.6 (`klipper` thing type), including its 4 toolheads (see [Multi-toolhead printers](#multi-toolhead-printers))

Other Buddy-firmware Prusa printers (MK3.5, MINI+, XL) and other Klipper/Moonraker printers are expected to work the same way, since they expose the same PrusaLink v1 and Moonraker APIs respectively, but have not been verified by the author. The `octoprint` thing type has not been tested against a live OctoPrint server; it is expected to work against any standard OctoPrint installation (the print preview additionally requires the [PrusaSlicer Thumbnails](https://plugins.octoprint.org/plugins/prusaslicerthumbnails/) plugin).

## Supported Things

| Thing ID | Description |
|---------------|------------------------------------------------------|
| `prusa-link` | Prusa printer via the PrusaLink v1 REST API |
| `klipper` | Klipper printer via the Moonraker REST API |
| `octoprint` | Printer managed by an OctoPrint server |

## Discovery

Auto-discovery is not supported. Things must be added manually.

## Thing Configuration

### PrusaLink (`prusa-link`)

| Parameter | Description | Default | Required |
|-------------------|---------------------------------------------------------------------------------|---------|----------|
| `hostname` | Hostname or IP address of the printer. | – | Yes |
| `port` | HTTP port of the PrusaLink API. | `80` | No |
| `apiKey` | API key shown in the printer's settings menu under **Network → API Key**. | – | Yes |
| `refreshInterval` | How often to poll the printer, in seconds. | `30` | No |

### Klipper (`klipper`)

| Parameter | Description | Default | Required |
|-------------------|---------------------------------------------------------------------------------|---------|----------|
| `hostname` | Hostname or IP address of the Moonraker server. | – | Yes |
| `port` | HTTP port of the Moonraker API. | `7125` | No |
| `apiKey` | Moonraker API key. Optional when accessing from a trusted local network. | – | No |
| `refreshInterval` | How often to poll the printer, in seconds. | `30` | No |

### OctoPrint (`octoprint`)

| Parameter | Description | Default | Required |
|-------------------|---------------------------------------------------------------------------------|---------|----------|
| `hostname` | Hostname or IP address of the OctoPrint server. | – | Yes |
| `port` | HTTP port of the OctoPrint API. | `5000` | No |
| `apiKey` | OctoPrint API key from **Settings → API → Global API Key**. | – | Yes |
| `refreshInterval` | How often to poll the printer, in seconds. | `30` | No |

## Channels

All three thing types expose the same set of channel IDs, but `nozzle-temperature-setpoint`, `bed-temperature-setpoint`, `print-speed`, and `fan-speed` are read-only on `prusa-link` things: PrusaLink on Buddy firmware does not expose an API to change these, so commands sent to them are ignored. On `klipper` and `octoprint` things, these four channels accept commands.

| Channel ID | Item Type | R/W | Description |
|-------------------------------|----------------------|-----|------------------------------------------------------------------------------------------------------|
| `printer-state` | String | R | Current printer state: `IDLE`, `PRINTING`, `PAUSED`, `FINISHED`, `ERROR`, or `BUSY`. |
| `job-name` | String | R | Name of the file currently loaded or being printed. |
| `job-progress` | Number:Dimensionless | R | Print completion percentage (0–100). |
| `time-elapsed` | Number:Time | R | Seconds elapsed since the print started. |
| `time-remaining` | Number:Time | R | Estimated seconds remaining. |
| `nozzle-temperature` | Number:Temperature | R | Current nozzle (hotend) temperature. |
| `nozzle-temperature-setpoint` | Number:Temperature | RW* | Nozzle temperature target. Send a temperature to change it. Read-only on `prusa-link`. |
| `bed-temperature` | Number:Temperature | R | Current heated bed temperature. |
| `bed-temperature-setpoint` | Number:Temperature | RW* | Bed temperature target. Send a temperature to change it. Read-only on `prusa-link`. |
| `print-speed` | Number:Dimensionless | RW* | Print speed as a percentage of the configured profile speed (1–200). Read-only on `prusa-link`. |
| `fan-speed` | Number:Dimensionless | RW* | Part-cooling fan speed percentage (0–100) on `klipper`/`octoprint`. On `prusa-link` this channel is `Number:Frequency` (RPM) and read-only, since Buddy firmware reports raw fan RPM and the maximum RPM varies by printer model. |
| `pause-resume` | Switch | RW | `ON` when the print is paused. Send `ON` to pause, `OFF` to resume. |
| `cancel` | Switch | W | Send `ON` to cancel the current print. Resets to `OFF` automatically. |
| `job-preview` | Image | R | Thumbnail image of the object being printed. Only populated when the sliced file contains embedded thumbnails. For OctoPrint, requires the [PrusaSlicer Thumbnails](https://plugins.octoprint.org/plugins/prusaslicerthumbnails/) plugin. |

### Multi-toolhead printers

Both `klipper` and `octoprint` things automatically detect additional toolheads and add extra channels for them; `prusa-link` does not, since PrusaLink's status API only ever reports one active nozzle even on tool-changer machines like the Prusa XL.

The added channels follow the same pattern on both platforms: `nozzle-temperature-N` / `nozzle-temperature-setpoint-N`, where `N` is the tool number. The primary toolhead is tool 1 and is exposed as the regular `nozzle-temperature`/`nozzle-temperature-setpoint` channels; the second toolhead is tool 2, the third is tool 3, and so on, with no fixed upper limit. These extra channels are not listed in the table above since they only appear on things with more than one toolhead; add them to your `.items` file the same way as the primary nozzle channels, substituting the tool number, e.g. `threedprinter:klipper:voron:nozzle-temperature-2`.

- **Klipper**: additional toolheads are configured as `extruder1`, `extruder2`, ... alongside the primary `extruder`. The binding queries Moonraker once at startup to discover how many the printer actually reports.
- **OctoPrint**: additional toolheads appear as `tool1`, `tool2`, ... in the same `temperature` object already polled every cycle (per the printer profile's configured extruder count), so no extra discovery request is needed.

## Full Example

### Things

`threedprinter.things`

```java
Thing threedprinter:prusa-link:mk4 "Prusa MK4" [
hostname="192.168.1.50",
apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
refreshInterval=15
]

Thing threedprinter:klipper:voron "Voron 2.4" [
hostname="voron.local",
port=7125,
refreshInterval=10
]

Thing threedprinter:octoprint:ender "Ender 3 (OctoPrint)" [
hostname="octopi.local",
apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
refreshInterval=20
]
```

### Items

`threedprinter.items`

```java
String Voron_State "Printer state [%s]" { channel="threedprinter:klipper:voron:printer-state" }
String Voron_JobName "Current job [%s]" { channel="threedprinter:klipper:voron:job-name" }
Number:Dimensionless Voron_Progress "Progress [%.1f %%]" { channel="threedprinter:klipper:voron:job-progress" }
Number:Time Voron_TimeElapsed "Time elapsed [%d %unit%]" { channel="threedprinter:klipper:voron:time-elapsed" }
Number:Time Voron_TimeRemaining "Time remaining [%d %unit%]" { channel="threedprinter:klipper:voron:time-remaining" }
Number:Temperature Voron_NozzleTemp "Nozzle temp [%.1f %unit%]" { channel="threedprinter:klipper:voron:nozzle-temperature" }
Number:Temperature Voron_NozzleTarget "Nozzle target [%.1f %unit%]" { channel="threedprinter:klipper:voron:nozzle-temperature-setpoint" }
Number:Temperature Voron_BedTemp "Bed temp [%.1f %unit%]" { channel="threedprinter:klipper:voron:bed-temperature" }
Number:Temperature Voron_BedTarget "Bed target [%.1f %unit%]" { channel="threedprinter:klipper:voron:bed-temperature-setpoint" }
Number:Dimensionless Voron_PrintSpeed "Print speed [%d %%]" { channel="threedprinter:klipper:voron:print-speed" }
Number:Dimensionless Voron_FanSpeed "Fan speed [%d %%]" { channel="threedprinter:klipper:voron:fan-speed" }
Switch Voron_PauseResume "Paused" { channel="threedprinter:klipper:voron:pause-resume" }
Switch Voron_Cancel "Cancel print" { channel="threedprinter:klipper:voron:cancel" }
Image Voron_Preview "Print preview" { channel="threedprinter:klipper:voron:job-preview" }
```

### Sitemap

`threedprinter.sitemap`

```perl
sitemap threedprinter label="3D Printers" {
Frame label="Voron 2.4" {
Text item=Voron_State
Text item=Voron_JobName
Text item=Voron_Progress
Text item=Voron_TimeElapsed
Text item=Voron_TimeRemaining
Text item=Voron_NozzleTemp
Setpoint item=Voron_NozzleTarget minValue=0 maxValue=300 step=5
Text item=Voron_BedTemp
Setpoint item=Voron_BedTarget minValue=0 maxValue=120 step=5
Slider item=Voron_PrintSpeed minValue=1 maxValue=200 step=10
Slider item=Voron_FanSpeed minValue=0 maxValue=100 step=5
Switch item=Voron_PauseResume label="Pause/Resume"
Switch item=Voron_Cancel label="Cancel Print"
Image item=Voron_Preview
}
}
```
17 changes: 17 additions & 0 deletions bundles/org.openhab.binding.threedprinter/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.3.0-SNAPSHOT</version>
</parent>

<artifactId>org.openhab.binding.threedprinter</artifactId>

<name>openHAB Add-ons :: Bundles :: 3D Printer Binding</name>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.threedprinter-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>

<feature name="openhab-binding-threedprinter" description="3D Printer Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.threedprinter/${project.version}</bundle>
</feature>
</features>
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.threedprinter.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;

/**
* The {@link ThreedprinterBindingConstants} class defines all constants used across the binding.
*
* @author Scott Hanson - Initial contribution
*/
@NonNullByDefault
public class ThreedprinterBindingConstants {

public static final String BINDING_ID = "threedprinter";

// Thing types
public static final ThingTypeUID THING_TYPE_PRUSALINK = new ThingTypeUID(BINDING_ID, "prusa-link");
public static final ThingTypeUID THING_TYPE_KLIPPER = new ThingTypeUID(BINDING_ID, "klipper");
public static final ThingTypeUID THING_TYPE_OCTOPRINT = new ThingTypeUID(BINDING_ID, "octoprint");

// Channels
public static final String CHANNEL_PRINTER_STATE = "printer-state";
public static final String CHANNEL_JOB_NAME = "job-name";
public static final String CHANNEL_JOB_PROGRESS = "job-progress";
public static final String CHANNEL_TIME_ELAPSED = "time-elapsed";
public static final String CHANNEL_TIME_REMAINING = "time-remaining";
public static final String CHANNEL_NOZZLE_TEMPERATURE = "nozzle-temperature";
public static final String CHANNEL_NOZZLE_TEMPERATURE_SETPOINT = "nozzle-temperature-setpoint";
public static final String CHANNEL_BED_TEMPERATURE = "bed-temperature";
public static final String CHANNEL_BED_TEMPERATURE_SETPOINT = "bed-temperature-setpoint";
public static final String CHANNEL_PRINT_SPEED = "print-speed";
public static final String CHANNEL_FAN_SPEED = "fan-speed";
public static final String CHANNEL_PAUSE_RESUME = "pause-resume";
public static final String CHANNEL_CANCEL = "cancel";
public static final String CHANNEL_JOB_PREVIEW = "job-preview";

// Printer state values
public static final String STATE_IDLE = "IDLE";
public static final String STATE_PRINTING = "PRINTING";
public static final String STATE_PAUSED = "PAUSED";
public static final String STATE_FINISHED = "FINISHED";
public static final String STATE_ERROR = "ERROR";
public static final String STATE_BUSY = "BUSY";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.threedprinter.internal;

import static org.openhab.binding.threedprinter.internal.ThreedprinterBindingConstants.*;

import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.threedprinter.internal.handler.KlipperHandler;
import org.openhab.binding.threedprinter.internal.handler.OctoPrintHandler;
import org.openhab.binding.threedprinter.internal.handler.PrusaLinkHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

/**
* Factory for creating 3D printer thing handlers.
*
* @author Scott Hanson - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.threedprinter", service = ThingHandlerFactory.class)
public class ThreedprinterHandlerFactory extends BaseThingHandlerFactory {

private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_PRUSALINK, THING_TYPE_KLIPPER,
THING_TYPE_OCTOPRINT);

private final HttpClient httpClient;

@Activate
public ThreedprinterHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
this.httpClient = httpClientFactory.getCommonHttpClient();
}

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES.contains(thingTypeUID);
}

@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID uid = thing.getThingTypeUID();
if (THING_TYPE_PRUSALINK.equals(uid)) {
return new PrusaLinkHandler(thing, httpClient);
}
if (THING_TYPE_KLIPPER.equals(uid)) {
return new KlipperHandler(thing, httpClient);
}
if (THING_TYPE_OCTOPRINT.equals(uid)) {
return new OctoPrintHandler(thing, httpClient);
}
return null;
}
}
Loading