- 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!)
```c
FLASH (rx) : ORIGIN = 0x10050000, LENGTH = 40K // Code + constants
RAM (xrw) : ORIGIN = 0x11001000, LENGTH = 4K // Variables only!
```
**Only 4K RAM available!** Keep global variables minimal. Use `const` for bitmap data.
## Constants
```c
#define SCREEN_WIDTH 200
#define SCREEN_HEIGHT 200
#define BLACK 0
#define WHITE 0xFF
#define GREY 0x80
#define MODE_EMPTY 0 // Rectangle outline
#define MODE_FILL 1 // Filled rectangle
// File access flags
#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
```c
typedef enum UPDAT_TYPE {
FULL_UPDATE, // Full screen refresh (prevents ghosting)
PART_UPDATE, // Partial refresh (faster but may ghost)
NONE_UPDATE, // No screen update
} UpdateType;
```
### Button Types
```c
typedef enum MBUTTON_TYPE {
KEY_NULL,
KEY_CENTER, // Center button press
KEY_UP, // Up button press
KEY_DOWN, // Down button press
KEY_CENTER_UP, // Center then Up
KEY_UP_CENTER, // Up then Center
KEY_CENTER_DOWN, // Center then Down
KEY_DOWN_CENTER, // Down then Center
KEY_UP_DOWN, // Up then Down
KEY_DOWN_UP, // Down then Up
KEY_BACK, // Back/exit
} ButtonType;
```
### Rotation
```c
typedef enum ROTATE {
ROTATE_0,
ROTATE_90,
ROTATE_180,
ROTATE_270,
} Rotate;
```
### Bluetooth State
```c
typedef enum BLUESTTE {
DISCONNECTED,
CONNECTING,
CONNECTED,
} BlueState;
```
### Weather Data
```c
typedef enum WEATHERDAY {
TOTDAY, // Today
TOMORROW,
AFTERTOMOROW, // Day after tomorrow
DAY_COUNT,
} WeatherDay;
typedef struct TODAYDATA {
uint16_t moonicon; // Moon phase icon index
uint16_t pressure; // Air pressure
uint16_t humidity; // Humidity percentage
uint8_t sunraise[6]; // Sunrise time string "HH:MM"
uint8_t sunset[6]; // Sunset time string "HH:MM"
uint8_t lunar[16]; // Lunar date string
} TodayData;
typedef struct WEATHER {
uint8_t date; // Day of month
char day_temp; // Daytime temperature
char night_temp; // Nighttime temperature
uint16_t day_icon; // Daytime weather icon
uint16_t night_icon; // Nighttime weather icon
} Weather;
```
## Bitmap Format
MoWatch uses **horizontal 8-point left-high-bit encoding**:
```c
// For a 120px wide image:
int16_t byteWidth = (120 + 7) / 8; // = 15 bytes per row
// To check if pixel (x, y) is black:
if (bitmap[y * byteWidth + x / 8] & (128 >> (x & 7))) {
// pixel is black
}
```
## 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
```c
// Clear entire screen
void eink_clear(uint16_t color);
// Draw single pixel
void eink_drawpixel(uint16_t x, uint16_t y, uint16_t color);
// Draw line
void eink_drawline(uint16_t start_x, uint16_t start_y,
uint16_t end_x, uint16_t end_y, uint16_t color);
// Draw dashed line
void eink_drawdashedline(uint16_t start_x, uint16_t start_y,
uint16_t end_x, uint16_t end_y, uint16_t color);
// Draw rectangle (MODE_EMPTY=outline, MODE_FILL=filled)
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);
// Draw circle
void eink_drawcircle(int x_center, int y_center, int radius,
int color, int fill_mode);
// Draw bitmap - THE MOST IMPORTANT FUNCTION!
// x, y: top-left position
// w, h: width and height in pixels
// data[]: bitmap array (horizontal 8-point left-high-bit)
// color: drawing color (BLACK or WHITE)
// transparent: 0=draw both colors, 1=only draw foreground
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);
// Draw ASCII string
// Returns: width of drawn string
uint16_t eink_drawstr(uint16_t x, uint16_t y, const unsigned char *chr,
uint16_t size, uint16_t color);
// Draw Chinese string (UTF-8)
uint16_t eink_drawchstr(uint16_t x, uint16_t y, const unsigned char *chr,
uint16_t color);
// Draw string within rectangle bounds
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);
// Set screen rotation
void eink_set_rotate(Rotate rotate);
// Get weather icon bitmap (32x32) into buffer
void get_weather_icon(uint16_t iconidx, uint8_t *buffer);
```
### Time Functions
```c
int RTC_getYear(void); // Full year (e.g., 2024)
int RTC_getMon(void); // Month 1-12
int RTC_getDay(void); // Day 1-31
int RTC_getHour(void); // Hour 0-23
int RTC_getMin(void); // Minute 0-59
int RTC_getSec(void); // Second 0-59
int RTC_getWeek(void); // Day of week (0=Sunday)
int RTC_getTimeStamp(void); // Unix timestamp
```
### System Functions
```c
// Get battery percentage (0-100)
uint8_t watch_app_battpercent(void);
// Exit app, return to main menu
void watch_app_exit(void);
// Set update interval for onUpdate() callback (in milliseconds)
void set_update_interval(uint32_t interval);
// Get current update interval
uint32_t get_update_interval(void);
// Log message (for debugging)
void watch_app_log(char* log);
// Get Bluetooth connection state
BlueState watch_app_bluestate(void);
// Check if weather data is available
uint8_t watch_app_isweather_ok(void);
// Get today's data (sunrise, sunset, lunar, etc.)
TodayData watch_app_getToday(void);
// Get weather for specified day
Weather watch_app_getweather(WeatherDay day);
// HTTP request (async, result via callback)
void watch_app_http_req(const char* url, void(req_callback)(char*));
```
### File Operations
```c
// Read file
// Returns: bytes read
uint32_t watch_app_read_file(const uint8_t* file_name, uint8_t* buffer,
uint32_t len, uint32_t seekofs);
// Write file
// fa_mode: FA_CREATE_ALWAYS, FA_OPEN_APPEND, etc.
uint32_t watch_app_write_file(const uint8_t* file_name, uint8_t* buffer,
uint32_t len, uint32_t seekofs, uint8_t fa_mode);
// Delete file
uint8_t watch_app_delete_file(const uint8_t* file_name);
// Create directory
uint8_t watch_app_mkdir(const uint8_t* dir);
```
### Memory Functions
```c
// Allocate memory (use sparingly - only 4K RAM!)
void *m_malloc(uint32_t size);
// Free memory
void m_free(void *ptr);
```
### Utility Functions
```c
// Format string (like sprintf)
void co_sprintf(char *out, const char *format, ...);
// Get UTF-8 string length
uint16_t utf_len(const unsigned char *chr);
// Random number
int rand(void);
void srand(unsigned int seed);
```
### Dialog Functions
```c
// Show message dialog with OK/Cancel
// submit callback receives: 1=OK, 0=Cancel
void create_msg_dialog(const uint8_t* title, const uint8_t* msg,
void (*submit)(uint8_t ok));
// Show menu dialog
void create_menu_dialog(const char* title, const char** menu_item_names,
const uint8_t count, void (*submit)(uint8_t confirm));
// Show number picker dialog
void create_picker_dialog(uint8_t num, const uint8_t* nums,
const uint8_t count, void (*submit)(uint8_t data));
```
## App Entry Points
```c
#include "header.h"
// Called when screen needs to be drawn
void onDraw(void) {
eink_clear(WHITE);
// Draw your UI here
}
// Called when button is pressed
// Return update type to trigger screen refresh
UpdateType onKey(ButtonType key) {
switch(key) {
case KEY_BACK:
watch_app_exit();
break;
case KEY_UP:
// Handle up button
break;
case KEY_DOWN:
// Handle down button
break;
}
return PART_UPDATE; // or NONE_UPDATE, FULL_UPDATE
}
// Called periodically based on set_update_interval()
UpdateType onUpdate(int delta) {
static uint8_t lastmin = 61;
if (lastmin != RTC_getMin()) {
lastmin = RTC_getMin();
return PART_UPDATE; // Refresh screen when minute changes
}
return NONE_UPDATE;
}
// App initialization - REQUIRED
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;
// Initialize your variables here
set_update_interval(1000); // Check every 1 second
}
```
## Complete Minimal Example
```c
// app_main.c - Simple digital clock using eink_drawstr
#include "header.h"
void onDraw(void) {
eink_clear(WHITE);
char buf[32];
// Draw time (large, centered)
co_sprintf(buf, "%02d:%02d", RTC_getHour(), RTC_getMin());
eink_drawstr(50, 80, (unsigned char*)buf, 32, BLACK);
// Draw date
co_sprintf(buf, "%04d-%02d-%02d", RTC_getYear(), RTC_getMon(), RTC_getDay());
eink_drawstr(40, 130, (unsigned char*)buf, 16, BLACK);
// Draw battery
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
Auf GitHub ansehen