Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions include/v2/hal/display_orientation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @file display_orientation.h
* @brief Abstract control over the panel mounting orientation.
*
*/
#pragma once

namespace pocketpd {

class DisplayOrientation {
public:
virtual ~DisplayOrientation() = default;

/**
* @brief Rotate all subsequent rendering by 180 degrees (true) or restore the native
* orientation (false). Takes effect on the next flush.
*/
virtual void set_flipped(bool flipped) = 0;
};

} // namespace pocketpd
3 changes: 2 additions & 1 deletion include/v2/hal/eeprom.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ namespace pocketpd {
struct Preferences {
bool skip_picker_on_boot = false;
bool voltage_comp_enabled = false;
bool flip_display = false;
};

static constexpr uint8_t PREFERENCES_LAYOUT_VERSION = 2;
static constexpr uint8_t PREFERENCES_LAYOUT_VERSION = 3;
static constexpr size_t SIZE = sizeof(Preferences);
static constexpr size_t EEPROM_PREFERENCES_BYTES = 1 + SIZE + 1;

Expand Down
8 changes: 7 additions & 1 deletion include/v2/hal/u8g2_display.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
#include "clib/u8g2.h"
#include <U8g2lib.h>

#include "v2/hal/display_orientation.h"

namespace pocketpd {

class U8g2Display : public tempo::Display {
class U8g2Display : public tempo::Display, public DisplayOrientation {
private:
U8G2_SSD1306_128X64_NONAME_F_HW_I2C m_u8g2{U8G2_R0, U8X8_PIN_NONE};

Expand Down Expand Up @@ -71,6 +73,10 @@ namespace pocketpd {
void draw_box(uint8_t x, uint8_t y, uint8_t w, uint8_t h) override {
m_u8g2.drawBox(x, y, w, h);
}

void set_flipped(bool flipped) override {
m_u8g2.setDisplayRotation(flipped ? U8G2_R2 : U8G2_R0);
}
};

} // namespace pocketpd
13 changes: 13 additions & 0 deletions include/v2/preferences_store.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ namespace pocketpd {
m_preferences.voltage_comp_enabled = v;
m_dirty = true;
}

bool flip_display() const {
return m_preferences.flip_display;
}

void set_flip_display(bool v) {
if (m_preferences.flip_display == v) {
return;
}

m_preferences.flip_display = v;
m_dirty = true;
}
};

} // namespace pocketpd
16 changes: 13 additions & 3 deletions include/v2/stages/settings_stage.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include "v2/app.h"
#include "v2/events.h"
#include "v2/hal/display_orientation.h"
#include "v2/preferences_store.h"
#include "v2/ui/table_view.h"

Expand All @@ -27,19 +28,22 @@ namespace pocketpd {
enum class Item : uint8_t {
SKIP_PICKER,
VOLTAGE_COMP,
FLIP_DISPLAY,
};

struct SettingItem {
Item item;
const char* label;
};

static constexpr std::array<SettingItem, 2> ITEMS = {{
static constexpr std::array<SettingItem, 3> ITEMS = {{
{Item::SKIP_PICKER, "Skip picker"},
{Item::VOLTAGE_COMP, "Voltage comp"},
{Item::FLIP_DISPLAY, "Flip display"},
}};

Display& m_display;
DisplayOrientation& m_orientation;
PreferencesStore& m_prefs;
TableView m_table{};

Expand All @@ -53,6 +57,8 @@ namespace pocketpd {
return m_prefs.skip_picker_on_boot();
case Item::VOLTAGE_COMP:
return m_prefs.voltage_comp_enabled();
case Item::FLIP_DISPLAY:
return m_prefs.flip_display();
}
return false;
}
Expand All @@ -65,6 +71,10 @@ namespace pocketpd {
case Item::VOLTAGE_COMP:
m_prefs.set_voltage_comp_enabled(!m_prefs.voltage_comp_enabled());
break;
case Item::FLIP_DISPLAY:
m_prefs.set_flip_display(!m_prefs.flip_display());
m_orientation.set_flipped(m_prefs.flip_display());
break;
}

draw();
Expand All @@ -88,8 +98,8 @@ namespace pocketpd {
}

public:
SettingsStage(Display& display, PreferencesStore& prefs)
: m_display(display), m_prefs(prefs) {}
SettingsStage(Display& display, DisplayOrientation& orientation, PreferencesStore& prefs)
: m_display(display), m_orientation(orientation), m_prefs(prefs) {}

void on_enter(Conductor&, uint32_t) override {
m_table.reset();
Expand Down
26 changes: 24 additions & 2 deletions include/v2/tasks/button_task.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "v2/input/button_gesture.h"
#include "v2/input/two_buttons_gesture.h"
#include "v2/pocketpd.h"
#include "v2/preferences_store.h"

namespace pocketpd {

Expand All @@ -32,19 +33,39 @@ namespace pocketpd {

std::array<DetectorRef, 3> m_detector_refs;
TwoButtonsGestureDetector m_combo_detector;
const PreferencesStore& m_prefs;

static constexpr uint32_t POLL_PERIOD_MS = 5;
static constexpr size_t IDX_ENCODER = 0;
static constexpr size_t IDX_L = 1;
static constexpr size_t IDX_R = 2;

/**
* @brief Flipped display means the unit is held upside down, so the physical L button
* sits on the user's right. Swap L/R at publish time; ENCODER and L_R are symmetric.
*/
ButtonId published_id(ButtonId id) const {
if (!m_prefs.flip_display()) {
return id;
}
switch (id) {
case ButtonId::L:
return ButtonId::R;
case ButtonId::R:
return ButtonId::L;
default:
return id;
}
}

public:
static constexpr const char* LOG_TAG = "ButtonTask";

ButtonTask(
tempo::ButtonInput& btn_encoder,
tempo::ButtonInput& btn_l,
tempo::ButtonInput& btn_r,
const PreferencesStore& prefs,
ButtonGestureConfig gesture_config = {}
)
: App::BackgroundTask(POLL_PERIOD_MS),
Expand All @@ -65,7 +86,8 @@ namespace pocketpd {
ButtonGestureDetector{gesture_config},
},
},
m_combo_detector(gesture_config) {}
m_combo_detector(gesture_config),
m_prefs(prefs) {}

const char* name() const override {
return "ButtonTask";
Expand Down Expand Up @@ -117,7 +139,7 @@ namespace pocketpd {
const uint32_t duration = ref.detector.duration(now_ms);
const char* msg = "button={} gesture={} hold_duration={}";
log.debug(msg, to_string(ref.id), to_string(gesture.value()), duration);
publish(ButtonEvent{ref.id, gesture.value()});
publish(ButtonEvent{published_id(ref.id), gesture.value()});
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ namespace pocketpd {
NormalStage normal_stage(u8g2_display, pd_sink, output_gate);
EnergyStage energy_stage(u8g2_display, output_gate);
MenuStage menu_stage(u8g2_display);
SettingsStage settings_stage(u8g2_display, prefs);
SettingsStage settings_stage(u8g2_display, u8g2_display, prefs);

// —— Tasks

ButtonTask button_task(encoder_button, l_button, r_button);
ButtonTask button_task(encoder_button, l_button, r_button, prefs);
EncoderTask encoder_task(encoder);
SensorTask sensor_task{power_monitor, supply_voltage_source};
EnergyTask energy_task{output_gate};
Expand Down Expand Up @@ -100,6 +100,7 @@ void setup() {
if (!prefs.load()) {
Serial.println("[main] preferences load failed; defaults restored");
}
u8g2_display.set_flipped(prefs.flip_display());
encoder.begin();

app.register_stage(boot_stage);
Expand Down
37 changes: 37 additions & 0 deletions test/mocks/MockDisplayOrientation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#pragma once

#include <gmock/gmock.h>

#include "v2/hal/display_orientation.h"

namespace pocketpd {

class MockDisplayOrientation : public DisplayOrientation {
public:
MOCK_METHOD(void, set_flipped, (bool), (override));
};

/**
* @brief Scripted DisplayOrientation for tests that want to read back the resulting state.
*/
class FakeDisplayOrientation : public DisplayOrientation {
private:
bool m_flipped = false;
int m_call_count = 0;

public:
void set_flipped(bool flipped) override {
m_flipped = flipped;
++m_call_count;
}

bool flipped() const {
return m_flipped;
}

int call_count() const {
return m_call_count;
}
};

} // namespace pocketpd
20 changes: 20 additions & 0 deletions test/test_v2_eeprom/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ TEST(EepromCodec, DefaultsHaveVoltageCompOff) {
EXPECT_FALSE(p.voltage_comp_enabled);
}

TEST(EepromCodec, FlipDisplayFieldRoundTrips) {
std::array<uint8_t, EEPROM_PREFERENCES_BYTES> buf{};

Preferences in{
.skip_picker_on_boot = false,
.voltage_comp_enabled = false,
.flip_display = true,
};
encode_preferences(in, buf.data());

Preferences out{};
EXPECT_TRUE(decode_preferences(buf.data(), out));
EXPECT_TRUE(out.flip_display);
}

TEST(EepromCodec, DefaultsHaveFlipDisplayOff) {
Preferences p{};
EXPECT_FALSE(p.flip_display);
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
Expand Down
Loading
Loading