Bambu Lab X1 X1C MQTT

Updated to latest firmware (01.03.00.00) and have noticed a few minor changes.

AMS Humidity is now in levels/tiers and no longer a %, which may explain what @planetix was getting if they were on early firmware. This is less granular, and I’ve seen some people discuss that it may not have been that accurate before hand, but all I can say is it was always about 2-6% below the humidity of the room it’s stored in. It’s now based on threshold levels, which I’d need to dig around to see what they could be. All I can say for certain is ~39-48% humidity is level 2 :stuck_out_tongue:

There’s a new property, “fan_gear”, which I have no idea what it is yet, or maybe I missed it beforehand. Idle was at 0, and just starting up for a print I saw it change to 25600.

IPCam has a new field called “resolution”. It seems by default it’s “720p”.

XCam also has more fields to display your settings, new ones such as printing_monitor, allow_skip_parts, buildplate_marker_detector, and halt_print_sensitivity.

I don’t see any other values removed as of yet, mostly just new ones or the one changed one + some changed behaviours.

The new slicer update adds in more features for the camera, specifically setting up a virtual camera to stream with say, OBS. However, it does quite a bit more than that. The bambu:// url @darkorb had for the video renderer is now generated for us in Appdata/Roaming/BambuStudio/cameratools/url.txt (or BambuStudio-Fever if using the fork), may only be after you setup “go live” once. It also includes the ffmpeg config, sdp file, BambuSource library and such. I can imagine setting up the camera rendering will be much easier now.

Now attempting to use the new SD card upload from slicer functionality and printing from the printer directly instead of slicer. The subtask_name (which previously was the file name from the slicer) is the same, but now with the .3mf file extension. Print_type is still cloud (may be due to me not being in LAN only mode).

I now have to adjust my logic for fetching the current print preview, as if you upload a 3mf file to the printer, and print directly from the file, it does not copy it to local_print.gcode.3mf, but uses the actual file. Probably adjusting the logic to check if the subtask name has an extension of 3mf, and if so, ftp/copy it over. If it failed then try again with local_print one. The FTP palette has a list operation, just going to iterate through there, if the subtask_name is matches one of the names, use it, otherwise use the default 3mf file. I have also confirmed that prints from SD card will always end in .3mf, whereas uploaded to print immediately via slicer are just the filename no extension - using new firmware/slicer, “_plate_1” is now appended to end of file name

Well, I got time to do some changes, as now I have a bug when doing print calibration, it zoomed my bed into the ground twice before I manually raised the build before homing :slight_smile:

Edit: Forgot to mention, at least the push commands for turning on/off the light and changing speed profile still work, haven’t tested others. And new observation - on print cancelled/failed, time remaining does not reset to 0 anymore.

Edit #3: Well, I don’t think “local_print.gcode.3mf” is that useful anymore at all for prints not sent to the sd card and printed immediately. It may be a bug currently but if I print instead of send, it still sends the whole file to the printer if I have “sent” one beforehand.

man I wish I had seen this tread before spending time finding things out for myself :smile:

Here is something I didn’t see mentioned here. This is how I parse actions or stages:

function getActionName(actionId) {
    switch (actionId) {
        case 0:
            return " ";  // idle or printing
        case 1:
            return "Auto bed leveling";
        case 2:
            return "Heatbed preheating";
        case 3:
            return "Sweeping XY mech mode";
        case 4:
            return "Changing filament";
        case 5:
            return "M400 pause";
        case 6:
            return "Paused due to filament runout";
        case 7:
            return "Heating hotend";
        case 8:
            return "Calibrating extrusion";
        case 9:
            return "Scanning bed surface";
        case 10:
            return "Inspecting first layer";
        case 11:
            return "Identifying build plate type";
        case 12:
            return "Calibrating Micro Lidar";
        case 13:
            return "Homing toolhead";
        case 14:
            return "Cleaning nozzle tip";
        case 15:
            return "Checking extruder temperature";
        case 16:
            return "Printing was paused by the user";
        case 17:
            return "Pause of front cover falling";
        case 18:
            return "Calibrating the micro lida";
        case 19:
            return "Calibrating extrusion flow";
        case 20:
            return "Paused due to nozzle temperature malfunction";
        case 21:
            return "Paused due to heat bed temperature malfunction";
        default:
            return actionId.toString()
    }
}

