Bygga
제품명을 박스에 입력하면 Bygga가 3D로 모든 조립 단계와 부품·스크류·도구를 보여주고 음성으로 안내합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Web console for Wi-Fi wall switches that control Philips Hue: install firmware over USB from the browser and choose what each button does. Next.js + Postgres.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Web console (Vercel + Neon Postgres) for Wi-Fi Hue wall switches. It never talks
to the Bridge. A XIAO or npm run push-from-bridge uploads topology.
Humans sign in with Google, GitHub or an emailed code (a password on self-hosted consoles
without email). Devices use an API key.
Two products, one console:
hue-simple-switch, ESP32-C6) — GPIO channels (boot and d0–d5), recipes per channel and event.hue-round-switch, ESP32-S3 + circle) — pages (room/zone group, tap / double-tap, scene lists, dimmer). Not GPIO.Everything in the three repos is English: UI, docs, comments.
This repo owns the contract the switches implement:
docs/device-api.md.docs/usb-protocol.md.docs/definitions.md.docs/round-pages.md.AGENTS.md ("Multi-repo") and the "Contract change" issue template.Shipped specs and prototypes are kept as history in docs/archive/.
Production host: https://hue.tineira.com.
cp .env.example .env.local
# DATABASE_URL (your own Postgres; DATABASE_DRIVER=pg unless it is Neon),
# AUTH_SECRET, USER_EMAIL, USER_PASSWORD, EMAIL_DEV_CONSOLE=1
npm install
npm run dev
Use a development database, never production. The console creates its tables and
the first user on the first request. The schema lives in db/schema.sql; after editing it,
run npm run schema and commit lib/generated/schema.ts too (CI checks they match). Open http://localhost:3000/login
and sign in with USER_EMAIL / USER_PASSWORD. Full walkthrough: CONTRIBUTING.md.
Boards are flashed and provisioned from Setup (/setup; /devices and
/install redirect there) in Chrome or Edge over USB. That screen writes Wi-Fi (Improv)
and a device token (HUESET), and points the XIAO at DEVICE_CONSOLE_URL
(else BETTER_AUTH_URL, else the Setup page's own address). The hosted console
sets https://hue.tineira.com. A board cannot reach localhost: to test against a
local console, set DEVICE_CONSOLE_URL to your machine's LAN address. Nothing is compiled into the firmware:
config.h holds only SERIAL_DEBUG, in dev too. Firmware images are uploaded by
firmware CI and served from the database (see below).
On a PC that can reach the Bridge:
# PowerShell
$env:HUE_BRIDGE_IP="192.168.100.12"
$env:HUE_APP_KEY="your-hue-key"
$env:CONSOLE_TOKEN="hsw_…"
$env:CONSOLE_URL="http://localhost:3000"
npm run push-from-bridge
You can run your own console (on Vercel, a home server or a Raspberry Pi) and set up
switches that talk to it instead of hue.tineira.com. In short:
AUTH_SECRET, USER_EMAIL and USER_PASSWORD (below), plus
DEVICE_CONSOLE_URL: the address switches use to reach your console. Setup writes
it to each board and shows it first.https://) or plain
http:// on your LAN, with Setup opened at http://localhost:3000 on the server.
Private CAs do not work.node scripts/import-firmware.mjs <round|simple> copies the hosted
console's current release, or your fork of a firmware repo uploads its own builds
once you set the CONSOLE_UPLOAD_URL variable and FIRMWARE_UPLOAD_TOKEN secret.Step by step: docs/self-hosting.md.
Sign-in is Better Auth running inside the console on the
same Postgres; there is no auth service to sign up for. Design history:
docs/archive/specs/multi-user-accounts.md.
Self-hosting needs only DATABASE_URL, AUTH_SECRET, USER_EMAIL and USER_PASSWORD.
That gives one account with a password; sign-up stays closed. Neon is the default
driver; for any other Postgres set DATABASE_DRIVER=pg.
Hosted (hue.tineira.com) adds, each optional and off until set:
| Env var | Turns on |
|---|---|
RESEND_API_KEY, EMAIL_FROM | Emailed 6-digit codes, invites and notices. Password sign-in turns off. EMAIL_FROM e.g. Hue Switch <[email protected]> (domain verified in Resend) |
SIGNUP_MODE | closed (default), invite, waitlist or open: the starting mode. A mode saved in /admin → Settings wins over it. Without email the console is always closed |
USER_CAP | Seats (accounts plus unused invites) the waitlist mode fills automatically, until the admin saves a cap in /admin (default: no cap) |
WAITLIST_EMAILS_PER_DAY | Waitlist invites and confirmations a day, within EMAIL_DAILY_CAP (default 40) |
RESEND_WEBHOOK_SECRET | Resend webhook at /api/webhooks/resend (email.bounced, email.complained, email.suppressed): undeliverable addresses leave the waitlist |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | Continue with Google. Callback https://<host>/api/auth/callback/google |
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET | Continue with GitHub. Callback https://<host>/api/auth/callback/github |
TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY | Cloudflare Turnstile on the email and waitlist forms |
BETTER_AUTH_URL | Public URL for OAuth callbacks and links in emails (else taken from the request) |
ADMIN_EMAILS | Comma-separated admins for /admin (default: USER_EMAIL) |
CRON_SECRET | The daily cleanup cron (vercel.json) |
EMAIL_DAILY_CAP | Emails a day before email sign-in says it is busy (default 90, under Resend's free 100) |
LIMIT_SWITCHES, LIMIT_BRIDGES, LIMIT_KEYS, LIMIT_SNAPSHOT_KB |
Local development without sending mail: EMAIL_DEV_CONSOLE=1 prints codes to the
server log instead (never in production).
See docs/device-api.md. Short version:
POST /api/device/register — Bearer device key; MAC, firmware, product, channels, rich snapshotGET /api/device/config?mac= — Simple: { rev, recipes[] } with channelId. Round: { rev, product, pageSwipeAxis, screenTimeoutSec, pages[], recipes[] } with pageId (not recipes-only)PUT /api/switches/{mac}/recipes — logged-in user, Simple GPIOPUT /api/switches/{mac}/pages — logged-in user, Round pagesPOST /api/ingest is gone (410). Postgres replaces the old file / KV store.
Firmware TLS against https://hue.tineira.com must verify the certificate.
Vercel skips the build when everything since the last successful deployment is
docs (docs/, *.md), or when the commit message has [skip deploy]
(vercel.json ignoreCommand, scripts/vercel-ignore.sh). docs/changelog.md
still deploys, since /changelog reads it. The firmware repos do the same for
their release workflow; there the manual tag is [skip ci].
A push to main in a firmware repo uploads a release candidate to the
console. It is stored, and it waits in /admin until a signed-in admin presses
Make current. Only then does Setup offer it to boards plugged in over USB,
Switches offer it over Wi-Fi, and /changelog list it. No commit lands in this
repo and nothing redeploys.
push to main (hue-round-switch / hue-simple-switch)
→ .github/workflows/firmware.yml: build (SERIAL_DEBUG 0), read FIRMWARE_VERSION
→ notes = that version's section of the firmware repo's CHANGELOG.md
→ POST https://hue.tineira.com/api/firmware/<product> [FIRMWARE_UPLOAD_TOKEN]
→ Postgres: firmware_releases + firmware_parts (approved_at null: waiting)
→ /admin → Firmware: the release is marked "waiting"; an admin presses Make current
→ firmware_current = this release, approved_at set
→ /firmware/<product>/manifest.json, OTA offers and /changelog use it on the next request
The upload token can only add releases. Choosing the current release takes an
admin session (ADMIN_EMAILS). This holds for a product's first release
too: until an admin makes it current, the product has no manifest and Setup has
nothing to install for it. The upload answers 201 (200 for a re-upload of
the same bins) with "current": false while the release waits. Bins of a
waiting release are not served, and its notes stay off /changelog, Setup and
Switches. Releases that were current before this rule existed count as released.
FIRMWARE_VERSION in the firmware source is the version the wizard shows.
Bumping it is what makes Setup offer Update. A rebuild without a bump is
answered 409 version_exists: the bins stay as they were (their URLs are cached
forever) and only the notes are updated. Design history:
docs/archive/specs/firmware-uploads.md.
The console checks every upload: all four parts, a major.minor.patch version,
non-empty notes, and the chip id inside bootloader.bin / firmware.bin
(ESP32-S3 for round, ESP32-C6 for simple). It keeps the bins of the last 5
releases per product, plus the current one and any still waiting. Release rows and their notes are
kept for the changelog.
When Setup offers Update, it lists the notes of every version after the
board's one up to the release it installs. A bullet that starts with
Important: is shown first, with an "Important" label, on Setup and on
/changelog. Use it for something the person must know or do before or right
after updating (a button to press, a setting that resets), not for new
features. Design history: docs/archive/specs/setup-update-notes.md.
Each upload also sends credits: the firmware repo's THIRD_PARTY.json, the
third-party software linked into the image (core, ESP-IDF, every library
pinned in sketch.yaml). Firmware CI fails when that file and sketch.yaml
disagree. The console shows the current release's list on the public
/credits page; the field is optional, and a re-upload of the same version
replaces it. Design history: docs/archive/specs/credits.md.
The console serves the four parts at fixed offsets, never taken from an upload. Same on both boards (Arduino-ESP32 3.3.12):
| Part | Offset | Source |
|---|---|---|
bootloader.bin | 0x0 | boards.txt build.bootloader_addr for XIAO_ESP32S3 and XIAO_ESP32C6 |
partitions.bin | 0x8000 | ESP32 image layout |
boot_app0.bin | 0xe000 | otadata in default_8MB.csv (Round) and hue-simple-switch/partitions.csv |
firmware.bin | 0x10000 | app0 in the same tables |
The wizard never erases flash. NVS (Hue, recipes, pages, console) must
survive a reflash.
One random secret, FIRMWARE_UPLOAD_TOKEN, stored in three places: Vercel
(Production) and the Actions secrets of both firmware repos. To set or rotate
it, run in PowerShell from this repo, then redeploy the console:
$t = -join ((1..48) | ForEach-Object { '{0:x}' -f (Get-Random -Maximum 16) }); $t | npx vercel env add FIRMWARE_UPLOAD_TOKEN production; $t | gh secret set FIRMWARE_UPLOAD_TOKEN -R tineira/hue-round-switch; $t | gh secret set FIRMWARE_UPLOAD_TOKEN -R tineira/hue-simple-switch; Remove-Variable t
(To rotate, remove the old Vercel value first with
npx vercel env rm FIRMWARE_UPLOAD_TOKEN production.)
To roll back (or forward), open /admin → Firmware as an admin and press
Make current on any stored release that still has its bins. Every change is
in the admin log. POST /api/firmware/<product>/current, which used to do this
with the upload token, now answers 410 gone.
Upload a local build (a folder with the four bins) with
scripts/upload-firmware.mjs <product> <dir> --version x.y.z --notes <file> [--credits <file>],
where --credits is the firmware repo's THIRD_PARTY.json. It waits in /admin like a CI
upload.
A self-hosted console copies a released version from another console with
scripts/import-firmware.mjs <product> [--from https://hue.tineira.com] [--version x.y.z]
(FIRMWARE_UPLOAD_TOKEN and CONSOLE_URL of the console receiving it). It reads the
public manifest, the parts (checked against their SHA-256 ETags) and
GET /firmware/<product>/<version>/notes, which returns a released version's
{ product, version, date, notes, credits } (404 for unknown or waiting versions).
| Symptom | Cause |
|---|---|
Firmware run fails: FIRMWARE_UPLOAD_TOKEN ... missing; cannot upload the release to the console | Secret missing or empty in that firmware repo |
Upload answers 401 | The firmware repo's secret and Vercel's differ; set both again |
Upload answers 503 upload_not_configured | No FIRMWARE_UPLOAD_TOKEN on Vercel, or no redeploy since it was set |
Upload answers 400 missing_notes | No ### <version> section in that repo's CHANGELOG.md |
Upload answers 400 invalid_image | A part is missing, empty, or built for the other chip |
Upload answers 409 version_exists | Same version, new bins: bump FIRMWARE_VERSION |
| Upload succeeded but Setup still offers the old version | The release is waiting: make it current in /admin → Firmware |
A new firmware repo needs: a firmware.yml upload step like the existing two,
a CHANGELOG.md, the FIRMWARE_UPLOAD_TOKEN secret, its product id in
lib/firmware.ts (chip id, family) and lib/web-setup/products.ts, the id in
the firmware_releases / firmware_current checks in db/schema.sql (then
npm run schema), a ## <Product> intro in docs/changelog.md, and a
row in AGENTS.md ("Multi-repo").
The Simple switch has an in-wall build on mains voltage. The designs are uncertified
and some are unproven. Read the Safety notice (/safety on any console, or
hue.tineira.com/safety) before you build anything.
Every account accepts it, and on hue.tineira.com the Terms, before using the console
(docs/archive/specs/terms-and-safety.md).
See CONTRIBUTING.md. Bugs and ideas go in GitHub issues.
Open work: GitHub issues.
The console and the switch firmwares are free and stay that way. If they are useful to you, you can sponsor the project on GitHub. Sponsorship helps pay for development and does not unlock anything.
Copyright (C) 2026 Tomas Neira and contributors.
The console is free software under the GNU Affero General Public License v3.0
(AGPL-3.0-only). If you run a modified copy as a network service, you must
offer its users the source of your version; the site footer's "Source" link does
that for this deployment. Contributions are accepted under the same license.
The switch firmwares, hue-round-switch
and hue-simple-switch, are MIT. The Simple's
mains carrier board and enclosure (hue-simple-switch/hardware/) are CERN-OHL-P-2.0.
Not affiliated with or endorsed by Signify. Philips Hue is a trademark of Signify.
Per-account limits (25, 5, 25, 512); /admin overrides one account |
CONTACT_EMAIL | Contact shown on /privacy (hue.tineira.com defaults to [email protected]) |
PRIVACY_URL | Replace the /privacy link under the sign-in form |
TERMS_URL, TERMS_VERSION | A self-hosted console's own Terms: /terms redirects there and accounts accept it on /accept. Change TERMS_VERSION when you change the text, so everyone accepts again. Without TERMS_URL, accounts accept only the Safety notice |