| name | mowatch-dev |
| description | Development guide for MoWatch e-ink smartwatch apps. Use when developing watch faces, apps, or processing images for MoWatch. Includes complete API reference, bitmap format, Docker build process, and image processing for 1-bit e-ink display. |
| license | MIT |
MoWatch App Development Skill
Use this skill when developing watch faces or apps for MoWatch e-ink smartwatch.
SDK Files Location
IMPORTANT: SDK source files are located in this skill's directory. Copy them to create a new project:
~/.claude/skills/mowatch-dev/
├── sdk/
│ ├── include/header.h # SDK header - copy to include/
│ └── libs/
│ ├── init_datas.c # API wrappers - copy to libs/
│ └── syscall_gcc.txt # Syscall addresses - copy to libs/
└── templates/
├── Makefile # Build config - copy to gcc/
├── app.ld # Linker script - copy to gcc/
└── createmwa.py # Package creator - copy to gcc/
To create a new project, read and copy these files using the Read tool.
Hardware Specifications
- Display: 200x200 pixel monochrome e-ink
- MCU: ARM Cortex-M3
- Colors: Black and white only (1-bit)
- Update: Partial refresh supported, full refresh every 5 minutes recommended
Memory Constraints (IMPORTANT!)
FLASH (rx) : ORIGIN = 0x10050000, LENGTH = 40K
RAM (xrw) : ORIGIN = 0x11001000, LENGTH = 4K
Only 4K RAM available! Keep global variables minimal. Use const for bitmap data.
Constants
#define SCREEN_WIDTH 200
#define SCREEN_HEIGHT 200
#define BLACK 0
#define WHITE 0xFF
#define GREY 0x80
#define MODE_EMPTY 0
#define MODE_FILL 1
#define FA_READ 0x01
#define FA_WRITE 0x02
#define FA_OPEN_EXISTING 0x00
#define FA_CREATE_NEW 0x04
#define FA_CREATE_ALWAYS 0x08
#define FA_OPEN_ALWAYS 0x10
#define FA_OPEN_APPEND 0x30
Enums and Data Structures
Update Types
typedef enum UPDAT_TYPE {
FULL_UPDATE,
PART_UPDATE,
NONE_UPDATE,
} UpdateType;
Button Types
typedef enum MBUTTON_TYPE {
KEY_NULL,
KEY_CENTER,
KEY_UP,
KEY_DOWN,
KEY_CENTER_UP,
KEY_UP_CENTER,
KEY_CENTER_DOWN,
KEY_DOWN_CENTER,
KEY_UP_DOWN,
KEY_DOWN_UP,
KEY_BACK,
} ButtonType;
Rotation
typedef enum ROTATE {
ROTATE_0,
ROTATE_90,
ROTATE_180,
ROTATE_270,
} Rotate;
Bluetooth State
typedef enum BLUESTTE {
DISCONNECTED,
CONNECTING,
CONNECTED,
} BlueState;
Weather Data
typedef enum WEATHERDAY {
TOTDAY,
TOMORROW,
AFTERTOMOROW,
DAY_COUNT,
} WeatherDay;
typedef struct TODAYDATA {
uint16_t moonicon;
uint16_t pressure;
uint16_t humidity;
uint8_t sunraise[6];
uint8_t sunset[6];
uint8_t lunar[16];
} TodayData;
typedef struct WEATHER {
uint8_t date;
char day_temp;
char night_temp;
uint16_t day_icon;
uint16_t night_icon;
} Weather;
Bitmap Format
MoWatch uses horizontal 8-point left-high-bit encoding:
int16_t byteWidth = (120 + 7) / 8;
if (bitmap[y * byteWidth + x / 8] & (128 >> (x & 7))) {
}
Project Structure
app_projects/
├── include/
│ └── header.h # SDK header (from skill sdk/)
├── libs/
│ ├── init_datas.c # SDK init (from skill sdk/)
│ ├── tiny-json.c # JSON library (optional)
│ └── syscall_gcc.txt # Syscall definitions (from skill sdk/)
└── app_yourapp/
├── code/
│ ├── app_main.c # Your main code
│ └── graphics.h # Your bitmap data (optional)
└── gcc/
├── Makefile # From skill templates/
├── app.ld # From skill templates/
└── createmwa.py # From skill templates/
Complete API Reference
Drawing Functions
void eink_clear(uint16_t color);
void eink_drawpixel(uint16_t x, uint16_t y, uint16_t color);
void eink_drawline(uint16_t start_x, uint16_t start_y,
uint16_t end_x, uint16_t end_y, uint16_t color);
void eink_drawdashedline(uint16_t start_x, uint16_t start_y,
uint16_t end_x, uint16_t end_y, uint16_t color);
void eink_drawrect(uint16_t start_x, uint16_t start_y,
uint16_t end_x, uint16_t end_y,
uint16_t color, uint8_t fill_mode);
void eink_drawcircle(int x_center, int y_center, int radius,
int color, int fill_mode);
void eink_draw_bmp(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
const uint8_t data[], uint16_t color, uint8_t transparent);
uint16_t eink_drawstr(uint16_t x, uint16_t y, const unsigned char *chr,
uint16_t size, uint16_t color);
uint16_t eink_drawchstr(uint16_t x, uint16_t y, const unsigned char *chr,
uint16_t color);
uint16_t eink_draw_rectstr(const unsigned char *chr,
uint16_t start_x, uint16_t start_y,
uint16_t end_x, uint16_t end_y, uint16_t color);
void eink_set_rotate(Rotate rotate);
void get_weather_icon(uint16_t iconidx, uint8_t *buffer);
Time Functions
int RTC_getYear(void);
int RTC_getMon(void);
int RTC_getDay(void);
int RTC_getHour(void);
int RTC_getMin(void);
int RTC_getSec(void);
int RTC_getWeek(void);
int RTC_getTimeStamp(void);
System Functions
uint8_t watch_app_battpercent(void);
void watch_app_exit(void);
void set_update_interval(uint32_t interval);
uint32_t get_update_interval(void);
void watch_app_log(char* log);
BlueState watch_app_bluestate(void);
uint8_t watch_app_isweather_ok(void);
TodayData watch_app_getToday(void);
Weather watch_app_getweather(WeatherDay day);
void watch_app_http_req(const char* url, void(req_callback)(char*));
File Operations
uint32_t watch_app_read_file(const uint8_t* file_name, uint8_t* buffer,
uint32_t len, uint32_t seekofs);
uint32_t watch_app_write_file(const uint8_t* file_name, uint8_t* buffer,
uint32_t len, uint32_t seekofs, uint8_t fa_mode);
uint8_t watch_app_delete_file(const uint8_t* file_name);
uint8_t watch_app_mkdir(const uint8_t* dir);
Memory Functions
void *m_malloc(uint32_t size);
void m_free(void *ptr);
Utility Functions
void co_sprintf(char *out, const char *format, ...);
uint16_t utf_len(const unsigned char *chr);
int rand(void);
void srand(unsigned int seed);
Dialog Functions
void create_msg_dialog(const uint8_t* title, const uint8_t* msg,
void (*submit)(uint8_t ok));
void create_menu_dialog(const char* title, const char** menu_item_names,
const uint8_t count, void (*submit)(uint8_t confirm));
void create_picker_dialog(uint8_t num, const uint8_t* nums,
const uint8_t count, void (*submit)(uint8_t data));
App Entry Points
#include "header.h"
void onDraw(void) {
eink_clear(WHITE);
}
UpdateType onKey(ButtonType key) {
switch(key) {
case KEY_BACK:
watch_app_exit();
break;
case KEY_UP:
break;
case KEY_DOWN:
break;
}
return PART_UPDATE;
}
UpdateType onUpdate(int delta) {
static uint8_t lastmin = 61;
if (lastmin != RTC_getMin()) {
lastmin = RTC_getMin();
return PART_UPDATE;
}
return NONE_UPDATE;
}
void app_init(intptr_t *draw_ptr_t, intptr_t *onkey_ptr_t,
intptr_t *update_ptr_t, intptr_t* func_arr) {
__initialize_datas(func_arr);
*draw_ptr_t = (intptr_t)onDraw;
*onkey_ptr_t = (intptr_t)onKey;
*update_ptr_t = (intptr_t)onUpdate;
set_update_interval(1000);
}
Complete Minimal Example
#include "header.h"
void onDraw(void) {
eink_clear(WHITE);
char buf[32];
co_sprintf(buf, "%02d:%02d", RTC_getHour(), RTC_getMin());
eink_drawstr(50, 80, (unsigned char*)buf, 32, BLACK);
co_sprintf(buf, "%04d-%02d-%02d", RTC_getYear(), RTC_getMon(), RTC_getDay());
eink_drawstr(40, 130, (unsigned char*)buf, 16, BLACK);
co_sprintf(buf, "BAT:%d%%", watch_app_battpercent());
eink_drawstr(140, 5, (unsigned char*)buf, 12, BLACK);
}
UpdateType onKey(ButtonType key) {
if (key == KEY_BACK || key == KEY_CENTER) {
watch_app_exit();
}
return NONE_UPDATE;
}
UpdateType onUpdate(int delta) {
static uint8_t lastmin = 61;
if (lastmin != RTC_getMin()) {
lastmin = RTC_getMin();
if (RTC_getMin() % 5 == 0) {
return FULL_UPDATE;
}
return PART_UPDATE;
}
return NONE_UPDATE;
}
void app_init(intptr_t *draw_ptr_t, intptr_t *onkey_ptr_t,
intptr_t *update_ptr_t, intptr_t* func_arr) {
__initialize_datas(func_arr);
*draw_ptr_t = (intptr_t)onDraw;
*onkey_ptr_t = (intptr_t)onKey;
*update_ptr_t = (intptr_t)onUpdate;
set_update_interval(1000);
}
Building with Docker
- Create Dockerfile (in SDK root, same level as app_projects):
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
gcc-arm-none-eabi \
make \
python3 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
- Build Docker image:
docker build -t mowatch-sdk .
- Compile:
docker run --rm -v "/path/to/app_projects:/build" mowatch-sdk make -C /build/app_yourapp/gcc
- Create .mwa package:
cd gcc && python3 createmwa.py
Quick Start: Create New Project
-
Read SDK files from skill directory:
~/.claude/skills/mowatch-dev/sdk/include/header.h
~/.claude/skills/mowatch-dev/sdk/libs/init_datas.c
~/.claude/skills/mowatch-dev/sdk/libs/syscall_gcc.txt
-
Read template files:
~/.claude/skills/mowatch-dev/templates/Makefile
~/.claude/skills/mowatch-dev/templates/app.ld
~/.claude/skills/mowatch-dev/templates/createmwa.py
-
Create project structure and copy files:
mkdir -p app_projects/include app_projects/libs
mkdir -p app_projects/app_mywatch/code app_projects/app_mywatch/gcc
-
Edit createmwa.py to set outputmwa name
-
Build:
docker run --rm -v "$(pwd)/app_projects:/build" mowatch-sdk make -C /build/app_mywatch/gcc
cd app_projects/app_mywatch/gcc && python3 createmwa.py
Image Processing for E-ink (Python)
Converting images to 1-bit bitmap:
from PIL import Image, ImageFilter
def process_for_eink(img_path, output_size=(120, 120)):
img = Image.open(img_path).convert('RGB')
img = img.resize(output_size, Image.Resampling.LANCZOS)
gray = img.convert('L')
edges = gray.filter(ImageFilter.FIND_EDGES)
threshold = 128
result = edges.point(lambda p: 0 if p > threshold else 255)
return result
def image_to_c_array(img, var_name):
"""Convert PIL image to C header array"""
width, height = img.size
pixels = list(img.getdata())
byte_width = (width + 7) // 8
data = []
for y in range(height):
for bx in range(byte_width):
byte = 0
for bit in range(8):
x = bx * 8 + bit
if x < width:
idx = y * width + x
if pixels[idx] < 128:
byte |= (128 >> bit)
data.append(byte)
lines = [f"const unsigned char {var_name}[{len(data)}] = {{"]
for i in range(0, len(data), 15):
chunk = data[i:i+15]
lines.append(" " + ", ".join(f"0x{b:02X}" for b in chunk) + ",")
lines.append("};")
return "\n".join(lines)
Installation to Watch
- Enable USB mode on MoWatch
- Mount appears as "NO NAME" volume
- Copy
.mwa file to /apps/ folder
- Safely eject:
diskutil eject "/Volumes/NO NAME"
- Select watch face from watch menu
Tips
- Memory: Only 4K RAM! Use
const for all bitmap data
- Refresh: Use PART_UPDATE when possible, FULL_UPDATE every 5 minutes
- Graphics: Keep simple - e-ink has slow refresh
- Digits: 20x32px works well for time display
- Layout: Leave margins from screen edges for HUD-style designs
- Testing: Use
watch_app_log() for debugging
- Battery: Check
watch_app_battpercent() to show battery status