Spoolman Integration

Yes, actually I built a v1.1 in May of 2022 and had a great (challenging) time getting it working well. When the v2 came out, I printed and built it right away. Moggie & team integrated some really awesome changes that makes it MUCH more consistent and MUCH less error prone. It was released at the end of the year 2023, I had it built and running on my printer in late Feb 2024. Because I had already tip-tuned the ERCF v1.1, I just carried that setup forward onto the v2 and didn’t bother with Filametrix (the filament cutter).

I remember going to CES in Las Vegas in 2014 and they had a huge showing of 3d printers that year. So much growth in the industry, I knew it was going to be a rabbit hole I wanted to jump down but I wanted to make sure I didn’t jump in too soon… after nearly a decade I decided it was time. I did some research and found Voron Design and built a Voron 2.4 in 2022 I didn’t have a 3d printer nor experience with one before 2022, it was really my first experience.

I’m a nerdy engineer-type so I’m really into building stuff diy… this is like legos for engineers. Probably the coolest thing was ordering a bunch of random hardware from China, having it arrive in like 30 different boxes over a month, then assembling the whole thing over the course of a few weeks. I prefer the self-sourcing over buying a kit because it seems so crazy to just take parts and put them together into something. There are a bunch of kits available and I think it’s probably cheaper to just buy a kit but I didn’t do that with any of my builds.

Anyway, after I had the printer working, I had the building bug and I needed to build something else – something harder. Everyone who I talk to about 3D printing and Vorons in particular, I say that the ERCF build is like Voron “hard-mode”. I think that’s definitely true for the v1.1 ERCF but the v2 takes much of the pain out if you build it with Filametrix. IMO, the most difficult part of the ERCF was tip-tuning because if you get it wrong, you’ll have a jam and there are so many variables to setting it correctly that it becomes almost trial and error to figure out the right combination! It literally took me like 3 months to get it working consistently on the v1.1 after working out a lot of the hardware problems.

I’d say: if you’re the kind of guy who does NOT want to spend a lot of time tweaking and tuning and playing with settings – you may not want to attempt the ERCF build. If a challenge appeals to you, go for it. Honestly, the cost of entry isn’t too bad – I think you can build a complete ERCF for a few hundred dollars in hardware, which is pretty amazing functionality for the price when you get it working well. I went nuts with my setup and took over a good portion of my guest room with the printing contraption.

Here’s a photo of what it looks like:

Sorry for the response bomb. :stuck_out_tongue: asking me about my printer experience is dangerous. haha

EDIT –
I’ll add one more thing here since this thread is really about spoolman. Happy Hare which is the software that moggieuk wrote to handle the ERCF works AMAZINGLY with Spoolman. If you do build an ERCF you’ll be blown away at how well the system works with Spoolman behind the scenes. Now that I have Home Assistant access to my filament list, I can easily tell what is currently loaded in the buffers! If you want to chat more about it, hit me up with DM.

1 Like

After trying unsuccessfully to get a few other Bambu + Spoolman integrations to update the filament remaining based on the AMS sensor I was finally able to get it up and running the Spoolman + HomeAssistant.

Here’s the automation yaml: gist:556e9a6a99767b530464b16e44d115f2 · GitHub

you need to set the list of possible triggers to your AMS trays, and set the spoolman device ID (alphanumeric id, not the human readable name) in the action variable logic. Then you have to create an extra spool field in Spoolman (I called mine rfid_tag_string). Once that is set up the automation will update the remaining filament in Spoolman any time it notices an AMS change and finds a matching spool in Spoolman.

It’s clunky, but better than my current strategy of never updating an excel spreadsheet. I’d love a more robust solution in the future, particularly one that made it easy to add newly seen spools to Spoolman.

EDIT: As soon as I posted this it clicked. That is the spoolman repo, not the HA integration. All is well now! :man_facepalming:

I am so excited I stumbled on this! It is going to so awesome for my setup.

What am I missing with the adding the repo to HACS? Is this the wrong repo url?

1 Like

