Files
esphome/tests/components/fusb302b/pd_test.cpp

524 lines
16 KiB
C++

#include <gtest/gtest.h>
#include <vector>
#include "esphome/components/fusb302b/pd.h"
namespace esphome::fusb302b::testing {
static constexpr uint32_t fixed_pdo(uint16_t mv, uint16_t ma) { return ((mv / 50u) << 10) | (ma / 10u); }
// An augmented (PPS) object; only its type matters to the sink
static constexpr uint32_t AUGMENTED_PDO = static_cast<uint32_t>(PD_PDO_TYPE_AUGMENTED) << 30;
// Unknown to the sink, so it must be answered with Reject
static constexpr uint8_t PD_CNTRL_DR_SWAP = 0x09;
class FakePhy : public PowerDelivery {
public:
using PowerDelivery::ams_start_;
using PowerDelivery::check_ams_;
using PowerDelivery::check_transition_timeout_;
using PowerDelivery::check_request_retry_;
using PowerDelivery::contract_;
using PowerDelivery::handle_message_;
using PowerDelivery::handle_transmit_failed_;
using PowerDelivery::retry_request_pending_;
using PowerDelivery::set_state_and_contract_;
using PowerDelivery::wait_received_ms_;
std::vector<PdMsg> sent;
bool send_ok{true};
int send_attempts{0};
int state_changes{0};
int soft_resets{0};
int source_caps{0};
int transition_timeouts{0};
int request_failures{0};
void receive(uint8_t type, uint8_t id, std::vector<uint32_t> objects = {}) {
PdMsg msg;
msg.type = type;
msg.id = id;
msg.num_of_obj = objects.size();
for (size_t i = 0; i < objects.size(); i++)
msg.data_objects[i] = objects[i];
this->handle_message_(msg);
}
void receive_source_caps(uint8_t id) {
this->receive(PD_DATA_SOURCE_CAP, id,
{fixed_pdo(5000, 3000), fixed_pdo(9000, 3000), fixed_pdo(15000, 3000), fixed_pdo(20000, 2250)});
}
PackedContract contract() const { return PackedContract{this->contract_}; }
// What the PHY does when a source is attached
void attach() { this->set_state_and_contract_(PdState::PD_STATE_DEFAULT_CONTRACT, DEFAULT_CONTRACT); }
// Moves the timestamps back instead of sleeping, since millis() is real time on the host
void advance_ms(uint32_t ms) {
this->ams_start_ -= ms;
this->wait_received_ms_ -= ms;
}
protected:
bool send_message(const PdMsg &msg) override {
this->send_attempts++;
if (!this->send_ok)
return false;
this->sent.push_back(msg);
return true;
}
void on_state_changed() override { this->state_changes++; }
void on_soft_reset_received() override { this->soft_resets++; }
void on_source_caps_received() override {
// The hook runs before the request is sent
EXPECT_EQ(this->sent.size(), static_cast<size_t>(this->source_caps));
this->source_caps++;
}
void on_transition_timeout() override { this->transition_timeouts++; }
void on_request_failed() override { this->request_failures++; }
};
TEST(FUSB302BPd, HeaderRoundTrip) {
PdMsg msg;
msg.type = PD_DATA_REQUEST;
msg.spec_rev = PD_SPEC_REV_2;
msg.id = 5;
msg.num_of_obj = 1;
// Type 2, spec revision 1 at bit 6, MessageID 5 at bit 9, one object at bit 12
EXPECT_EQ(msg.get_coded_header(), 0x1A42);
PdMsg decoded;
decoded.set_header(0x1A42 | (1u << 15));
EXPECT_EQ(decoded.type, PD_DATA_REQUEST);
EXPECT_EQ(decoded.spec_rev, PD_SPEC_REV_2);
EXPECT_EQ(decoded.id, 5);
EXPECT_EQ(decoded.num_of_obj, 1);
EXPECT_TRUE(decoded.extended);
}
TEST(FUSB302BPd, RequestsHighestVoltageNotAboveLimit) {
FakePhy pd;
pd.set_request_voltage(18);
pd.receive_source_caps(0);
EXPECT_EQ(pd.source_caps, 1);
ASSERT_EQ(pd.sent.size(), 1u);
EXPECT_EQ(pd.sent[0].type, PD_DATA_REQUEST);
EXPECT_EQ(pd.sent[0].data_objects[0] >> 28, 3u); // 15 V is object 3
EXPECT_EQ(pd.sent[0].data_objects[0] & 0x3FF, 300u);
}
TEST(FUSB302BPd, LowestRequestPicksFirstObject) {
FakePhy pd;
pd.set_request_voltage(5);
pd.receive_source_caps(0);
ASSERT_EQ(pd.sent.size(), 1u);
EXPECT_EQ(pd.sent[0].data_objects[0] >> 28, 1u);
}
TEST(FUSB302BPd, NonFixedObjectKeepsItsPosition) {
FakePhy pd;
pd.set_request_voltage(9);
pd.receive(PD_DATA_SOURCE_CAP, 0, {fixed_pdo(5000, 3000), AUGMENTED_PDO, fixed_pdo(9000, 2000)});
ASSERT_EQ(pd.sent.size(), 1u);
EXPECT_EQ(pd.sent[0].data_objects[0] >> 28, 3u);
EXPECT_EQ(pd.sent[0].data_objects[0] & 0x3FF, 200u);
}
TEST(FUSB302BPd, NoFixedObjectFallsBackToObjectOne) {
FakePhy pd;
pd.set_request_voltage(20);
pd.receive(PD_DATA_SOURCE_CAP, 0, {AUGMENTED_PDO, AUGMENTED_PDO});
ASSERT_EQ(pd.sent.size(), 1u);
const uint32_t rdo = pd.sent[0].data_objects[0];
EXPECT_EQ(rdo >> 28, 1u);
EXPECT_EQ(rdo & 0x3FF, 30u); // 300 mA maximum
EXPECT_EQ((rdo >> 10) & 0x3FF, 10u); // 100 mA operating
}
TEST(FUSB302BPd, AcceptThenPsRdyReachesExplicitContract) {
FakePhy pd;
pd.set_request_voltage(20);
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_ACCEPT, 1);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_TRANSITION);
pd.receive(PD_CNTRL_PS_RDY, 2);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_EXPLICIT_CONTRACT);
EXPECT_FLOAT_EQ(pd.contract().voltage(), 20.0f);
EXPECT_FLOAT_EQ(pd.contract().current(), 2.25f);
}
TEST(FUSB302BPd, AcceptPublishesStateAndContractOnce) {
FakePhy pd;
pd.set_request_voltage(20);
pd.receive_source_caps(0);
pd.state_changes = 0;
pd.receive(PD_CNTRL_ACCEPT, 1);
EXPECT_EQ(pd.state_changes, 1);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_TRANSITION);
EXPECT_FLOAT_EQ(pd.contract().voltage(), 20.0f);
}
TEST(FUSB302BPd, AcceptForSameContractSkipsTransition) {
FakePhy pd;
pd.set_request_voltage(20);
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_ACCEPT, 1);
pd.receive(PD_CNTRL_PS_RDY, 2);
pd.receive_source_caps(3);
pd.receive(PD_CNTRL_ACCEPT, 4);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_EXPLICIT_CONTRACT);
pd.receive(PD_CNTRL_PS_RDY, 5);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_EXPLICIT_CONTRACT);
EXPECT_FALSE(pd.check_ams_());
}
TEST(FUSB302BPd, PsRdyWithoutAcceptIsIgnored) {
FakePhy pd;
pd.receive(PD_CNTRL_PS_RDY, 0);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_DISCONNECTED);
pd.receive_source_caps(1);
pd.receive(PD_CNTRL_PS_RDY, 2);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_DISCONNECTED);
}
TEST(FUSB302BPd, PsRdyAfterRejectIsIgnored) {
FakePhy pd;
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_REJECT, 1);
EXPECT_FALSE(pd.check_ams_());
pd.receive(PD_CNTRL_PS_RDY, 2);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_DISCONNECTED);
}
TEST(FUSB302BPd, PsRdyAfterSequenceTimeoutIsIgnored) {
FakePhy pd;
pd.set_request_voltage(20);
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_ACCEPT, 1);
pd.advance_ms(2001);
pd.receive(PD_CNTRL_PS_RDY, 2);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_TRANSITION);
// The missing PS_RDY is a transition timeout, not a failed request
EXPECT_EQ(pd.request_failures, 0);
}
TEST(FUSB302BPd, UnansweredFirstRequestEndsNegotiation) {
FakePhy pd;
pd.attach();
pd.set_request_voltage(20);
pd.receive_source_caps(0);
EXPECT_TRUE(pd.check_ams_());
pd.advance_ms(2001);
EXPECT_FALSE(pd.check_ams_());
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_PD_TIMEOUT);
EXPECT_EQ(pd.request_failures, 1);
// Reported once
EXPECT_FALSE(pd.check_ams_());
EXPECT_EQ(pd.request_failures, 1);
}
TEST(FUSB302BPd, MissingPsRdyForSameContractFailsRequest) {
FakePhy pd;
pd.set_request_voltage(20);
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_ACCEPT, 1);
pd.receive(PD_CNTRL_PS_RDY, 2);
pd.receive_source_caps(3);
pd.receive(PD_CNTRL_ACCEPT, 4);
pd.advance_ms(2001);
EXPECT_FALSE(pd.check_ams_());
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_EXPLICIT_CONTRACT);
EXPECT_FLOAT_EQ(pd.contract().voltage(), 20.0f);
EXPECT_EQ(pd.request_failures, 1);
}
TEST(FUSB302BPd, TransitionTimeoutFallsBackToDefault) {
FakePhy pd;
pd.attach();
pd.set_request_voltage(20);
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_ACCEPT, 1);
pd.check_transition_timeout_();
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_TRANSITION);
EXPECT_EQ(pd.transition_timeouts, 0);
pd.advance_ms(2001);
pd.state_changes = 0;
pd.check_transition_timeout_();
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_DEFAULT_CONTRACT);
EXPECT_FLOAT_EQ(pd.contract().voltage(), 5.0f);
EXPECT_FLOAT_EQ(pd.contract().current(), 0.5f);
EXPECT_EQ(pd.state_changes, 1);
EXPECT_EQ(pd.transition_timeouts, 1);
// Reported once, and a late PS_RDY does not bring back the unconfirmed contract
pd.check_transition_timeout_();
pd.receive(PD_CNTRL_PS_RDY, 2);
EXPECT_EQ(pd.transition_timeouts, 1);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_DEFAULT_CONTRACT);
}
TEST(FUSB302BPd, RequestVoltageIsClamped) {
FakePhy pd;
pd.set_request_voltage(255);
EXPECT_EQ(pd.get_request_voltage(), MAX_REQUEST_VOLTAGE);
pd.set_request_voltage(0);
EXPECT_EQ(pd.get_request_voltage(), MIN_REQUEST_VOLTAGE);
}
TEST(FUSB302BPd, WaitRepeatsRequestAfterSinkRequestTime) {
FakePhy pd;
pd.set_request_voltage(20);
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_WAIT, 1);
EXPECT_FALSE(pd.check_ams_());
pd.check_request_retry_();
EXPECT_EQ(pd.sent.size(), 1u);
pd.advance_ms(101);
pd.check_request_retry_();
ASSERT_EQ(pd.sent.size(), 2u);
EXPECT_EQ(pd.sent[1].type, PD_DATA_REQUEST);
EXPECT_EQ(pd.sent[1].data_objects[0], pd.sent[0].data_objects[0]);
EXPECT_TRUE(pd.check_ams_());
}
TEST(FUSB302BPd, WaitRetriesStopAtLimit) {
FakePhy pd;
pd.attach();
pd.receive_source_caps(0);
for (uint8_t i = 1; i <= 10; i++) {
pd.receive(PD_CNTRL_WAIT, i % 8);
pd.advance_ms(101);
pd.check_request_retry_();
}
EXPECT_EQ(pd.sent.size(), 11u);
EXPECT_EQ(pd.request_failures, 0);
pd.receive(PD_CNTRL_WAIT, 3);
EXPECT_FALSE(pd.retry_request_pending_);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_PD_TIMEOUT);
EXPECT_EQ(pd.request_failures, 1);
pd.advance_ms(101);
pd.check_request_retry_();
EXPECT_EQ(pd.sent.size(), 11u);
}
TEST(FUSB302BPd, FailedRequestEndsNegotiation) {
FakePhy pd;
pd.attach();
pd.set_request_voltage(20);
pd.send_ok = false;
pd.receive_source_caps(0);
EXPECT_EQ(pd.send_attempts, 1);
EXPECT_FALSE(pd.check_ams_());
EXPECT_FALSE(pd.retry_request_pending_);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_PD_TIMEOUT);
EXPECT_EQ(pd.request_failures, 1);
pd.receive(PD_CNTRL_ACCEPT, 1);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_PD_TIMEOUT);
EXPECT_FLOAT_EQ(pd.contract().voltage(), 5.0f);
}
TEST(FUSB302BPd, FailedRetryIsTriedAgain) {
FakePhy pd;
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_WAIT, 1);
pd.send_ok = false;
pd.advance_ms(101);
pd.check_request_retry_();
EXPECT_EQ(pd.send_attempts, 2);
EXPECT_TRUE(pd.retry_request_pending_);
EXPECT_FALSE(pd.check_ams_());
pd.send_ok = true;
pd.check_request_retry_();
EXPECT_EQ(pd.send_attempts, 2); // waits another tSinkRequest first
pd.advance_ms(101);
pd.check_request_retry_();
EXPECT_EQ(pd.sent.size(), 2u);
EXPECT_FALSE(pd.retry_request_pending_);
}
TEST(FUSB302BPd, FailedRetriesStopAtLimit) {
FakePhy pd;
pd.attach();
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_WAIT, 1);
pd.send_ok = false;
for (int i = 0; i < 20 && pd.retry_request_pending_; i++) {
pd.advance_ms(101);
pd.check_request_retry_();
}
EXPECT_FALSE(pd.retry_request_pending_);
// The first request, then one attempt for each of the 10 allowed repeats
EXPECT_EQ(pd.send_attempts, 11);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_PD_TIMEOUT);
EXPECT_EQ(pd.request_failures, 1);
}
TEST(FUSB302BPd, RejectedFirstRequestEndsNegotiation) {
FakePhy pd;
pd.attach();
pd.set_request_voltage(20);
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_REJECT, 1);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_PD_TIMEOUT);
EXPECT_FLOAT_EQ(pd.contract().voltage(), 5.0f);
EXPECT_EQ(pd.request_failures, 1);
}
TEST(FUSB302BPd, RejectedRenegotiationKeepsContract) {
FakePhy pd;
pd.attach();
pd.set_request_voltage(20);
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_ACCEPT, 1);
pd.receive(PD_CNTRL_PS_RDY, 2);
pd.set_request_voltage(9);
pd.receive_source_caps(3);
EXPECT_EQ(pd.request_failures, 0);
pd.receive(PD_CNTRL_REJECT, 4);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_EXPLICIT_CONTRACT);
EXPECT_FLOAT_EQ(pd.contract().voltage(), 20.0f);
EXPECT_EQ(pd.request_failures, 1);
}
TEST(FUSB302BPd, RejectAndWaitOutsideRequestAreIgnored) {
FakePhy pd;
pd.attach();
pd.receive(PD_CNTRL_REJECT, 0);
pd.receive(PD_CNTRL_WAIT, 1);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_DEFAULT_CONTRACT);
EXPECT_FALSE(pd.retry_request_pending_);
EXPECT_EQ(pd.request_failures, 0);
}
TEST(FUSB302BPd, UnsupportedDataMessageIsIgnored) {
FakePhy pd;
pd.receive(PD_DATA_REQUEST, 0, {0});
EXPECT_TRUE(pd.sent.empty());
EXPECT_EQ(pd.source_caps, 0);
}
TEST(FUSB302BPd, AcceptOutsideSequenceIsIgnored) {
FakePhy pd;
pd.set_request_voltage(20);
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_REJECT, 1);
pd.receive(PD_CNTRL_ACCEPT, 2);
pd.receive(PD_CNTRL_PS_RDY, 3);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_DISCONNECTED);
EXPECT_FALSE(pd.contract().is_set());
}
TEST(FUSB302BPd, GoodCrcMustMatchMessageId) {
FakePhy pd;
pd.receive(PD_CNTRL_DR_SWAP, 0);
ASSERT_EQ(pd.sent.size(), 1u);
EXPECT_EQ(pd.sent[0].id, 0);
pd.receive(PD_CNTRL_GOODCRC, 1);
pd.receive(PD_CNTRL_DR_SWAP, 1);
ASSERT_EQ(pd.sent.size(), 2u);
EXPECT_EQ(pd.sent[1].id, 0);
pd.receive(PD_CNTRL_GOODCRC, 0);
pd.receive(PD_CNTRL_GOODCRC, 0); // duplicate
pd.receive(PD_CNTRL_DR_SWAP, 2);
ASSERT_EQ(pd.sent.size(), 3u);
EXPECT_EQ(pd.sent[2].id, 1);
}
TEST(FUSB302BPd, FailedTransmitAdvancesMessageId) {
FakePhy pd;
pd.receive_source_caps(0);
ASSERT_EQ(pd.sent.size(), 1u);
EXPECT_EQ(pd.sent[0].id, 0);
pd.handle_transmit_failed_();
// A late GoodCRC for the failed message must not advance the counter a second time
pd.receive(PD_CNTRL_GOODCRC, 0);
pd.receive(PD_CNTRL_DR_SWAP, 1);
ASSERT_EQ(pd.sent.size(), 2u);
EXPECT_EQ(pd.sent[1].id, 1);
}
TEST(FUSB302BPd, RetransmissionIsIgnored) {
FakePhy pd;
pd.receive_source_caps(0);
pd.receive_source_caps(0);
EXPECT_EQ(pd.sent.size(), 1u);
EXPECT_EQ(pd.source_caps, 1);
}
TEST(FUSB302BPd, SoftResetIsAccepted) {
FakePhy pd;
pd.receive(PD_CNTRL_SOFT_RESET, 0);
ASSERT_EQ(pd.sent.size(), 1u);
EXPECT_EQ(pd.sent[0].type, PD_CNTRL_ACCEPT);
EXPECT_EQ(pd.soft_resets, 1);
}
TEST(FUSB302BPd, SoftResetHookRunsWhenAcceptFails) {
FakePhy pd;
pd.send_ok = false;
pd.receive(PD_CNTRL_SOFT_RESET, 0);
EXPECT_EQ(pd.send_attempts, 1);
EXPECT_EQ(pd.soft_resets, 1);
}
TEST(FUSB302BPd, SoftResetDuringTransitionFallsBackToDefault) {
FakePhy pd;
pd.set_request_voltage(20);
pd.receive_source_caps(0);
pd.receive(PD_CNTRL_ACCEPT, 1);
pd.state_changes = 0;
pd.receive(PD_CNTRL_SOFT_RESET, 0);
EXPECT_EQ(pd.state_changes, 1);
EXPECT_EQ(pd.get_state(), PdState::PD_STATE_DEFAULT_CONTRACT);
EXPECT_FLOAT_EQ(pd.contract().voltage(), 5.0f);
EXPECT_FLOAT_EQ(pd.contract().current(), 0.5f);
}
TEST(FUSB302BPd, UnknownControlMessageIsRejected) {
FakePhy pd;
pd.receive(PD_CNTRL_DR_SWAP, 0);
ASSERT_EQ(pd.sent.size(), 1u);
EXPECT_EQ(pd.sent[0].type, PD_CNTRL_REJECT);
}
TEST(FUSB302BPd, SinkCapAt5VHasOneObject) {
FakePhy pd;
pd.receive(PD_CNTRL_GET_SINK_CAP, 0);
ASSERT_EQ(pd.sent.size(), 1u);
EXPECT_EQ(pd.sent[0].type, PD_DATA_SINK_CAP);
ASSERT_EQ(pd.sent[0].num_of_obj, 1);
const uint32_t pdo = pd.sent[0].data_objects[0];
EXPECT_EQ((pdo >> 10) & 0x3FF, 100u);
EXPECT_EQ(pdo & 0x3FF, 300u);
EXPECT_FALSE(pdo & (1u << 28));
EXPECT_TRUE(pdo & (1u << 26));
}
TEST(FUSB302BPd, SinkCapListsRequestedVoltage) {
FakePhy pd;
pd.set_request_voltage(20);
pd.receive(PD_CNTRL_GET_SINK_CAP, 0);
ASSERT_EQ(pd.sent.size(), 1u);
ASSERT_EQ(pd.sent[0].num_of_obj, 2);
EXPECT_TRUE(pd.sent[0].data_objects[0] & (1u << 28));
const uint32_t pdo = pd.sent[0].data_objects[1];
EXPECT_EQ(pdo >> 30, 0u);
EXPECT_EQ((pdo >> 10) & 0x3FF, 400u);
EXPECT_EQ(pdo & 0x3FF, 300u);
EXPECT_EQ(pdo >> 25, 0u); // capability flags are only valid in the first object
}
TEST(FUSB302BPd, SinkCapForOutOfRangeVoltageStaysValid) {
FakePhy pd;
pd.set_request_voltage(255);
pd.receive(PD_CNTRL_GET_SINK_CAP, 0);
ASSERT_EQ(pd.sent.size(), 1u);
ASSERT_EQ(pd.sent[0].num_of_obj, 2);
const uint32_t pdo = pd.sent[0].data_objects[1];
EXPECT_EQ((pdo >> 10) & 0x3FF, 400u);
EXPECT_EQ(pdo >> 20, 0u); // nothing spills into the peak current or flag bits
}
} // namespace esphome::fusb302b::testing