Something went wrong. Try again.
Live STIB-MIVB departures on the Pebble Time 2. It covers the bus, tram, and metro network of Brussels
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400#include "model.h"
static Stop s_boards[2][MAX_STOPS];static uint8_t s_board_count[2];static uint8_t s_active;static Page s_active_page = PAGE_COUNT;static Page s_staging_page = PAGE_COUNT;static Page s_requested_page = PAGE_TRANSIT;static uint16_t s_staging_mask;static bool s_staging_open;static Row s_glance_row;static bool s_glance_valid;static DepartureRef s_dep_refs[MAX_DEPARTURES];static uint8_t s_dep_count;static bool s_dep_dirty = true;static uint32_t s_dep_keys[MAX_STOPS][MAX_ROWS];
static bool page_valid(Page page) { return (unsigned)page < PAGE_COUNT;}
static bool page_active(Page page) { return page_valid(page) && page == s_active_page;}static ModelState s_state = STATE_LOADING;static bool s_vibrate = true;static uint8_t s_lang;static LineDetail s_line_detail;static WakeupId s_wakeup_id = -1;static char s_wakeup_stop_id[ID_LEN];static char s_wakeup_line[LINE_LEN];static uint8_t s_wakeup_kind;static time_t s_last_data[PAGE_COUNT];static bool s_nudged[PAGE_COUNT];
#define PERSIST_STOP_ID 1#define PERSIST_VIBRATE 2#define PERSIST_LANG 3#define PERSIST_WAKEUP_ID 4#define PERSIST_WAKEUP_STOP 5#define PERSIST_WAKEUP_LINE 6#define PERSIST_WAKEUP_KIND 7
void model_init(void) { if (persist_exists(PERSIST_VIBRATE)) { s_vibrate = persist_read_bool(PERSIST_VIBRATE); } if (persist_exists(PERSIST_LANG)) { s_lang = persist_read_int(PERSIST_LANG); } persist_delete(PERSIST_STOP_ID); if (persist_exists(PERSIST_WAKEUP_ID)) { s_wakeup_id = persist_read_int(PERSIST_WAKEUP_ID); } if (persist_exists(PERSIST_WAKEUP_STOP)) { persist_read_string(PERSIST_WAKEUP_STOP, s_wakeup_stop_id, ID_LEN); } if (persist_exists(PERSIST_WAKEUP_LINE)) { persist_read_string(PERSIST_WAKEUP_LINE, s_wakeup_line, LINE_LEN); } if (persist_exists(PERSIST_WAKEUP_KIND)) { s_wakeup_kind = persist_read_int(PERSIST_WAKEUP_KIND); }}
void model_deinit(void) { persist_write_bool(PERSIST_VIBRATE, s_vibrate); persist_write_int(PERSIST_LANG, s_lang);}
void model_begin(Page page, uint8_t stop_count) { if (!page_valid(page)) { return; } s_staging_open = true; s_staging_page = page; s_staging_mask = 0; uint8_t staging = s_active ^ 1; s_board_count[staging] = stop_count > MAX_STOPS ? MAX_STOPS : stop_count; memset(s_boards[staging], 0, sizeof(s_boards[staging])); for (uint8_t i = 0; i < MAX_STOPS; i++) { s_boards[staging][i].bikes_mechanical = -1; s_boards[staging][i].bikes_electrical = -1; s_boards[staging][i].docks = -1; } model_mark_dirty(page);}
Stop *model_staging_stop_at(uint8_t index) { return s_staging_open && index < MAX_STOPS ? &s_boards[s_active ^ 1][index] : NULL;}
void model_staging_note(uint8_t index) { if (s_staging_open && index < MAX_STOPS) { s_staging_mask |= (uint16_t)(1u << index); }}
void model_note_request(Page page) { if (page_valid(page)) { s_requested_page = page; }}
Page model_requested_page(void) { return s_requested_page;}
bool model_commit(Page page) { bool open = s_staging_open; Page staged = s_staging_page; uint16_t mask = s_staging_mask; s_staging_open = false; s_staging_page = PAGE_COUNT; s_staging_mask = 0; if (!open || page != staged || page != s_requested_page) { return false; } uint8_t staging = s_active ^ 1; uint16_t required = (uint16_t)((1u << s_board_count[staging]) - 1); if ((mask & required) != required) { return false; } s_active = staging; s_active_page = page; s_dep_dirty = true; if (page == PAGE_TRANSIT) { DepartureRef ref; s_glance_valid = model_departures(PAGE_TRANSIT, &ref, 1) == 1; if (s_glance_valid) { s_glance_row = s_boards[s_active][ref.stop].rows[ref.row]; } } return true;}
const Row *model_glance_row(void) { return s_glance_valid ? &s_glance_row : NULL;}
Stop *model_stop_at(Page page, uint8_t index) { return page_active(page) && index < MAX_STOPS ? &s_boards[s_active][index] : NULL;}
Stop *model_stop_by_id(Page page, const char *id) { if (!page_active(page) || id[0] == '\0') { return NULL; } for (uint8_t i = 0; i < s_board_count[s_active]; i++) { if (strcmp(s_boards[s_active][i].id, id) == 0) { return &s_boards[s_active][i]; } } return NULL;}
uint8_t model_stop_count(Page page) { return page_active(page) ? s_board_count[s_active] : 0;}
void model_set_state(ModelState state) { s_state = state;}
ModelState model_state(void) { return s_state;}
bool model_vibrate(void) { return s_vibrate;}
uint8_t model_lang(void) { return s_lang;}
void model_set_config(bool vibrate, uint8_t lang) { s_vibrate = vibrate; s_lang = lang;}
Departure model_classify(const Row *row) { Departure d; d.class = row->kind == 3 ? DEPARTURE_TRAIN : row->kind == 4 ? DEPARTURE_VILLO : DEPARTURE_TRANSIT; bool end = row->flags & ROW_FLAG_END; bool labeled = row->line[0] != '\0'; bool timed = !end && labeled && d.class == DEPARTURE_TRANSIT; d.info = d.class != DEPARTURE_TRANSIT || (end && labeled); d.nearby = timed || d.info; d.select = end ? ROW_SELECT_INERT : d.class == DEPARTURE_TRAIN ? ROW_SELECT_STATION : d.class == DEPARTURE_VILLO ? ROW_SELECT_STAND : labeled ? ROW_SELECT_LINE : ROW_SELECT_INERT; d.alarm = !end && labeled && d.class != DEPARTURE_VILLO; return d;}
void model_mark_dirty(Page page) { if (page_active(page)) { s_dep_dirty = true; }}
static uint32_t departure_key(const DepartureRef *ref) { return s_dep_keys[ref->stop][ref->row];}
static void departure_insert(DepartureRef *out, uint8_t *n, uint8_t cap, DepartureRef ref) { if (cap == 0) { return; } uint32_t key = departure_key(&ref); if (*n == cap && key >= departure_key(&out[*n - 1])) { return; } uint8_t limit = *n < cap ? *n : (uint8_t)(cap - 1); uint8_t pos = limit; while (pos > 0 && departure_key(&out[pos - 1]) > key) { pos--; } memmove(&out[pos + 1], &out[pos], (size_t)(limit - pos) * sizeof(DepartureRef)); out[pos] = ref; if (*n < cap) { (*n)++; }}
static uint8_t departures_rebuild(DepartureRef *out, uint8_t max) { const Stop *board = s_boards[s_active]; uint8_t count = s_board_count[s_active]; for (uint8_t i = 0; i < count; i++) { uint8_t rows = board[i].row_count > MAX_ROWS ? MAX_ROWS : board[i].row_count; for (uint8_t j = 0; j < rows; j++) { const Row *row = &board[i].rows[j]; uint32_t key = row->min1 < 0 ? UINT16_MAX : (uint16_t)row->min1; s_dep_keys[i][j] = model_classify(row).info ? key + 0x10000 : key; } }
uint8_t info_count = 0; if (max > 2 * MAX_INFO_ROWS) { for (uint8_t i = 0; i < count; i++) { uint8_t rows = board[i].row_count > MAX_ROWS ? MAX_ROWS : board[i].row_count; for (uint8_t j = 0; j < rows; j++) { if (s_dep_keys[i][j] >= 0x10000) { info_count++; } } } if (info_count > MAX_INFO_ROWS) { info_count = MAX_INFO_ROWS; } } uint8_t timed_cap = info_count < max ? (uint8_t)(max - info_count) : 0;
uint8_t n = 0; for (uint8_t i = 0; i < count; i++) { uint8_t rows = board[i].row_count > MAX_ROWS ? MAX_ROWS : board[i].row_count; for (uint8_t j = 0; j < rows; j++) { const Row *row = &board[i].rows[j]; if (!model_classify(row).nearby || s_dep_keys[i][j] >= 0x10000) { continue; } DepartureRef ref = { .stop = i, .row = j }; departure_insert(out, &n, timed_cap, ref); } }
DepartureRef *info_out = out + n; uint8_t info_cap = (uint8_t)(max - n); uint8_t info_n = 0; for (uint8_t i = 0; i < count; i++) { uint8_t rows = board[i].row_count > MAX_ROWS ? MAX_ROWS : board[i].row_count; for (uint8_t j = 0; j < rows; j++) { if (s_dep_keys[i][j] < 0x10000) { continue; } DepartureRef ref = { .stop = i, .row = j }; departure_insert(info_out, &info_n, info_cap, ref); } } return (uint8_t)(n + info_n);}
uint8_t model_departures(Page page, DepartureRef *out, uint8_t max) { if (max == 0 || !page_active(page)) { return 0; } if (s_dep_dirty) { s_dep_count = departures_rebuild(s_dep_refs, MAX_DEPARTURES); s_dep_dirty = false; } uint8_t n = s_dep_count < max ? s_dep_count : max; memcpy(out, s_dep_refs, (size_t)n * sizeof(DepartureRef)); return n;}
uint8_t model_distinct_stops(const DepartureRef *refs, uint8_t count) { uint32_t seen = 0; uint8_t n = 0; for (uint8_t i = 0; i < count; i++) { uint32_t bit = 1u << refs[i].stop; if (!(seen & bit)) { seen |= bit; n++; } } return n;}
LineDetail *model_line_detail(void) { return &s_line_detail;}
void model_line_begin(void) { memset(&s_line_detail, 0, sizeof(s_line_detail));}
bool model_line_complete(void) { return s_line_detail.header_received && s_line_detail.strip_received >= s_line_detail.strip_count;}
void model_note_data_arrival(Page page, time_t now) { if (!page_valid(page)) { return; } s_last_data[page] = now; s_nudged[page] = false;}
bool model_stale(Page page, time_t now) { return page_valid(page) && s_last_data[page] != 0 && now - s_last_data[page] > STALE_THRESHOLD_S;}
StalenessAction model_staleness_action(time_t now, time_t last_data, bool connected, bool already_nudged) { if (!connected || already_nudged) { return STALENESS_NONE; } if (now - last_data <= STALE_THRESHOLD_S) { return STALENESS_NONE; } return STALENESS_NUDGE;}
StalenessAction model_check_staleness(Page page, time_t now) { if (!page_valid(page)) { return STALENESS_NONE; } StalenessAction action = model_staleness_action(now, s_last_data[page], s_state != STATE_NO_PHONE, s_nudged[page]); if (action == STALENESS_NUDGE) { s_nudged[page] = true; } return action;}
WakeupId model_wakeup_id(void) { return s_wakeup_id;}
void model_set_wakeup_id(WakeupId id) { s_wakeup_id = id; if (id < 0) { persist_delete(PERSIST_WAKEUP_ID); persist_delete(PERSIST_WAKEUP_STOP); persist_delete(PERSIST_WAKEUP_LINE); persist_delete(PERSIST_WAKEUP_KIND); s_wakeup_stop_id[0] = '\0'; s_wakeup_line[0] = '\0'; s_wakeup_kind = 0; } else { persist_write_int(PERSIST_WAKEUP_ID, id); }}
void model_set_wakeup_label(const char *stop_id, const char *line, uint8_t kind) { strncpy(s_wakeup_stop_id, stop_id, ID_LEN - 1); s_wakeup_stop_id[ID_LEN - 1] = '\0'; strncpy(s_wakeup_line, line, LINE_LEN - 1); s_wakeup_line[LINE_LEN - 1] = '\0'; s_wakeup_kind = kind; persist_write_string(PERSIST_WAKEUP_STOP, s_wakeup_stop_id); persist_write_string(PERSIST_WAKEUP_LINE, s_wakeup_line); persist_write_int(PERSIST_WAKEUP_KIND, kind);}
const char *model_wakeup_stop_id(void) { return s_wakeup_stop_id;}
const char *model_wakeup_line(void) { return s_wakeup_line;}
uint8_t model_wakeup_kind(void) { return s_wakeup_kind;}