There’s now a way to do this using Bambu printers, requires another service running but it doesn’t rely on Bambu opening things up (which they likely won’t do) - GitHub - Rdiger-36/bambulab-ams-spoolman-filamentstatus: This project integrates a Bambulab AMS system with Spoolman to synchronize filament spool usage. It listens for MQTT updates from the printer and updates spool data on Spoolman.

I ran a batch of updates without doing a backup first, so of course now Spoolman isn’t populating sensors. I updated the config, uninstalled / reinstalled the custom integration, and even tried downgrading to 0.6.0 but I’ve lost all my spool sensors.

I do see this in the log: SpoolManCoordinator: Spool with ID ‘63’ has no name or material set. Can’t create sensor. Skipping.

Any help getting this back up and running would be appreciated!

Updating for anyone else in the same boat: the error message was a little misleading, it was filament ID that was missing the material, and a bug in the integration then exited the loop entirely instead of moving on to the next spool. I’ve filed a bug report with the author, but figured I’d post here in case anyone else runs into the same issue before it’s patched.

Hello, I’m working with Spoolman in Home Assistant — I’ve got everything working, but there’s no way to subtract the filament used from the total amount. I tried using the filament_used sensor from Moonraker, but it’s not ideal since it resets to 0 when the print completes or hits 100% status, so the value isn’t stored anywhere. Does anyone have an idea?

