diff --git a/custom_components/st_hass/frontend/st-panel.js b/custom_components/st_hass/frontend/st-panel.js index f639252..f779a22 100644 --- a/custom_components/st_hass/frontend/st-panel.js +++ b/custom_components/st_hass/frontend/st-panel.js @@ -2751,8 +2751,8 @@ END_PROGRAM`, this._syntaxOk = !0, this._triggers = [], this._diagnostics = [], this._isDeploying = !0, this._deployFeedback = null; try { const [{ transpile: n }, { deploy: o, HAApiClient: d }] = await Promise.all([ - import("./transpiler-deploy-st5GVH6E.js").then((L) => L.i), - import("./transpiler-deploy-st5GVH6E.js").then((L) => L.a) + import("./transpiler-deploy-41TsQdYw.js").then((L) => L.i), + import("./transpiler-deploy-41TsQdYw.js").then((L) => L.a) ]), c = n(t.ast, "home"); if (c.diagnostics.some((L) => L.severity === "Error")) { this._setDeployFeedback( diff --git a/custom_components/st_hass/frontend/transpiler-deploy-st5GVH6E.js b/custom_components/st_hass/frontend/transpiler-deploy-41TsQdYw.js similarity index 84% rename from custom_components/st_hass/frontend/transpiler-deploy-st5GVH6E.js rename to custom_components/st_hass/frontend/transpiler-deploy-41TsQdYw.js index 282af6f..3bb554f 100644 --- a/custom_components/st_hass/frontend/transpiler-deploy-st5GVH6E.js +++ b/custom_components/st_hass/frontend/transpiler-deploy-41TsQdYw.js @@ -1,7 +1,7 @@ var N = Object.defineProperty; -var A = (p, e, t) => e in p ? N(p, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : p[e] = t; -var c = (p, e, t) => A(p, typeof e != "symbol" ? e + "" : e, t); -import { a as E, b as C, w as M, p as f } from "./analyzer-DbAWr__X.js"; +var E = (l, e, t) => e in l ? N(l, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : l[e] = t; +var c = (l, e, t) => E(l, typeof e != "symbol" ? e + "" : e, t); +import { a as A, b as C, w as O, p as f } from "./analyzer-DbAWr__X.js"; class _ { constructor(e, t) { c(this, "context"); @@ -59,10 +59,10 @@ class _ { convertTimeToSeconds(e) { if (!/^T#/i.test(e)) return "0"; const t = e.slice(2), a = /(\d+)(ms|h|m|s)/gi; - let i = 0, n = ""; - for (const r of t.matchAll(a)) { - const s = parseInt(r[1], 10), o = r[2].toLowerCase(); - switch (n += r[0], o) { + let i = 0, r = ""; + for (const n of t.matchAll(a)) { + const s = parseInt(n[1], 10), o = n[2].toLowerCase(); + switch (r += n[0], o) { case "h": i += s * 3600; break; @@ -77,7 +77,7 @@ class _ { break; } } - return n.toLowerCase() === t.toLowerCase() ? String(i) : "0"; + return r.toLowerCase() === t.toLowerCase() ? String(i) : "0"; } // ========================================================================== // Variable Reference Generation @@ -244,8 +244,8 @@ class _ { throw new Error("MUX requires at least 2 arguments"); const t = e[0], a = e.slice(1); let i = a[a.length - 1]; - for (let n = a.length - 2; n >= 0; n--) - i = `(${a[n]} if ${t} == ${n} else ${i})`; + for (let r = a.length - 2; r >= 0; r--) + i = `(${a[r]} if ${t} == ${r} else ${i})`; return i; } generateLIMIT(e) { @@ -279,8 +279,8 @@ class _ { return `${this.generateExpression(e.object)}.${e.member}`; } } -function O(p, e) { - const t = `states('${p}')`, a = "['unavailable', 'unknown', 'none', '']"; +function M(l, e) { + const t = `states('${l}')`, a = "['unavailable', 'unknown', 'none', '']"; switch (e.toUpperCase()) { case "BOOL": return `{{ ${t} in ['on', 'true', 'True', '1'] }}`; @@ -294,15 +294,15 @@ function O(p, e) { return `{{ ${t} }}`; } } -function k(p, e) { - return `{% set last = states('${p}') %} +function H(l, e) { + return `{% set last = states('${l}') %} {% if last in ['unknown', 'unavailable', 'none', ''] %} true {% else %} {{ (as_timestamp(now()) - as_timestamp(last, 0)) > ${e} }} {% endif %}`; } -const T = 1e3; +const w = 1e3; class v { constructor(e, t, a) { c(this, "context"); @@ -317,8 +317,8 @@ class v { const t = []; return e.forEach((a, i) => { this.sourceMap && this.sourceMap.pushPath(String(i)); - const n = this.generateAction(a); - t.push(...n), this.sourceMap && this.sourceMap.popPath(); + const r = this.generateAction(a); + t.push(...r), this.sourceMap && this.sourceMap.popPath(); }), t; } /** @@ -372,54 +372,54 @@ class v { }; } generateEntityWrite(e, t, a) { - const i = e.split(".")[0], n = this.jinja.generateExpression(t); + const i = e.split(".")[0], r = this.jinja.generateExpression(t); return a.toUpperCase() === "BOOL" ? [{ - service: `{{ '${i}.turn_on' if ${n} else '${i}.turn_off' }}`, + service: `{{ '${i}.turn_on' if ${r} else '${i}.turn_off' }}`, target: { entity_id: e } }] : i === "input_number" || i === "number" ? [{ service: `${i}.set_value`, target: { entity_id: e }, - data: { value: `{{ ${n} }}` } + data: { value: `{{ ${r} }}` } }] : i === "input_text" ? [{ service: "input_text.set_value", target: { entity_id: e }, - data: { value: `{{ ${n} }}` } + data: { value: `{{ ${r} }}` } }] : [{ service: `${i}.turn_on`, target: { entity_id: e } }]; } generateHelperWrite(e, t, a) { - const i = e.split(".")[0], n = this.jinja.generateExpression(t); + const i = e.split(".")[0], r = this.jinja.generateExpression(t); switch (i) { case "input_boolean": return [{ - service: `{{ 'input_boolean.turn_on' if ${n} else 'input_boolean.turn_off' }}`, + service: `{{ 'input_boolean.turn_on' if ${r} else 'input_boolean.turn_off' }}`, target: { entity_id: e } }]; case "input_number": return [{ service: "input_number.set_value", target: { entity_id: e }, - data: { value: `{{ ${n} }}` } + data: { value: `{{ ${r} }}` } }]; case "input_text": return [{ service: "input_text.set_value", target: { entity_id: e }, - data: { value: `{{ ${n} }}` } + data: { value: `{{ ${r} }}` } }]; case "input_datetime": return [{ service: "input_datetime.set_datetime", target: { entity_id: e }, - data: { datetime: `{{ ${n} }}` } + data: { datetime: `{{ ${r} }}` } }]; case "counter": return [{ service: "counter.set_value", target: { entity_id: e }, - data: { value: `{{ ${n} }}` } + data: { value: `{{ ${r} }}` } }]; default: throw new Error(`Unknown helper type: ${i}`); @@ -446,17 +446,17 @@ class v { generateCase(e) { this.sourceMap && e.location && this.sourceMap.recordNode(e, "CASE statement"); const t = this.jinja.generateExpression(e.selector), a = []; - for (const n of e.cases) { - const r = n.values.map((o) => { - const u = this.jinja.generateExpression(o); + for (const r of e.cases) { + const n = r.values.map((o) => { + const p = this.jinja.generateExpression(o); return { condition: "template", - value_template: `{{ ${t} == ${u} }}` + value_template: `{{ ${t} == ${p} }}` }; - }), s = r.length === 1 ? r[0] : { condition: "or", conditions: r }; + }), s = n.length === 1 ? n[0] : { condition: "or", conditions: n }; a.push({ conditions: [s], - sequence: this.generateActions(n.body) + sequence: this.generateActions(r.body) }); } const i = { choose: a }; @@ -464,16 +464,16 @@ class v { } generateFor(e) { this.sourceMap && e.location && this.sourceMap.recordNode(e, "FOR statement"); - const t = this.jinja.generateExpression(e.from), a = this.jinja.generateExpression(e.to), i = e.by ? this.jinja.generateExpression(e.by) : "1", n = `{{ (((${a}) - (${t})) / (${i})) | int + 1 }}`, r = { + const t = this.jinja.generateExpression(e.from), a = this.jinja.generateExpression(e.to), i = e.by ? this.jinja.generateExpression(e.by) : "1", r = `{{ (((${a}) - (${t})) / (${i})) | int + 1 }}`, n = { variables: { [e.variable]: `{{ (${t}) + ((repeat.index | default(1) | int - 1) * (${i})) }}` } }; return { repeat: { - count: n, + count: r, sequence: [ - r, + n, ...this.generateActions(e.body) ] } @@ -483,7 +483,7 @@ class v { this.sourceMap && e.location && this.sourceMap.recordNode(e, "WHILE statement"); const t = this.generateCondition(e.condition), a = { condition: "template", - value_template: `{{ (repeat.index | default(1) | int) <= ${T} }}` + value_template: `{{ (repeat.index | default(1) | int) <= ${w} }}` }; return { repeat: { @@ -496,7 +496,7 @@ class v { this.sourceMap && e.location && this.sourceMap.recordNode(e, "REPEAT statement"); const t = this.generateCondition(e.condition), a = { condition: "template", - value_template: `{{ (repeat.index | default(1) | int) <= ${T} }}` + value_template: `{{ (repeat.index | default(1) | int) <= ${w} }}` }; return { repeat: { @@ -545,7 +545,7 @@ class v { }; } } -class H { +class k { constructor(e) { c(this, "jinja"); this.jinja = new _(e); @@ -566,20 +566,20 @@ class H { * leaves the instance/type resolution to the caller. */ parseTimerCall(e, t) { - var n; + var r; const a = t.name.toUpperCase(), i = {}; - for (const r of t.arguments) { - const s = (n = r.name) == null ? void 0 : n.toUpperCase(); + for (const n of t.arguments) { + const s = (r = n.name) == null ? void 0 : r.toUpperCase(); if (s) switch (s) { case "IN": - i.IN = this.jinja.generateExpression(r.value); + i.IN = this.jinja.generateExpression(n.value); break; case "PT": - i.PT = this.parseTimeToSeconds(r.value); + i.PT = this.parseTimeToSeconds(n.value); break; case "R": - i.R = this.jinja.generateExpression(r.value); + i.R = this.jinja.generateExpression(n.value); break; } } @@ -609,7 +609,7 @@ class H { // TON - On-Delay Timer // ========================================================================== transpileTON(e, t, a, i) { - const n = [ + const r = [ { // High-level choose block for TON behavior // Case 1: IN = TRUE and timer idle -> start timer @@ -631,7 +631,7 @@ class H { } ] } - ], r = this.generateFinishedAutomation( + ], n = this.generateFinishedAutomation( e, a, t.IN, @@ -643,8 +643,8 @@ class H { return { entities: a, helpers: i, - mainActions: n, - finishedAutomation: r, + mainActions: r, + finishedAutomation: n, outputMappings: s }; } @@ -652,7 +652,7 @@ class H { // TOF - Off-Delay Timer // ========================================================================== transpileTOF(e, t, a, i) { - const n = [ + const r = [ { choose: [ // Case 1: IN = TRUE -> cancel timer and set Q immediately @@ -674,7 +674,7 @@ class H { } ] } - ], r = this.generateFinishedAutomation( + ], n = this.generateFinishedAutomation( e, a, `not (${t.IN})`, @@ -685,8 +685,8 @@ class H { return { entities: a, helpers: i, - mainActions: n, - finishedAutomation: r, + mainActions: r, + finishedAutomation: n, outputMappings: s }; } @@ -694,27 +694,27 @@ class H { // TP - Pulse Timer // ========================================================================== transpileTP(e, t, a, i) { - const n = `input_boolean.${this.sanitize( + const r = `input_boolean.${this.sanitize( e.projectName )}_${this.sanitize(e.programName)}_${this.sanitize(e.name)}_triggered`; i.push({ - id: n, + id: r, type: "input_boolean", name: `ST ${e.programName} ${e.name} Triggered`, initial: !1 }); - const r = [ + const n = [ { choose: [ // Case 1: Rising edge (IN TRUE and not triggered) -> start pulse { conditions: [ this.templateCondition(t.IN), - this.stateCondition(n, "off"), + this.stateCondition(r, "off"), this.stateCondition(a.timerId, "idle") ], sequence: [ - this.booleanTurnOn(n), + this.booleanTurnOn(r), this.booleanTurnOn(a.outputHelperId), this.timerStart(a.timerId, t.PT) ] @@ -725,7 +725,7 @@ class H { this.templateCondition(`not (${t.IN})`), this.stateCondition(a.timerId, "idle") ], - sequence: [this.booleanTurnOff(n)] + sequence: [this.booleanTurnOff(r)] } ] } @@ -741,7 +741,7 @@ class H { return { entities: a, helpers: i, - mainActions: r, + mainActions: n, finishedAutomation: s, outputMappings: o }; @@ -856,32 +856,32 @@ class H { const t = e.raw; if (/^T#/i.test(t)) { const a = t.slice(2), i = /(\d+)(ms|h|m|s)/gi; - let n = 0, r = ""; + let r = 0, n = ""; for (const s of a.matchAll(i)) { - const o = parseInt(s[1], 10), u = s[2].toLowerCase(); - switch (r += s[0], u) { + const o = parseInt(s[1], 10), p = s[2].toLowerCase(); + switch (n += s[0], p) { case "h": - n += o * 3600; + r += o * 3600; break; case "m": - n += o * 60; + r += o * 60; break; case "s": - n += o; + r += o; break; case "ms": - n += o / 1e3; + r += o / 1e3; break; } } - if (r.toLowerCase() === a.toLowerCase()) - return String(n); + if (n.toLowerCase() === a.toLowerCase()) + return String(r); } } return this.jinja.generateExpression(e); } } -class L { +class j { constructor() { c(this, "timerMappings", /* @__PURE__ */ new Map()); } @@ -909,7 +909,7 @@ class L { return a === "Q" || a === "ET"; } } -class R { +class L { constructor(e) { c(this, "mappings", /* @__PURE__ */ new Map()); c(this, "currentPath", []); @@ -1058,7 +1058,7 @@ class I { c(this, "timerHelpers", []); c(this, "additionalAutomations", []); c(this, "timerMainActions", []); - this.ast = e, this.projectName = t, a && (this.sourceMapBuilder = new R({ + this.ast = e, this.projectName = t, a && (this.sourceMapBuilder = new L({ project: t, program: e.name, sourceFile: `${e.name}.st`, @@ -1070,26 +1070,26 @@ class I { * Transpile AST to HA automation and script */ transpile() { - this.depAnalysis = E(this.ast), this.storageAnalysis = C(this.ast, this.projectName), this.diagnostics.push( - ...this.depAnalysis.diagnostics.map((n) => { - var r; + this.depAnalysis = A(this.ast), this.storageAnalysis = C(this.ast, this.projectName), this.diagnostics.push( + ...this.depAnalysis.diagnostics.map((r) => { + var n; return { - severity: n.severity, - code: n.code, - message: n.message, - stLine: (r = n.location) == null ? void 0 : r.line + severity: r.severity, + code: r.code, + message: r.message, + stLine: (n = r.location) == null ? void 0 : n.line }; }), - ...this.storageAnalysis.diagnostics.map((n) => { - var r; + ...this.storageAnalysis.diagnostics.map((r) => { + var n; return { - severity: n.severity, - code: n.code, - message: n.message, - stLine: (r = n.location) == null ? void 0 : r.line + severity: r.severity, + code: r.code, + message: r.message, + stLine: (n = r.location) == null ? void 0 : n.line }; }) - ), this.buildContext(), this.timerTranspiler = new H(this.context), this.timerResolver = new L(), this.processTimerFBs(); + ), this.buildContext(), this.timerTranspiler = new k(this.context), this.timerResolver = new j(), this.processTimerFBs(); const e = this.generateAutomation(), t = this.generateScript(), a = this.collectHelpers(), i = this.sourceMapBuilder ? this.sourceMapBuilder.build(e.id, t.alias.replace(/\[ST\]\s*/, "").toLowerCase().replace(/[^a-z0-9_]/g, "_")) : { version: 1, project: this.projectName, @@ -1111,23 +1111,23 @@ class I { // Context Building // ========================================================================== buildContext() { - var a, i, n; + var a, i, r; const e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Map(); - for (const r of this.ast.variables) { - const s = this.storageAnalysis.variables.find((l) => l.name === r.name), o = this.depAnalysis.dependencies.find((l) => l.variableName === r.name), u = { - name: r.name, - dataType: r.dataType.name, - isInput: ((a = r.binding) == null ? void 0 : a.direction) === "INPUT" || r.section === "VAR_INPUT", - isOutput: ((i = r.binding) == null ? void 0 : i.direction) === "OUTPUT" || r.section === "VAR_OUTPUT", + for (const n of this.ast.variables) { + const s = this.storageAnalysis.variables.find((u) => u.name === n.name), o = this.depAnalysis.dependencies.find((u) => u.variableName === n.name), p = { + name: n.name, + dataType: n.dataType.name, + isInput: ((a = n.binding) == null ? void 0 : a.direction) === "INPUT" || n.section === "VAR_INPUT", + isOutput: ((i = n.binding) == null ? void 0 : i.direction) === "OUTPUT" || n.section === "VAR_OUTPUT", isPersistent: (s == null ? void 0 : s.storage.type) === "PERSISTENT", helperId: s == null ? void 0 : s.storage.helperId, - entityId: (o == null ? void 0 : o.entityId) || ((n = r.binding) == null ? void 0 : n.entityId) + entityId: (o == null ? void 0 : o.entityId) || ((r = n.binding) == null ? void 0 : r.entityId) }; - e.set(r.name, u), o && o.entityId && (o.direction === "INPUT" || o.direction === "OUTPUT") && t.set(r.name, { + e.set(n.name, p), o && o.entityId && (o.direction === "INPUT" || o.direction === "OUTPUT") && t.set(n.name, { entityId: o.entityId, - variableName: r.name, + variableName: n.name, direction: o.direction, - dataType: r.dataType.name + dataType: n.dataType.name }); } this.context = { @@ -1151,7 +1151,7 @@ class I { const a = t.dataType.name.toUpperCase(); (a === "TON" || a === "TOF" || a === "TP") && e.set(t.name, a); } - e.size !== 0 && M(this.ast, { + e.size !== 0 && O(this.ast, { onFunctionCall: (t) => { const a = e.get(t.name); if (!a) @@ -1161,10 +1161,10 @@ class I { type: a, programName: this.ast.name, projectName: this.projectName - }, n = this.timerTranspiler.parseTimerCall(t.name, t), r = { - IN: n.inputs.IN ?? "true", - PT: n.inputs.PT ?? "0" - }, s = this.timerTranspiler.transpileTimer(i, r); + }, r = this.timerTranspiler.parseTimerCall(t.name, t), n = { + IN: r.inputs.IN ?? "true", + PT: r.inputs.PT ?? "0" + }, s = this.timerTranspiler.transpileTimer(i, n); this.timerHelpers.push(...s.helpers), this.additionalAutomations.push(s.finishedAutomation), this.timerMainActions.push(...s.mainActions), this.timerResolver.registerTimer(i.name, s.outputMappings); } }); @@ -1173,8 +1173,8 @@ class I { // Automation Generation // ========================================================================== generateAutomation() { - var n, r; - const e = f(this.ast.pragmas), t = (n = e.find((s) => s.name === "throttle")) == null ? void 0 : n.value, a = (r = e.find((s) => s.name === "debounce")) == null ? void 0 : r.value, i = { + var r, n; + const e = f(this.ast.pragmas), t = (r = e.find((s) => s.name === "throttle")) == null ? void 0 : r.value, a = (n = e.find((s) => s.name === "debounce")) == null ? void 0 : n.value, i = { id: `st_${this.projectName}_${this.ast.name}`.toLowerCase().replace(/[^a-z0-9_]/g, "_"), alias: `[ST] ${this.ast.name}`, description: `Generated from ST program: ${this.ast.name}`, @@ -1247,10 +1247,10 @@ class I { parseTimeToSeconds(e) { if (!/^T#/i.test(e)) return 0; const t = e.slice(2), a = /(\d+)(ms|h|m|s)/gi; - let i = 0, n = ""; - for (const r of t.matchAll(a)) { - const s = parseInt(r[1], 10), o = r[2].toLowerCase(); - switch (n += r[0], o) { + let i = 0, r = ""; + for (const n of t.matchAll(a)) { + const s = parseInt(n[1], 10), o = n[2].toLowerCase(); + switch (r += n[0], o) { case "h": i += s * 3600; break; @@ -1265,7 +1265,7 @@ class I { break; } } - return n.toLowerCase() === t.toLowerCase() ? i : 0; + return r.toLowerCase() === t.toLowerCase() ? i : 0; } // ========================================================================== // Trigger Mapping @@ -1325,10 +1325,10 @@ class I { description: `Logic script for ST program: ${this.ast.name}`, mode: t, sequence: [] - }, n = this.generateVariableInitializers(); - Object.keys(n).length > 0 && (i.variables = n), this.sourceMapBuilder && this.sourceMapBuilder.pushPath("sequence"); - const r = a.generateActions(this.ast.body); - if (this.sourceMapBuilder && this.sourceMapBuilder.popPath(), i.sequence = [...this.timerMainActions, ...r], this.sourceMapBuilder) { + }, r = this.generateVariableInitializers(); + Object.keys(r).length > 0 && (i.variables = r), this.sourceMapBuilder && this.sourceMapBuilder.pushPath("sequence"); + const n = a.generateActions(this.ast.body); + if (this.sourceMapBuilder && this.sourceMapBuilder.popPath(), i.sequence = [...this.timerMainActions, ...n], this.sourceMapBuilder) { const o = this.sourceMapBuilder.buildEmbedded(); i.variables ? i.variables = { ...i.variables, @@ -1354,18 +1354,18 @@ class I { return e; } } -function j(p, e, t) { - return new I(p, e, t).transpile(); +function R(l, e, t) { + return new I(l, e, t).transpile(); } const F = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, ActionGenerator: v, JinjaGenerator: _, Transpiler: I, - generateEntityStateRead: O, - generateThrottleCondition: k, - transpile: j -}, Symbol.toStringTag, { value: "Module" })), d = class d { + generateEntityStateRead: M, + generateThrottleCondition: H, + transpile: R +}, Symbol.toStringTag, { value: "Module" })), h = class h { constructor(e) { c(this, "client"); c(this, "connection"); @@ -1381,7 +1381,7 @@ const F = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ try { return await this.client.callApi( "GET", - d.automationPath(e) + h.automationPath(e) ); } catch { return null; @@ -1390,14 +1390,14 @@ const F = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ async saveAutomation(e, t) { await this.client.callApi( "POST", - d.automationPath(e), + h.automationPath(e), t ); } async deleteAutomation(e) { await this.client.callApi( "DELETE", - d.automationPath(e) + h.automationPath(e) ); } async reloadAutomations() { @@ -1417,7 +1417,7 @@ const F = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ try { return await this.client.callApi( "GET", - d.scriptPath(e) + h.scriptPath(e) ); } catch { return null; @@ -1426,12 +1426,12 @@ const F = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ async saveScript(e, t) { await this.client.callApi( "POST", - d.scriptPath(e), + h.scriptPath(e), t ); } async deleteScript(e) { - await this.client.callApi("DELETE", d.scriptPath(e)); + await this.client.callApi("DELETE", h.scriptPath(e)); } async reloadScripts() { await this.connection.sendMessagePromise({ @@ -1450,8 +1450,8 @@ const F = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ } async getSTHelpers(e = "st_") { return (await this.getStates()).filter((a) => { - const [i, n] = a.entity_id.split("."); - return !!n && d.HELPER_DOMAINS.has(i) && n.startsWith(e); + const [i, r] = a.entity_id.split("."); + return !!r && h.HELPER_DOMAINS.has(i) && r.startsWith(e); }); } async deleteHelper(e) { @@ -1463,16 +1463,61 @@ const F = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ [`${t}_id`]: a }); } + /** + * HA derives a storage helper's immutable id from its name on create. Create + * with the requested object id first, verify what HA allocated, then restore + * the human-readable name through the update command (which keeps the id). + */ + async createHelperWithExactId(e, t, a) { + if (!t) { + await this.connection.sendMessagePromise({ + type: `${e}/create`, + ...a + }); + return; + } + const [i, r, ...n] = t.split("."); + if (i !== e || !r || n.length > 0) + throw new Error( + `Invalid ${e} entity id '${t}' for helper creation` + ); + const { name: s, ...o } = a, p = await this.connection.sendMessagePromise({ + type: `${e}/create`, + name: r, + ...o + }), u = p == null ? void 0 : p.id; + if (typeof u != "string") + throw new Error( + `Home Assistant did not return an id while creating ${t}` + ); + if (u !== r) + throw await this.deleteHelper(`${e}.${u}`), new Error( + `Home Assistant created ${e}.${u} instead of ${t}` + ); + if (s !== r) + try { + await this.connection.sendMessagePromise({ + type: `${e}/update`, + [`${e}_id`]: r, + name: s, + ...o + }); + } catch (d) { + try { + await this.deleteHelper(t); + } catch { + } + throw d; + } + } async createInputBoolean(e) { - await this.connection.sendMessagePromise({ - type: "input_boolean/create", + await this.createHelperWithExactId("input_boolean", e.id, { name: e.name, initial: e.initial ?? !1 }); } async createInputNumber(e) { - await this.connection.sendMessagePromise({ - type: "input_number/create", + await this.createHelperWithExactId("input_number", e.id, { name: e.name, initial: e.initial ?? 0, min: e.min ?? 0, @@ -1482,16 +1527,14 @@ const F = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ }); } async createInputText(e) { - await this.connection.sendMessagePromise({ - type: "input_text/create", + await this.createHelperWithExactId("input_text", e.id, { name: e.name, initial: e.initial ?? "", pattern: e.pattern }); } async createInputDateTime(e) { - await this.connection.sendMessagePromise({ - type: "input_datetime/create", + await this.createHelperWithExactId("input_datetime", e.id, { name: e.name, has_date: !0, has_time: !0, @@ -1499,8 +1542,7 @@ const F = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ }); } async createTimer(e) { - await this.connection.sendMessagePromise({ - type: "timer/create", + await this.createHelperWithExactId("timer", e.id, { name: e.name, duration: e.duration ?? "00:00:00" }); @@ -1551,7 +1593,7 @@ const F = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ } } }; -c(d, "HELPER_DOMAINS", /* @__PURE__ */ new Set([ +c(h, "HELPER_DOMAINS", /* @__PURE__ */ new Set([ "input_boolean", "input_number", "input_text", @@ -1560,37 +1602,39 @@ c(d, "HELPER_DOMAINS", /* @__PURE__ */ new Set([ "counter", "timer" ])); -let y = d; +let y = h; class b { constructor(e, t = "st_") { c(this, "api"); c(this, "projectPrefix"); this.api = e, this.projectPrefix = t; } - async calculateSync(e, t) { - const a = t ?? await this.getExistingHelpers(), i = new Set(a.map((s) => s.entityId)), n = new Set(e.map((s) => s.id)), r = { + async calculateSync(e, t, a) { + const r = (a ?? await this.getExistingHelpers(t)).filter( + (p) => this.isInScope(p.entityId, t) + ), n = new Set(r.map((p) => p.entityId)), s = new Set(e.map((p) => p.id)), o = { toCreate: [], toUpdate: [], toDelete: [], unchanged: [] }; - for (const s of e) - if (!i.has(s.id)) - r.toCreate.push(s); + for (const p of e) + if (!n.has(p.id)) + o.toCreate.push(p); else { - const o = a.find((u) => u.entityId === s.id); - o && this.needsUpdate(s, o) ? r.toUpdate.push(s) : r.unchanged.push(s.id); + const u = r.find((d) => d.entityId === p.id); + u && this.needsUpdate(p, u) ? o.toUpdate.push(p) : o.unchanged.push(p.id); } - for (const s of a) - n.has(s.entityId) || r.toDelete.push(s.entityId); - return r; - } - async getExistingHelpers() { - return (await this.api.getSTHelpers(this.projectPrefix)).map((t) => ({ - entityId: t.entity_id, - type: t.entity_id.split(".")[0], - state: t.state, - attributes: t.attributes + for (const p of r) + s.has(p.entityId) || o.toDelete.push(p.entityId); + return o; + } + async getExistingHelpers(e = this.projectPrefix) { + return (await this.api.getSTHelpers(e)).map((a) => ({ + entityId: a.entity_id, + type: a.entity_id.split(".")[0], + state: a.state, + attributes: a.attributes })); } toHelperConfig(e) { @@ -1669,8 +1713,8 @@ class b { if (e.type !== t.type) return !0; if (e.type === "input_number") { - const a = t.attributes, i = a.min, n = a.max; - if (e.min !== i || e.max !== n) + const a = t.attributes, i = a.min, r = a.max; + if (e.min !== i || e.max !== r) return !0; } return !1; @@ -1689,12 +1733,14 @@ class b { switch (e.type) { case "input_boolean": await this.api.createInputBoolean({ + id: e.id, name: t, initial: !!(e.initial ?? !1) }); break; case "input_number": await this.api.createInputNumber({ + id: e.id, name: t, initial: Number(e.initial ?? e.min ?? 0), min: e.min, @@ -1705,6 +1751,7 @@ class b { break; case "input_text": await this.api.createInputText({ + id: e.id, name: t, initial: String(e.initial ?? ""), pattern: e.pattern @@ -1712,12 +1759,14 @@ class b { break; case "input_datetime": await this.api.createInputDateTime({ + id: e.id, name: t, initial: String(e.initial ?? "") }); break; case "timer": await this.api.createTimer({ + id: e.id, name: t, duration: String(e.initial ?? "00:00:00") }); @@ -1726,6 +1775,10 @@ class b { throw new Error(`Unknown helper type: ${e.type} (${t})`); } } + isInScope(e, t) { + const a = e.split(".")[1]; + return !!a && a.startsWith(t); + } extractName(e) { const t = e.split("."); return t.length !== 2 ? e : t[1].split("_").map((a) => a.charAt(0).toUpperCase() + a.slice(1)).join(" "); @@ -1744,8 +1797,8 @@ class b { async getHelperStates(e) { const t = await this.api.getStates(), a = {}; for (const i of e) { - const n = t.find((r) => r.entity_id === i); - n && (a[i] = this.parseHelperValue(n)); + const r = t.find((n) => n.entity_id === i); + r && (a[i] = this.parseHelperValue(r)); } return a; } @@ -1771,7 +1824,7 @@ class b { } } } -const h = "st_hass_backups", w = 10; +const m = "st_hass_backups", T = 10; class $ { constructor(e) { c(this, "api"); @@ -1779,9 +1832,9 @@ class $ { this.api = e, this.helperManager = new b(e); } async createBackup(e, t) { - const a = await this.api.getAutomation(e), i = this.getScriptId(e), n = await this.api.getScript(i), s = (await this.helperManager.getExistingHelpers()).map( - (g) => this.helperManager.toHelperConfig(g) - ), o = s.map((g) => g.id), u = await this.helperManager.getHelperStates(o), l = { + const a = await this.api.getAutomation(e), i = this.getScriptId(e), r = await this.api.getScript(i), s = (await this.helperManager.getExistingHelpers()).map( + (d) => this.helperManager.toHelperConfig(d) + ), o = s.map((d) => d.id), p = await this.helperManager.getHelperStates(o), u = { id: this.generateId(), timestamp: /* @__PURE__ */ new Date(), projectName: "default", @@ -1790,12 +1843,12 @@ class $ { // eslint-disable-next-line @typescript-eslint/no-explicit-any automation: a, // eslint-disable-next-line @typescript-eslint/no-explicit-any - script: n, + script: r, helpers: s, - helperStates: u + helperStates: p } }; - return await this.saveBackup(l), l; + return await this.saveBackup(u), u; } async restoreBackup(e) { const t = await this.loadBackup(e); @@ -1821,7 +1874,7 @@ class $ { await this.helperManager.restoreHelperStates(t.data.helperStates), await this.api.reloadAutomations(), await this.api.reloadScripts(); } async listBackups() { - const e = window.localStorage.getItem(h); + const e = window.localStorage.getItem(m); if (!e) return []; try { return JSON.parse(e).map((a) => ({ @@ -1837,19 +1890,19 @@ class $ { } async deleteBackup(e) { const a = (await this.listBackups()).filter((i) => i.id !== e); - window.localStorage.setItem(h, JSON.stringify(a)); + window.localStorage.setItem(m, JSON.stringify(a)); } async saveBackup(e) { const t = await this.listBackups(); t.unshift(e); - const a = t.slice(0, w); - window.localStorage.setItem(h, JSON.stringify(a)); + const a = t.slice(0, T); + window.localStorage.setItem(m, JSON.stringify(a)); } - async cleanupOldBackups(e = w) { + async cleanupOldBackups(e = T) { const t = await this.listBackups(); if (t.length <= e) return 0; const a = t.slice(e), i = t.slice(0, e); - return window.localStorage.setItem(h, JSON.stringify(i)), a.length; + return window.localStorage.setItem(m, JSON.stringify(i)), a.length; } generateId() { return `backup_${Math.random().toString(36).slice(2)}_${Date.now().toString(36)}`; @@ -1858,17 +1911,17 @@ class $ { return `${e}_logic`; } } -const m = "st_hass_schemas"; +const g = "st_hass_schemas"; class P { save(e, t) { const a = this.loadAll(); - a[e] = t, localStorage.setItem(m, JSON.stringify(a)); + a[e] = t, localStorage.setItem(g, JSON.stringify(a)); } load(e) { return this.loadAll()[e] || null; } loadAll() { - const e = localStorage.getItem(m); + const e = localStorage.getItem(g); if (!e) return {}; try { return JSON.parse(e); @@ -1878,13 +1931,13 @@ class P { } delete(e) { const t = this.loadAll(); - delete t[e], localStorage.setItem(m, JSON.stringify(t)); + delete t[e], localStorage.setItem(g, JSON.stringify(t)); } clear() { - localStorage.removeItem(m); + localStorage.removeItem(g); } } -class U { +class x { /** * Detect migration issues between old and new schema */ @@ -1896,25 +1949,25 @@ class U { hasDestructiveChanges: !1, requiresUserInput: !1 }; - const i = new Map(e.variables.map((r) => [r.name, r])), n = new Map(t.variables.map((r) => [r.name, r])); - for (const [r, s] of i) - n.has(r) || a.push(this.createRemovedIssue(s)); - for (const [r, s] of n) { - const o = i.get(r); + const i = new Map(e.variables.map((n) => [n.name, n])), r = new Map(t.variables.map((n) => [n.name, n])); + for (const [n, s] of i) + r.has(n) || a.push(this.createRemovedIssue(s)); + for (const [n, s] of r) { + const o = i.get(n); if (!o) a.push(this.createAddedIssue(s)); else { - const u = this.detectChanges(o, s); - a.push(...u); + const p = this.detectChanges(o, s); + a.push(...p); } } return { issues: a, hasDestructiveChanges: a.some( - (r) => r.options.some((s) => s.isDestructive) + (n) => n.options.some((s) => s.isDestructive) ), requiresUserInput: a.some( - (r) => r.type === "removed" || r.type === "type_changed" + (n) => n.type === "removed" || n.type === "type_changed" ) }; } @@ -2028,7 +2081,7 @@ class S { c(this, "backupManager"); c(this, "schemaStorage"); c(this, "migrationDetector"); - this.api = e, this.helperManager = new b(e), this.backupManager = new $(e), this.schemaStorage = new P(), this.migrationDetector = new U(); + this.api = e, this.helperManager = new b(e), this.backupManager = new $(e), this.schemaStorage = new P(), this.migrationDetector = new x(); } async deploy(e, t = {}) { const a = this.createTransaction(e); @@ -2043,18 +2096,18 @@ class S { errors: [] }; a.status = "in_progress"; - for (const r of i) + for (const n of i) try { - await this.applyOperation(r), r.status = "applied"; + await this.applyOperation(n), n.status = "applied"; } catch (s) { - return r.status = "failed", r.error = this.formatError(s), await this.rollback(a), { + return n.status = "failed", n.error = this.formatError(s), await this.rollback(a), { success: !1, transactionId: a.id, operations: i, errors: [ { - operation: r, - message: r.error ?? "Unknown deploy error", + operation: n, + message: n.error ?? "Unknown deploy error", code: "DEPLOY_FAILED" } ] @@ -2078,7 +2131,7 @@ class S { }); } catch (i) { a.status = "failed"; - const n = { + const r = { message: this.formatError(i), code: "DEPLOY_ERROR" }; @@ -2086,7 +2139,7 @@ class S { success: !1, transactionId: a.id, operations: a.operations, - errors: [n] + errors: [r] }; } } @@ -2120,34 +2173,35 @@ class S { async calculateOperations(e) { const t = [], a = [e.automation, ...e.additionalAutomations ?? []]; for (const u of a) { - const l = await this.api.getAutomation(u.id); + const d = await this.api.getAutomation(u.id); t.push({ id: this.generateId(), - type: l ? "update" : "create", + type: d ? "update" : "create", entityType: "automation", entityId: u.id, - previousState: l ?? void 0, + previousState: d ?? void 0, newState: u, status: "pending" }); } - const i = this.getScriptId(e), n = await this.api.getScript(i); + const i = this.getScriptId(e), r = await this.api.getScript(i); t.push({ id: this.generateId(), - type: n ? "update" : "create", + type: r ? "update" : "create", entityType: "script", entityId: i, - previousState: n ?? void 0, + previousState: r ?? void 0, newState: e.script, status: "pending" }); - const r = await this.helperManager.getExistingHelpers(), s = new Map( - r.map((u) => [u.entityId, u]) - ), o = await this.helperManager.calculateSync( + const n = `${e.automation.id}_`, s = await this.helperManager.getExistingHelpers(n), o = new Map( + s.map((u) => [u.entityId, u]) + ), p = await this.helperManager.calculateSync( e.helpers, - r + n, + s ); - for (const u of o.toCreate) + for (const u of p.toCreate) t.push({ id: this.generateId(), type: "create", @@ -2156,26 +2210,26 @@ class S { newState: u, status: "pending" }); - for (const u of o.toUpdate) { - const l = s.get(u.id); + for (const u of p.toUpdate) { + const d = o.get(u.id); t.push({ id: this.generateId(), type: "update", entityType: "helper", entityId: u.id, - previousState: l ? this.helperManager.toHelperConfig(l) : void 0, + previousState: d ? this.helperManager.toHelperConfig(d) : void 0, newState: u, status: "pending" }); } - for (const u of o.toDelete) { - const l = s.get(u); + for (const u of p.toDelete) { + const d = o.get(u); t.push({ id: this.generateId(), type: "delete", entityType: "helper", entityId: u, - previousState: l ? this.helperManager.toHelperConfig(l) : void 0, + previousState: d ? this.helperManager.toHelperConfig(d) : void 0, status: "pending" }); } @@ -2187,17 +2241,17 @@ class S { * to drive migration flows. */ buildProgramSchema(e) { - const t = e.automation.alias.replace("[ST] ", ""), a = "default", i = e.helpers.map((n) => ({ - name: n.id, - dataType: n.type, - helperId: n.id, - helperType: n.type === "counter" ? "input_number" : n.type, - initialValue: n.initial, + const t = e.automation.alias.replace("[ST] ", ""), a = "default", i = e.helpers.map((r) => ({ + name: r.id, + dataType: r.type, + helperId: r.id, + helperType: r.type === "counter" ? "input_number" : r.type, + initialValue: r.initial, restorePolicy: 0, // restore policy is assigned at analysis time; not wired here yet - min: n.min, - max: n.max, - step: n.step + min: r.min, + max: r.max, + step: r.step })); return { programName: t, @@ -2212,8 +2266,8 @@ class S { * Intended to be called by higher-level UI logic before executing a deploy. */ createMigrationPlan(e) { - const t = this.buildProgramSchema(e), a = e.automation.id, i = this.schemaStorage.load(a), n = this.migrationDetector.detectIssues(i, t); - return this.schemaStorage.save(a, t), n; + const t = this.buildProgramSchema(e), a = e.automation.id, i = this.schemaStorage.load(a), r = this.migrationDetector.detectIssues(i, t); + return this.schemaStorage.save(a, t), r; } generateId() { return `op_${Math.random().toString(36).slice(2)}_${Date.now().toString(36)}`; @@ -2279,18 +2333,18 @@ class S { } async verifyDeployment(e) { const t = [e.automation, ...e.additionalAutomations ?? []]; - for (const r of t) - if (!await this.api.getAutomation(r.id)) return !1; + for (const n of t) + if (!await this.api.getAutomation(n.id)) return !1; const a = this.getScriptId(e); if (!await this.api.getScript(a)) return !1; - const n = await this.api.getStates(); - for (const r of e.helpers) - if (!n.some((o) => o.entity_id === r.id)) return !1; + const r = await this.api.getStates(); + for (const n of e.helpers) + if (!r.some((o) => o.entity_id === n.id)) return !1; return !0; } } -async function x(p, e, t) { - return new S(p).deploy(e, t); +async function U(l, e, t) { + return new S(l).deploy(e, t); } const z = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, @@ -2298,10 +2352,10 @@ const z = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ DeployManager: S, HAApiClient: y, HelperManager: b, - deploy: x + deploy: U }, Symbol.toStringTag, { value: "Module" })); export { z as a, F as i }; -//# sourceMappingURL=transpiler-deploy-st5GVH6E.js.map +//# sourceMappingURL=transpiler-deploy-41TsQdYw.js.map diff --git a/custom_components/st_hass/frontend/transpiler-deploy-41TsQdYw.js.map b/custom_components/st_hass/frontend/transpiler-deploy-41TsQdYw.js.map new file mode 100644 index 0000000..3d71d6f --- /dev/null +++ b/custom_components/st_hass/frontend/transpiler-deploy-41TsQdYw.js.map @@ -0,0 +1 @@ +{"version":3,"file":"transpiler-deploy-41TsQdYw.js","sources":["../../../frontend/src/transpiler/jinja-generator.ts","../../../frontend/src/transpiler/action-generator.ts","../../../frontend/src/transpiler/timer-transpiler.ts","../../../frontend/src/sourcemap/source-map.ts","../../../frontend/src/transpiler/transpiler.ts","../../../frontend/src/deploy/ha-api.ts","../../../frontend/src/deploy/helper-manager.ts","../../../frontend/src/deploy/backup-manager.ts","../../../frontend/src/restore/migration-handler.ts","../../../frontend/src/deploy/deploy-manager.ts"],"sourcesContent":["/**\r\n * Defensive Jinja Template Generator\r\n * \r\n * Generates null-safe Jinja2 templates for HA that handle\r\n * unavailable, unknown, and empty states gracefully.\r\n */\r\n\r\nimport type {\r\n Expression,\r\n BinaryExpression,\r\n UnaryExpression,\r\n Literal,\r\n VariableRef,\r\n FunctionCall,\r\n MemberAccess,\r\n} from \"../parser/ast\";\r\nimport type { TranspilerContext } from \"./types\";\r\nimport type { TimerOutputResolver } from \"./timer-transpiler\";\r\n\r\n// ============================================================================\r\n// Main Generator\r\n// ============================================================================\r\n\r\nexport class JinjaGenerator {\r\n private context: TranspilerContext;\r\n private timerResolver?: TimerOutputResolver;\r\n\r\n constructor(context: TranspilerContext, timerResolver?: TimerOutputResolver) {\r\n this.context = context;\r\n this.timerResolver = timerResolver;\r\n }\r\n\r\n /**\r\n * Generate a defensive Jinja template from an ST expression\r\n */\r\n generateExpression(expr: Expression): string {\r\n switch (expr.type) {\r\n case 'Literal':\r\n return this.generateLiteral(expr);\r\n\r\n case 'VariableRef':\r\n return this.generateVariableRef(expr);\r\n\r\n case 'BinaryExpression':\r\n return this.generateBinaryExpression(expr);\r\n\r\n case 'UnaryExpression':\r\n return this.generateUnaryExpression(expr);\r\n\r\n case 'FunctionCall':\r\n return this.generateFunctionCall(expr);\r\n\r\n case 'ParenExpression':\r\n return `(${this.generateExpression(expr.expression)})`;\r\n\r\n case 'MemberAccess':\r\n return this.generateMemberAccess(expr);\r\n\r\n default:\r\n throw new Error(`Unknown expression type: ${(expr as any).type}`);\r\n }\r\n }\r\n\r\n /**\r\n * Generate a complete condition template (wrapped in {{ }})\r\n */\r\n generateCondition(expr: Expression): string {\r\n return `{{ ${this.generateExpression(expr)} }}`;\r\n }\r\n\r\n // ==========================================================================\r\n // Literal Generation\r\n // ==========================================================================\r\n\r\n private generateLiteral(lit: Literal): string {\r\n switch (lit.kind) {\r\n case 'boolean':\r\n return lit.value ? 'true' : 'false';\r\n\r\n case 'integer':\r\n return String(lit.value);\r\n\r\n case 'real':\r\n return String(lit.value);\r\n\r\n case 'string':\r\n {\r\n // Escape single quotes in strings\r\n const escaped = String(lit.value).replace(/'/g, \"\\\\'\");\r\n return `'${escaped}'`;\r\n }\r\n\r\n case 'time':\r\n // Convert ST time literal to seconds for HA\r\n return this.convertTimeToSeconds(lit.raw);\r\n\r\n default:\r\n return String(lit.value);\r\n }\r\n }\r\n\r\n private convertTimeToSeconds(timeLiteral: string): string {\r\n if (!/^T#/i.test(timeLiteral)) return '0';\r\n\r\n const body = timeLiteral.slice(2);\r\n const pattern = /(\\d+)(ms|h|m|s)/gi;\r\n let totalSeconds = 0;\r\n let consumed = '';\r\n\r\n for (const match of body.matchAll(pattern)) {\r\n const value = parseInt(match[1], 10);\r\n const unit = match[2].toLowerCase();\r\n consumed += match[0];\r\n\r\n switch (unit) {\r\n case 'h':\r\n totalSeconds += value * 3600;\r\n break;\r\n case 'm':\r\n totalSeconds += value * 60;\r\n break;\r\n case 's':\r\n totalSeconds += value;\r\n break;\r\n case 'ms':\r\n totalSeconds += value / 1000;\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n return consumed.toLowerCase() === body.toLowerCase() ? String(totalSeconds) : '0';\r\n }\r\n\r\n // ==========================================================================\r\n // Variable Reference Generation\r\n // ==========================================================================\r\n\r\n private generateVariableRef(ref: VariableRef): string {\r\n const varInfo = this.context.variables.get(ref.name);\r\n\r\n if (!varInfo) {\r\n // Unknown variable - return as-is (might be a loop variable)\r\n return ref.name;\r\n }\r\n\r\n // Entity-bound variable → read from entity state\r\n if (varInfo.entityId) {\r\n return this.generateEntityRead(varInfo.entityId, varInfo.dataType);\r\n }\r\n\r\n // Persistent variable → read from helper\r\n if (varInfo.isPersistent && varInfo.helperId) {\r\n return this.generateHelperRead(varInfo.helperId, varInfo.dataType);\r\n }\r\n\r\n // Transient variable → just the variable name (from HA variables:)\r\n return ref.name;\r\n }\r\n\r\n /**\r\n * Generate defensive entity state read\r\n */\r\n private generateEntityRead(entityId: string, dataType: string): string {\r\n const state = `states('${entityId}')`;\r\n const invalid = `['unavailable', 'unknown', 'none', '']`;\r\n\r\n switch (dataType.toUpperCase()) {\r\n case 'BOOL':\r\n return `(${state} in ['on', 'true', 'True', '1'])`;\r\n\r\n case 'INT':\r\n case 'DINT':\r\n case 'SINT':\r\n case 'LINT':\r\n case 'UINT':\r\n case 'UDINT':\r\n case 'USINT':\r\n case 'ULINT':\r\n return `(${state} | int(default=0) if ${state} not in ${invalid} else 0)`;\r\n\r\n case 'REAL':\r\n case 'LREAL':\r\n return `(${state} | float(default=0.0) if ${state} not in ${invalid} else 0.0)`;\r\n\r\n case 'STRING':\r\n case 'WSTRING':\r\n return `(${state} if ${state} not in ['unavailable', 'unknown'] else '')`;\r\n\r\n default:\r\n return state;\r\n }\r\n }\r\n\r\n /**\r\n * Generate defensive helper state read\r\n */\r\n private generateHelperRead(helperId: string, dataType: string): string {\r\n const state = `states('${helperId}')`;\r\n const invalid = `['unavailable', 'unknown', 'none', '']`;\r\n\r\n switch (dataType.toUpperCase()) {\r\n case 'BOOL':\r\n return `(${state} == 'on')`;\r\n\r\n case 'INT':\r\n case 'DINT':\r\n case 'SINT':\r\n case 'LINT':\r\n case 'UINT':\r\n case 'UDINT':\r\n case 'USINT':\r\n case 'ULINT':\r\n return `(${state} | int(default=0) if ${state} not in ${invalid} else 0)`;\r\n\r\n case 'REAL':\r\n case 'LREAL':\r\n return `(${state} | float(default=0.0) if ${state} not in ${invalid} else 0.0)`;\r\n\r\n case 'STRING':\r\n case 'WSTRING':\r\n return `(${state} if ${state} not in ${invalid} else '')`;\r\n\r\n default:\r\n return state;\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Binary Expression Generation\r\n // ==========================================================================\r\n\r\n private generateBinaryExpression(expr: BinaryExpression): string {\r\n const left = this.generateExpression(expr.left);\r\n const right = this.generateExpression(expr.right);\r\n\r\n switch (expr.operator.toUpperCase()) {\r\n // Arithmetic\r\n case '+': return `(${left} + ${right})`;\r\n case '-': return `(${left} - ${right})`;\r\n case '*': return `(${left} * ${right})`;\r\n case '/': return `(${left} / ${right})`;\r\n case 'MOD': return `(${left} % ${right})`;\r\n\r\n // Comparison\r\n case '=': return `(${left} == ${right})`;\r\n case '<>': return `(${left} != ${right})`;\r\n case '<': return `(${left} < ${right})`;\r\n case '>': return `(${left} > ${right})`;\r\n case '<=': return `(${left} <= ${right})`;\r\n case '>=': return `(${left} >= ${right})`;\r\n\r\n // Logical\r\n case 'AND': return `(${left} and ${right})`;\r\n case 'OR': return `(${left} or ${right})`;\r\n case 'XOR': return `((${left} or ${right}) and not (${left} and ${right}))`;\r\n\r\n default:\r\n throw new Error(`Unknown operator: ${expr.operator}`);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Unary Expression Generation\r\n // ==========================================================================\r\n\r\n private generateUnaryExpression(expr: UnaryExpression): string {\r\n const operand = this.generateExpression(expr.operand);\r\n\r\n switch (expr.operator.toUpperCase()) {\r\n case 'NOT': return `(not ${operand})`;\r\n case '-': return `(-${operand})`;\r\n default:\r\n throw new Error(`Unknown unary operator: ${expr.operator}`);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Function Call Generation\r\n // ==========================================================================\r\n\r\n private generateFunctionCall(call: FunctionCall): string {\r\n const funcName = call.name.toUpperCase();\r\n const args = call.arguments.map(a => this.generateExpression(a.value));\r\n\r\n // Built-in functions with null-safe implementations\r\n switch (funcName) {\r\n // Selection functions\r\n case 'SEL':\r\n return this.generateSEL(args);\r\n case 'MUX':\r\n return this.generateMUX(args);\r\n\r\n // Limit functions\r\n case 'MIN':\r\n return `min(${args[0]}, ${args[1]})`;\r\n case 'MAX':\r\n return `max(${args[0]}, ${args[1]})`;\r\n case 'LIMIT':\r\n return this.generateLIMIT(args);\r\n\r\n // Math functions\r\n case 'ABS':\r\n return `(${args[0]} | abs)`;\r\n case 'SQRT':\r\n return this.generateSQRT(args);\r\n case 'TRUNC':\r\n return `(${args[0]} | int)`;\r\n case 'ROUND':\r\n return `(${args[0]} | round)`;\r\n\r\n // Type conversion\r\n case 'TO_INT':\r\n case 'TO_DINT':\r\n return `(${args[0]} | int(default=0))`;\r\n case 'TO_REAL':\r\n case 'TO_LREAL':\r\n return `(${args[0]} | float(default=0.0))`;\r\n case 'TO_STRING':\r\n return `(${args[0]} | string)`;\r\n case 'TO_BOOL':\r\n return `(${args[0]} | bool)`;\r\n\r\n // String functions\r\n case 'LEN':\r\n return `(${args[0]} | length)`;\r\n case 'CONCAT':\r\n return `(${args[0]} ~ ${args[1]})`;\r\n\r\n default:\r\n // Unknown function - pass through (might be custom)\r\n return `${funcName.toLowerCase()}(${args.join(', ')})`;\r\n }\r\n }\r\n\r\n private generateSEL(args: string[]): string {\r\n // SEL(G, IN0, IN1) - if G then IN1 else IN0\r\n if (args.length < 3) {\r\n throw new Error('SEL requires 3 arguments');\r\n }\r\n return `(${args[2]} if ${args[0]} else ${args[1]})`;\r\n }\r\n\r\n private generateMUX(args: string[]): string {\r\n // MUX(K, IN0, IN1, ...) - select INx based on K\r\n if (args.length < 2) {\r\n throw new Error('MUX requires at least 2 arguments');\r\n }\r\n const selector = args[0];\r\n const inputs = args.slice(1);\r\n \r\n // Generate nested if-else\r\n let result = inputs[inputs.length - 1]; // Default to last\r\n for (let i = inputs.length - 2; i >= 0; i--) {\r\n result = `(${inputs[i]} if ${selector} == ${i} else ${result})`;\r\n }\r\n return result;\r\n }\r\n\r\n private generateLIMIT(args: string[]): string {\r\n // LIMIT(MN, IN, MX) - clamp IN between MN and MX\r\n if (args.length < 3) {\r\n throw new Error('LIMIT requires 3 arguments');\r\n }\r\n const [mn, input, mx] = args;\r\n return `{% set _v = ${input} %}{% if _v is number %}{{ [[${mn}, _v] | max, ${mx}] | min }}{% else %}{{ ${mn} }}{% endif %}`;\r\n }\r\n\r\n private generateSQRT(args: string[]): string {\r\n // SQRT with negative number check\r\n if (args.length < 1) {\r\n throw new Error('SQRT requires 1 argument');\r\n }\r\n return `{% set _v = ${args[0]} %}{% if _v is number and _v >= 0 %}{{ _v | sqrt }}{% else %}{{ 0 }}{% endif %}`;\r\n }\r\n\r\n // ==========================================================================\r\n // Member Access Generation\r\n // ==========================================================================\r\n\r\n private generateMemberAccess(expr: MemberAccess): string {\r\n // Special handling for timer outputs like timer1.Q / timer1.ET\r\n if (this.timerResolver) {\r\n let root: Expression = expr.object;\r\n while (root.type === \"MemberAccess\") {\r\n root = root.object;\r\n }\r\n if (root.type === \"VariableRef\") {\r\n const mapped = this.timerResolver.resolveOutput(\r\n root.name,\r\n expr.member.toUpperCase() === \"ET\" ? \"ET\" : \"Q\",\r\n );\r\n if (mapped) {\r\n return mapped;\r\n }\r\n }\r\n }\r\n\r\n const obj = this.generateExpression(expr.object);\r\n return `${obj}.${expr.member}`;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Convenience Functions\r\n// ============================================================================\r\n\r\n/**\r\n * Generate a simple entity state read (without full context)\r\n */\r\nexport function generateEntityStateRead(entityId: string, dataType: string): string {\r\n const state = `states('${entityId}')`;\r\n const invalid = `['unavailable', 'unknown', 'none', '']`;\r\n\r\n switch (dataType.toUpperCase()) {\r\n case 'BOOL':\r\n return `{{ ${state} in ['on', 'true', 'True', '1'] }}`;\r\n case 'INT':\r\n case 'DINT':\r\n return `{{ ${state} | int(default=0) if ${state} not in ${invalid} else 0 }}`;\r\n case 'REAL':\r\n case 'LREAL':\r\n return `{{ ${state} | float(default=0.0) if ${state} not in ${invalid} else 0.0 }}`;\r\n default:\r\n return `{{ ${state} }}`;\r\n }\r\n}\r\n\r\n/**\r\n * Generate a throttle condition template with fallback for uninitialized helper\r\n */\r\nexport function generateThrottleCondition(lastRunHelper: string, throttleSeconds: number): string {\r\n // Timezone-safe comparison - see Transpiler.generateThrottleCondition.\r\n return `{% set last = states('${lastRunHelper}') %}\r\n{% if last in ['unknown', 'unavailable', 'none', ''] %}\r\n true\r\n{% else %}\r\n {{ (as_timestamp(now()) - as_timestamp(last, 0)) > ${throttleSeconds} }}\r\n{% endif %}`;\r\n}\r\n","/**\r\n * Action Generator - Convert ST Statements to HA Actions\r\n */\r\n\r\nimport type {\r\n Statement,\r\n IfStatement,\r\n CaseStatement,\r\n ForStatement,\r\n WhileStatement,\r\n RepeatStatement,\r\n AssignmentStatement,\r\n FunctionCallStatement,\r\n Expression,\r\n FunctionCall,\r\n MemberAccess,\r\n} from '../parser/ast';\r\nimport type {\r\n HAAction,\r\n HAChooseAction,\r\n HARepeatAction,\r\n HAServiceAction,\r\n HAVariablesAction,\r\n HACondition,\r\n HATemplateCondition,\r\n TranspilerContext,\r\n} from \"./types\";\r\nimport { JinjaGenerator } from \"./jinja-generator\";\r\nimport type { TimerOutputResolver } from \"./timer-transpiler\";\r\nimport type { SourceMapBuilder } from \"../sourcemap/source-map\";\r\n\r\nconst MAX_LOOP_ITERATIONS = 1000;\r\n\r\nexport class ActionGenerator {\r\n private context: TranspilerContext;\r\n private jinja: JinjaGenerator;\r\n private sourceMap?: SourceMapBuilder;\r\n\r\n constructor(context: TranspilerContext, timerResolver?: TimerOutputResolver, sourceMap?: SourceMapBuilder) {\r\n this.context = context;\r\n this.jinja = new JinjaGenerator(context, timerResolver);\r\n this.sourceMap = sourceMap;\r\n }\r\n\r\n /**\r\n * Generate HA actions from ST statements\r\n */\r\n generateActions(statements: Statement[]): HAAction[] {\r\n const actions: HAAction[] = [];\r\n statements.forEach((stmt, index) => {\r\n if (this.sourceMap) {\r\n this.sourceMap.pushPath(String(index));\r\n }\r\n const stmtActions = this.generateAction(stmt);\r\n actions.push(...stmtActions);\r\n if (this.sourceMap) {\r\n this.sourceMap.popPath();\r\n }\r\n });\r\n return actions;\r\n }\r\n\r\n /**\r\n * Generate single HA action from ST statement\r\n */\r\n generateAction(stmt: Statement): HAAction[] {\r\n switch (stmt.type) {\r\n case 'Assignment':\r\n return this.generateAssignment(stmt);\r\n\r\n case 'IfStatement':\r\n return [this.generateIf(stmt)];\r\n\r\n case 'CaseStatement':\r\n return [this.generateCase(stmt)];\r\n\r\n case 'ForStatement':\r\n return [this.generateFor(stmt)];\r\n\r\n case 'WhileStatement':\r\n return [this.generateWhile(stmt)];\r\n\r\n case 'RepeatStatement':\r\n return [this.generateRepeat(stmt)];\r\n\r\n case 'FunctionCallStatement':\r\n return this.generateFunctionCall(stmt);\r\n\r\n case 'ReturnStatement':\r\n return [{ stop: 'Return from program', error: false }];\r\n\r\n case 'ExitStatement':\r\n // EXIT in loops - handled by repeat structure\r\n return [{ stop: 'Exit loop', error: false }];\r\n\r\n default:\r\n throw new Error(`Unknown statement type: ${(stmt as any).type}`);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Assignment Generation\r\n // ==========================================================================\r\n\r\n private generateAssignment(stmt: AssignmentStatement): HAAction[] {\r\n // Record source map for assignment\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'Assignment');\r\n }\r\n\r\n const targetName = typeof stmt.target === 'string' \r\n ? stmt.target \r\n : this.getTargetName(stmt.target);\r\n\r\n const varInfo = this.context.variables.get(targetName);\r\n\r\n if (!varInfo) {\r\n // Unknown variable - use HA variables\r\n return [this.generateVariableAssignment(targetName, stmt.value)];\r\n }\r\n\r\n // Output entity → service call\r\n if (varInfo.isOutput && varInfo.entityId) {\r\n return this.generateEntityWrite(varInfo.entityId, stmt.value, varInfo.dataType);\r\n }\r\n\r\n // Persistent variable → helper service call\r\n if (varInfo.isPersistent && varInfo.helperId) {\r\n return this.generateHelperWrite(varInfo.helperId, stmt.value, varInfo.dataType);\r\n }\r\n\r\n // Transient variable → HA variables\r\n return [this.generateVariableAssignment(targetName, stmt.value)];\r\n }\r\n\r\n private getTargetName(target: string | MemberAccess): string {\r\n if (typeof target === 'string') {\r\n return target;\r\n }\r\n \r\n // target is a MemberAccess - get root variable name\r\n // e.g., for fb.output, get 'fb'\r\n let current: Expression = target;\r\n while (current.type === 'MemberAccess') {\r\n current = current.object;\r\n }\r\n if (current.type === 'VariableRef') {\r\n return current.name;\r\n }\r\n return 'unknown';\r\n }\r\n\r\n private generateVariableAssignment(name: string, value: Expression): HAVariablesAction {\r\n return {\r\n variables: {\r\n [name]: `{{ ${this.jinja.generateExpression(value)} }}`,\r\n },\r\n };\r\n }\r\n\r\n private generateEntityWrite(entityId: string, value: Expression, _dataType: string): HAAction[] {\r\n const domain = entityId.split('.')[0];\r\n const valueExpr = this.jinja.generateExpression(value);\r\n\r\n // Boolean entities (lights, switches, etc.)\r\n if (_dataType.toUpperCase() === 'BOOL') {\r\n return [{\r\n service: `{{ '${domain}.turn_on' if ${valueExpr} else '${domain}.turn_off' }}`,\r\n target: { entity_id: entityId },\r\n } as HAServiceAction];\r\n }\r\n\r\n // Numeric entities (input_number, etc.)\r\n if (domain === 'input_number' || domain === 'number') {\r\n return [{\r\n service: `${domain}.set_value`,\r\n target: { entity_id: entityId },\r\n data: { value: `{{ ${valueExpr} }}` },\r\n }];\r\n }\r\n\r\n // Text entities\r\n if (domain === 'input_text') {\r\n return [{\r\n service: 'input_text.set_value',\r\n target: { entity_id: entityId },\r\n data: { value: `{{ ${valueExpr} }}` },\r\n }];\r\n }\r\n\r\n // Default: try to set state (might not work for all entities)\r\n return [{\r\n service: `${domain}.turn_on`,\r\n target: { entity_id: entityId },\r\n }];\r\n }\r\n\r\n private generateHelperWrite(helperId: string, value: Expression, _dataType: string): HAAction[] {\r\n const domain = helperId.split('.')[0];\r\n const valueExpr = this.jinja.generateExpression(value);\r\n\r\n switch (domain) {\r\n case 'input_boolean':\r\n return [{\r\n service: `{{ 'input_boolean.turn_on' if ${valueExpr} else 'input_boolean.turn_off' }}`,\r\n target: { entity_id: helperId },\r\n } as HAServiceAction];\r\n\r\n case 'input_number':\r\n return [{\r\n service: 'input_number.set_value',\r\n target: { entity_id: helperId },\r\n data: { value: `{{ ${valueExpr} }}` },\r\n }];\r\n\r\n case 'input_text':\r\n return [{\r\n service: 'input_text.set_value',\r\n target: { entity_id: helperId },\r\n data: { value: `{{ ${valueExpr} }}` },\r\n }];\r\n\r\n case 'input_datetime':\r\n return [{\r\n service: 'input_datetime.set_datetime',\r\n target: { entity_id: helperId },\r\n data: { datetime: `{{ ${valueExpr} }}` },\r\n }];\r\n\r\n case 'counter':\r\n return [{\r\n service: 'counter.set_value',\r\n target: { entity_id: helperId },\r\n data: { value: `{{ ${valueExpr} }}` },\r\n }];\r\n\r\n default:\r\n throw new Error(`Unknown helper type: ${domain}`);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Control Flow Generation\r\n // ==========================================================================\r\n\r\n private generateIf(stmt: IfStatement): HAChooseAction {\r\n // Record source map for IF statement\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'IF statement');\r\n }\r\n\r\n const options: HAChooseAction['choose'] = [];\r\n\r\n // Main IF branch\r\n options.push({\r\n conditions: [this.generateCondition(stmt.condition)],\r\n sequence: this.generateActions(stmt.thenBranch),\r\n });\r\n\r\n // ELSIF branches\r\n for (const elsif of stmt.elsifBranches) {\r\n options.push({\r\n conditions: [this.generateCondition(elsif.condition)],\r\n sequence: this.generateActions(elsif.body),\r\n });\r\n }\r\n\r\n const result: HAChooseAction = { choose: options };\r\n\r\n // ELSE branch\r\n if (stmt.elseBranch && stmt.elseBranch.length > 0) {\r\n result.default = this.generateActions(stmt.elseBranch);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private generateCase(stmt: CaseStatement): HAChooseAction {\r\n // Record source map for CASE statement\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'CASE statement');\r\n }\r\n\r\n const selectorExpr = this.jinja.generateExpression(stmt.selector);\r\n const options: HAChooseAction['choose'] = [];\r\n\r\n for (const caseClause of stmt.cases) {\r\n // Generate condition for each case value\r\n const conditions: HACondition[] = caseClause.values.map(val => {\r\n const valExpr = this.jinja.generateExpression(val);\r\n return {\r\n condition: 'template' as const,\r\n value_template: `{{ ${selectorExpr} == ${valExpr} }}`,\r\n };\r\n });\r\n\r\n // For multiple values, wrap in OR\r\n const finalCondition: HACondition = conditions.length === 1\r\n ? conditions[0]\r\n : { condition: 'or', conditions };\r\n\r\n options.push({\r\n conditions: [finalCondition],\r\n sequence: this.generateActions(caseClause.body),\r\n });\r\n }\r\n\r\n const result: HAChooseAction = { choose: options };\r\n\r\n if (stmt.elseCase && stmt.elseCase.length > 0) {\r\n result.default = this.generateActions(stmt.elseCase);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private generateFor(stmt: ForStatement): HARepeatAction {\r\n // Record source map for FOR statement\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'FOR statement');\r\n }\r\n\r\n // Calculate iteration count\r\n const fromExpr = this.jinja.generateExpression(stmt.from);\r\n const toExpr = this.jinja.generateExpression(stmt.to);\r\n const byExpr = stmt.by ? this.jinja.generateExpression(stmt.by) : '1';\r\n\r\n // For safety, we use a count-based repeat with calculated iterations\r\n const countExpr = `{{ (((${toExpr}) - (${fromExpr})) / (${byExpr})) | int + 1 }}`;\r\n\r\n // Generate loop body with index variable\r\n const loopVarAssign: HAVariablesAction = {\r\n variables: {\r\n [stmt.variable]: `{{ (${fromExpr}) + ((repeat.index | default(1) | int - 1) * (${byExpr})) }}`,\r\n },\r\n };\r\n\r\n return {\r\n repeat: {\r\n count: countExpr,\r\n sequence: [\r\n loopVarAssign,\r\n ...this.generateActions(stmt.body),\r\n ],\r\n },\r\n };\r\n }\r\n\r\n private generateWhile(stmt: WhileStatement): HARepeatAction {\r\n // Record source map for WHILE statement\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'WHILE statement');\r\n }\r\n\r\n const condition = this.generateCondition(stmt.condition);\r\n const safetyCondition: HATemplateCondition = {\r\n condition: 'template',\r\n value_template: `{{ (repeat.index | default(1) | int) <= ${MAX_LOOP_ITERATIONS} }}`,\r\n };\r\n\r\n return {\r\n repeat: {\r\n while: [condition, safetyCondition],\r\n sequence: this.generateActions(stmt.body),\r\n },\r\n };\r\n }\r\n\r\n private generateRepeat(stmt: RepeatStatement): HARepeatAction {\r\n // Record source map for REPEAT statement\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'REPEAT statement');\r\n }\r\n\r\n const condition = this.generateCondition(stmt.condition);\r\n const safetyCondition: HATemplateCondition = {\r\n condition: 'template',\r\n value_template: `{{ (repeat.index | default(1) | int) <= ${MAX_LOOP_ITERATIONS} }}`,\r\n };\r\n\r\n // REPEAT...UNTIL = do-while, so we use until (condition becomes true to stop)\r\n // But also add safety to prevent infinite loops\r\n return {\r\n repeat: {\r\n until: [\r\n { condition: 'or', conditions: [condition, { condition: 'not', conditions: [safetyCondition] }] },\r\n ],\r\n sequence: this.generateActions(stmt.body),\r\n },\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Function Call Generation\r\n // ==========================================================================\r\n\r\n private generateFunctionCall(stmt: FunctionCallStatement): HAAction[] {\r\n const funcName = stmt.call.name.toUpperCase();\r\n\r\n // Handle special FB calls\r\n switch (funcName) {\r\n case 'R_TRIG':\r\n case 'F_TRIG':\r\n // Edge triggers are handled at trigger level, not as actions\r\n return [];\r\n\r\n case 'TON':\r\n case 'TOF':\r\n case 'TP':\r\n // Timer FB side effects (timer.start/cancel, helper writes) are handled\r\n // globally by the main Transpiler's timer processing. The call itself\r\n // does not emit additional actions here.\r\n return [];\r\n\r\n default:\r\n // Custom function call - might be a script call\r\n return [{\r\n service: `script.${funcName.toLowerCase()}`,\r\n data: this.buildFunctionCallData(stmt.call),\r\n }];\r\n }\r\n }\r\n\r\n private buildFunctionCallData(call: FunctionCall): Record {\r\n const data: Record = {};\r\n \r\n for (const arg of call.arguments) {\r\n const key = arg.name || `arg_${call.arguments.indexOf(arg)}`;\r\n data[key] = `{{ ${this.jinja.generateExpression(arg.value)} }}`;\r\n }\r\n \r\n return data;\r\n }\r\n\r\n // ==========================================================================\r\n // Helpers\r\n // ==========================================================================\r\n\r\n private generateCondition(expr: Expression): HATemplateCondition {\r\n return {\r\n condition: 'template',\r\n value_template: this.jinja.generateCondition(expr),\r\n };\r\n }\r\n}\r\n","/**\r\n * Timer Function Block Transpiler\r\n *\r\n * Converts ST timer FBs (TON, TOF, TP) to HA timer entities\r\n * with appropriate automation logic.\r\n *\r\n * This module is currently used in isolation (via its own tests)\r\n * and is designed to be wired into the main transpiler in a\r\n * follow-up integration step.\r\n */\r\n\r\nimport type { Expression, FunctionCall } from \"../parser/ast\";\r\nimport type {\r\n HAAutomation,\r\n HAAction,\r\n HACondition,\r\n HAServiceAction,\r\n TranspilerContext,\r\n} from \"./types\";\r\nimport type {\r\n TimerFBType,\r\n TimerInstance,\r\n TimerInputs,\r\n TimerEntities,\r\n TimerTranspileResult,\r\n TimerOutputMappings,\r\n ParsedTimerCall,\r\n} from \"./timer-types\";\r\nimport { JinjaGenerator } from \"./jinja-generator\";\r\n\r\n// ============================================================================\r\n// Main Timer Transpiler\r\n// ============================================================================\r\n\r\nexport class TimerTranspiler {\r\n private jinja: JinjaGenerator;\r\n\r\n constructor(_context: TranspilerContext) {\r\n this.jinja = new JinjaGenerator(_context);\r\n }\r\n\r\n /**\r\n * Check if a function call is a timer FB (by FB type name)\r\n */\r\n isTimerFBType(name: string): boolean {\r\n const upper = name.toUpperCase();\r\n return upper === \"TON\" || upper === \"TOF\" || upper === \"TP\";\r\n }\r\n\r\n /**\r\n * Parse a timer FB call\r\n *\r\n * Note: The current parser models FB calls with `call.name` equal to the\r\n * called symbol. Whether this is the instance name or FB type depends on\r\n * how the ST code is written; this helper is intentionally generic and\r\n * leaves the instance/type resolution to the caller.\r\n */\r\n parseTimerCall(instanceName: string, call: FunctionCall): ParsedTimerCall {\r\n const type = call.name.toUpperCase() as TimerFBType;\r\n const inputs: ParsedTimerCall[\"inputs\"] = {};\r\n\r\n for (const arg of call.arguments) {\r\n const paramName = arg.name?.toUpperCase();\r\n if (!paramName) continue;\r\n\r\n switch (paramName) {\r\n case \"IN\":\r\n inputs.IN = this.jinja.generateExpression(arg.value);\r\n break;\r\n case \"PT\":\r\n inputs.PT = this.parseTimeToSeconds(arg.value);\r\n break;\r\n case \"R\":\r\n inputs.R = this.jinja.generateExpression(arg.value);\r\n break;\r\n }\r\n }\r\n\r\n return { instanceName, type, inputs };\r\n }\r\n\r\n /**\r\n * Transpile a timer FB instance given its resolved type and inputs.\r\n *\r\n * The caller is responsible for:\r\n * - Determining the correct `TimerInstance` (name/program/project/type)\r\n * - Providing normalized `TimerInputs` (IN/PT Jinja expressions)\r\n */\r\n transpileTimer(instance: TimerInstance, inputs: TimerInputs): TimerTranspileResult {\r\n const entities = this.generateEntityIds(instance);\r\n const helpers = this.generateHelperConfigs(instance, entities);\r\n\r\n switch (instance.type) {\r\n case \"TON\":\r\n return this.transpileTON(instance, inputs, entities, helpers);\r\n case \"TOF\":\r\n return this.transpileTOF(instance, inputs, entities, helpers);\r\n case \"TP\":\r\n return this.transpileTP(instance, inputs, entities, helpers);\r\n default:\r\n throw new Error(`Unknown timer type: ${instance.type}`);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // TON - On-Delay Timer\r\n // ==========================================================================\r\n\r\n private transpileTON(\r\n instance: TimerInstance,\r\n inputs: TimerInputs,\r\n entities: TimerEntities,\r\n helpers: import(\"../analyzer/types\").HelperConfig[],\r\n ): TimerTranspileResult {\r\n const mainActions: HAAction[] = [\r\n {\r\n // High-level choose block for TON behavior\r\n // Case 1: IN = TRUE and timer idle -> start timer\r\n // Case 2: IN = FALSE -> cancel timer and reset Q\r\n choose: [\r\n {\r\n conditions: [\r\n this.templateCondition(inputs.IN),\r\n this.stateCondition(entities.timerId, \"idle\"),\r\n ],\r\n sequence: [this.timerStart(entities.timerId, inputs.PT)],\r\n },\r\n {\r\n conditions: [this.templateCondition(`not (${inputs.IN})`)],\r\n sequence: [\r\n this.timerCancel(entities.timerId),\r\n this.booleanTurnOff(entities.outputHelperId),\r\n ],\r\n },\r\n ],\r\n } as any,\r\n ];\r\n\r\n const finishedAutomation = this.generateFinishedAutomation(\r\n instance,\r\n entities,\r\n inputs.IN,\r\n [this.booleanTurnOn(entities.outputHelperId)],\r\n );\r\n\r\n const outputMappings: TimerOutputMappings = {\r\n Q: `(states('${entities.outputHelperId}') == 'on')`,\r\n ET: entities.elapsedHelperId\r\n ? `(states('${entities.elapsedHelperId}') | float(default=0))`\r\n : undefined,\r\n };\r\n\r\n return {\r\n entities,\r\n helpers,\r\n mainActions,\r\n finishedAutomation,\r\n outputMappings,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // TOF - Off-Delay Timer\r\n // ==========================================================================\r\n\r\n private transpileTOF(\r\n instance: TimerInstance,\r\n inputs: TimerInputs,\r\n entities: TimerEntities,\r\n helpers: import(\"../analyzer/types\").HelperConfig[],\r\n ): TimerTranspileResult {\r\n const mainActions: HAAction[] = [\r\n {\r\n choose: [\r\n // Case 1: IN = TRUE -> cancel timer and set Q immediately\r\n {\r\n conditions: [this.templateCondition(inputs.IN)],\r\n sequence: [\r\n this.timerCancel(entities.timerId),\r\n this.booleanTurnOn(entities.outputHelperId),\r\n ],\r\n },\r\n // Case 2: IN = FALSE and Q ON and timer idle -> start timer\r\n {\r\n conditions: [\r\n this.templateCondition(`not (${inputs.IN})`),\r\n this.stateCondition(entities.outputHelperId, \"on\"),\r\n this.stateCondition(entities.timerId, \"idle\"),\r\n ],\r\n sequence: [this.timerStart(entities.timerId, inputs.PT)],\r\n },\r\n ],\r\n } as any,\r\n ];\r\n\r\n const finishedAutomation = this.generateFinishedAutomation(\r\n instance,\r\n entities,\r\n `not (${inputs.IN})`,\r\n [this.booleanTurnOff(entities.outputHelperId)],\r\n );\r\n\r\n const outputMappings: TimerOutputMappings = {\r\n Q: `(states('${entities.outputHelperId}') == 'on')`,\r\n };\r\n\r\n return {\r\n entities,\r\n helpers,\r\n mainActions,\r\n finishedAutomation,\r\n outputMappings,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // TP - Pulse Timer\r\n // ==========================================================================\r\n\r\n private transpileTP(\r\n instance: TimerInstance,\r\n inputs: TimerInputs,\r\n entities: TimerEntities,\r\n helpers: import(\"../analyzer/types\").HelperConfig[],\r\n ): TimerTranspileResult {\r\n // Additional helper to track pulse trigger state\r\n const pulseTriggeredId = `input_boolean.${this.sanitize(\r\n instance.projectName,\r\n )}_${this.sanitize(instance.programName)}_${this.sanitize(instance.name)}_triggered`;\r\n\r\n helpers.push({\r\n id: pulseTriggeredId,\r\n type: \"input_boolean\",\r\n name: `ST ${instance.programName} ${instance.name} Triggered`,\r\n initial: false,\r\n });\r\n\r\n const mainActions: HAAction[] = [\r\n {\r\n choose: [\r\n // Case 1: Rising edge (IN TRUE and not triggered) -> start pulse\r\n {\r\n conditions: [\r\n this.templateCondition(inputs.IN),\r\n this.stateCondition(pulseTriggeredId, \"off\"),\r\n this.stateCondition(entities.timerId, \"idle\"),\r\n ],\r\n sequence: [\r\n this.booleanTurnOn(pulseTriggeredId),\r\n this.booleanTurnOn(entities.outputHelperId),\r\n this.timerStart(entities.timerId, inputs.PT),\r\n ],\r\n },\r\n // Case 2: IN FALSE and timer idle -> reset trigger flag\r\n {\r\n conditions: [\r\n this.templateCondition(`not (${inputs.IN})`),\r\n this.stateCondition(entities.timerId, \"idle\"),\r\n ],\r\n sequence: [this.booleanTurnOff(pulseTriggeredId)],\r\n },\r\n ],\r\n } as any,\r\n ];\r\n\r\n const finishedAutomation = this.generateFinishedAutomation(\r\n instance,\r\n entities,\r\n \"true\", // always execute when timer finishes\r\n [this.booleanTurnOff(entities.outputHelperId)],\r\n );\r\n\r\n const outputMappings: TimerOutputMappings = {\r\n Q: `(states('${entities.outputHelperId}') == 'on')`,\r\n };\r\n\r\n return {\r\n entities,\r\n helpers,\r\n mainActions,\r\n finishedAutomation,\r\n outputMappings,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Helper Generators\r\n // ==========================================================================\r\n\r\n private generateEntityIds(instance: TimerInstance): TimerEntities {\r\n const base = `st_${this.sanitize(instance.projectName)}_${this.sanitize(\r\n instance.programName,\r\n )}_${this.sanitize(instance.name)}`;\r\n\r\n return {\r\n timerId: `timer.${base}`,\r\n outputHelperId: `input_boolean.${base}_q`,\r\n elapsedHelperId: `input_number.${base}_et`,\r\n };\r\n }\r\n\r\n private generateHelperConfigs(\r\n instance: TimerInstance,\r\n entities: TimerEntities,\r\n ): import(\"../analyzer/types\").HelperConfig[] {\r\n const baseName = `ST ${instance.programName} ${instance.name}`;\r\n\r\n return [\r\n {\r\n id: entities.timerId,\r\n type: \"timer\",\r\n name: `${baseName} Timer`,\r\n },\r\n {\r\n id: entities.outputHelperId,\r\n type: \"input_boolean\",\r\n name: `${baseName} Q`,\r\n initial: false,\r\n },\r\n ];\r\n }\r\n\r\n private generateFinishedAutomation(\r\n instance: TimerInstance,\r\n entities: TimerEntities,\r\n additionalCondition: string,\r\n actions: HAAction[],\r\n ): HAAutomation {\r\n return {\r\n id: `st_${this.sanitize(instance.projectName)}_${this.sanitize(\r\n instance.programName,\r\n )}_${this.sanitize(instance.name)}_finished`,\r\n alias: `[ST] ${instance.programName} - ${instance.name} Finished`,\r\n description: `Timer finished handler for ${instance.type} ${instance.name}`,\r\n mode: \"single\",\r\n trigger: [\r\n {\r\n platform: \"event\",\r\n event_type: \"timer.finished\",\r\n event_data: {\r\n entity_id: entities.timerId,\r\n },\r\n },\r\n ],\r\n condition:\r\n additionalCondition !== \"true\"\r\n ? [\r\n {\r\n condition: \"template\",\r\n value_template: `{{ ${additionalCondition} }}`,\r\n },\r\n ]\r\n : undefined,\r\n action: actions,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Action Helpers\r\n // ==========================================================================\r\n\r\n private timerStart(timerId: string, durationSeconds: string): HAServiceAction {\r\n return {\r\n service: \"timer.start\",\r\n target: { entity_id: timerId },\r\n data: { duration: `{{ ${durationSeconds} }}` },\r\n };\r\n }\r\n\r\n private timerCancel(timerId: string): HAServiceAction {\r\n return {\r\n service: \"timer.cancel\",\r\n target: { entity_id: timerId },\r\n };\r\n }\r\n\r\n private booleanTurnOn(helperId: string): HAServiceAction {\r\n return {\r\n service: \"input_boolean.turn_on\",\r\n target: { entity_id: helperId },\r\n };\r\n }\r\n\r\n private booleanTurnOff(helperId: string): HAServiceAction {\r\n return {\r\n service: \"input_boolean.turn_off\",\r\n target: { entity_id: helperId },\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Condition Helpers\r\n // ==========================================================================\r\n\r\n private templateCondition(expression: string): HACondition {\r\n return {\r\n condition: \"template\",\r\n value_template: `{{ ${expression} }}`,\r\n };\r\n }\r\n\r\n private stateCondition(entityId: string, state: string): HACondition {\r\n return {\r\n condition: \"state\",\r\n entity_id: entityId,\r\n state,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Utility Helpers\r\n // ==========================================================================\r\n\r\n private sanitize(s: string): string {\r\n return s.toLowerCase().replace(/[^a-z0-9]/g, \"_\");\r\n }\r\n\r\n private parseTimeToSeconds(expr: Expression): string {\r\n if (expr.type === \"Literal\" && expr.kind === \"time\") {\r\n const raw = expr.raw;\r\n if (/^T#/i.test(raw)) {\r\n const body = raw.slice(2);\r\n const pattern = /(\\d+)(ms|h|m|s)/gi;\r\n let total = 0;\r\n let consumed = \"\";\r\n\r\n for (const match of body.matchAll(pattern)) {\r\n const value = parseInt(match[1], 10);\r\n const unit = match[2].toLowerCase();\r\n consumed += match[0];\r\n\r\n switch (unit) {\r\n case \"h\":\r\n total += value * 3600;\r\n break;\r\n case \"m\":\r\n total += value * 60;\r\n break;\r\n case \"s\":\r\n total += value;\r\n break;\r\n case \"ms\":\r\n total += value / 1000;\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n if (consumed.toLowerCase() === body.toLowerCase()) {\r\n return String(total);\r\n }\r\n }\r\n }\r\n\r\n // For variable expressions or non-time literals, treat as Jinja expression\r\n return this.jinja.generateExpression(expr);\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Timer Output Resolver\r\n// ============================================================================\r\n\r\n/**\r\n * Resolve timer output references in expressions.\r\n *\r\n * When code references `timer1.Q`, this can be used to map that to the\r\n * appropriate Jinja expression for the underlying helper entity.\r\n *\r\n * Note: Wiring into the main `JinjaGenerator` is planned but not required\r\n * for the core timer transpiler tests in this phase.\r\n */\r\nexport class TimerOutputResolver {\r\n private timerMappings: Map = new Map();\r\n\r\n registerTimer(instanceName: string, mappings: TimerOutputMappings): void {\r\n this.timerMappings.set(instanceName, mappings);\r\n }\r\n\r\n resolveOutput(instanceName: string, output: \"Q\" | \"ET\"): string | null {\r\n const mappings = this.timerMappings.get(instanceName);\r\n if (!mappings) return null;\r\n\r\n switch (output) {\r\n case \"Q\":\r\n return mappings.Q;\r\n case \"ET\":\r\n return mappings.ET ?? null;\r\n default:\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Check if a variable/member pair refers to a known timer output.\r\n */\r\n isTimerOutputRef(varName: string, member: string): boolean {\r\n if (!this.timerMappings.has(varName)) return false;\r\n const upper = member.toUpperCase();\r\n return upper === \"Q\" || upper === \"ET\";\r\n }\r\n}\r\n\r\n","/**\r\n * Source Map Generator\r\n * \r\n * Records mappings between generated YAML paths and original ST source locations.\r\n */\r\n\r\nimport type {\r\n SourceLocation,\r\n SourceMapEntry,\r\n SourceMap,\r\n YAMLPath,\r\n EmbeddedSourceMap,\r\n} from './source-map-types';\r\n\r\n// ============================================================================\r\n// Source Map Builder\r\n// ============================================================================\r\n\r\nexport class SourceMapBuilder {\r\n private mappings: Map = new Map();\r\n private currentPath: string[] = [];\r\n \r\n private project: string;\r\n private program: string;\r\n private sourceFile: string;\r\n private sourceHash: string;\r\n\r\n constructor(options: {\r\n project: string;\r\n program: string;\r\n sourceFile: string;\r\n sourceContent: string;\r\n }) {\r\n this.project = options.project;\r\n this.program = options.program;\r\n this.sourceFile = options.sourceFile;\r\n this.sourceHash = this.hashContent(options.sourceContent);\r\n }\r\n\r\n // ==========================================================================\r\n // Path Management\r\n // ==========================================================================\r\n\r\n /**\r\n * Push a path segment (e.g., entering an action array)\r\n */\r\n pushPath(segment: string | number): void {\r\n this.currentPath.push(String(segment));\r\n }\r\n\r\n /**\r\n * Pop the last path segment\r\n */\r\n popPath(): void {\r\n this.currentPath.pop();\r\n }\r\n\r\n /**\r\n * Get current YAML path as string\r\n */\r\n getCurrentPath(): YAMLPath {\r\n return this.currentPath.join('.');\r\n }\r\n\r\n /**\r\n * Execute function with temporary path segment\r\n */\r\n withPath(segment: string | number, fn: () => T): T {\r\n this.pushPath(segment);\r\n try {\r\n return fn();\r\n } finally {\r\n this.popPath();\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Recording Mappings\r\n // ==========================================================================\r\n\r\n /**\r\n * Record a mapping at the current path\r\n */\r\n record(location: Partial & { line: number; column: number }, description?: string): void {\r\n const path = this.getCurrentPath();\r\n if (path) {\r\n this.mappings.set(path, {\r\n st: {\r\n file: this.sourceFile,\r\n line: location.line,\r\n column: location.column,\r\n endLine: location.endLine,\r\n endColumn: location.endColumn,\r\n },\r\n description,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Record a mapping at a specific path\r\n */\r\n recordAt(path: YAMLPath, location: Partial & { line: number; column: number }, description?: string): void {\r\n this.mappings.set(path, {\r\n st: {\r\n file: this.sourceFile,\r\n line: location.line,\r\n column: location.column,\r\n endLine: location.endLine,\r\n endColumn: location.endColumn,\r\n },\r\n description,\r\n });\r\n }\r\n\r\n /**\r\n * Record mapping for an AST node\r\n * Handles both parser SourceLocation (startLine/startColumn) and sourcemap SourceLocation (line/column)\r\n */\r\n recordNode(node: { location?: any }, description?: string): void {\r\n if (!node.location) return;\r\n \r\n // Convert parser SourceLocation format to sourcemap format\r\n const location: SourceLocation = {\r\n file: this.sourceFile,\r\n line: node.location.startLine ?? node.location.line ?? 1,\r\n column: node.location.startColumn ?? node.location.column ?? 1,\r\n };\r\n \r\n if (node.location.endLine) {\r\n location.endLine = node.location.endLine;\r\n }\r\n if (node.location.endColumn) {\r\n location.endColumn = node.location.endColumn;\r\n }\r\n \r\n this.record(location, description);\r\n }\r\n\r\n // ==========================================================================\r\n // Building Output\r\n // ==========================================================================\r\n\r\n /**\r\n * Build the complete source map\r\n */\r\n build(automationId: string, scriptId?: string): SourceMap {\r\n return {\r\n version: 1,\r\n project: this.project,\r\n program: this.program,\r\n automationId,\r\n scriptId,\r\n generatedAt: new Date().toISOString(),\r\n mappings: Object.fromEntries(this.mappings),\r\n sourceHash: this.sourceHash,\r\n };\r\n }\r\n\r\n /**\r\n * Build source map for embedding in YAML variables\r\n */\r\n buildEmbedded(): EmbeddedSourceMap {\r\n return {\r\n _st_source_map: Object.fromEntries(this.mappings),\r\n _st_source_file: this.sourceFile,\r\n _st_source_hash: this.sourceHash,\r\n };\r\n }\r\n\r\n /**\r\n * Export mappings as JSON string (for storage)\r\n */\r\n toJSON(): string {\r\n return JSON.stringify(this.build(''), null, 2);\r\n }\r\n\r\n // ==========================================================================\r\n // Utilities\r\n // ==========================================================================\r\n\r\n private hashContent(content: string): string {\r\n // Simple hash for change detection\r\n let hash = 0;\r\n for (let i = 0; i < content.length; i++) {\r\n const char = content.charCodeAt(i);\r\n hash = ((hash << 5) - hash) + char;\r\n hash = hash & hash; // Convert to 32bit integer\r\n }\r\n return hash.toString(16);\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Source Map Reader\r\n// ============================================================================\r\n\r\nexport class SourceMapReader {\r\n private sourceMap: SourceMap;\r\n\r\n constructor(sourceMap: SourceMap) {\r\n this.sourceMap = sourceMap;\r\n }\r\n\r\n /**\r\n * Look up ST location for a YAML path\r\n */\r\n lookup(yamlPath: YAMLPath): SourceMapEntry | null {\r\n // Exact match\r\n if (this.sourceMap.mappings[yamlPath]) {\r\n return this.sourceMap.mappings[yamlPath];\r\n }\r\n\r\n // Try parent paths\r\n const parts = yamlPath.split('.');\r\n while (parts.length > 0) {\r\n parts.pop();\r\n const parentPath = parts.join('.');\r\n if (this.sourceMap.mappings[parentPath]) {\r\n return this.sourceMap.mappings[parentPath];\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Find all mappings within a path prefix\r\n */\r\n findByPrefix(prefix: YAMLPath): Array<{ path: YAMLPath; entry: SourceMapEntry }> {\r\n const results: Array<{ path: YAMLPath; entry: SourceMapEntry }> = [];\r\n \r\n for (const [path, entry] of Object.entries(this.sourceMap.mappings)) {\r\n if (path.startsWith(prefix)) {\r\n results.push({ path, entry });\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Get all mappings for a specific ST line\r\n */\r\n findByLine(file: string, line: number): Array<{ path: YAMLPath; entry: SourceMapEntry }> {\r\n const results: Array<{ path: YAMLPath; entry: SourceMapEntry }> = [];\r\n \r\n for (const [path, entry] of Object.entries(this.sourceMap.mappings)) {\r\n if (entry.st.file === file && entry.st.line === line) {\r\n results.push({ path, entry });\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Validate source map against current source\r\n */\r\n validateHash(currentContent: string): boolean {\r\n if (!this.sourceMap.sourceHash) return true;\r\n \r\n let hash = 0;\r\n for (let i = 0; i < currentContent.length; i++) {\r\n const char = currentContent.charCodeAt(i);\r\n hash = ((hash << 5) - hash) + char;\r\n hash = hash & hash;\r\n }\r\n \r\n return hash.toString(16) === this.sourceMap.sourceHash;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Integration with Transpiler\r\n// ============================================================================\r\n\r\n/**\r\n * Create a source map builder integrated with transpiler context\r\n */\r\nexport function createSourceMapBuilder(\r\n project: string,\r\n program: string,\r\n sourceFile: string,\r\n sourceContent: string\r\n): SourceMapBuilder {\r\n return new SourceMapBuilder({\r\n project,\r\n program,\r\n sourceFile,\r\n sourceContent,\r\n });\r\n}\r\n","/**\r\n * Main Transpiler - ST AST to HA Automation/Script\r\n */\r\n\r\nimport type { ProgramNode } from \"../parser/ast\";\r\nimport type { AnalysisResult } from \"../analyzer/types\";\r\nimport type {\r\n StorageAnalysisResult,\r\n HelperConfig,\r\n} from \"../analyzer/types\";\r\nimport type {\r\n TranspilerResult,\r\n HAAutomation,\r\n HAScript,\r\n TranspilerContext,\r\n VariableInfo,\r\n EntityInfo,\r\n TranspilerDiagnostic,\r\n HATrigger,\r\n HAAction,\r\n} from \"./types\";\r\nimport { analyzeDependencies } from \"../analyzer/dependency-analyzer\";\r\nimport { analyzeStorage } from \"../analyzer/storage-analyzer\";\r\nimport { ActionGenerator } from \"./action-generator\";\r\nimport { parsePragmas } from \"../analyzer/trigger-generator\";\r\nimport { TimerTranspiler, TimerOutputResolver } from \"./timer-transpiler\";\r\nimport { walkAST } from \"../analyzer/ast-visitor\";\r\nimport type { TimerInstance, TimerInputs } from \"./timer-types\";\r\nimport { SourceMapBuilder } from \"../sourcemap/source-map\";\r\n\r\nexport class Transpiler {\r\n private ast: ProgramNode;\r\n private projectName: string;\r\n private depAnalysis!: AnalysisResult;\r\n private storageAnalysis!: StorageAnalysisResult;\r\n private context!: TranspilerContext;\r\n private sourceMapBuilder?: SourceMapBuilder;\r\n private diagnostics: TranspilerDiagnostic[] = [];\r\n private timerTranspiler?: TimerTranspiler;\r\n private timerResolver?: TimerOutputResolver;\r\n private timerHelpers: HelperConfig[] = [];\r\n private additionalAutomations: HAAutomation[] = [];\r\n private timerMainActions: HAAction[] = [];\r\n\r\n constructor(ast: ProgramNode, projectName: string = 'default', sourceContent?: string) {\r\n this.ast = ast;\r\n this.projectName = projectName;\r\n \r\n // Initialize source map builder if source content is provided\r\n if (sourceContent) {\r\n this.sourceMapBuilder = new SourceMapBuilder({\r\n project: projectName,\r\n program: ast.name,\r\n sourceFile: `${ast.name}.st`,\r\n sourceContent: sourceContent, // Explicitly use parameter to avoid TS6133\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Transpile AST to HA automation and script\r\n */\r\n transpile(): TranspilerResult {\r\n // Phase 1: Run analyzers\r\n this.depAnalysis = analyzeDependencies(this.ast);\r\n this.storageAnalysis = analyzeStorage(this.ast, this.projectName);\r\n\r\n // Collect diagnostics from analyzers\r\n this.diagnostics.push(\r\n ...this.depAnalysis.diagnostics.map(d => ({\r\n severity: d.severity as 'Error' | 'Warning' | 'Info',\r\n code: d.code,\r\n message: d.message,\r\n stLine: d.location?.line,\r\n })),\r\n ...this.storageAnalysis.diagnostics.map(d => ({\r\n severity: d.severity as 'Error' | 'Warning' | 'Info',\r\n code: d.code,\r\n message: d.message,\r\n stLine: d.location?.line,\r\n }))\r\n );\r\n\r\n // Phase 2: Build transpiler context\r\n this.buildContext();\r\n\r\n // Phase 3: Initialize timer transpiler and process timer FBs\r\n this.timerTranspiler = new TimerTranspiler(this.context);\r\n this.timerResolver = new TimerOutputResolver();\r\n this.processTimerFBs();\r\n\r\n // Phase 4: Generate automation (triggers)\r\n const automation = this.generateAutomation();\r\n\r\n // Phase 5: Generate script (logic)\r\n const script = this.generateScript();\r\n\r\n // Phase 6: Collect helpers (storage + timers)\r\n const helpers = this.collectHelpers();\r\n\r\n // Phase 7: Build source map\r\n const sourceMap = this.sourceMapBuilder\r\n ? this.sourceMapBuilder.build(automation.id, script.alias.replace(/\\[ST\\]\\s*/, '').toLowerCase().replace(/[^a-z0-9_]/g, '_'))\r\n : {\r\n version: 1 as const,\r\n project: this.projectName,\r\n program: this.ast.name,\r\n automationId: automation.id,\r\n generatedAt: new Date().toISOString(),\r\n mappings: {},\r\n };\r\n\r\n return {\r\n automation,\r\n script,\r\n helpers,\r\n additionalAutomations: this.additionalAutomations,\r\n sourceMap,\r\n diagnostics: this.diagnostics,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Context Building\r\n // ==========================================================================\r\n\r\n private buildContext(): void {\r\n const variables = new Map();\r\n const entityBindings = new Map();\r\n\r\n // Build variable info map\r\n for (const varDecl of this.ast.variables) {\r\n const storageInfo = this.storageAnalysis.variables.find(v => v.name === varDecl.name);\r\n const depInfo = this.depAnalysis.dependencies.find(d => d.variableName === varDecl.name);\r\n\r\n const varInfo: VariableInfo = {\r\n name: varDecl.name,\r\n dataType: varDecl.dataType.name,\r\n isInput: varDecl.binding?.direction === 'INPUT' || varDecl.section === 'VAR_INPUT',\r\n isOutput: varDecl.binding?.direction === 'OUTPUT' || varDecl.section === 'VAR_OUTPUT',\r\n isPersistent: storageInfo?.storage.type === 'PERSISTENT',\r\n helperId: storageInfo?.storage.helperId,\r\n entityId: depInfo?.entityId || varDecl.binding?.entityId,\r\n };\r\n\r\n variables.set(varDecl.name, varInfo);\r\n\r\n // Build entity binding map\r\n if (depInfo && depInfo.entityId) {\r\n // Only include INPUT/OUTPUT, skip MEMORY for now\r\n if (depInfo.direction === 'INPUT' || depInfo.direction === 'OUTPUT') {\r\n entityBindings.set(varDecl.name, {\r\n entityId: depInfo.entityId,\r\n variableName: varDecl.name,\r\n direction: depInfo.direction,\r\n dataType: varDecl.dataType.name,\r\n });\r\n }\r\n }\r\n }\r\n\r\n this.context = {\r\n programName: this.ast.name,\r\n projectName: this.projectName,\r\n variables,\r\n entityBindings,\r\n currentPath: [],\r\n loopDepth: 0,\r\n safetyCounters: 0,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Timer FB Processing\r\n // ==========================================================================\r\n\r\n private processTimerFBs(): void {\r\n if (!this.timerTranspiler || !this.timerResolver) {\r\n return;\r\n }\r\n\r\n const timerVars = new Map();\r\n for (const varDecl of this.ast.variables) {\r\n const typeName = varDecl.dataType.name.toUpperCase();\r\n if (typeName === \"TON\" || typeName === \"TOF\" || typeName === \"TP\") {\r\n timerVars.set(varDecl.name, typeName as TimerInstance[\"type\"]);\r\n }\r\n }\r\n\r\n if (timerVars.size === 0) {\r\n return;\r\n }\r\n\r\n walkAST(this.ast, {\r\n onFunctionCall: (call) => {\r\n const timerType = timerVars.get(call.name);\r\n if (!timerType) {\r\n return;\r\n }\r\n\r\n const instance: TimerInstance = {\r\n name: call.name,\r\n type: timerType,\r\n programName: this.ast.name,\r\n projectName: this.projectName,\r\n };\r\n\r\n const parsed = this.timerTranspiler!.parseTimerCall(call.name, call);\r\n const inputs: TimerInputs = {\r\n IN: parsed.inputs.IN ?? \"true\",\r\n PT: parsed.inputs.PT ?? \"0\",\r\n };\r\n\r\n const result = this.timerTranspiler!.transpileTimer(instance, inputs);\r\n\r\n this.timerHelpers.push(...result.helpers);\r\n this.additionalAutomations.push(result.finishedAutomation);\r\n this.timerMainActions.push(...result.mainActions);\r\n this.timerResolver!.registerTimer(instance.name, result.outputMappings);\r\n },\r\n });\r\n }\r\n\r\n // ==========================================================================\r\n // Automation Generation\r\n // ==========================================================================\r\n\r\n private generateAutomation(): HAAutomation {\r\n const pragmas = parsePragmas(this.ast.pragmas);\r\n const throttle = pragmas.find(p => p.name === 'throttle')?.value as string | undefined;\r\n const debounce = pragmas.find(p => p.name === 'debounce')?.value as string | undefined;\r\n\r\n const automation: HAAutomation = {\r\n id: `st_${this.projectName}_${this.ast.name}`.toLowerCase().replace(/[^a-z0-9_]/g, '_'),\r\n alias: `[ST] ${this.ast.name}`,\r\n description: `Generated from ST program: ${this.ast.name}`,\r\n mode: 'single', // Automation is just dispatcher\r\n trigger: this.depAnalysis.triggers.map(t => this.mapTriggerConfig(t)),\r\n action: [],\r\n };\r\n\r\n // Add throttle condition if specified\r\n if (throttle) {\r\n const throttleHelper = this.buildEntityId('input_datetime', `st_${this.projectName}_${this.ast.name}_last_run`);\r\n const throttleSeconds = this.parseTimeToSeconds(throttle);\r\n\r\n automation.condition = [{\r\n condition: 'template',\r\n value_template: this.generateThrottleCondition(throttleHelper, throttleSeconds),\r\n }];\r\n\r\n // Add action to update last run time\r\n automation.action.push({\r\n service: 'input_datetime.set_datetime',\r\n target: { entity_id: throttleHelper },\r\n data: { datetime: '{{ now().isoformat() }}' },\r\n });\r\n }\r\n\r\n // Add debounce delay if specified\r\n if (debounce) {\r\n automation.mode = 'restart'; // Restart = debounce effect\r\n const debounceSeconds = this.parseTimeToSeconds(debounce);\r\n automation.action.push({\r\n delay: { seconds: debounceSeconds },\r\n });\r\n }\r\n\r\n // Call the logic script\r\n automation.action.push({\r\n service: 'script.turn_on',\r\n target: {\r\n entity_id: this.buildEntityId('script', `st_${this.projectName}_${this.ast.name}_logic`),\r\n },\r\n });\r\n\r\n return automation;\r\n }\r\n\r\n private collectHelpers(): HelperConfig[] {\r\n const helpers = [...this.storageAnalysis.helpers, ...this.timerHelpers];\r\n const throttleHelper = this.createThrottleHelper();\r\n if (throttleHelper) {\r\n helpers.push(throttleHelper);\r\n }\r\n\r\n const helperMap = new Map();\r\n for (const helper of helpers) {\r\n helperMap.set(helper.id, helper);\r\n }\r\n\r\n return [...helperMap.values()];\r\n }\r\n\r\n private createThrottleHelper(): HelperConfig | null {\r\n const pragmas = parsePragmas(this.ast.pragmas);\r\n const throttle = pragmas.find((p) => p.name === 'throttle')?.value as string | undefined;\r\n if (!throttle) {\r\n return null;\r\n }\r\n\r\n return {\r\n id: this.buildEntityId('input_datetime', `st_${this.projectName}_${this.ast.name}_last_run`),\r\n type: 'input_datetime',\r\n name: `ST ${this.ast.name} Last Run`,\r\n };\r\n }\r\n\r\n private buildEntityId(domain: string, objectId: string): string {\r\n return `${domain}.${objectId}`.toLowerCase().replace(/[^a-z0-9_.]/g, '_');\r\n }\r\n\r\n /**\r\n * Build the throttle condition (MUST-DO #10).\r\n *\r\n * `now()` is timezone-aware while an input_datetime state is naive\r\n * (\"2026-07-28 13:00:00\"), so subtracting the two datetimes raises\r\n * \"can't subtract offset-naive and offset-aware datetimes\". as_timestamp()\r\n * reads the naive helper state as local time and takes a default for the\r\n * uninitialised case, which keeps the first-run fallback intact.\r\n */\r\n private generateThrottleCondition(helperId: string, seconds: number): string {\r\n return `{% set last = states('${helperId}') %}\r\n{% if last in ['unknown', 'unavailable', 'none', ''] %}\r\n true\r\n{% else %}\r\n {{ (as_timestamp(now()) - as_timestamp(last, 0)) > ${seconds} }}\r\n{% endif %}`;\r\n }\r\n\r\n private parseTimeToSeconds(timeLiteral: string): number {\r\n if (!/^T#/i.test(timeLiteral)) return 0;\r\n\r\n const body = timeLiteral.slice(2);\r\n const pattern = /(\\d+)(ms|h|m|s)/gi;\r\n let totalSeconds = 0;\r\n let consumed = '';\r\n\r\n for (const match of body.matchAll(pattern)) {\r\n const value = parseInt(match[1], 10);\r\n const unit = match[2].toLowerCase();\r\n consumed += match[0];\r\n\r\n switch (unit) {\r\n case 'h':\r\n totalSeconds += value * 3600;\r\n break;\r\n case 'm':\r\n totalSeconds += value * 60;\r\n break;\r\n case 's':\r\n totalSeconds += value;\r\n break;\r\n case 'ms':\r\n totalSeconds += value / 1000;\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n return consumed.toLowerCase() === body.toLowerCase() ? totalSeconds : 0;\r\n }\r\n\r\n // ==========================================================================\r\n // Trigger Mapping\r\n // ==========================================================================\r\n\r\n private mapTriggerConfig(t: import('../analyzer/types').TriggerConfig): HATrigger {\r\n switch (t.platform) {\r\n case 'state':\r\n return {\r\n platform: 'state',\r\n entity_id: t.entity_id!,\r\n from: t.from,\r\n to: t.to,\r\n not_from: t.not_from,\r\n not_to: t.not_to,\r\n attribute: t.attribute,\r\n for: t.for,\r\n id: t.id,\r\n };\r\n case 'numeric_state':\r\n return {\r\n platform: 'numeric_state',\r\n entity_id: t.entity_id!,\r\n above: t.above,\r\n below: t.below,\r\n attribute: t.attribute,\r\n for: t.for,\r\n id: t.id,\r\n };\r\n case 'event':\r\n return {\r\n platform: 'event',\r\n event_type: t.event_type!,\r\n event_data: t.event_data,\r\n id: t.id,\r\n };\r\n case 'time':\r\n return {\r\n platform: 'time',\r\n at: t.at!,\r\n id: t.id,\r\n };\r\n default:\r\n // Fallback to state trigger\r\n return {\r\n platform: 'state',\r\n entity_id: t.entity_id || '',\r\n id: t.id,\r\n };\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Script Generation\r\n // ==========================================================================\r\n\r\n private generateScript(): HAScript {\r\n const pragmas = parsePragmas(this.ast.pragmas);\r\n const mode =\r\n (pragmas.find((p) => p.name === \"mode\")?.value as string) || \"restart\";\r\n\r\n const actionGenerator = new ActionGenerator(this.context, this.timerResolver, this.sourceMapBuilder);\r\n\r\n const script: HAScript = {\r\n alias: `[ST] ${this.ast.name} Logic`,\r\n description: `Logic script for ST program: ${this.ast.name}`,\r\n mode: mode as HAScript['mode'],\r\n sequence: [],\r\n };\r\n\r\n // Initialize transient variables\r\n const initVars = this.generateVariableInitializers();\r\n if (Object.keys(initVars).length > 0) {\r\n script.variables = initVars;\r\n }\r\n\r\n // Generate actions from body and prepend timer main actions\r\n // Set up source map path context for script sequence\r\n if (this.sourceMapBuilder) {\r\n this.sourceMapBuilder.pushPath('sequence');\r\n }\r\n const bodyActions = actionGenerator.generateActions(this.ast.body);\r\n if (this.sourceMapBuilder) {\r\n this.sourceMapBuilder.popPath();\r\n }\r\n script.sequence = [...this.timerMainActions, ...bodyActions];\r\n\r\n // Embed source map in script variables if available\r\n // Note: Embedded source map is stored as JSON string to match Record type\r\n if (this.sourceMapBuilder) {\r\n const embedded = this.sourceMapBuilder.buildEmbedded();\r\n if (script.variables) {\r\n script.variables = {\r\n ...script.variables,\r\n _st_source_map: JSON.stringify(embedded._st_source_map),\r\n _st_source_file: embedded._st_source_file,\r\n _st_source_hash: embedded._st_source_hash,\r\n };\r\n } else {\r\n script.variables = {\r\n _st_source_map: JSON.stringify(embedded._st_source_map),\r\n _st_source_file: embedded._st_source_file,\r\n _st_source_hash: embedded._st_source_hash,\r\n };\r\n }\r\n }\r\n\r\n return script;\r\n }\r\n\r\n private generateVariableInitializers(): Record {\r\n const vars: Record = {};\r\n\r\n for (const storageInfo of this.storageAnalysis.variables) {\r\n // Only initialize transient variables\r\n if (storageInfo.storage.type !== 'TRANSIENT') {\r\n continue;\r\n }\r\n\r\n const varDecl = this.ast.variables.find(v => v.name === storageInfo.name);\r\n if (!varDecl?.initialValue) {\r\n continue;\r\n }\r\n\r\n if (varDecl.initialValue.type === 'Literal') {\r\n // Convert literal to string representation\r\n if (varDecl.initialValue.kind === 'string') {\r\n vars[storageInfo.name] = String(varDecl.initialValue.value);\r\n } else if (varDecl.initialValue.kind === 'boolean') {\r\n vars[storageInfo.name] = varDecl.initialValue.value ? 'true' : 'false';\r\n } else {\r\n vars[storageInfo.name] = String(varDecl.initialValue.value);\r\n }\r\n }\r\n }\r\n\r\n return vars;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Convenience Function\r\n// ============================================================================\r\n\r\n/**\r\n * Transpile an ST program to HA automation and script\r\n */\r\nexport function transpile(ast: ProgramNode, projectName?: string, sourceContent?: string): TranspilerResult {\r\n const transpiler = new Transpiler(ast, projectName, sourceContent);\r\n return transpiler.transpile();\r\n}\r\n","/**\r\n * Home Assistant API Wrapper\r\n *\r\n * Automation and script configs go over REST (`hass.callApi`), everything else\r\n * over the WebSocket connection. See `HAClient` in ./types for why.\r\n */\r\n\r\nimport type {\r\n HAClient,\r\n HAConnection,\r\n HAState,\r\n HAAutomationConfig,\r\n HAScriptConfig,\r\n} from \"./types\";\r\n\r\n// ============================================================================\r\n// API Wrapper Class\r\n// ============================================================================\r\n\r\nexport class HAApiClient {\r\n private readonly client: HAClient;\r\n private readonly connection: HAConnection;\r\n private static readonly HELPER_DOMAINS = new Set([\r\n \"input_boolean\",\r\n \"input_number\",\r\n \"input_text\",\r\n \"input_datetime\",\r\n \"input_select\",\r\n \"counter\",\r\n \"timer\",\r\n ]);\r\n\r\n constructor(client: HAClient) {\r\n this.client = client;\r\n this.connection = client.connection;\r\n }\r\n\r\n // ==========================================================================\r\n // Automation API (REST)\r\n // ==========================================================================\r\n\r\n private static automationPath(automationId: string): string {\r\n return `config/automation/config/${encodeURIComponent(automationId)}`;\r\n }\r\n\r\n async getAutomation(\r\n automationId: string,\r\n ): Promise {\r\n try {\r\n return await this.client.callApi(\r\n \"GET\",\r\n HAApiClient.automationPath(automationId),\r\n );\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n async saveAutomation(\r\n automationId: string,\r\n config: HAAutomationConfig,\r\n ): Promise {\r\n await this.client.callApi(\r\n \"POST\",\r\n HAApiClient.automationPath(automationId),\r\n config,\r\n );\r\n }\r\n\r\n async deleteAutomation(automationId: string): Promise {\r\n await this.client.callApi(\r\n \"DELETE\",\r\n HAApiClient.automationPath(automationId),\r\n );\r\n }\r\n\r\n async reloadAutomations(): Promise {\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"automation\",\r\n service: \"reload\",\r\n });\r\n }\r\n\r\n // ==========================================================================\r\n // Script API (REST)\r\n // ==========================================================================\r\n\r\n private static scriptPath(scriptId: string): string {\r\n return `config/script/config/${encodeURIComponent(scriptId)}`;\r\n }\r\n\r\n async getScript(scriptId: string): Promise {\r\n try {\r\n return await this.client.callApi(\r\n \"GET\",\r\n HAApiClient.scriptPath(scriptId),\r\n );\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n async saveScript(scriptId: string, config: HAScriptConfig): Promise {\r\n await this.client.callApi(\r\n \"POST\",\r\n HAApiClient.scriptPath(scriptId),\r\n config,\r\n );\r\n }\r\n\r\n async deleteScript(scriptId: string): Promise {\r\n await this.client.callApi(\"DELETE\", HAApiClient.scriptPath(scriptId));\r\n }\r\n\r\n async reloadScripts(): Promise {\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"script\",\r\n service: \"reload\",\r\n });\r\n }\r\n\r\n // ==========================================================================\r\n // Helper API\r\n // ==========================================================================\r\n\r\n async getStates(): Promise {\r\n // Use WebSocket call to get all states\r\n return this.connection.sendMessagePromise({\r\n type: \"get_states\",\r\n });\r\n }\r\n\r\n async getSTHelpers(prefix: string = \"st_\"): Promise {\r\n const states = await this.getStates();\r\n return states.filter((s) => {\r\n const [domain, name] = s.entity_id.split(\".\");\r\n return (\r\n !!name &&\r\n HAApiClient.HELPER_DOMAINS.has(domain) &&\r\n name.startsWith(prefix)\r\n );\r\n });\r\n }\r\n\r\n async deleteHelper(entityId: string): Promise {\r\n const [domain, name] = entityId.split(\".\");\r\n await this.connection.sendMessagePromise({\r\n type: `${domain}/delete`,\r\n // e.g. input_number_id, input_boolean_id, ...\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n [`${domain}_id`]: name as any,\r\n });\r\n }\r\n\r\n /**\n * HA derives a storage helper's immutable id from its name on create. Create\n * with the requested object id first, verify what HA allocated, then restore\n * the human-readable name through the update command (which keeps the id).\n */\n private async createHelperWithExactId(\n domain: string,\n entityId: string | undefined,\n config: Record & { name: string },\n ): Promise {\n if (!entityId) {\n await this.connection.sendMessagePromise({\n type: `${domain}/create`,\n ...config,\n });\n return;\n }\n\n const [entityDomain, objectId, ...extraParts] = entityId.split(\".\");\n if (entityDomain !== domain || !objectId || extraParts.length > 0) {\n throw new Error(\n `Invalid ${domain} entity id '${entityId}' for helper creation`,\n );\n }\n\n const { name: friendlyName, ...settings } = config;\n const created = await this.connection.sendMessagePromise<{ id?: unknown }>({\n type: `${domain}/create`,\n name: objectId,\n ...settings,\n });\n const createdObjectId = created?.id;\n\n if (typeof createdObjectId !== \"string\") {\n throw new Error(\n `Home Assistant did not return an id while creating ${entityId}`,\n );\n }\n\n if (createdObjectId !== objectId) {\n await this.deleteHelper(`${domain}.${createdObjectId}`);\n throw new Error(\n `Home Assistant created ${domain}.${createdObjectId} instead of ${entityId}`,\n );\n }\n\n if (friendlyName === objectId) {\n return;\n }\n\n try {\n await this.connection.sendMessagePromise({\n type: `${domain}/update`,\n [`${domain}_id`]: objectId,\n name: friendlyName,\n ...settings,\n });\n } catch (error) {\n try {\n await this.deleteHelper(entityId);\n } catch {\n // Preserve the original update error if cleanup also fails.\n }\n throw error;\n }\n }\n\n async createInputBoolean(config: {\n id?: string;\n name: string;\n initial?: boolean;\n }): Promise {\n await this.createHelperWithExactId(\"input_boolean\", config.id, {\n name: config.name,\n initial: config.initial ?? false,\n });\n }\n\n async createInputNumber(config: {\n id?: string;\n name: string;\n initial?: number;\n min?: number;\n max?: number;\n step?: number;\n mode?: \"box\" | \"slider\";\n }): Promise {\n await this.createHelperWithExactId(\"input_number\", config.id, {\n name: config.name,\n initial: config.initial ?? 0,\n min: config.min ?? 0,\n max: config.max ?? 100,\n step: config.step ?? 1,\n mode: config.mode ?? \"box\",\n });\n }\n\n async createInputText(config: {\n id?: string;\n name: string;\n initial?: string;\n pattern?: string;\n }): Promise {\n await this.createHelperWithExactId(\"input_text\", config.id, {\n name: config.name,\n initial: config.initial ?? \"\",\n pattern: config.pattern,\n });\n }\n\n async createInputDateTime(config: {\n id?: string;\n name: string;\n initial?: string;\n }): Promise {\n await this.createHelperWithExactId(\"input_datetime\", config.id, {\n name: config.name,\n has_date: true,\n has_time: true,\n initial: config.initial ?? \"\",\n });\n }\n\n async createTimer(config: {\n id?: string;\n name: string;\n duration?: string;\n }): Promise {\n await this.createHelperWithExactId(\"timer\", config.id, {\n name: config.name,\n duration: config.duration ?? \"00:00:00\",\n });\n }\n\n async setHelperValue(entityId: string, value: unknown): Promise {\r\n const [domain] = entityId.split(\".\");\r\n\r\n switch (domain) {\r\n case \"input_boolean\":\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"input_boolean\",\r\n service: value ? \"turn_on\" : \"turn_off\",\r\n service_data: { entity_id: entityId },\r\n });\r\n break;\r\n\r\n case \"input_number\":\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"input_number\",\r\n service: \"set_value\",\r\n service_data: { entity_id: entityId, value: value as number },\r\n });\r\n break;\r\n\r\n case \"input_text\":\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"input_text\",\r\n service: \"set_value\",\r\n service_data: { entity_id: entityId, value: value as string },\r\n });\r\n break;\r\n\r\n case \"input_datetime\":\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"input_datetime\",\r\n service: \"set_datetime\",\r\n service_data: { entity_id: entityId, datetime: value as string },\r\n });\r\n break;\r\n\r\n case \"counter\":\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"counter\",\r\n service: \"set_value\",\r\n service_data: { entity_id: entityId, value: value as number },\r\n });\r\n break;\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n}\r\n","/**\r\n * Helper Manager - Sync ST helpers with Home Assistant\r\n */\r\n\r\nimport type { HelperConfig } from '../analyzer/types';\r\nimport type { HelperSyncResult, ExistingHelper, HAState } from './types';\r\nimport { HAApiClient } from './ha-api';\r\n\r\nexport class HelperManager {\r\n private readonly api: HAApiClient;\r\n private readonly projectPrefix: string;\r\n\r\n constructor(api: HAApiClient, projectPrefix: string = 'st_') {\r\n this.api = api;\r\n this.projectPrefix = projectPrefix;\r\n }\r\n\r\n async calculateSync(\r\n required: HelperConfig[],\r\n scopePrefix: string,\r\n existing?: ExistingHelper[],\r\n ): Promise {\r\n const discoveredHelpers =\r\n existing ?? (await this.getExistingHelpers(scopePrefix));\r\n const currentHelpers = discoveredHelpers.filter((helper) =>\r\n this.isInScope(helper.entityId, scopePrefix),\r\n );\r\n const existingIds = new Set(currentHelpers.map((h) => h.entityId));\r\n const requiredIds = new Set(required.map((h) => h.id));\r\n\r\n const result: HelperSyncResult = {\r\n toCreate: [],\r\n toUpdate: [],\r\n toDelete: [],\r\n unchanged: [],\r\n };\r\n\r\n for (const helper of required) {\r\n if (!existingIds.has(helper.id)) {\r\n result.toCreate.push(helper);\r\n } else {\r\n const existingHelper = currentHelpers.find((h) => h.entityId === helper.id);\r\n if (existingHelper && this.needsUpdate(helper, existingHelper)) {\r\n result.toUpdate.push(helper);\r\n } else {\r\n result.unchanged.push(helper.id);\r\n }\r\n }\r\n }\r\n\r\n for (const helper of currentHelpers) {\r\n if (!requiredIds.has(helper.entityId)) {\r\n result.toDelete.push(helper.entityId);\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n async getExistingHelpers(\r\n scopePrefix: string = this.projectPrefix,\r\n ): Promise {\r\n const states = await this.api.getSTHelpers(scopePrefix);\r\n\r\n return states.map((s) => ({\r\n entityId: s.entity_id,\r\n type: s.entity_id.split('.')[0],\r\n state: s.state,\r\n attributes: s.attributes,\r\n }));\r\n }\r\n\r\n toHelperConfig(existing: ExistingHelper): HelperConfig {\r\n const name =\r\n (typeof existing.attributes.friendly_name === 'string' &&\r\n existing.attributes.friendly_name) ||\r\n this.extractName(existing.entityId);\r\n\r\n switch (existing.type) {\r\n case 'input_boolean':\r\n return {\r\n id: existing.entityId,\r\n type: 'input_boolean',\r\n name,\r\n initial: existing.state === 'on',\r\n };\r\n\r\n case 'input_number':\r\n return {\r\n id: existing.entityId,\r\n type: 'input_number',\r\n name,\r\n initial: this.parseNumericValue(existing.state),\r\n min: this.parseOptionalNumber(existing.attributes.min),\r\n max: this.parseOptionalNumber(existing.attributes.max),\r\n step: this.parseOptionalNumber(existing.attributes.step),\r\n mode:\r\n existing.attributes.mode === 'slider' ||\r\n existing.attributes.mode === 'box'\r\n ? existing.attributes.mode\r\n : undefined,\r\n };\r\n\r\n case 'input_text':\r\n return {\r\n id: existing.entityId,\r\n type: 'input_text',\r\n name,\r\n initial: existing.state,\r\n pattern:\r\n typeof existing.attributes.pattern === 'string'\r\n ? existing.attributes.pattern\r\n : undefined,\r\n };\r\n\r\n case 'input_datetime':\r\n return {\r\n id: existing.entityId,\r\n type: 'input_datetime',\r\n name,\r\n initial: existing.state,\r\n };\r\n\r\n case 'input_select':\r\n return {\r\n id: existing.entityId,\r\n type: 'input_select',\r\n name,\r\n initial: existing.state,\r\n options: Array.isArray(existing.attributes.options)\r\n ? existing.attributes.options.filter(\r\n (option): option is string => typeof option === 'string',\r\n )\r\n : undefined,\r\n };\r\n\r\n case 'counter':\r\n return {\r\n id: existing.entityId,\r\n type: 'counter',\r\n name,\r\n initial: this.parseNumericValue(existing.state),\r\n min: this.parseOptionalNumber(existing.attributes.minimum),\r\n max: this.parseOptionalNumber(existing.attributes.maximum),\r\n step: this.parseOptionalNumber(existing.attributes.step),\r\n };\r\n\r\n case 'timer':\r\n return {\r\n id: existing.entityId,\r\n type: 'timer',\r\n name,\r\n initial:\r\n (typeof existing.attributes.duration === 'string' &&\r\n existing.attributes.duration) ||\r\n existing.state,\r\n };\r\n\r\n default:\r\n return {\r\n id: existing.entityId,\r\n type: existing.type as HelperConfig['type'],\r\n name,\r\n initial: existing.state,\r\n };\r\n }\r\n }\r\n\r\n private needsUpdate(required: HelperConfig, existing: ExistingHelper): boolean {\r\n if (required.type !== existing.type) {\r\n return true;\r\n }\r\n\r\n if (required.type === 'input_number') {\r\n const attrs = existing.attributes;\r\n const min = attrs.min as number | undefined;\r\n const max = attrs.max as number | undefined;\r\n if (required.min !== min || required.max !== max) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n async applySync(\r\n syncResult: HelperSyncResult,\r\n options: { skipDeletes?: boolean } = {},\r\n ): Promise {\r\n for (const helper of syncResult.toCreate) {\r\n await this.createHelper(helper);\r\n }\r\n\r\n for (const helper of syncResult.toUpdate) {\r\n await this.api.deleteHelper(helper.id);\r\n await this.createHelper(helper);\r\n }\r\n\r\n if (!options.skipDeletes) {\r\n for (const entityId of syncResult.toDelete) {\r\n await this.api.deleteHelper(entityId);\r\n }\r\n }\r\n }\r\n\r\n async createHelper(config: HelperConfig): Promise {\r\n const name = config.name || this.extractName(config.id);\r\n\r\n switch (config.type) {\r\n case 'input_boolean':\r\n await this.api.createInputBoolean({\r\n id: config.id,\r\n name,\r\n initial: Boolean(config.initial ?? false),\r\n });\r\n break;\r\n\r\n case 'input_number':\r\n await this.api.createInputNumber({\r\n id: config.id,\r\n name,\r\n initial: Number(config.initial ?? config.min ?? 0),\r\n min: config.min,\r\n max: config.max,\r\n step: config.step,\r\n mode: config.mode,\r\n });\r\n break;\r\n\r\n case 'input_text':\r\n await this.api.createInputText({\r\n id: config.id,\r\n name,\r\n initial: String(config.initial ?? ''),\r\n pattern: config.pattern,\r\n });\r\n break;\r\n\r\n case 'input_datetime':\r\n await this.api.createInputDateTime({\r\n id: config.id,\r\n name,\r\n initial: String(config.initial ?? ''),\r\n });\r\n break;\r\n\r\n case 'timer':\r\n await this.api.createTimer({\r\n id: config.id,\r\n name,\r\n duration: String(config.initial ?? '00:00:00'),\r\n });\r\n break;\r\n\r\n default:\r\n throw new Error(`Unknown helper type: ${config.type} (${name})`);\r\n }\r\n }\r\n\r\n private isInScope(entityId: string, scopePrefix: string): boolean {\r\n const objectId = entityId.split('.')[1];\r\n return !!objectId && objectId.startsWith(scopePrefix);\r\n }\r\n\r\n private extractName(entityId: string): string {\r\n const parts = entityId.split('.');\r\n if (parts.length !== 2) return entityId;\r\n\r\n return parts[1]\r\n .split('_')\r\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\r\n .join(' ');\r\n }\r\n\r\n private parseOptionalNumber(value: unknown): number | undefined {\r\n if (typeof value === 'number') return value;\r\n if (typeof value === 'string' && value.trim() !== '') {\r\n const parsed = Number(value);\r\n return Number.isNaN(parsed) ? undefined : parsed;\r\n }\r\n return undefined;\r\n }\r\n\r\n private parseNumericValue(value: string): number {\r\n const parsed = Number(value);\r\n return Number.isNaN(parsed) ? 0 : parsed;\r\n }\r\n\r\n async getHelperStates(helperIds: string[]): Promise> {\r\n const states = await this.api.getStates();\r\n const result: Record = {};\r\n\r\n for (const id of helperIds) {\r\n const state = states.find((s) => s.entity_id === id);\r\n if (state) {\r\n result[id] = this.parseHelperValue(state);\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private parseHelperValue(state: HAState): unknown {\r\n const domain = state.entity_id.split('.')[0];\r\n\r\n switch (domain) {\r\n case 'input_boolean':\r\n return state.state === 'on';\r\n\r\n case 'input_number':\r\n case 'counter':\r\n return Number.isNaN(Number(state.state)) ? 0 : Number(state.state);\r\n\r\n case 'input_text':\r\n case 'input_datetime':\r\n default:\r\n return state.state;\r\n }\r\n }\r\n\r\n async restoreHelperStates(states: Record): Promise {\r\n for (const [entityId, value] of Object.entries(states)) {\r\n try {\r\n await this.api.setHelperValue(entityId, value);\r\n } catch (error) {\r\n // eslint-disable-next-line no-console\r\n console.warn(`Failed to restore ${entityId}:`, error);\r\n }\r\n }\r\n }\r\n}\r\n\r\n","/**\r\n * Backup Manager - Save and restore deployment state\r\n */\r\n\r\nimport type { Backup } from './types';\r\nimport type { HelperConfig } from '../analyzer/types';\r\nimport { HAApiClient } from './ha-api';\r\nimport { HelperManager } from './helper-manager';\r\n\r\nconst BACKUP_STORAGE_KEY = 'st_hass_backups';\r\nconst MAX_BACKUPS = 10;\r\n\r\nexport class BackupManager {\r\n private readonly api: HAApiClient;\r\n private readonly helperManager: HelperManager;\r\n\r\n constructor(api: HAApiClient) {\r\n this.api = api;\r\n this.helperManager = new HelperManager(api);\r\n }\r\n\r\n async createBackup(automationId: string, programName: string): Promise {\r\n const automation = await this.api.getAutomation(automationId);\r\n\r\n const scriptId = this.getScriptId(automationId);\r\n const script = await this.api.getScript(scriptId);\r\n\r\n const helpers = await this.helperManager.getExistingHelpers();\r\n const helperConfigs: HelperConfig[] = helpers.map((h) =>\r\n this.helperManager.toHelperConfig(h),\r\n );\r\n\r\n const helperIds = helperConfigs.map((h) => h.id);\r\n const helperStates = await this.helperManager.getHelperStates(helperIds);\r\n\r\n const backup: Backup = {\r\n id: this.generateId(),\r\n timestamp: new Date(),\r\n projectName: 'default',\r\n programName,\r\n data: {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n automation: automation as any,\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n script: script as any,\r\n helpers: helperConfigs,\r\n helperStates,\r\n },\r\n };\r\n\r\n await this.saveBackup(backup);\r\n\r\n return backup;\r\n }\r\n\r\n async restoreBackup(backupId: string): Promise {\r\n const backup = await this.loadBackup(backupId);\r\n if (!backup) {\r\n throw new Error(`Backup not found: ${backupId}`);\r\n }\r\n\r\n if (backup.data.automation) {\r\n await this.api.saveAutomation(\r\n backup.data.automation.id,\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n backup.data.automation as any,\r\n );\r\n }\r\n\r\n if (backup.data.script && backup.data.automation) {\r\n const scriptId = this.getScriptId(backup.data.automation.id);\r\n await this.api.saveScript(\r\n scriptId,\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n backup.data.script as any,\r\n );\r\n }\r\n\r\n for (const helper of backup.data.helpers) {\r\n try {\r\n await this.helperManager.createHelper(helper);\r\n } catch {\r\n // Helper might already exist\r\n }\r\n }\r\n\r\n await this.helperManager.restoreHelperStates(backup.data.helperStates);\r\n\r\n await this.api.reloadAutomations();\r\n await this.api.reloadScripts();\r\n }\r\n\r\n async listBackups(): Promise {\r\n const stored = window.localStorage.getItem(BACKUP_STORAGE_KEY);\r\n if (!stored) return [];\r\n\r\n try {\r\n const backups = JSON.parse(stored) as Backup[];\r\n return backups.map((b) => ({\r\n ...b,\r\n timestamp: new Date(b.timestamp),\r\n }));\r\n } catch {\r\n return [];\r\n }\r\n }\r\n\r\n async loadBackup(backupId: string): Promise {\r\n const backups = await this.listBackups();\r\n return backups.find((b) => b.id === backupId) || null;\r\n }\r\n\r\n async deleteBackup(backupId: string): Promise {\r\n const backups = await this.listBackups();\r\n const filtered = backups.filter((b) => b.id !== backupId);\r\n window.localStorage.setItem(BACKUP_STORAGE_KEY, JSON.stringify(filtered));\r\n }\r\n\r\n private async saveBackup(backup: Backup): Promise {\r\n const backups = await this.listBackups();\r\n\r\n backups.unshift(backup);\r\n\r\n const trimmed = backups.slice(0, MAX_BACKUPS);\r\n\r\n window.localStorage.setItem(BACKUP_STORAGE_KEY, JSON.stringify(trimmed));\r\n }\r\n\r\n async cleanupOldBackups(keepCount: number = MAX_BACKUPS): Promise {\r\n const backups = await this.listBackups();\r\n\r\n if (backups.length <= keepCount) return 0;\r\n\r\n const toDelete = backups.slice(keepCount);\r\n const remaining = backups.slice(0, keepCount);\r\n\r\n window.localStorage.setItem(BACKUP_STORAGE_KEY, JSON.stringify(remaining));\r\n\r\n return toDelete.length;\r\n }\r\n\r\n private generateId(): string {\r\n return `backup_${Math.random().toString(36).slice(2)}_${Date.now().toString(36)}`;\r\n }\r\n\r\n private getScriptId(automationId: string): string {\r\n return `${automationId}_logic`;\r\n }\r\n}\r\n\r\n","/**\r\n * Migration Handler\r\n *\r\n * Handles schema changes between deployments with optional user interaction.\r\n */\r\n\r\nimport type {\r\n VariableSchema,\r\n ProgramSchema,\r\n MigrationIssue,\r\n MigrationOption,\r\n MigrationPlan,\r\n MigrationResolution,\r\n MigrationResult,\r\n StoredSchemas,\r\n} from \"./migration-types\";\r\n\r\n// ============================================================================\r\n// Schema Storage\r\n// ============================================================================\r\n\r\nconst SCHEMA_STORAGE_KEY = \"st_hass_schemas\";\r\n\r\nexport class SchemaStorage {\r\n save(programId: string, schema: ProgramSchema): void {\r\n const stored = this.loadAll();\r\n stored[programId] = schema;\r\n // localStorage is available in HA panel (browser) context\r\n localStorage.setItem(SCHEMA_STORAGE_KEY, JSON.stringify(stored));\r\n }\r\n\r\n load(programId: string): ProgramSchema | null {\r\n const stored = this.loadAll();\r\n return stored[programId] || null;\r\n }\r\n\r\n loadAll(): StoredSchemas {\r\n const raw = localStorage.getItem(SCHEMA_STORAGE_KEY);\r\n if (!raw) return {};\r\n try {\r\n return JSON.parse(raw) as StoredSchemas;\r\n } catch {\r\n return {};\r\n }\r\n }\r\n\r\n delete(programId: string): void {\r\n const stored = this.loadAll();\r\n delete stored[programId];\r\n localStorage.setItem(SCHEMA_STORAGE_KEY, JSON.stringify(stored));\r\n }\r\n\r\n clear(): void {\r\n localStorage.removeItem(SCHEMA_STORAGE_KEY);\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Migration Detector\r\n// ============================================================================\r\n\r\nexport class MigrationDetector {\r\n /**\r\n * Detect migration issues between old and new schema\r\n */\r\n detectIssues(\r\n oldSchema: ProgramSchema | null,\r\n newSchema: ProgramSchema,\r\n ): MigrationPlan {\r\n const issues: MigrationIssue[] = [];\r\n\r\n if (!oldSchema) {\r\n // First deployment, no migration needed\r\n return {\r\n issues: [],\r\n hasDestructiveChanges: false,\r\n requiresUserInput: false,\r\n };\r\n }\r\n\r\n const oldVars = new Map(oldSchema.variables.map((v) => [v.name, v]));\r\n const newVars = new Map(newSchema.variables.map((v) => [v.name, v]));\r\n\r\n // Check for removed variables\r\n for (const [name, oldVar] of oldVars) {\r\n if (!newVars.has(name)) {\r\n issues.push(this.createRemovedIssue(oldVar));\r\n }\r\n }\r\n\r\n // Check for added and changed variables\r\n for (const [name, newVar] of newVars) {\r\n const oldVar = oldVars.get(name);\r\n\r\n if (!oldVar) {\r\n issues.push(this.createAddedIssue(newVar));\r\n } else {\r\n // Check for changes\r\n const changeIssues = this.detectChanges(oldVar, newVar);\r\n issues.push(...changeIssues);\r\n }\r\n }\r\n\r\n return {\r\n issues,\r\n hasDestructiveChanges: issues.some((i) =>\r\n i.options.some((o) => o.isDestructive),\r\n ),\r\n requiresUserInput: issues.some(\r\n (i) => i.type === \"removed\" || i.type === \"type_changed\",\r\n ),\r\n };\r\n }\r\n\r\n private createRemovedIssue(oldVar: VariableSchema): MigrationIssue {\r\n return {\r\n type: \"removed\",\r\n variable: oldVar.name,\r\n helperId: oldVar.helperId,\r\n details: `Variable '${oldVar.name}' wurde aus dem Code entfernt`,\r\n oldSchema: oldVar,\r\n options: [\r\n {\r\n id: \"delete\",\r\n label: \"Helper löschen\",\r\n description: \"Entfernt den Helper und seinen Wert\",\r\n isDestructive: true,\r\n },\r\n {\r\n id: \"keep\",\r\n label: \"Helper behalten (orphaned)\",\r\n description: \"Behält den Helper, wird aber nicht mehr verwendet\",\r\n },\r\n ],\r\n defaultOption: \"delete\",\r\n };\r\n }\r\n\r\n private createAddedIssue(newVar: VariableSchema): MigrationIssue {\r\n return {\r\n type: \"added\",\r\n variable: newVar.name,\r\n helperId: newVar.helperId,\r\n details: `Neue Variable '${newVar.name}' hinzugefügt`,\r\n newSchema: newVar,\r\n options: [\r\n {\r\n id: \"create\",\r\n label: \"Helper erstellen\",\r\n description: `Erstellt neuen Helper mit Initialwert ${JSON.stringify(\r\n newVar.initialValue,\r\n )}`,\r\n },\r\n ],\r\n defaultOption: \"create\",\r\n };\r\n }\r\n\r\n private detectChanges(\r\n oldVar: VariableSchema,\r\n newVar: VariableSchema,\r\n ): MigrationIssue[] {\r\n const issues: MigrationIssue[] = [];\r\n\r\n // Type change\r\n if (oldVar.dataType !== newVar.dataType) {\r\n issues.push({\r\n type: \"type_changed\",\r\n variable: oldVar.name,\r\n helperId: oldVar.helperId,\r\n details: `Typ geändert: ${oldVar.dataType} → ${newVar.dataType}`,\r\n oldSchema: oldVar,\r\n newSchema: newVar,\r\n options: this.getTypeChangeOptions(oldVar, newVar),\r\n defaultOption: \"convert\",\r\n });\r\n }\r\n\r\n // Range change (for numbers)\r\n if (\r\n oldVar.helperType === \"input_number\" &&\r\n newVar.helperType === \"input_number\"\r\n ) {\r\n if (oldVar.min !== newVar.min || oldVar.max !== newVar.max) {\r\n issues.push({\r\n type: \"range_changed\",\r\n variable: oldVar.name,\r\n helperId: oldVar.helperId,\r\n details: `Bereich geändert: [${oldVar.min}, ${oldVar.max}] → [${newVar.min}, ${newVar.max}]`,\r\n oldSchema: oldVar,\r\n newSchema: newVar,\r\n options: [\r\n {\r\n id: \"update_range\",\r\n label: \"Bereich aktualisieren\",\r\n description:\r\n \"Aktualisiert min/max, Wert wird ggf. begrenzt\",\r\n },\r\n {\r\n id: \"reset\",\r\n label: \"Auf Initialwert zurücksetzen\",\r\n description: `Setzt auf ${newVar.initialValue}`,\r\n },\r\n ],\r\n defaultOption: \"update_range\",\r\n });\r\n }\r\n }\r\n\r\n return issues;\r\n }\r\n\r\n private getTypeChangeOptions(\r\n oldVar: VariableSchema,\r\n newVar: VariableSchema,\r\n ): MigrationOption[] {\r\n const options: MigrationOption[] = [];\r\n\r\n // Check if conversion is possible\r\n if (this.canConvert(oldVar.dataType, newVar.dataType)) {\r\n options.push({\r\n id: \"convert\",\r\n label: \"Wert konvertieren\",\r\n description: `Konvertiert ${oldVar.dataType} zu ${newVar.dataType}`,\r\n });\r\n }\r\n\r\n options.push({\r\n id: \"reset\",\r\n label: \"Auf Initialwert zurücksetzen\",\r\n description: `Setzt auf ${JSON.stringify(newVar.initialValue)}`,\r\n isDestructive: true,\r\n });\r\n\r\n options.push({\r\n id: \"keep_helper\",\r\n label: \"Alten Helper behalten, neuen erstellen\",\r\n description: \"Erstellt neuen Helper, alter wird orphaned\",\r\n });\r\n\r\n return options;\r\n }\r\n\r\n private canConvert(fromType: string, toType: string): boolean {\r\n const conversions: Record = {\r\n INT: [\"DINT\", \"REAL\", \"LREAL\", \"STRING\"],\r\n DINT: [\"REAL\", \"LREAL\", \"STRING\"],\r\n REAL: [\"LREAL\", \"STRING\"],\r\n LREAL: [\"STRING\"],\r\n BOOL: [\"INT\", \"STRING\"],\r\n };\r\n\r\n return conversions[fromType]?.includes(toType) ?? false;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Migration Executor\r\n// ============================================================================\r\n\r\nexport class MigrationExecutor {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n private haApi: any;\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n constructor(haApi: any) {\r\n this.haApi = haApi;\r\n }\r\n\r\n /**\r\n * Execute migration with user-provided resolutions\r\n */\r\n async execute(\r\n plan: MigrationPlan,\r\n resolutions: MigrationResolution[],\r\n ): Promise {\r\n const result: MigrationResult = {\r\n success: true,\r\n appliedChanges: [],\r\n errors: [],\r\n };\r\n\r\n // Create resolution map\r\n const resolutionMap = new Map(\r\n resolutions.map((r) => [r.issueId, r.selectedOption]),\r\n );\r\n\r\n for (const issue of plan.issues) {\r\n const selectedOption =\r\n resolutionMap.get(issue.variable) ?? issue.defaultOption;\r\n\r\n try {\r\n await this.applyResolution(issue, selectedOption);\r\n result.appliedChanges.push(`${issue.variable}: ${selectedOption}`);\r\n } catch (error) {\r\n result.success = false;\r\n result.errors.push(\r\n `${issue.variable}: ${\r\n error instanceof Error ? error.message : String(error)\r\n }`,\r\n );\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private async applyResolution(\r\n issue: MigrationIssue,\r\n option: string,\r\n ): Promise {\r\n switch (issue.type) {\r\n case \"added\":\r\n if (option === \"create\") {\r\n await this.createHelper(issue.newSchema!);\r\n }\r\n break;\r\n\r\n case \"removed\":\r\n if (option === \"delete\") {\r\n await this.deleteHelper(issue.helperId);\r\n }\r\n // 'keep' does nothing\r\n break;\r\n\r\n case \"type_changed\":\r\n await this.handleTypeChange(issue, option);\r\n break;\r\n\r\n case \"range_changed\":\r\n await this.handleRangeChange(issue, option);\r\n break;\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n private async handleTypeChange(\r\n issue: MigrationIssue,\r\n option: string,\r\n ): Promise {\r\n switch (option) {\r\n case \"convert\": {\r\n const currentValue = await this.getHelperValue(issue.helperId);\r\n const convertedValue = this.convertValue(\r\n currentValue,\r\n issue.oldSchema!.dataType,\r\n issue.newSchema!.dataType,\r\n );\r\n await this.deleteHelper(issue.helperId);\r\n await this.createHelper(issue.newSchema!, convertedValue);\r\n break;\r\n }\r\n\r\n case \"reset\":\r\n await this.deleteHelper(issue.helperId);\r\n await this.createHelper(issue.newSchema!);\r\n break;\r\n\r\n case \"keep_helper\": {\r\n // Create new helper with different ID\r\n const newHelperId = `${issue.newSchema!.helperId}_v2`;\r\n await this.createHelper({\r\n ...issue.newSchema!,\r\n helperId: newHelperId,\r\n });\r\n break;\r\n }\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n private async handleRangeChange(\r\n issue: MigrationIssue,\r\n option: string,\r\n ): Promise {\r\n if (option === \"update_range\") {\r\n const currentValue = await this.getHelperValue(issue.helperId);\r\n const clampedValue = Math.max(\r\n issue.newSchema!.min ?? Number.MIN_SAFE_INTEGER,\r\n Math.min(issue.newSchema!.max ?? Number.MAX_SAFE_INTEGER, Number(currentValue)),\r\n );\r\n\r\n await this.deleteHelper(issue.helperId);\r\n await this.createHelper(issue.newSchema!, clampedValue);\r\n } else if (option === \"reset\") {\r\n await this.deleteHelper(issue.helperId);\r\n await this.createHelper(issue.newSchema!);\r\n }\r\n }\r\n\r\n private convertValue(value: unknown, _fromType: string, toType: string): unknown {\r\n switch (toType) {\r\n case \"INT\":\r\n case \"DINT\":\r\n return Math.round(Number(value));\r\n case \"REAL\":\r\n case \"LREAL\":\r\n return Number(value);\r\n case \"STRING\":\r\n return String(value);\r\n case \"BOOL\":\r\n return Boolean(value);\r\n default:\r\n return value;\r\n }\r\n }\r\n\r\n private async createHelper(schema: VariableSchema, value?: unknown): Promise {\r\n const initialValue = value ?? schema.initialValue;\r\n\r\n switch (schema.helperType) {\r\n case \"input_boolean\":\r\n await this.haApi.createInputBoolean?.({\r\n id: schema.helperId,\r\n name: this.extractHelperName(schema.helperId),\r\n initial: Boolean(initialValue),\r\n });\r\n break;\r\n\r\n case \"input_number\":\r\n await this.haApi.createInputNumber?.({\r\n id: schema.helperId,\r\n name: this.extractHelperName(schema.helperId),\r\n initial: Number(initialValue),\r\n min: schema.min,\r\n max: schema.max,\r\n step: schema.step,\r\n mode: \"box\",\r\n });\r\n break;\r\n\r\n case \"input_text\":\r\n await this.haApi.createInputText?.({\r\n id: schema.helperId,\r\n name: this.extractHelperName(schema.helperId),\r\n initial: String(initialValue),\r\n });\r\n break;\r\n\r\n case \"input_datetime\":\r\n await this.haApi.createInputDateTime?.({\r\n id: schema.helperId,\r\n name: this.extractHelperName(schema.helperId),\r\n initial: String(initialValue),\r\n });\r\n break;\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n private async deleteHelper(helperId: string): Promise {\r\n await this.haApi.deleteHelper?.(helperId);\r\n }\r\n\r\n private async getHelperValue(helperId: string): Promise {\r\n return this.haApi.getState\r\n ? this.haApi.getState(helperId)\r\n : this.haApi.getHelperValue?.(helperId);\r\n }\r\n\r\n private extractHelperName(helperId: string): string {\r\n // input_number.st_prog_var → ST Prog Var\r\n const parts = helperId.split(\".\");\r\n if (parts.length > 1) {\r\n return parts[1]\r\n .replace(/^st_/, \"ST \")\r\n .replace(/_/g, \" \")\r\n .replace(/\\b\\w/g, (c: string) => c.toUpperCase());\r\n }\r\n return helperId;\r\n }\r\n}\r\n\r\n","/**\r\n * Deploy Manager - Transactional deployment with rollback\r\n */\r\n\r\nimport type { TranspilerResult } from '../transpiler/types';\r\nimport type { HelperConfig } from '../analyzer/types';\r\nimport type {\r\n DeployTransaction,\r\n DeployOperation,\r\n DeployResult,\r\n DeployError,\r\n} from './types';\r\nimport { HAApiClient } from './ha-api';\r\nimport { HelperManager } from './helper-manager';\r\nimport { BackupManager } from './backup-manager';\r\nimport {\r\n SchemaStorage,\r\n MigrationDetector,\r\n} from '../restore/migration-handler';\r\nimport type { ProgramSchema, VariableSchema, MigrationPlan } from '../restore/migration-types';\r\n\r\nexport class DeployManager {\r\n private readonly api: HAApiClient;\r\n private readonly helperManager: HelperManager;\r\n private readonly backupManager: BackupManager;\r\n private readonly schemaStorage: SchemaStorage;\r\n private readonly migrationDetector: MigrationDetector;\r\n\r\n constructor(api: HAApiClient) {\r\n this.api = api;\r\n this.helperManager = new HelperManager(api);\r\n this.backupManager = new BackupManager(api);\r\n this.schemaStorage = new SchemaStorage();\r\n this.migrationDetector = new MigrationDetector();\r\n }\r\n\r\n async deploy(\r\n result: TranspilerResult,\r\n options: {\r\n createBackup?: boolean;\r\n dryRun?: boolean;\r\n } = {},\r\n ): Promise {\r\n const transaction = this.createTransaction(result);\r\n\r\n try {\r\n await this.validateDeployment(result);\r\n\r\n if (options.createBackup) {\r\n await this.backupManager.createBackup(result.automation.id, result.automation.alias);\r\n }\r\n\r\n const operations = await this.calculateOperations(result);\r\n transaction.operations = operations;\r\n\r\n if (options.dryRun) {\r\n return {\r\n success: true,\r\n transactionId: transaction.id,\r\n operations,\r\n errors: [],\r\n };\r\n }\r\n\r\n transaction.status = 'in_progress';\r\n\r\n for (const op of operations) {\r\n try {\r\n await this.applyOperation(op);\r\n op.status = 'applied';\r\n } catch (error) {\r\n op.status = 'failed';\r\n op.error = this.formatError(error);\r\n\r\n await this.rollback(transaction);\r\n\r\n return {\r\n success: false,\r\n transactionId: transaction.id,\r\n operations,\r\n errors: [\r\n {\r\n operation: op,\r\n message: op.error ?? 'Unknown deploy error',\r\n code: 'DEPLOY_FAILED',\r\n },\r\n ],\r\n };\r\n }\r\n }\r\n\r\n await this.reloadAll();\r\n\r\n const verified = await this.verifyDeployment(result);\r\n if (!verified) {\r\n await this.rollback(transaction);\r\n\r\n return {\r\n success: false,\r\n transactionId: transaction.id,\r\n operations,\r\n errors: [\r\n {\r\n message: 'Deployment verification failed',\r\n code: 'VERIFY_FAILED',\r\n },\r\n ],\r\n };\r\n }\r\n\r\n transaction.status = 'committed';\r\n\r\n return {\r\n success: true,\r\n transactionId: transaction.id,\r\n operations,\r\n errors: [],\r\n };\r\n } catch (error) {\r\n transaction.status = 'failed';\r\n\r\n const deployError: DeployError = {\r\n message: this.formatError(error),\r\n code: 'DEPLOY_ERROR',\r\n };\r\n\r\n return {\r\n success: false,\r\n transactionId: transaction.id,\r\n operations: transaction.operations,\r\n errors: [deployError],\r\n };\r\n }\r\n }\r\n\r\n async rollback(transaction: DeployTransaction): Promise {\r\n const appliedOps = transaction.operations.filter((op) => op.status === 'applied').reverse();\r\n\r\n for (const op of appliedOps) {\r\n try {\r\n await this.revertOperation(op);\r\n op.status = 'reverted';\r\n } catch (error) {\r\n // eslint-disable-next-line no-console\r\n console.error(`Failed to revert operation ${op.id}:`, error);\r\n }\r\n }\r\n\r\n transaction.status = 'rolled_back';\r\n }\r\n\r\n private createTransaction(result: TranspilerResult): DeployTransaction {\r\n return {\r\n id: this.generateId(),\r\n timestamp: new Date(),\r\n projectName: 'default',\r\n programName: result.automation.alias.replace('[ST] ', ''),\r\n operations: [],\r\n status: 'pending',\r\n };\r\n }\r\n\r\n private async validateDeployment(result: TranspilerResult): Promise {\r\n const errors = result.diagnostics.filter((d) => d.severity === 'Error');\r\n if (errors.length > 0) {\r\n throw new Error(`Transpilation errors: ${errors.map((e) => e.message).join(', ')}`);\r\n }\r\n\r\n if (result.automation.trigger.length === 0) {\r\n throw new Error('Automation has no triggers - it will never execute');\r\n }\r\n }\r\n\r\n private async calculateOperations(result: TranspilerResult): Promise {\r\n const operations: DeployOperation[] = [];\r\n const automations = [result.automation, ...(result.additionalAutomations ?? [])];\r\n\r\n for (const automation of automations) {\r\n const existingAutomation = await this.api.getAutomation(automation.id);\r\n operations.push({\r\n id: this.generateId(),\r\n type: existingAutomation ? 'update' : 'create',\r\n entityType: 'automation',\r\n entityId: automation.id,\r\n previousState: existingAutomation ?? undefined,\r\n newState: automation,\r\n status: 'pending',\r\n });\r\n }\r\n\r\n const scriptId = this.getScriptId(result);\r\n const existingScript = await this.api.getScript(scriptId);\r\n operations.push({\r\n id: this.generateId(),\r\n type: existingScript ? 'update' : 'create',\r\n entityType: 'script',\r\n entityId: scriptId,\r\n previousState: existingScript ?? undefined,\r\n newState: result.script,\r\n status: 'pending',\r\n });\r\n\r\n const helperScopePrefix = `${result.automation.id}_`;\r\n const existingHelpers =\r\n await this.helperManager.getExistingHelpers(helperScopePrefix);\r\n const existingHelperById = new Map(\r\n existingHelpers.map((helper) => [helper.entityId, helper] as const),\r\n );\r\n const helperSync = await this.helperManager.calculateSync(\r\n result.helpers,\r\n helperScopePrefix,\r\n existingHelpers,\r\n );\r\n\r\n for (const helper of helperSync.toCreate) {\r\n operations.push({\r\n id: this.generateId(),\r\n type: 'create',\r\n entityType: 'helper',\r\n entityId: helper.id,\r\n newState: helper,\r\n status: 'pending',\r\n });\r\n }\r\n\r\n for (const helper of helperSync.toUpdate) {\r\n const previousHelper = existingHelperById.get(helper.id);\r\n operations.push({\r\n id: this.generateId(),\r\n type: 'update',\r\n entityType: 'helper',\r\n entityId: helper.id,\r\n previousState: previousHelper\r\n ? this.helperManager.toHelperConfig(previousHelper)\r\n : undefined,\r\n newState: helper,\r\n status: 'pending',\r\n });\r\n }\r\n\r\n for (const helperId of helperSync.toDelete) {\r\n const previousHelper = existingHelperById.get(helperId);\r\n operations.push({\r\n id: this.generateId(),\r\n type: 'delete',\r\n entityType: 'helper',\r\n entityId: helperId,\r\n previousState: previousHelper\r\n ? this.helperManager.toHelperConfig(previousHelper)\r\n : undefined,\r\n status: 'pending',\r\n });\r\n }\r\n\r\n return operations;\r\n }\r\n\r\n /**\r\n * Build a ProgramSchema from current helper configuration.\r\n * This does not change deploy behaviour and can be used by UI code\r\n * to drive migration flows.\r\n */\r\n public buildProgramSchema(result: TranspilerResult): ProgramSchema {\r\n const programName = result.automation.alias.replace('[ST] ', '');\r\n const projectName = 'default';\r\n\r\n const variables: VariableSchema[] = result.helpers.map((helper) => ({\r\n name: helper.id,\r\n dataType: helper.type,\r\n helperId: helper.id,\r\n helperType: helper.type === 'counter' ? 'input_number' : (helper.type as VariableSchema['helperType']),\r\n initialValue: helper.initial,\r\n restorePolicy: 0 as never, // restore policy is assigned at analysis time; not wired here yet\r\n min: helper.min,\r\n max: helper.max,\r\n step: helper.step,\r\n }));\r\n\r\n return {\r\n programName,\r\n projectName,\r\n variables,\r\n version: '1.0',\r\n generatedAt: new Date().toISOString(),\r\n };\r\n }\r\n\r\n /**\r\n * Create a migration plan based on previous and current schemas.\r\n * Intended to be called by higher-level UI logic before executing a deploy.\r\n */\r\n public createMigrationPlan(result: TranspilerResult): MigrationPlan {\r\n const schema = this.buildProgramSchema(result);\r\n const programId = result.automation.id;\r\n const previous = this.schemaStorage.load(programId);\r\n const plan = this.migrationDetector.detectIssues(previous, schema);\r\n this.schemaStorage.save(programId, schema);\r\n return plan;\r\n }\r\n\r\n private generateId(): string {\r\n return `op_${Math.random().toString(36).slice(2)}_${Date.now().toString(36)}`;\r\n }\r\n\r\n private formatError(error: unknown): string {\r\n if (error instanceof Error && error.message) {\r\n return error.message;\r\n }\r\n\r\n if (typeof error === 'string') {\r\n return error;\r\n }\r\n\r\n if (error && typeof error === 'object') {\r\n const candidate = error as {\r\n message?: unknown;\r\n body?: { message?: unknown; error?: unknown } | unknown;\r\n error?: unknown;\r\n code?: unknown;\r\n };\r\n\r\n if (typeof candidate.message === 'string' && candidate.message.trim()) {\r\n return candidate.message;\r\n }\r\n\r\n if (candidate.body && typeof candidate.body === 'object') {\r\n const body = candidate.body as { message?: unknown; error?: unknown };\r\n if (typeof body.message === 'string' && body.message.trim()) {\r\n return body.message;\r\n }\r\n if (typeof body.error === 'string' && body.error.trim()) {\r\n return body.error;\r\n }\r\n }\r\n\r\n if (typeof candidate.error === 'string' && candidate.error.trim()) {\r\n return candidate.error;\r\n }\r\n\r\n try {\r\n return JSON.stringify(error);\r\n } catch {\r\n // Fall through to generic message.\r\n }\r\n }\r\n\r\n return 'Unknown deploy error';\r\n }\r\n\r\n private getScriptId(result: TranspilerResult): string {\r\n return `${result.automation.id}_logic`;\r\n }\r\n\r\n private async applyOperation(op: DeployOperation): Promise {\r\n switch (op.entityType) {\r\n case 'automation':\r\n if (op.type === 'delete') {\r\n await this.api.deleteAutomation(op.entityId);\r\n } else {\r\n await this.api.saveAutomation(op.entityId, op.newState as never);\r\n }\r\n break;\r\n\r\n case 'script':\r\n if (op.type === 'delete') {\r\n await this.api.deleteScript(op.entityId);\r\n } else {\r\n await this.api.saveScript(op.entityId, op.newState as never);\r\n }\r\n break;\r\n\r\n case 'helper':\r\n if (op.type === 'delete') {\r\n await this.api.deleteHelper(op.entityId);\r\n } else if (op.type === 'update') {\r\n await this.api.deleteHelper(op.entityId);\r\n await this.helperManager.createHelper(op.newState as HelperConfig);\r\n } else {\r\n await this.helperManager.createHelper(op.newState as HelperConfig);\r\n }\r\n break;\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n private async revertOperation(op: DeployOperation): Promise {\r\n switch (op.type) {\r\n case 'create':\r\n if (op.entityType === 'automation') {\r\n await this.api.deleteAutomation(op.entityId);\r\n } else if (op.entityType === 'script') {\r\n await this.api.deleteScript(op.entityId);\r\n } else if (op.entityType === 'helper') {\r\n await this.api.deleteHelper(op.entityId);\r\n }\r\n break;\r\n\r\n case 'update':\r\n if (!op.previousState) return;\r\n if (op.entityType === 'automation') {\r\n await this.api.saveAutomation(op.entityId, op.previousState as never);\r\n } else if (op.entityType === 'script') {\r\n await this.api.saveScript(op.entityId, op.previousState as never);\r\n } else if (op.entityType === 'helper') {\r\n await this.api.deleteHelper(op.entityId);\r\n await this.helperManager.createHelper(op.previousState as HelperConfig);\r\n }\r\n break;\r\n\r\n case 'delete':\r\n if (!op.previousState) return;\r\n if (op.entityType === 'automation') {\r\n await this.api.saveAutomation(op.entityId, op.previousState as never);\r\n } else if (op.entityType === 'script') {\r\n await this.api.saveScript(op.entityId, op.previousState as never);\r\n } else if (op.entityType === 'helper') {\r\n await this.helperManager.createHelper(op.previousState as HelperConfig);\r\n }\r\n break;\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n private async reloadAll(): Promise {\r\n await this.api.reloadAutomations();\r\n await this.api.reloadScripts();\r\n }\r\n\r\n private async verifyDeployment(result: TranspilerResult): Promise {\r\n const automations = [result.automation, ...(result.additionalAutomations ?? [])];\r\n for (const automationConfig of automations) {\r\n const automation = await this.api.getAutomation(automationConfig.id);\r\n if (!automation) return false;\r\n }\r\n\r\n const scriptId = this.getScriptId(result);\r\n const script = await this.api.getScript(scriptId);\r\n if (!script) return false;\r\n\r\n const states = await this.api.getStates();\r\n for (const helper of result.helpers) {\r\n const exists = states.some((s) => s.entity_id === helper.id);\r\n if (!exists) return false;\r\n }\r\n\r\n return true;\r\n }\r\n}\r\n\r\nexport async function deploy(\r\n api: HAApiClient,\r\n result: TranspilerResult,\r\n options?: { createBackup?: boolean; dryRun?: boolean },\r\n): Promise {\r\n const manager = new DeployManager(api);\r\n return manager.deploy(result, options);\r\n}\r\n\r\n"],"names":["JinjaGenerator","context","timerResolver","__publicField","expr","lit","timeLiteral","body","pattern","totalSeconds","consumed","match","value","unit","ref","varInfo","entityId","dataType","state","invalid","helperId","left","right","operand","call","funcName","args","a","selector","inputs","result","i","mn","input","mx","root","mapped","generateEntityStateRead","generateThrottleCondition","lastRunHelper","throttleSeconds","MAX_LOOP_ITERATIONS","ActionGenerator","sourceMap","statements","actions","stmt","index","stmtActions","targetName","target","current","name","_dataType","domain","valueExpr","options","elsif","selectorExpr","caseClause","conditions","val","valExpr","finalCondition","fromExpr","toExpr","byExpr","countExpr","loopVarAssign","condition","safetyCondition","data","arg","key","TimerTranspiler","_context","upper","instanceName","type","paramName","_a","instance","entities","helpers","mainActions","finishedAutomation","outputMappings","pulseTriggeredId","base","baseName","additionalCondition","timerId","durationSeconds","expression","s","raw","total","TimerOutputResolver","mappings","output","varName","member","SourceMapBuilder","segment","fn","location","description","path","node","automationId","scriptId","content","hash","char","Transpiler","ast","projectName","sourceContent","analyzeDependencies","analyzeStorage","d","automation","script","variables","entityBindings","varDecl","storageInfo","v","depInfo","_b","_c","timerVars","typeName","walkAST","timerType","parsed","pragmas","parsePragmas","throttle","p","debounce","t","throttleHelper","debounceSeconds","helperMap","helper","objectId","seconds","mode","actionGenerator","initVars","bodyActions","embedded","vars","transpile","_HAApiClient","client","config","prefix","entityDomain","extraParts","friendlyName","settings","created","createdObjectId","error","HAApiClient","HelperManager","api","projectPrefix","required","scopePrefix","existing","currentHelpers","existingIds","h","requiredIds","existingHelper","option","attrs","min","max","syncResult","parts","word","helperIds","states","id","BACKUP_STORAGE_KEY","MAX_BACKUPS","BackupManager","programName","helperConfigs","helperStates","backup","backupId","stored","b","filtered","backups","trimmed","keepCount","toDelete","remaining","SCHEMA_STORAGE_KEY","SchemaStorage","programId","schema","MigrationDetector","oldSchema","newSchema","issues","oldVars","newVars","oldVar","newVar","changeIssues","o","fromType","toType","DeployManager","transaction","operations","op","deployError","appliedOps","errors","e","automations","existingAutomation","existingScript","helperScopePrefix","existingHelpers","existingHelperById","helperSync","previousHelper","previous","plan","candidate","automationConfig","deploy"],"mappings":"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"} \ No newline at end of file diff --git a/custom_components/st_hass/frontend/transpiler-deploy-st5GVH6E.js.map b/custom_components/st_hass/frontend/transpiler-deploy-st5GVH6E.js.map deleted file mode 100644 index 3508361..0000000 --- a/custom_components/st_hass/frontend/transpiler-deploy-st5GVH6E.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"transpiler-deploy-st5GVH6E.js","sources":["../../../frontend/src/transpiler/jinja-generator.ts","../../../frontend/src/transpiler/action-generator.ts","../../../frontend/src/transpiler/timer-transpiler.ts","../../../frontend/src/sourcemap/source-map.ts","../../../frontend/src/transpiler/transpiler.ts","../../../frontend/src/deploy/ha-api.ts","../../../frontend/src/deploy/helper-manager.ts","../../../frontend/src/deploy/backup-manager.ts","../../../frontend/src/restore/migration-handler.ts","../../../frontend/src/deploy/deploy-manager.ts"],"sourcesContent":["/**\r\n * Defensive Jinja Template Generator\r\n * \r\n * Generates null-safe Jinja2 templates for HA that handle\r\n * unavailable, unknown, and empty states gracefully.\r\n */\r\n\r\nimport type {\r\n Expression,\r\n BinaryExpression,\r\n UnaryExpression,\r\n Literal,\r\n VariableRef,\r\n FunctionCall,\r\n MemberAccess,\r\n} from \"../parser/ast\";\r\nimport type { TranspilerContext } from \"./types\";\r\nimport type { TimerOutputResolver } from \"./timer-transpiler\";\r\n\r\n// ============================================================================\r\n// Main Generator\r\n// ============================================================================\r\n\r\nexport class JinjaGenerator {\r\n private context: TranspilerContext;\r\n private timerResolver?: TimerOutputResolver;\r\n\r\n constructor(context: TranspilerContext, timerResolver?: TimerOutputResolver) {\r\n this.context = context;\r\n this.timerResolver = timerResolver;\r\n }\r\n\r\n /**\r\n * Generate a defensive Jinja template from an ST expression\r\n */\r\n generateExpression(expr: Expression): string {\r\n switch (expr.type) {\r\n case 'Literal':\r\n return this.generateLiteral(expr);\r\n\r\n case 'VariableRef':\r\n return this.generateVariableRef(expr);\r\n\r\n case 'BinaryExpression':\r\n return this.generateBinaryExpression(expr);\r\n\r\n case 'UnaryExpression':\r\n return this.generateUnaryExpression(expr);\r\n\r\n case 'FunctionCall':\r\n return this.generateFunctionCall(expr);\r\n\r\n case 'ParenExpression':\r\n return `(${this.generateExpression(expr.expression)})`;\r\n\r\n case 'MemberAccess':\r\n return this.generateMemberAccess(expr);\r\n\r\n default:\r\n throw new Error(`Unknown expression type: ${(expr as any).type}`);\r\n }\r\n }\r\n\r\n /**\r\n * Generate a complete condition template (wrapped in {{ }})\r\n */\r\n generateCondition(expr: Expression): string {\r\n return `{{ ${this.generateExpression(expr)} }}`;\r\n }\r\n\r\n // ==========================================================================\r\n // Literal Generation\r\n // ==========================================================================\r\n\r\n private generateLiteral(lit: Literal): string {\r\n switch (lit.kind) {\r\n case 'boolean':\r\n return lit.value ? 'true' : 'false';\r\n\r\n case 'integer':\r\n return String(lit.value);\r\n\r\n case 'real':\r\n return String(lit.value);\r\n\r\n case 'string':\r\n {\r\n // Escape single quotes in strings\r\n const escaped = String(lit.value).replace(/'/g, \"\\\\'\");\r\n return `'${escaped}'`;\r\n }\r\n\r\n case 'time':\r\n // Convert ST time literal to seconds for HA\r\n return this.convertTimeToSeconds(lit.raw);\r\n\r\n default:\r\n return String(lit.value);\r\n }\r\n }\r\n\r\n private convertTimeToSeconds(timeLiteral: string): string {\r\n if (!/^T#/i.test(timeLiteral)) return '0';\r\n\r\n const body = timeLiteral.slice(2);\r\n const pattern = /(\\d+)(ms|h|m|s)/gi;\r\n let totalSeconds = 0;\r\n let consumed = '';\r\n\r\n for (const match of body.matchAll(pattern)) {\r\n const value = parseInt(match[1], 10);\r\n const unit = match[2].toLowerCase();\r\n consumed += match[0];\r\n\r\n switch (unit) {\r\n case 'h':\r\n totalSeconds += value * 3600;\r\n break;\r\n case 'm':\r\n totalSeconds += value * 60;\r\n break;\r\n case 's':\r\n totalSeconds += value;\r\n break;\r\n case 'ms':\r\n totalSeconds += value / 1000;\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n return consumed.toLowerCase() === body.toLowerCase() ? String(totalSeconds) : '0';\r\n }\r\n\r\n // ==========================================================================\r\n // Variable Reference Generation\r\n // ==========================================================================\r\n\r\n private generateVariableRef(ref: VariableRef): string {\r\n const varInfo = this.context.variables.get(ref.name);\r\n\r\n if (!varInfo) {\r\n // Unknown variable - return as-is (might be a loop variable)\r\n return ref.name;\r\n }\r\n\r\n // Entity-bound variable → read from entity state\r\n if (varInfo.entityId) {\r\n return this.generateEntityRead(varInfo.entityId, varInfo.dataType);\r\n }\r\n\r\n // Persistent variable → read from helper\r\n if (varInfo.isPersistent && varInfo.helperId) {\r\n return this.generateHelperRead(varInfo.helperId, varInfo.dataType);\r\n }\r\n\r\n // Transient variable → just the variable name (from HA variables:)\r\n return ref.name;\r\n }\r\n\r\n /**\r\n * Generate defensive entity state read\r\n */\r\n private generateEntityRead(entityId: string, dataType: string): string {\r\n const state = `states('${entityId}')`;\r\n const invalid = `['unavailable', 'unknown', 'none', '']`;\r\n\r\n switch (dataType.toUpperCase()) {\r\n case 'BOOL':\r\n return `(${state} in ['on', 'true', 'True', '1'])`;\r\n\r\n case 'INT':\r\n case 'DINT':\r\n case 'SINT':\r\n case 'LINT':\r\n case 'UINT':\r\n case 'UDINT':\r\n case 'USINT':\r\n case 'ULINT':\r\n return `(${state} | int(default=0) if ${state} not in ${invalid} else 0)`;\r\n\r\n case 'REAL':\r\n case 'LREAL':\r\n return `(${state} | float(default=0.0) if ${state} not in ${invalid} else 0.0)`;\r\n\r\n case 'STRING':\r\n case 'WSTRING':\r\n return `(${state} if ${state} not in ['unavailable', 'unknown'] else '')`;\r\n\r\n default:\r\n return state;\r\n }\r\n }\r\n\r\n /**\r\n * Generate defensive helper state read\r\n */\r\n private generateHelperRead(helperId: string, dataType: string): string {\r\n const state = `states('${helperId}')`;\r\n const invalid = `['unavailable', 'unknown', 'none', '']`;\r\n\r\n switch (dataType.toUpperCase()) {\r\n case 'BOOL':\r\n return `(${state} == 'on')`;\r\n\r\n case 'INT':\r\n case 'DINT':\r\n case 'SINT':\r\n case 'LINT':\r\n case 'UINT':\r\n case 'UDINT':\r\n case 'USINT':\r\n case 'ULINT':\r\n return `(${state} | int(default=0) if ${state} not in ${invalid} else 0)`;\r\n\r\n case 'REAL':\r\n case 'LREAL':\r\n return `(${state} | float(default=0.0) if ${state} not in ${invalid} else 0.0)`;\r\n\r\n case 'STRING':\r\n case 'WSTRING':\r\n return `(${state} if ${state} not in ${invalid} else '')`;\r\n\r\n default:\r\n return state;\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Binary Expression Generation\r\n // ==========================================================================\r\n\r\n private generateBinaryExpression(expr: BinaryExpression): string {\r\n const left = this.generateExpression(expr.left);\r\n const right = this.generateExpression(expr.right);\r\n\r\n switch (expr.operator.toUpperCase()) {\r\n // Arithmetic\r\n case '+': return `(${left} + ${right})`;\r\n case '-': return `(${left} - ${right})`;\r\n case '*': return `(${left} * ${right})`;\r\n case '/': return `(${left} / ${right})`;\r\n case 'MOD': return `(${left} % ${right})`;\r\n\r\n // Comparison\r\n case '=': return `(${left} == ${right})`;\r\n case '<>': return `(${left} != ${right})`;\r\n case '<': return `(${left} < ${right})`;\r\n case '>': return `(${left} > ${right})`;\r\n case '<=': return `(${left} <= ${right})`;\r\n case '>=': return `(${left} >= ${right})`;\r\n\r\n // Logical\r\n case 'AND': return `(${left} and ${right})`;\r\n case 'OR': return `(${left} or ${right})`;\r\n case 'XOR': return `((${left} or ${right}) and not (${left} and ${right}))`;\r\n\r\n default:\r\n throw new Error(`Unknown operator: ${expr.operator}`);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Unary Expression Generation\r\n // ==========================================================================\r\n\r\n private generateUnaryExpression(expr: UnaryExpression): string {\r\n const operand = this.generateExpression(expr.operand);\r\n\r\n switch (expr.operator.toUpperCase()) {\r\n case 'NOT': return `(not ${operand})`;\r\n case '-': return `(-${operand})`;\r\n default:\r\n throw new Error(`Unknown unary operator: ${expr.operator}`);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Function Call Generation\r\n // ==========================================================================\r\n\r\n private generateFunctionCall(call: FunctionCall): string {\r\n const funcName = call.name.toUpperCase();\r\n const args = call.arguments.map(a => this.generateExpression(a.value));\r\n\r\n // Built-in functions with null-safe implementations\r\n switch (funcName) {\r\n // Selection functions\r\n case 'SEL':\r\n return this.generateSEL(args);\r\n case 'MUX':\r\n return this.generateMUX(args);\r\n\r\n // Limit functions\r\n case 'MIN':\r\n return `min(${args[0]}, ${args[1]})`;\r\n case 'MAX':\r\n return `max(${args[0]}, ${args[1]})`;\r\n case 'LIMIT':\r\n return this.generateLIMIT(args);\r\n\r\n // Math functions\r\n case 'ABS':\r\n return `(${args[0]} | abs)`;\r\n case 'SQRT':\r\n return this.generateSQRT(args);\r\n case 'TRUNC':\r\n return `(${args[0]} | int)`;\r\n case 'ROUND':\r\n return `(${args[0]} | round)`;\r\n\r\n // Type conversion\r\n case 'TO_INT':\r\n case 'TO_DINT':\r\n return `(${args[0]} | int(default=0))`;\r\n case 'TO_REAL':\r\n case 'TO_LREAL':\r\n return `(${args[0]} | float(default=0.0))`;\r\n case 'TO_STRING':\r\n return `(${args[0]} | string)`;\r\n case 'TO_BOOL':\r\n return `(${args[0]} | bool)`;\r\n\r\n // String functions\r\n case 'LEN':\r\n return `(${args[0]} | length)`;\r\n case 'CONCAT':\r\n return `(${args[0]} ~ ${args[1]})`;\r\n\r\n default:\r\n // Unknown function - pass through (might be custom)\r\n return `${funcName.toLowerCase()}(${args.join(', ')})`;\r\n }\r\n }\r\n\r\n private generateSEL(args: string[]): string {\r\n // SEL(G, IN0, IN1) - if G then IN1 else IN0\r\n if (args.length < 3) {\r\n throw new Error('SEL requires 3 arguments');\r\n }\r\n return `(${args[2]} if ${args[0]} else ${args[1]})`;\r\n }\r\n\r\n private generateMUX(args: string[]): string {\r\n // MUX(K, IN0, IN1, ...) - select INx based on K\r\n if (args.length < 2) {\r\n throw new Error('MUX requires at least 2 arguments');\r\n }\r\n const selector = args[0];\r\n const inputs = args.slice(1);\r\n \r\n // Generate nested if-else\r\n let result = inputs[inputs.length - 1]; // Default to last\r\n for (let i = inputs.length - 2; i >= 0; i--) {\r\n result = `(${inputs[i]} if ${selector} == ${i} else ${result})`;\r\n }\r\n return result;\r\n }\r\n\r\n private generateLIMIT(args: string[]): string {\r\n // LIMIT(MN, IN, MX) - clamp IN between MN and MX\r\n if (args.length < 3) {\r\n throw new Error('LIMIT requires 3 arguments');\r\n }\r\n const [mn, input, mx] = args;\r\n return `{% set _v = ${input} %}{% if _v is number %}{{ [[${mn}, _v] | max, ${mx}] | min }}{% else %}{{ ${mn} }}{% endif %}`;\r\n }\r\n\r\n private generateSQRT(args: string[]): string {\r\n // SQRT with negative number check\r\n if (args.length < 1) {\r\n throw new Error('SQRT requires 1 argument');\r\n }\r\n return `{% set _v = ${args[0]} %}{% if _v is number and _v >= 0 %}{{ _v | sqrt }}{% else %}{{ 0 }}{% endif %}`;\r\n }\r\n\r\n // ==========================================================================\r\n // Member Access Generation\r\n // ==========================================================================\r\n\r\n private generateMemberAccess(expr: MemberAccess): string {\r\n // Special handling for timer outputs like timer1.Q / timer1.ET\r\n if (this.timerResolver) {\r\n let root: Expression = expr.object;\r\n while (root.type === \"MemberAccess\") {\r\n root = root.object;\r\n }\r\n if (root.type === \"VariableRef\") {\r\n const mapped = this.timerResolver.resolveOutput(\r\n root.name,\r\n expr.member.toUpperCase() === \"ET\" ? \"ET\" : \"Q\",\r\n );\r\n if (mapped) {\r\n return mapped;\r\n }\r\n }\r\n }\r\n\r\n const obj = this.generateExpression(expr.object);\r\n return `${obj}.${expr.member}`;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Convenience Functions\r\n// ============================================================================\r\n\r\n/**\r\n * Generate a simple entity state read (without full context)\r\n */\r\nexport function generateEntityStateRead(entityId: string, dataType: string): string {\r\n const state = `states('${entityId}')`;\r\n const invalid = `['unavailable', 'unknown', 'none', '']`;\r\n\r\n switch (dataType.toUpperCase()) {\r\n case 'BOOL':\r\n return `{{ ${state} in ['on', 'true', 'True', '1'] }}`;\r\n case 'INT':\r\n case 'DINT':\r\n return `{{ ${state} | int(default=0) if ${state} not in ${invalid} else 0 }}`;\r\n case 'REAL':\r\n case 'LREAL':\r\n return `{{ ${state} | float(default=0.0) if ${state} not in ${invalid} else 0.0 }}`;\r\n default:\r\n return `{{ ${state} }}`;\r\n }\r\n}\r\n\r\n/**\r\n * Generate a throttle condition template with fallback for uninitialized helper\r\n */\r\nexport function generateThrottleCondition(lastRunHelper: string, throttleSeconds: number): string {\r\n // Timezone-safe comparison - see Transpiler.generateThrottleCondition.\r\n return `{% set last = states('${lastRunHelper}') %}\r\n{% if last in ['unknown', 'unavailable', 'none', ''] %}\r\n true\r\n{% else %}\r\n {{ (as_timestamp(now()) - as_timestamp(last, 0)) > ${throttleSeconds} }}\r\n{% endif %}`;\r\n}\r\n","/**\r\n * Action Generator - Convert ST Statements to HA Actions\r\n */\r\n\r\nimport type {\r\n Statement,\r\n IfStatement,\r\n CaseStatement,\r\n ForStatement,\r\n WhileStatement,\r\n RepeatStatement,\r\n AssignmentStatement,\r\n FunctionCallStatement,\r\n Expression,\r\n FunctionCall,\r\n MemberAccess,\r\n} from '../parser/ast';\r\nimport type {\r\n HAAction,\r\n HAChooseAction,\r\n HARepeatAction,\r\n HAServiceAction,\r\n HAVariablesAction,\r\n HACondition,\r\n HATemplateCondition,\r\n TranspilerContext,\r\n} from \"./types\";\r\nimport { JinjaGenerator } from \"./jinja-generator\";\r\nimport type { TimerOutputResolver } from \"./timer-transpiler\";\r\nimport type { SourceMapBuilder } from \"../sourcemap/source-map\";\r\n\r\nconst MAX_LOOP_ITERATIONS = 1000;\r\n\r\nexport class ActionGenerator {\r\n private context: TranspilerContext;\r\n private jinja: JinjaGenerator;\r\n private sourceMap?: SourceMapBuilder;\r\n\r\n constructor(context: TranspilerContext, timerResolver?: TimerOutputResolver, sourceMap?: SourceMapBuilder) {\r\n this.context = context;\r\n this.jinja = new JinjaGenerator(context, timerResolver);\r\n this.sourceMap = sourceMap;\r\n }\r\n\r\n /**\r\n * Generate HA actions from ST statements\r\n */\r\n generateActions(statements: Statement[]): HAAction[] {\r\n const actions: HAAction[] = [];\r\n statements.forEach((stmt, index) => {\r\n if (this.sourceMap) {\r\n this.sourceMap.pushPath(String(index));\r\n }\r\n const stmtActions = this.generateAction(stmt);\r\n actions.push(...stmtActions);\r\n if (this.sourceMap) {\r\n this.sourceMap.popPath();\r\n }\r\n });\r\n return actions;\r\n }\r\n\r\n /**\r\n * Generate single HA action from ST statement\r\n */\r\n generateAction(stmt: Statement): HAAction[] {\r\n switch (stmt.type) {\r\n case 'Assignment':\r\n return this.generateAssignment(stmt);\r\n\r\n case 'IfStatement':\r\n return [this.generateIf(stmt)];\r\n\r\n case 'CaseStatement':\r\n return [this.generateCase(stmt)];\r\n\r\n case 'ForStatement':\r\n return [this.generateFor(stmt)];\r\n\r\n case 'WhileStatement':\r\n return [this.generateWhile(stmt)];\r\n\r\n case 'RepeatStatement':\r\n return [this.generateRepeat(stmt)];\r\n\r\n case 'FunctionCallStatement':\r\n return this.generateFunctionCall(stmt);\r\n\r\n case 'ReturnStatement':\r\n return [{ stop: 'Return from program', error: false }];\r\n\r\n case 'ExitStatement':\r\n // EXIT in loops - handled by repeat structure\r\n return [{ stop: 'Exit loop', error: false }];\r\n\r\n default:\r\n throw new Error(`Unknown statement type: ${(stmt as any).type}`);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Assignment Generation\r\n // ==========================================================================\r\n\r\n private generateAssignment(stmt: AssignmentStatement): HAAction[] {\r\n // Record source map for assignment\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'Assignment');\r\n }\r\n\r\n const targetName = typeof stmt.target === 'string' \r\n ? stmt.target \r\n : this.getTargetName(stmt.target);\r\n\r\n const varInfo = this.context.variables.get(targetName);\r\n\r\n if (!varInfo) {\r\n // Unknown variable - use HA variables\r\n return [this.generateVariableAssignment(targetName, stmt.value)];\r\n }\r\n\r\n // Output entity → service call\r\n if (varInfo.isOutput && varInfo.entityId) {\r\n return this.generateEntityWrite(varInfo.entityId, stmt.value, varInfo.dataType);\r\n }\r\n\r\n // Persistent variable → helper service call\r\n if (varInfo.isPersistent && varInfo.helperId) {\r\n return this.generateHelperWrite(varInfo.helperId, stmt.value, varInfo.dataType);\r\n }\r\n\r\n // Transient variable → HA variables\r\n return [this.generateVariableAssignment(targetName, stmt.value)];\r\n }\r\n\r\n private getTargetName(target: string | MemberAccess): string {\r\n if (typeof target === 'string') {\r\n return target;\r\n }\r\n \r\n // target is a MemberAccess - get root variable name\r\n // e.g., for fb.output, get 'fb'\r\n let current: Expression = target;\r\n while (current.type === 'MemberAccess') {\r\n current = current.object;\r\n }\r\n if (current.type === 'VariableRef') {\r\n return current.name;\r\n }\r\n return 'unknown';\r\n }\r\n\r\n private generateVariableAssignment(name: string, value: Expression): HAVariablesAction {\r\n return {\r\n variables: {\r\n [name]: `{{ ${this.jinja.generateExpression(value)} }}`,\r\n },\r\n };\r\n }\r\n\r\n private generateEntityWrite(entityId: string, value: Expression, _dataType: string): HAAction[] {\r\n const domain = entityId.split('.')[0];\r\n const valueExpr = this.jinja.generateExpression(value);\r\n\r\n // Boolean entities (lights, switches, etc.)\r\n if (_dataType.toUpperCase() === 'BOOL') {\r\n return [{\r\n service: `{{ '${domain}.turn_on' if ${valueExpr} else '${domain}.turn_off' }}`,\r\n target: { entity_id: entityId },\r\n } as HAServiceAction];\r\n }\r\n\r\n // Numeric entities (input_number, etc.)\r\n if (domain === 'input_number' || domain === 'number') {\r\n return [{\r\n service: `${domain}.set_value`,\r\n target: { entity_id: entityId },\r\n data: { value: `{{ ${valueExpr} }}` },\r\n }];\r\n }\r\n\r\n // Text entities\r\n if (domain === 'input_text') {\r\n return [{\r\n service: 'input_text.set_value',\r\n target: { entity_id: entityId },\r\n data: { value: `{{ ${valueExpr} }}` },\r\n }];\r\n }\r\n\r\n // Default: try to set state (might not work for all entities)\r\n return [{\r\n service: `${domain}.turn_on`,\r\n target: { entity_id: entityId },\r\n }];\r\n }\r\n\r\n private generateHelperWrite(helperId: string, value: Expression, _dataType: string): HAAction[] {\r\n const domain = helperId.split('.')[0];\r\n const valueExpr = this.jinja.generateExpression(value);\r\n\r\n switch (domain) {\r\n case 'input_boolean':\r\n return [{\r\n service: `{{ 'input_boolean.turn_on' if ${valueExpr} else 'input_boolean.turn_off' }}`,\r\n target: { entity_id: helperId },\r\n } as HAServiceAction];\r\n\r\n case 'input_number':\r\n return [{\r\n service: 'input_number.set_value',\r\n target: { entity_id: helperId },\r\n data: { value: `{{ ${valueExpr} }}` },\r\n }];\r\n\r\n case 'input_text':\r\n return [{\r\n service: 'input_text.set_value',\r\n target: { entity_id: helperId },\r\n data: { value: `{{ ${valueExpr} }}` },\r\n }];\r\n\r\n case 'input_datetime':\r\n return [{\r\n service: 'input_datetime.set_datetime',\r\n target: { entity_id: helperId },\r\n data: { datetime: `{{ ${valueExpr} }}` },\r\n }];\r\n\r\n case 'counter':\r\n return [{\r\n service: 'counter.set_value',\r\n target: { entity_id: helperId },\r\n data: { value: `{{ ${valueExpr} }}` },\r\n }];\r\n\r\n default:\r\n throw new Error(`Unknown helper type: ${domain}`);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Control Flow Generation\r\n // ==========================================================================\r\n\r\n private generateIf(stmt: IfStatement): HAChooseAction {\r\n // Record source map for IF statement\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'IF statement');\r\n }\r\n\r\n const options: HAChooseAction['choose'] = [];\r\n\r\n // Main IF branch\r\n options.push({\r\n conditions: [this.generateCondition(stmt.condition)],\r\n sequence: this.generateActions(stmt.thenBranch),\r\n });\r\n\r\n // ELSIF branches\r\n for (const elsif of stmt.elsifBranches) {\r\n options.push({\r\n conditions: [this.generateCondition(elsif.condition)],\r\n sequence: this.generateActions(elsif.body),\r\n });\r\n }\r\n\r\n const result: HAChooseAction = { choose: options };\r\n\r\n // ELSE branch\r\n if (stmt.elseBranch && stmt.elseBranch.length > 0) {\r\n result.default = this.generateActions(stmt.elseBranch);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private generateCase(stmt: CaseStatement): HAChooseAction {\r\n // Record source map for CASE statement\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'CASE statement');\r\n }\r\n\r\n const selectorExpr = this.jinja.generateExpression(stmt.selector);\r\n const options: HAChooseAction['choose'] = [];\r\n\r\n for (const caseClause of stmt.cases) {\r\n // Generate condition for each case value\r\n const conditions: HACondition[] = caseClause.values.map(val => {\r\n const valExpr = this.jinja.generateExpression(val);\r\n return {\r\n condition: 'template' as const,\r\n value_template: `{{ ${selectorExpr} == ${valExpr} }}`,\r\n };\r\n });\r\n\r\n // For multiple values, wrap in OR\r\n const finalCondition: HACondition = conditions.length === 1\r\n ? conditions[0]\r\n : { condition: 'or', conditions };\r\n\r\n options.push({\r\n conditions: [finalCondition],\r\n sequence: this.generateActions(caseClause.body),\r\n });\r\n }\r\n\r\n const result: HAChooseAction = { choose: options };\r\n\r\n if (stmt.elseCase && stmt.elseCase.length > 0) {\r\n result.default = this.generateActions(stmt.elseCase);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private generateFor(stmt: ForStatement): HARepeatAction {\r\n // Record source map for FOR statement\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'FOR statement');\r\n }\r\n\r\n // Calculate iteration count\r\n const fromExpr = this.jinja.generateExpression(stmt.from);\r\n const toExpr = this.jinja.generateExpression(stmt.to);\r\n const byExpr = stmt.by ? this.jinja.generateExpression(stmt.by) : '1';\r\n\r\n // For safety, we use a count-based repeat with calculated iterations\r\n const countExpr = `{{ (((${toExpr}) - (${fromExpr})) / (${byExpr})) | int + 1 }}`;\r\n\r\n // Generate loop body with index variable\r\n const loopVarAssign: HAVariablesAction = {\r\n variables: {\r\n [stmt.variable]: `{{ (${fromExpr}) + ((repeat.index | default(1) | int - 1) * (${byExpr})) }}`,\r\n },\r\n };\r\n\r\n return {\r\n repeat: {\r\n count: countExpr,\r\n sequence: [\r\n loopVarAssign,\r\n ...this.generateActions(stmt.body),\r\n ],\r\n },\r\n };\r\n }\r\n\r\n private generateWhile(stmt: WhileStatement): HARepeatAction {\r\n // Record source map for WHILE statement\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'WHILE statement');\r\n }\r\n\r\n const condition = this.generateCondition(stmt.condition);\r\n const safetyCondition: HATemplateCondition = {\r\n condition: 'template',\r\n value_template: `{{ (repeat.index | default(1) | int) <= ${MAX_LOOP_ITERATIONS} }}`,\r\n };\r\n\r\n return {\r\n repeat: {\r\n while: [condition, safetyCondition],\r\n sequence: this.generateActions(stmt.body),\r\n },\r\n };\r\n }\r\n\r\n private generateRepeat(stmt: RepeatStatement): HARepeatAction {\r\n // Record source map for REPEAT statement\r\n if (this.sourceMap && stmt.location) {\r\n this.sourceMap.recordNode(stmt, 'REPEAT statement');\r\n }\r\n\r\n const condition = this.generateCondition(stmt.condition);\r\n const safetyCondition: HATemplateCondition = {\r\n condition: 'template',\r\n value_template: `{{ (repeat.index | default(1) | int) <= ${MAX_LOOP_ITERATIONS} }}`,\r\n };\r\n\r\n // REPEAT...UNTIL = do-while, so we use until (condition becomes true to stop)\r\n // But also add safety to prevent infinite loops\r\n return {\r\n repeat: {\r\n until: [\r\n { condition: 'or', conditions: [condition, { condition: 'not', conditions: [safetyCondition] }] },\r\n ],\r\n sequence: this.generateActions(stmt.body),\r\n },\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Function Call Generation\r\n // ==========================================================================\r\n\r\n private generateFunctionCall(stmt: FunctionCallStatement): HAAction[] {\r\n const funcName = stmt.call.name.toUpperCase();\r\n\r\n // Handle special FB calls\r\n switch (funcName) {\r\n case 'R_TRIG':\r\n case 'F_TRIG':\r\n // Edge triggers are handled at trigger level, not as actions\r\n return [];\r\n\r\n case 'TON':\r\n case 'TOF':\r\n case 'TP':\r\n // Timer FB side effects (timer.start/cancel, helper writes) are handled\r\n // globally by the main Transpiler's timer processing. The call itself\r\n // does not emit additional actions here.\r\n return [];\r\n\r\n default:\r\n // Custom function call - might be a script call\r\n return [{\r\n service: `script.${funcName.toLowerCase()}`,\r\n data: this.buildFunctionCallData(stmt.call),\r\n }];\r\n }\r\n }\r\n\r\n private buildFunctionCallData(call: FunctionCall): Record {\r\n const data: Record = {};\r\n \r\n for (const arg of call.arguments) {\r\n const key = arg.name || `arg_${call.arguments.indexOf(arg)}`;\r\n data[key] = `{{ ${this.jinja.generateExpression(arg.value)} }}`;\r\n }\r\n \r\n return data;\r\n }\r\n\r\n // ==========================================================================\r\n // Helpers\r\n // ==========================================================================\r\n\r\n private generateCondition(expr: Expression): HATemplateCondition {\r\n return {\r\n condition: 'template',\r\n value_template: this.jinja.generateCondition(expr),\r\n };\r\n }\r\n}\r\n","/**\r\n * Timer Function Block Transpiler\r\n *\r\n * Converts ST timer FBs (TON, TOF, TP) to HA timer entities\r\n * with appropriate automation logic.\r\n *\r\n * This module is currently used in isolation (via its own tests)\r\n * and is designed to be wired into the main transpiler in a\r\n * follow-up integration step.\r\n */\r\n\r\nimport type { Expression, FunctionCall } from \"../parser/ast\";\r\nimport type {\r\n HAAutomation,\r\n HAAction,\r\n HACondition,\r\n HAServiceAction,\r\n TranspilerContext,\r\n} from \"./types\";\r\nimport type {\r\n TimerFBType,\r\n TimerInstance,\r\n TimerInputs,\r\n TimerEntities,\r\n TimerTranspileResult,\r\n TimerOutputMappings,\r\n ParsedTimerCall,\r\n} from \"./timer-types\";\r\nimport { JinjaGenerator } from \"./jinja-generator\";\r\n\r\n// ============================================================================\r\n// Main Timer Transpiler\r\n// ============================================================================\r\n\r\nexport class TimerTranspiler {\r\n private jinja: JinjaGenerator;\r\n\r\n constructor(_context: TranspilerContext) {\r\n this.jinja = new JinjaGenerator(_context);\r\n }\r\n\r\n /**\r\n * Check if a function call is a timer FB (by FB type name)\r\n */\r\n isTimerFBType(name: string): boolean {\r\n const upper = name.toUpperCase();\r\n return upper === \"TON\" || upper === \"TOF\" || upper === \"TP\";\r\n }\r\n\r\n /**\r\n * Parse a timer FB call\r\n *\r\n * Note: The current parser models FB calls with `call.name` equal to the\r\n * called symbol. Whether this is the instance name or FB type depends on\r\n * how the ST code is written; this helper is intentionally generic and\r\n * leaves the instance/type resolution to the caller.\r\n */\r\n parseTimerCall(instanceName: string, call: FunctionCall): ParsedTimerCall {\r\n const type = call.name.toUpperCase() as TimerFBType;\r\n const inputs: ParsedTimerCall[\"inputs\"] = {};\r\n\r\n for (const arg of call.arguments) {\r\n const paramName = arg.name?.toUpperCase();\r\n if (!paramName) continue;\r\n\r\n switch (paramName) {\r\n case \"IN\":\r\n inputs.IN = this.jinja.generateExpression(arg.value);\r\n break;\r\n case \"PT\":\r\n inputs.PT = this.parseTimeToSeconds(arg.value);\r\n break;\r\n case \"R\":\r\n inputs.R = this.jinja.generateExpression(arg.value);\r\n break;\r\n }\r\n }\r\n\r\n return { instanceName, type, inputs };\r\n }\r\n\r\n /**\r\n * Transpile a timer FB instance given its resolved type and inputs.\r\n *\r\n * The caller is responsible for:\r\n * - Determining the correct `TimerInstance` (name/program/project/type)\r\n * - Providing normalized `TimerInputs` (IN/PT Jinja expressions)\r\n */\r\n transpileTimer(instance: TimerInstance, inputs: TimerInputs): TimerTranspileResult {\r\n const entities = this.generateEntityIds(instance);\r\n const helpers = this.generateHelperConfigs(instance, entities);\r\n\r\n switch (instance.type) {\r\n case \"TON\":\r\n return this.transpileTON(instance, inputs, entities, helpers);\r\n case \"TOF\":\r\n return this.transpileTOF(instance, inputs, entities, helpers);\r\n case \"TP\":\r\n return this.transpileTP(instance, inputs, entities, helpers);\r\n default:\r\n throw new Error(`Unknown timer type: ${instance.type}`);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // TON - On-Delay Timer\r\n // ==========================================================================\r\n\r\n private transpileTON(\r\n instance: TimerInstance,\r\n inputs: TimerInputs,\r\n entities: TimerEntities,\r\n helpers: import(\"../analyzer/types\").HelperConfig[],\r\n ): TimerTranspileResult {\r\n const mainActions: HAAction[] = [\r\n {\r\n // High-level choose block for TON behavior\r\n // Case 1: IN = TRUE and timer idle -> start timer\r\n // Case 2: IN = FALSE -> cancel timer and reset Q\r\n choose: [\r\n {\r\n conditions: [\r\n this.templateCondition(inputs.IN),\r\n this.stateCondition(entities.timerId, \"idle\"),\r\n ],\r\n sequence: [this.timerStart(entities.timerId, inputs.PT)],\r\n },\r\n {\r\n conditions: [this.templateCondition(`not (${inputs.IN})`)],\r\n sequence: [\r\n this.timerCancel(entities.timerId),\r\n this.booleanTurnOff(entities.outputHelperId),\r\n ],\r\n },\r\n ],\r\n } as any,\r\n ];\r\n\r\n const finishedAutomation = this.generateFinishedAutomation(\r\n instance,\r\n entities,\r\n inputs.IN,\r\n [this.booleanTurnOn(entities.outputHelperId)],\r\n );\r\n\r\n const outputMappings: TimerOutputMappings = {\r\n Q: `(states('${entities.outputHelperId}') == 'on')`,\r\n ET: entities.elapsedHelperId\r\n ? `(states('${entities.elapsedHelperId}') | float(default=0))`\r\n : undefined,\r\n };\r\n\r\n return {\r\n entities,\r\n helpers,\r\n mainActions,\r\n finishedAutomation,\r\n outputMappings,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // TOF - Off-Delay Timer\r\n // ==========================================================================\r\n\r\n private transpileTOF(\r\n instance: TimerInstance,\r\n inputs: TimerInputs,\r\n entities: TimerEntities,\r\n helpers: import(\"../analyzer/types\").HelperConfig[],\r\n ): TimerTranspileResult {\r\n const mainActions: HAAction[] = [\r\n {\r\n choose: [\r\n // Case 1: IN = TRUE -> cancel timer and set Q immediately\r\n {\r\n conditions: [this.templateCondition(inputs.IN)],\r\n sequence: [\r\n this.timerCancel(entities.timerId),\r\n this.booleanTurnOn(entities.outputHelperId),\r\n ],\r\n },\r\n // Case 2: IN = FALSE and Q ON and timer idle -> start timer\r\n {\r\n conditions: [\r\n this.templateCondition(`not (${inputs.IN})`),\r\n this.stateCondition(entities.outputHelperId, \"on\"),\r\n this.stateCondition(entities.timerId, \"idle\"),\r\n ],\r\n sequence: [this.timerStart(entities.timerId, inputs.PT)],\r\n },\r\n ],\r\n } as any,\r\n ];\r\n\r\n const finishedAutomation = this.generateFinishedAutomation(\r\n instance,\r\n entities,\r\n `not (${inputs.IN})`,\r\n [this.booleanTurnOff(entities.outputHelperId)],\r\n );\r\n\r\n const outputMappings: TimerOutputMappings = {\r\n Q: `(states('${entities.outputHelperId}') == 'on')`,\r\n };\r\n\r\n return {\r\n entities,\r\n helpers,\r\n mainActions,\r\n finishedAutomation,\r\n outputMappings,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // TP - Pulse Timer\r\n // ==========================================================================\r\n\r\n private transpileTP(\r\n instance: TimerInstance,\r\n inputs: TimerInputs,\r\n entities: TimerEntities,\r\n helpers: import(\"../analyzer/types\").HelperConfig[],\r\n ): TimerTranspileResult {\r\n // Additional helper to track pulse trigger state\r\n const pulseTriggeredId = `input_boolean.${this.sanitize(\r\n instance.projectName,\r\n )}_${this.sanitize(instance.programName)}_${this.sanitize(instance.name)}_triggered`;\r\n\r\n helpers.push({\r\n id: pulseTriggeredId,\r\n type: \"input_boolean\",\r\n name: `ST ${instance.programName} ${instance.name} Triggered`,\r\n initial: false,\r\n });\r\n\r\n const mainActions: HAAction[] = [\r\n {\r\n choose: [\r\n // Case 1: Rising edge (IN TRUE and not triggered) -> start pulse\r\n {\r\n conditions: [\r\n this.templateCondition(inputs.IN),\r\n this.stateCondition(pulseTriggeredId, \"off\"),\r\n this.stateCondition(entities.timerId, \"idle\"),\r\n ],\r\n sequence: [\r\n this.booleanTurnOn(pulseTriggeredId),\r\n this.booleanTurnOn(entities.outputHelperId),\r\n this.timerStart(entities.timerId, inputs.PT),\r\n ],\r\n },\r\n // Case 2: IN FALSE and timer idle -> reset trigger flag\r\n {\r\n conditions: [\r\n this.templateCondition(`not (${inputs.IN})`),\r\n this.stateCondition(entities.timerId, \"idle\"),\r\n ],\r\n sequence: [this.booleanTurnOff(pulseTriggeredId)],\r\n },\r\n ],\r\n } as any,\r\n ];\r\n\r\n const finishedAutomation = this.generateFinishedAutomation(\r\n instance,\r\n entities,\r\n \"true\", // always execute when timer finishes\r\n [this.booleanTurnOff(entities.outputHelperId)],\r\n );\r\n\r\n const outputMappings: TimerOutputMappings = {\r\n Q: `(states('${entities.outputHelperId}') == 'on')`,\r\n };\r\n\r\n return {\r\n entities,\r\n helpers,\r\n mainActions,\r\n finishedAutomation,\r\n outputMappings,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Helper Generators\r\n // ==========================================================================\r\n\r\n private generateEntityIds(instance: TimerInstance): TimerEntities {\r\n const base = `st_${this.sanitize(instance.projectName)}_${this.sanitize(\r\n instance.programName,\r\n )}_${this.sanitize(instance.name)}`;\r\n\r\n return {\r\n timerId: `timer.${base}`,\r\n outputHelperId: `input_boolean.${base}_q`,\r\n elapsedHelperId: `input_number.${base}_et`,\r\n };\r\n }\r\n\r\n private generateHelperConfigs(\r\n instance: TimerInstance,\r\n entities: TimerEntities,\r\n ): import(\"../analyzer/types\").HelperConfig[] {\r\n const baseName = `ST ${instance.programName} ${instance.name}`;\r\n\r\n return [\r\n {\r\n id: entities.timerId,\r\n type: \"timer\",\r\n name: `${baseName} Timer`,\r\n },\r\n {\r\n id: entities.outputHelperId,\r\n type: \"input_boolean\",\r\n name: `${baseName} Q`,\r\n initial: false,\r\n },\r\n ];\r\n }\r\n\r\n private generateFinishedAutomation(\r\n instance: TimerInstance,\r\n entities: TimerEntities,\r\n additionalCondition: string,\r\n actions: HAAction[],\r\n ): HAAutomation {\r\n return {\r\n id: `st_${this.sanitize(instance.projectName)}_${this.sanitize(\r\n instance.programName,\r\n )}_${this.sanitize(instance.name)}_finished`,\r\n alias: `[ST] ${instance.programName} - ${instance.name} Finished`,\r\n description: `Timer finished handler for ${instance.type} ${instance.name}`,\r\n mode: \"single\",\r\n trigger: [\r\n {\r\n platform: \"event\",\r\n event_type: \"timer.finished\",\r\n event_data: {\r\n entity_id: entities.timerId,\r\n },\r\n },\r\n ],\r\n condition:\r\n additionalCondition !== \"true\"\r\n ? [\r\n {\r\n condition: \"template\",\r\n value_template: `{{ ${additionalCondition} }}`,\r\n },\r\n ]\r\n : undefined,\r\n action: actions,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Action Helpers\r\n // ==========================================================================\r\n\r\n private timerStart(timerId: string, durationSeconds: string): HAServiceAction {\r\n return {\r\n service: \"timer.start\",\r\n target: { entity_id: timerId },\r\n data: { duration: `{{ ${durationSeconds} }}` },\r\n };\r\n }\r\n\r\n private timerCancel(timerId: string): HAServiceAction {\r\n return {\r\n service: \"timer.cancel\",\r\n target: { entity_id: timerId },\r\n };\r\n }\r\n\r\n private booleanTurnOn(helperId: string): HAServiceAction {\r\n return {\r\n service: \"input_boolean.turn_on\",\r\n target: { entity_id: helperId },\r\n };\r\n }\r\n\r\n private booleanTurnOff(helperId: string): HAServiceAction {\r\n return {\r\n service: \"input_boolean.turn_off\",\r\n target: { entity_id: helperId },\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Condition Helpers\r\n // ==========================================================================\r\n\r\n private templateCondition(expression: string): HACondition {\r\n return {\r\n condition: \"template\",\r\n value_template: `{{ ${expression} }}`,\r\n };\r\n }\r\n\r\n private stateCondition(entityId: string, state: string): HACondition {\r\n return {\r\n condition: \"state\",\r\n entity_id: entityId,\r\n state,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Utility Helpers\r\n // ==========================================================================\r\n\r\n private sanitize(s: string): string {\r\n return s.toLowerCase().replace(/[^a-z0-9]/g, \"_\");\r\n }\r\n\r\n private parseTimeToSeconds(expr: Expression): string {\r\n if (expr.type === \"Literal\" && expr.kind === \"time\") {\r\n const raw = expr.raw;\r\n if (/^T#/i.test(raw)) {\r\n const body = raw.slice(2);\r\n const pattern = /(\\d+)(ms|h|m|s)/gi;\r\n let total = 0;\r\n let consumed = \"\";\r\n\r\n for (const match of body.matchAll(pattern)) {\r\n const value = parseInt(match[1], 10);\r\n const unit = match[2].toLowerCase();\r\n consumed += match[0];\r\n\r\n switch (unit) {\r\n case \"h\":\r\n total += value * 3600;\r\n break;\r\n case \"m\":\r\n total += value * 60;\r\n break;\r\n case \"s\":\r\n total += value;\r\n break;\r\n case \"ms\":\r\n total += value / 1000;\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n if (consumed.toLowerCase() === body.toLowerCase()) {\r\n return String(total);\r\n }\r\n }\r\n }\r\n\r\n // For variable expressions or non-time literals, treat as Jinja expression\r\n return this.jinja.generateExpression(expr);\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Timer Output Resolver\r\n// ============================================================================\r\n\r\n/**\r\n * Resolve timer output references in expressions.\r\n *\r\n * When code references `timer1.Q`, this can be used to map that to the\r\n * appropriate Jinja expression for the underlying helper entity.\r\n *\r\n * Note: Wiring into the main `JinjaGenerator` is planned but not required\r\n * for the core timer transpiler tests in this phase.\r\n */\r\nexport class TimerOutputResolver {\r\n private timerMappings: Map = new Map();\r\n\r\n registerTimer(instanceName: string, mappings: TimerOutputMappings): void {\r\n this.timerMappings.set(instanceName, mappings);\r\n }\r\n\r\n resolveOutput(instanceName: string, output: \"Q\" | \"ET\"): string | null {\r\n const mappings = this.timerMappings.get(instanceName);\r\n if (!mappings) return null;\r\n\r\n switch (output) {\r\n case \"Q\":\r\n return mappings.Q;\r\n case \"ET\":\r\n return mappings.ET ?? null;\r\n default:\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Check if a variable/member pair refers to a known timer output.\r\n */\r\n isTimerOutputRef(varName: string, member: string): boolean {\r\n if (!this.timerMappings.has(varName)) return false;\r\n const upper = member.toUpperCase();\r\n return upper === \"Q\" || upper === \"ET\";\r\n }\r\n}\r\n\r\n","/**\r\n * Source Map Generator\r\n * \r\n * Records mappings between generated YAML paths and original ST source locations.\r\n */\r\n\r\nimport type {\r\n SourceLocation,\r\n SourceMapEntry,\r\n SourceMap,\r\n YAMLPath,\r\n EmbeddedSourceMap,\r\n} from './source-map-types';\r\n\r\n// ============================================================================\r\n// Source Map Builder\r\n// ============================================================================\r\n\r\nexport class SourceMapBuilder {\r\n private mappings: Map = new Map();\r\n private currentPath: string[] = [];\r\n \r\n private project: string;\r\n private program: string;\r\n private sourceFile: string;\r\n private sourceHash: string;\r\n\r\n constructor(options: {\r\n project: string;\r\n program: string;\r\n sourceFile: string;\r\n sourceContent: string;\r\n }) {\r\n this.project = options.project;\r\n this.program = options.program;\r\n this.sourceFile = options.sourceFile;\r\n this.sourceHash = this.hashContent(options.sourceContent);\r\n }\r\n\r\n // ==========================================================================\r\n // Path Management\r\n // ==========================================================================\r\n\r\n /**\r\n * Push a path segment (e.g., entering an action array)\r\n */\r\n pushPath(segment: string | number): void {\r\n this.currentPath.push(String(segment));\r\n }\r\n\r\n /**\r\n * Pop the last path segment\r\n */\r\n popPath(): void {\r\n this.currentPath.pop();\r\n }\r\n\r\n /**\r\n * Get current YAML path as string\r\n */\r\n getCurrentPath(): YAMLPath {\r\n return this.currentPath.join('.');\r\n }\r\n\r\n /**\r\n * Execute function with temporary path segment\r\n */\r\n withPath(segment: string | number, fn: () => T): T {\r\n this.pushPath(segment);\r\n try {\r\n return fn();\r\n } finally {\r\n this.popPath();\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Recording Mappings\r\n // ==========================================================================\r\n\r\n /**\r\n * Record a mapping at the current path\r\n */\r\n record(location: Partial & { line: number; column: number }, description?: string): void {\r\n const path = this.getCurrentPath();\r\n if (path) {\r\n this.mappings.set(path, {\r\n st: {\r\n file: this.sourceFile,\r\n line: location.line,\r\n column: location.column,\r\n endLine: location.endLine,\r\n endColumn: location.endColumn,\r\n },\r\n description,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Record a mapping at a specific path\r\n */\r\n recordAt(path: YAMLPath, location: Partial & { line: number; column: number }, description?: string): void {\r\n this.mappings.set(path, {\r\n st: {\r\n file: this.sourceFile,\r\n line: location.line,\r\n column: location.column,\r\n endLine: location.endLine,\r\n endColumn: location.endColumn,\r\n },\r\n description,\r\n });\r\n }\r\n\r\n /**\r\n * Record mapping for an AST node\r\n * Handles both parser SourceLocation (startLine/startColumn) and sourcemap SourceLocation (line/column)\r\n */\r\n recordNode(node: { location?: any }, description?: string): void {\r\n if (!node.location) return;\r\n \r\n // Convert parser SourceLocation format to sourcemap format\r\n const location: SourceLocation = {\r\n file: this.sourceFile,\r\n line: node.location.startLine ?? node.location.line ?? 1,\r\n column: node.location.startColumn ?? node.location.column ?? 1,\r\n };\r\n \r\n if (node.location.endLine) {\r\n location.endLine = node.location.endLine;\r\n }\r\n if (node.location.endColumn) {\r\n location.endColumn = node.location.endColumn;\r\n }\r\n \r\n this.record(location, description);\r\n }\r\n\r\n // ==========================================================================\r\n // Building Output\r\n // ==========================================================================\r\n\r\n /**\r\n * Build the complete source map\r\n */\r\n build(automationId: string, scriptId?: string): SourceMap {\r\n return {\r\n version: 1,\r\n project: this.project,\r\n program: this.program,\r\n automationId,\r\n scriptId,\r\n generatedAt: new Date().toISOString(),\r\n mappings: Object.fromEntries(this.mappings),\r\n sourceHash: this.sourceHash,\r\n };\r\n }\r\n\r\n /**\r\n * Build source map for embedding in YAML variables\r\n */\r\n buildEmbedded(): EmbeddedSourceMap {\r\n return {\r\n _st_source_map: Object.fromEntries(this.mappings),\r\n _st_source_file: this.sourceFile,\r\n _st_source_hash: this.sourceHash,\r\n };\r\n }\r\n\r\n /**\r\n * Export mappings as JSON string (for storage)\r\n */\r\n toJSON(): string {\r\n return JSON.stringify(this.build(''), null, 2);\r\n }\r\n\r\n // ==========================================================================\r\n // Utilities\r\n // ==========================================================================\r\n\r\n private hashContent(content: string): string {\r\n // Simple hash for change detection\r\n let hash = 0;\r\n for (let i = 0; i < content.length; i++) {\r\n const char = content.charCodeAt(i);\r\n hash = ((hash << 5) - hash) + char;\r\n hash = hash & hash; // Convert to 32bit integer\r\n }\r\n return hash.toString(16);\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Source Map Reader\r\n// ============================================================================\r\n\r\nexport class SourceMapReader {\r\n private sourceMap: SourceMap;\r\n\r\n constructor(sourceMap: SourceMap) {\r\n this.sourceMap = sourceMap;\r\n }\r\n\r\n /**\r\n * Look up ST location for a YAML path\r\n */\r\n lookup(yamlPath: YAMLPath): SourceMapEntry | null {\r\n // Exact match\r\n if (this.sourceMap.mappings[yamlPath]) {\r\n return this.sourceMap.mappings[yamlPath];\r\n }\r\n\r\n // Try parent paths\r\n const parts = yamlPath.split('.');\r\n while (parts.length > 0) {\r\n parts.pop();\r\n const parentPath = parts.join('.');\r\n if (this.sourceMap.mappings[parentPath]) {\r\n return this.sourceMap.mappings[parentPath];\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Find all mappings within a path prefix\r\n */\r\n findByPrefix(prefix: YAMLPath): Array<{ path: YAMLPath; entry: SourceMapEntry }> {\r\n const results: Array<{ path: YAMLPath; entry: SourceMapEntry }> = [];\r\n \r\n for (const [path, entry] of Object.entries(this.sourceMap.mappings)) {\r\n if (path.startsWith(prefix)) {\r\n results.push({ path, entry });\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Get all mappings for a specific ST line\r\n */\r\n findByLine(file: string, line: number): Array<{ path: YAMLPath; entry: SourceMapEntry }> {\r\n const results: Array<{ path: YAMLPath; entry: SourceMapEntry }> = [];\r\n \r\n for (const [path, entry] of Object.entries(this.sourceMap.mappings)) {\r\n if (entry.st.file === file && entry.st.line === line) {\r\n results.push({ path, entry });\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Validate source map against current source\r\n */\r\n validateHash(currentContent: string): boolean {\r\n if (!this.sourceMap.sourceHash) return true;\r\n \r\n let hash = 0;\r\n for (let i = 0; i < currentContent.length; i++) {\r\n const char = currentContent.charCodeAt(i);\r\n hash = ((hash << 5) - hash) + char;\r\n hash = hash & hash;\r\n }\r\n \r\n return hash.toString(16) === this.sourceMap.sourceHash;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Integration with Transpiler\r\n// ============================================================================\r\n\r\n/**\r\n * Create a source map builder integrated with transpiler context\r\n */\r\nexport function createSourceMapBuilder(\r\n project: string,\r\n program: string,\r\n sourceFile: string,\r\n sourceContent: string\r\n): SourceMapBuilder {\r\n return new SourceMapBuilder({\r\n project,\r\n program,\r\n sourceFile,\r\n sourceContent,\r\n });\r\n}\r\n","/**\r\n * Main Transpiler - ST AST to HA Automation/Script\r\n */\r\n\r\nimport type { ProgramNode } from \"../parser/ast\";\r\nimport type { AnalysisResult } from \"../analyzer/types\";\r\nimport type {\r\n StorageAnalysisResult,\r\n HelperConfig,\r\n} from \"../analyzer/types\";\r\nimport type {\r\n TranspilerResult,\r\n HAAutomation,\r\n HAScript,\r\n TranspilerContext,\r\n VariableInfo,\r\n EntityInfo,\r\n TranspilerDiagnostic,\r\n HATrigger,\r\n HAAction,\r\n} from \"./types\";\r\nimport { analyzeDependencies } from \"../analyzer/dependency-analyzer\";\r\nimport { analyzeStorage } from \"../analyzer/storage-analyzer\";\r\nimport { ActionGenerator } from \"./action-generator\";\r\nimport { parsePragmas } from \"../analyzer/trigger-generator\";\r\nimport { TimerTranspiler, TimerOutputResolver } from \"./timer-transpiler\";\r\nimport { walkAST } from \"../analyzer/ast-visitor\";\r\nimport type { TimerInstance, TimerInputs } from \"./timer-types\";\r\nimport { SourceMapBuilder } from \"../sourcemap/source-map\";\r\n\r\nexport class Transpiler {\r\n private ast: ProgramNode;\r\n private projectName: string;\r\n private depAnalysis!: AnalysisResult;\r\n private storageAnalysis!: StorageAnalysisResult;\r\n private context!: TranspilerContext;\r\n private sourceMapBuilder?: SourceMapBuilder;\r\n private diagnostics: TranspilerDiagnostic[] = [];\r\n private timerTranspiler?: TimerTranspiler;\r\n private timerResolver?: TimerOutputResolver;\r\n private timerHelpers: HelperConfig[] = [];\r\n private additionalAutomations: HAAutomation[] = [];\r\n private timerMainActions: HAAction[] = [];\r\n\r\n constructor(ast: ProgramNode, projectName: string = 'default', sourceContent?: string) {\r\n this.ast = ast;\r\n this.projectName = projectName;\r\n \r\n // Initialize source map builder if source content is provided\r\n if (sourceContent) {\r\n this.sourceMapBuilder = new SourceMapBuilder({\r\n project: projectName,\r\n program: ast.name,\r\n sourceFile: `${ast.name}.st`,\r\n sourceContent: sourceContent, // Explicitly use parameter to avoid TS6133\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Transpile AST to HA automation and script\r\n */\r\n transpile(): TranspilerResult {\r\n // Phase 1: Run analyzers\r\n this.depAnalysis = analyzeDependencies(this.ast);\r\n this.storageAnalysis = analyzeStorage(this.ast, this.projectName);\r\n\r\n // Collect diagnostics from analyzers\r\n this.diagnostics.push(\r\n ...this.depAnalysis.diagnostics.map(d => ({\r\n severity: d.severity as 'Error' | 'Warning' | 'Info',\r\n code: d.code,\r\n message: d.message,\r\n stLine: d.location?.line,\r\n })),\r\n ...this.storageAnalysis.diagnostics.map(d => ({\r\n severity: d.severity as 'Error' | 'Warning' | 'Info',\r\n code: d.code,\r\n message: d.message,\r\n stLine: d.location?.line,\r\n }))\r\n );\r\n\r\n // Phase 2: Build transpiler context\r\n this.buildContext();\r\n\r\n // Phase 3: Initialize timer transpiler and process timer FBs\r\n this.timerTranspiler = new TimerTranspiler(this.context);\r\n this.timerResolver = new TimerOutputResolver();\r\n this.processTimerFBs();\r\n\r\n // Phase 4: Generate automation (triggers)\r\n const automation = this.generateAutomation();\r\n\r\n // Phase 5: Generate script (logic)\r\n const script = this.generateScript();\r\n\r\n // Phase 6: Collect helpers (storage + timers)\r\n const helpers = this.collectHelpers();\r\n\r\n // Phase 7: Build source map\r\n const sourceMap = this.sourceMapBuilder\r\n ? this.sourceMapBuilder.build(automation.id, script.alias.replace(/\\[ST\\]\\s*/, '').toLowerCase().replace(/[^a-z0-9_]/g, '_'))\r\n : {\r\n version: 1 as const,\r\n project: this.projectName,\r\n program: this.ast.name,\r\n automationId: automation.id,\r\n generatedAt: new Date().toISOString(),\r\n mappings: {},\r\n };\r\n\r\n return {\r\n automation,\r\n script,\r\n helpers,\r\n additionalAutomations: this.additionalAutomations,\r\n sourceMap,\r\n diagnostics: this.diagnostics,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Context Building\r\n // ==========================================================================\r\n\r\n private buildContext(): void {\r\n const variables = new Map();\r\n const entityBindings = new Map();\r\n\r\n // Build variable info map\r\n for (const varDecl of this.ast.variables) {\r\n const storageInfo = this.storageAnalysis.variables.find(v => v.name === varDecl.name);\r\n const depInfo = this.depAnalysis.dependencies.find(d => d.variableName === varDecl.name);\r\n\r\n const varInfo: VariableInfo = {\r\n name: varDecl.name,\r\n dataType: varDecl.dataType.name,\r\n isInput: varDecl.binding?.direction === 'INPUT' || varDecl.section === 'VAR_INPUT',\r\n isOutput: varDecl.binding?.direction === 'OUTPUT' || varDecl.section === 'VAR_OUTPUT',\r\n isPersistent: storageInfo?.storage.type === 'PERSISTENT',\r\n helperId: storageInfo?.storage.helperId,\r\n entityId: depInfo?.entityId || varDecl.binding?.entityId,\r\n };\r\n\r\n variables.set(varDecl.name, varInfo);\r\n\r\n // Build entity binding map\r\n if (depInfo && depInfo.entityId) {\r\n // Only include INPUT/OUTPUT, skip MEMORY for now\r\n if (depInfo.direction === 'INPUT' || depInfo.direction === 'OUTPUT') {\r\n entityBindings.set(varDecl.name, {\r\n entityId: depInfo.entityId,\r\n variableName: varDecl.name,\r\n direction: depInfo.direction,\r\n dataType: varDecl.dataType.name,\r\n });\r\n }\r\n }\r\n }\r\n\r\n this.context = {\r\n programName: this.ast.name,\r\n projectName: this.projectName,\r\n variables,\r\n entityBindings,\r\n currentPath: [],\r\n loopDepth: 0,\r\n safetyCounters: 0,\r\n };\r\n }\r\n\r\n // ==========================================================================\r\n // Timer FB Processing\r\n // ==========================================================================\r\n\r\n private processTimerFBs(): void {\r\n if (!this.timerTranspiler || !this.timerResolver) {\r\n return;\r\n }\r\n\r\n const timerVars = new Map();\r\n for (const varDecl of this.ast.variables) {\r\n const typeName = varDecl.dataType.name.toUpperCase();\r\n if (typeName === \"TON\" || typeName === \"TOF\" || typeName === \"TP\") {\r\n timerVars.set(varDecl.name, typeName as TimerInstance[\"type\"]);\r\n }\r\n }\r\n\r\n if (timerVars.size === 0) {\r\n return;\r\n }\r\n\r\n walkAST(this.ast, {\r\n onFunctionCall: (call) => {\r\n const timerType = timerVars.get(call.name);\r\n if (!timerType) {\r\n return;\r\n }\r\n\r\n const instance: TimerInstance = {\r\n name: call.name,\r\n type: timerType,\r\n programName: this.ast.name,\r\n projectName: this.projectName,\r\n };\r\n\r\n const parsed = this.timerTranspiler!.parseTimerCall(call.name, call);\r\n const inputs: TimerInputs = {\r\n IN: parsed.inputs.IN ?? \"true\",\r\n PT: parsed.inputs.PT ?? \"0\",\r\n };\r\n\r\n const result = this.timerTranspiler!.transpileTimer(instance, inputs);\r\n\r\n this.timerHelpers.push(...result.helpers);\r\n this.additionalAutomations.push(result.finishedAutomation);\r\n this.timerMainActions.push(...result.mainActions);\r\n this.timerResolver!.registerTimer(instance.name, result.outputMappings);\r\n },\r\n });\r\n }\r\n\r\n // ==========================================================================\r\n // Automation Generation\r\n // ==========================================================================\r\n\r\n private generateAutomation(): HAAutomation {\r\n const pragmas = parsePragmas(this.ast.pragmas);\r\n const throttle = pragmas.find(p => p.name === 'throttle')?.value as string | undefined;\r\n const debounce = pragmas.find(p => p.name === 'debounce')?.value as string | undefined;\r\n\r\n const automation: HAAutomation = {\r\n id: `st_${this.projectName}_${this.ast.name}`.toLowerCase().replace(/[^a-z0-9_]/g, '_'),\r\n alias: `[ST] ${this.ast.name}`,\r\n description: `Generated from ST program: ${this.ast.name}`,\r\n mode: 'single', // Automation is just dispatcher\r\n trigger: this.depAnalysis.triggers.map(t => this.mapTriggerConfig(t)),\r\n action: [],\r\n };\r\n\r\n // Add throttle condition if specified\r\n if (throttle) {\r\n const throttleHelper = this.buildEntityId('input_datetime', `st_${this.projectName}_${this.ast.name}_last_run`);\r\n const throttleSeconds = this.parseTimeToSeconds(throttle);\r\n\r\n automation.condition = [{\r\n condition: 'template',\r\n value_template: this.generateThrottleCondition(throttleHelper, throttleSeconds),\r\n }];\r\n\r\n // Add action to update last run time\r\n automation.action.push({\r\n service: 'input_datetime.set_datetime',\r\n target: { entity_id: throttleHelper },\r\n data: { datetime: '{{ now().isoformat() }}' },\r\n });\r\n }\r\n\r\n // Add debounce delay if specified\r\n if (debounce) {\r\n automation.mode = 'restart'; // Restart = debounce effect\r\n const debounceSeconds = this.parseTimeToSeconds(debounce);\r\n automation.action.push({\r\n delay: { seconds: debounceSeconds },\r\n });\r\n }\r\n\r\n // Call the logic script\r\n automation.action.push({\r\n service: 'script.turn_on',\r\n target: {\r\n entity_id: this.buildEntityId('script', `st_${this.projectName}_${this.ast.name}_logic`),\r\n },\r\n });\r\n\r\n return automation;\r\n }\r\n\r\n private collectHelpers(): HelperConfig[] {\r\n const helpers = [...this.storageAnalysis.helpers, ...this.timerHelpers];\r\n const throttleHelper = this.createThrottleHelper();\r\n if (throttleHelper) {\r\n helpers.push(throttleHelper);\r\n }\r\n\r\n const helperMap = new Map();\r\n for (const helper of helpers) {\r\n helperMap.set(helper.id, helper);\r\n }\r\n\r\n return [...helperMap.values()];\r\n }\r\n\r\n private createThrottleHelper(): HelperConfig | null {\r\n const pragmas = parsePragmas(this.ast.pragmas);\r\n const throttle = pragmas.find((p) => p.name === 'throttle')?.value as string | undefined;\r\n if (!throttle) {\r\n return null;\r\n }\r\n\r\n return {\r\n id: this.buildEntityId('input_datetime', `st_${this.projectName}_${this.ast.name}_last_run`),\r\n type: 'input_datetime',\r\n name: `ST ${this.ast.name} Last Run`,\r\n };\r\n }\r\n\r\n private buildEntityId(domain: string, objectId: string): string {\r\n return `${domain}.${objectId}`.toLowerCase().replace(/[^a-z0-9_.]/g, '_');\r\n }\r\n\r\n /**\r\n * Build the throttle condition (MUST-DO #10).\r\n *\r\n * `now()` is timezone-aware while an input_datetime state is naive\r\n * (\"2026-07-28 13:00:00\"), so subtracting the two datetimes raises\r\n * \"can't subtract offset-naive and offset-aware datetimes\". as_timestamp()\r\n * reads the naive helper state as local time and takes a default for the\r\n * uninitialised case, which keeps the first-run fallback intact.\r\n */\r\n private generateThrottleCondition(helperId: string, seconds: number): string {\r\n return `{% set last = states('${helperId}') %}\r\n{% if last in ['unknown', 'unavailable', 'none', ''] %}\r\n true\r\n{% else %}\r\n {{ (as_timestamp(now()) - as_timestamp(last, 0)) > ${seconds} }}\r\n{% endif %}`;\r\n }\r\n\r\n private parseTimeToSeconds(timeLiteral: string): number {\r\n if (!/^T#/i.test(timeLiteral)) return 0;\r\n\r\n const body = timeLiteral.slice(2);\r\n const pattern = /(\\d+)(ms|h|m|s)/gi;\r\n let totalSeconds = 0;\r\n let consumed = '';\r\n\r\n for (const match of body.matchAll(pattern)) {\r\n const value = parseInt(match[1], 10);\r\n const unit = match[2].toLowerCase();\r\n consumed += match[0];\r\n\r\n switch (unit) {\r\n case 'h':\r\n totalSeconds += value * 3600;\r\n break;\r\n case 'm':\r\n totalSeconds += value * 60;\r\n break;\r\n case 's':\r\n totalSeconds += value;\r\n break;\r\n case 'ms':\r\n totalSeconds += value / 1000;\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n return consumed.toLowerCase() === body.toLowerCase() ? totalSeconds : 0;\r\n }\r\n\r\n // ==========================================================================\r\n // Trigger Mapping\r\n // ==========================================================================\r\n\r\n private mapTriggerConfig(t: import('../analyzer/types').TriggerConfig): HATrigger {\r\n switch (t.platform) {\r\n case 'state':\r\n return {\r\n platform: 'state',\r\n entity_id: t.entity_id!,\r\n from: t.from,\r\n to: t.to,\r\n not_from: t.not_from,\r\n not_to: t.not_to,\r\n attribute: t.attribute,\r\n for: t.for,\r\n id: t.id,\r\n };\r\n case 'numeric_state':\r\n return {\r\n platform: 'numeric_state',\r\n entity_id: t.entity_id!,\r\n above: t.above,\r\n below: t.below,\r\n attribute: t.attribute,\r\n for: t.for,\r\n id: t.id,\r\n };\r\n case 'event':\r\n return {\r\n platform: 'event',\r\n event_type: t.event_type!,\r\n event_data: t.event_data,\r\n id: t.id,\r\n };\r\n case 'time':\r\n return {\r\n platform: 'time',\r\n at: t.at!,\r\n id: t.id,\r\n };\r\n default:\r\n // Fallback to state trigger\r\n return {\r\n platform: 'state',\r\n entity_id: t.entity_id || '',\r\n id: t.id,\r\n };\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Script Generation\r\n // ==========================================================================\r\n\r\n private generateScript(): HAScript {\r\n const pragmas = parsePragmas(this.ast.pragmas);\r\n const mode =\r\n (pragmas.find((p) => p.name === \"mode\")?.value as string) || \"restart\";\r\n\r\n const actionGenerator = new ActionGenerator(this.context, this.timerResolver, this.sourceMapBuilder);\r\n\r\n const script: HAScript = {\r\n alias: `[ST] ${this.ast.name} Logic`,\r\n description: `Logic script for ST program: ${this.ast.name}`,\r\n mode: mode as HAScript['mode'],\r\n sequence: [],\r\n };\r\n\r\n // Initialize transient variables\r\n const initVars = this.generateVariableInitializers();\r\n if (Object.keys(initVars).length > 0) {\r\n script.variables = initVars;\r\n }\r\n\r\n // Generate actions from body and prepend timer main actions\r\n // Set up source map path context for script sequence\r\n if (this.sourceMapBuilder) {\r\n this.sourceMapBuilder.pushPath('sequence');\r\n }\r\n const bodyActions = actionGenerator.generateActions(this.ast.body);\r\n if (this.sourceMapBuilder) {\r\n this.sourceMapBuilder.popPath();\r\n }\r\n script.sequence = [...this.timerMainActions, ...bodyActions];\r\n\r\n // Embed source map in script variables if available\r\n // Note: Embedded source map is stored as JSON string to match Record type\r\n if (this.sourceMapBuilder) {\r\n const embedded = this.sourceMapBuilder.buildEmbedded();\r\n if (script.variables) {\r\n script.variables = {\r\n ...script.variables,\r\n _st_source_map: JSON.stringify(embedded._st_source_map),\r\n _st_source_file: embedded._st_source_file,\r\n _st_source_hash: embedded._st_source_hash,\r\n };\r\n } else {\r\n script.variables = {\r\n _st_source_map: JSON.stringify(embedded._st_source_map),\r\n _st_source_file: embedded._st_source_file,\r\n _st_source_hash: embedded._st_source_hash,\r\n };\r\n }\r\n }\r\n\r\n return script;\r\n }\r\n\r\n private generateVariableInitializers(): Record {\r\n const vars: Record = {};\r\n\r\n for (const storageInfo of this.storageAnalysis.variables) {\r\n // Only initialize transient variables\r\n if (storageInfo.storage.type !== 'TRANSIENT') {\r\n continue;\r\n }\r\n\r\n const varDecl = this.ast.variables.find(v => v.name === storageInfo.name);\r\n if (!varDecl?.initialValue) {\r\n continue;\r\n }\r\n\r\n if (varDecl.initialValue.type === 'Literal') {\r\n // Convert literal to string representation\r\n if (varDecl.initialValue.kind === 'string') {\r\n vars[storageInfo.name] = String(varDecl.initialValue.value);\r\n } else if (varDecl.initialValue.kind === 'boolean') {\r\n vars[storageInfo.name] = varDecl.initialValue.value ? 'true' : 'false';\r\n } else {\r\n vars[storageInfo.name] = String(varDecl.initialValue.value);\r\n }\r\n }\r\n }\r\n\r\n return vars;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Convenience Function\r\n// ============================================================================\r\n\r\n/**\r\n * Transpile an ST program to HA automation and script\r\n */\r\nexport function transpile(ast: ProgramNode, projectName?: string, sourceContent?: string): TranspilerResult {\r\n const transpiler = new Transpiler(ast, projectName, sourceContent);\r\n return transpiler.transpile();\r\n}\r\n","/**\r\n * Home Assistant API Wrapper\r\n *\r\n * Automation and script configs go over REST (`hass.callApi`), everything else\r\n * over the WebSocket connection. See `HAClient` in ./types for why.\r\n */\r\n\r\nimport type {\r\n HAClient,\r\n HAConnection,\r\n HAState,\r\n HAAutomationConfig,\r\n HAScriptConfig,\r\n} from \"./types\";\r\n\r\n// ============================================================================\r\n// API Wrapper Class\r\n// ============================================================================\r\n\r\nexport class HAApiClient {\r\n private readonly client: HAClient;\r\n private readonly connection: HAConnection;\r\n private static readonly HELPER_DOMAINS = new Set([\r\n \"input_boolean\",\r\n \"input_number\",\r\n \"input_text\",\r\n \"input_datetime\",\r\n \"input_select\",\r\n \"counter\",\r\n \"timer\",\r\n ]);\r\n\r\n constructor(client: HAClient) {\r\n this.client = client;\r\n this.connection = client.connection;\r\n }\r\n\r\n // ==========================================================================\r\n // Automation API (REST)\r\n // ==========================================================================\r\n\r\n private static automationPath(automationId: string): string {\r\n return `config/automation/config/${encodeURIComponent(automationId)}`;\r\n }\r\n\r\n async getAutomation(\r\n automationId: string,\r\n ): Promise {\r\n try {\r\n return await this.client.callApi(\r\n \"GET\",\r\n HAApiClient.automationPath(automationId),\r\n );\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n async saveAutomation(\r\n automationId: string,\r\n config: HAAutomationConfig,\r\n ): Promise {\r\n await this.client.callApi(\r\n \"POST\",\r\n HAApiClient.automationPath(automationId),\r\n config,\r\n );\r\n }\r\n\r\n async deleteAutomation(automationId: string): Promise {\r\n await this.client.callApi(\r\n \"DELETE\",\r\n HAApiClient.automationPath(automationId),\r\n );\r\n }\r\n\r\n async reloadAutomations(): Promise {\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"automation\",\r\n service: \"reload\",\r\n });\r\n }\r\n\r\n // ==========================================================================\r\n // Script API (REST)\r\n // ==========================================================================\r\n\r\n private static scriptPath(scriptId: string): string {\r\n return `config/script/config/${encodeURIComponent(scriptId)}`;\r\n }\r\n\r\n async getScript(scriptId: string): Promise {\r\n try {\r\n return await this.client.callApi(\r\n \"GET\",\r\n HAApiClient.scriptPath(scriptId),\r\n );\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n async saveScript(scriptId: string, config: HAScriptConfig): Promise {\r\n await this.client.callApi(\r\n \"POST\",\r\n HAApiClient.scriptPath(scriptId),\r\n config,\r\n );\r\n }\r\n\r\n async deleteScript(scriptId: string): Promise {\r\n await this.client.callApi(\"DELETE\", HAApiClient.scriptPath(scriptId));\r\n }\r\n\r\n async reloadScripts(): Promise {\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"script\",\r\n service: \"reload\",\r\n });\r\n }\r\n\r\n // ==========================================================================\r\n // Helper API\r\n // ==========================================================================\r\n\r\n async getStates(): Promise {\r\n // Use WebSocket call to get all states\r\n return this.connection.sendMessagePromise({\r\n type: \"get_states\",\r\n });\r\n }\r\n\r\n async getSTHelpers(prefix: string = \"st_\"): Promise {\r\n const states = await this.getStates();\r\n return states.filter((s) => {\r\n const [domain, name] = s.entity_id.split(\".\");\r\n return (\r\n !!name &&\r\n HAApiClient.HELPER_DOMAINS.has(domain) &&\r\n name.startsWith(prefix)\r\n );\r\n });\r\n }\r\n\r\n async deleteHelper(entityId: string): Promise {\r\n const [domain, name] = entityId.split(\".\");\r\n await this.connection.sendMessagePromise({\r\n type: `${domain}/delete`,\r\n // e.g. input_number_id, input_boolean_id, ...\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n [`${domain}_id`]: name as any,\r\n });\r\n }\r\n\r\n async createInputBoolean(config: {\r\n name: string;\r\n initial?: boolean;\r\n }): Promise {\r\n await this.connection.sendMessagePromise({\r\n type: \"input_boolean/create\",\r\n name: config.name,\r\n initial: config.initial ?? false,\r\n });\r\n }\r\n\r\n async createInputNumber(config: {\r\n name: string;\r\n initial?: number;\r\n min?: number;\r\n max?: number;\r\n step?: number;\r\n mode?: \"box\" | \"slider\";\r\n }): Promise {\r\n await this.connection.sendMessagePromise({\r\n type: \"input_number/create\",\r\n name: config.name,\r\n initial: config.initial ?? 0,\r\n min: config.min ?? 0,\r\n max: config.max ?? 100,\r\n step: config.step ?? 1,\r\n mode: config.mode ?? \"box\",\r\n });\r\n }\r\n\r\n async createInputText(config: {\r\n name: string;\r\n initial?: string;\r\n pattern?: string;\r\n }): Promise {\r\n await this.connection.sendMessagePromise({\r\n type: \"input_text/create\",\r\n name: config.name,\r\n initial: config.initial ?? \"\",\r\n pattern: config.pattern,\r\n });\r\n }\r\n\r\n async createInputDateTime(config: {\r\n name: string;\r\n initial?: string;\r\n }): Promise {\r\n await this.connection.sendMessagePromise({\r\n type: \"input_datetime/create\",\r\n name: config.name,\r\n has_date: true,\r\n has_time: true,\r\n initial: config.initial ?? \"\",\r\n });\r\n }\r\n\r\n async createTimer(config: {\r\n name: string;\r\n duration?: string;\r\n }): Promise {\r\n await this.connection.sendMessagePromise({\r\n type: \"timer/create\",\r\n name: config.name,\r\n duration: config.duration ?? \"00:00:00\",\r\n });\r\n }\r\n\r\n async setHelperValue(entityId: string, value: unknown): Promise {\r\n const [domain] = entityId.split(\".\");\r\n\r\n switch (domain) {\r\n case \"input_boolean\":\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"input_boolean\",\r\n service: value ? \"turn_on\" : \"turn_off\",\r\n service_data: { entity_id: entityId },\r\n });\r\n break;\r\n\r\n case \"input_number\":\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"input_number\",\r\n service: \"set_value\",\r\n service_data: { entity_id: entityId, value: value as number },\r\n });\r\n break;\r\n\r\n case \"input_text\":\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"input_text\",\r\n service: \"set_value\",\r\n service_data: { entity_id: entityId, value: value as string },\r\n });\r\n break;\r\n\r\n case \"input_datetime\":\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"input_datetime\",\r\n service: \"set_datetime\",\r\n service_data: { entity_id: entityId, datetime: value as string },\r\n });\r\n break;\r\n\r\n case \"counter\":\r\n await this.connection.sendMessagePromise({\r\n type: \"call_service\",\r\n domain: \"counter\",\r\n service: \"set_value\",\r\n service_data: { entity_id: entityId, value: value as number },\r\n });\r\n break;\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n}\r\n","/**\r\n * Helper Manager - Sync ST helpers with Home Assistant\r\n */\r\n\r\nimport type { HelperConfig } from '../analyzer/types';\r\nimport type { HelperSyncResult, ExistingHelper, HAState } from './types';\r\nimport { HAApiClient } from './ha-api';\r\n\r\nexport class HelperManager {\r\n private readonly api: HAApiClient;\r\n private readonly projectPrefix: string;\r\n\r\n constructor(api: HAApiClient, projectPrefix: string = 'st_') {\r\n this.api = api;\r\n this.projectPrefix = projectPrefix;\r\n }\r\n\r\n async calculateSync(\r\n required: HelperConfig[],\r\n existing?: ExistingHelper[],\r\n ): Promise {\r\n const currentHelpers = existing ?? (await this.getExistingHelpers());\r\n const existingIds = new Set(currentHelpers.map((h) => h.entityId));\r\n const requiredIds = new Set(required.map((h) => h.id));\r\n\r\n const result: HelperSyncResult = {\r\n toCreate: [],\r\n toUpdate: [],\r\n toDelete: [],\r\n unchanged: [],\r\n };\r\n\r\n for (const helper of required) {\r\n if (!existingIds.has(helper.id)) {\r\n result.toCreate.push(helper);\r\n } else {\r\n const existingHelper = currentHelpers.find((h) => h.entityId === helper.id);\r\n if (existingHelper && this.needsUpdate(helper, existingHelper)) {\r\n result.toUpdate.push(helper);\r\n } else {\r\n result.unchanged.push(helper.id);\r\n }\r\n }\r\n }\r\n\r\n for (const helper of currentHelpers) {\r\n if (!requiredIds.has(helper.entityId)) {\r\n result.toDelete.push(helper.entityId);\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n async getExistingHelpers(): Promise {\r\n const states = await this.api.getSTHelpers(this.projectPrefix);\r\n\r\n return states.map((s) => ({\r\n entityId: s.entity_id,\r\n type: s.entity_id.split('.')[0],\r\n state: s.state,\r\n attributes: s.attributes,\r\n }));\r\n }\r\n\r\n toHelperConfig(existing: ExistingHelper): HelperConfig {\r\n const name =\r\n (typeof existing.attributes.friendly_name === 'string' &&\r\n existing.attributes.friendly_name) ||\r\n this.extractName(existing.entityId);\r\n\r\n switch (existing.type) {\r\n case 'input_boolean':\r\n return {\r\n id: existing.entityId,\r\n type: 'input_boolean',\r\n name,\r\n initial: existing.state === 'on',\r\n };\r\n\r\n case 'input_number':\r\n return {\r\n id: existing.entityId,\r\n type: 'input_number',\r\n name,\r\n initial: this.parseNumericValue(existing.state),\r\n min: this.parseOptionalNumber(existing.attributes.min),\r\n max: this.parseOptionalNumber(existing.attributes.max),\r\n step: this.parseOptionalNumber(existing.attributes.step),\r\n mode:\r\n existing.attributes.mode === 'slider' ||\r\n existing.attributes.mode === 'box'\r\n ? existing.attributes.mode\r\n : undefined,\r\n };\r\n\r\n case 'input_text':\r\n return {\r\n id: existing.entityId,\r\n type: 'input_text',\r\n name,\r\n initial: existing.state,\r\n pattern:\r\n typeof existing.attributes.pattern === 'string'\r\n ? existing.attributes.pattern\r\n : undefined,\r\n };\r\n\r\n case 'input_datetime':\r\n return {\r\n id: existing.entityId,\r\n type: 'input_datetime',\r\n name,\r\n initial: existing.state,\r\n };\r\n\r\n case 'input_select':\r\n return {\r\n id: existing.entityId,\r\n type: 'input_select',\r\n name,\r\n initial: existing.state,\r\n options: Array.isArray(existing.attributes.options)\r\n ? existing.attributes.options.filter(\r\n (option): option is string => typeof option === 'string',\r\n )\r\n : undefined,\r\n };\r\n\r\n case 'counter':\r\n return {\r\n id: existing.entityId,\r\n type: 'counter',\r\n name,\r\n initial: this.parseNumericValue(existing.state),\r\n min: this.parseOptionalNumber(existing.attributes.minimum),\r\n max: this.parseOptionalNumber(existing.attributes.maximum),\r\n step: this.parseOptionalNumber(existing.attributes.step),\r\n };\r\n\r\n case 'timer':\r\n return {\r\n id: existing.entityId,\r\n type: 'timer',\r\n name,\r\n initial:\r\n (typeof existing.attributes.duration === 'string' &&\r\n existing.attributes.duration) ||\r\n existing.state,\r\n };\r\n\r\n default:\r\n return {\r\n id: existing.entityId,\r\n type: existing.type as HelperConfig['type'],\r\n name,\r\n initial: existing.state,\r\n };\r\n }\r\n }\r\n\r\n private needsUpdate(required: HelperConfig, existing: ExistingHelper): boolean {\r\n if (required.type !== existing.type) {\r\n return true;\r\n }\r\n\r\n if (required.type === 'input_number') {\r\n const attrs = existing.attributes;\r\n const min = attrs.min as number | undefined;\r\n const max = attrs.max as number | undefined;\r\n if (required.min !== min || required.max !== max) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n async applySync(\r\n syncResult: HelperSyncResult,\r\n options: { skipDeletes?: boolean } = {},\r\n ): Promise {\r\n for (const helper of syncResult.toCreate) {\r\n await this.createHelper(helper);\r\n }\r\n\r\n for (const helper of syncResult.toUpdate) {\r\n await this.api.deleteHelper(helper.id);\r\n await this.createHelper(helper);\r\n }\r\n\r\n if (!options.skipDeletes) {\r\n for (const entityId of syncResult.toDelete) {\r\n await this.api.deleteHelper(entityId);\r\n }\r\n }\r\n }\r\n\r\n async createHelper(config: HelperConfig): Promise {\r\n const name = config.name || this.extractName(config.id);\r\n\r\n switch (config.type) {\r\n case 'input_boolean':\r\n await this.api.createInputBoolean({\r\n name,\r\n initial: Boolean(config.initial ?? false),\r\n });\r\n break;\r\n\r\n case 'input_number':\r\n await this.api.createInputNumber({\r\n name,\r\n initial: Number(config.initial ?? config.min ?? 0),\r\n min: config.min,\r\n max: config.max,\r\n step: config.step,\r\n mode: config.mode,\r\n });\r\n break;\r\n\r\n case 'input_text':\r\n await this.api.createInputText({\r\n name,\r\n initial: String(config.initial ?? ''),\r\n pattern: config.pattern,\r\n });\r\n break;\r\n\r\n case 'input_datetime':\r\n await this.api.createInputDateTime({\r\n name,\r\n initial: String(config.initial ?? ''),\r\n });\r\n break;\r\n\r\n case 'timer':\r\n await this.api.createTimer({\r\n name,\r\n duration: String(config.initial ?? '00:00:00'),\r\n });\r\n break;\r\n\r\n default:\r\n throw new Error(`Unknown helper type: ${config.type} (${name})`);\r\n }\r\n }\r\n\r\n private extractName(entityId: string): string {\r\n const parts = entityId.split('.');\r\n if (parts.length !== 2) return entityId;\r\n\r\n return parts[1]\r\n .split('_')\r\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\r\n .join(' ');\r\n }\r\n\r\n private parseOptionalNumber(value: unknown): number | undefined {\r\n if (typeof value === 'number') return value;\r\n if (typeof value === 'string' && value.trim() !== '') {\r\n const parsed = Number(value);\r\n return Number.isNaN(parsed) ? undefined : parsed;\r\n }\r\n return undefined;\r\n }\r\n\r\n private parseNumericValue(value: string): number {\r\n const parsed = Number(value);\r\n return Number.isNaN(parsed) ? 0 : parsed;\r\n }\r\n\r\n async getHelperStates(helperIds: string[]): Promise> {\r\n const states = await this.api.getStates();\r\n const result: Record = {};\r\n\r\n for (const id of helperIds) {\r\n const state = states.find((s) => s.entity_id === id);\r\n if (state) {\r\n result[id] = this.parseHelperValue(state);\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private parseHelperValue(state: HAState): unknown {\r\n const domain = state.entity_id.split('.')[0];\r\n\r\n switch (domain) {\r\n case 'input_boolean':\r\n return state.state === 'on';\r\n\r\n case 'input_number':\r\n case 'counter':\r\n return Number.isNaN(Number(state.state)) ? 0 : Number(state.state);\r\n\r\n case 'input_text':\r\n case 'input_datetime':\r\n default:\r\n return state.state;\r\n }\r\n }\r\n\r\n async restoreHelperStates(states: Record): Promise {\r\n for (const [entityId, value] of Object.entries(states)) {\r\n try {\r\n await this.api.setHelperValue(entityId, value);\r\n } catch (error) {\r\n // eslint-disable-next-line no-console\r\n console.warn(`Failed to restore ${entityId}:`, error);\r\n }\r\n }\r\n }\r\n}\r\n\r\n","/**\r\n * Backup Manager - Save and restore deployment state\r\n */\r\n\r\nimport type { Backup } from './types';\r\nimport type { HelperConfig } from '../analyzer/types';\r\nimport { HAApiClient } from './ha-api';\r\nimport { HelperManager } from './helper-manager';\r\n\r\nconst BACKUP_STORAGE_KEY = 'st_hass_backups';\r\nconst MAX_BACKUPS = 10;\r\n\r\nexport class BackupManager {\r\n private readonly api: HAApiClient;\r\n private readonly helperManager: HelperManager;\r\n\r\n constructor(api: HAApiClient) {\r\n this.api = api;\r\n this.helperManager = new HelperManager(api);\r\n }\r\n\r\n async createBackup(automationId: string, programName: string): Promise {\r\n const automation = await this.api.getAutomation(automationId);\r\n\r\n const scriptId = this.getScriptId(automationId);\r\n const script = await this.api.getScript(scriptId);\r\n\r\n const helpers = await this.helperManager.getExistingHelpers();\r\n const helperConfigs: HelperConfig[] = helpers.map((h) =>\r\n this.helperManager.toHelperConfig(h),\r\n );\r\n\r\n const helperIds = helperConfigs.map((h) => h.id);\r\n const helperStates = await this.helperManager.getHelperStates(helperIds);\r\n\r\n const backup: Backup = {\r\n id: this.generateId(),\r\n timestamp: new Date(),\r\n projectName: 'default',\r\n programName,\r\n data: {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n automation: automation as any,\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n script: script as any,\r\n helpers: helperConfigs,\r\n helperStates,\r\n },\r\n };\r\n\r\n await this.saveBackup(backup);\r\n\r\n return backup;\r\n }\r\n\r\n async restoreBackup(backupId: string): Promise {\r\n const backup = await this.loadBackup(backupId);\r\n if (!backup) {\r\n throw new Error(`Backup not found: ${backupId}`);\r\n }\r\n\r\n if (backup.data.automation) {\r\n await this.api.saveAutomation(\r\n backup.data.automation.id,\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n backup.data.automation as any,\r\n );\r\n }\r\n\r\n if (backup.data.script && backup.data.automation) {\r\n const scriptId = this.getScriptId(backup.data.automation.id);\r\n await this.api.saveScript(\r\n scriptId,\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n backup.data.script as any,\r\n );\r\n }\r\n\r\n for (const helper of backup.data.helpers) {\r\n try {\r\n await this.helperManager.createHelper(helper);\r\n } catch {\r\n // Helper might already exist\r\n }\r\n }\r\n\r\n await this.helperManager.restoreHelperStates(backup.data.helperStates);\r\n\r\n await this.api.reloadAutomations();\r\n await this.api.reloadScripts();\r\n }\r\n\r\n async listBackups(): Promise {\r\n const stored = window.localStorage.getItem(BACKUP_STORAGE_KEY);\r\n if (!stored) return [];\r\n\r\n try {\r\n const backups = JSON.parse(stored) as Backup[];\r\n return backups.map((b) => ({\r\n ...b,\r\n timestamp: new Date(b.timestamp),\r\n }));\r\n } catch {\r\n return [];\r\n }\r\n }\r\n\r\n async loadBackup(backupId: string): Promise {\r\n const backups = await this.listBackups();\r\n return backups.find((b) => b.id === backupId) || null;\r\n }\r\n\r\n async deleteBackup(backupId: string): Promise {\r\n const backups = await this.listBackups();\r\n const filtered = backups.filter((b) => b.id !== backupId);\r\n window.localStorage.setItem(BACKUP_STORAGE_KEY, JSON.stringify(filtered));\r\n }\r\n\r\n private async saveBackup(backup: Backup): Promise {\r\n const backups = await this.listBackups();\r\n\r\n backups.unshift(backup);\r\n\r\n const trimmed = backups.slice(0, MAX_BACKUPS);\r\n\r\n window.localStorage.setItem(BACKUP_STORAGE_KEY, JSON.stringify(trimmed));\r\n }\r\n\r\n async cleanupOldBackups(keepCount: number = MAX_BACKUPS): Promise {\r\n const backups = await this.listBackups();\r\n\r\n if (backups.length <= keepCount) return 0;\r\n\r\n const toDelete = backups.slice(keepCount);\r\n const remaining = backups.slice(0, keepCount);\r\n\r\n window.localStorage.setItem(BACKUP_STORAGE_KEY, JSON.stringify(remaining));\r\n\r\n return toDelete.length;\r\n }\r\n\r\n private generateId(): string {\r\n return `backup_${Math.random().toString(36).slice(2)}_${Date.now().toString(36)}`;\r\n }\r\n\r\n private getScriptId(automationId: string): string {\r\n return `${automationId}_logic`;\r\n }\r\n}\r\n\r\n","/**\r\n * Migration Handler\r\n *\r\n * Handles schema changes between deployments with optional user interaction.\r\n */\r\n\r\nimport type {\r\n VariableSchema,\r\n ProgramSchema,\r\n MigrationIssue,\r\n MigrationOption,\r\n MigrationPlan,\r\n MigrationResolution,\r\n MigrationResult,\r\n StoredSchemas,\r\n} from \"./migration-types\";\r\n\r\n// ============================================================================\r\n// Schema Storage\r\n// ============================================================================\r\n\r\nconst SCHEMA_STORAGE_KEY = \"st_hass_schemas\";\r\n\r\nexport class SchemaStorage {\r\n save(programId: string, schema: ProgramSchema): void {\r\n const stored = this.loadAll();\r\n stored[programId] = schema;\r\n // localStorage is available in HA panel (browser) context\r\n localStorage.setItem(SCHEMA_STORAGE_KEY, JSON.stringify(stored));\r\n }\r\n\r\n load(programId: string): ProgramSchema | null {\r\n const stored = this.loadAll();\r\n return stored[programId] || null;\r\n }\r\n\r\n loadAll(): StoredSchemas {\r\n const raw = localStorage.getItem(SCHEMA_STORAGE_KEY);\r\n if (!raw) return {};\r\n try {\r\n return JSON.parse(raw) as StoredSchemas;\r\n } catch {\r\n return {};\r\n }\r\n }\r\n\r\n delete(programId: string): void {\r\n const stored = this.loadAll();\r\n delete stored[programId];\r\n localStorage.setItem(SCHEMA_STORAGE_KEY, JSON.stringify(stored));\r\n }\r\n\r\n clear(): void {\r\n localStorage.removeItem(SCHEMA_STORAGE_KEY);\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Migration Detector\r\n// ============================================================================\r\n\r\nexport class MigrationDetector {\r\n /**\r\n * Detect migration issues between old and new schema\r\n */\r\n detectIssues(\r\n oldSchema: ProgramSchema | null,\r\n newSchema: ProgramSchema,\r\n ): MigrationPlan {\r\n const issues: MigrationIssue[] = [];\r\n\r\n if (!oldSchema) {\r\n // First deployment, no migration needed\r\n return {\r\n issues: [],\r\n hasDestructiveChanges: false,\r\n requiresUserInput: false,\r\n };\r\n }\r\n\r\n const oldVars = new Map(oldSchema.variables.map((v) => [v.name, v]));\r\n const newVars = new Map(newSchema.variables.map((v) => [v.name, v]));\r\n\r\n // Check for removed variables\r\n for (const [name, oldVar] of oldVars) {\r\n if (!newVars.has(name)) {\r\n issues.push(this.createRemovedIssue(oldVar));\r\n }\r\n }\r\n\r\n // Check for added and changed variables\r\n for (const [name, newVar] of newVars) {\r\n const oldVar = oldVars.get(name);\r\n\r\n if (!oldVar) {\r\n issues.push(this.createAddedIssue(newVar));\r\n } else {\r\n // Check for changes\r\n const changeIssues = this.detectChanges(oldVar, newVar);\r\n issues.push(...changeIssues);\r\n }\r\n }\r\n\r\n return {\r\n issues,\r\n hasDestructiveChanges: issues.some((i) =>\r\n i.options.some((o) => o.isDestructive),\r\n ),\r\n requiresUserInput: issues.some(\r\n (i) => i.type === \"removed\" || i.type === \"type_changed\",\r\n ),\r\n };\r\n }\r\n\r\n private createRemovedIssue(oldVar: VariableSchema): MigrationIssue {\r\n return {\r\n type: \"removed\",\r\n variable: oldVar.name,\r\n helperId: oldVar.helperId,\r\n details: `Variable '${oldVar.name}' wurde aus dem Code entfernt`,\r\n oldSchema: oldVar,\r\n options: [\r\n {\r\n id: \"delete\",\r\n label: \"Helper löschen\",\r\n description: \"Entfernt den Helper und seinen Wert\",\r\n isDestructive: true,\r\n },\r\n {\r\n id: \"keep\",\r\n label: \"Helper behalten (orphaned)\",\r\n description: \"Behält den Helper, wird aber nicht mehr verwendet\",\r\n },\r\n ],\r\n defaultOption: \"delete\",\r\n };\r\n }\r\n\r\n private createAddedIssue(newVar: VariableSchema): MigrationIssue {\r\n return {\r\n type: \"added\",\r\n variable: newVar.name,\r\n helperId: newVar.helperId,\r\n details: `Neue Variable '${newVar.name}' hinzugefügt`,\r\n newSchema: newVar,\r\n options: [\r\n {\r\n id: \"create\",\r\n label: \"Helper erstellen\",\r\n description: `Erstellt neuen Helper mit Initialwert ${JSON.stringify(\r\n newVar.initialValue,\r\n )}`,\r\n },\r\n ],\r\n defaultOption: \"create\",\r\n };\r\n }\r\n\r\n private detectChanges(\r\n oldVar: VariableSchema,\r\n newVar: VariableSchema,\r\n ): MigrationIssue[] {\r\n const issues: MigrationIssue[] = [];\r\n\r\n // Type change\r\n if (oldVar.dataType !== newVar.dataType) {\r\n issues.push({\r\n type: \"type_changed\",\r\n variable: oldVar.name,\r\n helperId: oldVar.helperId,\r\n details: `Typ geändert: ${oldVar.dataType} → ${newVar.dataType}`,\r\n oldSchema: oldVar,\r\n newSchema: newVar,\r\n options: this.getTypeChangeOptions(oldVar, newVar),\r\n defaultOption: \"convert\",\r\n });\r\n }\r\n\r\n // Range change (for numbers)\r\n if (\r\n oldVar.helperType === \"input_number\" &&\r\n newVar.helperType === \"input_number\"\r\n ) {\r\n if (oldVar.min !== newVar.min || oldVar.max !== newVar.max) {\r\n issues.push({\r\n type: \"range_changed\",\r\n variable: oldVar.name,\r\n helperId: oldVar.helperId,\r\n details: `Bereich geändert: [${oldVar.min}, ${oldVar.max}] → [${newVar.min}, ${newVar.max}]`,\r\n oldSchema: oldVar,\r\n newSchema: newVar,\r\n options: [\r\n {\r\n id: \"update_range\",\r\n label: \"Bereich aktualisieren\",\r\n description:\r\n \"Aktualisiert min/max, Wert wird ggf. begrenzt\",\r\n },\r\n {\r\n id: \"reset\",\r\n label: \"Auf Initialwert zurücksetzen\",\r\n description: `Setzt auf ${newVar.initialValue}`,\r\n },\r\n ],\r\n defaultOption: \"update_range\",\r\n });\r\n }\r\n }\r\n\r\n return issues;\r\n }\r\n\r\n private getTypeChangeOptions(\r\n oldVar: VariableSchema,\r\n newVar: VariableSchema,\r\n ): MigrationOption[] {\r\n const options: MigrationOption[] = [];\r\n\r\n // Check if conversion is possible\r\n if (this.canConvert(oldVar.dataType, newVar.dataType)) {\r\n options.push({\r\n id: \"convert\",\r\n label: \"Wert konvertieren\",\r\n description: `Konvertiert ${oldVar.dataType} zu ${newVar.dataType}`,\r\n });\r\n }\r\n\r\n options.push({\r\n id: \"reset\",\r\n label: \"Auf Initialwert zurücksetzen\",\r\n description: `Setzt auf ${JSON.stringify(newVar.initialValue)}`,\r\n isDestructive: true,\r\n });\r\n\r\n options.push({\r\n id: \"keep_helper\",\r\n label: \"Alten Helper behalten, neuen erstellen\",\r\n description: \"Erstellt neuen Helper, alter wird orphaned\",\r\n });\r\n\r\n return options;\r\n }\r\n\r\n private canConvert(fromType: string, toType: string): boolean {\r\n const conversions: Record = {\r\n INT: [\"DINT\", \"REAL\", \"LREAL\", \"STRING\"],\r\n DINT: [\"REAL\", \"LREAL\", \"STRING\"],\r\n REAL: [\"LREAL\", \"STRING\"],\r\n LREAL: [\"STRING\"],\r\n BOOL: [\"INT\", \"STRING\"],\r\n };\r\n\r\n return conversions[fromType]?.includes(toType) ?? false;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Migration Executor\r\n// ============================================================================\r\n\r\nexport class MigrationExecutor {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n private haApi: any;\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n constructor(haApi: any) {\r\n this.haApi = haApi;\r\n }\r\n\r\n /**\r\n * Execute migration with user-provided resolutions\r\n */\r\n async execute(\r\n plan: MigrationPlan,\r\n resolutions: MigrationResolution[],\r\n ): Promise {\r\n const result: MigrationResult = {\r\n success: true,\r\n appliedChanges: [],\r\n errors: [],\r\n };\r\n\r\n // Create resolution map\r\n const resolutionMap = new Map(\r\n resolutions.map((r) => [r.issueId, r.selectedOption]),\r\n );\r\n\r\n for (const issue of plan.issues) {\r\n const selectedOption =\r\n resolutionMap.get(issue.variable) ?? issue.defaultOption;\r\n\r\n try {\r\n await this.applyResolution(issue, selectedOption);\r\n result.appliedChanges.push(`${issue.variable}: ${selectedOption}`);\r\n } catch (error) {\r\n result.success = false;\r\n result.errors.push(\r\n `${issue.variable}: ${\r\n error instanceof Error ? error.message : String(error)\r\n }`,\r\n );\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private async applyResolution(\r\n issue: MigrationIssue,\r\n option: string,\r\n ): Promise {\r\n switch (issue.type) {\r\n case \"added\":\r\n if (option === \"create\") {\r\n await this.createHelper(issue.newSchema!);\r\n }\r\n break;\r\n\r\n case \"removed\":\r\n if (option === \"delete\") {\r\n await this.deleteHelper(issue.helperId);\r\n }\r\n // 'keep' does nothing\r\n break;\r\n\r\n case \"type_changed\":\r\n await this.handleTypeChange(issue, option);\r\n break;\r\n\r\n case \"range_changed\":\r\n await this.handleRangeChange(issue, option);\r\n break;\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n private async handleTypeChange(\r\n issue: MigrationIssue,\r\n option: string,\r\n ): Promise {\r\n switch (option) {\r\n case \"convert\": {\r\n const currentValue = await this.getHelperValue(issue.helperId);\r\n const convertedValue = this.convertValue(\r\n currentValue,\r\n issue.oldSchema!.dataType,\r\n issue.newSchema!.dataType,\r\n );\r\n await this.deleteHelper(issue.helperId);\r\n await this.createHelper(issue.newSchema!, convertedValue);\r\n break;\r\n }\r\n\r\n case \"reset\":\r\n await this.deleteHelper(issue.helperId);\r\n await this.createHelper(issue.newSchema!);\r\n break;\r\n\r\n case \"keep_helper\": {\r\n // Create new helper with different ID\r\n const newHelperId = `${issue.newSchema!.helperId}_v2`;\r\n await this.createHelper({\r\n ...issue.newSchema!,\r\n helperId: newHelperId,\r\n });\r\n break;\r\n }\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n private async handleRangeChange(\r\n issue: MigrationIssue,\r\n option: string,\r\n ): Promise {\r\n if (option === \"update_range\") {\r\n const currentValue = await this.getHelperValue(issue.helperId);\r\n const clampedValue = Math.max(\r\n issue.newSchema!.min ?? Number.MIN_SAFE_INTEGER,\r\n Math.min(issue.newSchema!.max ?? Number.MAX_SAFE_INTEGER, Number(currentValue)),\r\n );\r\n\r\n await this.deleteHelper(issue.helperId);\r\n await this.createHelper(issue.newSchema!, clampedValue);\r\n } else if (option === \"reset\") {\r\n await this.deleteHelper(issue.helperId);\r\n await this.createHelper(issue.newSchema!);\r\n }\r\n }\r\n\r\n private convertValue(value: unknown, _fromType: string, toType: string): unknown {\r\n switch (toType) {\r\n case \"INT\":\r\n case \"DINT\":\r\n return Math.round(Number(value));\r\n case \"REAL\":\r\n case \"LREAL\":\r\n return Number(value);\r\n case \"STRING\":\r\n return String(value);\r\n case \"BOOL\":\r\n return Boolean(value);\r\n default:\r\n return value;\r\n }\r\n }\r\n\r\n private async createHelper(schema: VariableSchema, value?: unknown): Promise {\r\n const initialValue = value ?? schema.initialValue;\r\n\r\n switch (schema.helperType) {\r\n case \"input_boolean\":\r\n await this.haApi.createInputBoolean?.({\r\n name: this.extractHelperName(schema.helperId),\r\n initial: Boolean(initialValue),\r\n });\r\n break;\r\n\r\n case \"input_number\":\r\n await this.haApi.createInputNumber?.({\r\n name: this.extractHelperName(schema.helperId),\r\n initial: Number(initialValue),\r\n min: schema.min,\r\n max: schema.max,\r\n step: schema.step,\r\n mode: \"box\",\r\n });\r\n break;\r\n\r\n case \"input_text\":\r\n await this.haApi.createInputText?.({\r\n name: this.extractHelperName(schema.helperId),\r\n initial: String(initialValue),\r\n });\r\n break;\r\n\r\n case \"input_datetime\":\r\n await this.haApi.createInputDateTime?.({\r\n name: this.extractHelperName(schema.helperId),\r\n initial: String(initialValue),\r\n });\r\n break;\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n private async deleteHelper(helperId: string): Promise {\r\n await this.haApi.deleteHelper?.(helperId);\r\n }\r\n\r\n private async getHelperValue(helperId: string): Promise {\r\n return this.haApi.getState\r\n ? this.haApi.getState(helperId)\r\n : this.haApi.getHelperValue?.(helperId);\r\n }\r\n\r\n private extractHelperName(helperId: string): string {\r\n // input_number.st_prog_var → ST Prog Var\r\n const parts = helperId.split(\".\");\r\n if (parts.length > 1) {\r\n return parts[1]\r\n .replace(/^st_/, \"ST \")\r\n .replace(/_/g, \" \")\r\n .replace(/\\b\\w/g, (c: string) => c.toUpperCase());\r\n }\r\n return helperId;\r\n }\r\n}\r\n\r\n","/**\r\n * Deploy Manager - Transactional deployment with rollback\r\n */\r\n\r\nimport type { TranspilerResult } from '../transpiler/types';\r\nimport type { HelperConfig } from '../analyzer/types';\r\nimport type {\r\n DeployTransaction,\r\n DeployOperation,\r\n DeployResult,\r\n DeployError,\r\n} from './types';\r\nimport { HAApiClient } from './ha-api';\r\nimport { HelperManager } from './helper-manager';\r\nimport { BackupManager } from './backup-manager';\r\nimport {\r\n SchemaStorage,\r\n MigrationDetector,\r\n} from '../restore/migration-handler';\r\nimport type { ProgramSchema, VariableSchema, MigrationPlan } from '../restore/migration-types';\r\n\r\nexport class DeployManager {\r\n private readonly api: HAApiClient;\r\n private readonly helperManager: HelperManager;\r\n private readonly backupManager: BackupManager;\r\n private readonly schemaStorage: SchemaStorage;\r\n private readonly migrationDetector: MigrationDetector;\r\n\r\n constructor(api: HAApiClient) {\r\n this.api = api;\r\n this.helperManager = new HelperManager(api);\r\n this.backupManager = new BackupManager(api);\r\n this.schemaStorage = new SchemaStorage();\r\n this.migrationDetector = new MigrationDetector();\r\n }\r\n\r\n async deploy(\r\n result: TranspilerResult,\r\n options: {\r\n createBackup?: boolean;\r\n dryRun?: boolean;\r\n } = {},\r\n ): Promise {\r\n const transaction = this.createTransaction(result);\r\n\r\n try {\r\n await this.validateDeployment(result);\r\n\r\n if (options.createBackup) {\r\n await this.backupManager.createBackup(result.automation.id, result.automation.alias);\r\n }\r\n\r\n const operations = await this.calculateOperations(result);\r\n transaction.operations = operations;\r\n\r\n if (options.dryRun) {\r\n return {\r\n success: true,\r\n transactionId: transaction.id,\r\n operations,\r\n errors: [],\r\n };\r\n }\r\n\r\n transaction.status = 'in_progress';\r\n\r\n for (const op of operations) {\r\n try {\r\n await this.applyOperation(op);\r\n op.status = 'applied';\r\n } catch (error) {\r\n op.status = 'failed';\r\n op.error = this.formatError(error);\r\n\r\n await this.rollback(transaction);\r\n\r\n return {\r\n success: false,\r\n transactionId: transaction.id,\r\n operations,\r\n errors: [\r\n {\r\n operation: op,\r\n message: op.error ?? 'Unknown deploy error',\r\n code: 'DEPLOY_FAILED',\r\n },\r\n ],\r\n };\r\n }\r\n }\r\n\r\n await this.reloadAll();\r\n\r\n const verified = await this.verifyDeployment(result);\r\n if (!verified) {\r\n await this.rollback(transaction);\r\n\r\n return {\r\n success: false,\r\n transactionId: transaction.id,\r\n operations,\r\n errors: [\r\n {\r\n message: 'Deployment verification failed',\r\n code: 'VERIFY_FAILED',\r\n },\r\n ],\r\n };\r\n }\r\n\r\n transaction.status = 'committed';\r\n\r\n return {\r\n success: true,\r\n transactionId: transaction.id,\r\n operations,\r\n errors: [],\r\n };\r\n } catch (error) {\r\n transaction.status = 'failed';\r\n\r\n const deployError: DeployError = {\r\n message: this.formatError(error),\r\n code: 'DEPLOY_ERROR',\r\n };\r\n\r\n return {\r\n success: false,\r\n transactionId: transaction.id,\r\n operations: transaction.operations,\r\n errors: [deployError],\r\n };\r\n }\r\n }\r\n\r\n async rollback(transaction: DeployTransaction): Promise {\r\n const appliedOps = transaction.operations.filter((op) => op.status === 'applied').reverse();\r\n\r\n for (const op of appliedOps) {\r\n try {\r\n await this.revertOperation(op);\r\n op.status = 'reverted';\r\n } catch (error) {\r\n // eslint-disable-next-line no-console\r\n console.error(`Failed to revert operation ${op.id}:`, error);\r\n }\r\n }\r\n\r\n transaction.status = 'rolled_back';\r\n }\r\n\r\n private createTransaction(result: TranspilerResult): DeployTransaction {\r\n return {\r\n id: this.generateId(),\r\n timestamp: new Date(),\r\n projectName: 'default',\r\n programName: result.automation.alias.replace('[ST] ', ''),\r\n operations: [],\r\n status: 'pending',\r\n };\r\n }\r\n\r\n private async validateDeployment(result: TranspilerResult): Promise {\r\n const errors = result.diagnostics.filter((d) => d.severity === 'Error');\r\n if (errors.length > 0) {\r\n throw new Error(`Transpilation errors: ${errors.map((e) => e.message).join(', ')}`);\r\n }\r\n\r\n if (result.automation.trigger.length === 0) {\r\n throw new Error('Automation has no triggers - it will never execute');\r\n }\r\n }\r\n\r\n private async calculateOperations(result: TranspilerResult): Promise {\r\n const operations: DeployOperation[] = [];\r\n const automations = [result.automation, ...(result.additionalAutomations ?? [])];\r\n\r\n for (const automation of automations) {\r\n const existingAutomation = await this.api.getAutomation(automation.id);\r\n operations.push({\r\n id: this.generateId(),\r\n type: existingAutomation ? 'update' : 'create',\r\n entityType: 'automation',\r\n entityId: automation.id,\r\n previousState: existingAutomation ?? undefined,\r\n newState: automation,\r\n status: 'pending',\r\n });\r\n }\r\n\r\n const scriptId = this.getScriptId(result);\r\n const existingScript = await this.api.getScript(scriptId);\r\n operations.push({\r\n id: this.generateId(),\r\n type: existingScript ? 'update' : 'create',\r\n entityType: 'script',\r\n entityId: scriptId,\r\n previousState: existingScript ?? undefined,\r\n newState: result.script,\r\n status: 'pending',\r\n });\r\n\r\n const existingHelpers = await this.helperManager.getExistingHelpers();\r\n const existingHelperById = new Map(\r\n existingHelpers.map((helper) => [helper.entityId, helper] as const),\r\n );\r\n const helperSync = await this.helperManager.calculateSync(\r\n result.helpers,\r\n existingHelpers,\r\n );\r\n\r\n for (const helper of helperSync.toCreate) {\r\n operations.push({\r\n id: this.generateId(),\r\n type: 'create',\r\n entityType: 'helper',\r\n entityId: helper.id,\r\n newState: helper,\r\n status: 'pending',\r\n });\r\n }\r\n\r\n for (const helper of helperSync.toUpdate) {\r\n const previousHelper = existingHelperById.get(helper.id);\r\n operations.push({\r\n id: this.generateId(),\r\n type: 'update',\r\n entityType: 'helper',\r\n entityId: helper.id,\r\n previousState: previousHelper\r\n ? this.helperManager.toHelperConfig(previousHelper)\r\n : undefined,\r\n newState: helper,\r\n status: 'pending',\r\n });\r\n }\r\n\r\n for (const helperId of helperSync.toDelete) {\r\n const previousHelper = existingHelperById.get(helperId);\r\n operations.push({\r\n id: this.generateId(),\r\n type: 'delete',\r\n entityType: 'helper',\r\n entityId: helperId,\r\n previousState: previousHelper\r\n ? this.helperManager.toHelperConfig(previousHelper)\r\n : undefined,\r\n status: 'pending',\r\n });\r\n }\r\n\r\n return operations;\r\n }\r\n\r\n /**\r\n * Build a ProgramSchema from current helper configuration.\r\n * This does not change deploy behaviour and can be used by UI code\r\n * to drive migration flows.\r\n */\r\n public buildProgramSchema(result: TranspilerResult): ProgramSchema {\r\n const programName = result.automation.alias.replace('[ST] ', '');\r\n const projectName = 'default';\r\n\r\n const variables: VariableSchema[] = result.helpers.map((helper) => ({\r\n name: helper.id,\r\n dataType: helper.type,\r\n helperId: helper.id,\r\n helperType: helper.type === 'counter' ? 'input_number' : (helper.type as VariableSchema['helperType']),\r\n initialValue: helper.initial,\r\n restorePolicy: 0 as never, // restore policy is assigned at analysis time; not wired here yet\r\n min: helper.min,\r\n max: helper.max,\r\n step: helper.step,\r\n }));\r\n\r\n return {\r\n programName,\r\n projectName,\r\n variables,\r\n version: '1.0',\r\n generatedAt: new Date().toISOString(),\r\n };\r\n }\r\n\r\n /**\r\n * Create a migration plan based on previous and current schemas.\r\n * Intended to be called by higher-level UI logic before executing a deploy.\r\n */\r\n public createMigrationPlan(result: TranspilerResult): MigrationPlan {\r\n const schema = this.buildProgramSchema(result);\r\n const programId = result.automation.id;\r\n const previous = this.schemaStorage.load(programId);\r\n const plan = this.migrationDetector.detectIssues(previous, schema);\r\n this.schemaStorage.save(programId, schema);\r\n return plan;\r\n }\r\n\r\n private generateId(): string {\r\n return `op_${Math.random().toString(36).slice(2)}_${Date.now().toString(36)}`;\r\n }\r\n\r\n private formatError(error: unknown): string {\r\n if (error instanceof Error && error.message) {\r\n return error.message;\r\n }\r\n\r\n if (typeof error === 'string') {\r\n return error;\r\n }\r\n\r\n if (error && typeof error === 'object') {\r\n const candidate = error as {\r\n message?: unknown;\r\n body?: { message?: unknown; error?: unknown } | unknown;\r\n error?: unknown;\r\n code?: unknown;\r\n };\r\n\r\n if (typeof candidate.message === 'string' && candidate.message.trim()) {\r\n return candidate.message;\r\n }\r\n\r\n if (candidate.body && typeof candidate.body === 'object') {\r\n const body = candidate.body as { message?: unknown; error?: unknown };\r\n if (typeof body.message === 'string' && body.message.trim()) {\r\n return body.message;\r\n }\r\n if (typeof body.error === 'string' && body.error.trim()) {\r\n return body.error;\r\n }\r\n }\r\n\r\n if (typeof candidate.error === 'string' && candidate.error.trim()) {\r\n return candidate.error;\r\n }\r\n\r\n try {\r\n return JSON.stringify(error);\r\n } catch {\r\n // Fall through to generic message.\r\n }\r\n }\r\n\r\n return 'Unknown deploy error';\r\n }\r\n\r\n private getScriptId(result: TranspilerResult): string {\r\n return `${result.automation.id}_logic`;\r\n }\r\n\r\n private async applyOperation(op: DeployOperation): Promise {\r\n switch (op.entityType) {\r\n case 'automation':\r\n if (op.type === 'delete') {\r\n await this.api.deleteAutomation(op.entityId);\r\n } else {\r\n await this.api.saveAutomation(op.entityId, op.newState as never);\r\n }\r\n break;\r\n\r\n case 'script':\r\n if (op.type === 'delete') {\r\n await this.api.deleteScript(op.entityId);\r\n } else {\r\n await this.api.saveScript(op.entityId, op.newState as never);\r\n }\r\n break;\r\n\r\n case 'helper':\r\n if (op.type === 'delete') {\r\n await this.api.deleteHelper(op.entityId);\r\n } else if (op.type === 'update') {\r\n await this.api.deleteHelper(op.entityId);\r\n await this.helperManager.createHelper(op.newState as HelperConfig);\r\n } else {\r\n await this.helperManager.createHelper(op.newState as HelperConfig);\r\n }\r\n break;\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n private async revertOperation(op: DeployOperation): Promise {\r\n switch (op.type) {\r\n case 'create':\r\n if (op.entityType === 'automation') {\r\n await this.api.deleteAutomation(op.entityId);\r\n } else if (op.entityType === 'script') {\r\n await this.api.deleteScript(op.entityId);\r\n } else if (op.entityType === 'helper') {\r\n await this.api.deleteHelper(op.entityId);\r\n }\r\n break;\r\n\r\n case 'update':\r\n if (!op.previousState) return;\r\n if (op.entityType === 'automation') {\r\n await this.api.saveAutomation(op.entityId, op.previousState as never);\r\n } else if (op.entityType === 'script') {\r\n await this.api.saveScript(op.entityId, op.previousState as never);\r\n } else if (op.entityType === 'helper') {\r\n await this.api.deleteHelper(op.entityId);\r\n await this.helperManager.createHelper(op.previousState as HelperConfig);\r\n }\r\n break;\r\n\r\n case 'delete':\r\n if (!op.previousState) return;\r\n if (op.entityType === 'automation') {\r\n await this.api.saveAutomation(op.entityId, op.previousState as never);\r\n } else if (op.entityType === 'script') {\r\n await this.api.saveScript(op.entityId, op.previousState as never);\r\n } else if (op.entityType === 'helper') {\r\n await this.helperManager.createHelper(op.previousState as HelperConfig);\r\n }\r\n break;\r\n\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n private async reloadAll(): Promise {\r\n await this.api.reloadAutomations();\r\n await this.api.reloadScripts();\r\n }\r\n\r\n private async verifyDeployment(result: TranspilerResult): Promise {\r\n const automations = [result.automation, ...(result.additionalAutomations ?? [])];\r\n for (const automationConfig of automations) {\r\n const automation = await this.api.getAutomation(automationConfig.id);\r\n if (!automation) return false;\r\n }\r\n\r\n const scriptId = this.getScriptId(result);\r\n const script = await this.api.getScript(scriptId);\r\n if (!script) return false;\r\n\r\n const states = await this.api.getStates();\r\n for (const helper of result.helpers) {\r\n const exists = states.some((s) => s.entity_id === helper.id);\r\n if (!exists) return false;\r\n }\r\n\r\n return true;\r\n }\r\n}\r\n\r\nexport async function deploy(\r\n api: HAApiClient,\r\n result: TranspilerResult,\r\n options?: { createBackup?: boolean; dryRun?: boolean },\r\n): Promise {\r\n const manager = new DeployManager(api);\r\n return manager.deploy(result, options);\r\n}\r\n\r\n"],"names":["JinjaGenerator","context","timerResolver","__publicField","expr","lit","timeLiteral","body","pattern","totalSeconds","consumed","match","value","unit","ref","varInfo","entityId","dataType","state","invalid","helperId","left","right","operand","call","funcName","args","a","selector","inputs","result","i","mn","input","mx","root","mapped","generateEntityStateRead","generateThrottleCondition","lastRunHelper","throttleSeconds","MAX_LOOP_ITERATIONS","ActionGenerator","sourceMap","statements","actions","stmt","index","stmtActions","targetName","target","current","name","_dataType","domain","valueExpr","options","elsif","selectorExpr","caseClause","conditions","val","valExpr","finalCondition","fromExpr","toExpr","byExpr","countExpr","loopVarAssign","condition","safetyCondition","data","arg","key","TimerTranspiler","_context","upper","instanceName","type","paramName","_a","instance","entities","helpers","mainActions","finishedAutomation","outputMappings","pulseTriggeredId","base","baseName","additionalCondition","timerId","durationSeconds","expression","s","raw","total","TimerOutputResolver","mappings","output","varName","member","SourceMapBuilder","segment","fn","location","description","path","node","automationId","scriptId","content","hash","char","Transpiler","ast","projectName","sourceContent","analyzeDependencies","analyzeStorage","d","automation","script","variables","entityBindings","varDecl","storageInfo","v","depInfo","_b","_c","timerVars","typeName","walkAST","timerType","parsed","pragmas","parsePragmas","throttle","p","debounce","t","throttleHelper","debounceSeconds","helperMap","helper","objectId","seconds","mode","actionGenerator","initVars","bodyActions","embedded","vars","transpile","_HAApiClient","client","config","prefix","HAApiClient","HelperManager","api","projectPrefix","required","existing","currentHelpers","existingIds","h","requiredIds","existingHelper","option","attrs","min","max","syncResult","parts","word","helperIds","states","id","error","BACKUP_STORAGE_KEY","MAX_BACKUPS","BackupManager","programName","helperConfigs","helperStates","backup","backupId","stored","b","filtered","backups","trimmed","keepCount","toDelete","remaining","SCHEMA_STORAGE_KEY","SchemaStorage","programId","schema","MigrationDetector","oldSchema","newSchema","issues","oldVars","newVars","oldVar","newVar","changeIssues","o","fromType","toType","DeployManager","transaction","operations","op","deployError","appliedOps","errors","e","automations","existingAutomation","existingScript","existingHelpers","existingHelperById","helperSync","previousHelper","previous","plan","candidate","automationConfig","deploy"],"mappings":"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"} \ No newline at end of file diff --git a/frontend/e2e/deploy.spec.ts b/frontend/e2e/deploy.spec.ts index 670c1be..1c72b93 100644 --- a/frontend/e2e/deploy.spec.ts +++ b/frontend/e2e/deploy.spec.ts @@ -9,6 +9,7 @@ import { test, expect } from "@playwright/test"; import { authenticateHA, getAutomationConfig, + getEntityState, getScriptConfig, navigateToSTPanel, replaceEditorCode, @@ -18,6 +19,10 @@ import { // st_home_ and st_home__logic. const AUTOMATION_ID = "st_home_testprogram"; const SCRIPT_ID = "st_home_testprogram_logic"; +const PERSISTENT_HELPER_ID = + "input_number.st_home_persistentprogram_counter"; +const FOREIGN_HELPER_ID = + "input_number.st_home_foreignprogram_counter"; test.describe("Deploy Workflow", () => { test("should parse, analyze, transpile, and deploy a simple ST program", async ({ @@ -75,6 +80,48 @@ END_PROGRAM expect(script.sequence.length).toBeGreaterThan(0); }); + test("should deploy a persistent helper without touching another program", async ({ + page, + }) => { + const authToken = await authenticateHA(page); + await navigateToSTPanel(page); + + const stCode = ` +PROGRAM PersistentProgram +VAR + trigger_var AT %I* : BOOL := 'input_boolean.test_schalter_1'; + {persistent} + counter : INT := 0; +END_VAR + +IF trigger_var THEN + counter := counter + 1; +END_IF +END_PROGRAM + `.trim(); + + await replaceEditorCode(page, stCode); + await page.locator('button:has-text("Deploy")').first().click(); + + await expect(page.locator("text=/Deploy successful/i").first()).toBeVisible({ + timeout: 30000, + }); + + const helper = await getEntityState(page, PERSISTENT_HELPER_ID, authToken); + expect(helper.entity_id).toBe(PERSISTENT_HELPER_ID); + expect(helper.attributes.friendly_name).toBe( + "ST PersistentProgram - counter", + ); + + const foreignHelper = await getEntityState( + page, + FOREIGN_HELPER_ID, + authToken, + ); + expect(foreignHelper.entity_id).toBe(FOREIGN_HELPER_ID); + expect(foreignHelper.state).toBe("42.0"); + }); + test("should show syntax status in editor", async ({ page }) => { // Navigate to ST panel (handles login automatically) await navigateToSTPanel(page); diff --git a/frontend/src/deploy/backup-manager.test.ts b/frontend/src/deploy/backup-manager.test.ts index e2ced95..721c9d5 100644 --- a/frontend/src/deploy/backup-manager.test.ts +++ b/frontend/src/deploy/backup-manager.test.ts @@ -64,6 +64,8 @@ class FakeConnection implements HAClient, HAConnection { switch (message.type) { case "get_states": return this.states as unknown as T; + case "input_number/create": + return { id: message.name } as T; default: return undefined as unknown as T; } @@ -140,6 +142,16 @@ describe("BackupManager", () => { expect(conn.wsMessages).toContainEqual({ type: "input_number/create", + name: "st_default_prog_counter", + initial: 1, + min: 0, + max: 100, + step: 1, + mode: "box", + }); + expect(conn.wsMessages).toContainEqual({ + type: "input_number/update", + input_number_id: "st_default_prog_counter", name: "Counter", initial: 1, min: 0, diff --git a/frontend/src/deploy/deploy-manager.test.ts b/frontend/src/deploy/deploy-manager.test.ts index 74a27a6..4e17216 100644 --- a/frontend/src/deploy/deploy-manager.test.ts +++ b/frontend/src/deploy/deploy-manager.test.ts @@ -90,6 +90,8 @@ class FakeConnection implements HAClient, HAConnection { switch (message.type) { case "get_states": return this.states as unknown as T; + case "input_number/create": + return { id: message.name } as T; default: return undefined as unknown as T; } @@ -233,11 +235,45 @@ describe("DeployManager", () => { ); }); + it("deletes obsolete helpers only for the program being deployed", async () => { + const conn = new FakeConnection(); + conn.states = [ + { + entity_id: "input_number.st_default_prog_obsolete", + state: "0", + attributes: { min: 0, max: 100 }, + last_changed: "", + last_updated: "", + }, + { + entity_id: "input_number.st_default_other_counter", + state: "42", + attributes: { min: 0, max: 100 }, + last_changed: "", + last_updated: "", + }, + ]; + + const api = new HAApiClient(conn); + const manager = new DeployManager(api); + const deployResult = await manager.deploy(makeTranspilerResult()); + + expect(deployResult.success).toBe(true); + expect(conn.wsMessages).toContainEqual({ + type: "input_number/delete", + input_number_id: "st_default_prog_obsolete", + }); + expect(conn.wsMessages).not.toContainEqual({ + type: "input_number/delete", + input_number_id: "st_default_other_counter", + }); + }); + it("captures previous helper config for helper update and delete rollback", async () => { const conn = new FakeConnection(); conn.states = [ { - entity_id: "input_number.st_existing_helper", + entity_id: "input_number.st_default_prog_existing", state: "7", attributes: { friendly_name: "Existing Helper", @@ -250,7 +286,7 @@ describe("DeployManager", () => { last_updated: "", }, { - entity_id: "input_boolean.st_removed_helper", + entity_id: "input_boolean.st_default_prog_removed", state: "on", attributes: { friendly_name: "Removed Helper", @@ -265,7 +301,7 @@ describe("DeployManager", () => { const result = makeTranspilerResult(); result.helpers = [ { - id: "input_number.st_existing_helper", + id: "input_number.st_default_prog_existing", type: "input_number", name: "Existing Helper", initial: 7, @@ -285,11 +321,11 @@ describe("DeployManager", () => { (op) => op.entityType === "helper" && op.type === "delete" && - op.entityId === "input_boolean.st_removed_helper", + op.entityId === "input_boolean.st_default_prog_removed", ); expect(helperUpdate?.previousState).toEqual({ - id: "input_number.st_existing_helper", + id: "input_number.st_default_prog_existing", type: "input_number", name: "Existing Helper", initial: 7, @@ -299,7 +335,7 @@ describe("DeployManager", () => { mode: "box", }); expect(helperDelete?.previousState).toEqual({ - id: "input_boolean.st_removed_helper", + id: "input_boolean.st_default_prog_removed", type: "input_boolean", name: "Removed Helper", initial: true, @@ -341,18 +377,30 @@ describe("DeployManager", () => { }, }); - expect(conn.wsMessages[0]).toEqual({ - type: "input_number/delete", - input_number_id: "st_existing_helper", - }); - expect(conn.wsMessages[1]).toEqual({ - type: "input_number/create", - name: "Existing Helper", - initial: 7, - min: 0, - max: 20, - step: 1, - mode: "box", - }); + expect(conn.wsMessages).toEqual([ + { + type: "input_number/delete", + input_number_id: "st_existing_helper", + }, + { + type: "input_number/create", + name: "st_existing_helper", + initial: 7, + min: 0, + max: 20, + step: 1, + mode: "box", + }, + { + type: "input_number/update", + input_number_id: "st_existing_helper", + name: "Existing Helper", + initial: 7, + min: 0, + max: 20, + step: 1, + mode: "box", + }, + ]); }); }); diff --git a/frontend/src/deploy/deploy-manager.ts b/frontend/src/deploy/deploy-manager.ts index 44a8830..64588c9 100644 --- a/frontend/src/deploy/deploy-manager.ts +++ b/frontend/src/deploy/deploy-manager.ts @@ -200,12 +200,15 @@ export class DeployManager { status: 'pending', }); - const existingHelpers = await this.helperManager.getExistingHelpers(); + const helperScopePrefix = `${result.automation.id}_`; + const existingHelpers = + await this.helperManager.getExistingHelpers(helperScopePrefix); const existingHelperById = new Map( existingHelpers.map((helper) => [helper.entityId, helper] as const), ); const helperSync = await this.helperManager.calculateSync( result.helpers, + helperScopePrefix, existingHelpers, ); diff --git a/frontend/src/deploy/ha-api.ts b/frontend/src/deploy/ha-api.ts index 1fef9f8..b0b0b5b 100644 --- a/frontend/src/deploy/ha-api.ts +++ b/frontend/src/deploy/ha-api.ts @@ -154,18 +154,86 @@ export class HAApiClient { }); } + /** + * HA derives a storage helper's immutable id from its name on create. Create + * with the requested object id first, verify what HA allocated, then restore + * the human-readable name through the update command (which keeps the id). + */ + private async createHelperWithExactId( + domain: string, + entityId: string | undefined, + config: Record & { name: string }, + ): Promise { + if (!entityId) { + await this.connection.sendMessagePromise({ + type: `${domain}/create`, + ...config, + }); + return; + } + + const [entityDomain, objectId, ...extraParts] = entityId.split("."); + if (entityDomain !== domain || !objectId || extraParts.length > 0) { + throw new Error( + `Invalid ${domain} entity id '${entityId}' for helper creation`, + ); + } + + const { name: friendlyName, ...settings } = config; + const created = await this.connection.sendMessagePromise<{ id?: unknown }>({ + type: `${domain}/create`, + name: objectId, + ...settings, + }); + const createdObjectId = created?.id; + + if (typeof createdObjectId !== "string") { + throw new Error( + `Home Assistant did not return an id while creating ${entityId}`, + ); + } + + if (createdObjectId !== objectId) { + await this.deleteHelper(`${domain}.${createdObjectId}`); + throw new Error( + `Home Assistant created ${domain}.${createdObjectId} instead of ${entityId}`, + ); + } + + if (friendlyName === objectId) { + return; + } + + try { + await this.connection.sendMessagePromise({ + type: `${domain}/update`, + [`${domain}_id`]: objectId, + name: friendlyName, + ...settings, + }); + } catch (error) { + try { + await this.deleteHelper(entityId); + } catch { + // Preserve the original update error if cleanup also fails. + } + throw error; + } + } + async createInputBoolean(config: { + id?: string; name: string; initial?: boolean; }): Promise { - await this.connection.sendMessagePromise({ - type: "input_boolean/create", + await this.createHelperWithExactId("input_boolean", config.id, { name: config.name, initial: config.initial ?? false, }); } async createInputNumber(config: { + id?: string; name: string; initial?: number; min?: number; @@ -173,8 +241,7 @@ export class HAApiClient { step?: number; mode?: "box" | "slider"; }): Promise { - await this.connection.sendMessagePromise({ - type: "input_number/create", + await this.createHelperWithExactId("input_number", config.id, { name: config.name, initial: config.initial ?? 0, min: config.min ?? 0, @@ -185,12 +252,12 @@ export class HAApiClient { } async createInputText(config: { + id?: string; name: string; initial?: string; pattern?: string; }): Promise { - await this.connection.sendMessagePromise({ - type: "input_text/create", + await this.createHelperWithExactId("input_text", config.id, { name: config.name, initial: config.initial ?? "", pattern: config.pattern, @@ -198,11 +265,11 @@ export class HAApiClient { } async createInputDateTime(config: { + id?: string; name: string; initial?: string; }): Promise { - await this.connection.sendMessagePromise({ - type: "input_datetime/create", + await this.createHelperWithExactId("input_datetime", config.id, { name: config.name, has_date: true, has_time: true, @@ -211,11 +278,11 @@ export class HAApiClient { } async createTimer(config: { + id?: string; name: string; duration?: string; }): Promise { - await this.connection.sendMessagePromise({ - type: "timer/create", + await this.createHelperWithExactId("timer", config.id, { name: config.name, duration: config.duration ?? "00:00:00", }); diff --git a/frontend/src/deploy/helper-manager.test.ts b/frontend/src/deploy/helper-manager.test.ts index d968fa1..3127130 100644 --- a/frontend/src/deploy/helper-manager.test.ts +++ b/frontend/src/deploy/helper-manager.test.ts @@ -31,7 +31,9 @@ class FakeConnection implements HAClient, HAConnection { if (message.type === "get_states") { return this.states as unknown as T; } - // For helper tests we only care about delete helpers; no response needed. + if (message.type.endsWith("/create")) { + return { id: message.name } as T; + } return undefined as unknown as T; } @@ -58,7 +60,7 @@ describe("HelperManager", () => { const api = new HAApiClient(conn); const manager = new HelperManager(api, "st_"); - const sync = await manager.calculateSync(requiredHelpers); + const sync = await manager.calculateSync(requiredHelpers, "st_project_prog_"); expect(sync.toCreate.map((h) => h.id)).toEqual([requiredHelpers[0].id]); expect(sync.toDelete).toEqual([]); @@ -79,7 +81,7 @@ describe("HelperManager", () => { const api = new HAApiClient(conn); const manager = new HelperManager(api, "st_"); - const sync = await manager.calculateSync([]); + const sync = await manager.calculateSync([], "st_"); expect(sync.toCreate).toEqual([]); expect(sync.toUpdate).toEqual([]); @@ -114,27 +116,39 @@ describe("HelperManager", () => { const api = new HAApiClient(conn); const manager = new HelperManager(api, "st_"); - const sync = await manager.calculateSync([]); + const sync = await manager.calculateSync([], "st_"); expect(sync.toDelete).toEqual(["input_boolean.st_real_helper"]); }); - it("creates new input_number helpers via the create API instead of set_value", async () => { + it("creates input_number helpers with the requested entity id and friendly name", async () => { const conn = new FakeConnection(); const api = new HAApiClient(conn); const manager = new HelperManager(api, "st_"); await manager.createHelper(requiredHelpers[0]); - expect(conn.wsMessages).toContainEqual({ - type: "input_number/create", - name: "Threshold", - initial: 10, - min: 0, - max: 100, - step: 1, - mode: "box", - }); + expect(conn.wsMessages).toEqual([ + { + type: "input_number/create", + name: "st_project_prog_threshold", + initial: 10, + min: 0, + max: 100, + step: 1, + mode: "box", + }, + { + type: "input_number/update", + input_number_id: "st_project_prog_threshold", + name: "Threshold", + initial: 10, + min: 0, + max: 100, + step: 1, + mode: "box", + }, + ]); expect( conn.wsMessages.some( (message) => @@ -156,10 +170,18 @@ describe("HelperManager", () => { name: "Delay Timer", }); - expect(conn.wsMessages).toContainEqual({ - type: "timer/create", - name: "Delay Timer", - duration: "00:00:00", - }); + expect(conn.wsMessages).toEqual([ + { + type: "timer/create", + name: "st_project_prog_delay", + duration: "00:00:00", + }, + { + type: "timer/update", + timer_id: "st_project_prog_delay", + name: "Delay Timer", + duration: "00:00:00", + }, + ]); }); }); diff --git a/frontend/src/deploy/helper-manager.ts b/frontend/src/deploy/helper-manager.ts index aa1b079..03aa8a7 100644 --- a/frontend/src/deploy/helper-manager.ts +++ b/frontend/src/deploy/helper-manager.ts @@ -17,9 +17,14 @@ export class HelperManager { async calculateSync( required: HelperConfig[], + scopePrefix: string, existing?: ExistingHelper[], ): Promise { - const currentHelpers = existing ?? (await this.getExistingHelpers()); + const discoveredHelpers = + existing ?? (await this.getExistingHelpers(scopePrefix)); + const currentHelpers = discoveredHelpers.filter((helper) => + this.isInScope(helper.entityId, scopePrefix), + ); const existingIds = new Set(currentHelpers.map((h) => h.entityId)); const requiredIds = new Set(required.map((h) => h.id)); @@ -52,8 +57,10 @@ export class HelperManager { return result; } - async getExistingHelpers(): Promise { - const states = await this.api.getSTHelpers(this.projectPrefix); + async getExistingHelpers( + scopePrefix: string = this.projectPrefix, + ): Promise { + const states = await this.api.getSTHelpers(scopePrefix); return states.map((s) => ({ entityId: s.entity_id, @@ -202,6 +209,7 @@ export class HelperManager { switch (config.type) { case 'input_boolean': await this.api.createInputBoolean({ + id: config.id, name, initial: Boolean(config.initial ?? false), }); @@ -209,6 +217,7 @@ export class HelperManager { case 'input_number': await this.api.createInputNumber({ + id: config.id, name, initial: Number(config.initial ?? config.min ?? 0), min: config.min, @@ -220,6 +229,7 @@ export class HelperManager { case 'input_text': await this.api.createInputText({ + id: config.id, name, initial: String(config.initial ?? ''), pattern: config.pattern, @@ -228,6 +238,7 @@ export class HelperManager { case 'input_datetime': await this.api.createInputDateTime({ + id: config.id, name, initial: String(config.initial ?? ''), }); @@ -235,6 +246,7 @@ export class HelperManager { case 'timer': await this.api.createTimer({ + id: config.id, name, duration: String(config.initial ?? '00:00:00'), }); @@ -245,6 +257,11 @@ export class HelperManager { } } + private isInScope(entityId: string, scopePrefix: string): boolean { + const objectId = entityId.split('.')[1]; + return !!objectId && objectId.startsWith(scopePrefix); + } + private extractName(entityId: string): string { const parts = entityId.split('.'); if (parts.length !== 2) return entityId; diff --git a/frontend/src/restore/migration-handler.ts b/frontend/src/restore/migration-handler.ts index a8174de..0207285 100644 --- a/frontend/src/restore/migration-handler.ts +++ b/frontend/src/restore/migration-handler.ts @@ -415,6 +415,7 @@ export class MigrationExecutor { switch (schema.helperType) { case "input_boolean": await this.haApi.createInputBoolean?.({ + id: schema.helperId, name: this.extractHelperName(schema.helperId), initial: Boolean(initialValue), }); @@ -422,6 +423,7 @@ export class MigrationExecutor { case "input_number": await this.haApi.createInputNumber?.({ + id: schema.helperId, name: this.extractHelperName(schema.helperId), initial: Number(initialValue), min: schema.min, @@ -433,6 +435,7 @@ export class MigrationExecutor { case "input_text": await this.haApi.createInputText?.({ + id: schema.helperId, name: this.extractHelperName(schema.helperId), initial: String(initialValue), }); @@ -440,6 +443,7 @@ export class MigrationExecutor { case "input_datetime": await this.haApi.createInputDateTime?.({ + id: schema.helperId, name: this.extractHelperName(schema.helperId), initial: String(initialValue), }); diff --git a/tests/ha-fixture-config/input_number.yaml b/tests/ha-fixture-config/input_number.yaml index 702785b..a79d7d8 100644 --- a/tests/ha-fixture-config/input_number.yaml +++ b/tests/ha-fixture-config/input_number.yaml @@ -22,3 +22,12 @@ volume_level: step: 5 unit_of_measurement: "%" icon: mdi:volume-high + +# Represents a helper owned by a different deployed ST program. Deploying +# PersistentProgram must never include this entity in its cleanup scope. +st_home_foreignprogram_counter: + name: Foreign Program Counter + initial: 42 + min: 0 + max: 100 + step: 1