This flow was submitted before accounts existed, so nobody owns it yet. If you submitted it, open the edit link you were given — it still works — and you can attach the flow to an account from there. There is no deadline, and attaching it retires the link for good.
Pomodoro Clock
Pomodoro timer for AWTRIX clock. Displays work and break steps in a custom app with a progress bar. Use the middle button to pause/resume, left/right buttons to navigate steps.
| System | Node-RED |
|---|---|
| Firmware | AWTRIX 3 |
| Topic | Miscellaneous |
| Built by | chatteleyn |
| File | DY93AWpdoQ0y.json · 26.3 KB |
| Icons | — |
| Published | 23 Apr 2026 |
| Downloads | 31 |
A Node-RED flow that implements a Pomodoro timer as a custom AWTRIX clock app, controlled entirely via MQTT. The timer cycles through configurable work and break steps, displayed as a compact step indicator on the bottom row of the clock face alongside a countdown and progress bar. The middle (select) button pauses and resumes the timer, while the left and right buttons let you skip to the previous or next step. Sound notifications are played at each step transition.
DY93AWpdoQ0y.json
[
{
"id": "pomo-tab",
"type": "tab",
"label": "Awtrix Pomodoro",
"disabled": false,
"info": ""
},
{
"id": "bd369d53d63f924b",
"type": "group",
"z": "pomo-tab",
"style": {
"stroke": "#999999",
"stroke-opacity": "1",
"fill": "none",
"fill-opacity": "1",
"label": true,
"label-position": "nw",
"color": "#a4a4a4"
},
"nodes": [
"36133fe5cb5f20fd",
"b10bbeea2dccf905",
"b85768a6f5067684",
"218d223e91e922d0",
"a4e4249b7d1445ef",
"6cacbec04e65d8a3",
"5347854fb985cebb",
"cc435e2a87f7705f",
"8cc2e5de1d9d4536",
"891fce0518717790",
"c3ee30bd69339174",
"0748a0fe76b63a6a",
"f9c0e370a30f2053",
"3c4aebfb42d3c2eb",
"ec2da8818fe78884",
"b9070a7afc847e27",
"f40deec908cb16a0",
"4fc2fe6bebd30a58",
"5c1d6766a0f59e79",
"df79fbade1457b55",
"c56b360c5b17d663",
"6cd8ec8057c50d7f",
"ab3fefda96548f9f",
"96e3ab1d51fbb5ca",
"6038125ceeea4fae",
"526f8805b3044b18",
"ce7f08cb739a28db",
"b1788a3fb52df1f2",
"cdecbaf49d3e604e",
"a5cfbf85a165e5f6",
"dac9640b05a6c24d",
"a201e9a6dc6b01c9",
"16b167d58df7f922",
"ebcf2646aaca8d0f",
"fdf69bd9caea5ded"
],
"x": 14,
"y": 19,
"w": 1652,
"h": 822
},
{
"id": "36133fe5cb5f20fd",
"type": "inject",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Init",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "init",
"payload": "",
"payloadType": "date",
"x": 650,
"y": 160,
"wires": [
[
"b10bbeea2dccf905"
]
]
},
{
"id": "b10bbeea2dccf905",
"type": "change",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "",
"rules": [
{
"t": "set",
"p": "paused",
"pt": "flow",
"to": "true",
"tot": "bool"
},
{
"t": "set",
"p": "step",
"pt": "flow",
"to": "-1",
"tot": "num"
},
{
"t": "set",
"p": "icon",
"pt": "flow",
"to": "68088",
"tot": "num"
},
{
"t": "set",
"p": "prefix",
"pt": "flow",
"to": "awtrix",
"tot": "str"
},
{
"t": "set",
"p": "appname",
"pt": "flow",
"to": "pomodoro",
"tot": "str"
},
{
"t": "set",
"p": "workduration",
"pt": "flow",
"to": "25 * 60 * 1000",
"tot": "jsonata"
},
{
"t": "set",
"p": "breakduration",
"pt": "flow",
"to": "5 * 60 * 1000",
"tot": "jsonata"
},
{
"t": "set",
"p": "longbreakduration",
"pt": "flow",
"to": "20 * 60 * 1000",
"tot": "jsonata"
},
{
"t": "set",
"p": "looplength",
"pt": "flow",
"to": "4",
"tot": "num"
},
{
"t": "set",
"p": "currentapp",
"pt": "flow",
"to": "appname",
"tot": "flow"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 840,
"y": 160,
"wires": [
[
"c3ee30bd69339174"
]
]
},
{
"id": "b85768a6f5067684",
"type": "mqtt in",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "currentApp",
"topic": "awtrix/stats/currentApp",
"qos": "2",
"datatype": "auto-detect",
"broker": "pomo-mqtt-broker",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 120,
"y": 680,
"wires": [
[
"218d223e91e922d0"
]
]
},
{
"id": "218d223e91e922d0",
"type": "change",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "",
"rules": [
{
"t": "set",
"p": "currentapp",
"pt": "flow",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 320,
"y": 680,
"wires": [
[]
]
},
{
"id": "a4e4249b7d1445ef",
"type": "mqtt out",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Set Time",
"topic": "",
"qos": "0",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "pomo-mqtt-broker",
"x": 1380,
"y": 260,
"wires": []
},
{
"id": "6cacbec04e65d8a3",
"type": "function",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "msg",
"func": "let date = new Date(flow.get(\"countdown\"))\n\nlet minutes = date.getUTCMinutes()\nlet seconds = date.getUTCSeconds()\n\nconst WIDTH = 24;\nconst START = 8;\n\nconst looplength = flow.get(\"looplength\");\nconst step = flow.get(\"step\");\nconst totalLength = looplength * 5 - 1;\n\nfunction stepPos(i) {\n return i % 2 === 0 ? (i / 2) * 5 : Math.floor(i / 2) * 5 + 3;\n}\n\nlet scrollOffset = 0;\nif (totalLength > WIDTH) {\n const curPos = stepPos(step);\n scrollOffset = curPos - Math.floor(WIDTH / 2);\n scrollOffset = Math.max(0, Math.min(scrollOffset, totalLength - WIDTH));\n}\n\nconst baseX = totalLength <= WIDTH ? Math.round(START + (WIDTH - totalLength) / 2) : START;\n\nlet draw = [];\nfor (let i = 0; i < looplength * 2; i++) {\n const relX = baseX + stepPos(i) - scrollOffset;\n\n if (i % 2 === 0) {\n const x1 = relX, x2 = relX + 1;\n if (x2 < START || x1 >= START + WIDTH)\n continue;\n draw.push({ \"dl\": [Math.max(x1, START), 7, Math.min(x2, START + WIDTH - 1), 7, step === i ? \"#FFFFFF\" : \"#6C767B\"] });\n } else {\n if (relX < START || relX >= START + WIDTH)\n continue;\n let color;\n if (i === looplength * 2 - 1)\n color = step === i ? \"#FFD600\" : \"#FFFF8D\";\n else\n color = step === i ? \"#FFFFFF\" : \"#6C767B\";\n draw.push({ \"dp\": [relX, 7, color] });\n }\n}\n\nmsg.payload = {\n \"text\": (minutes < 10 ? \"0\" + minutes : minutes) + \":\" + (seconds < 10 ? \"0\" + seconds : seconds),\n \"icon\": flow.get(\"icon\"),\n \"color\": flow.get(\"step\") % 2 == 0 ? \"#FFFFFF\" : \"#00C853\",\n \"draw\": draw\n}\n\nmsg.topic = flow.get(\"prefix\") + \"/custom/\" + flow.get(\"appname\")\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1230,
"y": 260,
"wires": [
[
"a4e4249b7d1445ef"
]
]
},
{
"id": "5347854fb985cebb",
"type": "inject",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Clock",
"props": [
{
"p": "payload"
}
],
"repeat": "1",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 140,
"y": 240,
"wires": [
[
"cc435e2a87f7705f"
]
]
},
{
"id": "cc435e2a87f7705f",
"type": "switch",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Check app",
"property": "app",
"propertyType": "flow",
"rules": [
{
"t": "eq",
"v": "appname",
"vt": "flow"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 310,
"y": 240,
"wires": [
[
"8cc2e5de1d9d4536"
]
]
},
{
"id": "8cc2e5de1d9d4536",
"type": "switch",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Check paused",
"property": "paused",
"propertyType": "flow",
"rules": [
{
"t": "eq",
"v": "0",
"vt": "num"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 500,
"y": 240,
"wires": [
[
"891fce0518717790"
]
]
},
{
"id": "891fce0518717790",
"type": "function",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Set countdown",
"func": "flow.set(\"countdown\", flow.get(\"countdown\") - (Date.now() - flow.get(\"time\")))\nflow.set(\"time\", Date.now())\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 700,
"y": 240,
"wires": [
[
"0748a0fe76b63a6a"
]
]
},
{
"id": "c3ee30bd69339174",
"type": "function",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Set step",
"func": "let increment = msg.topic.endsWith(\"buttonLeft\") ? -1 : 1\n\nflow.set(\"step\", (flow.get(\"step\") + increment + flow.get(\"looplength\") * 2) % (flow.get(\"looplength\") * 2))\nflow.set(\"paused\", true)\n\nif(flow.get(\"step\") == flow.get(\"looplength\") * 2 - 1)\n flow.set(\"countdown\", flow.get(\"longbreakduration\"))\nelse if(flow.get(\"step\") % 2 == 0)\n flow.set(\"countdown\", flow.get(\"workduration\"))\nelse\n flow.set(\"countdown\", flow.get(\"breakduration\"))\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1080,
"y": 220,
"wires": [
[
"6cacbec04e65d8a3",
"4fc2fe6bebd30a58",
"fdf69bd9caea5ded"
]
]
},
{
"id": "0748a0fe76b63a6a",
"type": "switch",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "",
"property": "countdown",
"propertyType": "flow",
"rules": [
{
"t": "lte",
"v": "0",
"vt": "num"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 880,
"y": 240,
"wires": [
[
"c3ee30bd69339174"
],
[
"6cacbec04e65d8a3"
]
]
},
{
"id": "f9c0e370a30f2053",
"type": "function",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "msg",
"func": "msg.payload = \"RingRing:d=4,o=5,b=100:32b,32d6,32g6,32g6,32g6,8p,32b,32d6,32g6,32g6,32g6\"\n\nmsg.topic = flow.get(\"prefix\") + \"/rtttl\"\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1390,
"y": 180,
"wires": [
[
"3c4aebfb42d3c2eb"
]
]
},
{
"id": "3c4aebfb42d3c2eb",
"type": "mqtt out",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Play sound",
"topic": "",
"qos": "0",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "pomo-mqtt-broker",
"x": 1570,
"y": 180,
"wires": []
},
{
"id": "ec2da8818fe78884",
"type": "inject",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Remove",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 120,
"y": 800,
"wires": [
[
"b9070a7afc847e27"
]
]
},
{
"id": "b9070a7afc847e27",
"type": "function",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "msg",
"func": "msg.payload = {}\n\nmsg.topic = flow.get(\"prefix\") + \"/custom/\" + flow.get(\"appname\")\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 270,
"y": 800,
"wires": [
[
"f40deec908cb16a0"
]
]
},
{
"id": "f40deec908cb16a0",
"type": "mqtt out",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Remove app",
"topic": "",
"qos": "0",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "pomo-mqtt-broker",
"x": 430,
"y": 800,
"wires": []
},
{
"id": "4fc2fe6bebd30a58",
"type": "function",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "msg",
"func": "msg.payload = {\n \"name\": \"pomodoro\"\n}\n\nmsg.topic = flow.get(\"prefix\") + \"/switch\"\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1230,
"y": 220,
"wires": [
[
"ebcf2646aaca8d0f"
]
]
},
{
"id": "5c1d6766a0f59e79",
"type": "mqtt out",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Switch app",
"topic": "",
"qos": "0",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "pomo-mqtt-broker",
"x": 1570,
"y": 220,
"wires": []
},
{
"id": "df79fbade1457b55",
"type": "comment",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "🍅 POMODORO CLOCK",
"info": " - Set your custom parameters here\n - Press the clock \"select button\" to pause/unpause\n - Double press the \"right/left buttons\" to chnage the current step",
"x": 170,
"y": 60,
"wires": []
},
{
"id": "c56b360c5b17d663",
"type": "comment",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Set your custom parameters here",
"info": "Here you can set your custom constants and variables to be used for the clock",
"x": 830,
"y": 120,
"wires": []
},
{
"id": "6cd8ec8057c50d7f",
"type": "mqtt in",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "buttonRight",
"topic": "awtrix/stats/buttonRight",
"qos": "2",
"datatype": "auto-detect",
"broker": "pomo-mqtt-broker",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 130,
"y": 340,
"wires": [
[
"dac9640b05a6c24d",
"a201e9a6dc6b01c9"
]
]
},
{
"id": "ab3fefda96548f9f",
"type": "mqtt in",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "buttonSelect",
"topic": "awtrix/stats/buttonSelect",
"qos": "2",
"datatype": "auto-detect",
"broker": "pomo-mqtt-broker",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 130,
"y": 540,
"wires": [
[
"6038125ceeea4fae",
"ce7f08cb739a28db"
]
]
},
{
"id": "96e3ab1d51fbb5ca",
"type": "switch",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Check current app",
"property": "currentapp",
"propertyType": "flow",
"rules": [
{
"t": "eq",
"v": "appname",
"vt": "flow"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 510,
"y": 540,
"wires": [
[
"526f8805b3044b18"
]
]
},
{
"id": "6038125ceeea4fae",
"type": "switch",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Check button",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "0",
"vt": "num"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 310,
"y": 540,
"wires": [
[
"96e3ab1d51fbb5ca"
]
]
},
{
"id": "526f8805b3044b18",
"type": "function",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Toggle paused",
"func": "flow.set(\"time\", Date.now())\nflow.set(\"paused\", !flow.get(\"paused\"))\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 720,
"y": 540,
"wires": [
[]
]
},
{
"id": "ce7f08cb739a28db",
"type": "change",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "",
"rules": [
{
"t": "set",
"p": "lastbuttonstate",
"pt": "flow",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "lastbutton",
"pt": "flow",
"to": "topic",
"tot": "msg"
},
{
"t": "set",
"p": "lastbuttontime",
"pt": "flow",
"to": "",
"tot": "date"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 340,
"y": 600,
"wires": [
[]
]
},
{
"id": "b1788a3fb52df1f2",
"type": "mqtt in",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "buttonLeft",
"topic": "awtrix/stats/buttonLeft",
"qos": "2",
"datatype": "auto-detect",
"broker": "pomo-mqtt-broker",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 120,
"y": 420,
"wires": [
[
"dac9640b05a6c24d",
"a201e9a6dc6b01c9"
]
]
},
{
"id": "cdecbaf49d3e604e",
"type": "switch",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Check last button time",
"property": "lastbuttontime",
"propertyType": "flow",
"rules": [
{
"t": "gte",
"v": "$millis() - 500",
"vt": "jsonata"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 540,
"y": 360,
"wires": [
[
"16b167d58df7f922"
]
]
},
{
"id": "a5cfbf85a165e5f6",
"type": "change",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "",
"rules": [
{
"t": "set",
"p": "lastbuttontime",
"pt": "flow",
"to": "",
"tot": "date"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 550,
"y": 320,
"wires": [
[]
]
},
{
"id": "dac9640b05a6c24d",
"type": "function",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Check values",
"func": "if (flow.get(\"lastbutton\") == msg.topic && flow.get(\"lastbuttonstate\") == 1 && msg.payload == 0)\n return msg\n// else\n// node.warn(flow.get(\"lastbutton\") + \"&&\"+ flow.get(\"lastbuttonstate\") + \"&&\"+ msg.payload);",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 320,
"y": 360,
"wires": [
[
"cdecbaf49d3e604e",
"a5cfbf85a165e5f6"
]
]
},
{
"id": "a201e9a6dc6b01c9",
"type": "change",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "",
"rules": [
{
"t": "set",
"p": "lastbuttonstate",
"pt": "flow",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "lastbutton",
"pt": "flow",
"to": "topic",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 340,
"y": 400,
"wires": [
[]
]
},
{
"id": "16b167d58df7f922",
"type": "switch",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "Check current app",
"property": "currentapp",
"propertyType": "flow",
"rules": [
{
"t": "eq",
"v": "appname",
"vt": "flow"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 770,
"y": 360,
"wires": [
[
"c3ee30bd69339174"
]
]
},
{
"id": "ebcf2646aaca8d0f",
"type": "delay",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "",
"pauseType": "delay",
"timeout": "500",
"timeoutUnits": "milliseconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"allowrate": false,
"outputs": 1,
"x": 1390,
"y": 220,
"wires": [
[
"5c1d6766a0f59e79"
]
]
},
{
"id": "fdf69bd9caea5ded",
"type": "switch",
"z": "pomo-tab",
"g": "bd369d53d63f924b",
"name": "",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "empty"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 1240,
"y": 180,
"wires": [
[
"f9c0e370a30f2053"
]
]
},
{
"id": "pomo-mqtt-broker",
"type": "mqtt-broker",
"name": "Mosquitto",
"broker": "homeserver",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
More flows for Node-RED or Miscellaneous
Something wrong with this flow? Report it.