Bambu Lab X1 X1C MQTT

I have a new, or at least newly noticed problem… My ‘Start Time’ keeps resetting. Odd. I thought I noticed it yesterday but was elbow deep in a NAS rebuild. Just fired off a new print about 5 minutes ago and see that it keeps resetting to 0/1. Has anyone else experienced that?

image

Yep, so that is what happens when first enabling. If you restart the printer it should be stable once it loads up after a few minutes. I find that on the first boot after firmware update or just booting with it disabled then enabling it, it is unstable until you reboot with it enabled from the get-go.

I haven’t noticed this behaviour before, however if you’re using my nodered flows it could be a bug in an older version. I plan on updating them likely tomorrow sometime with a bunch of new features and sensors.

:slight_smile: I think I’m using a partial part of your old code (circa Jan '23). I never built it all the way out but might now that I saw your fancy configuration page!

I’d say if you want my new and improved one, wait until tomorrow. Currently I’m editing the files now for easy token replacement for my configurators. All kinds of fun new stuff like camera resolution changing, fully detailed HMS errors, and much much more.

2 Likes

Alright, got all of my flows updated now. Because it’s quite a major overhaul I wouldn’t be surprised if there was maybe a minor bug here or there but need to have others try to see if it will be fine or not.

As mentioned, all the flows have been updated, but I’ll just re-link to the basic flow here.

As part of this, I switched from using the gists as the codebase to a new repository so it is easier for people to add issues and track changes all in one place. It is still a WIP and I need to add the dashboard YAMLs to it still, among other things. My plan soon is to create a github pages site for it to also do the configuration in case my site ever goes down, it’s still an option. Additionally, I plan to soon document the featureset of each flow so people have a better idea of what my flows provide.
What I will also likely do is once a few people test to make sure this all works, I will do a fake “release” on the new repository. That way anyone watching/following the repo can get notified when I have a new significant update much more easily.

Some recent additions with the newly updated basic flow, aside from a much needed reorganization, is that variables are now consolidated in one area in case something needs quick changing. Some new sensors and controls are available, such as:

  • Reorganized HA sensors/controls based on sensor/config/diagnostic data.
  • Toggle video recording on/off (not timelapse)
  • Change camera resolution
  • Detect if printer is on 220v or not (and create a max bed-temp accordingly)
  • Detect if the extruder is “loaded” with filament currently or not
  • Read-Only “selects” to assist with automation setup (gives you a pre-defined list of states now)
  • Enriched HMS Errors. Instead of just the link to the wiki, it will also give the Severity, Source Module and English Description of the HMS error as long as it exists in Bambu’s own HMS documentation (requires nodered to be able to access an external URL to fetch)
  • Start Time Override option for P1* users since the printer may not provide start time. This is toggleable.
  • And a few more things :slight_smile:

Additionally, I made a new writeup for the X1C Camera setup, since it cannot be done via MQTT.

Looks awesome! I appreciate all your efforts into researching, creating, and sharing this!

I’m having 2 issues, both of which are probably user error…

  1. I can only seem to get a single stack on my card. Here is the starting snippet if anything jumps out:
  - type: custom:horizontal-layout
    title: newBBLB
    path: newbblb
    badges: []
    cards:
      - type: custom:layout-card
        layout_type: custom:horizontal-layout
        layout:
          max_cols: 3
        cards:
          - type: picture-elements
            view_layout:
              column: 1
            elements:
              - type: custom:config-template-card
                entities:
                  - sensor.ams_0_x1c_tray_0

  1. I appear to be losing connectivity or the communication at least. All the great data & icons will populate but then go offline for a bit and comeback. I noted this in a previous response (start time) so this may be due to having the printer and HA/MQTT on separate VLans but in case anyone else is experiencing I figured I would share. I’ll work on troubleshooting this (after the print is done :slight_smile: )

@WolfwithSword : Thank you very much for your effort and great work. I have also still one issue with the “Nozzle Soll Temp”. It is regular switching from a right value to unkown … any idea?

image

I think I might know the source of this (also caused by the change in behaviour of the MQTT messages) but do you currently have a target temperature set? Or is this while idle.

@Corey_Johnson Only thing I can see if maybe play around with other dashboard layouts? I’m using a grid layout and letting them take up specific columns. Wish your second issue, it could be the vlan? I have in the nodered flow some dependency on a ping command to help make sure the printer is online, helps to set state when mqtt is disconnected but printer is still pingable for example.

@WolfwithSword it is during printing … so target temp ist set.

Ah okay then, I was curious if it would be an issue but it didn’t come up for me, but given the nature of the new MQTT format, I must have missed it. Issue is because it must be sending just the target temp in a message and not including the nozzle temp in it I think. I will do some testing today/tomorrow and if that’s the case, will do a small bug fix in the flow.

If it is that, it would likely be just the contents of one node you’d need to replace so I’ll also make a gist to send you the update so you don’t need to redo the whole import again.

1 Like

Ping is what was missing - I added it to the pfSense rule between the vlans and now it appears stable, last 10 minutes or so at least.

I cleared my cache but am not seeing the filament-1.svg… hmm it’s in config/custom_icons/filament-1.svg unsure. Maybe that gets called in the beginning of a job and since this is a long-running job I’ll see it next time? **Update-FIXED - I reinstalled FontAwesome and restarted and all good now. Must have fumbled it the 1st time.

Not sure how exactly but was able to get it to 2 columns at least. I’ll keep playing with it. Still very impressive!

For the “Start Time” sensor, (in my case sensor.x1c_x1c_print_start_time), it’s almost like it is taking the time OF the message, or the update message, instead of the actual start time. Trying to understand the flow & settings (I’m still a NR newb).

Wow…thanks to everyone that contributed to get the MQTT integration going. It works great with my P1S. That and the inspiring dashboard feom @WolfwithSword allowed me to switch to LAN Only mode and still monitor my printer via HA mobile app.

I made a few of my own customizations to the great dashboards that were shared (used mushroom cards in a few places instead).

When Idle:

When Printing:
SmartSelect_20230806_170232_Chrome

3 Likes

Start time comes directly from the message - the only case where I create it is in the P1S/P1P since it isn’t reliably sent. What you probably see is me creating the end-time.

@stephack looks clean! I ended up making it very complex to be compact, but it’s annoying to edit. I initially did end up having something similar to this before sharing it, but I love how you’ve positioned everything still. Also good to know it’s working on the P1S - afaik you’re the first person I’ve heard using it for P1S :slight_smile:

1 Like

Very true. I wanted as much as possible on the screen without scrolling on my phone. I still need to add the completion time and an icon to open my camera stream…not the built in cam…hopefully Bambu allows lan access to the buitin cam on the P1S in the near future.

Fixed the temperature related bug I believe, as well as a few others. Ended up only modifying 3 nodes so rather than require a reimport for you guys I’ll paste it here. Already committed so regenerating would work still.

@Obicom

Bugs fixed:

  • Nozzle & Bed Temp would sometimes go unknown when a target temperature was set via printer. Cause was due to new mqtt message structure and I wasn’t handling when I set attributes of sensors without actual sensor value
  • Nozzle & Bed temperature Setting control was not working. Typo’d in their mqtt topics when I remade their flow. woops.
  • Possibly a bug with the new “Print Stage” sensor. Added in an override for “Idle” even though it shouldn’t be necessary.

For the 3 nodes you need to change if you don’t want to do a reimport this time:

Manual Edits

Open up these nodes on version 2.0.0 and replace their full contents as below. This will make it equivalent to 2.0.1

  • Stage Parser

let parseAction = flow.get("stageParser");

if(msg.payload.print.stg_cur != undefined){
    var current_action = parseAction(msg.payload.print.stg_cur);
    flow.set("stage_override", msg.payload.print.stg_cur);
    msg.payload.print.stg_cur = current_action;
    if (current_action == "Idle" && msg.payload.print.print_real_action == undefined) {
        msg.payload.print.print_action = current_action;
    }
}
var translated_stages = [];
if(msg.payload.print.stg  != undefined) {
    for (var stage of msg.payload.print.stg) {
        translated_stages.push(parseAction(stage))
    }
    msg.payload.print.stg = translated_stages;
}

if (msg.payload.print.print_real_action != undefined) {
    if (flow.get("stage_override") <= -1 && msg.payload.print.print_real_action <=0) {
        msg.payload.print.print_real_action = flow.get("stage_override") ;
    }
    msg.payload.print.print_action = parseAction(msg.payload.print.print_real_action);
}

node.send(msg);
  • “Sub” (Going into nozzle & temp setting)
let topic = flow.get("root_topic");
let device = flow.get("HA_DEVICE");
let config1 = {
    "action": "subscribe",
    "topic": {
        "topic": topic + "/number/" + device + "/set_bed_temp/set",
        "qos": 2
    }
}

let config2 = {
    "action": "subscribe",
    "topic": {
        "topic": topic + "/number/" + device + "/set_nozzle_temp/set",
        "qos": 2
    }
}

let config3 = {
    "action": "subscribe",
    "topic": {
        "topic": topic + "/fan/" + device + "/big_fan1/percent/set",
        "qos": 2
    }
}

let config4 = {
    "action": "subscribe",
    "topic": {
        "topic": topic + "/fan/" + device + "/big_fan2/percent/set",
        "qos": 2
    }
}

let config5 = {
    "action": "subscribe",
    "topic": {
        "topic": topic + "/fan/" + device + "/cooling_fan/percent/set",
        "qos": 2
    }
}

let config6 = {
    "action": "subscribe",
    "topic": {
        "topic": topic + "/fan/" + device + "/big_fan1/set",
        "qos": 2
    }
}

let config7 = {
    "action": "subscribe",
    "topic": {
        "topic": topic + "/fan/" + device + "/big_fan2/set",
        "qos": 2
    }
}

let config8 = {
    "action": "subscribe",
    "topic": {
        "topic": topic + "/fan/" + device + "/cooling_fan/set",
        "qos": 2
    }
}

node.send([config1, config2, config3. config4, config5, config6, config7, config8]);
  • “Printer State”
function sendNonEmpty(message) {
    if(message.payload != undefined && message.payload != "" && msg.payload != {}) {
        node.send(message);
    }
}

let hasAttr = false;

if (msg.payload == undefined) {
    return;
}
let root_topic = flow.get("root_topic");
let ha_device_name = flow.get("HA_DEVICE");
if (msg.topic == "machine_serial" || msg.topic == "print_preview" || msg.topic == "msg") {
    return;
}
if (msg.topic == "reset_filter" || msg.topic == "resume_print" || msg.topic == "pause_print"
    || msg.topic == "clear_external_spool" || msg.topic == "force_ftp_fetch"
    || msg.topic.includes("logo_light") || msg.topic.includes("nozzle_light")) {
    return;
}
if(msg.topic == "nozzle_diameter") {
    flow.set("nozzle_diameter", msg.payload);
}
if(msg.topic.startsWith("set_") && msg.topic.endsWith("_temp")) {
    return;
}
else if (msg.topic == "last_calibrated_pa" && msg.payload == "") {
    return;
}
else if (msg.topic == "toggle_timelapse" || msg.topic == "toggle_recording") {
    return;
}
else if (msg.topic == "hw_switch_state") {
    msg.topic = "extruder_loaded";
    if(msg.payload == "1") {
        msg.payload = true;
    }
    else {
        msg.payload = false;
    }
}
else if (msg.topic == "machine_name") {
    var oldTopic = msg.topic;
    hasAttr = true;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
    let oldPayload = msg.payload;
    msg.payload = { "Serial Number": flow.get("printer_serial")};
    node.send(msg);
    msg.payload = oldPayload;
    msg.topic = oldTopic;
}
else if (msg.topic == "wifi_signal")
    msg.payload = msg.payload.match(/^-?\d+/)[0];
else if (msg.topic == "xcam") {
    var oldTopic = msg.topic;
    hasAttr = true;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
    node.send(msg);
    msg.topic = oldTopic;
    msg.payload = msg.payload.status;
}
else if (msg.topic == "upload") {
    var oldTopic = msg.topic;
    hasAttr = true;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
    node.send(msg);
    msg.topic = oldTopic;
    msg.payload = msg.payload.message;
}
else if (msg.topic == "camera_rtsp") {
    var oldTopic = msg.topic;
    var oldPayload = msg.payload;
    hasAttr = true;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
    if (oldPayload != "Disabled") {
        msg.payload = {"URL": oldPayload, "User": "bblp", "Password": "<access_code>", "Protocol": "TCP", "SSL Verify": "disable", "Framerate": 30, "Port (Optional)": 322, "LAN Mode": "Not Required"};
        node.send(msg);
        oldPayload = "Enabled";
    }
    msg.topic = oldTopic;
    msg.payload = oldPayload;
}
else if (msg.topic == "upgrade_state") {
    var oldTopic = msg.topic;
    hasAttr = true;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
    node.send(msg);
    msg.topic = oldTopic;
    let state = "AVAILABLE";
    if (msg.payload.new_version_state == 1) {
        msg.payload = state;
    }
    else {
        msg.payload = msg.payload.status;
    }
}
else if (msg.topic == "stage") {
    var oldTopic = msg.topic;
    hasAttr = true;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";

    var current = msg.payload.current;
    var prev = {};
    var count = 0;
    
    if(msg.payload.prev != undefined) {
        for (var stg of msg.payload.prev) {
            prev[count.toString()] = stg;
            count += 1;
        }
            msg.payload = prev;
            node.send(msg);
    }

    msg.topic = oldTopic;
    msg.payload = current;
}
else if (msg.topic == "ipcam") {
    var oldTopic = msg.topic;
    hasAttr = true;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
    node.send(msg);
    msg.topic = oldTopic;
    msg.payload = msg.payload.ipcam_dev;
}
else if (msg.topic == "vt_tray") {
    var oldTopic = msg.topic;
    if (msg.payload.color != undefined
        && !msg.payload.color.startsWith("#")) {
        msg.payload.color = "#" + msg.payload.color;
    }
    hasAttr = true;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
    node.send(msg);
    msg.topic = oldTopic;
    msg.payload = msg.payload.type;
}
else if (msg.topic == "subtask") {
    var oldTopic = msg.topic;
    hasAttr = true;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
    node.send(msg);
    msg.topic = oldTopic;
    msg.payload = msg.payload.name;
}
else if (msg.topic == "print") {
    var oldTopic = msg.topic;
    hasAttr = true;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
    node.send(msg);
    msg.topic = oldTopic;
    msg.payload = msg.payload.type;
    if(msg.payload == undefined || msg.payload == "") {
        msg.payload = "None";
    }
}
else if (msg.topic == "bed_temperature") {
    var oldTopic = msg.topic;
    hasAttr = true;
    if (msg.payload.target_temperature != undefined) {
        msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
        node.send(msg);
    }
    if(msg.payload.temperature == undefined) {
        return;
    }
    msg.topic = oldTopic;
    msg.payload = msg.payload.temperature;
}
else if (msg.topic == "nozzle_temperature") {
    var oldTopic = msg.topic;
    hasAttr = true;
    if(msg.payload.target_temperature != undefined) {
        msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
        node.send(msg);
    }
    if (msg.payload.temperature == undefined) {
        return;
    }
    msg.topic = oldTopic;
    msg.payload = msg.payload.temperature;
}
else if (msg.topic == "chamber_temperature") {
    msg.payload = msg.payload.temperature;
}
else if (msg.topic == "gcode") {
    var oldTopic = msg.topic;
    hasAttr = true;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
    node.send(msg);
    msg.topic = oldTopic;
    msg.payload = msg.payload.file;
}
else if (msg.topic == "hms") {
    msg.topic = "HMS";
    var oldTopic = msg.topic;
    hasAttr = true;
    var numOfCodes = msg.payload == undefined ? 0 : msg.payload.length;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/attr";
    var links = {};
    for (var data of msg.payload) {
        let m = data.url;

        if ( data.description != undefined && data.description != "") {
            m = data.description + "\n"+ m;    
        }
        if(data.module != undefined && data.module != "") {
            m = "["+data.module+"]" + "\n" + m;
        }
        if (data.type != undefined && data.type != "") {
            m = "["+data.type+"]" + " " + m;
        }

        links[data.code.replaceAll(" ", "_")] = m;
    }
    msg.payload = links;
    node.send(msg);
    msg.topic = oldTopic;
    msg.payload = numOfCodes;
}
else if (msg.topic == "speed") {
    var oldTopic = msg.topic;
    hasAttr = true;
    msg.topic = root_topic + "/select/" + ha_device_name + "/" + msg.topic + "/attr";
    node.send(msg);
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + "speed_info" + "/attr";
    node.send(msg);
    msg.topic = "speed";
    msg.payload = msg.payload.profile;
    msg.topic = root_topic + "/select/" + ha_device_name + "/" + "speed" + "/state";
    node.send(msg);
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + "speed_info" + "/state";
    node.send(msg);
    return;
}
else if (msg.topic == "camera_resolution") {
    msg.topic = root_topic + "/select/" + ha_device_name + "/" + msg.topic + "/state";
    node.send(msg);
    return;
}
else if (msg.topic == "chamber_light") {
    msg.payload = msg.payload.toUpperCase();
    msg.topic = root_topic + "/light/" + ha_device_name + "/" + msg.topic + "/state";
    node.send(msg);
    return;
}
else if (msg.topic.match(/(.*)fan(.*)/) && msg.topic !== "fan_gear" && msg.topic != "heatbreak_fan"
    && msg.topic != "aux_part_fan") {
    let orig_topic = msg.topic;
    msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + orig_topic + "/state";
    node.send(msg);
    msg.topic = root_topic + "/fan/" + ha_device_name + "/" + orig_topic + "/percent/state";
    msg.payload = parseInt(msg.payload) / 10;
    node.send(msg);
    if(msg.payload == 0 || msg.payload == "0") {
        msg.payload = "OFF";
        msg.topic = root_topic + "/fan/" + ha_device_name + "/" + orig_topic+ "/state";
        node.send(msg);
    }
    else {
        msg.payload = "ON";
        msg.topic = root_topic + "/fan/" + ha_device_name + "/" + orig_topic + "/state";
        node.send(msg);
    }
    return;
}
msg.topic = root_topic + "/sensor/" + ha_device_name + "/" + msg.topic + "/state";

if(hasAttr) {
    sendNonEmpty(msg);
}
else {
    node.send(msg);
}
1 Like

By the way, you should be able to remove the outlines cards you have overlaying with card-mod and some css like

ha-card {
        border: none !important;
        box-shadow: none !important;
}
1 Like

Thank you for the hint @WolfwithSword
Look nice now … :slight_smile:

1 Like

like this, willing share the code for it? like the status and fans