Bambu Lab X1 X1C MQTT

Thought I’d share my dashboard and usecases for what I’ve done with extending the NR flow.

Node Red Flow Extensions


In addition to the published one in my gist, I extended it to FTP to the printer and get the 3mf file of the current print job. From this, it extracts both the print preview image, and the total net weight of filament used for the whole file.

I also have a DB schema setup that stores data for my printers and each print. Hooked up with a metering smart plug, I track the total kwh of the printer, and for each print the initial and later final kwh to calculate the amount used. This is multiplied by the electric rate at print time for the electric cost. It also takes in the amount of filament used, and if the print “FAILED”, it will multiply it by the final print progress %. This isn’t an exact measure but it’s better than nothing. It updates the print record on when running, paused, finished or failed.

Both of these keep using the HA integration palette nodes in NR, as it was the easiest way to get current states and detect state changes. I also have to rely on it 100% for some other printers, such as my MonoX resin printer, since I’m using a semi-broken component in HA for it, and nodered doesn’t have a palette for UART-WiFi.

Grafana Dashboard

The data entered into the DB is then used for my Grafana dashboard. First I have a list of all prints and some useful/calculated metrics. Printer name (selectable above, I also have other printers in here such as a resin printer), print start time, file/print name, duration (if in progress, then duration between start time and now), status (FINISH=Success, FAILED=Failed, RUNNING=In Progress, PAUSED=Paused), Kwh of the print (calculated column of final - initial kwh, but if in progress then current kwh - initial kwh), Electric cost is a generated column of electric rate * kwh of print, and if in progress it uses same calc as kwh, and finally Material used which as I mentioned, is the grams of filament for the print, and if failed, multiplied by progress - which is not always perfect but good enough. Previous prints have 0 grams because I didn’t add that until later.

Additionally, I have a column for print type (filament, resin). This also acts as a config column, which sets the unit of the material used column. Grams if filament, ML if resin.

I hope to expand this further by finding a way to match filament/resin types and costs which I’d need to store and likely manually enter for each, as it would span multiple printers and printer types.

Middle row are just electric costs of the selected printer. First is the total cost of idle time + prints, then there’s the cost of all prints combined in the time period, and finally the idle time costs. Idle time seems high because it’s since I installed the metering smart plug, and had run several prints before storing them in this db.

I have a table with one row each per printer for their lifetime kwh, and just update that for the total/idle costs calculations, which means it doesn’t work well with the timerange in grafana. I find it fine since I don’t care too much for finding my “idle costs” in a given timespan, moreso the “print costs” per timespan which does still work. Saves me from a bunch of db writes.

Final barchart is just the table, displayed as a barchart. I have another below it setup horizontally. That’s just for fun but I prefer the table myself, especially when the amount of records increases.

DB Table for prints for those interested. I have some columns I’m not using yet, but are placeholders for if I use them. Additionally, some columns I have in grafana are calculated in their queries (such as material units) or are from other tables related to my printers, but this is the most important one. It isn’t perfect but works for me!

CREATE TABLE IF NOT EXISTS prints (
    id SERIAL,
    start_epoch numeric PRIMARY KEY,
    printer varchar(25) NOT NULL,
    printer_serial varchar(20) NOT NULL,
    name varchar(75) NOT NULL,
    start_time timestamp DEFAULT (now() at time zone 'utc'),
    end_time timestamp,
    initial_kwh numeric DEFAULT 0.0,
    final_kwh numeric DEFAULT 0.0,
    kwh numeric GENERATED ALWAYS AS
  		(
        case WHEN final_kwh = 0.0 THEN 0.0
            ELSE final_kwh - initial_kwh
            end
      ) STORED,
    status varchar(10),
    electric_rate numeric NOT NULL,
    electric_cost numeric GENERATED ALWAYS AS ( 
        case WHEN final_kwh = 0.0 THEN 0.0
            ELSE ((electric_rate / 100) * (final_kwh - initial_kwh))
            end
      ) STORED,
    material_used numeric default 0.0,
    material_type varchar(10) default 'filament',
    material_price numeric default 0.0,
    material_cost numeric GENERATED ALWAYS AS (
        material_used * (material_price / 1000)
    ) STORED,
    material_description varchar(50)
);
HomeAssistant Dashboard

And finally, the HA dashboard. It’s pretty much the same as it was last time I shared it. Power, Speed and Light control. AMS preview (still need to put on temp/humidity icon badges on it just realized :laughing: ), print preview image inside the printer image, dynamic image and icon colour changing based on states, etc.

That’s 80% of everything I wanted out of it, but I can comfortably say that all my needs for monitoring and tracking my usage of the X1C are handled in HomeAssistant. Just need to make it easier to have a camera stream in here, better way to detect filaments (including if not via AMS), and some way to reverse engineer the bambu RFID system (or make my own replacement) then I’ll be (mostly) done :slight_smile:

