{
  "name": "Clawd (AWTRIX pet, off-device)",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "seconds",
              "secondsInterval": 2
            }
          ]
        }
      },
      "id": "trg-tick",
      "name": "Tick every 2s",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -400,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "return [{ json: { event: 'tick' } }];"
      },
      "id": "norm-tick",
      "name": "Normalize Tick",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -160,
        0
      ]
    },
    {
      "parameters": {
        "topics": "awtrixNG/state/buttons/+",
        "options": {}
      },
      "id": "trg-mqtt",
      "name": "Button Events (MQTT)",
      "type": "n8n-nodes-base.mqttTrigger",
      "typeVersion": 1,
      "position": [
        -400,
        260
      ],
      "credentials": {
        "mqtt": {
          "id": "REPLACE_WITH_YOUR_MQTT_CREDENTIAL_ID",
          "name": "AWTRIX broker"
        }
      },
      "notes": "Topic prefix 'awtrixNG' must match mqttPrefix in AWTRIX NG's MQTT settings."
    },
    {
      "parameters": {
        "jsCode": "const topic = $json.topic || '';\nconst parts = topic.split('/');\nconst btn = parts[parts.length - 1]; // 'select' | 'left' | 'right'\n// AWTRIX publishes the new button state (often \"true\"/\"1\" on press, \"false\"/\"0\" on release).\n// Only react on the press edge.\nconst raw = ($json.message || '').toString().trim().toLowerCase();\nconst isPress = raw === 'true' || raw === '1' || raw === 'on';\nif (!isPress) return [];\nreturn [{ json: { event: 'button', btn } }];"
      },
      "id": "norm-button",
      "name": "Normalize Button",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -160,
        260
      ],
      "notes": "Check AWTRIX NG's state/buttons/<button> payload format on your firmware version and adjust the isPress check if needed."
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "clawd-action",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "trg-webhook",
      "name": "HA Action (Webhook)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "webhookId": "clawd-action",
      "position": [
        -400,
        460
      ],
      "notes": "Body: {\"action\":\"feed\"|\"play\"|\"clean\"|\"med\"|\"sleep\"|\"stats\"}. Called from Home Assistant via rest_command."
    },
    {
      "parameters": {
        "jsCode": "const map = { feed: 1, play: 2, clean: 3, med: 4, sleep: 5, stats: 6 };\nconst body = $json.body || $json;\nconst id = map[(body.action || '').toString().toLowerCase()];\nif (!id) return [];\nreturn [{ json: { event: 'action', id } }];"
      },
      "id": "norm-action",
      "name": "Normalize Action",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -160,
        460
      ],
      "notes": "Maps HA's {\"action\":\"feed\"} straight to the same action() call the on-device menu would eventually fire - no menu-cycling/dwell needed."
    },
    {
      "parameters": {
        "jsCode": "// ============================================================================\n// Clawd Engine — n8n port of the AWTRIX Berry script \"Clawd\"\n// Persists pet state in this workflow's static data, so it survives across\n// executions without an external DB. See clawd-engine.js for the annotated\n// reference copy.\n// ============================================================================\n\nconst PET_NAME = 'Clawd';\n\nconst CM = {\n  o: '#D97757', O: '#E58963', q: '#A8553C', d: '#A94F38', c: '#F0906B',\n  x: '#C4694D', w: '#FFFFFF', r: '#E05540', e: '#F2E3C8', s: '#C96F4A',\n  y: '#FFD34D', m: '#8B5A2B', g: '#93A7C4', t: '#9AA0A6'\n};\n\nconst SPR = {\n  egg:   ['.eee.', 'eeeee', 'esees', 'eeeee', 'eseee', '.eee.'],\n  b1: { a: ['c...c', '.ooo.', 'owowo', '.d.d.'], b: ['.....', 'coooc', 'owowo', 'd...d'] },\n  b2: { a: ['c....c', '.oooo.', 'owoowo', '.oooo.', '.d..d.'], b: ['......', 'cooooc', 'owoowo', '.oooo.', 'd....d'] },\n  b3: { a: ['c.....c', 'c.ooo.c', '.ooooo.', 'owooowo', '.ooooo.', '.d.d.d.'], b: ['.......', 'c.ooo.c', 'coooooc', 'owooowo', '.ooooo.', 'd.d.d.d'] },\n  a0: { a: ['.y.yy.y.', 'c.OOOO.c', '.OOOOOO.', 'OOwOOwOO', '.OOOOOO.', '..OOOO..', '.d.d.d.d'], b: ['..y..y..', 'ccOOOOcc', '.OOOOOO.', 'OOwOOwOO', '.OOOOOO.', '..OOOO..', 'd.d.d.d.'] },\n  a1: { a: ['cc....cc', 'c.oooo.c', '.oooooo.', 'oowoowoo', '.oooooo.', '..oooo..', '.d.d.d.d'], b: ['........', 'ccoooocc', '.oooooo.', 'oowoowoo', '.oooooo.', '..oooo..', 'd.d.d.d.'] },\n  a2: { a: ['xx....xx', 'x.qqqq.x', '.qqqqqq.', 'qqrqqrqq', '.qqqqqq.', '..dddd..', '.d.d.d.d'], b: ['........', 'xxqqqqxx', '.qqqqqq.', 'qqrqqrqq', '.qqqqqq.', '..dddd..', 'd.d.d.d.'] },\n  ghost: ['.gggg.', 'gwggwg', 'gggggg', 'gggggg', 'g.g.g.'],\n  tomb:  ['.ttt.', 'ttttt', 'tt.tt', 'tt.tt', 'ttttt', 'ttttt'],\n  poop:  ['.m.', 'mmm'],\n  food:  ['.yy.', 'mmmm', '.ee.'],\n  heart: ['r.r', 'rrr', '.r.']\n};\n\nfunction pickFrame(pair) { return (Math.floor(Date.now() / 1200) % 2 === 0) ? pair.a : pair.b; }\n\nconst GLY = [\n  ['.r....r.', '..r..r..', '...rr...', '...rr...', '..r..r..', '.r....r.'],\n  ['........', '..yyyy..', '.yyyyyy.', '.mmmmmm.', '.rrrrrr.', '.eeeeee.'],\n  ['...y....', '..yyy...', 'yyyyyyy.', '.yyyyy..', '..y.y...', '.y...y..'],\n  ['......ee', '.....ee.', '....ee..', '.yee....', 'yyy.....', 'yy......'],\n  ['...rr...', '...rr...', '.rrrrrr.', '.rrrrrr.', '...rr...', '...rr...'],\n  ['..eee...', '.ee.....', '.ee.....', '.ee.....', '.ee.....', '..eee...'],\n  ['......w.', '......w.', '...w..w.', '...w..w.', 'w..w..w.', 'w..w..w.'],\n  ['.y.y.y..', '..yyy...', 'yyyyyyy.', '..yyy...', '.y.y.y..']\n];\nconst MLAB = ['EXIT', 'FEED', 'PLAY', 'CLEAN', 'MED', 'SLEEP', 'STATS'];\n\nconst SND = {\n  1: 'm:d=16,o=5,b=200:g,p,g,p,g',\n  2: 'c:d=32,o=6,b=220:e,g',\n  3: 'e:d=16,o=5,b=140:c,e,g,8c6,16p,8e6',\n  4: 'h:d=16,o=6,b=180:c,e,g,c7',\n  5: 'c:d=32,o=6,b=220:e,g',\n  6: 'x:d=16,o=4,b=200:c',\n  7: 'c:d=32,o=6,b=220:e,g',\n  8: 'w:d=16,o=6,b=180:c,e,g,8c7',\n  9: 'd:d=4,o=4,b=70:8e,8d,c'\n};\n\nfunction clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }\n\nfunction freshState(gen) {\n  return {\n    st: 0, ev: 0, va: 1,\n    h: 10000, ha: 10000, en: 10000, cl: 10000, hp: 10000,\n    sl: 0, slo: 0, sk: 0,\n    age: 0, warm: 0, cs: 0, gen: gen || 1,\n    pp: 0, pt: 0, cf: 0,\n    ui: 0, mi: 0, dwell: 0, rp: 0,\n    ev_k: 0, ev_t0: 0, stat_open: 0,\n    lastNotify: 0, decAcc: 0, aslast: null,\n    last_ts: Math.floor(Date.now() / 1000)\n  };\n}\n\nfunction decode(rows) {\n  const out = [];\n  for (let y = 0; y < rows.length; y++) {\n    const row = rows[y];\n    for (let x = 0; x < row.length; x++) {\n      const c = CM[row[x]];\n      if (c) out.push([x, y, c]);\n    }\n  }\n  return out;\n}\n\nfunction blit(draw, spriteRows, ox, oy, overrideColor) {\n  for (const [x, y, c] of decode(spriteRows)) {\n    draw.push(['pixel', ox + x, oy + y, overrideColor || c]);\n  }\n}\n\nfunction blitBlink(draw, spriteRows, ox, oy, skinColor, blinking) {\n  for (let y = 0; y < spriteRows.length; y++) {\n    const row = spriteRows[y];\n    for (let x = 0; x < row.length; x++) {\n      const ch = row[x];\n      let color = CM[ch];\n      if (!color) continue;\n      if (blinking && ch === 'w') color = skinColor;\n      draw.push(['pixel', ox + x, oy + y, color]);\n    }\n  }\n}\n\nconst STARS = [[3, 0], [8, 1], [13, 0], [1, 2], [15, 2]];\n\nfunction applyCatchup(s, dtSec) {\n  const el = Math.min(dtSec, 43200);\n  s.age += el;\n  if (s.st === 1) {\n    const d = el * 3 / 10;\n    s.h = clamp(s.h - d * 7 / 10, 0, 10000);\n    s.ha = clamp(s.ha - d / 2, 0, 10000);\n    s.en = clamp(s.en + d / 4, 0, 10000);\n    s.cl = clamp(s.cl - d / 5, 0, 10000);\n    if (s.pt > 0) {\n      s.pt -= d;\n      if (s.pt <= 0) { s.pt = 0; s.pp = Math.min(s.pp + 1, 3); s.cl = clamp(s.cl - 1500, 0, 10000); }\n    }\n  }\n}\n\nfunction decayStep(s) {\n  if (s.st !== 1) return;\n  const sl = s.sl === 1;\n  const xtra = (s.pp >= 2 || s.sk === 1) ? 5 : 0;\n  s.h  = clamp(s.h  - (sl ? 3 : 7), 0, 10000);\n  s.ha = clamp(s.ha - (sl ? 0 : 5) - xtra, 0, 10000);\n  s.en = clamp(s.en + (sl ? 12 : -4), 0, 10000);\n  s.cl = clamp(s.cl - 2 - 4 * s.pp, 0, 10000);\n\n  const vals = [s.h, s.ha, s.en, s.cl];\n  let cf = s.cf;\n  for (let i = 0; i < 4; i++) {\n    const bit = 1 << i;\n    if (vals[i] === 0) {\n      if ((cf & bit) === 0) { s.cs -= 25; cf ^= bit; }\n    } else if (vals[i] > 3000 && (cf & bit) !== 0) {\n      cf ^= bit;\n    }\n  }\n  s.cf = cf;\n\n  if (s.sk === 0 && !sl) {\n    if ((s.h < 2000 || s.ha < 2000 || s.cl < 2000 || s.pp >= 2) && Math.random() < 0.02) {\n      s.sk = 1; s.cs -= 25;\n    }\n  }\n\n  let dmg = 0;\n  if (s.h === 0) dmg += 12;\n  if (s.cl === 0) dmg += 6;\n  if (s.sk === 1) dmg += 8;\n  if (dmg > 0) s.hp = clamp(s.hp - dmg, 0, 10000);\n  else s.hp = clamp(s.hp + 5, 0, 10000);\n\n  if (s.hp <= 0) {\n    s.st = 2; s.ev_k = 9; s.ev_t0 = Date.now(); s.ui = 0;\n  }\n}\n\nfunction evolve(s, stage) {\n  s.ev = stage;\n  if (stage === 1) { s.st = 1; s.ev_k = 4; }\n  else {\n    if (stage === 4) {\n      s.va = s.cs >= 200 ? 0 : (s.cs <= -100 ? 2 : 1);\n    }\n    s.ev_k = 3;\n  }\n  s.ev_t0 = Date.now();\n}\n\nfunction checkEvolution(s) {\n  if (s.st !== 1) return;\n  if (s.ev === 1 && s.age >= 43200) evolve(s, 2);\n  else if (s.ev === 2 && s.age >= 129600) evolve(s, 3);\n  else if (s.ev === 3 && s.age >= 259200) evolve(s, 4);\n}\n\nfunction advanceTime(s) {\n  const now = Math.floor(Date.now() / 1000);\n  const dt = Math.max(0, now - (s.last_ts || now));\n  s.last_ts = now;\n\n  if (s.st === 0) {\n    s.age += dt;\n    if (s.age + s.warm >= 1800) evolve(s, 1);\n    return;\n  }\n  if (s.st !== 1) return;\n\n  if (dt > 60) { applyCatchup(s, dt); checkEvolution(s); return; }\n\n  s.age += dt;\n\n  const hr = new Date().getHours();\n  const auto = hr >= 22 || hr < 8;\n  if (s.aslast === null || s.aslast === undefined) {\n    s.aslast = auto;\n  } else if (auto !== s.aslast) {\n    s.aslast = auto;\n    s.slo = 0;\n  }\n  if (s.slo === 0) s.sl = auto ? 1 : 0;\n\n  if (s.pt > 0 && s.sl !== 1) {\n    s.pt -= dt;\n    if (s.pt <= 0) { s.pt = 0; s.pp = Math.min(s.pp + 1, 3); s.cl = clamp(s.cl - 1500, 0, 10000); }\n  }\n\n  s.decAcc = (s.decAcc || 0) + dt;\n  const steps = Math.min(360, Math.floor(s.decAcc / 10));\n  s.decAcc -= steps * 10;\n  for (let i = 0; i < steps; i++) decayStep(s);\n\n  checkEvolution(s);\n}\n\nfunction checkDwell(s) {\n  const now = Date.now();\n  if (s.ui === 1 && now - s.dwell >= 2000) {\n    action(s, s.mi);\n  } else if (s.ui === 5) {\n    if (s.rp === 0 && now - s.dwell >= 6000) {\n      s.ui = 0;\n    } else if (s.rp === 1 && now - s.dwell >= 3000) {\n      const gen = s.gen;\n      Object.assign(s, freshState(gen + 1));\n      s.ev_k = 7; s.ev_t0 = now;\n    }\n  } else if (s.ui === 3 && now - s.stat_open >= 8000) {\n    s.ui = 0;\n  }\n}\n\nfunction action(s, id) {\n  s.ui = 0;\n  if (id === 1) {\n    if (s.sl === 1 || s.st !== 1) { s.ev_k = 6; }\n    else {\n      if (s.h > 9000) s.ha = clamp(s.ha - 500, 0, 10000);\n      else if (s.h < 7000) s.cs += 10;\n      s.h = clamp(s.h + 4000, 0, 10000);\n      s.pt = 2700 + Math.floor(Math.random() * 2700);\n      s.ev_k = 1;\n    }\n  } else if (id === 2) {\n    if (s.sl === 1 || s.st !== 1 || s.en < 1500) { s.ev_k = 6; }\n    else {\n      s.ha = clamp(s.ha + 1500, 0, 10000);\n      s.en = clamp(s.en - 800, 0, 10000);\n      s.cs += 5;\n      s.ev_k = 8;\n    }\n  } else if (id === 3) {\n    if (s.sl === 1) { s.ev_k = 6; }\n    else {\n      if (s.pp > 0) s.cs += 10;\n      s.pp = 0; s.cl = 10000; s.ev_k = 2;\n    }\n  } else if (id === 4) {\n    if (s.sk === 1) { s.sk = 0; s.cs += 10; s.ha = clamp(s.ha - 500, 0, 10000); s.ev_k = 5; }\n    else { s.ev_k = 6; }\n  } else if (id === 5) {\n    if (s.sl === 1) { s.slo = 1; s.sl = 0; } else { s.slo = 2; s.sl = 1; }\n  } else if (id === 6) {\n    s.ui = 3; s.stat_open = Date.now();\n  }\n  s.ev_t0 = Date.now();\n}\n\nfunction onButton(s, btn) {\n  const now = Date.now();\n  if (btn !== 'select') {\n    if (s.ui !== 0) s.ui = 0;\n    return;\n  }\n  if (s.st === 0) {\n    s.warm = Math.min(s.warm + 60, 900);\n    s.ev_k = 7; s.ev_t0 = now;\n  } else if (s.st === 2) {\n    if (s.ui === 5) {\n      if (s.rp === 0) { s.rp = 1; s.dwell = now; }\n      else { s.ui = 0; }\n    } else { s.ui = 5; s.rp = 0; s.dwell = now; }\n  } else if (s.ui === 0) {\n    s.ui = 1; s.mi = 0; s.dwell = now;\n  } else if (s.ui === 1) {\n    s.mi = (s.mi + 1) % 7;\n    s.dwell = now;\n  } else if (s.ui === 3) {\n    s.ui = 0;\n  }\n}\n\nfunction renderPayload(s) {\n  if (s.ui === 3) {\n    const a = s.age;\n    const days = Math.floor(a / 86400), hrs = Math.floor((a % 86400) / 3600);\n    return {\n      text: `${PET_NAME}  AGE ${days}d${hrs}h  GEN ${s.gen}  CARE ${s.cs}  HP ${Math.floor(s.hp / 100)}%`,\n      textColor: '#F0E6D8', scroll: true\n    };\n  }\n\n  const draw = [['rectFill', 0, 0, 32, 8, '#000000']];\n\n  if (s.ui === 1) {\n    let lab = MLAB[s.mi], glyph = GLY[s.mi];\n    if (s.mi === 5 && s.sl === 1) { lab = 'WAKE'; glyph = GLY[7]; }\n    blit(draw, glyph, 0, 1);\n    draw.push(['text', 11, 1, lab, '#F0E6D8']);\n    draw.push(['rectFill', 0, 7, 32, 1, '#1A1A1A']);\n    const fw = clamp(Math.floor((Date.now() - s.dwell) * 32 / 2000), 0, 32);\n    if (fw > 0) draw.push(['rectFill', 0, 7, fw, 1, '#D97757']);\n    return { draw };\n  }\n\n  if (s.ui === 5) {\n    draw.push(['rectFill', 0, 7, 32, 1, '#1A1A1A']);\n    if (s.rp === 0) {\n      draw.push(['text', 7, 1, 'NEW EGG?', '#8A8178']);\n    } else {\n      draw.push(['text', 7, 1, 'NEW EGG?', '#F0E6D8']);\n      const fw = clamp(Math.floor((Date.now() - s.dwell) * 32 / 3000), 0, 32);\n      if (fw > 0) draw.push(['rectFill', 0, 7, fw, 1, '#D97757']);\n    }\n    return { draw };\n  }\n\n  if (s.st === 2) {\n    draw.push(['line', 0, 7, 16, 7, '#1E2430']);\n    blit(draw, SPR.tomb, 4, 1);\n    blit(draw, SPR.ghost, 11, (Math.floor(Date.now() / 800) % 2 === 0) ? 1 : 2);\n    return { draw };\n  }\n\n  const hr = new Date().getHours();\n  const night = s.sl === 1 || hr >= 20 || hr < 6;\n  draw.push(['line', 0, 7, 16, 7, night ? '#14203A' : '#1E4D1E']);\n  draw.push(['rectFill', 1, 0, 2, 2, night ? '#8C8655' : '#9A8430']);\n\n  if (night) {\n    const off = Math.floor(Date.now() / 400) % STARS.length;\n    STARS.forEach(([sx, sy], i) => {\n      if (i !== off) draw.push(['pixel', sx, sy, '#3A3A3A']);\n    });\n  }\n\n  const blinking = (Date.now() % 4000) < 150;\n\n  if (s.st === 0) {\n    const wob = Math.floor(Date.now() / 700) % 2;\n    blit(draw, SPR.egg, 5 + wob, 1);\n    const pr = clamp(Math.floor((s.age + s.warm) * 17 / 1800), 0, 17);\n    if (pr > 0) draw.push(['rectFill', 0, 7, pr, 1, '#D97757']);\n  } else {\n    let pair, w, h, skin;\n    if (s.ev === 1) { pair = SPR.b1; w = 5; h = 4; skin = CM.o; }\n    else if (s.ev === 2) { pair = SPR.b2; w = 6; h = 5; skin = CM.o; }\n    else if (s.ev === 3) { pair = SPR.b3; w = 7; h = 6; skin = CM.o; }\n    else if (s.va === 0) { pair = SPR.a0; w = 8; h = 7; skin = CM.O; }\n    else if (s.va === 2) { pair = SPR.a2; w = 8; h = 7; skin = CM.q; }\n    else { pair = SPR.a1; w = 8; h = 7; skin = CM.o; }\n    const asleep = s.sl === 1;\n    const frame = asleep ? pair.a : pickFrame(pair);\n    const ox = Math.floor((17 - w) / 2), oy = 7 - h;\n\n    for (let i = 0; i < s.pp; i++) blit(draw, SPR.poop, 12 + i, 5 - i);\n    blitBlink(draw, frame, ox, oy, skin, asleep || blinking);\n    if (asleep) draw.push(['text', ox + w + 1, 3, 'z', '#5C7FBF']);\n    if (s.sk === 1) { draw.push(['pixel', 0, 0, '#35C24A']); draw.push(['pixel', 0, 1, '#35C24A']); }\n\n    const ed = Date.now() - s.ev_t0;\n    if ((s.ev_k === 1 || s.ev_k === 8) && ed < 900) blit(draw, SPR.heart, 7, 0);\n    else if (s.ev_k === 2 && ed < 1200) {\n      const bx = Math.floor(ed * 17 / 1200);\n      draw.push(['line', bx, 2, bx, 6, '#F2E3C8']);\n    } else if (s.ev_k === 5 && ed < 700) {\n      draw.push(['line', 7, 1, 9, 1, '#35C24A']); draw.push(['line', 8, 0, 8, 2, '#35C24A']);\n    } else if (s.ev_k === 6 && ed < 700) {\n      draw.push(['line', 6, 1, 10, 5, '#E05540']); draw.push(['line', 10, 1, 6, 5, '#E05540']);\n    }\n  }\n\n  const barBlink = (Math.floor(Date.now() / 300) % 2) === 0;\n  const bars = [s.h, s.ha, s.en, s.cl];\n  const bcol = ['#D97757', '#E8C33F', '#3FB4E8', '#4FC96F'];\n  for (let i = 0; i < 4; i++) {\n    draw.push(['rectFill', 18, i * 2, 14, 1, '#202020']);\n    let w = Math.floor(bars[i] * 14 / 10000);\n    if (w < 1 && bars[i] > 0) w = 1;\n    if (w > 0 && (bars[i] >= 2500 || barBlink)) {\n      draw.push(['rectFill', 18, i * 2, w, 1, bcol[i]]);\n    }\n  }\n  return { draw };\n}\n\nfunction checkNotify(s) {\n  if (s.st !== 1 || s.sl === 1) return null;\n  const now = Date.now();\n  if (now - (s.lastNotify || 0) < 1800000) return null;\n  if (s.h < 1500 || s.ha < 1500 || s.cl < 1500 || s.sk === 1) {\n    s.lastNotify = now;\n    return { text: `${PET_NAME} needs you!`, textColor: '#D97757' };\n  }\n  return null;\n}\n\n// ---- run once per incoming item -----------------------------------------\nconst staticData = $getWorkflowStaticData('global');\nif (!staticData.clawd) staticData.clawd = freshState(1);\nconst s = staticData.clawd;\n\nconst input = $input.item.json;\nconst prevEvK = s.ev_k;\nconst prevSt = s.st;\nconst prevEv = s.ev;\nconst prevSk = s.sk;\n\nadvanceTime(s);\ncheckDwell(s);\nif (input.event === 'button') onButton(s, input.btn);\nelse if (input.event === 'action') action(s, input.id);\n\nconst payload = renderPayload(s);\nconst sound = (s.ev_k !== prevEvK && s.ev_k > 0) ? SND[s.ev_k] : null;\nconst notify = checkNotify(s);\n\nconst diedNow = prevSt !== 2 && s.st === 2;\nconst evolvedNow = prevEv !== s.ev;\nconst gotSickNow = prevSk !== 1 && s.sk === 1;\nconst switchTo = input.event !== 'tick' || diedNow || evolvedNow || gotSickNow;\n\nreturn [{ json: { payload, sound, notify, switchTo } }];\n"
      },
      "id": "engine",
      "name": "Clawd Engine",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        80,
        130
      ],
      "notes": "PET_NAME is set at the top of this node's code. Edit it there."
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "http://<AWTRIX_IP>/api/v1/apps/pushed/clawd",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.payload) }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "options": {}
      },
      "id": "http-draw",
      "name": "Push Draw",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        360,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "leftValue": "={{ $json.sound !== null && $json.sound !== undefined }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "if-sound",
      "name": "Has Sound",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        360,
        160
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://<AWTRIX_IP>/api/v1/sounds/play",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ rtttl: $json.sound }) }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "options": {}
      },
      "id": "http-sound",
      "name": "Play Sound",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        620,
        160
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "leftValue": "={{ $json.notify !== null && $json.notify !== undefined }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "if-notify",
      "name": "Has Notify",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        360,
        320
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://<AWTRIX_IP>/api/v1/notifications",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.notify) }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "options": {}
      },
      "id": "http-notify",
      "name": "Send Notify",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        620,
        320
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "leftValue": "={{ $json.switchTo === true }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "if-switchto",
      "name": "Has SwitchTo",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        360,
        480
      ]
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "http://<AWTRIX_IP>/api/v1/apps/active",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ name: 'clawd', fast: true }) }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "options": {}
      },
      "id": "http-switchto",
      "name": "Switch To Clawd",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        620,
        480
      ]
    }
  ],
  "connections": {
    "Tick every 2s": {
      "main": [
        [
          {
            "node": "Normalize Tick",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Tick": {
      "main": [
        [
          {
            "node": "Clawd Engine",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Button Events (MQTT)": {
      "main": [
        [
          {
            "node": "Normalize Button",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Button": {
      "main": [
        [
          {
            "node": "Clawd Engine",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HA Action (Webhook)": {
      "main": [
        [
          {
            "node": "Normalize Action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Action": {
      "main": [
        [
          {
            "node": "Clawd Engine",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clawd Engine": {
      "main": [
        [
          {
            "node": "Push Draw",
            "type": "main",
            "index": 0
          },
          {
            "node": "Has Sound",
            "type": "main",
            "index": 0
          },
          {
            "node": "Has Notify",
            "type": "main",
            "index": 0
          },
          {
            "node": "Has SwitchTo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Sound": {
      "main": [
        [
          {
            "node": "Play Sound",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Has Notify": {
      "main": [
        [
          {
            "node": "Send Notify",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Has SwitchTo": {
      "main": [
        [
          {
            "node": "Switch To Clawd",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  }
}