I cannot install the Addon via the HACS Repository. Everytime the Installation fails with this Text:

  • Failed to to call /addons/86eb421d_spoolman/install - Docker build failed for 86eb421d/aarch64-addon-spoolman:0.22.1.0 (exit code 1). Build output: #0 building with “default” instance using docker driver #1 [internal] load build definition from Dockerfile #1 transferring dockerfile: #1 transferring dockerfile: 1.11kB 0.0s done #1 DONE 1.2s #2 [internal] load metadata for ghcrio/hassio-addons/base:17.2.1 #2 DONE 0.6s #3 [internal] load .dockerignore #3 transferring context: #3 transferring context: 2B done #3 DONE 0.5s #4 [1/5] FROM ghcrio/hassio-addons/base:17.2.1@sha256:c89be04cfcceca110b3dee4e74b73d3c0df24eb99e75b1d10e1ac9de9251f733 #4 DONE 0.0s #5 [internal] load build context #5 transferring context: 4.21kB done #5 DONE 0.5s #6 [2/5] WORKDIR /var/spoolman #6 CACHED #7 [3/5] COPY ./scripts/install.sh /var/spoolman/install.sh #7 CACHED #8 [4/5] COPY ./scripts/start.sh /var/spoolman/start.sh #8 CACHED #9 [5/5] RUN apk add --no-cache unzip g++ python3-dev libffi-dev py3-pip py3-setuptools py3-wheel py3-pdm-backend py3-virtualenv libpq-dev && source_url=$(curl -s ttps://api.github.com/repos/Donkie/Spoolman/releases/tags/v0.22.1 | jq -r ‘.assets | select(.name == “spoolman.zip”).browser_download_url’) && curl -sSL $source_url -o /tmp/temp.zip && unzip /tmp/temp.zip -d /var/spoolman && touch /var/spoolman/.env && rm /tmp/temp.zip && rm -f -r tests_integration LICENSE README.md Dockerfile && chmod a+x /var/spoolman/install.sh && chmod a+x /var/spoolman/start.sh && bash /var/spoolman/install.sh #9 20.34 fetch ttps://dl-cdn.alpinelinux.org/alpine/v3.21/main/aarch64/APKINDEX.tar.gz #9 20.77 fetch ttps://dl-cdn.alpinelinux.org/alpine/v3.21/community/aarch64/APKINDEX.tar.gz #9 22.71 ERROR: unable to select packages: #9 22.72 libcrypto3-3.3.3-r0: #9 22.72 breaks: openssl-dev-3.3.4-r0[libcrypto3=3.3.4-r0] #9 22.72 satisfies: world[libcrypto3=3.3.3-r0] #9 22.72 libssl3-3.3.3-r0[libcrypto3=3.3.3-r0] #9 22.72 libssl3-3.3.3-r0[so:libcrypto.so.3] #9 22.72 python3-3.12.11-r0[so:libcrypto.so.3] #9 22.72 apk-tools-2.14.6-r3[so:libcrypto.so.3] #9 22.72 libpq-17.5-r0[so:libcrypto.so.3] #9 22.72 libcurl-8.12.1-r0[so:libcrypto.so.3] #9 22.72 libssl3-3.3.3-r0: #9 22.72 breaks: openssl-dev-3.3.4-r0[libssl3=3.3.4-r0] #9 22.72 satisfies: world[libssl3=3.3.3-r0] #9 22.72 python3-3.12.11-r0[libssl3>=3.3.0] #9 22.72 python3-3.12.11-r0[so:libssl.so.3] #9 22.72 python3-pycache-pyc0-3.12.11-r0[libssl3>=3.3.0] #9 22.72 apk-tools-2.14.6-r3[so:libssl.so.3] #9 22.72 libpq-17.5-r0[so:libssl.so.3] #9 22.72 libcurl-8.12.1-r0[so:libssl.so.3] #9 ERROR: process “/bin/ash -o pipefail -c apk add --no-cache unzip g++ python3-dev libffi-dev py3-pip py3-setuptools py3-wheel py3-pdm-backend py3-virtualenv libpq-dev && source_url=$(curl -s ttps://api.github.com/repos/Donkie/Spoolman/releases/tags/${SPOOLMAN_VERSION} | jq -r ‘.assets | select(.name == "spoolman.zip").browser_download_url’) && curl -sSL $source_url -o /tmp/temp.zip && unzip /tmp/temp.zip -d /var/spoolman && touch /var/spoolman/.env && rm /tmp/temp.zip && rm -f -r tests_integration LICENSE README.md Dockerfile && chmod a+x /var/spoolman/install.sh && chmod a+x /var/spoolman/start.sh && bash /var/spoolman/install.sh” did not complete successfully: exit code: 50 ------ > [5/5] RUN apk add --no-cache unzip g++ python3-dev libffi-dev py3-pip py3-setuptools py3-wheel py3-pdm-backend py3-virtualenv libpq-dev && source_url=$(curl -s ttps://api.github.com/repos/Donkie/Spoolman/releases/tags/v0.22.1 | jq -r ‘.assets | select(.name == “spoolman.zip”).browser_download_url’) && curl -sSL $source_url -o /tmp/temp.zip && unzip /tmp/temp.zip -d /var/spoolman && touch /var/spoolman/.env && rm /tmp/temp.zip && rm -f -r tests_integration LICENSE README.md Dockerfile && chmod a+x /var/spoolman/install.sh && chmod a+x /var/spoolman/start.sh && bash /var/spoolman/install.sh: 22.72 libcurl-8.12.1-r0[so:libcrypto.so.3] 22.72 libssl3-3.3.3-r0: 22.72 breaks: openssl-dev-3.3.4-r0[libssl3=3.3.4-r0] 22.72 satisfies: world[libssl3=3.3.3-r0] 22.72 python3-3.12.11-r0[libssl3>=3.3.0] 22.72 python3-3.12.11-r0[so:libssl.so.3] 22.72 python3-pycache-pyc0-3.12.11-r0[libssl3>=3.3.0] 22.72 apk-tools-2.14.6-r3[so:libssl.so.3] 22.72 libpq-17.5-r0[so:libssl.so.3] 22.72 libcurl-8.12.1-r0[so:libssl.so.3] ------ Dockerfile:14 -------------------- 13 | # Install dependencies 14 | >>> RUN apk add --no-cache \ 15 | >>> unzip \ 16 | >>> g++ \ 17 | >>> python3-dev \ 18 | >>> libffi-dev \ 19 | >>> py3-pip \ 20 | >>> py3-setuptools \ 21 | >>> py3-wheel \ 22 | >>> py3-pdm-backend \ 23 | >>> py3-virtualenv \ 24 | >>> libpq-dev \ 25 | >>> && source_url=$(curl -s ttps://api.github.com/repos/Donkie/Spoolman/releases/tags/${SPOOLMAN_VERSION} | jq -r ‘.assets | select(.name == “spoolman.zip”).browser_download_url’) \ 26 | >>> && curl -sSL $source_url -o /tmp/temp.zip \ 27 | >>> && unzip /tmp/temp.zip -d /var/spoolman \ 28 | >>> && touch /var/spoolman/.env \ 29 | >>> && rm /tmp/temp.zip \ 30 | >>> && rm -f -r tests_integration LICENSE README.md Dockerfile \ 31 | >>> && chmod a+x /var/spoolman/install.sh \ 32 | >>> && chmod a+x /var/spoolman/start.sh \ 33 | >>> && bash /var/spoolman/install.sh 34 | -------------------- ERROR: failed to build: failed to solve: process “/bin/ash -o pipefail -c apk add --no-cache unzip g++ python3-dev libffi-dev py3-pip py3-setuptools py3-wheel py3-pdm-backend py3-virtualenv libpq-dev && source_url=$(curl -s ttps://api.github.com/repos/Donkie/Spoolman/releases/tags/${SPOOLMAN_VERSION} | jq -r ‘.assets | select(.name == "spoolman.zip").browser_download_url’) && curl -sSL $source_url -o /tmp/temp.zip && unzip /tmp/temp.zip -d /var/spoolman && touch /var/spoolman/.env && rm /tmp/temp.zip && rm -f -r tests_integration LICENSE README.md Dockerfile && chmod a+x /var/spoolman/install.sh && chmod a+x /var/spoolman/start.sh && bash /var/spoolman/install.sh” did not complete successfully: exit code: 50

