diff --git a/.ha_mcp/backups/automation.1791477238729.20261009_003247.yaml b/.ha_mcp/backups/automation.1791477238729.20261009_003247.yaml
new file mode 100644
index 00000000..5a3a5fa9
--- /dev/null
+++ b/.ha_mcp/backups/automation.1791477238729.20261009_003247.yaml
@@ -0,0 +1,140 @@
+# ha_mcp_backup
+schema_version: 1
+domain: automation
+entity_id: '1791477238729'
+captured: '2026-10-09T00:32:47.977680+00:00'
+tool: ha_config_set_automation
+config:
+ id: '1791477238729'
+ alias: SparkX i7 - Deduct filament in Spoolman on print complete
+ description: On print completion, deducts the final segment (used_material_length
+ delta since last checkpoint x g/cm) from the Spoolman spool matching the latched
+ slot color (remaining weight > 0 guard), accumulates it per slot, then sends a
+ summary notification with total and per-slot breakdown whenever anything was deducted.
+ Silent when total is 0. Clears the latch and re-baselines the checkpoint.
+ triggers:
+ - trigger: state
+ entity_id: sensor.sparkx_i7_print_status
+ from: printing
+ to: completed
+ conditions: []
+ actions:
+ - variables:
+ old_slot: '{{ states(''input_text.sparkx_i7_print_spool_latch'') }}'
+ cur: '{{ states(''sensor.sparkx_i7_used_material_length'') | float(0) }}'
+ checkpoint: '{{ states(''input_number.sparkx_i7_spoolman_length_checkpoint'')
+ | float(0) }}'
+ - variables:
+ final_grams: '{{ (([cur | float - (checkpoint | float), 0] | max) * (states(''input_number.sparkx_i7_filament_g_cm'')
+ | float(0.03))) | round(2) }}'
+ color: '{{ states(''sensor.sparkx_i7_cfs_'' ~ (old_slot | lower | replace(''
+ '', ''_'')) ~ ''_color'') | replace(''#'', '''') | lower }}'
+ - variables:
+ spool_id: '{% set ns = namespace(id='''') %}{% for e in integration_entities(''spoolman'')
+ %}{% if e.endswith(''_color_hex'') and (states(e) | replace(''#'', '''') |
+ lower) == color and (state_attr(e[:-10], ''location'') | default('''', true)
+ | lower) == ''cfs lite'' %}{% set b = e[:-10] %}{% if (states(b) | float(0))
+ > 0 and ns.id == '''' %}{% set ns.id = states(b ~ ''_id'') %}{% endif %}{%
+ endif %}{% endfor %}{{ ns.id }}'
+ - variables:
+ helper_idx: '{{ old_slot.split('' '')[-1] if old_slot.split('' '')[-1] in [''1'',
+ ''2'', ''3'', ''4''] else '''' }}'
+ - variables:
+ nonhelper_g: '{{ final_grams if (final_grams | float > 0) and (spool_id not
+ in ['''', ''unknown'', ''unavailable'']) and (helper_idx == '''') else 0 }}'
+ - if:
+ condition: template
+ value_template: '{{ final_grams | float > 0 }}'
+ then:
+ - choose:
+ - conditions:
+ condition: template
+ value_template: '{{ spool_id not in ['''', ''unknown'', ''unavailable'']
+ }}'
+ sequence:
+ - action: spoolman.use_spool_filament
+ data:
+ id: '{{ spool_id | int(0) }}'
+ use_weight: '{{ final_grams | float(0) }}'
+ - if:
+ condition: template
+ value_template: '{{ helper_idx != '''' }}'
+ then:
+ - action: input_number.set_value
+ target:
+ entity_id: input_number.sparkx_i7_slot_{{ helper_idx }}_grams
+ data:
+ value: '{{ (states(''input_number.sparkx_i7_slot_'' ~ helper_idx ~ ''_grams'')
+ | float(0)) + (final_grams | float) }}'
+ default:
+ - action: notify.persistent_notification
+ data:
+ title: 'Spoolman: no spool for slot color'
+ message: 'Print completed: final segment of {{ final_grams }} g from {{
+ old_slot if old_slot else ''the active slot'' }} (color #{{ color | upper
+ }}) could not be deducted - no Spoolman spool with remaining weight matches
+ that color. Register the new roll in Spoolman, then adjust manually if
+ needed.'
+ - action: notify.mobile_app_francos_ipad
+ data:
+ title: 'Spoolman: no spool for slot color'
+ message: 'Print completed: final segment of {{ final_grams }} g from {{
+ old_slot if old_slot else ''the active slot'' }} (color #{{ color | upper
+ }}) could not be deducted - no Spoolman spool with remaining weight matches
+ that color. Register the new roll in Spoolman, then adjust manually if
+ needed.'
+ - action: notify.mobile_app_sm_f956w
+ data:
+ title: 'Spoolman: no spool for slot color'
+ message: 'Print completed: final segment of {{ final_grams }} g from {{
+ old_slot if old_slot else ''the active slot'' }} (color #{{ color | upper
+ }}) could not be deducted - no Spoolman spool with remaining weight matches
+ that color. Register the new roll in Spoolman, then adjust manually if
+ needed.'
+ - variables:
+ summary_total: '{% set ns = namespace(t = 0) %}{% for n in [''1'', ''2'', ''3'',
+ ''4''] %}{% set g = states(''input_number.sparkx_i7_slot_'' ~ n ~ ''_grams'')
+ | float(0) %}{% if g > 0 %}{% set ns.t = ns.t + g %}{% endif %}{% endfor %}{{
+ (ns.t + (nonhelper_g | float)) | round(1) }}'
+ - variables:
+ summary_lines: '{% set ns = namespace(l = []) %}{% for n in [''1'', ''2'', ''3'',
+ ''4''] %}{% set g = states(''input_number.sparkx_i7_slot_'' ~ n ~ ''_grams'')
+ | float(0) %}{% if g > 0 %}{% set ns.l = ns.l + [''Slot '' ~ n ~ '' ('' ~
+ states(''sensor.sparkx_i7_slot_'' ~ n ~ ''_color_name'') ~ ''): '' ~ (g |
+ round(1)) ~ '' g''] %}{% endif %}{% endfor %}{% if nonhelper_g | float > 0
+ %}{% set ns.l = ns.l + [(old_slot if old_slot else ''Unlatched'') ~ '': ''
+ ~ (nonhelper_g | float | round(1)) ~ '' g''] %}{% endif %}{{ ns.l | join(''\n'')
+ }}'
+ - if:
+ condition: template
+ value_template: '{{ summary_total | float > 0 }}'
+ then:
+ - action: notify.persistent_notification
+ data:
+ title: Print complete - Spoolman updated
+ message: 'Print complete - Spoolman deducted {{ summary_total }} g
+
+ {{ summary_lines }}'
+ - action: notify.mobile_app_francos_ipad
+ data:
+ title: Print complete - Spoolman updated
+ message: 'Print complete - Spoolman deducted {{ summary_total }} g
+
+ {{ summary_lines }}'
+ - action: notify.mobile_app_sm_f956w
+ data:
+ title: Print complete - Spoolman updated
+ message: 'Print complete - Spoolman deducted {{ summary_total }} g
+
+ {{ summary_lines }}'
+ - action: input_number.set_value
+ target:
+ entity_id: input_number.sparkx_i7_spoolman_length_checkpoint
+ data:
+ value: '{{ cur }}'
+ - action: input_text.set_value
+ target:
+ entity_id: input_text.sparkx_i7_print_spool_latch
+ data:
+ value: ''
+ mode: single
diff --git a/.ha_mcp/backups/automation.1791477238762.20261009_003247.yaml b/.ha_mcp/backups/automation.1791477238762.20261009_003247.yaml
new file mode 100644
index 00000000..5c3617d7
--- /dev/null
+++ b/.ha_mcp/backups/automation.1791477238762.20261009_003247.yaml
@@ -0,0 +1,151 @@
+# ha_mcp_backup
+schema_version: 1
+domain: automation
+entity_id: '1791477238762'
+captured: '2026-10-09T00:32:47.861324+00:00'
+tool: ha_config_set_automation
+config:
+ id: '1791477238762'
+ alias: SparkX i7 - Latch print spool for Spoolman
+ description: 'Segment tracking for Spoolman: at print start resets the length checkpoint
+ and per-slot gram counters and latches the active CFS slot. On every slot change
+ mid-print, deducts the exact extrusion of the finished segment (used_material_length
+ delta x g/cm) from the Spoolman spool matching the previous slot color hex (only
+ if that spool has remaining weight), accumulates grams per slot, notifies on no-match,
+ then re-baselines the checkpoint and advances the latch.'
+ triggers:
+ - trigger: state
+ entity_id: sensor.sparkx_i7_print_status
+ to: printing
+ id: print_started
+ - trigger: state
+ entity_id: sensor.sparkx_i7_active_filament_slot
+ id: slot_changed
+ conditions:
+ - condition: state
+ entity_id: sensor.sparkx_i7_print_status
+ state: printing
+ actions:
+ - variables:
+ new_slot: '{{ states(''sensor.sparkx_i7_active_filament_slot'') }}'
+ old_slot: '{{ states(''input_text.sparkx_i7_print_spool_latch'') }}'
+ cur: '{{ states(''sensor.sparkx_i7_used_material_length'') | float(0) }}'
+ - choose:
+ - conditions:
+ condition: trigger
+ id: print_started
+ sequence:
+ - action: input_number.set_value
+ target:
+ entity_id: input_number.sparkx_i7_spoolman_length_checkpoint
+ data:
+ value: '{{ cur }}'
+ - action: input_number.set_value
+ target:
+ entity_id:
+ - input_number.sparkx_i7_slot_1_grams
+ - input_number.sparkx_i7_slot_2_grams
+ - input_number.sparkx_i7_slot_3_grams
+ - input_number.sparkx_i7_slot_4_grams
+ data:
+ value: 0
+ - action: input_text.set_value
+ target:
+ entity_id: input_text.sparkx_i7_print_spool_latch
+ data:
+ value: '{{ new_slot if new_slot not in [''unknown'', ''unavailable''] else
+ '''' }}'
+ - conditions:
+ condition: template
+ value_template: '{{ old_slot not in ['''', ''unknown'', ''unavailable''] and
+ new_slot not in ['''', ''unknown'', ''unavailable''] and new_slot != old_slot
+ }}'
+ sequence:
+ - variables:
+ grams: '{{ (([cur | float - (states(''input_number.sparkx_i7_spoolman_length_checkpoint'')
+ | float(0)), 0] | max) * (states(''input_number.sparkx_i7_filament_g_cm'')
+ | float(0.03))) | round(2) }}'
+ color: '{{ states(''sensor.sparkx_i7_cfs_'' ~ (old_slot | lower | replace(''
+ '', ''_'')) ~ ''_color'') | replace(''#'', '''') | lower }}'
+ - variables:
+ spool_id: '{% set ns = namespace(id='''') %}{% for e in integration_entities(''spoolman'')
+ %}{% if e.endswith(''_color_hex'') and (states(e) | replace(''#'', '''')
+ | lower) == color and (state_attr(e[:-10], ''location'') | default('''',
+ true) | lower) == ''cfs lite'' %}{% set b = e[:-10] %}{% if (states(b)
+ | float(0)) > 0 and ns.id == '''' %}{% set ns.id = states(b ~ ''_id'')
+ %}{% endif %}{% endif %}{% endfor %}{{ ns.id }}'
+ - if:
+ condition: template
+ value_template: '{{ grams | float > 0 }}'
+ then:
+ - choose:
+ - conditions:
+ condition: template
+ value_template: '{{ spool_id not in ['''', ''unknown'', ''unavailable'']
+ }}'
+ sequence:
+ - action: spoolman.use_spool_filament
+ data:
+ id: '{{ spool_id | int(0) }}'
+ use_weight: '{{ grams | float(0) }}'
+ - if:
+ condition: template
+ value_template: '{{ old_slot.split('' '')[-1] in [''1'', ''2'', ''3'',
+ ''4''] }}'
+ then:
+ - action: input_number.set_value
+ target:
+ entity_id: input_number.sparkx_i7_slot_{{ old_slot.split(' ')[-1]
+ }}_grams
+ data:
+ value: '{{ (states(''input_number.sparkx_i7_slot_'' ~ old_slot.split(''
+ '')[-1] ~ ''_grams'') | float(0)) + (grams | float) }}'
+ default:
+ - action: notify.persistent_notification
+ data:
+ title: 'Spoolman: no spool for slot color'
+ message: 'Segment of {{ grams }} g from {{ old_slot }} (color #{{ color
+ | upper }}) could not be deducted - no Spoolman spool with remaining
+ weight matches that color. Register the new roll in Spoolman, then
+ adjust manually if needed.'
+ - action: notify.mobile_app_francos_ipad
+ data:
+ title: 'Spoolman: no spool for slot color'
+ message: 'Segment of {{ grams }} g from {{ old_slot }} (color #{{ color
+ | upper }}) could not be deducted - no Spoolman spool with remaining
+ weight matches that color. Register the new roll in Spoolman, then
+ adjust manually if needed.'
+ - action: notify.mobile_app_sm_f956w
+ data:
+ title: 'Spoolman: no spool for slot color'
+ message: 'Segment of {{ grams }} g from {{ old_slot }} (color #{{ color
+ | upper }}) could not be deducted - no Spoolman spool with remaining
+ weight matches that color. Register the new roll in Spoolman, then
+ adjust manually if needed.'
+ - action: input_number.set_value
+ target:
+ entity_id: input_number.sparkx_i7_spoolman_length_checkpoint
+ data:
+ value: '{{ cur }}'
+ - action: input_text.set_value
+ target:
+ entity_id: input_text.sparkx_i7_print_spool_latch
+ data:
+ value: '{{ new_slot }}'
+ - conditions:
+ condition: template
+ value_template: '{{ old_slot in ['''', ''unknown'', ''unavailable''] and new_slot
+ not in ['''', ''unknown'', ''unavailable''] }}'
+ sequence:
+ - action: input_number.set_value
+ target:
+ entity_id: input_number.sparkx_i7_spoolman_length_checkpoint
+ data:
+ value: '{{ cur }}'
+ - action: input_text.set_value
+ target:
+ entity_id: input_text.sparkx_i7_print_spool_latch
+ data:
+ value: '{{ new_slot }}'
+ mode: queued
+ max: 10
diff --git a/.ha_mcp/backups/dashboard.sparkx-i7.20261009_003247.yaml b/.ha_mcp/backups/dashboard.sparkx-i7.20261009_003247.yaml
new file mode 100644
index 00000000..a0a9ed8e
--- /dev/null
+++ b/.ha_mcp/backups/dashboard.sparkx-i7.20261009_003247.yaml
@@ -0,0 +1,1286 @@
+# ha_mcp_backup
+schema_version: 1
+domain: dashboard
+entity_id: sparkx-i7
+captured: '2026-10-09T00:32:47.610326+00:00'
+tool: ha_config_set_dashboard
+config:
+ views:
+ - max_columns: 4
+ path: print
+ sections:
+ - cards:
+ - bed: sensor.sparkx_i7_bed_temperature
+ camera: camera.sparkx_i7_printer_camera
+ grid_options:
+ columns: full
+ layer: sensor.sparkx_i7_working_layer
+ light: light.sparkx_i7_light
+ name: SparkX i7
+ nozzle: sensor.sparkx_i7_nozzle_temperature
+ pause_btn: button.sparkx_i7_pause_print
+ progress: sensor.sparkx_i7_print_progress
+ resume_btn: button.sparkx_i7_resume_print
+ status: sensor.sparkx_i7_print_status
+ stop_btn: button.sparkx_i7_stop_print
+ time_left: sensor.sparkx_i7_print_time_left
+ total_layers: sensor.sparkx_i7_total_layers
+ type: custom:k-printer-card
+ - grid_options:
+ columns: full
+ type: custom:webrtc-camera
+ ui: true
+ url: octoprint
+ - grid_options:
+ columns: full
+ type: custom:webrtc-camera
+ ui: true
+ url: octoprint2
+ - camera_image: camera.sparkx_i7_printer_camera
+ camera_view: live
+ entity: camera.sparkx_i7_printer_camera
+ fit_mode: cover
+ grid_options:
+ columns: full
+ show_name: true
+ show_state: true
+ type: picture-entity
+ - cards:
+ - grid_options:
+ columns: 6
+ image_entity: image.sparkx_i7_current_print_preview
+ show_name: false
+ type: picture
+ - entity: sensor.sparkx_i7_active_filament_slot
+ icon: mdi:palette
+ icon_color: '{% set color = state_attr(''sensor.sparkx_i7_active_filament_slot'',
+ ''color'') | default('''') | string %}{% set core = color | replace(''#'',
+ '''') %}{% if core | length >= 6 %}#{{ core[-6:] }}{% else %}var(--primary-text-color){%
+ endif %}'
+ multiline_secondary: true
+ name: Active Color
+ primary: '{% set slot = states(''sensor.sparkx_i7_active_filament_slot'')
+ %}{% if ''Slot 1'' in slot %}{{ states(''sensor.sparkx_i7_slot_1_color_name'')
+ }}{% elif ''Slot 2'' in slot %}{{ states(''sensor.sparkx_i7_slot_2_color_name'')
+ }}{% elif ''Slot 3'' in slot %}{{ states(''sensor.sparkx_i7_slot_3_color_name'')
+ }}{% elif ''Slot 4'' in slot %}{{ states(''sensor.sparkx_i7_slot_4_color_name'')
+ }}{% elif slot == ''External'' %}{{ states(''sensor.sparkx_i7_external_color_name'')
+ }}{% else %}Unknown{% endif %}'
+ secondary: "{% set slot = states('sensor.sparkx_i7_active_filament_slot')\
+ \ %}{{ slot }}{% if state_attr('sensor.sparkx_i7_active_filament_slot',\
+ \ 'filament') %} \xB7 {{ state_attr('sensor.sparkx_i7_active_filament_slot',\
+ \ 'filament') }}{% endif %}"
+ type: custom:mushroom-template-card
+ grid_options:
+ columns: full
+ type: horizontal-stack
+ - entity: sensor.sparkx_i7_print_status
+ grid_options:
+ columns: 12
+ icon: mdi:state-machine
+ name: Status
+ type: tile
+ - entity: sensor.sparkx_i7_position_z_2_decimals
+ grid_options:
+ columns: 4
+ icon: mdi:axis-z-arrow
+ name: Current Z
+ type: tile
+ - entity: sensor.sparkx_i7_position_x
+ grid_options:
+ columns: 4
+ icon: mdi:axis-x-arrow
+ name: X
+ type: tile
+ - entity: sensor.sparkx_i7_position_y
+ grid_options:
+ columns: 4
+ icon: mdi:axis-y-arrow
+ name: Y
+ type: tile
+ - entity: sensor.sparkx_i7_print_status
+ grid_options:
+ columns: 6
+ icon: mdi:message-alert-outline
+ multiline_secondary: true
+ name: Printer Message
+ primary: '{{ states("sensor.sparkx_i7_print_status") | replace("_", " ") |
+ title }}'
+ secondary: '{% set err = state_attr("sensor.sparkx_i7_print_status", "err")
+ or {} %}{% set code = err.get("errcode", 0) | int(0) %}{% set msg = err.get("value",
+ "") %}{% if state_attr("sensor.sparkx_i7_print_status", "paused_flag") |
+ default(false) %}Print paused{% elif msg %}ERROR {{ code }}: {{ msg }}{%
+ elif code %}Printer error {{ code }}{% else %}No printer error{% endif %}'
+ type: custom:mushroom-template-card
+ - entity: sensor.sparkx_i7_print_status
+ grid_options:
+ columns: 6
+ icon: mdi:file-document-outline
+ multiline_secondary: true
+ name: Current File
+ primary: Current File
+ secondary: '{% set file = state_attr("sensor.sparkx_i7_print_status", "file")
+ or "" %}{% if file %}{{ file.split("/")[-1] | replace(".gcode", "") }}{%
+ else %}No active file{% endif %}'
+ type: custom:mushroom-template-card
+ type: grid
+ - cards:
+ - heading: Print Progress
+ icon: mdi:progress-clock
+ type: heading
+ - entity: sensor.sparkx_i7_print_progress
+ grid_options:
+ columns: 6
+ rows: 3
+ max: 100
+ min: 0
+ name: Progress
+ severity:
+ green: 0
+ red: 90
+ yellow: 40
+ type: gauge
+ unit: '%'
+ - grid_options:
+ columns: 6
+ icon: mdi:clock-start
+ multiline_secondary: true
+ name: Elapsed
+ primary: Elapsed
+ secondary: '{% set s = states(''sensor.sparkx_i7_print_job_time'') | float(0)
+ %}{% set h = (s / 3600) | int %}{% set m = ((s % 3600) / 60) | int %}{%
+ if h %}{{ h }} hr {{ m }} min{% else %}{{ m }} min{% endif %}'
+ type: custom:mushroom-template-card
+ - grid_options:
+ columns: 6
+ icon: mdi:clock-end
+ multiline_secondary: true
+ name: Remaining
+ primary: Remaining
+ secondary: '{% set s = states(''sensor.sparkx_i7_print_time_left'') | float(-1)
+ %}{% if s <= 0 %}No estimate{% else %}{% set h = (s / 3600) | int %}{% set
+ m = ((s % 3600) / 60) | int %}{% if h %}{{ h }} hr {{ m }} min{% else %}{{
+ m }} min{% endif %}{% endif %}'
+ type: custom:mushroom-template-card
+ - grid_options:
+ columns: 6
+ icon: mdi:clock-outline
+ multiline_secondary: true
+ primary: Est. Finish
+ secondary: "{% set left = states('sensor.sparkx_i7_print_time_left') | int(0)\
+ \ %}{% if left > 0 %}{{ (now() + timedelta(seconds=left)).strftime('%I:%M\
+ \ %p').lstrip('0') }}{% else %}\u2014{% endif %}"
+ type: custom:mushroom-template-card
+ - grid_options:
+ columns: 6
+ icon: mdi:layers
+ multiline_secondary: true
+ name: Layer
+ primary: Layer
+ secondary: '{% set c = states(''sensor.sparkx_i7_working_layer'') | int(0)
+ %}{% set t = states(''sensor.sparkx_i7_total_layers'') | int(0) %}{{ c }}
+ of {{ t }}'
+ type: custom:mushroom-template-card
+ - entity: light.sparkx_i7_light
+ grid_options:
+ columns: 6
+ icon: mdi:lightbulb
+ name: Printer Light
+ type: tile
+ - detail: 1
+ entity: sensor.sparkx_i7_print_progress
+ graph: line
+ grid_options:
+ columns: full
+ hours_to_show: 24
+ name: Progress History
+ type: sensor
+ - entity: number.sparkx_i7_print_tuning
+ grid_options:
+ columns: 6
+ icon: mdi:speedometer
+ name: Print Tuning
+ type: tile
+ - entity: sensor.sparkx_i7_print_speed
+ grid_options:
+ columns: 6
+ icon: mdi:speedometer
+ name: Speed
+ type: tile
+ - entity: sensor.sparkx_i7_real_time_flow
+ grid_options:
+ columns: 6
+ icon: mdi:cube-send
+ name: Real-Time Flow
+ type: tile
+ - entity: sensor.sparkx_i7_flow_rate
+ grid_options:
+ columns: 6
+ icon: mdi:chart-gantt
+ name: Flow Rate
+ type: tile
+ - entity: sensor.sparkx_i7_current_object
+ grid_options:
+ columns: 6
+ icon: mdi:cube-outline
+ name: Object
+ type: tile
+ - entity: sensor.sparkx_i7_object_count
+ grid_options:
+ columns: 6
+ icon: mdi:counter
+ name: Object Count
+ type: tile
+ type: grid
+ - cards:
+ - heading: Temperatures
+ icon: mdi:thermometer
+ type: heading
+ - entity: sensor.sparkx_i7_nozzle_temperature
+ grid_options:
+ columns: 4
+ max: 300
+ min: 0
+ name: Nozzle
+ severity:
+ green: 0
+ red: 240
+ yellow: 180
+ type: gauge
+ unit: "\xB0C"
+ - entity: sensor.sparkx_i7_bed_temperature
+ grid_options:
+ columns: 4
+ max: 100
+ min: 0
+ name: Bed
+ severity:
+ green: 0
+ red: 100
+ yellow: 60
+ type: gauge
+ unit: "\xB0C"
+ - entity: number.sparkx_i7_nozzle_target
+ grid_options:
+ columns: 6
+ icon: mdi:printer-3d-nozzle
+ name: Nozzle Target
+ type: tile
+ - entity: number.sparkx_i7_bed_target
+ grid_options:
+ columns: 6
+ icon: mdi:radiator
+ name: Bed Target
+ type: tile
+ - entities:
+ - color: '#ef5350'
+ entity: sensor.sparkx_i7_nozzle_temperature
+ name: Actual
+ - color: '#4caf50'
+ entity: number.sparkx_i7_nozzle_target
+ name: Target
+ expand_legend: true
+ grid_options:
+ columns: full
+ hours_to_show: 24
+ show_names: true
+ title: Nozzle Temperature
+ type: history-graph
+ - entities:
+ - color: '#42a5f5'
+ entity: sensor.sparkx_i7_bed_temperature
+ name: Actual
+ - color: '#4caf50'
+ entity: number.sparkx_i7_bed_target
+ name: Target
+ expand_legend: true
+ grid_options:
+ columns: full
+ hours_to_show: 24
+ show_names: true
+ title: Bed Temperature
+ type: history-graph
+ type: grid
+ - cards:
+ - heading: Controls
+ icon: mdi:gesture-tap-button
+ type: heading
+ - entity: button.sparkx_i7_pause_print
+ grid_options:
+ columns: 4
+ icon: mdi:pause
+ name: Pause
+ tap_action:
+ action: perform-action
+ perform_action: button.press
+ target:
+ entity_id: button.sparkx_i7_pause_print
+ type: tile
+ - entity: button.sparkx_i7_resume_print
+ grid_options:
+ columns: 4
+ icon: mdi:play
+ name: Resume
+ tap_action:
+ action: perform-action
+ perform_action: button.press
+ target:
+ entity_id: button.sparkx_i7_resume_print
+ type: tile
+ - entity: button.sparkx_i7_reconnect
+ grid_options:
+ columns: 4
+ icon: mdi:lan-connect
+ name: Connect
+ tap_action:
+ action: perform-action
+ perform_action: button.press
+ target:
+ entity_id: button.sparkx_i7_reconnect
+ type: tile
+ - entity: button.sparkx_i7_stop_print
+ grid_options:
+ columns: 4
+ icon: mdi:cancel
+ name: Cancel Print
+ tap_action:
+ action: perform-action
+ perform_action: button.press
+ target:
+ entity_id: button.sparkx_i7_stop_print
+ type: tile
+ - entity: button.sparkx_i7_home_xy_then_z
+ grid_options:
+ columns: 4
+ icon: mdi:home-circle
+ name: Home
+ tap_action:
+ action: perform-action
+ perform_action: button.press
+ target:
+ entity_id: button.sparkx_i7_home_xy_then_z
+ type: tile
+ type: grid
+ - cards:
+ - heading: Filament
+ icon: mdi:printer-3d-nozzle-outline
+ type: heading
+ - entity: sensor.sparkx_i7_filament_weight_estimated
+ grid_options:
+ columns: 6
+ icon: '{% set st = states(''sensor.sparkx_i7_print_status'') %}{% if st in
+ [''printing'', ''paused''] %}mdi:printer-3d-nozzle{% else %}mdi:calendar-today{%
+ endif %}'
+ multiline_secondary: true
+ name: Filament Used
+ primary: '{% set st = states(''sensor.sparkx_i7_print_status'') %}{% if st
+ in [''printing'', ''paused''] %}{{ states(''sensor.sparkx_i7_filament_weight_estimated'')
+ | round(1) }} g{% else %}No active job{% endif %}'
+ secondary: "{% set st = states('sensor.sparkx_i7_print_status') %}{% if st\
+ \ in ['printing', 'paused'] %}{{ states('sensor.sparkx_i7_used_material_length')\
+ \ | round(0) }} cm this job{% else %}{{ states('sensor.sparkx_i7_filament_weight_daily')\
+ \ | round(1) }} g \xB7 {{ states('sensor.sparkx_i7_filament_cost_daily')\
+ \ | round(2) }} $ today{% endif %}"
+ type: custom:mushroom-template-card
+ - entity: input_number.sparkx_i7_filament_g_cm
+ grid_options:
+ columns: 6
+ icon: mdi:weight-kilogram
+ name: g/cm
+ type: tile
+ - entity: sensor.sparkx_i7_used_material_length
+ grid_options:
+ columns: 6
+ icon: mdi:counter
+ multiline_secondary: true
+ name: Used Length
+ primary: '{% set st = states(''sensor.sparkx_i7_print_status'') %}{% if st
+ in [''printing'', ''paused''] %}{{ states(''sensor.sparkx_i7_used_material_length'')
+ | round(0) }} cm{% else %}{{ (states(''sensor.sparkx_i7_filament_weight_daily'')
+ | float(0) / states(''input_number.sparkx_i7_filament_g_cm'') | float(0.0298))
+ | round(0) }} cm{% endif %}'
+ secondary: '{% set st = states(''sensor.sparkx_i7_print_status'') %}{% if
+ st in [''printing'', ''paused''] %}this job (printer-reported){% else %}today,
+ from today''s weight{% endif %}'
+ type: custom:mushroom-template-card
+ - entity: sensor.sparkx_i7_filament_cost
+ grid_options:
+ columns: 3
+ icon: mdi:cash
+ name: Job Cost (total)
+ type: tile
+ - entity: sensor.sparkx_i7_filament_cost_daily
+ grid_options:
+ columns: 3
+ icon: mdi:cash-multiple
+ name: Today
+ type: tile
+ - entity: sensor.sparkx_i7_filament_cost_weekly
+ grid_options:
+ columns: 3
+ icon: mdi:cash-multiple
+ name: This Week
+ type: tile
+ - entity: sensor.sparkx_i7_filament_cost_monthly
+ grid_options:
+ columns: 3
+ icon: mdi:cash-multiple
+ name: This Month
+ type: tile
+ type: grid
+ title: Print
+ type: sections
+ - max_columns: 2
+ path: history
+ sections:
+ - cards:
+ - heading: Temperature History
+ icon: mdi:chart-line-variant
+ type: heading
+ - entities:
+ - color: '#ef5350'
+ entity: sensor.sparkx_i7_nozzle_temperature
+ name: Nozzle
+ - color: '#ffab91'
+ entity: number.sparkx_i7_nozzle_target
+ name: Nozzle Target
+ - color: '#42a5f5'
+ entity: sensor.sparkx_i7_bed_temperature
+ name: Bed
+ - color: '#90caf9'
+ entity: number.sparkx_i7_bed_target
+ name: Bed Target
+ expand_legend: true
+ grid_options:
+ columns: full
+ hours_to_show: 24
+ show_names: true
+ title: Temperatures (24h)
+ type: history-graph
+ - entities:
+ - color: '#ef5350'
+ entity: sensor.sparkx_i7_nozzle_temperature
+ name: Nozzle
+ - color: '#42a5f5'
+ entity: sensor.sparkx_i7_bed_temperature
+ name: Bed
+ expand_legend: true
+ grid_options:
+ columns: full
+ hours_to_show: 168
+ show_names: true
+ title: Temperatures (7 days)
+ type: history-graph
+ type: grid
+ - cards:
+ - heading: Print Activity
+ icon: mdi:chart-box-outline
+ type: heading
+ - apex_config:
+ chart:
+ height: 300
+ stroke:
+ width: 1
+ graph_span: 24h
+ grid_options:
+ columns: full
+ header:
+ colorize_states: true
+ show: true
+ show_states: true
+ title: Z Position (24h)
+ series:
+ - color: '#ab47bc'
+ curve: straight
+ entity: sensor.sparkx_i7_position_z
+ fill_raw: last
+ name: Current Z
+ type: line
+ type: custom:apexcharts-card
+ - entities:
+ - color: '#66bb6a'
+ entity: sensor.sparkx_i7_print_progress
+ name: Progress
+ grid_options:
+ columns: full
+ hours_to_show: 24
+ show_names: true
+ title: Print Progress (24h)
+ type: history-graph
+ - entities:
+ - color: '#26c6da'
+ entity: sensor.sparkx_i7_print_job_time
+ name: Elapsed
+ - color: '#ffa726'
+ entity: sensor.sparkx_i7_print_time_left
+ name: Remaining
+ grid_options:
+ columns: full
+ hours_to_show: 24
+ show_names: true
+ title: Print Duration (24h)
+ type: history-graph
+ type: grid
+ title: History
+ type: sections
+ - icon: mdi:printer-3d-nozzle-outline
+ max_columns: 4
+ path: cfs-fans
+ sections:
+ - cards:
+ - heading: CFS Box 1
+ icon: mdi:file-cabinet
+ type: heading
+ - entity: sensor.sparkx_i7_cfs_box_1_temperature
+ grid_options:
+ columns: 6
+ icon: mdi:thermometer
+ name: Temperature
+ type: tile
+ - entity: sensor.sparkx_i7_cfs_box_1_humidity
+ grid_options:
+ columns: 6
+ icon: mdi:water-percent
+ name: Humidity
+ type: tile
+ - grid_options:
+ columns: 6
+ icon: mdi:printer-3d-nozzle
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_1_color'')|default('''')|string
+ %}{% set core = c|replace(''#'','''') %}{% if core|length >= 6 %}#{{ core[-6:]
+ }}{% endif %}'
+ multiline_secondary: true
+ name: Slot 1
+ primary: '{{ states(''sensor.sparkx_i7_cfs_box_1_slot_1_filament'') }}'
+ secondary: "{% set col = states('sensor.sparkx_i7_slot_1_color_name') %}{%\
+ \ if col %}{{ col }} \xB7 {% endif %}{{ states('sensor.sparkx_i7_cfs_box_1_slot_1_remaining')\
+ \ }}% left"
+ type: custom:mushroom-template-card
+ - grid_options:
+ columns: 6
+ icon: mdi:printer-3d-nozzle
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_2_color'')|default('''')|string
+ %}{% set core = c|replace(''#'','''') %}{% if core|length >= 6 %}#{{ core[-6:]
+ }}{% endif %}'
+ multiline_secondary: true
+ name: Slot 2
+ primary: '{{ states(''sensor.sparkx_i7_cfs_box_1_slot_2_filament'') }}'
+ secondary: "{% set col = states('sensor.sparkx_i7_slot_2_color_name') %}{%\
+ \ if col %}{{ col }} \xB7 {% endif %}{{ states('sensor.sparkx_i7_cfs_box_1_slot_2_remaining')\
+ \ }}% left"
+ type: custom:mushroom-template-card
+ - grid_options:
+ columns: 6
+ icon: mdi:printer-3d-nozzle
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_3_color'')|default('''')|string
+ %}{% set core = c|replace(''#'','''') %}{% if core|length >= 6 %}#{{ core[-6:]
+ }}{% endif %}'
+ multiline_secondary: true
+ name: Slot 3
+ primary: '{{ states(''sensor.sparkx_i7_cfs_box_1_slot_3_filament'') }}'
+ secondary: "{% set col = states('sensor.sparkx_i7_slot_3_color_name') %}{%\
+ \ if col %}{{ col }} \xB7 {% endif %}{{ states('sensor.sparkx_i7_cfs_box_1_slot_3_remaining')\
+ \ }}% left"
+ type: custom:mushroom-template-card
+ - grid_options:
+ columns: 6
+ icon: mdi:printer-3d-nozzle
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_4_color'')|default('''')|string
+ %}{% set core = c|replace(''#'','''') %}{% if core|length >= 6 %}#{{ core[-6:]
+ }}{% endif %}'
+ multiline_secondary: true
+ name: Slot 4
+ primary: '{{ states(''sensor.sparkx_i7_cfs_box_1_slot_4_filament'') }}'
+ secondary: "{% set col = states('sensor.sparkx_i7_slot_4_color_name') %}{%\
+ \ if col %}{{ col }} \xB7 {% endif %}{{ states('sensor.sparkx_i7_cfs_box_1_slot_4_remaining')\
+ \ }}% left"
+ type: custom:mushroom-template-card
+ type: grid
+ - cards:
+ - heading: External / Active
+ icon: mdi:printer-3d
+ type: heading
+ - cards:
+ - entity: sensor.sparkx_i7_active_filament_slot
+ grid_options:
+ columns: 6
+ icon: mdi:swap-horizontal
+ name: Active Slot
+ type: tile
+ - entity: sensor.sparkx_i7_active_filament_slot
+ icon: mdi:palette
+ icon_color: '{% set color = state_attr(''sensor.sparkx_i7_active_filament_slot'',
+ ''color'') | default('''') | string %}{% set core = color | replace(''#'',
+ '''') %}{% if core | length >= 6 %}#{{ core[-6:] }}{% else %}var(--primary-text-color){%
+ endif %}'
+ multiline_secondary: true
+ name: Active Color
+ primary: '{% set slot = states(''sensor.sparkx_i7_active_filament_slot'')
+ %}{% if ''Slot 1'' in slot %}{{ states(''sensor.sparkx_i7_slot_1_color_name'')
+ }}{% elif ''Slot 2'' in slot %}{{ states(''sensor.sparkx_i7_slot_2_color_name'')
+ }}{% elif ''Slot 3'' in slot %}{{ states(''sensor.sparkx_i7_slot_3_color_name'')
+ }}{% elif ''Slot 4'' in slot %}{{ states(''sensor.sparkx_i7_slot_4_color_name'')
+ }}{% elif slot == ''External'' %}{{ states(''sensor.sparkx_i7_external_color_name'')
+ }}{% else %}Unknown{% endif %}'
+ secondary: "{% set slot = states('sensor.sparkx_i7_active_filament_slot')\
+ \ %}{{ slot }}{% if state_attr('sensor.sparkx_i7_active_filament_slot',\
+ \ 'filament') %} \xB7 {{ state_attr('sensor.sparkx_i7_active_filament_slot',\
+ \ 'filament') }}{% endif %}"
+ type: custom:mushroom-template-card
+ grid_options:
+ columns: full
+ type: horizontal-stack
+ - grid_options:
+ columns: 6
+ icon: mdi:lan-outline
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_external_color'')|default('''')|string
+ %}{% set core = c|replace(''#'','''') %}{% if core|length >= 6 %}#{{ core[-6:]
+ }}{% endif %}'
+ multiline_secondary: true
+ name: External Filament
+ primary: '{{ states(''sensor.sparkx_i7_cfs_external_filament'') }}'
+ secondary: "{% set col = states('sensor.sparkx_i7_external_color_name') %}{%\
+ \ if col %}{{ col }} \xB7 {% endif %}{{ states('sensor.sparkx_i7_cfs_external_remaining')\
+ \ }}% left"
+ type: custom:mushroom-template-card
+ - entity: sensor.sparkx_i7_filament_status
+ grid_options:
+ columns: 6
+ icon: mdi:printer-3d-nozzle-alert
+ name: Filament Status
+ type: tile
+ type: grid
+ - cards:
+ - heading: Fans
+ icon: mdi:fan
+ type: heading
+ - entity: fan.sparkx_i7_model_fan
+ features:
+ - type: fan-speed
+ grid_options:
+ columns: 4
+ icon: mdi:fan
+ name: Model Fan
+ type: tile
+ - entity: fan.sparkx_i7_case_fan
+ features:
+ - type: fan-speed
+ grid_options:
+ columns: 4
+ icon: mdi:fan
+ name: Case Fan
+ type: tile
+ - entity: fan.sparkx_i7_side_fan
+ features:
+ - type: fan-speed
+ grid_options:
+ columns: 4
+ icon: mdi:fan
+ name: Side Fan
+ type: tile
+ type: grid
+ title: CFS / Fans
+ type: sections
+ - title: Spoolman
+ path: spoolman
+ icon: mdi:package-variant-closed
+ type: sections
+ max_columns: 4
+ sections:
+ - type: grid
+ cards:
+ - type: heading
+ heading: "CFS Slots \u2014 per-color deduction"
+ icon: mdi:printer-filament
+ - type: custom:mushroom-template-card
+ entity: sensor.sparkx_i7_cfs_box_1_slot_1_color
+ name: Slot 1
+ icon: mdi:printer-filament
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_1_color'')
+ | replace(''#'', '''') | lower %}{% if c | length == 6 %}#{{ c }}{% else
+ %}var(--primary-color){% endif %}'
+ primary: '{{ states(''sensor.sparkx_i7_slot_1_color_name'') }} (#{{ states(''sensor.sparkx_i7_cfs_box_1_slot_1_color'')
+ | replace(''#'', '''') | upper }})'
+ secondary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_1_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}\u2022 Spool\
+ \ {{ ns.id }} \xB7 {{ states('sensor.spoolman_spool_' ~ ns.id ~ '_vendor')\
+ \ | default('?', true) }} \xB7 {{ states('sensor.spoolman_spool_' ~ ns.id\
+ \ ~ '_material') | default('?', true) }} \xB7 {{ (states('sensor.spoolman_spool_'\
+ \ ~ ns.id) | float(0)) | round(1) }} g left{% elif color in ['', 'unknown',\
+ \ 'unavailable'] %}\u2022 Slot color unavailable{% else %}\u2022 No spool\
+ \ with stock matches this color{% endif %}\n\u2022 This print: {{ (states('input_number.sparkx_i7_slot_1_grams')\
+ \ | float(0)) | round(1) }} g"
+ multiline_secondary: true
+ grid_options:
+ columns: full
+ picture: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_1_color'') |
+ replace(''#'','''') | lower %}{% set svg = '''' %}{{ ''data:image/svg+xml;base64,''
+ ~ svg | base64_encode if c | length == 6 else '''' }}'
+ - type: custom:mushroom-template-card
+ entity: sensor.sparkx_i7_cfs_box_1_slot_2_color
+ name: Slot 2
+ icon: mdi:printer-filament
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_2_color'')
+ | replace(''#'', '''') | lower %}{% if c | length == 6 %}#{{ c }}{% else
+ %}var(--primary-color){% endif %}'
+ primary: '{{ states(''sensor.sparkx_i7_slot_2_color_name'') }} (#{{ states(''sensor.sparkx_i7_cfs_box_1_slot_2_color'')
+ | replace(''#'', '''') | upper }})'
+ secondary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_2_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}\u2022 Spool\
+ \ {{ ns.id }} \xB7 {{ states('sensor.spoolman_spool_' ~ ns.id ~ '_vendor')\
+ \ | default('?', true) }} \xB7 {{ states('sensor.spoolman_spool_' ~ ns.id\
+ \ ~ '_material') | default('?', true) }} \xB7 {{ (states('sensor.spoolman_spool_'\
+ \ ~ ns.id) | float(0)) | round(1) }} g left{% elif color in ['', 'unknown',\
+ \ 'unavailable'] %}\u2022 Slot color unavailable{% else %}\u2022 No spool\
+ \ with stock matches this color{% endif %}\n\u2022 This print: {{ (states('input_number.sparkx_i7_slot_2_grams')\
+ \ | float(0)) | round(1) }} g"
+ multiline_secondary: true
+ grid_options:
+ columns: full
+ picture: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_2_color'') |
+ replace(''#'','''') | lower %}{% set svg = '''' %}{{ ''data:image/svg+xml;base64,''
+ ~ svg | base64_encode if c | length == 6 else '''' }}'
+ - type: custom:mushroom-template-card
+ entity: sensor.sparkx_i7_cfs_box_1_slot_3_color
+ name: Slot 3
+ icon: mdi:printer-filament
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_3_color'')
+ | replace(''#'', '''') | lower %}{% if c | length == 6 %}#{{ c }}{% else
+ %}var(--primary-color){% endif %}'
+ primary: '{{ states(''sensor.sparkx_i7_slot_3_color_name'') }} (#{{ states(''sensor.sparkx_i7_cfs_box_1_slot_3_color'')
+ | replace(''#'', '''') | upper }})'
+ secondary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_3_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}\u2022 Spool\
+ \ {{ ns.id }} \xB7 {{ states('sensor.spoolman_spool_' ~ ns.id ~ '_vendor')\
+ \ | default('?', true) }} \xB7 {{ states('sensor.spoolman_spool_' ~ ns.id\
+ \ ~ '_material') | default('?', true) }} \xB7 {{ (states('sensor.spoolman_spool_'\
+ \ ~ ns.id) | float(0)) | round(1) }} g left{% elif color in ['', 'unknown',\
+ \ 'unavailable'] %}\u2022 Slot color unavailable{% else %}\u2022 No spool\
+ \ with stock matches this color{% endif %}\n\u2022 This print: {{ (states('input_number.sparkx_i7_slot_3_grams')\
+ \ | float(0)) | round(1) }} g"
+ multiline_secondary: true
+ grid_options:
+ columns: full
+ picture: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_3_color'') |
+ replace(''#'','''') | lower %}{% set svg = '''' %}{{ ''data:image/svg+xml;base64,''
+ ~ svg | base64_encode if c | length == 6 else '''' }}'
+ - type: custom:mushroom-template-card
+ entity: sensor.sparkx_i7_cfs_box_1_slot_4_color
+ name: Slot 4
+ icon: mdi:printer-filament
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_4_color'')
+ | replace(''#'', '''') | lower %}{% if c | length == 6 %}#{{ c }}{% else
+ %}var(--primary-color){% endif %}'
+ primary: '{{ states(''sensor.sparkx_i7_slot_4_color_name'') }} (#{{ states(''sensor.sparkx_i7_cfs_box_1_slot_4_color'')
+ | replace(''#'', '''') | upper }})'
+ secondary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_4_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}\u2022 Spool\
+ \ {{ ns.id }} \xB7 {{ states('sensor.spoolman_spool_' ~ ns.id ~ '_vendor')\
+ \ | default('?', true) }} \xB7 {{ states('sensor.spoolman_spool_' ~ ns.id\
+ \ ~ '_material') | default('?', true) }} \xB7 {{ (states('sensor.spoolman_spool_'\
+ \ ~ ns.id) | float(0)) | round(1) }} g left{% elif color in ['', 'unknown',\
+ \ 'unavailable'] %}\u2022 Slot color unavailable{% else %}\u2022 No spool\
+ \ with stock matches this color{% endif %}\n\u2022 This print: {{ (states('input_number.sparkx_i7_slot_4_grams')\
+ \ | float(0)) | round(1) }} g"
+ multiline_secondary: true
+ grid_options:
+ columns: full
+ picture: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_4_color'') |
+ replace(''#'','''') | lower %}{% set svg = '''' %}{{ ''data:image/svg+xml;base64,''
+ ~ svg | base64_encode if c | length == 6 else '''' }}'
+ - type: tile
+ entity: input_number.sparkx_i7_spoolman_length_checkpoint
+ name: Length Checkpoint (cm)
+ icon: mdi:ruler-measure
+ grid_options:
+ columns: full
+ - type: tile
+ entity: input_text.sparkx_i7_print_spool_latch
+ name: Latched CFS Slot
+ icon: mdi:link-variant
+ grid_options:
+ columns: full
+ - type: tile
+ entity: sensor.sparkx_i7_used_material_length
+ name: Used Material Total (cm)
+ icon: mdi:plus-minus
+ grid_options:
+ columns: full
+ - type: tile
+ entity: sensor.sparkx_i7_filament_weight_estimated
+ name: Current Job Weight (est.)
+ icon: mdi:weight-gram
+ grid_options:
+ columns: 12
+ rows: 1
+ - type: custom:mushroom-template-card
+ entity: sensor.sparkx_i7_active_filament_slot
+ name: Next Deduction Match
+ icon: mdi:magnet
+ icon_color: '{% set slot = states(''sensor.sparkx_i7_active_filament_slot'')
+ %}{% set c = states(''sensor.sparkx_i7_cfs_'' ~ (slot | lower | replace(''
+ '', ''_'')) ~ ''_color'') | replace(''#'', '''') | lower %}{% if c | length
+ == 6 %}#{{ c }}{% else %}var(--primary-color){% endif %}'
+ primary: '{{ states(''sensor.sparkx_i7_active_filament_slot'') }}'
+ secondary: "{% set slot = states('sensor.sparkx_i7_active_filament_slot')\
+ \ %}{% set ce = 'sensor.sparkx_i7_cfs_' ~ (slot | lower | replace(' ', '_'))\
+ \ ~ '_color' %}{% set color = states(ce) | replace('#', '') | lower %}{%\
+ \ set ns = namespace(id='') %}{% for e in integration_entities('spoolman')\
+ \ %}{% if e.endswith('_color_hex') and (states(e) | replace('#', '') | lower)\
+ \ == color and (state_attr(e[:-10], 'location') | default('', true) | lower)\
+ \ == 'cfs lite' %}{% set b = e[:-10] %}{% if (states(b) | float(0)) > 0\
+ \ and ns.id == '' %}{% set ns.id = states(b ~ '_id') %}{% endif %}{% endif\
+ \ %}{% endfor %}{% if ns.id %}Color #{{ color | upper }} \u2192 spool {{\
+ \ ns.id }} ({{ states('sensor.spoolman_spool_' ~ ns.id ~ '_filament_name')\
+ \ }} \xB7 {{ (states('sensor.spoolman_spool_' ~ ns.id) | float(0)) | round(1)\
+ \ }} g left){% elif slot in ['', 'unknown', 'unavailable'] %}No slot latched{%\
+ \ elif color in ['', 'unknown', 'unavailable'] %}Slot color unavailable{%\
+ \ else %}Color #{{ color | upper }} \u2014 no spool with stock{% endif %}"
+ multiline_secondary: true
+ grid_options:
+ columns: full
+ - type: markdown
+ content: "**Spoolman UI:** [http://2c829f0e-spoolman.local.hass.io:7912](http://2c829f0e-spoolman.local.hass.io:7912)\n\
+ \nEach CFS slot is matched to the Spoolman spool with the same **color hex**\
+ \ that still has stock (remaining > 0) **and whose location is CFS Lite**.\
+ \ All 4 rolls live in the **CFS Lite** location in Spoolman.\n\n**Replaced\
+ \ a roll?** Register the new roll in Spoolman with the same color hex and\
+ \ location CFS Lite. No Home Assistant changes needed \u2014 a same-color\
+ \ roll in another location (e.g. Storage) is ignored, and exhausted spools\
+ \ are skipped automatically."
+ grid_options:
+ columns: full
+ - type: grid
+ cards:
+ - type: heading
+ heading: "Spool Details \u2014 CFS Lite"
+ icon: mdi:spool
+ - type: custom:mushroom-template-card
+ entity: sensor.sparkx_i7_cfs_box_1_slot_1_color
+ name: Slot 1
+ icon: mdi:spool
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_1_color'')
+ | replace(''#'', '''') | lower %}{% if c | length == 6 %}#{{ c }}{% else
+ %}var(--primary-color){% endif %}'
+ picture: '{% set color = states(''sensor.sparkx_i7_cfs_box_1_slot_1_color'')
+ | replace(''#'', '''') | lower %}{% set ns = namespace(id='''') %}{% for
+ e in integration_entities(''spoolman'') %}{% if e.endswith(''_color_hex'')
+ and (states(e) | replace(''#'', '''') | lower) == color and (state_attr(e[:-10],
+ ''location'') | default('''', true) | lower) == ''cfs lite'' %}{% set b
+ = e[:-10] %}{% if (states(b) | float(0)) > 0 and ns.id == '''' %}{% set
+ ns.id = states(b ~ ''_id'') %}{% endif %}{% endif %}{% endfor %}{{ state_attr(''sensor.spoolman_spool_''
+ ~ ns.id, ''entity_picture'') if ns.id and state_attr(''sensor.spoolman_spool_''
+ ~ ns.id, ''entity_picture'') else '''' }}'
+ primary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_1_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}Spool {{ ns.id\
+ \ }} \xB7 {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_name')\
+ \ | default('?', true) }} \xB7 {{ (states('sensor.spoolman_spool_' ~ ns.id)\
+ \ | float(0)) | round(1) }} g left{% elif color in ['', 'unknown', 'unavailable']\
+ \ %}Slot color unavailable{% else %}No spool with stock matches this color{%\
+ \ endif %}"
+ secondary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_1_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}\u2022 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_vendor_name') | default('?', true) }} \xB7 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_material') | default('?', true) }} \xB7 #{{ color |\
+ \ upper }}\n\u2022 Remaining: {{ (states('sensor.spoolman_spool_' ~ ns.id)\
+ \ | float(0)) | round(1) }} g \xB7 used {{ (state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_weight') | float(0)) | round(1) }} g ({{ (state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_percentage') | float(0)) | round(1) }}%)\n\u2022 Remaining\
+ \ length: {{ ((state_attr('sensor.spoolman_spool_' ~ ns.id, 'remaining_length')\
+ \ | float(0)) / 1000) | round(1) }} m \xB7 used {{ ((state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_length') | float(0)) / 1000) | round(1) }} m\n\u2022 Spool:\
+ \ \xD8{{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_diameter')\
+ \ | default('?', true) }} mm \xB7 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_density') | default('?', true) }} g/cm\xB3 \xB7 {{\
+ \ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_weight') | default('?',\
+ \ true) }} g \xB7 art. {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_article_number')\
+ \ | default('?', true) }}\n\u2022 Temps: bed {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_settings_bed_temp') | default('?', true) }}\xB0C \xB7\
+ \ hotend {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_settings_extruder_temp')\
+ \ | default('?', true) }}\xB0C\n\u2022 Flow: {{ states('sensor.spoolman_spool_'\
+ \ ~ ns.id ~ '_flow_rate') }} \xB7 Runout est.: {{ states('sensor.spoolman_spool_'\
+ \ ~ ns.id ~ '_estimated_runout') }}\n\u2022 Registered: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'registered') | default('?', true) }} \xB7 Location: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'location') | default('?', true) }} \xB7 Price: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_price') | default('?', true) }}{% elif color in ['',\
+ \ 'unknown', 'unavailable'] %}\u2022 Slot color unavailable{% else %}\u2022\
+ \ No spool with stock matches this color{% endif %}"
+ multiline_secondary: true
+ grid_options:
+ columns: full
+ - type: custom:mushroom-template-card
+ entity: sensor.sparkx_i7_cfs_box_1_slot_2_color
+ name: Slot 2
+ icon: mdi:spool
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_2_color'')
+ | replace(''#'', '''') | lower %}{% if c | length == 6 %}#{{ c }}{% else
+ %}var(--primary-color){% endif %}'
+ picture: '{% set color = states(''sensor.sparkx_i7_cfs_box_1_slot_2_color'')
+ | replace(''#'', '''') | lower %}{% set ns = namespace(id='''') %}{% for
+ e in integration_entities(''spoolman'') %}{% if e.endswith(''_color_hex'')
+ and (states(e) | replace(''#'', '''') | lower) == color and (state_attr(e[:-10],
+ ''location'') | default('''', true) | lower) == ''cfs lite'' %}{% set b
+ = e[:-10] %}{% if (states(b) | float(0)) > 0 and ns.id == '''' %}{% set
+ ns.id = states(b ~ ''_id'') %}{% endif %}{% endif %}{% endfor %}{{ state_attr(''sensor.spoolman_spool_''
+ ~ ns.id, ''entity_picture'') if ns.id and state_attr(''sensor.spoolman_spool_''
+ ~ ns.id, ''entity_picture'') else '''' }}'
+ primary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_2_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}Spool {{ ns.id\
+ \ }} \xB7 {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_name')\
+ \ | default('?', true) }} \xB7 {{ (states('sensor.spoolman_spool_' ~ ns.id)\
+ \ | float(0)) | round(1) }} g left{% elif color in ['', 'unknown', 'unavailable']\
+ \ %}Slot color unavailable{% else %}No spool with stock matches this color{%\
+ \ endif %}"
+ secondary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_2_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}\u2022 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_vendor_name') | default('?', true) }} \xB7 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_material') | default('?', true) }} \xB7 #{{ color |\
+ \ upper }}\n\u2022 Remaining: {{ (states('sensor.spoolman_spool_' ~ ns.id)\
+ \ | float(0)) | round(1) }} g \xB7 used {{ (state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_weight') | float(0)) | round(1) }} g ({{ (state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_percentage') | float(0)) | round(1) }}%)\n\u2022 Remaining\
+ \ length: {{ ((state_attr('sensor.spoolman_spool_' ~ ns.id, 'remaining_length')\
+ \ | float(0)) / 1000) | round(1) }} m \xB7 used {{ ((state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_length') | float(0)) / 1000) | round(1) }} m\n\u2022 Spool:\
+ \ \xD8{{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_diameter')\
+ \ | default('?', true) }} mm \xB7 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_density') | default('?', true) }} g/cm\xB3 \xB7 {{\
+ \ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_weight') | default('?',\
+ \ true) }} g \xB7 art. {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_article_number')\
+ \ | default('?', true) }}\n\u2022 Temps: bed {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_settings_bed_temp') | default('?', true) }}\xB0C \xB7\
+ \ hotend {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_settings_extruder_temp')\
+ \ | default('?', true) }}\xB0C\n\u2022 Flow: {{ states('sensor.spoolman_spool_'\
+ \ ~ ns.id ~ '_flow_rate') }} \xB7 Runout est.: {{ states('sensor.spoolman_spool_'\
+ \ ~ ns.id ~ '_estimated_runout') }}\n\u2022 Registered: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'registered') | default('?', true) }} \xB7 Location: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'location') | default('?', true) }} \xB7 Price: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_price') | default('?', true) }}{% elif color in ['',\
+ \ 'unknown', 'unavailable'] %}\u2022 Slot color unavailable{% else %}\u2022\
+ \ No spool with stock matches this color{% endif %}"
+ multiline_secondary: true
+ grid_options:
+ columns: full
+ - type: custom:mushroom-template-card
+ entity: sensor.sparkx_i7_cfs_box_1_slot_3_color
+ name: Slot 3
+ icon: mdi:spool
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_3_color'')
+ | replace(''#'', '''') | lower %}{% if c | length == 6 %}#{{ c }}{% else
+ %}var(--primary-color){% endif %}'
+ picture: '{% set color = states(''sensor.sparkx_i7_cfs_box_1_slot_3_color'')
+ | replace(''#'', '''') | lower %}{% set ns = namespace(id='''') %}{% for
+ e in integration_entities(''spoolman'') %}{% if e.endswith(''_color_hex'')
+ and (states(e) | replace(''#'', '''') | lower) == color and (state_attr(e[:-10],
+ ''location'') | default('''', true) | lower) == ''cfs lite'' %}{% set b
+ = e[:-10] %}{% if (states(b) | float(0)) > 0 and ns.id == '''' %}{% set
+ ns.id = states(b ~ ''_id'') %}{% endif %}{% endif %}{% endfor %}{{ state_attr(''sensor.spoolman_spool_''
+ ~ ns.id, ''entity_picture'') if ns.id and state_attr(''sensor.spoolman_spool_''
+ ~ ns.id, ''entity_picture'') else '''' }}'
+ primary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_3_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}Spool {{ ns.id\
+ \ }} \xB7 {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_name')\
+ \ | default('?', true) }} \xB7 {{ (states('sensor.spoolman_spool_' ~ ns.id)\
+ \ | float(0)) | round(1) }} g left{% elif color in ['', 'unknown', 'unavailable']\
+ \ %}Slot color unavailable{% else %}No spool with stock matches this color{%\
+ \ endif %}"
+ secondary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_3_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}\u2022 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_vendor_name') | default('?', true) }} \xB7 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_material') | default('?', true) }} \xB7 #{{ color |\
+ \ upper }}\n\u2022 Remaining: {{ (states('sensor.spoolman_spool_' ~ ns.id)\
+ \ | float(0)) | round(1) }} g \xB7 used {{ (state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_weight') | float(0)) | round(1) }} g ({{ (state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_percentage') | float(0)) | round(1) }}%)\n\u2022 Remaining\
+ \ length: {{ ((state_attr('sensor.spoolman_spool_' ~ ns.id, 'remaining_length')\
+ \ | float(0)) / 1000) | round(1) }} m \xB7 used {{ ((state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_length') | float(0)) / 1000) | round(1) }} m\n\u2022 Spool:\
+ \ \xD8{{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_diameter')\
+ \ | default('?', true) }} mm \xB7 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_density') | default('?', true) }} g/cm\xB3 \xB7 {{\
+ \ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_weight') | default('?',\
+ \ true) }} g \xB7 art. {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_article_number')\
+ \ | default('?', true) }}\n\u2022 Temps: bed {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_settings_bed_temp') | default('?', true) }}\xB0C \xB7\
+ \ hotend {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_settings_extruder_temp')\
+ \ | default('?', true) }}\xB0C\n\u2022 Flow: {{ states('sensor.spoolman_spool_'\
+ \ ~ ns.id ~ '_flow_rate') }} \xB7 Runout est.: {{ states('sensor.spoolman_spool_'\
+ \ ~ ns.id ~ '_estimated_runout') }}\n\u2022 Registered: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'registered') | default('?', true) }} \xB7 Location: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'location') | default('?', true) }} \xB7 Price: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_price') | default('?', true) }}{% elif color in ['',\
+ \ 'unknown', 'unavailable'] %}\u2022 Slot color unavailable{% else %}\u2022\
+ \ No spool with stock matches this color{% endif %}"
+ multiline_secondary: true
+ grid_options:
+ columns: full
+ - type: custom:mushroom-template-card
+ entity: sensor.sparkx_i7_cfs_box_1_slot_4_color
+ name: Slot 4
+ icon: mdi:spool
+ icon_color: '{% set c = states(''sensor.sparkx_i7_cfs_box_1_slot_4_color'')
+ | replace(''#'', '''') | lower %}{% if c | length == 6 %}#{{ c }}{% else
+ %}var(--primary-color){% endif %}'
+ picture: '{% set color = states(''sensor.sparkx_i7_cfs_box_1_slot_4_color'')
+ | replace(''#'', '''') | lower %}{% set ns = namespace(id='''') %}{% for
+ e in integration_entities(''spoolman'') %}{% if e.endswith(''_color_hex'')
+ and (states(e) | replace(''#'', '''') | lower) == color and (state_attr(e[:-10],
+ ''location'') | default('''', true) | lower) == ''cfs lite'' %}{% set b
+ = e[:-10] %}{% if (states(b) | float(0)) > 0 and ns.id == '''' %}{% set
+ ns.id = states(b ~ ''_id'') %}{% endif %}{% endif %}{% endfor %}{{ state_attr(''sensor.spoolman_spool_''
+ ~ ns.id, ''entity_picture'') if ns.id and state_attr(''sensor.spoolman_spool_''
+ ~ ns.id, ''entity_picture'') else '''' }}'
+ primary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_4_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}Spool {{ ns.id\
+ \ }} \xB7 {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_name')\
+ \ | default('?', true) }} \xB7 {{ (states('sensor.spoolman_spool_' ~ ns.id)\
+ \ | float(0)) | round(1) }} g left{% elif color in ['', 'unknown', 'unavailable']\
+ \ %}Slot color unavailable{% else %}No spool with stock matches this color{%\
+ \ endif %}"
+ secondary: "{% set color = states('sensor.sparkx_i7_cfs_box_1_slot_4_color')\
+ \ | replace('#', '') | lower %}{% set ns = namespace(id='') %}{% for e in\
+ \ integration_entities('spoolman') %}{% if e.endswith('_color_hex') and\
+ \ (states(e) | replace('#', '') | lower) == color and (state_attr(e[:-10],\
+ \ 'location') | default('', true) | lower) == 'cfs lite' %}{% set b = e[:-10]\
+ \ %}{% if (states(b) | float(0)) > 0 and ns.id == '' %}{% set ns.id = states(b\
+ \ ~ '_id') %}{% endif %}{% endif %}{% endfor %}{% if ns.id %}\u2022 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_vendor_name') | default('?', true) }} \xB7 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_material') | default('?', true) }} \xB7 #{{ color |\
+ \ upper }}\n\u2022 Remaining: {{ (states('sensor.spoolman_spool_' ~ ns.id)\
+ \ | float(0)) | round(1) }} g \xB7 used {{ (state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_weight') | float(0)) | round(1) }} g ({{ (state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_percentage') | float(0)) | round(1) }}%)\n\u2022 Remaining\
+ \ length: {{ ((state_attr('sensor.spoolman_spool_' ~ ns.id, 'remaining_length')\
+ \ | float(0)) / 1000) | round(1) }} m \xB7 used {{ ((state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'used_length') | float(0)) / 1000) | round(1) }} m\n\u2022 Spool:\
+ \ \xD8{{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_diameter')\
+ \ | default('?', true) }} mm \xB7 {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_density') | default('?', true) }} g/cm\xB3 \xB7 {{\
+ \ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_weight') | default('?',\
+ \ true) }} g \xB7 art. {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_article_number')\
+ \ | default('?', true) }}\n\u2022 Temps: bed {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_settings_bed_temp') | default('?', true) }}\xB0C \xB7\
+ \ hotend {{ state_attr('sensor.spoolman_spool_' ~ ns.id, 'filament_settings_extruder_temp')\
+ \ | default('?', true) }}\xB0C\n\u2022 Flow: {{ states('sensor.spoolman_spool_'\
+ \ ~ ns.id ~ '_flow_rate') }} \xB7 Runout est.: {{ states('sensor.spoolman_spool_'\
+ \ ~ ns.id ~ '_estimated_runout') }}\n\u2022 Registered: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'registered') | default('?', true) }} \xB7 Location: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'location') | default('?', true) }} \xB7 Price: {{ state_attr('sensor.spoolman_spool_'\
+ \ ~ ns.id, 'filament_price') | default('?', true) }}{% elif color in ['',\
+ \ 'unknown', 'unavailable'] %}\u2022 Slot color unavailable{% else %}\u2022\
+ \ No spool with stock matches this color{% endif %}"
+ multiline_secondary: true
+ grid_options:
+ columns: full
+ - type: grid
+ cards:
+ - type: heading
+ heading: Adjust Remaining
+ icon: mdi:pencil-outline
+ - type: entities
+ title: Set value (g)
+ show_header_toggle: false
+ entities:
+ - input_number.spoolman_set_remaining
+ grid_options:
+ columns: full
+ - type: tile
+ entity: input_number.spoolman_set_remaining
+ name: Apply to Slot 1
+ icon: mdi:content-save-outline
+ grid_options:
+ columns: 3
+ tap_action:
+ action: perform-action
+ perform_action: script.spoolman_set_spool_remaining
+ data:
+ slot: 1
+ confirmation:
+ text: Overwrite remaining weight of the spool matching Slot 1 (by color
+ hex) in Spoolman?
+ - type: tile
+ entity: input_number.spoolman_set_remaining
+ name: Apply to Slot 2
+ icon: mdi:content-save-outline
+ grid_options:
+ columns: 3
+ tap_action:
+ action: perform-action
+ perform_action: script.spoolman_set_spool_remaining
+ data:
+ slot: 2
+ confirmation:
+ text: Overwrite remaining weight of the spool matching Slot 2 (by color
+ hex) in Spoolman?
+ - type: tile
+ entity: input_number.spoolman_set_remaining
+ name: Apply to Slot 3
+ icon: mdi:content-save-outline
+ grid_options:
+ columns: 3
+ tap_action:
+ action: perform-action
+ perform_action: script.spoolman_set_spool_remaining
+ data:
+ slot: 3
+ confirmation:
+ text: Overwrite remaining weight of the spool matching Slot 3 (by color
+ hex) in Spoolman?
+ - type: tile
+ entity: input_number.spoolman_set_remaining
+ name: Apply to Slot 4
+ icon: mdi:content-save-outline
+ grid_options:
+ columns: 3
+ tap_action:
+ action: perform-action
+ perform_action: script.spoolman_set_spool_remaining
+ data:
+ slot: 4
+ confirmation:
+ text: Overwrite remaining weight of the spool matching Slot 4 (by color
+ hex) in Spoolman?
+ - type: markdown
+ content: "**1.** Type the new remaining grams above. **2.** Tap **Apply to\
+ \ Slot N** for the slot holding that roll \u2014 it resolves the spool by\
+ \ the slot's color hex, so it also works when the spool is exhausted (e.g.\
+ \ right after replacing a roll). **3.** The value is **absolute** \u2014\
+ \ it replaces Spoolman's remaining weight; print deductions continue from\
+ \ there. Avoid adjusting during a running print."
+ grid_options:
+ columns: full
+ - type: grid
+ cards:
+ - type: heading
+ heading: Print Linkage
+ icon: mdi:printer-3d-nozzle
+ - type: tile
+ entity: automation.sparkx_i7_latch_print_spool_for_spoolman
+ name: Latch + Segment Deduction
+ icon: mdi:link-variant
+ grid_options:
+ columns: 3
+ - type: tile
+ entity: automation.sparkx_i7_deduct_filament_in_spoolman_on_print_complete
+ name: Print Complete Deduction
+ icon: mdi:minus-circle-outline
+ grid_options:
+ columns: 3
+ - type: tile
+ entity: sensor.sparkx_i7_filament_weight_estimated
+ name: Current Job Weight (est.)
+ icon: mdi:weight-gram
+ grid_options:
+ columns: 3
+ - type: tile
+ entity: input_number.sparkx_i7_filament_g_cm
+ name: Filament g/cm
+ icon: mdi:ruler
+ grid_options:
+ columns: 3
+ - type: tile
+ entity: sensor.sparkx_i7_active_filament_slot
+ name: Active CFS Slot
+ icon: mdi:palette-swatch-variant
+ grid_options:
+ columns: 3
+ - type: tile
+ entity: sensor.sparkx_i7_used_material_length
+ name: Used Material Total (cm)
+ icon: mdi:plus-minus
+ grid_options:
+ columns: 6
+ rows: 1
+ - type: grid
+ cards:
+ - type: heading
+ heading: "Spool History \u2014 CFS Lite (auto)"
+ icon: mdi:history
+ - type: custom:auto-entities
+ filter:
+ include:
+ - entity_id: sensor.spoolman_spool_*
+ attributes:
+ location: CFS Lite
+ sort:
+ method: attribute
+ attribute: id
+ numeric: true
+ card:
+ type: history-graph
+ title: Remaining weight (7 days)
+ hours_to_show: 168
+ show_names: true
+ show_empty: false
+ grid_options:
+ columns: full
+ - type: markdown
+ content: "Every spool with location **CFS Lite** joins this graph automatically\
+ \ \u2014 register a new roll in Spoolman (same color hex, location CFS Lite)\
+ \ and it appears without dashboard edits. History follows the spool entity;\
+ \ a replaced roll keeps its own history under its own spool id."
+ grid_options:
+ columns: full
diff --git a/.ha_mcp/logs/mcp_usage.jsonl b/.ha_mcp/logs/mcp_usage.jsonl
index 42f3b988..df091160 100644
--- a/.ha_mcp/logs/mcp_usage.jsonl
+++ b/.ha_mcp/logs/mcp_usage.jsonl
@@ -4155,3 +4155,6 @@
{"timestamp": "2026-10-09T00:31:08.025982+00:00", "tool_name": "ha_config_get_dashboard", "parameters": {"url_path": "sparkx-i7", "list_only": false, "force_reload": false, "entity_id": "sensor.sparkx_i7_active_filament_slot", "card_type": null, "heading": null, "include_config": false, "include_screenshot": false, "view_path": null, "mode": null, "query": null}, "execution_time_ms": 88.17911148071289, "success": true, "error_message": null, "response_size_bytes": 1379, "user_context": null}
{"timestamp": "2026-10-09T00:31:09.198470+00:00", "tool_name": "ha_config_get_automation", "parameters": {"identifier": "1791477238729"}, "execution_time_ms": 1289.0384197235107, "success": true, "error_message": null, "response_size_bytes": 5811, "user_context": null}
{"timestamp": "2026-10-09T00:31:09.481568+00:00", "tool_name": "ha_config_get_automation", "parameters": {"identifier": "1791477238762"}, "execution_time_ms": 1559.7484111785889, "success": true, "error_message": null, "response_size_bytes": 5503, "user_context": null}
+{"timestamp": "2026-10-09T00:32:48.159296+00:00", "tool_name": "ha_config_set_dashboard", "parameters": {"url_path": "sparkx-i7", "config": null, "python_transform": "config['views'][3]['sections'][0]['cards'][10]['content'] = config['views'][3]['sections'][0]['cards'][10]['content'].replace('http://2c829f0e-spoolman.local.hass.io:7912', 'http://192.168.1.140:7912')", "config_hash": "3e1e0c8a1bbb7498", "title": null, "icon": null, "require_admin": null, "show_in_sidebar": null, "MandatoryBPS": false, "BestPracticeKey": null, "return_screenshot": false, "view_path": null, "patch": null}, "execution_time_ms": 165.86875915527344, "success": true, "error_message": null, "response_size_bytes": 1030, "user_context": null}
+{"timestamp": "2026-10-09T00:32:50.390327+00:00", "tool_name": "ha_config_set_automation", "parameters": {"config": null, "identifier": "1791477238762", "python_transform": "config['description'] = config['description'].replace('from the Spoolman spool matching the previous slot color hex', 'from the Spoolman spool in the CFS Lite location matching the previous slot color hex')\nfor s in config['actions'][1]['choose'][1]['sequence'][2]['then'][0]['default']:\n d = s['data']\n d['message'] = d['message'].replace('no Spoolman spool with remaining weight matches that color', 'no Spoolman spool in the CFS Lite location with remaining weight matches that color').replace('Register the new roll in Spoolman, then adjust manually if needed.', 'Register the new roll in Spoolman with location CFS Lite, then adjust manually if needed.')", "config_hash": "733d8e8ed285ab77", "take_control_of_blueprint": false, "category": null, "wait": true, "enabled": null, "run_actions": false, "MandatoryBPS": false, "BestPracticeKey": null}, "execution_time_ms": 2440.6590461730957, "success": true, "error_message": null, "response_size_bytes": 7646, "user_context": null}
+{"timestamp": "2026-10-09T00:32:51.251728+00:00", "tool_name": "ha_config_set_automation", "parameters": {"config": null, "identifier": "1791477238729", "python_transform": "config['description'] = config['description'].replace('from the Spoolman spool matching the latched slot color', 'from the Spoolman spool in the CFS Lite location matching the latched slot color')\nfor s in config['actions'][5]['then'][0]['default']:\n d = s['data']\n d['message'] = d['message'].replace('no Spoolman spool with remaining weight matches that color', 'no Spoolman spool in the CFS Lite location with remaining weight matches that color').replace('Register the new roll in Spoolman, then adjust manually if needed.', 'Register the new roll in Spoolman with location CFS Lite, then adjust manually if needed.')", "config_hash": "3e4b8c309716746d", "take_control_of_blueprint": false, "category": null, "wait": true, "enabled": null, "run_actions": false, "MandatoryBPS": false, "BestPracticeKey": null}, "execution_time_ms": 3172.0316410064697, "success": true, "error_message": null, "response_size_bytes": 7608, "user_context": null}
diff --git a/.storage/lovelace.sparkx_i7 b/.storage/lovelace.sparkx_i7
index 10ec5ab5..b12b1991 100644
--- a/.storage/lovelace.sparkx_i7
+++ b/.storage/lovelace.sparkx_i7
@@ -1036,7 +1036,7 @@
},
{
"type": "markdown",
- "content": "**Spoolman UI:** [http://2c829f0e-spoolman.local.hass.io:7912](http://2c829f0e-spoolman.local.hass.io:7912)\n\nEach CFS slot is matched to the Spoolman spool with the same **color hex** that still has stock (remaining > 0) **and whose location is CFS Lite**. All 4 rolls live in the **CFS Lite** location in Spoolman.\n\n**Replaced a roll?** Register the new roll in Spoolman with the same color hex and location CFS Lite. No Home Assistant changes needed — a same-color roll in another location (e.g. Storage) is ignored, and exhausted spools are skipped automatically.",
+ "content": "**Spoolman UI:** [http://192.168.1.140:7912](http://192.168.1.140:7912)\n\nEach CFS slot is matched to the Spoolman spool with the same **color hex** that still has stock (remaining > 0) **and whose location is CFS Lite**. All 4 rolls live in the **CFS Lite** location in Spoolman.\n\n**Replaced a roll?** Register the new roll in Spoolman with the same color hex and location CFS Lite. No Home Assistant changes needed — a same-color roll in another location (e.g. Storage) is ignored, and exhausted spools are skipped automatically.",
"grid_options": {
"columns": "full"
}
diff --git a/automations.yaml b/automations.yaml
index 1356c6c8..200df987 100644
--- a/automations.yaml
+++ b/automations.yaml
@@ -4377,10 +4377,11 @@
- id: '1791477238729'
alias: SparkX i7 - Deduct filament in Spoolman on print complete
description: On print completion, deducts the final segment (used_material_length
- delta since last checkpoint x g/cm) from the Spoolman spool matching the latched
- slot color (remaining weight > 0 guard), accumulates it per slot, then sends a
- summary notification with total and per-slot breakdown whenever anything was deducted.
- Silent when total is 0. Clears the latch and re-baselines the checkpoint.
+ delta since last checkpoint x g/cm) from the Spoolman spool in the CFS Lite location
+ matching the latched slot color (remaining weight > 0 guard), accumulates it per
+ slot, then sends a summary notification with total and per-slot breakdown whenever
+ anything was deducted. Silent when total is 0. Clears the latch and re-baselines
+ the checkpoint.
triggers:
- trigger: state
entity_id: sensor.sparkx_i7_print_status
@@ -4441,25 +4442,25 @@
title: 'Spoolman: no spool for slot color'
message: 'Print completed: final segment of {{ final_grams }} g from {{
old_slot if old_slot else ''the active slot'' }} (color #{{ color | upper
- }}) could not be deducted - no Spoolman spool with remaining weight matches
- that color. Register the new roll in Spoolman, then adjust manually if
- needed.'
+ }}) could not be deducted - no Spoolman spool in the CFS Lite location
+ with remaining weight matches that color. Register the new roll in Spoolman
+ with location CFS Lite, then adjust manually if needed.'
- action: notify.mobile_app_francos_ipad
data:
title: 'Spoolman: no spool for slot color'
message: 'Print completed: final segment of {{ final_grams }} g from {{
old_slot if old_slot else ''the active slot'' }} (color #{{ color | upper
- }}) could not be deducted - no Spoolman spool with remaining weight matches
- that color. Register the new roll in Spoolman, then adjust manually if
- needed.'
+ }}) could not be deducted - no Spoolman spool in the CFS Lite location
+ with remaining weight matches that color. Register the new roll in Spoolman
+ with location CFS Lite, then adjust manually if needed.'
- action: notify.mobile_app_sm_f956w
data:
title: 'Spoolman: no spool for slot color'
message: 'Print completed: final segment of {{ final_grams }} g from {{
old_slot if old_slot else ''the active slot'' }} (color #{{ color | upper
- }}) could not be deducted - no Spoolman spool with remaining weight matches
- that color. Register the new roll in Spoolman, then adjust manually if
- needed.'
+ }}) could not be deducted - no Spoolman spool in the CFS Lite location
+ with remaining weight matches that color. Register the new roll in Spoolman
+ with location CFS Lite, then adjust manually if needed.'
- variables:
summary_total: '{% set ns = namespace(t = 0) %}{% for n in [''1'', ''2'', ''3'',
''4''] %}{% set g = states(''input_number.sparkx_i7_slot_'' ~ n ~ ''_grams'')
@@ -4512,9 +4513,10 @@
description: 'Segment tracking for Spoolman: at print start resets the length checkpoint
and per-slot gram counters and latches the active CFS slot. On every slot change
mid-print, deducts the exact extrusion of the finished segment (used_material_length
- delta x g/cm) from the Spoolman spool matching the previous slot color hex (only
- if that spool has remaining weight), accumulates grams per slot, notifies on no-match,
- then re-baselines the checkpoint and advances the latch.'
+ delta x g/cm) from the Spoolman spool in the CFS Lite location matching the previous
+ slot color hex (only if that spool has remaining weight), accumulates grams per
+ slot, notifies on no-match, then re-baselines the checkpoint and advances the
+ latch.'
triggers:
- trigger: state
entity_id: sensor.sparkx_i7_print_status
@@ -4607,23 +4609,23 @@
data:
title: 'Spoolman: no spool for slot color'
message: 'Segment of {{ grams }} g from {{ old_slot }} (color #{{ color
- | upper }}) could not be deducted - no Spoolman spool with remaining
- weight matches that color. Register the new roll in Spoolman, then
- adjust manually if needed.'
+ | upper }}) could not be deducted - no Spoolman spool in the CFS Lite
+ location with remaining weight matches that color. Register the new
+ roll in Spoolman with location CFS Lite, then adjust manually if needed.'
- action: notify.mobile_app_francos_ipad
data:
title: 'Spoolman: no spool for slot color'
message: 'Segment of {{ grams }} g from {{ old_slot }} (color #{{ color
- | upper }}) could not be deducted - no Spoolman spool with remaining
- weight matches that color. Register the new roll in Spoolman, then
- adjust manually if needed.'
+ | upper }}) could not be deducted - no Spoolman spool in the CFS Lite
+ location with remaining weight matches that color. Register the new
+ roll in Spoolman with location CFS Lite, then adjust manually if needed.'
- action: notify.mobile_app_sm_f956w
data:
title: 'Spoolman: no spool for slot color'
message: 'Segment of {{ grams }} g from {{ old_slot }} (color #{{ color
- | upper }}) could not be deducted - no Spoolman spool with remaining
- weight matches that color. Register the new roll in Spoolman, then
- adjust manually if needed.'
+ | upper }}) could not be deducted - no Spoolman spool in the CFS Lite
+ location with remaining weight matches that color. Register the new
+ roll in Spoolman with location CFS Lite, then adjust manually if needed.'
- action: input_number.set_value
target:
entity_id: input_number.sparkx_i7_spoolman_length_checkpoint
diff --git a/zigbee2mqtt/state.json b/zigbee2mqtt/state.json
index 3b9d4b1d..4dc6379c 100644
--- a/zigbee2mqtt/state.json
+++ b/zigbee2mqtt/state.json
@@ -4,7 +4,7 @@
"state": "ON",
"led_brightness": 100,
"countdown_to_turn_off": 0,
- "voltage": 119.6,
+ "voltage": 119.2,
"countdown_to_turn_on": 0,
"ac_frequency": 60,
"power_factor": 0.62,
@@ -15,9 +15,9 @@
"latest_source": "https://raw.githubusercontent.com/Koenkk/zigbee-OTA/master/images/ThirdReality/SmartPlug_Zigbee_PROD_OTA_V101_1.01.01.ota",
"latest_release_notes": null
},
- "power": 24.5,
+ "power": 192.1,
"linkquality": 149,
- "current": 0.46,
+ "current": 2.54,
"power_on_behavior": "on"
},
"0xffffb40e0607af27": {
@@ -27,7 +27,7 @@
"led_brightness": 100,
"countdown_to_turn_off": 0,
"countdown_to_turn_on": 0,
- "power": 2.1,
+ "power": 1.9,
"current": 0.09,
"energy": 54.08,
"power_factor": 0.18,
@@ -45,9 +45,9 @@
"state": "ON",
"led_brightness": 100,
"countdown_to_turn_off": 0,
- "voltage": 119.6,
+ "voltage": 119,
"countdown_to_turn_on": 0,
- "energy": 103.89,
+ "energy": 103.9,
"power_factor": 0.2,
"ac_frequency": 60,
"update": {
@@ -58,7 +58,7 @@
"latest_release_notes": null
},
"linkquality": 145,
- "power": 84.5,
+ "power": 83.8,
"current": 0.83,
"power_on_behavior": "on"
},
@@ -74,12 +74,12 @@
"led_brightness": 100,
"countdown_to_turn_off": 0,
"countdown_to_turn_on": 0,
- "voltage": 118,
+ "voltage": 118.5,
"state": "ON",
"ac_frequency": 60,
"energy": 211.06,
- "power": 100,
- "current": 0.92,
+ "power": 101.5,
+ "current": 0.93,
"power_factor": 0.95,
"update": {
"state": "idle",
@@ -95,7 +95,7 @@
"led_brightness": 100,
"countdown_to_turn_off": 0,
"countdown_to_turn_on": 0,
- "voltage": 118.9,
+ "voltage": 119,
"energy": 100.77,
"state": "ON",
"power": 10.8,
@@ -114,12 +114,12 @@
},
"0xffffb40e060895b3": {
"state": "ON",
- "voltage": 119.5,
+ "voltage": 119.4,
"ac_frequency": 60,
"energy": 14.23,
"current": 0.01,
"power": 0.1,
- "power_factor": 0.11,
+ "power_factor": 0.22,
"linkquality": 116,
"update": {
"state": "idle",
@@ -136,7 +136,7 @@
"0xffffb40e0608864e": {
"led_brightness": 100,
"countdown_to_turn_off": 0,
- "voltage": 119.9,
+ "voltage": 120.1,
"energy": 34.53,
"countdown_to_turn_on": 0,
"state": "ON",
@@ -224,7 +224,7 @@
},
"0xb40e060fffe717c5": {
"battery": 100,
- "occupancy": true,
+ "occupancy": false,
"tamper": false,
"battery_low": false,
"linkquality": 145,