12static constexpr uint32_t AMS_TIMEOUT_MS = 2000;
14static constexpr uint32_t SINK_REQUEST_MS = 100;
15static constexpr uint8_t MAX_WAIT_RETRIES = 10;
18static constexpr uint32_t SINK_PDO_HIGHER_CAPABILITY = 1u << 28;
19static constexpr uint32_t SINK_PDO_USB_COMMS_CAPABLE = 1u << 26;
21static constexpr uint16_t SINK_PDO_CURRENT = 300;
28 info.max_v = (pdo >> 10) & 0x3FF;
29 info.max_i = pdo & 0x3FF;
35static constexpr uint32_t sink_fixed_pdo(uint16_t voltage, uint16_t current) {
41 this->
type = header & 0x1F;
43 this->
id = (header >> 9) & 0x7;
45 this->
extended = (header >> 15) & 0x1;
50 return static_cast<uint16_t
>(this->
type & 0x1F) | (
static_cast<uint16_t
>(this->
spec_rev) << 6) |
51 (
static_cast<uint16_t
>(this->
id & 0x7) << 9) | (
static_cast<uint16_t
>(this->
num_of_obj & 0x7) << 12) |
52 (
static_cast<uint16_t
>(this->
extended) << 15);
58 msg.
id = this->msg_counter_ % 8;
65 msg.
id = this->msg_counter_ % 8;
72 this->last_received_msg_id_ = 255;
73 this->msg_counter_ = 0;
74 this->active_ams_ =
false;
75 this->ps_rdy_expected_ =
false;
77 this->wait_retries_ = 0;
86 this->accepted_contract_ = contract;
88 if (this->
contract_.exchange(packed) != packed)
93 this->accepted_contract_ = {};
99 this->accepted_contract_ = contract;
102 const bool contract_changed = this->
contract_.exchange(packed) != packed;
103 const bool state_changed = this->
state_.exchange(state) !=
state;
104 if (contract_changed || state_changed)
109 this->active_ams_ = active;
111 this->ps_rdy_expected_ =
false;
121 ESP_LOGW(TAG,
"Source did not complete the request in time");
122 this->request_failed_();
125 return this->active_ams_;
131 if (this->send_request_()) {
133 }
else if (this->wait_retries_ < MAX_WAIT_RETRIES) {
135 this->wait_retries_++;
138 ESP_LOGW(TAG,
"Giving up repeating the request");
140 this->request_failed_();
144bool PowerDelivery::send_request_() {
148 ESP_LOGW(TAG,
"Sending the request failed");
153void PowerDelivery::request_failed_() {
164 ESP_LOGW(TAG,
"Source did not report the new supply ready, falling back to default power");
172 ESP_LOGD(TAG,
"Message %u not acknowledged", this->msg_counter_ % 8);
173 this->msg_counter_++;
179 if (msg.
id != this->msg_counter_ % 8) {
180 ESP_LOGD(TAG,
"Ignoring GoodCRC for MessageID %u, expected %u", msg.
id, this->msg_counter_ % 8);
184 this->msg_counter_++;
189 this->handle_soft_reset_(msg);
193 if (msg.
id == this->last_received_msg_id_)
195 this->last_received_msg_id_ = msg.
id;
198 this->handle_control_message_(msg);
200 this->handle_data_message_(msg);
204void PowerDelivery::handle_data_message_(
const PdMsg &msg) {
207 ESP_LOGV(TAG,
"Ignoring data message type %u%s", msg.
type,
208 msg.
extended ? LOG_STR_LITERAL(
" (extended)") : LOG_STR_LITERAL(
""));
212 this->respond_to_source_caps_(msg);
215void PowerDelivery::handle_control_message_(
const PdMsg &msg) {
219 ESP_LOGD(TAG,
"Ignoring Accept outside a request");
222 this->ps_rdy_expected_ =
true;
223 if (this->requested_contract_ != this->accepted_contract_)
228 if (!this->
check_ams_() || !this->ps_rdy_expected_) {
229 ESP_LOGD(TAG,
"Ignoring PS_RDY without an accepted request");
237 ESP_LOGD(TAG,
"Ignoring Reject outside a request");
240 ESP_LOGW(TAG,
"Source rejected the request");
242 this->request_failed_();
246 ESP_LOGD(TAG,
"Ignoring Wait outside a request");
250 if (this->wait_retries_ < MAX_WAIT_RETRIES) {
251 ESP_LOGD(TAG,
"Source asked to wait, repeating the request");
252 this->wait_retries_++;
256 ESP_LOGW(TAG,
"Source kept answering Wait, giving up");
257 this->request_failed_();
269 pdos[
count++] = sink_fixed_pdo(100, SINK_PDO_CURRENT) | SINK_PDO_USB_COMMS_CAPABLE |
270 (request_v > 100 ? SINK_PDO_HIGHER_CAPABILITY : 0);
272 pdos[
count++] = sink_fixed_pdo(request_v, SINK_PDO_CURRENT);
283void PowerDelivery::handle_soft_reset_(
const PdMsg &msg) {
285 this->last_received_msg_id_ = msg.id;
289 ESP_LOGW(TAG,
"Accepting the soft reset failed");
297void PowerDelivery::respond_to_source_caps_(
const PdMsg &msg) {
301 PdContract selected{};
303 for (uint8_t idx = 0; idx < msg.num_of_obj; idx++) {
304 PdContract info = parse_pdo(msg.data_objects[idx]);
307 if (
position == 0 || (info.max_v <= request_v && info.max_v > selected.max_v)) {
314 uint32_t rdo = (1u << 24) | (1u << 25);
316 ESP_LOGW(TAG,
"No usable PDO in source capabilities, requesting 5 V");
320 rdo |= (30u << 0) | (10u << 10);
323 const uint32_t current = selected.max_i;
324 rdo |= (current << 0) | (current << 10);
327 ESP_LOGD(TAG,
"Requesting PDO %u: %.2f V",
position, selected.max_v * 0.05f);
328 this->requested_contract_ = selected;
329 this->last_rdo_ = rdo;
331 this->wait_retries_ = 0;
333 if (!this->send_request_())
334 this->request_failed_();
PdMsg make_control_msg_(PdControlMsgType type) const
virtual void on_source_caps_received()=0
Called when source capabilities arrive, before the request for them is sent.
void set_contract_(const PdContract &contract)
uint32_t wait_received_ms_
bool retry_request_pending_
std::atomic< uint8_t > request_voltage_
PdMsg make_data_msg_(PdDataMsgType type, const uint32_t *objects, uint8_t len) const
void set_ams_(bool active)
std::atomic< uint32_t > contract_
void set_state_(PdState state)
std::atomic< PdState > state_
void handle_message_(const PdMsg &msg)
void check_request_retry_()
Sends the last request again once tSinkRequest has passed after the source answered Wait.
virtual void on_state_changed()=0
Called after state_ or contract_ changed, to let the main loop publish it.
virtual bool send_message(const PdMsg &msg)=0
Returns false when the message could not be handed to the chip; the PHY logs the failure.
void handle_transmit_failed_()
Called by the PHY when the chip gave up on a message after its retries without receiving a GoodCRC.
virtual void on_soft_reset_received()=0
Called after the source soft reset the protocol; it sends new source capabilities next.
virtual void on_request_failed()=0
Called when a request ended without a new contract: it was rejected, the source kept answering Wait o...
bool check_ams_()
Returns true while an atomic message sequence is in progress.
void set_state_and_contract_(PdState state, const PdContract &contract)
Stores both before calling on_state_changed() once, so the main loop never sees the new state with th...
void check_transition_timeout_()
Falls back to default power when the source accepted a request but did not report the new supply read...
virtual void on_transition_timeout()=0
Called after an accepted request was not followed by PS_RDY in time and the state fell back to defaul...
ESPHOME_LOG_TAG(TAG, "fusb302b")
@ PD_STATE_EXPLICIT_CONTRACT
@ PD_STATE_DEFAULT_CONTRACT
@ PD_STATE_PD_TIMEOUT
Negotiation is over without a PD contract (no capabilities, or the request was not accepted); the sou...
@ PD_PDO_TYPE_FIXED_SUPPLY
uint32_t IRAM_ATTR HOT millis()
static PackedContract from(const PdContract &contract)
void set_header(uint16_t header)
uint16_t get_coded_header() const
uint32_t data_objects[PD_MAX_NUM_DATA_OBJECTS]