diff --git a/.ha_mcp/backups/automation.1791477238729.20261009_002125.yaml b/.ha_mcp/backups/automation.1791477238729.20261009_002125.yaml new file mode 100644 index 00000000..06fc8601 --- /dev/null +++ b/.ha_mcp/backups/automation.1791477238729.20261009_002125.yaml @@ -0,0 +1,139 @@ +# ha_mcp_backup +schema_version: 1 +domain: automation +entity_id: '1791477238729' +captured: '2026-10-09T00:21:25.595930+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 %}{% 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_002125.yaml b/.ha_mcp/backups/automation.1791477238762.20261009_002125.yaml new file mode 100644 index 00000000..442d88a3 --- /dev/null +++ b/.ha_mcp/backups/automation.1791477238762.20261009_002125.yaml @@ -0,0 +1,150 @@ +# ha_mcp_backup +schema_version: 1 +domain: automation +entity_id: '1791477238762' +captured: '2026-10-09T00:21:25.534171+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 %}{% 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/logs/mcp_usage.jsonl b/.ha_mcp/logs/mcp_usage.jsonl index c64b3e9a..081f5eb9 100644 --- a/.ha_mcp/logs/mcp_usage.jsonl +++ b/.ha_mcp/logs/mcp_usage.jsonl @@ -4134,3 +4134,5 @@ {"timestamp": "2026-10-09T00:18:39.940032+00:00", "tool_name": "ha_config_get_dashboard", "parameters": {"url_path": null, "list_only": false, "force_reload": false, "entity_id": null, "card_type": null, "heading": null, "include_config": false, "include_screenshot": false, "view_path": null, "mode": "search", "query": "cfs lite"}, "execution_time_ms": 140.5770778656006, "success": true, "error_message": null, "response_size_bytes": 2137, "user_context": null} {"timestamp": "2026-10-09T00:20:35.524223+00:00", "tool_name": "ha_config_get_automation", "parameters": {"identifier": "1791477238729"}, "execution_time_ms": 661.4665985107422, "success": true, "error_message": null, "response_size_bytes": 5731, "user_context": null} {"timestamp": "2026-10-09T00:20:35.586619+00:00", "tool_name": "ha_config_get_automation", "parameters": {"identifier": "1791477238762"}, "execution_time_ms": 691.5700435638428, "success": true, "error_message": null, "response_size_bytes": 5423, "user_context": null} +{"timestamp": "2026-10-09T00:21:27.061369+00:00", "tool_name": "ha_config_set_automation", "parameters": {"config": null, "identifier": "1791477238762", "python_transform": "config['actions'][1]['choose'][1]['sequence'][1]['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 }}\"", "config_hash": "006205c46f1536e7", "take_control_of_blueprint": false, "category": null, "wait": true, "enabled": null, "run_actions": false, "MandatoryBPS": false, "BestPracticeKey": null}, "execution_time_ms": 1457.3009014129639, "success": true, "error_message": null, "response_size_bytes": 7481, "user_context": null} +{"timestamp": "2026-10-09T00:21:27.113179+00:00", "tool_name": "ha_config_set_automation", "parameters": {"config": null, "identifier": "1791477238729", "python_transform": "config['actions'][2]['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 }}\"", "config_hash": "12ad28705dc169cf", "take_control_of_blueprint": false, "category": null, "wait": true, "enabled": null, "run_actions": false, "MandatoryBPS": false, "BestPracticeKey": null}, "execution_time_ms": 1505.1090717315674, "success": true, "error_message": null, "response_size_bytes": 7449, "user_context": null} diff --git a/automations.yaml b/automations.yaml index 20e8c4b6..1356c6c8 100644 --- a/automations.yaml +++ b/automations.yaml @@ -4401,9 +4401,10 @@ - variables: spool_id: '{% set ns = namespace(id='''') %}{% for e in integration_entities(''spoolman'') %}{% if e.endswith(''_color_hex'') and (states(e) | replace(''#'', '''') | - lower) == color %}{% set b = e[:-10] %}{% if (states(b) | float(0)) > 0 and - ns.id == '''' %}{% set ns.id = states(b ~ ''_id'') %}{% endif %}{% endif %}{% - endfor %}{{ ns.id }}' + 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 '''' }}' @@ -4571,9 +4572,10 @@ - variables: spool_id: '{% set ns = namespace(id='''') %}{% for e in integration_entities(''spoolman'') %}{% if e.endswith(''_color_hex'') and (states(e) | replace(''#'', '''') - | lower) == color %}{% set b = e[:-10] %}{% if (states(b) | float(0)) - > 0 and ns.id == '''' %}{% set ns.id = states(b ~ ''_id'') %}{% endif - %}{% endif %}{% endfor %}{{ ns.id }}' + | 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 }}'