From 5caf53a496cd28582252ccb3dd192cf4d2ea0a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Calv=C3=A1rio?= <12759754+Calvario@users.noreply.github.com> Date: Thu, 21 May 2026 17:09:13 +0100 Subject: [PATCH] =?UTF-8?q?Add=20GPS=20altitude=20support,=20add=20channel?= =?UTF-8?q?=20=E2=80=9Creply=20to=E2=80=9D=20targeting,=20fix=20snake=20wa?= =?UTF-8?q?ll=20collisions,=20fix=20unread=20navigation=20incorrectly=20re?= =?UTF-8?q?turning=20to=20home,=20and=20fix=20BLE=20continuing=20to=20adve?= =?UTF-8?q?rtise=20after=20being=20disabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zephcore/adapters/ble/ZephyrBLE.cpp | 16 +++ zephcore/adapters/ble/ZephyrBLE.h | 3 + .../helpers/ui-joystick/joystick_screens.h | 24 +++- .../helpers/ui-joystick/joystick_ui_hooks.cpp | 39 +++--- .../helpers/ui-joystick/joystick_ui_task.cpp | 22 ++-- .../helpers/ui-joystick/joystick_ui_task.h | 2 +- .../helpers/ui-joystick/screens/messaging.cpp | 41 ++++--- .../ui-joystick/screens/screen_helpers.h | 23 ++++ .../helpers/ui-joystick/screens/system.cpp | 28 +++-- .../helpers/ui-joystick/screens/tools.cpp | 111 ++++++++++++------ zephcore/src/main_companion.cpp | 4 +- 11 files changed, 216 insertions(+), 97 deletions(-) diff --git a/zephcore/adapters/ble/ZephyrBLE.cpp b/zephcore/adapters/ble/ZephyrBLE.cpp index 2987ce3..b5a1783 100644 --- a/zephcore/adapters/ble/ZephyrBLE.cpp +++ b/zephcore/adapters/ble/ZephyrBLE.cpp @@ -147,6 +147,9 @@ static bool adv_stop_for_interval_change; * (SerialBLEInterface.cpp:343). */ static bool adv_running; +/* Administrative BLE state */ +static bool ble_enabled = true; + /* Runtime BLE passkey */ static uint32_t ble_passkey = CONFIG_ZEPHCORE_BLE_PASSKEY; @@ -781,6 +784,10 @@ static void adv_slow_work_fn(struct k_work *work) static void start_adv(void) { + if (!ble_enabled) { + return; + } + uint16_t interval = fast_adv_active ? BT_ADV_FAST_INTERVAL : BT_ADV_INTERVAL; struct bt_le_adv_param adv_param = { @@ -908,6 +915,7 @@ size_t zephcore_ble_send(const uint8_t *data, uint16_t len) void zephcore_ble_set_enabled(bool enable) { + ble_enabled = enable; if (!enable) { /* Disconnect current connection if any */ if (current_conn) { @@ -917,6 +925,10 @@ void zephcore_ble_set_enabled(bool enable) /* Stop advertising */ bt_le_adv_stop(); adv_running = false; + + /* No future fast->slow transition needed */ + k_work_cancel_delayable(&adv_slow_work); + LOG_INF("BLE disabled"); } else { /* Re-enable advertising — start fast window */ @@ -926,6 +938,10 @@ void zephcore_ble_set_enabled(bool enable) LOG_INF("BLE enabled"); } } +bool zephcore_ble_is_enabled(void) +{ + return ble_enabled; +} bool zephcore_ble_is_active(void) { diff --git a/zephcore/adapters/ble/ZephyrBLE.h b/zephcore/adapters/ble/ZephyrBLE.h index 265c9e0..f8a18ab 100644 --- a/zephcore/adapters/ble/ZephyrBLE.h +++ b/zephcore/adapters/ble/ZephyrBLE.h @@ -43,6 +43,9 @@ size_t zephcore_ble_send(const uint8_t *data, uint16_t len); /** Enable/disable BLE. Disabling disconnects and stops advertising. */ void zephcore_ble_set_enabled(bool enable); +/** True if BLE is enabled */ +bool zephcore_ble_is_enabled(void); + /** True if BLE is the active transport and ready to send. */ bool zephcore_ble_is_active(void); diff --git a/zephcore/helpers/ui-joystick/joystick_screens.h b/zephcore/helpers/ui-joystick/joystick_screens.h index 49ce533..66d777c 100644 --- a/zephcore/helpers/ui-joystick/joystick_screens.h +++ b/zephcore/helpers/ui-joystick/joystick_screens.h @@ -111,6 +111,8 @@ class GPSSettingsScreen : public UIScreen { float _speed_kmh, _heading_deg; bool _heading_valid; uint32_t _heading_hold_until; + bool _show_alt; + uint32_t _alt_cycle_ms; struct k_timer _sample_timer; static void sampleTimerCb(struct k_timer *t); void sampleGPS(); @@ -235,6 +237,13 @@ class T9InputScreen : public UIScreen { public: T9InputScreen(JoystickUITask *task); void clearInput() { memset(_input, 0, sizeof(_input)); _cursor = 0; } + void setInitialInput(const char *text) { + if (!text || !text[0]) return; + strncpy(_input, text, sizeof(_input) - 1); + _input[sizeof(_input) - 1] = '\0'; + _cursor = (int)strlen(_input); + _last_key = -1; _letter_index = 0; _last_press_time = 0; + } const char *getInput() const { return _input; } int render(JoystickDisplay &display) override; bool handleInput(char c) override; @@ -313,17 +322,24 @@ public: /* ===== SnakeScreen ===== */ class SnakeScreen : public UIScreen { JoystickUITask *_task; - static const int GRID_W = 21; - static const int GRID_H = 5; - int8_t _snake_x[GRID_W * GRID_H]; - int8_t _snake_y[GRID_W * GRID_H]; + /* runtime grid */ + int _grid_w; + int _grid_h; + int _max_len; + /* snake storage (safe upper bound) */ + static constexpr int MAX_SNAKE_LEN = 1024; + int8_t _snake_x[MAX_SNAKE_LEN]; + int8_t _snake_y[MAX_SNAKE_LEN]; int _snake_len; int8_t _food_x, _food_y; + uint32_t _next_move; int _score; + bool _grid_ready; struct k_timer _tick_timer; volatile bool _tick_due; /* set in timer ISR, consumed in render */ static void tickTimerCb(struct k_timer *t); + void updateGrid(JoystickDisplay &display); void placeFood(); void reset(); void advanceGame(); diff --git a/zephcore/helpers/ui-joystick/joystick_ui_hooks.cpp b/zephcore/helpers/ui-joystick/joystick_ui_hooks.cpp index 49008d2..76e381e 100644 --- a/zephcore/helpers/ui-joystick/joystick_ui_hooks.cpp +++ b/zephcore/helpers/ui-joystick/joystick_ui_hooks.cpp @@ -197,13 +197,6 @@ extern "C" void ui_notify_packet_sent(void) } } -extern "C" void ui_set_msg_count(uint16_t count) -{ - if (s_task) { - s_task->msgRead((int)count); - } -} - extern "C" void ui_set_ble_status(bool connected, const char *name) { (void)name; @@ -232,21 +225,6 @@ extern "C" void ui_notify_radio_stats(uint32_t pkt_recv, uint32_t pkt_sent, uint } } -/* ===== Pull-model no-ops ===== - * The joystick UI reads all hardware state on demand at render time, so these - * push-model hooks from the old button UI have no work to do. */ -extern "C" void ui_set_gps_data(bool, uint8_t, int32_t, int32_t, int32_t) {} -extern "C" void ui_set_clock(uint32_t) {} -extern "C" void ui_add_recent(const char *, int16_t, uint32_t) {} -extern "C" void ui_set_node_name(const char *) {} -extern "C" void ui_clear_recent(void) {} -extern "C" void ui_set_sensor_data(int16_t, uint32_t, uint16_t, uint16_t) {} -extern "C" void ui_set_gps_available(bool) {} -extern "C" void ui_set_gps_enabled(bool) {} -extern "C" void ui_set_gps_state(uint8_t, uint32_t, uint32_t) {} -extern "C" void ui_set_buzzer_quiet(bool) {} -extern "C" void ui_set_offgrid_mode(bool) {} - extern "C" void ui_set_battery(uint16_t mv, uint8_t /*pct*/) { if (s_task) { @@ -267,3 +245,20 @@ extern "C" void ui_refresh_display(void) s_task->forceRefresh(); } } + + +/* ===== Pull-model no-ops ===== + * The joystick UI reads all hardware state on demand at render time, so these + * push-model hooks from the old button UI have no work to do. */ +extern "C" void ui_set_gps_data(bool, uint8_t, int32_t, int32_t, int32_t) {} +extern "C" void ui_set_clock(uint32_t) {} +extern "C" void ui_add_recent(const char *, int16_t, uint32_t) {} +extern "C" void ui_set_node_name(const char *) {} +extern "C" void ui_clear_recent(void) {} +extern "C" void ui_set_sensor_data(int16_t, uint32_t, uint16_t, uint16_t) {} +extern "C" void ui_set_gps_available(bool) {} +extern "C" void ui_set_gps_enabled(bool) {} +extern "C" void ui_set_gps_state(uint8_t, uint32_t, uint32_t) {} +extern "C" void ui_set_buzzer_quiet(bool) {} +extern "C" void ui_set_offgrid_mode(bool) {} +extern "C" void ui_set_msg_count(uint16_t count) {} diff --git a/zephcore/helpers/ui-joystick/joystick_ui_task.cpp b/zephcore/helpers/ui-joystick/joystick_ui_task.cpp index eda442e..d517c6e 100644 --- a/zephcore/helpers/ui-joystick/joystick_ui_task.cpp +++ b/zephcore/helpers/ui-joystick/joystick_ui_task.cpp @@ -300,6 +300,16 @@ void JoystickUITask::gotoRadioStatsScreen() { setCurrScreen(_radio_stats); } void JoystickUITask::gotoRepeatersScreen() { setCurrScreen(_repeaters); } void JoystickUITask::gotoChannelsScreen() { setCurrScreen(_channels); } void JoystickUITask::gotoT9InputScreen() { setCurrScreen(_t9_input); } + +void JoystickUITask::gotoT9InputScreenWithPrefix(const char *prefix) +{ + if (!_t9_input) return; + auto *t9 = static_cast(_t9_input); + t9->clearInput(); + t9->setInitialInput(prefix); + setCurrScreen(_t9_input); +} + void JoystickUITask::gotoRenameNodeScreen() { setCurrScreen(_rename_node); } void JoystickUITask::gotoBLECodeScreen() { setCurrScreen(_ble_code); } void JoystickUITask::gotoStatsScreen() { setCurrScreen(_stats); } @@ -670,7 +680,7 @@ void JoystickUITask::toggleWakeOnMsg() /* ===== Notifications from mesh ===== */ void JoystickUITask::newMsg(uint8_t path_len, const char *from_name, const char *text, int msgcount) { - (void)msgcount; /* tracked by CompanionMesh; we mirror via msgRead() side-effect only */ + (void)msgcount; /* tracked by CompanionMesh */ if (_unread) { /* When BLE phone is connected it pulls offline queue and marks read; * keep the message in our local history but don't count as unread. */ @@ -723,16 +733,6 @@ void JoystickUITask::newChannelMsg(const char *channel_name, const char *text, #endif } -void JoystickUITask::msgRead(int msgcount) -{ - /* Called from CompanionMesh when the BLE-offline-queue drains to 0 - * (phone synced); auto-navigate home if the user is sitting on the - * Unread list looking at what just got cleared. */ - if (msgcount == 0 && _curr == _unread) { - gotoHomeScreen(); - } -} - void JoystickUITask::notify() { _next_refresh = 0; diff --git a/zephcore/helpers/ui-joystick/joystick_ui_task.h b/zephcore/helpers/ui-joystick/joystick_ui_task.h index c011639..882ed3f 100644 --- a/zephcore/helpers/ui-joystick/joystick_ui_task.h +++ b/zephcore/helpers/ui-joystick/joystick_ui_task.h @@ -51,7 +51,6 @@ public: /* Called from CompanionMesh callbacks (mesh thread context) */ void newMsg(uint8_t path_len, const char *from_name, const char *text, int msgcount); void newChannelMsg(const char *channel_name, const char *text, uint32_t ts, uint8_t path_len); - void msgRead(int msgcount); void notify(); /* BLE connect/disconnect, ACK, etc. */ /* Alert overlay */ @@ -70,6 +69,7 @@ public: void gotoRepeatersScreen(); void gotoChannelsScreen(); void gotoT9InputScreen(); + void gotoT9InputScreenWithPrefix(const char *prefix); void gotoRenameNodeScreen(); void gotoBLECodeScreen(); void gotoStatsScreen(); diff --git a/zephcore/helpers/ui-joystick/screens/messaging.cpp b/zephcore/helpers/ui-joystick/screens/messaging.cpp index 5b6402f..920f6e1 100644 --- a/zephcore/helpers/ui-joystick/screens/messaging.cpp +++ b/zephcore/helpers/ui-joystick/screens/messaging.cpp @@ -307,22 +307,22 @@ bool UnreadScreen::handleInput(char key) return true; } if (key == KEY_ENTER_LONG) { - /* Reply to channel message if applicable */ - if (entry && strchr(entry->msg, '#') != nullptr) { - const char *hash_pos = strchr(entry->msg, '#'); - char channel_name[32] = {}; - const char *space_after = hash_pos ? strchr(hash_pos, ' ') : nullptr; - if (space_after) { - size_t len = space_after - hash_pos; - if (len < sizeof(channel_name)) { - memcpy(channel_name, hash_pos, len); - channel_name[len] = '\0'; - } - } - if (channel_name[0]) { - _task->setComposeChannel(-1, channel_name); + if (!entry) return true; + /* Determine if this is a channel message by checking origin for '#' */ + char origin_sender[32] = {}; + uint8_t path_len_origin = OUT_PATH_UNKNOWN; + parseMessageOriginAndPath(entry->origin, origin_sender, sizeof(origin_sender), + &path_len_origin); + if (origin_sender[0] == '#') { + /* Channel message: channel name follows '#' in origin_sender */ + const char *ch_name = origin_sender + 1; + _task->setComposeChannel(-1, ch_name); + char at_buf[38]; + if (buildChannelReplyPrefix(entry->msg, path_len_origin, + at_buf, sizeof(at_buf))) { + _task->gotoT9InputScreenWithPrefix(at_buf); + } else { _task->gotoT9InputScreen(); - return true; } } return true; @@ -479,8 +479,9 @@ bool ChannelsScreen::handleInput(char key) if (_msg_details) { const char *msg = ""; uint32_t ts = 0; + uint8_t path_len = OUT_PATH_UNKNOWN; int max_scroll = 0; - if (_task->getChannelPreviewFor(_msg_channel, _msg_scroll, msg, ts)) { + if (_task->getChannelPreviewFor(_msg_channel, _msg_scroll, msg, ts, &path_len)) { max_scroll = getMessageDetailMaxScrollSanitized( _task->getDisplay(), msg, 20, 5, 5); } @@ -492,7 +493,13 @@ bool ChannelsScreen::handleInput(char key) } if (key == KEY_ENTER_LONG) { _task->setComposeChannel(_msg_channel_idx, _msg_channel); - _task->gotoT9InputScreen(); + char at_buf[38]; + if (buildChannelReplyPrefix(msg, path_len, + at_buf, sizeof(at_buf))) { + _task->gotoT9InputScreenWithPrefix(at_buf); + } else { + _task->gotoT9InputScreen(); + } return true; } return false; diff --git a/zephcore/helpers/ui-joystick/screens/screen_helpers.h b/zephcore/helpers/ui-joystick/screens/screen_helpers.h index f1ee4b2..ded5dd5 100644 --- a/zephcore/helpers/ui-joystick/screens/screen_helpers.h +++ b/zephcore/helpers/ui-joystick/screens/screen_helpers.h @@ -531,6 +531,29 @@ static inline void renderT9Keypad(JoystickDisplay &display, const char * const * } } +/* ===== buildChannelReplyPrefix ===== */ +/* Builds "@[SenderName] " from a "SenderName: text" channel message. + * Returns false for outbound messages (OUT_PATH_SENT) or if no ": " separator + * is found. out_buf must be at least (name_len + 5) bytes; 38 is always safe. */ +static inline bool buildChannelReplyPrefix( + const char *msg, uint8_t path_len, + char *out_buf, size_t out_size +){ + if (path_len == OUT_PATH_SENT) return false; + const char *sep = strstr(msg, ": "); + if (!sep || sep == msg) return false; + size_t nlen = (size_t)(sep - msg); + if (nlen > 32) nlen = 32; + if (out_size < nlen + 5) return false; /* '@' '[' name ']' ' ' '\0' */ + out_buf[0] = '@'; + out_buf[1] = '['; + memcpy(out_buf + 2, msg, nlen); + out_buf[2 + nlen] = ']'; + out_buf[3 + nlen] = ' '; + out_buf[4 + nlen] = '\0'; + return true; +} + /* Helper: leave compose input and return to appropriate screen */ static inline void leaveComposeInput(class JoystickUITask *task); diff --git a/zephcore/helpers/ui-joystick/screens/system.cpp b/zephcore/helpers/ui-joystick/screens/system.cpp index 7334f3d..8a8ea3e 100644 --- a/zephcore/helpers/ui-joystick/screens/system.cpp +++ b/zephcore/helpers/ui-joystick/screens/system.cpp @@ -621,7 +621,8 @@ GPSSettingsScreen::GPSSettingsScreen(JoystickUITask *task, mesh::RTCClock *rtc) : _task(task), _rtc(rtc), _selected(0), _have_prev_fix(false), _prev_lat_e6(0), _prev_lon_e6(0), _last_sample_ms(0), _speed_kmh(0.0f), _heading_deg(0.0f), - _heading_valid(false), _heading_hold_until(0) + _heading_valid(false), _heading_hold_until(0), + _show_alt(false), _alt_cycle_ms(0) { k_timer_init(&_sample_timer, sampleTimerCb, NULL); k_timer_user_data_set(&_sample_timer, this); @@ -660,12 +661,20 @@ void GPSSettingsScreen::onDisplayOn() void GPSSettingsScreen::sampleGPS() { + /* Cycle between position and altitude view every 4 seconds */ + uint32_t now = k_uptime_get_32(); + if (_alt_cycle_ms == 0) { + _alt_cycle_ms = now + GPS_HEADING_HOLD_MS; + } else if (now >= _alt_cycle_ms) { + _show_alt = !_show_alt; + _alt_cycle_ms = now + GPS_HEADING_HOLD_MS; + } + if (!_task->isGPSAvailable() || !_task->getGPSState()) return; struct gps_position pos; gps_get_position(&pos); if (!pos.valid) return; - uint32_t now = k_uptime_get_32(); if (now - _last_sample_ms < 1000) return; int32_t lat = (int32_t)(pos.latitude_ndeg / 1000LL); @@ -737,11 +746,16 @@ int GPSSettingsScreen::render(JoystickDisplay &display) snprintf(satellites_line, sizeof(satellites_line), "Sat: %d (%s)", sat, fix_str); if (has_fix) { - double lat = (double)(pos.latitude_ndeg / 1000000LL) / 1000.0; - double lon = (double)(pos.longitude_ndeg / 1000000LL) / 1000.0; - snprintf(lat_lon_line, sizeof(lat_lon_line), "%.4f%c %.4f%c", - fabs(lat), lat >= 0 ? 'N' : 'S', - fabs(lon), lon >= 0 ? 'E' : 'W'); + if (_show_alt) { + float alt_m = (float)pos.altitude_mm / 1000.0f; + snprintf(lat_lon_line, sizeof(lat_lon_line), "Alt: %.1f m", (double)alt_m); + } else { + double lat = (double)(pos.latitude_ndeg / 1000000LL) / 1000.0; + double lon = (double)(pos.longitude_ndeg / 1000000LL) / 1000.0; + snprintf(lat_lon_line, sizeof(lat_lon_line), "%.4f%c %.4f%c", + fabs(lat), lat >= 0 ? 'N' : 'S', + fabs(lon), lon >= 0 ? 'E' : 'W'); + } if (_heading_valid) { snprintf(speed_line, sizeof(speed_line), "%.1f km/h | %s %.0f", diff --git a/zephcore/helpers/ui-joystick/screens/tools.cpp b/zephcore/helpers/ui-joystick/screens/tools.cpp index 29ddce5..dd34a83 100644 --- a/zephcore/helpers/ui-joystick/screens/tools.cpp +++ b/zephcore/helpers/ui-joystick/screens/tools.cpp @@ -456,9 +456,11 @@ static int8_t s_ndir_y = 0; static int s_state = STATE_READY; SnakeScreen::SnakeScreen(JoystickUITask *task) - : _task(task), _snake_len(0), - _food_x(0), _food_y(0), _score(0), - _tick_due(false) + : _task(task), _grid_w(0), _grid_h(0), + _max_len(0), _snake_len(0), + _food_x(0), _food_y(0), + _next_move(0), _score(0), + _grid_ready(false), _tick_due(false) { memset(_snake_x, 0, sizeof(_snake_x)); memset(_snake_y, 0, sizeof(_snake_y)); @@ -505,58 +507,93 @@ void SnakeScreen::onDisplayOn() if (s_state == STATE_PLAYING) startTicking(); } +void SnakeScreen::updateGrid(JoystickDisplay &display) +{ + _grid_w = display.width() / SNAKE_CELL; + _grid_h = (display.height() - SNAKE_HEADER) / SNAKE_CELL; + + if (_grid_w < 5) _grid_w = 5; + if (_grid_h < 3) _grid_h = 3; + + _max_len = _grid_w * _grid_h; + + if (_max_len > MAX_SNAKE_LEN) + _max_len = MAX_SNAKE_LEN; + + _grid_ready = true; +} + void SnakeScreen::reset() { - int cx = GRID_W / 2, cy = GRID_H / 2; _snake_len = 3; - _snake_x[0] = cx; _snake_y[0] = cy; - _snake_x[1] = cx - 1; _snake_y[1] = cy; - _snake_x[2] = cx - 2; _snake_y[2] = cy; s_dir_x = 1; s_dir_y = 0; s_ndir_x = 1; s_ndir_y = 0; _score = 0; s_state = STATE_READY; _tick_due = false; - placeFood(); + _next_move = 0; + if (_grid_ready) { + int cx = _grid_w / 2; + int cy = _grid_h / 2; + _snake_x[0] = cx; _snake_y[0] = cy; + _snake_x[1] = cx - 1; _snake_y[1] = cy; + _snake_x[2] = cx - 2; _snake_y[2] = cy; + placeFood(); + } } void SnakeScreen::placeFood() { for (int tries = 0; tries < 200; tries++) { - int8_t x = (int8_t)(sys_rand32_get() % GRID_W); - int8_t y = (int8_t)(sys_rand32_get() % GRID_H); + int8_t x = sys_rand32_get() % _grid_w; + int8_t y = sys_rand32_get() % _grid_h; bool hit = false; for (int i = 0; i < _snake_len; i++) { - if (_snake_x[i] == x && _snake_y[i] == y) { hit = true; break; } + if (_snake_x[i] == x && _snake_y[i] == y) { + hit = true; + break; + } + } + if (!hit) { + _food_x = x; _food_y = y; + return; } - if (!hit) { _food_x = x; _food_y = y; return; } } _food_x = 0; _food_y = 0; } -static bool headHitsBody(const int8_t *sx, const int8_t *sy, int len) -{ - for (int i = 1; i < len; i++) { - if (sx[0] == sx[i] && sy[0] == sy[i]) return true; - } - return false; -} - void SnakeScreen::advanceGame() { + if (s_state != STATE_PLAYING) return; + if (!_grid_ready) return; + uint32_t now = k_uptime_get_32(); + if (now < _next_move) return; + _next_move = now + SNAKE_TICK_MS; + s_dir_x = s_ndir_x; s_dir_y = s_ndir_y; - int8_t nx = _snake_x[0] + s_dir_x; - int8_t ny = _snake_y[0] + s_dir_y; + int nx = _snake_x[0] + s_dir_x; + int ny = _snake_y[0] + s_dir_y; - if (nx < 0 || ny < 0 || nx >= GRID_W || ny >= GRID_H) { + /* WALL CHECK */ + if (nx < 0 || ny < 0 || nx >= _grid_w || ny >= _grid_h) { s_state = STATE_OVER; k_timer_stop(&_tick_timer); return; } bool eat = (nx == _food_x && ny == _food_y); - if (eat && _snake_len < SNAKE_MAX_LEN) _snake_len++; + + int body_to_check = eat ? _snake_len : (_snake_len - 1); + for (int i = 1; i < body_to_check; i++) { + if (_snake_x[i] == nx && _snake_y[i] == ny) { + s_state = STATE_OVER; + k_timer_stop(&_tick_timer); + return; + } + } + + if (eat && _snake_len < _max_len) _snake_len++; for (int i = _snake_len - 1; i > 0; i--) { _snake_x[i] = _snake_x[i - 1]; @@ -564,16 +601,17 @@ void SnakeScreen::advanceGame() } _snake_x[0] = nx; _snake_y[0] = ny; - if (headHitsBody(_snake_x, _snake_y, _snake_len)) { - s_state = STATE_OVER; - k_timer_stop(&_tick_timer); - return; + if (eat) { + _score++; + placeFood(); } - if (eat) { _score++; placeFood(); } } int SnakeScreen::render(JoystickDisplay &display) { + // Use the correct size based on the display + updateGrid(display); + /* Advance game on tick fire (set by k_timer ISR). */ if (_tick_due) { _tick_due = false; @@ -614,10 +652,17 @@ int SnakeScreen::render(JoystickDisplay &display) bool SnakeScreen::handleInput(char c) { - if (c == KEY_UP && s_dir_y == 0) { s_ndir_x = 0; s_ndir_y = -1; return true; } - if (c == KEY_DOWN && s_dir_y == 0) { s_ndir_x = 0; s_ndir_y = 1; return true; } - if (c == KEY_LEFT && s_dir_x == 0) { s_ndir_x = -1; s_ndir_y = 0; return true; } - if (c == KEY_RIGHT && s_dir_x == 0) { s_ndir_x = 1; s_ndir_y = 0; return true; } + /* One turn per tick: if a direction change is already queued for the + * upcoming tick, ignore further direction input. Also block the 180° + * reverse (would move head straight into body[1]). + * Once the tick fires, s_dir == s_ndir again and new input is accepted. */ + bool turn_queued = (s_ndir_x != s_dir_x || s_ndir_y != s_dir_y); + if (!turn_queued) { + if (c == KEY_UP && s_dir_y != 1) { s_ndir_x = 0; s_ndir_y = -1; return true; } + if (c == KEY_DOWN && s_dir_y != -1) { s_ndir_x = 0; s_ndir_y = 1; return true; } + if (c == KEY_LEFT && s_dir_x != 1) { s_ndir_x = -1; s_ndir_y = 0; return true; } + if (c == KEY_RIGHT && s_dir_x != -1) { s_ndir_x = 1; s_ndir_y = 0; return true; } + } if (c == KEY_ENTER) { if (s_state == STATE_READY || s_state == STATE_OVER) { reset(); s_state = STATE_PLAYING; diff --git a/zephcore/src/main_companion.cpp b/zephcore/src/main_companion.cpp index 0a663d8..771e7df 100644 --- a/zephcore/src/main_companion.cpp +++ b/zephcore/src/main_companion.cpp @@ -334,7 +334,7 @@ static void mesh_event_loop(void) * transiently (HCI timeout, controller pacing) the device * would silently stop advertising and be undiscoverable * until next reboot. Cheap to nudge it back here. */ - if (!zephcore_ble_is_connected() && !zephcore_ble_is_advertising()) { + if (zephcore_ble_is_enabled && !zephcore_ble_is_connected() && !zephcore_ble_is_advertising()) { LOG_WRN("BLE adv watchdog: not advertising, re-enabling"); zephcore_ble_set_enabled(true); } @@ -675,7 +675,7 @@ int main(void) ui_set_battery(zephyr_board.getBattMilliVolts(), 0); ui_set_gps_available(gps_is_available()); ui_set_gps_enabled(companion_mesh.prefs.gps_enabled != 0); - ui_set_ble_enabled(true); /* BLE starts advertising at boot */ + ui_set_ble_enabled(companion_mesh.prefs.ble_disabled != 1); /* BLE starts advertising at boot */ /* Restore offgrid mode (client repeat) state from persisted prefs */ ui_set_offgrid_mode(companion_mesh.prefs.client_repeat != 0);