Public Access
Light the screen (dimmed) while a Notification's Toast shows
An Off screen turns on dimmed for the Toast's duration so the user can see what beeped. It isn't user activity: the timeouts aren't restarted, and a key pressed meanwhile reaches the App. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -12,6 +12,12 @@ bool PowerPolicy::activity(uint32_t nowMs) {
|
|||||||
ScreenState PowerPolicy::update(uint32_t nowMs) {
|
ScreenState PowerPolicy::update(uint32_t nowMs) {
|
||||||
uint32_t idle = nowMs - lastActivityMs_;
|
uint32_t idle = nowMs - lastActivityMs_;
|
||||||
state_ = idle >= offMs_ ? ScreenState::Off : idle >= dimMs_ ? ScreenState::Dimmed : ScreenState::On;
|
state_ = idle >= offMs_ ? ScreenState::Off : idle >= dimMs_ ? ScreenState::Dimmed : ScreenState::On;
|
||||||
|
if (notifying_) {
|
||||||
|
if (static_cast<int32_t>(nowMs - notifyUntilMs_) >= 0)
|
||||||
|
notifying_ = false;
|
||||||
|
else if (state_ == ScreenState::Off)
|
||||||
|
state_ = ScreenState::Dimmed;
|
||||||
|
}
|
||||||
return state_;
|
return state_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ class PowerPolicy {
|
|||||||
// Records a key press. Returns true if the key only woke an Off screen and must not reach the App.
|
// Records a key press. Returns true if the key only woke an Off screen and must not reach the App.
|
||||||
bool activity(uint32_t nowMs);
|
bool activity(uint32_t nowMs);
|
||||||
|
|
||||||
|
// A Notification is showing until untilMs: an Off screen lights up Dimmed meanwhile. It's not
|
||||||
|
// user activity, so it doesn't restart the timeouts.
|
||||||
|
void notify(uint32_t nowMs, uint32_t untilMs) {
|
||||||
|
(void)nowMs;
|
||||||
|
notifyUntilMs_ = untilMs;
|
||||||
|
notifying_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
ScreenState update(uint32_t nowMs);
|
ScreenState update(uint32_t nowMs);
|
||||||
ScreenState state() const { return state_; }
|
ScreenState state() const { return state_; }
|
||||||
|
|
||||||
@@ -26,6 +34,8 @@ class PowerPolicy {
|
|||||||
uint32_t dimMs_;
|
uint32_t dimMs_;
|
||||||
uint32_t offMs_;
|
uint32_t offMs_;
|
||||||
uint32_t lastActivityMs_ = 0;
|
uint32_t lastActivityMs_ = 0;
|
||||||
|
uint32_t notifyUntilMs_ = 0;
|
||||||
|
bool notifying_ = false;
|
||||||
ScreenState state_ = ScreenState::On;
|
ScreenState state_ = ScreenState::On;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ void setup() {
|
|||||||
power = new PowerService(settings);
|
power = new PowerService(settings);
|
||||||
clockService = new ClockService(settings, bus);
|
clockService = new ClockService(settings, bus);
|
||||||
notifier = new Notifier(bus, settings);
|
notifier = new Notifier(bus, settings);
|
||||||
|
notifier->onShow = [](uint32_t now, uint32_t until) { power->onNotification(now, until); };
|
||||||
services.add(*power);
|
services.add(*power);
|
||||||
services.add(*clockService);
|
services.add(*clockService);
|
||||||
services.add(*battery);
|
services.add(*battery);
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ class PowerService : public Service {
|
|||||||
// Call for every key press before routing it. Returns true if the key must not reach the App.
|
// Call for every key press before routing it. Returns true if the key must not reach the App.
|
||||||
bool onKey(uint32_t nowMs) { return policy_.activity(nowMs); }
|
bool onKey(uint32_t nowMs) { return policy_.activity(nowMs); }
|
||||||
|
|
||||||
|
// A Toast is showing until untilMs: light an Off screen (dimmed) so it can be read.
|
||||||
|
void onNotification(uint32_t nowMs, uint32_t untilMs) { policy_.notify(nowMs, untilMs); }
|
||||||
|
|
||||||
ScreenState screen() const { return applied_; }
|
ScreenState screen() const { return applied_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ void Notifier::update(uint32_t nowMs) {
|
|||||||
if (shown && shown->id != alertedId_) {
|
if (shown && shown->id != alertedId_) {
|
||||||
alertedId_ = shown->id;
|
alertedId_ = shown->id;
|
||||||
alert(shown->level, nowMs);
|
alert(shown->level, nowMs);
|
||||||
|
if (onShow) onShow(nowMs, nowMs + kToastMs);
|
||||||
}
|
}
|
||||||
if (ledOn_ && static_cast<int32_t>(nowMs - ledOffAtMs_) >= 0) {
|
if (ledOn_ && static_cast<int32_t>(nowMs - ledOffAtMs_) >= 0) {
|
||||||
rgbLedWrite(kLedPin, 0, 0, 0);
|
rgbLedWrite(kLedPin, 0, 0, 0);
|
||||||
|
|||||||
+7
-1
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include "event_bus.h"
|
#include "event_bus.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "toast_queue.h"
|
#include "toast_queue.h"
|
||||||
@@ -14,11 +16,15 @@ class Notifier {
|
|||||||
void update(uint32_t nowMs); // alerts for a newly shown Toast, ends the LED flash
|
void update(uint32_t nowMs); // alerts for a newly shown Toast, ends the LED flash
|
||||||
ToastQueue& toasts() { return toasts_; }
|
ToastQueue& toasts() { return toasts_; }
|
||||||
|
|
||||||
|
// Called when a Toast appears, with the time it will disappear (e.g. to light the screen).
|
||||||
|
std::function<void(uint32_t nowMs, uint32_t untilMs)> onShow;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void alert(NotificationLevel level, uint32_t nowMs);
|
void alert(NotificationLevel level, uint32_t nowMs);
|
||||||
|
|
||||||
const Settings& settings_;
|
const Settings& settings_;
|
||||||
ToastQueue toasts_{3000, 8};
|
static constexpr uint32_t kToastMs = 3000;
|
||||||
|
ToastQueue toasts_{kToastMs, 8};
|
||||||
uint32_t ledOffAtMs_ = 0;
|
uint32_t ledOffAtMs_ = 0;
|
||||||
bool ledOn_ = false;
|
bool ledOn_ = false;
|
||||||
uint32_t alertedId_ = 0;
|
uint32_t alertedId_ = 0;
|
||||||
|
|||||||
@@ -38,6 +38,39 @@ void test_new_timeouts_apply_immediately() {
|
|||||||
TEST_ASSERT_EQUAL(static_cast<int>(ScreenState::Off), static_cast<int>(p.update(20000)));
|
TEST_ASSERT_EQUAL(static_cast<int>(ScreenState::Off), static_cast<int>(p.update(20000)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_notification_lights_an_off_screen_dimmed_until_it_ends() {
|
||||||
|
PowerPolicy p(30000, 60000);
|
||||||
|
p.activity(0);
|
||||||
|
p.update(70000); // off
|
||||||
|
p.notify(70000, 73000);
|
||||||
|
TEST_ASSERT_EQUAL(static_cast<int>(ScreenState::Dimmed), static_cast<int>(p.update(70001)));
|
||||||
|
TEST_ASSERT_EQUAL(static_cast<int>(ScreenState::Dimmed), static_cast<int>(p.update(72999)));
|
||||||
|
TEST_ASSERT_EQUAL(static_cast<int>(ScreenState::Off), static_cast<int>(p.update(73000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_notification_does_not_count_as_activity() {
|
||||||
|
PowerPolicy p(30000, 60000);
|
||||||
|
p.activity(0);
|
||||||
|
p.notify(25000, 28000);
|
||||||
|
TEST_ASSERT_EQUAL(static_cast<int>(ScreenState::Dimmed), static_cast<int>(p.update(30000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_notification_never_brightens_or_dims_an_on_screen() {
|
||||||
|
PowerPolicy p(30000, 60000);
|
||||||
|
p.activity(0);
|
||||||
|
p.notify(1000, 4000);
|
||||||
|
TEST_ASSERT_EQUAL(static_cast<int>(ScreenState::On), static_cast<int>(p.update(2000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_key_on_a_notification_lit_screen_acts() {
|
||||||
|
PowerPolicy p(30000, 60000);
|
||||||
|
p.activity(0);
|
||||||
|
p.update(70000);
|
||||||
|
p.notify(70000, 73000);
|
||||||
|
p.update(70001);
|
||||||
|
TEST_ASSERT_FALSE(p.activity(71000)); // the screen is visible: the key reaches the App
|
||||||
|
}
|
||||||
|
|
||||||
void test_short_press_reported_on_release() {
|
void test_short_press_reported_on_release() {
|
||||||
PowerButton b(2000);
|
PowerButton b(2000);
|
||||||
TEST_ASSERT_EQUAL(static_cast<int>(ButtonAction::None), static_cast<int>(b.update(true, 0)));
|
TEST_ASSERT_EQUAL(static_cast<int>(ButtonAction::None), static_cast<int>(b.update(true, 0)));
|
||||||
@@ -59,6 +92,10 @@ int main() {
|
|||||||
RUN_TEST(test_key_on_dimmed_screen_wakes_and_still_acts);
|
RUN_TEST(test_key_on_dimmed_screen_wakes_and_still_acts);
|
||||||
RUN_TEST(test_key_on_off_screen_only_wakes);
|
RUN_TEST(test_key_on_off_screen_only_wakes);
|
||||||
RUN_TEST(test_new_timeouts_apply_immediately);
|
RUN_TEST(test_new_timeouts_apply_immediately);
|
||||||
|
RUN_TEST(test_notification_lights_an_off_screen_dimmed_until_it_ends);
|
||||||
|
RUN_TEST(test_notification_does_not_count_as_activity);
|
||||||
|
RUN_TEST(test_notification_never_brightens_or_dims_an_on_screen);
|
||||||
|
RUN_TEST(test_key_on_a_notification_lit_screen_acts);
|
||||||
RUN_TEST(test_short_press_reported_on_release);
|
RUN_TEST(test_short_press_reported_on_release);
|
||||||
RUN_TEST(test_long_press_fires_once_while_still_held);
|
RUN_TEST(test_long_press_fires_once_while_still_held);
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
|
|||||||
Reference in New Issue
Block a user