@stomb This integration thread is not related to spoolman addon itself. I guess you could get faster help in the GitHub of spoolman.

This addon is exactly what i was looking for.
The obstacle i’m trying to overcome is klipper. I am running Bambulab printer without AMS so that means no klipper and no AMS sensors.

How would i go aabout updating which filament is currently active one? I think i’m looking for some manual drop down menu in HA which would assign custom attribute to spoolman sensors (like ‘active_spool: true’ or something like that).

Because then i could just manually select what’s active any time i change it physically in the printer and then automations would take care of updating filament attributes in spoolman instance.

Did anyone try to figure this out?

I’ve modified the card in github to get it :

type: custom:auto-entities
filter:
  include:
    - integration: "*spoolman*"
      sort:
        method: attribute
        attribute: klipper_active_spool
        reverse: false
      attributes:
        klipper_active_spool: true
      options:
        type: custom:mushroom-template-card
        vertical: false
        icon_color: "#{{ state_attr(entity, 'filament_color_hex') }}"
        icon: mdi:movie-roll
        primary: >
          {{ state_attr(entity, 'filament_material')}} - {{state_attr(entity,
          'filament_vendor_name') }} - {{state_attr(entity, 'filament_name') }}
        secondary: "{{ (state_attr(entity, 'remaining_weight') | float)  | round(2) }} g"
        tap_action:
          action: more-info
sort:
  method: attribute
  attribute: klipper_active_spool
  reverse: true
card:
  type: grid
  columns: 1
  square: false
card_param: cards
grid_options:
  rows: 1
  columns: 12

hello why have conf error in this code?

