{"id":39913224,"url":"https://github.com/openscript/pixelwidget","last_synced_at":"2026-01-18T16:34:12.069Z","repository":{"id":332006129,"uuid":"1127136045","full_name":"openscript/pixelwidget","owner":"openscript","description":"MQTT (via TLS) Pixel Control for Ulanzi TC001 with ESPHome","archived":false,"fork":false,"pushed_at":"2026-01-12T01:31:21.000Z","size":2146,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-12T04:55:20.006Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/openscript.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-03T08:54:16.000Z","updated_at":"2026-01-12T01:31:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/openscript/pixelwidget","commit_stats":null,"previous_names":["openscript/pixelwidget"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/openscript/pixelwidget","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openscript%2Fpixelwidget","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openscript%2Fpixelwidget/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openscript%2Fpixelwidget/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openscript%2Fpixelwidget/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/openscript","download_url":"https://codeload.github.com/openscript/pixelwidget/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openscript%2Fpixelwidget/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28541689,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T14:59:57.589Z","status":"ssl_error","status_checked_at":"2026-01-18T14:59:46.540Z","response_time":98,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-18T16:34:11.958Z","updated_at":"2026-01-18T16:34:12.053Z","avatar_url":"https://github.com/openscript.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# PixelWidget - MQTT Pixel Control for Ulanzi TC001\n\nControl individual pixels on a Ulanzi TC001 (32x8 LED matrix) via MQTT (with TLS).\n\n## Hardware\n\n- **Device**: Ulanzi TC001 (ESP32 + 32x8 WS2812 LED matrix)\n- **Matrix**: 256 LEDs in zigzag arrangement\n- **Coordinates**: X: 0-31 (left to right), Y: 0-7 (top to bottom)\n\n## Setup\n\n1. Fill in your WiFi and MQTT credentials in `secrets.yaml`\n2. Flash with: `esphome run pixelwidget.yaml`\n\n## MQTT Topics\n\n### Control Commands\n\n| Topic | Payload Format | Description |\n|-------|----------------|-------------|\n| `pixelwidget/pixel/set` | `x,y,r,g,b` | Set single pixel color |\n| `pixelwidget/pixel/fill` | `r,g,b` | Fill entire display with color |\n| `pixelwidget/pixel/clear` | (any) | Clear display (all black) |\n| `pixelwidget/pixel/line` | `x1,y1,x2,y2,r,g,b` | Draw a line |\n| `pixelwidget/pixel/rect` | `x,y,w,h,r,g,b` | Draw rectangle outline |\n| `pixelwidget/pixel/fillrect` | `x,y,w,h,r,g,b` | Draw filled rectangle |\n| `pixelwidget/pixel/image` | Base64 RGB data | Set entire display (see below) |\n| `pixelwidget/brightness` | `0-255` | Set display brightness |\n\n### Status Topics\n\n| Topic | Description |\n|-------|-------------|\n| `pixelwidget/status` | Online/offline status (birth/will) |\n| `pixelwidget/button` | Button press events: `left`, `middle`, `right` |\n\n### Image Request Topic\n\n| Topic | Payload | Trigger | Description |\n|-------|---------|---------|-------------|\n| `pixelwidget/image/request` | `1` | Automatic | Sent on MQTT connection (after 1s delay) and on middle button press to request the current display image from the backend |\n\n## Image Synchronization\n\nThe device automatically requests the current display image in two scenarios:\n\n1. **On MQTT connection** - After successfully connecting to the broker, the device sends an image request after a 1-second delay to ensure stability\n2. **On middle button press** - When the middle button is pressed, it wakes the display and requests the current image\n\nYour backend service should:\n- Subscribe to `pixelwidget/image/request`\n- When received, fetch or generate the current display image as a 32x8 RGB image (see [Sending a Full Image](#sending-a-full-image) section)\n- Publish the base64-encoded image to `pixelwidget/pixel/image`\n\nThis enables the display to stay synchronized with your application state across reboots and wake events.\n\n## Examples\n\n### Using mosquitto_pub (CLI)\n\n```bash\n# Set pixel at (0,0) to red\nmosquitto_pub -h \u003cbroker\u003e -p 8883 -u \u003cuser\u003e -P \u003cpass\u003e --cafile ca.crt \\\n  -t \"pixelwidget/pixel/set\" -m \"0,0,255,0,0\"\n\n# Fill display with blue\nmosquitto_pub -h \u003cbroker\u003e -p 8883 -u \u003cuser\u003e -P \u003cpass\u003e --cafile ca.crt \\\n  -t \"pixelwidget/pixel/fill\" -m \"0,0,255\"\n\n# Clear display\nmosquitto_pub -h \u003cbroker\u003e -p 8883 -u \u003cuser\u003e -P \u003cpass\u003e --cafile ca.crt \\\n  -t \"pixelwidget/pixel/clear\" -m \"\"\n\n# Draw red line from (0,0) to (31,7)\nmosquitto_pub -h \u003cbroker\u003e -p 8883 -u \u003cuser\u003e -P \u003cpass\u003e --cafile ca.crt \\\n  -t \"pixelwidget/pixel/line\" -m \"0,0,31,7,255,0,0\"\n\n# Draw green rectangle at (5,2) with size 10x4\nmosquitto_pub -h \u003cbroker\u003e -p 8883 -u \u003cuser\u003e -P \u003cpass\u003e --cafile ca.crt \\\n  -t \"pixelwidget/pixel/rect\" -m \"5,2,10,4,0,255,0\"\n\n# Set brightness to 50%\nmosquitto_pub -h \u003cbroker\u003e -p 8883 -u \u003cuser\u003e -P \u003cpass\u003e --cafile ca.crt \\\n  -t \"pixelwidget/brightness\" -m \"128\"\n```\n\n### Sending a Full Image\n\nThe `pixelwidget/pixel/image` topic accepts a base64-encoded RGB image. The image must be exactly 32x8 pixels (256 pixels × 3 bytes = 768 bytes of RGB data).\n\n**Image format:**\n- Raw RGB bytes, row by row (top to bottom, left to right)\n- Each pixel is 3 bytes: R, G, B (0-255 each)\n- Total: 768 bytes → Base64 encoded = 1024 characters\n\n**Python example:**\n\n```python\nimport base64\nimport ssl\nimport paho.mqtt.client as mqtt\n\n# Create a 32x8 RGB image (red gradient)\nrgb_data = bytearray()\nfor y in range(8):\n    for x in range(32):\n        rgb_data.extend([x * 8, 0, 0])  # Red gradient\n\n# Encode to base64\nimage_b64 = base64.b64encode(rgb_data).decode('ascii')\nprint(image_b64)\n\n# Send via MQTT\nclient = mqtt.Client()\nclient.username_pw_set(\"display\", \"your_password\")\nclient.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS)\nclient.connect(\"your-cluster.s1.eu.hivemq.cloud\", 8883, 60)\nclient.publish(\"pixelwidget/pixel/image\", image_b64)\nclient.disconnect()\n```\n\n**Node.js example:**\n\n```javascript\nconst mqtt = require('mqtt');\n\n// Create a 32x8 RGB image (blue gradient)\nconst rgbData = Buffer.alloc(768);\nfor (let y = 0; y \u003c 8; y++) {\n  for (let x = 0; x \u003c 32; x++) {\n    const idx = (y * 32 + x) * 3;\n    rgbData[idx] = 0;           // R\n    rgbData[idx + 1] = 0;       // G\n    rgbData[idx + 2] = x * 8;   // B gradient\n  }\n}\n\nconst client = mqtt.connect('mqtts://your-cluster.s1.eu.hivemq.cloud:8883', {\n  username: 'display',\n  password: 'your_password'\n});\n\nclient.on('connect', () =\u003e {\n  client.publish('pixelwidget/pixel/image', rgbData.toString('base64'));\n  client.end();\n});\n```\n\n### Using Python (paho-mqtt)\n\n```python\nimport ssl\nimport paho.mqtt.client as mqtt\n\n# Connect to HiveMQ Cloud\nclient = mqtt.Client()\nclient.username_pw_set(\"display\", \"your_password\")\nclient.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS)\nclient.connect(\"your-cluster.s1.eu.hivemq.cloud\", 8883, 60)\n\n# Set pixel at (10, 3) to green\nclient.publish(\"pixelwidget/pixel/set\", \"10,3,0,255,0\")\n\n# Draw a red diagonal line\nclient.publish(\"pixelwidget/pixel/line\", \"0,0,31,7,255,0,0\")\n\n# Fill with purple\nclient.publish(\"pixelwidget/pixel/fill\", \"128,0,128\")\n\nclient.disconnect()\n```\n\n### Using Node.js\n\n```javascript\nconst mqtt = require('mqtt');\n\nconst client = mqtt.connect('mqtts://your-cluster.s1.eu.hivemq.cloud:8883', {\n  username: 'display',\n  password: 'your_password'\n});\n\nclient.on('connect', () =\u003e {\n  // Set multiple pixels for a pattern\n  for (let x = 0; x \u003c 32; x++) {\n    const color = x % 2 === 0 ? '255,0,0' : '0,255,0';\n    client.publish('pixelwidget/pixel/set', `${x},0,${color}`);\n  }\n});\n```\n\n## Coordinate System\n\n```\n      X →\n    0 1 2 3 ... 31\n  ┌─────────────────┐\n0 │ ● ● ● ● ... ● │  Y\n1 │ ● ● ● ● ... ● │  ↓\n2 │ ● ● ● ● ... ● │\n3 │ ● ● ● ● ... ● │\n4 │ ● ● ● ● ... ● │\n5 │ ● ● ● ● ... ● │\n6 │ ● ● ● ● ... ● │\n7 │ ● ● ● ● ... ● │\n  └─────────────────┘\n```\n\n- Origin (0,0) is top-left\n- X increases to the right (0-31)\n- Y increases downward (0-7)\n- Colors are RGB values 0-255\n\n## Files\n\n- `pixelwidget.yaml` - Main ESPHome configuration with MQTT pixel control\n- `secrets.yaml` - WiFi and MQTT credentials (do not commit)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenscript%2Fpixelwidget","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenscript%2Fpixelwidget","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenscript%2Fpixelwidget/lists"}