{"id":34633172,"url":"https://github.com/codedgar/gmk87-node","last_synced_at":"2026-04-02T12:11:23.383Z","repository":{"id":320402013,"uuid":"1081209907","full_name":"codedgar/gmk87-node","owner":"codedgar","description":"Open source configurator for the Zuoya GMK87 keyboard. Upload display images, configure    RGB lighting, and sync time via USB HID. Includes desktop app, CLI, and Node.js API.","archived":false,"fork":false,"pushed_at":"2026-03-24T21:38:26.000Z","size":2771,"stargazers_count":16,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-26T02:55:21.746Z","etag":null,"topics":["gmk87","keyboard","reverse-engineering","usb","usb-hid","zuoya"],"latest_commit_sha":null,"homepage":"https://codedgar.github.io/gmk87-node/","language":"JavaScript","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/codedgar.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":null,"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":"2025-10-22T13:13:25.000Z","updated_at":"2026-03-25T15:46:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"d4a97d68-5eb8-4156-95aa-fb10bcbcba38","html_url":"https://github.com/codedgar/gmk87-node","commit_stats":null,"previous_names":["codedgar/gmk87-node"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/codedgar/gmk87-node","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codedgar%2Fgmk87-node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codedgar%2Fgmk87-node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codedgar%2Fgmk87-node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codedgar%2Fgmk87-node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codedgar","download_url":"https://codeload.github.com/codedgar/gmk87-node/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codedgar%2Fgmk87-node/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31305975,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T09:48:21.550Z","status":"ssl_error","status_checked_at":"2026-04-02T09:48:19.196Z","response_time":89,"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":["gmk87","keyboard","reverse-engineering","usb","usb-hid","zuoya"],"created_at":"2025-12-24T16:55:00.499Z","updated_at":"2026-04-02T12:11:23.375Z","avatar_url":"https://github.com/codedgar.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GMK87 Configurator\n\n![Status](https://img.shields.io/badge/status-stable-green)\n![Node](https://img.shields.io/badge/node-%3E%3D14.0.0-brightgreen)\n![License](https://img.shields.io/badge/license-MIT-blue)\n\nUpload images to the keyboard display, configure RGB lighting, sync the clock, and apply presets on the Zuoya GMK87 keyboard.\n\n## Hardware\n\n- **Keyboard:** Zuoya GMK87\n- **Vendor ID:** `0x320f` | **Product ID:** `0x5055`\n- **Display:** 240x135 pixels, RGB565, 2 image slots\n- **USB Interface:** 3 (vendor-specific, `usagePage 0xFF1C`)\n\n## App\n\nDesktop application with a graphical interface. Supports Windows, macOS, and Linux.\n\n[![Windows](https://img.shields.io/badge/Windows-Download-0078D6?style=for-the-badge\u0026logo=windows\u0026logoColor=white)](https://github.com/codedgar/gmk87-node/releases/latest)\n[![macOS](https://img.shields.io/badge/macOS-Download-000000?style=for-the-badge\u0026logo=apple\u0026logoColor=white)](https://github.com/codedgar/gmk87-node/releases/latest)\n[![Linux](https://img.shields.io/badge/Linux-Download-FCC624?style=for-the-badge\u0026logo=linux\u0026logoColor=black)](https://github.com/codedgar/gmk87-node/releases/latest)\n\nGo to [Releases](https://github.com/codedgar/gmk87-node/releases/latest), download the file for your OS, and install it.\n\n| OS | File |\n|---|---|\n| Windows | `.exe` installer |\n| macOS | `.dmg` |\n| Linux | `.AppImage` or `.deb` |\n\n\u003e **Linux users:** Copy the included `50-gmk87.rules` to `/etc/udev/rules.d/` and reload udev to allow HID access without root.\n\n## CLI\n\nCommand-line tools for the terminal. Requires [Node.js](https://nodejs.org) (v14+).\n\n### Install\n\n```bash\ngit clone https://github.com/codedgar/gmk87-node.git\ncd gmk87-node\nnpm install\n```\n\n### Commands\n\n| Command | What it does |\n|---|---|\n| `npm run sendimage` | Upload images to the display |\n| `npm run lights` | Configure RGB underglow and LEDs |\n| `npm run preset` | Apply a preset lighting profile |\n| `npm run timesync` | Sync system time to the keyboard |\n\n### Upload images\n\nBoth slots at once (recommended, upload session overwrites all image memory):\n\n```bash\nnpm run sendimage -- --slot0 image1.png --slot1 image2.jpg\n```\n\nSingle slot (the other slot will be blank):\n\n```bash\nnpm run sendimage -- --file image.png --slot 0\n```\n\nAnimated GIFs:\n\n```bash\nnpm run sendimage -- --slot0 animation.gif --slot1 static.png --ms 100\n```\n\nOptions:\n\n| Flag | Description |\n|---|---|\n| `--slot0` / `--slot1` | Path for each display slot (static or GIF) |\n| `--file` + `--slot` | Single image mode |\n| `--ms \u003cnumber\u003e` | Animation delay in ms (min 60, default 100) |\n| `--show` | Which slot to display after upload |\n\nImages are automatically resized to 240x135. Max 36 frames total across both slots.\n\n### Configure lighting\n\n```bash\nnpm run lights -- --effect breathing --brightness 7 --red 255 --green 0 --blue 0\nnpm run lights -- --led-color blue --led-mode 3\nnpm run lights -- --effect rainbow-waterfall --brightness 9 --speed 3\n```\n\nUnderglow options:\n\n| Flag | Values |\n|---|---|\n| `--effect` | Name or 0-18 |\n| `--brightness` | 0-9 |\n| `--speed` | 0-9 (0 = fast, 9 = slow) |\n| `--orientation` | 0 (left-to-right) or 1 (right-to-left) |\n| `--rainbow` | true / false |\n| `--red`, `--green`, `--blue` | 0-255 |\n\nLED options:\n\n| Flag | Values |\n|---|---|\n| `--led-mode` | 0-4 |\n| `--led-color` | Name or 0-8 (red, orange, yellow, green, teal, blue, purple, white, off) |\n| `--led-saturation` | 0-9 |\n| `--led-rainbow` | true / false |\n\nOther:\n\n| Flag | Values |\n|---|---|\n| `--winlock` | true / false |\n| `--show-image` | 0 (time), 1 (slot 0), 2 (slot 1) |\n\n### Apply presets\n\n```bash\nnpm run preset -- gaming\n```\n\nAvailable: `gaming`, `relaxed`, `party`, `minimal`, `productivity`, `purple-wave`, `matrix`, `sunset`\n\n### Sync time\n\n```bash\nnpm run timesync\n```\n\n### Debug logging\n\n```bash\nDEBUG=1 npm run sendimage -- --slot0 image.png --slot1 image2.jpg\n```\n\n## API\n\nImport `src/api.js` to control the keyboard from your own code.\n\n```bash\nnpm install codedgar/gmk87-node\n```\n\n```js\nimport gmk87 from \"gmk87-hid-uploader\";\n```\n\nOr with named imports:\n\n```js\nimport { uploadImage, setLighting, showSlot, syncTime, readConfig, getKeyboardInfo } from \"gmk87-hid-uploader\";\n```\n\n### Functions\n\n#### `uploadImage(imagePath, slot, options)`\n\nUpload static images or GIFs to the display.\n\n```js\nawait gmk87.uploadImage(\"cat.png\", 0, { slot0File: \"cat.png\", slot1File: \"dog.jpg\" });\nawait gmk87.uploadImage(\"anim.gif\", 0, { slot0File: \"anim.gif\", frameDuration: 100 });\n```\n\n#### `setLighting(changes)`\n\nConfigure underglow and LED settings. Uses read-modify-write, so unspecified settings are preserved.\n\n```js\nawait gmk87.setLighting({\n  underglow: { effect: 5, brightness: 7, hue: { red: 255, green: 0, blue: 128 } },\n});\n\nawait gmk87.setLighting({\n  led: { mode: 3, color: 5 },\n});\n```\n\n#### `showSlot(slot)`\n\nSwitch the display. `0` = time, `1` = slot 0, `2` = slot 1.\n\n```js\nawait gmk87.showSlot(2);\n```\n\n#### `syncTime()`\n\nSend system time to the keyboard clock.\n\n```js\nawait gmk87.syncTime();\n```\n\n#### `readConfig()`\n\nRead the current keyboard configuration.\n\n```js\nconst config = await gmk87.readConfig();\n// config.underglow  -\u003e { effect, brightness, speed, orientation, rainbow, hue }\n// config.led        -\u003e { mode, saturation, rainbow, color }\n// config.showImage  -\u003e 0, 1, or 2\n```\n\n#### `getKeyboardInfo()`\n\nGet device info (manufacturer, product, vendor/product IDs).\n\n```js\nconst info = gmk87.getKeyboardInfo();\n```\n\nAll API functions handle device open/close automatically.\n\n## Protocol\n\nBased on USB captures of the official Zuoya app. Uses command-response on USB interface 3.\n\n### Frame structure\n\n64-byte HID reports:\n\n```\n[0]    = 0x04 (report ID)\n[1-2]  = checksum (uint16 LE, sum of bytes 3-63)\n[3]    = command byte\n[4]    = data length\n[5-7]  = position (24-bit LE)\n[8-63] = data payload (56 bytes max)\n```\n\n## References\n\n- Python reference implementation by Jochen Eisinger (included as `reference.py`, BSD license)\n- [@ikkentim](https://github.com/ikkentim) for the original C# reverse engineering: https://github.com/ikkentim/gmk87-usb-reverse\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodedgar%2Fgmk87-node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodedgar%2Fgmk87-node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodedgar%2Fgmk87-node/lists"}