Personal digital signage system for Pixoo64 and other LED matrix displays. Displays real-time data like blood sugar (Dexcom CGM), time, weather, and sleep readiness (Oura Ring).
Read the full story: Building a Personal Digital Signage System
┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Lambda │────▶│ WebSocket │────▶│ Relay (CLI) │────▶│ Pixoo64 │
│ (content) │ │ API │ │ ↗ glucagent │ │ (display) │
└─────────────┘ └──────┬──────┘ └──────────────┘ └─────────────┘
│
│ ┌──────────────┐
└────────────▶│ Web Emulator │
│ (browser) │
└──────────────┘
Relay moved. The local Pixoo relay CLI and Lightsail deploy scripts now live in
jwulff/glucagentunderpackages/relay/anddeploy/lightsail/. This repo still hosts the cloud Lambdas, WebSocket API, and web emulator; new Pixoo-side work happens in glucagent.
- Features
- Quick Start (Local Development)
- Fork & Deploy Your Own
- Architecture
- Packages
- API Reference
- Connecting a Pixoo display
- Cost Estimate
- Troubleshooting
- License
- Real-time updates - Content pushes to displays every minute via WebSocket
- Multiple displays - Connect Pixoo64 devices, web browsers, or custom terminals
- Blood sugar tracking - Integrates with Dexcom CGM (requires Dexcom Share account)
- Sleep readiness - Integrates with Oura Ring (optional)
- Web emulator - View your display in any browser
- Serverless - Runs on AWS Lambda, scales to zero when idle
- Low cost - ~$5-15/month for typical usage
Run locally without deploying to AWS:
# Clone the repo
git clone https://github.com/YOUR_USERNAME/signage.git
cd signage
# Install dependencies (requires Node.js 20+ and pnpm 9+)
pnpm install
# Start local development server
pnpm dev:localThis starts:
- Web emulator at http://localhost:5173
- WebSocket server at ws://localhost:8080
On first run, you'll be prompted for Dexcom credentials (optional - skip to use mock data).
Before starting, ensure you have:
| Requirement | Version | Check Command |
|---|---|---|
| Node.js | 20+ | node --version |
| pnpm | 9+ | pnpm --version |
| AWS CLI | 2+ | aws --version |
| GitHub account | - | - |
| AWS account | - | - |
| Domain name | - | For custom URLs (optional but recommended) |
- Fork this repository on GitHub
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/signage.git cd signage - Install dependencies:
pnpm install
- Verify tests pass:
pnpm test pnpm lint
- Go to AWS IAM Console
- Create a new user (e.g.,
signage-deploy) - Attach the
AdministratorAccesspolicy (or create a custom policy - see below) - Create an Access Key and save the credentials
Minimum IAM Policy (instead of AdministratorAccess)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"cloudformation:*",
"lambda:*",
"apigateway:*",
"dynamodb:*",
"s3:*",
"cloudfront:*",
"iam:*",
"events:*",
"secretsmanager:*",
"route53:*",
"acm:*",
"logs:*",
"bedrock:InvokeModel"
],
"Resource": "*"
}
]
}aws configure
# Enter your Access Key ID, Secret Access Key
# Default region: us-east-1 (required - SST deploys here)
# Default output: jsonVerify:
aws sts get-caller-identitySST can create custom domains for your APIs. Domain configuration is controlled via the SIGNAGE_DOMAIN environment variable.
-
Register a domain (if you don't have one) via Route 53 or any registrar
-
Create a Route 53 Hosted Zone for your domain:
aws route53 create-hosted-zone --name yourdomain.com --caller-reference $(date +%s) -
Update your domain's nameservers to point to Route 53 (if using external registrar)
-
Set the domain when deploying:
# For manual deployments SIGNAGE_DOMAIN=yourdomain.com pnpm deploy # For GitHub Actions, add SIGNAGE_DOMAIN as a repository secret (see Step 4)
This will create these subdomains:
signage.yourdomain.com- Web emulatorapi.signage.yourdomain.com- HTTP APIws.signage.yourdomain.com- WebSocket API
For non-prod stages, the stage name is prefixed (e.g.,
dev.signage.yourdomain.com).
If you don't set SIGNAGE_DOMAIN, SST will use default AWS URLs:
https://abc123.execute-api.us-east-1.amazonaws.comhttps://d1234567890.cloudfront.net
This is useful for testing or if you don't have a custom domain.
For automated CI/CD deployments, add these secrets to your GitHub repository:
- Go to your repo → Settings → Secrets and variables → Actions
- Add these secrets:
| Secret Name | Value | Required |
|---|---|---|
AWS_ACCESS_KEY_ID |
Your IAM user's access key ID | Yes |
AWS_SECRET_ACCESS_KEY |
Your IAM user's secret access key | Yes |
SIGNAGE_DOMAIN |
Your domain (e.g., yourdomain.com) |
No |
Note: If SIGNAGE_DOMAIN is not set, SST will use default AWS URLs instead of custom domains.
Deploy to the dev stage first:
pnpm deployThis takes 3-5 minutes and outputs your deployed URLs:
✔ Complete
SignageApi: wss://abc123.execute-api.us-east-1.amazonaws.com
SignageTestApi: https://xyz789.execute-api.us-east-1.amazonaws.com
SignageWeb: https://d1234567890.cloudfront.net
pnpm deploy:prodAfter the first manual deployment, pushing to main automatically deploys to dev.
To deploy to production, use the GitHub Actions workflow:
- Go to Actions → Deploy
- Click Run workflow
- Select
prodstage
If you use a Dexcom CGM:
- Create a follower account at dexcom.com/share
- Set SST secrets:
# For dev stage pnpm sst secret set DexcomUsername your_username pnpm sst secret set DexcomPassword your_password # For prod stage pnpm sst secret set DexcomUsername your_username --stage prod pnpm sst secret set DexcomPassword your_password --stage prod
Displays your daily Oura readiness and sleep scores with color-coded status:
- Green (85-100): Optimal
- Yellow-green (70-84): Good
- Yellow (60-69): Fair
- Orange (50-59): Low
- Red (<50): Poor
Format: J 75/82 (readiness/sleep). Supports multiple users.
Setup:
- Create an OAuth app at cloud.ouraring.com
- Set redirect URIs in your Oura app:
- Development:
https://api.dev.signage.yourdomain.com/oura/auth/callback - Production:
https://api.signage.yourdomain.com/oura/auth/callback
- Development:
- Set SST secrets:
pnpm sst secret set OuraClientId your_client_id pnpm sst secret set OuraClientSecret your_client_secret # For prod stage pnpm sst secret set OuraClientId your_client_id --stage prod pnpm sst secret set OuraClientSecret your_client_secret --stage prod
- Authorize each user by visiting (the
nameparameter is required):The first letter of the name becomes the initial shown on the display.https://api.signage.yourdomain.com/oura/auth/start?name=John
How it works:
- Readiness and sleep scores are fetched daily at 8 AM Pacific
- Scores are cached and displayed until the next fetch
- If no data is available, shows "--" in gray
Open your web URL in a browser:
https://signage.yourdomain.com
The Pixoo relay CLI (local or Lightsail-hosted) lives in jwulff/glucagent — see that repo's packages/relay/ for the CLI and deploy/lightsail/ for the cloud-host setup.
| Service | Purpose |
|---|---|
| API Gateway WebSocket | Real-time connections to displays |
| API Gateway HTTP | REST endpoints for testing |
| Lambda | Content generation and WebSocket handlers |
| DynamoDB | Connection state and cached data |
| CloudFront + S3 | Web emulator hosting |
| EventBridge | Scheduled content updates (every minute) |
| Secrets Manager | API credentials storage |
| Bedrock | AI news digest (optional) |
- EventBridge triggers the compositor Lambda every minute
- Lambda fetches data (Dexcom, weather, Oura) and renders a 64×64 frame
- Frame is broadcast via WebSocket to all connected terminals
- Terminals (relay, web emulator) display the frame
| Package | Description |
|---|---|
@signage/core |
Shared types and Pixoo protocol (RGB encoding) |
@signage/functions |
Lambda handlers for WebSocket and HTTP APIs |
@signage/web |
React web emulator with canvas-based display |
@signage/local-dev |
Local development server (no AWS needed) |
The Pixoo relay CLI (@signage/relay) and Lightsail deploy scripts moved to jwulff/glucagent.
Generate and broadcast test patterns:
# Rainbow gradient
curl "https://api.signage.yourdomain.com/test-bitmap?pattern=rainbow"
# Color bars
curl "https://api.signage.yourdomain.com/test-bitmap?pattern=bars"
# Custom text
curl "https://api.signage.yourdomain.com/test-bitmap?pattern=text&text=Hello&color=pink"| Parameter | Values | Default |
|---|---|---|
pattern |
rainbow, bars, text |
rainbow |
text |
any string | Hello |
color |
white, red, green, blue, yellow, cyan, magenta, orange, pink |
white |
curl "https://api.signage.yourdomain.com/health"The Pixoo relay CLI and the optional AWS Lightsail cloud-relay setup live in jwulff/glucagent:
packages/relay/— Node.js CLI that bridges the cloud WebSocket API to the Pixoo's local HTTP endpoint (local-network or cloud-hosted).deploy/lightsail/— Lightsail + WireGuard setup for 24/7 operation without keeping a local computer on (~$3.50/month).
Point the relay at this repo's WebSocket URL (e.g. wss://ws.signage.yourdomain.com) and it will receive frames broadcast by the Lambdas here.
| Component | Monthly Cost |
|---|---|
| Lambda | ~$1-3 (free tier covers most) |
| DynamoDB | ~$1-5 (on-demand) |
| API Gateway | ~$1-5 (free tier: 1M requests) |
| CloudFront/S3 | ~$0.50-2 |
| Secrets Manager | ~$0.40 |
| Total AWS | ~$5-15 |
The optional Lightsail relay (~$3.50/month) is described in jwulff/glucagent.
| Issue | Solution |
|---|---|
pnpm: command not found |
Install pnpm: npm install -g pnpm |
Cannot find module 'sst' |
Run pnpm install in project root |
| Deployment fails with credentials error | Check aws sts get-caller-identity works |
| WebSocket connection fails | Verify domain configuration in infra/api.ts |
| Dexcom widget shows no data | Run pnpm sst secret list to verify secrets are set |
| Relay can't find Pixoo | See relay troubleshooting in jwulff/glucagent |
| CloudFront shows old content | Wait 5 minutes or invalidate cache in AWS console |
View Lambda logs:
pnpm sst dev # Shows logs in real-time during developmentOr in AWS Console:
- Go to CloudWatch → Log groups
- Find
/aws/lambda/...groups for your functions
pnpm dev:localpnpm devpnpm test # Run once
pnpm test:watch # Watch modepnpm buildMIT License - see LICENSE for details.
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests:
pnpm test && pnpm lint - Submit a pull request
See CONTRIBUTING.md for detailed guidelines.

