diff --git a/app/boards/arm/glove80/glove80.keymap b/app/boards/arm/glove80/glove80.keymap index a3b33a3a42b..fa33506d14e 100644 --- a/app/boards/arm/glove80/glove80.keymap +++ b/app/boards/arm/glove80/glove80.keymap @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #define HYPER LC(LS(LG(LALT))) @@ -16,42 +16,84 @@ // layers #define DEFAULT 0 #define LOWER 1 +#define MAGIC 2 +#define FACTORY_TEST 3 / { + behaviors { + // For the "layer" key, it'd nice to be able to use it as either a shift or a toggle. + // Configure it as a tap dance, so the first tap (or hold) is a &mo and the second is a &to + layer_td: tap_dance_0 { + compatible = "zmk,behavior-tap-dance"; + label = "LAYER_TAP_DANCE"; + #binding-cells = <0>; + tapping-term-ms = <200>; + bindings = <&mo LOWER>, <&to LOWER>; + }; + + mht: magic_layer_indicator { + compatible = "zmk,behavior-hold-tap"; + label = "MAGIC_HOLD_TAP"; + #binding-cells = <3>; + flavor = "tap-preferred"; + tapping-term-ms = <200>; + bindings = <&mo>, <&rgb_ug>; + }; + }; + keymap { - compatible = "zmk,keymap"; - - default_layer { - // --------------------------------------------------------------------------------------------------------------------------------- - // | F1 | F2 | F3 | F4 | F5 | | F6 | F7 | F8 | F9 | F10 | - // | = | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | - | - // | TAB | Q | W | E | R | T | | Y | U | I | O | P | \ | - // | ESC | A | S | D | F | G | | H | J | K | L | ; | ' | - // | ` | Z | X | C | V | B | LSHFT | LCTRL | LOWER | | LGUI | RCTRL | RSHFT | N | M | , | . | / | PGUP | - // | MAGIC | HOME| END | LEFT | RIGHT| | BSPC | DEL | LALT | | RALT | RET | SPACE | | UP | DOWN | [ | ] | PGDN | - - - // MAGIC is currently bound to the same as LAYER - these will be fixed later - bindings = < - &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &kp F7 &kp F8 &kp F9 &kp F10 - &kp EQUAL &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp MINUS - &kp TAB &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH - &kp ESC &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SEMI &kp SQT - &kp GRAVE &kp Z &kp X &kp C &kp V &kp B &kp LSHFT &kp LCTRL &mo LOWER &kp LGUI &kp RCTRL &kp RSHFT &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp PG_UP - &mo LOWER &kp HOME &kp END &kp LEFT &kp RIGHT &kp BSPC &kp DEL &kp LALT &kp RALT &kp RET &kp SPACE &kp UP &kp DOWN &kp LBKT &kp RBKT &kp PG_DN - >; - }; - - lower_layer { - bindings = < - &bt BT_SEL 0 &bt BT_SEL 1 &bt BT_SEL 2 &bt BT_SEL 3 &bt BT_SEL 4 &none &none &none &kp F11 &kp F12 - &bt BT_CLR &bt BT_CLR &out OUT_USB &out OUT_BLE &out OUT_TOG &none &none &none &none &none &none &none - &bootloader &rgb_ug RGB_TOG &rgb_ug RGB_EFF &rgb_ug RGB_BRI &rgb_ug RGB_BRD &kp K_VOL_UP &none &none &none &none &none &bootloader - &reset &none &none &none &none &kp K_VOL_DN &none &none &none &none &none &reset - &ext_power EP_ON &ext_power EP_OFF &none &none &none &kp K_MUTE &none &kp HYPER &none &none &none &none &none &none &none &none &none &none - &none &kp CAPS &kp INS &none &none &none &none &none &none &none &none &none &none &none &none &none - >; - }; + compatible = "zmk,keymap"; + + default_layer { + // --------------------------------------------------------------------------------------------------------------------------------- + // | F1 | F2 | F3 | F4 | F5 | | F6 | F7 | F8 | F9 | F10 | + // | = | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | - | + // | TAB | Q | W | E | R | T | | Y | U | I | O | P | \ | + // | ESC | A | S | D | F | G | | H | J | K | L | ; | ' | + // | ` | Z | X | C | V | B | LSHFT | LCTRL | LOWER | | LGUI | RCTRL | RSHFT | N | M | , | . | / | PGUP | + // | MAGIC | HOME| END | LEFT | RIGHT| | BSPC | DEL | LALT | | RALT | RET | SPACE | | UP | DOWN | [ | ] | PGDN | + + bindings = < + &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &kp F7 &kp F8 &kp F9 &kp F10 + &kp EQUAL &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp MINUS + &kp TAB &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp ESC &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SEMI &kp SQT + &kp GRAVE &kp Z &kp X &kp C &kp V &kp B &kp LSHFT &kp LCTRL &layer_td &kp LGUI &kp RCTRL &kp RSHFT &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp PG_UP + &mht MAGIC RGB_STATUS &kp HOME &kp END &kp LEFT &kp RIGHT &kp BSPC &kp DEL &kp LALT &kp RALT &kp RET &kp SPACE &kp UP &kp DOWN &kp LBKT &kp RBKT &kp PG_DN + >; + }; + + lower_layer { + bindings = < + &kp C_BRI_DN &kp C_BRI_UP &kp C_PREV &kp C_NEXT &kp C_PP &kp C_MUTE &kp C_VOL_DN &kp C_VOL_UP &none &kp PAUSE_BREAK + &trans &none &none &kp F11 &kp F12 &kp HOME &kp LPAR &kp KP_NUM &kp KP_EQUAL &kp KP_DIVIDE &kp KP_MULTIPLY &kp PSCRN + &trans &none &none &kp UP &none &kp END &kp RPAR &kp KP_N7 &kp KP_N8 &kp KP_N9 &kp KP_MINUS &kp SLCK + &trans &none &kp LEFT &kp DOWN &kp RIGHT &kp PGUP &kp PRCNT &kp KP_N4 &kp KP_N5 &kp KP_N6 &kp KP_PLUS &none + &trans &kp K_CMENU &none &none &none &kp PGDN &trans &trans &to DEFAULT &trans &trans &trans &kp COMMA &kp KP_N1 &kp KP_N2 &kp KP_N3 &kp KP_ENTER &trans + &trans &kp CAPS &kp INS &kp F11 &kp F12 &trans &trans &trans &trans &trans &trans &kp KP_N0 &kp KP_N0 &kp KP_DOT &kp KP_ENTER &trans + >; + }; + + magic_layer { + bindings = < + &bt BT_CLR &none &none &none &none &none &none &none &none &none &bt BT_CLR_ALL + &none &none &none &none &none &none &none &none &none &none &none &none + &none &rgb_ug RGB_SPI &rgb_ug RGB_SAI &rgb_ug RGB_HUI &rgb_ug RGB_BRI &rgb_ug RGB_TOG &none &none &none &none &none &none + &bootloader &rgb_ug RGB_SPD &rgb_ug RGB_SAD &rgb_ug RGB_HUD &rgb_ug RGB_BRD &rgb_ug RGB_EFF &none &none &none &none &none &bootloader + &reset &none &none &none &none &none &bt BT_SEL 3 &bt BT_SEL 4 &none &none &none &none &none &none &none &none &none &reset + &none &out OUT_TOG &out OUT_BLE &out OUT_USB &none &bt BT_SEL 0 &bt BT_SEL 1 &bt BT_SEL 2 &none &none &none &none &none &none &none &to FACTORY_TEST + >; + }; + + factory_test_layer { + bindings = < + &kp N0 &kp N6 &kp N2 &kp N8 &kp N4 &kp N4 &kp N8 &kp N2 &kp N6 &kp N0 + &kp N1 &kp N7 &kp N3 &kp N9 &kp N5 &kp N0 &kp N0 &kp N5 &kp N9 &kp N3 &kp N7 &kp N1 + &kp N2 &kp N8 &kp N4 &kp N0 &kp N6 &kp N1 &kp N1 &kp N6 &kp N0 &kp N4 &kp N8 &kp N2 + &kp N3 &kp N9 &kp N5 &kp N1 &kp N7 &kp N2 &kp N2 &kp N7 &kp N1 &kp N5 &kp N9 &kp N3 + &kp N4 &kp N0 &kp N6 &kp N2 &kp N8 &kp N3 &kp N4 &kp N5 &kp N6 &kp N6 &kp N5 &kp N4 &kp N3 &kp N8 &kp N2 &kp N6 &kp N0 &kp N4 + &kp N5 &kp N1 &kp N7 &kp N3 &kp N9 &kp N7 &kp N8 &kp N9 &kp N9 &kp N8 &kp N7 &kp N9 &kp N3 &kp N7 &kp N1 &kp N5 + >; + }; }; }; - diff --git a/app/include/dt-bindings/zmk/bt.h b/app/include/dt-bindings/zmk/bt.h index 8ca10606942..539d2cd017e 100644 --- a/app/include/dt-bindings/zmk/bt.h +++ b/app/include/dt-bindings/zmk/bt.h @@ -8,7 +8,7 @@ #define BT_NXT_CMD 1 #define BT_PRV_CMD 2 #define BT_SEL_CMD 3 -// #define BT_FULL_RESET_CMD 4 +#define BT_CLR_ALL_CMD 4 /* Note: Some future commands will include additional parameters, so we @@ -19,3 +19,4 @@ defines these aliases up front. #define BT_NXT BT_NXT_CMD 0 #define BT_PRV BT_PRV_CMD 0 #define BT_SEL BT_SEL_CMD +#define BT_CLR_ALL BT_CLR_ALL_CMD diff --git a/app/include/zmk/ble.h b/app/include/zmk/ble.h index 4380a33a901..8a8b502bcfe 100644 --- a/app/include/zmk/ble.h +++ b/app/include/zmk/ble.h @@ -24,6 +24,7 @@ int zmk_ble_clear_bonds(); int zmk_ble_prof_next(); int zmk_ble_prof_prev(); int zmk_ble_prof_select(uint8_t index); +int zmk_ble_clear_all_bonds(); int zmk_ble_active_profile_index(); bt_addr_le_t *zmk_ble_active_profile_addr(); diff --git a/app/src/behaviors/behavior_bt.c b/app/src/behaviors/behavior_bt.c index 79b805b6bc0..17b000a008c 100644 --- a/app/src/behaviors/behavior_bt.c +++ b/app/src/behaviors/behavior_bt.c @@ -30,6 +30,8 @@ static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, return zmk_ble_prof_prev(); case BT_SEL_CMD: return zmk_ble_prof_select(binding->param2); + case BT_CLR_ALL_CMD: + return zmk_ble_clear_all_bonds(); default: LOG_ERR("Unknown BT command: %d", binding->param1); } diff --git a/app/src/ble.c b/app/src/ble.c index b10aa20b5f7..e0a507d0f50 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -225,6 +225,23 @@ int zmk_ble_clear_bonds() { return 0; }; +int zmk_ble_clear_all_bonds() { + LOG_DBG("zmk_ble_clear_all_bonds()"); + + // Unpair all profiles + for (uint8_t i = 0; i < ZMK_BLE_PROFILE_COUNT; i++) { + if (bt_addr_le_cmp(&profiles[i].peer, BT_ADDR_LE_ANY)) { + bt_unpair(BT_ID_DEFAULT, &profiles[i].peer); + set_profile_address(i, BT_ADDR_LE_ANY); + } + } + + // Automatically switch to profile 0 + zmk_ble_prof_select(0); + + return 0; +}; + int zmk_ble_active_profile_index() { return active_profile; } #if IS_ENABLED(CONFIG_SETTINGS) diff --git a/lambda/Gemfile b/lambda/Gemfile new file mode 100644 index 00000000000..a5e25f43a26 --- /dev/null +++ b/lambda/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org' +gem 'aws_lambda_ric' diff --git a/lambda/Gemfile.lock b/lambda/Gemfile.lock new file mode 100644 index 00000000000..8b6c1f95c58 --- /dev/null +++ b/lambda/Gemfile.lock @@ -0,0 +1,13 @@ +GEM + remote: https://rubygems.org/ + specs: + aws_lambda_ric (2.0.0) + +PLATFORMS + ruby + +DEPENDENCIES + aws_lambda_ric + +BUNDLED WITH + 2.1.4 diff --git a/lambda/app.rb b/lambda/app.rb new file mode 100644 index 00000000000..621a6cfea68 --- /dev/null +++ b/lambda/app.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require 'tmpdir' +require 'json' +require 'base64' + +module LambdaFunction + class Handler + class << self + # ALB event structure: + # { + # "requestContext": { }, + # "httpMethod": "GET", + # "path": "/", + # "queryStringParameters": {parameters}, + # "headers": { }, + # "isBase64Encoded": false, + # "body": "request_body" + # } + # + # Handle the single route: POST /compile + def process(event:, context:) + unless event['path'] == '/compile' + return error_response(404, error: "Unknown route: #{event['path']}") + end + + unless event['httpMethod'] == 'POST' + return error_response(404, error: "No route for HTTP method : #{event['httpMethod']}") + end + + keymap_data = event['body'] + + unless keymap_data + return error_response(400, error: 'Missing POST body') + end + + if event['isBase64Encoded'] + keymap_data = Base64.decode64(keymap_data) + end + + compile(keymap_data) + end + + private + + def compile(keymap_data) + in_build_dir do + File.open('build.keymap', 'w') do |io| + io.write(keymap_data) + end + + compile_output = nil + + IO.popen(['compileZmk', './build.keymap'], err: [:child, :out]) do |io| + compile_output = io.read + end + + compile_output = compile_output.split("\n") + + unless $?.success? + status = $?.exitstatus + return error_response(400, error: "Compile failed with exit status #{status}", detail: compile_output) + end + + unless File.exist?('zephyr/zmk.uf2') + return error_response(500, error: 'Compile failed to produce result binary', detail: compile_output) + end + + file_response(File.read('zephyr/zmk.uf2'), compile_output) + rescue StandardError => e + error_response(500, error: 'Unexpected error', detail: e.message) + end + end + + # Lambda is single-process per container, and we get substantial speedups + # from ccache by always building in the same path + BUILD_DIR = '/tmp/build' + + def in_build_dir + FileUtils.remove_entry(BUILD_DIR, true) + Dir.mkdir(BUILD_DIR) + Dir.chdir(BUILD_DIR) + yield + ensure + FileUtils.remove_entry(BUILD_DIR, true) rescue nil + end + + def file_response(file, compile_output) + file64 = Base64.strict_encode64(file) + + headers = { + 'Content-Type' => 'application/octet-stream', + } + + headers.merge!('X-Debug-Output' => compile_output.to_json) if ENV.include?('DEBUG') + + { + 'isBase64Encoded' => true, + 'statusCode' => 200, + 'body' => file64, + 'headers' => headers, + } + end + + def error_response(code, error:, detail: nil) + { + 'isBase64Encoded' => false, + 'statusCode' => code, + 'body' => { error: error, detail: detail }.to_json, + 'headers' => { + 'Content-Type' => 'application/json' + } + } + end + end + end +end diff --git a/lambda/default.nix b/lambda/default.nix new file mode 100644 index 00000000000..70caabff7fa --- /dev/null +++ b/lambda/default.nix @@ -0,0 +1,26 @@ +{ pkgs ? import ./nix/pinned-nixpkgs.nix }: + +with pkgs; + +let + bundleEnv = bundlerEnv { + name = "lambda-bundler-env"; + inherit ruby; + gemfile = ./Gemfile; + lockfile = ./Gemfile.lock; + gemset = ./gemset.nix; + }; + + source = stdenv.mkDerivation { + name = "lambda-builder"; + version = "0.0.1"; + src = ./.; + installPhase = '' + cp -r ./ $out + ''; + }; + +in +{ + inherit bundleEnv source; +} diff --git a/lambda/gemset.nix b/lambda/gemset.nix new file mode 100644 index 00000000000..6b2fd1a0207 --- /dev/null +++ b/lambda/gemset.nix @@ -0,0 +1,12 @@ +{ + aws_lambda_ric = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "19c4xlgnhgwf3n3z57z16nmr76jd2vihhshknm5zqip2g00awhi1"; + type = "gem"; + }; + version = "2.0.0"; + }; +} diff --git a/nix/ccache.nix b/nix/ccache.nix new file mode 100644 index 00000000000..030153140e2 --- /dev/null +++ b/nix/ccache.nix @@ -0,0 +1,43 @@ +{ stdenv, lib, makeWrapper, ccache +, unwrappedCC ? stdenv.cc.cc, extraConfig ? "" }: + +# copied from ccache in nixpkgs, modified to glob over prefixes. Also doesn't +# pass lib. Why was it passing lib? +stdenv.mkDerivation { + name = "ccache-links"; + passthru = { + isClang = unwrappedCC.isClang or false; + isGNU = unwrappedCC.isGNU or false; + }; + nativeBuildInputs = [ makeWrapper ]; + buildCommand = '' + mkdir -p $out/bin + + wrap() { + local cname="$(basename $1)" + if [ -x "${unwrappedCC}/bin/$cname" ]; then + echo "Wrapping $1" + makeWrapper ${ccache}/bin/ccache $out/bin/$cname \ + --run ${lib.escapeShellArg extraConfig} \ + --add-flags ${unwrappedCC}/bin/$cname + fi + } + + wrapAll() { + for prog in "$@"; do + wrap "$prog" + done + } + + wrapAll ${unwrappedCC}/bin/{*cc,*c++,*gcc,*g++,*clang,*clang++} + + for executable in $(ls ${unwrappedCC}/bin); do + if [ ! -x "$out/bin/$executable" ]; then + ln -s ${unwrappedCC}/bin/$executable $out/bin/$executable + fi + done + for file in $(ls ${unwrappedCC} | grep -vw bin); do + ln -s ${unwrappedCC}/$file $out/$file + done + ''; +} diff --git a/release.nix b/release.nix new file mode 100644 index 00000000000..5f93081f367 --- /dev/null +++ b/release.nix @@ -0,0 +1,128 @@ +{ pkgs ? import {} }: + +let + lib = pkgs.lib; + zmkPkgs = (import ./default.nix { inherit pkgs; }); + lambda = (import ./lambda { inherit pkgs; }); + ccacheWrapper = pkgs.callPackage ./nix/ccache.nix {}; + + nix-utils = pkgs.fetchFromGitHub { + owner = "iknow"; + repo = "nix-utils"; + rev = "c13c7a23836c8705452f051d19fc4dff05533b53"; + sha256 = "0ax7hld5jf132ksdasp80z34dlv75ir0ringzjs15mimrkw8zcac"; + }; + + ociTools = pkgs.callPackage "${nix-utils}/oci" {}; + + inherit (zmkPkgs) zmk zephyr; + + accounts = { + users.deploy = { + uid = 999; + group = "deploy"; + home = "/home/deploy"; + shell = "/bin/sh"; + }; + groups.deploy.gid = 999; + }; + + baseLayer = { + name = "base-layer"; + path = [ pkgs.busybox ]; + entries = ociTools.makeFilesystem { + inherit accounts; + tmp = true; + usrBinEnv = "${pkgs.busybox}/bin/env"; + binSh = "${pkgs.busybox}/bin/sh"; + }; + }; + + depsLayer = { + name = "deps-layer"; + path = [ pkgs.ccache ]; + includes = zmk.buildInputs ++ zmk.nativeBuildInputs ++ zmk.zephyrModuleDeps; + }; + + zmkCompileScript = let + zmk' = zmk.override { + gcc-arm-embedded = ccacheWrapper.override { + unwrappedCC = pkgs.gcc-arm-embedded; + }; + }; + in pkgs.writeShellScriptBin "compileZmk" '' + set -eo pipefail + + if [ ! -f "$1" ]; then + echo "Usage: compileZmk [file.keymap]" >&2 + exit 1 + fi + + KEYMAP="$(${pkgs.busybox}/bin/realpath $1)" + export PATH=${lib.makeBinPath (with pkgs; zmk'.nativeBuildInputs ++ [ ccache ])}:$PATH + export CMAKE_PREFIX_PATH=${zephyr} + + export CCACHE_BASEDIR=$PWD + export CCACHE_NOHASHDIR=t + export CCACHE_COMPILERCHECK=none + + if [ -n "$DEBUG" ]; then ccache -z; fi + + cmake -G Ninja -S ${zmk'.src}/app ${lib.escapeShellArgs zmk'.cmakeFlags} "-DUSER_CACHE_DIR=/tmp/.cache" "-DKEYMAP_FILE=$KEYMAP" + ninja + + if [ -n "$DEBUG" ]; then ccache -s; fi + ''; + + ccacheCache = pkgs.runCommandNoCC "ccache-cache" { + nativeBuildInputs = [ zmkCompileScript ]; + } '' + export CCACHE_DIR=$out + + mkdir /tmp/build + cd /tmp/build + + compileZmk ${zmk.src}/app/boards/arm/glove80/glove80.keymap + ''; + + appLayer = { + name = "app-layer"; + path = [ startLambda zmkCompileScript ]; + }; + + entrypoint = pkgs.writeShellScriptBin "entrypoint" '' + set -euo pipefail + + if [ ! -d "$CCACHE_DIR" ]; then + cp -r ${ccacheCache} "$CCACHE_DIR" + chmod -R u=rwX,go=u-w "$CCACHE_DIR" + fi + + if [ ! -d /tmp/build ]; then + mkdir /tmp/build + fi + + exec "$@" + ''; + + startLambda = pkgs.writeShellScriptBin "startLambda" '' + set -euo pipefail + export PATH=${lib.makeBinPath [ zmkCompileScript ]}:$PATH + cd ${lambda.source} + ${lambda.bundleEnv}/bin/bundle exec aws_lambda_ric "app.LambdaFunction::Handler.process" + ''; + + lambdaImage = ociTools.makeSimpleImage { + name = "zmk-builder-lambda"; + layers = [ baseLayer depsLayer appLayer ]; + config = { + User = "deploy"; + WorkingDir = "/tmp"; + Entrypoint = [ "${entrypoint}/bin/entrypoint" ]; + Cmd = [ "startLambda" ]; + Env = [ "CCACHE_DIR=/tmp/ccache" ]; + }; + }; +in { + inherit lambdaImage zmkCompileScript ccacheCache; +}