2 Likes

Would definitely love an intergration

Thanks, worked.

How did you connect via MQTT Explorer to P1P? what user/pass?

Connected with no tls option enabled and deleted standard mqtt explorer topics and add only one mentioned above.

2 Likes

I am new to HA so this might be a newbie question.
I have a basic HAOS setup and everything works fine.
The only thing that is wrong is the fan speed.

Its JSON output shows:

"big_fan1_speed": "10",
"big_fan2_speed": "8",
"heatbreak_fan_speed": "15",

Which in reality correspond to: ( That 80% from the pic is me messing around trying to get the right value)

"big_fan1_speed": 70%,
"big_fan2_speed": 60%,
"heatbreak_fan_speed": 100%,

I am not sure how to convert that into the actual speed.
So 15 correspond to 100%, that is all I know haha
I have tried

{{ value_json.print.big_fan2_speed | int(base=16)*100/16 }}"

Which returns 50% but that is wrong

This is the whole template:

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 %}

Thank you for any help.

Any chance you mind sharing those updates in your NR gist?

2 Likes

The numbers are directly tied to a percent and skips some in the 0-15 range, so the two methods are to either just map the numbers to their flat %, or to do a calculation with a ceiling, i.e.

percentage = (int(speed) / 15) * 100 // get percentage, speed is 0-15 from fan
return math.ceil( percentage / 10) * 10 // ceiling to map to actual percentage, always a multiple of 10

Not sure of the yaml template / jinga2 version of that, but hopefully that’s helpful!

Tossed them into the same gist as a second file, and left a comment for everything you need to setup! One thing to note is the FTP password can’t be set by the flow json, so that node-server-entity will need to be manually edited with the LAN only password. Also mentioned in the Rev 13/14 update comment.

1 Like

For those (like me) who are having trouble putting the pieces together enough to connect to their P1P via MQTT, here’s what worked for me, in the form of images:

Then click Advanced and remove the default topics, replacing with device/YOUR_SERIAL_HERE/report :

Then go back and connect, and you’ll see something like this:

It looks like certain status info can be scraped from strings if desired, such as these:

{
  "mc_print": {
    "param": "[BMC] Print percent:83.00%,0h34m left line=263739",
    "command": "push_info",
    "sequence_id": "80550"
  }
}
01/22/2023 6:14:41 PM 
{"mc_print":{"param":"[AMS][TASK]tray_exist:0x0;tray_read_done:0x0,vailed:0x0,reading:0x0","command":"push_info","sequence_id":"80575"}}
01/22/2023 6:14:41 PM(-0 seconds) 
{"mc_print":{"param":"[AMS][TASK]ams num:0,ams_exist:0x0,tray_now: 255","command":"push_info","sequence_id":"80574"}}
01/22/2023 6:14:41 PM(-0 seconds) 
{"mc_print":{"param":"[AMS][Period]:len=0.000m;buf=0.00;hall_e=20mV,cut=1547mV","command":"push_info","sequence_id":"80573"}}
01/22/2023 6:14:41 PM(-0 seconds) 
{"mc_print":{"param":"[AMS][Period]:bldc_i=0.00,u=0.00,spd=0.00;dw_spd=0.00;bdc_i=0.00,u=0.00,spd=0.00","command":"push_info","sequence_id":"80572"}}
3 Likes

I don’t have a P1P to test but these mc_print messages are pretty much the same as the X1C’s, but the difference is the X1C provides a bulk of information at once in the normal “print” message. Should still be able to scrape most of what you can from these miscellaneous messages, and whenever a value changes in the “print” too.

I wonder if the P1P can also receive commands as well - would be a good way to get some information. For example, if you send this payload to device/{SERIAL}/request:
{"payload": { "info": { "sequence_id": "20004", "command": "get_version" }, "user_id": "1234567890" }}
You should receive back a message giving the software + hardware versions and serial numbers of the various components (printer, some printer submodules, and any ams units). It will be {payload: info {}} and in info, the “command” will be “get_version”.

I’m printing right now on my P1P so lots of reports coming through but sending that request doesn’t seem to be triggering a version report back on the /report topic. I’ll try again once my print is done.

Actually I think it is working. Shortly after I send it I get a bunch of [SYS] responses that aren’t a normal part of the constant spew. E.g.:

