|
@@ -335,6 +335,111 @@ template<typename Driver> PackedResolverFn make_resolver_bw_packed(uint8_t p0cmd
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Captures powerOff() or hibernate() as its own standalone command sequence -
|
|
|
|
|
+// NOT folded into the refresh_seq phase-segmentation above, and NOT captured
|
|
|
|
|
+// from the same live session as each other. Every GxEPD2 driver's powerOff()
|
|
|
|
|
+// guards on `_power_is_on` (set true by the refresh() a normal update ends
|
|
|
|
|
+// with), and hibernate() calls powerOff() again internally - so capturing
|
|
|
|
|
+// both from one session would have the second call's powerOff() portion
|
|
|
|
|
+// silently emit nothing (already off from the first). Each call here gets its
|
|
|
|
|
+// own fresh instance, replayed through the exact init/clear/write/refresh
|
|
|
|
|
+// setup the main capture above uses, so `call` always runs with
|
|
|
|
|
+// _power_is_on true, matching a real update-then-power-down.
|
|
|
|
|
+template<typename Driver, typename MethodCall> std::vector<Cmd> capture_power_sequence(MethodCall call) {
|
|
|
|
|
+ constexpr int16_t CS = 10, DC = 11, RST = 12, BUSY = 13;
|
|
|
|
|
+ Trace::set_dc_pin(DC);
|
|
|
|
|
+ Peek<Driver> d(CS, DC, RST, BUSY);
|
|
|
|
|
+ Trace::last_busy_release_value = d.busy_level() == HIGH ? LOW : HIGH;
|
|
|
|
|
+ d.selectSPI(SPI, SPISettings());
|
|
|
|
|
+ d.init(0);
|
|
|
|
|
+
|
|
|
|
|
+ const uint32_t plane_bytes = (uint32_t) Driver::WIDTH * Driver::HEIGHT / 8;
|
|
|
|
|
+ auto *buf = new uint8_t[plane_bytes];
|
|
|
|
|
+ memset(buf, 0xA5, plane_bytes);
|
|
|
|
|
+ d.writeScreenBuffer();
|
|
|
|
|
+ d.writeImage(buf, buf, 0, 0, Driver::WIDTH, Driver::HEIGHT);
|
|
|
|
|
+ d.refresh(false);
|
|
|
|
|
+ delete[] buf;
|
|
|
|
|
+
|
|
|
|
|
+ Trace::start_event_record();
|
|
|
|
|
+ call(d);
|
|
|
|
|
+ Trace::stop_event_record();
|
|
|
|
|
+ return group(0, Trace::events.size());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// (power_off_sequence, hibernate_sequence) for Driver, each captured via
|
|
|
|
|
+// capture_power_sequence above. hibernate_sequence is hibernate()'s tail
|
|
|
|
|
+// PAST whatever it shares with powerOff() - every driver observed so far
|
|
|
|
|
+// calls powerOff() internally then adds exactly one more command (the
|
|
|
|
|
+// deep-sleep trigger) - since power_off_sequence already has that shared
|
|
|
|
|
+// part and CycleEngine runs the two back to back (see epd_command.h); a
|
|
|
|
|
+// prefix mismatch (seen nowhere so far) falls back to the full hibernate
|
|
|
|
|
+// capture rather than guess.
|
|
|
|
|
+template<typename Driver> std::pair<std::vector<Cmd>, std::vector<Cmd>> capture_power_off_and_hibernate() {
|
|
|
|
|
+ std::vector<Cmd> power_off_seq = capture_power_sequence<Driver>([](Peek<Driver> &d) { d.powerOff(); });
|
|
|
|
|
+ std::vector<Cmd> hibernate_full_seq = capture_power_sequence<Driver>([](Peek<Driver> &d) { d.hibernate(); });
|
|
|
|
|
+ std::vector<Cmd> hibernate_seq;
|
|
|
|
|
+ if (hibernate_full_seq.size() > power_off_seq.size()) {
|
|
|
|
|
+ bool prefix_matches = true;
|
|
|
|
|
+ for (size_t i = 0; i < power_off_seq.size() && prefix_matches; i++)
|
|
|
|
|
+ if (hibernate_full_seq[i].cmd != power_off_seq[i].cmd || hibernate_full_seq[i].data != power_off_seq[i].data)
|
|
|
|
|
+ prefix_matches = false;
|
|
|
|
|
+ if (prefix_matches)
|
|
|
|
|
+ hibernate_seq.assign(hibernate_full_seq.begin() + power_off_seq.size(), hibernate_full_seq.end());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (hibernate_seq.empty())
|
|
|
|
|
+ hibernate_seq = hibernate_full_seq;
|
|
|
|
|
+ return {power_off_seq, hibernate_seq};
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Prints just the POWER_OFF/HIBERNATE arrays + EpdCommand tables (plus the
|
|
|
|
|
+// descriptor-field lines to paste) for Driver - for splicing
|
|
|
|
|
+// power_off_sequence/hibernate_sequence into an EXISTING, hand-maintained
|
|
|
|
|
+// panel_*.h (one shipped before these fields existed) without regenerating,
|
|
|
|
|
+// and losing, everything else already in it. tools/capture.py --power.
|
|
|
|
|
+template<typename Driver> void print_power_sequences(const char *macro) {
|
|
|
|
|
+ auto captured = capture_power_off_and_hibernate<Driver>();
|
|
|
|
|
+ const std::vector<Cmd> &power_off_seq = captured.first;
|
|
|
|
|
+ const std::vector<Cmd> &hibernate_seq = captured.second;
|
|
|
|
|
+
|
|
|
|
|
+ auto emit_seq = [&](const char *seqsuffix, const std::vector<Cmd> &cmds) {
|
|
|
|
|
+ for (size_t i = 0; i < cmds.size(); i++)
|
|
|
|
|
+ if (!cmds[i].data.empty()) {
|
|
|
|
|
+ char suf[64];
|
|
|
|
|
+ snprintf(suf, sizeof(suf), "%s_%zu_%02X", seqsuffix, i, cmds[i].cmd);
|
|
|
|
|
+ print_data_array(macro, suf, cmds[i].data);
|
|
|
|
|
+ }
|
|
|
|
|
+ printf("\ninline const EpdCommand %s_%s[] = {\n", macro, seqsuffix);
|
|
|
|
|
+ for (size_t i = 0; i < cmds.size(); i++) {
|
|
|
|
|
+ char suf[64];
|
|
|
|
|
+ snprintf(suf, sizeof(suf), "%s_%zu_%02X", seqsuffix, i, cmds[i].cmd);
|
|
|
|
|
+ print_cmd(macro, suf, cmds[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+ printf("};\n");
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (!power_off_seq.empty()) {
|
|
|
|
|
+ emit_seq("POWER_OFF", power_off_seq);
|
|
|
|
|
+ printf("// splice into the descriptor:\n");
|
|
|
|
|
+ printf("// .power_off_sequence = %s_POWER_OFF,\n", macro);
|
|
|
|
|
+ printf("// .power_off_sequence_len = sizeof(%s_POWER_OFF) / sizeof(%s_POWER_OFF[0]),\n", macro, macro);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printf("// powerOff(): empty capture - this driver's full refresh already leaves\n"
|
|
|
|
|
+ "// _power_is_on false (its _Update_Full() powers itself down), so powerOff()\n"
|
|
|
|
|
+ "// has nothing left to do. No power_off_sequence to add.\n");
|
|
|
|
|
+ }
|
|
|
|
|
+ printf("\n");
|
|
|
|
|
+ if (!hibernate_seq.empty()) {
|
|
|
|
|
+ emit_seq("HIBERNATE", hibernate_seq);
|
|
|
|
|
+ printf("// splice into the descriptor:\n");
|
|
|
|
|
+ printf("// .hibernate_sequence = %s_HIBERNATE,\n", macro);
|
|
|
|
|
+ printf("// .hibernate_sequence_len = sizeof(%s_HIBERNATE) / sizeof(%s_HIBERNATE[0]),\n", macro, macro);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printf("// hibernate(): empty capture (unexpected - every driver seen so far adds at\n"
|
|
|
|
|
+ "// least the deep-sleep trigger when rst is wired). Review by hand.\n");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
template<typename Driver>
|
|
template<typename Driver>
|
|
|
void emit_descriptor_impl(const char *class_name, const char *macro, const PackedResolverFn &resolve_packed) {
|
|
void emit_descriptor_impl(const char *class_name, const char *macro, const PackedResolverFn &resolve_packed) {
|
|
|
constexpr int16_t CS = 10, DC = 11, RST = 12, BUSY = 13;
|
|
constexpr int16_t CS = 10, DC = 11, RST = 12, BUSY = 13;
|
|
@@ -594,6 +699,12 @@ void emit_descriptor_impl(const char *class_name, const char *macro, const Packe
|
|
|
refresh_seq.resize(last_wait + 1);
|
|
refresh_seq.resize(last_wait + 1);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // powerOff()/hibernate(), each its own fresh-session capture - see
|
|
|
|
|
+ // capture_power_off_and_hibernate's doc comment.
|
|
|
|
|
+ auto power_captured = capture_power_off_and_hibernate<Driver>();
|
|
|
|
|
+ std::vector<Cmd> &power_off_seq = power_captured.first;
|
|
|
|
|
+ std::vector<Cmd> &hibernate_seq = power_captured.second;
|
|
|
|
|
+
|
|
|
// --- emit ---
|
|
// --- emit ---
|
|
|
printf("// GENERATED by emit_descriptor from %s - candidate.\n", class_name);
|
|
printf("// GENERATED by emit_descriptor from %s - candidate.\n", class_name);
|
|
|
printf("// Command sequences, plane layout and plane_invert are observed here;\n");
|
|
printf("// Command sequences, plane layout and plane_invert are observed here;\n");
|
|
@@ -637,6 +748,12 @@ void emit_descriptor_impl(const char *class_name, const char *macro, const Packe
|
|
|
if (per_plane)
|
|
if (per_plane)
|
|
|
emit_seq("WINDOW_SEQ1", window1);
|
|
emit_seq("WINDOW_SEQ1", window1);
|
|
|
emit_seq("REFRESH", refresh_seq);
|
|
emit_seq("REFRESH", refresh_seq);
|
|
|
|
|
+ const bool has_power_off = !power_off_seq.empty();
|
|
|
|
|
+ const bool has_hibernate = !hibernate_seq.empty();
|
|
|
|
|
+ if (has_power_off)
|
|
|
|
|
+ emit_seq("POWER_OFF", power_off_seq);
|
|
|
|
|
+ if (has_hibernate)
|
|
|
|
|
+ emit_seq("HIBERNATE", hibernate_seq);
|
|
|
|
|
|
|
|
printf("inline const EpdPanelDescriptor %s = {\n", macro);
|
|
printf("inline const EpdPanelDescriptor %s = {\n", macro);
|
|
|
printf(" .width = %u,\n", (unsigned) Driver::WIDTH);
|
|
printf(" .width = %u,\n", (unsigned) Driver::WIDTH);
|
|
@@ -730,6 +847,15 @@ void emit_descriptor_impl(const char *class_name, const char *macro, const Packe
|
|
|
}
|
|
}
|
|
|
printf(" .refresh_sequence = %s_REFRESH,\n", macro);
|
|
printf(" .refresh_sequence = %s_REFRESH,\n", macro);
|
|
|
printf(" .refresh_sequence_len = sizeof(%s_REFRESH) / sizeof(%s_REFRESH[0]),\n", macro, macro);
|
|
printf(" .refresh_sequence_len = sizeof(%s_REFRESH) / sizeof(%s_REFRESH[0]),\n", macro, macro);
|
|
|
|
|
+ if (has_power_off) {
|
|
|
|
|
+ printf(" .power_off_sequence = %s_POWER_OFF, // probe: powerOff(), fresh session after a normal refresh\n", macro);
|
|
|
|
|
+ printf(" .power_off_sequence_len = sizeof(%s_POWER_OFF) / sizeof(%s_POWER_OFF[0]),\n", macro, macro);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (has_hibernate) {
|
|
|
|
|
+ printf(" .hibernate_sequence = %s_HIBERNATE, // probe: hibernate()'s tail past powerOff() (see file header)\n",
|
|
|
|
|
+ macro);
|
|
|
|
|
+ printf(" .hibernate_sequence_len = sizeof(%s_HIBERNATE) / sizeof(%s_HIBERNATE[0]),\n", macro, macro);
|
|
|
|
|
+ }
|
|
|
printf("};\n\n} // namespace esphome::gxepd2\n");
|
|
printf("};\n\n} // namespace esphome::gxepd2\n");
|
|
|
}
|
|
}
|
|
|
|
|
|