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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ Hold the R button to open the energy screen. It shows power, live voltage and cu

Hold the left button to open the menu.

- **Skip picker:** When true, PocketPD boots straight to the operating screen using the first profile 5V as default instead of stopping at the picker.
- **Voltage comp:** When true, PocketPD watches the load-side voltage and raises the PPS request in 20 mV steps, up to 500 mV, to cancel the drop across the cable and connectors. It runs only while the output is on and a PPS profile is active, and it resets whenever the output is off or you change profiles.
- **Restore profile:** When enabled, PocketPD saves the last profile plus its voltage/current and, on boot, restores them and jumps straight to the operating screen. When switching to another charger, PocketPD defaults to profile picker if the voltage/current fall outside of last profile range.
- **Voltage comp:** When enabled, PocketPD watches the load-side voltage and raises the PPS request in 20 mV steps, up to 500 mV, to cancel the drop across the cable and connectors. It runs only while the output is on and a PPS profile is active, and it resets whenever the output is off or you change profiles.

### Non-PD sources

Expand Down
23 changes: 20 additions & 3 deletions include/v2/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ namespace pocketpd {
uint32_t vbus_mv = 0;
uint32_t current_ma = 0;

LoadReading ema(const LoadReading& sample ) const {
LoadReading ema(const LoadReading& sample) const {
return {
.timestamp_ms = sample.timestamp_ms,
.vbus_mv = Filter::ema(vbus_mv, sample.vbus_mv, LOAD_EMA_DEN, SNAP_MV),
Expand Down Expand Up @@ -137,8 +137,25 @@ namespace pocketpd {
int32_t offset_mv = 0;
};

/**
* @brief Current output profile after any change. `pdo_index = -1` means no
* profile (passthrough). PreferenceTask records it as the last-used profile.
*/
struct ActiveProfileEvent {
bool is_pps = false;
int pdo_index = -1;
int mv = 0;
int ma = 0;
};

using Event = tempo::Events<
PdReadyEvent, ButtonEvent, EncoderEvent, SensorEvent, EnergyEvent,
PpsTargetEvent, CompStateEvent>;
PdReadyEvent,
ButtonEvent,
EncoderEvent,
SensorEvent,
EnergyEvent,
PpsTargetEvent,
CompStateEvent,
ActiveProfileEvent>;

} // namespace pocketpd
32 changes: 24 additions & 8 deletions include/v2/hal/eeprom.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,40 @@

namespace pocketpd {

struct LastProfile {
bool is_pps = false;
uint16_t voltage_mv = 0;
uint16_t current_ma = 0;
uint8_t pdo_index = 0;
};

inline bool operator==(const LastProfile& a, const LastProfile& b) {
return a.is_pps == b.is_pps && a.voltage_mv == b.voltage_mv &&
a.current_ma == b.current_ma && a.pdo_index == b.pdo_index;
}

inline bool operator!=(const LastProfile& a, const LastProfile& b) {
return !(a == b);
}

struct Preferences {
bool skip_picker_on_boot = false;
bool voltage_comp_enabled = false;
bool flip_display = false;
bool restore_last_profile_enabled = false;
bool voltage_compensate_enabled = false;
bool flip_display_enabled = false;
LastProfile last_profile;
};

inline bool operator==(const Preferences& a, const Preferences& b) {
return a.skip_picker_on_boot == b.skip_picker_on_boot &&
a.voltage_comp_enabled == b.voltage_comp_enabled &&
a.flip_display == b.flip_display;
return a.restore_last_profile_enabled == b.restore_last_profile_enabled &&
a.voltage_compensate_enabled == b.voltage_compensate_enabled &&
a.flip_display_enabled == b.flip_display_enabled && a.last_profile == b.last_profile;
}

inline bool operator!=(const Preferences& a, const Preferences& b) {
return !(a == b);
}

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

Expand All @@ -50,7 +67,6 @@ namespace pocketpd {
return crc;
}


inline void encode_preferences(const Preferences& payload, uint8_t* out) {
out[0] = PREFERENCES_LAYOUT_VERSION;
std::memcpy(out + 1, &payload, SIZE);
Expand Down
2 changes: 1 addition & 1 deletion include/v2/preferences_store.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ namespace pocketpd {
if (!m_eeprom.save(m_preferences)) {
return false;
}

m_dirty = false;
return true;
}
Expand Down
41 changes: 30 additions & 11 deletions include/v2/stages/normal_stage.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,21 @@ namespace pocketpd {

int8_t m_active_pdo_index = -1;
int8_t m_last_active_index = -1;
int32_t m_restore_mv = -1;
int32_t m_restore_ma = -1;

Mode m_mode;

IntervalTimer m_render_interval{40};
uint8_t m_arrow_frame = 0;
uint32_t m_last_draw_ms = 0;
bool m_blink_visible = true;

bool m_locked = false;
int32_t m_comp_offset_mv = 0;

// After OFF->ON, the first INA226 read returns a stale conversion (latched while FET
// was off; observed ~200 mV instead of true VBUS). Discard N load samples and hold the
// seeded supply value until the sensor has a fresh conversion in hand.

// After OFF->ON, the first INA226 read returns a stale conversion so we discard N load
// samples and hold the seeded supply value until the sensor is fresh.
uint8_t m_postenable_discard_left = 0;
static constexpr uint8_t POSTENABLE_DISCARD_SAMPLES = 2;

Expand Down Expand Up @@ -110,8 +112,10 @@ namespace pocketpd {
return p ? p->current_idx : 0;
}

void prepare(int8_t pdo_index = -1) {
void prepare(int8_t pdo_index = -1, int32_t restore_mv = -1, int32_t restore_ma = -1) {
m_active_pdo_index = pdo_index;
m_restore_mv = restore_mv;
m_restore_ma = restore_ma;
}

void on_enter(Conductor&, uint32_t) override {
Expand Down Expand Up @@ -141,6 +145,8 @@ namespace pocketpd {
enter_fixed_profile();
}

m_restore_mv = -1;
m_restore_ma = -1;
m_last_active_index = m_active_pdo_index;
draw();
}
Expand Down Expand Up @@ -213,6 +219,14 @@ namespace pocketpd {

pps->on_encoder(event);
publish(PpsTargetEvent{m_active_pdo_index, pps->target_mv, pps->target_ma});
publish(
ActiveProfileEvent{
true,
m_active_pdo_index,
pps->target_mv,
pps->target_ma,
}
);
},
[&](const SensorEvent& event) {
if (m_postenable_discard_left > 0) {
Expand All @@ -233,9 +247,7 @@ namespace pocketpd {
}
}
},
[&](const CompStateEvent& evt) {
m_comp_offset_mv = evt.offset_mv;
},
[&](const CompStateEvent& evt) { m_comp_offset_mv = evt.offset_mv; },
[](const auto&) {},
};

Expand All @@ -247,6 +259,10 @@ namespace pocketpd {
bool same_profile = m_active_pdo_index == m_last_active_index;
if (!same_profile) {
PPSMode pps{m_pd_sink, m_active_pdo_index};
if (m_restore_mv >= 0) {
pps.target_mv = m_restore_mv;
pps.target_ma = m_restore_ma;
}
m_mode = pps;

const auto msg = "Entered PPS profile pdo_index={} target_mv={} target_ma={}";
Expand All @@ -256,11 +272,13 @@ namespace pocketpd {
auto& pps = std::get<PPSMode>(m_mode);
pps.clamp();
publish(PpsTargetEvent{m_active_pdo_index, pps.target_mv, pps.target_ma});
publish(ActiveProfileEvent{true, m_active_pdo_index, pps.target_mv, pps.target_ma});
}

void enter_fixed_profile() {
const int nominal_mv = m_pd_sink.pdo_max_voltage_mv(m_active_pdo_index);
m_mode = FixedMode{
.pdo_max_mv = m_pd_sink.pdo_max_voltage_mv(m_active_pdo_index),
.pdo_max_mv = nominal_mv,
.pdo_max_ma = m_pd_sink.pdo_max_current_ma(m_active_pdo_index),
};

Expand All @@ -269,6 +287,7 @@ namespace pocketpd {
log.error("set_pdo({}) failed", m_active_pdo_index);
}
publish(PpsTargetEvent{-1, 0, 0});
publish(ActiveProfileEvent{false, m_active_pdo_index, nominal_mv, 0});
}

NormalViewModel build_view_model() const {
Expand Down
62 changes: 55 additions & 7 deletions include/v2/stages/obtain_stage.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,68 @@
*/
#pragma once

#include <tempo/bus/publisher.h>
#include <tempo/bus/visit.h>
#include <tempo/core/time.h>

#include <algorithm>
#include <array>
#include <cstdint>
#include <variant>

#include <tempo/bus/publisher.h>
#include <tempo/bus/visit.h>
#include <tempo/core/time.h>

#include "AP33772_debug.h"
#include "v2/app.h"
#include "v2/preferences_store.h"
#include "v2/events.h"
#include "v2/hal/pd_sink_controller.h"
#include "v2/pocketpd.h"
#include "v2/preferences_store.h"

namespace pocketpd {

/**
* @brief Validate the saved profile against the PDO at its saved index and
* copy it into `out` ready to apply. PPS current is clamped to the PDO's
* max, and fixed current stays 0.
*
* @return false if restore is off, the index is gone, or the profile there
* no longer matches (out untouched).
*/
inline bool resolve_profile(
const Preferences& prefs, const PdSinkController& sink, LastProfile& out
) {
if (!prefs.restore_last_profile_enabled) {
return false;
}

const LastProfile& saved = prefs.last_profile;
const int index = static_cast<int>(saved.pdo_index);
if (index < 0 || index >= sink.pdo_count()) {
return false;
}

const int saved_mv = static_cast<int>(saved.voltage_mv);
bool matches = false;
if (saved.is_pps) {
auto min_mv = sink.pdo_min_voltage_mv(index);
auto max_mv = sink.pdo_max_voltage_mv(index);
matches = sink.is_index_pps(index) && saved_mv >= min_mv && saved_mv <= max_mv;
} else {
matches = sink.is_index_fixed(index) && sink.pdo_max_voltage_mv(index) == saved_mv;
}

if (!matches) {
return false;
}

out = saved;
if (saved.is_pps) {
out.current_ma = static_cast<uint16_t>(
std::min(static_cast<int>(saved.current_ma), sink.pdo_max_current_ma(index))
);
}
return true;
}

class ObtainStage : public App::Stage,
public App::UseLog<ObtainStage>,
public App::UsePublisher<ObtainStage> {
Expand Down Expand Up @@ -54,8 +99,11 @@ namespace pocketpd {
log.error("PD negotiation failed");
}

if (m_prefs.get().skip_picker_on_boot) {
conductor.replace<NormalStage>();
LastProfile resolved;
if (resolve_profile(m_prefs.get(), m_pd_sink, resolved)) {
conductor.replace<NormalStage>(
resolved.pdo_index, resolved.voltage_mv, resolved.current_ma
);
}
}

Expand Down
30 changes: 15 additions & 15 deletions include/v2/stages/settings_stage.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ namespace pocketpd {
using Display = tempo::Display;

enum class Item : uint8_t {
SKIP_PICKER,
VOLTAGE_COMP,
RESTORE_PROFILE,
VOLTAGE_COMPENSATE,
FLIP_DISPLAY,
};

Expand All @@ -37,8 +37,8 @@ namespace pocketpd {
};

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

Expand All @@ -54,28 +54,28 @@ namespace pocketpd {
bool value_at(Item item) const {
const Preferences prefs = m_prefs.get();
switch (item) {
case Item::SKIP_PICKER:
return prefs.skip_picker_on_boot;
case Item::VOLTAGE_COMP:
return prefs.voltage_comp_enabled;
case Item::RESTORE_PROFILE:
return prefs.restore_last_profile_enabled;
case Item::VOLTAGE_COMPENSATE:
return prefs.voltage_compensate_enabled;
case Item::FLIP_DISPLAY:
return prefs.flip_display;
return prefs.flip_display_enabled;
}
return false;
}

void toggle_current() {
Preferences prefs = m_prefs.get();
switch (ITEMS[m_table.cursor()].item) {
case Item::SKIP_PICKER:
prefs.skip_picker_on_boot = !prefs.skip_picker_on_boot;
case Item::RESTORE_PROFILE:
prefs.restore_last_profile_enabled = !prefs.restore_last_profile_enabled;
break;
case Item::VOLTAGE_COMP:
prefs.voltage_comp_enabled = !prefs.voltage_comp_enabled;
case Item::VOLTAGE_COMPENSATE:
prefs.voltage_compensate_enabled = !prefs.voltage_compensate_enabled;
break;
case Item::FLIP_DISPLAY:
prefs.flip_display = !prefs.flip_display;
m_orientation.set_flipped(prefs.flip_display);
prefs.flip_display_enabled = !prefs.flip_display_enabled;
m_orientation.set_flipped(prefs.flip_display_enabled);
break;
}
m_prefs.set(prefs);
Expand Down
2 changes: 1 addition & 1 deletion include/v2/tasks/button_task.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ namespace pocketpd {
* 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.get().flip_display) {
if (!m_prefs.get().flip_display_enabled) {
return id;
}
switch (id) {
Expand Down
2 changes: 1 addition & 1 deletion include/v2/tasks/encoder_task.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ namespace pocketpd {
int delta = pos - m_last_position;
if (delta != 0) {
// Flipped display turns the knob around with the unit, so CW now reads as CCW.
if (m_prefs.get().flip_display) {
if (m_prefs.get().flip_display_enabled) {
delta = -delta;
}
publish(EncoderEvent{delta});
Expand Down
Loading
Loading