{"mc_print":{"param":"[SYS] Dev[uart0]fw_cnt1172 tx=1097431 rx=15072 dis=1039 he=0 pe=1","command":"push_info","sequence_id":"8817"}}
{"mc_print":{"param":"[SYS] Task GcodeRunner cpu.max=1263.6540 stk.max=0x2000773c.0x20006e54 stkSz=2816 stkFr=972","command":"push_info","sequence_id":"8815"}}
{"mc_print":{"param":"[SYS] Task main cpu.max=5421.6777 stk.max=0x20000efc.0x20000010 stkSz=4096 stkFr=1840","command":"push_info","sequence_id":"8814"}}

AMS humidity is available there too which was something I hoped to be able to access in home assistant:

{"mc_print":{"param":"[AMS][TASK]ams0 temp:26.9;humidity:23%;humidity_idx:4","command":"push_info","sequence_id":"8839"}}

And turning the light on and off generates this report data:

{"print":{"bed_temper":46.5625,"nozzle_temper":39.875,"wifi_signal":"-60dBm","lights_report":[{"node":"chamber_light","mode":"on"}],"command":"push_status","msg":1,"sequence_id":"3319"}}
{"print":{"bed_temper":46.0625,"nozzle_temper":39.375,"wifi_signal":"-60dBm","lights_report":[{"node":"chamber_light","mode":"off"}],"command":"push_status","msg":1,"sequence_id":"3331"}}

LED control works by sending the already discovered commands to the request topic:

{"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"}
{"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"}

So that means led state and control can be enabled in home assistant. That’s one of my top hopes to automate - turning it on when a print starts and off when it finishes :slight_smile:

The camera record on/off commands seem to work:

{"camera":{"command":"ipcam_record_set","control":"enable","sequence_id":"20008","result":"SUCCESS"}}
2 Likes

It’s strange it didn’t toss back the info / version report - i’d expect those sys messages to randomly come through like on the X1C.

Toggling the light would be a change in the common “print” message, which would force it to send an update. I’d assume changing speed profile either via printer/slicer or mqtt would do the same too.

I wonder how the AMS filament change/set commands would report back for the P1P. I wonder how you might be able to get the status of the AMS trays back, which is in use, colour etc. From the miscellaneous messages I think you can get tray_now, humidity, humidity_level, temperature, and a few smaller things.

Did not know about the ipcam_record_set control message yet. Interesting. That’s for the full-on recording right and not timelapse? With that being a separate command? I know I’ve somehow a month or two ago managed to make the printer do a full recording of a 2 hour print instead and still have no clue how :laughing:

On the P1P ‘full’ video recording can be turned on and off in the settings on the lcd screen. ‘Full’ in this case is 2-3 minutes snippets of 0.5fps camera sped up to 10fps. So each video is 40-60 minutes of wallclock.

the HMS codes are here, and they seem to map hexadecimal

your examples:

attr: 50336000 = in HEX signed 2's 03001100
code: 131074 = in HEX signed 2's : 00020002

Doesnt map.

attr: 50335744 = in HEX signed 2's 03001000
code: 131074 = in HEX signed 2's : 00020002

Doesnt map.

But…

attr: 201327360 = in HEX signed 2's 0C000300 
code: 131074 = in HEX signed 2's : 00020002

== HMS_0C00_0300_0002_0002: First layer inspection terminated due to abnormal lidar data

So I guess the internal HMS codes are a bunch of statuscodes as reference, but only the errors are linked to troubleshooting, and/or only the ones we as consumers can troubleshoot are posted online.

Would be a nice feature if that part of the integration converted the errorcode to the correct link on the bambu wiki:

https://wiki.bambulab.com/en/x1/troubleshooting/hmscode/HMS_ {{ATTR2HEX}}_{{CODE2HEX}}
1 Like

That’s great! I think for the nodered flow I will add in these mappings and maybe add as an attribute to the HMS code value, the link to the wiki for each one.

I’ll work on this soon!

Edit: Forgot the HMS values in the print messages is an array. Don’t want to have sensor bloat so I think just having a single sensor for HMS codes, with value being number of hms codes there are, and the attributes is just a list of the URLS for the codes - no descriptions.

Updated the NodeRed flow with the HMS code translations.

When you have HMS errors, there will be a new sensor called “HMS Errors”. The value is the count of errors, and if >0, the attributes will be mapped as showing the error code and the appropriate wiki link for the X1 HMS error - not all HMS errors have links though, so be warned.

I also added buttons for Pause/Resume/Stop a print that are only available when appropriate (pause on when running, resume only when paused, stop only when printing - running or paused). image

Note: If you pause a print, there is a chance the mobile app will give you a notif that it was “paused with error”. No clue why, it’s the generic pause message that I send it…

Edit: Here’s a screenshot of the HMS Errors - lucky me, got a z axis homing error randomly to help show it! Unfortunately it’s one of the codes that does not have a wiki page. image

2 Likes

Requeriments (HACS or manual installation):