Skip to content

Commit 94a7a6d

Browse files
committed
Add precipitation probability display and update documentation
Features: - Add precipitation probability in top-left header (2-digit percentage) - Move pollen display to top-right header for symmetry - Both use bold font with inverted colors for header visibility - Auto-update colors when switching themes Technical Changes: - Add PRECIPITATION_PROBABILITY message key to package.json - Fetch current hour's precip probability from Open-Meteo hourly data - Display with leading zero format (%02d): 01, 10, 89, etc. - Add persistence support (PERSIST_KEY 25) - Update apply_color_theme() to handle pollen and precip layers Documentation: - Update README.md with new header layout diagram - Add precipitation probability to features list - Update claude.md with detailed technical documentation - Add new screenshots showing updated layout (normal + inverted themes) - Update annotated screenshot with precipitation and pollen labels UI Layout: - Header now: [Rain%] [City Name] [Pollen] - Clean 2-digit percentage shows chance of rain - Pollen moved from time box to header for better balance
1 parent d548094 commit 94a7a6d

8 files changed

Lines changed: 106 additions & 15 deletions

File tree

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@ A high-density, data-rich watchface for the Pebble 2 Duo (B&W, 144x168), featuri
3434
- Extrapolates additional arrivals from bus interval when API returns fewer predictions
3535
- Configurable route, stop, and direction
3636
- Syncs with API every 30 minutes alongside weather data
37+
- **Precipitation Probability**: Current hour's chance of rain displayed as 2-digit percentage (e.g., "01" = 1%, "89" = 89%)
38+
- Shown in top-left corner of header
39+
- Updates every 30 minutes from Open-Meteo hourly forecast
3740
- **Weather Icons**: Current conditions + tomorrow's forecast icons
3841
- **Temperature Data**: Current, low, and high with visual indicators
3942
- **Wind & Environmental**: Wind speed, UV Index, Air Quality Index (AQI)
4043
- **Pollen Tracking**: Tree, grass, and weed pollen levels (0-5 scale) with type indicator
44+
- Displayed in top-right corner of header (e.g., "T4" = Tree pollen level 4)
4145
- **Tide Information**: Next high/low tide with wave icon from NOAA
4246
- **Sunrise/Sunset Times**: Daily solar data with arrow indicators
4347
- **Configurable**: Show/hide individual data fields via settings
@@ -182,10 +186,10 @@ Optimized for Pebble hardware:
182186

183187
```
184188
┌─────────────────────────┐
185-
San Francisco │ ← Header: City Name (centered)
189+
01 San Francisco P0 │ ← Header: Rain% | City Name | Pollen
186190
├─────────────────────────┤
187191
│ ☀ Wed, Nov 6 ☁ │ ← Today/Date/Tomorrow Weather Icons
188-
P0 19:09 │ ← Pollen (bottom-left) | Large Time Display
192+
19:09 │ ← Large Time Display
189193
│ High UV 11AM-3PM │ ← Weather/Health Alert (dynamic, only when active)
190194
├─────────────────────────┤
191195
│ 3,12 │ 58° │ 40|68 │ ← 3x3 Grid Row 1: MUNI | Current Temp | Lo|Hi
@@ -195,6 +199,11 @@ Optimized for Pebble hardware:
195199
└─────────────────────────┘
196200
```
197201

202+
**Header (Top Bar):**
203+
- **Left**: Precipitation probability (2-digit percentage, e.g., "01" = 1%, "89" = 89%)
204+
- **Center**: City name from GPS
205+
- **Right**: Pollen level (type + index, e.g., "T4" = Tree pollen level 4, "P0" = no pollen)
206+
198207
**Grid Layout (3x3, 48px × 24px cells):**
199208
- **Row 1 (Temp)**: MUNI countdown | Current temp | Low|High temps
200209
- **Row 2 (Data)**: Wind speed | UV index | Air quality
@@ -262,6 +271,7 @@ Communication between C and JavaScript:
262271
- `TEMPERATURE`, `TEMP_MAX`, `TEMP_MIN`
263272
- `WIND_SPEED`, `WIND_MAX`
264273
- `UV_INDEX`, `AQI`
274+
- `PRECIPITATION_PROBABILITY`
265275
- `WEATHER_CODE`, `WEATHER_CODE_TOMORROW`
266276
- `SUNRISE`, `SUNSET`
267277

