Files
ptprnt/src/printers/P700Printer.cpp
Moritz Martinius 7437d79393
All checks were successful
Build ptprnt / build (push) Successful in 3m41s
Update copyright
2025-10-12 22:04:29 +02:00

239 lines
7.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
ptrnt - print labels on linux
Copyright (C) 2025 Moritz Martinius
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "P700Printer.hpp"
#include <spdlog/spdlog.h>
#include <cassert>
#include <cstdint>
#include <iterator>
#include <memory>
#include <thread>
#include <vector>
#include "../graphics/Bitmap.hpp"
#include "../graphics/Monochrome.hpp"
#include "../libusbwrap/LibUsbTypes.hpp"
#include "spdlog/fmt/bin_to_hex.h"
namespace ptprnt::printer {
const PrinterInfo P700Printer::mInfo = {.driverName = "P700",
.name = "Brother P-touch P700",
.version = "v1.0",
.usbId{0x04f9, 0x2061},
.pixelLines = 128};
P700Printer::~P700Printer() {
P700Printer::detachUsbDevice();
if (mUsbHndl) {
mUsbHndl->close();
}
}
const std::string_view P700Printer::getDriverName() {
return mInfo.driverName;
}
const std::string_view P700Printer::getName() {
return mInfo.name;
}
const std::string_view P700Printer::getVersion() {
return mInfo.version;
}
const PrinterInfo P700Printer::getPrinterInfo() {
return mInfo;
}
const PrinterStatus P700Printer::getPrinterStatus() {
using namespace std::chrono_literals;
send(p700::commands::GET_STATUS);
int tx = 0;
int tries = 0;
std::vector<uint8_t> recvBuf(32);
while (tries++ < MAX_TRIES_GET_STATUS) {
std::this_thread::sleep_for(100ms);
mUsbHndl->bulkTransfer(p700::commands::PRINTER_INFO[0], recvBuf, &tx, 0);
}
return PrinterStatus{.tapeWidthMm = recvBuf[10]};
}
const libusbwrap::usbId P700Printer::getUsbId() {
return mInfo.usbId;
}
bool P700Printer::attachUsbDevice(std::shared_ptr<libusbwrap::IUsbDevice> usbHndl) {
if (!usbHndl->open()) {
spdlog::error("Unable to open USB device: {}", usbHndl->getLastErrorString());
return false;
}
if (!usbHndl->detachKernelDriver(0)) {
spdlog::error("Device is already in use or couldn't be detached from kernel: {}",
usbHndl->getLastErrorString());
return false;
}
if (!usbHndl->claimInterface(0)) {
spdlog::error("Could not claim interface 0: {}", usbHndl->getLastErrorString());
return false;
}
mUsbHndl = std::move(usbHndl);
return true;
}
bool P700Printer::detachUsbDevice() {
if (!mUsbHndl) {
spdlog::warn("No device to detach...");
return true;
}
if (!mUsbHndl->releaseInterface(0)) {
spdlog::error("Could not release interface 0: {}", mUsbHndl->getLastErrorString());
return false;
}
return true;
}
bool P700Printer::printBitmap(const graphics::Bitmap<graphics::ALPHA8>& bitmap) {
// Convert bitmap to MonochromeData and delegate to printMonochromeData
auto pixels = bitmap.getPixelsCpy();
auto mono = graphics::Monochrome(pixels, bitmap.getWidth(), bitmap.getHeight());
auto monoData = mono.get();
return printMonochromeData(monoData);
}
bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) {
// Send initialization sequence
// The INITIALIZE command needs to be sent as a 128-byte packet with ESC @ at the end
std::vector<uint8_t> initCmd(128, 0x00);
initCmd[126] = p700::commands::INITIALIZE[0]; // ESC
initCmd[127] = p700::commands::INITIALIZE[1]; // @
send(initCmd);
// Status is already queried in getPrinterStatus()
send(p700::commands::PRINT_MODE);
send(p700::commands::AUTO_STATUS);
send(p700::commands::MODE_SETTING);
// Send raster data row by row in reverse order (bottom to top)
// The printer feeds tape as it prints, so first row sent appears at the end
for (int row = data.height - 1; row >= 0; row--) {
std::vector<uint8_t> rowData;
// Extract row data byte by byte
for (uint32_t col = 0; col < data.width; col += 8) {
uint8_t byte = 0;
for (int bit = 0; bit < 8 && (col + bit) < data.width; bit++) {
if (data.getBit(col + bit, row)) {
byte |= (1 << (7 - bit));
}
}
rowData.push_back(byte);
}
// Build raster line command: G + length_high + 0x00 + length_low + data
std::vector<uint8_t> buf;
buf.push_back(0x47); // 'G' command
buf.push_back((rowData.size() + 1) & 0xFF); // length + 1 (low byte)
buf.push_back(0x00); // high byte (always 0 for our data size)
buf.push_back((rowData.size() - 1) & 0xFF); // length - 1
buf.insert(buf.end(), rowData.begin(), rowData.end());
if (!send(buf)) {
spdlog::error("Error sending raster line {} to printer", row);
return false;
}
}
// Send print finalization commands
send(p700::commands::EJECT);
return true;
}
bool P700Printer::printLabel(std::unique_ptr<graphics::ILabel> label) {
// Convert label directly to MonochromeData
// getRaw() returns data in Cairo surface coordinates matching getWidth() × getHeight()
auto pixels = label->getRaw();
// Create monochrome data in landscape orientation (as stored in Cairo surface)
auto mono = graphics::Monochrome(pixels, label->getWidth(), label->getHeight(), graphics::Orientation::LANDSCAPE);
auto monoData = mono.get();
// Transform to portrait orientation for printing
monoData.transformTo(graphics::Orientation::PORTRAIT);
spdlog::debug("Label surface was {}x{}, after transform to portrait: {}x{}", label->getWidth(), label->getHeight(),
monoData.width, monoData.height);
return printMonochromeData(monoData);
}
bool P700Printer::print() {
send(p700::commands::LF);
send(p700::commands::FF);
send(p700::commands::EJECT);
return true;
}
bool P700Printer::send(const std::vector<uint8_t>& data) {
if (mUsbHndl == nullptr || data.size() > 128) {
spdlog::error("Invalid device handle or invalid data.");
return false;
}
spdlog::trace("USB Tx → 0x02 {:03d} bytes: {:Xn}", data.size(), spdlog::to_hex(data));
#ifdef USB_TRACE_ONLY
// Trace mode: Log the data that would be sent without actually sending it
return true;
#else
int tx = 0;
if (!mUsbHndl->bulkTransfer(0x02, data, &tx, 0)) {
spdlog::error("Error writing command to Printer: {}", mUsbHndl->getLastErrorString());
return false;
}
assert(tx > 0);
if (static_cast<std::uint32_t>(tx) != data.size()) {
spdlog::error("Could not transfer all data via USB bulk transfer. Only sent {} of {} bytes", tx, data.size());
return false;
}
return true;
#endif
}
bool P700Printer::init() {
std::vector<uint8_t> cmd(102);
cmd[100] = 0x1b; /* ESC */
cmd[101] = 0x40; /* @ */
return send(cmd);
}
} // namespace ptprnt::printer