diff --git a/.ha_mcp/backups/automation.1791477238762.20261015_132353.yaml b/.ha_mcp/backups/automation.1791477238762.20261015_132353.yaml new file mode 100644 index 00000000..dfbbed14 --- /dev/null +++ b/.ha_mcp/backups/automation.1791477238762.20261015_132353.yaml @@ -0,0 +1,152 @@ +# ha_mcp_backup +schema_version: 1 +domain: automation +entity_id: '1791477238762' +captured: '2026-10-15T13:23:53.053853+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 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 + 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 (state_attr(e[:-10], ''location'') + | default('''', true) | lower) == (''cfs lite '' ~ old_slot.split(''_'')[-1]) + %}{% 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 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 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 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 + 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/automation.1791477238799.20261015_132353.yaml b/.ha_mcp/backups/automation.1791477238799.20261015_132353.yaml new file mode 100644 index 00000000..dfd7c90c --- /dev/null +++ b/.ha_mcp/backups/automation.1791477238799.20261015_132353.yaml @@ -0,0 +1,122 @@ +# ha_mcp_backup +schema_version: 1 +domain: automation +entity_id: '1791477238799' +captured: '2026-10-15T13:23:53.149869+00:00' +tool: ha_config_set_automation +config: + id: '1791477238799' + alias: SparkX i7 - Filament sufficiency check at print start + description: When a print starts and stays printing for 45 s, reports how much filament + the job needs, how much has already been used and how much is left, then compares + the loaded CFS Lite spools against that. A single-colour job whose colour is in + a loaded slot is checked exactly against that slot's spool; otherwise the slicer + gives no per-colour figure, so each loaded spool is compared against the whole + job total. Always sends a notification. + triggers: + - trigger: state + entity_id: sensor.sparkx_i7_print_status + to: printing + for: 00:00:45 + conditions: [] + actions: + - variables: + gcm: '{{ states(''input_number.sparkx_i7_filament_g_cm'') | float(0.0298) }}' + length_cm: '{{ states(''sensor.sparkx_i7_expected_material_length'') | float(0) + }}' + weight_g: '{{ states(''sensor.sparkx_i7_expected_material_weight'') | float(0) + }}' + job_g: '{{ ((weight_g | float) if (weight_g | float) > 0 else (length_cm | float) + * (gcm | float)) | round(1) }}' + used_len: '{{ states(''sensor.sparkx_i7_used_material_length'') | float(0) }}' + used_g: '{{ (((used_len | float) * (gcm | float)) | round(1)) }}' + gcode_file: '{{ state_attr(''sensor.sparkx_i7_expected_material_length'', ''gcode_file'') + | default('''', true) }}' + - variables: + report: "{% set cols = namespace(list=[]) %}\n{% for n in ['1','2','3','4']\ + \ %}{% set cc = states('sensor.sparkx_i7_cfs_box_1_slot_' ~ n ~ '_color')\ + \ | replace('#','') | lower %}{% if cc not in ['','none','unknown','unavailable']\ + \ %}{% set cols.list = cols.list + [cc] %}{% endif %}{% endfor %}\n{% set\ + \ gc = namespace(list=[]) %}\n{% for p in (state_attr('sensor.sparkx_i7_expected_material_length','color')\ + \ | default('', true) | string).split(';') %}{% set q = p | replace('#','')\ + \ | lower | trim %}{% if q not in ['','none','unknown','unavailable'] %}{%\ + \ set gc.list = gc.list + [q] %}{% endif %}{% endfor %}\n{% set mc = namespace(list=[])\ + \ %}\n{% for p in (state_attr('sensor.sparkx_i7_expected_material_length','material')\ + \ | default('', true) | string).split(';') %}{% set q = p | trim %}{% if q\ + \ not in ['','none','unknown','unavailable'] %}{% set mc.list = mc.list +\ + \ [q] %}{% endif %}{% endfor %}\n{% set single = (gc.list | length) == 1 %}\n\ + {% set gcol = gc.list[0] if single else '' %}\n{% set aslot = (states('sensor.sparkx_i7_active_filament_slot')\ + \ | default('', true) | string).split('_')[-1] %}\n{% set aslot = aslot if\ + \ aslot in ['1','2','3','4'] else '' %}\n{% set rem_g = ([((job_g | float)\ + \ - (used_g | float)), 0] | max) | round(1) %}\n{% set ns = namespace(cover=[],\ + \ below=[], short=[], ok=[], missing=[], loaded=[], match_label='', match_rem=0)\ + \ %}\n{% for n in ['1','2','3','4'] %}\n{% set c = states('sensor.sparkx_i7_cfs_box_1_slot_'\ + \ ~ n ~ '_color') | replace('#','') | lower %}\n{% if c not in ['','none','unknown','unavailable']\ + \ %}\n{% set cname = states('sensor.sparkx_i7_slot_' ~ n ~ '_color_name')\ + \ %}\n{% set flabel = states('sensor.sparkx_i7_cfs_box_1_slot_' ~ n ~ '_filament')\ + \ %}\n{% set label = 'Slot ' ~ n ~ ' ' ~ (cname if cname not in ['','unknown','unavailable']\ + \ else (flabel if flabel not in ['','unknown','unavailable'] else ('#' ~ (c\ + \ | upper)))) %}\n{% set sp = namespace(id='', rem=0, fw=0) %}\n{% for e in\ + \ integration_entities('spoolman') if e.endswith('_color_hex') %}\n{% if sp.id\ + \ == '' and (state_attr(e[:-10],'location') | default('', true) | lower) ==\ + \ ('cfs lite ' ~ n) %}\n{% set sp.id = e[:-10] %}\n{% set sp.rem = states(e[:-10])\ + \ | float(0) %}\n{% set sp.fw = state_attr(e[:-10],'filament_weight') | float(0)\ + \ %}\n{% endif %}\n{% endfor %}\n{% set pct = ((((sp.rem | float) / (sp.fw\ + \ | float)) * 100) | round(0) | int) if (sp.fw | float) > 0 else 0 %}\n{%\ + \ if sp.id != '' %}{% set ns.loaded = ns.loaded + [label ~ ': ' ~ (sp.rem\ + \ | round(1)) ~ ' g (' ~ pct ~ '%)'] %}{% endif %}\n{% if sp.id != '' and\ + \ single and n == aslot %}{% set ns.match_label = label %}{% set ns.match_rem\ + \ = sp.rem %}{% endif %}\n{% if sp.id != '' %}\n{% if single and n == aslot\ + \ %}\n{% if (sp.rem | float) < (job_g | float) %}{% set ns.short = ns.short\ + \ + [label ~ ': ' ~ (sp.rem | round(1)) ~ ' g (' ~ pct ~ '%) left, ' ~ job_g\ + \ ~ ' g needed'] %}\n{% else %}{% set ns.ok = ns.ok + [label ~ ': ' ~ (sp.rem\ + \ | round(1)) ~ ' g (' ~ pct ~ '%) left, ' ~ job_g ~ ' g needed'] %}{% endif\ + \ %}\n{% elif single %}\n{% if (sp.rem | float) >= (job_g | float) %}{% set\ + \ ns.cover = ns.cover + [label ~ ': ' ~ (sp.rem | round(1)) ~ ' g (' ~ pct\ + \ ~ '%)'] %}\n{% else %}{% set ns.below = ns.below + [label ~ ': ' ~ (sp.rem\ + \ | round(1)) ~ ' g (' ~ pct ~ '%)'] %}{% endif %}\n{% else %}\n{% if (sp.rem\ + \ | float) < (job_g | float) %}{% set ns.short = ns.short + [label ~ ': '\ + \ ~ (sp.rem | round(1)) ~ ' g (' ~ pct ~ '%) left, up to ' ~ job_g ~ ' g needed']\ + \ %}\n{% else %}{% set ns.ok = ns.ok + [label ~ ': ' ~ (sp.rem | round(1))\ + \ ~ ' g (' ~ pct ~ '%) left, up to ' ~ job_g ~ ' g needed'] %}{% endif %}\n\ + {% endif %}\n{% elif job_g > 0 %}\n{% set ns.missing = ns.missing + [label\ + \ ~ ' (#' ~ (c | upper) ~ ')'] %}\n{% endif %}\n{% endif %}\n{% endfor %}\n\ + Job: {{ gcode_file if gcode_file not in ['','unknown','unavailable'] else\ + \ (state_attr('sensor.sparkx_i7_print_status','file') | default('', true)\ + \ | string).split('/')[-1] }}\nNeeds {{ job_g | round(1) }} g ({{ length_cm\ + \ | round(1) }} cm) \xB7 used {{ used_g }} g \xB7 {{ rem_g }} g left to print\n\ + Slicer: {{ (mc.list | join(', ')) if mc.list else 'material unknown' }} \xB7\ + \ {{ ('single colour #' ~ (gcol | upper)) if single else ((gc.list | length)\ + \ ~ ' colours') }}\n{% if job_g <= 0 %}Cannot verify: slicer filament estimate\ + \ not available yet.{% if ns.loaded %}\nLoaded:\n- {{ ns.loaded | join('\\\ + n- ') }}{% endif %}\n{% elif single and aslot != '' %}\n{% if ns.short %}NOT\ + \ ENOUGH filament:\n- {{ ns.short | join('\\n- ') }}\n{% elif ns.match_label\ + \ %}Enough filament: {{ ns.match_label }}: {{ ns.match_rem | round(1) }} g\ + \ left, {{ job_g }} g needed.\n{% else %}Active slot {{ aslot }} has no Spoolman\ + \ spool at CFS Lite, so remaining cannot be checked.{% endif %}\n{% elif single\ + \ %}No exact colour match for the print colour #{{ gcol | upper }} in a loaded\ + \ CFS slot, so the exact spool cannot be identified.{% if ns.cover %}\nWould\ + \ cover {{ job_g }} g:\n- {{ ns.cover | join('\\n- ') }}{% endif %}{% if ns.below\ + \ %}\nBelow {{ job_g }} g:\n- {{ ns.below | join('\\n- ') }}{% endif %}{%\ + \ if not ns.cover and not ns.below %}\nNo loaded CFS slot has a matching Spoolman\ + \ spool to compare.{% endif %}\n{% elif ns.short %}NOT ENOUGH filament - possible\ + \ shortfall:\n- {{ ns.short | join('\\n- ') }}{% if ns.ok %}\nOK (would still\ + \ cover the whole job):\n- {{ ns.ok | join('\\n- ') }}{% endif %}{% if ns.missing\ + \ %}\nNo Spoolman spool matched for:\n- {{ ns.missing | join('\\n- ') }}{%\ + \ endif %}\n{% elif ns.ok %}Enough filament:\n- {{ ns.ok | join('\\n- ') }}{%\ + \ if ns.missing %}\nNo Spoolman spool matched for:\n- {{ ns.missing | join('\\\ + n- ') }}{% endif %}\n{% elif ns.missing %}Could not match any loaded CFS slot\ + \ to a Spoolman spool.{% endif %}" + - action: notify.persistent_notification + data: + title: Print started - filament check + message: '{{ report }}' + - action: notify.mobile_app_francos_ipad + data: + title: Print started - filament check + message: '{{ report }}' + - action: notify.mobile_app_sm_f956w + data: + title: Print started - filament check + message: '{{ report }}' + mode: single diff --git a/.ha_mcp/backups/script.spoolman_set_spool_remaining.20261015_132353.yaml b/.ha_mcp/backups/script.spoolman_set_spool_remaining.20261015_132353.yaml new file mode 100644 index 00000000..96a42aa1 --- /dev/null +++ b/.ha_mcp/backups/script.spoolman_set_spool_remaining.20261015_132353.yaml @@ -0,0 +1,60 @@ +# ha_mcp_backup +schema_version: 1 +domain: script +entity_id: spoolman_set_spool_remaining +captured: '2026-10-15T13:23:53.118614+00:00' +tool: ha_config_set_script +config: + alias: Spoolman - Set Spool Remaining + fields: + slot: + name: CFS Slot + description: CFS slot number 1-4 - resolves the spool by the slot color hex + selector: + number: + min: 1 + max: 4 + mode: box + spool_id: + name: Spool ID + description: Direct Spoolman spool ID - used when no slot is given + selector: + number: + min: 1 + max: 9999 + mode: box + sequence: + - variables: + resolved_id: '{% set n = (slot | int(0)) if slot is defined else 0 %}{% if n + >= 1 %}{% set ns = namespace(id='''') %}{% for e in integration_entities(''spoolman'') + %}{% if e.endswith(''_color_hex'') and (state_attr(e[:-10], ''location'') + | default('''', true) | lower) == (''cfs lite '' ~ (n | int)) and ns.id == + '''' %}{% set ns.id = states(e[:-10] ~ ''_id'') %}{% endif %}{% endfor %}{{ + ns.id }}{% else %}{{ (spool_id | int(0)) if spool_id is defined else 0 }}{% + endif %}' + slot_n: '{{ (slot | int(0)) if slot is defined else 0 }}' + - choose: + - conditions: + condition: template + value_template: '{{ resolved_id | int(0) > 0 }}' + sequence: + - action: spoolman.patch_spool + data: + id: '{{ resolved_id | int(0) }}' + remaining_weight: '{{ states(''input_number.spoolman_set_remaining'') | + float(0) }}' + default: + - action: notify.persistent_notification + data: + title: Spoolman - remaining not set + message: '{% if slot_n | int >= 1 %}No Spoolman spool matched CFS slot {{ + slot_n }} (color {{ states(''sensor.sparkx_i7_cfs_box_1_slot_'' ~ (slot_n + | int) ~ ''_color'') }}) in the CFS Lite location - register a spool with + that color hex and location in Spoolman, then try again.{% else %}No spool + resolved - pass spool_id or a CFS slot 1-4. Nothing was set.{% endif %}' + mode: single + description: Sets a Spoolman spool remaining weight from the shared helper input_number.spoolman_set_remaining. + Pass slot (1-4) to resolve the spool by the CFS slot color hex in the CFS Lite + location (no remaining-weight guard, so an exhausted spool can be reset to full), + or pass spool_id directly. Notifies via persistent notification when nothing could + be resolved. diff --git a/.ha_mcp/logs/mcp_usage.jsonl b/.ha_mcp/logs/mcp_usage.jsonl index 7f7d2624..77ad5023 100644 --- a/.ha_mcp/logs/mcp_usage.jsonl +++ b/.ha_mcp/logs/mcp_usage.jsonl @@ -4564,3 +4564,6 @@ {"timestamp": "2026-10-15T13:22:39.822007+00:00", "tool_name": "ha_config_get_automation", "parameters": {"identifier": "1791477238762"}, "execution_time_ms": 334.63048934936523, "success": true, "error_message": null, "response_size_bytes": 5649, "user_context": null} {"timestamp": "2026-10-15T13:22:39.905414+00:00", "tool_name": "ha_config_get_automation", "parameters": {"identifier": "1791477238799"}, "execution_time_ms": 413.3596420288086, "success": true, "error_message": null, "response_size_bytes": 7574, "user_context": null} {"timestamp": "2026-10-15T13:23:18.159012+00:00", "tool_name": "ha_config_set_automation", "parameters": {"config": null, "identifier": "1791477238729", "python_transform": "_repl = [(\"matching the latched slot color\", \"matching the latched slot's CFS Lite location\"), (\"Spoolman: no spool for slot color\", \"Spoolman: no spool for the latched slot\"), (\"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.\", \"no Spoolman spool with remaining weight is in that slot's CFS Lite location. Register the new roll in Spoolman in that location, then adjust manually if needed.\")]\nstack = [config]\nfor _ in range(20000):\n if not stack:\n break\n node = stack.pop()\n if isinstance(node, dict):\n for k in list(node.keys()):\n v = node[k]\n if isinstance(v, str):\n for a, b in _repl:\n if a in v:\n v = v.replace(a, b)\n node[k] = v\n elif isinstance(v, (dict, list)):\n stack.append(v)\n elif isinstance(node, list):\n for i in range(len(node)):\n v = node[i]\n if isinstance(v, str):\n for a, b in _repl:\n if a in v:\n v = v.replace(a, b)\n node[i] = v\n elif isinstance(v, (dict, list)):\n stack.append(v)", "config_hash": "75d85b947ad8ec44", "take_control_of_blueprint": false, "category": null, "wait": true, "enabled": null, "run_actions": false, "MandatoryBPS": true, "BestPracticeKey": null}, "execution_time_ms": 622.8833198547363, "success": true, "error_message": null, "response_size_bytes": 83935, "user_context": null} +{"timestamp": "2026-10-15T13:23:54.686981+00:00", "tool_name": "ha_config_set_automation", "parameters": {"config": null, "identifier": "1791477238762", "python_transform": "_repl = [(\"matching the previous slot color hex\", \"matching the previous slot's CFS Lite location\"), (\"Spoolman: no spool for slot color\", \"Spoolman: no spool for the latched slot\"), (\"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.\", \"no Spoolman spool with remaining weight is in that slot's CFS Lite location. Register the new roll in Spoolman in that location, then adjust manually if needed.\")]\nstack = [config]\nfor _ in range(20000):\n if not stack:\n break\n node = stack.pop()\n if isinstance(node, dict):\n for k in list(node.keys()):\n v = node[k]\n if isinstance(v, str):\n for a, b in _repl:\n if a in v:\n v = v.replace(a, b)\n node[k] = v\n elif isinstance(v, (dict, list)):\n stack.append(v)\n elif isinstance(node, list):\n for i in range(len(node)):\n v = node[i]\n if isinstance(v, str):\n for a, b in _repl:\n if a in v:\n v = v.replace(a, b)\n node[i] = v\n elif isinstance(v, (dict, list)):\n stack.append(v)", "config_hash": "08b02cf1d51b4a70", "take_control_of_blueprint": false, "category": null, "wait": true, "enabled": null, "run_actions": false, "MandatoryBPS": false, "BestPracticeKey": null}, "execution_time_ms": 1540.0571823120117, "success": true, "error_message": null, "response_size_bytes": 9229, "user_context": null} +{"timestamp": "2026-10-15T13:23:54.831977+00:00", "tool_name": "ha_config_set_script", "parameters": {"script_id": "spoolman_set_spool_remaining", "config": null, "python_transform": "_repl = [(\"CFS slot number 1-4 - resolves the spool by the slot color hex\", \"CFS slot number 1-4 - resolves the spool by the slot's CFS Lite location\"), (\"register a spool with that color hex and location in Spoolman, then try again.\", \"register a spool with that CFS Lite location in Spoolman, then try again.\"), (\"Pass slot (1-4) to resolve the spool by the CFS slot color hex in the CFS Lite location\", \"Pass slot (1-4) to resolve the spool by the CFS slot location in the CFS Lite\")]\nstack = [config]\nfor _ in range(20000):\n if not stack:\n break\n node = stack.pop()\n if isinstance(node, dict):\n for k in list(node.keys()):\n v = node[k]\n if isinstance(v, str):\n for a, b in _repl:\n if a in v:\n v = v.replace(a, b)\n node[k] = v\n elif isinstance(v, (dict, list)):\n stack.append(v)\n elif isinstance(node, list):\n for i in range(len(node)):\n v = node[i]\n if isinstance(v, str):\n for a, b in _repl:\n if a in v:\n v = v.replace(a, b)\n node[i] = v\n elif isinstance(v, (dict, list)):\n stack.append(v)", "config_hash": "47a9069453dcf38d", "take_control_of_blueprint": false, "category": null, "wait": true, "run": null, "variables": null, "MandatoryBPS": false, "BestPracticeKey": null}, "execution_time_ms": 1638.6640071868896, "success": true, "error_message": null, "response_size_bytes": 7396, "user_context": null} +{"timestamp": "2026-10-15T13:23:55.041771+00:00", "tool_name": "ha_config_set_automation", "parameters": {"config": null, "identifier": "1791477238799", "python_transform": "_repl = [(\"A single-colour job whose colour is in a loaded slot is checked exactly against that slot's spool; otherwise the\", \"A single-colour job is checked exactly against the spool in the print's active CFS slot; otherwise the\"), (\"No exact colour match for the print colour #{{ gcol | upper }} in a loaded CFS slot, so the exact spool cannot be identified.\", \"No active CFS slot reported for the print colour #{{ gcol | upper }}, so the exact spool cannot be identified.\")]\nstack = [config]\nfor _ in range(20000):\n if not stack:\n break\n node = stack.pop()\n if isinstance(node, dict):\n for k in list(node.keys()):\n v = node[k]\n if isinstance(v, str):\n for a, b in _repl:\n if a in v:\n v = v.replace(a, b)\n node[k] = v\n elif isinstance(v, (dict, list)):\n stack.append(v)\n elif isinstance(node, list):\n for i in range(len(node)):\n v = node[i]\n if isinstance(v, str):\n for a, b in _repl:\n if a in v:\n v = v.replace(a, b)\n node[i] = v\n elif isinstance(v, (dict, list)):\n stack.append(v)", "config_hash": "a2c48f9b809f1fb2", "take_control_of_blueprint": false, "category": null, "wait": true, "enabled": null, "run_actions": false, "MandatoryBPS": false, "BestPracticeKey": null}, "execution_time_ms": 1822.0086097717285, "success": true, "error_message": null, "response_size_bytes": 1582, "user_context": null} diff --git a/automations.yaml b/automations.yaml index 49db0b3c..ea475f5d 100644 --- a/automations.yaml +++ b/automations.yaml @@ -4554,9 +4554,9 @@ 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 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.' + slot''s CFS Lite location (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 @@ -4647,25 +4647,25 @@ default: - action: notify.persistent_notification data: - title: 'Spoolman: no spool for slot color' + title: 'Spoolman: no spool for the latched slot' message: 'Segment of {{ grams }} g from {{ old_slot }} (color #{{ color - | 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.' + | upper }}) could not be deducted - no Spoolman spool with remaining + weight is in that slot''s CFS Lite location. Register the new roll + in Spoolman in that location, then adjust manually if needed.' - action: notify.mobile_app_francos_ipad data: - title: 'Spoolman: no spool for slot color' + title: 'Spoolman: no spool for the latched slot' message: 'Segment of {{ grams }} g from {{ old_slot }} (color #{{ color - | 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.' + | upper }}) could not be deducted - no Spoolman spool with remaining + weight is in that slot''s CFS Lite location. Register the new roll + in Spoolman in that location, then adjust manually if needed.' - action: notify.mobile_app_sm_f956w data: - title: 'Spoolman: no spool for slot color' + title: 'Spoolman: no spool for the latched slot' message: 'Segment of {{ grams }} g from {{ old_slot }} (color #{{ color - | 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.' + | upper }}) could not be deducted - no Spoolman spool with remaining + weight is in that slot''s CFS Lite location. Register the new roll + in Spoolman in that location, then adjust manually if needed.' - action: input_number.set_value target: entity_id: input_number.sparkx_i7_spoolman_length_checkpoint @@ -4776,10 +4776,10 @@ alias: SparkX i7 - Filament sufficiency check at print start description: When a print starts and stays printing for 45 s, reports how much filament the job needs, how much has already been used and how much is left, then compares - the loaded CFS Lite spools against that. A single-colour job whose colour is in - a loaded slot is checked exactly against that slot's spool; otherwise the slicer - gives no per-colour figure, so each loaded spool is compared against the whole - job total. Always sends a notification. + the loaded CFS Lite spools against that. A single-colour job is checked exactly + against the spool in the print's active CFS slot; otherwise the slicer gives no + per-colour figure, so each loaded spool is compared against the whole job total. + Always sends a notification. triggers: - trigger: state entity_id: sensor.sparkx_i7_print_status @@ -4948,9 +4948,8 @@ {% else %}Active slot {{ aslot }} has no Spoolman spool at CFS Lite, so remaining cannot be checked.{% endif %} - {% elif single %}No exact colour match for the print colour #{{ gcol | upper - }} in a loaded CFS slot, so the exact spool cannot be identified.{% if ns.cover - %} + {% elif single %}No active CFS slot reported for the print colour #{{ gcol + | upper }}, so the exact spool cannot be identified.{% if ns.cover %} Would cover {{ job_g }} g: diff --git a/scripts.yaml b/scripts.yaml index 015aa868..0231cd4f 100644 --- a/scripts.yaml +++ b/scripts.yaml @@ -245,7 +245,8 @@ spoolman_set_spool_remaining: fields: slot: name: CFS Slot - description: CFS slot number 1-4 - resolves the spool by the slot color hex + description: CFS slot number 1-4 - resolves the spool by the slot's CFS Lite + location selector: number: min: 1 @@ -263,8 +264,8 @@ spoolman_set_spool_remaining: - variables: resolved_id: '{% set n = (slot | int(0)) if slot is defined else 0 %}{% if n >= 1 %}{% set ns = namespace(id='''') %}{% for e in integration_entities(''spoolman'') - %}{% if e.endswith(''_color_hex'') and (state_attr(e[:-10], ''location'') | - default('''', true) | lower) == (''cfs lite '' ~ (n | int)) and ns.id == + %}{% if e.endswith(''_color_hex'') and (state_attr(e[:-10], ''location'') + | default('''', true) | lower) == (''cfs lite '' ~ (n | int)) and ns.id == '''' %}{% set ns.id = states(e[:-10] ~ ''_id'') %}{% endif %}{% endfor %}{{ ns.id }}{% else %}{{ (spool_id | int(0)) if spool_id is defined else 0 }}{% endif %}' @@ -286,11 +287,11 @@ spoolman_set_spool_remaining: message: '{% if slot_n | int >= 1 %}No Spoolman spool matched CFS slot {{ slot_n }} (color {{ states(''sensor.sparkx_i7_cfs_box_1_slot_'' ~ (slot_n | int) ~ ''_color'') }}) in the CFS Lite location - register a spool with - that color hex and location in Spoolman, then try again.{% else %}No spool - resolved - pass spool_id or a CFS slot 1-4. Nothing was set.{% endif %}' + that CFS Lite location in Spoolman, then try again.{% else %}No spool resolved + - pass spool_id or a CFS slot 1-4. Nothing was set.{% endif %}' mode: single description: Sets a Spoolman spool remaining weight from the shared helper input_number.spoolman_set_remaining. - Pass slot (1-4) to resolve the spool by the CFS slot color hex in the CFS Lite - location (no remaining-weight guard, so an exhausted spool can be reset to full), - or pass spool_id directly. Notifies via persistent notification when nothing could - be resolved. + Pass slot (1-4) to resolve the spool by the CFS slot location in the CFS Lite + (no remaining-weight guard, so an exhausted spool can be reset to full), or pass + spool_id directly. Notifies via persistent notification when nothing could be + resolved.