claude.md

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,10 @@ fitzface/
115115

116116
```
117117
┌────────────────────────────┐ y=0
118-
San Francisco │ Header: City Name (centered)
118+
01 San Francisco P0 │ Header: Rain% (left) | City Name (center) | Pollen (right)
119119
├────────────────────────────┤ y=20 (DIVIDER)
120120
│ ☀ Wed, Nov 06 ☁ │ Today/Date/Tomorrow icons
121-
P0 19:09 │ Pollen (bottom-left) | Large Time (centered)
121+
19:09 Large Time (centered)
122122
│ High UV 11AM-3PM │ Weather/Health Alert (y=80, dynamic, only when active)
123123
├────────────────────────────┤ y=96 (DIVIDER - 3x3 GRID STARTS)
124124
│ 3,12 │ 58° │ 40|68 │ Row 1: MUNI buses | Current temp | Lo|Hi
@@ -130,6 +130,11 @@ fitzface/
130130
└────────────────────────────┘ y=168
131131
```
132132

133+
**Header Bar (y=0-20):**
134+
- **Left (x=4)**: Precipitation probability - 2-digit percentage (e.g., "01" = 1%, "89" = 89%)
135+
- **Center**: City name from GPS reverse geocoding
136+
- **Right (x=110)**: Pollen level - type + index (e.g., "T4" = Tree pollen level 4, "P0" = no pollen)
137+
133138
**3x3 Grid Layout (48px × 24px cells):**
134139
- Top-left cell: MUNI bus countdown (e.g., "3,12" for 3 and 12 minutes)
135140
- All cells aligned and centered for balance
@@ -381,7 +386,41 @@ if (code >= 66 && code <= 67) { /* Freezing rain */ }
381386
- **Empty response**: Bus may not be running (late night, holidays, route changes)
382387
- **Stale JavaScript**: Phone caches JS separately from .pbw - do full uninstall/reinstall if changes don't appear
383388

384-
### 9. **Pollen Tracking System**
389+
### 9. **Precipitation Probability Display**
390+
391+
**Data Source**: Open-Meteo hourly forecast
392+
- Automatically fetched with weather data every 30 minutes
393+
- Uses current hour's precipitation probability (0-100%)
394+
- No additional API calls required
395+
396+
**Display** (`src/c/fitzface.c:376-383`):
397+
- Top-left corner of header (position: 4, 3)
398+
- Shows 2-digit percentage with leading zero (e.g., "01" = 1%, "89" = 89%, "100" = 100%)
399+
- Format: `%02d` ensures consistent width
400+
- Uses bold font (GOTHIC_14_BOLD) with inverted colors for header visibility
401+
402+
**Data Processing** (`src/pkjs/index.js:849-852`):
403+
- Extracts `precipitation_probability[0]` from hourly forecast array
404+
- Sends as integer (0-100) via `PRECIPITATION_PROBABILITY` message key
405+
- Falls back to 0 if data unavailable
406+
407+
**Persistence** (`src/c/fitzface.c:71, 560-561, 618`):
408+
- Stored in `PERSIST_KEY_PRECIPITATION_PROBABILITY` (key 25)
409+
- Survives watchface reloads
410+
- Updates every 30 minutes with weather sync
411+
412+
**Color Theme Support** (`src/c/fitzface.c:846`):
413+
- Automatically updates text color when switching themes
414+
- White text on black header (normal mode)
415+
- Black text on white header (inverted mode)
416+
417+
**Update Frequency**:
418+
- Syncs every 30 minutes with weather, AQI, tide, MUNI, and pollen requests
419+
- Always shows current hour's probability for immediate relevance
420+
421+
---
422+
423+
### 10. **Pollen Tracking System**
385424

386425
**Configuration** (`src/pkjs/config.js:180-210`):
387426
- Enable/disable toggle for pollen display
@@ -402,11 +441,12 @@ if (code >= 66 && code <= 67) { /* Freezing rain */ }
402441
- 4: High
403442
- 5: Very High
404443

405-
**Display** (`src/c/fitzface.c:344-370`):
406-
- Bottom-left of time box (position: 11, 64)
444+
**Display** (`src/c/fitzface.c:385-409`):
445+
- Top-right corner of header (position: 110, 3)
407446
- Shows highest pollen type with level (e.g., "T4", "G3", "W2")
408447
- Type indicators: T=Tree, G=Grass, W=Weed, P=Pollen (when all types are 0)
409448
- Always displays pollen field when pollen data is available (shows "P0" when all are 0)
449+
- Uses bold font (GOTHIC_14_BOLD) with inverted colors for header visibility
410450

411451
**Alert Integration** (`src/pkjs/index.js:727-751`):
412452
- Triggers alert when any pollen type ≥4

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"SUNSET",
4141
"WIND_MAX",
4242
"AQI",
43+
"PRECIPITATION_PROBABILITY",
4344
"TIDE_NEXT_TIME",
4445
"TIDE_NEXT_TYPE",
4546
"TIDE_NEXT_HEIGHT",

screenshots/annotated.png

5.87 KB
Loading

screenshots/inverted.png

31 Bytes
Loading

screenshots/normal.png

21 Bytes
Loading

src/c/fitzface.c

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#define KEY_SUNSET MESSAGE_KEY_SUNSET
1313
#define KEY_WIND_MAX MESSAGE_KEY_WIND_MAX
1414
#define KEY_AQI MESSAGE_KEY_AQI
15+
#define KEY_PRECIPITATION_PROBABILITY MESSAGE_KEY_PRECIPITATION_PROBABILITY
1516
#define KEY_TIDE_NEXT_TIME MESSAGE_KEY_TIDE_NEXT_TIME
1617
#define KEY_TIDE_NEXT_TYPE MESSAGE_KEY_TIDE_NEXT_TYPE
1718
#define KEY_TIDE_NEXT_HEIGHT MESSAGE_KEY_TIDE_NEXT_HEIGHT
@@ -68,6 +69,7 @@
6869
#define PERSIST_KEY_POLLEN_TREE 18
6970
#define PERSIST_KEY_POLLEN_GRASS 19
7071
#define PERSIST_KEY_POLLEN_WEED 20
72+
#define PERSIST_KEY_PRECIPITATION_PROBABILITY 25
7173

7274
// Configuration persistence
7375
#define PERSIST_KEY_CONFIG_TEMP_UNIT 50
@@ -112,9 +114,12 @@ static GBitmap *s_temp_arrow_high_bitmap;
112114
// MUNI bus tracking (top-left grid cell)
113115
static TextLayer *s_muni_layer;
114116

115-
// Pollen display (bottom-left of time box)
117+
// Pollen display (top right of header)
116118
static TextLayer *s_pollen_layer;
117119

120+
// Precipitation probability (top left of header)
121+
static TextLayer *s_precip_layer;
122+
118123
// Footer
119124
static TextLayer *s_tide_layer;
120125
static TextLayer *s_sunrise_layer;
@@ -136,6 +141,7 @@ typedef struct {
136141
int temp_max;
137142
int temp_min;
138143
int aqi;
144+
int precipitation_probability; // 0-100%
139145
int tide_time;
140146
int tide_type; // 0 = low, 1 = high
141147
int sunrise;
@@ -367,7 +373,16 @@ static void update_weather_display() {
367373
// MUNI bus countdown updated separately (recalculated every minute)
368374
update_muni_display();
369375

370-
// Pollen display (bottom-left of time box) - show worst type + level
376+
// Precipitation probability display (top left corner) - show as 2-digit percentage
377+
static char precip_buffer[8];
378+
if (s_weather_data.precipitation_probability >= 0) {
379+
snprintf(precip_buffer, sizeof(precip_buffer), "%02d", s_weather_data.precipitation_probability);
380+
text_layer_set_text(s_precip_layer, precip_buffer);
381+
} else {
382+
text_layer_set_text(s_precip_layer, "");
383+
}
384+
385+
// Pollen display (top right corner) - show worst type + level
371386
static char pollen_buffer[8];
372387
if (s_weather_data.pollen_tree >= 0 || s_weather_data.pollen_grass >= 0 || s_weather_data.pollen_weed >= 0) {
373388
// Find highest pollen count
@@ -543,6 +558,8 @@ static void load_persisted_data() {
543558
persist_read_int(PERSIST_KEY_POLLEN_GRASS) : -1;
544559
s_weather_data.pollen_weed = persist_exists(PERSIST_KEY_POLLEN_WEED) ?
545560
persist_read_int(PERSIST_KEY_POLLEN_WEED) : -1;
561+
s_weather_data.precipitation_probability = persist_exists(PERSIST_KEY_PRECIPITATION_PROBABILITY) ?
562+
persist_read_int(PERSIST_KEY_PRECIPITATION_PROBABILITY) : 0;
546563
} else {
547564
// Default values
548565
s_weather_data.temperature = 0;
@@ -599,6 +616,7 @@ static void save_weather_data() {
599616
persist_write_int(PERSIST_KEY_POLLEN_TREE, s_weather_data.pollen_tree);
600617
persist_write_int(PERSIST_KEY_POLLEN_GRASS, s_weather_data.pollen_grass);
601618
persist_write_int(PERSIST_KEY_POLLEN_WEED, s_weather_data.pollen_weed);
619+
persist_write_int(PERSIST_KEY_PRECIPITATION_PROBABILITY, s_weather_data.precipitation_probability);
602620
}
603621

604622
// Load configuration
@@ -639,6 +657,7 @@ static void inbox_received_callback(DictionaryIterator *iterator, void *context)
639657
Tuple *weather_code_tuple = dict_find(iterator, KEY_WEATHER_CODE);
640658
Tuple *weather_code_tomorrow_tuple = dict_find(iterator, KEY_WEATHER_CODE_TOMORROW);
641659
Tuple *aqi_tuple = dict_find(iterator, KEY_AQI);
660+
Tuple *precip_prob_tuple = dict_find(iterator, KEY_PRECIPITATION_PROBABILITY);
642661
Tuple *temp_max_tuple = dict_find(iterator, KEY_TEMP_MAX);
643662
Tuple *temp_min_tuple = dict_find(iterator, KEY_TEMP_MIN);
644663
Tuple *tide_time_tuple = dict_find(iterator, KEY_TIDE_NEXT_TIME);
@@ -674,6 +693,7 @@ static void inbox_received_callback(DictionaryIterator *iterator, void *context)
674693
if (weather_code_tuple) s_weather_data.weather_code = (int)weather_code_tuple->value->int32;
675694
if (weather_code_tomorrow_tuple) s_weather_data.weather_code_tomorrow = (int)weather_code_tomorrow_tuple->value->int32;
676695
if (aqi_tuple) s_weather_data.aqi = (int)aqi_tuple->value->int32;
696+
if (precip_prob_tuple) s_weather_data.precipitation_probability = (int)precip_prob_tuple->value->int32;
677697
if (temp_max_tuple) s_weather_data.temp_max = (int)temp_max_tuple->value->int32;
678698
if (temp_min_tuple) s_weather_data.temp_min = (int)temp_min_tuple->value->int32;
679699
if (tide_time_tuple) s_weather_data.tide_time = (int)tide_time_tuple->value->int32;
@@ -822,7 +842,8 @@ static void apply_color_theme() {
822842
text_layer_set_text_color(s_uv_layer, get_foreground_color());
823843
text_layer_set_text_color(s_aqi_layer, get_foreground_color());
824844
text_layer_set_text_color(s_tide_layer, get_foreground_color());
825-
text_layer_set_text_color(s_pollen_layer, get_foreground_color());
845+
text_layer_set_text_color(s_pollen_layer, get_background_color()); // Inverted for header
846+
text_layer_set_text_color(s_precip_layer, get_background_color()); // Inverted for header
826847
text_layer_set_text_color(s_sunrise_layer, get_foreground_color());
827848
text_layer_set_text_color(s_sunset_layer, get_foreground_color());
828849

@@ -899,6 +920,17 @@ static void main_window_load(Window *window) {
899920
);
900921
layer_add_child(window_layer, text_layer_get_layer(s_location_layer));
901922

923+
// Precipitation probability (top left corner of header) - inverted colors for black header
924+
s_precip_layer = create_text_layer_colored(
925+
GRect(4, 3, 30, 14),
926+
GTextAlignmentLeft,
927+
fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD),
928+
get_background_color(), // Inverted: white text on black header, or black on white header
929+
GColorClear
930+
);
931+
layer_add_child(window_layer, text_layer_get_layer(s_precip_layer));
932+
text_layer_set_text(s_precip_layer, ""); // Empty by default
933+
902934
// === TIME SECTION (26-80) ===
903935
// Date (centered) - smaller, above time (add this FIRST so icons appear on top)
904936
s_date_layer = create_text_layer(
@@ -928,11 +960,13 @@ static void main_window_load(Window *window) {
928960
);
929961
layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
930962

931-
// Pollen display (bottom-left of time box)
932-
s_pollen_layer = create_text_layer(
933-
GRect(11, 64, 30, 14),
934-
GTextAlignmentLeft,
935-
fonts_get_system_font(FONT_KEY_GOTHIC_14)
963+
// Pollen display (top right corner, next to city name) - inverted colors for black header
964+
s_pollen_layer = create_text_layer_colored(
965+
GRect(110, 3, 30, 14),
966+
GTextAlignmentRight,
967+
fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD),
968+
get_background_color(), // Inverted: white text on black header, or black on white header
969+
GColorClear
936970
);
937971
layer_add_child(window_layer, text_layer_get_layer(s_pollen_layer));
938972
text_layer_set_text(s_pollen_layer, ""); // Empty by default
@@ -1127,6 +1161,7 @@ static void main_window_unload(Window *window) {
11271161
text_layer_destroy(s_aqi_layer);
11281162
text_layer_destroy(s_tide_layer);
11291163
text_layer_destroy(s_pollen_layer);
1164+
text_layer_destroy(s_precip_layer);
11301165
text_layer_destroy(s_sunrise_layer);
11311166
text_layer_destroy(s_sunset_layer);
11321167
}

src/pkjs/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,11 @@ function sendDataToWatch(location, weatherData, aqiData, tideData, muniData, pol
846846
message.WEATHER_CODE = weatherData.current.weather_code || 0;
847847
}
848848

849+
// Current precipitation probability (from hourly data - use current or next hour)
850+
if (weatherData && weatherData.hourly && weatherData.hourly.precipitation_probability) {
851+
message.PRECIPITATION_PROBABILITY = weatherData.hourly.precipitation_probability[0] || 0;
852+
}
853+
849854
if (weatherData && weatherData.daily) {
850855
message.TEMP_MAX = Math.round(weatherData.daily.temperature_2m_max[0]);
851856
message.TEMP_MIN = Math.round(weatherData.daily.temperature_2m_min[0]);

0 commit comments

Comments
 (0)