Skip to content

Commit

Permalink
Add dongle_display shield
Browse files Browse the repository at this point in the history
  • Loading branch information
englmaxi committed May 8, 2024
1 parent dddc77f commit 4544da3
Show file tree
Hide file tree
Showing 30 changed files with 1,951 additions and 40 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
on: [push, pull_request, workflow_dispatch]

jobs:
build:
uses: zmkfirmware/zmk/.github/workflows/build-user-config.yml@main
on: [push, pull_request, workflow_dispatch]

jobs:
build:
uses: zmkfirmware/zmk/.github/workflows/build-user-config.yml@main
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Dongle Display

The ZMK shield in this module repository, when added, replaces the built-in status screen with a custom one designed for use with 128x64-pixel OLED displays.
As it shows only the peripheral battery levels, it is recommended to be used on dongles only.

## Usage

To use, first add this module to your `config/west.yml` by adding a new entry to `remotes` and `projects`:

```yaml west.yml
manifest:
remotes:
- name: zmkfirmware
url-base: https://github.com/zmkfirmware
- name: englmaxi
url-base: https://github.com/englmaxi
projects:
- name: zmk
remote: zmkfirmware
revision: main
import: app/west.yml
- name: zmk-dongle-display
remote: englmaxi
revision: win # Windows modifier symbols
# revision: mac # MacOS modifier symbols
self:
path: config
```
Than replace the build-in status screen by adding `dongle_display` to your `build.yaml`:

```yaml build.yaml
---
include:
- board: seeeduino_xiao_ble
shield: sweep_central_dongle dongle_display
```

