[bambulab] Initial contribution (#18369)
* Init BambuLab Signed-off-by: Martin Grześlowski <martin.grzeslowski@gmail.com>pull/18390/head
parent
5ef6840d22
commit
9ac731580e
|
@ -38,6 +38,7 @@
|
|||
/bundles/org.openhab.binding.automower/ @maxpg
|
||||
/bundles/org.openhab.binding.avmfritz/ @cweitkamp
|
||||
/bundles/org.openhab.binding.awattar/ @Wolfgang1966
|
||||
/bundles/org.openhab.binding.bambulab/ @magx2
|
||||
/bundles/org.openhab.binding.benqprojector/ @mlobstein
|
||||
/bundles/org.openhab.binding.bigassfan/ @mhilbush
|
||||
/bundles/org.openhab.binding.bluetooth/ @cdjackson @cpmeister
|
||||
|
|
|
@ -181,6 +181,11 @@
|
|||
<artifactId>org.openhab.binding.awattar</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.bambulab</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.benqprojector</artifactId>
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,172 @@
|
|||
# BambuLab Binding
|
||||
|
||||
This addon supports connecting with BambuLab 3D printers in local mode.
|
||||
While cloud mode is theoretically possible, it is not supported by the addon developers.
|
||||
|
||||
## Cloud Mode
|
||||
|
||||
Cloud mode is possible but not officially supported by the addon developers.
|
||||
|
||||
To use cloud mode, follow these steps:
|
||||
|
||||
### Find Username
|
||||
|
||||
Log in to Maker World and visit [my-preferences](https://makerworld.com/api/v1/design-user-service/my/preference) to retrieve a JSON response containing your data.
|
||||
The relevant field is `uid`, which represents the unique ID of your account.
|
||||
Use this value as the `username` in the configuration (advanced field) with the prefix `u_`.
|
||||
|
||||
### Access Token
|
||||
|
||||
To obtain an access token, follow these steps:
|
||||
|
||||
1. Log in using your email and password.
|
||||
2. Confirm the login using a token received via email.
|
||||
|
||||
#### Step 1: Login with Email and Password
|
||||
|
||||
```shell
|
||||
curl -X POST "https://api.bambulab.com/v1/user-service/user/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"account": "you@email.io",
|
||||
"password": "superduperpassword123"
|
||||
}'
|
||||
```
|
||||
|
||||
#### Step 2: Confirm Login with Token from Email
|
||||
|
||||
```shell
|
||||
curl -X POST "https://api.bambulab.com/v1/user-service/user/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"account": "you@email.io",
|
||||
"code": "123456"
|
||||
}'
|
||||
```
|
||||
|
||||
You will receive a long access code in the response. Copy it and use it as the `accessCode` parameter.
|
||||
|
||||
**Note:** This access code expires after three months. When it expires, repeat the process to obtain a new one.
|
||||
|
||||
### Hostname
|
||||
|
||||
Use `us.mqtt.bambulab.com` as the hostname.
|
||||
|
||||
## Supported Things
|
||||
|
||||
- `printer`: Represents a BambuLab 3D printer.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|--------------|---------|----------|-------------------------------------------------------------------------------------------------|
|
||||
| `serial` | Text | Yes | Unique serial number of the printer. |
|
||||
| `scheme` | Text | No | URI scheme. (Advanced) |
|
||||
| `hostname` | Text | Yes | IP address of the printer or `us.mqtt.bambulab.com` for cloud mode. |
|
||||
| `port` | Integer | No | URI port. (Advanced) |
|
||||
| `username` | Text | No | `bblp` for local mode or your Bambu Lab user (starting with `u_`). (Advanced) |
|
||||
| `accessCode` | Text | Yes | Access code for the printer. The method of obtaining this varies between local and cloud modes. |
|
||||
|
||||
## Channels
|
||||
|
||||
| Channel ID | Type | Description |
|
||||
|---------------------------|---------------------|------------------------------------------------------------------|
|
||||
| `nozzle-temperature` | Temperature Channel | Current temperature of the nozzle. |
|
||||
| `nozzle-target-temperature` | Temperature Channel | Target temperature of the nozzle. |
|
||||
| `bed-temperature` | Temperature Channel | Current temperature of the heated bed. |
|
||||
| `bed-target-temperature` | Temperature Channel | Target temperature of the heated bed. |
|
||||
| `chamber-temperature` | Temperature Channel | Current temperature inside the printer chamber. |
|
||||
| `mc-print-stage` | String Channel | Current stage of the print process. |
|
||||
| `mc-percent` | Percent Channel | Percentage of the print completed. |
|
||||
| `mc-remaining-time` | Number Channel | Estimated time remaining for the print (in seconds). |
|
||||
| `wifi-signal` | WiFi Channel | Current WiFi signal strength. |
|
||||
| `bed-type` | String Channel | Type of the printer's heated bed. |
|
||||
| `gcode-file` | String Channel | Name of the currently loaded G-code file. |
|
||||
| `gcode-state` | String Channel | Current state of the G-code execution. |
|
||||
| `reason` | String Channel | Reason for pausing or stopping the print. |
|
||||
| `result` | String Channel | Final result or status of the print job. |
|
||||
| `gcode-file-prepare-percent` | Percent Channel | Percentage of G-code file preparation completed. |
|
||||
| `big-fan1-speed` | Number Channel | Speed of the first large cooling fan (RPM). |
|
||||
| `big-fan2-speed` | Number Channel | Speed of the second large cooling fan (RPM). |
|
||||
| `heat-break-fan-speed` | Number Channel | Speed of the heat break cooling fan (RPM). |
|
||||
| `layer-num` | Number Channel | Current layer being printed. |
|
||||
| `speed-level` | Number Channel | Current speed setting of the print job. |
|
||||
| `time-laps` | Boolean Channel | Indicates whether time-lapse recording is enabled. |
|
||||
| `use-ams` | Boolean Channel | Indicates whether the Automatic Material System (AMS) is active. |
|
||||
| `vibration-calibration` | Boolean Channel | Indicates whether vibration calibration has been performed. |
|
||||
| `led-chamber` | On/Off Command | Controls the LED lighting inside the printer chamber. |
|
||||
| `led-work` | On/Off Command | Controls the LED lighting for the work area. |
|
||||
|
||||
## Full Example
|
||||
|
||||
### `bambulab.things` Example
|
||||
|
||||
```java
|
||||
Thing bambulab:printer:myprinter "My BambuLab Printer" @ "3D Printing Area" [
|
||||
serial="ABC123456789",
|
||||
hostname="192.168.1.100",
|
||||
accessCode="your_access_code_here"
|
||||
]
|
||||
```
|
||||
|
||||
### `bambulab.items` Exmaple
|
||||
|
||||
```java
|
||||
Number:Temperature NozzleTemperature "Nozzle Temperature [%.1f °C]" { channel="bambulab:printer:myprinter:nozzle-temperature" }
|
||||
Number:Temperature BedTemperature "Bed Temperature [%.1f °C]" { channel="bambulab:printer:myprinter:bed-temperature" }
|
||||
String PrintStage "Print Stage [%s]" { channel="bambulab:printer:myprinter:mc-print-stage" }
|
||||
Switch LedChamber "Chamber LED" { channel="bambulab:printer:myprinter:led-chamber" }
|
||||
```
|
||||
|
||||
## Actions
|
||||
|
||||
The printer thing supports actions:
|
||||
|
||||
```java
|
||||
rule "test"
|
||||
when
|
||||
/* when */
|
||||
then
|
||||
val actions = getActions("bambulab", "bambulab:printer:as8af03m38")
|
||||
if(actions !==null){
|
||||
// Refresh all channels
|
||||
actions.refreshChannels()
|
||||
actions.sendCommand("Pushing:1:1")
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
### `refreshChannels`
|
||||
|
||||
Reports the complete status of the printer.
|
||||
This is unnecessary for the X1 series since it already transmits the full object each time.
|
||||
However, the P1 series only sends the values that have been updated compared to the previous report.
|
||||
As a rule of thumb, refrain from executing this command at intervals less than 5 minutes on the P1P, as it may cause lag due to its hardware limitations.
|
||||
|
||||
### `sendCommand`
|
||||
|
||||
The `sendCommand` method expects a string command in the format:
|
||||
|
||||
```
|
||||
CommandType:Parameter1:Parameter2:...
|
||||
```
|
||||
|
||||
#### Possible Commands:
|
||||
|
||||
| Command Type | Parameters | Description |
|
||||
|----------------------|------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
|
||||
| `Pushing` | `version(int)`,`pushTarget(int)` (optional) | Sends a push command. |
|
||||
| `Print` | `START` / `STOP` / `PAUSE` | Controls the print job. |
|
||||
| `ChangeFilament` | `target(int)`,`currentTemperature(int))`,`targetTemperature(int)` | Changes filament using. |
|
||||
| `AmsUserSetting` | `amsId(int)`,`startupReadOption(boolean)`,`trayReadOption(boolean)` | Sets AMS user settings. |
|
||||
| `AmsFilamentSetting` | `amsId(int)`,`trayId(int)`,`trayInfoIdx(string)`,`trayColor(string)`,`nozzleTempMin(int)`,`nozzleTempMax(int)`,`trayType(string)` | Configures filament settings. |
|
||||
| `AmsControl` | ` RESUME` / `RESET` / `PAUSE` | Sends an AMS control command. |
|
||||
| `PrintSpeed` | `SILENT` / `STANDARD` / `SPORT` / `LUDICROUS` | Adjusts print speed. |
|
||||
| `GCodeFile` | `filename(string)` | Loads a G-code file. |
|
||||
| `GCodeLine` | `userId(string)\nlines(string...)` | Sends multiple G-code lines. Lines are enter (`\n`) separated |
|
||||
| `LedControl` | (`CHAMBER_LIGHT` / `WORK_LIGHT`),(`ON` / `OFF` / `FLASHING`),`ledOnTime(int)?`,`ledOffTime(int)?`,`loopTimes(int)?`,`intervalTime(int)?` | Controls LED lighting. |
|
||||
| `System` | `GET_ACCESS_CODE` | Executes a system command. |
|
||||
| `IpCamRecord` | `enable(boolean)` | Starts or stops IP camera recording. |
|
||||
| `Info` | `GET_VERSION` | Sends a info command. |
|
||||
| `IpCamTimelaps` | `enable(boolean)` | Enables or disables timelapse recording. |
|
||||
| `XCamControl` | (`FIRST_LAYER_INSPECTOR` / `SPAGHETTI_DETECTOR`),`control(boolean)`,`printHalt(boolean)` | Controls XCam settings. |
|
|
@ -0,0 +1,54 @@
|
|||
<?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.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.bambulab</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: BambuLab Binding</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>pl.grzeslowski</groupId>
|
||||
<artifactId>JBambuAPI</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.paho</groupId>
|
||||
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
|
||||
<version>1.2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.18.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.16</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TEST -->
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.25.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.11.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.bambulab-${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-bambulab" description="BambuLab Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bambulab/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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.bambulab.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link BambuLabBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Martin Grześlowski - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BambuLabBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "bambulab";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID PRINTER_THING_TYPE = new ThingTypeUID(BINDING_ID, "printer");
|
||||
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public enum Channel {
|
||||
CHANNEL_NOZZLE_TEMPERATURE("nozzle-temperature"),
|
||||
CHANNEL_NOZZLE_TARGET_TEMPERATURE("nozzle-target-temperature"),
|
||||
CHANNEL_BED_TEMPERATURE("bed-temperature"),
|
||||
CHANNEL_BED_TARGET_TEMPERATURE("bed-target-temperature"),
|
||||
CHANNEL_CHAMBER_TEMPERATURE("chamber-temperature"),
|
||||
CHANNEL_MC_PRINT_STAGE("mc-print-stage"),
|
||||
CHANNEL_MC_PERCENT("mc-percent"),
|
||||
CHANNEL_MC_REMAINING_TIME("mc-remaining-time"),
|
||||
CHANNEL_WIFI_SIGNAL("wifi-signal"),
|
||||
CHANNEL_BED_TYPE("bed-type"),
|
||||
CHANNEL_GCODE_FILE("gcode-file"),
|
||||
CHANNEL_GCODE_STATE("gcode-state"),
|
||||
CHANNEL_REASON("reason"),
|
||||
CHANNEL_RESULT("result"),
|
||||
CHANNEL_GCODE_FILE_PREPARE_PERCENT("gcode-file-prepare-percent"),
|
||||
CHANNEL_BIG_FAN_1_SPEED("big-fan1-speed"),
|
||||
CHANNEL_BIG_FAN_2_SPEED("big-fan2-speed"),
|
||||
CHANNEL_HEAT_BREAK_FAN_SPEED("heat-break-fan-speed"),
|
||||
CHANNEL_LAYER_NUM("layer-num"),
|
||||
CHANNEL_SPEED_LEVEL("speed-level"),
|
||||
CHANNEL_TIME_LAPS("time-laps"),
|
||||
CHANNEL_USE_AMS("use-ams"),
|
||||
CHANNEL_VIBRATION_CALIBRATION("vibration-calibration"),
|
||||
CHANNEL_LED_CHAMBER_LIGHT("led-chamber", true),
|
||||
CHANNEL_LED_WORK_LIGHT("led-work", true);
|
||||
|
||||
private final String name;
|
||||
private final boolean supportCommand;
|
||||
|
||||
Channel(String name, boolean supportCommand) {
|
||||
this.name = name;
|
||||
this.supportCommand = supportCommand;
|
||||
}
|
||||
|
||||
private Channel(String name) {
|
||||
this(name, false);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isSupportCommand() {
|
||||
return supportCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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.bambulab.internal;
|
||||
|
||||
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.PRINTER_THING_TYPE;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.Component;
|
||||
|
||||
/**
|
||||
* The {@link BambuLabHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Martin Grześlowski - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.bambulab", service = ThingHandlerFactory.class)
|
||||
public class BambuLabHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(PRINTER_THING_TYPE);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (PRINTER_THING_TYPE.equals(thingTypeUID)) {
|
||||
return new PrinterHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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.bambulab.internal;
|
||||
|
||||
import static java.lang.Boolean.parseBoolean;
|
||||
import static java.lang.Integer.parseInt;
|
||||
import static java.util.Arrays.copyOfRange;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.BINDING_ID;
|
||||
import static pl.grzeslowski.jbambuapi.PrinterClient.Channel.LedControlCommand.LedMode.FLASHING;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.AmsControlCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.AmsFilamentSettingCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.AmsUserSettingCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.ChangeFilamentCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.Command;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.GCodeFileCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.GCodeLineCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.InfoCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.IpCamRecordCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.IpCamTimelapsCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.LedControlCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.LedControlCommand.LedMode;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.LedControlCommand.LedNode;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.PrintCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.PrintSpeedCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.PushingCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.SystemCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.XCamControlCommand;
|
||||
|
||||
/**
|
||||
* @author Martin Grzeslowski - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = BINDING_ID)
|
||||
@NonNullByDefault
|
||||
public class PrinterActions implements ThingActions {
|
||||
private @Nullable PrinterHandler handler;
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler thingHandler) {
|
||||
this.handler = (PrinterHandler) thingHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@RuleAction(label = "@text/action.sendCommand.label", description = "@text/action.sendCommand.description")
|
||||
public void sendCommand(
|
||||
@ActionInput(name = "time", label = "@text/action.sendCommand.commandLabel", description = "@text/action.sendCommand.commandDescription") String stringCommand) {
|
||||
var localHandler = handler;
|
||||
if (localHandler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var command = parseCommand(stringCommand);
|
||||
localHandler.sendCommand(command);
|
||||
}
|
||||
|
||||
private Command parseCommand(String stringCommand) {
|
||||
var split = stringCommand.split(":");
|
||||
if (split.length <= 1) {
|
||||
throw new IllegalArgumentException("Command too short, class name not passed. Command: " + stringCommand);
|
||||
}
|
||||
var commandName = split[0] + "Command";
|
||||
var tail = tail(split);
|
||||
if (commandName.equals(InfoCommand.class.getSimpleName())) {
|
||||
return parseInfoCommand(tail);
|
||||
}
|
||||
if (commandName.equals(PushingCommand.class.getSimpleName())) {
|
||||
return parsePushingCommand(tail);
|
||||
}
|
||||
if (commandName.equals(PrintCommand.class.getSimpleName())) {
|
||||
return parsePrintCommand(tail);
|
||||
}
|
||||
if (commandName.equals(ChangeFilamentCommand.class.getSimpleName())) {
|
||||
return parseChangeFilamentCommand(tail);
|
||||
}
|
||||
if (commandName.equals(AmsUserSettingCommand.class.getSimpleName())) {
|
||||
return parseAmsUserSettingCommand(tail);
|
||||
}
|
||||
if (commandName.equals(AmsFilamentSettingCommand.class.getSimpleName())) {
|
||||
return parseAmsFilamentSettingCommand(tail);
|
||||
}
|
||||
if (commandName.equals(AmsControlCommand.class.getSimpleName())) {
|
||||
return parseAmsControlCommand(tail);
|
||||
}
|
||||
if (commandName.equals(PrintSpeedCommand.class.getSimpleName())) {
|
||||
return parsePrintSpeedCommand(tail);
|
||||
}
|
||||
if (commandName.equals(GCodeFileCommand.class.getSimpleName())) {
|
||||
return parseGCodeFileCommand(tail);
|
||||
}
|
||||
if (commandName.equals(GCodeLineCommand.class.getSimpleName())) {
|
||||
var gcodeLineSplit = stringCommand.split(":", 2);
|
||||
requireLength(gcodeLineSplit, 2);
|
||||
return parseGCodeLineCommand(gcodeLineSplit[1]);
|
||||
}
|
||||
if (commandName.equals(LedControlCommand.class.getSimpleName())) {
|
||||
return parseLedControlCommand(tail);
|
||||
}
|
||||
if (commandName.equals(SystemCommand.class.getSimpleName())) {
|
||||
return parseSystemCommand(tail);
|
||||
}
|
||||
if (commandName.equals(IpCamRecordCommand.class.getSimpleName())) {
|
||||
return parseIpCamRecordCommand(tail);
|
||||
}
|
||||
if (commandName.equals(IpCamTimelapsCommand.class.getSimpleName())) {
|
||||
return parseIpCamTimelapsCommand(tail);
|
||||
}
|
||||
if (commandName.equals(XCamControlCommand.class.getSimpleName())) {
|
||||
return parseXCamControlCommand(tail);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown command name: " + commandName);
|
||||
}
|
||||
|
||||
private String[] tail(String[] command) {
|
||||
return copyOfRange(command, 1, command.length);
|
||||
}
|
||||
|
||||
private void requireLength(String[] commandLine, int length) {
|
||||
if (commandLine.length != length) {
|
||||
throw new IllegalArgumentException("Command line length does not match! Should be %s, but was %s!"
|
||||
.formatted(length, commandLine.length));
|
||||
}
|
||||
}
|
||||
|
||||
private InfoCommand parseInfoCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 1);
|
||||
return InfoCommand.valueOf(commandLine[0]);
|
||||
}
|
||||
|
||||
private PushingCommand parsePushingCommand(String[] commandLine) {
|
||||
if (commandLine.length == 0) {
|
||||
return PushingCommand.defaultPushingCommand();
|
||||
}
|
||||
requireLength(commandLine, 2);
|
||||
return new PushingCommand(parseInt(commandLine[0]), parseInt(commandLine[1]));
|
||||
}
|
||||
|
||||
private PrintCommand parsePrintCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 1);
|
||||
return PrintCommand.valueOf(commandLine[0]);
|
||||
}
|
||||
|
||||
private ChangeFilamentCommand parseChangeFilamentCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 3);
|
||||
return new ChangeFilamentCommand(parseInt(commandLine[0]), parseInt(commandLine[1]), parseInt(commandLine[2]));
|
||||
}
|
||||
|
||||
private AmsUserSettingCommand parseAmsUserSettingCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 3);
|
||||
return new AmsUserSettingCommand(parseInt(commandLine[0]), parseBoolean(commandLine[1]),
|
||||
parseBoolean(commandLine[2]));
|
||||
}
|
||||
|
||||
private AmsFilamentSettingCommand parseAmsFilamentSettingCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 7);
|
||||
return new AmsFilamentSettingCommand(parseInt(commandLine[0]), parseInt(commandLine[1]), commandLine[2],
|
||||
commandLine[3], parseInt(commandLine[4]), parseInt(commandLine[5]), commandLine[6]);
|
||||
}
|
||||
|
||||
private AmsControlCommand parseAmsControlCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 1);
|
||||
return AmsControlCommand.valueOf(commandLine[0]);
|
||||
}
|
||||
|
||||
private PrintSpeedCommand parsePrintSpeedCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 1);
|
||||
return PrintSpeedCommand.valueOf(commandLine[0]);
|
||||
}
|
||||
|
||||
private GCodeFileCommand parseGCodeFileCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 1);
|
||||
return new GCodeFileCommand(commandLine[0]);
|
||||
}
|
||||
|
||||
private GCodeLineCommand parseGCodeLineCommand(String commandLine) {
|
||||
var split = commandLine.split("\n");
|
||||
if (split.length < 2) {
|
||||
throw new IllegalArgumentException("There are no lines for GCodeLineCommand!");
|
||||
}
|
||||
var lines = Arrays.stream(split).skip(1).toList();
|
||||
return new GCodeLineCommand(lines, split[0]);
|
||||
}
|
||||
|
||||
private LedControlCommand parseLedControlCommand(String[] commandLine) {
|
||||
if (commandLine.length < 2) {
|
||||
throw new IllegalArgumentException(
|
||||
"Command line length does not match! Should be %s, but was %s!".formatted(2, commandLine.length));
|
||||
}
|
||||
var ledNode = LedNode.valueOf(commandLine[0]);
|
||||
var ledMode = LedMode.valueOf(commandLine[1]);
|
||||
@Nullable
|
||||
Integer ledOnTime = null, ledOffTime = null, loopTimes = null, intervalTime = null;
|
||||
if (ledMode == FLASHING) {
|
||||
requireLength(commandLine, 6);
|
||||
ledOnTime = parseInt(commandLine[2]);
|
||||
ledOffTime = parseInt(commandLine[3]);
|
||||
loopTimes = parseInt(commandLine[4]);
|
||||
intervalTime = parseInt(commandLine[5]);
|
||||
}
|
||||
return new LedControlCommand(ledNode, ledMode, ledOnTime, ledOffTime, loopTimes, intervalTime);
|
||||
}
|
||||
|
||||
private SystemCommand parseSystemCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 1);
|
||||
return SystemCommand.valueOf(commandLine[0]);
|
||||
}
|
||||
|
||||
private IpCamRecordCommand parseIpCamRecordCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 1);
|
||||
return new IpCamRecordCommand(parseBoolean(commandLine[0]));
|
||||
}
|
||||
|
||||
private IpCamTimelapsCommand parseIpCamTimelapsCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 1);
|
||||
return new IpCamTimelapsCommand(parseBoolean(commandLine[0]));
|
||||
}
|
||||
|
||||
private XCamControlCommand parseXCamControlCommand(String[] commandLine) {
|
||||
requireLength(commandLine, 3);
|
||||
return new XCamControlCommand(XCamControlCommand.Module.valueOf(commandLine[0]), parseBoolean(commandLine[1]),
|
||||
parseBoolean(commandLine[2]));
|
||||
}
|
||||
|
||||
public static void sendCommand(@Nullable ThingActions actions, String stringCommand) {
|
||||
((PrinterActions) requireNonNull(actions)).sendCommand(stringCommand);
|
||||
}
|
||||
|
||||
@RuleAction(label = "@text/action.refreshChannels.label", description = "@text/action.refreshChannels.description")
|
||||
public void refreshChannels() {
|
||||
var localHandler = handler;
|
||||
if (localHandler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
localHandler.refreshChannels();
|
||||
}
|
||||
|
||||
public static void refreshChannels(@Nullable ThingActions actions) {
|
||||
((PrinterActions) requireNonNull(actions)).refreshChannels();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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.bambulab.internal;
|
||||
|
||||
import static pl.grzeslowski.jbambuapi.PrinterClientConfig.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link PrinterConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Martin Grześlowski - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PrinterConfiguration {
|
||||
public String serial = "";
|
||||
public String scheme = SCHEME;
|
||||
public String hostname = "";
|
||||
public int port = DEFAULT_PORT;
|
||||
public String username = LOCAL_USERNAME;
|
||||
public String accessCode = "";
|
||||
}
|
|
@ -0,0 +1,310 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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.bambulab.internal;
|
||||
|
||||
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.Channel.*;
|
||||
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
||||
import static org.openhab.core.library.unit.Units.DECIBEL_MILLIWATTS;
|
||||
import static org.openhab.core.thing.ThingStatus.*;
|
||||
import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR;
|
||||
import static org.openhab.core.types.UnDefType.UNDEF;
|
||||
import static pl.grzeslowski.jbambuapi.PrinterClient.Channel.LedControlCommand.*;
|
||||
import static pl.grzeslowski.jbambuapi.PrinterClient.Channel.LedControlCommand.LedNode.*;
|
||||
import static pl.grzeslowski.jbambuapi.PrinterClient.Channel.PushingCommand.defaultPushingCommand;
|
||||
import static pl.grzeslowski.jbambuapi.PrinterClientConfig.requiredFields;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClientConfig;
|
||||
import pl.grzeslowski.jbambuapi.PrinterWatcher;
|
||||
import pl.grzeslowski.jbambuapi.Report;
|
||||
|
||||
/**
|
||||
* The {@link PrinterHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Martin Grześlowski - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PrinterHandler extends BaseThingHandler implements PrinterWatcher.StateSubscriber {
|
||||
private static final Pattern DBM_PATTERN = Pattern.compile("^(-?\\d+)dBm$");
|
||||
private Logger logger = LoggerFactory.getLogger(PrinterHandler.class);
|
||||
|
||||
private @Nullable PrinterClient client;
|
||||
|
||||
public PrinterHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (!CHANNEL_LED_CHAMBER_LIGHT.getName().equals(channelUID.getId())
|
||||
&& !CHANNEL_LED_WORK_LIGHT.getName().equals(channelUID.getId())) {
|
||||
return;
|
||||
}
|
||||
var ledNode = CHANNEL_LED_CHAMBER_LIGHT.getName().equals(channelUID.getId()) ? CHAMBER_LIGHT : WORK_LIGHT;
|
||||
var bambuCommand = "ON".equals(command.toFullString()) ? on(ledNode) : off(ledNode);
|
||||
sendCommand(bambuCommand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
var config = getConfigAs(PrinterConfiguration.class);
|
||||
|
||||
if (config.serial.isBlank()) {
|
||||
updateStatus(OFFLINE, CONFIGURATION_ERROR, "@text/printer.handler.init.noSerial");
|
||||
return;
|
||||
}
|
||||
logger = LoggerFactory.getLogger(PrinterHandler.class.getName() + "." + config.serial);
|
||||
|
||||
if (config.hostname.isBlank()) {
|
||||
updateStatus(OFFLINE, CONFIGURATION_ERROR, "@text/printer.handler.init.noHostname");
|
||||
return;
|
||||
}
|
||||
|
||||
var scheme = config.scheme;
|
||||
var port = config.port;
|
||||
var rawUri = "%s%s:%d".formatted(scheme, config.hostname, port);
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(rawUri);
|
||||
} catch (URISyntaxException e) {
|
||||
updateStatus(OFFLINE, CONFIGURATION_ERROR,
|
||||
"@text/printer.handler.init.invalidHostname[\"%s\"]".formatted(rawUri));
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.accessCode.isBlank()) {
|
||||
updateStatus(OFFLINE, CONFIGURATION_ERROR, "@text/printer.handler.init.noAccessCode");
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.username.isBlank()) {
|
||||
config.username = PrinterClientConfig.LOCAL_USERNAME;
|
||||
}
|
||||
|
||||
updateStatus(UNKNOWN);
|
||||
|
||||
PrinterClient localClient;
|
||||
try {
|
||||
localClient = client = new PrinterClient(
|
||||
requiredFields(uri, config.username, config.serial, config.accessCode.toCharArray()));
|
||||
} catch (Exception e) {
|
||||
logger.debug("Cannot create MQTT client", e);
|
||||
updateStatus(OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
scheduler.execute(() -> {
|
||||
try {
|
||||
logger.debug("Trying to connect to the printer broker");
|
||||
localClient.connect();
|
||||
var printerWatcher = new PrinterWatcher();
|
||||
localClient.subscribe(printerWatcher);
|
||||
printerWatcher.subscribe(this);
|
||||
// send request to update all channels
|
||||
refreshChannels();
|
||||
updateStatus(ONLINE);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Cannot connect to MQTT client", e);
|
||||
updateStatus(OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());
|
||||
}
|
||||
});
|
||||
} catch (RejectedExecutionException ex) {
|
||||
logger.debug("Task was rejected", ex);
|
||||
updateStatus(OFFLINE, CONFIGURATION_ERROR, ex.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
void refreshChannels() {
|
||||
sendCommand(defaultPushingCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
var localClient = client;
|
||||
client = null;
|
||||
if (localClient != null) {
|
||||
try {
|
||||
localClient.close();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Could not correctly dispose PrinterClient", e);
|
||||
}
|
||||
}
|
||||
logger = LoggerFactory.getLogger(PrinterHandler.class);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newState(@Nullable Report delta, @Nullable Report fullState) {
|
||||
logger.trace("New Printer state from delta {}", delta);
|
||||
// only need to update channels from delta
|
||||
// do not need to use full state, because at some point in past channels was already updated with its values
|
||||
if (delta == null) {
|
||||
return;
|
||||
}
|
||||
updatePrinterChannels(delta);
|
||||
// if got new Printer state (and not failed) then make sure that thing status in ONLINE
|
||||
updateStatus(ONLINE);
|
||||
}
|
||||
|
||||
private void updatePrinterChannels(Report state) {
|
||||
// Print
|
||||
var print = state.print();
|
||||
if (print == null) {
|
||||
return;
|
||||
}
|
||||
// tempers
|
||||
updateCelsiusState(CHANNEL_NOZZLE_TEMPERATURE.getName(), print.nozzleTemper());
|
||||
updateCelsiusState(CHANNEL_NOZZLE_TARGET_TEMPERATURE.getName(), print.nozzleTargetTemper());
|
||||
updateCelsiusState(CHANNEL_BED_TEMPERATURE.getName(), print.bedTemper());
|
||||
updateCelsiusState(CHANNEL_BED_TARGET_TEMPERATURE.getName(), print.bedTargetTemper());
|
||||
updateCelsiusState(CHANNEL_CHAMBER_TEMPERATURE.getName(), print.chamberTemper());
|
||||
// string
|
||||
updateStringState(CHANNEL_MC_PRINT_STAGE.getName(), print.mcPrintStage());
|
||||
updateStringState(CHANNEL_BED_TYPE.getName(), print.bedType());
|
||||
updateStringState(CHANNEL_GCODE_FILE.getName(), print.gcodeFile());
|
||||
updateStringState(CHANNEL_GCODE_STATE.getName(), print.gcodeState());
|
||||
updateStringState(CHANNEL_REASON.getName(), print.reason());
|
||||
updateStringState(CHANNEL_RESULT.getName(), print.result());
|
||||
// percent
|
||||
updatePercentState(CHANNEL_MC_PERCENT.getName(), print.mcPercent());
|
||||
updatePercentState(CHANNEL_GCODE_FILE_PREPARE_PERCENT.getName(), print.gcodeFilePreparePercent());
|
||||
// decimal
|
||||
updateDecimalState(CHANNEL_MC_REMAINING_TIME.getName(), print.mcRemainingTime());
|
||||
updateDecimalState(CHANNEL_BIG_FAN_1_SPEED.getName(), print.bigFan1Speed());
|
||||
updateDecimalState(CHANNEL_BIG_FAN_2_SPEED.getName(), print.bigFan2Speed());
|
||||
updateDecimalState(CHANNEL_HEAT_BREAK_FAN_SPEED.getName(), print.heatbreakFanSpeed());
|
||||
updateDecimalState(CHANNEL_LAYER_NUM.getName(), print.layerNum());
|
||||
updateDecimalState(CHANNEL_SPEED_LEVEL.getName(), print.spdLvl());
|
||||
// boolean
|
||||
updateBooleanState(CHANNEL_TIME_LAPS.getName(), print.timelapse());
|
||||
updateBooleanState(CHANNEL_USE_AMS.getName(), print.useAms());
|
||||
updateBooleanState(CHANNEL_VIBRATION_CALIBRATION.getName(), print.vibrationCali());
|
||||
// other
|
||||
if (print.wifiSignal() != null) {
|
||||
updateState(CHANNEL_WIFI_SIGNAL.getName(), parseWifiChannel(print.wifiSignal()));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCelsiusState(String channelId, @Nullable Double temperature) {
|
||||
if (temperature == null) {
|
||||
return;
|
||||
}
|
||||
updateState(channelId, new QuantityType<>(temperature, CELSIUS));
|
||||
}
|
||||
|
||||
private void updateStringState(String channelId, @Nullable String string) {
|
||||
if (string == null) {
|
||||
return;
|
||||
}
|
||||
updateState(channelId, new StringType(string));
|
||||
}
|
||||
|
||||
private void updateDecimalState(String channelId, @Nullable Number number) {
|
||||
if (number == null) {
|
||||
return;
|
||||
}
|
||||
updateState(channelId, new DecimalType(number));
|
||||
}
|
||||
|
||||
private void updateDecimalState(String channelId, @Nullable String number) {
|
||||
if (number == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var state = new DecimalType(Double.parseDouble(number));
|
||||
updateState(channelId, state);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Cannot parse decimal number {}", number, e);
|
||||
updateState(channelId, UNDEF);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBooleanState(String channelId, @Nullable Boolean bool) {
|
||||
if (bool == null) {
|
||||
return;
|
||||
}
|
||||
updateState(channelId, OnOffType.from(bool));
|
||||
}
|
||||
|
||||
private void updatePercentState(String channelId, @Nullable Integer integer) {
|
||||
if (integer == null) {
|
||||
return;
|
||||
}
|
||||
updateState(channelId, new PercentType(integer));
|
||||
}
|
||||
|
||||
private void updatePercentState(String channelId, @Nullable String integer) {
|
||||
if (integer == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var state = new PercentType(integer);
|
||||
updateState(channelId, state);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Cannot parse percent number {}", integer, e);
|
||||
updateState(channelId, UNDEF);
|
||||
}
|
||||
}
|
||||
|
||||
private State parseWifiChannel(String wifi) {
|
||||
var matcher = DBM_PATTERN.matcher(wifi);
|
||||
if (!matcher.matches()) {
|
||||
return UNDEF;
|
||||
}
|
||||
|
||||
var integer = matcher.group(1);
|
||||
try {
|
||||
var value = Integer.parseInt(integer);
|
||||
return new QuantityType<>(value, DECIBEL_MILLIWATTS);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Cannot parse integer {} from wifi {}", integer, wifi, e);
|
||||
return UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
public void sendCommand(PrinterClient.Channel.Command command) {
|
||||
logger.debug("Sending command {}", command);
|
||||
var localClient = client;
|
||||
if (localClient == null) {
|
||||
logger.warn("Client not connected. Cannot send command {}", command);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
localClient.getChannel().sendCommand(command);
|
||||
} catch (Exception e) {
|
||||
updateStatus(OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<addon:addon id="bambulab" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
|
||||
|
||||
<type>binding</type>
|
||||
<name>BambuLab Binding</name>
|
||||
<description>This is the binding for BambuLab.</description>
|
||||
<connection>local</connection>
|
||||
</addon:addon>
|
|
@ -0,0 +1,210 @@
|
|||
# add-on
|
||||
|
||||
addon.bambulab.name = BambuLab Binding
|
||||
addon.bambulab.description = This is the binding for BambuLab.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.bambulab.printer.label = Bambu Lab Printer
|
||||
thing-type.bambulab.printer.description = Represents a Bambu Lab 3D printer connected via MQTT
|
||||
thing-type.bambulab.printer.channel.bed-target-temperature.label = Bed Target Temperature
|
||||
thing-type.bambulab.printer.channel.bed-target-temperature.description = Target temperature of the heated bed.
|
||||
thing-type.bambulab.printer.channel.bed-temperature.label = Bed Temperature
|
||||
thing-type.bambulab.printer.channel.bed-temperature.description = Current temperature of the heated bed.
|
||||
thing-type.bambulab.printer.channel.bed-type.label = Bed Type
|
||||
thing-type.bambulab.printer.channel.bed-type.description = Type of the printer's heated bed.
|
||||
thing-type.bambulab.printer.channel.big-fan1-speed.label = Big Fan 1 Speed
|
||||
thing-type.bambulab.printer.channel.big-fan1-speed.description = Speed of the first large cooling fan (RPM).
|
||||
thing-type.bambulab.printer.channel.big-fan2-speed.label = Big Fan 2 Speed
|
||||
thing-type.bambulab.printer.channel.big-fan2-speed.description = Speed of the second large cooling fan (RPM).
|
||||
thing-type.bambulab.printer.channel.chamber-temperature.label = Chamber Temperature
|
||||
thing-type.bambulab.printer.channel.chamber-temperature.description = Current temperature inside the printer chamber.
|
||||
thing-type.bambulab.printer.channel.gcode-file.label = G-code File
|
||||
thing-type.bambulab.printer.channel.gcode-file.description = Name of the currently loaded G-code file.
|
||||
thing-type.bambulab.printer.channel.gcode-file-prepare-percent.label = G-code Preparation Progress
|
||||
thing-type.bambulab.printer.channel.gcode-file-prepare-percent.description = Percentage of G-code file preparation completed.
|
||||
thing-type.bambulab.printer.channel.gcode-state.label = G-code State
|
||||
thing-type.bambulab.printer.channel.gcode-state.description = Current state of the G-code execution.
|
||||
thing-type.bambulab.printer.channel.heat-break-fan-speed.label = Heat Break Fan Speed
|
||||
thing-type.bambulab.printer.channel.heat-break-fan-speed.description = Speed of the heat break cooling fan (RPM).
|
||||
thing-type.bambulab.printer.channel.layer-num.label = Current Layer Number
|
||||
thing-type.bambulab.printer.channel.layer-num.description = Current layer being printed.
|
||||
thing-type.bambulab.printer.channel.led-chamber.label = Chamber LED
|
||||
thing-type.bambulab.printer.channel.led-chamber.description = Controls the LED lighting inside the printer chamber.
|
||||
thing-type.bambulab.printer.channel.led-work.label = Work Area LED
|
||||
thing-type.bambulab.printer.channel.led-work.description = Controls the LED lighting for the work area.
|
||||
thing-type.bambulab.printer.channel.mc-percent.label = Print Progress
|
||||
thing-type.bambulab.printer.channel.mc-percent.description = Percentage of the print completed.
|
||||
thing-type.bambulab.printer.channel.mc-print-stage.label = Print Stage
|
||||
thing-type.bambulab.printer.channel.mc-print-stage.description = Current stage of the print process.
|
||||
thing-type.bambulab.printer.channel.mc-remaining-time.label = Remaining Print Time
|
||||
thing-type.bambulab.printer.channel.mc-remaining-time.description = Estimated time remaining for the print in seconds.
|
||||
thing-type.bambulab.printer.channel.nozzle-target-temperature.label = Nozzle Target Temperature
|
||||
thing-type.bambulab.printer.channel.nozzle-target-temperature.description = Target temperature of the nozzle.
|
||||
thing-type.bambulab.printer.channel.nozzle-temperature.label = Nozzle Temperature
|
||||
thing-type.bambulab.printer.channel.nozzle-temperature.description = Current temperature of the nozzle.
|
||||
thing-type.bambulab.printer.channel.reason.label = Pause/Stop Reason
|
||||
thing-type.bambulab.printer.channel.reason.description = Reason for pausing or stopping the print.
|
||||
thing-type.bambulab.printer.channel.result.label = Print Result
|
||||
thing-type.bambulab.printer.channel.result.description = Final result or status of the print job.
|
||||
thing-type.bambulab.printer.channel.speed-level.label = Print Speed Level
|
||||
thing-type.bambulab.printer.channel.speed-level.description = Current speed setting of the print job.
|
||||
thing-type.bambulab.printer.channel.time-laps.label = time-lapse Enabled
|
||||
thing-type.bambulab.printer.channel.time-laps.description = Indicates whether time-lapse recording is enabled.
|
||||
thing-type.bambulab.printer.channel.use-ams.label = AMS System in Use
|
||||
thing-type.bambulab.printer.channel.use-ams.description = Indicates whether the Automatic Material System (AMS) is active.
|
||||
thing-type.bambulab.printer.channel.vibration-calibration.label = Vibration Calibration
|
||||
thing-type.bambulab.printer.channel.vibration-calibration.description = Indicates whether vibration calibration has been performed.
|
||||
thing-type.bambulab.printer.channel.wifi-signal.label = WiFi Signal Strength
|
||||
thing-type.bambulab.printer.channel.wifi-signal.description = Current WiFi signal strength.
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.bambulab.printer.accessCode.label = Access Code
|
||||
thing-type.config.bambulab.printer.accessCode.description = Access code of the printer. The method of obtaining it differs for local and cloud modes.
|
||||
thing-type.config.bambulab.printer.hostname.label = Hostname
|
||||
thing-type.config.bambulab.printer.hostname.description = IP address of the printer or `us.mqtt.bambulab.com` for cloud mode.
|
||||
thing-type.config.bambulab.printer.port.label = Port
|
||||
thing-type.config.bambulab.printer.port.description = URI port.
|
||||
thing-type.config.bambulab.printer.scheme.label = Scheme
|
||||
thing-type.config.bambulab.printer.scheme.description = URI scheme.
|
||||
thing-type.config.bambulab.printer.serial.label = Serial Number
|
||||
thing-type.config.bambulab.printer.serial.description = Unique serial number of the printer.
|
||||
thing-type.config.bambulab.printer.username.label = Username
|
||||
thing-type.config.bambulab.printer.username.description = `bblp` for local mode or your Bambu Lab user (starts with `u_`).
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.bambulab.boolean.label = Boolean Channel
|
||||
channel-type.bambulab.number.label = Number Channel
|
||||
channel-type.bambulab.on-off-command.label = ON/OFF Channel
|
||||
channel-type.bambulab.on-off-command.state.option.ON = ON
|
||||
channel-type.bambulab.on-off-command.state.option.OFF = OFF
|
||||
channel-type.bambulab.on-off-command.command.option.ON = ON
|
||||
channel-type.bambulab.on-off-command.command.option.OFF = OFF
|
||||
channel-type.bambulab.percent.label = Percent Channel
|
||||
channel-type.bambulab.string.label = String Channel
|
||||
channel-type.bambulab.temperature-measurement.label = Current Temperature
|
||||
channel-type.bambulab.temperature-setpoint.label = Target Temperature
|
||||
channel-type.bambulab.wifi.label = Wi-Fi Signal Strength
|
||||
channel-type.bambulab.wifi.description = Current Wi-Fi signal strength.
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.bambulab.temperature.label = Temperature
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.bambulab.printer.channel.bedTargetTemperature.label = Bed Target Temperature
|
||||
thing-type.bambulab.printer.channel.bedTargetTemperature.description = Target temperature of the heated bed.
|
||||
thing-type.bambulab.printer.channel.bedTemperature.label = Bed Temperature
|
||||
thing-type.bambulab.printer.channel.bedTemperature.description = Current temperature of the heated bed.
|
||||
thing-type.bambulab.printer.channel.bedType.label = Bed Type
|
||||
thing-type.bambulab.printer.channel.bedType.description = Type of the printer's heated bed.
|
||||
thing-type.bambulab.printer.channel.bigFan1Speed.label = Big Fan 1 Speed
|
||||
thing-type.bambulab.printer.channel.bigFan1Speed.description = Speed of the first large cooling fan (RPM).
|
||||
thing-type.bambulab.printer.channel.bigFan2Speed.label = Big Fan 2 Speed
|
||||
thing-type.bambulab.printer.channel.bigFan2Speed.description = Speed of the second large cooling fan (RPM).
|
||||
thing-type.bambulab.printer.channel.chamberTemperature.label = Chamber Temperature
|
||||
thing-type.bambulab.printer.channel.chamberTemperature.description = Current temperature inside the printer chamber.
|
||||
thing-type.bambulab.printer.channel.gcodeFile.label = G-code File
|
||||
thing-type.bambulab.printer.channel.gcodeFile.description = Name of the currently loaded G-code file.
|
||||
thing-type.bambulab.printer.channel.gcodeFilePreparePercent.label = G-code Preparation Progress
|
||||
thing-type.bambulab.printer.channel.gcodeFilePreparePercent.description = Percentage of G-code file preparation completed.
|
||||
thing-type.bambulab.printer.channel.gcodeState.label = G-code State
|
||||
thing-type.bambulab.printer.channel.gcodeState.description = Current state of the G-code execution.
|
||||
thing-type.bambulab.printer.channel.heatBreakFanSpeed.label = Heat Break Fan Speed
|
||||
thing-type.bambulab.printer.channel.heatBreakFanSpeed.description = Speed of the heat break cooling fan (RPM).
|
||||
thing-type.bambulab.printer.channel.layerNum.label = Current Layer Number
|
||||
thing-type.bambulab.printer.channel.layerNum.description = Current layer being printed.
|
||||
thing-type.bambulab.printer.channel.ledChamber.label = Chamber LED
|
||||
thing-type.bambulab.printer.channel.ledChamber.description = Controls the LED lighting inside the printer chamber.
|
||||
thing-type.bambulab.printer.channel.ledWork.label = Work Area LED
|
||||
thing-type.bambulab.printer.channel.ledWork.description = Controls the LED lighting for the work area.
|
||||
thing-type.bambulab.printer.channel.mcPercent.label = Print Progress
|
||||
thing-type.bambulab.printer.channel.mcPercent.description = Percentage of the print completed.
|
||||
thing-type.bambulab.printer.channel.mcPrintStage.label = Print Stage
|
||||
thing-type.bambulab.printer.channel.mcPrintStage.description = Current stage of the print process.
|
||||
thing-type.bambulab.printer.channel.mcRemainingTime.label = Remaining Print Time
|
||||
thing-type.bambulab.printer.channel.mcRemainingTime.description = Estimated time remaining for the print in seconds.
|
||||
thing-type.bambulab.printer.channel.nozzleTargetTemperature.label = Nozzle Target Temperature
|
||||
thing-type.bambulab.printer.channel.nozzleTargetTemperature.description = Target temperature of the nozzle.
|
||||
thing-type.bambulab.printer.channel.nozzleTemperature.label = Nozzle Temperature
|
||||
thing-type.bambulab.printer.channel.nozzleTemperature.description = Current temperature of the nozzle.
|
||||
thing-type.bambulab.printer.channel.speedLevel.label = Print Speed Level
|
||||
thing-type.bambulab.printer.channel.speedLevel.description = Current speed setting of the print job.
|
||||
thing-type.bambulab.printer.channel.timeLaps.label = Timelapse Enabled
|
||||
thing-type.bambulab.printer.channel.timeLaps.description = Indicates whether timelapse recording is enabled.
|
||||
thing-type.bambulab.printer.channel.useAms.label = AMS System in Use
|
||||
thing-type.bambulab.printer.channel.useAms.description = Indicates whether the Automatic Material System (AMS) is active.
|
||||
thing-type.bambulab.printer.channel.vibrationCalibration.label = Vibration Calibration
|
||||
thing-type.bambulab.printer.channel.vibrationCalibration.description = Indicates whether vibration calibration has been performed.
|
||||
thing-type.bambulab.printer.channel.wifiSignal.label = WiFi Signal Strength
|
||||
thing-type.bambulab.printer.channel.wifiSignal.description = Current WiFi signal strength.
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.bambulab.boolean-channel.label = Boolean Channel
|
||||
channel-type.bambulab.number-channel.label = Number Channel
|
||||
channel-type.bambulab.on-off-command-channel.label = ON/OFF Channel
|
||||
channel-type.bambulab.on-off-command-channel.state.option.ON = ON
|
||||
channel-type.bambulab.on-off-command-channel.state.option.OFF = OFF
|
||||
channel-type.bambulab.on-off-command-channel.command.option.ON = ON
|
||||
channel-type.bambulab.on-off-command-channel.command.option.OFF = OFF
|
||||
channel-type.bambulab.percent-channel.label = Percent Channel
|
||||
channel-type.bambulab.string-channel.label = String Channel
|
||||
channel-type.bambulab.temperature-channel.label = Temperature
|
||||
channel-type.bambulab.wifi-channel.label = WiFi Signal Strength
|
||||
channel-type.bambulab.wifi-channel.description = Current WiFi signal strength.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.bambulab.printer.channel.command.label = Printer Command
|
||||
thing-type.bambulab.printer.channel.command.description = Current command being executed by the printer.
|
||||
thing-type.bambulab.printer.channel.message.label = Message Code
|
||||
thing-type.bambulab.printer.channel.message.description = Message code from the printer.
|
||||
thing-type.bambulab.printer.channel.sequenceId.label = Sequence ID
|
||||
thing-type.bambulab.printer.channel.sequenceId.description = Unique sequence identifier for commands.
|
||||
thing-type.bambulab.printer.channel.buildplateMarkerDetector.label = Buildplate Marker Detector
|
||||
thing-type.bambulab.printer.channel.buildplateMarkerDetector.description = Indicates if the buildplate marker detection is enabled.
|
||||
thing-type.bambulab.printer.channel.ipcam.label = IP Camera
|
||||
thing-type.bambulab.printer.channel.ipcam.description = IP camera details of the printer.
|
||||
thing-type.bambulab.printer.channel.ipcamDev.label = IP Camera Device
|
||||
thing-type.bambulab.printer.channel.ipcamDev.description = IP camera device associated with the printer.
|
||||
thing-type.bambulab.printer.channel.ipcamRecord.label = IP Camera Record
|
||||
thing-type.bambulab.printer.channel.ipcamRecord.description = Current recording status of the IP camera.
|
||||
thing-type.bambulab.printer.channel.modeBits.label = Mode Bits
|
||||
thing-type.bambulab.printer.channel.modeBits.description = Mode bits setting of the IP camera.
|
||||
thing-type.bambulab.printer.channel.net.label = Network
|
||||
thing-type.bambulab.printer.channel.net.description = Network configuration and info.
|
||||
thing-type.bambulab.printer.channel.netConf.label = Network Configuration
|
||||
thing-type.bambulab.printer.channel.netConf.description = Configuration settings of the printer network.
|
||||
thing-type.bambulab.printer.channel.resolution.label = Camera Resolution
|
||||
thing-type.bambulab.printer.channel.resolution.description = Resolution of the IP camera.
|
||||
thing-type.bambulab.printer.channel.timelapse.label = Timelapse
|
||||
thing-type.bambulab.printer.channel.timelapse.description = Status of the timelapse recording.
|
||||
thing-type.bambulab.printer.channel.tutkServer.label = Tutk Server
|
||||
thing-type.bambulab.printer.channel.tutkServer.description = Tutk server details for IP camera.
|
||||
thing-type.bambulab.printer.channel.upgradeState.label = Upgrade State
|
||||
thing-type.bambulab.printer.channel.upgradeState.description = Current state of the printer firmware upgrade.
|
||||
thing-type.bambulab.printer.channel.upload.label = Upload
|
||||
thing-type.bambulab.printer.channel.upload.description = Upload status details.
|
||||
thing-type.bambulab.printer.channel.uploadMessage.label = Upload Message
|
||||
thing-type.bambulab.printer.channel.uploadMessage.description = Message related to the upload status.
|
||||
thing-type.bambulab.printer.channel.uploadProgress.label = Upload Progress
|
||||
thing-type.bambulab.printer.channel.uploadProgress.description = File upload progress percentage.
|
||||
thing-type.bambulab.printer.channel.uploadStatus.label = Upload Status
|
||||
thing-type.bambulab.printer.channel.uploadStatus.description = Current file upload status.
|
||||
thing-type.bambulab.printer.channel.xcam.label = XCam
|
||||
thing-type.bambulab.printer.channel.xcam.description = XCam details of the printer.
|
||||
|
||||
# other
|
||||
|
||||
action.sendCommand.label = Sends command to 3D printer
|
||||
action.sendCommand.description = Transmits a command to the 3D printer for execution, enabling direct control over its operations.
|
||||
action.refreshChannels.label = Reports the complete status of the printer
|
||||
action.refreshChannels.description = This is unnecessary for the X1 series since it already transmits the full object each time. However, the P1 series only sends the values that have been updated compared to the previous report. As a rule of thumb, refrain from executing this command at intervals less than 5 minutes on the P1P, as it may cause lag due to its hardware limitations.
|
||||
printer.handler.init.noHostname = Please pass hostname!
|
||||
printer.handler.init.invalidHostname = Invalid hostname "{0}"!
|
||||
printer.handler.init.noSerial = Please pass serial!
|
||||
printer.handler.init.noAccessCode = Please pass access code!
|
|
@ -0,0 +1,70 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bambulab"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
<channel-type id="temperature-measurement">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Current Temperature</label>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="temperature-setpoint">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Target Temperature</label>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="string">
|
||||
<item-type>String</item-type>
|
||||
<label>String Channel</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="wifi">
|
||||
<item-type unitHint="dBm">Number:Power</item-type>
|
||||
<label>Wi-Fi Signal Strength</label>
|
||||
<description>Current Wi-Fi signal strength.</description>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="number">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number Channel</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="percent">
|
||||
<item-type unitHint="%">Number:Dimensionless</item-type>
|
||||
<label>Percent Channel</label>
|
||||
<state readOnly="true" min="0" max="100" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="boolean">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Boolean Channel</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="on-off-command">
|
||||
<item-type>String</item-type>
|
||||
<label>ON/OFF Channel</label>
|
||||
<category>Lightbulb</category>
|
||||
<state>
|
||||
<options>
|
||||
<option value="ON">ON</option>
|
||||
<option value="OFF">OFF</option>
|
||||
</options>
|
||||
</state>
|
||||
<command>
|
||||
<options>
|
||||
<option value="ON">ON</option>
|
||||
<option value="OFF">OFF</option>
|
||||
</options>
|
||||
</command>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,156 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bambulab"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="printer">
|
||||
<label>Bambu Lab Printer</label>
|
||||
<description>Represents a Bambu Lab 3D printer connected via MQTT</description>
|
||||
|
||||
<channels>
|
||||
<channel id="nozzle-temperature" typeId="temperature-measurement">
|
||||
<label>Nozzle Temperature</label>
|
||||
<description>Current temperature of the nozzle.</description>
|
||||
</channel>
|
||||
<channel id="nozzle-target-temperature" typeId="temperature-setpoint">
|
||||
<label>Nozzle Target Temperature</label>
|
||||
<description>Target temperature of the nozzle.</description>
|
||||
</channel>
|
||||
<channel id="bed-temperature" typeId="temperature-measurement">
|
||||
<label>Bed Temperature</label>
|
||||
<description>Current temperature of the heated bed.</description>
|
||||
</channel>
|
||||
<channel id="bed-target-temperature" typeId="temperature-setpoint">
|
||||
<label>Bed Target Temperature</label>
|
||||
<description>Target temperature of the heated bed.</description>
|
||||
</channel>
|
||||
<channel id="chamber-temperature" typeId="temperature-measurement">
|
||||
<label>Chamber Temperature</label>
|
||||
<description>Current temperature inside the printer chamber.</description>
|
||||
</channel>
|
||||
<channel id="mc-print-stage" typeId="string">
|
||||
<label>Print Stage</label>
|
||||
<description>Current stage of the print process.</description>
|
||||
</channel>
|
||||
<channel id="mc-percent" typeId="percent">
|
||||
<label>Print Progress</label>
|
||||
<description>Percentage of the print completed.</description>
|
||||
</channel>
|
||||
<channel id="mc-remaining-time" typeId="number">
|
||||
<label>Remaining Print Time</label>
|
||||
<description>Estimated time remaining for the print in seconds.</description>
|
||||
</channel>
|
||||
<channel id="wifi-signal" typeId="wifi">
|
||||
<label>WiFi Signal Strength</label>
|
||||
<description>Current WiFi signal strength.</description>
|
||||
</channel>
|
||||
<channel id="bed-type" typeId="string">
|
||||
<label>Bed Type</label>
|
||||
<description>Type of the printer's heated bed.</description>
|
||||
</channel>
|
||||
<channel id="gcode-file" typeId="string">
|
||||
<label>G-code File</label>
|
||||
<description>Name of the currently loaded G-code file.</description>
|
||||
</channel>
|
||||
<channel id="gcode-state" typeId="string">
|
||||
<label>G-code State</label>
|
||||
<description>Current state of the G-code execution.</description>
|
||||
</channel>
|
||||
<channel id="reason" typeId="string">
|
||||
<label>Pause/Stop Reason</label>
|
||||
<description>Reason for pausing or stopping the print.</description>
|
||||
</channel>
|
||||
<channel id="result" typeId="string">
|
||||
<label>Print Result</label>
|
||||
<description>Final result or status of the print job.</description>
|
||||
</channel>
|
||||
<channel id="gcode-file-prepare-percent" typeId="percent">
|
||||
<label>G-code Preparation Progress</label>
|
||||
<description>Percentage of G-code file preparation completed.</description>
|
||||
</channel>
|
||||
<channel id="big-fan1-speed" typeId="number">
|
||||
<label>Big Fan 1 Speed</label>
|
||||
<description>Speed of the first large cooling fan (RPM).</description>
|
||||
</channel>
|
||||
<channel id="big-fan2-speed" typeId="number">
|
||||
<label>Big Fan 2 Speed</label>
|
||||
<description>Speed of the second large cooling fan (RPM).</description>
|
||||
</channel>
|
||||
<channel id="heat-break-fan-speed" typeId="number">
|
||||
<label>Heat Break Fan Speed</label>
|
||||
<description>Speed of the heat break cooling fan (RPM).</description>
|
||||
</channel>
|
||||
<channel id="layer-num" typeId="number">
|
||||
<label>Current Layer Number</label>
|
||||
<description>Current layer being printed.</description>
|
||||
</channel>
|
||||
<channel id="speed-level" typeId="number">
|
||||
<label>Print Speed Level</label>
|
||||
<description>Current speed setting of the print job.</description>
|
||||
</channel>
|
||||
<channel id="time-laps" typeId="boolean">
|
||||
<label>time-lapse Enabled</label>
|
||||
<description>Indicates whether time-lapse recording is enabled.</description>
|
||||
</channel>
|
||||
<channel id="use-ams" typeId="boolean">
|
||||
<label>AMS System in Use</label>
|
||||
<description>Indicates whether the Automatic Material System (AMS) is active.</description>
|
||||
</channel>
|
||||
<channel id="vibration-calibration" typeId="boolean">
|
||||
<label>Vibration Calibration</label>
|
||||
<description>Indicates whether vibration calibration has been performed.</description>
|
||||
</channel>
|
||||
|
||||
<!-- command channels -->
|
||||
<channel id="led-chamber" typeId="on-off-command">
|
||||
<label>Chamber LED</label>
|
||||
<description>Controls the LED lighting inside the printer chamber.</description>
|
||||
</channel>
|
||||
<channel id="led-work" typeId="on-off-command">
|
||||
<label>Work Area LED</label>
|
||||
<description>Controls the LED lighting for the work area.</description>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<representation-property>serial</representation-property>
|
||||
<config-description>
|
||||
<parameter name="serial" type="text" required="true">
|
||||
<label>Serial Number</label>
|
||||
<description>Unique serial number of the printer.</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="scheme" type="text" required="false">
|
||||
<label>Scheme</label>
|
||||
<description>URI scheme.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="hostname" type="text" required="true">
|
||||
<label>Hostname</label>
|
||||
<description>IP address of the printer or `us.mqtt.bambulab.com` for cloud mode.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
|
||||
<parameter name="port" type="integer" required="false">
|
||||
<label>Port</label>
|
||||
<description>URI port.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="username" type="text">
|
||||
<label>Username</label>
|
||||
<description>`bblp` for local mode or your Bambu Lab user (starts with `u_`).</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="accessCode" type="text" required="true">
|
||||
<label>Access Code</label>
|
||||
<description>Access code of the printer. The method of obtaining it differs for local and cloud modes.
|
||||
</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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.bambulab.internal;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static pl.grzeslowski.jbambuapi.PrinterClient.Channel.LedControlCommand.LedMode.*;
|
||||
import static pl.grzeslowski.jbambuapi.PrinterClient.Channel.LedControlCommand.LedNode.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.assertj.core.api.ThrowableAssert;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.AmsControlCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.AmsFilamentSettingCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.AmsUserSettingCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.ChangeFilamentCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.Command;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.InfoCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.IpCamRecordCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.LedControlCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.PrintSpeedCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.PushingCommand;
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient.Channel.SystemCommand;
|
||||
|
||||
/**
|
||||
* @author Martin Grzeslowski - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PrinterActionsTest {
|
||||
PrinterActions printerActions = new PrinterActions();
|
||||
@Mock
|
||||
@Nullable
|
||||
PrinterHandler printerHandler;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
printerActions.setThingHandler(requireNonNull(printerHandler));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should correctly parse GCodeLineCommand")
|
||||
void gcodeLines() {
|
||||
// given
|
||||
var command = """
|
||||
GCodeLine:123
|
||||
G28 ; Home all axes
|
||||
G90 ; Set to absolute positioning
|
||||
G1 X50 Y50 Z10 F1500 ; Move to position (50,50,10) at 1500 mm/min
|
||||
G1 X100 Y100 Z20 F2000 ; Move to position (100,100,20) at 2000 mm/min
|
||||
M104 S200 ; Set hotend temperature to 200°C""";
|
||||
|
||||
// when
|
||||
printerActions.sendCommand(command);
|
||||
|
||||
// then
|
||||
verify(requireNonNull(printerHandler)).sendCommand(new PrinterClient.Channel.GCodeLineCommand(
|
||||
List.of("G28 ; Home all axes", "G90 ; Set to absolute positioning",
|
||||
"G1 X50 Y50 Z10 F1500 ; Move to position (50,50,10) at 1500 mm/min",
|
||||
"G1 X100 Y100 Z20 F2000 ; Move to position (100,100,20) at 2000 mm/min",
|
||||
"M104 S200 ; Set hotend temperature to 200°C"),
|
||||
"123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should throw exception if there are no lines for GCodeLineCommand")
|
||||
void gcodeLineNoLines() {
|
||||
// given
|
||||
var command = "GCodeLine:123";
|
||||
|
||||
// when
|
||||
ThrowableAssert.ThrowingCallable when = () -> printerActions.sendCommand(command);
|
||||
|
||||
// then
|
||||
assertThatThrownBy(when)//
|
||||
.isInstanceOf(IllegalArgumentException.class)//
|
||||
.hasMessage("There are no lines for GCodeLineCommand!");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{index}: should {0}")
|
||||
@MethodSource
|
||||
void shouldRunCommand(String command, Command expectedCommand) {
|
||||
// when
|
||||
printerActions.sendCommand(command);
|
||||
|
||||
// then
|
||||
verify(requireNonNull(printerHandler)).sendCommand(expectedCommand);
|
||||
}
|
||||
|
||||
static Stream<Arguments> shouldRunCommand() {
|
||||
var infoCommandStream = Arrays.stream(InfoCommand.values())//
|
||||
.map(value -> Arguments.of("Info:" + value.name(), value));
|
||||
var pushingCommandStream = stream(Arguments.of("Pushing:11:22", new PushingCommand(11, 22)));
|
||||
var printCommandStream = Arrays.stream(PrinterClient.Channel.PrintCommand.values())//
|
||||
.map(value -> Arguments.of("Print:" + value.name(), value));
|
||||
var changeFilamentCommandStream = stream(
|
||||
Arguments.of("ChangeFilament:11:22:33", new ChangeFilamentCommand(11, 22, 33)));
|
||||
var amsUserSettingCommandStream = stream(
|
||||
Arguments.of("AmsUserSetting:11:tRuE:FaLsE", new AmsUserSettingCommand(11, true, false)));
|
||||
var amsFilamentSettingCommandStream = stream(Arguments.of("AmsFilamentSetting:11:22:s3:s4:55:66:s7",
|
||||
new AmsFilamentSettingCommand(11, 22, "s3", "s4", 55, 66, "s7")));
|
||||
var amsControlCommandStream = Arrays.stream(AmsControlCommand.values())//
|
||||
.map(value -> Arguments.of("AmsControl:" + value.name(), value));
|
||||
var printSpeedCommandStream = Arrays.stream(PrintSpeedCommand.values())//
|
||||
.map(value -> Arguments.of("PrintSpeed:" + value.name(), value));
|
||||
var gCodeFileCommandStream = stream(
|
||||
Arguments.of("GCodeFile:s1", new PrinterClient.Channel.GCodeFileCommand("s1")));
|
||||
var gCodeLineCommandStream = stream(Arguments.of("""
|
||||
GCodeLine:s1
|
||||
l1
|
||||
l2
|
||||
l3""", new PrinterClient.Channel.GCodeLineCommand(List.of("l1", "l2", "l3"), "s1")));
|
||||
var ledControlCommandStream = stream(//
|
||||
Arguments.of("LedControl:CHAMBER_LIGHT:ON",
|
||||
new LedControlCommand(CHAMBER_LIGHT, ON, null, null, null, null)), //
|
||||
Arguments.of("LedControl:WORK_LIGHT:OFF",
|
||||
new LedControlCommand(WORK_LIGHT, OFF, null, null, null, null)), //
|
||||
Arguments.of("LedControl:CHAMBER_LIGHT:FLASHING:11:22:33:44",
|
||||
new LedControlCommand(CHAMBER_LIGHT, FLASHING, 11, 22, 33, 44))//
|
||||
);
|
||||
var systemCommandStream = Arrays.stream(SystemCommand.values())//
|
||||
.map(value -> Arguments.of("System:" + value.name(), value));
|
||||
var ipCamRecordCommandStream = stream(//
|
||||
Arguments.of("IpCamRecord:tRue", new IpCamRecordCommand(true)), //
|
||||
Arguments.of("IpCamRecord:fAlSe", new IpCamRecordCommand(false))//
|
||||
);
|
||||
var ipCamTimelapsCommandStream = stream(//
|
||||
Arguments.of("IpCamTimelaps:tRue", new PrinterClient.Channel.IpCamTimelapsCommand(true)), //
|
||||
Arguments.of("IpCamTimelaps:fAlSe", new PrinterClient.Channel.IpCamTimelapsCommand(false))//
|
||||
);
|
||||
var xCamControlCommandStream = Arrays.stream(PrinterClient.Channel.XCamControlCommand.Module.values())//
|
||||
.map(moduleValue -> Arguments.of("XCamControl:%s:trUE:FAlse".formatted(moduleValue),
|
||||
new PrinterClient.Channel.XCamControlCommand(moduleValue, true, false)));
|
||||
|
||||
return concat(infoCommandStream, pushingCommandStream, printCommandStream, changeFilamentCommandStream,
|
||||
amsUserSettingCommandStream, amsFilamentSettingCommandStream, amsControlCommandStream,
|
||||
printSpeedCommandStream, gCodeFileCommandStream, gCodeLineCommandStream, ledControlCommandStream,
|
||||
systemCommandStream, ipCamRecordCommandStream, ipCamTimelapsCommandStream, xCamControlCommandStream);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
static Stream<Arguments> concat(Stream<Arguments>... streams) {
|
||||
return Stream.of(streams).flatMap(s -> s);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
static <T> Stream<T> stream(T... values) {
|
||||
return Stream.of(values);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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.bambulab.internal;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.function.Predicate.not;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.Channel.*;
|
||||
import static pl.grzeslowski.jbambuapi.PrinterClient.Channel.LedControlCommand.*;
|
||||
import static pl.grzeslowski.jbambuapi.PrinterClient.Channel.LedControlCommand.LedNode.*;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.openhab.binding.bambulab.internal.BambuLabBindingConstants.Channel;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
import pl.grzeslowski.jbambuapi.PrinterClient;
|
||||
|
||||
/**
|
||||
* @author Martin Grześlowski - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@NonNullByDefault
|
||||
class PrinterHandlerTest {
|
||||
@ParameterizedTest(name = "Should handle {0} command for {1} channel and send {2}")
|
||||
@MethodSource
|
||||
public void testSendLightCommand(OnOffType command, Channel channel, PrinterClient.Channel.Command sendCommand) {
|
||||
// Given
|
||||
var printerHandler = spy(new PrinterHandler(mock(Thing.class)));
|
||||
var channelUID = new ChannelUID("bambulab:printer:test:" + channel);
|
||||
doNothing().when(printerHandler).sendCommand(any(PrinterClient.Channel.Command.class));
|
||||
|
||||
// When
|
||||
printerHandler.handleCommand(channelUID, command);
|
||||
|
||||
// Then
|
||||
verify(printerHandler).sendCommand(eq(sendCommand));
|
||||
}
|
||||
|
||||
static Stream<Arguments> testSendLightCommand() {
|
||||
return Stream.of(//
|
||||
Arguments.of(OnOffType.ON, CHANNEL_LED_CHAMBER_LIGHT, on(CHAMBER_LIGHT)), //
|
||||
Arguments.of(OnOffType.OFF, CHANNEL_LED_CHAMBER_LIGHT, off(CHAMBER_LIGHT)), //
|
||||
Arguments.of(OnOffType.ON, CHANNEL_LED_WORK_LIGHT, on(WORK_LIGHT)), //
|
||||
Arguments.of(OnOffType.OFF, CHANNEL_LED_WORK_LIGHT, off(WORK_LIGHT)));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Command to channel {0} should not invoke `client.sendCommand`")
|
||||
@MethodSource
|
||||
public void notImplementedCommands(Channel channel) {
|
||||
// Given
|
||||
var printerHandler = spy(new PrinterHandler(mock(Thing.class)));
|
||||
var channelUID = new ChannelUID("bambulab:printer:test:" + channel);
|
||||
|
||||
// When
|
||||
printerHandler.handleCommand(channelUID, OnOffType.ON);
|
||||
|
||||
// Then
|
||||
verify(printerHandler, never()).sendCommand(any());
|
||||
}
|
||||
|
||||
static Stream<Arguments> notImplementedCommands() {
|
||||
return stream(Channel.values())//
|
||||
.filter(not(Channel::isSupportCommand))//
|
||||
.map(Arguments::of);
|
||||
}
|
||||
}
|
|
@ -94,11 +94,11 @@ public class PiHoleHandler extends BaseThingHandler implements AdminService {
|
|||
hostname = new URI(config.hostname);
|
||||
} catch (URISyntaxException e) {
|
||||
updateStatus(OFFLINE, CONFIGURATION_ERROR,
|
||||
"@token/handler.init.invalidHostname[\"" + config.hostname + "\"]");
|
||||
"@text/handler.init.invalidHostname[\"" + config.hostname + "\"]");
|
||||
return;
|
||||
}
|
||||
if (config.token.isEmpty()) {
|
||||
updateStatus(OFFLINE, CONFIGURATION_ERROR, "@token/handler.init.noToken");
|
||||
updateStatus(OFFLINE, CONFIGURATION_ERROR, "@text/handler.init.noToken");
|
||||
return;
|
||||
}
|
||||
adminService = new JettyAdminService(config.token, hostname, httpClient);
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
<module>org.openhab.binding.automower</module>
|
||||
<module>org.openhab.binding.avmfritz</module>
|
||||
<module>org.openhab.binding.awattar</module>
|
||||
<module>org.openhab.binding.bambulab</module>
|
||||
<module>org.openhab.binding.benqprojector</module>
|
||||
<module>org.openhab.binding.bigassfan</module>
|
||||
<module>org.openhab.binding.bluetooth</module>
|
||||
|
|
Loading…
Reference in New Issue