type: custom:tabbed-card
options: {}
tabs:
  - label: In Stampa / Caricati
    card:
      type: custom:auto-entities
      filter:
        include:
          - integration: "*spoolman*"
            attributes:
              archived: false
            state_filter:
              - operator: template
                value: |
                  {% set trays = states.sensor
                     | selectattr('entity_id', 'search', 'bambu_h2d_ams.*tray')
                     | selectattr('attributes.tag_uid','!=','')
                     | selectattr('attributes.tag_uid','!=',None)
                     | selectattr('attributes.tag_uid','!=','0000000000000000')
                     | list %}
                  {% set this_rfid = state_attr(entity, 'extra_rfid_tag_string2') %}
                  {% set active_trays = trays | selectattr('attributes.active', 'eq', true) | list %}
                  {% set active_match = active_trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length > 0 %}
                  {{ state_attr(entity, 'klipper_active_spool') == true or active_match }}
      options:
        type: custom:mushroom-template-card
        vertical: false
        icon_color: "#{{ state_attr(entity, 'filament_color_hex') }}"
        icon: mdi:printer-3d-nozzle
        card_mod:
          style: >
            {% set trays = states.sensor
               | selectattr('entity_id', 'search', 'bambu_h2d_ams.*tray')
               | selectattr('attributes.tag_uid','!=','')
               | selectattr('attributes.tag_uid','!=',None)
               | selectattr('attributes.tag_uid','!=','0000000000000000')
               | list %}
            {% set this_rfid = state_attr(entity, 'extra_rfid_tag_string2') %}
            {% set active_trays = trays | selectattr('attributes.active', 'eq', true) | list %}
            {% set match_count = trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length %}
            {% set active_match = active_trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length > 0 %}
            {% if active_match %}
              :host {
                background: linear-gradient(135deg, #d4edda, #c3e6cb) !important;
                border: 2px solid #28a745 !important;
                border-radius: 12px !important;
              }
            {% elif match_count > 0 %}
              :host {
                background: linear-gradient(135deg, #fff3cd, #ffeaa7) !important;
                border: 2px solid #ffc107 !important;
                border-radius: 12px !important;
              }
            {% endif %}
        badge_icon: >
          {% set trays = states.sensor
             | selectattr('entity_id', 'search', 'bambu_h2d_ams.*tray')
             | selectattr('attributes.tag_uid','!=','')
             | selectattr('attributes.tag_uid','!=',None)
             | selectattr('attributes.tag_uid','!=','0000000000000000')
             | list %}
          {% set this_rfid = state_attr(entity, 'extra_rfid_tag_string2') %}
          {% set active_trays = trays | selectattr('attributes.active', 'eq', true) | list %}
          {% set match_count = trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length %}
          {% set active_match = active_trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length > 0 %}
          {% if active_match %}
            mdi:play-circle
          {% elif match_count > 0 %}
            mdi:printer-3d
          {% elif state_attr(entity, 'archived') == true %}
            mdi:archive
          {% elif state_attr(entity, 'klipper_active_spool') == true %}
            mdi:check-circle
          {% endif %}
        badge_color: >
          {% set trays = states.sensor
             | selectattr('entity_id', 'search', 'bambu_h2d_ams.*tray')
             | selectattr('attributes.tag_uid','!=','')
             | selectattr('attributes.tag_uid','!=',None)
             | selectattr('attributes.tag_uid','!=','0000000000000000')
             | list %}
          {% set this_rfid = state_attr(entity, 'extra_rfid_tag_string2') %}
          {% set active_trays = trays | selectattr('attributes.active', 'eq', true) | list %}
          {% set match_count = trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length %}
          {% set active_match = active_trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length > 0 %}
          {% if active_match %}
            green
          {% elif match_count > 0 %}
            yellow
          {% elif state_attr(entity, 'archived') == true %}
            orange
          {% elif state_attr(entity, 'klipper_active_spool') == true %}
            green
          {% else %}
            default_color
          {% endif %}
        primary: |
          {% set vendor = state_attr(entity, 'filament_vendor_name') %}
          {% set name = state_attr(entity, 'filament_name') %}
          {% set material = state_attr(entity, 'filament_material') %}
          {% set location = state_attr(entity, 'location') %}
          {% if vendor or name or material or location %}
            {{ vendor if vendor else '' }} {{ name if name else '' }} {{ material if material else '' }} {{ '(' ~ location ~ ')' if location else '' }}
          {% else %}
            Informazioni non disponibili
          {% endif %}
        secondary: "{{ (state_attr(entity, 'remaining_weight') | float) | round(2) }} g"
        tap_action:
          action: more-info
      sort:
        method: attribute
        attribute: filament_color_hex
        numeric: false
        reverse: false
      card:
        square: false
        type: grid
      card_param: cards
      grid_options:
        columns: 48
        rows: auto

  - label: Tutti i Filamenti
    card:
      type: custom:auto-entities
      filter:
        include:
          - integration: "*spoolman*"
            attributes:
              archived: false
      options:
        type: custom:mushroom-template-card
        vertical: false
        icon_color: "#{{ state_attr(entity, 'filament_color_hex') }}"
        icon: mdi:printer-3d-nozzle
        card_mod:
          style: >
            {% set trays = states.sensor
               | selectattr('entity_id', 'search', 'bambu_h2d_ams.*tray')
               | selectattr('attributes.tag_uid','!=','')
               | selectattr('attributes.tag_uid','!=',None)
               | selectattr('attributes.tag_uid','!=','0000000000000000')
               | list %}
            {% set this_rfid = state_attr(entity, 'extra_rfid_tag_string2') %}
            {% set active_trays = trays | selectattr('attributes.active', 'eq', true) | list %}
            {% set match_count = trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length %}
            {% set active_match = active_trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length > 0 %}
            {% if active_match %}
              :host {
                background: linear-gradient(135deg, #d4edda, #c3e6cb) !important;
                border: 2px solid #28a745 !important;
                border-radius: 12px !important;
              }
            {% elif match_count > 0 %}
              :host {
                background: linear-gradient(135deg, #fff3cd, #ffeaa7) !important;
                border: 2px solid #ffc107 !important;
                border-radius: 12px !important;
              }
            {% endif %}
        badge_icon: >
          {% set trays = states.sensor
             | selectattr('entity_id', 'search', 'bambu_h2d_ams.*tray')
             | selectattr('attributes.tag_uid','!=','')
             | selectattr('attributes.tag_uid','!=',None)
             | selectattr('attributes.tag_uid','!=','0000000000000000')
             | list %}
          {% set this_rfid = state_attr(entity, 'extra_rfid_tag_string2') %}
          {% set active_trays = trays | selectattr('attributes.active', 'eq', true) | list %}
          {% set match_count = trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length %}
          {% set active_match = active_trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length > 0 %}
          {% if active_match %}
            mdi:play-circle
          {% elif match_count > 0 %}
            mdi:printer-3d
          {% elif state_attr(entity, 'archived') == true %}
            mdi:archive
          {% elif state_attr(entity, 'klipper_active_spool') == true %}
            mdi:check-circle
          {% endif %}
        badge_color: >
          {% set trays = states.sensor
             | selectattr('entity_id', 'search', 'bambu_h2d_ams.*tray')
             | selectattr('attributes.tag_uid','!=','')
             | selectattr('attributes.tag_uid','!=',None)
             | selectattr('attributes.tag_uid','!=','0000000000000000')
             | list %}
          {% set this_rfid = state_attr(entity, 'extra_rfid_tag_string2') %}
          {% set active_trays = trays | selectattr('attributes.active', 'eq', true) | list %}
          {% set match_count = trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length %}
          {% set active_match = active_trays | selectattr('attributes.tag_uid', 'eq', this_rfid) | list | length > 0 %}
          {% if active_match %}
            green
          {% elif match_count > 0 %}
            yellow
          {% elif state_attr(entity, 'archived') == true %}
            orange
          {% elif state_attr(entity, 'klipper_active_spool') == true %}
            green
          {% else %}
            default_color
          {% endif %}
        primary: |
          {% set vendor = state_attr(entity, 'filament_vendor_name') %}
          {% set name = state_attr(entity, 'filament_name') %}
          {% set material = state_attr(entity, 'filament_material') %}
          {% set location = state_attr(entity, 'location') %}
          {% if vendor or name or material or location %}
            {{ vendor if vendor else '' }} {{ name if name else '' }} {{ material if material else '' }} {{ '(' ~ location ~ ')' if location else '' }}
          {% else %}
            Informazioni non disponibili
          {% endif %}
        secondary: "{{ (state_attr(entity, 'remaining_weight') | float) | round(2) }} g"
        tap_action:
          action: more-info
      sort:
        method: attribute
        attribute: filament_color_hex
        numeric: false
        reverse: false
      card:
        square: false
        type: grid
      card_param: cards
      grid_options:
        columns: 48
        rows: auto

`