blob: a76cf19aa4c033224063134fbcd91bd8b632937d [file] [log] [blame]
// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "modules/state_manager/state_manager.h"
#include <array>
#include "modules/led/polychrome_led_fake.h"
#include "modules/pubsub/pubsub.h"
#include "modules/pubsub/pubsub_events.h"
#include "modules/worker/test_worker.h"
#include "pw_chrono/system_clock.h"
#include "pw_thread/sleep.h"
#include "pw_unit_test/framework.h"
namespace sense {
using namespace std::chrono_literals;
class StateManagerTest : public ::testing::Test {
protected:
StateManagerTest()
: ::testing::Test(),
pubsub_(worker_),
state_manager_(pubsub_, led_),
event_(TimerExpired{.token = 0}) {}
void SetUp() override {
reference_led_.SetColor(0);
reference_led_.SetBrightness(AmbientLightAdjustedLed::kDefaultBrightness);
reference_led_.Enable();
reference_led_.TurnOn();
led_.EnableWaiting();
}
void SetExpectedColor(uint16_t air_quality) {
auto led_value = AirSensor::GetLedValue(air_quality);
reference_led_.SetColor(led_value.r(), led_value.g(), led_value.b());
}
void SetExpectedColor(AirSensor::Score air_quality) {
SetExpectedColor(static_cast<uint16_t>(air_quality));
}
void SetExpectedBrightness(uint8_t brightness) {
reference_led_.SetBrightness(brightness);
}
uint16_t GetExpectedRed() { return reference_led_.red(); }
uint16_t GetExpectedGreen() { return reference_led_.green(); }
uint16_t GetExpectedBlue() { return reference_led_.blue(); }
void TearDown() override { worker_.Stop(); }
TestWorker<> worker_;
GenericPubSubBuffer<Event, 20, 10> pubsub_;
PolychromeLedFake led_;
StateManager state_manager_;
Event event_;
pw::sync::ThreadNotification morse_encode_request_;
pw::sync::ThreadNotification timer_request_;
pw::sync::ThreadNotification state_update_notification_;
private:
PolychromeLedFake reference_led_;
};
TEST_F(StateManagerTest, UpdateAirQuality) {
uint16_t air_quality = 800;
ASSERT_TRUE(pubsub_.Publish(AirQuality{.score = air_quality}));
led_.Await();
SetExpectedColor(air_quality);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
}
TEST_F(StateManagerTest, MorseReadout) {
ASSERT_TRUE(pubsub_.SubscribeTo<SenseState>(
[this](SenseState) { state_update_notification_.release(); }));
// The manager needs at least one score to switch to Morce code mode.
uint16_t air_quality = 800;
ASSERT_TRUE(pubsub_.Publish(AirQuality{.score = air_quality}));
led_.Await();
state_update_notification_.acquire();
ASSERT_TRUE(pubsub_.SubscribeTo<MorseEncodeRequest>(
[this](MorseEncodeRequest) { morse_encode_request_.release(); }));
ASSERT_TRUE(pubsub_.Publish(ButtonY(true)));
morse_encode_request_.acquire();
state_update_notification_.acquire();
// Responds to Morse code edges.
EXPECT_TRUE(led_.is_on());
ASSERT_TRUE(pubsub_.Publish(
MorseCodeValue{.turn_on = false, .message_finished = false}));
led_.Await();
EXPECT_FALSE(led_.is_on());
}
TEST_F(StateManagerTest, UpdateAirQualityAndTriggerAlarm) {
ASSERT_TRUE(pubsub_.SubscribeTo<MorseEncodeRequest>(
[this](MorseEncodeRequest) { morse_encode_request_.release(); }));
uint16_t air_quality = 100;
ASSERT_TRUE(pubsub_.Publish(AirQuality{.score = air_quality}));
led_.Await();
SetExpectedColor(air_quality);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
// Alarm triggered; responds to Morse code edges.
morse_encode_request_.acquire();
ASSERT_TRUE(pubsub_.Publish(
MorseCodeValue{.turn_on = false, .message_finished = false}));
led_.Await();
EXPECT_FALSE(led_.is_on());
}
TEST_F(StateManagerTest, UpdateAirQualityAndDisableAlarm) {
ASSERT_TRUE(pubsub_.SubscribeTo<MorseEncodeRequest>(
[this](MorseEncodeRequest) { morse_encode_request_.release(); }));
ASSERT_TRUE(pubsub_.SubscribeTo<SenseState>(
[this](SenseState) { state_update_notification_.release(); }));
// Alarm triggered; responds to Morse code edges.
uint16_t air_quality = 200;
ASSERT_TRUE(pubsub_.Publish(AirQuality{.score = air_quality}));
led_.Await();
state_update_notification_.acquire();
// Alarm triggered; responds to Morse code edges.
morse_encode_request_.acquire();
ASSERT_TRUE(pubsub_.Publish(
MorseCodeValue{.turn_on = false, .message_finished = true}));
led_.Await();
EXPECT_FALSE(led_.is_on());
air_quality = 1000;
ASSERT_TRUE(pubsub_.Publish(AirQuality{.score = air_quality}));
led_.Await();
state_update_notification_.acquire();
// The state manager updates the LED before disabling the alarm. There's no
// other event to synchronize on, so send another update to synchronize on
// and ensure the state change is complete.
ASSERT_TRUE(pubsub_.Publish(AirQuality{.score = air_quality}));
led_.Await();
state_update_notification_.acquire();
// Don't check for a specific color. The air quality score is smoothed
// exponentially, so only check that the alarm is disabled and the LED is on.
EXPECT_TRUE(led_.is_on());
}
TEST_F(StateManagerTest, SilenceAlarm) {
ASSERT_TRUE(pubsub_.SubscribeTo<MorseEncodeRequest>(
[this](MorseEncodeRequest) { morse_encode_request_.release(); }));
ASSERT_TRUE(pubsub_.SubscribeTo<SenseState>(
[this](SenseState) { state_update_notification_.release(); }));
// Trigger an alarm.
ASSERT_TRUE(pubsub_.Publish(AirQuality{.score = 100}));
led_.Await();
state_update_notification_.acquire();
// Alarm triggered; responds to Morse code edges.
morse_encode_request_.acquire();
EXPECT_TRUE(led_.is_on());
ASSERT_TRUE(pubsub_.Publish(
MorseCodeValue{.turn_on = false, .message_finished = false}));
led_.Await();
EXPECT_FALSE(led_.is_on());
// Disable alarm.
ASSERT_TRUE(pubsub_.Publish(ButtonX(true)));
ASSERT_TRUE(pubsub_.Publish(AirQuality{.score = 100}));
led_.Await();
state_update_notification_.acquire();
// Alarm disabled; does not respond to Morse code events
EXPECT_TRUE(led_.is_on());
ASSERT_TRUE(pubsub_.Publish(
MorseCodeValue{.turn_on = false, .message_finished = false}));
EXPECT_FALSE(led_.TryAwaitFor(100ms));
EXPECT_TRUE(led_.is_on());
}
TEST_F(StateManagerTest, IncrementThresholdAndTimeout) {
ASSERT_TRUE(pubsub_.SubscribeTo<TimerRequest>([this](TimerRequest request) {
event_ = request;
timer_request_.release();
}));
ASSERT_TRUE(pubsub_.SubscribeTo<MorseEncodeRequest>(
[this](MorseEncodeRequest) { morse_encode_request_.release(); }));
ASSERT_TRUE(pubsub_.SubscribeTo<SenseState>(
[this](SenseState) { state_update_notification_.release(); }));
ASSERT_TRUE(pubsub_.Publish(ButtonB(true)));
led_.Await();
SetExpectedColor(AirSensor::Score::kYellow);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
timer_request_.acquire();
TimerRequest request = std::get<TimerRequest>(event_);
EXPECT_EQ(request.token, StateManager::kThresholdModeToken);
EXPECT_EQ(request.timeout_s, StateManager::kThresholdModeTimeout);
ASSERT_TRUE(pubsub_.Publish(ButtonA(true)));
led_.Await();
SetExpectedColor(AirSensor::Score::kLightGreen);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
timer_request_.acquire();
request = std::get<TimerRequest>(event_);
EXPECT_EQ(request.token, StateManager::kThresholdModeToken);
EXPECT_EQ(request.timeout_s, StateManager::kThresholdModeTimeout);
ASSERT_TRUE(pubsub_.Publish(ButtonA(true)));
led_.Await();
SetExpectedColor(AirSensor::Score::kGreen);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
timer_request_.acquire();
request = std::get<TimerRequest>(event_);
EXPECT_EQ(request.token, StateManager::kThresholdModeToken);
EXPECT_EQ(request.timeout_s, StateManager::kThresholdModeTimeout);
ASSERT_TRUE(pubsub_.Publish(ButtonA(true)));
led_.Await();
SetExpectedColor(AirSensor::Score::kBlueGreen);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
timer_request_.acquire();
request = std::get<TimerRequest>(event_);
EXPECT_EQ(request.token, StateManager::kThresholdModeToken);
EXPECT_EQ(request.timeout_s, StateManager::kThresholdModeTimeout);
ASSERT_TRUE(pubsub_.Publish(ButtonA(true)));
led_.Await();
SetExpectedColor(AirSensor::Score::kCyan);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
timer_request_.acquire();
request = std::get<TimerRequest>(event_);
EXPECT_EQ(request.token, StateManager::kThresholdModeToken);
EXPECT_EQ(request.timeout_s, StateManager::kThresholdModeTimeout);
// At max threshold; cannot increase further.
ASSERT_TRUE(pubsub_.Publish(ButtonA(true)));
SetExpectedColor(AirSensor::Score::kCyan);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
timer_request_.acquire();
request = std::get<TimerRequest>(event_);
EXPECT_EQ(request.token, StateManager::kThresholdModeToken);
EXPECT_EQ(request.timeout_s, StateManager::kThresholdModeTimeout);
// Now time out of the threshold mode.
ASSERT_TRUE(pubsub_.Publish(
TimerExpired{.token = StateManager::kThresholdModeToken}));
morse_encode_request_.acquire();
state_update_notification_.acquire();
}
TEST_F(StateManagerTest, DecrementThresholdAndTimeout) {
ASSERT_TRUE(pubsub_.SubscribeTo<TimerRequest>([this](TimerRequest request) {
event_ = request;
timer_request_.release();
}));
ASSERT_TRUE(pubsub_.SubscribeTo<MorseEncodeRequest>(
[this](MorseEncodeRequest) { morse_encode_request_.release(); }));
ASSERT_TRUE(pubsub_.SubscribeTo<SenseState>(
[this](SenseState) { state_update_notification_.release(); }));
ASSERT_TRUE(pubsub_.Publish(ButtonA(true)));
led_.Await();
SetExpectedColor(AirSensor::Score::kYellow);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
timer_request_.acquire();
TimerRequest request = std::get<TimerRequest>(event_);
EXPECT_EQ(request.token, StateManager::kThresholdModeToken);
EXPECT_EQ(request.timeout_s, StateManager::kThresholdModeTimeout);
ASSERT_TRUE(pubsub_.Publish(ButtonB(true)));
led_.Await();
SetExpectedColor(AirSensor::Score::kOrange);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
timer_request_.acquire();
request = std::get<TimerRequest>(event_);
EXPECT_EQ(request.token, StateManager::kThresholdModeToken);
EXPECT_EQ(request.timeout_s, StateManager::kThresholdModeTimeout);
ASSERT_TRUE(pubsub_.Publish(ButtonB(true)));
led_.Await();
SetExpectedColor(AirSensor::Score::kRed);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
timer_request_.acquire();
request = std::get<TimerRequest>(event_);
EXPECT_EQ(request.token, StateManager::kThresholdModeToken);
EXPECT_EQ(request.timeout_s, StateManager::kThresholdModeTimeout);
// At min threshold; cannot decrease further.
ASSERT_TRUE(pubsub_.Publish(ButtonB(true)));
SetExpectedColor(AirSensor::Score::kRed);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
timer_request_.acquire();
request = std::get<TimerRequest>(event_);
EXPECT_EQ(request.token, StateManager::kThresholdModeToken);
EXPECT_EQ(request.timeout_s, StateManager::kThresholdModeTimeout);
// Now time out of the threshold mode.
ASSERT_TRUE(pubsub_.Publish(
TimerExpired{.token = StateManager::kThresholdModeToken}));
morse_encode_request_.acquire();
state_update_notification_.acquire();
}
TEST_F(StateManagerTest, AdjustBrightness) {
ASSERT_TRUE(pubsub_.Publish(AmbientLightSample{.sample_lux = 2000.f}));
led_.Await();
}
TEST_F(StateManagerTest, AdjustBrightnessMin) {
ASSERT_TRUE(pubsub_.Publish(AmbientLightSample{.sample_lux = 20.f}));
led_.Await();
SetExpectedBrightness(AmbientLightAdjustedLed::kMinBrightness);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
}
TEST_F(StateManagerTest, AdjustBrightnessMax) {
ASSERT_TRUE(pubsub_.Publish(AmbientLightSample{.sample_lux = 20000.f}));
led_.Await();
SetExpectedBrightness(AmbientLightAdjustedLed::kMaxBrightness);
EXPECT_EQ(led_.red(), GetExpectedRed());
EXPECT_EQ(led_.green(), GetExpectedGreen());
EXPECT_EQ(led_.blue(), GetExpectedBlue());
}
} // namespace sense