Skip to content

Instantly share code, notes, and snippets.

@yokoyama-flogics
Last active February 27, 2023 05:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yokoyama-flogics/9be2b507da84c742ed50ee7b38b78c74 to your computer and use it in GitHub Desktop.
Save yokoyama-flogics/9be2b507da84c742ed50ee7b38b78c74 to your computer and use it in GitHub Desktop.
Decoding Dymo LabelWriter data stream (https://flogics.com/wp/ja/2023/02/dymo-cups-troubleshoot/)
# Copyright 2023, Atsushi Yokoyama, Firmlogics
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import sys
from PIL import Image
DEBUG = False
SYN = 0x16
ETB = 0x17
ESC = 0x1B
WAITING_CMD = 0
PROCESSING_CMD = 1
DEFAULT_BYTES_PER_LINE = 84
global dot_tab, bytes_per_line, img, raster
dot_tab = 0
bytes_per_line = DEFAULT_BYTES_PER_LINE
img = None
raster = []
def usage():
print("usage: python {} input-file-name".format(sys.argv[0]))
def send_line_tab(f):
bytes = f.read(2)
print("send_line_tab: {} {}".format(bytes[0], bytes[1]))
def set_dot_tab(f):
global dot_tab
bytes = f.read(1)
dot_tab = int(bytes[0])
print("set_dot_tab: {}".format(dot_tab))
def text_speed_mode():
print("text_speed_mode:")
def set_print_density_normal():
print("set_print_density: NORMAL")
def set_label_length(f):
bytes = f.read(2)
label_length = bytes[0] * 256 + bytes[1]
print(
"set_label_length: {} ({:.2f} in)".format(
label_length, label_length / 300
)
)
def skip_n_lines(f):
bytes = f.read(2)
print("skip_n_lines: {}".format(bytes[1]))
def set_bytes_per_line(f):
global bytes_per_line
bytes = f.read(1)
bytes_per_line = bytes[0]
print("set_bytes_per_line: {}".format(bytes_per_line))
def transfer_print_data(f):
global raster
row = [0] * (dot_tab * 8)
bytes = f.read(bytes_per_line)
sys.stdout.write("transfer_print_data:")
for i in range(len(bytes)):
byte = bytes[i]
sys.stdout.write(" ")
sys.stdout.write(hex(int(byte)))
for i in range(8):
row.append((byte >> (7 - i)) & 1)
sys.stdout.write("\n")
row.extend([0] * (DEFAULT_BYTES_PER_LINE * 8 - len(row)))
raster.extend(row)
def transfer_compressed_print_data(f):
global raster
row = [0] * (dot_tab * 8)
len_bits = 0
sys.stdout.write("transfer_compressed_print_data:")
while True:
bytes = f.read(1)
sys.stdout.write(" ")
byte = int(bytes[0])
sys.stdout.write(hex(byte))
col = byte >> 7
length = (byte & 0x7F) + 1
row.extend([col] * length)
len_bits += length
if len_bits >= bytes_per_line * 8:
break
sys.stdout.write("\n")
row.extend([0] * (DEFAULT_BYTES_PER_LINE * 8 - len(row)))
raster.extend(row)
def short_form_feed_cmd():
print("short_form_feed_cmd:")
def form_feed_cmd():
print("form_feed_cmd:")
def main():
global img
if len(sys.argv) < 2:
usage()
sys.exit(-1)
infile = open(sys.argv[1], "rb")
state = WAITING_CMD
while True:
if state == WAITING_CMD:
bytes = infile.read(1)
if len(bytes) == 0:
break
prefix = bytes[0]
# print("read:", hex(int(prefix)))
if not (prefix in [ESC, SYN, ETB]):
if DEBUG:
print("Expecting ESC, SYN or ETB. Failed.")
break
if prefix == SYN:
transfer_print_data(infile)
elif prefix == ETB:
transfer_compressed_print_data(infile)
else:
cmd = infile.read(1)[0]
# print("read:", hex(int(cmd)))
if cmd != ESC:
if DEBUG:
print("new state: PROCESSING_CMD")
state = PROCESSING_CMD
elif state == PROCESSING_CMD:
if cmd > 0x20 and cmd < 0x7F:
ascii_cmd = chr(cmd)
else:
ascii_cmd = "?"
if DEBUG:
print("cmd: {} ({})".format(hex(int(cmd)), ascii_cmd))
if ascii_cmd == "Q":
send_line_tab(infile)
elif ascii_cmd == "B":
set_dot_tab(infile)
elif ascii_cmd == "h":
text_speed_mode()
elif ascii_cmd == "e":
set_print_density_normal()
elif ascii_cmd == "L":
set_label_length(infile)
elif ascii_cmd == "f":
skip_n_lines(infile)
elif ascii_cmd == "D":
set_bytes_per_line(infile)
elif ascii_cmd == "G":
short_form_feed_cmd()
elif ascii_cmd == "E":
form_feed_cmd()
else:
print("cmd: {} ({})".format(hex(int(cmd)), ascii_cmd))
print("UNKNOWN COMMAND")
if DEBUG:
print("new state: WATING_CMD")
state = WAITING_CMD
width = DEFAULT_BYTES_PER_LINE * 8
img = Image.new("1", (width, len(raster) // width))
img.putdata(list(map(lambda _: 1 - _, raster)))
img.show()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment