{"id":15431458,"url":"https://github.com/mrusme/kiwi","last_synced_at":"2025-04-19T17:24:16.121Z","repository":{"id":75669101,"uuid":"180449944","full_name":"mrusme/kiwi","owner":"mrusme","description":"Pimoroni Keybow based, WiFi-enabled Macro Pad (a.k.a. poor-man's Elgato Stream Deck)","archived":false,"fork":false,"pushed_at":"2022-12-17T17:23:27.000Z","size":26040,"stargazers_count":61,"open_issues_count":1,"forks_count":6,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-10-18T06:52:08.792Z","etag":null,"topics":["elgato","elgato-stream-deck","elixir","iot-device","keyboard","keybow","kiwi","mechanical-keyboard","nerves","nerves-project","obs","obs-studio","obs-websocket","pimoroni","pimoroni-keybow","raspberry","raspberry-pi","raspberrypi","stream-deck","wifi"],"latest_commit_sha":null,"homepage":"https://マリウス.com/kiwi-a-nerves-based-firmware-for-the-pimoroni-keybow/","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrusme.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"custom":["https://github.com/mrusme#support"]}},"created_at":"2019-04-09T21:10:18.000Z","updated_at":"2024-08-17T21:51:32.000Z","dependencies_parsed_at":"2023-06-07T07:45:34.469Z","dependency_job_id":null,"html_url":"https://github.com/mrusme/kiwi","commit_stats":{"total_commits":110,"total_committers":2,"mean_commits":55.0,"dds":0.1636363636363637,"last_synced_commit":"521e69a1e382272406181b2c558b899039fb41fe"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrusme%2Fkiwi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrusme%2Fkiwi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrusme%2Fkiwi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrusme%2Fkiwi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrusme","download_url":"https://codeload.github.com/mrusme/kiwi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249747858,"owners_count":21319790,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["elgato","elgato-stream-deck","elixir","iot-device","keyboard","keybow","kiwi","mechanical-keyboard","nerves","nerves-project","obs","obs-studio","obs-websocket","pimoroni","pimoroni-keybow","raspberry","raspberry-pi","raspberrypi","stream-deck","wifi"],"created_at":"2024-10-01T18:22:25.849Z","updated_at":"2025-04-19T17:24:16.086Z","avatar_url":"https://github.com/mrusme.png","language":"Elixir","funding_links":["https://github.com/mrusme#support"],"categories":[],"sub_categories":[],"readme":"Kiwi\n----\n\nKiwi – **K**eyboard **I**nterface for **W**ireless **I**nteraction – is a \n[Nerves](https://github.com/nerves-project)-based firmware for \n[Pimoroni Keybow](https://learn.pimoroni.com/keybow) that transforms the device \ninto a powerful wireless (WiFi) controller for Philips Hue, IFTTT, OBS and\neverything else that has an HTTP API!\n\nWith Kiwi, you can turn your Pimoroni Keybow into a poor-man's Elgato Stream\nDeck!\n\n![Kiwi](docs/kiwi.gif)\n\n## Requirements\n\n- 2.4 GHz WiFi\n- [Pimoroni \n  Keybow](https://shop.pimoroni.com/products/keybow?variant=21246333190227)\n- microSD card\n\n## Installation\n\n- Download the latest firmware (`kiwi.tar.gz`) from the \n  [releases page](https://github.com/mrusme/kiwi/releases/latest) and unpack \n  it it: `tar -xzf kiwi.tar.gz`\n- Insert the microSD card into the card reader of your computer\n- Raw-copy the firmware to your microSD card, e.g. using `dd`: \n  `sudo dd bs=1m if=./kiwi.fw of=/dev/SDCARD_DEVICE` (where `SDCARD_DEVICE` is \n  your microSD card, e.g. `/dev/rdisk3`)\n- If your computer did not automatically mount a partition (`BOOT-A`) from the \n  microSD card after the copy has finished, eject the card and plug it back in\n- Open the folder of your mounted partition (`BOOT-A`) and create a file named \n  `kiwi.txt` (small caps, no spaces) with \n  [the following content](kiwi.txt).\n- Adjust the SSID, the PSK (and the KEY_MGMT) values according to your WiFi \n  configuration\n- If you want to control [OBS](https://obsproject.com) from Kiwi, fill \n  `NERVES_NETWORK_OBS_SOCKET` with the URL to the OBS web-socket, otherwise \n  remove the whole line\n- Save the file and unmount the partition\n- Insert the microSD card into your Keybow and connect it to a power source\n- Check your WiFi access point's web-interface to find out the IP address that \n  was assigned to your Keybow\n\n## Configuration\n\nAs soon as you found the IP you can start configuring the device via its API.\n\n## API\n\nThe Kiwi API is accessible via `http://10.10.10.10:8080/` (where `10.10.10.10` \nis the IP address of the Keybow on your WiFi). Check out the full [OpenAPI 3\nspecification](https://github.com/mrusme/kiwi/blob/master/api.yaml)!\n\n\u003e Note: All example API calls in this documentation are being performed using \n\u003e [`curl`](https://curl.haxx.se), as it's available for the majority of \n\u003e platforms.\n\n### Endpoint: Settings\n\nThe Kiwi API provides a `/settings` endpoint for configuring each individual \nkey.\n\n#### **GET** `/settings`\n\nRetrieve a list of all currently configured settings (e.g. for backing up your \ncurrent configuration).\n\n```sh\ncurl \"http://10.10.10.10:8080/settings\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{}'\n```\n\n#### **POST** `/settings`\n\nBulk upsert endpoint, allows upserting a list of settings (e.g. for recovery of \na backup). This endpoint can be used for importing a backed-up configuration.\n\n```sh\ncurl -X \"POST\" \"http://10.10.10.10:8080/settings\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{\n          \"settings\": [\n            {\n              \"id\": \"key_1_in_row_1\",\n              \"object\": {\n                \"keydown\": {\n                  \"http\": [\n\n                    ...\n\n                  ]\n                }\n              }\n            },\n            {\n              \"id\": \"animation_main\",\n              \"object\": {\n                \"frames\": [\n\n                    ...\n\n                ]\n              }\n            }\n          ]\n        }'\n```\n\n#### **GET** `/settings/keys`\n\nRetrieve a list of all currently configured keys.\n\n```sh\ncurl \"http://10.10.10.10:8080/settings/keys\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{}'\n```\n\n#### **GET** `/settings/keys/:key`\n\nRetrieve the current configuration for a specific key.\n\n```sh\ncurl \"http://10.10.10.10:8080/settings/keys/key_1_in_row_1\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{}'\n```\n\n#### **POST** `/settings/keys/:key`\n\n`:key` can be one of the following values:\n\n- `key_1_in_row_1`\n- `key_2_in_row_1`\n- `key_3_in_row_1`\n- `key_1_in_row_2`\n- `key_2_in_row_2`\n- `key_3_in_row_2`\n- `key_1_in_row_3`\n- `key_2_in_row_3`\n- `key_3_in_row_3`\n- `key_1_in_row_4`\n- `key_2_in_row_4`\n- `key_3_in_row_4`\n\nThe key names relate to the position of the key on the Keybow when the device \nis in front of you with the `Keybow` label in the top right corner and the \n`pimoroni.com/keybow` url on the bottom right:\n\n```\n                                             Keybow\n╔════════════════╦════════════════╦════════════════╗\n║                ║                ║                ║\n║                ║                ║                ║\n║ key_1_in_row_1 ║ key_2_in_row_1 ║ key_3_in_row_1 ║\n║                ║                ║                ║\n║                ║                ║                ║\n╠════════════════╬════════════════╬════════════════╣\n║                ║                ║                ║\n║                ║                ║                ║\n║ key_1_in_row_2 ║ key_2_in_row_2 ║ key_3_in_row_2 ║\n║                ║                ║                ║\n║                ║                ║                ║\n╠════════════════╬════════════════╬════════════════╣\n║                ║                ║                ║\n║                ║                ║                ║\n║ key_1_in_row_3 ║ key_2_in_row_3 ║ key_3_in_row_3 ║\n║                ║                ║                ║\n║                ║                ║                ║\n╠════════════════╬════════════════╬════════════════╣\n║                ║                ║                ║\n║                ║                ║                ║\n║ key_1_in_row_4 ║ key_2_in_row_4 ║ key_3_in_row_4 ║\n║                ║                ║                ║\n║                ║                ║                ║\n╚════════════════╩════════════════╩════════════════╝\n                                pimoroni.com/keybow\n```\n\n##### HTTP actions\n\nHTTP actions allow you to run arbitrary HTTP requests (`GET`, `PUT`, `POST`, \n`DELETE`) in order to control basically everything that provides a more or less \nsane HTTP API, like IoT devices or web services. \n\nLet's have a look at an example:\n\nIn order to configure the first key of the first row to perform a HTTP post to \n[IFTTT's Maker Webhooks](https://ifttt.com/maker_webhooks) \n(click *Documentation* on that site) when it's pressed (down), run the \nfollowing command:\n\n```sh\ncurl -X POST \"http://10.10.10.10:8080/settings/keys/key_1_in_row_1\" \\\n    -H \"Content-Type: application/json; charset=utf-8\" \\\n    -d $'{\n          \"object\": {\n            \"keydown\": {\n              \"http\": [\n                {\n                  \"body\": \"{}\",\n                  \"method\": \"post\",\n                  \"headers\": {\n                    \"content-type\": \"application/json\"\n                  },\n                  \"url\": \"https://maker.ifttt.com/trigger/key_1_in_row_1/with/key/your-ifttt-key-here\"\n                }\n              ]\n            }\n          }\n        }'\n```\n\nAs soon as the curl command returns with HTTP status code `200 OK` the key was \nset up and its configuration stored to the Keybow's internal storage (which is \nthe microSD card, of course). You can now press the key (the first one on the \ntop left) to run the HTTP call.\n\nHowever, you might have noticed, that the `http` property is not simply an \nobject but rather an array containing objects. This allows you to define \nmultiple HTTP actions to run with the press of a single button. In order to do \nso, simply add another HTTP request object to the `http` array:\n\n```sh\ncurl -X POST \"http://10.10.10.10:8080/settings/keys/key_1_in_row_1\" \\\n    -H \"Content-Type: application/json; charset=utf-8\" \\\n    -d $'{\n          \"object\": {\n            \"keydown\": {\n              \"http\": [\n                {\n                  \"body\": \"{}\",\n                  \"method\": \"post\",\n                  \"headers\": {\n                    \"content-type\": \"application/json\"\n                  },\n                  \"url\": \"https://maker.ifttt.com/trigger/turn_lights_off/with/key/your-ifttt-key-here\"\n                },\n                {\n                  \"body\": \"{}\",\n                  \"method\": \"post\",\n                  \"headers\": {\n                    \"content-type\": \"application/json\"\n                  },\n                  \"url\": \"https://maker.ifttt.com/trigger/power_on_television/with/key/your-ifttt-key-here\"\n                }\n              ]\n            }\n          }\n        }'\n```\n\nThe HTTP requests defined within the `http` array are being run **one after \nanother** (in the order defined within the array, from top to bottom) and not \nin parallel. This means that the second request waits for the first one to \ncomplete until it executes. Also keep in mind that, as of right now, subsequent \nrequests don't care about their prior request's return status and will run no \nmatter what.\n\n##### Scripted HTTP actions (advanced topic)\n\nNow that we've learned how HTTP requests work and that we can have multiple \nrequests running one after another, we can dive deeper into how HTTP requests \ncan be scripted.\n\nLet's assume, you would like one button to trigger an API that turns a \nlightbulb on or off. The API accepts a boolean value as the lightbulb's state, \nwith `true` being on, `false` being off. Now, in order to program a single \nbutton to trigger the lightbulb on **and** off with each keypress, you need to \nhave a dynamic value within your request's body, that changes, depending on the \ncurrent state of the lightbulb. This state could be retrieved inside a separate \nHTTP `GET` request that runs prior to the one updating the lightbulb's status.\n\nKiwi allows you to do just this. Let's have a look on how such a `http` \ndefinition could look like for a Philips Hue connected lightbulb:\n\n```sh\ncurl -X \"POST\" \"http://10.10.10.10:8080/settings/keys/key_1_in_row_1\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{\n          \"object\": {\n            \"keydown\": {\n              \"http\": [\n                {\n                  \"body\": \"{}\",\n                  \"method\": \"get\",\n                  \"headers\": {\n                    \"content-type\": \"application/json\"\n                  },\n                  \"url\": \"http://YOUR-HUE-BRIDGE-IP-HERE/api/YOUR-GENERATED-USERNAME-HERE/lights/1\"\n                },\n                {\n                  \"body\": \"{\\\\\"on\\\\\": \u003c\u003c{Map.get(previous_http_response, :body) |\u003e Jason.decode!() |\u003e Map.get(\\\\\"state\\\\\") |\u003e Map.get(\\\\\"on\\\\\") |\u003e Kernel.not}\u003e\u003e}\",\n                  \"method\": \"put\",\n                  \"headers\": {\n                    \"content-type\": \"application/json\"\n                  },\n                  \"url\": \"http://YOUR-HUE-BRIDGE-IP-HERE/api/YOUR-GENERATED-USERNAME-HERE/lights/1/state\"\n                }\n              ]\n            }\n          }\n        }'\n```\n\nAs you can see, we're doing two requests here. First, we run a `GET` request \nthat retrieves the current status of the Hue lightbulb. In the second request \nthen, we use the the scripted HTTP action feature within the request the body.\nIn order to make the script identifiable to Kiwi, it needs to be surrounded by \n`\u003c\u003c{}\u003e\u003e`. As script language we use Elixir.\n\nEvery request that uses scripting retrieves a variable named \n`previous_http_response` which either contains the HTTP response of the \npreviously executed HTTP request or `nil`. `previous_http_response` is of type \n[`%Mojito.Response{}`](https://github.com/appcues/mojito). In this example, we \nextract the `body` from the `%Mojito.Response{}` (which is a JSON string) and \ndecode it using `Jason.decode!`. Afterwards we `Map.get` the `state` map from \nthe decoded body map and then `Map.get` its `on` property – which is a boolean \nvalue. If `on` is `true`, it means that our light is currently turned on. \nIf it's `false`, it means that it's currently off. Last but not least, we pipe \nthe boolean value to `Kernel.not`, which inverts the boolean state. The the \ninverted state is the return of this script, will be converted to a JSON \nrepresentation and used as a value in the very place our `\u003c\u003c{ ... }\u003e\u003e` is.\n\nThe result of all this: If the current state of the lightbulb is `false`, it's \nbeing inverted to `true` and set as value for the `on` property inside our \nrequest's body. The request will then execute with a JSON body that says \n`{\"on\": true}`, so that the Hue turns the lightbulb on. When we press the key \nanother time, the `GET` request will retrieve `true` inside of \n`response.body.state.on` and the upcoming `PUT` request will fetch this value, \ninvert it and send `{\"on\": false}`, so that the light turns back off.\n\nScripted HTTP actions allow you to do many fancy things with little knowledge \nof HTTP requests and Elixir. However keep in mind that the scripts you write \nare being executed within the same environment in which Kiwi runs and have \npretty much the same permissions (access keys, access LEDs, access your WiFi). \nHence, always make sure to validate data that you retrieve from endpoints you \nhave no control of!\n\n##### LED actions\n\nOf course you can also do crazy things with the integrated LEDs your Keybow has. \nIn order to configure a fancy key-press light-animation, run the following \ncommand:\n\n```sh\ncurl -X POST \"http://10.10.10.10:8080/settings/keys/key_1_in_row_1\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{\n          \"object\": {\n            \"keydown\": {\n              \"led\": {\n                \"frames\": [\n                  {\n                    \"sleep\": 50,\n                    \"keys\": {\n                      \"key_1_in_row_1\": {\n                        \"brightness\": 227,\n                        \"red\": 55,\n                        \"blue\": 0,\n                        \"green\": 0\n                      }\n                    }\n                  },\n                  {\n                    \"sleep\": 50,\n                    \"keys\": {\n                      \"key_1_in_row_1\": {\n                        \"brightness\": 227,\n                        \"red\": 155,\n                        \"blue\": 0,\n                        \"green\": 0\n                      }\n                    }\n                  },\n                  {\n                    \"sleep\": 50,\n                    \"keys\": {\n                      \"key_1_in_row_1\": {\n                        \"brightness\": 227,\n                        \"red\": 255,\n                        \"blue\": 0,\n                        \"green\": 0\n                      }\n                    }\n                  },\n                  {\n                    \"sleep\": 50,\n                    \"keys\": {\n                      \"key_1_in_row_1\": {\n                        \"brightness\": 227,\n                        \"red\": 155,\n                        \"blue\": 0,\n                        \"green\": 0\n                      }\n                    }\n                  },\n                  {\n                    \"sleep\": 50,\n                    \"keys\": {\n                      \"key_1_in_row_1\": {\n                        \"brightness\": 227,\n                        \"red\": 55,\n                        \"blue\": 0,\n                        \"green\": 0\n                      }\n                    }\n                  },\n                  {\n                    \"sleep\": 0,\n                    \"keys\": {\n                      \"key_1_in_row_1\": {\n                        \"brightness\": 227,\n                        \"red\": 0,\n                        \"blue\": 0,\n                        \"green\": 0\n                      }\n                    }\n                  }\n                ]\n              }\n            }\n          }\n        }'\n```\n\nWhen you now press the key, it should light up in red and quickly fade off. \nAs you can see, `frames` is an array of animation frames containing the \nduration between each frame (`sleep`) and the `keys` that should be targeted. \nYou can even specify multiple keys by adding them to the `keys` object.\n\n##### Combined actions\n\nActions can be combined by adding all desired action to the JSON, e.g.:\n\n```sh\ncurl -X \"POST\" \"http://10.10.10.10:8080/settings/keys/key_2_in_row_1\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{\n          \"object\": {\n            \"keydown\": {\n              \"led\": { ... },\n              \"http\": [{ ... }]\n            }\n          }\n        }'\n```\n\n#### **GET** `/settings/animations/:animation`\n\nRetrieve the current configuration for a specific animation.\n\n```sh\ncurl \"http://10.10.10.10:8080/settings/animations/animation_main\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{}'\n```\n\n#### **POST** `/settings/animations/:animation`\n\nThis endpoint allows setting constantly playing LED animations on the keyboard. \n`:animation` defines the animation you'd like to define. By default, Kiwi loads \nthe animation `animation_main`.\n\nYou can configure `animation_main` like this:\n\n```sh\ncurl -X \"POST\" \"http://10.10.10.10:8080/settings/animations/animation_main\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{\n          \"object\": {\n            \"frames\": [\n              {\n                \"sleep\": 500,\n                \"keys\": {\n                  \"key_1_in_row_1\": {\n                    \"red\": 255,\n                    \"green\": 0,\n                    \"blue\": 0\n                  }\n                }\n              },\n              ...\n              ...\n              ...\n              ...\n              ...\n            ]\n          }\n        }'\n```\n\nAlternatively you can use [image2kiwi](https://github.com/mrusme/image2kiwi) to \ngenerate an animation from a JPG, PNG, GIF, etc. Please refer to its \ndocumentation on how image generation works. Here's the curl example of how to \nset a generated animation:\n\n```sh\ncurl -X \"POST\" \"http://10.0.0.219:8080/settings/animations/animation_main\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d \"{\\\"object\\\": {\\\"frames\\\": $(python3 ./image2kiwi.py ./rainbow.gif) }}\"\n```\n\n![Animation](docs/image2kiwi-animation.gif)\n\n*Notice: image2kiwi scales the image to max 3x4px. If you pass an image that's \n300x300px, its aspect will be kept and it will be scaled to 3x3px. Hence, the \nbottom three keys won't light up.*\n\n## Integrations\n\n### Philips Hue Bridge\n\nFind the IP address of your Philips Hue Bridge on your network and create a \ndedicated user for Kiwi:\n\n```sh\ncurl -X \"POST\" \"http://YOUR-HUE-BRIDGE-IP-HERE/api\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{\n          \"devicetype\": \"kiwi#kiwi\"\n        }'\n```\n\nThis request will return an auto-generated username. With this you can then \ncheck all your connected lights:\n\n```sh\ncurl \"http://YOUR-HUE-BRIDGE-IP-HERE/api/YOUR-GENERATED-USERNAME-HERE/lights\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{}'\n```\n\nAfter you've identified the light you'd like to turn on/off, configure two keys:\n\n```sh\ncurl -X \"POST\" \"http://10.10.10.10:8080/settings/keys/key_1_in_row_1\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{\n          \"object\": {\n            \"keydown\": {\n              \"http\": [{\n                \"body\": \"{\\\\\"on\\\\\": true}\",\n                \"method\": \"put\",\n                \"headers\": {\n                  \"content-type\": \"application/json\"\n                },\n                \"url\": \"http://YOUR-HUE-BRIDGE-IP-HERE/api/YOUR-GENERATED-USERNAME-HERE/lights/1/state\"\n              }]\n            }\n          }\n        }'\n```\n\n```sh\ncurl -X \"POST\" \"http://10.10.10.10:8080/settings/keys/key_2_in_row_1\" \\\n     -H 'Content-Type: application/json; charset=utf-8' \\\n     -d $'{\n          \"object\": {\n            \"keydown\": {\n              \"http\": {\n                \"body\": \"{\\\\\"on\\\\\": false}\",\n                \"method\": \"put\",\n                \"headers\": {\n                  \"content-type\": \"application/json\"\n                },\n                \"url\": \"http://YOUR-HUE-BRIDGE-IP-HERE/api/YOUR-GENERATED-USERNAME-HERE/lights/1/state\"\n              }\n            }\n          }\n        }'\n```\n\nThat's it!\n\n![Party party!](docs/philips-hue.gif)\n\n**Want to turn your Philips Hue lights on and off with a single button? Check \nthe advanced [scripted HTTP actions](#scripted-http-actions-advanced-topic) \ntopic!**\n\n## Development\n\n### Requirements\n\n- WiFi\n- Pimoroni Keybow\n- An empty microSD card\n- Elixir installed on your computer\n\n### Installation\n\nMake sure to have Erlang and Elixir installed on your computer!\n\n```bash\n$ git clone https://github.com/mrusme/kiwi.git\n$ cd kiwi\n$ make dependencies\n```\n\n*Note: For development it makes sense that you enable the WiFi configuration \nblock within `config/config.exs`, so that you can pass the WiFi config via \nenvironment variables during the build.*\n\nInsert microSD card into the microSD card reader of your computer and find its \nblock device, e.g. /dev/disk3 (on a Mac), unmount it and use its raw version:\n\n```bash\n$ diskutil umountDisk /dev/disk3\n$ NERVES_NETWORK_KEY_MGMT=WPA-PSK NERVES_NETWORK_SSID=yourWifiNetworkName NERVES_NETWORK_PSK=yourWiFiPassword SD_CARD=/dev/rdisk3 make sdcard\n```\n\nEject the microSD card, insert it into your Keybow's Raspberry Pi Zero and \nconnect it to a power source.\n\n### Configuration\n\nAs soon as the device has booted it should be connected to the WiFi. If it \nisn't you probably screwed up `NERVES_NETWORK_KEY_MGMT`, `NERVES_NETWORK_SSID` \nand/or `NERVES_NETWORK_PSK` and need to re-run the installation with correct \nvalues.\\\nSorry.\n\nCheck your WiFi access point's web-interface to find out the IP address that \nwas assigned to your Keybow. As soon as you found the IP you can start \nconfiguring the device via its API.\n\n### Hardware information\n\nHere's some useful information if you might want to start contributing to this \nproject yourself and want to save yourself from having to browse the \nofficial (undocumented!) Keybow's firmware code.\n\n#### Keybow keyboard GPIO pin IDs\n\n```\n          Keybow\n╔════╦════╦════╗\n║ 20 ║ 16 ║ 26 ║\n╠════╬════╬════╣\n║  6 ║ 12 ║ 13 ║\n╠════╬════╬════╣\n║ 22 ║ 24 ║  5 ║\n╠════╬════╬════╣\n║ 17 ║ 27 ║ 23 ║\n╚════╩════╩════╝\n```\n\n#### APA102 LEDs\n\n![LED frame](docs/led-frame.png)\n\n- 4 bytes padding at the beginning\n- 4 bytes per LED: `brightness, blue, green, red`\n- 4 bytes padding at the end\n\n```elixir\n[\n    0,0,0,0,      \n\n    227,0,0,0, \n    227,0,0,0, \n    227,0,0,0, \n    227,0,0,0, \n    227,0,0,0, \n    227,0,0,0, \n    227,0,0,0, \n    227,0,0,0, \n    227,0,0,0, \n    227,0,0,0, \n    227,0,0,0, \n    227,0,0,0,      \n\n    255,255,255,255\n]\n```\n\n[@Gadgetoid](https://github.com/Gadgetoid) provided very good \n[insight on this topic here](https://github.com/pimoroni/keybow-firmware/issues/23#issuecomment-486163227). \nQuote:\n\n\u003e This magic number is the LED frame marker, which is indicated by 3 high bits set to 1: 0b11100000 plus 5 bits of global (applies to R, G and B LEds in a package) brightness: 0b00011111 (giving an 0-31 brightness range). Since we don't want to drive at maximum brightness, but still want colours to have the full 255255255 range, we use global brightness to handle the dimming across the LEDs.\n\nQuote from the \n[linked wordpress.com site](https://cpldcpu.wordpress.com/2014/08/27/apa102/):\n\n\u003e ...\n\u003e \n\u003e Each update consists of a start frame of 32 zeroes, 32 bits for every LED and \n\u003e an end frame of 32 ones.  I am not sure what the “End Frame” is good for, \n\u003e since its encoding is indistinguishable from a LED set to maximum brightness \n\u003e and will simply be forwarded to the next LED. In my experiments, omitting the \n\u003e end frame did not have any impact.\n\u003e \n\u003e One interesting addition is the “global” field for each LED, which allows to \n\u003e control the brightness of the LED in 32 steps from 0 to 31. When trying \n\u003e different parameters, I was quite surprised to observe that the LEDs did not \n\u003e show any visible pulse-width-modulation (PWM) flicker at all when the global \n\u003e brightness was set to 31. This is quite different from the WS2812, which \n\u003e shows visible PWM flicker when moving the LEDs.\n\u003e \n\u003e Interestingly, the APA102 started to flicker once I set the global brightness \n\u003e to 30 or below.\n\u003e \n\u003e ...\n\nThanks to Tim who apparently runs that blog!\n\n## Troubleshooting\n\n### Have you tried turning it off and on again?\n\nIn most cases, simply unplugging and plugging the Keybow back in will fix every \nissue that might occur during runtime.\n\n### Investigating\n\nYou can connect to your Kiwi instance through SSH:\n\n```sh\nssh root@10.10.10.10\n```\n\nThere you'll end up inside a IEx in which you can start investigating your \nissue. Keep in mind that there will be no Logger output unless you execute \n`RingLogger.next` or attach to the RingLogger using `RingLogger.attach`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrusme%2Fkiwi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrusme%2Fkiwi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrusme%2Fkiwi/lists"}