Repository navigation
feat: FLOW-UFO toy drones over UDP (#187) - #188
compot-dev wants to merge 577 commits into
Conversation
- New section Transports: TCP / UDP / Auto / Multicast, what each does and its fallback; OpenIPC Majestic and XM reply 400/451 to RTSP multicast and play over TCP - udp:// slots for Majestic outgoing: URL forms, codec by payload type, codec=/pt=, one port per slot, the outgoing destination in /etc/majestic.yaml, /api/v1/outgoing.json, multicast destinations (confirmed in #74) - The transport extra of a shared link, with an adb example - Requirements, features, quick start, the menu table, the reserved ninth cell, Known limitations (RTSP multicast needs Android 8.0+), Architecture (Transport, UdpReceiver, RtpReorder, RtpListener) and the security note updated; EN/RU/ZH
docs: transports and udp:// slots in the READMEs (#75)
…80, #82, #83) - Each cell shows small text in its bottom-left corner, updated every second: 'WxH codec decoded/received fps' while playing, otherwise Connecting…, Buffering…, Disconnected, a short connection error (Port in use, Connection refused, No answer from camera, Wrong login or password, …) or a decoder limit (#80, #82). The same line goes to logcat every 5 s. - Received frames are counted by the RTP marker bit, decoded frames by releaseOutputBuffer; MediaCodecManager exposes them with the output size. - Instance limit (#83): the first decoder of a type reports its declared limit (getMaxSupportedInstances, Android 6+) and throughput (logged). A cell that would exceed the limit shows 'Decoder limit: N × H.265' and retries with back-off until an instance is free. RtspSession reports connection failures through Callback.onFailure (default no-op). - Overload (#83): judged by measurement, not by the declared budget, which is far too low on some boxes. When a settled cell decodes under 85 % of what it receives for 5 s, the highest-numbered playing cell is paused ('Paused: decoder overloaded') until the grid is reopened. - Tests: CellStatsTest.
…own (#83) On the device the first version paused cells down to two: decoded/received also drops when frames are broken by packet loss on the network, which says nothing about the decoder. A cell now counts as behind only when the decoder makes it lose frames: its NAL queue overflows, or dequeueInputBuffer finds no free buffer (that NAL is dropped). After a pause the load gets 10 s to settle before the next decision. decoded/received stays in the corner text as information.
- DebugServer: a plain ServerSocket on its own thread, always on for the process, started by the activity. GET only (405 otherwise), one request per connection, 3 s socket timeouts, two workers and a short queue; extra connections are closed. A port that cannot be bound is retried every 10 s and reported in the JSON. - Only clients on the local network are answered: loopback, 10/8, 172.16/12, 192.168/16, link-local (169.254/16, fe80::/10), IPv6 fc00::/7; others get 403. - / is a static page that fetches /stats.json every 1.5 s and renders it. /stats.json: app version and git hash, device, SoC, Android, screen, the device addresses as http://<ip>:8085/ (no 127/8, no 169.254/16), mode, the single-view status or every cell with its corner text, in/lost fps, paused and audio. The state is read on the UI thread; if it does not answer within 1 s the JSON says "ui_thread": "not responding" (useful for the ANR in #76). /log: the last 500 lines of the app's own log (logcat without READ_LOGS). - Logins and passwords never leave the device: URLs and log lines go through the same redaction as the log (#55), plus Authorization headers. - NetInfo: the device addresses and the local-client test; #87 will use it for the U cell hint. - README (EN/RU/ZH): the debug page and a security note.
Seen on the box: an XM slot showed user=admin, only the password was masked.
…data, U view, write deadline - IPv6 site-local (fec0::/10) is no longer a local client: isSiteLocalAddress() counts for IPv4 only. - /log is served from LogBuffer, a ring of the last 500 redacted lines fed by a logcat reader that follows the app's own log for the life of the process. - Each cell reports behind_s (seconds in a row the decoder made it lose frames), with overload_after_s for the threshold (#83). Full rate / key frames comes with #86. - In the U view the JSON has a "udp" object instead of the last single-view state. - A request has a 5 s deadline, writing included: a client that stops reading has its socket closed and cannot hold a worker.
feat: debug HTTP server on port 8085, LAN clients only (#96)
…he screen (#98) - /snapshot.jpg: the multi-view grid (2x2 / 3x3) or the single view, from the frames the app is showing. Cells are copied from their SurfaceViews with PixelCopy at 320x180 (Android 7+), the single view with TextureView.getBitmap() at 640x360; one JPEG, quality 70. No extra decoder, nothing pulled from the camera. - A cell is drawn only while it plays (not paused, frames decoded in the last second; single view: frames decoded since the previous snapshot). Otherwise it is black with its slot and status in red, never a stale frame. Labels and a timestamp on the image. - Only on request: one capture at a time, a picture younger than 1 s is served again. The cells are chosen on the UI thread, copying and JPEG encoding run on the server thread; PixelCopy callbacks on their own HandlerThread. - Page: the snapshot above the stats, refreshed every 3, 5 or 10 s (selector, default 5 s, remembered), the next one requested after the previous has loaded. Stats and snapshots are requested only while the tab is visible. - README (EN/RU/ZH): /snapshot.jpg.
On the AG-216 with 6 playing cells one snapshot took about 4 s with one PixelCopy after another, and the first one ran into the 5 s request deadline. The copies are now requested together and awaited with one 2 s budget.
…s, JPEG 60 (#98) On the AG-216 decoding takes nearly all CPU: encoding the 960x540 JPEG took 1-2 s. The server workers now run at background priority, so a request waits instead of the video; cells are 256x144 (768x432 for 3x3) at quality 60, and a request may take up to 10 s.
…per source, hidden tabs - A cell is live in a snapshot when it is connected, not paused and showed a frame in the last 2 s (MediaCodecManager records when it rendered the last frame; reset when the decoder closes). Replaces the sampled fps, which could lag a stopped stream. - Single view: live when the status is connected and a frame was rendered in the last 2 s, instead of 'frames since the previous snapshot'. - The 1 s snapshot cache is tied to the source: a picture taken from an activity that was replaced or went away meanwhile is not served. No lock on the UI thread: a generation counter, since a capture in progress may be waiting for the UI thread. - The page's first stats and snapshot requests also wait while the tab is hidden.
feat: debug page snapshots of what the decoder shows, grid like multi-view (#98)
…, JSON header first - Page: a header with "OpenIPC Decoder", the version and git hash, the device time and uptime; under it JSON, LOG and the snapshot interval; the rest is the snapshot, kept at 16:9 and as large as the window allows. The stats tables are gone. - The text of each cell is HTML over the picture, refreshed with the stats every 1.5 s: slot, camera address and transport on top; size, codec and decoded/received fps below (orange when under 90 %), lost frames and decoder lag in red. A cell without video is covered with its slot and status in red. - /snapshot.jpg?labels=0 gives the frames only (no text, no grid lines) for the page; /snapshot.jpg keeps its labels. The 1 s reuse is per variant. - /stats.json is indented, and starts with what the page header shows: app, version, git, time, uptime_s, device, android, screen, addresses, server, then ui_thread, mode, active_slot, overload_after_s, and the cells last. Cells and the single view get "video" (playing now: connected, not paused, a frame in the last 2 s); cells get out_fps; the single view's resolution moves to "size".
…, cache per variant - The single view's video flag (and its snapshot) requires the app in the foreground; stopping the streams sets its state to disconnected. - The page checks the paused / UDP modes before the single view or the cells. - The no-video cover is opaque: a stale frame of the last snapshot cannot show through. - Cell header: a bracketed IPv6 camera address is shown whole. - The 1 s snapshot reuse keeps one picture per variant (frames only / labelled), so the page and a curl poller do not evict each other.
feat: debug page — snapshot fills the page with the cell text over it, JSON header first
…th a monitor), firmware and developer settings
… on narrow screens (#103)
docs: LABORATORY — Aceline-1 and Aceline-2 test boxes
…vity state, abandoned captures
feat: debug page — sharper snapshots and a header that works on phones (#103)
…meanwhile, before a NAL unit is dropped (#89)
…or mappings, Telegram only for v2/basic, LABORATORY without per-version releases (Qodo on #195)
…lots, dqt switch) and the KY UFO 7099 heartbeat (#187) Compared with marshallrichards/turbodrone (Apache-2.0, wifi_uav and cooingdv backends; no code taken over, credited in docs/toys.md and the comments): - ACK quality parameters 32 4B 14 2D by default; ?qpara= (or ?q=) with 8 hex digits - bytes 0x34/0x35 are main/flow camera status: shown in the stats, no family warning - slots 5 or more frames behind are left out of the ACK instead of a seq+4 request - port 8800 (default) probes 8801 too, one socket per port; locks onto the socket the first 0x93 packet arrives on (source address only, the drone sends from port 1234); BL618 timing on 8801: probes every 200 ms, ACKs every 50 ms only with a record - ?dqt=zigzag|raster|q50 for the JPEG quantisation tables, shown in the stats - no flight command in the ACK, with the reason in a comment - RTSP: 01 01 to UDP 7099 every second for rtsp://<host>:7070/webcam, or with ?heartbeat=<port> (off with ?heartbeat=off; the option is not sent to the camera); heartbeat object in /stats.json - subnet hint in the status when a flowufo:// slot targets the phone itself or another toy family's network - docs/toys.md: families by Wi-Fi name, the new options, KY UFO RTSP, credits (#196)
jieli://192.168.80.1 for toys with an HF_xxxxxxxx Wi-Fi (Jieli AC79, vendor app DVRunning 2), after the PCAPdroid capture in #196 and turbodrone's research notes (Apache-2.0, behaviour reference only, no code taken over): - CTP on TCP 3333 (CTP: + u16 topic length + topic + u32 JSON length + JSON), a reader that reassembles split and joined messages and skips what is not CTP - APP_ACCESS, DATE_TIME, CTP_KEEP_ALIVE every min(timeout / 4, 7.5 s), OPEN_RT_STREAM with the size of VIDEO_PARAM (else 640x480 at 25), CLOSE_RT_STREAM on stop - video expected on the phone's UDP 6666 (unverified): PT 26 into the RTP/JPEG path, PT 96 into the H.264 path, whole JPEG datagrams too; first packets logged with sender, PT and bytes; no video for 5 s: said in the status, UDP 1234 and 2224 opened for the log only, OPEN_RT_STREAM again every 10 s - options w, h, fps, rtp, format; sockets bound to the toy's network (TCP from Android 6) - jieli object in /stats.json; subnet hints both ways between flowufo:// and jieli:// - never FLIGHT_CONTROL, nothing to UDP 3456 - docs/toys.md: HF / Jieli section, credits; unit tests with a fake toy on loopback
|
/review |
|
Code review by qodo was updated up to the latest commit b898ca1 |
…silence reset, network binding from Android 6 (Qodo on #188)
|
Qodo review of b898ca1, addressed in 4a9245a:
The other items were already marked resolved. |
… video (Qodo on #198) - a KEEP_ALIVE_INTERVAL announced after the first keepalive applies at once: the next one is due interval ms after the last one sent, not on the old schedule; test with the announcement after the first keepalive - the quiet status from the CTP thread and "connected" from the UDP thread are serialised, and a quiet notice is dropped when video arrived meanwhile
…g from #188 with the shared network lookup)
feat: Jieli HF toy drones over CTP (#187)
…aries, so chatty graphics logs no longer push them out (#187)
…r-clockwise, ?rotate= to override (#200) The K200's camera is mounted sideways. For :7070/webcam slots the software MJPEG path draws the frames turned 90° CCW and reports the turned size, so the view fits a landscape picture; ?rotate=0|90|180|270 overrides it for any RTSP MJPEG slot and is not sent to the camera. H.264/H.265 are not turned (#201). /stats.json gets rotate; screenshot and debug snapshot copy the turned surface.
|
/review |
| private void open(int i, InetAddress target) throws IOException { | ||
| DatagramSocket s = new DatagramSocket(); | ||
| try { | ||
| s.setReceiveBufferSize(1024 * 1024); | ||
| } catch (IOException ignored) {} | ||
| if (setup != null) network = setup.prepare(s, target); |
There was a problem hiding this comment.
3. Failed startup leaks drone sockets 🐞 Bug ☼ Reliability
Client.open() creates a datagram socket but does not close it if preparation throws, and run() opens both probe sockets before entering its cleanup try. If opening the second socket fails, the first remains open across connection retries; repeated failures can exhaust available sockets.
Agent Prompt
## Issue description
A failed probe-socket startup can leave previously created sockets open.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/FlowUfo.java[820-830]
- android/app/src/main/java/com/openipc/decoder/FlowUfo.java[857-876]
## Recommended Fix
Close a newly created socket if preparation fails, and move the initial multi-socket opening inside the run method's cleanup region. Test failure while opening the second probe socket.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| synchronized (lock) { | ||
| for (DatagramSocket s : udpSockets) s.close(); | ||
| if (tcp != null && !tcpConnected) { | ||
| try { | ||
| tcp.close(); | ||
| } catch (IOException ignored) {} | ||
| } |
There was a problem hiding this comment.
4. Stopping a toy can leave its session stuck 🐞 Bug ☼ Reliability
Jieli.Client.close() leaves the TCP socket open once tcpConnected is true, while send() performs writes without a timeout. If the peer stops reading and a CTP write blocks, stopping the slot cannot release that write or reach the session's finally block that closes the connection.
Agent Prompt
## Issue description
A blocked CTP write can prevent a connected toy session from stopping indefinitely.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Jieli.java[645-659]
- android/app/src/main/java/com/openipc/decoder/Jieli.java[817-835]
- android/app/src/main/java/com/openipc/decoder/Jieli.java[890-895]
## Recommended Fix
Preserve the graceful CLOSE_RT_STREAM attempt when possible, but add a bounded shutdown path that closes an established TCP socket if the session thread cannot exit, releasing a blocked write.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
Code review by qodo was updated up to the latest commit b93fd47 |
|
#200 (MJPEG of rtsp://…:7070/webcam turned 90° counter-clockwise,
Findings 3, 4 and 11 (FlowUfo socket lifetime and reopen timing) are in the FLOW-UFO client, not in this change, and are left for the #187 work. |
The README (EN/RU/ZH) keeps the overview, screenshots, download, requirements, quick start, a short controls summary, known limitations, a documentation table, support and license. The details move to English-only documents in docs/, each with its own table of contents: - usage.md: menu, gestures and keys, remote, multi-view, Slides, U cell, WebUI, share intent, screenshots, Reset - transports.md: URL formats and options per transport, codecs, authentication, what the servers support - debug.md: debug server, page, snapshot, stream checks, a /stats.json field reference from the code, /log, corner text, bug reports - architecture.md: data path diagram, classes, threads, decode planner (#85, #189), SurfaceView rendering (#61, #76), settings, layout - testing.md: branches, CI, unit tests, versions and releases, branch builds, mapping.txt (#181), local builds - security.md: network, credential store, debug server LAN rule, log redaction, WebUI, permissions - devices.md: verified and not working devices, hardware decoder notes - toys.md: drones and Wi-Fi toys, FLOW-UFO in testing (#187, #188), unknown toys, what to send The #76 freeze is no longer a known limitation (fixed in 2.09, #185). The device report form points to docs/devices.md. Docs only, no version bump.
|
Continued in #215: GitHub closed this PR automatically when |
…en startup fails; Jieli: only usable video counts as receiving, a stopped slot cannot hang on a blocked CTP write, diagnostic ports freed after 30 s, topic stats bounded, a dropout after the stream request reconnects without back-off
Refs #187. Draft until a device test on a real drone.
What it does
A new slot type
flowufo://plays the video of cheap Wi-Fi toy drones with aFLOW-UFO-xxxxxxaccess point (BL60x camera), in single view and in grid cells, like anhttp://MJPEG slot. Video only: no flight commands are ever sent.192.168.169.1:8800) from an ephemeral port. ProbeEF 00 04 00every 100 ms until the first fragment; after 3 s without fragments everything is reset and probing starts again.93 <type> <len>: type 1 frame fragments (1024 data bytes each) are collected into four slots (slot = (seq − 1) & 3, a sliding window); type 4 service messages are only logged.EF 02every 25 ms with the fixed 0x58-byte header (no command, QPara19 32 23 4B, camera 0) and per-frame records: received-fragment mask (status 0/1), "send whole" (status 3) for a slot lagging the newest frame by ≥ 5, and for the next frame after > 19 ms without fragments when all slots are complete. The frame currently arriving is left out.ConnectivityManager, new normal permissionACCESS_NETWORK_STATE), so a Wi-Fi without internet is used even when mobile data is on./stats.jsonhas aflowufoobject per cell / insingle(state, network, frames/fragments per second, fragments, duplicates, lost/late frames, whole/next requests, resets, last service message, header of the first fragment, send errors). Single-view stream checks skip these slots (the drone talks to one client).How to use
Put
flowufo://192.168.169.1:8800into a slot (flowufo://alone means the same). Connect the phone/box to the drone'sFLOW-UFO-…Wi-Fi first.Not verified (written from a static description, no drone yet)
baseat 0x18 likewise. The log prints bytes 0x08–0x17 of the first fragments.short_fragmentsin stats.json).What a tester should send
http://<device>:8085/stats.json(theflowufoobject) andhttp://<device>:8085/log(lines withFLOW-UFO). The debug server must be reachable — on a phone that is only on the toy's Wi-Fi, open them on the phone itself (http://127.0.0.1:8085/...).Notes for testers: the phone must stay on the toy's Wi-Fi even though it has no internet; Android may ask "This network has no internet access. Stay connected?" — answer yes (and "don't ask again"). Mobile data may stay on.