Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
dde2618
rtc: select 24-hour mode before a time write, and take no time in 12-…
ptr727 Oct 6, 2026
52d83b9
rtc: clear the 12-hour bit only on the adopted chip, and never from a…
ptr727 Oct 6, 2026
e6d568b
rtc: read an all-1s 12-hour mode register as unreadable
ptr727 Oct 6, 2026
235c3c0
rtc: refuse a 12-hour mode register read that shows a bit reading 0
ptr727 Oct 6, 2026
b18542c
Merge upstream dev (81d98ab) into work/rtc-24h-mode
ptr727 Oct 6, 2026
d927e93
rtc: require a zero bit for an out-of-block twelve-hour-bit
ptr727 Oct 6, 2026
70def66
rtc: leave the 12-hour bit to the next save, and reject a zero byte o…
ptr727 Oct 6, 2026
9a42030
rtc: require bit 0 out of block, the hours register in block, and a m…
ptr727 Oct 6, 2026
59ba354
rtc: honour twelve-hour-bit's zero bits in the time block too
ptr727 Oct 6, 2026
74c7e0c
rtc: read back the 12-hour clear and repair a torn write
ptr727 Oct 6, 2026
c18e1f0
rtc: promise a sync only for the RTC it will write
ptr727 Oct 6, 2026
474d84c
Merge remote-tracking branch 'origin/dev' into work/rv3028-boot-24h
ptr727 Oct 7, 2026
2bde420
rtc: clear the RV3028 12-hour bit once at boot instead, as the mainta…
ptr727 Oct 7, 2026
9b68b7e
rtc: write the RTC time only for years 2000-2099
ptr727 Oct 7, 2026
069c974
UI fixes
liquidraver Oct 7, 2026
ee08b21
rtc: clear the 12-hour bit only on a clean identification
ptr727 Oct 7, 2026
3c10138
rtc: say the 12-hour clear needs a clean identification
ptr727 Oct 7, 2026
5dd3308
Merge pull request #105 from ptr727/rtc-year-range
liquidraver Oct 7, 2026
290b0c2
guidelines for Giskard Reventlov vol.1
liquidraver Oct 7, 2026
8d2daf3
Merge remote-tracking branch 'upstream/dev' into work/rv3028-boot-24h
ptr727 Oct 8, 2026
0c82ea1
rtc: take no time when the 12-hour clear fails, as the maintainer asked
ptr727 Oct 8, 2026
5396538
rtc: name the re-read failure in the 12-hour clear's text
ptr727 Oct 8, 2026
e5623aa
rtc: scope the no-time rule to every clean RV-3028, note the store delay
ptr727 Oct 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# AGENTS.md

Rules for AI-assisted contributions to ZephCore. They apply to human contributors too. Read the section of
`docs/DESIGN.md` (invariants, layer rules) and `docs/ARCHITECTURE.md` (component reference) for the area you
touch before writing code. Decisions are in `docs/adr/`.

## How a change is judged

1. **Find the root cause.** The PR states the cause and the evidence for it (datasheet section, register dump,
log, trace). If the cause is not known, say so; do not ship a guess.
2. **No band-aids.** Do not add a retry, delay, timeout, re-init, read-back check or watchdog to make a symptom
go away. Recovery code is accepted only for a named, understood hardware failure, in the layer that owns the
state, with its bound (`docs/DESIGN.md` §8.7). There is no hardware watchdog and none will be added.
3. **The smallest change that fixes it.** Close what is reachable today, not what might be reachable later. No
speculative guards, build asserts, new abstractions, options or refactors riding along. One concern per PR.
If the diff is larger than the bug, expect to be asked to cut it.
4. **Do what upstream MeshCore does** when it already solves the problem, unless ours can be more accurate at no
compatibility cost (`docs/DESIGN.md` §9).
5. **Comment diet.** A comment says what the code does or the invariant it keeps, in a few lines. Rationale,
history, incidents, measurements and rejected alternatives go in the PR description and `docs/`, not in code.
Do not comment the obvious.
6. **Fix it for every board.** A fix or feature that concerns behaviour goes in the common path, for all
platforms and roles. Do not scope it to the board or SoC family where it was found or tested, and do not
exclude a platform because the problem cannot happen there today; a later port or Zephyr bump can make it
real. Untested boards still get the change; the PR says which hardware it ran on.
7. **Gate on the capability, never on the name.** The only reason to gate code is that the hardware is absent,
and the test for that is a devicetree node or property, or a Kconfig symbol for the feature. Never
`CONFIG_SOC_*`, `CONFIG_BOARD_*` or a board name as a stand-in for "has this part" or "has this bug". A board
that lacks the hardware then carries no code for it, and a new board that has it gets the behaviour for free.
8. **A real platform difference is stated as one.** If something truly is specific to one SoC or chip (an
erratum, a vendor blob, a peripheral that exists nowhere else), it lives in that platform's layer and the PR
cites the source that makes it specific. "Only seen on X" is not that.

