# MF283V autonomous hardware-lab protocol

Updated: 2026-08-19

This document describes the local hardware bench available for autonomous
development and qualification of the ZTE MF283V OpenWrt port. It is private
project-operational material. Do not include this document, its network
details, or its credentials in commits, patches, pull requests, public logs,
or upstream documentation.

## Bench topology

- The controller is an Orange Pi reachable without a password as
  `root@192.168.1.108`.
- The Orange Pi GPIOs control MF283V power and the EDL strap.
- The MF283V serial console is connected directly to the Orange Pi as
  `/dev/ttyS1`.
- The MF283V USB port is connected to the development host running the current
  session, not to the Orange Pi. Its USB devices should be accessible without
  `sudo`.
- A SIM is installed for modem tests. Its PIN is recorded in
  [Sensitive local data](#sensitive-local-data).
- A 24 MS/s logic analyser can be connected to requested modem test points by
  the project owner. It is not assumed to be connected unless the owner says
  so.

Run Orange Pi commands through:

```sh
ssh root@192.168.1.108
```

For a single non-interactive operation, prefer an explicit command such as:

```sh
ssh root@192.168.1.108 'gpio read 2; gpio read 6'
```

## GPIO assignments and state inspection

- GPIO 2 controls modem power: `0` is off and `1` is on.
- GPIO 6 controls the EDL strap: `1` closes the strap and `0` releases it.

Before every autonomous hardware test, verify that both control pins are
configured as outputs.  Reconfigure either pin when `gpio qmode` does not
return `OUT`, then repeat the query before changing its value:

```sh
ssh root@192.168.1.108 '
  [ "$(gpio qmode 2)" = OUT ] || gpio mode 2 out
  [ "$(gpio qmode 6)" = OUT ] || gpio mode 6 out
  gpio qmode 2
  gpio qmode 6
'
```

Do not assume that a previous session left the GPIO direction configured.
Driving a power or EDL value is permitted only after both queries report
`OUT`.

Read the current state before changing it:

```sh
ssh root@192.168.1.108 'gpio read 2; gpio read 6'
```

Keep GPIO 6 at `0` during normal operation. If an operation is interrupted,
inspect both GPIOs and release GPIO 6 before attempting a normal boot.

## Serial console

Open an interactive console through the Orange Pi:

```sh
ssh -t root@192.168.1.108 'picocom -b 115200 /dev/ttyS1'
```

Only one process should own `/dev/ttyS1` at a time. The usual picocom exit
sequence is `Ctrl-A`, then `Ctrl-X`. Serial output can contain damaged or
interleaved characters; confirm important failures with a second observation
or a machine-readable interface where possible.

## Normal power control

Power off:

```sh
ssh root@192.168.1.108 'gpio write 2 0'
```

Power on:

```sh
ssh root@192.168.1.108 'gpio write 2 1'
```

Normal cold power cycle:

```sh
ssh root@192.168.1.108 \
  'gpio write 2 0; sleep 1; gpio write 2 1'
```

One second is sufficient for an ordinary reset.  Use at least five seconds
when leaving EDL or the downloaded programmer, and eight seconds before
asserting the EDL strap.  The longer pre-EDL interval has proved reliable when
the previous USB function otherwise remains enumerated:

```sh
ssh root@192.168.1.108 \
  'gpio write 6 0; gpio write 2 0; sleep 5; gpio write 2 1'
```

Do not power-cycle while NAND/UBI is being written unless the current test
explicitly requires interruption testing.

After every hardware test, including a failed or interrupted test, power off
the MF283V bench before ending the session:

```sh
ssh root@192.168.1.108 'gpio write 2 0'
```

Verify that GPIO 2 reads `0`.  If an EDL operation was involved, also release
GPIO 6 before leaving the bench.

## Entering EDL

The complete sequence is executed on the Orange Pi. It turns power off, holds
the EDL strap while power is applied, and then releases the strap:

```sh
ssh root@192.168.1.108 '
  gpio write 6 0
  gpio write 2 0
  sleep 8
  gpio write 6 1
  gpio write 2 1
  sleep 3
  gpio write 6 0
  sleep 1
'
```

Afterward, verify that GPIO 2 is `1` and GPIO 6 is `0`:

```sh
ssh root@192.168.1.108 'gpio read 2; gpio read 6'
```

## Finding the EDL USB serial port

USB enumeration is observed on the development host. Never assume that EDL is
still `/dev/ttyUSB1`: its number can change after reconnects and power cycles.
Inspect the available nodes immediately before running `qdload`:

```sh
ls -l /dev/serial/by-id 2>/dev/null || true
ls -l /dev/ttyUSB* 2>/dev/null || true
lsusb
```

If the identity is still unclear, compare the device list before and after the
EDL sequence, or inspect recent kernel messages:

```sh
dmesg | tail -80
```

Use the newly appeared EDL node as `EDL_PORT`. Validate the resolved value
before passing it to a tool; do not use a broad glob as a write target.

```sh
EDL_PORT=/dev/ttyUSB1
test -c "$EDL_PORT" && printf 'EDL port: %s\n' "$EDL_PORT"
```

The command-execution environment can expose `/dev` from the host while
leaving `/sys` unmounted or empty.  In that case `lsusb`, udev queries and
`/sys/class/tty` cannot identify the device, and disconnected `ttyACM*` or
`ttyUSB*` nodes may remain in `/dev`.  Compare node timestamps before and
after entering EDL, select only the newly created character device, and use
the mandatory `qdload` success markers below as the final identity check.  Do
not reject a newly created node merely because it has no matching sysfs path
in this restricted environment.

Before entering EDL, make sure that no USB-serial discovery application or
modem-management daemon can probe the QHSUSB port.  Both `ModemManager` and
Cura have interfered with this bench: either can open a newly enumerated serial
node before `qdload` receives the Sahara Hello packet, or reopen the programmer
port and interrupt an active `qwdirect` write.  The latter presents as repeated
`Нет ответа от модема` and `Ошибка обработки команды чтения памяти` messages
even though `qwdirect` may eventually return zero.  Stop or inhibit all such
software for the complete `qdload` plus `qwdirect` session, and restore it only
after the board has left EDL.  If it cannot be controlled from the current
account, keep the board powered off and request host administrator assistance;
racing another serial-port consumer is not a safe write procedure.

## Loading the EDL programmer

On this development host `qdload` is not currently installed in `PATH`. Run
the workspace copy from `qtools-maks` and supply its MDM9607 programmer image:

```sh
cd /home/claude/openwrt-md283v/qtools-maks
./qdload -t -p "$EDL_PORT" -k 11 -i loaders/NPRG9x07p.bin
```

The local `qdload` implementation requires the programmer filename after the
options; `-i` selects full HELLO initialisation but does not select an image.
The optional `-t` also reads and validates the device partition table after
the programmer starts.

Do not use the process exit status alone as the acceptance criterion.  A
failed programmer transfer has been observed to return zero.  Preserve and
inspect stdout; a successful MDM9607 session must contain both:

```text
Загрузчик передан успешно
Чипсет: MDM9x07
```

If either marker is absent, do not run `qwdirect`.  Power the board fully off
for five seconds, re-enter EDL, resolve the new serial port and retry `qdload`.

For the validated boot-partition-only test workflow, the exact write geometry
is block `0x132` for `0x52` eraseblocks.  The decimal-form tool output must say
`стартовый блок 132, размер 052` before the write is accepted.  This rule is a
geometry check, not blanket permission to flash an arbitrary image:

```sh
cd /home/claude/openwrt-md283v/qtools-maks
./qwdirect -p "$EDL_PORT" -k 11 -b 132 -f o \
  -s /absolute/path/to/09-0-boot-openwrt-production.bin
```

This step establishes the EDL/programmer session. It does not by itself grant
permission to erase or write arbitrary NAND partitions. Before any persistent
write, resolve the partition by label, validate the intended image and hash,
and limit the operation to the partition explicitly in scope. Never derive a
write target from Linux `mtdN` numbering alone.

The recovery and current development installation details remain in
[RECOVERY.md](RECOVERY.md) and [INSTALL.md](INSTALL.md), respectively.

## Boot after an EDL operation

After the programmer or flashing operation has completed, perform a normal
power cycle on the Orange Pi:

```sh
ssh root@192.168.1.108 \
  'gpio write 2 0; sleep 5; gpio write 6 0; gpio write 2 1'
```

The explicit `gpio write 6 0` prevents accidentally re-entering EDL after an
interrupted sequence. Observe the subsequent boot through the serial console
and verify USB enumeration on the development host.

## Non-interactive qualification techniques

For paced login and command injection when an interactive PTY is unavailable,
open `/dev/ttyS1` from a short Python program on the Orange Pi with
`O_NOCTTY|O_NONBLOCK`, configure both speeds to `termios.B115200`, and use
`select.select()` between writes.  One-second pauses around the login prompts
and longer command-specific waits avoided lost characters during the stock
update tests.  Do not run this concurrently with picocom, screen, `cat`, or
another serial reader.

Send long commands one byte at a time with approximately 4 ms spacing.  A
nonblocking `os.write()` can itself raise `BlockingIOError`; wait for the file
descriptor to become writable and retry the same byte rather than skipping it.
For stock login, keep one descriptor open for the complete exchange, wait for
the literal `login:` and `Password:` prompts, pause before each response, and
terminate each response with carriage return only.  Sending CRLF caused the
following stage to consume the extra LF, while sending the password immediately
after the prompt consistently lost its first bytes.  The stock console
credential is recorded only in [Sensitive local data](#sensitive-local-data).

The stock shell echoes an injected command before printing its output.  Do not
wait for a literal completion token that is already present in that command:
the echo will satisfy the wait before the command has run.  Construct the
token at execution time instead, for example by sending
`printf '__CAPTURE_%s__\n' DONE` and waiting for `__CAPTURE_DONE__`.  Parse the
last complete pair of dynamically constructed begin/end markers when the
serial stream may contain output from an earlier attempt.

Capture a complete cold boot without taking an interactive terminal by
starting this command before the power cycle:

```sh
ssh root@192.168.1.108 \
  'stty -F /dev/ttyS1 115200 raw -echo -ixon -ixoff; timeout 80 cat /dev/ttyS1'
```

The serial stream may still contain damaged characters, so use the USB ACM
console for exact post-boot counters and state.  A detached `screen` session
can own the console while commands are injected with `screen -X stuff` and its
log is inspected.  Start only one such session and terminate it after the
test.  The detached launcher can appear to remain blocked even though
`screen -ls` already shows a usable session; check the session list before
starting another owner of the port.

Early boot output includes a NUL between firmware stages.  Tools such as
`grep` may consequently classify a captured UART log as binary.  Remove only
NUL bytes before applying text filters:

```sh
tr -d '\000' </tmp/mf283v-uart.log | grep 'Booting Linux'
```

When sending AT commands from an automated router-console session, keep the
WWAN port open on one shell file descriptor for both transmit and receive.
Opening it separately for the write and read can lose the response.  Avoid
embedding a carriage return in `screen -X stuff`; generate byte 13 on the
router instead:

```sh
exec 3<>/dev/wwan0at0
awk 'BEGIN { printf "ATI%c", 13 }' >&3
busybox timeout 4 dd bs=1 count=256 <&3 2>/dev/null
exec 3>&-
```

When injecting that command from the development host, put the complete
router command in a double-quoted `stuff` argument and keep the awk program
single-quoted.  Send the console Enter key in a separate invocation.  This
prevents `screen` and the two shells from consuming the quotes or turning the
AT carriage return into an early console Enter:

```sh
screen -S mf283v -p 0 -X stuff \
  "exec 3<>/dev/wwan0at0; awk 'BEGIN { printf \"ATI%c\", 13 }' >&3; busybox timeout 4 dd bs=1 count=256 <&3 2>/dev/null; exec 3>&-"
screen -S mf283v -p 0 -X stuff $'\r'
```

The current router image does not provide a `base64` applet, so do not depend
on base64-decoding a prebuilt AT command during qualification.

### Host-side stock USB qualification

The development host needs both `/dev/bus/usb` and `/sys/bus/usb` visible for
descriptor, driver-binding and ADB tests.  The stock firmware has so far used
ZTE products `0016`, `1403`, `1404` and `1405`; a single unrepeatable
transition also exposed `1244`.  Host udev policy grants the `uucp` group
access to these exact products and to their ZTE `sr*` block device.  If a new
product or device node is inaccessible, stop and request an explicit rule;
do not widen permissions to all USB or block devices.

The Android platform-tools binary is not in the normal host `PATH`.  Invoke
it as:

```sh
/home/claude/Android/Sdk/platform-tools/adb
```

Resolve the network interface after every re-enumeration from the USB device
and its bound driver instead of retaining an old `enp*` name.  Temporary IPv4
addresses require host administrator privileges and disappear when the USB
interface is removed.  Remove them explicitly when the interface remains
enumerated; otherwise verify that the interface disappeared during the final
power-off cleanup.

The stock `bridge0` floods ARP over both the USB network function and the
connected physical LAN.  Linux's default weak-host ARP behavior can then
answer for an address assigned to USB through the host's physical Ethernet
MAC.  A successful ping alone is therefore insufficient evidence of a USB
data path when the modem LAN cable is connected to the laboratory switch.
Bind the client operation to the current USB interface, compare its packet
counters before and after, and verify the target neighbor and target-side
function counters.  For an unambiguous reverse-path test, disconnect the
modem's LAN cable or temporarily restrict ARP replies on the host with project
owner assistance.  Do not disable the development host's own network access.

The factory ECM/RNDIS mass-storage compositions may enumerate a host `sr*`
device while reads return `No medium`.  Before diagnosing host storage, inspect
the gadget LUN `file` attributes: the tested factory runtime advertised two
empty LUNs, so no backing image existed to read.

### Direct wired transfer to stock firmware

The connected modem LAN socket reaches the same laboratory switch as the
development host.  Stock firmware uses `bridge0` at `192.168.0.1`; a temporary
address in the host subnet makes it directly reachable without changing the
saved factory configuration:

```sh
ip addr add 192.168.1.147/24 dev bridge0 2>/dev/null || true
```

For a controlled console update, receive into a name the stock updater does
not monitor, then compare size and SHA-256 before renaming it.  Permit only the
temporary TCP port needed by the receiver:

```sh
iptables -C INPUT -p tcp --dport 1234 -j ACCEPT 2>/dev/null || \
  iptables -I INPUT 1 -p tcp --dport 1234 -j ACCEPT
rm -f /cache/incoming.bin
tcpsvd 0.0.0.0 1234 sh -c 'cat > /cache/incoming.bin' &
```

Send from the development host with one TCP descriptor:

```sh
exec 3<>/dev/tcp/192.168.1.147/1234
cat /absolute/path/to/root_uImage.bin >&3
exec 3>&-
```

The stock ICMP policy can drop ping while this TCP connection and the HTTP
server both work.  Treat an exact received SHA-256, not ping, as the transfer
acceptance criterion.  The temporary address also exposes the stock web UI at
`http://192.168.1.147/`; it disappears on reboot.  Be aware that stock
`dnsmasq` remains active on the bridge while it is connected to the laboratory
switch.

The tested stock web upload handler calls `statvfs("/mmc2")` but the normal
factory boot does not create that path.  Without it, even a sub-5-MiB upload
returns `space_not_enough`.  For a nonpersistent browser qualification only,
prepare enough temporary space before opening the page:

```sh
mkdir -p /mmc2
mountpoint -q /mmc2 || mount -t tmpfs -o size=8m tmpfs /mmc2
df -h /mmc2
```

This workaround disappears on reboot and must not be confused with a fix for
the later `CPE_UPGRADE` handoff, which is still under investigation.

## Autonomous-operation boundaries

The following bench operations are available without further physical setup:

- inspect GPIO state;
- read and interact with the serial console;
- perform normal power cycles;
- enter EDL and identify the resulting host USB port;
- load the EDL programmer with `qdload`;
- use the modem's normal USB interfaces without `sudo`;
- run non-destructive network, WLAN, Ethernet, MSS, WWAN and AT-command tests.

Persistent NAND writes are allowed only when they are part of the active task
and the exact image, partition and expected result are known. Operations on
SBL, MIBIB, EFS, TZ, RPM, identity/calibration data or other boot-chain and
factory partitions require explicit user direction. Preserve serial and tool
logs for failures, but redact the SIM PIN and other private lab data before
placing excerpts in a public report.

Request the project owner to attach the 24 MS/s logic analyser when electrical
waveform evidence is needed. State the exact signals/test points, trigger
condition and expected event before asking for the physical connection.
