2. Architecture of the printing stack¶
2.1 Layers¶
com.ileadtek.peripage.mvp.ui.* Activities (TextPrintActivity, ImagePrintActivity, ...)
|
y6.b (singleton y6.b.h5()) app-level device manager [extends y6.f]
y6.f (5701 lines) device model registry + print orchestration
|
y6.a (2771 lines) protocol session [extends y0.d]
|
y0.d -> y0.h (501 lines) *** PeriPage command builder ***
|
y0.e ("BluetoothPortPrint") Bluetooth Classic RFCOMM socket + RX thread
There is a second, unrelated SDK in package gb.* (also SPP). It is selected only when
So gb.* is the A3 SDK. P21 does not use it — P21 goes through y0.h.
com.clj.fastble (BLE/GATT) is referenced only from discovery UI
(DeviceActivity, DiscoveryFragment, CupSearchDeviceDialog) — it is used for
scanning and for a few BLE-only accessories, not for the P21 data path.
This describes the app, not the hardware. The P21 itself is dual-mode and exposes a perfectly usable BLE GATT service (
FF00) that the app never touches — see §5.13. Everything below concerns the SPP path the app actually uses.
2.2 Transport: Bluetooth Classic SPP (RFCOMM)¶
y0.e is the socket layer (log tag BluetoothPortPrint):
BluetoothDevice dev = adapter.getRemoteDevice(mac);
BluetoothSocket s = dev.createRfcommSocketToServiceRecord(
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")); // standard SPP
s.connect();
// fallback if that throws:
s = (BluetoothSocket) dev.getClass()
.getMethod("createRfcommSocket", int.class).invoke(dev, 1); // channel 1
s.connect();
- Adapter must report
STATE_ON(12); it polls every 200 ms up to the timeout. - Default connect timeout
y0.h.f51257c = 3000ms. - On success it grabs
getOutputStream()/getInputStream()and starts one RX thread.
Write path — y0.h.G(byte[])¶
Every command and every raster payload goes through one function:
public boolean G(byte[] buf) {
if (!port.connected) return false;
int remaining = buf.length;
while (true) {
Thread.sleep(1); // 1 ms pacing between chunks
if (remaining <= 1024)
return port.write(buf, buf.length - remaining, remaining);
if (!port.write(buf, buf.length - remaining, 1024)) return false;
remaining -= 1024;
}
}
=> Writes are split into 1024-byte chunks with a 1 ms sleep between them. That is the only flow control on the TX side. Keep the pacing, but see §3.5 — on stacks that do not fragment for you, chunk to the negotiated RFCOMM MTU (126 bytes on the test unit) rather than 1024.
Read path — y0.e RX thread (y0.g)¶
The RX thread polls InputStream.available() every 100 ms (y0.e.n(timeout)),
then hands buffers to y0.e.l(byte[]), which only interprets 2-byte replies:
| Reply (2 bytes) | Handler msg | Meaning |
|---|---|---|
FF 01 |
1 | status / notification |
FF 02 |
2 | " |
FF 03 |
3 | " |
FF 04 |
4 | " |
FF 05 |
5 | " |
FF 06 |
18 | " |
FE xx |
6 | " |
FD 03 |
16 | " |
FD 04 |
17 | " |
FD xx |
7 | " |
FC xx |
8 | " |
| anything else | 0 | raw payload passed up |
Framing rules used by the RX thread (y0.g.run()):
- If a buffer is longer than 2 bytes, has even length and is not length 3, it is split into 2-byte pairs, each dispatched 10 ms apart.
- Length-3 buffers are special-cased when the first or last byte is
0xAA(AAis dispatched alone, the other two as a pair). - When flag
f51242pis set (set byy0.h.h(), the10 FF 31 03query), the reader instead accumulates an ASCII-bracket frame[…](0x5B…0x5D) and posts it as message 9. This is the string-response mode used for device info/Wi-Fi scan results.