Merge USB session safety and preserve GPS preference compatibility
This commit is contained in:
commit
24fcbf3247
62 files changed
+2479
-240
No files matched your search
@@ -7,6 +7,7 @@ uint32_t tud_cdc_n_write_available(uint8_t instance);
|
||||
uint32_t tud_cdc_n_write(uint8_t instance, const void* data, uint32_t size);
|
||||
uint32_t tud_cdc_n_write_flush(uint8_t instance);
|
||||
bool tud_cdc_n_write_clear(uint8_t instance);
|
||||
void tud_cdc_n_read_flush(uint8_t instance);
|
||||
bool tud_mounted();
|
||||
bool tud_connect();
|
||||
bool tud_disconnect();
|
||||
+203
-16
@@ -2,6 +2,8 @@
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include "helpers/UsbLogging.h"
|
||||
#include "helpers/UsbLoggingClientActivity.h"
|
||||
#include "MeshCore.h"
|
||||
@@ -9,9 +11,9 @@
|
||||
|
||||
MockSerial Serial;
|
||||
static bool connected = true;
|
||||
static bool mounted = true;
|
||||
static unsigned disconnect_calls = 0;
|
||||
static unsigned connect_calls = 0;
|
||||
static bool mounted = true, attached = true, arm_packets = false;
|
||||
static std::string endpoint;
|
||||
static unsigned detach_calls = 0, attach_calls = 0;
|
||||
static bool auto_drain = false;
|
||||
static std::string fifo;
|
||||
static std::string host;
|
||||
@@ -19,10 +21,26 @@ static unsigned write_calls = 0;
|
||||
static unsigned flush_calls = 0;
|
||||
static unsigned clear_calls = 0;
|
||||
static void (*during_write)() = nullptr;
|
||||
static void (*during_disconnect)() = nullptr;
|
||||
|
||||
extern "C" void meshEsp32TinyUsbCdcLineState(bool dtr);
|
||||
extern "C" void meshEsp32TinyUsbTxComplete();
|
||||
extern "C" bool meshEsp32TinyUsbAcceptRx();
|
||||
extern "C" void meshEsp32TinyUsbDeviceSessionBoundary(bool mounted);
|
||||
extern "C" bool meshEsp32TinyUsbTxPending() { return !endpoint.empty(); }
|
||||
bool tud_mounted() { return mounted; }
|
||||
bool tud_disconnect() { ++disconnect_calls; mounted = false; connected = false; return true; }
|
||||
bool tud_connect() { ++connect_calls; return true; }
|
||||
bool tud_disconnect() {
|
||||
++detach_calls;
|
||||
if (during_disconnect) during_disconnect();
|
||||
attached = false;
|
||||
connected = false;
|
||||
return true;
|
||||
}
|
||||
bool tud_connect() { ++attach_calls; attached = true; return true; }
|
||||
void tud_cdc_n_read_flush(uint8_t instance) {
|
||||
assert(instance == 0);
|
||||
assert(mounted && "RX flush must not rearm a reset/unopened OUT endpoint");
|
||||
}
|
||||
|
||||
bool tud_cdc_n_connected(uint8_t instance) {
|
||||
assert(!mock_isr);
|
||||
@@ -47,6 +65,12 @@ uint32_t tud_cdc_n_write_flush(uint8_t instance) {
|
||||
assert(!mock_isr);
|
||||
assert(instance == 0);
|
||||
++flush_calls;
|
||||
if (arm_packets) {
|
||||
if (!endpoint.empty()) return 0;
|
||||
const auto size = fifo.size();
|
||||
endpoint.swap(fifo);
|
||||
return size;
|
||||
}
|
||||
if (!auto_drain) return 0;
|
||||
const auto size = fifo.size();
|
||||
host += fifo;
|
||||
@@ -78,10 +102,10 @@ static void drain_all() {
|
||||
|
||||
static void fresh_session() {
|
||||
connected = false;
|
||||
arduino_usb_cdc_event_data_t event;
|
||||
event.line_state.dtr = false;
|
||||
Serial.callback(nullptr, nullptr, ARDUINO_USB_CDC_LINE_STATE_EVENT, &event);
|
||||
meshEsp32TinyUsbCdcLineState(false); // synchronous TinyUSB owner hook
|
||||
assert(!meshEsp32TinyUsbAcceptRx()); // late old RX cannot refill Arduino queue
|
||||
connected = true; // fast close/reopen, without an intervening service poll
|
||||
meshEsp32TinyUsbCdcLineState(true);
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(mesh::takeUsbTerminalSessionReset());
|
||||
assert(!mesh::takeUsbTerminalSessionReset());
|
||||
@@ -113,25 +137,33 @@ static void check_logging_watchdog_transport_observations_and_recovery() {
|
||||
// Purge and probe merely change queues; neither is host acknowledgement.
|
||||
assert(mesh::observeUsbLoggingTransport().tx_progress == initial.tx_progress);
|
||||
host += fifo; fifo.clear();
|
||||
Serial.callback(nullptr, nullptr, ARDUINO_USB_CDC_TX_EVENT, nullptr);
|
||||
assert(Serial.callback == nullptr); // No delayed event owns health/session state.
|
||||
meshEsp32TinyUsbTxComplete();
|
||||
const auto completed = mesh::observeUsbLoggingTransport();
|
||||
assert(completed.tx_progress == initial.tx_progress + 1);
|
||||
assert(!completed.pending);
|
||||
const auto detached_at = millis();
|
||||
const auto previous_detach = detach_calls, previous_attach = attach_calls;
|
||||
assert(mesh::recoverUsbLoggingTransport(2) == mesh::UsbLoggingRecoveryResult::Attempted);
|
||||
assert(disconnect_calls == 1 && connect_calls == 0);
|
||||
assert(detach_calls == previous_detach + 1 && attach_calls == previous_attach);
|
||||
assert(mesh::isUsbLoggingTransportRecoveryPending());
|
||||
assert(millis() == detached_at); // No blocking wait or descriptor rebuild.
|
||||
mounted = false;
|
||||
meshEsp32TinyUsbDeviceSessionBoundary(false);
|
||||
g_mock_millis += 49;
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(connect_calls == 0);
|
||||
assert(attach_calls == previous_attach);
|
||||
++g_mock_millis;
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(connect_calls == 1 && !mesh::observeUsbLoggingTransport().host_connected);
|
||||
assert(attach_calls == previous_attach + 1 && !mesh::observeUsbLoggingTransport().host_connected);
|
||||
assert(!mesh::isUsbLoggingTransportRecoveryPending());
|
||||
assert(!meshEsp32TinyUsbAcceptRx()); // Await fresh owner mount, not queue admission.
|
||||
mock_isr = true;
|
||||
assert(mesh::recoverUsbLoggingTransport(1) == mesh::UsbLoggingRecoveryResult::Deferred);
|
||||
mesh::probeUsbLoggingTransport();
|
||||
mock_isr = false;
|
||||
mounted = connected = true;
|
||||
meshEsp32TinyUsbDeviceSessionBoundary(true);
|
||||
fresh_session();
|
||||
}
|
||||
|
||||
@@ -152,6 +184,32 @@ static void check_native_short_writes_and_mota() {
|
||||
fresh_session();
|
||||
}
|
||||
|
||||
static void check_watchdog_restarts_abandoned_mount() {
|
||||
fresh_session();
|
||||
const unsigned old_detaches = detach_calls, old_attaches = attach_calls;
|
||||
assert(mesh::recoverUsbLoggingTransport(2) == mesh::UsbLoggingRecoveryResult::Attempted);
|
||||
mounted = false;
|
||||
meshEsp32TinyUsbDeviceSessionBoundary(false);
|
||||
delay(50);
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(detach_calls == old_detaches + 1 && attach_calls == old_attaches + 1);
|
||||
assert(!meshEsp32TinyUsbAcceptRx()); // Host never sends fresh mount.
|
||||
// A later watchdog stage must not get permanently stuck behind the old
|
||||
// awaiting-mount quarantine, nor attach before its own full 50 ms deadline.
|
||||
assert(mesh::recoverUsbLoggingTransport(2) == mesh::UsbLoggingRecoveryResult::Attempted);
|
||||
assert(detach_calls == old_detaches + 2);
|
||||
delay(49);
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(attach_calls == old_attaches + 1);
|
||||
delay(1);
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(attach_calls == old_attaches + 2);
|
||||
assert(!meshEsp32TinyUsbAcceptRx() && !mesh::isUsbLoggingTransportRecoveryPending());
|
||||
mounted = true;
|
||||
meshEsp32TinyUsbDeviceSessionBoundary(true);
|
||||
fresh_session();
|
||||
}
|
||||
|
||||
static void check_ordered_text_and_functional_reserve() {
|
||||
const std::string log = "RAW: " + std::string(545, 'L') + "\r\n";
|
||||
const std::string reply = " -> " + std::string(2177, 'R') + "\r\n";
|
||||
@@ -189,8 +247,17 @@ static void check_stalled_host_and_visible_overflow() {
|
||||
static void check_disconnect_cleanup_keeps_new_host_input() {
|
||||
assert(put(mesh::usbConsolePort(), std::string(1000, 'O')) == 1000);
|
||||
const auto before = clear_calls;
|
||||
Serial.rx_count = 4;
|
||||
fresh_session();
|
||||
Serial.rx_count = 12; // old owner queued commands while output was paused
|
||||
connected = false;
|
||||
meshEsp32TinyUsbCdcLineState(false);
|
||||
assert(Serial.rx_count == 0);
|
||||
connected = true;
|
||||
meshEsp32TinyUsbCdcLineState(true);
|
||||
Serial.rx_count = 4; // new host sends its first query BEFORE main-loop cleanup
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(mesh::takeUsbTerminalSessionReset());
|
||||
assert(mesh::tryCompleteUsbTerminalSessionReset());
|
||||
host.clear();
|
||||
assert(clear_calls > before);
|
||||
assert(!mesh::hasPendingUsbTerminalOutput());
|
||||
assert(mesh::usbConsolePort().available() == 4);
|
||||
@@ -202,6 +269,118 @@ static void check_disconnect_cleanup_keeps_new_host_input() {
|
||||
fresh_session();
|
||||
}
|
||||
|
||||
static void check_armed_endpoint_reenumerates_without_leaking() {
|
||||
fresh_session();
|
||||
arm_packets = true;
|
||||
const unsigned disconnected_before = detach_calls, connected_before = attach_calls;
|
||||
assert(put(mesh::usbConsolePort(), "old packet") == 10);
|
||||
assert(endpoint == "old packet" && fifo.empty());
|
||||
Serial.rx_count = 12;
|
||||
connected = false;
|
||||
meshEsp32TinyUsbCdcLineState(false);
|
||||
assert(detach_calls == disconnected_before + 1 && !attached);
|
||||
assert(Serial.rx_count == 0 && !meshEsp32TinyUsbAcceptRx());
|
||||
assert(put(mesh::usbCompanionPort(), "racing old writer") == 0);
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(attach_calls == connected_before); // real detach interval, no wait loop
|
||||
delay(21);
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(attach_calls == connected_before + 1 && attached);
|
||||
assert(!meshEsp32TinyUsbAcceptRx()); // retain quarantine until reset/mount
|
||||
endpoint.clear(); // actual bus reset cancels controller packet, not write_clear
|
||||
fifo.clear();
|
||||
meshEsp32TinyUsbDeviceSessionBoundary(true);
|
||||
connected = true;
|
||||
meshEsp32TinyUsbCdcLineState(true);
|
||||
Serial.rx_count = 4;
|
||||
assert(meshEsp32TinyUsbAcceptRx());
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(mesh::takeUsbTerminalSessionReset());
|
||||
assert(mesh::tryCompleteUsbTerminalSessionReset());
|
||||
assert(mesh::usbConsolePort().available() == 4);
|
||||
arm_packets = false;
|
||||
drain_all();
|
||||
assert(host.find("old packet") == std::string::npos);
|
||||
fresh_session();
|
||||
}
|
||||
|
||||
static void check_unmount_purges_rx_without_rearming_endpoint() {
|
||||
fresh_session();
|
||||
Serial.rx_count = 12;
|
||||
mounted = false;
|
||||
connected = false;
|
||||
meshEsp32TinyUsbDeviceSessionBoundary(false);
|
||||
assert(Serial.rx_count == 0);
|
||||
assert(!meshEsp32TinyUsbAcceptRx());
|
||||
mesh::serviceUsbLoggingPort();
|
||||
mounted = true;
|
||||
meshEsp32TinyUsbDeviceSessionBoundary(true);
|
||||
connected = true;
|
||||
meshEsp32TinyUsbCdcLineState(true);
|
||||
Serial.rx_count = 4;
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(mesh::takeUsbTerminalSessionReset());
|
||||
assert(mesh::tryCompleteUsbTerminalSessionReset());
|
||||
assert(mesh::usbConsolePort().available() == 4);
|
||||
fresh_session();
|
||||
}
|
||||
|
||||
static void check_logging_disable_retains_functional_reply() {
|
||||
fresh_session();
|
||||
const std::string reply(500, 'R');
|
||||
assert(put(mesh::usbConsolePort(), reply) == reply.size());
|
||||
mesh::setUsbLoggingEnabled(false);
|
||||
assert(mesh::hasPendingUsbTerminalOutput());
|
||||
assert(put(mesh::usbConsolePort(), "next reply") == 10);
|
||||
drain_all();
|
||||
assert(host == reply + "next reply");
|
||||
mesh::setUsbLoggingEnabled(true);
|
||||
fresh_session();
|
||||
}
|
||||
|
||||
static std::atomic<bool> writer_started{false}, writer_release{false};
|
||||
static std::thread* racing_writer = nullptr;
|
||||
static unsigned race_attach_baseline = 0;
|
||||
static void check_detach_publication_waits_for_physical_disconnect() {
|
||||
fresh_session();
|
||||
writer_started = false;
|
||||
writer_release = false;
|
||||
during_write = [] {
|
||||
writer_started.store(true, std::memory_order_release);
|
||||
while (!writer_release.load(std::memory_order_acquire)) std::this_thread::yield();
|
||||
};
|
||||
std::thread writer([] { assert(put(mesh::usbCompanionPort(), "racing old") == 10); });
|
||||
racing_writer = &writer;
|
||||
while (!writer_started.load(std::memory_order_acquire)) std::this_thread::yield();
|
||||
race_attach_baseline = attach_calls;
|
||||
during_disconnect = [] {
|
||||
// The writer finishes just after the owner's failed exclusive attempt.
|
||||
writer_release.store(true, std::memory_order_release);
|
||||
racing_writer->join();
|
||||
delay(25); // stale deadline would now allow the other core to reconnect
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(attach_calls == race_attach_baseline);
|
||||
};
|
||||
connected = false;
|
||||
meshEsp32TinyUsbCdcLineState(false);
|
||||
during_disconnect = nullptr;
|
||||
during_write = nullptr;
|
||||
assert(!attached && !meshEsp32TinyUsbAcceptRx());
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(attach_calls == race_attach_baseline);
|
||||
delay(21);
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(attach_calls == race_attach_baseline + 1 && fifo.empty());
|
||||
meshEsp32TinyUsbDeviceSessionBoundary(true);
|
||||
connected = true;
|
||||
meshEsp32TinyUsbCdcLineState(true);
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(mesh::takeUsbTerminalSessionReset());
|
||||
assert(mesh::tryCompleteUsbTerminalSessionReset());
|
||||
assert(host.find("racing old") == std::string::npos);
|
||||
fresh_session();
|
||||
}
|
||||
|
||||
static void check_debug_formatter_and_reentrancy() {
|
||||
mesh::setUsbDebugEnabled(true);
|
||||
auto_drain = true;
|
||||
@@ -273,10 +452,11 @@ static void check_protocol_switch_cancels_text_and_overflow_marker() {
|
||||
assert(put(mesh::usbConsolePort(), std::string(5000, 'X')) == 0);
|
||||
assert(put(mesh::usbLoggingPort(), std::string(550, 'L')) == 550);
|
||||
mesh::setUsbLoggingEnabled(false);
|
||||
assert(!mesh::hasPendingUsbTerminalOutput());
|
||||
assert(mesh::hasPendingUsbTerminalOutput()); // no protocol switch yet
|
||||
// A protocol owner may discard application text without resetting USB. The
|
||||
// already accepted FIFO prefix stays ordered before the new binary frame.
|
||||
mesh::discardUsbTerminalOutput();
|
||||
assert(!mesh::hasPendingUsbTerminalOutput());
|
||||
host += fifo;
|
||||
fifo.clear();
|
||||
assert(put(mesh::usbCompanionPort(), "binary") == 6);
|
||||
@@ -297,17 +477,24 @@ int main() {
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
mesh::beginUsbLoggingPort();
|
||||
mesh::beginUsbLoggingPort();
|
||||
meshEsp32TinyUsbDeviceSessionBoundary(true);
|
||||
meshEsp32TinyUsbCdcLineState(true);
|
||||
mesh::setUsbLoggingEnabled(true);
|
||||
assert(Serial.registrations == 1);
|
||||
assert(Serial.registrations == 0); // delayed Arduino events cannot purge new RX
|
||||
assert(!Serial.debug_enabled);
|
||||
mesh::serviceUsbLoggingPort();
|
||||
assert(&mesh::usbConsolePort() == &mesh::usbTerminalPort());
|
||||
check_logging_watchdog_transport_observations_and_recovery();
|
||||
check_watchdog_restarts_abandoned_mount();
|
||||
check_independent_debug_gate();
|
||||
check_native_short_writes_and_mota();
|
||||
check_ordered_text_and_functional_reserve();
|
||||
check_stalled_host_and_visible_overflow();
|
||||
check_disconnect_cleanup_keeps_new_host_input();
|
||||
check_armed_endpoint_reenumerates_without_leaking();
|
||||
check_unmount_purges_rx_without_rearming_endpoint();
|
||||
check_logging_disable_retains_functional_reply();
|
||||
check_detach_publication_waits_for_physical_disconnect();
|
||||
check_debug_formatter_and_reentrancy();
|
||||
check_cached_logging_gate_and_isr();
|
||||
check_protocol_switch_cancels_text_and_overflow_marker();
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
struct MockNrfUsbd { uint32_t USBPULLUP = 1; };
|
||||
extern MockNrfUsbd mock_usbd;
|
||||
#define NRF_USBD (&mock_usbd)
|
||||
inline void __ISB() {}
|
||||
inline void __DSB() {}
|
||||
+8
-3
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
#include <nrf.h>
|
||||
#include <helpers/UsbLogging.h>
|
||||
#include <helpers/UsbLoggingClientActivity.h>
|
||||
#include <cassert>
|
||||
@@ -9,6 +10,7 @@
|
||||
|
||||
MockSerial Serial;
|
||||
MockUsbDevice TinyUSBDevice;
|
||||
MockNrfUsbd mock_usbd;
|
||||
static bool mounted = true, dtr = true, dfu = false;
|
||||
static bool attached = true, armed_in = false;
|
||||
static unsigned detaches = 0, attaches = 0;
|
||||
@@ -22,8 +24,8 @@ extern "C" void tud_umount_cb();
|
||||
bool tud_mounted() { return mounted; }
|
||||
bool tud_connect() { mounted = true; return true; }
|
||||
bool tud_disconnect() { mounted = false; dtr = false; return true; }
|
||||
void MockUsbDevice::detach() { attached = false; ++detaches; }
|
||||
void MockUsbDevice::attach() { attached = true; ++attaches; }
|
||||
void MockUsbDevice::detach() { attached = false; mock_usbd.USBPULLUP = 0; ++detaches; }
|
||||
void MockUsbDevice::attach() { attached = true; mock_usbd.USBPULLUP = 1; ++attaches; }
|
||||
extern "C" bool mesh_tud_cdc_n_tx_pending(uint8_t n) {
|
||||
assert(n == 0);
|
||||
return armed_in;
|
||||
@@ -141,8 +143,11 @@ int main() {
|
||||
armed_in = true;
|
||||
dtr = false;
|
||||
tud_cdc_line_state_cb(0, false, false);
|
||||
assert(fifo.empty() && armed_in && !attached && detaches == 1);
|
||||
assert(fifo.empty() && armed_in && mock_usbd.USBPULLUP == 0);
|
||||
assert(attached && detaches == 0); // Owner callback only quarantines pull-up.
|
||||
assert(mesh::isUsbLoggingTransportRecoveryPending());
|
||||
mesh::serviceUsbLoggingPort(); // Actual stack detach belongs to application loop.
|
||||
assert(!attached && detaches == 1);
|
||||
assert(mesh::recoverUsbLoggingTransport(2) == mesh::UsbLoggingRecoveryResult::Attempted);
|
||||
const auto detached_at = g_mock_millis;
|
||||
g_mock_millis += 20;
|
||||
|
||||
@@ -48,8 +48,10 @@ extern MockFicr mock_ficr;
|
||||
#define WB_IO2 2
|
||||
#define OUTPUT 1
|
||||
#define HIGH 1
|
||||
#define INPUT_PULLUP 2
|
||||
inline void pinMode(int, int) {}
|
||||
inline void digitalWrite(int, int) {}
|
||||
inline int digitalRead(int) { return HIGH; }
|
||||
inline void vTaskDelete(void*) {}
|
||||
inline void vTaskDelay(uint32_t value) { delay(value); }
|
||||
inline uint32_t pdMS_TO_TICKS(uint32_t value) { return value; }
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define ETH_MISO_PIN 1
|
||||
#define ETH_MOSI_PIN 2
|
||||
#define ETH_SCLK_PIN 3
|
||||
#define ETH_CS_PIN 4
|
||||
#define ETH_INT_PIN 5
|
||||
|
||||
struct ch390_config_t {
|
||||
int spi_miso_gpio = 0;
|
||||
int spi_mosi_gpio = 0;
|
||||
int spi_sck_gpio = 0;
|
||||
int spi_cs_gpio = 0;
|
||||
int int_gpio = 0;
|
||||
};
|
||||
|
||||
#define CH390_DEFAULT_CONFIG() ch390_config_t{}
|
||||
|
||||
class MockCh390 {
|
||||
public:
|
||||
bool valid_hostname = false;
|
||||
unsigned begins = 0;
|
||||
bool setHostname(const char*) const { return valid_hostname; }
|
||||
bool begin(const ch390_config_t&) { ++begins; return true; }
|
||||
};
|
||||
|
||||
extern MockCh390 CH390;
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <array>
|
||||
|
||||
class TwoWire {
|
||||
public:
|
||||
bool present = true;
|
||||
bool all_addresses_present = false;
|
||||
unsigned probes = 0;
|
||||
unsigned fail_probe_count = 0;
|
||||
std::array<uint8_t, 5> frame{{0, 1, 2, 3, 4}};
|
||||
uint8_t reply_length = 5;
|
||||
uint8_t current_address = 0;
|
||||
unsigned cursor = 0;
|
||||
void beginTransmission(uint8_t address) { current_address = address; ++probes; }
|
||||
uint8_t endTransmission() const {
|
||||
if (probes <= fail_probe_count) return 2;
|
||||
return (all_addresses_present || (present && current_address == 0x2E)) ? 0 : 2;
|
||||
}
|
||||
uint8_t requestFrom(uint8_t, uint8_t) { cursor = 0; return reply_length; }
|
||||
int available() const { return static_cast<int>(reply_length) - static_cast<int>(cursor); }
|
||||
int read() { return cursor < reply_length ? frame[cursor++] : -1; }
|
||||
};
|
||||
|
||||
extern TwoWire Wire;
|
||||
+95
@@ -1,6 +1,8 @@
|
||||
#include <helpers/NonBlockingWriteStream.h>
|
||||
#include <helpers/nrf52/EthernetCLI.h>
|
||||
#include "NetworkLoggingMacro.h"
|
||||
#include <helpers/ui/CHSC6XTouch.h>
|
||||
#include <helpers/ethernet/ch390/CH390Config.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
@@ -8,6 +10,8 @@ uint32_t mock_millis = 0;
|
||||
RawSerial Serial;
|
||||
MockFicr mock_ficr;
|
||||
MockEthernet Ethernet;
|
||||
TwoWire Wire;
|
||||
MockCh390 CH390;
|
||||
std::string ethernet_output;
|
||||
std::string ethernet_input;
|
||||
bool accept_client = false;
|
||||
@@ -153,5 +157,96 @@ int main() {
|
||||
assert(evaluated == 0 && usb.output.empty() && usb.tries == 0);
|
||||
#endif
|
||||
assert(mock_millis == 0);
|
||||
|
||||
// Touch probe, late-presence and optional raw frames use the same runtime
|
||||
// logging gate as every other diagnostic, including protected boot mode.
|
||||
reset();
|
||||
logging_enabled = false;
|
||||
Wire = TwoWire();
|
||||
CHSC6XTouch found;
|
||||
assert(found.begin());
|
||||
Wire.frame[0] = 0x1F;
|
||||
assert(!found.checkTap(500));
|
||||
assert(found.checkTap(600));
|
||||
assert(usb.output.empty() && usb.tries == 0);
|
||||
|
||||
Wire = TwoWire();
|
||||
Wire.present = false;
|
||||
CHSC6XTouch late;
|
||||
assert(!late.begin());
|
||||
assert(Wire.probes == 1); // No diagnostic I2C scan when logging is off.
|
||||
Wire.present = true;
|
||||
Wire.frame[0] = 0x1F;
|
||||
assert(!late.checkTap(500));
|
||||
assert(late.checkTap(600));
|
||||
Wire.reply_length = 2;
|
||||
late.checkTap(700);
|
||||
assert(usb.output.empty() && usb.tries == 0);
|
||||
|
||||
reset();
|
||||
debug_enabled = false;
|
||||
Wire = TwoWire();
|
||||
CHSC6XTouch packet_only;
|
||||
assert(packet_only.begin());
|
||||
Wire.frame[0] = 0x1F;
|
||||
assert(!packet_only.checkTap(500));
|
||||
assert(packet_only.checkTap(600));
|
||||
assert(logging_enabled && usb.output.empty() && usb.tries == 0);
|
||||
Wire = TwoWire();
|
||||
Wire.present = false;
|
||||
CHSC6XTouch quiet_scan;
|
||||
assert(!quiet_scan.begin() && Wire.probes == 1);
|
||||
|
||||
reset();
|
||||
Wire = TwoWire();
|
||||
CHSC6XTouch visible;
|
||||
assert(visible.begin());
|
||||
assert(usb.output == "Touch: CHSC6X found at 0x2E\n");
|
||||
visible.checkTap(500);
|
||||
#if defined(DISPLAY_TOUCH_DEBUG)
|
||||
assert(usb.output.find("Touch: raw 00 01 02 03 04") != std::string::npos);
|
||||
#endif
|
||||
usb.output.clear();
|
||||
usb.capacity = 0;
|
||||
CHSC6XTouch stalled;
|
||||
assert(stalled.begin());
|
||||
stalled.checkTap(500);
|
||||
assert(usb.output.empty());
|
||||
|
||||
reset();
|
||||
Wire = TwoWire();
|
||||
Wire.present = false;
|
||||
Wire.all_addresses_present = false;
|
||||
CHSC6XTouch absent;
|
||||
assert(!absent.begin());
|
||||
assert(usb.output.find("Touch: idle or absent at 0x2E; I2C bus currently answers:\n") == 0);
|
||||
assert(Wire.probes == 113);
|
||||
|
||||
reset();
|
||||
usb.capacity = 640;
|
||||
Wire = TwoWire();
|
||||
Wire.all_addresses_present = true;
|
||||
Wire.fail_probe_count = 1; // Idle boot probe, then every scan address replies.
|
||||
CHSC6XTouch full_scan;
|
||||
assert(!full_scan.begin());
|
||||
assert(usb.output.size() > 600 && usb.output.size() < 640 && usb.tries == 1);
|
||||
assert(usb.output.substr(usb.output.size() - 6) == " 0x77\n");
|
||||
|
||||
reset();
|
||||
CH390 = MockCh390();
|
||||
logging_enabled = false;
|
||||
assert(beginConfiguredCH390("invalid hostname"));
|
||||
assert(CH390.begins == 1 && usb.output.empty() && usb.tries == 0);
|
||||
logging_enabled = true;
|
||||
debug_enabled = false;
|
||||
assert(beginConfiguredCH390("invalid hostname"));
|
||||
assert(usb.output.empty() && usb.tries == 0);
|
||||
debug_enabled = true;
|
||||
assert(beginConfiguredCH390("invalid hostname"));
|
||||
assert(usb.output == "Network: invalid Ethernet hostname invalid hostname\n");
|
||||
usb.output.clear();
|
||||
usb.capacity = 0;
|
||||
assert(beginConfiguredCH390("invalid hostname"));
|
||||
assert(usb.output.empty());
|
||||
std::cout << "PASS: gated USB diagnostics\n";
|
||||
}
|
||||
Reference in new issue
Block a user