## Invariants: do not change without an ADR

- Byte compatibility with Arduino MeshCore on the air and on the companion protocol; `prefs.json` format.
- The wall clock only moves forward.
- Mesh, GPS state and UI run on the main thread; loops are event-driven, no polling.
- No heap in the packet path. Companion builds are RAM-bound.
- Never write to a peripheral that was not cleanly identified first.
- BLE: no proactive SMP Security Request, keep `BT_LE_ADV_OPT_USE_IDENTITY` (ADR 0004), leave connection
parameters alone.
- Files ported from MeshCore (listed in `.editorconfig`) keep upstream's text verbatim; every change in them is
fenced `// ZEPHCORE:`. Do not restyle or de-duplicate them.
- `zephcore/patches/zephyr`: one patch owns each Zephyr file.
- `board.conf` does not repeat what a parent conf or the devicetree already provides.

## Mechanics

- Branch from `dev`, target `dev`, rebase instead of merging.
- Tabs for indentation, except the upstream-ported files.
- Build with `--pristine` when the board or role changes; ESP32 S3/C-series repeaters need `--sysbuild`.
- Host tests: `python3 tests/run.py run --profile quick` (Linux or WSL). A new case needs a
`tests/catalog.json` entry.
- New board: `zephcore.yml` without `release:`, then `python zephcore/scripts/board_manifest.py check` and
`... docs`.
- Docs in the same PR: a clause in `docs/ARCHITECTURE.md` when behaviour, an interface or a limit changes;
`docs/CLI_commands.md` for any CLI change; an ADR when a decision is taken or reversed.

## PR description