-1 is probably error or something not sure yet

1 Like

This is awesome! I had been wanting to try to convert all the flags and stage id’s and other misc id’s for a while but have been too busy to look for what each means, especially when some are very briefly present.

I assume then that “stg” probably is the last 4 stages done, that makes a lot of sense.

I also just recently discovered a potential issue with the new firmware. It seems that sometimes after turning the printer back on, it loads up the previously printed file and states/stages as the current task and sets the state to FAILED on boot. Beforehand it would just be empty and have state of IDLE. It even had my last 4 stages in stg array from my last printer yesterday.

Funny part is, it also set my stg_cur to -1, so -1 could be idle. It doesn’t always happen but it’s interesting. Rebooting cleared it all back to idle, but stg_cur is still -1, so I think it is idle. Stg array was empty too thankfully.

Edit: Updated my NR flow gist with the stage translations and some other changes for newest firmware :slight_smile:

Yup! I also have that bug but I recently got my machine and didn’t really know if it was like that before or not :smiley:

By any chance do you know if there is a way to use mqtt while in cloud mode? or enable the camera stream while in local mode?

I am happy with mqtt but the lack of camera is really a deal breaker for me :confused:

The MQTT server should be up regardless of mode I think? But the camera is a bit tricky. I haven’t gotten it done myself yet but others have. Camera stream at least to the slicer and such should still work if you’re on the same LAN and in LAN mode.

Most solutions people have for the camera stream in HA and the newly added approach in the slicer (go live) require bambu studio to be running the camera stream as well it seems, or using their bambu source as an executable and some other stuff.

There might be a way around it, but personally I’m going to wait and see if the whole bambu studio could get dockerized and include the camera stream as part of that, since if I am going to have the slicer running all the time, I’d rather it be running in a docker on my server so I can access it from any pc.

If you want to look into ways to get the camera stream, some helpful files are now generated in Appdata/Roaming/Bambu-Studio/cameratools

1 Like

Hey @WolfwithSword or others, I should know this, but don’t: What does the correct topic string look like? I want to access the values from MQTT outside of Node-Red, specifically via an ESP8266.

I get that it’s device/<serial#ofprinter>/report but how do I go from there to, for example, get the value of gcode_state? I’ve tried

text_sensor:
  - platform: mqtt_subscribe
    name: "X1C Printer Status"
    id: x1cstatus
    topic: device/00xxxxxyyy/report/gcode_state

But I’m not getting anything returned to the sensor. I can see in my logs I am connecting successfully, also I have the above working in Node Red, so I’m thinking I am just overlooking something obvious, formatting-wise. Any advice?

Should just be device/00_ID/report as the topic, which then sends messages (may need a parent of payload? unsure here). So in the ESP you should be parsing the received stream as some JSON then parse it through there. gcode_state Would be print.gcode_state - print is the parent object/key for all the messages.

Probably some threads out there for capturing mqtt and json payloads on ESPHome forums if not the official docs since it looks like you’re using that.

Here’s a copy of my mqtt payload from earlier today when it was just idle. Should help you go through the json pathing. Example MQTT Payload Report - Bambulab X1C - 2022-12-18 · GitHub

Oh, that looks like fun. Didn’t notice my humidity level was 3 there. From when I copied that earlier today, it looks like humidity in the room was just fluctuating above 50%, so I guess level 3 is 50%+, while level 2 is <50% to at least 38%, probably lower. Probably 25% if they want a linear scale. Man I wish they kept the proper % as a secondary value, it was actually quite accurate for me.

