Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 48 additions & 34 deletions peripage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,15 +482,21 @@ def setConcentration(self, concentration: int, wait: bool=False) -> None:
Request: `10ff1000+bytes[1]:big_endian`.

Arguments:
* `concentration` - concentration value from range `(0, 1, 2)`
"""

if concentration <= 0:
request = bytes.fromhex('10ff100000')
elif concentration == 1:
request = bytes.fromhex('10ff100001')
elif concentration >= 2:
request = bytes.fromhex('10ff100002')
* `concentration` - concentration value, byte range `(0-255)`. Values
`0`, `1`, `2` are the originally documented/tested levels and keep
producing the exact same bytes as before. The wire protocol itself
does not clamp to this range: the official Android app's
decompiled code sends this same opcode with no range check at all,
and on a real Peripage A40 values `3`/`4` are accepted without
error (no visible density difference was observed above `2` on
that specific unit, so treat anything past `2` as
hardware-dependent/experimental, not guaranteed to improve
anything). See the writeup this finding came from:
https://github.com/RainManVays/perripage-ferrum/blob/main/docs/bluetooth-protocol-trace-analysis.md
"""

concentration = min(0xff, max(0x00, concentration))
request = bytes.fromhex('10ff1000') + int.to_bytes(concentration, 1, 'big')

if wait:
return self.askPrinter(request)
Expand Down Expand Up @@ -712,16 +718,27 @@ def printRowBytesList(self, rowbytes: typing.Iterable[bytes], delay: float=0.01)
`Printer.getRowBytes()` constant, input is truncated. If size of input
is under the `Printer.getRowBytes()`, it will be padded with zeros.

This printer supports pages up to `0xffff` rows, but current
implementation relies on chunked data with height limit of `0xff` and
automatically slices the input into chunks.
This printer supports pages up to `0xffff` rows in a single request —
`row_bytes`/`height` are both encoded as 2-byte little-endian fields
(see Request line below), which for every existing supported model
(`row_bytes` always <= 255) produces byte-for-byte identical wire
data to the previous one-byte-plus-zero-padding encoding. Only
`height` above `0xff` actually changes behavior: previously this was
silently sliced into multiple `0xff`-row chunks (each with its own
`reset()`), which was an artifact of this method's own encoding
choice, not a firmware limit -- confirmed by reverse engineering the
official Android app, whose equivalent height field is a full 16
bits, and by printing a real 300-row image in one continuous
request on a Peripage A40 with no corruption at the old 255-row
boundary. See:
https://github.com/RainManVays/perripage-ferrum/blob/main/docs/bluetooth-protocol-trace-analysis.md

Note: In case of A6+, preamble is `1d76300048000100` that can be viewed
as `[ 1d7630, 0030, 0001 ]`, where `1d7630` is printing operation
request, `0030` is big endian bytes per row, `0001` is big endian input
height.
request, `0030` is little endian bytes per row, `0001` is little
endian input height.

Request: chunked `1d763000+bytes[1]:big_endian+00+bytes[1]:big_endian+00+bytes[Printer.getRowBytes()*chunk_height]`.
Request: `1d763000+bytes[2]:little_endian+bytes[2]:little_endian+bytes[Printer.getRowBytes()*height]`.

Arguments:
* `rowbytes` - list of bytes defining each row of the image. If row
Expand All @@ -734,31 +751,28 @@ def printRowBytesList(self, rowbytes: typing.Iterable[bytes], delay: float=0.01)
return

expectedLen = self.getRowBytes()
chunks = [ rowbytes[i:i+0xff] for i in range(0, len(rowbytes), 0xff) ]

for chunk in chunks:
height = len(rowbytes)

# Reset state before print
self.reset()
# Reset state before print
self.reset()

# 1d763000 30 00 01 00
# Send preamble: `1d763000` + row_bytes:bytes[1] + `00` + chunk_size:bytes[1] + `00`
request = bytes.fromhex('1d763000') + int.to_bytes(self.getRowBytes(), 1, 'big') + bytes.fromhex('00') + int.to_bytes(len(chunk), 1, 'big') + bytes.fromhex('00')
# Send preamble: `1d763000` + row_bytes:bytes[2]:little_endian + height:bytes[2]:little_endian
request = bytes.fromhex('1d763000') + int.to_bytes(expectedLen, 2, 'little') + int.to_bytes(height, 2, 'little')

# Flush preamble
self.tellPrinter(request)
# Flush preamble
self.tellPrinter(request)

# Flush rows dith delay
for row in chunk:
# trunc/pad
if len(row) < expectedLen:
row = row.ljust(expectedLen, b'\0')
elif len(row) > expectedLen:
row = row[:expectedLen]
# Flush rows with delay
for row in rowbytes:
# trunc/pad
if len(row) < expectedLen:
row = row.ljust(expectedLen, b'\0')
elif len(row) > expectedLen:
row = row[:expectedLen]

self.tellPrinter(row)
self.tellPrinter(row)

time.sleep(delay)
time.sleep(delay)

def printRowBytesIterator(self, rowiterator: typing.Iterable[bytes], delay: float=0.01) -> None:
"""
Expand Down