mirror of
https://github.com/zmkfirmware/zmk.git
synced 2026-03-19 20:45:18 -05:00
Compare commits
78 Commits
v0.2-branc
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f40e7a4441 | ||
|
|
9a8fdd66ea | ||
|
|
0b5a103c18 | ||
|
|
f09e551929 | ||
|
|
ee69b9e3c7 | ||
|
|
5144de677d | ||
|
|
5225952f96 | ||
|
|
9fcce45cb5 | ||
|
|
62007e500b | ||
|
|
ccf20a1f5c | ||
|
|
c7fae18ae1 | ||
|
|
61da930ed5 | ||
|
|
3609ac6fc8 | ||
|
|
45700887ba | ||
|
|
a8a392807e | ||
|
|
f3233c1b60 | ||
|
|
919bce7962 | ||
|
|
cc19ff7c5b | ||
|
|
90363719a2 | ||
|
|
e57bf5af37 | ||
|
|
9aaf87c6d2 | ||
|
|
edf5c0814f | ||
|
|
6c266b7123 | ||
|
|
c25e927a2f | ||
|
|
c86f0d6ff4 | ||
|
|
af967667b0 | ||
|
|
2ae5185419 | ||
|
|
1530ae36c2 | ||
|
|
1bac680c4f | ||
|
|
d09087f4dc | ||
|
|
6d7bbc8670 | ||
|
|
8059e671b2 | ||
|
|
61325ee82c | ||
|
|
342d838913 | ||
|
|
7292df02d4 | ||
|
|
fe91cc6625 | ||
|
|
cef7af4408 | ||
|
|
9e905d6593 | ||
|
|
e93cd31a58 | ||
|
|
6b44d33db2 | ||
|
|
462d48b78e | ||
|
|
eb99b4ede0 | ||
|
|
239baa4875 | ||
|
|
9da5d3ba82 | ||
|
|
f568b25e56 | ||
|
|
cb00077273 | ||
|
|
461f5c832f | ||
|
|
879cff7595 | ||
|
|
ad6a181d7e | ||
|
|
f1b944b1ef | ||
|
|
4235c8b491 | ||
|
|
e3030bfcc8 | ||
|
|
00ff486931 | ||
|
|
c4ee8ab86b | ||
|
|
d9576c5534 | ||
|
|
c6738ce2e5 | ||
|
|
2a7ab8ed0a | ||
|
|
2c0e7daced | ||
|
|
84772ebf14 | ||
|
|
7823a43f62 | ||
|
|
90bca78300 | ||
|
|
a34839f001 | ||
|
|
6f85f48b19 | ||
|
|
9aadc3e5ab | ||
|
|
1c76bcb0a1 | ||
|
|
d733fbafa5 | ||
|
|
49f86f7ed0 | ||
|
|
5bb39ec3ea | ||
|
|
147c340c6e | ||
|
|
5ba7e260f4 | ||
|
|
bffbccc748 | ||
|
|
b366df8b17 | ||
|
|
4da89bd997 | ||
|
|
5d9920406c | ||
|
|
f5a838b4bd | ||
|
|
eb170c930f | ||
|
|
241ff39556 | ||
|
|
f20e6ea759 |
2
.github/workflows/ble-test.yml
vendored
2
.github/workflows/ble-test.yml
vendored
@@ -61,7 +61,7 @@ jobs:
|
||||
- name: Enable babblesim group filter
|
||||
run: west config manifest.group-filter -- +babblesim
|
||||
- name: Update modules (west update)
|
||||
run: west update
|
||||
run: west update --fetch-opt=--filter=tree:0
|
||||
- name: Export Zephyr CMake package (west zephyr-export)
|
||||
run: west zephyr-export
|
||||
- name: Build BabbleSim components
|
||||
|
||||
9
.github/workflows/build-user-config.yml
vendored
9
.github/workflows/build-user-config.yml
vendored
@@ -34,13 +34,10 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install yaml2json
|
||||
run: python3 -m pip install remarshal
|
||||
|
||||
- name: Fetch Build Matrix
|
||||
run: |
|
||||
echo "build_matrix=$(yaml2json '${{ inputs.build_matrix_path }}' | jq -c .)" >> $GITHUB_ENV
|
||||
yaml2json "${{ inputs.build_matrix_path }}" | jq
|
||||
echo "build_matrix=$(yq -oj -I0 '${{ inputs.build_matrix_path }}')" >> $GITHUB_ENV
|
||||
yq -oj "${{ inputs.build_matrix_path }}"
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -117,7 +114,7 @@ jobs:
|
||||
|
||||
- name: West Update
|
||||
working-directory: ${{ env.base_dir }}
|
||||
run: west update
|
||||
run: west update --fetch-opt=--filter=tree:0
|
||||
|
||||
- name: West Zephyr export
|
||||
working-directory: ${{ env.base_dir }}
|
||||
|
||||
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
- name: Initialize workspace (west init)
|
||||
run: west init -l app
|
||||
- name: Update modules (west update)
|
||||
run: west update
|
||||
run: west update --fetch-opt=--filter=tree:0
|
||||
- name: Export Zephyr CMake package (west zephyr-export)
|
||||
run: west zephyr-export
|
||||
- name: Use Node.js
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
- name: Install @actions/artifact
|
||||
run: npm install @actions/artifact
|
||||
- name: Build
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
id: boards-list
|
||||
with:
|
||||
script: |
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
throw new Error('Failed to build one or more configurations');
|
||||
}
|
||||
- name: Upload artifacts
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
continue-on-error: ${{ github.event_name == 'pull_request' }}
|
||||
id: boards-upload
|
||||
with:
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
include-list: ${{ steps.compile-list.outputs.result }}
|
||||
steps:
|
||||
- name: Join build lists
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
id: compile-list
|
||||
with:
|
||||
script: |
|
||||
@@ -196,7 +196,7 @@ jobs:
|
||||
node-version: "14.x"
|
||||
- name: Install js-yaml
|
||||
run: npm install js-yaml
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
id: core-list
|
||||
with:
|
||||
script: |
|
||||
@@ -225,7 +225,7 @@ jobs:
|
||||
node-version: "14.x"
|
||||
- name: Install js-yaml
|
||||
run: npm install js-yaml
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
id: boards-list
|
||||
with:
|
||||
script: |
|
||||
@@ -302,7 +302,7 @@ jobs:
|
||||
nightly-include: ${{ steps.nightly-list.outputs.result }}
|
||||
steps:
|
||||
- name: Create nightly list
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
id: nightly-list
|
||||
with:
|
||||
script: |
|
||||
@@ -355,7 +355,7 @@ jobs:
|
||||
- name: Install js-yaml
|
||||
run: npm install js-yaml
|
||||
- name: Aggregate Metadata
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
id: aggregate-metadata
|
||||
with:
|
||||
script: |
|
||||
@@ -373,7 +373,7 @@ jobs:
|
||||
result-encoding: string
|
||||
|
||||
- name: Organize Metadata
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
id: organize-metadata
|
||||
with:
|
||||
script: |
|
||||
@@ -430,12 +430,12 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: tj-actions/changed-files@v45
|
||||
- uses: tj-actions/changed-files@9200e69727eb73eb060652b19946b8a2fdfb654b # pin to v45.0.8 due to https://github.com/tj-actions/changed-files/issues/2463 https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised
|
||||
id: changed-files
|
||||
with:
|
||||
json: true
|
||||
escape_json: false
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
id: board-changes
|
||||
with:
|
||||
script: |
|
||||
@@ -443,7 +443,7 @@ jobs:
|
||||
const boardChanges = changedFiles.filter(f => f.startsWith('app/boards'));
|
||||
return boardChanges.length ? 'true' : 'false';
|
||||
result-encoding: string
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
id: core-changes
|
||||
with:
|
||||
script: |
|
||||
|
||||
66
.github/workflows/release-please.yml
vendored
66
.github/workflows/release-please.yml
vendored
@@ -11,28 +11,70 @@ permissions:
|
||||
name: release-please
|
||||
|
||||
jobs:
|
||||
release-please:
|
||||
handle-commit:
|
||||
name: Handle new commit
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_created: ${{ steps.release.outputs.release_created }}
|
||||
major: ${{ steps.release.outputs.major }}
|
||||
minor: ${{ steps.release.outputs.minor }}
|
||||
patch: ${{ steps.release.outputs.patch }}
|
||||
steps:
|
||||
- uses: googleapis/release-please-action@v4
|
||||
id: release
|
||||
with:
|
||||
token: ${{ secrets.ZMK_RELEASE_PLEASE_TOKEN }}
|
||||
target-branch: ${{ github.ref_name }}
|
||||
|
||||
release-new-version:
|
||||
name: Release new version
|
||||
needs: handle-commit
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ needs.handle-commit.outputs.release_created }}
|
||||
env:
|
||||
ZMK_RELEASE_PLEASE_TOKEN: ${{ secrets.ZMK_RELEASE_PLEASE_TOKEN }}
|
||||
VERSION: v${{ needs.handle-commit.outputs.major }}.${{ needs.handle-commit.outputs.minor }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ steps.release.outputs.release_created }}
|
||||
- name: create major, minor branch
|
||||
if: ${{ steps.release.outputs.release_created && steps.release.outputs.patch == '0' }}
|
||||
|
||||
- name: Create major.minor branch
|
||||
if: ${{ needs.handle-commit.outputs.patch == '0' }}
|
||||
run: |
|
||||
git remote add gh-token-branch "https://x-access-token:${{ secrets.ZMK_RELEASE_PLEASE_TOKEN }}@github.com/${{ github.repository }}.git"
|
||||
git checkout -b v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}-branch
|
||||
git push gh-token-branch v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}-branch
|
||||
git remote add gh-token-branch "https://x-access-token:$ZMK_RELEASE_PLEASE_TOKEN@github.com/${{ github.repository }}.git"
|
||||
git checkout -b $VERSION-branch
|
||||
git push gh-token-branch $VERSION-branch
|
||||
|
||||
- name: tag major and minor versions
|
||||
if: ${{ steps.release.outputs.release_created }}
|
||||
run: |
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
git remote add gh-token "https://x-access-token:${{ secrets.ZMK_RELEASE_PLEASE_TOKEN }}@github.com/${{ github.repository }}.git"
|
||||
git tag -d v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true
|
||||
git tag -a v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} -m "Release v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}"
|
||||
git push --force gh-token v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}
|
||||
git remote add gh-token "https://x-access-token:$ZMK_RELEASE_PLEASE_TOKEN@github.com/${{ github.repository }}.git"
|
||||
git tag -d $VERSION || true
|
||||
git tag -a $VERSION -m "Release $VERSION"
|
||||
git push --force gh-token $VERSION
|
||||
|
||||
bump-user-config-template-version:
|
||||
name: Bump user config template version
|
||||
needs: handle-commit
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ needs.handle-commit.outputs.release_created }}
|
||||
env:
|
||||
ZMK_RELEASE_PLEASE_TOKEN: ${{ secrets.ZMK_RELEASE_PLEASE_TOKEN }}
|
||||
VERSION: v${{ needs.handle-commit.outputs.major }}.${{ needs.handle-commit.outputs.minor }}
|
||||
steps:
|
||||
- name: Bump user config template
|
||||
run: |
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "VERSION is not set, exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git clone "https://x-access-token:$ZMK_RELEASE_PLEASE_TOKEN@github.com/zmkfirmware/unified-zmk-config-template.git"
|
||||
cd unified-zmk-config-template
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
sed -i 's/^\(\s*\)revision: .*/\1revision: '"$VERSION"'/' config/west.yml
|
||||
sed -i 's|uses: zmkfirmware/zmk/.github/workflows/build-user-config.yml@.*|uses: zmkfirmware/zmk/.github/workflows/build-user-config.yml@'"$VERSION"'|' .github/workflows/build.yml
|
||||
git add .
|
||||
git commit -m "Version bump to $VERSION"
|
||||
git push origin main
|
||||
|
||||
28
.github/workflows/stale.yml
vendored
Normal file
28
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: "Mark and close stale PRs in the repo"
|
||||
on:
|
||||
schedule:
|
||||
- cron: "00 14 * * *" # runs daily at 14:00 https://crontab.guru/#00_14_*_*_*
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/stale@v9.1.0
|
||||
with:
|
||||
days-before-pr-stale: 300 # ~10 months
|
||||
stale-pr-label: "stale"
|
||||
stale-pr-message: >
|
||||
This PR has been automatically marked as stale because it has not
|
||||
had activity in 10 months. It will be closed in 14 days if no
|
||||
further activity occurs. Feel free to give a status update or
|
||||
re-open when it has been rebased and is ready for review (again).
|
||||
Thanks!
|
||||
days-before-pr-close: 14
|
||||
close-pr-message: >
|
||||
This PR was closed because it had no activity for over 10 months.
|
||||
Feel free to give a status update or re-open when it has been
|
||||
rebased and is ready for review (again).
|
||||
days-before-issue-stale: 1000 # ~3 years
|
||||
days-before-issue-close: -1
|
||||
ascending: true # Process older PRs first
|
||||
operations-per-run: 30 # Default value, listed here again to make it explicit
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -61,7 +61,7 @@ jobs:
|
||||
- name: Initialize workspace (west init)
|
||||
run: west init -l app
|
||||
- name: Update modules (west update)
|
||||
run: west update
|
||||
run: west update --fetch-opt=--filter=tree:0
|
||||
- name: Export Zephyr CMake package (west zephyr-export)
|
||||
run: west zephyr-export
|
||||
- name: Test ${{ matrix.test }}
|
||||
|
||||
@@ -12,8 +12,8 @@ repos:
|
||||
types_or: [c++, c]
|
||||
args:
|
||||
- -i
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v2.7.1
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: 787fb9f542b140ba0b2aced38e6a3e68021647a3
|
||||
hooks:
|
||||
- id: prettier
|
||||
exclude: |
|
||||
@@ -22,9 +22,6 @@ repos:
|
||||
CHANGELOG.md|
|
||||
.release-please-manifest.json
|
||||
)$
|
||||
# Workaround for https://github.com/pre-commit/mirrors-prettier/issues/29
|
||||
additional_dependencies:
|
||||
- prettier@2.8.7
|
||||
- repo: https://github.com/jorisroovers/gitlint
|
||||
rev: v0.19.1
|
||||
hooks:
|
||||
|
||||
@@ -1 +1 @@
|
||||
{".":"0.2.0"}
|
||||
{".":"0.3.0"}
|
||||
|
||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"ms-python.black-formatter",
|
||||
"ms-python.python",
|
||||
"ms-vscode.cpptools",
|
||||
"plorefice.devicetree",
|
||||
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -3,7 +3,7 @@
|
||||
"*.overlay": "dts",
|
||||
"*.keymap": "dts"
|
||||
},
|
||||
"python.formatting.provider": "black",
|
||||
"python.analysis.include": ["app/scripts", "zephyr/scripts"],
|
||||
"[c]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"[python]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "ms-python.python"
|
||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||
},
|
||||
"[css][json][jsonc][html][markdown][yaml]": {
|
||||
"editor.formatOnSave": true,
|
||||
|
||||
54
CHANGELOG.md
54
CHANGELOG.md
@@ -1,5 +1,59 @@
|
||||
# Changelog
|
||||
|
||||
## [0.3.0](https://github.com/zmkfirmware/zmk/compare/v0.2.1...v0.3.0) (2025-08-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ble:** Add function to get profile address by index ([#2992](https://github.com/zmkfirmware/zmk/issues/2992)) ([9e905d6](https://github.com/zmkfirmware/zmk/commit/9e905d65936348824588dc3f424755353ac61186))
|
||||
* **ci:** Add stale GitHub Action to automatically close stale PRs ([#2924](https://github.com/zmkfirmware/zmk/issues/2924)) ([6d7bbc8](https://github.com/zmkfirmware/zmk/commit/6d7bbc8670d175fd63e8c834feb41f80e7b52e74))
|
||||
* **display:** nice!view individual profile status ([#2265](https://github.com/zmkfirmware/zmk/issues/2265)) ([d09087f](https://github.com/zmkfirmware/zmk/commit/d09087f4dc280b8fdb1d32d63b03cc10162b89ce))
|
||||
* Full-Duplex Wired Split ([#2766](https://github.com/zmkfirmware/zmk/issues/2766)) ([147c340](https://github.com/zmkfirmware/zmk/commit/147c340c6e8d377304acfdd64dc86cf83ebdfef2))
|
||||
* **metadata:** Add metadata to the mouse_key_press behavior ([#2950](https://github.com/zmkfirmware/zmk/issues/2950)) ([239baa4](https://github.com/zmkfirmware/zmk/commit/239baa487509ace108d36f0e5c627d61a3d95f53))
|
||||
* **pointing:** Allow peripheral input processing to stop propagation ([#2844](https://github.com/zmkfirmware/zmk/issues/2844)) ([462d48b](https://github.com/zmkfirmware/zmk/commit/462d48b78edac8bedb75666699ea4fa446d2152c))
|
||||
* **shield:** Add underglow for reviung5 ([#2191](https://github.com/zmkfirmware/zmk/issues/2191)) ([ad6a181](https://github.com/zmkfirmware/zmk/commit/ad6a181d7ec34fb6e31134f6bb991a9b2d0b8f78))
|
||||
* **shields:** Add a physical layout for a_dux ([#3000](https://github.com/zmkfirmware/zmk/issues/3000)) ([7292df0](https://github.com/zmkfirmware/zmk/commit/7292df02d4b05d783f432f8658de22d940909fe4))
|
||||
* **shields:** Add physical layouts for tester_xiao and tester_pro_micro ([#2852](https://github.com/zmkfirmware/zmk/issues/2852)) ([eb170c9](https://github.com/zmkfirmware/zmk/commit/eb170c930f56e3fb3df0b813d987abfd1dc31b9a))
|
||||
* **shields:** Add tester_pro_micro layouts ([eb170c9](https://github.com/zmkfirmware/zmk/commit/eb170c930f56e3fb3df0b813d987abfd1dc31b9a))
|
||||
* **shields:** Add tester_xiao layouts ([eb170c9](https://github.com/zmkfirmware/zmk/commit/eb170c930f56e3fb3df0b813d987abfd1dc31b9a))
|
||||
* **split:** Add full-duplex wired split support ([147c340](https://github.com/zmkfirmware/zmk/commit/147c340c6e8d377304acfdd64dc86cf83ebdfef2))
|
||||
* **split:** Runtime selection of split transport ([6b44d33](https://github.com/zmkfirmware/zmk/commit/6b44d33db2f4bad7d98e475e6f7968493b05af73))
|
||||
* **split:** Runtime selection of split transport ([#2886](https://github.com/zmkfirmware/zmk/issues/2886)) ([6b44d33](https://github.com/zmkfirmware/zmk/commit/6b44d33db2f4bad7d98e475e6f7968493b05af73))
|
||||
* **split:** Suspend/resume wired UART devices. ([6b44d33](https://github.com/zmkfirmware/zmk/commit/6b44d33db2f4bad7d98e475e6f7968493b05af73))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **behaviors:** Correct macro release state for parametrized ([1bac680](https://github.com/zmkfirmware/zmk/commit/1bac680c4fb4f07e43c01754b6f1e72dab455e50))
|
||||
* **behaviors:** Correct macro release state for parametrized macros ([#2942](https://github.com/zmkfirmware/zmk/issues/2942)) ([1bac680](https://github.com/zmkfirmware/zmk/commit/1bac680c4fb4f07e43c01754b6f1e72dab455e50))
|
||||
* **ble,hid:** Fix smooth scrolling over BLE ([#2998](https://github.com/zmkfirmware/zmk/issues/2998)) ([342d838](https://github.com/zmkfirmware/zmk/commit/342d83891301b1be53233a12c7723bb99cbe5ff6)), closes [#2957](https://github.com/zmkfirmware/zmk/issues/2957)
|
||||
* **boards:** Disable high voltage DC-DC by default ([#2995](https://github.com/zmkfirmware/zmk/issues/2995)) ([8059e67](https://github.com/zmkfirmware/zmk/commit/8059e671b24a261939401afb5a65c4fa756adc2d)), closes [#2990](https://github.com/zmkfirmware/zmk/issues/2990)
|
||||
* changed shebang to make scripts more platform independent ([#2893](https://github.com/zmkfirmware/zmk/issues/2893)) ([84772eb](https://github.com/zmkfirmware/zmk/commit/84772ebf14e5a7c67ba573a61f0a50048802c799))
|
||||
* **ci:** pin tj-actions/changed-files due to compromise ([#2874](https://github.com/zmkfirmware/zmk/issues/2874)) ([4da89bd](https://github.com/zmkfirmware/zmk/commit/4da89bd99716bf6c1d7d788f3cdaec4cee7403e9))
|
||||
* **combos:** Properly clean up all old candidates. ([#2928](https://github.com/zmkfirmware/zmk/issues/2928)) ([e3030bf](https://github.com/zmkfirmware/zmk/commit/e3030bfcc87b7f511b0ebe993fb1f1f06215982e))
|
||||
* **combos:** Restore prompts for two deprecated Kconfigs ([#2926](https://github.com/zmkfirmware/zmk/issues/2926)) ([00ff486](https://github.com/zmkfirmware/zmk/commit/00ff48693113ed74a3345aa1ac81fdea302b3a09))
|
||||
* **core:** Correctly sync BAS battery level ([#2977](https://github.com/zmkfirmware/zmk/issues/2977)) ([af96766](https://github.com/zmkfirmware/zmk/commit/af967667b0e139a963178e63028c7be341cade9e))
|
||||
* **display:** Make stock battery widget depend on the right symbol ([#2953](https://github.com/zmkfirmware/zmk/issues/2953)) ([9da5d3b](https://github.com/zmkfirmware/zmk/commit/9da5d3ba82b38b74ad798a82a838d84c52220bbe))
|
||||
* **docs:** Fix soft off waker configuration example ([#2960](https://github.com/zmkfirmware/zmk/issues/2960)) ([eb99b4e](https://github.com/zmkfirmware/zmk/commit/eb99b4ede06bc01674ce16217ebbad40bc11ec50))
|
||||
* **docs:** remove title as alt text ([#2922](https://github.com/zmkfirmware/zmk/issues/2922)) ([d9576c5](https://github.com/zmkfirmware/zmk/commit/d9576c55346acfc8eed36709aaae28f91e0d06ad))
|
||||
* Fix build with Studio and USB but not UART ([#2996](https://github.com/zmkfirmware/zmk/issues/2996)) ([cef7af4](https://github.com/zmkfirmware/zmk/commit/cef7af4408cc44c20fab93a0b2e20b3429d0a98e))
|
||||
* **hid:** Fix scroll value truncation ([#2865](https://github.com/zmkfirmware/zmk/issues/2865)) ([2c0e7da](https://github.com/zmkfirmware/zmk/commit/2c0e7daced1421c34a9d417b7d3e9bccbf0ebd7f)), closes [#2864](https://github.com/zmkfirmware/zmk/issues/2864)
|
||||
* **pointing:** Avoids mutex leak for default layer toggle event ([#2934](https://github.com/zmkfirmware/zmk/issues/2934)) ([461f5c8](https://github.com/zmkfirmware/zmk/commit/461f5c832fb8854d87dca54d113d306323697219))
|
||||
* Properly override stack size on RP2040 ([147c340](https://github.com/zmkfirmware/zmk/commit/147c340c6e8d377304acfdd64dc86cf83ebdfef2))
|
||||
* **split:** add source to battery event ([#2901](https://github.com/zmkfirmware/zmk/issues/2901)) ([6f85f48](https://github.com/zmkfirmware/zmk/commit/6f85f48b19afae04f98e9abacb36ce1425b61f78))
|
||||
* **split:** Compile and run properly in wired polling mode. ([#3012](https://github.com/zmkfirmware/zmk/issues/3012)) ([2ae5185](https://github.com/zmkfirmware/zmk/commit/2ae51854192aed92af95536f79547e2928cd1bf5))
|
||||
* **split:** Conditionally build all split code ([#2884](https://github.com/zmkfirmware/zmk/issues/2884)) ([5bb39ec](https://github.com/zmkfirmware/zmk/commit/5bb39ec3eae23055593350cb3689a8240028181e))
|
||||
* **split:** Enable wired split by default if DTS is set ([#3010](https://github.com/zmkfirmware/zmk/issues/3010)) ([1530ae3](https://github.com/zmkfirmware/zmk/commit/1530ae36c22e3e2285e895737c74de5d960a5ae4))
|
||||
* **split:** Minor wired split fixes. ([6b44d33](https://github.com/zmkfirmware/zmk/commit/6b44d33db2f4bad7d98e475e6f7968493b05af73))
|
||||
* Unconditionally define HID payloads to avoid error. ([6b44d33](https://github.com/zmkfirmware/zmk/commit/6b44d33db2f4bad7d98e475e6f7968493b05af73))
|
||||
|
||||
## [0.2.1](https://github.com/zmkfirmware/zmk/compare/v0.2.0...v0.2.1) (2025-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **behaviors:** Proper comma separated device list ([#2850](https://github.com/zmkfirmware/zmk/issues/2850)) ([f20e6ea](https://github.com/zmkfirmware/zmk/commit/f20e6ea7594b49eef1e3acc017529073a0409962))
|
||||
|
||||
## [0.2.0](https://github.com/zmkfirmware/zmk/compare/v0.1.0...v0.2.0) (2025-03-01)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
module.exports = {
|
||||
endOfLine: "auto",
|
||||
trailingComma: "es5",
|
||||
};
|
||||
|
||||
@@ -96,7 +96,7 @@ target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/battery.c)
|
||||
target_sources_ifdef(CONFIG_ZMK_HID_INDICATORS app PRIVATE src/events/hid_indicators_changed.c)
|
||||
|
||||
target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c)
|
||||
add_subdirectory(src/split)
|
||||
add_subdirectory_ifdef(CONFIG_ZMK_SPLIT src/split)
|
||||
|
||||
target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c)
|
||||
target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c)
|
||||
|
||||
16
app/Kconfig
16
app/Kconfig
@@ -189,10 +189,6 @@ config BT_CTLR_PHY_2M
|
||||
config BT_TINYCRYPT_ECC
|
||||
default y if BT_HCI && !BT_CTLR
|
||||
|
||||
config SYSTEM_WORKQUEUE_STACK_SIZE
|
||||
default 4096 if SOC_RP2040
|
||||
default 2048
|
||||
|
||||
config ZMK_BLE_THREAD_STACK_SIZE
|
||||
int "BLE notify thread stack size"
|
||||
default 768
|
||||
@@ -448,12 +444,16 @@ config ZMK_COMBO_MAX_PRESSED_COMBOS
|
||||
default 4
|
||||
|
||||
config ZMK_COMBO_MAX_COMBOS_PER_KEY
|
||||
int "Maximum number of combos per key"
|
||||
default 5
|
||||
int "Deprecated: Max combos per key"
|
||||
default 0
|
||||
help
|
||||
Deprecated: Storage for combos is now determined automatically
|
||||
|
||||
config ZMK_COMBO_MAX_KEYS_PER_COMBO
|
||||
int "Maximum number of keys per combo"
|
||||
default 4
|
||||
int "Deprecated: Max keys per combo"
|
||||
default 0
|
||||
help
|
||||
Deprecated: This is now auto-calculated based on `key-positions` in devicetree
|
||||
|
||||
# Combo options
|
||||
endmenu
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# Copyright (c) 2024 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
config SYSTEM_WORKQUEUE_STACK_SIZE
|
||||
default 2048 if SOC_RP2040
|
||||
default 2048 if ZMK_BLE
|
||||
|
||||
# HID
|
||||
if ZMK_HID_REPORT_TYPE_HKRO
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ VERSION_MAJOR = 0
|
||||
# x-release-please-end
|
||||
|
||||
# x-release-please-start-minor
|
||||
VERSION_MINOR = 2
|
||||
VERSION_MINOR = 3
|
||||
# x-release-please-end
|
||||
|
||||
# x-release-please-start-patch
|
||||
|
||||
@@ -6,7 +6,7 @@ if BOARD_GLOVE80_LH
|
||||
config BOARD
|
||||
default "glove80 lh"
|
||||
|
||||
config ZMK_SPLIT_BLE_ROLE_CENTRAL
|
||||
config ZMK_SPLIT_ROLE_CENTRAL
|
||||
default y
|
||||
|
||||
endif # BOARD_GLOVE80_LH
|
||||
|
||||
@@ -7,7 +7,7 @@ config BOARD_ENABLE_DCDC
|
||||
config BOARD_ENABLE_DCDC_HV
|
||||
bool "High voltage DCDC converter"
|
||||
select SOC_DCDC_NRF52X_HV
|
||||
default y
|
||||
default n
|
||||
depends on (BOARD_MIKOTO)
|
||||
|
||||
choice BOARD_MIKOTO_CHARGER_CURRENT
|
||||
|
||||
@@ -9,5 +9,5 @@ config BOARD_ENABLE_DCDC
|
||||
config BOARD_ENABLE_DCDC_HV
|
||||
bool "High voltage DCDC converter"
|
||||
select SOC_DCDC_NRF52X_HV
|
||||
default y
|
||||
default n
|
||||
depends on (BOARD_NICE_NANO_V2)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
file_format: "1"
|
||||
id: seeeduino_xiao
|
||||
name: Seeeduino XIAO
|
||||
name: Seeed Studio XIAO SAMD21
|
||||
type: board
|
||||
arch: arm
|
||||
outputs:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
file_format: "1"
|
||||
id: seeeduino_xiao_ble
|
||||
name: Seeeduino XIAO BLE
|
||||
name: Seeed Studio XIAO nRF52840
|
||||
type: board
|
||||
arch: arm
|
||||
outputs:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
file_format: "1"
|
||||
id: seeeduino_xiao_rp2040
|
||||
name: Seeeduino XIAO RP2040
|
||||
name: Seeed Studio XIAO RP2040
|
||||
type: board
|
||||
arch: arm
|
||||
outputs:
|
||||
|
||||
@@ -5,9 +5,9 @@ type: interconnect
|
||||
url: https://wiki.seeedstudio.com/Seeeduino-XIAO/
|
||||
manufacturer: Seeed
|
||||
description: |
|
||||
The Seeed(uino) XIAO is a popular smaller format micro-controller, that has gained popularity as an alternative
|
||||
The Seeed Studio XIAO is a popular smaller format micro-controller, that has gained popularity as an alternative
|
||||
to the SparkFun Pro Micro. Since its creation, several pin compatible controllers, such
|
||||
as the Seeeduino XIAO BLE, Adafruit QT Py and Adafruit QT Py RP2040, have become available.
|
||||
as the Seeed Studio XIAO nRF52840 (also known as XIAO BLE), Adafruit QT Py and Adafruit QT Py RP2040, have become available.
|
||||
node_labels:
|
||||
gpio: xiao_d
|
||||
i2c: xiao_i2c
|
||||
|
||||
12
app/boards/nrf52840dongle_nrf52840.conf
Normal file
12
app/boards/nrf52840dongle_nrf52840.conf
Normal file
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2025 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
CONFIG_ZMK_BLE=y
|
||||
CONFIG_ZMK_USB=y
|
||||
|
||||
CONFIG_MPU_ALLOW_FLASH_WRITE=y
|
||||
CONFIG_NVS=y
|
||||
CONFIG_SETTINGS_NVS=y
|
||||
CONFIG_FLASH=y
|
||||
CONFIG_FLASH_PAGE_LAYOUT=y
|
||||
CONFIG_FLASH_MAP=y
|
||||
@@ -3,3 +3,10 @@ CONFIG_SERIAL=n
|
||||
CONFIG_UART_CONSOLE=n
|
||||
CONFIG_UART_INTERRUPT_DRIVEN=n
|
||||
CONFIG_ZMK_USB=y
|
||||
|
||||
CONFIG_MPU_ALLOW_FLASH_WRITE=y
|
||||
CONFIG_NVS=y
|
||||
CONFIG_SETTINGS_NVS=y
|
||||
CONFIG_FLASH=y
|
||||
CONFIG_FLASH_PAGE_LAYOUT=y
|
||||
CONFIG_FLASH_MAP=y
|
||||
15
app/boards/rpi_pico.overlay
Normal file
15
app/boards/rpi_pico.overlay
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
&uart0 { status = "disabled"; };
|
||||
|
||||
&code_partition {
|
||||
reg = <0x100 (DT_SIZE_M(2) - 0x100 - DT_SIZE_K(512))>;
|
||||
};
|
||||
|
||||
&flash0 {
|
||||
partitions {
|
||||
storage_partition: partition@180000 {
|
||||
reg = <0x180000 DT_SIZE_K(512)>;
|
||||
read-only;
|
||||
};
|
||||
};
|
||||
};
|
||||
50
app/boards/shields/a_dux/a_dux-layouts.dtsi
Normal file
50
app/boards/shields/a_dux/a_dux-layouts.dtsi
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <physical_layouts.dtsi>
|
||||
|
||||
/ {
|
||||
physical_layout0: physical_layout_0 {
|
||||
compatible = "zmk,physical-layout";
|
||||
display-name = "Default";
|
||||
|
||||
keys // w h x y rot rx ry
|
||||
= <&key_physical_attrs 100 100 0 133 0 0 0>
|
||||
, <&key_physical_attrs 100 100 100 31 0 0 0>
|
||||
, <&key_physical_attrs 100 100 200 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 300 28 0 0 0>
|
||||
, <&key_physical_attrs 100 100 400 42 0 0 0>
|
||||
, <&key_physical_attrs 100 100 800 42 0 0 0>
|
||||
, <&key_physical_attrs 100 100 900 28 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1000 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1100 31 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1200 133 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 233 0 0 0>
|
||||
, <&key_physical_attrs 100 100 100 131 0 0 0>
|
||||
, <&key_physical_attrs 100 100 200 100 0 0 0>
|
||||
, <&key_physical_attrs 100 100 300 128 0 0 0>
|
||||
, <&key_physical_attrs 100 100 400 142 0 0 0>
|
||||
, <&key_physical_attrs 100 100 800 142 0 0 0>
|
||||
, <&key_physical_attrs 100 100 900 128 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1000 100 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1100 131 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1200 233 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 333 0 0 0>
|
||||
, <&key_physical_attrs 100 100 100 231 0 0 0>
|
||||
, <&key_physical_attrs 100 100 200 200 0 0 0>
|
||||
, <&key_physical_attrs 100 100 300 228 0 0 0>
|
||||
, <&key_physical_attrs 100 100 400 242 0 0 0>
|
||||
, <&key_physical_attrs 100 100 800 242 0 0 0>
|
||||
, <&key_physical_attrs 100 100 900 228 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1000 200 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1100 231 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1200 333 0 0 0>
|
||||
, <&key_physical_attrs 100 100 400 375 0 0 0>
|
||||
, <&key_physical_attrs 100 100 500 400 0 0 0>
|
||||
, <&key_physical_attrs 100 100 700 400 0 0 0>
|
||||
, <&key_physical_attrs 100 100 800 375 0 0 0>
|
||||
;
|
||||
};
|
||||
};
|
||||
@@ -5,12 +5,18 @@
|
||||
*/
|
||||
|
||||
#include <dt-bindings/zmk/matrix_transform.h>
|
||||
#include "a_dux-layouts.dtsi"
|
||||
|
||||
&physical_layout0 {
|
||||
transform = <&default_transform>;
|
||||
};
|
||||
|
||||
|
||||
/ {
|
||||
|
||||
chosen {
|
||||
zmk,kscan = &kscan0;
|
||||
zmk,matrix-transform = &default_transform;
|
||||
zmk,physical-layout = &physical_layout0;
|
||||
};
|
||||
|
||||
default_transform: keymap_transform_0 {
|
||||
@@ -49,5 +55,4 @@
|
||||
<&pro_micro 9 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -4,9 +4,21 @@ The nice!view is a low-power, high refresh rate display meant to replace I2C OLE
|
||||
|
||||
This shield requires that an `&nice_view_spi` labeled SPI bus is provided with _at least_ MOSI, SCK, and CS pins defined.
|
||||
|
||||
## Custom widget
|
||||
|
||||
The nice!view shield includes a custom vertical widget.
|
||||
|
||||
Profile indicators show the status of the first five BLE profiles using numbers from 1 to 5.
|
||||
The number corresponding to the currently selected profile is drawn in a filled disk
|
||||
and the circle outline around each profile number correspond to the following states:
|
||||
|
||||
- solid outline: connected
|
||||
- dashed outline: not connected
|
||||
- no outline: not bound
|
||||
|
||||
## Disable custom widget
|
||||
|
||||
The nice!view shield includes a custom vertical widget. To use the built-in ZMK one, add the following item to your `.conf` file:
|
||||
To use the built-in ZMK widget instead of the custom nice!view one, add the following item to your `.conf` file:
|
||||
|
||||
```
|
||||
CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN=y
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2023 The ZMK Contributors
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
*/
|
||||
@@ -33,6 +33,8 @@ struct output_status_state {
|
||||
int active_profile_index;
|
||||
bool active_profile_connected;
|
||||
bool active_profile_bonded;
|
||||
bool profiles_connected[NICEVIEW_PROFILE_COUNT];
|
||||
bool profiles_bonded[NICEVIEW_PROFILE_COUNT];
|
||||
};
|
||||
|
||||
struct layer_status_state {
|
||||
@@ -142,15 +144,24 @@ static void draw_middle(lv_obj_t *widget, lv_color_t cbuf[], const struct status
|
||||
lv_canvas_draw_rect(canvas, 0, 0, CANVAS_SIZE, CANVAS_SIZE, &rect_black_dsc);
|
||||
|
||||
// Draw circles
|
||||
int circle_offsets[5][2] = {
|
||||
int circle_offsets[NICEVIEW_PROFILE_COUNT][2] = {
|
||||
{13, 13}, {55, 13}, {34, 34}, {13, 55}, {55, 55},
|
||||
};
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int i = 0; i < NICEVIEW_PROFILE_COUNT; i++) {
|
||||
bool selected = i == state->active_profile_index;
|
||||
|
||||
lv_canvas_draw_arc(canvas, circle_offsets[i][0], circle_offsets[i][1], 13, 0, 360,
|
||||
&arc_dsc);
|
||||
if (state->profiles_connected[i]) {
|
||||
lv_canvas_draw_arc(canvas, circle_offsets[i][0], circle_offsets[i][1], 13, 0, 360,
|
||||
&arc_dsc);
|
||||
} else if (state->profiles_bonded[i]) {
|
||||
const int segments = 8;
|
||||
const int gap = 20;
|
||||
for (int j = 0; j < segments; ++j)
|
||||
lv_canvas_draw_arc(canvas, circle_offsets[i][0], circle_offsets[i][1], 13,
|
||||
360. / segments * j + gap / 2.0,
|
||||
360. / segments * (j + 1) - gap / 2.0, &arc_dsc);
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
lv_canvas_draw_arc(canvas, circle_offsets[i][0], circle_offsets[i][1], 9, 0, 359,
|
||||
@@ -234,6 +245,10 @@ static void set_output_status(struct zmk_widget_status *widget,
|
||||
widget->state.active_profile_index = state->active_profile_index;
|
||||
widget->state.active_profile_connected = state->active_profile_connected;
|
||||
widget->state.active_profile_bonded = state->active_profile_bonded;
|
||||
for (int i = 0; i < NICEVIEW_PROFILE_COUNT; ++i) {
|
||||
widget->state.profiles_connected[i] = state->profiles_connected[i];
|
||||
widget->state.profiles_bonded[i] = state->profiles_bonded[i];
|
||||
}
|
||||
|
||||
draw_top(widget->obj, widget->cbuf, &widget->state);
|
||||
draw_middle(widget->obj, widget->cbuf2, &widget->state);
|
||||
@@ -245,12 +260,17 @@ static void output_status_update_cb(struct output_status_state state) {
|
||||
}
|
||||
|
||||
static struct output_status_state output_status_get_state(const zmk_event_t *_eh) {
|
||||
return (struct output_status_state){
|
||||
struct output_status_state state = {
|
||||
.selected_endpoint = zmk_endpoints_selected(),
|
||||
.active_profile_index = zmk_ble_active_profile_index(),
|
||||
.active_profile_connected = zmk_ble_active_profile_is_connected(),
|
||||
.active_profile_bonded = !zmk_ble_active_profile_is_open(),
|
||||
};
|
||||
for (int i = 0; i < MIN(NICEVIEW_PROFILE_COUNT, ZMK_BLE_PROFILE_COUNT); ++i) {
|
||||
state.profiles_connected[i] = zmk_ble_profile_is_connected(i);
|
||||
state.profiles_bonded[i] = !zmk_ble_profile_is_open(i);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
ZMK_DISPLAY_WIDGET_LISTENER(widget_output_status, struct output_status_state,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2023 The ZMK Contributors
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
*/
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <lvgl.h>
|
||||
#include <zmk/endpoints.h>
|
||||
|
||||
#define NICEVIEW_PROFILE_COUNT 5
|
||||
|
||||
#define CANVAS_SIZE 68
|
||||
|
||||
#define LVGL_BACKGROUND \
|
||||
@@ -23,6 +25,8 @@ struct status_state {
|
||||
int active_profile_index;
|
||||
bool active_profile_connected;
|
||||
bool active_profile_bonded;
|
||||
bool profiles_connected[NICEVIEW_PROFILE_COUNT];
|
||||
bool profiles_bonded[NICEVIEW_PROFILE_COUNT];
|
||||
uint8_t layer_index;
|
||||
const char *layer_label;
|
||||
uint8_t wpm[10];
|
||||
|
||||
52
app/boards/shields/reviung5/boards/nice_nano_v2.overlay
Normal file
52
app/boards/shields/reviung5/boards/nice_nano_v2.overlay
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <dt-bindings/led/led.h>
|
||||
|
||||
&pinctrl {
|
||||
spi3_default: spi3_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_MOSI, 0, 6)>;
|
||||
};
|
||||
};
|
||||
|
||||
spi3_sleep: spi3_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_MOSI, 0, 6)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&spi3 {
|
||||
compatible = "nordic,nrf-spim";
|
||||
status = "okay";
|
||||
|
||||
pinctrl-0 = <&spi3_default>;
|
||||
pinctrl-1 = <&spi3_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
led_strip: ws2812@0 {
|
||||
compatible = "worldsemi,ws2812-spi";
|
||||
|
||||
/* SPI */
|
||||
reg = <0>; /* ignored, but necessary for SPI bindings */
|
||||
spi-max-frequency = <4000000>;
|
||||
|
||||
/* WS2812 */
|
||||
chain-length = <4>;
|
||||
spi-one-frame = <0x70>;
|
||||
spi-zero-frame = <0x40>;
|
||||
|
||||
color-mapping = <LED_COLOR_ID_GREEN LED_COLOR_ID_RED LED_COLOR_ID_BLUE>;
|
||||
};
|
||||
};
|
||||
|
||||
/ {
|
||||
chosen {
|
||||
zmk,underglow = &led_strip;
|
||||
};
|
||||
};
|
||||
@@ -1,3 +1,7 @@
|
||||
# Encoder support. Uncomment to enable.
|
||||
# Uncomment the following two lines to add support for encoders
|
||||
# CONFIG_EC11=y
|
||||
# CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y
|
||||
|
||||
# Uncomment the following two lines to enable RGB underglow
|
||||
# CONFIG_ZMK_RGB_UNDERGLOW=y
|
||||
# CONFIG_WS2812_STRIP=y
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2022 The ZMK Contributors
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
@@ -8,20 +8,31 @@
|
||||
#include <dt-bindings/zmk/keys.h>
|
||||
#include <dt-bindings/zmk/bt.h>
|
||||
#include <dt-bindings/zmk/outputs.h>
|
||||
#include <dt-bindings/zmk/rgb.h>
|
||||
|
||||
#define BASE 0
|
||||
#define BLE 1
|
||||
#define RGB 2
|
||||
|
||||
/ {
|
||||
|
||||
behaviors {
|
||||
rgb_encoder: rgb_encoder {
|
||||
compatible = "zmk,behavior-sensor-rotate";
|
||||
#sensor-binding-cells = <0>;
|
||||
bindings = <&rgb_ug RGB_BRI>, <&rgb_ug RGB_BRD>;
|
||||
};
|
||||
};
|
||||
|
||||
keymap {
|
||||
compatible = "zmk,keymap";
|
||||
|
||||
base_layer {
|
||||
display-name = "BASE";
|
||||
bindings = <
|
||||
// ╭─────────────┬──────────────┬──────────────────┬─────────────┬─────────────╮
|
||||
&mo BLE &kp C_PREVIOUS &kp C_PLAY_PAUSE &kp C_NEXT &kp C_MUTE
|
||||
// ╰─────────────┴──────────────┴──────────────────┴─────────────┴─────────────╯
|
||||
// ╭─────────┬────────────────┬──────────────────┬────────────┬────────────────╮
|
||||
&mo BLE &kp C_PREVIOUS &kp C_PLAY_PAUSE &kp C_NEXT < RGB C_MUTE
|
||||
// ╰─────────┴────────────────┴──────────────────┴────────────┴────────────────╯
|
||||
>;
|
||||
sensor-bindings = <&inc_dec_kp C_VOL_UP C_VOL_DN>;
|
||||
};
|
||||
@@ -29,10 +40,20 @@
|
||||
ble_layer {
|
||||
display-name = "BLE";
|
||||
bindings = <
|
||||
// ╭─────────────┬─────────────┬─────────────┬─────────────┬─────────────╮
|
||||
&trans &out OUT_TOG &bt BT_PRV &bt BT_NXT &bt BT_CLR
|
||||
// ╰─────────────┴─────────────┴─────────────┴─────────────┴─────────────╯
|
||||
// ╭────────┬──────────────┬────────────┬────────────┬────────────╮
|
||||
&trans &out OUT_TOG &bt BT_PRV &bt BT_NXT &bt BT_CLR
|
||||
// ╰────────┴──────────────┴────────────┴────────────┴────────────╯
|
||||
>;
|
||||
};
|
||||
|
||||
rgb_layer {
|
||||
display-name = "RGB";
|
||||
bindings = <
|
||||
// ╭──────────────────┬─────────────────┬─────────────────┬──────────────────────────────────┬────────╮
|
||||
&rgb_ug RGB_TOG &rgb_ug RGB_EFR &rgb_ug RGB_EFF &rgb_ug RGB_COLOR_HSB(307,89,98) &trans
|
||||
// ╰──────────────────┴─────────────────┴─────────────────┴──────────────────────────────────┴────────╯
|
||||
>;
|
||||
sensor-bindings = <&rgb_encoder>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -7,3 +7,4 @@ requires: [pro_micro]
|
||||
features:
|
||||
- keys
|
||||
- encoder
|
||||
- underglow
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <physical_layouts.dtsi>
|
||||
|
||||
/ {
|
||||
tester_position_map {
|
||||
compatible = "zmk,physical-layout-position-map";
|
||||
complete;
|
||||
|
||||
pinout_map: pinout_positions {
|
||||
physical-layout = <&physical_layout0>;
|
||||
positions = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17>;
|
||||
};
|
||||
inline_map: single_row_positions {
|
||||
physical-layout = <&physical_layout1>;
|
||||
positions = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17>;
|
||||
};
|
||||
};
|
||||
|
||||
physical_layout0: physical_layout_0 {
|
||||
compatible = "zmk,physical-layout";
|
||||
display-name = "Pro Micro Pinout";
|
||||
|
||||
// Map key positions to the Pro Micro pinout. The coordinate
|
||||
// deltas jump around a little because some of the Pro Micro
|
||||
// pins are out of order: D0 is "after" D1 and D16 is "before"
|
||||
// D14.
|
||||
keys // w h x y rot rx ry
|
||||
= <&key_physical_attrs 100 100 0 100 0 0 0> // D0
|
||||
, <&key_physical_attrs 100 100 0 0 0 0 0> // D1
|
||||
, <&key_physical_attrs 100 100 0 400 0 0 0> // D2
|
||||
, <&key_physical_attrs 100 100 0 500 0 0 0> // D3
|
||||
, <&key_physical_attrs 100 100 0 600 0 0 0> // D4
|
||||
, <&key_physical_attrs 100 100 0 700 0 0 0> // D5
|
||||
, <&key_physical_attrs 100 100 0 800 0 0 0> // D6
|
||||
, <&key_physical_attrs 100 100 0 900 0 0 0> // D7
|
||||
, <&key_physical_attrs 100 100 0 1000 0 0 0> // D8
|
||||
, <&key_physical_attrs 100 100 0 1100 0 0 0> // D9
|
||||
, <&key_physical_attrs 100 100 600 1100 0 0 0> // D10
|
||||
, <&key_physical_attrs 100 100 600 900 0 0 0> // D14
|
||||
, <&key_physical_attrs 100 100 600 800 0 0 0> // D15
|
||||
, <&key_physical_attrs 100 100 600 1000 0 0 0> // D16
|
||||
, <&key_physical_attrs 100 100 600 700 0 0 0> // D18
|
||||
, <&key_physical_attrs 100 100 600 600 0 0 0> // D19
|
||||
, <&key_physical_attrs 100 100 600 500 0 0 0> // D20
|
||||
, <&key_physical_attrs 100 100 600 400 0 0 0> // D21
|
||||
;
|
||||
};
|
||||
|
||||
physical_layout1: physical_layout_1 {
|
||||
compatible = "zmk,physical-layout";
|
||||
display-name = "Single Row";
|
||||
|
||||
// Single row of eighteen "keys", in ascending "Arduino" order.
|
||||
keys // w h x y rot rx ry
|
||||
= <&key_physical_attrs 100 100 0 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 100 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 200 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 300 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 400 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 500 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 700 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 800 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 900 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1000 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1100 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1200 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1300 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1400 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1500 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1600 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1700 0 0 0 0>
|
||||
;
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,18 @@
|
||||
#include <dt-bindings/zmk/matrix_transform.h>
|
||||
#include "tester_pro_micro-layouts.dtsi"
|
||||
|
||||
&physical_layout0 {
|
||||
transform = <&transform0>;
|
||||
};
|
||||
|
||||
&physical_layout1 {
|
||||
transform = <&transform0>;
|
||||
};
|
||||
|
||||
/ {
|
||||
chosen {
|
||||
zmk,kscan = &kscan0;
|
||||
zmk,matrix-transform = &transform0;
|
||||
zmk,physical-layout = &physical_layout0;
|
||||
};
|
||||
|
||||
kscan0: kscan {
|
||||
@@ -41,4 +50,4 @@
|
||||
RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10) RC(0,11) RC(0,12) RC(0,13) RC(0,14) RC(0,15) RC(0,16) RC(0,17)
|
||||
>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
12
app/boards/shields/tester_rpi_pico/Kconfig.defconfig
Normal file
12
app/boards/shields/tester_rpi_pico/Kconfig.defconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
if SHIELD_TESTER_RPI_PICO
|
||||
|
||||
config ZMK_KEYBOARD_NAME
|
||||
default "ZMK Tester"
|
||||
|
||||
config ZMK_BLE
|
||||
def_bool n
|
||||
|
||||
config SETTINGS
|
||||
def_bool n
|
||||
|
||||
endif
|
||||
2
app/boards/shields/tester_rpi_pico/Kconfig.shield
Normal file
2
app/boards/shields/tester_rpi_pico/Kconfig.shield
Normal file
@@ -0,0 +1,2 @@
|
||||
config SHIELD_TESTER_RPI_PICO
|
||||
def_bool $(shields_list_contains,tester_rpi_pico)
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <physical_layouts.dtsi>
|
||||
|
||||
/ {
|
||||
tester_position_map {
|
||||
compatible = "zmk,physical-layout-position-map";
|
||||
complete;
|
||||
|
||||
pinout_map: pinout_positions {
|
||||
physical-layout = <&physical_layout0>;
|
||||
positions = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25>;
|
||||
};
|
||||
inline_map: single_row_positions {
|
||||
physical-layout = <&physical_layout1>;
|
||||
positions = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25>;
|
||||
};
|
||||
};
|
||||
|
||||
physical_layout0: physical_layout_0 {
|
||||
compatible = "zmk,physical-layout";
|
||||
display-name = "Rpi Pico Pinout";
|
||||
|
||||
kscan = <&kscan0>;
|
||||
transform = <&matrix_transform0>;
|
||||
|
||||
keys // w h x y rot rx ry
|
||||
= <&key_physical_attrs 100 100 0 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 100 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 300 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 400 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 500 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 600 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 800 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 900 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 1000 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 1100 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 1300 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 1400 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 1500 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 1600 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 1800 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 1900 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 1900 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 1800 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 1600 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 1500 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 1400 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 1300 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 1100 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 900 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 800 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 600 0 0 0>
|
||||
;
|
||||
};
|
||||
|
||||
physical_layout1: physical_layout_1 {
|
||||
compatible = "zmk,physical-layout";
|
||||
display-name = "Single Row";
|
||||
|
||||
// Single row of eighteen "keys", in ascending "Arduino" order.
|
||||
keys // w h x y rot rx ry
|
||||
= <&key_physical_attrs 100 100 0 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 100 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 200 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 300 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 400 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 500 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 700 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 800 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 900 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1000 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1100 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1200 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1300 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1400 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1500 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1600 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1700 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1800 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1900 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 2000 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 2100 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 2200 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 2300 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 2400 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 2500 0 0 0 0>
|
||||
;
|
||||
};
|
||||
};
|
||||
77
app/boards/shields/tester_rpi_pico/tester_rpi_pico.keymap
Normal file
77
app/boards/shields/tester_rpi_pico/tester_rpi_pico.keymap
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <behaviors.dtsi>
|
||||
#include <dt-bindings/zmk/keys.h>
|
||||
|
||||
#define PIN_MACRO(name, pin) \
|
||||
/ { \
|
||||
macros { \
|
||||
name: name { \
|
||||
compatible = "zmk,behavior-macro"; \
|
||||
wait-ms = <5>; \
|
||||
tap-ms = <5>; \
|
||||
#binding-cells = <0>; \
|
||||
bindings = <&kp P &kp I &kp N &kp SPACE>, pin, <&kp ENTER>; \
|
||||
}; \
|
||||
}; \
|
||||
};
|
||||
|
||||
PIN_MACRO(pin0, <&kp N0>)
|
||||
PIN_MACRO(pin1, <&kp N1>)
|
||||
PIN_MACRO(pin2, <&kp N2>)
|
||||
PIN_MACRO(pin3, <&kp N3>)
|
||||
PIN_MACRO(pin4, <&kp N4>)
|
||||
PIN_MACRO(pin5, <&kp N5>)
|
||||
PIN_MACRO(pin6, <&kp N6>)
|
||||
PIN_MACRO(pin7, <&kp N7>)
|
||||
PIN_MACRO(pin8, <&kp N8>)
|
||||
PIN_MACRO(pin9, <&kp N9>)
|
||||
PIN_MACRO(pin10, <&kp N1 &kp N0>)
|
||||
PIN_MACRO(pin11, <&kp N1 &kp N1>)
|
||||
PIN_MACRO(pin12, <&kp N1 &kp N2>)
|
||||
PIN_MACRO(pin13, <&kp N1 &kp N3>)
|
||||
PIN_MACRO(pin14, <&kp N1 &kp N4>)
|
||||
PIN_MACRO(pin15, <&kp N1 &kp N5>)
|
||||
PIN_MACRO(pin16, <&kp N1 &kp N6>)
|
||||
PIN_MACRO(pin17, <&kp N1 &kp N7>)
|
||||
PIN_MACRO(pin18, <&kp N1 &kp N8>)
|
||||
PIN_MACRO(pin19, <&kp N1 &kp N9>)
|
||||
PIN_MACRO(pin20, <&kp N2 &kp N0>)
|
||||
PIN_MACRO(pin21, <&kp N2 &kp N1>)
|
||||
PIN_MACRO(pin22, <&kp N2 &kp N2>)
|
||||
PIN_MACRO(pin26, <&kp N2 &kp N6>)
|
||||
PIN_MACRO(pin27, <&kp N2 &kp N7>)
|
||||
PIN_MACRO(pin28, <&kp N2 &kp N8>)
|
||||
|
||||
/ {
|
||||
keymap {
|
||||
compatible = "zmk,keymap";
|
||||
|
||||
default_layer {
|
||||
bindings = <&pin0
|
||||
&pin1
|
||||
&pin2
|
||||
&pin3
|
||||
&pin4
|
||||
&pin5
|
||||
&pin6
|
||||
&pin7
|
||||
&pin8
|
||||
&pin9
|
||||
&pin10
|
||||
&pin11
|
||||
&pin12
|
||||
&pin13
|
||||
&pin14
|
||||
&pin15
|
||||
&pin16
|
||||
&pin17
|
||||
&pin18
|
||||
&pin19
|
||||
&pin20
|
||||
&pin21
|
||||
&pin22
|
||||
&pin26
|
||||
&pin27
|
||||
&pin28>;
|
||||
};
|
||||
};
|
||||
};
|
||||
63
app/boards/shields/tester_rpi_pico/tester_rpi_pico.overlay
Normal file
63
app/boards/shields/tester_rpi_pico/tester_rpi_pico.overlay
Normal file
@@ -0,0 +1,63 @@
|
||||
#include <dt-bindings/zmk/matrix_transform.h>
|
||||
#include "tester_rpi_pico-layouts.dtsi"
|
||||
|
||||
&physical_layout0 {
|
||||
transform = <&transform0>;
|
||||
};
|
||||
|
||||
&physical_layout1 {
|
||||
transform = <&transform0>;
|
||||
};
|
||||
|
||||
/ {
|
||||
chosen {
|
||||
zmk,kscan = &kscan0;
|
||||
zmk,physical-layout = &physical_layout0;
|
||||
};
|
||||
|
||||
kscan0: kscan {
|
||||
compatible = "zmk,kscan-gpio-direct";
|
||||
wakeup-source;
|
||||
debounce-press-ms = <10>;
|
||||
debounce-release-ms = <10>;
|
||||
input-gpios
|
||||
= <&pico_header 0 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 2 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 3 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 6 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 7 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 8 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 9 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 11 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 12 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 13 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 14 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 15 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 16 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 17 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 18 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 19 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 20 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 21 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 22 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 26 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 27 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
, <&pico_header 28 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
|
||||
;
|
||||
};
|
||||
|
||||
transform0: keymap_transform {
|
||||
compatible = "zmk,matrix-transform";
|
||||
columns = <26>;
|
||||
rows = <1>;
|
||||
map = <
|
||||
RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) RC(0,8) RC(0,9)
|
||||
RC(0,10) RC(0,11) RC(0,12) RC(0,13) RC(0,14) RC(0,15) RC(0,16) RC(0,17) RC(0,18) RC(0,19)
|
||||
RC(0,20) RC(0,21) RC(0,22) RC(0,23) RC(0,24) RC(0,25)
|
||||
>;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
file_format: "1"
|
||||
id: tester_rpi_pico
|
||||
name: TesterRpiPico
|
||||
type: shield
|
||||
url: https://zmk.dev/docs/troubleshooting/hardware-issues
|
||||
requires: [rpi_pico]
|
||||
66
app/boards/shields/tester_xiao/tester_xiao-layouts.dtsi
Normal file
66
app/boards/shields/tester_xiao/tester_xiao-layouts.dtsi
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <physical_layouts.dtsi>
|
||||
|
||||
// The tester might not have any physical keys at all, so these
|
||||
// physical layouts are pretty much imaginary.
|
||||
|
||||
/ {
|
||||
tester_position_map {
|
||||
compatible = "zmk,physical-layout-position-map";
|
||||
complete;
|
||||
|
||||
pinout_map: pinout_positions {
|
||||
physical-layout = <&physical_layout0>;
|
||||
positions = <0 1 2 3 4 5 6 7 8 9 10>;
|
||||
};
|
||||
inline_map: single_row_positions {
|
||||
physical-layout = <&physical_layout1>;
|
||||
positions = <0 1 2 3 4 5 6 7 8 9 10>;
|
||||
};
|
||||
};
|
||||
|
||||
physical_layout0: physical_layout_0 {
|
||||
compatible = "zmk,physical-layout";
|
||||
display-name = "XIAO Pinout";
|
||||
|
||||
// Map key positions to the XIAO pinout.
|
||||
keys // w h x y rot rx ry
|
||||
= <&key_physical_attrs 100 100 0 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 100 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 200 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 300 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 400 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 500 0 0 0>
|
||||
, <&key_physical_attrs 100 100 0 600 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 600 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 500 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 400 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 300 0 0 0>
|
||||
;
|
||||
};
|
||||
|
||||
physical_layout1: physical_layout_1 {
|
||||
compatible = "zmk,physical-layout";
|
||||
display-name = "Single Row";
|
||||
|
||||
// Single row of eleven "keys".
|
||||
keys // w h x y rot rx ry
|
||||
= <&key_physical_attrs 100 100 0 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 100 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 200 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 300 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 400 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 500 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 600 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 700 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 800 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 900 0 0 0 0>
|
||||
, <&key_physical_attrs 100 100 1000 0 0 0 0>
|
||||
;
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,18 @@
|
||||
#include <dt-bindings/zmk/matrix_transform.h>
|
||||
#include "tester_xiao-layouts.dtsi"
|
||||
|
||||
&physical_layout0 {
|
||||
transform = <&transform0>;
|
||||
};
|
||||
|
||||
&physical_layout1 {
|
||||
transform = <&transform0>;
|
||||
};
|
||||
|
||||
/ {
|
||||
chosen {
|
||||
zmk,kscan = &kscan0;
|
||||
zmk,matrix-transform = &transform0;
|
||||
zmk,physical-layout = &physical_layout0;
|
||||
};
|
||||
|
||||
kscan0: kscan {
|
||||
@@ -34,4 +43,4 @@
|
||||
RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10)
|
||||
>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,11 +11,20 @@ left_encoder: &encoder {
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
&arduino_serial {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
/ {
|
||||
chosen {
|
||||
zmk,physical-layout = &split_matrix_physical_layout;
|
||||
};
|
||||
|
||||
wired_split {
|
||||
compatible = "zmk,wired-split";
|
||||
device = <&arduino_serial>;
|
||||
};
|
||||
|
||||
split_matrix_transform: split_matrix_transform {
|
||||
compatible = "zmk,matrix-transform";
|
||||
rows = <4>;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
mkp: mouse_key_press {
|
||||
compatible = "zmk,behavior-mouse-key-press";
|
||||
#binding-cells = <1>;
|
||||
display-name = "Mouse Key Press";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -25,4 +25,3 @@ child-binding:
|
||||
type: boolean
|
||||
layers:
|
||||
type: array
|
||||
default: [-1]
|
||||
|
||||
27
app/dts/bindings/zmk,wired-split.yaml
Normal file
27
app/dts/bindings/zmk,wired-split.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# Copyright (c) 2025 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
description: |
|
||||
Complete specification of wired split connection
|
||||
|
||||
compatible: "zmk,wired-split"
|
||||
|
||||
properties:
|
||||
device:
|
||||
type: phandle
|
||||
required: true
|
||||
description: The UART device for wired split communication
|
||||
|
||||
detect-gpios:
|
||||
type: phandle-array
|
||||
description: |
|
||||
If your split includes support for an extra GPIO to detect presence of the split cable, set
|
||||
this to the GPIO pin used to detect the connection.
|
||||
|
||||
half-duplex:
|
||||
type: boolean
|
||||
description: "Experimental: Enable half-duplex protocol mode"
|
||||
|
||||
dir-gpios:
|
||||
type: phandle-array
|
||||
description: "Experimental: Set the communication direction. Used for RS-422 style comms."
|
||||
9
app/include/linker/zmk-split-transport-central.ld
Normal file
9
app/include/linker/zmk-split-transport-central.ld
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/linker/linker-defs.h>
|
||||
|
||||
ITERABLE_SECTION_ROM(zmk_split_transport_central, 4)
|
||||
9
app/include/linker/zmk-split-transport-peripheral.ld
Normal file
9
app/include/linker/zmk-split-transport-peripheral.ld
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/linker/linker-defs.h>
|
||||
|
||||
ITERABLE_SECTION_ROM(zmk_split_transport_peripheral, 4)
|
||||
@@ -10,8 +10,7 @@
|
||||
#include <zmk/ble/profile.h>
|
||||
|
||||
#define ZMK_BLE_IS_CENTRAL \
|
||||
(IS_ENABLED(CONFIG_ZMK_SPLIT) && IS_ENABLED(CONFIG_ZMK_BLE) && \
|
||||
IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL))
|
||||
(IS_ENABLED(CONFIG_ZMK_SPLIT_BLE) && IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL))
|
||||
|
||||
#if ZMK_BLE_IS_CENTRAL
|
||||
#define ZMK_BLE_PROFILE_COUNT (CONFIG_BT_MAX_PAIRED - CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS)
|
||||
@@ -29,10 +28,14 @@ int zmk_ble_prof_disconnect(uint8_t index);
|
||||
|
||||
int zmk_ble_active_profile_index(void);
|
||||
int zmk_ble_profile_index(const bt_addr_le_t *addr);
|
||||
bt_addr_le_t *zmk_ble_profile_address(uint8_t index);
|
||||
|
||||
bt_addr_le_t *zmk_ble_active_profile_addr(void);
|
||||
struct bt_conn *zmk_ble_active_profile_conn(void);
|
||||
|
||||
bool zmk_ble_profile_is_connected(uint8_t index);
|
||||
bool zmk_ble_profile_is_open(uint8_t index);
|
||||
|
||||
bool zmk_ble_active_profile_is_open(void);
|
||||
bool zmk_ble_active_profile_is_connected(void);
|
||||
char *zmk_ble_active_profile_name(void);
|
||||
|
||||
@@ -208,7 +208,7 @@ static const uint8_t zmk_hid_report_desc[] = {
|
||||
HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP),
|
||||
HID_USAGE(HID_USAGE_GD_X),
|
||||
HID_USAGE(HID_USAGE_GD_Y),
|
||||
HID_LOGICAL_MIN16(0xFF, -0x7F),
|
||||
HID_LOGICAL_MIN16(0x00, 0x80),
|
||||
HID_LOGICAL_MAX16(0xFF, 0x7F),
|
||||
HID_REPORT_SIZE(0x10),
|
||||
HID_REPORT_COUNT(0x02),
|
||||
@@ -226,7 +226,7 @@ static const uint8_t zmk_hid_report_desc[] = {
|
||||
HID_FEATURE(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS),
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
|
||||
HID_USAGE(HID_USAGE_GD_WHEEL),
|
||||
HID_LOGICAL_MIN16(0xFF, -0x7F),
|
||||
HID_LOGICAL_MIN16(0x00, 0x80),
|
||||
HID_LOGICAL_MAX16(0xFF, 0x7F),
|
||||
HID_PHYSICAL_MIN8(0x00),
|
||||
HID_PHYSICAL_MAX8(0x00),
|
||||
@@ -242,7 +242,7 @@ static const uint8_t zmk_hid_report_desc[] = {
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
|
||||
HID_USAGE_PAGE(HID_USAGE_CONSUMER),
|
||||
HID_USAGE16_SINGLE(HID_USAGE_CONSUMER_AC_PAN),
|
||||
HID_LOGICAL_MIN16(0xFF, -0x7F),
|
||||
HID_LOGICAL_MIN16(0x00, 0x80),
|
||||
HID_LOGICAL_MAX16(0xFF, 0x7F),
|
||||
HID_PHYSICAL_MIN8(0x00),
|
||||
HID_PHYSICAL_MAX8(0x00),
|
||||
@@ -289,8 +289,6 @@ struct zmk_hid_keyboard_report {
|
||||
struct zmk_hid_keyboard_report_body body;
|
||||
} __packed;
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS)
|
||||
|
||||
struct zmk_hid_led_report_body {
|
||||
uint8_t leds;
|
||||
} __packed;
|
||||
@@ -300,8 +298,6 @@ struct zmk_hid_led_report {
|
||||
struct zmk_hid_led_report_body body;
|
||||
} __packed;
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_HID_INDICATORS)
|
||||
|
||||
struct zmk_hid_consumer_report_body {
|
||||
#if IS_ENABLED(CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC)
|
||||
uint8_t keys[CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE];
|
||||
@@ -377,9 +373,9 @@ int zmk_hid_mouse_button_release(zmk_mouse_button_t button);
|
||||
int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons);
|
||||
int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons);
|
||||
void zmk_hid_mouse_movement_set(int16_t x, int16_t y);
|
||||
void zmk_hid_mouse_scroll_set(int8_t x, int8_t y);
|
||||
void zmk_hid_mouse_scroll_set(int16_t x, int16_t y);
|
||||
void zmk_hid_mouse_movement_update(int16_t x, int16_t y);
|
||||
void zmk_hid_mouse_scroll_update(int8_t x, int8_t y);
|
||||
void zmk_hid_mouse_scroll_update(int16_t x, int16_t y);
|
||||
void zmk_hid_mouse_clear(void);
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_POINTING)
|
||||
|
||||
@@ -7,13 +7,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include <zephyr/devicetree.h>
|
||||
|
||||
#define ZMK_KEYMAP_LAYERS_FOREACH(_fn) \
|
||||
COND_CODE_1(IS_ENABLED(CONFIG_ZMK_STUDIO), (DT_FOREACH_CHILD(DT_INST(0, zmk_keymap), _fn)), \
|
||||
(DT_FOREACH_CHILD_STATUS_OKAY(DT_INST(0, zmk_keymap), _fn)))
|
||||
|
||||
#define ZMK_KEYMAP_LAYERS_FOREACH_SEP(_fn, _sep) \
|
||||
COND_CODE_1(IS_ENABLED(CONFIG_ZMK_STUDIO), \
|
||||
(DT_FOREACH_CHILD_SEP(DT_INST(0, zmk_keymap), _fn, _sep)), \
|
||||
(DT_FOREACH_CHILD_STATUS_OKAY_SEP(DT_INST(0, zmk_keymap), _fn, _sep)))
|
||||
|
||||
#define ZMK_LAYER_CHILD_LEN_PLUS_ONE(node) 1 +
|
||||
#define ZMK_KEYMAP_LAYERS_LEN \
|
||||
(COND_CODE_1( \
|
||||
IS_ENABLED(CONFIG_ZMK_STUDIO), \
|
||||
(DT_FOREACH_CHILD(DT_INST(0, zmk_keymap), ZMK_LAYER_CHILD_LEN_PLUS_ONE)), \
|
||||
(DT_FOREACH_CHILD_STATUS_OKAY(DT_INST(0, zmk_keymap), ZMK_LAYER_CHILD_LEN_PLUS_ONE))) 0)
|
||||
#define ZMK_KEYMAP_LAYERS_LEN (ZMK_KEYMAP_LAYERS_FOREACH(ZMK_LAYER_CHILD_LEN_PLUS_ONE) 0)
|
||||
|
||||
/**
|
||||
* @brief A layer ID is a stable identifier to refer to a layer, regardless of ordering.
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/bluetooth/addr.h>
|
||||
#include <zmk/behavior.h>
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
#include <zmk/hid_indicators_types.h>
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event, bool state);
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators);
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
|
||||
int zmk_split_get_peripheral_battery_level(uint8_t source, uint8_t *level);
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
@@ -37,11 +37,3 @@ struct zmk_split_input_event_payload {
|
||||
uint32_t value;
|
||||
uint8_t sync;
|
||||
} __packed;
|
||||
|
||||
int zmk_split_bt_position_pressed(uint8_t position);
|
||||
int zmk_split_bt_position_released(uint8_t position);
|
||||
int zmk_split_bt_sensor_triggered(uint8_t sensor_index,
|
||||
const struct zmk_sensor_channel_data channel_data[],
|
||||
size_t channel_data_size);
|
||||
|
||||
int zmk_split_bt_report_input(uint8_t reg, uint8_t type, uint16_t code, int32_t value, bool sync);
|
||||
|
||||
48
app/include/zmk/split/central.h
Normal file
48
app/include/zmk/split/central.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/bluetooth/addr.h>
|
||||
#include <zmk/behavior.h>
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE)
|
||||
|
||||
#include <zmk/ble.h>
|
||||
#define BLE_PERIPHERAL_COUNT ZMK_SPLIT_BLE_PERIPHERAL_COUNT
|
||||
|
||||
#else
|
||||
|
||||
#define BLE_PERIPHERAL_COUNT 0
|
||||
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED)
|
||||
#define WIRED_PERIPHERAL_COUNT 1
|
||||
#else
|
||||
#define WIRED_PERIPHERAL_COUNT 0
|
||||
#endif
|
||||
|
||||
#define ZMK_SPLIT_CENTRAL_PERIPHERAL_COUNT MAX(BLE_PERIPHERAL_COUNT, WIRED_PERIPHERAL_COUNT)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
#include <zmk/hid_indicators_types.h>
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
int zmk_split_central_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event, bool state);
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
int zmk_split_central_update_hid_indicator(zmk_hid_indicators_t indicators);
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
|
||||
int zmk_split_central_get_peripheral_battery_level(uint8_t source, uint8_t *level);
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
11
app/include/zmk/split/peripheral.h
Normal file
11
app/include/zmk/split/peripheral.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zmk/split/transport/types.h>
|
||||
|
||||
int zmk_split_peripheral_report_event(const struct zmk_split_transport_peripheral_event *event);
|
||||
44
app/include/zmk/split/transport/central.h
Normal file
44
app/include/zmk/split/transport/central.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/types.h>
|
||||
|
||||
#include <zmk/split/transport/types.h>
|
||||
|
||||
struct zmk_split_transport_central;
|
||||
|
||||
typedef int (*zmk_split_transport_central_status_changed_cb_t)(
|
||||
const struct zmk_split_transport_central *transport, struct zmk_split_transport_status status);
|
||||
|
||||
typedef int (*zmk_split_transport_central_send_command_t)(
|
||||
uint8_t source, struct zmk_split_transport_central_command cmd);
|
||||
typedef int (*zmk_split_transport_central_get_available_source_ids_t)(uint8_t *sources);
|
||||
typedef int (*zmk_split_transport_central_set_status_callback_t)(
|
||||
zmk_split_transport_central_status_changed_cb_t cb);
|
||||
|
||||
struct zmk_split_transport_central_api {
|
||||
zmk_split_transport_central_send_command_t send_command;
|
||||
zmk_split_transport_central_get_available_source_ids_t get_available_source_ids;
|
||||
zmk_split_transport_set_enabled_t set_enabled;
|
||||
zmk_split_transport_get_status_t get_status;
|
||||
zmk_split_transport_central_set_status_callback_t set_status_callback;
|
||||
};
|
||||
|
||||
struct zmk_split_transport_central {
|
||||
const struct zmk_split_transport_central_api *api;
|
||||
};
|
||||
|
||||
int zmk_split_transport_central_peripheral_event_handler(
|
||||
const struct zmk_split_transport_central *transport, uint8_t source,
|
||||
struct zmk_split_transport_peripheral_event ev);
|
||||
|
||||
#define ZMK_SPLIT_TRANSPORT_CENTRAL_REGISTER(name, _api, priority) \
|
||||
STRUCT_SECTION_ITERABLE_NAMED(zmk_split_transport_central, _CONCAT(priority, _##name), \
|
||||
name) = { \
|
||||
.api = _api, \
|
||||
};
|
||||
43
app/include/zmk/split/transport/peripheral.h
Normal file
43
app/include/zmk/split/transport/peripheral.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/types.h>
|
||||
|
||||
#include <zmk/split/transport/types.h>
|
||||
|
||||
struct zmk_split_transport_peripheral;
|
||||
|
||||
typedef int (*zmk_split_transport_peripheral_status_changed_cb_t)(
|
||||
const struct zmk_split_transport_peripheral *transport,
|
||||
struct zmk_split_transport_status status);
|
||||
|
||||
typedef int (*zmk_split_transport_peripheral_report_event_callback_t)(
|
||||
const struct zmk_split_transport_peripheral_event *event);
|
||||
typedef int (*zmk_split_transport_peripheral_set_status_callback_t)(
|
||||
zmk_split_transport_peripheral_status_changed_cb_t cb);
|
||||
|
||||
struct zmk_split_transport_peripheral_api {
|
||||
zmk_split_transport_peripheral_report_event_callback_t report_event;
|
||||
zmk_split_transport_set_enabled_t set_enabled;
|
||||
zmk_split_transport_get_status_t get_status;
|
||||
zmk_split_transport_peripheral_set_status_callback_t set_status_callback;
|
||||
};
|
||||
|
||||
struct zmk_split_transport_peripheral {
|
||||
const struct zmk_split_transport_peripheral_api *api;
|
||||
};
|
||||
|
||||
int zmk_split_transport_peripheral_command_handler(
|
||||
const struct zmk_split_transport_peripheral *transport,
|
||||
struct zmk_split_transport_central_command cmd);
|
||||
|
||||
#define ZMK_SPLIT_TRANSPORT_PERIPHERAL_REGISTER(name, _api, priority) \
|
||||
STRUCT_SECTION_ITERABLE_NAMED(zmk_split_transport_peripheral, _CONCAT(priority, _##name), \
|
||||
name) = { \
|
||||
.api = _api, \
|
||||
};
|
||||
91
app/include/zmk/split/transport/types.h
Normal file
91
app/include/zmk/split/transport/types.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zmk/hid_indicators_types.h>
|
||||
#include <zmk/sensors.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
enum zmk_split_transport_connections_status {
|
||||
ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_DISCONNECTED = 0,
|
||||
ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_SOME_CONNECTED,
|
||||
ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_ALL_CONNECTED,
|
||||
};
|
||||
|
||||
struct zmk_split_transport_status {
|
||||
bool available;
|
||||
bool enabled;
|
||||
enum zmk_split_transport_connections_status connections;
|
||||
};
|
||||
|
||||
typedef struct zmk_split_transport_status (*zmk_split_transport_get_status_t)(void);
|
||||
typedef int (*zmk_split_transport_set_enabled_t)(bool enabled);
|
||||
|
||||
enum zmk_split_transport_peripheral_event_type {
|
||||
ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_KEY_POSITION_EVENT,
|
||||
ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_SENSOR_EVENT,
|
||||
ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_INPUT_EVENT,
|
||||
ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT,
|
||||
};
|
||||
|
||||
struct zmk_split_transport_peripheral_event {
|
||||
enum zmk_split_transport_peripheral_event_type type;
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint8_t position;
|
||||
uint8_t pressed;
|
||||
} key_position_event;
|
||||
|
||||
struct {
|
||||
struct zmk_sensor_channel_data channel_data;
|
||||
|
||||
uint8_t sensor_index;
|
||||
} sensor_event;
|
||||
|
||||
struct {
|
||||
uint8_t reg;
|
||||
uint8_t sync;
|
||||
uint8_t type;
|
||||
uint16_t code;
|
||||
int32_t value;
|
||||
} input_event;
|
||||
|
||||
struct {
|
||||
uint8_t level;
|
||||
} battery_event;
|
||||
} data;
|
||||
} __packed;
|
||||
|
||||
enum zmk_split_transport_central_command_type {
|
||||
ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_POLL_EVENTS,
|
||||
ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR,
|
||||
ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_PHYSICAL_LAYOUT,
|
||||
ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS,
|
||||
} __packed;
|
||||
|
||||
struct zmk_split_transport_central_command {
|
||||
enum zmk_split_transport_central_command_type type;
|
||||
|
||||
union {
|
||||
struct {
|
||||
char behavior_dev[16];
|
||||
uint32_t param1, param2;
|
||||
uint32_t position;
|
||||
uint8_t event_source;
|
||||
uint8_t state;
|
||||
} invoke_behavior;
|
||||
|
||||
struct {
|
||||
uint8_t layout_idx;
|
||||
} set_physical_layout;
|
||||
|
||||
struct {
|
||||
zmk_hid_indicators_t indicators;
|
||||
} set_hid_indicators;
|
||||
} data;
|
||||
} __packed;
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2023 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
@@ -93,7 +93,18 @@ static int zmk_battery_update(const struct device *battery) {
|
||||
|
||||
if (last_state_of_charge != state_of_charge.val1) {
|
||||
last_state_of_charge = state_of_charge.val1;
|
||||
|
||||
rc = raise_zmk_battery_state_changed(
|
||||
(struct zmk_battery_state_changed){.state_of_charge = last_state_of_charge});
|
||||
|
||||
if (rc != 0) {
|
||||
LOG_ERR("Failed to raise battery state changed event: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_BT_BAS)
|
||||
if (bt_bas_get_battery_level() != last_state_of_charge) {
|
||||
LOG_DBG("Setting BAS GATT battery level to %d.", last_state_of_charge);
|
||||
|
||||
rc = bt_bas_set_battery_level(last_state_of_charge);
|
||||
@@ -102,10 +113,8 @@ static int zmk_battery_update(const struct device *battery) {
|
||||
LOG_WRN("Failed to set BAS GATT battery level (err %d)", rc);
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
rc = raise_zmk_battery_state_changed(
|
||||
(struct zmk_battery_state_changed){.state_of_charge = last_state_of_charge});
|
||||
}
|
||||
#endif
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,8 @@
|
||||
|
||||
#endif
|
||||
|
||||
#include <zmk/ble.h>
|
||||
#if ZMK_BLE_IS_CENTRAL
|
||||
#include <zmk/split/bluetooth/central.h>
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT) && IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
#include <zmk/split/central.h>
|
||||
#endif
|
||||
|
||||
#include <drivers/behavior.h>
|
||||
@@ -95,19 +94,19 @@ int zmk_behavior_invoke_binding(const struct zmk_behavior_binding *src_binding,
|
||||
case BEHAVIOR_LOCALITY_CENTRAL:
|
||||
return invoke_locally(&binding, event, pressed);
|
||||
case BEHAVIOR_LOCALITY_EVENT_SOURCE:
|
||||
#if ZMK_BLE_IS_CENTRAL // source is a member of event because CONFIG_ZMK_SPLIT is enabled
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT) && IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
if (event.source == ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL) {
|
||||
return invoke_locally(&binding, event, pressed);
|
||||
} else {
|
||||
return zmk_split_bt_invoke_behavior(event.source, &binding, event, pressed);
|
||||
return zmk_split_central_invoke_behavior(event.source, &binding, event, pressed);
|
||||
}
|
||||
#else
|
||||
return invoke_locally(&binding, event, pressed);
|
||||
#endif
|
||||
case BEHAVIOR_LOCALITY_GLOBAL:
|
||||
#if ZMK_BLE_IS_CENTRAL
|
||||
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
|
||||
zmk_split_bt_invoke_behavior(i, &binding, event, pressed);
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT) && IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
for (int i = 0; i < ZMK_SPLIT_CENTRAL_PERIPHERAL_COUNT; i++) {
|
||||
zmk_split_central_invoke_behavior(i, &binding, event, pressed);
|
||||
}
|
||||
#endif
|
||||
return invoke_locally(&binding, event, pressed);
|
||||
|
||||
@@ -84,7 +84,8 @@ static int caps_word_keycode_state_changed_listener(const zmk_event_t *eh);
|
||||
ZMK_LISTENER(behavior_caps_word, caps_word_keycode_state_changed_listener);
|
||||
ZMK_SUBSCRIPTION(behavior_caps_word, zmk_keycode_state_changed);
|
||||
|
||||
static const struct device *devs[] = {DT_INST_FOREACH_STATUS_OKAY(DEVICE_DT_INST_GET)};
|
||||
#define GET_DEV(inst) DEVICE_DT_INST_GET(inst),
|
||||
static const struct device *devs[] = {DT_INST_FOREACH_STATUS_OKAY(GET_DEV)};
|
||||
|
||||
static bool caps_word_is_caps_includelist(const struct behavior_caps_word_config *config,
|
||||
uint16_t usage_page, uint8_t usage_id,
|
||||
|
||||
@@ -77,7 +77,8 @@ static int key_repeat_keycode_state_changed_listener(const zmk_event_t *eh);
|
||||
ZMK_LISTENER(behavior_key_repeat, key_repeat_keycode_state_changed_listener);
|
||||
ZMK_SUBSCRIPTION(behavior_key_repeat, zmk_keycode_state_changed);
|
||||
|
||||
static const struct device *devs[] = {DT_INST_FOREACH_STATUS_OKAY(DEVICE_DT_INST_GET)};
|
||||
#define GET_DEV(inst) DEVICE_DT_INST_GET(inst),
|
||||
static const struct device *devs[] = {DT_INST_FOREACH_STATUS_OKAY(GET_DEV)};
|
||||
|
||||
static int key_repeat_keycode_state_changed_listener(const zmk_event_t *eh) {
|
||||
struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh);
|
||||
|
||||
@@ -129,7 +129,9 @@ static int behavior_macro_init(const struct device *dev) {
|
||||
LOG_DBG("Release will resume at %d", state->release_state.start_index);
|
||||
break;
|
||||
} else {
|
||||
// Ignore regular invokable bindings
|
||||
// Mostly ignore regular invokable bindings, except they will consume macro parameters
|
||||
state->release_state.param1_source = PARAM_SOURCE_BINDING;
|
||||
state->release_state.param2_source = PARAM_SOURCE_BINDING;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,27 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
|
||||
|
||||
static const struct behavior_parameter_value_metadata param_values[] = {
|
||||
{.display_name = "MB1", .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, .value = MB1},
|
||||
{.display_name = "MB2", .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, .value = MB2},
|
||||
{.display_name = "MB3", .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, .value = MB3},
|
||||
{.display_name = "MB4", .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, .value = MB4},
|
||||
{.display_name = "MB5", .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, .value = MB5}};
|
||||
|
||||
static const struct behavior_parameter_metadata_set param_metadata_set[] = {{
|
||||
.param1_values = param_values,
|
||||
.param1_values_len = ARRAY_SIZE(param_values),
|
||||
}};
|
||||
|
||||
static const struct behavior_parameter_metadata metadata = {
|
||||
.sets_len = ARRAY_SIZE(param_metadata_set),
|
||||
.sets = param_metadata_set,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
static void process_key_state(const struct device *dev, int32_t val, bool pressed) {
|
||||
for (int i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) {
|
||||
if (val & BIT(i)) {
|
||||
@@ -47,7 +68,12 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
|
||||
}
|
||||
|
||||
static const struct behavior_driver_api behavior_mouse_key_press_driver_api = {
|
||||
.binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released};
|
||||
.binding_pressed = on_keymap_binding_pressed,
|
||||
.binding_released = on_keymap_binding_released,
|
||||
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
|
||||
.parameter_metadata = &metadata,
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
|
||||
};
|
||||
|
||||
#define MKP_INST(n) \
|
||||
BEHAVIOR_DT_INST_DEFINE(n, NULL, NULL, NULL, NULL, POST_KERNEL, \
|
||||
|
||||
@@ -78,7 +78,7 @@ static struct bt_data zmk_ble_ad[] = {
|
||||
),
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE) && IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
|
||||
static bt_addr_le_t peripheral_addrs[ZMK_SPLIT_BLE_PERIPHERAL_COUNT];
|
||||
|
||||
@@ -95,8 +95,13 @@ static void raise_profile_changed_event_callback(struct k_work *work) {
|
||||
|
||||
K_WORK_DEFINE(raise_profile_changed_event_work, raise_profile_changed_event_callback);
|
||||
|
||||
bool zmk_ble_active_profile_is_open(void) {
|
||||
return !bt_addr_le_cmp(&profiles[active_profile].peer, BT_ADDR_LE_ANY);
|
||||
bool zmk_ble_active_profile_is_open(void) { return zmk_ble_profile_is_open(active_profile); }
|
||||
|
||||
bool zmk_ble_profile_is_open(uint8_t index) {
|
||||
if (index >= ZMK_BLE_PROFILE_COUNT) {
|
||||
return false;
|
||||
}
|
||||
return !bt_addr_le_cmp(&profiles[index].peer, BT_ADDR_LE_ANY);
|
||||
}
|
||||
|
||||
void set_profile_address(uint8_t index, const bt_addr_le_t *addr) {
|
||||
@@ -115,9 +120,16 @@ void set_profile_address(uint8_t index, const bt_addr_le_t *addr) {
|
||||
}
|
||||
|
||||
bool zmk_ble_active_profile_is_connected(void) {
|
||||
return zmk_ble_profile_is_connected(active_profile);
|
||||
}
|
||||
|
||||
bool zmk_ble_profile_is_connected(uint8_t index) {
|
||||
if (index >= ZMK_BLE_PROFILE_COUNT) {
|
||||
return false;
|
||||
}
|
||||
struct bt_conn *conn;
|
||||
struct bt_conn_info info;
|
||||
bt_addr_le_t *addr = zmk_ble_active_profile_addr();
|
||||
bt_addr_le_t *addr = &profiles[index].peer;
|
||||
if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) {
|
||||
return false;
|
||||
} else if ((conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr)) == NULL) {
|
||||
@@ -250,6 +262,13 @@ int zmk_ble_profile_index(const bt_addr_le_t *addr) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
bt_addr_le_t *zmk_ble_profile_address(uint8_t index) {
|
||||
if (index >= ZMK_BLE_PROFILE_COUNT) {
|
||||
return (bt_addr_le_t *)(BT_ADDR_LE_NONE);
|
||||
}
|
||||
return &profiles[index].peer;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||
static void ble_save_profile_work(struct k_work *work) {
|
||||
settings_save_one("ble/active_profile", &active_profile, sizeof(active_profile));
|
||||
@@ -357,7 +376,7 @@ int zmk_ble_set_device_name(char *name) {
|
||||
return update_advertising();
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE) && IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
|
||||
int zmk_ble_put_peripheral_addr(const bt_addr_le_t *addr) {
|
||||
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
|
||||
@@ -446,7 +465,7 @@ static int ble_profiles_handle_set(const char *name, size_t len, settings_read_c
|
||||
return err;
|
||||
}
|
||||
}
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE) && IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
else if (settings_name_steq(name, "peripheral_addresses", &next) && next) {
|
||||
if (len != sizeof(bt_addr_le_t)) {
|
||||
return -EINVAL;
|
||||
|
||||
380
app/src/combo.c
380
app/src/combo.c
@@ -9,6 +9,7 @@
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/dlist.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#include <drivers/behavior.h>
|
||||
@@ -26,53 +27,99 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
|
||||
|
||||
#if CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO > 0
|
||||
|
||||
#warning \
|
||||
"CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO is deprecated, and is auto-calculated from the devicetree now."
|
||||
|
||||
#endif
|
||||
|
||||
#if CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY > 0
|
||||
|
||||
#warning "CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY is deprecated, and is auto-calculated."
|
||||
|
||||
#endif
|
||||
|
||||
#define COMBOS_KEYS_BYTE_ARRAY(node_id) \
|
||||
uint8_t _CONCAT(combo_prop_, node_id)[DT_PROP_LEN(node_id, key_positions)];
|
||||
|
||||
#define MAX_COMBO_KEYS sizeof(union {DT_INST_FOREACH_CHILD(0, COMBOS_KEYS_BYTE_ARRAY)})
|
||||
|
||||
struct combo_cfg {
|
||||
int32_t key_positions[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO];
|
||||
int32_t key_position_len;
|
||||
struct zmk_behavior_binding behavior;
|
||||
int32_t key_positions[MAX_COMBO_KEYS];
|
||||
int16_t key_position_len;
|
||||
int16_t require_prior_idle_ms;
|
||||
int32_t timeout_ms;
|
||||
int32_t require_prior_idle_ms;
|
||||
uint32_t layer_mask;
|
||||
struct zmk_behavior_binding behavior;
|
||||
// if slow release is set, the combo releases when the last key is released.
|
||||
// otherwise, the combo releases when the first key is released.
|
||||
bool slow_release;
|
||||
// the virtual key position is a key position outside the range used by the keyboard.
|
||||
// it is necessary so hold-taps can uniquely identify a behavior.
|
||||
int32_t virtual_key_position;
|
||||
int32_t layers_len;
|
||||
int8_t layers[];
|
||||
};
|
||||
|
||||
struct active_combo {
|
||||
const struct combo_cfg *combo;
|
||||
uint16_t combo_idx;
|
||||
// key_positions_pressed is filled with key_positions when the combo is pressed.
|
||||
// The keys are removed from this array when they are released.
|
||||
// Once this array is empty, the behavior is released.
|
||||
uint32_t key_positions_pressed_count;
|
||||
struct zmk_position_state_changed_event
|
||||
key_positions_pressed[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO];
|
||||
uint16_t key_positions_pressed_count;
|
||||
struct zmk_position_state_changed_event key_positions_pressed[MAX_COMBO_KEYS];
|
||||
};
|
||||
|
||||
struct combo_candidate {
|
||||
const struct combo_cfg *combo;
|
||||
// the time after which this behavior should be removed from candidates.
|
||||
// by keeping track of when the candidate should be cleared there is no
|
||||
// possibility of accidental releases.
|
||||
int64_t timeout_at;
|
||||
};
|
||||
#define PROP_BIT_AT_IDX(n, prop, idx) BIT(DT_PROP_BY_IDX(n, prop, idx))
|
||||
|
||||
uint32_t pressed_keys_count = 0;
|
||||
#define NODE_PROP_BITMASK(n, prop) \
|
||||
COND_CODE_1(DT_NODE_HAS_PROP(n, prop), \
|
||||
(DT_FOREACH_PROP_ELEM_SEP(n, prop, PROP_BIT_AT_IDX, (|))), (0))
|
||||
|
||||
#define GET_KEY_POSITION_MASK_PORTION(idx, n) ((NODE_PROP_BITMASK(n, key_positions) >> idx) & 0xFF)
|
||||
|
||||
#define COMBO_INST(n, positions) \
|
||||
COND_CODE_1(IS_EQ(DT_PROP_LEN(n, key_positions), positions), \
|
||||
( \
|
||||
{ \
|
||||
.timeout_ms = DT_PROP(n, timeout_ms), \
|
||||
.require_prior_idle_ms = DT_PROP(n, require_prior_idle_ms), \
|
||||
.key_positions = DT_PROP(n, key_positions), \
|
||||
.key_position_len = DT_PROP_LEN(n, key_positions), \
|
||||
.behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, n), \
|
||||
.slow_release = DT_PROP(n, slow_release), \
|
||||
.layer_mask = NODE_PROP_BITMASK(n, layers), \
|
||||
}, ), \
|
||||
())
|
||||
|
||||
#define COMBO_CONFIGS_WITH_MATCHING_POSITIONS_LEN(positions, _ignore) \
|
||||
DT_INST_FOREACH_CHILD_VARGS(0, COMBO_INST, positions)
|
||||
|
||||
// We do some magic here to generate the `combos` array by "key position length", looping
|
||||
// by key position length and on each iteration, only include entries where the `key-positions`
|
||||
// length matches.
|
||||
// Doing so allows our bitmasks to be "shorted key positions list first" when searching for matches.
|
||||
// `20` is chosen as a reasonable limit, since the theoretical maximum number of keys you might
|
||||
// reasonably press simultaneously with 10 fingers is 20 keys, two keys per finger.
|
||||
static const struct combo_cfg combos[] = {
|
||||
LISTIFY(20, COMBO_CONFIGS_WITH_MATCHING_POSITIONS_LEN, (), 0)};
|
||||
|
||||
#define COMBO_ONE(n) +1
|
||||
|
||||
#define COMBO_CHILDREN_COUNT (0 DT_INST_FOREACH_CHILD(0, COMBO_ONE))
|
||||
|
||||
// We need at least 4 bytes to avoid alignment issues
|
||||
#define BYTES_FOR_COMBOS_MASK DIV_ROUND_UP(COMBO_CHILDREN_COUNT, 32)
|
||||
|
||||
uint8_t pressed_keys_count = 0;
|
||||
// set of keys pressed
|
||||
struct zmk_position_state_changed_event pressed_keys[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO] = {};
|
||||
struct zmk_position_state_changed_event pressed_keys[MAX_COMBO_KEYS] = {};
|
||||
// the set of candidate combos based on the currently pressed_keys
|
||||
struct combo_candidate candidates[CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY];
|
||||
uint32_t candidates[BYTES_FOR_COMBOS_MASK];
|
||||
// the last candidate that was completely pressed
|
||||
const struct combo_cfg *fully_pressed_combo = NULL;
|
||||
int16_t fully_pressed_combo = INT16_MAX;
|
||||
// a lookup dict that maps a key position to all combos on that position
|
||||
const struct combo_cfg *combo_lookup[ZMK_KEYMAP_LEN][CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY] = {NULL};
|
||||
uint32_t combo_lookup[ZMK_KEYMAP_LEN][BYTES_FOR_COMBOS_MASK] = {};
|
||||
// combos that have been activated and still have (some) keys pressed
|
||||
// this array is always contiguous from 0.
|
||||
struct active_combo active_combos[CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS] = {NULL};
|
||||
int active_combo_count = 0;
|
||||
struct active_combo active_combos[CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS] = {};
|
||||
uint8_t active_combo_count = 0;
|
||||
|
||||
struct k_work_delayable timeout_task;
|
||||
int64_t timeout_task_timeout_at;
|
||||
@@ -90,52 +137,22 @@ static void store_last_tapped(int64_t timestamp) {
|
||||
|
||||
// Store the combo key pointer in the combos array, one pointer for each key position
|
||||
// The combos are sorted shortest-first, then by virtual-key-position.
|
||||
static int initialize_combo(const struct combo_cfg *new_combo) {
|
||||
for (int i = 0; i < new_combo->key_position_len; i++) {
|
||||
int32_t position = new_combo->key_positions[i];
|
||||
if (position >= ZMK_KEYMAP_LEN) {
|
||||
LOG_ERR("Unable to initialize combo, key position %d does not exist", position);
|
||||
return -EINVAL;
|
||||
}
|
||||
static int initialize_combo(size_t index) {
|
||||
const struct combo_cfg *new_combo = &combos[index];
|
||||
|
||||
const struct combo_cfg *insert_combo = new_combo;
|
||||
bool set = false;
|
||||
for (int j = 0; j < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; j++) {
|
||||
const struct combo_cfg *combo_at_j = combo_lookup[position][j];
|
||||
if (combo_at_j == NULL) {
|
||||
combo_lookup[position][j] = insert_combo;
|
||||
set = true;
|
||||
break;
|
||||
}
|
||||
if (combo_at_j->key_position_len < insert_combo->key_position_len ||
|
||||
(combo_at_j->key_position_len == insert_combo->key_position_len &&
|
||||
combo_at_j->virtual_key_position < insert_combo->virtual_key_position)) {
|
||||
continue;
|
||||
}
|
||||
// put insert_combo in this spot, move all other combos up.
|
||||
combo_lookup[position][j] = insert_combo;
|
||||
insert_combo = combo_at_j;
|
||||
}
|
||||
if (!set) {
|
||||
LOG_ERR("Too many combos for key position %d, CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY %d.",
|
||||
position, CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY);
|
||||
return -ENOMEM;
|
||||
}
|
||||
for (size_t kp = 0; kp < new_combo->key_position_len; kp++) {
|
||||
sys_bitfield_set_bit((mem_addr_t)&combo_lookup[new_combo->key_positions[kp]], index);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool combo_active_on_layer(const struct combo_cfg *combo, uint8_t layer) {
|
||||
if (combo->layers[0] == -1) {
|
||||
// -1 in the first layer position is global layer scope
|
||||
if (!combo->layer_mask) {
|
||||
return true;
|
||||
}
|
||||
for (int j = 0; j < combo->layers_len; j++) {
|
||||
if (combo->layers[j] == layer) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
return combo->layer_mask & BIT(layer);
|
||||
}
|
||||
|
||||
static bool is_quick_tap(const struct combo_cfg *combo, int64_t timestamp) {
|
||||
@@ -145,66 +162,58 @@ static bool is_quick_tap(const struct combo_cfg *combo, int64_t timestamp) {
|
||||
static int setup_candidates_for_first_keypress(int32_t position, int64_t timestamp) {
|
||||
int number_of_combo_candidates = 0;
|
||||
uint8_t highest_active_layer = zmk_keymap_highest_layer_active();
|
||||
for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) {
|
||||
const struct combo_cfg *combo = combo_lookup[position][i];
|
||||
if (combo == NULL) {
|
||||
return number_of_combo_candidates;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(combos); i++) {
|
||||
if (sys_bitfield_test_bit((mem_addr_t)&combo_lookup[position], i)) {
|
||||
const struct combo_cfg *combo = &combos[i];
|
||||
if (combo_active_on_layer(combo, highest_active_layer) &&
|
||||
!is_quick_tap(combo, timestamp)) {
|
||||
sys_bitfield_set_bit((mem_addr_t)&candidates, i);
|
||||
number_of_combo_candidates++;
|
||||
}
|
||||
// LOG_DBG("combo timeout %d %d %d", position, i, candidates[i].timeout_at);
|
||||
}
|
||||
if (combo_active_on_layer(combo, highest_active_layer) && !is_quick_tap(combo, timestamp)) {
|
||||
candidates[number_of_combo_candidates].combo = combo;
|
||||
candidates[number_of_combo_candidates].timeout_at = timestamp + combo->timeout_ms;
|
||||
number_of_combo_candidates++;
|
||||
}
|
||||
// LOG_DBG("combo timeout %d %d %d", position, i, candidates[i].timeout_at);
|
||||
}
|
||||
|
||||
return number_of_combo_candidates;
|
||||
}
|
||||
|
||||
static inline uint8_t zero_one_or_more_bits(uint32_t field) {
|
||||
if (field == 0) {
|
||||
return 0;
|
||||
}
|
||||
if ((field & (field - 1)) == 0) {
|
||||
return 1;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int filter_candidates(int32_t position) {
|
||||
// this code iterates over candidates and the lookup together to filter in O(n)
|
||||
// assuming they are both sorted on key_position_len, virtual_key_position
|
||||
int matches = 0, lookup_idx = 0, candidate_idx = 0;
|
||||
while (lookup_idx < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY &&
|
||||
candidate_idx < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY) {
|
||||
const struct combo_cfg *candidate = candidates[candidate_idx].combo;
|
||||
const struct combo_cfg *lookup = combo_lookup[position][lookup_idx];
|
||||
if (candidate == NULL || lookup == NULL) {
|
||||
break;
|
||||
}
|
||||
if (candidate->virtual_key_position == lookup->virtual_key_position) {
|
||||
candidates[matches] = candidates[candidate_idx];
|
||||
matches++;
|
||||
candidate_idx++;
|
||||
lookup_idx++;
|
||||
} else if (candidate->key_position_len > lookup->key_position_len) {
|
||||
lookup_idx++;
|
||||
} else if (candidate->key_position_len < lookup->key_position_len) {
|
||||
candidate_idx++;
|
||||
} else if (candidate->virtual_key_position > lookup->virtual_key_position) {
|
||||
lookup_idx++;
|
||||
} else if (candidate->virtual_key_position < lookup->virtual_key_position) {
|
||||
candidate_idx++;
|
||||
int matches = 0;
|
||||
for (int i = 0; i < BYTES_FOR_COMBOS_MASK; i++) {
|
||||
candidates[i] &= combo_lookup[position][i];
|
||||
if (matches < 2) {
|
||||
matches += zero_one_or_more_bits(candidates[i]);
|
||||
}
|
||||
}
|
||||
// clear unmatched candidates
|
||||
for (int i = matches; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) {
|
||||
candidates[i].combo = NULL;
|
||||
}
|
||||
// LOG_DBG("combo matches after filter %d", matches);
|
||||
|
||||
LOG_DBG("combo matches after filter %d", matches);
|
||||
return matches;
|
||||
}
|
||||
|
||||
static int64_t first_candidate_timeout() {
|
||||
if (pressed_keys_count == 0) {
|
||||
return LONG_MAX;
|
||||
}
|
||||
|
||||
int64_t first_timeout = LONG_MAX;
|
||||
for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) {
|
||||
if (candidates[i].combo == NULL) {
|
||||
break;
|
||||
}
|
||||
if (candidates[i].timeout_at < first_timeout) {
|
||||
first_timeout = candidates[i].timeout_at;
|
||||
for (int i = 0; i < ARRAY_SIZE(combos); i++) {
|
||||
if (sys_bitfield_test_bit((mem_addr_t)&candidates, i)) {
|
||||
first_timeout = MIN(first_timeout, combos[i].timeout_ms);
|
||||
}
|
||||
}
|
||||
return first_timeout;
|
||||
|
||||
return pressed_keys[0].data.timestamp + first_timeout;
|
||||
}
|
||||
|
||||
static inline bool candidate_is_completely_pressed(const struct combo_cfg *candidate) {
|
||||
@@ -219,26 +228,17 @@ static inline bool candidate_is_completely_pressed(const struct combo_cfg *candi
|
||||
static int cleanup();
|
||||
|
||||
static int filter_timed_out_candidates(int64_t timestamp) {
|
||||
int remaining_candidates = 0;
|
||||
for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) {
|
||||
struct combo_candidate *candidate = &candidates[i];
|
||||
if (candidate->combo == NULL) {
|
||||
break;
|
||||
}
|
||||
if (candidate->timeout_at > timestamp) {
|
||||
bool need_to_bubble_up = remaining_candidates != i;
|
||||
if (need_to_bubble_up) {
|
||||
// bubble up => reorder candidates so they're contiguous
|
||||
candidates[remaining_candidates].combo = candidate->combo;
|
||||
candidates[remaining_candidates].timeout_at = candidate->timeout_at;
|
||||
// clear the previous location
|
||||
candidates[i].combo = NULL;
|
||||
candidates[i].timeout_at = 0;
|
||||
}
|
||||
__ASSERT(pressed_keys_count > 0, "Searching for a candidate timeout with no keys pressed");
|
||||
|
||||
remaining_candidates++;
|
||||
} else {
|
||||
candidate->combo = NULL;
|
||||
int remaining_candidates = 0;
|
||||
for (int i = 0; i < ARRAY_SIZE(combos); i++) {
|
||||
if (sys_bitfield_test_bit((mem_addr_t)&candidates, i)) {
|
||||
|
||||
if (pressed_keys[0].data.timestamp + combos[i].timeout_ms > timestamp) {
|
||||
remaining_candidates++;
|
||||
} else {
|
||||
sys_bitfield_clear_bit((mem_addr_t)&candidates, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,18 +249,8 @@ static int filter_timed_out_candidates(int64_t timestamp) {
|
||||
return remaining_candidates;
|
||||
}
|
||||
|
||||
static int clear_candidates() {
|
||||
for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) {
|
||||
if (candidates[i].combo == NULL) {
|
||||
return i;
|
||||
}
|
||||
candidates[i].combo = NULL;
|
||||
}
|
||||
return CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY;
|
||||
}
|
||||
|
||||
static int capture_pressed_key(const struct zmk_position_state_changed *ev) {
|
||||
if (pressed_keys_count == CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY) {
|
||||
if (pressed_keys_count == MAX_COMBO_KEYS) {
|
||||
return ZMK_EV_EVENT_BUBBLE;
|
||||
}
|
||||
|
||||
@@ -271,7 +261,7 @@ static int capture_pressed_key(const struct zmk_position_state_changed *ev) {
|
||||
const struct zmk_listener zmk_listener_combo;
|
||||
|
||||
static int release_pressed_keys() {
|
||||
uint32_t count = pressed_keys_count;
|
||||
uint8_t count = pressed_keys_count;
|
||||
pressed_keys_count = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
struct zmk_position_state_changed_event *ev = &pressed_keys[i];
|
||||
@@ -288,9 +278,10 @@ static int release_pressed_keys() {
|
||||
return count;
|
||||
}
|
||||
|
||||
static inline int press_combo_behavior(const struct combo_cfg *combo, int32_t timestamp) {
|
||||
static inline int press_combo_behavior(int combo_idx, const struct combo_cfg *combo,
|
||||
int32_t timestamp) {
|
||||
struct zmk_behavior_binding_event event = {
|
||||
.position = combo->virtual_key_position,
|
||||
.position = ZMK_VIRTUAL_KEY_POSITION_COMBO(combo_idx),
|
||||
.timestamp = timestamp,
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||
.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL,
|
||||
@@ -302,9 +293,10 @@ static inline int press_combo_behavior(const struct combo_cfg *combo, int32_t ti
|
||||
return zmk_behavior_invoke_binding(&combo->behavior, event, true);
|
||||
}
|
||||
|
||||
static inline int release_combo_behavior(const struct combo_cfg *combo, int32_t timestamp) {
|
||||
static inline int release_combo_behavior(int combo_idx, const struct combo_cfg *combo,
|
||||
int32_t timestamp) {
|
||||
struct zmk_behavior_binding_event event = {
|
||||
.position = combo->virtual_key_position,
|
||||
.position = ZMK_VIRTUAL_KEY_POSITION_COMBO(combo_idx),
|
||||
.timestamp = timestamp,
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||
.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL,
|
||||
@@ -316,7 +308,7 @@ static inline int release_combo_behavior(const struct combo_cfg *combo, int32_t
|
||||
|
||||
static void move_pressed_keys_to_active_combo(struct active_combo *active_combo) {
|
||||
|
||||
int combo_length = MIN(pressed_keys_count, active_combo->combo->key_position_len);
|
||||
int combo_length = MIN(pressed_keys_count, combos[active_combo->combo_idx].key_position_len);
|
||||
for (int i = 0; i < combo_length; i++) {
|
||||
active_combo->key_positions_pressed[i] = pressed_keys[i];
|
||||
}
|
||||
@@ -330,10 +322,10 @@ static void move_pressed_keys_to_active_combo(struct active_combo *active_combo)
|
||||
pressed_keys_count -= combo_length;
|
||||
}
|
||||
|
||||
static struct active_combo *store_active_combo(const struct combo_cfg *combo) {
|
||||
static struct active_combo *store_active_combo(int32_t combo_idx) {
|
||||
for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS; i++) {
|
||||
if (active_combos[i].combo == NULL) {
|
||||
active_combos[i].combo = combo;
|
||||
if (active_combos[i].combo_idx == UINT16_MAX) {
|
||||
active_combos[i].combo_idx = combo_idx;
|
||||
active_combo_count++;
|
||||
return &active_combos[i];
|
||||
}
|
||||
@@ -344,15 +336,16 @@ static struct active_combo *store_active_combo(const struct combo_cfg *combo) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void activate_combo(const struct combo_cfg *combo) {
|
||||
struct active_combo *active_combo = store_active_combo(combo);
|
||||
static void activate_combo(int combo_idx) {
|
||||
struct active_combo *active_combo = store_active_combo(combo_idx);
|
||||
if (active_combo == NULL) {
|
||||
// unable to store combo
|
||||
release_pressed_keys();
|
||||
return;
|
||||
}
|
||||
move_pressed_keys_to_active_combo(active_combo);
|
||||
press_combo_behavior(combo, active_combo->key_positions_pressed[0].data.timestamp);
|
||||
press_combo_behavior(combo_idx, &combos[combo_idx],
|
||||
active_combo->key_positions_pressed[0].data.timestamp);
|
||||
}
|
||||
|
||||
static void deactivate_combo(int active_combo_index) {
|
||||
@@ -361,8 +354,8 @@ static void deactivate_combo(int active_combo_index) {
|
||||
memcpy(&active_combos[active_combo_index], &active_combos[active_combo_count],
|
||||
sizeof(struct active_combo));
|
||||
}
|
||||
active_combos[active_combo_count].combo = NULL;
|
||||
active_combos[active_combo_count] = (struct active_combo){0};
|
||||
active_combos[active_combo_count].combo_idx = UINT16_MAX;
|
||||
}
|
||||
|
||||
/* returns true if a key was released. */
|
||||
@@ -371,8 +364,8 @@ static bool release_combo_key(int32_t position, int64_t timestamp) {
|
||||
struct active_combo *active_combo = &active_combos[combo_idx];
|
||||
|
||||
bool key_released = false;
|
||||
bool all_keys_pressed =
|
||||
active_combo->key_positions_pressed_count == active_combo->combo->key_position_len;
|
||||
bool all_keys_pressed = active_combo->key_positions_pressed_count ==
|
||||
combos[active_combo->combo_idx].key_position_len;
|
||||
bool all_keys_released = true;
|
||||
for (int i = 0; i < active_combo->key_positions_pressed_count; i++) {
|
||||
if (key_released) {
|
||||
@@ -387,9 +380,9 @@ static bool release_combo_key(int32_t position, int64_t timestamp) {
|
||||
|
||||
if (key_released) {
|
||||
active_combo->key_positions_pressed_count--;
|
||||
if ((active_combo->combo->slow_release && all_keys_released) ||
|
||||
(!active_combo->combo->slow_release && all_keys_pressed)) {
|
||||
release_combo_behavior(active_combo->combo, timestamp);
|
||||
const struct combo_cfg *c = &combos[active_combo->combo_idx];
|
||||
if ((c->slow_release && all_keys_released) || (!c->slow_release && all_keys_pressed)) {
|
||||
release_combo_behavior(active_combo->combo_idx, c, timestamp);
|
||||
}
|
||||
if (all_keys_released) {
|
||||
deactivate_combo(combo_idx);
|
||||
@@ -402,10 +395,10 @@ static bool release_combo_key(int32_t position, int64_t timestamp) {
|
||||
|
||||
static int cleanup() {
|
||||
k_work_cancel_delayable(&timeout_task);
|
||||
clear_candidates();
|
||||
if (fully_pressed_combo != NULL) {
|
||||
memset(candidates, 0, BYTES_FOR_COMBOS_MASK * sizeof(uint32_t));
|
||||
if (fully_pressed_combo != INT16_MAX) {
|
||||
activate_combo(fully_pressed_combo);
|
||||
fully_pressed_combo = NULL;
|
||||
fully_pressed_combo = INT16_MAX;
|
||||
}
|
||||
return release_pressed_keys();
|
||||
}
|
||||
@@ -427,7 +420,7 @@ static void update_timeout_task() {
|
||||
|
||||
static int position_state_down(const zmk_event_t *ev, struct zmk_position_state_changed *data) {
|
||||
int num_candidates;
|
||||
if (candidates[0].combo == NULL) {
|
||||
if (!pressed_keys_count) {
|
||||
num_candidates = setup_candidates_for_first_keypress(data->position, data->timestamp);
|
||||
if (num_candidates == 0) {
|
||||
return ZMK_EV_EVENT_BUBBLE;
|
||||
@@ -436,27 +429,31 @@ static int position_state_down(const zmk_event_t *ev, struct zmk_position_state_
|
||||
filter_timed_out_candidates(data->timestamp);
|
||||
num_candidates = filter_candidates(data->position);
|
||||
}
|
||||
update_timeout_task();
|
||||
|
||||
const struct combo_cfg *candidate_combo = candidates[0].combo;
|
||||
LOG_DBG("combo: capturing position event %d", data->position);
|
||||
int ret = capture_pressed_key(data);
|
||||
switch (num_candidates) {
|
||||
case 0:
|
||||
update_timeout_task();
|
||||
|
||||
if (num_candidates) {
|
||||
for (int i = 0; i < ARRAY_SIZE(combos); i++) {
|
||||
if (sys_bitfield_test_bit((mem_addr_t)&candidates, i)) {
|
||||
const struct combo_cfg *candidate_combo = &combos[i];
|
||||
if (candidate_is_completely_pressed(candidate_combo)) {
|
||||
fully_pressed_combo = i;
|
||||
if (num_candidates == 1) {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cleanup();
|
||||
return ret;
|
||||
case 1:
|
||||
if (candidate_is_completely_pressed(candidate_combo)) {
|
||||
fully_pressed_combo = candidate_combo;
|
||||
cleanup();
|
||||
}
|
||||
return ret;
|
||||
default:
|
||||
if (candidate_is_completely_pressed(candidate_combo)) {
|
||||
fully_pressed_combo = candidate_combo;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int position_state_up(const zmk_event_t *ev, struct zmk_position_state_changed *data) {
|
||||
@@ -481,8 +478,11 @@ static void combo_timeout_handler(struct k_work *item) {
|
||||
return;
|
||||
}
|
||||
if (filter_timed_out_candidates(timeout_task_timeout_at) == 0) {
|
||||
LOG_DBG("CLEANUP!");
|
||||
cleanup();
|
||||
}
|
||||
|
||||
LOG_DBG("ABOUT TO UPDATE IN TIMEOUT");
|
||||
update_timeout_task();
|
||||
}
|
||||
|
||||
@@ -520,26 +520,16 @@ ZMK_LISTENER(combo, behavior_combo_listener);
|
||||
ZMK_SUBSCRIPTION(combo, zmk_position_state_changed);
|
||||
ZMK_SUBSCRIPTION(combo, zmk_keycode_state_changed);
|
||||
|
||||
#define COMBO_INST(n) \
|
||||
static const struct combo_cfg combo_config_##n = { \
|
||||
.timeout_ms = DT_PROP(n, timeout_ms), \
|
||||
.require_prior_idle_ms = DT_PROP(n, require_prior_idle_ms), \
|
||||
.key_positions = DT_PROP(n, key_positions), \
|
||||
.key_position_len = DT_PROP_LEN(n, key_positions), \
|
||||
.behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, n), \
|
||||
.virtual_key_position = ZMK_VIRTUAL_KEY_POSITION_COMBO(__COUNTER__), \
|
||||
.slow_release = DT_PROP(n, slow_release), \
|
||||
.layers = DT_PROP(n, layers), \
|
||||
.layers_len = DT_PROP_LEN(n, layers), \
|
||||
};
|
||||
|
||||
#define INITIALIZE_COMBO(n) initialize_combo(&combo_config_##n);
|
||||
|
||||
DT_INST_FOREACH_CHILD(0, COMBO_INST)
|
||||
|
||||
static int combo_init(void) {
|
||||
for (size_t i = 0; i < CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS; i++) {
|
||||
active_combos[i].combo_idx = UINT16_MAX;
|
||||
}
|
||||
|
||||
k_work_init_delayable(&timeout_task, combo_timeout_handler);
|
||||
DT_INST_FOREACH_CHILD(0, INITIALIZE_COMBO);
|
||||
LOG_WRN("Have %d combos!", ARRAY_SIZE(combos));
|
||||
for (int i = 0; i < ARRAY_SIZE(combos); i++) {
|
||||
initialize_combo(i);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ config ZMK_WIDGET_LAYER_STATUS
|
||||
|
||||
config ZMK_WIDGET_BATTERY_STATUS
|
||||
bool "Widget for battery charge information, using small icons"
|
||||
depends on BT
|
||||
depends on ZMK_BATTERY_REPORTING
|
||||
select LV_USE_LABEL
|
||||
|
||||
if ZMK_WIDGET_BATTERY_STATUS
|
||||
|
||||
@@ -445,7 +445,7 @@ void zmk_hid_mouse_movement_update(int16_t hwheel, int16_t wheel) {
|
||||
LOG_DBG("Mouse movement updated to %d/%d", mouse_report.body.d_x, mouse_report.body.d_y);
|
||||
}
|
||||
|
||||
void zmk_hid_mouse_scroll_set(int8_t hwheel, int8_t wheel) {
|
||||
void zmk_hid_mouse_scroll_set(int16_t hwheel, int16_t wheel) {
|
||||
mouse_report.body.d_scroll_x = hwheel;
|
||||
mouse_report.body.d_scroll_y = wheel;
|
||||
|
||||
@@ -453,7 +453,7 @@ void zmk_hid_mouse_scroll_set(int8_t hwheel, int8_t wheel) {
|
||||
mouse_report.body.d_scroll_y);
|
||||
}
|
||||
|
||||
void zmk_hid_mouse_scroll_update(int8_t hwheel, int8_t wheel) {
|
||||
void zmk_hid_mouse_scroll_update(int16_t hwheel, int16_t wheel) {
|
||||
mouse_report.body.d_scroll_x += hwheel;
|
||||
mouse_report.body.d_scroll_y += wheel;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <zmk/hid_indicators.h>
|
||||
#include <zmk/events/hid_indicators_changed.h>
|
||||
#include <zmk/events/endpoint_changed.h>
|
||||
#include <zmk/split/bluetooth/central.h>
|
||||
#include <zmk/split/central.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
@@ -32,8 +32,8 @@ static void raise_led_changed_event(struct k_work *_work) {
|
||||
|
||||
raise_zmk_hid_indicators_changed((struct zmk_hid_indicators_changed){.indicators = indicators});
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) && IS_ENABLED(CONFIG_ZMK_SPLIT_BLE)
|
||||
zmk_split_bt_update_hid_indicator(indicators);
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) && IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||
zmk_split_central_update_hid_indicator(indicators);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -168,9 +168,28 @@ static ssize_t read_hids_mouse_input_report(struct bt_conn *conn, const struct b
|
||||
|
||||
static ssize_t read_hids_mouse_feature_report(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
void *buf, uint16_t len, uint16_t offset) {
|
||||
struct zmk_hid_mouse_report_body *report_body = &zmk_hid_get_mouse_report()->body;
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body,
|
||||
sizeof(struct zmk_hid_mouse_report_body));
|
||||
|
||||
int profile = zmk_ble_profile_index(bt_conn_get_dst(conn));
|
||||
if (profile < 0) {
|
||||
LOG_DBG(" BT_ATT_ERR_UNLIKELY");
|
||||
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
|
||||
}
|
||||
|
||||
struct zmk_endpoint_instance endpoint = {
|
||||
.transport = ZMK_TRANSPORT_BLE,
|
||||
.ble = {.profile_index = profile},
|
||||
};
|
||||
|
||||
struct zmk_pointing_resolution_multipliers mult =
|
||||
zmk_pointing_resolution_multipliers_get_profile(endpoint);
|
||||
|
||||
struct zmk_hid_mouse_resolution_feature_report_body report = {
|
||||
.wheel_res = mult.wheel,
|
||||
.hwheel_res = mult.hor_wheel,
|
||||
};
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, &report,
|
||||
sizeof(struct zmk_hid_mouse_resolution_feature_report_body));
|
||||
}
|
||||
|
||||
static ssize_t write_hids_mouse_feature_report(struct bt_conn *conn,
|
||||
|
||||
@@ -74,12 +74,7 @@ static uint8_t keymap_layer_orders[ZMK_KEYMAP_LAYERS_LEN];
|
||||
|
||||
#define KEYMAP_VAR(_name, _opts, no_init) \
|
||||
static _opts struct zmk_behavior_binding _name[ZMK_KEYMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = { \
|
||||
COND_CODE_0( \
|
||||
no_init, \
|
||||
(COND_CODE_1(IS_ENABLED(CONFIG_ZMK_STUDIO), \
|
||||
(DT_INST_FOREACH_CHILD_SEP(0, TRANSFORMED_LAYER, (, ))), \
|
||||
(DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(0, TRANSFORMED_LAYER, (, ))))), \
|
||||
(0))};
|
||||
COND_CODE_0(no_init, (ZMK_KEYMAP_LAYERS_FOREACH_SEP(TRANSFORMED_LAYER, (, ))), (0))};
|
||||
|
||||
KEYMAP_VAR(zmk_keymap, COND_CODE_1(IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE), (), (const)),
|
||||
IS_ENABLED(CONFIG_ZMK_STUDIO))
|
||||
@@ -89,14 +84,14 @@ KEYMAP_VAR(zmk_keymap, COND_CODE_1(IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE
|
||||
KEYMAP_VAR(zmk_stock_keymap, const, 0)
|
||||
|
||||
static char zmk_keymap_layer_names[ZMK_KEYMAP_LAYERS_LEN][CONFIG_ZMK_KEYMAP_LAYER_NAME_MAX_LEN] = {
|
||||
DT_INST_FOREACH_CHILD_SEP(0, LAYER_NAME, (, ))};
|
||||
ZMK_KEYMAP_LAYERS_FOREACH_SEP(LAYER_NAME, (, ))};
|
||||
|
||||
static uint32_t changed_layer_names = 0;
|
||||
|
||||
#else
|
||||
|
||||
static const char *zmk_keymap_layer_names[ZMK_KEYMAP_LAYERS_LEN] = {
|
||||
DT_INST_FOREACH_CHILD_SEP(0, LAYER_NAME, (, ))};
|
||||
ZMK_KEYMAP_LAYERS_FOREACH_SEP(LAYER_NAME, (, ))};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -134,9 +134,6 @@ static int handle_layer_state_changed(const struct device *dev, const zmk_event_
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
if (data->state.toggle_layer == 0) {
|
||||
return ZMK_EV_EVENT_BUBBLE;
|
||||
}
|
||||
if (!zmk_keymap_layer_active(zmk_keymap_layer_index_to_id(data->state.toggle_layer))) {
|
||||
LOG_DBG("Deactivating layer that was activated by this processor");
|
||||
data->state.is_active = false;
|
||||
|
||||
@@ -44,7 +44,7 @@ int zmk_input_split_report_peripheral_event(uint8_t reg, uint8_t type, uint16_t
|
||||
|
||||
#else
|
||||
|
||||
#include <zmk/split/bluetooth/service.h>
|
||||
#include <zmk/split/peripheral.h>
|
||||
|
||||
#define ZIS_INST(n) \
|
||||
static const struct zmk_input_processor_entry processors_##n[] = \
|
||||
@@ -56,11 +56,23 @@ int zmk_input_split_report_peripheral_event(uint8_t reg, uint8_t type, uint16_t
|
||||
"Peripheral input splits need an `input` property set"); \
|
||||
void split_input_handler_##n(struct input_event *evt) { \
|
||||
for (size_t i = 0; i < ARRAY_SIZE(processors_##n); i++) { \
|
||||
zmk_input_processor_handle_event(processors_##n[i].dev, evt, processors_##n[i].param1, \
|
||||
processors_##n[i].param2, NULL); \
|
||||
int ret = zmk_input_processor_handle_event(processors_##n[i].dev, evt, \
|
||||
processors_##n[i].param1, \
|
||||
processors_##n[i].param2, NULL); \
|
||||
if (ret != ZMK_INPUT_PROC_CONTINUE) { \
|
||||
return; \
|
||||
} \
|
||||
} \
|
||||
zmk_split_bt_report_input(DT_INST_REG_ADDR(n), evt->type, evt->code, evt->value, \
|
||||
evt->sync); \
|
||||
struct zmk_split_transport_peripheral_event ev = { \
|
||||
.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_INPUT_EVENT, \
|
||||
.data = {.input_event = { \
|
||||
.reg = DT_INST_REG_ADDR(n), \
|
||||
.type = evt->type, \
|
||||
.code = evt->code, \
|
||||
.value = evt->value, \
|
||||
.sync = evt->sync, \
|
||||
}}}; \
|
||||
zmk_split_peripheral_report_event(&ev); \
|
||||
} \
|
||||
INPUT_CALLBACK_DEFINE(DEVICE_DT_GET(DT_INST_PHANDLE(n, device)), split_input_handler_##n);
|
||||
|
||||
|
||||
@@ -13,7 +13,13 @@
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
static struct zmk_pointing_resolution_multipliers multipliers[ZMK_ENDPOINT_COUNT];
|
||||
static struct zmk_pointing_resolution_multipliers multipliers[ZMK_ENDPOINT_COUNT] = {
|
||||
[0 ... ZMK_ENDPOINT_COUNT - 1] =
|
||||
{
|
||||
.wheel = 15,
|
||||
.hor_wheel = 15,
|
||||
},
|
||||
};
|
||||
|
||||
struct zmk_pointing_resolution_multipliers
|
||||
zmk_pointing_resolution_multipliers_get_current_profile(void) {
|
||||
|
||||
@@ -3,4 +3,16 @@
|
||||
|
||||
if (CONFIG_ZMK_SPLIT_BLE)
|
||||
add_subdirectory(bluetooth)
|
||||
endif()
|
||||
|
||||
if (CONFIG_ZMK_SPLIT_WIRED)
|
||||
add_subdirectory(wired)
|
||||
endif()
|
||||
|
||||
if (CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
target_sources(app PRIVATE central.c)
|
||||
zephyr_linker_sources(SECTIONS ../../include/linker/zmk-split-transport-central.ld)
|
||||
else()
|
||||
target_sources(app PRIVATE peripheral.c)
|
||||
zephyr_linker_sources(SECTIONS ../../include/linker/zmk-split-transport-peripheral.ld)
|
||||
endif()
|
||||
@@ -9,16 +9,20 @@ if ZMK_SPLIT
|
||||
config ZMK_SPLIT_ROLE_CENTRAL
|
||||
bool "Split central device"
|
||||
|
||||
choice ZMK_SPLIT_TRANSPORT
|
||||
prompt "Split transport"
|
||||
|
||||
config ZMK_SPLIT_BLE
|
||||
bool "BLE"
|
||||
bool "BLE Split"
|
||||
default y
|
||||
depends on ZMK_BLE
|
||||
select BT_USER_PHY_UPDATE
|
||||
select BT_AUTO_PHY_UPDATE
|
||||
|
||||
endchoice
|
||||
config ZMK_SPLIT_WIRED
|
||||
bool "Wired Split"
|
||||
default y
|
||||
depends on DT_HAS_ZMK_WIRED_SPLIT_ENABLED
|
||||
select SERIAL
|
||||
select RING_BUFFER
|
||||
select CRC
|
||||
|
||||
config ZMK_SPLIT_PERIPHERAL_HID_INDICATORS
|
||||
bool "Peripheral HID Indicators"
|
||||
@@ -29,3 +33,4 @@ config ZMK_SPLIT_PERIPHERAL_HID_INDICATORS
|
||||
endif # ZMK_SPLIT
|
||||
|
||||
rsource "bluetooth/Kconfig"
|
||||
rsource "wired/Kconfig"
|
||||
|
||||
@@ -2,3 +2,5 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
rsource "bluetooth/Kconfig.defaults"
|
||||
rsource "wired/Kconfig.defaults"
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
if (NOT CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
target_sources(app PRIVATE split_listener.c)
|
||||
target_sources(app PRIVATE service.c)
|
||||
target_sources(app PRIVATE peripheral.c)
|
||||
endif()
|
||||
|
||||
@@ -5,6 +5,11 @@ if ZMK_SPLIT && ZMK_SPLIT_BLE
|
||||
|
||||
menu "BLE Transport"
|
||||
|
||||
config ZMK_SPLIT_BLE_PRIORITY
|
||||
int "BLE transport priority"
|
||||
help
|
||||
Lower number priorities transports are favored over higher numbers.
|
||||
|
||||
# Added for backwards compatibility. New shields / board should set `ZMK_SPLIT_ROLE_CENTRAL` only.
|
||||
config ZMK_SPLIT_BLE_ROLE_CENTRAL
|
||||
bool
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
|
||||
if ZMK_BLE
|
||||
|
||||
if ZMK_SPLIT_BLE && ZMK_SPLIT_ROLE_CENTRAL
|
||||
if ZMK_SPLIT_BLE
|
||||
|
||||
config ZMK_SPLIT_BLE_PRIORITY
|
||||
default 1
|
||||
|
||||
if ZMK_SPLIT_ROLE_CENTRAL
|
||||
|
||||
config ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS
|
||||
default 1
|
||||
@@ -17,6 +22,8 @@ config BT_MAX_PAIRED
|
||||
#ZMK_SPLIT_BLE && ZMK_SPLIT_ROLE_CENTRAL
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
if !ZMK_SPLIT_BLE
|
||||
|
||||
config BT_MAX_CONN
|
||||
|
||||
@@ -23,6 +23,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
#include <zmk/ble.h>
|
||||
#include <zmk/behavior.h>
|
||||
#include <zmk/sensors.h>
|
||||
#include <zmk/split/transport/central.h>
|
||||
#include <zmk/split/bluetooth/uuid.h>
|
||||
#include <zmk/split/bluetooth/service.h>
|
||||
#include <zmk/event_manager.h>
|
||||
@@ -128,22 +129,24 @@ void release_peripheral_input_subs(struct bt_conn *conn) {
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
|
||||
|
||||
static zmk_split_transport_central_status_changed_cb_t transport_status_cb;
|
||||
static bool is_enabled;
|
||||
|
||||
static struct peripheral_slot peripherals[ZMK_SPLIT_BLE_PERIPHERAL_COUNT];
|
||||
|
||||
static bool is_scanning = false;
|
||||
|
||||
static const struct bt_uuid_128 split_service_uuid = BT_UUID_INIT_128(ZMK_SPLIT_BT_SERVICE_UUID);
|
||||
|
||||
K_MSGQ_DEFINE(peripheral_event_msgq, sizeof(struct zmk_position_state_changed),
|
||||
struct peripheral_event_wrapper {
|
||||
uint8_t source;
|
||||
struct zmk_split_transport_peripheral_event event;
|
||||
};
|
||||
|
||||
K_MSGQ_DEFINE(peripheral_event_msgq, sizeof(struct peripheral_event_wrapper),
|
||||
CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE, 4);
|
||||
|
||||
void peripheral_event_work_callback(struct k_work *work) {
|
||||
struct zmk_position_state_changed ev;
|
||||
while (k_msgq_get(&peripheral_event_msgq, &ev, K_NO_WAIT) == 0) {
|
||||
LOG_DBG("Trigger key position state change for %d", ev.position);
|
||||
raise_zmk_position_state_changed(ev);
|
||||
}
|
||||
}
|
||||
void peripheral_event_work_callback(struct k_work *work);
|
||||
|
||||
K_WORK_DEFINE(peripheral_event_work, peripheral_event_work_callback);
|
||||
|
||||
@@ -190,10 +193,13 @@ int release_peripheral_slot(int index) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (slot->position_state[i] & BIT(j)) {
|
||||
uint32_t position = (i * 8) + j;
|
||||
struct zmk_position_state_changed ev = {.source = index,
|
||||
.position = position,
|
||||
.state = false,
|
||||
.timestamp = k_uptime_get()};
|
||||
struct peripheral_event_wrapper ev = {
|
||||
.source = index,
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_KEY_POSITION_EVENT,
|
||||
.data = {.key_position_event = {
|
||||
.position = position,
|
||||
.pressed = false,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
@@ -250,19 +256,13 @@ int confirm_peripheral_slot_conn(struct bt_conn *conn) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void notify_transport_status(void);
|
||||
|
||||
static void notify_status_work_cb(struct k_work *_work) { notify_transport_status(); }
|
||||
|
||||
static K_WORK_DEFINE(notify_status_work, notify_status_work_cb);
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
K_MSGQ_DEFINE(peripheral_sensor_event_msgq, sizeof(struct zmk_sensor_event),
|
||||
CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE, 4);
|
||||
|
||||
void peripheral_sensor_event_work_callback(struct k_work *work) {
|
||||
struct zmk_sensor_event ev;
|
||||
while (k_msgq_get(&peripheral_sensor_event_msgq, &ev, K_NO_WAIT) == 0) {
|
||||
LOG_DBG("Trigger sensor change for %d", ev.sensor_index);
|
||||
raise_zmk_sensor_event(ev);
|
||||
}
|
||||
}
|
||||
|
||||
K_WORK_DEFINE(peripheral_sensor_event_work, peripheral_sensor_event_work_callback);
|
||||
|
||||
static uint8_t split_central_sensor_notify_func(struct bt_conn *conn,
|
||||
struct bt_gatt_subscribe_params *params,
|
||||
@@ -282,15 +282,20 @@ static uint8_t split_central_sensor_notify_func(struct bt_conn *conn,
|
||||
|
||||
struct sensor_event sensor_event;
|
||||
memcpy(&sensor_event, data, MIN(length, sizeof(sensor_event)));
|
||||
struct zmk_sensor_event ev = {
|
||||
.sensor_index = sensor_event.sensor_index,
|
||||
.channel_data_size = MIN(sensor_event.channel_data_size, ZMK_SENSOR_EVENT_MAX_CHANNELS),
|
||||
.timestamp = k_uptime_get()};
|
||||
if (sensor_event.channel_data_size != 1) {
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
memcpy(ev.channel_data, sensor_event.channel_data,
|
||||
sizeof(struct zmk_sensor_channel_data) * sensor_event.channel_data_size);
|
||||
k_msgq_put(&peripheral_sensor_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_sensor_event_work);
|
||||
struct peripheral_event_wrapper event_wrapper = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_SENSOR_EVENT,
|
||||
.data = {.sensor_event = {
|
||||
.channel_data = sensor_event.channel_data[0],
|
||||
.sensor_index = sensor_event.sensor_index,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &event_wrapper, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
@@ -298,27 +303,6 @@ static uint8_t split_central_sensor_notify_func(struct bt_conn *conn,
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
|
||||
|
||||
struct zmk_input_event_msg {
|
||||
uint8_t reg;
|
||||
struct zmk_split_input_event_payload payload;
|
||||
};
|
||||
|
||||
K_MSGQ_DEFINE(peripheral_input_event_msgq, sizeof(struct zmk_input_event_msg), 5, 4);
|
||||
// CONFIG_ZMK_SPLIT_BLE_CENTRAL_INPUT_QUEUE_SIZE, 4);
|
||||
|
||||
void peripheral_input_event_work_callback(struct k_work *work) {
|
||||
struct zmk_input_event_msg msg;
|
||||
while (k_msgq_get(&peripheral_input_event_msgq, &msg, K_NO_WAIT) == 0) {
|
||||
int ret = zmk_input_split_report_peripheral_event(
|
||||
msg.reg, msg.payload.type, msg.payload.code, msg.payload.value, msg.payload.sync);
|
||||
if (ret < 0) {
|
||||
LOG_WRN("Failed to report peripheral event %d", ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
K_WORK_DEFINE(input_event_work, peripheral_input_event_work_callback);
|
||||
|
||||
static uint8_t peripheral_input_event_notify_cb(struct bt_conn *conn,
|
||||
struct bt_gatt_subscribe_params *params,
|
||||
const void *data, uint16_t length) {
|
||||
@@ -335,18 +319,25 @@ static uint8_t peripheral_input_event_notify_cb(struct bt_conn *conn,
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
struct zmk_input_event_msg msg;
|
||||
|
||||
memcpy(&msg.payload, data, MIN(length, sizeof(struct zmk_split_input_event_payload)));
|
||||
|
||||
LOG_DBG("Got an input event with type %d, code %d, value %d, sync %d", msg.payload.type,
|
||||
msg.payload.code, msg.payload.value, msg.payload.sync);
|
||||
struct zmk_split_input_event_payload payload;
|
||||
memcpy(&payload, data, MIN(length, sizeof(struct zmk_split_input_event_payload)));
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(peripheral_input_slots); i++) {
|
||||
if (&peripheral_input_slots[i].sub == params) {
|
||||
msg.reg = peripheral_input_slots[i].reg;
|
||||
k_msgq_put(&peripheral_input_event_msgq, &msg, K_NO_WAIT);
|
||||
k_work_submit(&input_event_work);
|
||||
struct peripheral_event_wrapper event_wrapper = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_INPUT_EVENT,
|
||||
.data = {.input_event = {
|
||||
.reg = peripheral_input_slots[i].reg,
|
||||
.sync = payload.sync,
|
||||
.code = payload.code,
|
||||
.type = payload.type,
|
||||
.value = payload.value,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &event_wrapper, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,20 +367,21 @@ static uint8_t split_central_notify_func(struct bt_conn *conn,
|
||||
for (int i = 0; i < POSITION_STATE_DATA_LEN; i++) {
|
||||
slot->changed_positions[i] = ((uint8_t *)data)[i] ^ slot->position_state[i];
|
||||
slot->position_state[i] = ((uint8_t *)data)[i];
|
||||
LOG_DBG("data: %d", slot->position_state[i]);
|
||||
}
|
||||
LOG_HEXDUMP_DBG(slot->position_state, POSITION_STATE_DATA_LEN, "data");
|
||||
|
||||
for (int i = 0; i < POSITION_STATE_DATA_LEN; i++) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (slot->changed_positions[i] & BIT(j)) {
|
||||
uint32_t position = (i * 8) + j;
|
||||
bool pressed = slot->position_state[i] & BIT(j);
|
||||
struct zmk_position_state_changed ev = {.source =
|
||||
peripheral_slot_index_for_conn(conn),
|
||||
.position = position,
|
||||
.state = pressed,
|
||||
.timestamp = k_uptime_get()};
|
||||
|
||||
struct peripheral_event_wrapper ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_KEY_POSITION_EVENT,
|
||||
.data = {.key_position_event = {
|
||||
.position = position,
|
||||
.pressed = pressed,
|
||||
}}}};
|
||||
k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
}
|
||||
@@ -401,35 +393,6 @@ static uint8_t split_central_notify_func(struct bt_conn *conn,
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
|
||||
static uint8_t peripheral_battery_levels[ZMK_SPLIT_BLE_PERIPHERAL_COUNT] = {0};
|
||||
|
||||
int zmk_split_get_peripheral_battery_level(uint8_t source, uint8_t *level) {
|
||||
if (source >= ARRAY_SIZE(peripheral_battery_levels)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (peripherals[source].state != PERIPHERAL_SLOT_STATE_CONNECTED) {
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
*level = peripheral_battery_levels[source];
|
||||
return 0;
|
||||
}
|
||||
|
||||
K_MSGQ_DEFINE(peripheral_batt_lvl_msgq, sizeof(struct zmk_peripheral_battery_state_changed),
|
||||
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_QUEUE_SIZE, 4);
|
||||
|
||||
void peripheral_batt_lvl_change_callback(struct k_work *work) {
|
||||
struct zmk_peripheral_battery_state_changed ev;
|
||||
while (k_msgq_get(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT) == 0) {
|
||||
LOG_DBG("Triggering peripheral battery level change %u", ev.state_of_charge);
|
||||
peripheral_battery_levels[ev.source] = ev.state_of_charge;
|
||||
raise_zmk_peripheral_battery_state_changed(ev);
|
||||
}
|
||||
}
|
||||
|
||||
K_WORK_DEFINE(peripheral_batt_lvl_work, peripheral_batt_lvl_change_callback);
|
||||
|
||||
static uint8_t split_central_battery_level_notify_func(struct bt_conn *conn,
|
||||
struct bt_gatt_subscribe_params *params,
|
||||
const void *data, uint16_t length) {
|
||||
@@ -454,10 +417,16 @@ static uint8_t split_central_battery_level_notify_func(struct bt_conn *conn,
|
||||
LOG_DBG("[BATTERY LEVEL NOTIFICATION] data %p length %u", data, length);
|
||||
uint8_t battery_level = ((uint8_t *)data)[0];
|
||||
LOG_DBG("Battery level: %u", battery_level);
|
||||
struct zmk_peripheral_battery_state_changed ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn), .state_of_charge = battery_level};
|
||||
k_msgq_put(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_batt_lvl_work);
|
||||
|
||||
struct peripheral_event_wrapper ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT,
|
||||
.data = {.battery_event = {
|
||||
.level = battery_level,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
@@ -493,10 +462,15 @@ static uint8_t split_central_battery_level_read_func(struct bt_conn *conn, uint8
|
||||
|
||||
LOG_DBG("Battery level: %u", battery_level);
|
||||
|
||||
struct zmk_peripheral_battery_state_changed ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn), .state_of_charge = battery_level};
|
||||
k_msgq_put(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_batt_lvl_work);
|
||||
struct peripheral_event_wrapper ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT,
|
||||
.data = {.battery_event = {
|
||||
.level = battery_level,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
@@ -909,6 +883,11 @@ static void split_central_device_found(const bt_addr_le_t *addr, int8_t rssi, ui
|
||||
}
|
||||
|
||||
static int start_scanning(void) {
|
||||
if (!is_enabled) {
|
||||
LOG_DBG("Not scanning, we're disabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// No action is necessary if central is already scanning.
|
||||
if (is_scanning) {
|
||||
LOG_DBG("Scanning already running");
|
||||
@@ -966,6 +945,7 @@ static void split_central_connected(struct bt_conn *conn, uint8_t conn_err) {
|
||||
|
||||
confirm_peripheral_slot_conn(conn);
|
||||
split_central_process_connection(conn);
|
||||
k_work_submit(¬ify_status_work);
|
||||
}
|
||||
|
||||
static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) {
|
||||
@@ -977,10 +957,19 @@ static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) {
|
||||
LOG_DBG("Disconnected: %s (reason %d)", addr, reason);
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
struct zmk_peripheral_battery_state_changed ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn), .state_of_charge = 0};
|
||||
k_msgq_put(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_batt_lvl_work);
|
||||
struct peripheral_event_wrapper ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT,
|
||||
.data = {.battery_event = {
|
||||
.level = 0,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
// struct zmk_peripheral_battery_state_changed ev = {
|
||||
// .source = peripheral_slot_index_for_conn(conn), .state_of_charge = 0};
|
||||
// k_msgq_put(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT);
|
||||
// k_work_submit(&peripheral_batt_lvl_work);
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
|
||||
@@ -990,9 +979,11 @@ static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) {
|
||||
err = release_peripheral_slot_for_conn(conn);
|
||||
|
||||
if (err < 0) {
|
||||
return;
|
||||
LOG_WRN("Failed to release peripheral slot (%d)", err);
|
||||
}
|
||||
|
||||
k_work_submit(¬ify_status_work);
|
||||
|
||||
start_scanning();
|
||||
}
|
||||
|
||||
@@ -1027,17 +1018,16 @@ K_THREAD_STACK_DEFINE(split_central_split_run_q_stack,
|
||||
|
||||
struct k_work_q split_central_split_run_q;
|
||||
|
||||
struct zmk_split_run_behavior_payload_wrapper {
|
||||
struct central_cmd_wrapper {
|
||||
uint8_t source;
|
||||
struct zmk_split_run_behavior_payload payload;
|
||||
struct zmk_split_transport_central_command cmd;
|
||||
};
|
||||
|
||||
K_MSGQ_DEFINE(zmk_split_central_split_run_msgq,
|
||||
sizeof(struct zmk_split_run_behavior_payload_wrapper),
|
||||
K_MSGQ_DEFINE(zmk_split_central_split_run_msgq, sizeof(struct central_cmd_wrapper),
|
||||
CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_QUEUE_SIZE, 4);
|
||||
|
||||
void split_central_split_run_callback(struct k_work *work) {
|
||||
struct zmk_split_run_behavior_payload_wrapper payload_wrapper;
|
||||
struct central_cmd_wrapper payload_wrapper;
|
||||
|
||||
LOG_DBG("");
|
||||
|
||||
@@ -1046,34 +1036,85 @@ void split_central_split_run_callback(struct k_work *work) {
|
||||
LOG_ERR("Source not connected");
|
||||
continue;
|
||||
}
|
||||
if (!peripherals[payload_wrapper.source].run_behavior_handle) {
|
||||
LOG_ERR("Run behavior handle not found");
|
||||
continue;
|
||||
|
||||
switch (payload_wrapper.cmd.type) {
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR: {
|
||||
if (!peripherals[payload_wrapper.source].run_behavior_handle) {
|
||||
LOG_ERR("Run behavior handle not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
struct zmk_split_run_behavior_payload payload = {
|
||||
.data = {
|
||||
.param1 = payload_wrapper.cmd.data.invoke_behavior.param1,
|
||||
.param2 = payload_wrapper.cmd.data.invoke_behavior.param2,
|
||||
.position = payload_wrapper.cmd.data.invoke_behavior.position,
|
||||
.source = payload_wrapper.cmd.data.invoke_behavior.event_source,
|
||||
.state = payload_wrapper.cmd.data.invoke_behavior.state ? 1 : 0,
|
||||
}};
|
||||
const size_t payload_dev_size = sizeof(payload.behavior_dev);
|
||||
if (strlcpy(payload.behavior_dev, payload_wrapper.cmd.data.invoke_behavior.behavior_dev,
|
||||
payload_dev_size) >= payload_dev_size) {
|
||||
LOG_ERR("Truncated behavior label %s to %s before invoking peripheral behavior",
|
||||
payload_wrapper.cmd.data.invoke_behavior.behavior_dev,
|
||||
payload.behavior_dev);
|
||||
}
|
||||
|
||||
int err = bt_gatt_write_without_response(
|
||||
peripherals[payload_wrapper.source].conn,
|
||||
peripherals[payload_wrapper.source].run_behavior_handle, &payload,
|
||||
sizeof(struct zmk_split_run_behavior_payload), true);
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to write the behavior characteristic (err %d)", err);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_PHYSICAL_LAYOUT:
|
||||
update_peripheral_selected_layout(
|
||||
&peripherals[payload_wrapper.source],
|
||||
payload_wrapper.cmd.data.set_physical_layout.layout_idx);
|
||||
break;
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS:
|
||||
LOG_WRN("do the indicators dance");
|
||||
if (peripherals[payload_wrapper.source].update_hid_indicators == 0) {
|
||||
// It appears that sometimes the peripheral is considered connected
|
||||
// before the GATT characteristics have been discovered. If this is
|
||||
// the case, the update_hid_indicators handle will not yet be set.
|
||||
LOG_WRN("NO HANDLE TO SET ON PERIPHERAL");
|
||||
break;
|
||||
}
|
||||
|
||||
int err = bt_gatt_write_without_response(
|
||||
peripherals[payload_wrapper.source].conn,
|
||||
peripherals[payload_wrapper.source].run_behavior_handle, &payload_wrapper.payload,
|
||||
sizeof(struct zmk_split_run_behavior_payload), true);
|
||||
int err = bt_gatt_write_without_response(
|
||||
peripherals[payload_wrapper.source].conn,
|
||||
peripherals[payload_wrapper.source].update_hid_indicators,
|
||||
&payload_wrapper.cmd.data.set_hid_indicators.indicators,
|
||||
sizeof(payload_wrapper.cmd.data.set_hid_indicators.indicators), true);
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to write the behavior characteristic (err %d)", err);
|
||||
if (err) {
|
||||
LOG_ERR("Failed to write HID indicator characteristic (err %d)", err);
|
||||
}
|
||||
break;
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
default:
|
||||
LOG_WRN("Unsupported wrapped central command type %d", payload_wrapper.cmd.type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
K_WORK_DEFINE(split_central_split_run_work, split_central_split_run_callback);
|
||||
|
||||
static int
|
||||
split_bt_invoke_behavior_payload(struct zmk_split_run_behavior_payload_wrapper payload_wrapper) {
|
||||
static int split_bt_invoke_behavior_payload(struct central_cmd_wrapper payload_wrapper) {
|
||||
LOG_DBG("");
|
||||
|
||||
int err = k_msgq_put(&zmk_split_central_split_run_msgq, &payload_wrapper, K_MSEC(100));
|
||||
if (err) {
|
||||
switch (err) {
|
||||
case -EAGAIN: {
|
||||
LOG_WRN("Consumer message queue full, popping first message and queueing again");
|
||||
struct zmk_split_run_behavior_payload_wrapper discarded_report;
|
||||
LOG_WRN("Run command message queue full, popping first message and queueing again");
|
||||
struct central_cmd_wrapper discarded_report;
|
||||
k_msgq_get(&zmk_split_central_split_run_msgq, &discarded_report, K_NO_WAIT);
|
||||
return split_bt_invoke_behavior_payload(payload_wrapper);
|
||||
}
|
||||
@@ -1088,66 +1129,9 @@ split_bt_invoke_behavior_payload(struct zmk_split_run_behavior_payload_wrapper p
|
||||
return 0;
|
||||
};
|
||||
|
||||
int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event, bool state) {
|
||||
struct zmk_split_run_behavior_payload payload = {.data = {
|
||||
.param1 = binding->param1,
|
||||
.param2 = binding->param2,
|
||||
.position = event.position,
|
||||
.source = event.source,
|
||||
.state = state ? 1 : 0,
|
||||
}};
|
||||
const size_t payload_dev_size = sizeof(payload.behavior_dev);
|
||||
if (strlcpy(payload.behavior_dev, binding->behavior_dev, payload_dev_size) >=
|
||||
payload_dev_size) {
|
||||
LOG_ERR("Truncated behavior label %s to %s before invoking peripheral behavior",
|
||||
binding->behavior_dev, payload.behavior_dev);
|
||||
}
|
||||
static int finish_init();
|
||||
|
||||
struct zmk_split_run_behavior_payload_wrapper wrapper = {.source = source, .payload = payload};
|
||||
return split_bt_invoke_behavior_payload(wrapper);
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
static zmk_hid_indicators_t hid_indicators = 0;
|
||||
|
||||
static void split_central_update_indicators_callback(struct k_work *work) {
|
||||
zmk_hid_indicators_t indicators = hid_indicators;
|
||||
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
|
||||
if (peripherals[i].state != PERIPHERAL_SLOT_STATE_CONNECTED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (peripherals[i].update_hid_indicators == 0) {
|
||||
// It appears that sometimes the peripheral is considered connected
|
||||
// before the GATT characteristics have been discovered. If this is
|
||||
// the case, the update_hid_indicators handle will not yet be set.
|
||||
continue;
|
||||
}
|
||||
|
||||
int err = bt_gatt_write_without_response(peripherals[i].conn,
|
||||
peripherals[i].update_hid_indicators, &indicators,
|
||||
sizeof(indicators), true);
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to write HID indicator characteristic (err %d)", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static K_WORK_DEFINE(split_central_update_indicators, split_central_update_indicators_callback);
|
||||
|
||||
int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators) {
|
||||
hid_indicators = indicators;
|
||||
return k_work_submit_to_queue(&split_central_split_run_q, &split_central_update_indicators);
|
||||
}
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
static int finish_init() {
|
||||
return IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) ? 0 : start_scanning();
|
||||
}
|
||||
static bool settings_loaded = false;
|
||||
|
||||
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||
|
||||
@@ -1185,4 +1169,127 @@ static int zmk_split_bt_central_listener_cb(const zmk_event_t *eh) {
|
||||
}
|
||||
|
||||
ZMK_LISTENER(zmk_split_bt_central, zmk_split_bt_central_listener_cb);
|
||||
ZMK_SUBSCRIPTION(zmk_split_bt_central, zmk_physical_layout_selection_changed);
|
||||
ZMK_SUBSCRIPTION(zmk_split_bt_central, zmk_physical_layout_selection_changed);
|
||||
|
||||
static int split_central_bt_send_command(uint8_t source,
|
||||
struct zmk_split_transport_central_command cmd) {
|
||||
if (source >= ARRAY_SIZE(peripherals)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (cmd.type) {
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS:
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_PHYSICAL_LAYOUT:
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR: {
|
||||
struct central_cmd_wrapper wrapper = {.source = source, .cmd = cmd};
|
||||
return split_bt_invoke_behavior_payload(wrapper);
|
||||
}
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_POLL_EVENTS:
|
||||
return -ENOTSUP;
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int split_central_bt_get_available_source_ids(uint8_t *sources) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
|
||||
if (peripherals[i].state != PERIPHERAL_SLOT_STATE_CONNECTED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sources[count++] = i;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int split_central_bt_set_enabled(bool enabled) {
|
||||
is_enabled = enabled;
|
||||
if (enabled) {
|
||||
return start_scanning();
|
||||
} else {
|
||||
int err = stop_scanning();
|
||||
if (err < 0) {
|
||||
LOG_WRN("Failed to stop scanning for peripherals (%d)", err);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
|
||||
if (peripherals[i].state != PERIPHERAL_SLOT_STATE_CONNECTED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
err = bt_conn_disconnect(peripherals[i].conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
||||
if (err < 0) {
|
||||
LOG_WRN("Failed to disconnect a peripheral (%d)", err);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
split_central_bt_set_status_callback(zmk_split_transport_central_status_changed_cb_t cb) {
|
||||
transport_status_cb = cb;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct zmk_split_transport_status split_central_bt_get_status() {
|
||||
uint8_t _source_ids[ZMK_SPLIT_BLE_PERIPHERAL_COUNT];
|
||||
|
||||
int count = split_central_bt_get_available_source_ids(_source_ids);
|
||||
|
||||
enum zmk_split_transport_connections_status conn_status;
|
||||
|
||||
if (count == 0) {
|
||||
conn_status = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_DISCONNECTED;
|
||||
} else if (count == ZMK_SPLIT_BLE_PERIPHERAL_COUNT) {
|
||||
conn_status = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_ALL_CONNECTED;
|
||||
} else {
|
||||
conn_status = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_SOME_CONNECTED;
|
||||
}
|
||||
|
||||
return (struct zmk_split_transport_status){
|
||||
.available = !IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) && settings_loaded,
|
||||
.enabled = is_enabled,
|
||||
.connections = conn_status,
|
||||
};
|
||||
}
|
||||
|
||||
static const struct zmk_split_transport_central_api central_api = {
|
||||
.send_command = split_central_bt_send_command,
|
||||
.get_available_source_ids = split_central_bt_get_available_source_ids,
|
||||
.set_enabled = split_central_bt_set_enabled,
|
||||
.set_status_callback = split_central_bt_set_status_callback,
|
||||
.get_status = split_central_bt_get_status,
|
||||
};
|
||||
|
||||
ZMK_SPLIT_TRANSPORT_CENTRAL_REGISTER(bt_central, ¢ral_api, CONFIG_ZMK_SPLIT_BLE_PRIORITY);
|
||||
|
||||
static void notify_transport_status(void) {
|
||||
if (transport_status_cb) {
|
||||
transport_status_cb(&bt_central, split_central_bt_get_status());
|
||||
}
|
||||
}
|
||||
|
||||
static int finish_init() {
|
||||
settings_loaded = true;
|
||||
|
||||
if (!transport_status_cb) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return transport_status_cb(&bt_central, split_central_bt_get_status());
|
||||
}
|
||||
|
||||
void peripheral_event_work_callback(struct k_work *work) {
|
||||
struct peripheral_event_wrapper ev;
|
||||
while (k_msgq_get(&peripheral_event_msgq, &ev, K_NO_WAIT) == 0) {
|
||||
LOG_DBG("Trigger key position state change for %d",
|
||||
ev.event.data.key_position_event.position);
|
||||
zmk_split_transport_central_peripheral_event_handler(&bt_central, ev.source, ev.event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/battery.h>
|
||||
#include <zmk/events/battery_state_changed.h>
|
||||
#include <zmk/split/bluetooth/central.h>
|
||||
#include <zmk/split/central.h>
|
||||
|
||||
static void blvl_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) {
|
||||
ARG_UNUSED(attr);
|
||||
@@ -32,7 +32,7 @@ static ssize_t read_blvl(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
uint16_t len, uint16_t offset) {
|
||||
const uint8_t source = (uint8_t)(uint32_t)attr->user_data;
|
||||
uint8_t level = 0;
|
||||
int rc = zmk_split_get_peripheral_battery_level(source, &level);
|
||||
int rc = zmk_split_central_get_peripheral_battery_level(source, &level);
|
||||
|
||||
if (rc == -EINVAL) {
|
||||
LOG_ERR("Invalid peripheral index requested for battery level read: %d", source);
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
#include <zephyr/bluetooth/gatt.h>
|
||||
#include <zephyr/bluetooth/hci_types.h>
|
||||
|
||||
#include "peripheral.h"
|
||||
#include "service.h"
|
||||
|
||||
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||
|
||||
#include <zephyr/settings/settings.h>
|
||||
@@ -70,6 +73,7 @@ static int start_advertising(bool low_duty) {
|
||||
};
|
||||
|
||||
static bool low_duty_advertising = false;
|
||||
static bool enabled = false;
|
||||
|
||||
static void advertising_cb(struct k_work *work) {
|
||||
const int err = start_advertising(low_duty_advertising);
|
||||
@@ -86,7 +90,7 @@ static void connected(struct bt_conn *conn, uint8_t err) {
|
||||
raise_zmk_split_peripheral_status_changed(
|
||||
(struct zmk_split_peripheral_status_changed){.connected = is_connected});
|
||||
|
||||
if (err == BT_HCI_ERR_ADV_TIMEOUT) {
|
||||
if (err == BT_HCI_ERR_ADV_TIMEOUT && enabled) {
|
||||
low_duty_advertising = true;
|
||||
k_work_submit(&advertising_work);
|
||||
}
|
||||
@@ -104,8 +108,10 @@ static void disconnected(struct bt_conn *conn, uint8_t reason) {
|
||||
raise_zmk_split_peripheral_status_changed(
|
||||
(struct zmk_split_peripheral_status_changed){.connected = is_connected});
|
||||
|
||||
low_duty_advertising = false;
|
||||
k_work_submit(&advertising_work);
|
||||
if (enabled) {
|
||||
low_duty_advertising = false;
|
||||
k_work_submit(&advertising_work);
|
||||
}
|
||||
}
|
||||
|
||||
static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) {
|
||||
@@ -146,6 +152,85 @@ bool zmk_split_bt_peripheral_is_connected(void) { return is_connected; }
|
||||
|
||||
bool zmk_split_bt_peripheral_is_bonded(void) { return is_bonded; }
|
||||
|
||||
static zmk_split_transport_peripheral_status_changed_cb_t transport_status_cb;
|
||||
|
||||
static int
|
||||
split_peripheral_bt_set_status_callback(zmk_split_transport_peripheral_status_changed_cb_t cb) {
|
||||
transport_status_cb = cb;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void find_first_conn(struct bt_conn *conn, void *data) {
|
||||
struct bt_conn **cp = (struct bt_conn **)data;
|
||||
|
||||
*cp = conn;
|
||||
}
|
||||
|
||||
static int split_peripheral_bt_set_enabled(bool en) {
|
||||
int err;
|
||||
|
||||
enabled = en;
|
||||
if (en) {
|
||||
k_work_submit(&advertising_work);
|
||||
return 0;
|
||||
} else {
|
||||
struct bt_conn *conn = NULL;
|
||||
bt_conn_foreach(BT_CONN_TYPE_LE, find_first_conn, &conn);
|
||||
if (conn) {
|
||||
err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
||||
if (err < 0) {
|
||||
LOG_WRN("Failed to disconnect connection to central (%d)", err);
|
||||
}
|
||||
}
|
||||
|
||||
err = bt_le_adv_stop();
|
||||
|
||||
if (err < 0) {
|
||||
LOG_WRN("Failed to stop advertising (%d)", err);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void notify_transport_status(void);
|
||||
|
||||
static void notify_status_work_cb(struct k_work *_work) { notify_transport_status(); }
|
||||
|
||||
static K_WORK_DEFINE(notify_status_work, notify_status_work_cb);
|
||||
|
||||
static bool settings_loaded = false;
|
||||
|
||||
static struct zmk_split_transport_status split_peripheral_bt_get_status(void) {
|
||||
return (struct zmk_split_transport_status){
|
||||
.available = !IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) && settings_loaded,
|
||||
.enabled = enabled,
|
||||
.connections = zmk_split_bt_peripheral_is_connected()
|
||||
? ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_ALL_CONNECTED
|
||||
: ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_DISCONNECTED,
|
||||
};
|
||||
}
|
||||
|
||||
static const struct zmk_split_transport_peripheral_api peripheral_api = {
|
||||
.report_event = zmk_split_transport_peripheral_bt_report_event,
|
||||
.set_enabled = split_peripheral_bt_set_enabled,
|
||||
.set_status_callback = split_peripheral_bt_set_status_callback,
|
||||
.get_status = split_peripheral_bt_get_status,
|
||||
};
|
||||
|
||||
ZMK_SPLIT_TRANSPORT_PERIPHERAL_REGISTER(bt_peripheral, &peripheral_api,
|
||||
CONFIG_ZMK_SPLIT_BLE_PRIORITY);
|
||||
|
||||
struct zmk_split_transport_peripheral *zmk_split_transport_peripheral_bt(void) {
|
||||
return &bt_peripheral;
|
||||
}
|
||||
|
||||
static void notify_transport_status(void) {
|
||||
if (transport_status_cb) {
|
||||
transport_status_cb(&bt_peripheral, split_peripheral_bt_get_status());
|
||||
}
|
||||
}
|
||||
|
||||
static int zmk_peripheral_ble_complete_startup(void) {
|
||||
#if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START)
|
||||
LOG_WRN("Clearing all existing BLE bond information from the keyboard");
|
||||
@@ -156,7 +241,9 @@ static int zmk_peripheral_ble_complete_startup(void) {
|
||||
bt_conn_auth_info_cb_register(&zmk_peripheral_ble_auth_info_cb);
|
||||
|
||||
low_duty_advertising = false;
|
||||
k_work_submit(&advertising_work);
|
||||
|
||||
settings_loaded = true;
|
||||
k_work_submit(¬ify_status_work);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
|
||||
12
app/src/split/bluetooth/peripheral.h
Normal file
12
app/src/split/bluetooth/peripheral.h
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zmk/split/transport/types.h>
|
||||
#include <zmk/split/transport/peripheral.h>
|
||||
|
||||
struct zmk_split_transport_peripheral *zmk_split_transport_peripheral_bt(void);
|
||||
@@ -18,12 +18,16 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
#include <zephyr/bluetooth/uuid.h>
|
||||
|
||||
#include <drivers/behavior.h>
|
||||
#include <zmk/stdlib.h>
|
||||
#include <zmk/behavior.h>
|
||||
#include <zmk/matrix.h>
|
||||
#include <zmk/physical_layouts.h>
|
||||
#include <zmk/split/transport/peripheral.h>
|
||||
#include <zmk/split/bluetooth/uuid.h>
|
||||
#include <zmk/split/bluetooth/service.h>
|
||||
|
||||
#include "peripheral.h"
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
#include <zmk/events/hid_indicators_changed.h>
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
@@ -60,48 +64,7 @@ static ssize_t split_svc_pos_state(struct bt_conn *conn, const struct bt_gatt_at
|
||||
|
||||
static ssize_t split_svc_run_behavior(struct bt_conn *conn, const struct bt_gatt_attr *attrs,
|
||||
const void *buf, uint16_t len, uint16_t offset,
|
||||
uint8_t flags) {
|
||||
struct zmk_split_run_behavior_payload *payload = attrs->user_data;
|
||||
uint16_t end_addr = offset + len;
|
||||
|
||||
LOG_DBG("offset %d len %d", offset, len);
|
||||
|
||||
if (end_addr > sizeof(struct zmk_split_run_behavior_payload)) {
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
||||
}
|
||||
|
||||
memcpy(payload + offset, buf, len);
|
||||
|
||||
// We run if:
|
||||
// 1: We've gotten all the position/state/param data.
|
||||
// 2: We have a null terminated string for the behavior device label.
|
||||
const size_t behavior_dev_offset =
|
||||
offsetof(struct zmk_split_run_behavior_payload, behavior_dev);
|
||||
if ((end_addr > sizeof(struct zmk_split_run_behavior_data)) &&
|
||||
payload->behavior_dev[end_addr - behavior_dev_offset - 1] == '\0') {
|
||||
struct zmk_behavior_binding binding = {
|
||||
.param1 = payload->data.param1,
|
||||
.param2 = payload->data.param2,
|
||||
.behavior_dev = payload->behavior_dev,
|
||||
};
|
||||
LOG_DBG("%s with params %d %d: pressed? %d", binding.behavior_dev, binding.param1,
|
||||
binding.param2, payload->data.state);
|
||||
struct zmk_behavior_binding_event event = {.position = payload->data.position,
|
||||
.timestamp = k_uptime_get()};
|
||||
int err;
|
||||
if (payload->data.state > 0) {
|
||||
err = behavior_keymap_binding_pressed(&binding, event);
|
||||
} else {
|
||||
err = behavior_keymap_binding_released(&binding, event);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to invoke behavior %s: %d", binding.behavior_dev, err);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
uint8_t flags);
|
||||
|
||||
static ssize_t split_svc_num_of_positions(struct bt_conn *conn, const struct bt_gatt_attr *attrs,
|
||||
void *buf, uint16_t len, uint16_t offset) {
|
||||
@@ -285,12 +248,12 @@ int send_position_state() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zmk_split_bt_position_pressed(uint8_t position) {
|
||||
static int zmk_split_bt_position_pressed(uint8_t position) {
|
||||
WRITE_BIT(position_state[position / 8], position % 8, true);
|
||||
return send_position_state();
|
||||
}
|
||||
|
||||
int zmk_split_bt_position_released(uint8_t position) {
|
||||
static int zmk_split_bt_position_released(uint8_t position) {
|
||||
WRITE_BIT(position_state[position / 8], position % 8, false);
|
||||
return send_position_state();
|
||||
}
|
||||
@@ -332,9 +295,9 @@ int send_sensor_state(struct sensor_event ev) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zmk_split_bt_sensor_triggered(uint8_t sensor_index,
|
||||
const struct zmk_sensor_channel_data channel_data[],
|
||||
size_t channel_data_size) {
|
||||
static int zmk_split_bt_sensor_triggered(uint8_t sensor_index,
|
||||
const struct zmk_sensor_channel_data channel_data[],
|
||||
size_t channel_data_size) {
|
||||
if (channel_data_size > ZMK_SENSOR_EVENT_MAX_CHANNELS) {
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -349,7 +312,8 @@ int zmk_split_bt_sensor_triggered(uint8_t sensor_index,
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
|
||||
|
||||
int zmk_split_bt_report_input(uint8_t reg, uint8_t type, uint16_t code, int32_t value, bool sync) {
|
||||
static int zmk_split_bt_report_input(uint8_t reg, uint8_t type, uint16_t code, int32_t value,
|
||||
bool sync) {
|
||||
|
||||
for (size_t i = 0; i < split_svc.attr_count; i++) {
|
||||
if (bt_uuid_cmp(split_svc.attrs[i].uuid,
|
||||
@@ -380,3 +344,94 @@ static int service_init(void) {
|
||||
}
|
||||
|
||||
SYS_INIT(service_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY);
|
||||
|
||||
int zmk_split_transport_peripheral_bt_report_event(
|
||||
const struct zmk_split_transport_peripheral_event *ev) {
|
||||
switch (ev->type) {
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_KEY_POSITION_EVENT:
|
||||
if (ev->data.key_position_event.pressed) {
|
||||
zmk_split_bt_position_pressed(ev->data.key_position_event.position);
|
||||
} else {
|
||||
zmk_split_bt_position_released(ev->data.key_position_event.position);
|
||||
}
|
||||
break;
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_SENSOR_EVENT:
|
||||
zmk_split_bt_sensor_triggered(ev->data.sensor_event.sensor_index,
|
||||
&ev->data.sensor_event.channel_data, 1);
|
||||
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_INPUT_EVENT:
|
||||
return zmk_split_bt_report_input(ev->data.input_event.reg, ev->data.input_event.type,
|
||||
ev->data.input_event.code, ev->data.input_event.value,
|
||||
ev->data.input_event.sync);
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING)
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT:
|
||||
// The BLE transport uses standard BAS service for propagation, so just return success here.
|
||||
return 0;
|
||||
#endif
|
||||
default:
|
||||
LOG_WRN("Unhandled event type %d", ev->type);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t split_svc_run_behavior(struct bt_conn *conn, const struct bt_gatt_attr *attrs,
|
||||
const void *buf, uint16_t len, uint16_t offset,
|
||||
uint8_t flags) {
|
||||
struct zmk_split_run_behavior_payload *payload = attrs->user_data;
|
||||
uint16_t end_addr = offset + len;
|
||||
|
||||
LOG_DBG("offset %d len %d", offset, len);
|
||||
|
||||
if (end_addr > sizeof(struct zmk_split_run_behavior_payload)) {
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
||||
}
|
||||
|
||||
memcpy(payload + offset, buf, len);
|
||||
|
||||
// We run if:
|
||||
// 1: We've gotten all the position/state/param data.
|
||||
// 2: We have a null terminated string for the behavior device label.
|
||||
const size_t behavior_dev_offset =
|
||||
offsetof(struct zmk_split_run_behavior_payload, behavior_dev);
|
||||
if ((end_addr > sizeof(struct zmk_split_run_behavior_data)) &&
|
||||
payload->behavior_dev[end_addr - behavior_dev_offset - 1] == '\0') {
|
||||
|
||||
struct zmk_split_transport_central_command cmd = {
|
||||
.type = ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR,
|
||||
.data = {.invoke_behavior = {
|
||||
.param1 = payload->data.param1,
|
||||
.param2 = payload->data.param2,
|
||||
.position = payload->data.position,
|
||||
.state = payload->data.state,
|
||||
}}};
|
||||
|
||||
const size_t payload_dev_size = sizeof(cmd.data.invoke_behavior.behavior_dev);
|
||||
if (strlcpy(cmd.data.invoke_behavior.behavior_dev, payload->behavior_dev,
|
||||
payload_dev_size) >= payload_dev_size) {
|
||||
LOG_ERR("Truncated behavior label %s to %s before invoking peripheral behavior",
|
||||
payload->behavior_dev, cmd.data.invoke_behavior.behavior_dev);
|
||||
}
|
||||
|
||||
LOG_DBG("%s with params %d %d: pressed? %d", cmd.data.invoke_behavior.behavior_dev,
|
||||
cmd.data.invoke_behavior.param1, cmd.data.invoke_behavior.param2,
|
||||
cmd.data.invoke_behavior.state);
|
||||
|
||||
int err = zmk_split_transport_peripheral_command_handler(
|
||||
zmk_split_transport_peripheral_bt(), cmd);
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to invoke behavior %s: %d", payload->behavior_dev, err);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
12
app/src/split/bluetooth/service.h
Normal file
12
app/src/split/bluetooth/service.h
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zmk/split/transport/types.h>
|
||||
|
||||
int zmk_split_transport_peripheral_bt_report_event(
|
||||
const struct zmk_split_transport_peripheral_event *ev);
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include <zmk/split/bluetooth/service.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
#include <zmk/events/sensor_event.h>
|
||||
#include <zmk/hid.h>
|
||||
#include <zmk/sensors.h>
|
||||
#include <zmk/endpoints.h>
|
||||
|
||||
int split_listener(const zmk_event_t *eh) {
|
||||
LOG_DBG("");
|
||||
const struct zmk_position_state_changed *pos_ev;
|
||||
if ((pos_ev = as_zmk_position_state_changed(eh)) != NULL) {
|
||||
if (pos_ev->state) {
|
||||
return zmk_split_bt_position_pressed(pos_ev->position);
|
||||
} else {
|
||||
return zmk_split_bt_position_released(pos_ev->position);
|
||||
}
|
||||
}
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
const struct zmk_sensor_event *sensor_ev;
|
||||
if ((sensor_ev = as_zmk_sensor_event(eh)) != NULL) {
|
||||
return zmk_split_bt_sensor_triggered(sensor_ev->sensor_index, sensor_ev->channel_data,
|
||||
sensor_ev->channel_data_size);
|
||||
}
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
return ZMK_EV_EVENT_BUBBLE;
|
||||
}
|
||||
|
||||
ZMK_LISTENER(split_listener, split_listener);
|
||||
ZMK_SUBSCRIPTION(split_listener, zmk_position_state_changed);
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
ZMK_SUBSCRIPTION(split_listener, zmk_sensor_event);
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
227
app/src/split/central.c
Normal file
227
app/src/split/central.c
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <zmk/stdlib.h>
|
||||
#include <zmk/split/transport/central.h>
|
||||
#include <zmk/split/central.h>
|
||||
#include <zmk/hid_indicators_types.h>
|
||||
#include <zmk/pointing/input_split.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/battery_state_changed.h>
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
#include <zmk/events/sensor_event.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
const struct zmk_split_transport_central *active_transport;
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
|
||||
static uint8_t peripheral_battery_levels[ZMK_SPLIT_CENTRAL_PERIPHERAL_COUNT] = {0};
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
|
||||
int zmk_split_transport_central_peripheral_event_handler(
|
||||
const struct zmk_split_transport_central *transport, uint8_t source,
|
||||
struct zmk_split_transport_peripheral_event ev) {
|
||||
if (transport != active_transport) {
|
||||
// Ignoring events from non-active transport
|
||||
LOG_WRN("Ignoring peripheral event from non-active transport");
|
||||
return -EINVAL;
|
||||
}
|
||||
switch (ev.type) {
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_KEY_POSITION_EVENT: {
|
||||
struct zmk_position_state_changed state_ev = {.source = source,
|
||||
.position =
|
||||
ev.data.key_position_event.position,
|
||||
.state = ev.data.key_position_event.pressed,
|
||||
.timestamp = k_uptime_get()};
|
||||
return raise_zmk_position_state_changed(state_ev);
|
||||
}
|
||||
#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_INPUT_EVENT: {
|
||||
return zmk_input_split_report_peripheral_event(
|
||||
ev.data.input_event.reg, ev.data.input_event.type, ev.data.input_event.code,
|
||||
ev.data.input_event.value, ev.data.input_event.sync);
|
||||
}
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_POINTING)
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT: {
|
||||
struct zmk_peripheral_battery_state_changed battery_ev = {
|
||||
.source = source,
|
||||
.state_of_charge = ev.data.battery_event.level,
|
||||
};
|
||||
peripheral_battery_levels[source] = ev.data.battery_event.level;
|
||||
return raise_zmk_peripheral_battery_state_changed(battery_ev);
|
||||
}
|
||||
#endif
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_SENSOR_EVENT: {
|
||||
struct zmk_sensor_event sensor_ev = {.sensor_index = ev.data.sensor_event.sensor_index,
|
||||
.channel_data_size = 1,
|
||||
.timestamp = k_uptime_get()};
|
||||
|
||||
sensor_ev.channel_data[0] = ev.data.sensor_event.channel_data;
|
||||
|
||||
return raise_zmk_sensor_event(sensor_ev);
|
||||
}
|
||||
default:
|
||||
LOG_WRN("GOT AN UNKNOWN EVENT TYPE %d", ev.type);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
}
|
||||
|
||||
int zmk_split_central_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event, bool state) {
|
||||
if (!active_transport || !active_transport->api || !active_transport->api->send_command) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
struct zmk_split_transport_central_command command =
|
||||
(struct zmk_split_transport_central_command){
|
||||
.type = ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR,
|
||||
.data =
|
||||
{
|
||||
.invoke_behavior =
|
||||
{
|
||||
.param1 = binding->param1,
|
||||
.param2 = binding->param2,
|
||||
.position = event.position,
|
||||
.event_source = event.source,
|
||||
.state = state ? 1 : 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const size_t payload_dev_size = sizeof(command.data.invoke_behavior.behavior_dev);
|
||||
if (strlcpy(command.data.invoke_behavior.behavior_dev, binding->behavior_dev,
|
||||
payload_dev_size) >= payload_dev_size) {
|
||||
LOG_ERR("Truncated behavior label %s to %s before invoking peripheral behavior",
|
||||
binding->behavior_dev, command.data.invoke_behavior.behavior_dev);
|
||||
}
|
||||
|
||||
return active_transport->api->send_command(source, command);
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
int zmk_split_central_update_hid_indicator(zmk_hid_indicators_t indicators) {
|
||||
if (!active_transport || !active_transport->api ||
|
||||
!active_transport->api->get_available_source_ids || !active_transport->api->send_command) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
uint8_t source_ids[ZMK_SPLIT_CENTRAL_PERIPHERAL_COUNT];
|
||||
|
||||
int ret = active_transport->api->get_available_source_ids(source_ids);
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct zmk_split_transport_central_command command =
|
||||
(struct zmk_split_transport_central_command){
|
||||
.type = ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS,
|
||||
.data =
|
||||
{
|
||||
.set_hid_indicators =
|
||||
{
|
||||
.indicators = indicators,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < ret; i++) {
|
||||
ret = active_transport->api->send_command(source_ids[i], command);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
|
||||
int zmk_split_central_get_peripheral_battery_level(uint8_t source, uint8_t *level) {
|
||||
if (source >= ARRAY_SIZE(peripheral_battery_levels)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*level = peripheral_battery_levels[source];
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
|
||||
static int select_first_available_transport(void) {
|
||||
// Transports are sorted by priority, so find the first
|
||||
// One that's available, and enable it. Any transport that
|
||||
// Doesn't support `get_status` is assumed to be always
|
||||
// available and fully connected.
|
||||
STRUCT_SECTION_FOREACH(zmk_split_transport_central, t) {
|
||||
if (!t->api->get_status || t->api->get_status().available) {
|
||||
|
||||
if (active_transport == t) {
|
||||
LOG_DBG("First available is already selected, moving on");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (active_transport && active_transport->api->set_enabled) {
|
||||
int err = active_transport->api->set_enabled(false);
|
||||
if (err < 0) {
|
||||
LOG_WRN("Error disabling previously selected split transport (%d)", err);
|
||||
}
|
||||
}
|
||||
|
||||
active_transport = t;
|
||||
int err = 0;
|
||||
if (active_transport->api->set_enabled) {
|
||||
err = active_transport->api->set_enabled(true);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int transport_status_changed_cb(const struct zmk_split_transport_central *central,
|
||||
struct zmk_split_transport_status status) {
|
||||
if (central == active_transport) {
|
||||
LOG_DBG("Central at %p changed status: enabled %d, available %d, connections %d", central,
|
||||
status.enabled, status.available, status.connections);
|
||||
if (status.connections == ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_DISCONNECTED) {
|
||||
return select_first_available_transport();
|
||||
}
|
||||
} else {
|
||||
// Just to be sure, in case a higher priority transport becomes available
|
||||
select_first_available_transport();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int central_init(void) {
|
||||
STRUCT_SECTION_FOREACH(zmk_split_transport_central, t) {
|
||||
if (!t->api->set_status_callback) {
|
||||
continue;
|
||||
}
|
||||
|
||||
t->api->set_status_callback(transport_status_changed_cb);
|
||||
}
|
||||
|
||||
return select_first_available_transport();
|
||||
}
|
||||
|
||||
SYS_INIT(central_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
||||
190
app/src/split/peripheral.c
Normal file
190
app/src/split/peripheral.c
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <zmk/stdlib.h>
|
||||
#include <zmk/split/transport/peripheral.h>
|
||||
|
||||
#include <drivers/behavior.h>
|
||||
#include <zmk/behavior.h>
|
||||
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
#include <zmk/events/sensor_event.h>
|
||||
#include <zmk/events/battery_state_changed.h>
|
||||
|
||||
#include <zephyr/init.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
const struct zmk_split_transport_peripheral *active_transport;
|
||||
|
||||
int zmk_split_transport_peripheral_command_handler(
|
||||
const struct zmk_split_transport_peripheral *transport,
|
||||
struct zmk_split_transport_central_command cmd) {
|
||||
LOG_DBG("");
|
||||
|
||||
switch (cmd.type) {
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR: {
|
||||
struct zmk_behavior_binding binding = {
|
||||
.param1 = cmd.data.invoke_behavior.param1,
|
||||
.param2 = cmd.data.invoke_behavior.param2,
|
||||
.behavior_dev = cmd.data.invoke_behavior.behavior_dev,
|
||||
};
|
||||
LOG_DBG("%s with params %d %d: pressed? %d", binding.behavior_dev, binding.param1,
|
||||
binding.param2, cmd.data.invoke_behavior.state);
|
||||
struct zmk_behavior_binding_event event = {.position = cmd.data.invoke_behavior.position,
|
||||
.timestamp = k_uptime_get()};
|
||||
int err;
|
||||
if (cmd.data.invoke_behavior.state > 0) {
|
||||
err = behavior_keymap_binding_pressed(&binding, event);
|
||||
} else {
|
||||
err = behavior_keymap_binding_released(&binding, event);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to invoke behavior %s: %d", binding.behavior_dev, err);
|
||||
}
|
||||
}
|
||||
default:
|
||||
LOG_WRN("Unhandled command type %d", cmd.type);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zmk_split_peripheral_report_event(const struct zmk_split_transport_peripheral_event *event) {
|
||||
if (!active_transport || !active_transport->api || !active_transport->api->report_event) {
|
||||
LOG_WRN("No active transport that supports reporting events!");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return active_transport->api->report_event(event);
|
||||
}
|
||||
|
||||
static int select_first_available_transport(void) {
|
||||
// Transports are sorted by priority, so find the first
|
||||
// One that's available, and enable it. Any transport that
|
||||
// Doesn't support `get_status` is assumed to be always
|
||||
// available and fully connected.
|
||||
STRUCT_SECTION_FOREACH(zmk_split_transport_peripheral, t) {
|
||||
if (!t->api->get_status || t->api->get_status().available) {
|
||||
if (active_transport == t) {
|
||||
LOG_DBG("First available is already selected, moving on");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (active_transport && active_transport->api->set_enabled) {
|
||||
int err = active_transport->api->set_enabled(false);
|
||||
if (err < 0) {
|
||||
LOG_WRN("Error disabling previously selected split transport (%d)", err);
|
||||
}
|
||||
}
|
||||
|
||||
active_transport = t;
|
||||
int err = 0;
|
||||
if (active_transport->api->set_enabled) {
|
||||
err = active_transport->api->set_enabled(true);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int transport_status_changed_cb(const struct zmk_split_transport_peripheral *p,
|
||||
struct zmk_split_transport_status status) {
|
||||
if (p == active_transport) {
|
||||
LOG_DBG("Peripheral at %p changed status: enabled %d, available %d, connections %d", p,
|
||||
status.enabled, status.available, status.connections);
|
||||
if (status.connections == ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_DISCONNECTED) {
|
||||
LOG_DBG("Find us a new active transport!");
|
||||
|
||||
return select_first_available_transport();
|
||||
}
|
||||
} else {
|
||||
select_first_available_transport();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int peripheral_init(void) {
|
||||
STRUCT_SECTION_FOREACH(zmk_split_transport_peripheral, t) {
|
||||
if (!t->api->set_status_callback) {
|
||||
continue;
|
||||
}
|
||||
|
||||
t->api->set_status_callback(transport_status_changed_cb);
|
||||
}
|
||||
|
||||
return select_first_available_transport();
|
||||
}
|
||||
|
||||
SYS_INIT(peripheral_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
||||
|
||||
int split_peripheral_listener(const zmk_event_t *eh) {
|
||||
LOG_DBG("");
|
||||
const struct zmk_position_state_changed *pos_ev;
|
||||
if ((pos_ev = as_zmk_position_state_changed(eh)) != NULL) {
|
||||
struct zmk_split_transport_peripheral_event ev = {
|
||||
.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_KEY_POSITION_EVENT,
|
||||
.data = {.key_position_event = {
|
||||
.position = pos_ev->position,
|
||||
.pressed = pos_ev->state,
|
||||
}}};
|
||||
|
||||
zmk_split_peripheral_report_event(&ev);
|
||||
}
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
const struct zmk_sensor_event *sensor_ev;
|
||||
if ((sensor_ev = as_zmk_sensor_event(eh)) != NULL) {
|
||||
if (sensor_ev->channel_data_size != 1) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
struct zmk_split_transport_peripheral_event ev = {
|
||||
.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_SENSOR_EVENT,
|
||||
.data = {.sensor_event = {
|
||||
.channel_data = sensor_ev->channel_data[0],
|
||||
.sensor_index = sensor_ev->sensor_index,
|
||||
}}};
|
||||
|
||||
zmk_split_peripheral_report_event(&ev);
|
||||
}
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING)
|
||||
const struct zmk_battery_state_changed *battery_ev;
|
||||
if ((battery_ev = as_zmk_battery_state_changed(eh)) != NULL) {
|
||||
struct zmk_split_transport_peripheral_event ev = {
|
||||
.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT,
|
||||
.data = {.battery_event = {
|
||||
.level = battery_ev->state_of_charge,
|
||||
}}};
|
||||
|
||||
zmk_split_peripheral_report_event(&ev);
|
||||
}
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING)
|
||||
|
||||
return ZMK_EV_EVENT_BUBBLE;
|
||||
}
|
||||
|
||||
ZMK_LISTENER(split_peripheral, split_peripheral_listener);
|
||||
ZMK_SUBSCRIPTION(split_peripheral, zmk_position_state_changed);
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
ZMK_SUBSCRIPTION(split_peripheral, zmk_sensor_event);
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING)
|
||||
ZMK_SUBSCRIPTION(split_peripheral, zmk_battery_state_changed);
|
||||
#endif
|
||||
6
app/src/split/wired/CMakeLists.txt
Normal file
6
app/src/split/wired/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) 2025 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
target_sources(app PRIVATE wired.c)
|
||||
target_sources_ifdef(CONFIG_ZMK_SPLIT_ROLE_CENTRAL app PRIVATE central.c)
|
||||
target_sources_ifndef(CONFIG_ZMK_SPLIT_ROLE_CENTRAL app PRIVATE peripheral.c)
|
||||
57
app/src/split/wired/Kconfig
Normal file
57
app/src/split/wired/Kconfig
Normal file
@@ -0,0 +1,57 @@
|
||||
# Copyright (c) 2025 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
if ZMK_SPLIT_WIRED
|
||||
|
||||
config ZMK_SPLIT_WIRED_PRIORITY
|
||||
int "Wired transport priority"
|
||||
help
|
||||
Lower number priorities transports are favored over higher numbers.
|
||||
|
||||
choice
|
||||
prompt "UART Mode"
|
||||
|
||||
config ZMK_SPLIT_WIRED_UART_MODE_ASYNC
|
||||
bool "Async (DMA) Mode"
|
||||
# For now, don't use async/DMA on nRF52 due to RX bug (fixed
|
||||
# in newer Zephyr version?)
|
||||
depends on SERIAL_SUPPORT_ASYNC
|
||||
select UART_ASYNC_API
|
||||
|
||||
config ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT
|
||||
bool "Interrupt Mode"
|
||||
depends on SERIAL_SUPPORT_INTERRUPT
|
||||
select UART_INTERRUPT_DRIVEN
|
||||
|
||||
config ZMK_SPLIT_WIRED_UART_MODE_POLLING
|
||||
bool "Polling Mode"
|
||||
|
||||
endchoice
|
||||
|
||||
if ZMK_SPLIT_WIRED_UART_MODE_POLLING
|
||||
|
||||
config ZMK_SPLIT_WIRED_POLLING_RX_PERIOD
|
||||
int "Ticks between RX polls"
|
||||
|
||||
endif
|
||||
|
||||
if ZMK_SPLIT_WIRED_UART_MODE_ASYNC
|
||||
|
||||
config ZMK_SPLIT_WIRED_ASYNC_RX_TIMEOUT
|
||||
int "RX Timeout (in microseconds) before reporting received data"
|
||||
|
||||
endif
|
||||
|
||||
config ZMK_SPLIT_WIRED_CMD_BUFFER_ITEMS
|
||||
int "Number of central commands to buffer for TX/RX"
|
||||
|
||||
config ZMK_SPLIT_WIRED_EVENT_BUFFER_ITEMS
|
||||
int "Number of peripheral events to buffer for TX/RX"
|
||||
|
||||
config ZMK_SPLIT_WIRED_HALF_DUPLEX_RX_TIMEOUT
|
||||
int "RX timeout (in ms) when polling peripheral(s) and waiting for any response"
|
||||
|
||||
config ZMK_SPLIT_WIRED_HALF_DUPLEX_RX_COMPLETE_TIMEOUT
|
||||
int "RX complete timeout (in ticks) when polling peripheral(s) after receiving some response data"
|
||||
|
||||
endif
|
||||
37
app/src/split/wired/Kconfig.defaults
Normal file
37
app/src/split/wired/Kconfig.defaults
Normal file
@@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2025 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
if ZMK_SPLIT_WIRED
|
||||
|
||||
config ZMK_SPLIT_WIRED_PRIORITY
|
||||
default 0
|
||||
|
||||
config ZMK_SPLIT_WIRED_CMD_BUFFER_ITEMS
|
||||
default 4
|
||||
|
||||
config ZMK_SPLIT_WIRED_EVENT_BUFFER_ITEMS
|
||||
default 16
|
||||
|
||||
|
||||
if ZMK_SPLIT_WIRED_UART_MODE_POLLING
|
||||
|
||||
config ZMK_SPLIT_WIRED_POLLING_RX_PERIOD
|
||||
default 10
|
||||
|
||||
endif
|
||||
|
||||
|
||||
if ZMK_SPLIT_WIRED_UART_MODE_ASYNC
|
||||
|
||||
config ZMK_SPLIT_WIRED_ASYNC_RX_TIMEOUT
|
||||
default 20
|
||||
|
||||
endif
|
||||
|
||||
config ZMK_SPLIT_WIRED_HALF_DUPLEX_RX_TIMEOUT
|
||||
default 15
|
||||
|
||||
config ZMK_SPLIT_WIRED_HALF_DUPLEX_RX_COMPLETE_TIMEOUT
|
||||
default 20
|
||||
|
||||
endif
|
||||
487
app/src/split/wired/central.c
Normal file
487
app/src/split/wired/central.c
Normal file
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/types.h>
|
||||
#include <zephyr/init.h>
|
||||
|
||||
#include <zephyr/pm/device.h>
|
||||
#include <zephyr/pm/device_runtime.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <zephyr/sys/crc.h>
|
||||
#include <zephyr/sys/ring_buffer.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
|
||||
#include <zmk/stdlib.h>
|
||||
#include <zmk/behavior.h>
|
||||
#include <zmk/sensors.h>
|
||||
#include <zmk/split/transport/central.h>
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
#include <zmk/events/sensor_event.h>
|
||||
#include <zmk/pointing/input_split.h>
|
||||
#include <zmk/hid_indicators_types.h>
|
||||
#include <zmk/physical_layouts.h>
|
||||
|
||||
#include "wired.h"
|
||||
|
||||
#define DT_DRV_COMPAT zmk_wired_split
|
||||
|
||||
#define IS_HALF_DUPLEX_MODE \
|
||||
(DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) && DT_INST_PROP_OR(0, half_duplex, false))
|
||||
|
||||
#define RX_BUFFER_SIZE \
|
||||
((sizeof(struct event_envelope) + sizeof(struct msg_postfix)) * \
|
||||
CONFIG_ZMK_SPLIT_WIRED_EVENT_BUFFER_ITEMS)
|
||||
#define TX_BUFFER_SIZE \
|
||||
((sizeof(struct command_envelope) + sizeof(struct msg_postfix)) * \
|
||||
CONFIG_ZMK_SPLIT_WIRED_CMD_BUFFER_ITEMS)
|
||||
|
||||
#if IS_HALF_DUPLEX_MODE
|
||||
|
||||
static K_SEM_DEFINE(tx_sem, 0, 1);
|
||||
|
||||
#endif
|
||||
|
||||
RING_BUF_DECLARE(rx_buf, RX_BUFFER_SIZE);
|
||||
RING_BUF_DECLARE(tx_buf, TX_BUFFER_SIZE);
|
||||
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
|
||||
|
||||
static const struct device *uart = DEVICE_DT_GET(DT_INST_PHANDLE(0, device));
|
||||
|
||||
#define HAS_DIR_GPIO (IS_HALF_DUPLEX_MODE && DT_INST_NODE_HAS_PROP(0, dir_gpios))
|
||||
|
||||
#if HAS_DIR_GPIO
|
||||
|
||||
static const struct gpio_dt_spec dir_gpio = GPIO_DT_SPEC_INST_GET(0, dir_gpios);
|
||||
|
||||
#endif
|
||||
|
||||
#define HAS_DETECT_GPIO DT_INST_NODE_HAS_PROP(0, detect_gpios)
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static const struct gpio_dt_spec detect_gpio = GPIO_DT_SPEC_INST_GET(0, detect_gpios);
|
||||
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#error \
|
||||
"Need to create a node with compatible of 'zmk,wired-split` with a `device` property set to an enabled UART. See http://zmk.dev/docs/development/hardware-integration/new-shield#wired-split"
|
||||
|
||||
#endif
|
||||
|
||||
static void publish_events_work(struct k_work *work);
|
||||
|
||||
K_WORK_DEFINE(publish_events, publish_events_work);
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
|
||||
uint8_t async_rx_buf[RX_BUFFER_SIZE / 2][2];
|
||||
|
||||
static struct zmk_split_wired_async_state async_state = {
|
||||
.process_tx_work = &publish_events,
|
||||
.rx_bufs = {async_rx_buf[0], async_rx_buf[1]},
|
||||
.rx_bufs_len = RX_BUFFER_SIZE / 2,
|
||||
.rx_size_process_trigger = MSG_EXTRA_SIZE + 1,
|
||||
.rx_buf = &rx_buf,
|
||||
.tx_buf = &tx_buf,
|
||||
#if HAS_DIR_GPIO
|
||||
.dir_gpio = &dir_gpio,
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#if IS_HALF_DUPLEX_MODE
|
||||
|
||||
static int can_tx(void) { return k_sem_take(&tx_sem, K_NO_WAIT); }
|
||||
|
||||
#else
|
||||
|
||||
static inline int can_tx(void) { return 0; }
|
||||
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING)
|
||||
|
||||
static void send_pending_tx_work_cb(struct k_work *work);
|
||||
|
||||
static K_WORK_DEFINE(wired_central_tx_work, send_pending_tx_work_cb);
|
||||
|
||||
static void read_timer_cb(struct k_timer *_timer) {
|
||||
zmk_split_wired_poll_in(&rx_buf, uart, &publish_events, NULL);
|
||||
// Check if we found any bytes, read some, or read all the bytes in the RX
|
||||
}
|
||||
|
||||
static K_TIMER_DEFINE(wired_central_read_timer, read_timer_cb, NULL);
|
||||
|
||||
#endif
|
||||
|
||||
static void begin_tx(void) {
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
uart_irq_tx_enable(uart);
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
zmk_split_wired_async_tx(&async_state);
|
||||
#else
|
||||
k_work_submit(&wired_central_tx_work);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void begin_rx(void) {
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
|
||||
pm_device_runtime_get(uart);
|
||||
#elif IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
pm_device_action_run(uart, PM_DEVICE_ACTION_RESUME);
|
||||
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
uart_irq_rx_enable(uart);
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
zmk_split_wired_async_rx(&async_state);
|
||||
#else
|
||||
k_timer_start(&wired_central_read_timer, K_TICKS(CONFIG_ZMK_SPLIT_WIRED_POLLING_RX_PERIOD),
|
||||
K_TICKS(CONFIG_ZMK_SPLIT_WIRED_POLLING_RX_PERIOD));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static void stop_rx(void) {
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
uart_irq_rx_disable(uart);
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
zmk_split_wired_async_rx_cancel(&async_state);
|
||||
#else
|
||||
k_timer_stop(&wired_central_read_timer);
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
|
||||
pm_device_runtime_put(uart);
|
||||
#elif IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
pm_device_action_run(uart, PM_DEVICE_ACTION_SUSPEND);
|
||||
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
}
|
||||
|
||||
#endif // HAS_DETECT_GPIO
|
||||
|
||||
static ssize_t get_payload_data_size(const struct zmk_split_transport_central_command *cmd) {
|
||||
switch (cmd->type) {
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_POLL_EVENTS:
|
||||
return 0;
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR:
|
||||
return sizeof(cmd->data.invoke_behavior);
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_PHYSICAL_LAYOUT:
|
||||
return sizeof(cmd->data.set_physical_layout);
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS:
|
||||
return sizeof(cmd->data.set_hid_indicators);
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
}
|
||||
|
||||
static int split_central_wired_send_command(uint8_t source,
|
||||
struct zmk_split_transport_central_command cmd) {
|
||||
if (source != 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ssize_t data_size = get_payload_data_size(&cmd);
|
||||
if (data_size < 0) {
|
||||
LOG_WRN("Failed to determine payload data size %d", data_size);
|
||||
return data_size;
|
||||
}
|
||||
|
||||
// Data + type + source
|
||||
size_t payload_size =
|
||||
data_size + sizeof(source) + sizeof(enum zmk_split_transport_central_command_type);
|
||||
|
||||
if (ring_buf_space_get(&tx_buf) < MSG_EXTRA_SIZE + payload_size) {
|
||||
LOG_WRN("No room to send command to the peripheral %d", source);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
struct command_envelope env = {.prefix =
|
||||
{
|
||||
.magic_prefix = ZMK_SPLIT_WIRED_ENVELOPE_MAGIC_PREFIX,
|
||||
.payload_size = payload_size,
|
||||
},
|
||||
.payload = {
|
||||
.source = source,
|
||||
.cmd = cmd,
|
||||
}};
|
||||
|
||||
struct msg_postfix postfix = {.crc =
|
||||
crc32_ieee((void *)&env, sizeof(env.prefix) + payload_size)};
|
||||
|
||||
ring_buf_put(&tx_buf, (uint8_t *)&env, sizeof(env.prefix) + payload_size);
|
||||
ring_buf_put(&tx_buf, (uint8_t *)&postfix, sizeof(postfix));
|
||||
|
||||
if (can_tx() >= 0) {
|
||||
begin_tx();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if IS_HALF_DUPLEX_MODE
|
||||
|
||||
void rx_done_cb(struct k_work *work);
|
||||
|
||||
static K_WORK_DELAYABLE_DEFINE(rx_done_work, rx_done_cb);
|
||||
|
||||
void rx_done_cb(struct k_work *work) {
|
||||
k_sem_give(&tx_sem);
|
||||
|
||||
// Poll for the next event data!
|
||||
split_central_wired_send_command(0,
|
||||
(struct zmk_split_transport_central_command){
|
||||
.type = ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_POLL_EVENTS,
|
||||
});
|
||||
|
||||
k_work_reschedule(&rx_done_work, K_MSEC(CONFIG_ZMK_SPLIT_WIRED_HALF_DUPLEX_RX_TIMEOUT));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
|
||||
static void serial_cb(const struct device *dev, void *user_data) {
|
||||
|
||||
while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
|
||||
if (uart_irq_rx_ready(dev)) {
|
||||
zmk_split_wired_fifo_read(dev, &rx_buf, &publish_events, NULL);
|
||||
#if IS_HALF_DUPLEX_MODE
|
||||
k_work_reschedule(&rx_done_work,
|
||||
K_TICKS(CONFIG_ZMK_SPLIT_WIRED_HALF_DUPLEX_RX_COMPLETE_TIMEOUT));
|
||||
#endif
|
||||
}
|
||||
|
||||
if (uart_irq_tx_complete(dev)) {
|
||||
|
||||
if (ring_buf_size_get(&tx_buf) == 0) {
|
||||
uart_irq_tx_disable(dev);
|
||||
#if HAS_DIR_GPIO
|
||||
gpio_pin_set_dt(&dir_gpio, 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (uart_irq_tx_ready(dev)) {
|
||||
#if HAS_DIR_GPIO
|
||||
gpio_pin_set_dt(&dir_gpio, 1);
|
||||
#endif
|
||||
zmk_split_wired_fifo_fill(dev, &tx_buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING)
|
||||
|
||||
static void send_pending_tx_work_cb(struct k_work *work) {
|
||||
zmk_split_wired_poll_out(&tx_buf, uart);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static void notify_transport_status(void);
|
||||
|
||||
static struct gpio_callback detect_callback;
|
||||
|
||||
static void notify_status_work_cb(struct k_work *_work) { notify_transport_status(); }
|
||||
|
||||
static K_WORK_DEFINE(notify_status_work, notify_status_work_cb);
|
||||
|
||||
static void detect_pin_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
|
||||
const gpio_port_pins_t pin) {
|
||||
k_work_submit(¬ify_status_work);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int zmk_split_wired_central_init(void) {
|
||||
if (!device_is_ready(uart)) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
|
||||
pm_device_runtime_put(uart);
|
||||
#elif IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
pm_device_action_run(uart, PM_DEVICE_ACTION_SUSPEND);
|
||||
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
|
||||
int ret = uart_irq_callback_user_data_set(uart, serial_cb, NULL);
|
||||
|
||||
if (ret < 0) {
|
||||
if (ret == -ENOTSUP) {
|
||||
LOG_ERR("Interrupt-driven UART API support not enabled");
|
||||
} else if (ret == -ENOSYS) {
|
||||
LOG_ERR("UART device does not support interrupt-driven API");
|
||||
} else {
|
||||
LOG_ERR("Error setting UART callback: %d\n", ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uart_irq_rx_enable(uart);
|
||||
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
|
||||
async_state.uart = uart;
|
||||
int ret = zmk_split_wired_async_init(&async_state);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to set up async wired split UART (%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_*)
|
||||
|
||||
#if IS_HALF_DUPLEX_MODE
|
||||
|
||||
#if HAS_DIR_GPIO
|
||||
gpio_pin_configure_dt(&dir_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
#endif
|
||||
|
||||
#endif // IS_HALF_DUPLEX_MODE
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
gpio_pin_configure_dt(&detect_gpio, GPIO_INPUT);
|
||||
|
||||
gpio_init_callback(&detect_callback, detect_pin_irq_callback_handler, BIT(detect_gpio.pin));
|
||||
int err = gpio_add_callback(detect_gpio.port, &detect_callback);
|
||||
if (err) {
|
||||
LOG_ERR("Error adding the callback to the detect pin: %i", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = gpio_pin_interrupt_configure_dt(&detect_gpio, GPIO_INT_EDGE_BOTH);
|
||||
if (err < 0) {
|
||||
LOG_WRN("Failed to so configure interrupt for detection pin (%d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
#endif // HAS_DETECT_GPIO
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYS_INIT(zmk_split_wired_central_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
||||
|
||||
static int split_central_wired_get_available_source_ids(uint8_t *sources) {
|
||||
sources[0] = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int split_central_wired_set_enabled(bool enabled) {
|
||||
if (enabled) {
|
||||
begin_rx();
|
||||
#if IS_HALF_DUPLEX_MODE
|
||||
k_work_schedule(&rx_done_work, K_MSEC(CONFIG_ZMK_SPLIT_WIRED_HALF_DUPLEX_RX_TIMEOUT));
|
||||
#endif
|
||||
return 0;
|
||||
#if HAS_DETECT_GPIO
|
||||
} else {
|
||||
#if IS_HALF_DUPLEX_MODE
|
||||
k_work_cancel_delayable(&rx_done_work);
|
||||
#endif
|
||||
stop_rx();
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static zmk_split_transport_central_status_changed_cb_t transport_status_cb;
|
||||
|
||||
static int
|
||||
split_central_wired_set_status_callback(zmk_split_transport_central_status_changed_cb_t cb) {
|
||||
transport_status_cb = cb;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct zmk_split_transport_status split_central_wired_get_status() {
|
||||
int detected = gpio_pin_get_dt(&detect_gpio);
|
||||
if (detected > 0) {
|
||||
return (struct zmk_split_transport_status){
|
||||
.available = true,
|
||||
.enabled = true, // Track this
|
||||
.connections = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_ALL_CONNECTED,
|
||||
|
||||
};
|
||||
} else {
|
||||
return (struct zmk_split_transport_status){
|
||||
.available = false,
|
||||
.enabled = true, // Track this
|
||||
.connections = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_DISCONNECTED,
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAS_DETECT_GPIO
|
||||
|
||||
static const struct zmk_split_transport_central_api central_api = {
|
||||
.send_command = split_central_wired_send_command,
|
||||
.get_available_source_ids = split_central_wired_get_available_source_ids,
|
||||
.set_enabled = split_central_wired_set_enabled,
|
||||
#if HAS_DETECT_GPIO
|
||||
.set_status_callback = split_central_wired_set_status_callback,
|
||||
.get_status = split_central_wired_get_status,
|
||||
#endif // HAS_DETECT_GPIO
|
||||
};
|
||||
|
||||
ZMK_SPLIT_TRANSPORT_CENTRAL_REGISTER(wired_central, ¢ral_api, CONFIG_ZMK_SPLIT_WIRED_PRIORITY);
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static void notify_transport_status(void) {
|
||||
if (transport_status_cb) {
|
||||
transport_status_cb(&wired_central, split_central_wired_get_status());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void publish_events_work(struct k_work *work) {
|
||||
|
||||
#if IS_HALF_DUPLEX_MODE
|
||||
k_work_reschedule(&rx_done_work,
|
||||
K_MSEC(CONFIG_ZMK_SPLIT_WIRED_HALF_DUPLEX_RX_COMPLETE_TIMEOUT));
|
||||
#endif // IS_HALF_DUPLEX_MODE
|
||||
|
||||
while (ring_buf_size_get(&rx_buf) > MSG_EXTRA_SIZE) {
|
||||
struct event_envelope env;
|
||||
int item_err =
|
||||
zmk_split_wired_get_item(&rx_buf, (uint8_t *)&env, sizeof(struct event_envelope));
|
||||
switch (item_err) {
|
||||
case 0:
|
||||
zmk_split_transport_central_peripheral_event_handler(&wired_central, env.payload.source,
|
||||
env.payload.event);
|
||||
break;
|
||||
case -EAGAIN:
|
||||
return;
|
||||
default:
|
||||
LOG_WRN("Issue fetching an item from the RX buffer: %d", item_err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
456
app/src/split/wired/peripheral.c
Normal file
456
app/src/split/wired/peripheral.c
Normal file
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/types.h>
|
||||
#include <zephyr/init.h>
|
||||
|
||||
#include <zephyr/pm/device.h>
|
||||
#include <zephyr/pm/device_runtime.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <zephyr/sys/crc.h>
|
||||
#include <zephyr/sys/ring_buffer.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
|
||||
#include <zmk/stdlib.h>
|
||||
#include <zmk/behavior.h>
|
||||
#include <zmk/sensors.h>
|
||||
#include <zmk/split/transport/peripheral.h>
|
||||
#include <zmk/split/transport/types.h>
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
#include <zmk/events/sensor_event.h>
|
||||
#include <zmk/pointing/input_split.h>
|
||||
#include <zmk/hid_indicators_types.h>
|
||||
#include <zmk/physical_layouts.h>
|
||||
|
||||
#include "wired.h"
|
||||
|
||||
#define DT_DRV_COMPAT zmk_wired_split
|
||||
|
||||
#define IS_HALF_DUPLEX_MODE \
|
||||
(DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) && DT_INST_PROP_OR(0, half_duplex, false))
|
||||
|
||||
#define TX_BUFFER_SIZE \
|
||||
((sizeof(struct event_envelope) + sizeof(struct msg_postfix)) * \
|
||||
CONFIG_ZMK_SPLIT_WIRED_EVENT_BUFFER_ITEMS)
|
||||
#define RX_BUFFER_SIZE \
|
||||
((sizeof(struct command_envelope) + sizeof(struct msg_postfix)) * \
|
||||
CONFIG_ZMK_SPLIT_WIRED_CMD_BUFFER_ITEMS)
|
||||
|
||||
RING_BUF_DECLARE(chosen_rx_buf, RX_BUFFER_SIZE);
|
||||
RING_BUF_DECLARE(chosen_tx_buf, TX_BUFFER_SIZE);
|
||||
|
||||
static const uint8_t peripheral_id = 0;
|
||||
|
||||
K_SEM_DEFINE(tx_sem, 0, 1);
|
||||
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
|
||||
|
||||
static const struct device *uart = DEVICE_DT_GET(DT_INST_PHANDLE(0, device));
|
||||
|
||||
#define HAS_DIR_GPIO (DT_INST_NODE_HAS_PROP(0, dir_gpios))
|
||||
|
||||
#if HAS_DIR_GPIO
|
||||
|
||||
static const struct gpio_dt_spec dir_gpio = GPIO_DT_SPEC_INST_GET(0, dir_gpios);
|
||||
|
||||
#endif
|
||||
|
||||
#define HAS_DETECT_GPIO DT_INST_NODE_HAS_PROP(0, detect_gpios)
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static const struct gpio_dt_spec detect_gpio = GPIO_DT_SPEC_INST_GET(0, detect_gpios);
|
||||
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#error \
|
||||
"Need to create a node with compatible of 'zmk,wired-split` with a `device` property set to an enabled UART. See http://zmk.dev/docs/development/hardware-integration/new-shield#wired-split"
|
||||
|
||||
#endif
|
||||
|
||||
static void publish_commands_work(struct k_work *work);
|
||||
|
||||
K_WORK_DEFINE(publish_commands, publish_commands_work);
|
||||
|
||||
static void process_tx_cb(void);
|
||||
K_MSGQ_DEFINE(cmd_msg_queue, sizeof(struct zmk_split_transport_central_command), 3, 4);
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
|
||||
uint8_t async_rx_buf[RX_BUFFER_SIZE / 2][2];
|
||||
|
||||
static struct zmk_split_wired_async_state async_state = {
|
||||
.rx_bufs = {async_rx_buf[0], async_rx_buf[1]},
|
||||
.rx_bufs_len = RX_BUFFER_SIZE / 2,
|
||||
.rx_size_process_trigger = sizeof(struct command_envelope),
|
||||
.process_tx_callback = process_tx_cb,
|
||||
.rx_buf = &chosen_rx_buf,
|
||||
.tx_buf = &chosen_tx_buf,
|
||||
#if HAS_DIR_GPIO
|
||||
.dir_gpio = &dir_gpio,
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING)
|
||||
|
||||
static void wired_peripheral_read_tick_cb(struct k_timer *timer) {
|
||||
zmk_split_wired_poll_in(&chosen_rx_buf, uart, NULL, process_tx_cb);
|
||||
}
|
||||
|
||||
static K_TIMER_DEFINE(wired_peripheral_read_timer, wired_peripheral_read_tick_cb, NULL);
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING)
|
||||
|
||||
static void begin_rx(void) {
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
|
||||
pm_device_runtime_get(uart);
|
||||
#elif IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
pm_device_action_run(uart, PM_DEVICE_ACTION_RESUME);
|
||||
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
uart_irq_rx_enable(uart);
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
zmk_split_wired_async_rx(&async_state);
|
||||
#else
|
||||
k_timer_start(&wired_peripheral_read_timer, K_TICKS(CONFIG_ZMK_SPLIT_WIRED_POLLING_RX_PERIOD),
|
||||
K_TICKS(CONFIG_ZMK_SPLIT_WIRED_POLLING_RX_PERIOD));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static void stop_rx(void) {
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
uart_irq_rx_disable(uart);
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
zmk_split_wired_async_rx_cancel(&async_state);
|
||||
#else
|
||||
k_timer_stop(&wired_peripheral_read_timer);
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
|
||||
pm_device_runtime_put(uart);
|
||||
#elif IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
pm_device_action_run(uart, PM_DEVICE_ACTION_SUSPEND);
|
||||
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
}
|
||||
|
||||
#endif // HAS_DETECT_GPIO
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
|
||||
static void serial_cb(const struct device *dev, void *user_data) {
|
||||
while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
|
||||
if (uart_irq_rx_ready(dev)) {
|
||||
zmk_split_wired_fifo_read(dev, &chosen_rx_buf, NULL, process_tx_cb);
|
||||
}
|
||||
|
||||
if (uart_irq_tx_complete(dev)) {
|
||||
if (ring_buf_size_get(&chosen_tx_buf) == 0) {
|
||||
uart_irq_tx_disable(dev);
|
||||
}
|
||||
|
||||
#if HAS_DIR_GPIO
|
||||
gpio_pin_set_dt(&dir_gpio, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (uart_irq_tx_ready(dev)) {
|
||||
#if HAS_DIR_GPIO
|
||||
gpio_pin_set_dt(&dir_gpio, 1);
|
||||
#endif
|
||||
zmk_split_wired_fifo_fill(dev, &chosen_tx_buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING)
|
||||
|
||||
static void send_pending_tx_work_cb(struct k_work *work) {
|
||||
zmk_split_wired_poll_out(&chosen_tx_buf, uart);
|
||||
}
|
||||
|
||||
static K_WORK_DEFINE(send_pending_tx, send_pending_tx_work_cb);
|
||||
|
||||
#endif
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static void notify_transport_status(void);
|
||||
|
||||
static struct gpio_callback detect_callback;
|
||||
|
||||
static void notify_status_work_cb(struct k_work *_work) { notify_transport_status(); }
|
||||
|
||||
static K_WORK_DEFINE(notify_status_work, notify_status_work_cb);
|
||||
|
||||
static void detect_pin_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
|
||||
const gpio_port_pins_t pin) {
|
||||
k_work_submit(¬ify_status_work);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int zmk_split_wired_peripheral_init(void) {
|
||||
if (!device_is_ready(uart)) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
|
||||
pm_device_runtime_put(uart);
|
||||
#elif IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
pm_device_action_run(uart, PM_DEVICE_ACTION_SUSPEND);
|
||||
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
|
||||
#if HAS_DIR_GPIO
|
||||
gpio_pin_configure_dt(&dir_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
/* configure interrupt and callback to receive data */
|
||||
int ret = uart_irq_callback_user_data_set(uart, serial_cb, NULL);
|
||||
|
||||
if (ret < 0) {
|
||||
if (ret == -ENOTSUP) {
|
||||
LOG_ERR("Interrupt-driven UART API support not enabled");
|
||||
} else if (ret == -ENOSYS) {
|
||||
LOG_ERR("UART device does not support interrupt-driven API");
|
||||
} else {
|
||||
LOG_ERR("Error setting UART callback: %d\n", ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
async_state.uart = uart;
|
||||
int ret = zmk_split_wired_async_init(&async_state);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to set up async wired split UART (%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
gpio_pin_configure_dt(&detect_gpio, GPIO_INPUT);
|
||||
|
||||
gpio_init_callback(&detect_callback, detect_pin_irq_callback_handler, BIT(detect_gpio.pin));
|
||||
int err = gpio_add_callback(detect_gpio.port, &detect_callback);
|
||||
if (err) {
|
||||
LOG_ERR("Error adding the callback to the detect pin: %i", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = gpio_pin_interrupt_configure_dt(&detect_gpio, GPIO_INT_EDGE_BOTH);
|
||||
if (err < 0) {
|
||||
LOG_WRN("Failed to so configure interrupt for detection pin (%d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
#endif // HAS_DETECT_GPIO
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYS_INIT(zmk_split_wired_peripheral_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
||||
|
||||
static void begin_tx(void) {
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
uart_irq_tx_enable(uart);
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
zmk_split_wired_async_tx(&async_state);
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING)
|
||||
k_work_submit(&send_pending_tx);
|
||||
#endif
|
||||
}
|
||||
|
||||
static ssize_t get_payload_data_size(const struct zmk_split_transport_peripheral_event *evt) {
|
||||
switch (evt->type) {
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_INPUT_EVENT:
|
||||
return sizeof(evt->data.input_event);
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_KEY_POSITION_EVENT:
|
||||
return sizeof(evt->data.key_position_event);
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_SENSOR_EVENT:
|
||||
return sizeof(evt->data.sensor_event);
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT:
|
||||
return sizeof(evt->data.battery_event);
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
split_peripheral_wired_report_event(const struct zmk_split_transport_peripheral_event *event) {
|
||||
ssize_t data_size = get_payload_data_size(event);
|
||||
if (data_size < 0) {
|
||||
LOG_WRN("Failed to determine payload data size %d", data_size);
|
||||
return data_size;
|
||||
}
|
||||
|
||||
// Data + type + source
|
||||
size_t payload_size =
|
||||
data_size + sizeof(peripheral_id) + sizeof(enum zmk_split_transport_peripheral_event_type);
|
||||
|
||||
if (ring_buf_space_get(&chosen_tx_buf) < MSG_EXTRA_SIZE + payload_size) {
|
||||
LOG_WRN("No room to send peripheral to the central (have %d but only space for %d)",
|
||||
MSG_EXTRA_SIZE + payload_size, ring_buf_space_get(&chosen_tx_buf));
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
struct event_envelope env = {.prefix =
|
||||
{
|
||||
.magic_prefix = ZMK_SPLIT_WIRED_ENVELOPE_MAGIC_PREFIX,
|
||||
.payload_size = payload_size,
|
||||
},
|
||||
.payload = {
|
||||
.source = peripheral_id,
|
||||
.event = *event,
|
||||
}};
|
||||
|
||||
struct msg_postfix postfix = {.crc =
|
||||
crc32_ieee((void *)&env, sizeof(env.prefix) + payload_size)};
|
||||
|
||||
LOG_HEXDUMP_DBG(&env, sizeof(env.prefix) + payload_size, "Payload");
|
||||
|
||||
size_t put = ring_buf_put(&chosen_tx_buf, (uint8_t *)&env, sizeof(env.prefix) + payload_size);
|
||||
if (put != sizeof(env.prefix) + payload_size) {
|
||||
LOG_WRN("Failed to put the whole message (%d vs %d)", put,
|
||||
sizeof(env.prefix) + payload_size);
|
||||
}
|
||||
put = ring_buf_put(&chosen_tx_buf, (uint8_t *)&postfix, sizeof(postfix));
|
||||
if (put != sizeof(postfix)) {
|
||||
LOG_WRN("Failed to put the whole message (%d vs %d)", put, sizeof(postfix));
|
||||
}
|
||||
|
||||
#if !IS_HALF_DUPLEX_MODE
|
||||
begin_tx();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool is_enabled;
|
||||
|
||||
static int split_peripheral_wired_set_enabled(bool enabled) {
|
||||
if (is_enabled == enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
is_enabled = enabled;
|
||||
|
||||
if (enabled) {
|
||||
begin_rx();
|
||||
return 0;
|
||||
#if HAS_DETECT_GPIO
|
||||
} else {
|
||||
stop_rx();
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static zmk_split_transport_peripheral_status_changed_cb_t transport_status_cb;
|
||||
|
||||
static int
|
||||
split_peripheral_wired_set_status_callback(zmk_split_transport_peripheral_status_changed_cb_t cb) {
|
||||
transport_status_cb = cb;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct zmk_split_transport_status split_peripheral_wired_get_status() {
|
||||
int detected = gpio_pin_get_dt(&detect_gpio);
|
||||
if (detected > 0) {
|
||||
return (struct zmk_split_transport_status){
|
||||
.available = true,
|
||||
.enabled = true, // Track this
|
||||
.connections = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_ALL_CONNECTED,
|
||||
|
||||
};
|
||||
} else {
|
||||
return (struct zmk_split_transport_status){
|
||||
.available = false,
|
||||
.enabled = true, // Track this
|
||||
.connections = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_DISCONNECTED,
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAS_DETECT_GPIO
|
||||
|
||||
static const struct zmk_split_transport_peripheral_api peripheral_api = {
|
||||
.report_event = split_peripheral_wired_report_event,
|
||||
.set_enabled = split_peripheral_wired_set_enabled,
|
||||
#if HAS_DETECT_GPIO
|
||||
.set_status_callback = split_peripheral_wired_set_status_callback,
|
||||
.get_status = split_peripheral_wired_get_status,
|
||||
#endif // HAS_DETECT_GPIO
|
||||
};
|
||||
|
||||
ZMK_SPLIT_TRANSPORT_PERIPHERAL_REGISTER(wired_peripheral, &peripheral_api,
|
||||
CONFIG_ZMK_SPLIT_WIRED_PRIORITY);
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static void notify_transport_status(void) {
|
||||
if (transport_status_cb) {
|
||||
LOG_DBG("Invoking the status CB");
|
||||
transport_status_cb(&wired_peripheral, split_peripheral_wired_get_status());
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAS_DETECT_GPIO
|
||||
|
||||
static void process_tx_cb(void) {
|
||||
while (ring_buf_size_get(&chosen_rx_buf) > MSG_EXTRA_SIZE) {
|
||||
struct command_envelope env;
|
||||
int item_err = zmk_split_wired_get_item(&chosen_rx_buf, (uint8_t *)&env,
|
||||
sizeof(struct command_envelope));
|
||||
switch (item_err) {
|
||||
case 0:
|
||||
if (env.payload.cmd.type == ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_POLL_EVENTS) {
|
||||
begin_tx();
|
||||
} else {
|
||||
int ret = k_msgq_put(&cmd_msg_queue, &env.payload.cmd, K_NO_WAIT);
|
||||
if (ret < 0) {
|
||||
LOG_WRN("Failed to queue command for processing (%d)", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
k_work_submit(&publish_commands);
|
||||
}
|
||||
break;
|
||||
case -EAGAIN:
|
||||
return;
|
||||
default:
|
||||
LOG_WRN("Issue fetching an item from the RX buffer: %d", item_err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
static void publish_commands_work(struct k_work *work) {
|
||||
struct zmk_split_transport_central_command cmd;
|
||||
|
||||
while (k_msgq_get(&cmd_msg_queue, &cmd, K_NO_WAIT) >= 0) {
|
||||
zmk_split_transport_peripheral_command_handler(&wired_peripheral, cmd);
|
||||
}
|
||||
}
|
||||
327
app/src/split/wired/wired.c
Normal file
327
app/src/split/wired/wired.c
Normal file
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "wired.h"
|
||||
|
||||
#include <zephyr/sys/crc.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING)
|
||||
|
||||
void zmk_split_wired_poll_out(struct ring_buf *tx_buf, const struct device *uart) {
|
||||
uint8_t *buf;
|
||||
uint32_t claim_len;
|
||||
while ((claim_len = ring_buf_get_claim(tx_buf, &buf, MIN(32, tx_buf->size))) > 0) {
|
||||
LOG_HEXDUMP_DBG(buf, claim_len, "TX Bytes");
|
||||
for (int i = 0; i < claim_len; i++) {
|
||||
uart_poll_out(uart, buf[i]);
|
||||
}
|
||||
|
||||
ring_buf_get_finish(tx_buf, claim_len);
|
||||
}
|
||||
}
|
||||
|
||||
int zmk_split_wired_poll_in(struct ring_buf *rx_buf, const struct device *uart,
|
||||
struct k_work *process_data_work,
|
||||
zmk_split_wired_process_tx_callback_t process_data_cb) {
|
||||
uint8_t *buf;
|
||||
uint32_t read = 0;
|
||||
uint32_t claim_len = ring_buf_put_claim(rx_buf, &buf, ring_buf_space_get(rx_buf));
|
||||
if (claim_len < 1) {
|
||||
LOG_WRN("No room available for reading in from the serial port");
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
bool all_read = false;
|
||||
while (read < claim_len) {
|
||||
if (uart_poll_in(uart, buf + read) < 0) {
|
||||
all_read = true;
|
||||
break;
|
||||
}
|
||||
|
||||
read++;
|
||||
}
|
||||
|
||||
ring_buf_put_finish(rx_buf, read);
|
||||
|
||||
if (ring_buf_size_get(rx_buf) > 0) {
|
||||
if (process_data_work) {
|
||||
k_work_submit(process_data_work);
|
||||
} else if (process_data_cb) {
|
||||
process_data_cb();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Also indicate if no bytes read at all?
|
||||
return (all_read ? 1 : 0);
|
||||
}
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
|
||||
void zmk_split_wired_fifo_read(const struct device *dev, struct ring_buf *buf,
|
||||
struct k_work *process_work,
|
||||
zmk_split_wired_process_tx_callback_t process_cb) {
|
||||
// TODO: Add error checking on platforms that support it
|
||||
uint32_t last_read = 0, len = 0;
|
||||
do {
|
||||
uint8_t *buffer;
|
||||
len = ring_buf_put_claim(buf, &buffer, buf->size);
|
||||
if (len > 0) {
|
||||
last_read = uart_fifo_read(dev, buffer, len);
|
||||
|
||||
ring_buf_put_finish(buf, last_read);
|
||||
} else {
|
||||
LOG_ERR("Dropping incoming RPC byte, insufficient room in the RX buffer. Bump "
|
||||
"CONFIG_ZMK_STUDIO_RPC_RX_BUF_SIZE.");
|
||||
uint8_t dummy;
|
||||
last_read = uart_fifo_read(dev, &dummy, 1);
|
||||
}
|
||||
} while (last_read && last_read == len);
|
||||
|
||||
if (process_work) {
|
||||
k_work_submit(process_work);
|
||||
} else if (process_cb) {
|
||||
process_cb();
|
||||
}
|
||||
}
|
||||
|
||||
void zmk_split_wired_fifo_fill(const struct device *dev, struct ring_buf *tx_buf) {
|
||||
uint32_t len;
|
||||
while ((len = ring_buf_size_get(tx_buf)) > 0) {
|
||||
uint8_t *buf;
|
||||
uint32_t claim_len = ring_buf_get_claim(tx_buf, &buf, tx_buf->size);
|
||||
|
||||
if (claim_len <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
int sent = uart_fifo_fill(dev, buf, claim_len);
|
||||
|
||||
ring_buf_get_finish(tx_buf, MAX(sent, 0));
|
||||
|
||||
if (sent <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if (ring_buf_size_get(tx_buf) == 0) {
|
||||
// uart_irq_tx_disable(dev);
|
||||
// }
|
||||
}
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
|
||||
enum ASYNC_STATE_BITS {
|
||||
ASYNC_STATE_BIT_RXBUF0_USED = 0,
|
||||
ASYNC_STATE_BIT_RXBUF1_USED,
|
||||
};
|
||||
|
||||
void zmk_split_wired_async_tx(struct zmk_split_wired_async_state *state) {
|
||||
uint8_t *buf;
|
||||
uint32_t claim_len = ring_buf_get_claim(state->tx_buf, &buf, ring_buf_size_get(state->tx_buf));
|
||||
|
||||
if (claim_len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state->dir_gpio) {
|
||||
gpio_pin_set_dt(state->dir_gpio, 1);
|
||||
}
|
||||
|
||||
#if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
LOG_DBG("Sending %d", claim_len);
|
||||
#endif
|
||||
int err = uart_tx(state->uart, buf, claim_len, SYS_FOREVER_US);
|
||||
if (err < 0) {
|
||||
LOG_DBG("NO TX %d", err);
|
||||
}
|
||||
}
|
||||
|
||||
int zmk_split_wired_async_rx(struct zmk_split_wired_async_state *state) {
|
||||
|
||||
atomic_set_bit(&state->state, ASYNC_STATE_BIT_RXBUF0_USED);
|
||||
atomic_clear_bit(&state->state, ASYNC_STATE_BIT_RXBUF1_USED);
|
||||
|
||||
int ret = uart_rx_enable(state->uart, state->rx_bufs[0], state->rx_bufs_len,
|
||||
CONFIG_ZMK_SPLIT_WIRED_ASYNC_RX_TIMEOUT);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to enable RX (%d)", ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int zmk_split_wired_async_rx_cancel(struct zmk_split_wired_async_state *state) {
|
||||
return uart_rx_disable(state->uart);
|
||||
}
|
||||
|
||||
static void restart_rx_work_cb(struct k_work *work) {
|
||||
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||||
struct zmk_split_wired_async_state *state =
|
||||
CONTAINER_OF(dwork, struct zmk_split_wired_async_state, restart_rx_work);
|
||||
|
||||
zmk_split_wired_async_rx(state);
|
||||
}
|
||||
|
||||
static void async_uart_cb(const struct device *dev, struct uart_event *ev, void *user_data) {
|
||||
struct zmk_split_wired_async_state *state = (struct zmk_split_wired_async_state *)user_data;
|
||||
|
||||
switch (ev->type) {
|
||||
case UART_TX_ABORTED:
|
||||
// This can only really occur for a TX timeout for a HW flow control UART setup. What to do
|
||||
// here in practice?
|
||||
LOG_WRN("TX Aborted");
|
||||
break;
|
||||
case UART_TX_DONE:
|
||||
LOG_DBG("TX Done %d", ev->data.tx.len);
|
||||
ring_buf_get_finish(state->tx_buf, ev->data.tx.len);
|
||||
if (ring_buf_size_get(state->tx_buf) > 0) {
|
||||
zmk_split_wired_async_tx(state);
|
||||
} else {
|
||||
if (state->dir_gpio) {
|
||||
gpio_pin_set_dt(state->dir_gpio, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UART_RX_RDY: {
|
||||
size_t received =
|
||||
ring_buf_put(state->rx_buf, &ev->data.rx.buf[ev->data.rx.offset], ev->data.rx.len);
|
||||
if (received < ev->data.rx.len) {
|
||||
LOG_ERR("RX overrun!");
|
||||
break;
|
||||
}
|
||||
|
||||
// LOG_DBG("RX %d and now buffer is %d", received, ring_buf_size_get(state->rx_buf));
|
||||
if (state->process_tx_callback) {
|
||||
state->process_tx_callback();
|
||||
} else if (state->process_tx_work) {
|
||||
k_work_submit(state->process_tx_work);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UART_RX_BUF_RELEASED:
|
||||
if (ev->data.rx_buf.buf == state->rx_bufs[0]) {
|
||||
atomic_clear_bit(&state->state, ASYNC_STATE_BIT_RXBUF0_USED);
|
||||
} else if (ev->data.rx_buf.buf == state->rx_bufs[1]) {
|
||||
atomic_clear_bit(&state->state, ASYNC_STATE_BIT_RXBUF1_USED);
|
||||
}
|
||||
|
||||
break;
|
||||
case UART_RX_BUF_REQUEST:
|
||||
if (!atomic_test_and_set_bit(&state->state, ASYNC_STATE_BIT_RXBUF0_USED)) {
|
||||
uart_rx_buf_rsp(state->uart, state->rx_bufs[0], state->rx_bufs_len);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!atomic_test_and_set_bit(&state->state, ASYNC_STATE_BIT_RXBUF1_USED)) {
|
||||
uart_rx_buf_rsp(state->uart, state->rx_bufs[1], state->rx_bufs_len);
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_WRN("No RX buffers available!");
|
||||
break;
|
||||
case UART_RX_STOPPED:
|
||||
// LOG_WRN("UART RX Stopped %d with state %ld", ev->data.rx_stop.reason, state->state);
|
||||
break;
|
||||
case UART_RX_DISABLED: {
|
||||
k_work_schedule(&state->restart_rx_work, K_MSEC(1));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int zmk_split_wired_async_init(struct zmk_split_wired_async_state *state) {
|
||||
__ASSERT(state != NULL, "State is null");
|
||||
|
||||
k_work_init_delayable(&state->restart_rx_work, restart_rx_work_cb);
|
||||
|
||||
int ret = uart_callback_set(state->uart, async_uart_cb, state);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to set up async callback on UART (%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// atomic_set_bit(&state->state, ASYNC_STATE_BIT_RXBUF0_USED);
|
||||
|
||||
// ret = uart_rx_enable(state->uart, state->rx_bufs[0], state->rx_bufs_len,
|
||||
// CONFIG_ZMK_SPLIT_WIRED_ASYNC_RX_TIMEOUT);
|
||||
// if (ret < 0) {
|
||||
// LOG_ERR("Failed to enable RX (%d)", ret);
|
||||
// return ret;
|
||||
// }
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int zmk_split_wired_get_item(struct ring_buf *rx_buf, uint8_t *env, size_t env_size) {
|
||||
while (ring_buf_size_get(rx_buf) > sizeof(struct msg_prefix) + sizeof(struct msg_postfix)) {
|
||||
struct msg_prefix prefix;
|
||||
|
||||
__ASSERT_EVAL(
|
||||
(void)ring_buf_peek(rx_buf, (uint8_t *)&prefix, sizeof(prefix)),
|
||||
uint32_t peek_read = ring_buf_peek(rx_buf, (uint8_t *)&prefix, sizeof(prefix)),
|
||||
peek_read == sizeof(prefix), "Somehow read less than we expect from the RX buffer");
|
||||
|
||||
if (memcmp(&prefix.magic_prefix, &ZMK_SPLIT_WIRED_ENVELOPE_MAGIC_PREFIX,
|
||||
sizeof(prefix.magic_prefix)) != 0) {
|
||||
uint8_t discarded_byte;
|
||||
ring_buf_get(rx_buf, &discarded_byte, 1);
|
||||
|
||||
LOG_WRN("Prefix mismatch, discarding byte %0x", discarded_byte);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t payload_to_read = sizeof(prefix) + prefix.payload_size;
|
||||
|
||||
if (payload_to_read > env_size) {
|
||||
LOG_WRN("Invalid message with payload %d bigger than expected max %d", payload_to_read,
|
||||
env_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ring_buf_size_get(rx_buf) < payload_to_read + sizeof(struct msg_postfix)) {
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
// Now that prefix matches, read it out so we can read the rest of the payload.
|
||||
__ASSERT_EVAL((void)ring_buf_get(rx_buf, env, payload_to_read),
|
||||
uint32_t read = ring_buf_get(rx_buf, env, payload_to_read),
|
||||
read == payload_to_read,
|
||||
"Somehow read less than we expect from the RX buffer");
|
||||
|
||||
struct msg_postfix postfix;
|
||||
__ASSERT_EVAL((void)ring_buf_get(rx_buf, (uint8_t *)&postfix, sizeof(postfix)),
|
||||
uint32_t read = ring_buf_get(rx_buf, (uint8_t *)&postfix, sizeof(postfix)),
|
||||
read == sizeof(postfix),
|
||||
"Somehow read less of the postfix than we expect from the RX buffer");
|
||||
|
||||
uint32_t crc = crc32_ieee(env, payload_to_read);
|
||||
if (crc != postfix.crc) {
|
||||
LOG_WRN("Data corruption in received peripheral event, ignoring %d vs %d", crc,
|
||||
postfix.crc);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EAGAIN;
|
||||
}
|
||||
96
app/src/split/wired/wired.h
Normal file
96
app/src/split/wired/wired.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/sys/ring_buffer.h>
|
||||
#include <zephyr/device.h>
|
||||
|
||||
#include <zmk/split/transport/types.h>
|
||||
|
||||
#define ZMK_SPLIT_WIRED_ENVELOPE_MAGIC_PREFIX "ZmKw"
|
||||
|
||||
struct msg_prefix {
|
||||
uint8_t magic_prefix[sizeof(ZMK_SPLIT_WIRED_ENVELOPE_MAGIC_PREFIX) - 1];
|
||||
uint8_t payload_size;
|
||||
} __packed;
|
||||
|
||||
struct command_payload {
|
||||
uint8_t source;
|
||||
struct zmk_split_transport_central_command cmd;
|
||||
} __packed;
|
||||
|
||||
struct command_envelope {
|
||||
struct msg_prefix prefix;
|
||||
struct command_payload payload;
|
||||
} __packed;
|
||||
|
||||
struct event_payload {
|
||||
uint8_t source;
|
||||
struct zmk_split_transport_peripheral_event event;
|
||||
} __packed;
|
||||
|
||||
struct event_envelope {
|
||||
struct msg_prefix prefix;
|
||||
struct event_payload payload;
|
||||
} __packed;
|
||||
|
||||
struct msg_postfix {
|
||||
uint32_t crc;
|
||||
} __packed;
|
||||
|
||||
#define MSG_EXTRA_SIZE (sizeof(struct msg_prefix) + sizeof(struct msg_postfix))
|
||||
|
||||
typedef void (*zmk_split_wired_process_tx_callback_t)(void);
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING)
|
||||
|
||||
void zmk_split_wired_poll_out(struct ring_buf *tx_buf, const struct device *uart);
|
||||
|
||||
int zmk_split_wired_poll_in(struct ring_buf *rx_buf, const struct device *uart,
|
||||
struct k_work *process_data_work,
|
||||
zmk_split_wired_process_tx_callback_t process_data_cb);
|
||||
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
|
||||
void zmk_split_wired_fifo_read(const struct device *dev, struct ring_buf *buf,
|
||||
struct k_work *process_work,
|
||||
zmk_split_wired_process_tx_callback_t process_cb);
|
||||
void zmk_split_wired_fifo_fill(const struct device *dev, struct ring_buf *tx_buf);
|
||||
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
|
||||
struct zmk_split_wired_async_state {
|
||||
atomic_t state;
|
||||
|
||||
uint8_t *rx_bufs[2];
|
||||
size_t rx_bufs_len;
|
||||
size_t rx_size_process_trigger;
|
||||
|
||||
struct ring_buf *tx_buf;
|
||||
struct ring_buf *rx_buf;
|
||||
|
||||
zmk_split_wired_process_tx_callback_t process_tx_callback;
|
||||
|
||||
const struct device *uart;
|
||||
|
||||
struct k_work_delayable restart_rx_work;
|
||||
struct k_work *process_tx_work;
|
||||
const struct gpio_dt_spec *dir_gpio;
|
||||
};
|
||||
|
||||
int zmk_split_wired_async_init(struct zmk_split_wired_async_state *state);
|
||||
void zmk_split_wired_async_tx(struct zmk_split_wired_async_state *state);
|
||||
int zmk_split_wired_async_rx(struct zmk_split_wired_async_state *state);
|
||||
int zmk_split_wired_async_rx_cancel(struct zmk_split_wired_async_state *state);
|
||||
|
||||
#endif
|
||||
|
||||
int zmk_split_wired_get_item(struct ring_buf *rx_buf, uint8_t *env, size_t env_size);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user