Docsv1.0
Docs/Guides/Hue Lights

Hue Lights

Change your Philips Hue lights when you receive a tip.

Hue Lights Integration

React to tips in real time by flashing and changing the color of your Philips Hue lights. Tip amounts map to colors: green for small tips, blue for medium, purple for large.

Info

This integration uses the Philips Hue local bridge API — no cloud account required. Your Hue Bridge must be on the same local network as the machine running this script. This does not work on remote servers unless you expose your bridge (not recommended).


Prerequisites

  • Philips Hue Bridge (v2 square bridge required for local API)
  • A Tizemint API token (tzmnt_...) from Settings → API Tokens
  • Your lights' group or individual bulb IDs
  • Node.js 18+

Step 1: Find Your Hue Bridge IP

bash
# Option 1: Check your router's connected devices list
# Option 2: Use the Hue discovery endpoint
curl https://discovery.meethue.com/
# Returns: [{"id":"...","internalipaddress":"192.168.1.x"}]

Note the internalipaddress — you'll use this as HUE_BRIDGE_IP.

Step 2: Create a Hue API User

Press the physical button on your Hue Bridge, then within 30 seconds run:

bash
curl -X POST http://YOUR_BRIDGE_IP/api \
  -H "Content-Type: application/json" \
  -d '{"devicetype":"tizemint#tipbot"}'
# Returns: [{"success":{"username":"abc123def456..."}}]

Save the returned username as your HUE_USERNAME.

Step 3: Find Your Light or Group IDs

bash
# List all lights
curl http://YOUR_BRIDGE_IP/api/YOUR_HUE_USERNAME/lights
 
# List all groups (rooms/zones)
curl http://YOUR_BRIDGE_IP/api/YOUR_HUE_USERNAME/groups

Note the numeric IDs for the lights or groups you want to control.

Step 4: Install Dependencies

bash
npm init -y
npm install eventsource

Step 5: The Full Script

js
// tizemint-hue.js
import EventSource from "eventsource";
 
// --- Configuration ---
const TIZEMINT_TOKEN = process.env.TIZEMINT_TOKEN; // tzmnt_...
const HUE_BRIDGE_IP = process.env.HUE_BRIDGE_IP;  // e.g. 192.168.1.50
const HUE_USERNAME = process.env.HUE_USERNAME;     // From Step 2
// Comma-separated light IDs to control, e.g. "1,2,3"
const LIGHT_IDS = (process.env.HUE_LIGHT_IDS || "1").split(",").map(Number);
// Or use a group: set HUE_GROUP_ID=1 to control an entire room
const HUE_GROUP_ID = process.env.HUE_GROUP_ID ? Number(process.env.HUE_GROUP_ID) : null;
 
if (!TIZEMINT_TOKEN || !HUE_BRIDGE_IP || !HUE_USERNAME) {
  console.error("Missing required env vars: TIZEMINT_TOKEN, HUE_BRIDGE_IP, HUE_USERNAME");
  process.exit(1);
}
 
// --- Color mapping by tip amount (cents) ---
function getTipColor(amountCents) {
  const dollars = amountCents / 100;
  if (dollars >= 20) {
    // Large tip: purple
    return { hue: 48000, sat: 254, bri: 254 };
  } else if (dollars >= 5) {
    // Medium tip: blue
    return { hue: 46920, sat: 254, bri: 200 };
  } else {
    // Small tip: green
    return { hue: 25500, sat: 254, bri: 180 };
  }
}
 
// --- Hue API helpers ---
const hueBase = `http://${HUE_BRIDGE_IP}/api/${HUE_USERNAME}`;
 
async function setLightState(lightId, state) {
  const res = await fetch(`${hueBase}/lights/${lightId}/state`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(state),
  });
  return res.json();
}
 
async function setGroupState(groupId, state) {
  const res = await fetch(`${hueBase}/groups/${groupId}/action`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(state),
  });
  return res.json();
}
 
async function setState(state) {
  if (HUE_GROUP_ID !== null) {
    return setGroupState(HUE_GROUP_ID, state);
  }
  return Promise.all(LIGHT_IDS.map((id) => setLightState(id, state)));
}
 
