blob: cb38e4eced0fed4352096e9e52d1071678d624c0 [file]
// 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 <cmath>
#include "pw_assert/check.h"
#include "pw_log/log.h"
#include "pw_string/format.h"
namespace sense {
void LedOutputStateMachine::UpdateLed(uint8_t red,
uint8_t green,
uint8_t blue,
uint8_t brightness) {
if (red_ != red || green_ != green || blue_ != blue) {
red_ = red;
green_ = green;
blue_ = blue;
if (state_ == kPassthrough) {
led_->SetColor(red_, green_, blue_);
}
}
if (brightness_ != brightness) {
brightness_ = brightness;
if (state_ == kPassthrough) {
led_->SetBrightness(brightness_);
}
}
}
StateManager::StateManager(PubSub& pubsub, PolychromeLed& led)
: pubsub_(&pubsub),
led_(led, brightness_),
state_(*this),
demo_mode_timer_([this](auto) { state_.get().DemoModeTimerExpired(); }) {
pubsub_->Subscribe([this](Event event) { Update(event); });
}
void StateManager::Update(Event event) {
switch (static_cast<EventType>(event.index())) {
case kAirQuality:
last_air_quality_score_ = std::get<AirQuality>(event).score;
break;
case kAlarmStateChange:
state_.get().AlarmStateChanged(std::get<AlarmStateChange>(event).alarm);
break;
case kButtonA:
HandleButtonPress(std::get<ButtonA>(event).pressed(),
&State::ButtonAReleased);
break;
case kButtonB:
HandleButtonPress(std::get<ButtonB>(event).pressed(),
&State::ButtonBReleased);
break;
case kButtonX:
HandleButtonPress(std::get<ButtonX>(event).pressed(),
&State::ButtonXReleased);
break;
case kButtonY:
HandleButtonPress(std::get<ButtonY>(event).pressed(),
&State::ButtonYReleased);
break;
case kLedValueAirQualityMode:
state_.get().AirQualityModeLedValue(
std::get<LedValueAirQualityMode>(event));
break;
case kMorseCodeValue:
state_.get().MorseCodeEdge(std::get<MorseCodeValue>(event));
break;
case kAmbientLightSample:
UpdateAverageAmbientLight(std::get<AmbientLightSample>(event).sample_lux);
state_.get().AmbientLightUpdate();
break;
case kTimerRequest:
case kTimerExpired:
case kProximitySample:
case kAlarmSilenceRequest:
case kAirQualityThreshold:
case kMorseEncodeRequest:
case kProximityStateChange:
break; // ignore these events
}
}
void StateManager::HandleButtonPress(bool pressed, void (State::* function)()) {
if (pressed) {
led_.Override(0xffffff, 160); // Bright white while pressed.
} else {
led_.EndOverride();
(state_.get().*function)();
}
}
void StateManager::StartMorseReadout(bool repeat) {
pw::Status status = pw::string::FormatOverwrite(
air_quality_score_string_, "%hu", last_air_quality_score_);
PW_CHECK_OK(status);
pubsub_->Publish(MorseEncodeRequest{.message = air_quality_score_string_,
.repeat = repeat ? 0u : 1u});
PW_LOG_INFO("Current air quality score: %hu", last_air_quality_score_);
}
void StateManager::DisplayThreshold() {
led_.SetColor(AirSensor::GetLedValue(current_threshold_));
}
void StateManager::IncrementThreshold(
pw::chrono::SystemClock::duration timeout) {
demo_mode_timer_.Cancel();
uint16_t candidate_threshold = current_threshold_ + kThresholdIncrement;
current_threshold_ =
candidate_threshold < kMaxThreshold ? candidate_threshold : kMaxThreshold;
pubsub_->Publish(AirQualityThreshold{
.alarm = current_threshold_,
.silence =
static_cast<uint16_t>(current_threshold_ + kThresholdIncrement),
});
DisplayThreshold();
demo_mode_timer_.InvokeAfter(timeout);
}
void StateManager::DecrementThreshold(
pw::chrono::SystemClock::duration timeout) {
demo_mode_timer_.Cancel();
if (current_threshold_ > 0) {
current_threshold_ -= kThresholdIncrement;
}
pubsub_->Publish(AirQualityThreshold{
.alarm = current_threshold_,
.silence =
static_cast<uint16_t>(current_threshold_ + kThresholdIncrement),
});
DisplayThreshold();
demo_mode_timer_.InvokeAfter(timeout);
}
void StateManager::UpdateAverageAmbientLight(float ambient_light_sample_lux) {
static constexpr float kDecayFactor = 0.25;
if (std::isnan(ambient_light_lux_)) {
ambient_light_lux_ = ambient_light_sample_lux;
} else {
ambient_light_lux_ +=
(ambient_light_sample_lux - ambient_light_lux_) * kDecayFactor;
}
}
void StateManager::UpdateBrightnessFromAmbientLight() {
static constexpr float kMinLux = 40.f;
static constexpr float kMaxLux = 3000.f;
if (ambient_light_lux_ < kMinLux) {
brightness_ = kMinBrightness;
} else if (ambient_light_lux_ > kMaxLux) {
brightness_ = kMaxBrightness;
} else {
constexpr float kBrightnessRange = kMaxBrightness - kMinBrightness;
brightness_ = static_cast<uint8_t>(
std::lround((ambient_light_lux_ - kMinLux) / (kMaxLux - kMinLux) *
kBrightnessRange) +
kMinBrightness);
}
PW_LOG_DEBUG(
"Ambient light: mean=%.1f, led=%hhu", ambient_light_lux_, brightness_);
led_.SetBrightness(brightness_);
}
void StateManager::LogStateChange(const char* old_state) const {
PW_LOG_INFO("StateManager: %s -> %s", old_state, state_.get().name());
}
} // namespace sense