- Cause, fix, and what was deliberately left out.
- Which boards it was built for, and which hardware it actually ran on. Never claim hardware verification that
was not done.
- Flash and RAM delta when code is added to a common path.
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,7 @@ Repeaters and room servers default to `CONFIG_ZEPHCORE_REPEATER_GPS_INTERVAL_SEC

**Telemetry** (`REQ_TYPE_GET_TELEMETRY_DATA`, every role, upstream's shape): battery on channel 1, then `sensors.querySensors()` (`adapters/sensors/ZephyrSensorManager.cpp`): GPS position on channel 1 while the GPS is on and has a fix, each environment sensor and power-monitor channel found at boot on its own channel from 2 up (probe order), board-local analog sensors (T1000-E) on channel 1; then the MCU temperature on channel 1. Servers give guests battery + MCU temperature only and honour the requester's inverse permission mask, as upstream. Encoded by `helpers/compat/CayenneLPP.h`: upstream's library API and wire format, but values round to the nearest step where the library truncates.

**Clock** (`adapters/clock/ZephyrRTCClock`): `setCurrentTime()` also writes the hardware RTC when the board has one (coalesced on the system work queue), as upstream's `AutoDiscoverRTCClock` writes its chip; `seedCurrentTime()` is the boot restore, not written back. A chip is adopted at boot only once it is identified (`adapters/clock/ZephyrRTCDiscover.c`), because other parts can answer at the same address: a device is passed over when two reads each show a bit set that the chip's data sheet fixes at zero, or a time field out of range, and a time is restored only from a read in which every field is valid. The time is written in each chip's own encoding (the RX8130CE takes a one-hot weekday, `weekday-one-hot` in its descriptor), and an RV-3028 whose descriptor carries `rv3028-eeprom-config` (the RAK12002 on a RAK4631) has that configuration, its backup switchover, stored in the chip's EEPROM. Without a hardware RTC, the last time read is kept in no-init memory (RTC slow memory on ESP32) and restored after a reset that keeps RAM (reboot, crash, watchdog), as upstream's `ESP32RTCClock`; a power cycle starts from 1970 again.
**Clock** (`adapters/clock/ZephyrRTCClock`): `setCurrentTime()` also writes the hardware RTC when the board has one (coalesced on the system work queue; a time outside 2000-2099 is not written), as upstream's `AutoDiscoverRTCClock` writes its chip; `seedCurrentTime()` is the boot restore, not written back. A chip is adopted at boot only once it is identified (`adapters/clock/ZephyrRTCDiscover.c`), because other parts can answer at the same address: a device is passed over when two reads each show a bit set that the chip's data sheet fixes at zero, or a time field out of range, and a time is restored only from a read in which every field is valid. An RV-3028 cleanly identified in 12-hour mode has its `twelve-hour-bit` cleared first, so the chip converts its own hours. No time is taken from a cleanly identified RV-3028 whose `twelve-hour-bit` register cannot be read or reads FFh, or whose clear or re-read fails. The time is written in each chip's own encoding (the RX8130CE takes a one-hot weekday, `weekday-one-hot` in its descriptor), and an RV-3028 whose descriptor carries `rv3028-eeprom-config` (the RAK12002 on a RAK4631) has that configuration, its backup switchover, stored in the chip's EEPROM. Without a hardware RTC, the last time read is kept in no-init memory (RTC slow memory on ESP32) and restored after a reset that keeps RAM (reboot, crash, watchdog), as upstream's `ESP32RTCClock`; a power cycle starts from 1970 again.

**LED master switch** (`helpers/led_gate.{c,h}`, `set leds on|off`, all roles): one process-wide
flag every LED driver consults — heartbeat and unread-message LEDs in `helpers/ui/ui_common.c`, the
Expand Down
4 changes: 4 additions & 0 deletions releasenotes/RELEASE_NOTES_1.17.7-zephcore.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ matches the SoftDevice version of your bootloader.
measurement the chip rejected the command that puts it back into its duty cycle. The next channel
check recovered it and no packets were lost; the driver now returns the chip to standby first, and
the command is accepted.
- **Joystick UI** (Wio Tracker L1 and other joystick boards): switching the GPS on from the GPS
screen showed "GPS disabled", and switching it off showed "GPS enabled". The GPS itself switched
correctly; the message now matches. The 3-tap buzzer and 4-tap GPS shortcuts now show a message
too, like the LED and advert shortcuts.
- **New board**: Seeed LR2021 LoRa Plus EVK with a XIAO nRF54LM20A. Build it from source; there is no
published firmware for it yet.
- **Zephyr** updated to `74b7173e9c9`. One change in it would have been visible and is handled:
Expand Down
5 changes: 3 additions & 2 deletions zephcore/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -911,8 +911,9 @@ config ZEPHCORE_RTC_AUTODISCOVER
backed RTC opt in by including boards/common/rtc-i2c.dtsi or
declaring their own node; if a chip holds a valid time it is
restored at boot — shown tagged "L" until the next external sync.
On every GPS/app/CLI sync the time is written back so it survives
power-off. A node with rv3028-eeprom-config has its time read and
On every GPS/app/CLI sync to a time within 2000-2099 the time is
written back so it survives power-off. A node with
rv3028-eeprom-config has its time read and
written with the chip's backup switch flag cleared before and
checked after, and, when its chip is the one adopted, a read
identifies it, and a checked time read just before the store shows
Expand Down
56 changes: 51 additions & 5 deletions zephcore/adapters/clock/ZephyrRTCDiscover.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ struct rtc_desc {
uint8_t status_mask; /* "time unreliable" bit within status_reg */
const uint8_t *zero; /* 7 bytes of bits the data sheet shows as 0, or NULL */
bool week_one_hot; /* weekday as one bit per day, not 0-6 */
uint8_t h12_reg; /* register of the 12-hour bit */
uint8_t h12_mask; /* 12-hour bit, or 0 if none */
#if RTC_RV3028_CFG
const uint8_t *cfg; /* rv3028-eeprom-config triplets, or NULL */
uint8_t cfg_len;
Expand Down Expand Up @@ -79,6 +81,10 @@ DT_FOREACH_STATUS_OKAY(RTC_COMPAT, RTC_CFG_ARRAY)
#define RTC_CFG_FIELDS(node)
#endif

#define RTC_H12(node, i) \
COND_CODE_1(DT_NODE_HAS_PROP(node, twelve_hour_bit), \
(DT_PROP_BY_IDX(node, twelve_hour_bit, i)), (0))

#define RTC_DESC_ENTRY(node) \
{ \
.bus = DEVICE_DT_GET(DT_BUS(node)), \
Expand All @@ -91,6 +97,8 @@ DT_FOREACH_STATUS_OKAY(RTC_COMPAT, RTC_CFG_ARRAY)
DT_NODE_HAS_PROP(node, zero_mask), \
(RTC_ZERO_NAME(node)), (NULL)), \
.week_one_hot = DT_PROP(node, weekday_one_hot), \
.h12_reg = (uint8_t)RTC_H12(node, 0), \
.h12_mask = (uint8_t)RTC_H12(node, 1), \
RTC_CFG_FIELDS(node) \
.name = DT_NODE_FULL_NAME(node), \
},
Expand Down Expand Up @@ -224,8 +232,33 @@ static enum rtc_verdict rtc_identify(const struct rtc_desc *d, uint8_t blk[7])
return RTC_FOUND;
}

/* The 7-byte time block for an epoch, in d's register order. */
static void rtc_time_block(const struct rtc_desc *d, uint32_t epoch, uint8_t blk[7])
/* Clear a set 12-hour bit and re-read blk; the chip converts its hours
* itself. False on a failed or FFh read of the bit, or a failed clear or
* re-read. */
static bool rtc_clear_12h(const struct rtc_desc *d, uint8_t blk[7])
{
uint8_t r;

if (d->h12_mask == 0) {
return true;
}
if (i2c_reg_read_byte(d->bus, d->addr, d->h12_reg, &r) != 0 || r == 0xFF) {
return false;
}
if (!(r & d->h12_mask)) {
return true;
}
if (i2c_reg_write_byte(d->bus, d->addr, d->h12_reg, r & ~d->h12_mask) != 0 ||
i2c_burst_read(d->bus, d->addr, d->time_reg, blk, 7) != 0) {
return false;
}
LOG_INF("%s: 12-hour mode cleared", d->name);
return true;
}

/* The 7-byte time block for an epoch, in d's register order. False outside
* 2000-2099: the year is written as two BCD digits with no century. */
static bool rtc_time_block(const struct rtc_desc *d, uint32_t epoch, uint8_t blk[7])
{
int y;
unsigned m, day;
Expand All @@ -236,6 +269,9 @@ static void rtc_time_block(const struct rtc_desc *d, uint32_t epoch, uint8_t blk
unsigned sec = rem % 60;
unsigned dow = (unsigned)(((epoch / 86400) + 4) % 7); /* 1970-01-01 = Thu */

if (y < 2000 || y > 2099) {
return false;
}
blk[0] = BIN2BCD(sec);
blk[1] = BIN2BCD(min);
blk[2] = BIN2BCD(hour);
Expand All @@ -244,6 +280,7 @@ static void rtc_time_block(const struct rtc_desc *d, uint32_t epoch, uint8_t blk
blk[d->date_index == 4 ? 3 : 4] = d->week_one_hot ? (uint8_t)BIT(dow) : (uint8_t)dow;
blk[5] = BIN2BCD(m);
blk[6] = BIN2BCD((unsigned)(y % 100));
return true;
}

/* Clear the power-loss flag (for chips whose flag is a separate reg;
Expand Down Expand Up @@ -537,8 +574,11 @@ static void rv3028_save_retry_fn(struct k_work *work)
uint8_t blk[7];

ARG_UNUSED(work);
rtc_time_block(s_active, s_save_epoch +
(uint32_t)((k_uptime_get() - s_save_at) / 1000), blk);
if (!rtc_time_block(s_active, s_save_epoch +
(uint32_t)((k_uptime_get() - s_save_at) / 1000), blk)) {
LOG_WRN("RTC %s: time outside 2000-2099 not written", s_active->name);
return;
}
if (!rv3028_steady(s_active, blk, true)) {
if (++s_save_tries < RTC_SAVE_RETRIES) {
k_work_schedule(&s_save_retry, RTC_SAVE_RETRY);
Expand Down Expand Up @@ -576,6 +616,9 @@ static bool rtc_probe(uint32_t *epoch_out)
if (v == RTC_ABSENT) {
continue;
}
if (v == RTC_FOUND && !rtc_clear_12h(d, blk)) {
v = RTC_FOUND_GARBLED;
}

#if RTC_RV3028_CFG
/* Only a clean identification is configured: the store writes to
Expand Down Expand Up @@ -675,7 +718,10 @@ void zephcore_rtc_save(uint32_t epoch)
const struct rtc_desc *d = s_active;
uint8_t blk[7];

rtc_time_block(d, epoch, blk);
if (!rtc_time_block(d, epoch, blk)) {
LOG_WRN("RTC %s: time outside 2000-2099 not written", d->name);
return;
}
#if RTC_RV3028_CFG
if (d->cfg != NULL) {
(void)k_work_cancel_delayable(&s_save_retry);
Expand Down
10 changes: 6 additions & 4 deletions zephcore/adapters/clock/ZephyrRTCDiscover.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
*
* If a present chip holds a valid time, zephcore_rtc_restore() returns it so
* the soft clock can be seeded at boot (shown tagged "L" — local). Every
* ZephyrRTCClock::setCurrentTime() writes it back via zephcore_rtc_save(), so
* time survives the next power-off.
* ZephyrRTCClock::setCurrentTime() within 2000-2099 writes it back via
* zephcore_rtc_save(), so time survives the next power-off.
*/

#pragma once
Expand All @@ -28,7 +28,9 @@ extern "C" {
* sane time (year >= 2025 and its power-loss flag clear): store its Unix
* epoch in *epoch_out and return true. The first chip found (valid time or
* not) is remembered as the write-back target. Returns false if none is
* present or none holds a trustworthy time.
* present or none holds a trustworthy time. On a clean identification, a set
* twelve-hour-bit is cleared before the time is taken. No time is taken if its
* register cannot be read or reads FFh, or if the clear or the re-read fails.
*
* A descriptor with rv3028-eeprom-config also has that config stored in the
* chip's EEPROM, retried on the system work queue if it fails.
Expand All @@ -40,7 +42,7 @@ bool zephcore_rtc_restore(uint32_t *epoch_out);
* power-loss flag. No-op if no RTC was discovered. Intended for real syncs
* (GPS/app/CLI), not per-packet clock nudges. Call only from the system work
* queue: an unconfirmed write is repeated there and shares state with this
* call.
* call. A time outside 2000-2099 is not written.
*/
void zephcore_rtc_save(uint32_t epoch);

Expand Down
1 change: 1 addition & 0 deletions zephcore/boards/common/rtc-i2c.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ rtc_rv3028: rtc-rv3028@52 {
date-index = <4>;
status-reg = <0x0e>; /* status */
status-mask = <0x01>; /* PORF — power-on reset */
twelve-hour-bit = [10 02]; /* Control 2 12_24, p. 24 */
/* RV-3028-C7 Application Manual Rev. 1.4, 3.2, p. 12 */
zero-mask = [80 80 c0 f8 c0 e0 00];
};
Expand Down
1 change: 1 addition & 0 deletions zephcore/boards/nrf52840/rak4631/board.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@
date-index = <4>;
status-reg = <0x0e>; /* status */
status-mask = <0x01>; /* PORF — power-on reset */
twelve-hour-bit = [10 02]; /* Control 2 12_24, p. 24 */
/* RV-3028-C7 Application Manual Rev. 1.4, 3.2, p. 12 */
zero-mask = [80 80 c0 f8 c0 e0 00];
/* The RAK12002's backup is a supercapacitor. As the RV-3028-C7
Expand Down
27 changes: 19 additions & 8 deletions zephcore/dts/bindings/rtc/zephcore,rtc-i2c.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ properties:
bit 6) rather than a number 0-6. Set it for a chip whose data sheet
says so, such as the RX8130CE.

twelve-hour-bit:
type: uint8-array
description: >
[register mask]: a bit outside the time block that reads set in
12-hour mode, where clearing it makes the chip convert its own
hours to 24-hour, such as the RV3028's Control 2 12_24. Once the
chip is cleanly identified, a set bit is cleared before a time is
taken from it. No time is taken if the register cannot be read or
reads FFh, or if the write or the re-read fails.

rv3028-eeprom-config:
type: uint8-array
description: >
Expand All @@ -80,11 +90,12 @@ properties:
it, off once the store has disabled it, until a retry or that
refresh. The store is retried up to 3 times, 10 minutes apart,
identifying the chip again first; a boot whose read could not
identify it cleanly waits for those retries. The time is read at
boot and written on each sync with the chip's backup switch flag
cleared before and checked after. A write sets year 00h first and
the real year last, so a write cut by a power loss reads as "time
not yet set", and a write not confirmed is repeated every 5 s, up
to 12 times. Set it only on a board whose backup supply is known:
trickle charging suits a supercapacitor or rechargeable cell,
never a primary cell.
identify it cleanly, or whose 12-hour clear failed, waits for those
retries. The time is read at
boot and written on each sync within 2000-2099 with the chip's
backup switch flag cleared before and checked after. A write sets
year 00h first and the real year last, so a write cut by a power
loss reads as "time not yet set", and a write not confirmed is
repeated every 5 s, up to 12 times. Set it only on a board whose
backup supply is known: trickle charging suits a supercapacitor or
rechargeable cell, never a primary cell.
12 changes: 10 additions & 2 deletions zephcore/helpers/ui-joystick/joystick_ui_task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,7 @@ void JoystickUITask::loop()
bool consumed = false;
if (key == KEY_BUZZ_TOGGLE) {
toggleBuzzer();
showAlert(isBuzzerQuiet() ? "Buzzer off" : "Buzzer on", 1000);
consumed = true;
} else if (key == KEY_GPS_TOGGLE) {
toggleGPS();
Expand Down Expand Up @@ -995,8 +996,15 @@ bool JoystickUITask::getGPSState() const

void JoystickUITask::toggleGPS()
{
if (!gps_is_available()) return;
mesh_gps_set_enabled(!gps_is_enabled());
if (!gps_is_available()) {
showAlert("No GPS", 1000);
return;
}
/* The mesh loop applies the change later, so getGPSState() is still
* the old state here: the alert names the requested one. */
bool enable = !gps_is_enabled();
mesh_gps_set_enabled(enable);
showAlert(enable ? "GPS enabled" : "GPS disabled", 1000);
}

uint32_t JoystickUITask::getGpsDutySec() const
Expand Down
1 change: 1 addition & 0 deletions zephcore/helpers/ui-joystick/joystick_ui_task.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ class JoystickUITask {
void toggleBuzzer();
bool isGPSAvailable() const;
bool getGPSState() const;
/* Requests the opposite state and shows the alert for it. */
void toggleGPS();
/* GPS duty-cycle interval, seconds (0 = always on). Steps through a fixed
* preset ladder (step = +1/-1) rather than raw seconds, since the valid
Expand Down
1 change: 0 additions & 1 deletion zephcore/helpers/ui-joystick/screens/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,6 @@ bool GPSSettingsScreen::handleInput(char key)
if (key == KEY_ENTER) {
if (_selected == 0) {
_task->toggleGPS();
_task->showAlert(_task->getGPSState() ? "GPS enabled" : "GPS disabled", 1000);
} else {
_task->adjustGpsDuty(1);
}
Expand Down
Loading