Skip to content

Instantly share code, notes, and snippets.

@xssfox
Created October 19, 2022 19:58
Show Gist options
  • Save xssfox/93eb82c7aa350cda7c7c4e19e3c939e0 to your computer and use it in GitHub Desktop.
Save xssfox/93eb82c7aa350cda7c7c4e19e3c939e0 to your computer and use it in GitHub Desktop.

First up you need to run this python3 script and leave it running - it's a web server that lets you control the printer from HTTP commands

https://gist.github.com/xssfox/b911e0781a763d258d21262c5fdd2dec

You probably need to have lucon.ttf in your systems font dir or in scripts directory

And you'll need bleak, Pillow and flask installed by pip

Some people have reported issues getting TypeError: a bytes-like object is required, not 'list' in this case :

replace

await client.write_gatt_char(PrinterCharacteristic, formatMessage(GetDevState, [0x00]) + formatMessage(ControlLattice, FinishLattice))

with

await client.write_gatt_char(PrinterCharacteristic, bytes(formatMessage(GetDevState, [0x00]) + formatMessage(ControlLattice, FinishLattice)))

and

await client.write_gatt_char(PrinterCharacteristic, data[:PacketLength])

with

await client.write_gatt_char(PrinterCharacteristic, bytes(data[:PacketLength]))

Once the script is running ok you should see something like

getting status
sending to printer
received notification
7: [ 51 78 A3 01 03 00 00 02 27 DF FF ]
printer status byte: 00000000

You can then test print by using curl to send a request to it (or anything like postman)

curl --location --request POST 'localhost:5000' \
--form 'text="hello world"' \ 
--form 'size="48"' \ 
--form 'feed="100"'

For the APRS part you'll also need aprs, requests and aprslib pip packages installed. I'm using the DejaVuSansMono.ttf font in the script so either have that or update to script to whatever font you have

Update the CALLSIGN, SSID and PRINT_SERVER variables i the script (probably want http://localhost:5000)

import socket
socket.setdefaulttimeout(10)
import aprslib
import aprs
import PIL.Image
import PIL.ImageDraw
import PIL.ImageFont
import PIL.ImageChops
import requests
from io import BytesIO
import traceback
import logging
import time
import datetime
WIDTH = 384
MESSAGE_TIME = 4*60*60 # 4 hours in seconds
messages_heard = {}
CALLSIGN="VK4XSS"
SSID="1"
PRINT_SERVER="http://file.foxden:5000"
def callback(packet):
try:
packet = aprslib.parse(bytes(packet))
print(packet)
if 'msgNo' in packet:
if isDup(packet['from'], packet["message_text"], packet['msgNo']): # remove duplicates
sendAck(packet['from'], packet['msgNo'])
return
if packet["format"] == "message" and packet['addresse'].upper() == f"{CALLSIGN}-{SSID}":
print("ok to print")
print_docket(
packet["from"],
packet["message_text"],
",".join(packet["path"])
)
print("printed")
if 'msgNo' in packet:
logging.info(f"Sending ack to {packet['from']}")
sendAck(packet['from'], packet['msgNo'])
logging.info(f"Sent ack to {packet['from']}")
if isDup(packet['from'], packet["message_text"], packet['msgNo']): # remove duplicates
return
except:
print(traceback.format_exc())
class CustomFormatter(logging.Formatter):
grey = "\x1b[2m"
green = "\x1b[32;20m"
yellow = "\x1b[33;20m"
red = "\x1b[31;20m"
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
FORMATS = {
logging.DEBUG: grey + format + reset,
logging.INFO: green + format + reset,
logging.WARNING: yellow + format + reset,
logging.ERROR: red + format + reset,
logging.CRITICAL: bold_red + format + reset
}
def format(self, record):
log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
return formatter.format(record)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(CustomFormatter())
logging.getLogger().addHandler(ch)
def isDup(callsign, message, messageno):
# expire duplicates
for k,v in messages_heard.copy().items():
if v < datetime.datetime.utcnow() - datetime.timedelta(seconds=MESSAGE_TIME):
logging.debug(f"Removing {k} from messages_heard : {v}")
messages_heard.pop(k, None)
if (callsign, message, messageno) in messages_heard:
logging.debug(f"Message {(callsign, message, messageno)} already heard at {messages_heard[(callsign, message, messageno)]}")
return True
else:
logging.debug(f"Adding {(callsign, message, messageno)} to messages_heard")
messages_heard[(callsign, message, messageno)] = datetime.datetime.utcnow()
return False
def sendAck(callsign, msgNo):
callsign = callsign.ljust(9, ' ')
time.sleep(1)
a.send((f"{CALLSIGN}-{SSID}>APRS,TCPIP*::"+callsign+":ack"+msgNo).encode("ascii"))
print("sent ack")
def get_wrapped_text(text: str, font: PIL.ImageFont.ImageFont,
line_length: int):
lines = ['']
for word in text.split():
line = f'{lines[-1]} {word}'.strip()
if font.getlength(line) <= line_length:
lines[-1] = line
else:
lines.append(word)
return '\n'.join(lines)
def print_docket(from_call, message, via):
print("printing")
image = PIL.Image.new("RGB", [384, 180], (255,255,255))
font_smaller = PIL.ImageFont.truetype("DejaVuSansMono.ttf", 18)
font_small = PIL.ImageFont.truetype("DejaVuSansMono.ttf", 24)
font_large = PIL.ImageFont.truetype("DejaVuSansMono.ttf", 32)
lines = []
for line in message.splitlines():
lines.append(get_wrapped_text(line, font_small, WIDTH))
lines = "\n".join(lines)
d = PIL.ImageDraw.Draw(image)
d.text((0,0), from_call, fill=(0,0,0), font=font_large)
d.text((0,30), via, fill=(0,0,0), font=font_smaller)
d.text((0,60), lines, fill=(0,0,0), font=font_small)
output_bytes = BytesIO()
print("saving")
image.save(output_bytes,format="png")
output_bytes.seek(0)
print("sending")
send_to_server(output_bytes)
print("sent to server")
#
def send_to_server(bytes_send):
url = PRINT_SERVER
payload={'feed': '100', 'font':'DejaVuSansMono.ttf'}
files=[
('image',('Untitled.png',bytes_send,'image/png'))
]
headers = {}
response = requests.request("POST", url, headers=headers, data=payload, files=files)
print(response.text)
a = aprs.TCP(CALLSIGN.encode(), str(aprslib.passcode(CALLSIGN)).encode(), aprs_filter=f"g/{CALLSIGN}-{SSID}".encode()) # filter
a.start()
a.interface.settimeout(None)
a.receive(callback=callback)
@Hoshnasi
Copy link

Fantastic!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment