Loading...
Searching...
No Matches
weather_store.c File Reference

The active weather store: holds the readings, owns the appmessage weather channels, and asks the phone for more whenever its turn on the face's cadence finds a poll due. More...

#include "io/stores/weather_store.h"
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <limits.h>
#include "io/appmessage/appmessage.h"
#include "io/stores/store_cadence.h"
#include "io/stores/store_persist.h"
#include "io/stores/store_fetch.h"
#include "text/cstring_fit.h"
#include "weather/forecast_age.h"
#include "weather/weather_reading.h"

Macros

#define WEATHER_FIRST_POLL_MS   500
 Delay before the first fetch after launch, in ms.
Cold boot re-asks

The phone JS can take several seconds to come up on a cold launch, so a first request that lands before it is ready would leave the face blank until the next full poll (30 min). Until the first reading arrives the store keeps asking on this short cadence, for a bounded number of tries, then settles.

#define WEATHER_BOOT_RETRY_MS   3000
 Gap between the short re-asks, in ms.
#define WEATHER_BOOT_RETRIES   8
 How many short re-asks a cold launch gets before settling.

Functions

static void reset_state (void)
 Clear the reading back to no-data for every field.
static void mark_dirty (void)
 Mark the reading as needing a save.
static void persist_flush (void)
 The coalesced write, run once per inbox via the transport's inbox-complete hook.
static void inbox_done (void)
 Runs once a whole inbound message is handled: one repaint for everything it brought, then the save.
static void apply_seed (const WeatherSeed *seed)
 Prefill the whole store from a seed, for dev builds and screenshots.
static void on_weather (const WeatherMessage *msg)
 The weather channel. The message holds every group the phone sent, and weather_reading_apply keeps what it can. A failed fetch keeps the last good reading rather than blanking, and it refreshes on the next poll that lands.
static void on_unit_changed (bool fahrenheit)
 The wearer switched the temperature unit. The reading in hand converts to it so the face never shows a Celsius number with an F beside it while the new fetch is on its way.
static void boot_fire (void *data)
 The short boot re-ask, and only that.
static void cadence_poll (void)
 The store's turn on the face's cadence: poll when the deadline has come round.
void weather_store_subscribe (void(*cb)(void))
 Hand it the function to call when the weather changes (the screen redraw).
void weather_store_init (WeatherConfig cfg, const WeatherSeed *seed)
 Start the store with its rules. Pass seed = NULL for normal use.
void weather_store_reconfigure (WeatherConfig cfg)
 Re-apply the rules (e.g. the poll interval changed). Moves the next poll to the new interval, and switching the store off here stops it asking the phone.
int weather_store_temp (void)
 The temperature reading in the user's unit, or WEATHER_NO_TEMP if we have not got one yet.
static bool forecast_today (void)
 Whether the high, low, UV, and rain chance are today's.
const char * weather_store_cond (void)
 The short word for the sky, or empty if we have not got one yet.
const char * weather_store_cond_label (void)
 The sky in words, such as "Partly Cloudy", for a face that declares WEATHER_CONDITION_LABEL.
int weather_store_humidity (void)
 The humidity in percent, or -1 if we have not got one yet.
int weather_store_wind_kmh (void)
 The wind speed in km/h, or -1 if we have not got one yet.
const char * weather_store_wind_dir (void)
 The wind direction like "NW", or empty if we have not got one yet.
int weather_store_sunrise (void)
 Sunrise as minutes past midnight, or -1 if we have not got one yet.
int weather_store_sunset (void)
 Sunset as minutes past midnight, or -1 if we have not got one yet.
int weather_store_uv (void)
 Today's UV index, or -1 if we have not got one yet or the day it came is over.
int weather_store_temp_max (void)
 Today's high, or WEATHER_NO_TEMP if we have not got one yet or the day it came is over.
int weather_store_temp_min (void)
 Today's low, or WEATHER_NO_TEMP if we have not got one yet or the day it came is over.
int weather_store_precip_chance (void)
 Today's chance of rain in percent, or -1 if we have not got one yet or the day it came is over.
int weather_store_feels_like (void)
 How warm it feels, or WEATHER_NO_TEMP if we have not got one yet.
int weather_store_pressure (void)
 The sea level pressure in hPa, or -1 if we have not got one yet.
int weather_store_dew_point (void)
 The dew point temperature, or WEATHER_NO_TEMP if we have not got one yet.
const WeatherHourly * weather_store_forecast_hourly (void)
 The hourly forecast strip, starting at the hour now.
const WeatherDaily * weather_store_forecast_daily (void)
 The daily forecast strip, starting today.
int weather_store_age_s (void)
 How many seconds since the last reading turned up, or -1 if we have none.

Variables

static WeatherState s_state
 Every reading the store holds, laid out as the blob that gets persisted.
static void(*) s_cb (void)
 Called whenever a reading changes, so the face can redraw.
static int s_boot_retries
 Short cold-boot re-asks used so far, until the first reading lands.
static StoreFetch s_fetch
 The interval, the boot re-ask timer, and when the next poll is due. Live gates the cache too.
static uint32_t s_persist_key
 The persist slot the face handed us for the saved reading.
static bool s_dirty
 A channel touched the state this inbox, so persist_flush writes it once.
static bool s_changed
 A channel touched the state this inbox, so the face repaints once at the end.
static bool s_heard
 The phone answered a weather request this launch, a failed fetch included.
static uint32_t s_saved_sum
 Sum of the reading last written, so a reply that changes nothing is not written again.

Detailed Description

The active weather store: holds the readings, owns the appmessage weather channels, and asks the phone for more whenever its turn on the face's cadence finds a poll due.

Macro Definition Documentation

◆ WEATHER_FIRST_POLL_MS

#define WEATHER_FIRST_POLL_MS   500

Delay before the first fetch after launch, in ms.

It fires from the event loop so appmessage is open by then. The recurring poll then runs at the configured interval.

Function Documentation

◆ apply_seed()

void apply_seed ( const WeatherSeed * seed)
static

Prefill the whole store from a seed, for dev builds and screenshots.

Copies the extra readings too, so a seeded face shows full weather rather than just the temperature and condition. s_cb is still NULL at the point init calls this, so no redraw fires here.

Parameters
seedThe prefill to apply.

◆ boot_fire()

void boot_fire ( void * data)
static

The short boot re-ask, and only that.

A cold launch can send its first request before the phone JS is up, and the reconnect refresh only fires on a real disconnect to connect transition rather than on the initial connect, so without this the face would sit blank until the next deadline came round. It re-asks on a short cadence for a bounded number of tries, then stops and leaves the recurring poll to the cadence.

Parameters
dataThe timer context (unused).

◆ forecast_today()

bool forecast_today ( void )
static

Whether the high, low, UV, and rain chance are today's.

They are only good for the day they came. For some providers they ride on a second request, and when only that one fails the rest of the reading keeps landing, so without this yesterday's high and low would show as today's.

◆ inbox_done()

void inbox_done ( void )
static

Runs once a whole inbound message is handled: one repaint for everything it brought, then the save.

A temperature unit change can land in the same message as a weather reading, and each comes in through its own handler. Waiting for the end of the message means the face redraws once and sees everything the message brought together, and flash is written at most once per message.

◆ mark_dirty()

void mark_dirty ( void )
static

Mark the reading as needing a save.

A combined weather push fires several channel handlers in one inbox, so rather than each writing the whole struct to flash, each just flags it dirty and persist_flush does one write when the inbox is fully dispatched, which is one write per poll instead of one per channel.

◆ on_unit_changed()

void on_unit_changed ( bool fahrenheit)
static

The wearer switched the temperature unit. The reading in hand converts to it so the face never shows a Celsius number with an F beside it while the new fetch is on its way.

Parameters
fahrenheitTrue when the new unit is Fahrenheit.

◆ on_weather()

void on_weather ( const WeatherMessage * msg)
static

The weather channel. The message holds every group the phone sent, and weather_reading_apply keeps what it can. A failed fetch keeps the last good reading rather than blanking, and it refreshes on the next poll that lands.

Parameters
msgThe message, read straight off the inbox.

◆ persist_flush()

void persist_flush ( void )
static

The coalesced write, run once per inbox via the transport's inbox-complete hook.

Only a live face writes, and this never runs from reset_state, so a failed fetch can't stomp the last good reading.