If you’re wanting to access it on an ESPHome based device, I recommend just using Home Assistant Sensor — ESPHome to show them on the ESP based device. I found doing it other ways was very painful.

Below is a config for MQTT to be pulled into Home Assistant to get the details and create sensors, without using Node Red (and how I do it). It may not be the best way, but it’s worked for me for a good long time at point and through firmware updates. I have my MQTT Broker connect to the Printer as a bridge, or you can connect HA directly to the printer if you don’t use MQTT already.

Replace 0123456789012345 with your device ID.

mqtt:
  switch:
    - name: "X1C Chamber Light"
      state_topic: "device/0123456789012345/report"
      command_topic: "device/0123456789012345/request"
      unique_id: "0123456789012345_switch_chamber_light"
      icon: "mdi:lightbulb"
      state_on: "on"
      state_off: "off"
      payload_on: '{"system":{"sequence_id":"2003","command":"ledctrl","led_node":"chamber_light","led_mode":"on","led_on_time": 500,"led_off_time": 500,"loop_times": 0,"interval_time":0},"user_id":"123456789"}'
      payload_off: '{"system":{"sequence_id":"2003","command":"ledctrl","led_node":"chamber_light","led_mode":"off","led_on_time": 500,"led_off_time": 500,"loop_times": 0,"interval_time":0},"user_id":"123456789"}'
      value_template: >
        {% if value_json.print is defined and value_json.print.lights_report is defined %}
          {{ value_json.print.lights_report[0].mode }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
  sensor:
    - name: "X1C AMS Temperature"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_ams_0_temp"
      unit_of_measurement: "°C"
      value_template: >
        {% if value_json.print is defined and value_json.print.ams is defined %}
          {{ value_json.print.ams.ams[0].temp }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Bed Temperature Target"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_bed_target_temper"
      unit_of_measurement: "°C"
      value_template: >
        {% if value_json.print is defined and value_json.print.bed_target_temper is defined %}
          {{ value_json.print.bed_target_temper }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Bed Temperature"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_bed_temper"
      unit_of_measurement: "°C"
      value_template: >
        {% if value_json.print is defined and value_json.print.bed_temper is defined %}
          {{ value_json.print.bed_temper }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Chamber Temperature"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_chamber_temper"
      unit_of_measurement: "°C"
      value_template: >
        {% if value_json.print is defined and value_json.print.chamber_temper is defined %}
          {{ value_json.print.chamber_temper }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Part Cooling Fan Speed"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_cooling_fan_speed"
      unit_of_measurement: "%"
      value_template: >
        {% if value_json.print is defined and value_json.print.cooling_fan_speed is defined %}
          {{ value_json.print.cooling_fan_speed }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Cooling Fan Speed"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_heatbreak_fan_speed"
      unit_of_measurement: "%"
      value_template: >
        {% if value_json.print is defined and value_json.print.heatbreak_fan_speed is defined %}
          {{ value_json.print.heatbreak_fan_speed }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Nozzle Temperature Target"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_nozzle_target_temper"
      unit_of_measurement: "°C"
      value_template: >
        {% if value_json.print is defined and value_json.print.nozzle_target_temper is defined %}
          {{ value_json.print.nozzle_target_temper }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Nozzle Temperature"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_nozzle_temper"
      unit_of_measurement: "°C"
      value_template: >
        {% if value_json.print is defined and value_json.print.nozzle_temper is defined %}
          {{ value_json.print.nozzle_temper }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Aux Cooling Fan Speed"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_big_fan1_speed"
      unit_of_measurement: "%"
      value_template: >
        {% if value_json.print is defined and value_json.print.big_fan1_speed is defined %}
          {{ value_json.print.big_fan1_speed }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Big Fan2 Speed"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_big_fan2_speed"
      unit_of_measurement: "%"
      value_template: >
        {% if value_json.print is defined and value_json.print.big_fan2_speed is defined %}
          {{ value_json.print.big_fan2_speed }}"
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Print Status"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_gcode_state"
      value_template: >
        {% if value_json.print is defined and value_json.print.gcode_state is defined %}
          {{ value_json.print.gcode_state | capitalize }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Print Progress"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_mc_percent"
      unit_of_measurement: "%"
      value_template: >
        {% if value_json.print is defined and value_json.print.mc_percent is defined %}
          {{ value_json.print.mc_percent }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}
    - name: "X1C Print Time Remaining"
      state_topic: "device/0123456789012345/report"
      unique_id: "0123456789012345_mc_remaining_time"
      unit_of_measurement: "min"
      value_template: >
        {% if value_json.print is defined and value_json.print.mc_remaining_time is defined %}
          {{ value_json.print.mc_remaining_time }}
        {% else %}
          {{ states(entity_id) }}
        {% endif %}

I’m aware the Fan Speeds don’t render right in it; you might be able to fix that up - I’ve just not had the time.

3 Likes

For some reason the fan speed is between 0 and 15. So the real percentage will be something like : 6%, 13%, 20%, 33%… they jump a couple numbers here and there.

I’m using something like this in node red, not the most elegant solution but AFAIK it shows the same fan reading as in the app:
{{( fan-speed-var /15) |round(1, 'ceil')*100}}

*replace the fan-speed-var with the actual fan speed variable.

It may be that my perception of speed/units and such is really bad, but I thought that must be the actual speed, seeing as I’ve seen it sometimes go around 13,14,15 as it ramps up and down, with large jumps from 0, 2, 5, 10, etc. If it’s also “levelled” then wow, looks like I still have work to do on that :laughing:

In any case, converting to a % is also nice to have! Anything we can extrapolate from the data that isn’t immediately present is always a bonus :slight_smile:

Thanks! I didn’t have the print. parent object in the statement, that was the obvious bit I overlooked :slight_smile:
Agree on the humidity reporting, I’m in the Bambu beta and have already made a feature request to have percentage restored to MQTT. It’s likely still calculating it, they just changed how it is reported based on their color-code system.

Thanks for this! I’ve used Home Assistant sensors before (to pull in temp/humidity sensors and other simple stuff) but your example gives me a lot of additional ideas.

The reason I am trying to do this in a self-contained way with ESP is I’d like this project I am working on to be usable by anyone with the printer, whether they have a home automation controller like HA or not.
ESPHome makes it stupid-easy to flash ESP devices via browser, and with about $20 USD in parts folks can built a small DC relay controller that turns power on and off for devices.

For example lots of folks install 24vdc lights in their X1 series, and there’s a recirculating carbon air filter called the BentoBox (link on Printables) that can use 24vdc fans. Both can tap in to the 24vdc PS of the Bambu itself so you don’t have to run external power and can keep the wiring completely self-contained.

What I am building is a little-how to on how to use an ESP8266 based device, a tiny buck converter to power the 5v ESP, and a DC relay to control power to the extra fans/lights. Using MQTT I can detect the state of the printer i.e. is it printing something, or not, and based on that state enable/disable power automatically via a virtual switch controlling the relay from the ESP.

This is fairly simple to do with ESPHome and Home Assistant but not everyone is going to have the latter or want it and ESPHome devices can connect and read MQTT text (or numbers) values so…

Anyway that is what I am up to, I am planning to publish a Gist on it to share when I get everything working. Part of the fun for me is learning how to do it, too, so again thanks for the help!

1 Like

Arrgh. @darkorb, you weren’t kidding this is a pain in the rear.

I’m wondering if I’m going about this wrong - currently I’m struggling with how to have the ESPHome device subscribe to the printer’s mqtt topic and parse the json from “device/<printer-serial-#>/report” for print.gcode_status. This looks like it will be way more complicated than it appears and all my current attempts have failed.

Is there a specific topic I can listen for that reflects the status of the printer i.e. if it is printing or not? @WolfwithSword have you found anything?

I know I can import the sensor from Home Assistant, since Node Red handles the tricky bits already, but I’m trying to see if this can work without it.

Pretty sure the only topic that the printer spits out is the report. It sometimes has non-print messages but those are mostly useless. Haven’t snooped it or anything but I think most/all other topics are push-only to the printer.

I may be mistaken but I think with ESPHome you can define all your sensors, then have a single mqtt section with an on_json_message topic listener, then in the “then” have a lambda fill in all your other sensors with id(name)?

Like NR, ESPHome I’m still a bit new to so take my idea with a grain of salt. I’ve got a few basic nodes setup monitoring temp, humidity, particulates, an IR blaster etc, yet I still can’t figure out why a light in ESPHome is broken in HomeAssistant, constantly showing 1% brightness in HA :laughing: Been driving me nuts since forever.

As @WolfwithSword mentioned there is only the one topic for information. The only other one is request which is what the printer is subscribed to itself for taking commands.

The problem I ran into is in part that the ESP Devices went out of memory handling the at least once a second JSON blobs being thrown at it, causing them to crash. Entirely possible it’s because I was doing dumb things, but I figured as I already had the data from MQTT (not NodeRed) and my ESPHome is managed by HA anyway that I’d just cut my losses and do it that way.

Yeah, this is ultimately where I’ve ended up - the ESP can’t handle the unfiltered JSON blobs and crashes. I was making progress using on_json_message and filtering out the gcode status but ultimately it won’t work due to the shear amount of data that gets thrown at it every second.

I’ll use a HA sensor for now. I’m going to make a feature request to Bambu to improve external MQTT, who knows if they’ll listen but worth a try. At the very least give us some basic status topics to subscribe to so we don’t have to jump through hoops.

So I have the latest version of Bambu Studio and I have installed/enabled ‘Go Live’… How are people using that stream inside HA? Do I have to link back to the .sdp file?

Well technically I was able to read the sdp file from VLC on the same machine however when I try to replicate on a different machine, it no worky. I tried updating the ip in the file (127.0.0.1) to the ip running Bambu Studio and where the sdp file is. Also tried the printer’s IP just in case.

I haven’t gotten it running myself, but most people seem to have the RTSP video being streamed either from a custom docker, or using the go-live feature. Both approach require Bambu Studio to be open and video playing in the devices tab, as it doesn’t stream video from the printer, rather the slicer is the host-server for the video. Not sure if anyone has figured out a way to get a stream from the printer without Bambu Studio running. Would love to hear if anyone has gotten it working without needing BS running.

I think thanks to the new go-live update it may be possible to reverse engineer it, at least to setup a docker that doesn’t require bambu-studio to be running and serve as the video stream server.

A side-note for other visualizations we might be able to get, I think I saw during a print hints at messages related to mesh levelling data? Would be great if that’s the case, I think it would be cool to render a 3D display of the mesh levelling data as well. Though I will say I’ve been quite lucky, my bed and sticker-sheets have been pretty consistent in adhesion and flatness - haven’t even used my PEI sheet yet, stickers still rocking!

1 Like

I only run Windows in VMs so it doesn’t bother me to leave Bambu Studio running (assuming it doesn’t crash). I can get it to play in VLC within the same VM but so far I have been unable to stream to any other PCs or VMs. It may be a port/security thing though as I keep things pretty locked down so I’ll keep tinkering with it.

The video is based on a service from https://www.throughtek.com/ - This is the same service used by some others (Wyze). Makes sense to use an off the shelf service.

Might give some directions to go one. As it stands, the negotiation to start the stream goes via that service, which triggers the LAN side to be enabled (including the info about the client/server for UDP NAT punching).

Is almost the summer break for me here, so I’m hoping to have a bit more of a dig into this during that time :upside_down_face:
Edit: Here is some Python that interacts with that service. wyzecam/tutk.py at main · kroo/wyzecam (github.com). The tutk part should be what’s needed, the auth details float around in MQTT.

1 Like