begin documenting the project, README

This commit is contained in:
GiggioG 2025-09-07 03:32:22 +03:00
parent ca42b7bf20
commit 1bc7bd7565
11 changed files with 731 additions and 1 deletions

133
README.md Normal file
View File

@ -0,0 +1,133 @@
# thermal-printer
A project to reverse engineer the protocol of my thermal printer - Vyzio B15 (X6) and [implement it myself](#Usage), so I can print from my laptop.
This project was inspired by [WerWolv's Cat printer blogpost](https://werwolv.net/blog/cat_printer)
, but I avoided looking at their code and peaking at the blogpost as much as i could.
## Protocol
### Command structure
Consists of commands. Each command has the following structure:
`0x51` - Magic value
`0x78` - Magic value
`0x..` - Command byte
`0x00` or `0x01` - Direction (0 for Phone->Printer, 1 for reverse)
`0x..` - Data length, low byte
`0x00` - Data length, high byte (always zero)
`data` * N - Data
`crc8` - crc8 of only the data
`0xFF` - End magic
### Printing an image
And the app sends the following commands to print an image (refer to the [commands](#getcommandcodecmdname)):
1. Blackening (quality) - always 51 (3) for my printer. It is from 1 to 5 (49 to 53).
2. Energy - only if the print type isn't TEXT: corresponds to the _Print depth_ set in the app, with the formula `energy = 7500 + (PD - 4)*0.15*7500`. The Print depth is from 1 to 7.
3. PrintType - Image(`0x00`), Text(`0x01`) of Label(`0x03`). Still can't find the differences between them, but I think that Image allows for darker images, but I'm not sure.
4. FeedPaper(speed) - I don't know what it does, but the speed is 10 for text and 30 for images and labels.
5. Drawing - commands with either run-length encoded or bit-packed lines
6. FeedPaper(25), Paper(0x30), Paper(0x30), FeedPaper(25)
I don't know what the FeedPaper calls do, but the Paper command feeds the paper so the printed image emerges.
## Usage
In the `./src` directory you can find python files with functions to use the protocol and example usage in `./src/main.py`.
### commands.py
Here you can find functions to create the bytes to command the printer.
#### checkImage(imgPath)
Checks if the image at this path is usable for the printer (384 px wide and 1bpp)
#### calcCrc8(data)
Calculates the crc8 checksum
#### commandBytes(cmd, data)
Generates the byte array for a command
#### getCommandCode(cmdName)
Returns the byte corresponding to the command. Accepts:
| Name(s) | Description | Byte |
| - | - | - |
| BLACKENING _(QUALITY)_ | Sets the quality level for the printer. I recommend keeping at default value 51. | `0xA4` |
| ENER(A)GY | Sets the energy (print depth) - the strength of heating of the black pixels. Use the `getEnergy()` function. Default value is 7500 for my model. | `0xAF` |
| PRINT_TYPE | Sets the print type. | `0xBE` |
| FEED_PAPER | Idk what, **DOES NOT** feed the paper. | `0xBD` |
| DRAW_COMPRESSED | Draws a row of pixels, encoded with run-length encoding. The highest bit of each byte is the value and the other 7 encode the run length. | `0xBF` |
| DRAW_PACKED | Draws a row of pixels where each pixel corresponds to one bit in the command's data. | `0xA2` |
| PAPER | Actually feeds the paper. Takes number of lines to move, with data size 2 (potentially 16 bite, little endian). | `0xA1` |
#### runLenght(line)
Creates a byte array with the line compresses as explained above (see DRAW_COMPRESSED).
#### bitPack(line)
Creates a byte array with each pixel corresponding to a bit.
#### toArrayLE(num)
Converts 16-bit number to little-endian
#### getEnergy(printDepth)
Calculates the energy corresponding to a certain value of "Print depth" in the app
#### getTypeByte(typeStr)
Returns byte code for the print type. Supports
| Type | Byte |
| - | - |
| IMAGE | `0x00` |
| TEXT | `0x01` |
| LABEL | `0x03` |
#### getSpeed(typeStr)
Returns the app's default "printSpeed" for types. Supports "IMAGE" (30) and "TEXT" (10). This is used with FEED_PAPER, idk how.
#### readImage(imgPath)
Reads image from the path specified.
#### compressImageToCmds(img)
For each line applies both run-length encoding and bit packing and chooses the shorter. Packages each line's compressed data into the appropriate command.
#### saveCommandsToFile(commands, saveToFile=None, saveToFileHuman=None)
Saves a list of commands to a binary file and/or a human-readable hex file.
#### genMoveUp(lines=0x60)
Generates the paper feeding commands at the end of the app's routine for printing an image.
#### genCommands( img, ... _(see source code)_ )
Generates the commands for printing an image with the specified parameters.
### connection.py and class Connection
#### async connect()
Connects to the device and awaits OKAY status.
#### async disconnect()
Disconnects from the device.
#### \_\_init\_\_(macAddress)
Sets up the Connection for the .connect method.
#### send(commands, delay=0)
Writes each command individually to the appropriate characteristic, waiting _delay_ seconds between each one. In the end asks device for status and awaits OKAY.
#### Context managers
The Connection class supports the context manager protocol. It automatically connects and disconnects to the device.
```python
async with Connection("AA:BB:CC:DD:EE:FF") as conn:
...
```
#### FakeConnection
This class mirrors the methods of Connection, but its constructor takes paths to log files, where the sent data gets dumped.
### decode.py
#### readHumanFile(fileName)
Reads a human-readable file of hex values into an array of bytes.
#### readFile(fileName)
Reads a binary file into an array of bytes.
#### decodeCommands(cmdBytes, saveFilename=None)
Decodes the bytes given into the image they convey and returns it. If _saveFilename_ is set, then it saves the image there.
### textToImage.py
#### textToImage(text, ... _(see source code)_ )
Generates a PIL image from the given text.
## Write up
You can read about my process of reverse engineering the protocol [here](WRITE_UP.md).

1
WRITE_UP.md Normal file
View File

@ -0,0 +1 @@
# TODO

View File

@ -0,0 +1,135 @@
MAGIC VALUE: 0x51 78
```js
function getHex(){return Array.from(new Uint8Array(arguments)).map(e=>e.toString(16).padStart(2, '0').toUpperCase()).join(' ');}
```
0. Inspo (cat printer) i motivaciq
1. Sniff s ESP32 i nrF connect
2. TestGen.py, Gledane samo na dannite
- 0x5178 100% e magic value kato header
- packetitie zavurshvat na 0xFF
- `ggVGgJ:%s/5178/^V^M5178/g^MggddG:s/0*$/^M:%s/\(\w\w\)\(\w\w\)/\1 \2/g^M:%&g^M` for data parsing
- 2ta scripta za visualizirane, ne bqh zabelqzal, che e B/W dithered, a ne grayscale, ochakvah da pop outne kartinkata
- dataToBin.py, da go gledam s hex editor => do nikude
3. zabivane v PrintPreviewActivity.java
- printBtn
- printData
- nedecompiliraniq handler, emptyMessage-5 => emptyMessage-4
4. Zabivane v activityType (LABEL, OFFICE, ...)
- predpolagam label, produljavam
5. PrintModelUtils i PrinterModel => x6.java, izvadq argumentite
- legendarno vim macro:
`0f(l"ddt,xxj0f(ldw"edt,xxjvi{:s/<C-R>e;/<C-R>d;<CR>?{k0`
6. naj-nakraq v pravilnata posoka: `BitmapToData()` v `PrintDataUtils.java`
7. status byte-ovete rediscovernati: 0x`51 78 A3 00 01 00 00 00 FF` obratno v PrintData (PrintPreviewActivity)
8. v BitmapToData se vika eachLinePixTo...B, no to ne e decomp, zatova gledam eachLine...Gray, sigurno e sushtoto
- zashto tf raw byte-ovete na dithera se zavirat direktno v packeta, bez packet (0x`51 78 ... FF`).
9. bluskam si glavata, no vednaga vijdam, che v `eachLineP...B` ne e sushtoto, oshte v purvite redove se getva
width / 8, ochevidno e razlichno.
10. buildvame packeta:
- enerAgy packet 0x`51 78 AF 00 02 00 <nrg_low> <nrg_high> <crc> FF`
- printType packet 0x`51 78 BE 00 01 00 <type> <crc> FF`
Type: `00` - image, `01` - text, `02` - tattoo, `03` - label
- feedPaper packet 0x`51 78 BD 00 01 00 <textSpeed> <crc> FF`
11. obratno v BitmapToData:
- blackeningPacket 0x`51 78 A4 00 01 00 33 99 FF` (quality 3 (51) (0x33) - vinagi za moq printer e tova quality)
te sa ot 1 do 5 (ot 49 do 53)
- feedPaper 0x`51 78 BD 00 01 00 0F <crc> FF` - feedPaper(15)
12. obratno v `eachLinePixToCmdB`: polzvam nqkolko regexa, ne pomagat osobeno mn
- zagrqvam, che DataTrim() vzima (runLenght, byte, arrayList), i byte-a legit e 0x00 ili 0x01
vseki byte, kojto pravi e `<1b: stojnost na byte-a, 0 ili 1><7b: duljina na run-length(do 127)>`
- run-length encoding, ako e po-zle ot bitpacking, togava bit-packvame
- L_0x02fb dataTrim-vame 0, ako izobshto nishto ne sme imali
- v L_0x0328: stranni kriterii dali sme kompresirali:
ne sme kompresirali, ako: 1. purviq byte e 0xFF, 2. byte-ovete ne sa width/8 + 1
- 0x`51 78` + -94(0x`A2`) ako ne sme kompresirali, -65(0x`BF`), ako sme kompresirali
- 0x`51 78 <A2 | BF> 0 <datalen_low> <datalen_high | 00> <dannite, run-lenghtnati ili bitpack-nati> <crc> FF`
- da se izfukam, che kompresiqta q nqma v originalniq writeup
12. naj-nakraq q svurshih, obratno v BitmapToData
- paper packet, ot BluetoothOrder 0x`51 78 A1 00 02 00 30 00 F9 FF`, sled vseki PrintBean, kojto ima .isAddWhite()
- maj nqma takiva
13. finalna naredba na komandite
14. decode.py, bachka s probite (primeri)
15. kakvo e energy-to?
- gledam v koda: (concentration-a - Code.DEFCONCENTRATION(4))\*0.15\*d + d, trqbva da e 0x7B2A(=10875), zashtoto tova poluchavam
- osuznavam, che concentration sigurno e print depth, zashtoto defaulta e 4 ot 1 do 7 (DEF _\[ault\]_ CONCENTRATION = 4)
- az polzvam 7, znachi (7-4)*0.15.d+d = 10875 => d = 7500, no moderateConcentration-a na X6 e 8000.....
- moqt printer e x6_n or PrinterModelUtils.java, vse pak ne obqsnqva zashto isCanPrintLabel qvno e false
- hipoteza: energy = (printDepth - 4)\*0.15\*7500 + 7500, shte testvam
- hipoteza 2: textEnergy=> energy da e =0 pri textov rejim
16. kakuv e print type-a
- probvam da send-na snimka kato text, image; text kato text,image; opitam se da dokaram label
17. textSpeed-a e model.textSpeed(10) za text(0x`01`), model.imgSpeed(30) za image(0x`00`) i label(0x`03`)
- probvajki prednoto, sledq feedPaper-a
18. Da probvam:
1. da dokaram print type: label
2. da dokaram print type: text
- vidq dali energy == 0
3. probvam pone 2 drugi energy-ta, da vidq dali scale-va pravilno
4. probvam da sendna snimka kato text, obratnoto, sledq feedPaper-a
19. Findings:
- kogato printiram document, type: img, printType-a se meni, no nikoga nqma energy i textSpeed = 10
- energyto naistina e po onazi formula
- tova s documenta e izkluchenie, sus image i text:
1. pri image ima energy po formulata, BD=00, textSpeed=30
2. pri text nqma energy, BD=01, textSpeed=10
- label-a se durji tochno kato chist image, samo BD=03; nezavisimo dali type-a e setnat na Text ili Image
- kato mu dam poveche kopiq apparently ne sa otdelni bean-ove, poneje ima blackening pred vsqko i dopulnitelen
feedPaper(25) sled vsqko => probvam s multiple beans
20. Recreate in script
- crc-to, deleted59 => gledam lipsvashtoto sus xor na vsichki ostanali, razbiram che e `0x3d`
- printImage.py -> uspeshno generira commandi
21. Make script actually send BLE packets
- reading what printer sends back, the 4th byte is 0x00 if phone->printer and 0x01 if printer->phone
21. status (0x`A3`) response (length 3)
1. status, ends with:
- =0: ok,
- 0b1: out of paper,
- 0b10: compartment open,
- 0b100: overheated,
- 0b1000: low battery,
- 0b10000: currently charging,
- 0b10000000: currently printing
2. labelNum, kakvoto i da e tova
3. battery level, po nqkakva skala?
22. Bachka
23. Kakvo ostana?
- kakvo pravi 0xBD FeedPaper?
24. Look back
- ako imah akul i bqh zabelqzal, che e cherno-bql, mojeshe samo ot protocol capturi-te
no nqmashe da imam compressiq, da znam za type-ove, za energy i t.n.
# Vim commands for easier decompiling
1. `:v/L_0x....:/norm A;^[`
2. `:%s/\(\d\+\)(0x.*)/\1/g`
3. `:%s/\(.* \)\?r\(\d\+\) = \(.*\);$\n\t\(.* \)r\2 = \(.*\);$/\=("\t".submatch(4)."r".submatch(2)." = ".substitute(submatch(5),"r".submatch(2),submatch(3),"")."; \/\/ rule 1")/g`
4. `:%s/\(.* \)\?r\(\d\+\) = \(.*\);$\n\tr\2 = \(.*\);$/\=("\t".submatch(1)."r".submatch(2)." = ".substitute(submatch(4),"r".submatch(2),submatch(3),"")."; \/\/ rule 2")/g`
5. `:%s/int r\(\d\+\) = r\1 \([+-\*\/]\) \(.\+\);/r\1 \2= \3; \/\/ rule 3/g`
6. `:v/r\(\d\+\) = \(\-\?\d\+\);\n.*r\1 \?=.*/ s/r\(\d\+\) = \(\-\?\d\+\);$\n\t\(.*[ (]\)r\1\(.*\)/r\1 = \2;^M\t\3\2\4 \/\/ rule 4/g`
7. `:%s/boolean r\(\d\+\) = \(.*\);\n\tif (r\1\(.*\)$/if ((r\1 = \2\)\3 \/\/ rule XXX/g`
8. `:%s/if (\(.*\)) goto \(L_0x....\);\n\tif (\(.*\)) goto \2;/if ((\1) || (\3)) goto \2; \/\/ rule 6/g`
- broken but still cool: `:%s/if \(.*\) goto \(L_0x....\);$\n\(^\t.*$\n\)\+\tgoto \(L_0x....\);\n\2:$\n\(^\t.*$\n\)\+\4:/if \1 { \/\/ rule 5^M\5\t} else {^M\3\t}/g`
# command order
1. blackening /// ne za vseki bean
2. vsichkite komandi ot EachLinePixToCmdB
1. enerAgy
2. printType
3. feedPaper(textSpeed)
4. draw
3. paper (ako bean-a ima addWhite)
4. feedPaper(25)
5. paper x2
bi trqbvalo da gi nqma, poneje X6 e isCanPrintLabel.
6. feedPaper(25) /// ne za vseki bean
7. ne poluchavam devInfo (0x`51 78 A8 00 01 00 00 00 FF`), nz zashto
i tuk i pri dvata `paper`-a se durji, sqkash X6 ne e isCanPrintLabel
# todo
- [ ] napisha property-ta na X6_n (ot file)
- [ ] writeup

View File

@ -0,0 +1,18 @@
import os
import glob
os.makedirs("databin", exist_ok=True)
for data_file in glob.glob("data/data*.txt"):
with open(data_file, "r") as f:
hex_data = f.read().replace('\n', '').replace(' ', '')
# Convert hex string to bytes
byte_data = bytes.fromhex(hex_data)
# Generate output filename: databin/data#.bin
base = os.path.splitext(os.path.basename(data_file))[0]
out_file = f"databin/{base}.bin"
with open(out_file, "wb") as fout:
fout.write(byte_data)

View File

@ -0,0 +1,86 @@
import os
from PIL import Image
import sys
import glob
os.makedirs("decodedImg", exist_ok=True)
for in_file in glob.glob("data/data*.txt"):
cmds = []
with open(in_file, "r") as file:
data = file.read().split('\n')
data = [d.strip() for d in data if len(d)>0]
binary = []
for d in data:
binary.extend(bytes.fromhex(d))
idx = -1
data = []
cmdCode = 0x00
dataLen = 0
crc = 0x00
for i, b in enumerate(binary):
idx += 1
#print(i, idx, hex(b))
if b == 0x51 and idx == 0:
pass
elif b == 0x78 and idx == 1:
pass
elif idx == 2:
cmdCode = b
elif b == 0x00 and idx == 3:
pass
elif idx == 4:
dataLen = b
elif idx == 5:
if b != 0:
dataLen = dataLen*0x100 + b
elif idx >= 6 and idx < 6+dataLen:
data.append(b)
elif idx == 6+dataLen:
crc = b
elif idx == 6+dataLen+1 and b == 0xFF:
o = {
"cmd": cmdCode,
"data": data,
"crc": crc
}
cmds.append(o)
idx = -1
crc = 0
data = []
cmdCode = 0
else:
idx = -1
crc = 0
data = []
cmdCode = 0
#print(cmds)
#for c in cmds:
# print(hex(c["cmd"]), hex(len(c["data"])), hex(c["crc"]))
rows = []
for c in cmds:
if c["cmd"] == 0xa2:
s = "".join([bin(b)[2:10].zfill(8)[::-1] for b in c["data"]])
row = [int(x)==1 for x in s]
rows.append(row)
elif c["cmd"] == 0xbf:
row = []
for b in c["data"]:
bit = (b & (1<<7)) == 128
times = b & ~(1<<7)
row.extend([bit] * times)
rows.append(row)
HEIGHT = len(rows)
WIDTH = max(len(r) for r in rows)
img = Image.new("1", (WIDTH, HEIGHT))
for y in range(HEIGHT):
for x in range(WIDTH):
img.putpixel((x,y), not rows[y][x])
base = in_file.split('\\')[-1].split('.')[0] # Get base name without extension
output_file = f"decodedImg/{base}.png"
img.save(output_file)

View File

@ -0,0 +1,113 @@
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
// https://www.uuidgenerator.net/
class ServerCallbacks : public BLEServerCallbacks{
void onConnect(BLEServer *pServer){
Serial.println("Connected.");
}
void onDisconnect(BLEServer *pServer){
pServer->startAdvertising();
Serial.println("Disconnected, restarting advertising.");
}
};
const int MAX_LINE = 2000;
int lineCol = 0;
void myPrint(String s){
int len = s.length();
if(lineCol + len >= MAX_LINE){
Serial.println("");
lineCol = 0;
}
Serial.print(s);
lineCol += len;
}
void printReceived(BLECharacteristic *pRxCharacteristic){
String uuid = pRxCharacteristic->getUUID().toString();
String rxValue = pRxCharacteristic->getValue();
// Serial.print(uuid);
// Serial.print(" > ");
String hexByte = "##";
for(int i = 0; i < rxValue.length(); i++){
uint8_t byte = rxValue[i];
hexByte[0] = byte/16;
hexByte[1] = byte%16;
for(int i = 0; i < 2; i++){
hexByte[i] += (hexByte[i] >= 10 ? ('A'-10) : '0');
}
myPrint(hexByte);
}
}
BLECharacteristic *pRxChar = nullptr, *pTxChar = nullptr;
class WriteCharacteristicCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pRxCharacteristic){
printReceived(pRxCharacteristic);
if(pRxCharacteristic == pRxChar){
pTxChar->setValue("\x51\x78\xA3\x01\x03\x00\x00\x0F\x24\x3F\xFF");
pTxChar->notify();
}
}
};
void setup() {
Serial.begin(115200);
/// SETUP BLE
BLEDevice::init("X6");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
{
BLEService *pService_AE30 = pServer->createService("AE30");
BLECharacteristic *pChar_AE01 = pService_AE30->createCharacteristic("AE01", BLECharacteristic::PROPERTY_WRITE_NR);
pChar_AE01->setCallbacks(new WriteCharacteristicCallbacks());
pRxChar = pChar_AE01;
BLECharacteristic *pChar_AE02 = pService_AE30->createCharacteristic("AE02", BLECharacteristic::PROPERTY_NOTIFY);
pChar_AE02->addDescriptor(new BLE2902());
pTxChar = pChar_AE02;
BLECharacteristic *pChar_AE03 = pService_AE30->createCharacteristic("AE03", BLECharacteristic::PROPERTY_WRITE_NR);
pChar_AE03->setCallbacks(new WriteCharacteristicCallbacks());
BLECharacteristic *pChar_AE04 = pService_AE30->createCharacteristic("AE04", BLECharacteristic::PROPERTY_NOTIFY);
pChar_AE04->addDescriptor(new BLE2902());
BLECharacteristic *pChar_AE05 = pService_AE30->createCharacteristic("AE05", BLECharacteristic::PROPERTY_INDICATE);
pChar_AE05->addDescriptor(new BLE2902());
BLECharacteristic *pChar_AE10 = pService_AE30->createCharacteristic("AE10", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
pChar_AE10->setCallbacks(new WriteCharacteristicCallbacks());
pService_AE30->start();
}
{
BLEService *pService_AE3A = pServer->createService("AE3A");
BLECharacteristic *pChar_AE3B = pService_AE3A->createCharacteristic("AE3B", BLECharacteristic::PROPERTY_WRITE_NR);
pChar_AE3B->addDescriptor(new BLE2902());
BLECharacteristic *pChar_AE3C = pService_AE3A->createCharacteristic("AE3C", BLECharacteristic::PROPERTY_NOTIFY);
pChar_AE3C->addDescriptor(new BLE2902());
pService_AE3A->start();
}
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->start();
Serial.println("Setup done!");
}
void loop(){}

View File

@ -0,0 +1,43 @@
from math import ceil
import os
from PIL import Image
import glob
WIDTH = 256
OFFSET = 9
os.makedirs("dataImg", exist_ok=True)
for data_file in glob.glob("data/data*.txt"):
with open(data_file, "r") as f:
data = f.read().replace('\n', '')
bytes_array = [int(data[i:i+2], 16) for i in range(0, len(data), 2)]
HEIGHT = 1 + ceil((len(bytes_array)-OFFSET)/WIDTH)
IMG_WIDTH = max(WIDTH, OFFSET)
def getColor(value):
return (value, value, value)
img = Image.new("RGB", (IMG_WIDTH, HEIGHT))
for i in range(0, IMG_WIDTH):
if i < OFFSET:
img.putpixel((i, 0), getColor(bytes_array[i]))
else:
img.putpixel((i, 0), (255, 0, 255))
for i in range(1, HEIGHT):
for j in range(0, IMG_WIDTH):
if j >= WIDTH:
img.putpixel((j, i), (255, 0, 255))
continue
idx = OFFSET + (i - 1) * WIDTH + j
if idx < len(bytes_array):
img.putpixel((j, i), getColor(bytes_array[idx]))
else:
img.putpixel((j, i), (255, 0, 255))
base = data_file.split('\\')[-1].split('.')[0] # Get base name without extension
output_file = f"dataImg/{base}.png"
img.save(output_file)

View File

@ -0,0 +1,47 @@
from math import ceil
from PIL import Image
import glob
import os
os.makedirs("dataImg", exist_ok=True)
def getColor(value):
return (value, value, value)
# Find all files matching data*.txt
for data_file in glob.glob("data/data*.txt"):
with open(data_file, "r") as f:
data = f.read().replace('\n', '')
bytes_array = [int(data[i:i+2], 16) for i in range(0, len(data), 2)]
runs_array = []
run = []
runSum = 0
for b in bytes_array:
run.append(b)
runSum += b
if b == 255:
runs_array.append(run)
run = []
if len(run) > 0 and runSum > 0:
runs_array.append(run)
if not runs_array:
continue
HEIGHT = len(runs_array)
WIDTH = max(len(run) for run in runs_array)
img = Image.new("RGB", (WIDTH, HEIGHT))
for i in range(HEIGHT):
for j in range(WIDTH):
if j < len(runs_array[i]):
img.putpixel((j, i), getColor(runs_array[i][j]))
else:
img.putpixel((j, i), (255, 0, 255))
# Generate output filename: image#.png
base = data_file.split('\\')[-1].split('.')[0] # Get base name without extension
output_file = f"dataImg/{base}.png"
img.save(output_file)

View File

@ -0,0 +1,104 @@
from math import ceil
import random
from PIL import Image
import zipfile
import os
def getColor(value):
return (value, value, value)
WIDTH = 384
def makeTestImage(testGen, name):
rows = testGen()
HEIGHT = len(rows)
print("Test image " + name + " is " + str(WIDTH) + "x" + str(HEIGHT))
img = Image.new("RGB", (WIDTH, HEIGHT))
for i in range(HEIGHT):
for j in range(WIDTH):
img.putpixel((j, i), getColor(rows[i][j]))
img.save("testImages/" + name + ".png")
def genTest1():
rows = []
i = 0
while 2**i <= WIDTH:
row = [255]
power = 2**i
for j in range(1, WIDTH):
color = row[j-1]
if j % power == 0:
color = 255-color
row.append(color)
rows.append(row)
i += 1
return rows
def genTest2():
rows = []
i = 0
while 2**i <= WIDTH:
row = []
power = 2**i
for j in range(0, WIDTH):
color = (j % power) * (255 // max(power-1, 1))
row.append(color)
rows.append(row)
i += 1
return rows
def genTest3():
rows = []
for _ in range(9):
row = []
for j in range(WIDTH):
color = (j%2) * 255
row.append(color)
rows.append(row)
return rows
def genTest4():
blackRow = [0 for _ in range(WIDTH)]
whiteRow = [255 for _ in range(WIDTH)]
rows = []
for i in range(9):
rows.append(blackRow if i % 2 == 0 else whiteRow)
return rows
def genTest5():
blackRow = [0 for _ in range(WIDTH)]
whiteRow = [255 for _ in range(WIDTH)]
rows = []
for i in range(9):
rows.append(whiteRow if i % 2 == 0 else blackRow)
return rows
def genTest6():
rows = []
for _ in range(9):
row = []
for _ in range(WIDTH):
color = random.randint(0, 255)
row.append(color)
rows.append(row)
return rows
tests = [
(genTest1, "test1"),
(genTest2, "test2"),
(genTest3, "test3"),
(genTest4, "test4"),
(genTest5, "test5"),
(genTest6, "test6")
]
os.makedirs("testImages", exist_ok=True)
for testGen, name in tests:
makeTestImage(testGen, name)
with zipfile.ZipFile("testImages/test_images.zip", "w") as zf:
for _, name in tests:
zf.write("testImages/" + name + ".png")

View File

@ -0,0 +1,50 @@
public static PrinterModel.DataBean x6_n = new PrinterModel.DataBean();
public DataBean() {
this.grayPrint = false;
this.grayThinEneragy = 0;
this.grayModerationEneragy = 0;
this.grayDeepenEneragy = 0;
this.slowInterval = 0;
this.grayImageSpeed = 40;
this.grayScale = 1.0d;
this.showElectricityModel = 0;
this.addMorPix = true;
this.A4XII = false;
this.tattooPaper = false;
this.tattooSpeed = 0;
this.tattooEnergy = 0;
this.addMorePixNum = -1;
this.d1key = "";
this.useNewActivation = false;
this.lzoVersion = "";
this.labelDevice = false;
this.labelDeviceSize = 0.0f;
this.autoLabelCheck = false;
this.corePrint = false;
this.modelNo = "X6"; //FROM FUNCTION
this.model = 0; //FROM FUNCTION
this.size = 2; //FROM FUNCTION
this.paperSize = 384; //FROM FUNCTION
this.printSize = 384; //FROM FUNCTION
this.oneLength = 8; //FROM FUNCTION
this.headName = "X6-"; //FROM FUNCTION
this.canChangeMTU = true; //FROM FUNCTION
this.devdpi = 200; //FROM FUNCTION
this.imgPrintSpeed = 30; //FROM FUNCTION
this.textPrintSpeed = 10; //FROM FUNCTION
this.imgMTU = 123; //FROM FUNCTION
this.newCompress = true; //FROM FUNCTION
this.paperNum = 2; //FROM FUNCTION
this.interval = 6; //FROM FUNCTION
this.thinEneragy = 7500; //FROM FUNCTION
this.moderationEneragy = 7500; //FROM FUNCTION
this.deepenEneragy = 7500; //FROM FUNCTION
this.textEneragy = 0; //FROM FUNCTION
this.hasId = true; //FROM FUNCTION
this.useSPP = false; //FROM FUNCTION
this.newFormat = false; //FROM FUNCTION
this.canPrintLabel = true; //FROM FUNCTION
this.labelValue = "1"; //FROM FUNCTION
this.backPaperNum = 40; //FROM FUNCTION
this.detectionValue = 1; //FROM FUNCTION
}

View File

@ -1,5 +1,5 @@
import asyncio
from import datetime import datetime
from datetime import datetime
from commands import *
from connection import Connection, FakeConnection;