Repository navigation
Push Version 0.1.0 to main - #1
Merged
Merged
Conversation
Analyse von OWL, OpenDrop und dem iOS-26-tauglichen airdrop-mt7921-Projekt, ESP-IDF-Fähigkeiten für Raw-TX/Promiscuous-RX, C3 vs. C5, Architektur- optionen für Linux/Windows und ein Fahrplan beginnend mit Hardware-Spikes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Captures and serial logs contain MAC addresses and device names of nearby people; keep them out of the public repository. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Portiert aus OWL (GPL-3.0): Aufbau von PSF/MIF-Action-Frames und AWDL-Datenframes sowie Parsing empfangener Action-Frames (Kanalsequenz, ARPA-Name, Version, Election-Master). Offset-basiert über awdl_wire.h, damit endian- und alignment-sicher und ohne ESP-IDF nativ testbar. Der Host-Test baut PSF/MIF/Daten, parst sie zurück und prueft, dass die TLVs den Puffer exakt kacheln. Der erzeugte MIF stimmt im Layout mit OWL ueberein (u.a. Sync-Params-TLV-Laenge 73, DPS-Flags 0x8f24). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
ESP-IDF projekt in firmware/spikes/ mit USB-Serial/JTAG-Konsole: - radio.c: esp_wifi-Glue, Raw-TX (esp_wifi_80211_tx), Promiscuous-RX gefiltert auf die AWDL-BSSID, RX/TX-Zaehler, Peer-Ereignis-Queue, tx-done-Status fuer den ACK-Test. - console.c: Befehle info/mac/chan/rx/tx/txdata/peers/stats/chanlat/usbbench fuer die fuenf Spikes aus docs/SPIKES.md. - Baut sauber fuer esp32c3 (ESP-IDF 6.0.3); Host-Test weiterhin gruen. Dazu docs/SPIKES.md (Testplan + Wireshark-/Monitor-Setup), firmware/README.md und tools/usb_throughput.py. Der awdl-Kern bleibt ESP-IDF-frei und testbar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Die oeffentliche esp_wifi_register_80211_tx_cb / esp_80211_tx_info_t API kam erst in ESP-IDF 5.5; auf aelteren Installationen schlug der Build fehl (unknown type esp_80211_tx_info_t). Die interne esp_wifi_set_tx_done_cb aus esp_private/wifi.h gibt es mit identischer Signatur in 5.1..6.x und liefert den txStatus, den der ACK-Test braucht. Keine Versionsabhaengigkeit mehr. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Ohne Default-Event-Loop scheitert das Posten der WLAN-Events (E wifi:failed to post WiFi event ... ret=259 = ESP_ERR_INVALID_STATE). esp_netif_init() + esp_event_loop_create_default() in radio_init ergaenzt; esp_event/esp_netif zu REQUIRES. Behebt die Fehlermeldungen beim Start. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Sendet von der Monitor-Karte des Laptops Unicast-AWDL-Datenframes an eine Ziel-MAC (den C3), byte-genau so aufgebaut wie awdl_parse_data der Firmware sie erwartet. So klaert Spike 3: ACKt der C3 Frames an seine eigene MAC (parallele Aufzeichnung) und liefert er sie an den Promiscuous-Callback (C3-Zaehler rx_data). Variante A/B und die Pruefschritte stehen im Modul-Docstring. --dry-run baut/hexdumpt ohne scapy/root. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Ein 802.11-ACK traegt nur eine Adresse (RA), und das ist der Absender des bestaetigten Frames, also unsere --src, nicht die MAC des C3. Die Doku und der --listen-Filter pruefen jetzt RA == --src. Spike 3 damit eindeutig: Zustellung ja (rx_data) und HW-ACK ja (ACK mit RA == --src). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
usb_throughput.py kann den usbbench-Befehl jetzt selbst an den C3 senden (--trigger KIB) und misst gap-basiert (Uhr startet beim ersten Byte, stoppt nach einer Pause), statt ein festes Fenster zu mitteln. Macht Spike 5 zu einem Host-seitigen Einzelbefehl. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Plattformunabhaengige Portierung aus OWL (GPL-3.0), host-getestet:
- awdl_sync.{h,c}: AWDL-Zeitmathematik (AW/EAW, Phasenfehler, Nachfuehren
auf den Master) aus src/sync.c.
- awdl_election.{h,c}: Election-Zustand und Vergleiche aus src/election.c.
- awdl_peers.{h,c}: feste Peer-Tabelle ohne dynamischen Speicher plus der
Election-Lauf ueber das Array.
- Parser zieht jetzt auch Sync-Params (tx_down_counter, aw_counter) und die
vollen Election-Felder (v1 und v2) heraus.
Host-Test erweitert (Sync-Params/Election durchs Parsen, Sync-Mathematik,
Election-Lauf inkl. Alterung) - alle gruen; C3-Build sauber (ESP-IDF 6.0.3).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Aus dem adversarialen Review der OWL-Portierung: - tlv_end schrieb die Laengen-Rueckpatchung ungeprueft und bedingungslos; bei zu kleinem cap ein Out-of-Bounds-Write + size_t-Unterlauf. tlv_end nimmt jetzt cap und ist ein No-op, wenn der TL-Header nicht passte. Damit haelt awdl_build_action den 'schreibt nie ueber cap'-Vertrag ein. - Der Parser schrieb Election-Felder direkt in *out; ein laengen-verkuerztes v2-TLV konnte bereits geparste v1-Daten teilweise ueberschreiben und galt trotzdem als gueltig. Sync-/v1-/v2-Faelle lesen jetzt in lokale Variablen und uebernehmen erst bei vollstaendigem Erfolg (Frames kommen aus der Luft). Regressionstests ergaenzt (cap-Respekt mit Canary, verkuerztes v2 ohne Clobber); Host-Tests inkl. ASan/UBSan gruen, C3-Build sauber. Sync- und Election-Module wurden als korrekt bestaetigt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- awdl_node.{h,c}: plattformunabhaengiges Gehirn, das Self/Sync/Election/Peers
verbindet. on_rx_action aktualisiert den Peer, faehrt die Election und fuehrt
bei unserem Master die AW-Uhr nach; tick altert Peers und waehlt neu; build
erzeugt PSF/MIF mit der synchronisierten Zeit. Host-Test prueft: iPhone wird
Master, Sync-Fehler ~0 nach Adoption, Peer wird gehalten und altert korrekt.
- awdl_build_action nimmt jetzt die Sync-Zeitwerte als Parameter (aus unserer
Uhr) statt einer freilaufenden Epoche.
- radio.c: statt eigener Peer-Queue ein registrierter Handler, der geparste
Action-Frames an die App gibt.
- main/awdl_app.{h,c}: ein FreeRTOS-Task besitzt den Node, verarbeitet RX aus
einer Queue und sendet PSF/MIF + Cleanup zeitgesteuert; alles unter einem
Mutex. Frame-Puffer statisch (Stack-Schutz), groessere Task-Stacks.
- console: tx/peers ueber die App, neuer sync-Befehl (Master/Hoehe/Sync-Fehler/
Peers); mac/chan bauen den Node neu.
Host-Tests inkl. ASan/UBSan gruen; C3-Build sauber (ESP-IDF 6.0.3).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Der erste Lock auf einen Master verglich zwei fremde Uhren (Boot-Epoche vs. iPhone-Epoche) und meldete einen riesigen Scheinfehler (z.B. -235896 TU). Das ist kein Drift, sondern nur der einmalige Versatz. Der Node merkt sich jetzt den getrackten Master; der erste Lock meldet sync_err 0, erst Folge- Frames desselben Masters zeigen den echten (kleinen) Drift. sync_locked wird geloescht, sobald kein Master mehr da ist. Host-Test prueft beides. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
iOS lehnt eine Sequenz ab, die stur einen Kanal in allen 16 Slots behauptet (mt7921-Findings). Der Node spiegelt jetzt die Sequenz des Masters: Kanal 6 genau in dessen Ch-6-Slots, sonst 0 (abwesend) - ehrlich und strukturell Apple-aehnlich. Ohne Master faellt er auf die statische Einzelkanal-Sequenz zurueck. awdl_self_set_chanseq_intersect/_static ergaenzt; Node ruft das nach jeder Election. Host-Test prueft Spiegelung (6 in Slots 0/8, sonst 0) und Rueckfall; inkl. ASan/UBSan gruen, C3-Build sauber. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Aus dem Design-Review der Kanalsequenz-Spiegelung: - Leere Schnittmenge (Master ohne Ch-6-Slot) faellt jetzt auf die statische Alles-6-Sequenz zurueck statt 'nirgends praesent' zu bewerben. - TX ist slot-bewusst: PSF/MIF gehen nur in Slots raus, die wir als praesent bewerben (awdl_node_tx_slot_ready); so senden wir nicht in als abwesend beworbenen Slots und treffen die Overlap-Slots des iPhones. Konsolen-tx sendet weiterhin sofort (force). Eigener Sync bleibt empfangsgetrieben. - Header-Kommentar entschaerft: Akzeptanz einer Nur-Ch6-Sequenz und von Slot-0=0 ist auf Hardware noch offen (M2). Host-Tests ergaenzt (leere Schnittmenge, realistische Slot-0-Infra-Sequenz, TX-Slot-Gating); inkl. ASan/UBSan gruen, C3-Build sauber. Keine Code-Bugs im Review, nur diese Design-Punkte. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Erster Baustein des M2-Datenpfads: ein COBS-gerahmtes, typ-getaggtes
Nachrichtenprotokoll ueber USB-Serial/JTAG.
- awdl_cobs.{h,c}: plattformunabhaengiger COBS-Encoder/-Decoder + Byte-weiser
Stream-Reassembler. Host-Test mit Vektoren, Kantenfaellen (254/255-Byte-
Bloecke, eingebettete Nullen, Overflow, Truncation) und 20000-Iterationen-
Fuzz, inkl. ASan/UBSan.
- main/bridge.{h,c}: ESP-Transport auf dem nativen usb_serial_jtag-Treiber,
RX-Task, Dispatch; M2.0 behandelt ECHO (Transporttest) und LOG. Nachrichten-
typen FRAME/PEER_*/CMD fuer spaetere Stufen schon definiert. Log-Ausgabe
wird stummgeschaltet, damit sie den Binaerstrom nicht korrumpiert.
- Kconfig-Option ESPDROP_BRIDGE_MODE: Bruecken- statt Konsolenmodus; app_main
verzweigt. M2.0 bringt WLAN noch nicht hoch (reiner Transporttest).
- tools/bridge_echo.py: Host-Echo-Test; sein COBS stimmt byte-genau mit der
Firmware ueberein (gegengeprueft, 20k-Fuzz).
Beide C3-Builds sauber (Konsole 822 KB, Bruecke 149 KB); Host-Tests gruen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Aus dem Design-Review der Bruecke (COBS-Codec war sauber): - s_started wird vor xTaskCreate gesetzt, damit der hoeher-priorisierte RX-Task ein sofort anliegendes ECHO nicht verwirft (haette das erste Echo im Test verschlucken koennen). - bridge_send schreibt den ganzen Frame in einer Schleife ueber Kurz-Writes und nutzt ein beschraenktes Write-Timeout (1 s), damit ein nicht leerender Host nicht den Mutex und damit alle Sender dauerhaft blockiert. - s_tx_lock wird auf den Fehlerpfaden freigegeben. - Log-Stummschaltung ehrlicher kommentiert (ROM-/Panic-Ausgabe bleibt; COBS resynchronisiert am naechsten Delimiter). - RX-Task ignoriert n<=0 defensiv. Beide C3-Builds sauber, Host-Tests inkl. ASan/UBSan gruen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
… the bridge Symptom (host echo test): replies grew cumulatively and never matched, i.e. the 0x00 frame delimiters were being lost. Cause: in bridge mode the native usb_serial_jtag driver AND the ESP console (on USB-Serial/JTAG via sdkconfig.defaults) both owned the same endpoint; the console's echo / line discipline corrupted the binary COBS stream. Fix: sdkconfig.bridge.defaults sets CONFIG_ESP_CONSOLE_NONE=y (plus ESPDROP_BRIDGE_MODE), so only the native driver touches the port. main's CMake REQUIRES is now config-INDEPENDENT (IDF expands requirements before CONFIG_* is set, so a config-conditional REQUIRES silently dropped esp_driver_usb_serial_jtag and broke the build); app_main.c keeps the #if mode split. Build bridge mode in its own dir with the layered defaults. Builds verified: bridge 143 KB (console NONE), console 822 KB. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
undefined reference to usb_serial_jtag_driver_install on IDF < 5.3: the driver moved from the legacy 'driver' component (5.1/5.2) to 'esp_driver_usb_serial_jtag' (5.3+). main's REQUIRES now picks the component from IDF_VERSION (available during the early requirements pass, unlike CONFIG_*), so it links on 5.1 through 6.x. Verified on 6.0.3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Flip the version test so a missing/empty IDF_VERSION picks esp_driver_usb_serial_jtag (correct on 5.3+) instead of falling through to the legacy 'driver' (which on 5.3+ lacks the symbol -> undefined reference). Only positively-detected 5.1/5.2 uses 'driver'. A configure-time message prints the chosen component and IDF version for diagnosis. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
…rial_jtag) Root cause of the link error on a fresh build_bridge dir: idf.py defaults a new build to target esp32, which has no USB-Serial/JTAG peripheral, so CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED is off and the driver source (with usb_serial_jtag_driver_install) is not compiled -> undefined reference, even though the correct component is required. sdkconfig.bridge.defaults now pins CONFIG_IDF_TARGET=esp32c3; the configure message also prints the resolved target. Verified: fresh build resolves to esp32c3 and links. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
The M2.0 echo test reported growing MISMATCHes (decoded reply +1026 bytes per echo) even though the C3 was sending correctly delimited frames. Root cause was entirely host-side, in tools/bridge_echo.py's Reader: feed() reset self.buf *after* the yield. The echo loop breaks right after the first ECHO_REPLY, which suspends and abandons the feed() generator at the yield, so the buffer reset never ran. Each subsequent reply was then appended onto all prior ones and COBS-decoded merged, giving exactly (n+1)*1025 + (n-1) - 1 payload bytes at echo n (2050, 3076, 4102, ...). Fix feed() to consume the whole chunk and return a list of completed frames, resetting the buffer before any frame is handed out. This also removes a second latent bug: abandoning the read_for generator on break dropped any bytes following the delimiter in that chunk. Verified by simulation (500/500 OK, including adversarially misaligned read chunks). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Host: - tools/espdrop_link.py: shared COBS codec, fixed Reader, frame(), open_serial_raw, write_all, read_frames. Single source of truth so the device and every host tool agree on the wire format byte-for-byte. - tools/awdl_bridge.py: creates a Linux TAP 'awdl0', sets its MAC to the C3's Wi-Fi MAC (queried via the `mac` command), brings it up, and shuttles Ethernet frames between awdl0 and the device over BRIDGE_MSG_FRAME. Also an stdin console: mac | stats | ping | testframe. - tools/bridge_echo.py: refactored onto espdrop_link (no behaviour change). Device (bridge.c/.h): - handle BRIDGE_MSG_CMD -> CMD_REPLY (mac via esp_read_mac, stats, ping, testframe) and account incoming BRIDGE_MSG_FRAME. No radio yet (M2.1), so host->device frames are counted and dropped; M2.2+ transmits them. - send_test_frame emits one synthetic Ethernet frame (ethertype 0x88b5) so the device->host->awdl0 direction is testable before any radio exists. - CMakeLists: add esp_hw_support for esp_mac.h / esp_read_mac. Validated in-container against a real kernel TAP (create/teardown, frame write to an UP interface) and by codec/echo round-trip simulation (500/500, incl. full-MTU frames and adversarially misaligned reads). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Review follow-up on the M2.1 bridge: - awdl_bridge.py: the usb read path now treats EOF (empty read) and OSError as a device disconnect and breaks cleanly, instead of busy-spinning at 100% CPU (EOF) or crashing with a traceback (OSError) when the C3 is unplugged or reset. All setup (MAC query, TAP creation, `ip link`) now runs inside the try/finally so teardown and the summary always print. The stdin command send and query_mac's write are wrapped so a stalled/gone link warns instead of crashing. Frame forwarding uses a bounded 1s write timeout so one direction under USB backpressure cannot stall the other for long (a queued design is deferred to the throughput milestones). - bridge.c: route CMD replies through a cmd_reply() helper that derives the length from strlen, so a truncated snprintf can never be reported as longer than its buffer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
…ath) Bridge mode now brings up the radio and the AWDL node (as M1 validated: sync, election, peers, slot-gated PSF/MIF) and forwards received AWDL data frames to the host as Ethernet, so iPhone traffic on channel 6 shows up on awdl0 in Wireshark/tcpdump. - radio.c/.h: add a data-frame handler (mirror of the action handler). The promiscuous RX callback reconstructs Ethernet from a parsed data frame (dst=addr1, src=addr2, carried ethertype + L3 payload) and hands it to the consumer from Wi-Fi task context. - bridge.c/.h: a drop-on-overflow RX-forward queue + dedicated task. The Wi-Fi callback (bridge_rx_frame) builds the Ethernet frame into a serialized scratch slot and enqueues; the task drains it with bridge_send(FRAME), so the bounded USB write never runs on the Wi-Fi or AWDL-timing tasks. `stats` now also reports frames_to_host and rx_drops. - app_main.c: bridge branch does radio_init + awdl_app_start + bridge_start + promiscuous RX, in that order so Wi-Fi/PHY boot logs precede the COBS stream. - awdl_frame.c: awdl_parse_data now accepts QoS-data frames (subtype 8, +2-byte QoS control) and ignores 802.11 flag bits, so real Apple data frames parse instead of being rejected on a stray flag. Non-QoS behaviour is unchanged. - test: add test_data_qos_and_flags (QoS offset, flag tolerance, subtype rejection). Full native host suite passes under the existing build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
…re visible Review follow-up: - app_main.c referenced ESP_OK in the CONFIG_ESPDROP_BRIDGE_MODE branch (radio_init / radio_promisc checks) but only esp_log.h was in scope, which does not pull in esp_err.h on IDF 5.x. The console branch compiled only because esp_console.h includes esp_err.h; the bridge build - M2.2's target - would fail with "'ESP_OK' undeclared". Add an explicit esp_err.h include. - bridge_start: the bridge_rx_task-creation failure path returns after ESP_LOG was silenced, which suppressed app_main's "bridge_start failed" error. Restore the log level on that path so the failure is diagnosable (nothing has been transmitted yet, so no COBS stream to corrupt). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
… cmd First M2.3 increment: frames the host sends on awdl0 are now transmitted over the radio as AWDL data frames, so the device path is bidirectional. - bridge.c/.h: BRIDGE_MSG_FRAME now hands the Ethernet frame to a registered sink (bridge_set_frame_sink) in addition to accounting it. - awdl_app.c/.h: awdl_app_tx_ethernet() queues one host IPv6 frame (ethertype 0x86dd only; awdl0 is IPv6-only). The AWDL task flushes the queue with awdl_build_data()+radio_tx() only during slots we advertise as present (the master's mirrored ch6 slots), so data TX lands when the peer is on ch6 - the same slot-gating used for PSF/MIF. Build+send happen under the node lock that guards self state (seq numbers). - app_main.c: register awdl_app_tx_ethernet as the bridge frame sink. - bridge.c: add a `radio` command reporting the Wi-Fi counters (rx_action, rx_data, tx_ok/err, tx_ack/noack). tx_ack (hardware ACK on a unicast frame) tells us whether our TX actually reached the iPhone - the key M2.3 signal. - tools/awdl_bridge.py: list `radio` in the command hint (no code change). M2.3b will auto-populate the host neighbour table (PEER_ADD/REMOVE) and tune TX reliability; for now a static `ip -6 neigh` entry makes ping6 testable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
…o limit Review follow-up (verified finding): - awdl_build_data adds 40 bytes of framing (24 B 802.11 + 8 B LLC/SNAP + 8 B AWDL data header) on top of the IPv6 payload. esp_wifi_80211_tx rejects frames > 1500 bytes, so a full-MTU 1484-byte payload built a 1524-byte frame that the driver dropped (counted only as tx_err). Harmless for ping, but it would silently drop every near-MTU segment of a bulk transfer (M2.4). Lower TX_PAYLOAD_MAX to 1460 (1500 - 40) and the host TAP MTU default to 1460 so the IP stack never generates packets the device cannot transmit. - radio command: print the counters with %u (32-bit casts) like `stats`, instead of %llu, avoiding any newlib-nano long-long printf doubt. Counts fit 32 bits for a test run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
So OpenDrop (M2.4) and ping6 work without manually running `ip -6 neigh`: - awdl_app.c/.h: the AWDL task diffs its peer table each 1 Hz tick against a shadow snapshot and fires a registered callback (awdl_app_set_peer_cb) with added/removed peer MACs, outside the node lock. Registering re-syncs so a late-registered sink still learns existing peers. - app_main.c: map peer events to BRIDGE_MSG_PEER_ADD / PEER_REMOVE (6-byte MAC). - tools/awdl_bridge.py: on PEER_ADD, derive the peer's fe80:: link-local by modified EUI-64 and `ip -6 neigh replace <ll> lladdr <mac> dev awdl0 nud permanent`; on PEER_REMOVE, delete it. Permanent entries avoid NDP over the lossy link. Suppressed with --no-config. EUI-64 verified against a real device (a2:77:35:18:28:f3 -> fe80::a077:35ff:fe18:28f3). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Receiving no longer needs awdl0, root or a firewall rule either. Firmware - sockproxy: TCP_LISTEN opens a listening pcb on the AWDL interface; on accept it allocates a device-side id (0x8000+), sends TCP_OPENED with accepted=1 and reuses the same data/credit/close path as an outbound connection. One listener; reset closes it. Host - proxy.Manager.listen + dispatch: a TCP_OPENED for an id the host did not allocate (accepted) makes a TcpConn and calls the accept callback. - device.listen(port, on_accept). - mdns.Responder: announces _airdrop._tcp (SRV/TXT/AAAA, AAAA = the ESP's EUI-64 link-local from the HELLO mac) every 5 s and answers PTR queries, unicast for a QU question; goodbye on stop. - receive.py + espdrop receive: the patched OpenDrop server on 127.0.0.1, the device listening on 8771, each accepted connection spliced to a fresh loopback connection into that server, announced by the responder. Tests: an inbound-bridging fake device and a real OpenDrop sender drive /Discover, /Ask, /Upload against espdrop receive's server; the new ReceiveEndToEndTest checks the file arrives. 20 host tests pass. C5/C3 bridge and C5 console build. Not yet tested on hardware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
OpenDrop takes two things from the system that Windows lacks; the host app now provides both, and Linux behaves as before: - TLS certificate: made with python-cryptography before OpenDrop would run openssl (same files, RSA 2048, self-signed, CN = name). - libarchive: LIBARCHIVE, a DLL recorded by the installer, archive.dll on PATH, then Windows' own System32 archiveint.dll; probed before libarchive-c binds it, with advice when none works. On Windows the archive writer names members with "/" (OpenDrop's os.path.join gives ".\name") and reads the files in Python, not through libarchive's code-page paths. tools/opendrop_install.py builds the venv without bash, git or openssl: opendrop 0.13.0 plus the 17 airdrop-mt7921 patches at 27edf28, applied by a strict built-in patcher, every patch and resulting file checked against pinned digests of the bash install (byte-identical OpenDrop on both platforms), plus pyserial and cryptography. Same stamps as the bash script, so either recognises the other's venv. tools/espdrop.cmd starts the app with that venv in cmd and PowerShell. espdrop check reports Python, pyserial, zeroconf, libarchive, OpenDrop and the TLS state, and packs, reads and unpacks a test archive. pyserial timeouts are only set when they change (each set reconfigures the port on Windows). 31 host tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Every command on Windows ended in "Write timeout": the ESP took no data over USB, most likely because the chip was held in reset. The USB-Serial/JTAG keeps the chip in reset while RTS is on and DTR off, and with pyserial's default (both on) usbser.sys does not reliably pass DTR on. On Windows the port is now opened with both lines off, so nothing from the host can reset the chip; Linux keeps the default, where the open raises both lines at once. A write timeout now comes with a hint (replug the ESP, check the firmware). 32 host tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
On Windows the iPad accepted the /Ask and then reset the connection on
the /Upload (WinError 10054). OpenDrop builds FileBomPath in /Ask with
os.path.join(".", name), so it announced .\test.jpg while the archive
held ./test.jpg. The /Ask plist now gets ./name too on its way out.
The Windows archive writer now also gives the member one link and an
inode number, as OpenDrop's read_disk path does on Linux; a bare
in-memory entry had 0 for both.
send now reports its steps with times (found after, TCP + TLS connect,
accepted after, upload size and rate), and -v (also for receive) logs
everything with milliseconds, to see where the wait before the Accept
prompt goes. 34 host tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
find and send work with the iPad from Windows; receive and non-ASCII file names are still to be tested there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Session (host/espdrop/session.py) is the core a GUI builds on: one owner of the serial port that stays. Receiving is ready the whole time, sends run alongside, browsing is on demand with the ESP's BLE wake, and when the ESP goes away the session reconnects and restarts receiving. Every change is an event (device, power, browse, receiver, send with progress, receive with progress and the files it left). - Receiving asks first: a handler subclass replaces OpenDrop's accept-all /Ask with on_ask(IncomingAsk: sender, files, links, thumbnail); no answer in time is a no. The upload reports bytes and the new files. - Receivers get their names from /Discover; send picks by name, id or part of the name, and the CLI lists them to choose from when several answer. - Sends can be cancelled (TcpConn.abort) and report progress against the archive OpenDrop actually uploads. - OpenDrop's HTTPS connections are routed by peer address, so several /Discovers and sends can run at once. - The mDNS browser and responder share UDP 5353 on the device. CLI: receive asks y/N (--yes accepts all, -d DIR), find shows names, send -r NAME. 43 host tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
AWDL kept the radio receiving and sending all the time, so the C5 ran warm even unused. The radio now sleeps (AWDL task paused, esp_wifi_stop) unless something needs it, and wakes with channel, TX power and promiscuous mode re-applied. - Policy in components/awdl/awdl_power.c, with host tests: ON mode never sleeps; AUTO keeps the radio on for a host hold (power hold), the BLE wake of a send, or an open TCP connection plus 20 s. A host that stays silent for 90 s loses its requests. - main/power.c runs it on a small task; `power` shows the state, `power hold <s>` / `power release` / `power on` / `power auto` set it. - Boot: asleep until a host says otherwise. HELLO L2 (awdl_bridge.py) sets ON, as before; HELLO IP sets AUTO. - Host: the session holds the radio while receiving (with an optional window that closes by itself), browsing or sending, and releases it otherwise; the CLI holds it for find, send and receive. No listening for other devices' Bluetooth: receiving works while the app keeps a receive window open. Builds for the C5 (bridge and console) and the C3 without warnings. 44 host tests, C host tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
espdrop gui (Windows: tools\espdrop-gui.cmd, no console) is one window that owns the ESP while it runs: - device and radio state (connected, radio on and why, or asleep); - receiving in a window that closes by itself (10 min, 1 h, or until off), the name others see, and the download folder; - a dialog before every incoming transfer: sender, files, thumbnail, Annehmen / Ablehnen, declined after 60 s without an answer; - search with the BLE wake, receivers listed by their /Discover names, send by dropping files on one or with a file dialog; - transfers with progress, cancel and open; tray icon, notifications, reconnect after unplugging. Session events reach the GUI thread through a Qt signal; calls that talk to the device run off it. The installer adds PySide6 and espdrop check reports it. Offscreen GUI tests drive search and send, accept and decline through the dialog. 48 host tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
The receive side can now keep the Wi-Fi radio asleep and wake it only when a phone nearby actually wants to share, so the chip stays cool without AWDL running all the time. - An iPhone/iPad with its share sheet open sends a Continuity "AirDrop" advert (type 0x05) to find receivers. The firmware runs a passive BLE scan (NimBLE observer role) that watches for it; nothing about the sender is kept, only "an AirDrop advert is in range". Each advert arms a ~25 s wake, re-armed while the share sheet stays open; the busy grace then covers the transfer. - awdl_power gains a listen flag and an advert wake, gated by host presence, with host tests. main/power.c starts/stops the scan to match, and `power listen on|off` plus a listen=/adverts= status drive it. A HELLO cap bit (0x04) tells the host the ESP can wake itself. - Host: when the ESP reports the cap, receiving uses `power listen on` (radio sleeps between adverts) instead of a continuous hold, in the session, the CLI and the GUI (which shows "Funk schläft (empfangsbereit)"); without it, it falls back to the hold. The mDNS responder announces a little faster (2.5 s) so discovery after a wake lands inside the phone's share-sheet window. Builds for the C5 (21% free) and C3 (37% free) with the observer role. 49 host tests, C host tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- Autostart (menu EspDrop): Windows HKCU Run key, Linux XDG autostart.
It starts `espdrop gui --background`: tray only, receiving until
switched off; an incoming transfer brings up only its accept dialog
(its own top-level window), and a notification says when files
arrived (clicking it opens them). The window stays hidden.
- Sending from the file manager (menu EspDrop): Windows "Senden an ->
EspDrop (AirDrop)" (a shortcut written through WScript.Shell) and "Mit
EspDrop senden" in the classic right-click menu (HKCU\Software\Classes\
*\shell); Linux "Öffnen mit" via a desktop entry. Both run `espdrop gui
--send FILE...`, which opens "An wen senden?": the devices a search
finds, one click sends, progress, closes itself when sent.
- One copy per user: a later start hands its files (or "show") to the
running app over a QLocalServer socket and exits, so the file manager
and autostart never fight for the serial port; files from one Explorer
call per file join the same dialog.
Windows' own share sheet ("Teilen") lists only packaged (MSIX) apps; that
waits for a packaged build. All per user, no admin rights. 57 host tests,
incl. the registry and SendTo against a stand-in winreg.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
The desktop app now looks like Apple's own apps instead of stock Qt:
- ui.py, a small design system drawn in code (no image or font files):
light and dark colours that follow the system, the system UI face
(Segoe UI Variable on Windows 11, Inter or the desktop font on Linux),
a style sheet for buttons, fields, menus and scroll bars, and the
controls Qt has no Apple-shaped version of: switch, segmented control,
round device avatars (outline of iPhone/iPad/Mac or initials) with a
ring for waiting and progress, a radar while searching, a thin progress
line, rounded cards with inset separators, line icons, our own mark.
- The window is one column of cards: AirDrop ("Empfangen von: Aus /
10 Minuten / Für alle" as on the iPhone, name, folder), devices nearby
as avatars to click or drop files on, transfers with progress, cancel
and show in folder, and Allgemein with switches for autostart and the
right-click menu; port, log and quit sit behind the "…" button.
- Incoming transfers ask in a floating card with the picture ("„Bens
iPhone“ möchte ein Foto teilen."), a countdown line and Ablehnen /
Annehmen, with no default button so Enter accepts nothing by accident.
- Sending from the file manager opens an AirDrop sheet: the files on top,
the devices as avatars, one click sends.
Same behaviour and session underneath; 59 host tests (the GUI ones drive
the new controls offscreen).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- The receiver's share sheet need not be open when sending (confirmed with
the ESP's BLE wake): the hints are gone from the app, the CLI, the tools
and the docs; the old spike notes say they were superseded.
- Autostart and the right-click entries are on from the first start (once;
switched off they stay off). Every start writes the icon file
(%LOCALAPPDATA%\EspDrop\espdrop.ico, ~/.local/share/espdrop/espdrop.png)
and points the entries at it and at the app as it runs now. The ICO holds
classic bitmaps up to 128 px and PNG at 256 px. The right-click registry
entry is written before the PowerShell "Send to" shortcut, so it works
where PowerShell is blocked.
- Windows taskbar: own AppUserModelID, so it shows our icon, not Python's.
- Received files land in Downloads itself. OpenDrop unpacks into a hidden
staging folder (Downloads/.espdrop-incoming), then each file moves out
under a free name ('IMG_1 (1).JPG'): nothing gets overwritten; what a
crash or a locked file leaves there moves out at the next start.
- Allgemein -> Erscheinungsbild: Automatisch / Hell / Dunkel (also sets the
title bar on Windows with Qt 6.8+).
- AirDrop -> "In die Zwischenablage kopieren" (off by default): received
files as files, a single photo also as an image, links as text.
- Single instance: the running copy's server no longer deleteLater()s the
sockets of later starts. A deferred delete still queued when the server
went deleted the socket a second time (crash at quit, and in the tests);
finished connections are now deleted explicitly.
68 host tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- espdrop/flash.py: the firmware is checked before anything is written (an image of the whole flash as releases ship it, or an ESP-IDF build folder of the bridge; ESP image headers for one chip, the bootloader where that chip's ROM looks, the partition table, the app's description with its version), then esptool writes it as a library in this process: the C5's USB-Serial/JTAG resets it into the ROM bootloader (no buttons), write, MD5 verify, restart. The connected chip must match first. Progress comes from esptool's documented custom-logger interface, by flash address, so it stays monotonic across several parts. Errors are explained in words. - Desktop app: Allgemein -> "ESP-Firmware" shows what the ESP runs and offers "Aktualisieren ..." when the app ships a newer one; an ESP that does not answer (no, other or too old firmware) gets "Firmware installieren ..." under the title. The dialog lets go of the port, flashes in a thread, connects again; it cannot be closed (nor the app quit) while writing. - espdrop flash [IMAGE]; espdrop status shows the firmware version; espdrop --version; espdrop check lists esptool; the installer adds it. - Firmware: version from host/espdrop/version.py (PROJECT_VER, 0.1.0) and a `version` command (version, IDF, chip, revision, protocol). Not built here (no ESP-IDF in this environment): the CMake regex was run with cmake, the command's code compiled with gcc -Wall -Wextra -Werror -Wformat=2 against stand-in types. - Session: the firmware version in the connected event; `silent` counts HELLOs in a row an ESP did not answer. - tools/firmware_image.py: the one-file release image from a build (esptool merge-bin), read back and checked the same way. 88 host tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- tools/build_app.py: Nuitka translates the Python code (ours, the patched OpenDrop, every library) to C and compiles it with the Python runtime, Qt and the ESP firmware into one folder (EspDrop/EspDrop(.exe)), packed as a zip (Windows) or tar.gz (Linux). It checks the build venv (patched OpenDrop, PySide6, esptool, Nuitka), draws the icons, takes a firmware image or build folder (checked as the app checks it), bundles OpenDrop's root certificate and esptool's flasher stubs, on Linux libarchive as a fallback, LICENSE and a list of the packages with their licenses. --smoke starts the result: --version, check, the window offscreen. - The compiled program is the whole app: without arguments the window, with arguments the command line (Windows: attaches to the console it was started from). espdrop/frozen.py finds the program file (Nuitka sets __compiled__, not sys.frozen), so autostart and the right-click entries start the program itself; libarchive next to it is a last candidate. - Linux: an application menu entry (espdrop.desktop) at every start, and the windows name it as their desktop file, so Wayland docks show our icon. - espdrop check verifies OpenDrop's root certificate the way AirDropConfig looks it up. ESPDROP_INSTANCE and ESPDROP_NO_DESKTOP let a test copy run beside the real app without touching the desktop. - .gitignore: the test_awdl_power binary. 91 host tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
On pushes that touch the code: the ESP32-C5 bridge firmware with ESP-IDF 6.0.3 (espressif/idf:v6.0.3), merged into one image with tools/firmware_image.py; the app compiled with Nuitka on Windows and on Ubuntu 22.04 with that firmware inside, each started with --smoke; the host tests (offscreen) and the firmware host tests. The files are the run's artifacts; a tag v* drafts a release with them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- frozen.program(): in a Nuitka standalone folder sys.executable is the folder plus the build Python's name (python / python.exe), not the program, so autostart and the right-click entries would have started a file that is not there. Now __compiled__.process_exe (the program file), and for a onefile build the binary the user started (containing_dir plus argv[0], .exe added on Windows when typed without). - espdrop check shows that path in the program; the build's smoke test requires it to be the built file, so this cannot come back unnoticed. - Windows: espdrop.cmd next to EspDrop.exe, so cmd and PowerShell wait for the program (it opens no console of its own) and get its exit code. - THIRD_PARTY.txt: a license given as UNKNOWN falls back to the package's classifier (OpenDrop: GPLv3). Nuitka: zeroconf's compiled modules as they are (--no-prefer-source-code). 91 host tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
On Windows the path the program reports (Nuitka resolves it) and Python's realpath may differ in case or by a \\?\ prefix; compare both normalized. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- The first local build ran, but the program stopped at once: "'espdrop' is not a package". Python, and Nuitka when it compiles, look in the main script's folder first, and from tools/ `import espdrop` finds tools/espdrop.py rather than the package. The compiled program now starts from host/espdrop_app.py, next to the package. - Nuitka names the program folder <name>.dist: build/app/EspDrop.dist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
The second local build started and checked itself: program path, Qt, esptool, libarchive fine, but OpenDrop did not load: fleep (its file type lookup) reads fleep/data.json at import, and Nuitka bundles data files only for packages it is told about. A scan of every bundled package for data files missing in the program found only this one that is needed (the others: markdown_it's port.yaml and pkg_resources' api_tests.txt). A missing file of OpenDrop's own was reported as a libarchive problem; now it says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- The CI builds (Windows with MSVC, Linux) compiled and started: program path, Qt, esptool, libarchive (Windows' own archiveint.dll) fine. Then `check` stopped: it tells the patched OpenDrop by reading opendrop/server.py, which the program does not have (the marker is a comment, gone when compiled). In the program it now says "checked when this program was built": the build refuses an unpatched OpenDrop. - Qt's X11 plugin needs xcb libraries (since Qt 6.5 libxcb-cursor0 and a few more) that minimal installs lack; the window then aborts under X11. check names the missing ones with their Debian packages; the build refuses to run without them, so the program carries them (CI installs them); --smoke on Linux also starts the window under Xvfb through that plugin. Wayland does without. 93 host tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
The build's own check found it missing on Ubuntu 22.04 (Qt's X11 plugin needs it) and named the package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- The name others see is now this PC's name (Windows' computer name; on Linux the "pretty" hostname if set, else the hostname, without a domain), not "EspDrop". In the window the field stays empty and shows that name greyed until the user types one; espdrop -n has it as default. - Receiving a photo from an iPhone left test.JPG and ._test.JPG: iOS sends AppleDouble metadata (extended attributes) beside each file, which Apple's own unpacking folds into the file. They are removed in the staging folder now, also inside sent folders, recognised by the AppleDouble magic so a real file named "._x" stays. - build_app.py prints the newest glibc the Linux program asks for, measured from every file of it. 96 host tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Measured in the CI build: glibc 2.35 or newer, x86_64. Named the distributions that have it and those that do not, X11 and Wayland, what the tray icon needs, the serial port group; Windows 10/11, 64 bit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- build_app.py makes a single file by default (Nuitka onefile):
EspDrop-<version>-windows-x64.exe and EspDrop-<version>-linux-x86_64,
plus THIRD_PARTY-<platform>.txt. It unpacks itself once per build into
the user's cache ({CACHE_DIR}/EspDrop/app/v<version>-<build>) and
starts from there, so autostart does not unpack 200 MB each time; the
app removes older builds' folders there (and only there). Licenses,
the icon and Linux' libarchive go into the file. --folder keeps the
folder build.
- frozen.bundle_dir(): the program's own files are in the unpacked
folder, not beside the single file (libarchive fallback, X11 check).
- CI: zstandard for the packing; uploads the single files; a tag v*
drafts "EspDrop v<x>" with docs/RELEASE.md as its text (checkout
first, it would clean the downloaded files away).
- docs/RELEASE.md: downloads, Windows and Linux start, which Linux, the
ESP, use.
98 host tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
The Windows CI start of the single file failed at the second start: Windows handed the program its own path in the 8.3 short form (...\V01~1.0-2), the cleanup compared paths as text, took the running build's folder for an old one and deleted it, and the next extension module could not load. Folders are now compared as the same folder (os.path.samefile); one that cannot be compared is left alone. A test gives our folder under another spelling. 99 host tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- espdrop/i18n.py: the texts stay German in the code; _() gives them in the language chosen under Allgemein -> Sprache (Automatisch, Deutsch, English). Automatisch is German on a German system, English on any other. Texts with values use format placeholders, so English can order them its own way. A new language shows after a restart; "Jetzt neu starten" restarts the app at once. - Every text the window, the dialogs, the firmware messages and the desktop entries (right-click menu text, menu comments) show goes through _(); numbers and build dates follow the language. The right-click entry is rewritten when the language changed. - test_i18n.py: every _() text has an English version with the same placeholders, and no German text (or text that has a translation) is shown without _(); a GUI test runs the window in English. 103 host tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
- espdrop/install.py: the single-file program, started from anywhere else (the download), copies itself to %LOCALAPPDATA%\Programs\EspDrop (with a Start menu entry and an entry in "Installed apps" whose Uninstall runs `EspDrop uninstall`) or ~/.local/bin/espdrop, starts from there and removes the download when it was in the Downloads folder. A running copy (an older version) is asked to quit first, so starting a newer download updates. Not for the folder build, test runs or ESPDROP_PORTABLE=1. - Uninstall from the app's menu, from "Installed apps" or with `EspDrop uninstall`: program, desktop entries, settings and unpacked copies go; received files stay. On Windows the running file is deleted by a detached cmd once the app has quit. - The compiled program on Windows no longer sets its own app id, so its Start menu entry and taskbar pins belong to the running window. - README.md: what EspDrop is, what one needs, install, use, building, credits and license. docs/RELEASE.md in English (with a German paragraph) for the release page. 109 host tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
Double-clicked, the program has no console and its standard handles are not valid ones; a child process that inherits them fails at once with "WinError 6: The handle is invalid". Installing hit that (the PowerShell call for the Start menu entry, starting the installed copy), and so would the language restart and uninstalling. Every child now gets DEVNULL for stdin, stdout and stderr instead. The CI smoke test missed it: it starts the program with valid handles and without installing. --smoke-install (CI only, it installs for real on Windows) now starts the single file from Downloads the way Explorer does (os.startfile: no console, no handles) and checks that it installs itself, removes the download and is in the Start menu and "Installed apps"; on Linux in a throwaway home folder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XzgaN4Bv1m8Ka9iqo7QVUN
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.