The shield assumes that the dongle is already set up and working with the built-in status screen.
Have a look at the shields in my [`zmk-config`](https://github.com/englmaxi/zmk-config) to see how I did it.

## Widgets
- active hid indicators (CLCK, NLCK, SLCK)
- active modifiers
- bongo cat
- highest layer name
- output status
- peripheral battery levels

## Demo
![output](https://github.com/englmaxi/zmk-config/assets/43675074/8d268f23-1a4f-44c3-817e-c36dc96a1f8b)
![mods](https://github.com/englmaxi/zmk-config/assets/43675074/af9ec3f5-8f61-4629-abed-14ba0047f0bd)

## Dongle Designs
- [case1](/cases)
- [case2](/cases)
- [PCB with OLED and reset button breakouts](https://github.com/spe2/zmk_dongle_hardware)
- [Macintosh](https://makerworld.com/en/models/403660)
- [Redox](https://makerworld.com/en/models/242951)
18 changes: 18 additions & 0 deletions boards/shields/dongle_display/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
if(CONFIG_ZMK_DISPLAY AND CONFIG_ZMK_DISPLAY_STATUS_SCREEN_CUSTOM AND ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL))
zephyr_library()
zephyr_library_sources(${ZEPHYR_BASE}/misc/empty_file.c)
zephyr_library_include_directories(${ZEPHYR_LVGL_MODULE_DIR})
zephyr_library_include_directories(${ZEPHYR_BASE}/lib/gui/lvgl/)
zephyr_library_include_directories(${ZEPHYR_BASE}/drivers)
zephyr_library_include_directories(${CMAKE_SOURCE_DIR}/include)
zephyr_library_sources(custom_status_screen.c)
zephyr_library_sources(widgets/battery_status.c)
zephyr_library_sources(widgets/bongo_cat.c)
zephyr_library_sources(widgets/bongo_cat_images.c)
target_sources_ifdef(CONFIG_ZMK_HID_INDICATORS app PRIVATE widgets/hid_indicators.c)
zephyr_library_sources(widgets/layer_status.c)
zephyr_library_sources(widgets/modifiers.c)
zephyr_library_sources(widgets/modifiers_sym.c)
zephyr_library_sources(widgets/output_status.c)
zephyr_library_sources(widgets/output_status_sym.c)
endif()
41 changes: 41 additions & 0 deletions boards/shields/dongle_display/Kconfig.defconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT

if SHIELD_DONGLE_DISPLAY

choice ZMK_DISPLAY_STATUS_SCREEN
default ZMK_DISPLAY_STATUS_SCREEN_CUSTOM
endchoice

config ZMK_DISPLAY_STATUS_SCREEN_CUSTOM
select LV_USE_LABEL
select LV_USE_IMG
select LV_USE_CANVAS
select LV_USE_ANIMIMG
select LV_USE_ANIMATION
select LV_USE_LINE
select LV_FONT_UNSCII_8
select ZMK_WPM
imply ZMK_HID_INDICATORS

choice ZMK_DISPLAY_WORK_QUEUE
default ZMK_DISPLAY_WORK_QUEUE_DEDICATED
endchoice

config LV_Z_MEM_POOL_SIZE
default 8192

config LV_Z_VDB_SIZE
default 64

config LV_DPI_DEF
default 148

config LV_Z_BITS_PER_PIXEL
default 1

choice LV_COLOR_DEPTH
default LV_COLOR_DEPTH_1
endchoice

endif
5 changes: 5 additions & 0 deletions boards/shields/dongle_display/Kconfig.shield
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT

config SHIELD_DONGLE_DISPLAY
def_bool $(shields_list_contains,dongle_display)
63 changes: 63 additions & 0 deletions boards/shields/dongle_display/custom_status_screen.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include "custom_status_screen.h"
#include "widgets/battery_status.h"
#include "widgets/modifiers.h"
#include "widgets/bongo_cat.h"
#include "widgets/layer_status.h"
#include "widgets/output_status.h"
#include "widgets/hid_indicators.h"

#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

static struct zmk_widget_output_status output_status_widget;
static struct zmk_widget_layer_status layer_status_widget;
static struct zmk_widget_peripheral_battery_status peripheral_battery_status_widget;
static struct zmk_widget_modifiers modifiers_widget;
static struct zmk_widget_bongo_cat bongo_cat_widget;

#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS)
static struct zmk_widget_hid_indicators hid_indicators_widget;
#endif

lv_style_t global_style;

lv_obj_t *zmk_display_status_screen() {
lv_obj_t *screen;

screen = lv_obj_create(NULL);

lv_style_init(&global_style);
lv_style_set_text_font(&global_style, &lv_font_unscii_8);
lv_style_set_text_letter_space(&global_style, 1);
lv_style_set_text_line_space(&global_style, 1);
lv_obj_add_style(screen, &global_style, LV_PART_MAIN);

zmk_widget_output_status_init(&output_status_widget, screen);
lv_obj_align(zmk_widget_output_status_obj(&output_status_widget), LV_ALIGN_TOP_LEFT, 0, 0);

zmk_widget_bongo_cat_init(&bongo_cat_widget, screen);
lv_obj_align(zmk_widget_bongo_cat_obj(&bongo_cat_widget), LV_ALIGN_BOTTOM_RIGHT, 0, -7);

zmk_widget_modifiers_init(&modifiers_widget, screen);
lv_obj_align(zmk_widget_modifiers_obj(&modifiers_widget), LV_ALIGN_BOTTOM_LEFT, 0, 0);

#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS)
zmk_widget_hid_indicators_init(&hid_indicators_widget, screen);
lv_obj_align_to(zmk_widget_hid_indicators_obj(&hid_indicators_widget), zmk_widget_modifiers_obj(&modifiers_widget), LV_ALIGN_OUT_TOP_LEFT, 0, -2);
#endif

zmk_widget_layer_status_init(&layer_status_widget, screen);
// lv_obj_align(zmk_widget_layer_status_obj(&layer_status_widget), LV_ALIGN_BOTTOM_LEFT, 2, -18);
lv_obj_align_to(zmk_widget_layer_status_obj(&layer_status_widget), zmk_widget_bongo_cat_obj(&bongo_cat_widget), LV_ALIGN_BOTTOM_LEFT, 0, 5);

zmk_widget_peripheral_battery_status_init(&peripheral_battery_status_widget, screen);
lv_obj_align(zmk_widget_peripheral_battery_status_obj(&peripheral_battery_status_widget), LV_ALIGN_TOP_RIGHT, 0, 0);

return screen;
}
12 changes: 12 additions & 0 deletions boards/shields/dongle_display/custom_status_screen.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
*
* Copyright (c) 2024 The ZMK Contributors
* SPDX-License-Identifier: MIT
*
*/

#pragma once

#include <lvgl.h>

lv_obj_t *zmk_display_status_screen();
4 changes: 4 additions & 0 deletions boards/shields/dongle_display/dongle_display.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CONFIG_ZMK_DISPLAY=y
CONFIG_ZMK_IDLE_TIMEOUT=60000
CONFIG_BT_BAS=n
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING=y
5 changes: 5 additions & 0 deletions boards/shields/dongle_display/dongle_display.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
7 changes: 7 additions & 0 deletions boards/shields/dongle_display/dongle_display.zmk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
file_format: "1"
id: dongle_display
name: dongle_display
type: shield
url: https://github.com/englmaxi/zmk-dongle-display
requires:
- i2c_oled
115 changes: 115 additions & 0 deletions boards/shields/dongle_display/widgets/battery_status.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <zephyr/kernel.h>
#include <zephyr/bluetooth/services/bas.h>

#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

#include <zmk/display.h>
#include <zmk/display/widgets/battery_status.h>
#include <zmk/usb.h>
#include <zmk/ble.h>
#include <zmk/events/usb_conn_state_changed.h>
#include <zmk/event_manager.h>
#include <zmk/events/battery_state_changed.h>

#include "battery_status.h"

static sys_slist_t widgets = SYS_SLIST_STATIC_INIT(&widgets);

struct peripheral_battery_state {
uint8_t source;
uint8_t level;
};

static lv_color_t battery_image_buffer[ZMK_SPLIT_BLE_PERIPHERAL_COUNT][5 * 8];

static void draw_battery(lv_obj_t *canvas, uint8_t level) {
lv_canvas_fill_bg(canvas, lv_color_black(), LV_OPA_COVER);

lv_draw_rect_dsc_t rect_fill_dsc;
lv_draw_rect_dsc_init(&rect_fill_dsc);
rect_fill_dsc.bg_color = lv_color_white();

lv_canvas_set_px(canvas, 0, 0, lv_color_white());
lv_canvas_set_px(canvas, 4, 0, lv_color_white());

if (level > 90) {
// full
} else if (level > 70) {
lv_canvas_draw_rect(canvas, 1, 2, 3, 1, &rect_fill_dsc);
} else if (level > 50) {
lv_canvas_draw_rect(canvas, 1, 2, 3, 2, &rect_fill_dsc);
} else if (level > 30) {
lv_canvas_draw_rect(canvas, 1, 2, 3, 3, &rect_fill_dsc);
} else if (level > 10) {
lv_canvas_draw_rect(canvas, 1, 2, 3, 4, &rect_fill_dsc);
} else {
lv_canvas_draw_rect(canvas, 1, 2, 3, 5, &rect_fill_dsc);
}
}

static void set_battery_symbol(lv_obj_t *widget, struct peripheral_battery_state state) {
lv_obj_t *symbol = lv_obj_get_child(widget, state.source * 2);
lv_obj_t *label = lv_obj_get_child(widget, state.source * 2 + 1);

draw_battery(symbol, state.level);
lv_label_set_text_fmt(label, "%3u%%", state.level);

if (state.level > 0) {
lv_obj_clear_flag(symbol, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(label, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(symbol, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(label, LV_OBJ_FLAG_HIDDEN);
}
}

void battery_status_update_cb(struct peripheral_battery_state state) {
struct zmk_widget_battery_status *widget;
SYS_SLIST_FOR_EACH_CONTAINER(&widgets, widget, node) { set_battery_symbol(widget->obj, state); }
}

static struct peripheral_battery_state battery_status_get_state(const zmk_event_t *eh) {
const struct zmk_peripheral_battery_state_changed *ev = as_zmk_peripheral_battery_state_changed(eh);
return (struct peripheral_battery_state){
.source = ev->source,
.level = ev->state_of_charge,
};
}

ZMK_DISPLAY_WIDGET_LISTENER(widget_battery_status, struct peripheral_battery_state,
battery_status_update_cb, battery_status_get_state)

ZMK_SUBSCRIPTION(widget_battery_status, zmk_peripheral_battery_state_changed);

int zmk_widget_peripheral_battery_status_init(struct zmk_widget_peripheral_battery_status *widget, lv_obj_t *parent) {
widget->obj = lv_obj_create(parent);

lv_obj_set_size(widget->obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);

for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
lv_obj_t *image_canvas = lv_canvas_create(widget->obj);
lv_obj_t *battery_label = lv_label_create(widget->obj);

lv_canvas_set_buffer(image_canvas, battery_image_buffer[i], 5, 8, LV_IMG_CF_TRUE_COLOR);

lv_obj_align(image_canvas, LV_ALIGN_TOP_RIGHT, 0, i * 10);
lv_obj_align(battery_label, LV_ALIGN_TOP_RIGHT, -7, i * 10);
}

sys_slist_append(&widgets, &widget->node);

widget_battery_status_init();

return 0;
}

lv_obj_t *zmk_widget_peripheral_battery_status_obj(struct zmk_widget_peripheral_battery_status *widget) {
return widget->obj;
}
18 changes: 18 additions & 0 deletions boards/shields/dongle_display/widgets/battery_status.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#pragma once

#include <lvgl.h>
#include <zephyr/kernel.h>

struct zmk_widget_peripheral_battery_status {
sys_snode_t node;
lv_obj_t *obj;
};

int zmk_widget_peripheral_battery_status_init(struct zmk_widget_peripheral_battery_status *widget, lv_obj_t *parent);
lv_obj_t *zmk_widget_peripheral_battery_status_obj(struct zmk_widget_peripheral_battery_status *widget);
Loading

0 comments on commit 4544da3

Please sign in to comment.