// --- Flash effect ---
// Saves current state, flashes the tip color, then restores
async function flashTipColor(amountCents) {
  const color = getTipColor(amountCents);
 
  // Flash on: set color, alert once
  await setState({
    on: true,
    ...color,
    transitiontime: 2, // 0.2 seconds
    alert: "select",   // Single flash
  });
 
  // Hold the color for 3 seconds
  await new Promise((r) => setTimeout(r, 3000));
 
  // Restore to a neutral warm white
  await setState({
    on: true,
    hue: 8000,
    sat: 140,
    bri: 200,
    transitiontime: 10, // 1 second fade
  });
}
 
// --- SSE connection ---
function connect() {
  const url = `https://tizemint.com/api/tip-events/stream?token=${TIZEMINT_TOKEN}`;
  const es = new EventSource(url);
 
  es.onopen = () => {
    console.log("[hue-bridge] Connected to Tizemint event stream");
  };
 
  es.onmessage = async (event) => {
    let data;
    try {
      data = JSON.parse(event.data);
    } catch {
      return; // Ignore keep-alive pings
    }
 
    if (data.type === "tip:received") {
      const dollars = (data.amount / 100).toFixed(2);
      console.log(`[hue-bridge] Tip received: $${dollars} from ${data.displayName}`);
      await flashTipColor(data.amount);
    }
 
    if (data.type === "tip:goal-completed") {
      console.log(`[hue-bridge] Goal completed! Running celebration flash...`);
      // Cycle through colors for goal completion
      await celebrationFlash();
    }
  };
 
  es.onerror = () => {
    console.log("[hue-bridge] Connection lost, reconnecting in 5s...");
    es.close();
    setTimeout(connect, 5000);
  };
}
 
// --- Celebration: cycle red → green → blue on goal complete ---
async function celebrationFlash() {
  const colors = [
    { hue: 0, sat: 254, bri: 254 },     // Red
    { hue: 25500, sat: 254, bri: 254 }, // Green
    { hue: 46920, sat: 254, bri: 254 }, // Blue
    { hue: 48000, sat: 254, bri: 254 }, // Purple
  ];
 
  for (const color of colors) {
    await setState({ on: true, ...color, transitiontime: 3 });
    await new Promise((r) => setTimeout(r, 400));
  }
 
  // Return to warm white
  await setState({ on: true, hue: 8000, sat: 140, bri: 200, transitiontime: 10 });
}
 
connect();

Step 6: Run It

bash
TIZEMINT_TOKEN=tzmnt_your_token \
HUE_BRIDGE_IP=192.168.1.50 \
HUE_USERNAME=abc123def456 \
HUE_LIGHT_IDS=1,2 \
node tizemint-hue.js

To control an entire room instead of individual bulbs:

bash
# Find your group ID first: curl http://BRIDGE_IP/api/USERNAME/groups
TIZEMINT_TOKEN=tzmnt_your_token \
HUE_BRIDGE_IP=192.168.1.50 \
HUE_USERNAME=abc123def456 \
HUE_GROUP_ID=1 \
node tizemint-hue.js

Color Reference

Tip AmountColorHue Value
Under $5Green25500
$5 – $19.99Blue46920
$20 and abovePurple48000
Goal completedCycle—

Hue values are on a 0–65535 scale. Adjust the color mapping function to match your preferences.


Running as a Background Service

bash
npm install -g pm2
 
pm2 start tizemint-hue.js \
  --name tizemint-hue \
  --env TIZEMINT_TOKEN=tzmnt_... \
  --env HUE_BRIDGE_IP=192.168.1.50 \
  --env HUE_USERNAME=abc123 \
  --env HUE_LIGHT_IDS=1,2
 
pm2 save
pm2 startup

Troubleshooting

401 errors from the Hue bridge Your HUE_USERNAME may be invalid or expired. Repeat Step 2 to create a new user.

Lights don't change color Ensure the lights are Hue Color bulbs — Hue White bulbs only support brightness, not color. Check that your light IDs are correct with the /lights endpoint.

Script connects but nothing happens when I test with a tip Confirm the Tizemint token is valid by checking Settings → API Tokens. Also verify the bridge IP hasn't changed (DHCP leases can shift).

The script is on a server, not my local machine The Hue local API is only accessible on your LAN. You'd need to expose the bridge via a tunnel (not recommended for security reasons) or run the script on a device on the same network as your bridge — a Raspberry Pi or your stream PC works well.