From c939773800ca416ae32a473b3e974b973e3aa529 Mon Sep 17 00:00:00 2001 From: ondro Date: Mon, 22 Jul 2013 20:37:42 +0200 Subject: [PATCH 01/20] initial commit of theirs code --- .gitignore | 11 + Changelog | 6 + PDDemu-debug.py | 633 ++++++++++++++++++++++++++++++++ PDDemulate.py | 637 +++++++++++++++++++++++++++++++++ README | 60 ++++ addpattern.py | 226 ++++++++++++ brother.py | 380 ++++++++++++++++++++ dumppattern.py | 202 +++++++++++ experimental/FB100-Notes.txt | 53 +++ experimental/FDif.py | 328 +++++++++++++++++ experimental/driveread.py | 82 +++++ file-analysis/README | 1 + file-analysis/adumper.py | 100 ++++++ file-analysis/bdump.c | 451 +++++++++++++++++++++++ file-analysis/brother.py | 463 ++++++++++++++++++++++++ file-analysis/compare.py | 46 +++ file-analysis/diff.py | 121 +++++++ file-analysis/dumprows.py | 59 +++ file-analysis/filecombine.sh | 13 + file-analysis/makecsv.py | 68 ++++ file-analysis/motif-memory.ods | Bin 0 -> 12909 bytes file-analysis/unknowns.ods | Bin 0 -> 9249 bytes insertpattern.py | 205 +++++++++++ process_image.py | 40 +++ splitfile2track.py | 26 ++ textconversion/maketext.sh | 25 ++ 26 files changed, 4236 insertions(+) create mode 100644 .gitignore create mode 100644 Changelog create mode 100644 PDDemu-debug.py create mode 100644 PDDemulate.py create mode 100644 README create mode 100644 addpattern.py create mode 100644 brother.py create mode 100644 dumppattern.py create mode 100644 experimental/FB100-Notes.txt create mode 100644 experimental/FDif.py create mode 100644 experimental/driveread.py create mode 100644 file-analysis/README create mode 100644 file-analysis/adumper.py create mode 100644 file-analysis/bdump.c create mode 100644 file-analysis/brother.py create mode 100644 file-analysis/compare.py create mode 100644 file-analysis/diff.py create mode 100644 file-analysis/dumprows.py create mode 100644 file-analysis/filecombine.sh create mode 100644 file-analysis/makecsv.py create mode 100644 file-analysis/motif-memory.ods create mode 100644 file-analysis/unknowns.ods create mode 100644 insertpattern.py create mode 100644 process_image.py create mode 100644 splitfile2track.py create mode 100644 textconversion/maketext.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f12819f --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# specific to knitting machine source +img +img.start +docs +test-data + +# binary files +*.bmp +*.xcf +*.png +*.dat diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..9d79ead --- /dev/null +++ b/Changelog @@ -0,0 +1,6 @@ +====================================================================== +Feb 19, 2012 +Renamed PDDemulate-1.0.py to PDDemulate.py, and put the version number in the script. +Print version number on script start +PDDemulate version 1.1 fixes a problem that was see on some models of knitting machine. + diff --git a/PDDemu-debug.py b/PDDemu-debug.py new file mode 100644 index 0000000..4f0ac42 --- /dev/null +++ b/PDDemu-debug.py @@ -0,0 +1,633 @@ +#!/usr/bin/env python + +# Copyright 2009 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +# This software emulates the external floppy disk drive used +# by the Brother Electroknit KH-930E computerized knitting machine. +# It may work for other models, but has only been tested with the +# Brother KH-930E +# +# This emulates the disk drive and stores the saved data from +# the knitting machine on the linux file system. It does not +# read or write floppy disks. +# +# The disk drive used by the brother knitting machine is the same +# as a Tandy PDD1 drive. This software does not support the entire +# command API of the PDD1, only what is required for the knitting +# machine. +# + +# +# Notes about data storage: +# +# The external floppy disk is formatted with 80 sectors of 1024 +# bytes each. These sectors are numbered (internally) from 0-79. +# When starting this emulator, a base directory is specified. +# In this directory the emulator creates 80 files, one for each +# sector. These are kept sync'd with the emulator's internal +# storage of the same sectors. For each sector, there are two +# files, nn.dat, and nn.id, where 00 <= nn <= 79. +# +# The knitting machine uses two sectors for each saved set of +# information, which are referred to in the knitting machine +# manual as 'tracks' (which they were on the floppy disk). Each +# pair of even/odd numbered sectors is a track. Tracks are +# numbered 1-40. The knitting machine always writes sectors +# in even/odd pairs, and when the odd sector is written, both +# sectors are concatenated to a file named fileqq.dat, where +# qq is the sector number. +# + +# The Knitting machine does not parse the returned hex ascii values +# unless they are ALL UPPER CASE. Lower case characters a-f appear +# to parse az zeros. + +# You will need the (very nice) pySerial module, found here: +# http://pyserial.wiki.sourceforge.net/pySerial + +import sys +import os +import os.path +import string +from array import * +import serial + +version = '1.0' + +# +# Note that this code makes a fundamental assumption which +# is only true for the disk format used by the brother knitting +# machine, which is that there is only one logical sector (LS) per +# physical sector (PS). The PS size is fixed at 1280 bytes, and +# the brother uses a LS size of 1024 bytes, so only one can fit. +# + +class DiskSector(): + def __init__(self, fn): + self.sectorSz = 1024 + self.idSz = 12 + self.data = '' + self.id = '' + #self.id = array('c') + + dfn = fn + ".dat" + idfn = fn + ".id" + + try: + try: + self.df = open(dfn, 'r+') + except IOError: + self.df = open(dfn, 'w') + + try: + self.idf = open(idfn, 'r+') + except IOError: + self.idf = open(idfn, 'w') + + dfs = os.path.getsize(dfn) + idfs = os.path.getsize(idfn) + + except: + print 'Unable to open files using base name <%s>' % fn + raise + + try: + if dfs == 0: + # New or empty file + self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) + self.writeDFile() + elif dfs == self.sectorSz: + # Existing file + self.data = self.df.read(self.sectorSz) + else: + print 'Found a data file <%s> with the wrong size' % dfn + raise IOError + except: + print 'Unable to handle data file <%s>' % fn + raise + + try: + if idfs == 0: + # New or empty file + self.id = ''.join([chr(0) for num in xrange(self.idSz)]) + self.writeIdFile() + elif idfs == self.idSz: + # Existing file + self.id = self.idf.read(self.idSz) + else: + print 'Found an ID file <%s> with the wrong size, is %d should be %d' % (idfn, idfs, self.idSz) + raise IOError + except: + print 'Unable to handle id file <%s>' % fn + raise + + return + + def __del__(self): + return + + def format(self): + self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) + self.writeDFile() + self.id = ''.join([chr(0) for num in xrange(self.idSz)]) + self.writeIdFile() + + def writeDFile(self): + self.df.seek(0) + self.df.write(self.data) + self.df.flush() + return + + def writeIdFile(self): + self.idf.seek(0) + self.idf.write(self.id) + self.idf.flush() + return + + def read(self, length): + if length != self.sectorSz: + print 'Error, read of %d bytes when expecting %d' % (length, self.sectorSz) + raise IOError + return self.data + + def write(self, indata): + if len(indata) != self.sectorSz: + print 'Error, write of %d bytes when expecting %d' % (len(indata), self.sectorSz) + raise IOError + self.data = indata + self.writeDFile() + return + + def getSectorId(self): + return self.id + + def setSectorId(self, newid): + if len(newid) != self.idSz: + print 'Error, bad id length of %d bytes when expecting %d' % (len(newid), self.id) + raise IOError + self.id = newid + self.writeIdFile() + print 'Wrote New ID: ', + self.dumpId() + return + + def dumpId(self): + for i in self.id: + print '%02X ' % ord(i), + print + +class Disk(): + def __init__(self, basename): + self.numSectors = 80 + self.Sectors = [] + self.filespath = "" + # Set up disk Files and internal buffers + + # if absolute path, just accept it + if os.path.isabs(basename): + dirpath = basename + else: + dirpath = os.path.abspath(basename) + + if os.path.exists(dirpath): + if not os.access(dirpath, os.R_OK | os.W_OK): + print 'Directory <%s> exists but cannot be accessed, check permissions' % dirpath + raise IOError + elif not os.path.isdir(dirpath): + print 'Specified path <%s> exists but is not a directory' % dirpath + raise IOError + else: + try: + os.mkdir(dirpath) + except: + print 'Unable to create directory <%s>' % dirpath + raise IOError + + self.filespath = dirpath + # we have a directory now - set up disk sectors + for i in range(self.numSectors): + fname = os.path.join(dirpath, '%02d' % i) + ds = DiskSector(fname) + self.Sectors.append(ds) + return + + def __del__(self): + return + + def format(self): + for i in range(self.numSectors): + self.Sectors[i].format() + return + + def findSectorID(self, psn, id): + for i in range(psn, self.numSectors): + sid = self.Sectors[i].getSectorId() + if id == sid: + return '00' + '%02X' % i + '0000' + return '40000000' + + def getSectorID(self, psn): + return self.Sectors[psn].getSectorId() + + def setSectorID(self, psn, id): + self.Sectors[psn].setSectorId(id) + return + + def writeSector(self, psn, lsn, indata): + self.Sectors[psn].write(indata) + if psn % 2: + filenum = ((psn-1)/2)+1 + filename = 'file-%02d.dat' % filenum + # we wrote an odd sector, so create the + # associated file + fn1 = os.path.join(self.filespath, '%02d.dat' % (psn-1)) + fn2 = os.path.join(self.filespath, '%02d.dat' % psn) + outfn = os.path.join(self.filespath, filename) + cmd = 'cat %s %s > %s' % (fn1, fn2, outfn) + os.system(cmd) + return + + def readSector(self, psn, lsn): + return self.Sectors[psn].read(1024) + +class PDDemulator(): + + def __init__(self, basename): + self.verbose = True + self.noserial = False + self.ser = None + self.disk = Disk(basename) + self.FDCmode = False + # bytes per logical sector + self.bpls = 1024 + self.formatLength = {'0':64, '1':80, '2': 128, '3': 256, '4': 512, '5': 1024, '6': 1280} + return + + def __del__(self): + return + + def open(self, cport='/dev/ttyUSB0'): + if self.noserial is False: + self.ser = serial.Serial(port=cport, baudrate=9600, parity='N', stopbits=1, timeout=1, xonxoff=0, rtscts=0, dsrdtr=0) + if self.ser == None: + print 'Unable to open serial device %s' % cport + raise IOError + return + + def close(self, foo): + if self.noserial is not False: + if ser: + ser.close() + return + + def dumpchars(self): + num = 1 + while 1: + inc = self.ser.read() + if len(inc) != 0: + print 'flushed 0x%02X (%d)' % (ord(inc), num) + num = num + 1 + else: + break + return + + def readsomechars(self, num): + sch = self.ser.read(num) + return sch + + def readchar(self): + inc = '' + while len(inc) == 0: + inc = self.ser.read() + return inc + + def writebytes(self, bytes): + self.ser.write(bytes) + return + + def readFDDRequest(self): + inbuf = [] + # read through a carriage return + # parameters are seperated by commas + while 1: + inc = self.readchar() + if inc == '\r': + break + elif inc == ' ': + continue + else: + inbuf.append(inc) + + all = string.join(inbuf, '') + rv = all.split(',') + return rv + + def getPsnLsn(self, info): + psn = 0 + lsn = 1 + if len(info) >= 1 and info[0] != '': + val = int(info[0]) + if psn <= 79: + psn = val + if len(info) > 1 and info[1] != '': + val = int(info[0]) + return psn, lsn + + def readOpmodeRequest(self, req): + buff = array('b') + sum = req + reqlen = ord(self.readchar()) + buff.append(reqlen) + sum = sum + reqlen + + for x in range(reqlen, 0, -1): + rb = ord(self.readchar()) + buff.append(rb) + sum = sum + rb + + # calculate ckecksum + sum = sum % 0x100 + sum = sum ^ 0xFF + + cksum = ord(self.readchar()) + + if cksum == sum: + return buff + else: + if self.verbose: + print 'Checksum mismatch!!' + return None + + def handleRequests(self): + synced = False + while True: + inc = self.readchar() + if self.FDCmode: + self.handleFDCmodeRequest(inc) + else: + # in OpMode, look for ZZ + #inc = self.readchar() + if inc != 'Z': + continue + inc = self.readchar() + if inc == 'Z': + self.handleOpModeRequest() + # never returns + return + + def handleOpModeRequest(self): + req = ord(self.ser.read()) + #print 'Request: 0X%02X' % req + if req == 0x08: + # Change to FDD emulation mode (no data returned) + inbuf = self.readOpmodeRequest(req) + if inbuf != None: + # Change Modes, leave any incoming serial data in buffer + self.FDCmode = True + else: + print 'Invalid OpMode request code 0X%02X received' % req + return + + def handleFDCmodeRequest(self, cmd): + # Commands may be followed by an optional space + # PSN (physical sector) range 0-79 + # LSN (logical sector) range 0-(number of logical sectors in a physical sector) + # LSN defaults to 1 if not supplied + # + # Result code information (verbatim from the Tandy reference): + # + # After the drive receives a command in FDC-emulation mode, it transmits + # 8 byte characters which represent 4 bytes of status code in hexadecimal. + # + # * The first and second bytes contain the error status. A value of '00' + # indicates that no error occurred + # + # * The third and fourth bytes usually contain the number of the physical + # sector where data is kept in the buffer + # + # For the D, F, and S commands, the contents of these bytes are different. + # See the command descriptions in these cases. + # + # * The fifth-eighth bytes usual show the logical sector length of the data + # kept in the RAM buffer, except the third and fourth digits are 'FF' + # + # In the case of an S, C, or M command -- or an F command that ends in + # an error -- the bytes contain '0000' + # + + if cmd == '\r': + return + + if cmd == 'Z': + # Hmmm, looks like we got the start of an Opmode Request + inc = self.readchar() + if inc == 'Z': + # definitely! + print 'Detected Opmode Request in FDC Mode, switching to OpMode' + self.FDCmode = False + self.handleOpModeRequest() + + elif cmd == 'M': + # apparently not used by brother knitting machine + print 'FDC Change Modes' + raise + # following parameter - 0=FDC, 1=Operating + + elif cmd == 'D': + # apparently not used by brother knitting machine + print 'FDC Check Device' + raise + # Sends result in third and fourth bytes of result code + # See doc - return zero for disk installed and not swapped + + elif cmd == 'F'or cmd == 'G': + #rint 'FDC Format', + info = self.readFDDRequest() + + if len(info) != 1: + print 'wrong number of params (%d) received, assuming 1024 bytes per sector' % len(info) + bps = 1024 + else: + try: + bps = self.formatLength[info[0]] + except KeyError: + print 'Invalid code %c for format, assuming 1024 bytes per sector' % info[0] + bps = 1024 + # we assume 1024 because that's what the brother machine uses + if self.bpls != bps: + print 'Bad news, differing sector sizes' + self.bpls = bps + + self.disk.format() + + # But this is probably more correct + self.writebytes('00000000') + + # After a format, we always start out with OPMode again + self.FDCmode = False + + elif cmd == 'A': + # Followed by physical sector number (0-79), defaults to 0 + # returns ID data, not sector data + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Read ID Section %d' % psn + + try: + id = self.disk.getSectorID(psn) + except: + print 'Error getting Sector ID %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + # see whether to send data + go = self.readchar() + if go == '\r': + self.write(id) + + elif cmd == 'R': + # Followed by Physical Sector Number PSN and Logical Sector Number LSN + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Read one Logical Sector %d' % psn + + try: + sd = self.disk.readSector(psn, lsn) + except: + print 'Failed to read Sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + # see whether to send data + go = self.readchar() + if go == '\r': + self.writebytes(sd) + + elif cmd == 'S': + # We receive (optionally) PSN, (optionally) LSN + # This is not documented well at all in the manual + # What is expected is that all sectors will be searched + # and the sector number of the first matching sector + # will be returned. The brother machine always sends + # PSN = 0, so it is unknown whether searching should + # start at Sector 0 or at the PSN sector + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Search ID Section %d' % psn + + # Now we must send status (success) + self.writebytes('00' + '%02X' % psn + '0000') + + #self.writebytes('00000000') + + # we receive 12 bytes here + # compare with the specified sector (formatted is apparently zeros) + id = self.readsomechars(12) + print 'checking ID for sector %d' % psn + + try: + status = self.disk.findSectorID(psn, id) + except: + print "FAIL" + status = '30000000' + raise + + print 'returning %s' % status + # guessing - doc is unclear, but says that S always ends in 0000 + # MATCH 00000000 + # MATCH 02000000 + # infinite retries 10000000 + # infinite retries 20000000 + # blinking error 30000000 + # blinking error 40000000 + # infinite retries 50000000 + # infinite retries 60000000 + # infinite retries 70000000 + # infinite retries 80000000 + + self.writebytes(status) + + # Stay in FDC mode + + elif cmd == 'B' or cmd == 'C': + # Followed by PSN 0-79, defaults to 0 + # When received, send result status, if not error, wait + # for data to be written, then after write, send status again + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Write ID section %d' % psn + + self.writebytes('00' + '%02X' % psn + '0000') + + id = self.readsomechars(12) + + try: + self.disk.setSectorID(psn, id) + except: + print 'Failed to write ID for sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + elif cmd == 'W' or cmd == 'X': + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Write logical sector %d' % psn + + # Now we must send status (success) + self.writebytes('00' + '%02X' % psn + '0000') + + indata = self.readsomechars(1024) + try: + self.disk.writeSector(psn, lsn, indata) + except: + print 'Failed to write data for sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + else: + print 'Unknown FDC command <0x02%X> received' % ord(cmd) + + # return to Operational Mode + return + +# meat and potatos here + +if len(sys.argv) < 3: + print '%s version %s' % (sys.argv[0], version) + print 'Usage: %s basedir serialdevice' % sys.argv[0] + sys.exit() + +print 'Preparing . . . Please Wait' +emu = PDDemulator(sys.argv[1]) + +emu.open(cport=sys.argv[2]) + +print 'Ready!' +while 1: + emu.handleRequests() + +emu.close() diff --git a/PDDemulate.py b/PDDemulate.py new file mode 100644 index 0000000..3f1dce0 --- /dev/null +++ b/PDDemulate.py @@ -0,0 +1,637 @@ +#!/usr/bin/env python + +# Copyright 2009 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +# This software emulates the external floppy disk drive used +# by the Brother Electroknit KH-930E computerized knitting machine. +# It may work for other models, but has only been tested with the +# Brother KH-930E +# +# This emulates the disk drive and stores the saved data from +# the knitting machine on the linux file system. It does not +# read or write floppy disks. +# +# The disk drive used by the brother knitting machine is the same +# as a Tandy PDD1 drive. This software does not support the entire +# command API of the PDD1, only what is required for the knitting +# machine. +# + +# +# Notes about data storage: +# +# The external floppy disk is formatted with 80 sectors of 1024 +# bytes each. These sectors are numbered (internally) from 0-79. +# When starting this emulator, a base directory is specified. +# In this directory the emulator creates 80 files, one for each +# sector. These are kept sync'd with the emulator's internal +# storage of the same sectors. For each sector, there are two +# files, nn.dat, and nn.id, where 00 <= nn <= 79. +# +# The knitting machine uses two sectors for each saved set of +# information, which are referred to in the knitting machine +# manual as 'tracks' (which they were on the floppy disk). Each +# pair of even/odd numbered sectors is a track. Tracks are +# numbered 1-40. The knitting machine always writes sectors +# in even/odd pairs, and when the odd sector is written, both +# sectors are concatenated to a file named fileqq.dat, where +# qq is the sector number. +# + +# The Knitting machine does not parse the returned hex ascii values +# unless they are ALL UPPER CASE. Lower case characters a-f appear +# to parse az zeros. + +# You will need the (very nice) pySerial module, found here: +# http://pyserial.wiki.sourceforge.net/pySerial + +import sys +import os +import os.path +import string +from array import * +import serial + +version = '1.0' + +# +# Note that this code makes a fundamental assumption which +# is only true for the disk format used by the brother knitting +# machine, which is that there is only one logical sector (LS) per +# physical sector (PS). The PS size is fixed at 1280 bytes, and +# the brother uses a LS size of 1024 bytes, so only one can fit. +# + +class DiskSector(): + def __init__(self, fn): + self.sectorSz = 1024 + self.idSz = 12 + self.data = '' + self.id = '' + #self.id = array('c') + + dfn = fn + ".dat" + idfn = fn + ".id" + + try: + try: + self.df = open(dfn, 'r+') + except IOError: + self.df = open(dfn, 'w') + + try: + self.idf = open(idfn, 'r+') + except IOError: + self.idf = open(idfn, 'w') + + dfs = os.path.getsize(dfn) + idfs = os.path.getsize(idfn) + + except: + print 'Unable to open files using base name <%s>' % fn + raise + + try: + if dfs == 0: + # New or empty file + self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) + self.writeDFile() + elif dfs == self.sectorSz: + # Existing file + self.data = self.df.read(self.sectorSz) + else: + print 'Found a data file <%s> with the wrong size' % dfn + raise IOError + except: + print 'Unable to handle data file <%s>' % fn + raise + + try: + if idfs == 0: + # New or empty file + self.id = ''.join([chr(0) for num in xrange(self.idSz)]) + self.writeIdFile() + elif idfs == self.idSz: + # Existing file + self.id = self.idf.read(self.idSz) + else: + print 'Found an ID file <%s> with the wrong size, is %d should be %d' % (idfn, idfs, self.idSz) + raise IOError + except: + print 'Unable to handle id file <%s>' % fn + raise + + return + + def __del__(self): + return + + def format(self): + self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) + self.writeDFile() + self.id = ''.join([chr(0) for num in xrange(self.idSz)]) + self.writeIdFile() + + def writeDFile(self): + self.df.seek(0) + self.df.write(self.data) + self.df.flush() + return + + def writeIdFile(self): + self.idf.seek(0) + self.idf.write(self.id) + self.idf.flush() + return + + def read(self, length): + if length != self.sectorSz: + print 'Error, read of %d bytes when expecting %d' % (length, self.sectorSz) + raise IOError + return self.data + + def write(self, indata): + if len(indata) != self.sectorSz: + print 'Error, write of %d bytes when expecting %d' % (len(indata), self.sectorSz) + raise IOError + self.data = indata + self.writeDFile() + return + + def getSectorId(self): + return self.id + + def setSectorId(self, newid): + if len(newid) != self.idSz: + print 'Error, bad id length of %d bytes when expecting %d' % (len(newid), self.id) + raise IOError + self.id = newid + self.writeIdFile() + print 'Wrote New ID: ', + self.dumpId() + return + + def dumpId(self): + for i in self.id: + print '%02X ' % ord(i), + print + +class Disk(): + def __init__(self, basename): + self.numSectors = 80 + self.Sectors = [] + self.filespath = "" + # Set up disk Files and internal buffers + + # if absolute path, just accept it + if os.path.isabs(basename): + dirpath = basename + else: + dirpath = os.path.abspath(basename) + + if os.path.exists(dirpath): + if not os.access(dirpath, os.R_OK | os.W_OK): + print 'Directory <%s> exists but cannot be accessed, check permissions' % dirpath + raise IOError + elif not os.path.isdir(dirpath): + print 'Specified path <%s> exists but is not a directory' % dirpath + raise IOError + else: + try: + os.mkdir(dirpath) + except: + print 'Unable to create directory <%s>' % dirpath + raise IOError + + self.filespath = dirpath + # we have a directory now - set up disk sectors + for i in range(self.numSectors): + fname = os.path.join(dirpath, '%02d' % i) + ds = DiskSector(fname) + self.Sectors.append(ds) + return + + def __del__(self): + return + + def format(self): + for i in range(self.numSectors): + self.Sectors[i].format() + return + + def findSectorID(self, psn, id): + for i in range(psn, self.numSectors): + sid = self.Sectors[i].getSectorId() + if id == sid: + return '00' + '%02X' % i + '0000' + return '40000000' + + def getSectorID(self, psn): + return self.Sectors[psn].getSectorId() + + def setSectorID(self, psn, id): + self.Sectors[psn].setSectorId(id) + return + + def writeSector(self, psn, lsn, indata): + self.Sectors[psn].write(indata) + if psn % 2: + filenum = ((psn-1)/2)+1 + filename = 'file-%02d.dat' % filenum + # we wrote an odd sector, so create the + # associated file + fn1 = os.path.join(self.filespath, '%02d.dat' % (psn-1)) + fn2 = os.path.join(self.filespath, '%02d.dat' % psn) + outfn = os.path.join(self.filespath, filename) + cmd = 'cat %s %s > %s' % (fn1, fn2, outfn) + os.system(cmd) + return + + def readSector(self, psn, lsn): + return self.Sectors[psn].read(1024) + +class PDDemulator(): + + def __init__(self, basename): + self.verbose = True + self.noserial = False + self.ser = None + self.disk = Disk(basename) + self.FDCmode = False + # bytes per logical sector + self.bpls = 1024 + self.formatLength = {'0':64, '1':80, '2': 128, '3': 256, '4': 512, '5': 1024, '6': 1280} + return + + def __del__(self): + return + + def open(self, cport='/dev/ttyUSB0'): + if self.noserial is False: + self.ser = serial.Serial(port=cport, baudrate=9600, parity='N', stopbits=1, timeout=1, xonxoff=0, rtscts=0, dsrdtr=0) +# self.ser.setRTS(True) + if self.ser == None: + print 'Unable to open serial device %s' % cport + raise IOError + return + + def close(self): + if self.noserial is not False: + if ser: + ser.close() + return + + def dumpchars(self): + num = 1 + while 1: + inc = self.ser.read() + if len(inc) != 0: + print 'flushed 0x%02X (%d)' % (ord(inc), num) + num = num + 1 + else: + break + return + + def readsomechars(self, num): + sch = self.ser.read(num) + return sch + + def readchar(self): + inc = '' + while len(inc) == 0: + inc = self.ser.read() + return inc + + def writebytes(self, bytes): + self.ser.write(bytes) + return + + def readFDDRequest(self): + inbuf = [] + # read through a carriage return + # parameters are seperated by commas + while 1: + inc = self.readchar() + if inc == '\r': + break + elif inc == ' ': + continue + else: + inbuf.append(inc) + + all = string.join(inbuf, '') + rv = all.split(',') + return rv + + def getPsnLsn(self, info): + psn = 0 + lsn = 1 + if len(info) >= 1 and info[0] != '': + val = int(info[0]) + if psn <= 79: + psn = val + if len(info) > 1 and info[1] != '': + val = int(info[0]) + return psn, lsn + + def readOpmodeRequest(self, req): + buff = array('b') + sum = req + reqlen = ord(self.readchar()) + buff.append(reqlen) + sum = sum + reqlen + + for x in range(reqlen, 0, -1): + rb = ord(self.readchar()) + buff.append(rb) + sum = sum + rb + + # calculate ckecksum + sum = sum % 0x100 + sum = sum ^ 0xFF + + cksum = ord(self.readchar()) + + if cksum == sum: + return buff + else: + if self.verbose: + print 'Checksum mismatch!!' + return None + + def handleRequests(self): + synced = False + while True: + inc = self.readchar() + if self.FDCmode: + self.handleFDCmodeRequest(inc) + else: + # in OpMode, look for ZZ + #inc = self.readchar() + if inc != 'Z': + continue + inc = self.readchar() + if inc == 'Z': + self.handleOpModeRequest() + # never returns + return + + def handleOpModeRequest(self): + req = ord(self.ser.read()) + print 'Request: 0X%02X' % req + if req == 0x08: + # Change to FDD emulation mode (no data returned) + inbuf = self.readOpmodeRequest(req) + if inbuf != None: + # Change Modes, leave any incoming serial data in buffer + self.FDCmode = True + else: + print 'Invalid OpMode request code 0X%02X received' % req + return + + def handleFDCmodeRequest(self, cmd): + # Commands may be followed by an optional space + # PSN (physical sector) range 0-79 + # LSN (logical sector) range 0-(number of logical sectors in a physical sector) + # LSN defaults to 1 if not supplied + # + # Result code information (verbatim from the Tandy reference): + # + # After the drive receives a command in FDC-emulation mode, it transmits + # 8 byte characters which represent 4 bytes of status code in hexadecimal. + # + # * The first and second bytes contain the error status. A value of '00' + # indicates that no error occurred + # + # * The third and fourth bytes usually contain the number of the physical + # sector where data is kept in the buffer + # + # For the D, F, and S commands, the contents of these bytes are different. + # See the command descriptions in these cases. + # + # * The fifth-eighth bytes usual show the logical sector length of the data + # kept in the RAM buffer, except the third and fourth digits are 'FF' + # + # In the case of an S, C, or M command -- or an F command that ends in + # an error -- the bytes contain '0000' + # + + if cmd == '\r': + return + + if cmd == 'Z': + # Hmmm, looks like we got the start of an Opmode Request + inc = self.readchar() + if inc == 'Z': + # definitely! + print 'Detected Opmode Request in FDC Mode, switching to OpMode' + self.FDCmode = False + self.handleOpModeRequest() + + elif cmd == 'M': + # apparently not used by brother knitting machine + print 'FDC Change Modes' + raise + # following parameter - 0=FDC, 1=Operating + + elif cmd == 'D': + # apparently not used by brother knitting machine + print 'FDC Check Device' + raise + # Sends result in third and fourth bytes of result code + # See doc - return zero for disk installed and not swapped + + elif cmd == 'F'or cmd == 'G': + #rint 'FDC Format', + info = self.readFDDRequest() + + if len(info) != 1: + print 'wrong number of params (%d) received, assuming 1024 bytes per sector' % len(info) + bps = 1024 + else: + try: + bps = self.formatLength[info[0]] + except KeyError: + print 'Invalid code %c for format, assuming 1024 bytes per sector' % info[0] + bps = 1024 + # we assume 1024 because that's what the brother machine uses + if self.bpls != bps: + print 'Bad news, differing sector sizes' + self.bpls = bps + + self.disk.format() + + # But this is probably more correct + self.writebytes('00000000') + + # After a format, we always start out with OPMode again + self.FDCmode = False + + elif cmd == 'A': + # Followed by physical sector number (0-79), defaults to 0 + # returns ID data, not sector data + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Read ID Section %d' % psn + + try: + id = self.disk.getSectorID(psn) + except: + print 'Error getting Sector ID %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + # see whether to send data + go = self.readchar() + if go == '\r': + self.writebytes(id) + + elif cmd == 'R': + # Followed by Physical Sector Number PSN and Logical Sector Number LSN + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Read one Logical Sector %d' % psn + + try: + sd = self.disk.readSector(psn, lsn) + except: + print 'Failed to read Sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + # see whether to send data + go = self.readchar() + if go == '\r': + self.writebytes(sd) + + elif cmd == 'S': + # We receive (optionally) PSN, (optionally) LSN + # This is not documented well at all in the manual + # What is expected is that all sectors will be searched + # and the sector number of the first matching sector + # will be returned. The brother machine always sends + # PSN = 0, so it is unknown whether searching should + # start at Sector 0 or at the PSN sector + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Search ID Section %d' % psn + + # Now we must send status (success) + self.writebytes('00' + '%02X' % psn + '0000') + + #self.writebytes('00000000') + + # we receive 12 bytes here + # compare with the specified sector (formatted is apparently zeros) + id = self.readsomechars(12) + print 'checking ID for sector %d' % psn + + try: + status = self.disk.findSectorID(psn, id) + except: + print "FAIL" + status = '30000000' + raise + + print 'returning %s' % status + # guessing - doc is unclear, but says that S always ends in 0000 + # MATCH 00000000 + # MATCH 02000000 + # infinite retries 10000000 + # infinite retries 20000000 + # blinking error 30000000 + # blinking error 40000000 + # infinite retries 50000000 + # infinite retries 60000000 + # infinite retries 70000000 + # infinite retries 80000000 + + self.writebytes(status) + + # Stay in FDC mode + + elif cmd == 'B' or cmd == 'C': + # Followed by PSN 0-79, defaults to 0 + # When received, send result status, if not error, wait + # for data to be written, then after write, send status again + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Write ID section %d' % psn + + self.writebytes('00' + '%02X' % psn + '0000') + + id = self.readsomechars(12) + + try: + self.disk.setSectorID(psn, id) + except: + print 'Failed to write ID for sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + elif cmd == 'W' or cmd == 'X': + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Write logical sector %d' % psn + + # Now we must send status (success) + self.writebytes('00' + '%02X' % psn + '0000') + + indata = self.readsomechars(1024) + try: + self.disk.writeSector(psn, lsn, indata) + except: + print 'Failed to write data for sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + else: + print 'Unknown FDC command <0x02%X> received' % ord(cmd) + + # return to Operational Mode + return + +# meat and potatos here + +if len(sys.argv) < 3: + print '%s version %s' % (sys.argv[0], version) + print 'Usage: %s basedir serialdevice' % sys.argv[0] + sys.exit() + +print 'Preparing . . . Please Wait' +emu = PDDemulator(sys.argv[1]) + +emu.open(cport=sys.argv[2]) + +print 'PDDtmulate Version 1.1 Ready!' +try: + while 1: + emu.handleRequests() +except (KeyboardInterrupt): + pass + +emu.close() diff --git a/README b/README new file mode 100644 index 0000000..d523fce --- /dev/null +++ b/README @@ -0,0 +1,60 @@ +Please see the Changelog file for the latest changes + +These files are related to the Brother KH-930E knitting machine, and other similar models. + +=== NOTE === + +The emulator script was named PDDemulate-1.0.py, and the instructions in a lor of forums for using it have that name. +The script has been renamed, and is now simply PDDemulate.py. + +============ + +The files in the top directory are the ones used for the knitting project that Becky Stern and Limor Fried did: + +http://blog.makezine.com/archive/2010/11/how-to_hacking_the_brother_kh-930e.html +http://blog.craftzine.com/archive/2010/11/hack_your_knitting_machine.html + +The subdirectories contain the following: + +* docs: + + Documentation for the project, including the data file format information and + scans of old manuals which are hard to find. + +* experimental: + + Some never-tested code to talk to a Tandy PDD-1 or Brother disk drive. + +* file-analysis: + + Various scripts used to reverse-engineer the brother data format, as well as some spreadsheets used. + These may or may nor work, but may be useful for some. + +* test-data: + + A saved set of data from the PDDemulator, with dicumentation abotu what's saved in each memory location. + A good way to play with the file analysis tools, and may give some insight into the reverse engineering + process. + +* textconversion + + The beginnings of work to convert text to a knittable banner. + +-------------------------- + +The Brother knitting machines can save data to an external floppy disk drive, which connects to the machine using a serial cable. + +These external floppy drives are difficult to find and expensive, and the physical format of the floppy disks is different than 3.25" PC drives. + +The program PDDemulate acts like a floppy drive, and runs on linux machines, allowing you to save and restore data from the knitting machine. + +Most of the formatting of the saved data files has been figured out, and the tools used to do that are also in this repository. + +There is also an example of how to generate a text banner in a .png image file, +which may be useful to some. + +The work that Steve Conklin did was based on earlier work by John R. Hogerhuis. + +This extended by Becky and Limor and others, including Travis Goodspeed: + +http://travisgoodspeed.blogspot.com/2010/12/hacking-knitting-machines-keypad.html diff --git a/addpattern.py b/addpattern.py new file mode 100644 index 0000000..f4a3f31 --- /dev/null +++ b/addpattern.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python + +# Copyright 2009 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys +import brother +import Image +import array + +TheImage = None + +################## + +def roundeven(val): + return (val+(val%2)) + +def roundeight(val): + if val % 8: + return val + (8-(val%8)) + else: + return val + +def roundfour(val): + if val % 4: + return val + (4-(val%4)) + else: + return val + +def nibblesPerRow(stitches): + # there are four stitches per nibble + # each row is nibble aligned + return(roundfour(stitches)/4) + +def bytesPerPattern(stitches, rows): + nibbs = rows * nibblesPerRow(stitches) + bytes = roundeven(nibbs)/2 + return bytes + +def bytesForMemo(rows): + bytes = roundeven(rows)/2 + return bytes + +############## + + +version = '1.0' + + +print "I dont work, sorry!" +sys.exit() + +imgfile = sys.argv[2] + +bf = brother.brotherFile(sys.argv[1]) + + +pats = bf.getPatterns() + +# find first unused pattern bank +patternbank = 100 +for i in range(99): + bytenum = i*7 + if (bf.getIndexedByte(bytenum) != 0x1): + print "first unused pattern bank #", i + patternbank = i + break + +if (patternbank == 100): + print "sorry, no free banks!" + exit + +# ok got a bank, now lets figure out how big this thing we want to insert is +TheImage = Image.open(imgfile) +TheImage.load() + +im_size = TheImage.size +width = im_size[0] +print "width:",width +height = im_size[1] +print "height:", height + +# debugging stuff here +x = 0 +y = 0 + +x = width - 1 +while x > 0: + value = TheImage.getpixel((x,y)) + if value: + sys.stdout.write('* ') + else: + sys.stdout.write(' ') + #sys.stdout.write(str(value)) + x = x-1 + if x == 0: #did we hit the end of the line? + y = y+1 + x = width - 1 + print " " + if y == height: + break +# debugging stuff done + +# create the program entry +progentry = [] +progentry.append(0x1) # is used +progentry.append(0x20) # no idea what this is but dont make it 0x0 +progentry.append( (int(width / 100) << 4) | (int((width%100) / 10) & 0xF) ) +progentry.append( (int(width % 10) << 4) | (int(height / 100) & 0xF) ) +progentry.append( (int((height % 100)/10) << 4) | (int(height % 10) & 0xF) ) + +# now we have to pick out a 'program name' +patternnum = 901 # start with 901? i dunno +for pat in pats: + if (pat["number"] >= patternnum): + patternnum = pat["number"] + 1 + +#print patternnum +progentry.append(int(patternnum / 100) & 0xF) +progentry.append( (int((patternnum % 100)/10) << 4) | (int(patternnum % 10) & 0xF) ) + +print "Program entry: ",map(hex, progentry) + +# now to make the actual, yknow memo+pattern data + +# the memo seems to be always blank. i have no idea really +memoentry = [] +for i in range(bytesForMemo(height)): + memoentry.append(0x0) + +# now for actual real live pattern data! +pattmemnibs = [] +for r in range(height): + row = [] # we'll chunk in bits and then put em into nibbles + for s in range(width): + value = TheImage.getpixel((width-s-1,height-r-1)) + if (value != 0): + row.append(1) + else: + row.append(0) + #print row + # turn it into nibz + for s in range(roundfour(width) / 4): + n = 0 + for nibs in range(4): + #print "row size = ", len(row), "index = ",s*4+nibs + + if (len(row) == (s*4+nibs)): + break # padding! + + + if (row[s*4 + nibs]): + n |= 1 << nibs + pattmemnibs.append(n) + print hex(n), + print + + +if (len(pattmemnibs) % 2): + # odd nibbles, buffer to a byte + pattmemnibs.append(0x0) + +print len(pattmemnibs), "nibbles of data" + +# turn into bytes +pattmem = [] +for i in range (len(pattmemnibs) / 2): + pattmem.append( pattmemnibs[i*2] | (pattmemnibs[i*2 + 1] << 4)) + +print map(hex, pattmem) +# whew. + + +# now to insert this data into the file + +# where to place the pattern program entry +patternbankptr = patternbank*7 + +# write the new pattern program +for i in range(7): + bf.setIndexedByte(patternbankptr+i, progentry[i]) + + +# now we have to figure out the -end- of the last pattern is +endaddr = 0x6df + +for p in pats: + endaddr = min(p['pattend'], endaddr) +print "top address = ", hex(endaddr) + +beginaddr = endaddr - bytesForMemo(height) - len(pattmem) -1 +print "end will be at ", hex(beginaddr) + +if beginaddr <= 0x2B8: + print "sorry, this will collide with the pattern entry data!" + exit + +# write the memo and pattern entry from the -end- to the -beginning- (up!) +for i in range(len(memoentry)): + bf.setIndexedByte(endaddr, 0) + endaddr -= 1 + +for i in range(len(pattmem)): + bf.setIndexedByte(endaddr, pattmem[i]) + endaddr -= 1 + +# push the data to a file +outfile = open(sys.argv[3], 'wb') + +d = bf.getFullData() +outfile.write(d) +outfile.close() diff --git a/brother.py b/brother.py new file mode 100644 index 0000000..2790532 --- /dev/null +++ b/brother.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python + +# Copyright 2009 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys +import array +#import os +#import os.path +#import string +from array import * + +__version__ = '1.0' + +# Some file location constants +initPatternOffset = 0x06DF # programmed patterns start here, grow down +currentPatternAddr = 0x07EA # stored in MSN and following byte +currentRowAddr = 0x06FF +nextRowAddr = 0x072F +currentRowNumberAddr = 0x0702 +carriageStatusAddr = 0x070F +selectAddr = 0x07EA + + + +# various unknowns which are probably something we care about +unknownList = {'0700':0x0700, '0701':0x0701, + '0704':0x0704, '0705':0x0705, '0706':0x0706, '0707':0x0707, + '0708':0x0708, '0709':0x0709, '070A':0x070A, '070B':0x070B, + '070C':0x070C, '070D':0x070D, '070E':0x070E, '0710':0x0710, + '0711':0x0711, '0712':0x0712, '0713':0x0713, '0714':0x0714, + '0715':0x0715} + +def nibbles(achar): + #print '0x%02X' % ord(achar) + msn = (ord(achar) & 0xF0) >> 4 + lsn = ord(achar) & 0x0F + return msn, lsn + +def hto(hundreds, tens, ones): + return (100 * hundreds) + (10 * tens) + ones + +def roundeven(val): + return (val+(val%2)) + +def roundeight(val): + if val % 8: + return val + (8-(val%8)) + else: + return val + +def roundfour(val): + if val % 4: + return val + (4-(val%4)) + else: + return val + +def nibblesPerRow(stitches): + # there are four stitches per nibble + # each row is nibble aligned + return(roundfour(stitches)/4) + +def bytesPerPattern(stitches, rows): + nibbs = rows * nibblesPerRow(stitches) + bytes = roundeven(nibbs)/2 + return bytes + +def bytesForMemo(rows): + bytes = roundeven(rows)/2 + return bytes + +def bytesPerPatternAndMemo(stitches, rows): + patbytes = bytesPerPattern(stitches, rows) + memobytes = bytesForMemo(rows) + return patbytes + memobytes + +class brotherFile(object): + + def __init__(self, fn): + self.dfn = None + self.verbose = False + try: + try: + self.df = open(fn, 'rb+') # YOU MUST HAVE BINARY FORMAT!!! + except IOError: + # for now, read only + raise + #self.df = open(fn, 'w') + except: + print 'Unable to open brother file <%s>' % fn + raise + try: + self.data = self.df.read(2048) + self.df.close() + except: + print 'Unable to read 2048 bytes from file <%s>' % fn + raise + self.dfn = fn + return + + def __del__(self): + return + + def getIndexedByte(self, index): + return ord(self.data[index]) + + def setIndexedByte(self, index, b): + # python strings are mutable so we + # will convert the string to a char array, poke + # and convert back + dataarray = array('c') + dataarray.fromstring(self.data) + + if self.verbose: + print "* writing ", hex(b), "to", hex(index) + #print dataarray + + # this is the actual edit + dataarray[index] = chr(b) + + # save the new string. sure its not very memory-efficient + # but who cares? + self.data = dataarray.tostring() + + # handy for debugging + def getFullData(self): + return self.data + + def getIndexedNibble(self, offset, nibble): + # nibbles is zero based + bytes = nibble/2 + m, l = nibbles(self.data[offset-bytes]) + if nibble % 2: + return m + else: + return l + + def getRowData(self, pattOffset, stitches, rownumber): + row=array('B') + nibspr = nibblesPerRow(stitches) + startnib = nibspr * rownumber + endnib = startnib + nibspr + + for i in range(startnib, endnib, 1): + nib = self.getIndexedNibble(pattOffset, i) + row.append(nib & 0x01) + stitches = stitches - 1 + if stitches: + row.append((nib & 0x02) >> 1) + stitches = stitches - 1 + if stitches: + row.append((nib & 0x04) >> 2) + stitches = stitches - 1 + if stitches: + row.append((nib & 0x08) >> 3) + stitches = stitches - 1 + + return row + + def getPatterns(self, patternNumber = None): + """ + Get a list of custom patterns stored in the file, or + information for a single pattern. + Pattern information is stored at the beginning + of the file, with seven bytes per pattern and + 99 possible patterns, numbered 901-999. + Returns: A list of tuples: + patternNumber + stitches + rows + patternOffset + memoOffset + """ + patlist = [] + idx = 0 + pptr = initPatternOffset + for pi in range(1, 100): + flag = ord(self.data[idx]) + if self.verbose: + print 'Entry %d, flag is 0x%02X' % (pi, flag) + idx = idx + 1 + unknown = ord(self.data[idx]) + idx = idx + 1 + rh, rt = nibbles(self.data[idx]) + idx = idx + 1 + ro, sh = nibbles(self.data[idx]) + idx = idx + 1 + st, so = nibbles(self.data[idx]) + idx = idx + 1 + unk, ph = nibbles(self.data[idx]) + idx = idx + 1 + pt, po = nibbles(self.data[idx]) + idx = idx + 1 + rows = hto(rh,rt,ro) + stitches = hto(sh,st,so) + patno = hto(ph,pt,po) + # we have this entry + if self.verbose: + print ' Pattern %3d: %3d Rows, %3d Stitches - ' % (patno, rows, stitches) + print 'Unk = %d, Unknown = 0x%02X (%d)' % (unk, unknown, unknown) + if flag != 0: + # valid entry + memoff = pptr + if self.verbose: + print "Memo #",patno, "offset ", hex(memoff) + patoff = pptr - bytesForMemo(rows) + if self.verbose: + print "Pattern #",patno, "offset ", hex(patoff) + pptr = pptr - bytesPerPatternAndMemo(stitches, rows) + if self.verbose: + print "Ending offset ", hex(pptr) + # TODO figure out how to calculate pattern length + #pptr = pptr - something + if patternNumber: + if patternNumber == patno: + patlist.append({'number':patno, 'stitches':stitches, 'rows':rows, 'memo':memoff, 'pattern':patoff, 'pattend':pptr}) + else: + patlist.append({'number':patno, 'stitches':stitches, 'rows':rows, 'memo':memoff, 'pattern':patoff, 'pattend':pptr}) + else: + break + return patlist + + def getMemo(self): + """ + Return an array containing the memo + information for the pattern currently in memory + """ + patt = self.patternNumber() + if patt > 900: + return self.getPatternMemo(patt) + else: + rows = 0 # TODO XXXXXXXXX + return [0] + + def patternNumber(self): + sn, pnh = nibbles(self.data[currentPatternAddr]) + pnt, pno = nibbles(self.data[currentPatternAddr+1]) + pattern = hto(pnh,pnt,pno) + return(pattern) + + def getPatternMemo(self, patternNumber): + """ + Return an array containing the memo + information for a custom pattern. The array + is the same length as the number of rows + in the pattern. + """ + list = self.getPatterns(patternNumber) + if len(list) == 0: + return None + memos = array('B') + memoOff = list[0]['memo'] + rows = list[0]['rows'] + memlen = roundeven(rows)/2 + # memo is padded to en even byte + for i in range(memoOff, memoOff-memlen, -1): + msn, lsn = nibbles(self.data[i]) + memos.append(lsn) + rows = rows - 1 + if (rows): + memos.append(msn) + rows = rows - 1 + return memos + + def getPattern(self, patternNumber): + """ + Return an array containing the pattern + information for a pattern. + """ + list = self.getPatterns(patternNumber) + if len(list) == 0: + return None + pattern = [] + + patoff = list[0]['pattern'] + rows = list[0]['rows'] + stitches = list[0]['stitches'] + + #print 'patoff = 0x%04X' % patoff + #print 'rows = ', rows + #print 'stitches = ', stitches + for i in range(0, rows): + arow = self.getRowData(patoff, stitches, i) + #print arow + pattern.append(arow) + return pattern + + def displayPattern(self, patternNumber): + """ + Display a user pattern stored in file saved + from the brother knitting machine. Patterns + in memory are stored with the beginning of the + pattern at the highest memory address. + """ + + return + + def rowNumber(self): + sn, rnh = nibbles(self.data[currentRowNumberAddr]) + rnt, rno = nibbles(self.data[currentRowNumberAddr+1]) + rowno = hto(rnh,rnt,rno) + return(rowno) + + def nextRow(self): + return self.getRowData(nextRowAddr, 200, 0) + + def selectorValue(self): + return ord(self.data[selectAddr]) + + def carriageStatus(self): + return ord(self.data[carriageStatusAddr]) + + def motifData(self): + motiflist = [] + addr = 0x07FB + for i in range(6): + mph, mpt = nibbles(self.data[addr]) + if mph & 8: + mph = mph - 8 + side = 'right' + else: + side = 'left' + mpo, foo = nibbles(self.data[addr+1]) + mch, mct = nibbles(self.data[addr+2]) + mco, bar = nibbles(self.data[addr+3]) + pos = hto(mph,mpt,mpo) + cnt = hto(mch,mct,mco) + motiflist.append({'position':pos, 'copies':cnt, 'side':side}) + addr = addr - 3 + return motiflist + + def patternPosition(self): + addr = 0x07FE + foo, ph = nibbles(self.data[addr]) + if ph & 8: + ph = ph - 8 + side = 'right' + else: + side = 'left' + pt, po = nibbles(self.data[addr+1]) + pos = hto(ph,pt,po) + + return {'position':pos, 'side':side} + + # these are hardcoded for now + def unknownOne(self): + info = array('B') + for i in range(0x06E0, 0x06E5): + info.append(ord(self.data[i])) + return info + + def unknownMemoRange(self): + info = array('B') + for i in range(0x0731, 0x0787): + info.append(ord(self.data[i])) + return info + + def unknownEndRange(self): + info = array('B') + for i in range(0x07D0, 0x07E9): + info.append(ord(self.data[i])) + return info + + def unknownAddrs(self): + return unknownList.items() + diff --git a/dumppattern.py b/dumppattern.py new file mode 100644 index 0000000..6694b99 --- /dev/null +++ b/dumppattern.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python + +# Copyright 2009 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys +import brother + +DEBUG = 0 + +########## + +def roundeven(val): + return (val+(val%2)) + +def roundeight(val): + if val % 8: + return val + (8-(val%8)) + else: + return val + +def roundfour(val): + if val % 4: + return val + (4-(val%4)) + else: + return val + +def nibblesPerRow(stitches): + # there are four stitches per nibble + # each row is nibble aligned + return(roundfour(stitches)/4) + +def bytesPerPattern(stitches, rows): + nibbs = rows * nibblesPerRow(stitches) + bytes = roundeven(nibbs)/2 + return bytes + +def bytesForMemo(rows): + bytes = roundeven(rows)/2 + return bytes + +############## + + +version = '1.0' + +if len(sys.argv) < 2: + print 'Usage: %s file [patternnum]' % sys.argv[0] + print 'Dumps user programs (901-999) from brother data files' + sys.exit() + +if len(sys.argv) == 3: + patt = int(sys.argv[2]) +else: + patt = 0 + +bf = brother.brotherFile(sys.argv[1]) + +if patt == 0: + pats = bf.getPatterns() + print 'Pattern Stitches Rows' + for pat in pats: + print ' %3d %3d %3d' % (pat["number"], pat["stitches"], pat["rows"]) + + if DEBUG: + print "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+" + print "Data file" + print "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+" + + # first dump the 99 'pattern id' blocks + for i in range(99): + print "program entry",i + # each block is 7 bytes + bytenum = i*7 + + pattused = bf.getIndexedByte(bytenum) + print "\t",hex(bytenum),": ",hex(pattused), + if (pattused == 1): + print "\t(used)" + else: + print "\t(unused)" + #print "\t-skipped-" + #continue + bytenum += 1 + + unk1 = bf.getIndexedByte(bytenum) + print "\t",hex(bytenum),": ",hex(unk1),"\t(unknown)" + bytenum += 1 + + rows100 = bf.getIndexedByte(bytenum) + print "\t",hex(bytenum),": ",hex(rows100),"\t(rows = ", (rows100 >> 4)*100, " + ", (rows100 & 0xF)*10 + bytenum += 1 + + rows1 = bf.getIndexedByte(bytenum) + print "\t",hex(bytenum),": ",hex(rows1),"\t\t+ ", (rows1 >> 4), " stiches = ", (rows1 & 0xF)*100,"+" + bytenum += 1 + + stitches10 = bf.getIndexedByte(bytenum) + print "\t",hex(bytenum),": ",hex(stitches10),"\t\t+ ", (stitches10 >> 4)*10, " +", (stitches10 & 0xF),")" + bytenum += 1 + + prog100 = bf.getIndexedByte(bytenum) + print "\t",hex(bytenum),": ",hex(prog100),"\t(unknown , prog# = ", (prog100&0xF) * 100,"+" + bytenum += 1 + + prog10 = bf.getIndexedByte(bytenum) + print "\t",hex(bytenum),": ",hex(prog10),"\t\t + ", (prog10>>4) * 10,"+",(prog10&0xF),")" + bytenum += 1 + + print "============================================" + print "Program memory grows -up-" + # now we're onto data data + + # dump the first program + pointer = 0x6DF # this is the 'bottom' of the memory + for i in range(99): + # of course, not all patterns will get dumped + pattused = bf.getIndexedByte(i*7) + if (pattused != 1): + # :( + break + # otherwise its a valid pattern + print "pattern bank #", i + # calc pattern size + rows100 = bf.getIndexedByte(i*7 + 2) + rows1 = bf.getIndexedByte(i*7 + 3) + stitches10 = bf.getIndexedByte(i*7 + 4) + + rows = (rows100 >> 4)*100 + (rows100 & 0xF)*10 + (rows1 >> 4); + stitches = (rows1 & 0xF)*100 + (stitches10 >> 4)*10 + (stitches10 & 0xF) + print "rows = ", rows, "stitches = ", stitches +# print "total nibs per row = ", nibblesPerRow(stitches) + + + # dump the memo data + print "memo length =",bytesForMemo(rows) + for i in range (bytesForMemo(rows)): + b = pointer - i + print "\t",hex(b),": ",hex(bf.getIndexedByte(b)) + pointer -= bytesForMemo(rows) + + print "pattern length = ", bytesPerPattern(stitches, rows) + for i in range (bytesPerPattern(stitches, rows)): + b = pointer - i + print "\t",hex(b),": ",hex(bf.getIndexedByte(b)), + for j in range(8): + if (bf.getIndexedByte(b) & (1< <%s>' % pcmd +# self.dumpchars() +# ds_string = command +# cs = self.calcChecksum(ds_string) +# ds_string = ds_string + cs +# print "cR sending . . ." +# print dump(ds_string) +# self.writebytes(ds_string) +# response = self.readsomechars() +# print 'Command got a response of ', response +# return response + + def __FDCcommandResponse(self, command, expected): + if self.verbose: + pcmd = command.strip() + print '-------------------------------------\nwriting FDC command ===> <%s>' % pcmd + self.dumpchars() + self.writebytes(command) + response = self.getFDCresponse(expected) + return response + # + # Begin Op Mode commands + # + + def EnterFDCMode(self): + if False: + command = "ZZ" + chr(0x08) + chr(0) + cs = self.calcChecksum(command) + command = command + cs + "\r" + else: + command = "ZZ" + chr(0x08) + chr(0) + chr(0xF7) + "\r" + if self.verbose: + print "Entering FDC Mode, sending:" + print dump(command), + self.writebytes(command) + # There's no response to this command, so allow time to complete + time.sleep(.010) + if self.verbose: + print "Done entering FDC Mode\n" + return + + # + # Begin FDC mode commands + # + + def FDCChangeMode(self, mode = '1'): + """ + Change the disk drive mode. Default to changing from FDC + emulation mode back to Operational Mode. + """ + command = "M " + mode + result = self.__FDCcommandResponse(command, 8) + if not self.isSuccess(result): + raise IOError + return + + def FDCcheckDeviceCondition(self): + """ + Send the 'D' command and return the result + """ + command = "D\r" + result = self.__FDCcommandResponse(command, 8) + if self.verbose: + # third byte: + # 30 - Normal + # 45 - Door open or no disk + # 32 - write protected + print "third byte = 0x%X" % ord(result[2]) + return result + + def FDCformat(self, sectorSize='5', verify=True): + """ + Format the floppy with the requested + """ + if verify: + command = "F" + else: + command = "G" + command = command + sectorSize + "\r" + result = self.__FDCcommandResponse(command, 8) + if not self.isSuccess(result): + raise IOError + return + + def FDCreadIdSection(self, psn = '0'): + """ + Read the ID section of a physical sector, and return + the ID, which should be 12 bytes long + """ + if self.verbose: + print "FDCreadIdSection: Enter" + command = "A " + psn + "\r" + result = self.__FDCcommandResponse(command, 8) + if not self.isSuccess(result): + raise IOError + result = self.__FDCcommandResponse("\r", 12) + if self.verbose: + print "FDCreadIdSection data:" + print dump(result) + print "FDCreadIdSection: Exit" + return result + + def FDCreadSector(self, psn = '0', lsn = '1'): + """ + Read the data from a logical sector. + psn is Physical sector number, in the range 0-79 + lsn is the logical sector number, in range of 1 + to the max for the physical sector size + """ + if self.verbose: + print "FDCreadSector: Enter" + command = "R " + psn + ","+ lsn + "\r" + result = self.__FDCcommandResponse(command, 8) + if not self.isSuccess(result): + raise IOError + if self.verbose: + print "FDCreadSector: Phase two" + result = self.__FDCcommandResponse("\r", 1024) + if self.verbose: + print "FDCreadSector data:" + print dump(result) + print "FDCreadSector: Exit" + return result + + def FDCsearchIdSection(self, data, psn = '0'): + """ + Compare the data to the sector ID + psn is Physical sector number, in the range 0-79 + Data length must be 12 bytes + """ + if len(data) != 12: + raise ValueError("ID data must be 12 characters long") + if self.verbose: + print "FDCsearchIdSection: Enter" + command = "S" + psn + "\r" + result = self.__FDCcommandResponse(command, 8) + if not self.isSuccess(result): + raise IOError + if self.verbose: + print "FDCsearchIdSection data:" + print dump(data) + result = self.__FDCcommandResponse(data, 12) + if self.verbose: + print "FDCsearchIdSection: Exit" + print "NOT SURE WHAT WE EXPECT HERE" + print "FDCsearchIdSection status is:" + print dump(result) + if not self.isSuccess(result): + raise IOError + return + + def FDCwriteIdSection(self, data, psn = '0', verify = True): + """ + Write the data to the sector ID + psn is Physical sector number, in the range 0-79 + Data length must be 12 bytes + """ + if len(data) != 12: + raise ValueError("ID data must be 12 characters long") + if verify: + command = "B" + else: + command = "C" + if self.verbose: + print "FDCwriteIdSection: Enter" + command = command + psn + "\r" + result = self.__FDCcommandResponse(command, 8) + if not self.isSuccess(result): + raise IOError + if self.verbose: + print "FDCwriteIdSection data:" + print dump(data) + result = self.__FDCcommandResponse(data, 8) + if self.verbose: + print "FDCwriteIdSection: Exit" + if not self.isSuccess(result): + raise IOError + return + +#W Write log sector w/verify +# (two stages - second stage send data to be written) +#X Write log sector w/o vfy +# (two stages - second stage send data to be written) + def FDCwriteSector(self, data, psn = '0', lsn = '1', verify = True): + """ + Write the data to the sector + psn is Physical sector number, in the range 0-79, defaults to 0 + lsn is logical sector number, defaults to 1 + """ + if verify: + command = "W" + else: + command = "X" + if self.verbose: + print "FDCwriteSector: Enter" + command = command + psn + "," + lsn + "\r" + result = self.__FDCcommandResponse(command, 8) + if not self.isSuccess(result): + raise IOError + if self.verbose: + print "FDCwriteSector data:" + print dump(data) + result = self.__FDCcommandResponse(data, 8) + if self.verbose: + print "FDCwriteSector: Exit" + if not self.isSuccess(result): + raise IOError + return diff --git a/experimental/driveread.py b/experimental/driveread.py new file mode 100644 index 0000000..bf70038 --- /dev/null +++ b/experimental/driveread.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +# Copyright 2012 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys +import os +#import os.path +#import string +import time +import serial + +from FDif import PDD1, dump + +# meat and potatos here + +if len(sys.argv) < 3: + print 'Usage: %s serialdevice dirname' % sys.argv[0] + print + print 'Reads all sectors and sector IDs from a disk and' + print 'copies it into the directory specified.' + print 'The format is the same as PDDemulate uses.' + sys.exit() + + +bdir = os.path.relpath(sys.argv[2]) +print "bdir = ", bdir +if os.path.exists(bdir): + print "<%s> already exists, exiting . . ." % bdir + sys.exit() + +os.mkdir(bdir) + +drive = PDD1() + +drive.open(cport=sys.argv[1]) + +for sn in range(80): + ifn = "%02d.id" % sn + dfn = "%02d.dat" % sn + Fid = open(os.path.join(bdir, ifn), 'w') + Fdata = open(os.path.join(bdir, dfn), 'w') + + sid = drive.FDCreadIdSection(psn = '%d' % sn) + print "Sector %02d ID: " % sn + print dump(sid) + Fid.write(sid) + + data = drive.FDCreadSector(psn = '%d' % sn) + print "Sector %02d Data: " % sn + print dump(data) + Fdata.write(data) + + Fid.close() + Fdata.close() + + if sn % 2: + filenum = ((sn-1)/2)+1 + filename = 'file-%02d.dat' % filenum + # we wrote an odd sector, so create the + # associated file + fn1 = os.path.join(bdir, '%02d.dat' % (sn-1)) + fn2 = os.path.join(bdir, '%02d.dat' % sn) + outfn = os.path.join(bdir, filename) + cmd = 'cat %s %s > %s' % (fn1, fn2, outfn) + os.system(cmd) + +drive.close() diff --git a/file-analysis/README b/file-analysis/README new file mode 100644 index 0000000..da36a4c --- /dev/null +++ b/file-analysis/README @@ -0,0 +1 @@ +These tools and files are related to understanding and manipulating the data format saved on disk by the brother knitting machine. In most cases they are designed to work on the data files created by the PDDemulate floppy drive emulator. diff --git a/file-analysis/adumper.py b/file-analysis/adumper.py new file mode 100644 index 0000000..6950f7f --- /dev/null +++ b/file-analysis/adumper.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python + +# Copyright 2009 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# +# Pattern Number +# Position of the selector +# Pattern position for the selector setting +# Pattern position and number of repeats for each motif for selector (2) settings +# Variation key settings +# Memo display information +# Row numbers and memo key information + +# 200 needles +# pattern position is Yellow or Green (left or right) +# up to 6 motifs - + +import sys +from struct import * +from collections import namedtuple +#import os +#import os.path +#import string +#from array import * + +def dumpPattern(pat): + bits = [] + for short in pat: + for i in range(15, -1, -1): + mask = (2 ** i) + if short & (2 ** i): + bits.append('*') + else: + bits.append(' ') + print "".join(bits) + return + + +if len(sys.argv) < 3: + print 'Usage: %s fileA fileB' % sys.argv[0] + sys.exit() + +f1 = open(sys.argv[1]) +#f2 = open(sys.argv[2]) + +d1 = f1.read(2048) +#d2 = f2.read(2048) + +# head - 1766 bytes +fmt = '1766B' +# first pattern +fmt += '13H' +# after first pattern +fmt += '22B' + + +# second pattern +fmt += '13H' +# tail +fmt += '208B' + + +#lead, pattern1, dummy, pattern2, tail = struct.unpack(fmt, d1) +#lead[1766], pattern1[13], dummy[22], pattern2[13], tail[208] = unpack(fmt, d1) +foo = unpack(fmt, d1) +print len(foo) +#bar = foo[0:1766] +baz = foo[1766:1779] +dumpPattern(baz) +#print bar +#print baz +#print foo + +#inrun = False +#for i in range(len(d1)): +# if d1[i] != d2[i]: +# if not inrun: +# print '--' +# inrun = True +# print '%04X: %02X %02X' % (i, ord(d1[i]), ord(d2[i])) +# else: +# inrun = False +# +#f1.close() +#f2.close() diff --git a/file-analysis/bdump.c b/file-analysis/bdump.c new file mode 100644 index 0000000..5c480b5 --- /dev/null +++ b/file-analysis/bdump.c @@ -0,0 +1,451 @@ +/* + * bdump -- Dump info from a brother knitting machine file + * + +Copyright 2009 Steve Conklin +steve at conklinhouse dot com + +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 2 +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, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +This file is for experimentation, and reflects whatever I was working on. +It may be helpful as a starting point, but probably isn't of general use. + +Book says max save of "approximately 13,600 stitches" +This is about 1700 bytes +2048 - 1700 = 348 + + +--- + +# +# Pattern Number +# Position of the selector +# Pattern position for the selector setting +# Pattern position and number of repeats for each motif for selector (2) settings +# Variation key settings +# Memo display information +# Row numbers and memo key information + +# 200 needles +# Max of 998 rows when entering your own patterns (depends on number of stitches in a row??) +# 998 rows would take 0x1F3 bytes for memo info, and there's not room where I think it should be +# pattern position is Yellow or Green (left or right) +# up to 6 motifs - + + + */ + +#include +#include +#include +#include + +// defines that are probably correct and will stay + +// Sizes +#define NEEDLELEN 13 // 13 bytes for 200 bits + +// Pattern variations +#define VAR_REV 0x01 // reverse +#define VAR_MH 0x02 // mirror horizontal +#define VAR_SH 0x08 // stretch horizontal +#define VAR_SV 0x10 // stretch vertical +#define VAR_IV 0x04 // invert horizontal +#define VAR_KHC 0x20 // KHC +#define VAR_KRC 0x40 // KRC +#define VAR_MB 0x80 // M button +/* + if (bd.variations & VAR_REV) + printf("REVERSE "); + if (bd.variations & VAR_MH) + printf("MIRROR_H "); + if (bd.variations & VAR_SH) + printf("STRETCH H "); + if (bd.variations & VAR_SV) + printf("STRETCH V "); + if (bd.variations & VAR_IV) + printf("INVERT V "); + if (bd.variations & VAR_KHC) + printf("KHC "); + if (bd.variations & VAR_KRC) + printf("KRC "); + if (bd.variations & VAR_UNK) + printf("UNKNOWN "); +*/ +// Selector switch +#define SEL_ONE 0x10 +#define SEL_TWO 0x20 + +static void hex_dump(void *data, int size) +{ + /* dumps size bytes of *data to stdout. Looks like: + * [0000] 75 6E 6B 6E 6F 77 6E 20 + * 30 FF 00 00 00 00 39 00 unknown 0.....9. + * (in a single line of course) + */ + + unsigned char *p = data; + unsigned char c; + int n; + int marked = 0; + char bytestr[4] = {0}; + char addrstr[10] = {0}; + char hexstr[ 16*3 + 5] = {0}; + char charstr[16*1 + 5] = {0}; + for(n=1;n<=size;n++) { + if (n%16 == 1) { + /* store address for this line */ + snprintf(addrstr, sizeof(addrstr), "%.4x", + ((unsigned int)p-(unsigned int)data) ); + } + + c = *p; + if (isalnum(c) == 0) { + c = '.'; + } + + + /* store hex str (for left side) */ + snprintf(bytestr, sizeof(bytestr), "%02X ", *p); + strncat(hexstr, bytestr, sizeof(hexstr)-strlen(hexstr)-1); + + /* store char str (for right side) */ + snprintf(bytestr, sizeof(bytestr), "%c", c); + strncat(charstr, bytestr, sizeof(charstr)-strlen(charstr)-1); + + if(n%16 == 0) { + /* line completed */ + printf("[%4.4s] %-50.50s %s\n", addrstr, hexstr, charstr); + hexstr[0] = 0; + charstr[0] = 0; + } else if(n%8 == 0) { + /* half line: add whitespaces */ + strncat(hexstr, " ", sizeof(hexstr)-strlen(hexstr)-1); + strncat(charstr, " ", sizeof(charstr)-strlen(charstr)-1); + } + p++; /* next byte */ + } + + if (strlen(hexstr) > 0) { + /* print rest of buffer if not empty */ + printf("[%4.4s] %-50.50s %s\n", addrstr, hexstr, charstr); + } +} + +/* + * TODO This needs to be reworked. These represent the + * values for all 200 needles. There are two copies in the file, + * probably for 'this row' and 'next row'. + * + * For both the needle values, patterns, and memo information, + * the data is always stored from higher to lower addresses + * in the file. + */ +void olddumpNeedles(unsigned short *pp) { + unsigned short *pp1 = pp; + unsigned short pb; + int i, j; + for (i=0; i=0; j--) { + if (pb & (1<=0; j--) { + if (pb & (1<status == 1) { + if (1) { + printf("Position %d:\n", i); + ival = (pptr->num_h * 100) + (pptr->num_t * 10) + (pptr->num_o); + printf("Status 0x%02X (%d)\n", pptr->status, pptr->status); + printf(" Pgm Number: % 3d\n", ival); + printf(" Unk nibble: % 3d\n", pptr->unk); + printf(" unk1 0x%02X (%d)\n", pptr->unk1, pptr->unk1); + ival = (pptr->rows_h * 100) + (pptr->rows_t * 10) + (pptr->rows_o); + printf(" Rows: % 3d\n", ival); + ival = (pptr->stitches_h * 100) + (pptr->stitches_t * 10) + (pptr->stitches_o); + printf(" Stitches: % 3d\n", ival); + + printf("--------\n"); + } + } + + printf("===== head =====\n"); + hex_dump(bd.head, sizeof(bd.head)); + + printf("===== Needles1 =====\n"); + dumpNeedles(bd.needles1); + + printf("===== afterfirst1 =====\n"); + hex_dump(bd.afterfirst1, sizeof(bd.afterfirst1)); + + printf("===== Variations =====\n"); + printf("variations = 0x%02X - ", bd.variations); + if (bd.variations & VAR_REV) + printf("REVERSE "); + if (bd.variations & VAR_MH) + printf("MIRROR_H "); + if (bd.variations & VAR_SH) + printf("STRETCH H "); + if (bd.variations & VAR_SV) + printf("STRETCH V "); + if (bd.variations & VAR_IV) + printf("INVERT V "); + if (bd.variations & VAR_KHC) + printf("KHC "); + if (bd.variations & VAR_KRC) + printf("KRC "); + //if (bd.variations & VAR_UNK) + //printf("UNKNOWN "); + printf("\n"); + + printf("===== afterfirst2 =====\n"); + hex_dump(bd.afterfirst2, sizeof(bd.afterfirst2)); + + printf("===== Needles2 =====\n"); + dumpNeedles(bd.needles2); + + printf("===== tail1 =====\n"); + hex_dump(bd.tail1, sizeof(bd.tail1)); + + printf("===== selector =====\n"); // 0x7EA + //printf("Selector = 0x%02X | %d\n", *bd.selector, *bd.selector); + cval = *bd.selector & 0xF0; + selector = cval; + if (cval == SEL_ONE) { + printf("Selector One (all over pattern):\n"); + } else if (cval == SEL_TWO) { + printf("Selector Two (motifs):\n"); + } else { + printf("Unknown selector value of 0x%X found\n", cval); + } + + printf("===== selector low nibble =====\n"); // 0x7EA + printf("Selector low nibble = 0x%02X\n", *bd.selector & 0x0F); + + printf("===== selfiller = 0x%02X =====\n", bd.selfiller); + + // read the motifs + // Motif data is three bytes for each motif, but it is not + // byte aligned - it is off alignment by 4 bits. + cptr = &bd.motifdata[0]; + cval = *cptr & 0xF0; + printf("MSB at beggining of motif info = 0x%02X\n", cval); + for(i=5; i>=0; i--) { + // First byte, skip MSB + cval = *cptr & 0x0F; // LSB + //printf("cval (LSB) = 0x%02X\n", cval); + if (cval & 8) { + motif[i].right = 1; + cval &= 0x07; + } else { + motif[i].right = 0; + } + if (cval > 1) + printf("Unexpected value of %d for position hundreds, motif %d\n", cval, i); + motif[i].pos = cval * 100; + // Second byte + cptr++; + //cval = *cptr; + //printf("cval = 0x%02X\n", cval); + cval = (*cptr>>4) & 0x0F; // MSB + motif[i].pos += cval * 10; + cval = *cptr & 0x0F; // LSB + motif[i].pos += cval; + // Third byte + cptr++; + //cval = *cptr; + //printf("cval = 0x%02X\n", cval); + cval = (*cptr>>4) & 0x0F; // MSB + motif[i].copies = cval * 100; + cval = *cptr & 0x0F; // LSB + motif[i].copies += cval * 10; + // Fourth byte + cptr++; + cval = (*cptr>>4) & 0x0F; // MSB + //printf("cval (MSB) = 0x%02X\n", cval); + motif[i].copies += cval; + // Leave pointer so we grab LSB next round + //printf("--\n"); + } + // now in the last three nibbles is the starting + // location for the pattern for Selector One + // First byte, skip MSB + cval = *cptr & 0x0F; // LSB + if (cval & 8) { + selone_right = 1; + cval &= 0x07; + } else { + selone_right = 0; + } + if (cval > 1) + printf("Unexpected value of %d for selector One position hundreds\n", cval); + selone_pos = cval * 100; + // Second byte + cptr++; + cval = (*cptr>>4) & 0x0F; // MSB + selone_pos += cval * 10; + cval = *cptr & 0x0F; // LSB + selone_pos += cval; + + // see how many are in use - the first zero for + // number of copies is the end + nummotifs = 6; + for (i=0; i<=5; i++) { + if (motif[i].copies == 0) { + nummotifs = i; + break; + } + } + + printf("========== Selector Two (motifs) ==========\n"); + printf("%d motifs are used\n", nummotifs); + for (i=0; i<=5; i++) { + printf("Motif %d: ", i+1); + printf(" %3d Copies ", motif[i].copies); + if (motif[i].right) + printf("starting at %3d Right ", motif[i].pos); + else + printf("starting at %3d Left ", motif[i].pos); + if (i+1 > nummotifs) + printf(" (UNUSED)\n"); + else + printf("\n"); + + } + printf("========== Selector One (all over pattern) ==========\n"); + if (selone_right) + printf("Selector One pattern start is position %3d Right\n", selone_pos); + else + printf("Selector One pattern start is position %3d Left\n", selone_pos); +} diff --git a/file-analysis/brother.py b/file-analysis/brother.py new file mode 100644 index 0000000..412dfdc --- /dev/null +++ b/file-analysis/brother.py @@ -0,0 +1,463 @@ +#!/usr/bin/env python + +# Copyright 2009 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys +import array +#import os +#import os.path +#import string +#from array import * + +__version__ = '1.1' + +# Some file location constants +initPatternOffset = 0x06DF # programmed patterns start here, grow down +currentPatternAddr = 0x07EA # stored in MSN and following byte +currentRowAddr = 0x06FF +nextRowAddr = 0x072F +currentRowNumberAddr = 0x0702 +carriageStatusAddr = 0x070F +selectAddr = 0x07EA + +patternDirSize = 7 + + +# various unknowns which are probably something we care about +unknownList = {'0700':0x0700, '0701':0x0701, + '0704':0x0704, '0705':0x0705, '0706':0x0706, '0707':0x0707, + '0708':0x0708, '0709':0x0709, '070A':0x070A, '070B':0x070B, + '070C':0x070C, '070D':0x070D, '070E':0x070E, '0710':0x0710, + '0711':0x0711, '0712':0x0712, '0713':0x0713, '0714':0x0714, + '0715':0x0715} + +def nibbles(achar): + #print '0x%02X' % ord(achar) + msn = (ord(achar) & 0xF0) >> 4 + lsn = ord(achar) & 0x0F + return msn, lsn + +def hto(hundreds, tens, ones): + return (100 * hundreds) + (10 * tens) + ones + +def roundeven(val): + return (val+(val%2)) + +def roundeight(val): + if val % 8: + return val + (8-(val%8)) + else: + return val + +def roundfour(val): + if val % 4: + return val + (4-(val%4)) + else: + return val + +def nibblesPerRow(stitches): + # there are four stitches per nibble + # each row is nibble aligned + return(roundfour(stitches)/4) + +def bytesPerPattern(stitches, rows): + nibbs = rows * nibblesPerRow(stitches) + bytes = roundeven(nibbs)/2 + return bytes + +def bytesForMemo(rows): + bytes = roundeven(rows)/2 + return bytes + +def bytesPerPatternAndMemo(stitches, rows): + patbytes = bytesPerPattern(stitches, rows) + memobytes = bytesForMemo(rows) + return patbytes + memobytes + +class brotherFile(object): + + def __init__(self, filename = ""): + self.dfn = None + self.verbose = False + if filename != "": + try: + try: + self.df = open(filename, 'r+') + except IOError: + # for now, read only + self.df = open(filename, 'w') + except: + print 'Unable to open brother file <%s>' % filename + raise + try: + self.data = self.df.read(2048) + self.df.close() + except: + print 'Unable to read 2048 bytes from file <%s>' % filename + raise + self.dfn = filename + else: + # created without a file associated, just create an empty one + self.clearFileData() + return + + def __del__(self): + if self.dfn: + self.dfn.close() + return + + def clearFileData(): + """ + Clear the file memory as if a memory clear has been performed on + the knitting machine (code 888) + """ + self.data = [] + for i in range(2048): + self.data.append(char(0)) + # make this look like cleared memory in the KM + self.data[0x0005] = 0x09 + self.data[0x0006] = 0x01 + self.data[0x0700] = 0x01 + self.data[0x0701] = 0x20 + self.data[0x0710] = 0x07 + self.data[0x0711] = 0xF9 + self.data[0x07EA] = 0x10 + + if self.dfn: + self.dfn.seek(0) + self.dfn.write(self.data) + self.dfn.flush() + return + + def getIndexedByte(self, index): + """ + Returns the byte at the offset + """ + return ord(self.data[index]) + + def getIndexedNibble(self, offset, nibble): + """ + Accepts an offset into the file and + a nibble index. Nibble index is subtracted + from the offset (into lower addresses) and + the indexed nibble is returned + """ + # nibbles is zero based + bytes = nibble/2 + m, l = nibbles(self.data[offset-bytes]) + if nibble % 2: + return m + else: + return l + + def getRowData(self, pattOffset, stitches, rownumber): + """ + Given an offset into the file, the pattern width + and the row number, returns an array with the row data + """ + row=array.array('B') + nibspr = nibblesPerRow(stitches) + startnib = nibspr * rownumber + endnib = startnib + nibspr + + for i in range(startnib, endnib, 1): + nib = self.getIndexedNibble(pattOffset, i) + row.append(nib & 0x01) + stitches = stitches - 1 + if stitches: + row.append((nib & 0x02) >> 1) + stitches = stitches - 1 + if stitches: + row.append((nib & 0x04) >> 2) + stitches = stitches - 1 + if stitches: + row.append((nib & 0x08) >> 3) + stitches = stitches - 1 + + return row + + def getPatterns(self, patternNumber = None): + """ + Get a list of custom patterns stored in the file, or + information for a single pattern. + Pattern information is stored at the beginning + of the file, with seven bytes per pattern and + 99 possible patterns, numbered 901-999. + Returns a list of dictionaries + number: pattern number + stitches: number of stitches per row + rows: number of rows in pattern + memo: memo offset address + pattern: pattern offset address + """ + patlist = [] + idx = 0 + pptr = initPatternOffset + for pi in range(1, 100): + flag = ord(self.data[idx]) + if self.verbose: + print 'Entry %d, flag is 0x%02X' % (pi, flag) + idx = idx + 1 + unknown = ord(self.data[idx]) + idx = idx + 1 + rh, rt = nibbles(self.data[idx]) + idx = idx + 1 + ro, sh = nibbles(self.data[idx]) + idx = idx + 1 + st, so = nibbles(self.data[idx]) + idx = idx + 1 + unk, ph = nibbles(self.data[idx]) + idx = idx + 1 + pt, po = nibbles(self.data[idx]) + idx = idx + 1 + rows = hto(rh,rt,ro) + stitches = hto(sh,st,so) + patno = hto(ph,pt,po) + # we have this entry + if self.verbose: + print ' Pattern %3d: %3d Rows, %3d Stitches - ' % (patno, rows, stitches) + print 'Unk = %d, Unknown = 0x%02X (%d)' % (unk, unknown, unknown) + if flag == 1: + # valid entry + memoff = pptr + patoff = pptr - bytesForMemo(rows) + pptr = pptr - bytesPerPatternAndMemo(stitches, rows) + # TODO figure out how to calculate pattern length + #pptr = pptr - something + if patternNumber: + if patternNumber == patno: + patlist.append({'number':patno, 'stitches':stitches, 'rows':rows, 'memo':memoff, 'pattern':patoff}) + else: + patlist.append({'number':patno, 'stitches':stitches, 'rows':rows, 'memo':memoff, 'pattern':patoff}) + else: + break + return patlist + + def getMemo(self): + """ + Return an array containing the memo + information for the currently selected pattern + """ + patt = self.patternNumber() + if patt > 900: + return self.getPatternMemo(patt) + else: + # TODO Until we figure out how to know how many + # rows in a pattern, we don't know how much data + # to return + rows = 0 # TODO XXXXXXXXX + return [0] + + def patternNumber(self): + """ + Returns the number of the currently selected pattern + """ + sn, pnh = nibbles(self.data[currentPatternAddr]) + pnt, pno = nibbles(self.data[currentPatternAddr+1]) + pattern = hto(pnh,pnt,pno) + return(pattern) + + def getPatternMemo(self, patternNumber): + """ + Return an array containing the memo + information for a custom pattern. The array + is the same length as the number of rows + in the pattern. + """ + list = self.getPatterns(patternNumber) + if len(list) == 0: + return None + memos = array.array('B') + memoOff = list[0]['memo'] + rows = list[0]['rows'] + memlen = roundeven(rows)/2 + # memo is padded to en even byte + for i in range(memoOff, memoOff-memlen, -1): + msn, lsn = nibbles(self.data[i]) + memos.append(lsn) + rows = rows - 1 + if (rows): + memos.append(msn) + rows = rows - 1 + return memos + + def getPattern(self, patternNumber): + """ + Return an array containing the pattern + rows for a pattern. Only works for + pattern numbers > 900 + """ + list = self.getPatterns(patternNumber) + if len(list) == 0: + return None + pattern = [] + + patoff = list[0]['pattern'] + rows = list[0]['rows'] + stitches = list[0]['stitches'] + + #print 'patoff = 0x%04X' % patoff + #print 'rows = ', rows + #print 'stitches = ', stitches + for i in range(0, rows): + arow = self.getRowData(patoff, stitches, i) + #print arow + pattern.append(arow) + return pattern + + def calcFreePatternSpace(): + """ + Returns the number of bytes available for + storage of a pattern + """ + patlist = getPatterns() + numpats = len(patlist) + # get the lowest address used by a pattern + if numpats == 0: + freetop = initPatternOffset + else: + # get info from last pattern + lp = patlist[-1] + patoff = lp['pattern'] + patsize = bytesPerPatternAndMemo(lp['stitches'], lp['rows']) + freetop = patoff - patsize + # need to have two free pattern entries + # one for the entry we're adding and one to flag end + freebottom = (numpats + 2) * patternDirSize + freespace = freetop - freebottom + if (self.verbose): + print 'freetop = 0x%04X, freebottom = 0x%0xX, free = 0x%04X' % (freetop, freebottom, freespace) + return freespace + + def deletePattern(self, patternNumber): + """ + Delete a pattern from user memory + """ + if (patternNumber < 901) or (patternNumber > 999): + raise ValueError + pinfo = getPatterns(patternNumber) + if len(pinfo) == 0: + raise ValueError + # TODO walk the directory, delete the entry, compress pattern memory + return + + def savePattern(self, pattern, memo = None): + """ + accepts a pattern (array of row data) and + an optional memo array. This is stored in the + next available custom pattern slot. + """ + rows = len(pattern) + stitches = len(pattern[0]) + freespace = calcFreePatternSpace() + patsize = bytesPerPatternAndMemo(stitches, rows) + if patsize > freespace: + raise MemoryError + + + + return + + def rowNumber(self): + """ + Returns the current row number in the pattern being knitted + """ + sn, rnh = nibbles(self.data[currentRowNumberAddr]) + rnt, rno = nibbles(self.data[currentRowNumberAddr+1]) + rowno = hto(rnh,rnt,rno) + return(rowno) + + def selectorValue(self): + """ + Returns the numerical value stored for Selector, + either 1 or 2 + """ + return ord(self.data[selectAddr]) + + def carriageStatus(self): + """ + Returns carriage status - known values are + 0x00 when moving left and 0x02 when moving right + """ + return ord(self.data[carriageStatusAddr]) + + def motifData(self): + motiflist = [] + addr = 0x07FB + for i in range(6): + mph, mpt = nibbles(self.data[addr]) + if mph & 8: + mph = mph - 8 + side = 'right' + else: + side = 'left' + mpo, foo = nibbles(self.data[addr+1]) + mch, mct = nibbles(self.data[addr+2]) + mco, bar = nibbles(self.data[addr+3]) + pos = hto(mph,mpt,mpo) + cnt = hto(mch,mct,mco) + motiflist.append({'position':pos, 'copies':cnt, 'side':side}) + addr = addr - 3 + return motiflist + + def patternPosition(self): + """ + This returns the starting position that is saved for + selector=1 (all over patterning) + """ + addr = 0x07FE + foo, ph = nibbles(self.data[addr]) + if ph & 8: + ph = ph - 8 + side = 'right' + else: + side = 'left' + pt, po = nibbles(self.data[addr+1]) + pos = hto(ph,pt,po) + + return {'position':pos, 'side':side} + + def nextRow(self): + """ + Returns the row data from the memory locations which + store the next row to be knitted + """ + return self.getRowData(nextRowAddr, 200, 0) + + # Debugging and developmental things - these are hardcoded for now + def unknownOne(self): + info = array.array('B') + for i in range(0x06E0, 0x06E5): + info.append(ord(self.data[i])) + return info + + def unknownMemoRange(self): + info = array.array('B') + for i in range(0x0731, 0x0787): + info.append(ord(self.data[i])) + return info + + def unknownEndRange(self): + info = array.array('B') + for i in range(0x07D0, 0x07E9): + info.append(ord(self.data[i])) + return info + + def unknownAddrs(self): + return unknownList.items() + diff --git a/file-analysis/compare.py b/file-analysis/compare.py new file mode 100644 index 0000000..9f9f138 --- /dev/null +++ b/file-analysis/compare.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Copyright 2010 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# +# Compare two files and dump the differences +# + +import sys +if len(sys.argv) < 3: + print 'Usage: %s fileA fileB' % sys.argv[0] + sys.exit() + +f1 = open(sys.argv[1]) +f2 = open(sys.argv[2]) + +d1 = f1.read(2048) +d2 = f2.read(2048) + +inrun = False +for i in range(len(d1)): + if d1[i] != d2[i]: + if not inrun: + print '--' + inrun = True + print '%04X: %02X %02X | %03d %03d' % (i, ord(d1[i]), ord(d2[i]), ord(d1[i]), ord(d2[i])) + else: + inrun = False + +f1.close() +f2.close() diff --git a/file-analysis/diff.py b/file-analysis/diff.py new file mode 100644 index 0000000..296b59f --- /dev/null +++ b/file-analysis/diff.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +# Copyright 2010 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys +import brother + +if len(sys.argv) < 3: + print 'Usage: %s fileA fileB' % sys.argv[0] + sys.exit() + +b1 = brother.brotherFile(sys.argv[1]) +b2 = brother.brotherFile(sys.argv[2]) + +# Selector Mode +# Pattern Start +# Motif information +# Custom pattern list, patterns, memo info +# Memo data +# Pattern Number +# Current Row Number +# Unknowns from 0x0700 to 0x0715 +# Unknowns from 0x07D0 to 0x07E9 + +sel1 = b1.selectorValue() +sel2 = b2.selectorValue() + +print 'Selector %4d %4d' % (sel1, sel2) + +pn1 = b1.patternNumber() +pn2 = b2.patternNumber() +print 'Pattern # %4d %4d' % (pn1, pn2) + +memo1 = b1.getMemo() +memo2 = b2.getMemo() +print 'memo len %4d %4d' % (len(memo1), len(memo2)) + +rn1 = b1.rowNumber() +rn2 = b2.rowNumber() +print 'row number %4d %4d' % (rn1, rn2) + +cs1 = b1.carriageStatus() +cs2 = b2.carriageStatus() +print 'carr status 0x%02X 0x%02X' % (cs1, cs2) + +md1 = b1.motifData() +md2 = b2.motifData() +for i in range(len(md1)): + one = md1[i] + two = md2[i] + print 'mot %2d pos %4d %s %4d %s' % (i+1, one['position'], one['side'], two['position'], two['side']), + print 'mot %2d cnt %4d %4d' % (i+1, one['copies'], two['copies']) +print + +pp1 = b1.patternPosition() +pp2 = b2.patternPosition() +print 'pattern pos %4d %s %4d %s' % (pp1['position'], pp1['side'], pp2['position'], pp2['side']) + +print + +uk1 = b1.unknownOne() +uk2 = b2.unknownOne() +print 'Unknown One:' +for i in range(len(uk1)): + if uk1[i] or uk2[i]: + print ' %4d %4d' % (uk1[i], uk2[i]), + if (uk1[i] != uk2[i]): + print ' <======' + else: + print + +print 'Unknown Memo Range:' +uk1 = b1.unknownMemoRange() +uk2 = b2.unknownMemoRange() +for i in range(len(uk1)): + if uk1[i] or uk2[i]: + print ' %4d %4d' % (uk1[i], uk2[i]), + if (uk1[i] != uk2[i]): + print ' <======' + else: + print + +print 'Unknown End Range:' +uk1 = b1.unknownEndRange() +uk2 = b2.unknownEndRange() +for i in range(len(uk1)): + if uk1[i] or uk2[i]: + print ' %4d %4d' % (uk1[i], uk2[i]), + if (uk1[i] != uk2[i]): + print ' <======' + else: + print + +print 'Individual unknowns:' +b1lst = b1.unknownAddrs() +b2lst = b2.unknownAddrs() +for i in range(len(b1lst)): + lt = b1lst[i] + lval = b1.getIndexedByte(lt[1]) + rval = b2.getIndexedByte(lt[1]) + print '0x%s 0x%02X 0x%02X (%3d) (%3d)' % (lt[0], lval, rval, lval, rval), + if lval != rval: + print '<----' + else: + print + diff --git a/file-analysis/dumprows.py b/file-analysis/dumprows.py new file mode 100644 index 0000000..2250a6c --- /dev/null +++ b/file-analysis/dumprows.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python + +# Copyright 2009 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys +import brother + +version = '1.0' + +if len(sys.argv) < 2: + print 'Usage: %s file [start] [end]' % sys.argv[0] + print 'Dumps both rows of needle data from brother data files' + print 'Optional start and end needle numbers, default to 1 and 200' + sys.exit() + +leftend = 0 +rightend = 200 +if len(sys.argv) == 3: + leftend = int(sys.argv[2]) +if len(sys.argv) == 4: + leftend = int(sys.argv[2]) + rightend = int(sys.argv[3]) + + +bf = brother.brotherFile(sys.argv[1]) + +currentrow = bf.currentRow() +nextrow = bf.nextRow() + +print ' Next:', +for stitch in nextrow[leftend:rightend]: + if(stitch) == 0: + print ' ', + else: + print '*', +print +print 'Current:', +for stitch in currentrow[leftend:rightend]: + if(stitch) == 0: + print ' ', + else: + print '*', +print + diff --git a/file-analysis/filecombine.sh b/file-analysis/filecombine.sh new file mode 100644 index 0000000..a033d74 --- /dev/null +++ b/file-analysis/filecombine.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +cat base/00.dat base/01.dat > base/file01.dat +cat base/02.dat base/03.dat > base/file02.dat +cat base/04.dat base/05.dat > base/file03.dat +cat base/06.dat base/07.dat > base/file04.dat +cat base/08.dat base/09.dat > base/file05.dat +cat base/10.dat base/11.dat > base/file06.dat +cat base/12.dat base/13.dat > base/file07.dat +cat base/14.dat base/15.dat > base/file08.dat +cat base/16.dat base/17.dat > base/file09.dat +cat base/18.dat base/19.dat > base/file10.dat + diff --git a/file-analysis/makecsv.py b/file-analysis/makecsv.py new file mode 100644 index 0000000..30aeab5 --- /dev/null +++ b/file-analysis/makecsv.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +# Copyright 2010 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Take existing files in a base directory and convert them +# to a csv file for importing in a spreadsheet + +import sys +#import os +#import os.path +#import string +#from array import * + +if len(sys.argv) < 2: + print 'Usage: %s basedir' % sys.argv[0] + sys.exit() + +f = [] +d = [] + +for idx in range(10): + fname = sys.argv[1] + "/file-%02d.dat" % (idx + 1) + #print "trying %s" % fname + try: + fd = open(fname) + f.append(fd) + d.append(fd.read(2048)) + fd.close() + except IOError: + break + +# Now dump to a csv + +# Header row +print 'Address, Notes', +for fnum in range(len(f)): + print ', file-%02d' % (fnum+1), +print "" + +# Spare rows +print ',' +print ',' +print ',' + + + +# data rows +for i in range(2048): + print '0x%04X,' % i, + # Add comment fields here + for fnum in range(len(f)): + print ', 0x%02X' % ord(d[fnum][i]), + print "" diff --git a/file-analysis/motif-memory.ods b/file-analysis/motif-memory.ods new file mode 100644 index 0000000000000000000000000000000000000000..902f4f3c157636cb6ee856405e4cf21a457efad2 GIT binary patch literal 12909 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 RQo~35wh^;2CE)7r{{YFAikkoc literal 0 HcmV?d00001 diff --git a/insertpattern.py b/insertpattern.py new file mode 100644 index 0000000..c6df7b7 --- /dev/null +++ b/insertpattern.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python + +# Copyright 2009 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys +import brother +import Image +import array + +TheImage = None + +################## + +def roundeven(val): + return (val+(val%2)) + +def roundeight(val): + if val % 8: + return val + (8-(val%8)) + else: + return val + +def roundfour(val): + if val % 4: + return val + (4-(val%4)) + else: + return val + +def nibblesPerRow(stitches): + # there are four stitches per nibble + # each row is nibble aligned + return(roundfour(stitches)/4) + +def bytesPerPattern(stitches, rows): + nibbs = rows * nibblesPerRow(stitches) + bytes = roundeven(nibbs)/2 + return bytes + +def bytesForMemo(rows): + bytes = roundeven(rows)/2 + return bytes + +############## + + +version = '1.0' + +if len(sys.argv) < 5: + print 'Usage: %s oldbrotherfile pattern# image.bmp newbrotherfile' % sys.argv[0] + sys.exit() + + +bf = brother.brotherFile(sys.argv[1]) +pattnum = sys.argv[2] +imgfile = sys.argv[3] + + +pats = bf.getPatterns() + +# ok got a bank, now lets figure out how big this thing we want to insert is +TheImage = Image.open(imgfile) +TheImage.load() + +im_size = TheImage.size +width = im_size[0] +print "width:",width +height = im_size[1] +print "height:", height + + + +# find the program entry +thePattern = None + +for pat in pats: + if (int(pat["number"]) == int(pattnum)): + #print "found it!" + thePattern = pat +if (thePattern == None): + print "Pattern #",pattnum,"not found!" + exit(0) + +if (height != thePattern["rows"] or width != thePattern["stitches"]): + print "Pattern is the wrong size, the BMP is ",height,"x",width,"and the pattern is ",thePattern["rows"], "x", thePattern["stitches"] + exit(0) + +# debugging stuff here +x = 0 +y = 0 + +x = width - 1 +while x > 0: + value = TheImage.getpixel((x,y)) + if value: + sys.stdout.write('* ') + else: + sys.stdout.write(' ') + #sys.stdout.write(str(value)) + x = x-1 + if x == 0: #did we hit the end of the line? + y = y+1 + x = width - 1 + print " " + if y == height: + break +# debugging stuff done + +# now to make the actual, yknow memo+pattern data + +# the memo seems to be always blank. i have no idea really +memoentry = [] +for i in range(bytesForMemo(height)): + memoentry.append(0x0) + +# now for actual real live pattern data! +pattmemnibs = [] +for r in range(height): + row = [] # we'll chunk in bits and then put em into nibbles + for s in range(width): + value = TheImage.getpixel((width-s-1,height-r-1)) + if (value != 0): + row.append(1) + else: + row.append(0) + #print row + # turn it into nibz + for s in range(roundfour(width) / 4): + n = 0 + for nibs in range(4): + #print "row size = ", len(row), "index = ",s*4+nibs + + if (len(row) == (s*4+nibs)): + break # padding! + + if (row[s*4 + nibs]): + n |= 1 << nibs + pattmemnibs.append(n) + #print hex(n), + + +if (len(pattmemnibs) % 2): + # odd nibbles, buffer to a byte + pattmemnibs.append(0x0) + +#print len(pattmemnibs), "nibbles of data" + +# turn into bytes +pattmem = [] +for i in range (len(pattmemnibs) / 2): + pattmem.append( pattmemnibs[i*2] | (pattmemnibs[i*2 + 1] << 4)) + +#print map(hex, pattmem) +# whew. + + +# now to insert this data into the file + +# now we have to figure out the -end- of the last pattern is +endaddr = 0x6df + +beginaddr = thePattern["pattend"] +endaddr = beginaddr + bytesForMemo(height) + len(pattmem) +print "beginning will be at ", hex(beginaddr), "end at", hex(endaddr) + +# Note - It's note certain that in all cases this collision test is needed. What's happening +# when you write below this address (as the pattern grows downward in memory) in that you begin +# to overwrite the pattern index data that starts at low memory. Since you overwrite the info +# for highest memory numbers first, you may be able to get away with it as long as you don't +# attempt to use higher memories. +# Steve + +if beginaddr <= 0x2B8: + print "sorry, this will collide with the pattern entry data since %s is <= 0x2B8!" % hex(beginaddr) + #exit + +# write the memo and pattern entry from the -end- to the -beginning- (up!) +for i in range(len(memoentry)): + bf.setIndexedByte(endaddr, 0) + endaddr -= 1 + +for i in range(len(pattmem)): + bf.setIndexedByte(endaddr, pattmem[i]) + endaddr -= 1 + +# push the data to a file +outfile = open(sys.argv[4], 'wb') + +d = bf.getFullData() +outfile.write(d) +outfile.close() diff --git a/process_image.py b/process_image.py new file mode 100644 index 0000000..5c4cad1 --- /dev/null +++ b/process_image.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +import Image +import sys + +def getprops(): + x = 0 + y = 0 + im_file = Image.open(file_name) + im_file.load() + im_size = im_file.size + width = im_size[0] + print "width:",width + height = im_size[1] + print "height:", height + x = width - 1 + while x > 0: + value = im_file.getpixel((x,y)) + if value: + sys.stdout.write('* ') + else: + sys.stdout.write(' ') + #sys.stdout.write(str(value)) + x = x-1 + if x == 0: #did we hit the end of the line? + y = y+1 + x = width - 1 + print " " + if y == height: + return + +def main(): + getprops() + return + +file_name = "./qr_test.bmp" + +main() + + +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 diff --git a/splitfile2track.py b/splitfile2track.py new file mode 100644 index 0000000..75a0f4e --- /dev/null +++ b/splitfile2track.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +import sys + + +if len(sys.argv) != 2: + print 'Usage: %s file.dat' % sys.argv[0] + print 'Splits a 2K file.dat file into two 1K track files track0.dat and track1.dat' + sys.exit() + + + +infile = open(sys.argv[1], 'rb') + +track0file = open("track0.dat", 'wb') +track1file = open("track1.dat", 'wb') + +t0dat = infile.read(1024) +t1dat = infile.read(1024) + + +track0file.write(t0dat) +track0file.close() + +track1file.write(t1dat) +track1file.close() diff --git a/textconversion/maketext.sh b/textconversion/maketext.sh new file mode 100644 index 0000000..f8b7a05 --- /dev/null +++ b/textconversion/maketext.sh @@ -0,0 +1,25 @@ +#/usr/bin/bash + +# Copyright 2010 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +convert -size 280x40 xc:white -colors 2 +antialias -font courier -pointsize 40 -draw "text 10,32 'Hello World'" -rotate -90 testa.png + +#convert -size 280x40 xc:white -colors 2 +antialias -font courier -pointsize 40 -draw "text 10,32 'Hello World'" testa.png + +#convert -size 460x72 xc:white -colors 2 +antialias -font Bookman-DemiItalic -pointsize 72 -draw "text 10,60 'Hello World'" testa.png + From ec36be7fadff7f5f6093640875f07df382427eec Mon Sep 17 00:00:00 2001 From: ondro Date: Mon, 22 Jul 2013 22:26:40 +0200 Subject: [PATCH 02/20] PDD emulator in tk application --- .gitignore | 3 + app/config.py | 7 + app/main.py | 58 ++++ app/pdd/PDDemulate.py | 638 ++++++++++++++++++++++++++++++++++++++++++ app/pdd/__init__.py | 0 5 files changed, 706 insertions(+) create mode 100644 app/config.py create mode 100644 app/main.py create mode 100644 app/pdd/PDDemulate.py create mode 100644 app/pdd/__init__.py diff --git a/.gitignore b/.gitignore index f12819f..c943194 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ test-data *.xcf *.png *.dat + +# python files +*.pyc \ No newline at end of file diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..c5da3f5 --- /dev/null +++ b/app/config.py @@ -0,0 +1,7 @@ +class Config: + def __init__(self): + self.imgdir = "img"; +# Linux: +# self.device = ""; +# Windows: + self.device = "com33"; \ No newline at end of file diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..4f22f6f --- /dev/null +++ b/app/main.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +import Tkinter +from pdd import PDDemulate as pdd +import config +import serial + +c = config.Config() + +class KnittingApp(Tkinter.Tk): + + def __init__(self,parent): + Tkinter.Tk.__init__(self,parent) + self.parent = parent + self.initialize() + self.runEmulator() + + def initialize(self): + self.grid() + + self.entry = Tkinter.Entry(self) + self.entry.grid(column=0,row=0,sticky='EW') + + button = Tkinter.Button(self,text=u"Click me !") + button.grid(column=1,row=0) + + label = Tkinter.Label(self, anchor="w",fg="white",bg="blue") + label.grid(column=0,row=1,columnspan=2,sticky='EW') + + self.grid_columnconfigure(0,weight=1) + self.resizable(True,False) + + def runEmulator(self): + print 'Preparing emulator. . . Please Wait' + try: + self.emu = pdd.PDDemulator(c.imgdir) + + self.emu.open(cport=c.device) + print 'PDDemulate Version 1.1 Ready!' + self.after(5, self.runEmulator) + except Exception, e: + print "Exception:", e + self.quitApplication() + + def emulatorLoop(self): + emu.handleRequests() + + def quitApplication(self): + if self.emu is not None: + self.emu.close() + self.after_idle(self.quit) + + +if __name__ == "__main__": + app = KnittingApp(None) + app.title('Knitting pattern uploader') + app.mainloop() \ No newline at end of file diff --git a/app/pdd/PDDemulate.py b/app/pdd/PDDemulate.py new file mode 100644 index 0000000..30d7b68 --- /dev/null +++ b/app/pdd/PDDemulate.py @@ -0,0 +1,638 @@ +#!/usr/bin/env python + +# Copyright 2009 Steve Conklin +# steve at conklinhouse dot com +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +# This software emulates the external floppy disk drive used +# by the Brother Electroknit KH-930E computerized knitting machine. +# It may work for other models, but has only been tested with the +# Brother KH-930E +# +# This emulates the disk drive and stores the saved data from +# the knitting machine on the linux file system. It does not +# read or write floppy disks. +# +# The disk drive used by the brother knitting machine is the same +# as a Tandy PDD1 drive. This software does not support the entire +# command API of the PDD1, only what is required for the knitting +# machine. +# + +# +# Notes about data storage: +# +# The external floppy disk is formatted with 80 sectors of 1024 +# bytes each. These sectors are numbered (internally) from 0-79. +# When starting this emulator, a base directory is specified. +# In this directory the emulator creates 80 files, one for each +# sector. These are kept sync'd with the emulator's internal +# storage of the same sectors. For each sector, there are two +# files, nn.dat, and nn.id, where 00 <= nn <= 79. +# +# The knitting machine uses two sectors for each saved set of +# information, which are referred to in the knitting machine +# manual as 'tracks' (which they were on the floppy disk). Each +# pair of even/odd numbered sectors is a track. Tracks are +# numbered 1-40. The knitting machine always writes sectors +# in even/odd pairs, and when the odd sector is written, both +# sectors are concatenated to a file named fileqq.dat, where +# qq is the sector number. +# + +# The Knitting machine does not parse the returned hex ascii values +# unless they are ALL UPPER CASE. Lower case characters a-f appear +# to parse az zeros. + +# You will need the (very nice) pySerial module, found here: +# http://pyserial.wiki.sourceforge.net/pySerial + +import sys +import os +import os.path +import string +from array import * +import serial + +version = '1.0' + +# +# Note that this code makes a fundamental assumption which +# is only true for the disk format used by the brother knitting +# machine, which is that there is only one logical sector (LS) per +# physical sector (PS). The PS size is fixed at 1280 bytes, and +# the brother uses a LS size of 1024 bytes, so only one can fit. +# + +class DiskSector(): + def __init__(self, fn): + self.sectorSz = 1024 + self.idSz = 12 + self.data = '' + self.id = '' + #self.id = array('c') + + dfn = fn + ".dat" + idfn = fn + ".id" + + try: + try: + self.df = open(dfn, 'r+') + except IOError: + self.df = open(dfn, 'w') + + try: + self.idf = open(idfn, 'r+') + except IOError: + self.idf = open(idfn, 'w') + + dfs = os.path.getsize(dfn) + idfs = os.path.getsize(idfn) + + except: + print 'Unable to open files using base name <%s>' % fn + raise + + try: + if dfs == 0: + # New or empty file + self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) + self.writeDFile() + elif dfs == self.sectorSz: + # Existing file + self.data = self.df.read(self.sectorSz) + else: + print 'Found a data file <%s> with the wrong size' % dfn + raise IOError + except: + print 'Unable to handle data file <%s>' % fn + raise + + try: + if idfs == 0: + # New or empty file + self.id = ''.join([chr(0) for num in xrange(self.idSz)]) + self.writeIdFile() + elif idfs == self.idSz: + # Existing file + self.id = self.idf.read(self.idSz) + else: + print 'Found an ID file <%s> with the wrong size, is %d should be %d' % (idfn, idfs, self.idSz) + raise IOError + except: + print 'Unable to handle id file <%s>' % fn + raise + + return + + def __del__(self): + return + + def format(self): + self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) + self.writeDFile() + self.id = ''.join([chr(0) for num in xrange(self.idSz)]) + self.writeIdFile() + + def writeDFile(self): + self.df.seek(0) + self.df.write(self.data) + self.df.flush() + return + + def writeIdFile(self): + self.idf.seek(0) + self.idf.write(self.id) + self.idf.flush() + return + + def read(self, length): + if length != self.sectorSz: + print 'Error, read of %d bytes when expecting %d' % (length, self.sectorSz) + raise IOError + return self.data + + def write(self, indata): + if len(indata) != self.sectorSz: + print 'Error, write of %d bytes when expecting %d' % (len(indata), self.sectorSz) + raise IOError + self.data = indata + self.writeDFile() + return + + def getSectorId(self): + return self.id + + def setSectorId(self, newid): + if len(newid) != self.idSz: + print 'Error, bad id length of %d bytes when expecting %d' % (len(newid), self.id) + raise IOError + self.id = newid + self.writeIdFile() + print 'Wrote New ID: ', + self.dumpId() + return + + def dumpId(self): + for i in self.id: + print '%02X ' % ord(i), + print + +class Disk(): + def __init__(self, basename): + self.numSectors = 80 + self.Sectors = [] + self.filespath = "" + # Set up disk Files and internal buffers + + # if absolute path, just accept it + if os.path.isabs(basename): + dirpath = basename + else: + dirpath = os.path.abspath(basename) + + if os.path.exists(dirpath): + if not os.access(dirpath, os.R_OK | os.W_OK): + print 'Directory <%s> exists but cannot be accessed, check permissions' % dirpath + raise IOError + elif not os.path.isdir(dirpath): + print 'Specified path <%s> exists but is not a directory' % dirpath + raise IOError + else: + try: + os.mkdir(dirpath) + except: + print 'Unable to create directory <%s>' % dirpath + raise IOError + + self.filespath = dirpath + # we have a directory now - set up disk sectors + for i in range(self.numSectors): + fname = os.path.join(dirpath, '%02d' % i) + ds = DiskSector(fname) + self.Sectors.append(ds) + return + + def __del__(self): + return + + def format(self): + for i in range(self.numSectors): + self.Sectors[i].format() + return + + def findSectorID(self, psn, id): + for i in range(psn, self.numSectors): + sid = self.Sectors[i].getSectorId() + if id == sid: + return '00' + '%02X' % i + '0000' + return '40000000' + + def getSectorID(self, psn): + return self.Sectors[psn].getSectorId() + + def setSectorID(self, psn, id): + self.Sectors[psn].setSectorId(id) + return + + def writeSector(self, psn, lsn, indata): + self.Sectors[psn].write(indata) + if psn % 2: + filenum = ((psn-1)/2)+1 + filename = 'file-%02d.dat' % filenum + # we wrote an odd sector, so create the + # associated file + fn1 = os.path.join(self.filespath, '%02d.dat' % (psn-1)) + fn2 = os.path.join(self.filespath, '%02d.dat' % psn) + outfn = os.path.join(self.filespath, filename) + cmd = 'cat %s %s > %s' % (fn1, fn2, outfn) + os.system(cmd) + return + + def readSector(self, psn, lsn): + return self.Sectors[psn].read(1024) + +class PDDemulator(): + + def __init__(self, basename): + self.verbose = True + self.noserial = False + self.ser = None + self.disk = Disk(basename) + self.FDCmode = False + # bytes per logical sector + self.bpls = 1024 + self.formatLength = {'0':64, '1':80, '2': 128, '3': 256, '4': 512, '5': 1024, '6': 1280} + return + + def __del__(self): + return + + def open(self, cport='/dev/ttyUSB0'): + if self.noserial is False: + self.ser = serial.Serial(port=cport, baudrate=9600, parity='N', stopbits=1, timeout=1, xonxoff=0, rtscts=0, dsrdtr=0) +# self.ser.setRTS(True) + if self.ser == None: + print 'Unable to open serial device %s' % cport + raise IOError + return + + def close(self): + if self.noserial is not False: + if ser: + ser.close() + return + + def dumpchars(self): + num = 1 + while 1: + inc = self.ser.read() + if len(inc) != 0: + print 'flushed 0x%02X (%d)' % (ord(inc), num) + num = num + 1 + else: + break + return + + def readsomechars(self, num): + sch = self.ser.read(num) + return sch + + def readchar(self): + inc = '' + while len(inc) == 0: + inc = self.ser.read() + return inc + + def writebytes(self, bytes): + self.ser.write(bytes) + return + + def readFDDRequest(self): + inbuf = [] + # read through a carriage return + # parameters are seperated by commas + while 1: + inc = self.readchar() + if inc == '\r': + break + elif inc == ' ': + continue + else: + inbuf.append(inc) + + all = string.join(inbuf, '') + rv = all.split(',') + return rv + + def getPsnLsn(self, info): + psn = 0 + lsn = 1 + if len(info) >= 1 and info[0] != '': + val = int(info[0]) + if psn <= 79: + psn = val + if len(info) > 1 and info[1] != '': + val = int(info[0]) + return psn, lsn + + def readOpmodeRequest(self, req): + buff = array('b') + sum = req + reqlen = ord(self.readchar()) + buff.append(reqlen) + sum = sum + reqlen + + for x in range(reqlen, 0, -1): + rb = ord(self.readchar()) + buff.append(rb) + sum = sum + rb + + # calculate ckecksum + sum = sum % 0x100 + sum = sum ^ 0xFF + + cksum = ord(self.readchar()) + + if cksum == sum: + return buff + else: + if self.verbose: + print 'Checksum mismatch!!' + return None + + def handleRequests(self): + synced = False + while True: + inc = self.readchar() + if self.FDCmode: + self.handleFDCmodeRequest(inc) + else: + # in OpMode, look for ZZ + #inc = self.readchar() + if inc != 'Z': + continue + inc = self.readchar() + if inc == 'Z': + self.handleOpModeRequest() + # never returns + return + + def handleOpModeRequest(self): + req = ord(self.ser.read()) + print 'Request: 0X%02X' % req + if req == 0x08: + # Change to FDD emulation mode (no data returned) + inbuf = self.readOpmodeRequest(req) + if inbuf != None: + # Change Modes, leave any incoming serial data in buffer + self.FDCmode = True + else: + print 'Invalid OpMode request code 0X%02X received' % req + return + + def handleFDCmodeRequest(self, cmd): + # Commands may be followed by an optional space + # PSN (physical sector) range 0-79 + # LSN (logical sector) range 0-(number of logical sectors in a physical sector) + # LSN defaults to 1 if not supplied + # + # Result code information (verbatim from the Tandy reference): + # + # After the drive receives a command in FDC-emulation mode, it transmits + # 8 byte characters which represent 4 bytes of status code in hexadecimal. + # + # * The first and second bytes contain the error status. A value of '00' + # indicates that no error occurred + # + # * The third and fourth bytes usually contain the number of the physical + # sector where data is kept in the buffer + # + # For the D, F, and S commands, the contents of these bytes are different. + # See the command descriptions in these cases. + # + # * The fifth-eighth bytes usual show the logical sector length of the data + # kept in the RAM buffer, except the third and fourth digits are 'FF' + # + # In the case of an S, C, or M command -- or an F command that ends in + # an error -- the bytes contain '0000' + # + + if cmd == '\r': + return + + if cmd == 'Z': + # Hmmm, looks like we got the start of an Opmode Request + inc = self.readchar() + if inc == 'Z': + # definitely! + print 'Detected Opmode Request in FDC Mode, switching to OpMode' + self.FDCmode = False + self.handleOpModeRequest() + + elif cmd == 'M': + # apparently not used by brother knitting machine + print 'FDC Change Modes' + raise + # following parameter - 0=FDC, 1=Operating + + elif cmd == 'D': + # apparently not used by brother knitting machine + print 'FDC Check Device' + raise + # Sends result in third and fourth bytes of result code + # See doc - return zero for disk installed and not swapped + + elif cmd == 'F'or cmd == 'G': + #rint 'FDC Format', + info = self.readFDDRequest() + + if len(info) != 1: + print 'wrong number of params (%d) received, assuming 1024 bytes per sector' % len(info) + bps = 1024 + else: + try: + bps = self.formatLength[info[0]] + except KeyError: + print 'Invalid code %c for format, assuming 1024 bytes per sector' % info[0] + bps = 1024 + # we assume 1024 because that's what the brother machine uses + if self.bpls != bps: + print 'Bad news, differing sector sizes' + self.bpls = bps + + self.disk.format() + + # But this is probably more correct + self.writebytes('00000000') + + # After a format, we always start out with OPMode again + self.FDCmode = False + + elif cmd == 'A': + # Followed by physical sector number (0-79), defaults to 0 + # returns ID data, not sector data + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Read ID Section %d' % psn + + try: + id = self.disk.getSectorID(psn) + except: + print 'Error getting Sector ID %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + # see whether to send data + go = self.readchar() + if go == '\r': + self.writebytes(id) + + elif cmd == 'R': + # Followed by Physical Sector Number PSN and Logical Sector Number LSN + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Read one Logical Sector %d' % psn + + try: + sd = self.disk.readSector(psn, lsn) + except: + print 'Failed to read Sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + # see whether to send data + go = self.readchar() + if go == '\r': + self.writebytes(sd) + + elif cmd == 'S': + # We receive (optionally) PSN, (optionally) LSN + # This is not documented well at all in the manual + # What is expected is that all sectors will be searched + # and the sector number of the first matching sector + # will be returned. The brother machine always sends + # PSN = 0, so it is unknown whether searching should + # start at Sector 0 or at the PSN sector + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Search ID Section %d' % psn + + # Now we must send status (success) + self.writebytes('00' + '%02X' % psn + '0000') + + #self.writebytes('00000000') + + # we receive 12 bytes here + # compare with the specified sector (formatted is apparently zeros) + id = self.readsomechars(12) + print 'checking ID for sector %d' % psn + + try: + status = self.disk.findSectorID(psn, id) + except: + print "FAIL" + status = '30000000' + raise + + print 'returning %s' % status + # guessing - doc is unclear, but says that S always ends in 0000 + # MATCH 00000000 + # MATCH 02000000 + # infinite retries 10000000 + # infinite retries 20000000 + # blinking error 30000000 + # blinking error 40000000 + # infinite retries 50000000 + # infinite retries 60000000 + # infinite retries 70000000 + # infinite retries 80000000 + + self.writebytes(status) + + # Stay in FDC mode + + elif cmd == 'B' or cmd == 'C': + # Followed by PSN 0-79, defaults to 0 + # When received, send result status, if not error, wait + # for data to be written, then after write, send status again + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Write ID section %d' % psn + + self.writebytes('00' + '%02X' % psn + '0000') + + id = self.readsomechars(12) + + try: + self.disk.setSectorID(psn, id) + except: + print 'Failed to write ID for sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + elif cmd == 'W' or cmd == 'X': + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Write logical sector %d' % psn + + # Now we must send status (success) + self.writebytes('00' + '%02X' % psn + '0000') + + indata = self.readsomechars(1024) + try: + self.disk.writeSector(psn, lsn, indata) + except: + print 'Failed to write data for sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + else: + print 'Unknown FDC command <0x02%X> received' % ord(cmd) + + # return to Operational Mode + return + +# meat and potatos here + +if __name__ == "__main__": + if len(sys.argv) < 3: + print '%s version %s' % (sys.argv[0], version) + print 'Usage: %s basedir serialdevice' % sys.argv[0] + sys.exit() + + print 'Preparing . . . Please Wait' + emu = PDDemulator(sys.argv[1]) + + emu.open(cport=sys.argv[2]) + + print 'PDDtmulate Version 1.1 Ready!' + try: + while 1: + emu.handleRequests() + except (KeyboardInterrupt): + pass + + emu.close() diff --git a/app/pdd/__init__.py b/app/pdd/__init__.py new file mode 100644 index 0000000..e69de29 From 1d82edd5340461a65b7103c42fe2a9ac6fde7cbf Mon Sep 17 00:00:00 2001 From: ondro Date: Mon, 22 Jul 2013 22:40:12 +0200 Subject: [PATCH 03/20] Fix of PDD: emulator not blocking event loop --- app/config.py | 2 +- app/main.py | 4 +- app/pdd/PDDemulate.py | 1065 +++++++++++++++++++++-------------------- 3 files changed, 538 insertions(+), 533 deletions(-) diff --git a/app/config.py b/app/config.py index c5da3f5..9d33ac0 100644 --- a/app/config.py +++ b/app/config.py @@ -4,4 +4,4 @@ def __init__(self): # Linux: # self.device = ""; # Windows: - self.device = "com33"; \ No newline at end of file + self.device = "com34"; \ No newline at end of file diff --git a/app/main.py b/app/main.py index 4f22f6f..3ab58a1 100644 --- a/app/main.py +++ b/app/main.py @@ -38,13 +38,13 @@ def runEmulator(self): self.emu.open(cport=c.device) print 'PDDemulate Version 1.1 Ready!' - self.after(5, self.runEmulator) + self.after(5, self.emulatorLoop) except Exception, e: print "Exception:", e self.quitApplication() def emulatorLoop(self): - emu.handleRequests() + self.emu.handleRequest() def quitApplication(self): if self.emu is not None: diff --git a/app/pdd/PDDemulate.py b/app/pdd/PDDemulate.py index 30d7b68..7b682b7 100644 --- a/app/pdd/PDDemulate.py +++ b/app/pdd/PDDemulate.py @@ -79,541 +79,546 @@ # class DiskSector(): - def __init__(self, fn): - self.sectorSz = 1024 - self.idSz = 12 - self.data = '' - self.id = '' - #self.id = array('c') - - dfn = fn + ".dat" - idfn = fn + ".id" - - try: - try: - self.df = open(dfn, 'r+') - except IOError: - self.df = open(dfn, 'w') - - try: - self.idf = open(idfn, 'r+') - except IOError: - self.idf = open(idfn, 'w') - - dfs = os.path.getsize(dfn) - idfs = os.path.getsize(idfn) - - except: - print 'Unable to open files using base name <%s>' % fn - raise - - try: - if dfs == 0: - # New or empty file - self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) - self.writeDFile() - elif dfs == self.sectorSz: - # Existing file - self.data = self.df.read(self.sectorSz) - else: - print 'Found a data file <%s> with the wrong size' % dfn - raise IOError - except: - print 'Unable to handle data file <%s>' % fn - raise - - try: - if idfs == 0: - # New or empty file - self.id = ''.join([chr(0) for num in xrange(self.idSz)]) - self.writeIdFile() - elif idfs == self.idSz: - # Existing file - self.id = self.idf.read(self.idSz) - else: - print 'Found an ID file <%s> with the wrong size, is %d should be %d' % (idfn, idfs, self.idSz) - raise IOError - except: - print 'Unable to handle id file <%s>' % fn - raise - - return - - def __del__(self): - return - - def format(self): - self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) - self.writeDFile() - self.id = ''.join([chr(0) for num in xrange(self.idSz)]) - self.writeIdFile() - - def writeDFile(self): - self.df.seek(0) - self.df.write(self.data) - self.df.flush() - return - - def writeIdFile(self): - self.idf.seek(0) - self.idf.write(self.id) - self.idf.flush() - return - - def read(self, length): - if length != self.sectorSz: - print 'Error, read of %d bytes when expecting %d' % (length, self.sectorSz) - raise IOError - return self.data - - def write(self, indata): - if len(indata) != self.sectorSz: - print 'Error, write of %d bytes when expecting %d' % (len(indata), self.sectorSz) - raise IOError - self.data = indata - self.writeDFile() - return - - def getSectorId(self): - return self.id - - def setSectorId(self, newid): - if len(newid) != self.idSz: - print 'Error, bad id length of %d bytes when expecting %d' % (len(newid), self.id) - raise IOError - self.id = newid - self.writeIdFile() - print 'Wrote New ID: ', - self.dumpId() - return - - def dumpId(self): - for i in self.id: - print '%02X ' % ord(i), - print + def __init__(self, fn): + self.sectorSz = 1024 + self.idSz = 12 + self.data = '' + self.id = '' + #self.id = array('c') + + dfn = fn + ".dat" + idfn = fn + ".id" + + try: + try: + self.df = open(dfn, 'r+') + except IOError: + self.df = open(dfn, 'w') + + try: + self.idf = open(idfn, 'r+') + except IOError: + self.idf = open(idfn, 'w') + + dfs = os.path.getsize(dfn) + idfs = os.path.getsize(idfn) + + except: + print 'Unable to open files using base name <%s>' % fn + raise + + try: + if dfs == 0: + # New or empty file + self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) + self.writeDFile() + elif dfs == self.sectorSz: + # Existing file + self.data = self.df.read(self.sectorSz) + else: + print 'Found a data file <%s> with the wrong size' % dfn + raise IOError + except: + print 'Unable to handle data file <%s>' % fn + raise + + try: + if idfs == 0: + # New or empty file + self.id = ''.join([chr(0) for num in xrange(self.idSz)]) + self.writeIdFile() + elif idfs == self.idSz: + # Existing file + self.id = self.idf.read(self.idSz) + else: + print 'Found an ID file <%s> with the wrong size, is %d should be %d' % (idfn, idfs, self.idSz) + raise IOError + except: + print 'Unable to handle id file <%s>' % fn + raise + + return + + def __del__(self): + return + + def format(self): + self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) + self.writeDFile() + self.id = ''.join([chr(0) for num in xrange(self.idSz)]) + self.writeIdFile() + + def writeDFile(self): + self.df.seek(0) + self.df.write(self.data) + self.df.flush() + return + + def writeIdFile(self): + self.idf.seek(0) + self.idf.write(self.id) + self.idf.flush() + return + + def read(self, length): + if length != self.sectorSz: + print 'Error, read of %d bytes when expecting %d' % (length, self.sectorSz) + raise IOError + return self.data + + def write(self, indata): + if len(indata) != self.sectorSz: + print 'Error, write of %d bytes when expecting %d' % (len(indata), self.sectorSz) + raise IOError + self.data = indata + self.writeDFile() + return + + def getSectorId(self): + return self.id + + def setSectorId(self, newid): + if len(newid) != self.idSz: + print 'Error, bad id length of %d bytes when expecting %d' % (len(newid), self.id) + raise IOError + self.id = newid + self.writeIdFile() + print 'Wrote New ID: ', + self.dumpId() + return + + def dumpId(self): + for i in self.id: + print '%02X ' % ord(i), + print class Disk(): - def __init__(self, basename): - self.numSectors = 80 - self.Sectors = [] - self.filespath = "" - # Set up disk Files and internal buffers - - # if absolute path, just accept it - if os.path.isabs(basename): - dirpath = basename - else: - dirpath = os.path.abspath(basename) - - if os.path.exists(dirpath): - if not os.access(dirpath, os.R_OK | os.W_OK): - print 'Directory <%s> exists but cannot be accessed, check permissions' % dirpath - raise IOError - elif not os.path.isdir(dirpath): - print 'Specified path <%s> exists but is not a directory' % dirpath - raise IOError - else: - try: - os.mkdir(dirpath) - except: - print 'Unable to create directory <%s>' % dirpath - raise IOError - - self.filespath = dirpath - # we have a directory now - set up disk sectors - for i in range(self.numSectors): - fname = os.path.join(dirpath, '%02d' % i) - ds = DiskSector(fname) - self.Sectors.append(ds) - return - - def __del__(self): - return - - def format(self): - for i in range(self.numSectors): - self.Sectors[i].format() - return - - def findSectorID(self, psn, id): - for i in range(psn, self.numSectors): - sid = self.Sectors[i].getSectorId() - if id == sid: - return '00' + '%02X' % i + '0000' - return '40000000' - - def getSectorID(self, psn): - return self.Sectors[psn].getSectorId() - - def setSectorID(self, psn, id): - self.Sectors[psn].setSectorId(id) - return - - def writeSector(self, psn, lsn, indata): - self.Sectors[psn].write(indata) - if psn % 2: - filenum = ((psn-1)/2)+1 - filename = 'file-%02d.dat' % filenum - # we wrote an odd sector, so create the - # associated file - fn1 = os.path.join(self.filespath, '%02d.dat' % (psn-1)) - fn2 = os.path.join(self.filespath, '%02d.dat' % psn) - outfn = os.path.join(self.filespath, filename) - cmd = 'cat %s %s > %s' % (fn1, fn2, outfn) - os.system(cmd) - return - - def readSector(self, psn, lsn): - return self.Sectors[psn].read(1024) + def __init__(self, basename): + self.numSectors = 80 + self.Sectors = [] + self.filespath = "" + # Set up disk Files and internal buffers + + # if absolute path, just accept it + if os.path.isabs(basename): + dirpath = basename + else: + dirpath = os.path.abspath(basename) + + if os.path.exists(dirpath): + if not os.access(dirpath, os.R_OK | os.W_OK): + print 'Directory <%s> exists but cannot be accessed, check permissions' % dirpath + raise IOError + elif not os.path.isdir(dirpath): + print 'Specified path <%s> exists but is not a directory' % dirpath + raise IOError + else: + try: + os.mkdir(dirpath) + except: + print 'Unable to create directory <%s>' % dirpath + raise IOError + + self.filespath = dirpath + # we have a directory now - set up disk sectors + for i in range(self.numSectors): + fname = os.path.join(dirpath, '%02d' % i) + ds = DiskSector(fname) + self.Sectors.append(ds) + return + + def __del__(self): + return + + def format(self): + for i in range(self.numSectors): + self.Sectors[i].format() + return + + def findSectorID(self, psn, id): + for i in range(psn, self.numSectors): + sid = self.Sectors[i].getSectorId() + if id == sid: + return '00' + '%02X' % i + '0000' + return '40000000' + + def getSectorID(self, psn): + return self.Sectors[psn].getSectorId() + + def setSectorID(self, psn, id): + self.Sectors[psn].setSectorId(id) + return + + def writeSector(self, psn, lsn, indata): + self.Sectors[psn].write(indata) + if psn % 2: + filenum = ((psn-1)/2)+1 + filename = 'file-%02d.dat' % filenum + # we wrote an odd sector, so create the + # associated file + fn1 = os.path.join(self.filespath, '%02d.dat' % (psn-1)) + fn2 = os.path.join(self.filespath, '%02d.dat' % psn) + outfn = os.path.join(self.filespath, filename) + cmd = 'cat %s %s > %s' % (fn1, fn2, outfn) + os.system(cmd) + return + + def readSector(self, psn, lsn): + return self.Sectors[psn].read(1024) class PDDemulator(): - def __init__(self, basename): - self.verbose = True - self.noserial = False - self.ser = None - self.disk = Disk(basename) - self.FDCmode = False - # bytes per logical sector - self.bpls = 1024 - self.formatLength = {'0':64, '1':80, '2': 128, '3': 256, '4': 512, '5': 1024, '6': 1280} - return - - def __del__(self): - return - - def open(self, cport='/dev/ttyUSB0'): - if self.noserial is False: - self.ser = serial.Serial(port=cport, baudrate=9600, parity='N', stopbits=1, timeout=1, xonxoff=0, rtscts=0, dsrdtr=0) -# self.ser.setRTS(True) - if self.ser == None: - print 'Unable to open serial device %s' % cport - raise IOError - return - - def close(self): - if self.noserial is not False: - if ser: - ser.close() - return - - def dumpchars(self): - num = 1 - while 1: - inc = self.ser.read() - if len(inc) != 0: - print 'flushed 0x%02X (%d)' % (ord(inc), num) - num = num + 1 - else: - break - return - - def readsomechars(self, num): - sch = self.ser.read(num) - return sch - - def readchar(self): - inc = '' - while len(inc) == 0: - inc = self.ser.read() - return inc - - def writebytes(self, bytes): - self.ser.write(bytes) - return - - def readFDDRequest(self): - inbuf = [] - # read through a carriage return - # parameters are seperated by commas - while 1: - inc = self.readchar() - if inc == '\r': - break - elif inc == ' ': - continue - else: - inbuf.append(inc) - - all = string.join(inbuf, '') - rv = all.split(',') - return rv - - def getPsnLsn(self, info): - psn = 0 - lsn = 1 - if len(info) >= 1 and info[0] != '': - val = int(info[0]) - if psn <= 79: - psn = val - if len(info) > 1 and info[1] != '': - val = int(info[0]) - return psn, lsn - - def readOpmodeRequest(self, req): - buff = array('b') - sum = req - reqlen = ord(self.readchar()) - buff.append(reqlen) - sum = sum + reqlen - - for x in range(reqlen, 0, -1): - rb = ord(self.readchar()) - buff.append(rb) - sum = sum + rb - - # calculate ckecksum - sum = sum % 0x100 - sum = sum ^ 0xFF - - cksum = ord(self.readchar()) - - if cksum == sum: - return buff - else: - if self.verbose: - print 'Checksum mismatch!!' - return None - - def handleRequests(self): - synced = False - while True: - inc = self.readchar() - if self.FDCmode: - self.handleFDCmodeRequest(inc) - else: - # in OpMode, look for ZZ - #inc = self.readchar() - if inc != 'Z': - continue - inc = self.readchar() - if inc == 'Z': - self.handleOpModeRequest() - # never returns - return - - def handleOpModeRequest(self): - req = ord(self.ser.read()) - print 'Request: 0X%02X' % req - if req == 0x08: - # Change to FDD emulation mode (no data returned) - inbuf = self.readOpmodeRequest(req) - if inbuf != None: - # Change Modes, leave any incoming serial data in buffer - self.FDCmode = True - else: - print 'Invalid OpMode request code 0X%02X received' % req - return - - def handleFDCmodeRequest(self, cmd): - # Commands may be followed by an optional space - # PSN (physical sector) range 0-79 - # LSN (logical sector) range 0-(number of logical sectors in a physical sector) - # LSN defaults to 1 if not supplied - # - # Result code information (verbatim from the Tandy reference): - # - # After the drive receives a command in FDC-emulation mode, it transmits - # 8 byte characters which represent 4 bytes of status code in hexadecimal. - # - # * The first and second bytes contain the error status. A value of '00' - # indicates that no error occurred - # - # * The third and fourth bytes usually contain the number of the physical - # sector where data is kept in the buffer - # - # For the D, F, and S commands, the contents of these bytes are different. - # See the command descriptions in these cases. - # - # * The fifth-eighth bytes usual show the logical sector length of the data - # kept in the RAM buffer, except the third and fourth digits are 'FF' - # - # In the case of an S, C, or M command -- or an F command that ends in - # an error -- the bytes contain '0000' - # - - if cmd == '\r': - return - - if cmd == 'Z': - # Hmmm, looks like we got the start of an Opmode Request - inc = self.readchar() - if inc == 'Z': - # definitely! - print 'Detected Opmode Request in FDC Mode, switching to OpMode' - self.FDCmode = False - self.handleOpModeRequest() - - elif cmd == 'M': - # apparently not used by brother knitting machine - print 'FDC Change Modes' - raise - # following parameter - 0=FDC, 1=Operating - - elif cmd == 'D': - # apparently not used by brother knitting machine - print 'FDC Check Device' - raise - # Sends result in third and fourth bytes of result code - # See doc - return zero for disk installed and not swapped - - elif cmd == 'F'or cmd == 'G': - #rint 'FDC Format', - info = self.readFDDRequest() - - if len(info) != 1: - print 'wrong number of params (%d) received, assuming 1024 bytes per sector' % len(info) - bps = 1024 - else: - try: - bps = self.formatLength[info[0]] - except KeyError: - print 'Invalid code %c for format, assuming 1024 bytes per sector' % info[0] - bps = 1024 - # we assume 1024 because that's what the brother machine uses - if self.bpls != bps: - print 'Bad news, differing sector sizes' - self.bpls = bps - - self.disk.format() - - # But this is probably more correct - self.writebytes('00000000') - - # After a format, we always start out with OPMode again - self.FDCmode = False - - elif cmd == 'A': - # Followed by physical sector number (0-79), defaults to 0 - # returns ID data, not sector data - info = self.readFDDRequest() - psn, lsn = self.getPsnLsn(info) - print 'FDC Read ID Section %d' % psn - - try: - id = self.disk.getSectorID(psn) - except: - print 'Error getting Sector ID %d, quitting' % psn - self.writebytes('80000000') - raise - - self.writebytes('00' + '%02X' % psn + '0000') - - # see whether to send data - go = self.readchar() - if go == '\r': - self.writebytes(id) - - elif cmd == 'R': - # Followed by Physical Sector Number PSN and Logical Sector Number LSN - info = self.readFDDRequest() - psn, lsn = self.getPsnLsn(info) - print 'FDC Read one Logical Sector %d' % psn - - try: - sd = self.disk.readSector(psn, lsn) - except: - print 'Failed to read Sector %d, quitting' % psn - self.writebytes('80000000') - raise - - self.writebytes('00' + '%02X' % psn + '0000') - - # see whether to send data - go = self.readchar() - if go == '\r': - self.writebytes(sd) - - elif cmd == 'S': - # We receive (optionally) PSN, (optionally) LSN - # This is not documented well at all in the manual - # What is expected is that all sectors will be searched - # and the sector number of the first matching sector - # will be returned. The brother machine always sends - # PSN = 0, so it is unknown whether searching should - # start at Sector 0 or at the PSN sector - info = self.readFDDRequest() - psn, lsn = self.getPsnLsn(info) - print 'FDC Search ID Section %d' % psn - - # Now we must send status (success) - self.writebytes('00' + '%02X' % psn + '0000') - - #self.writebytes('00000000') - - # we receive 12 bytes here - # compare with the specified sector (formatted is apparently zeros) - id = self.readsomechars(12) - print 'checking ID for sector %d' % psn - - try: - status = self.disk.findSectorID(psn, id) - except: - print "FAIL" - status = '30000000' - raise - - print 'returning %s' % status - # guessing - doc is unclear, but says that S always ends in 0000 - # MATCH 00000000 - # MATCH 02000000 - # infinite retries 10000000 - # infinite retries 20000000 - # blinking error 30000000 - # blinking error 40000000 - # infinite retries 50000000 - # infinite retries 60000000 - # infinite retries 70000000 - # infinite retries 80000000 - - self.writebytes(status) - - # Stay in FDC mode - - elif cmd == 'B' or cmd == 'C': - # Followed by PSN 0-79, defaults to 0 - # When received, send result status, if not error, wait - # for data to be written, then after write, send status again - info = self.readFDDRequest() - psn, lsn = self.getPsnLsn(info) - print 'FDC Write ID section %d' % psn - - self.writebytes('00' + '%02X' % psn + '0000') - - id = self.readsomechars(12) - - try: - self.disk.setSectorID(psn, id) - except: - print 'Failed to write ID for sector %d, quitting' % psn - self.writebytes('80000000') - raise - - self.writebytes('00' + '%02X' % psn + '0000') - - elif cmd == 'W' or cmd == 'X': - info = self.readFDDRequest() - psn, lsn = self.getPsnLsn(info) - print 'FDC Write logical sector %d' % psn - - # Now we must send status (success) - self.writebytes('00' + '%02X' % psn + '0000') - - indata = self.readsomechars(1024) - try: - self.disk.writeSector(psn, lsn, indata) - except: - print 'Failed to write data for sector %d, quitting' % psn - self.writebytes('80000000') - raise - - self.writebytes('00' + '%02X' % psn + '0000') - - else: - print 'Unknown FDC command <0x02%X> received' % ord(cmd) - - # return to Operational Mode - return + def __init__(self, basename): + self.verbose = True + self.noserial = False + self.ser = None + self.disk = Disk(basename) + self.FDCmode = False + # bytes per logical sector + self.bpls = 1024 + self.formatLength = {'0':64, '1':80, '2': 128, '3': 256, '4': 512, '5': 1024, '6': 1280} + return + + def __del__(self): + return + + def open(self, cport='/dev/ttyUSB0'): + if self.noserial is False: + self.ser = serial.Serial(port=cport, baudrate=9600, parity='N', stopbits=1, timeout=1, xonxoff=0, rtscts=0, dsrdtr=0) +# self.ser.setRTS(True) + if self.ser == None: + print 'Unable to open serial device %s' % cport + raise IOError + return + + def close(self): + if self.noserial is not False: + if ser: + ser.close() + return + + def dumpchars(self): + num = 1 + while 1: + inc = self.ser.read() + if len(inc) != 0: + print 'flushed 0x%02X (%d)' % (ord(inc), num) + num = num + 1 + else: + break + return + + def readsomechars(self, num): + sch = self.ser.read(num) + return sch + + def readchar(self): + inc = '' + while len(inc) == 0: + inc = self.ser.read() + return inc + + def writebytes(self, bytes): + self.ser.write(bytes) + return + + def readFDDRequest(self): + inbuf = [] + # read through a carriage return + # parameters are seperated by commas + while 1: + inc = self.readchar() + if inc == '\r': + break + elif inc == ' ': + continue + else: + inbuf.append(inc) + + all = string.join(inbuf, '') + rv = all.split(',') + return rv + + def getPsnLsn(self, info): + psn = 0 + lsn = 1 + if len(info) >= 1 and info[0] != '': + val = int(info[0]) + if psn <= 79: + psn = val + if len(info) > 1 and info[1] != '': + val = int(info[0]) + return psn, lsn + + def readOpmodeRequest(self, req): + buff = array('b') + sum = req + reqlen = ord(self.readchar()) + buff.append(reqlen) + sum = sum + reqlen + + for x in range(reqlen, 0, -1): + rb = ord(self.readchar()) + buff.append(rb) + sum = sum + rb + + # calculate ckecksum + sum = sum % 0x100 + sum = sum ^ 0xFF + + cksum = ord(self.readchar()) + + if cksum == sum: + return buff + else: + if self.verbose: + print 'Checksum mismatch!!' + return None + + def handleRequests(self): + synced = False + while True: + self.handleRequest() + # never returns + return + + def handleRequest(self): + if self.ser.inWaiting() == 0: + return + inc = self.readchar() + if self.FDCmode: + self.handleFDCmodeRequest(inc) + else: + # in OpMode, look for ZZ + #inc = self.readchar() + if inc != 'Z': + return + inc = self.readchar() + if inc == 'Z': + self.handleOpModeRequest() + + def handleOpModeRequest(self): + req = ord(self.ser.read()) + print 'Request: 0X%02X' % req + if req == 0x08: + # Change to FDD emulation mode (no data returned) + inbuf = self.readOpmodeRequest(req) + if inbuf != None: + # Change Modes, leave any incoming serial data in buffer + self.FDCmode = True + else: + print 'Invalid OpMode request code 0X%02X received' % req + return + + def handleFDCmodeRequest(self, cmd): + # Commands may be followed by an optional space + # PSN (physical sector) range 0-79 + # LSN (logical sector) range 0-(number of logical sectors in a physical sector) + # LSN defaults to 1 if not supplied + # + # Result code information (verbatim from the Tandy reference): + # + # After the drive receives a command in FDC-emulation mode, it transmits + # 8 byte characters which represent 4 bytes of status code in hexadecimal. + # + # * The first and second bytes contain the error status. A value of '00' + # indicates that no error occurred + # + # * The third and fourth bytes usually contain the number of the physical + # sector where data is kept in the buffer + # + # For the D, F, and S commands, the contents of these bytes are different. + # See the command descriptions in these cases. + # + # * The fifth-eighth bytes usual show the logical sector length of the data + # kept in the RAM buffer, except the third and fourth digits are 'FF' + # + # In the case of an S, C, or M command -- or an F command that ends in + # an error -- the bytes contain '0000' + # + + if cmd == '\r': + return + + if cmd == 'Z': + # Hmmm, looks like we got the start of an Opmode Request + inc = self.readchar() + if inc == 'Z': + # definitely! + print 'Detected Opmode Request in FDC Mode, switching to OpMode' + self.FDCmode = False + self.handleOpModeRequest() + + elif cmd == 'M': + # apparently not used by brother knitting machine + print 'FDC Change Modes' + raise + # following parameter - 0=FDC, 1=Operating + + elif cmd == 'D': + # apparently not used by brother knitting machine + print 'FDC Check Device' + raise + # Sends result in third and fourth bytes of result code + # See doc - return zero for disk installed and not swapped + + elif cmd == 'F'or cmd == 'G': + #rint 'FDC Format', + info = self.readFDDRequest() + + if len(info) != 1: + print 'wrong number of params (%d) received, assuming 1024 bytes per sector' % len(info) + bps = 1024 + else: + try: + bps = self.formatLength[info[0]] + except KeyError: + print 'Invalid code %c for format, assuming 1024 bytes per sector' % info[0] + bps = 1024 + # we assume 1024 because that's what the brother machine uses + if self.bpls != bps: + print 'Bad news, differing sector sizes' + self.bpls = bps + + self.disk.format() + + # But this is probably more correct + self.writebytes('00000000') + + # After a format, we always start out with OPMode again + self.FDCmode = False + + elif cmd == 'A': + # Followed by physical sector number (0-79), defaults to 0 + # returns ID data, not sector data + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Read ID Section %d' % psn + + try: + id = self.disk.getSectorID(psn) + except: + print 'Error getting Sector ID %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + # see whether to send data + go = self.readchar() + if go == '\r': + self.writebytes(id) + + elif cmd == 'R': + # Followed by Physical Sector Number PSN and Logical Sector Number LSN + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Read one Logical Sector %d' % psn + + try: + sd = self.disk.readSector(psn, lsn) + except: + print 'Failed to read Sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + # see whether to send data + go = self.readchar() + if go == '\r': + self.writebytes(sd) + + elif cmd == 'S': + # We receive (optionally) PSN, (optionally) LSN + # This is not documented well at all in the manual + # What is expected is that all sectors will be searched + # and the sector number of the first matching sector + # will be returned. The brother machine always sends + # PSN = 0, so it is unknown whether searching should + # start at Sector 0 or at the PSN sector + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Search ID Section %d' % psn + + # Now we must send status (success) + self.writebytes('00' + '%02X' % psn + '0000') + + #self.writebytes('00000000') + + # we receive 12 bytes here + # compare with the specified sector (formatted is apparently zeros) + id = self.readsomechars(12) + print 'checking ID for sector %d' % psn + + try: + status = self.disk.findSectorID(psn, id) + except: + print "FAIL" + status = '30000000' + raise + + print 'returning %s' % status + # guessing - doc is unclear, but says that S always ends in 0000 + # MATCH 00000000 + # MATCH 02000000 + # infinite retries 10000000 + # infinite retries 20000000 + # blinking error 30000000 + # blinking error 40000000 + # infinite retries 50000000 + # infinite retries 60000000 + # infinite retries 70000000 + # infinite retries 80000000 + + self.writebytes(status) + + # Stay in FDC mode + + elif cmd == 'B' or cmd == 'C': + # Followed by PSN 0-79, defaults to 0 + # When received, send result status, if not error, wait + # for data to be written, then after write, send status again + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Write ID section %d' % psn + + self.writebytes('00' + '%02X' % psn + '0000') + + id = self.readsomechars(12) + + try: + self.disk.setSectorID(psn, id) + except: + print 'Failed to write ID for sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + elif cmd == 'W' or cmd == 'X': + info = self.readFDDRequest() + psn, lsn = self.getPsnLsn(info) + print 'FDC Write logical sector %d' % psn + + # Now we must send status (success) + self.writebytes('00' + '%02X' % psn + '0000') + + indata = self.readsomechars(1024) + try: + self.disk.writeSector(psn, lsn, indata) + except: + print 'Failed to write data for sector %d, quitting' % psn + self.writebytes('80000000') + raise + + self.writebytes('00' + '%02X' % psn + '0000') + + else: + print 'Unknown FDC command <0x02%X> received' % ord(cmd) + + # return to Operational Mode + return # meat and potatos here From 6ff2111ee85187631098b7ba184160e0e5637f64 Mon Sep 17 00:00:00 2001 From: ondro Date: Wed, 24 Jul 2013 21:55:39 +0200 Subject: [PATCH 04/20] Restructuring of application code --- app/gui/Gui.py | 36 +++++++++++++ app/gui/__init__.py | 0 app/main.py | 56 ++------------------- app/{config.py => tkapp/Config.py} | 0 app/tkapp/KnittingApp.py | 81 ++++++++++++++++++++++++++++++ app/tkapp/Messages.py | 3 ++ app/tkapp/__init__.py | 0 7 files changed, 123 insertions(+), 53 deletions(-) create mode 100644 app/gui/Gui.py create mode 100644 app/gui/__init__.py rename app/{config.py => tkapp/Config.py} (100%) create mode 100644 app/tkapp/KnittingApp.py create mode 100644 app/tkapp/Messages.py create mode 100644 app/tkapp/__init__.py diff --git a/app/gui/Gui.py b/app/gui/Gui.py new file mode 100644 index 0000000..5e569e1 --- /dev/null +++ b/app/gui/Gui.py @@ -0,0 +1,36 @@ +import Tkinter + +class Gui: + def initializeMainWindow(self,w): + w.title('Knitting pattern uploader') + w.grid() + + label = Tkinter.Label(w, text=u"Device path (port):") + label.grid(column=0, row=0, sticky='W') + + entryText = Tkinter.StringVar() + w.deviceEntry = Tkinter.Entry(w, textvariable=entryText) + w.deviceEntry.grid(column=1,row=0,sticky='EW') + w.deviceEntry.entryText = entryText + +# ,text=u"Click me !" + caption = Tkinter.StringVar() + self.emuButton = Tkinter.Button(w, textvariable = caption, command = w.emuButtonClicked) + self.emuButton.caption = caption + self.emuButton.grid(column=2,row=0) + self.emuButtonStopped() + + label = Tkinter.Label(w, anchor="w",fg="white",bg="blue") + label.grid(column=0,row=1,columnspan=2,sticky='EW') + + w.grid_columnconfigure(0,weight=1) + w.resizable(True,False) + + def emuButtonStopped(self): + b = self.emuButton + b.caption.set(u"Start emulator...") + + def emuButtonStarted(self): + b = self.emuButton + b.caption.set(u"Stop emulator...") + diff --git a/app/gui/__init__.py b/app/gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/main.py b/app/main.py index 3ab58a1..cf72d16 100644 --- a/app/main.py +++ b/app/main.py @@ -1,58 +1,8 @@ #!/usr/bin/python # -*- coding: UTF-8 -*- -import Tkinter -from pdd import PDDemulate as pdd -import config -import serial - -c = config.Config() - -class KnittingApp(Tkinter.Tk): - - def __init__(self,parent): - Tkinter.Tk.__init__(self,parent) - self.parent = parent - self.initialize() - self.runEmulator() - - def initialize(self): - self.grid() - - self.entry = Tkinter.Entry(self) - self.entry.grid(column=0,row=0,sticky='EW') - - button = Tkinter.Button(self,text=u"Click me !") - button.grid(column=1,row=0) - - label = Tkinter.Label(self, anchor="w",fg="white",bg="blue") - label.grid(column=0,row=1,columnspan=2,sticky='EW') - - self.grid_columnconfigure(0,weight=1) - self.resizable(True,False) - - def runEmulator(self): - print 'Preparing emulator. . . Please Wait' - try: - self.emu = pdd.PDDemulator(c.imgdir) - - self.emu.open(cport=c.device) - print 'PDDemulate Version 1.1 Ready!' - self.after(5, self.emulatorLoop) - except Exception, e: - print "Exception:", e - self.quitApplication() - - def emulatorLoop(self): - self.emu.handleRequest() - - def quitApplication(self): - if self.emu is not None: - self.emu.close() - self.after_idle(self.quit) - - +from tkapp.KnittingApp import KnittingApp + if __name__ == "__main__": - app = KnittingApp(None) - app.title('Knitting pattern uploader') + app = KnittingApp() app.mainloop() \ No newline at end of file diff --git a/app/config.py b/app/tkapp/Config.py similarity index 100% rename from app/config.py rename to app/tkapp/Config.py diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py new file mode 100644 index 0000000..11e7895 --- /dev/null +++ b/app/tkapp/KnittingApp.py @@ -0,0 +1,81 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +from Config import Config +from Messages import Messages +from gui.Gui import Gui +from pdd.PDDemulate import PDDemulator +import Tkinter + +class KnittingApp(Tkinter.Tk): + + def __init__(self,parent=None): + Tkinter.Tk.__init__(self,parent) + self.parent = parent + self.initialize() + #self.startEmulator() + + def initialize(self): + self.msg = Messages() + self._cfg = None + self.gui = Gui() + self.gui.initializeMainWindow(self) + self.deviceEntry.entryText.set(self.getConfig().device) + self.initEmulator() + + def initEmulator(self): + self.emu = PDDemulator(self.getConfig().imgdir) + self.setEmulatorStarted(False) + + def emuButtonClicked(self): + self.getConfig().device = self.deviceEntry.entryText.get() + if self.emu.started: + self.stopEmulator() + else: + self.startEmulator() + + def startEmulator(self): + print 'Preparing emulator. . . Please Wait' + try: + self.emu.open(cport=self.getConfig().device) + print 'PDDemulate Version 1.1 Ready!' + self.setEmulatorStarted(True) + self.after(5, self.emulatorLoop) + except Exception, e: + print "Exception:", e + + self.setEmulatorStarted(False) + + def emulatorLoop(self): + self.emu.handleRequest() + + def stopEmulator(self): + if self.emu is not None: + self.emu.close() + print 'PDDemulate stopped.' + self.setEmulatorStarted(False) + self.initEmulator() + + def quitApplication(self): + self.stopEmulator() + self.after_idle(self.quit) + + def setEmulatorStarted(self, started): + self.emu.started = started + if started: + self.gui.emuButtonStarted() + else: + self.gui.emuButtonStopped() + + def getConfig(self): + if self._cfg is None: + self._cfg = Config() + if not hasattr(self._cfg, "device"): + self._cfg.device = u"" + return self._cfg + + + +if __name__ == "__main__": + app = KnittingApp(None) + app.mainloop() \ No newline at end of file diff --git a/app/tkapp/Messages.py b/app/tkapp/Messages.py new file mode 100644 index 0000000..0489ef1 --- /dev/null +++ b/app/tkapp/Messages.py @@ -0,0 +1,3 @@ +class Messages: + def __init__(self): + pass \ No newline at end of file diff --git a/app/tkapp/__init__.py b/app/tkapp/__init__.py new file mode 100644 index 0000000..e69de29 From f675e74439c6366a8f013eed97d7278fdf07b62c Mon Sep 17 00:00:00 2001 From: ondro Date: Wed, 24 Jul 2013 23:09:51 +0200 Subject: [PATCH 05/20] Listeners on PDD emulator. Show name of file written by emulator --- app/gui/Gui.py | 27 ++++++++++++++++++++++----- app/pdd/PDDemulate.py | 15 +++++++++++++++ app/tkapp/KnittingApp.py | 26 ++++++++++++++++++-------- app/tkapp/Messages.py | 21 +++++++++++++++++++-- 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/app/gui/Gui.py b/app/gui/Gui.py index 5e569e1..a4dcd99 100644 --- a/app/gui/Gui.py +++ b/app/gui/Gui.py @@ -5,23 +5,40 @@ def initializeMainWindow(self,w): w.title('Knitting pattern uploader') w.grid() + row = 0 + label = Tkinter.Label(w, text=u"Device path (port):") - label.grid(column=0, row=0, sticky='W') + label.grid(column=0, row=row, sticky='W') entryText = Tkinter.StringVar() w.deviceEntry = Tkinter.Entry(w, textvariable=entryText) - w.deviceEntry.grid(column=1,row=0,sticky='EW') + w.deviceEntry.grid(column=1,row=row,sticky='EW') w.deviceEntry.entryText = entryText # ,text=u"Click me !" caption = Tkinter.StringVar() self.emuButton = Tkinter.Button(w, textvariable = caption, command = w.emuButtonClicked) self.emuButton.caption = caption - self.emuButton.grid(column=2,row=0) + self.emuButton.grid(column=2,row=row) self.emuButtonStopped() + + row = 1 + + label = Tkinter.Label(w, text=u"Dat file:") + label.grid(column=0, row=row, sticky='W') + + entryText = Tkinter.StringVar() + w.datFileEntry = Tkinter.Entry(w, textvariable=entryText) + w.datFileEntry.grid(column=1,row=row,sticky='EW',columnspan=2) + w.datFileEntry.entryText = entryText + + row = 2 - label = Tkinter.Label(w, anchor="w",fg="white",bg="blue") - label.grid(column=0,row=1,columnspan=2,sticky='EW') + labelText = Tkinter.StringVar() + label = Tkinter.Label(w, anchor="w",fg="white",bg="blue",textvariable=labelText) + label.grid(column=0,row=row,columnspan=3,sticky='EW') + label.caption = labelText + w.infoLabel = label w.grid_columnconfigure(0,weight=1) w.resizable(True,False) diff --git a/app/pdd/PDDemulate.py b/app/pdd/PDDemulate.py index 7b682b7..0033dbd 100644 --- a/app/pdd/PDDemulate.py +++ b/app/pdd/PDDemulate.py @@ -193,10 +193,17 @@ def dumpId(self): print class Disk(): + """ + Fields: + self.lastDatFilePath : string + """ + def __init__(self, basename): self.numSectors = 80 self.Sectors = [] self.filespath = "" + "" + self.lastDatFilePath = None # Set up disk Files and internal buffers # if absolute path, just accept it @@ -261,6 +268,7 @@ def writeSector(self, psn, lsn, indata): outfn = os.path.join(self.filespath, filename) cmd = 'cat %s %s > %s' % (fn1, fn2, outfn) os.system(cmd) + self.lastDatFilePath = outfn return def readSector(self, psn, lsn): @@ -269,6 +277,7 @@ def readSector(self, psn, lsn): class PDDemulator(): def __init__(self, basename): + self.listeners = [] # list of PDDEmulatorListener self.verbose = True self.noserial = False self.ser = None @@ -607,6 +616,8 @@ def handleFDCmodeRequest(self, cmd): indata = self.readsomechars(1024) try: self.disk.writeSector(psn, lsn, indata) + for l in self.listeners: + l.dataReceived(self.disk.lastDatFilePath) except: print 'Failed to write data for sector %d, quitting' % psn self.writebytes('80000000') @@ -620,6 +631,10 @@ def handleFDCmodeRequest(self, cmd): # return to Operational Mode return +class PDDEmulatorListener: + def dataReceived(self, fullFilePath): + pass + # meat and potatos here if __name__ == "__main__": diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py index 11e7895..39aa9c0 100644 --- a/app/tkapp/KnittingApp.py +++ b/app/tkapp/KnittingApp.py @@ -5,6 +5,7 @@ from Messages import Messages from gui.Gui import Gui from pdd.PDDemulate import PDDemulator +from pdd.PDDemulate import PDDEmulatorListener import Tkinter class KnittingApp(Tkinter.Tk): @@ -16,7 +17,7 @@ def __init__(self,parent=None): #self.startEmulator() def initialize(self): - self.msg = Messages() + self.msg = Messages(self) self._cfg = None self.gui = Gui() self.gui.initializeMainWindow(self) @@ -25,8 +26,9 @@ def initialize(self): def initEmulator(self): self.emu = PDDemulator(self.getConfig().imgdir) + self.emu.listeners.append(PDDListener(self)) self.setEmulatorStarted(False) - + def emuButtonClicked(self): self.getConfig().device = self.deviceEntry.entryText.get() if self.emu.started: @@ -35,15 +37,15 @@ def emuButtonClicked(self): self.startEmulator() def startEmulator(self): - print 'Preparing emulator. . . Please Wait' + self.msg.showInfo('Preparing emulator. . . Please Wait') try: - self.emu.open(cport=self.getConfig().device) - print 'PDDemulate Version 1.1 Ready!' + port = self.getConfig().device + self.emu.open(cport=port) + self.msg.showInfo('PDDemulate Version 1.1 Ready!') self.setEmulatorStarted(True) self.after(5, self.emulatorLoop) except Exception, e: - print "Exception:", e - + self.msg.showError('Ensure that TFDI cable is connected to port ' + port + '\n\nError: ' + str(e)) self.setEmulatorStarted(False) def emulatorLoop(self): @@ -52,7 +54,7 @@ def emulatorLoop(self): def stopEmulator(self): if self.emu is not None: self.emu.close() - print 'PDDemulate stopped.' + self.msg.showInfo('PDDemulate stopped.') self.setEmulatorStarted(False) self.initEmulator() @@ -75,6 +77,14 @@ def getConfig(self): return self._cfg +class PDDListener(PDDEmulatorListener): + + def __init__(self, app): + self.app = app + + def dataReceived(self, fullFilePath): + self.app.datFileEntry.entryText.set(fullFilePath) + if __name__ == "__main__": app = KnittingApp(None) diff --git a/app/tkapp/Messages.py b/app/tkapp/Messages.py index 0489ef1..42de04d 100644 --- a/app/tkapp/Messages.py +++ b/app/tkapp/Messages.py @@ -1,3 +1,20 @@ +import tkMessageBox as mb +import sys + class Messages: - def __init__(self): - pass \ No newline at end of file + def __init__(self, knittinApp): + self.app = knittinApp + + def showError(self, msg): + self.clear() + sys.stderr.write('Error: ' + str(msg) + '\n') + mb.showerror('Error:', str(msg)) + + def showInfo(self, msg): + self.clear() + print msg + self.app.infoLabel.caption.set('Info: ' + str(msg)) + + def clear(self): + self.app.infoLabel.caption.set('') + \ No newline at end of file From 97476e28c1ae7bfa89005efe636b0f0d13911c43 Mon Sep 17 00:00:00 2001 From: ondro Date: Thu, 25 Jul 2013 00:00:43 +0200 Subject: [PATCH 06/20] Bug fix in emulator loop --- app/tkapp/Config.py | 10 ++++++---- app/tkapp/KnittingApp.py | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/tkapp/Config.py b/app/tkapp/Config.py index 9d33ac0..2edb339 100644 --- a/app/tkapp/Config.py +++ b/app/tkapp/Config.py @@ -1,7 +1,9 @@ +import os + class Config: def __init__(self): self.imgdir = "img"; -# Linux: -# self.device = ""; -# Windows: - self.device = "com34"; \ No newline at end of file + if os.sys.platform == 'win32': + self.device = "com34" + else: + self.device = "/dev/ttyUSB0" \ No newline at end of file diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py index 39aa9c0..04c8bb4 100644 --- a/app/tkapp/KnittingApp.py +++ b/app/tkapp/KnittingApp.py @@ -43,13 +43,15 @@ def startEmulator(self): self.emu.open(cport=port) self.msg.showInfo('PDDemulate Version 1.1 Ready!') self.setEmulatorStarted(True) - self.after(5, self.emulatorLoop) + self.after_idle(self.emulatorLoop) except Exception, e: self.msg.showError('Ensure that TFDI cable is connected to port ' + port + '\n\nError: ' + str(e)) self.setEmulatorStarted(False) def emulatorLoop(self): - self.emu.handleRequest() + if self.emu.started: + self.emu.handleRequest() + self.after_idle(self.emulatorLoop) def stopEmulator(self): if self.emu is not None: From 32cf227a813162bd2a74a921813831e985236397 Mon Sep 17 00:00:00 2001 From: ondro Date: Thu, 25 Jul 2013 00:24:05 +0200 Subject: [PATCH 07/20] Read datFile from config and change default window size. --- app/gui/Gui.py | 7 ++++--- app/tkapp/Config.py | 3 ++- app/tkapp/KnittingApp.py | 5 ++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/gui/Gui.py b/app/gui/Gui.py index a4dcd99..2ff548a 100644 --- a/app/gui/Gui.py +++ b/app/gui/Gui.py @@ -3,6 +3,7 @@ class Gui: def initializeMainWindow(self,w): w.title('Knitting pattern uploader') + w.geometry("800x200") w.grid() row = 0 @@ -22,7 +23,7 @@ def initializeMainWindow(self,w): self.emuButton.grid(column=2,row=row) self.emuButtonStopped() - row = 1 + row += 1 label = Tkinter.Label(w, text=u"Dat file:") label.grid(column=0, row=row, sticky='W') @@ -32,7 +33,7 @@ def initializeMainWindow(self,w): w.datFileEntry.grid(column=1,row=row,sticky='EW',columnspan=2) w.datFileEntry.entryText = entryText - row = 2 + row += 1 labelText = Tkinter.StringVar() label = Tkinter.Label(w, anchor="w",fg="white",bg="blue",textvariable=labelText) @@ -40,7 +41,7 @@ def initializeMainWindow(self,w): label.caption = labelText w.infoLabel = label - w.grid_columnconfigure(0,weight=1) + w.grid_columnconfigure(1,weight=1) w.resizable(True,False) def emuButtonStopped(self): diff --git a/app/tkapp/Config.py b/app/tkapp/Config.py index 2edb339..806fcda 100644 --- a/app/tkapp/Config.py +++ b/app/tkapp/Config.py @@ -1,9 +1,10 @@ -import os +import os class Config: def __init__(self): self.imgdir = "img"; if os.sys.platform == 'win32': self.device = "com34" + self.datFile = 'c:\\Documents and Settings\\ondro\\VirtualBox shared folder\\knitting\\app\\img\\file-01.dat' else: self.device = "/dev/ttyUSB0" \ No newline at end of file diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py index 04c8bb4..60d1fae 100644 --- a/app/tkapp/KnittingApp.py +++ b/app/tkapp/KnittingApp.py @@ -22,6 +22,7 @@ def initialize(self): self.gui = Gui() self.gui.initializeMainWindow(self) self.deviceEntry.entryText.set(self.getConfig().device) + self.datFileEntry.entryText.set(self.getConfig().datFile) self.initEmulator() def initEmulator(self): @@ -76,6 +77,8 @@ def getConfig(self): self._cfg = Config() if not hasattr(self._cfg, "device"): self._cfg.device = u"" + if not hasattr(self._cfg, "datFile"): + self._cfg.datFile = u"" return self._cfg @@ -89,5 +92,5 @@ def dataReceived(self, fullFilePath): if __name__ == "__main__": - app = KnittingApp(None) + app = KnittingApp() app.mainloop() \ No newline at end of file From 69586ed38f48e7a925abe9f03d7d130716bbf0d4 Mon Sep 17 00:00:00 2001 From: ondro Date: Sat, 27 Jul 2013 01:05:05 +0200 Subject: [PATCH 08/20] Display list of patterns in dat file and bitmap for selected pattern. Changed tabs to 4 spaces. Using now original modified scripts under main folder. Application is executed using guimain.py --- PDDemulate.py | 71 ++-- app/{pdd => }/__init__.py | 0 app/gui/Gui.py | 149 ++++++--- app/pdd/PDDemulate.py | 658 -------------------------------------- app/tkapp/KnittingApp.py | 208 +++++++----- dumppattern.py | 321 +++++++++++-------- app/main.py => guimain.py | 2 +- 7 files changed, 456 insertions(+), 953 deletions(-) rename app/{pdd => }/__init__.py (100%) delete mode 100644 app/pdd/PDDemulate.py rename app/main.py => guimain.py (70%) diff --git a/PDDemulate.py b/PDDemulate.py index 3f1dce0..d749681 100644 --- a/PDDemulate.py +++ b/PDDemulate.py @@ -193,10 +193,17 @@ def dumpId(self): print class Disk(): + """ + Fields: + self.lastDatFilePath : string + """ + def __init__(self, basename): self.numSectors = 80 self.Sectors = [] self.filespath = "" + "" + self.lastDatFilePath = None # Set up disk Files and internal buffers # if absolute path, just accept it @@ -261,6 +268,7 @@ def writeSector(self, psn, lsn, indata): outfn = os.path.join(self.filespath, filename) cmd = 'cat %s %s > %s' % (fn1, fn2, outfn) os.system(cmd) + self.lastDatFilePath = outfn return def readSector(self, psn, lsn): @@ -269,6 +277,7 @@ def readSector(self, psn, lsn): class PDDemulator(): def __init__(self, basename): + self.listeners = [] # list of PDDEmulatorListener self.verbose = True self.noserial = False self.ser = None @@ -378,20 +387,25 @@ def readOpmodeRequest(self, req): def handleRequests(self): synced = False while True: - inc = self.readchar() - if self.FDCmode: - self.handleFDCmodeRequest(inc) - else: - # in OpMode, look for ZZ - #inc = self.readchar() - if inc != 'Z': - continue - inc = self.readchar() - if inc == 'Z': - self.handleOpModeRequest() + self.handleRequest() # never returns return + def handleRequest(self): + if self.ser.inWaiting() == 0: + return + inc = self.readchar() + if self.FDCmode: + self.handleFDCmodeRequest(inc) + else: + # in OpMode, look for ZZ + #inc = self.readchar() + if inc != 'Z': + return + inc = self.readchar() + if inc == 'Z': + self.handleOpModeRequest() + def handleOpModeRequest(self): req = ord(self.ser.read()) print 'Request: 0X%02X' % req @@ -602,6 +616,8 @@ def handleFDCmodeRequest(self, cmd): indata = self.readsomechars(1024) try: self.disk.writeSector(psn, lsn, indata) + for l in self.listeners: + l.dataReceived(self.disk.lastDatFilePath) except: print 'Failed to write data for sector %d, quitting' % psn self.writebytes('80000000') @@ -615,23 +631,28 @@ def handleFDCmodeRequest(self, cmd): # return to Operational Mode return +class PDDEmulatorListener: + def dataReceived(self, fullFilePath): + pass + # meat and potatos here -if len(sys.argv) < 3: - print '%s version %s' % (sys.argv[0], version) - print 'Usage: %s basedir serialdevice' % sys.argv[0] - sys.exit() +if __name__ == "__main__": + if len(sys.argv) < 3: + print '%s version %s' % (sys.argv[0], version) + print 'Usage: %s basedir serialdevice' % sys.argv[0] + sys.exit() -print 'Preparing . . . Please Wait' -emu = PDDemulator(sys.argv[1]) + print 'Preparing . . . Please Wait' + emu = PDDemulator(sys.argv[1]) -emu.open(cport=sys.argv[2]) + emu.open(cport=sys.argv[2]) -print 'PDDtmulate Version 1.1 Ready!' -try: - while 1: - emu.handleRequests() -except (KeyboardInterrupt): - pass + print 'PDDtmulate Version 1.1 Ready!' + try: + while 1: + emu.handleRequests() + except (KeyboardInterrupt): + pass -emu.close() + emu.close() diff --git a/app/pdd/__init__.py b/app/__init__.py similarity index 100% rename from app/pdd/__init__.py rename to app/__init__.py diff --git a/app/gui/Gui.py b/app/gui/Gui.py index 2ff548a..a3476e3 100644 --- a/app/gui/Gui.py +++ b/app/gui/Gui.py @@ -1,54 +1,107 @@ import Tkinter class Gui: - def initializeMainWindow(self,w): - w.title('Knitting pattern uploader') - w.geometry("800x200") - w.grid() + def initializeMainWindow(self,w): + self.initMainWindow(w) - row = 0 - - label = Tkinter.Label(w, text=u"Device path (port):") - label.grid(column=0, row=row, sticky='W') - - entryText = Tkinter.StringVar() - w.deviceEntry = Tkinter.Entry(w, textvariable=entryText) - w.deviceEntry.grid(column=1,row=row,sticky='EW') - w.deviceEntry.entryText = entryText - -# ,text=u"Click me !" - caption = Tkinter.StringVar() - self.emuButton = Tkinter.Button(w, textvariable = caption, command = w.emuButtonClicked) - self.emuButton.caption = caption - self.emuButton.grid(column=2,row=row) - self.emuButtonStopped() + self._maxColumns = 1000 + self._row = 0 + self.createDeviceWidgets() + self.createEmulatorButton() - row += 1 - - label = Tkinter.Label(w, text=u"Dat file:") - label.grid(column=0, row=row, sticky='W') - - entryText = Tkinter.StringVar() - w.datFileEntry = Tkinter.Entry(w, textvariable=entryText) - w.datFileEntry.grid(column=1,row=row,sticky='EW',columnspan=2) - w.datFileEntry.entryText = entryText - - row += 1 - - labelText = Tkinter.StringVar() - label = Tkinter.Label(w, anchor="w",fg="white",bg="blue",textvariable=labelText) - label.grid(column=0,row=row,columnspan=3,sticky='EW') - label.caption = labelText - w.infoLabel = label - - w.grid_columnconfigure(1,weight=1) - w.resizable(True,False) + self._row += 1 + self.createDatFileWidgets() + + self._row += 1 + self.createPatternsPanel() - def emuButtonStopped(self): - b = self.emuButton - b.caption.set(u"Start emulator...") - - def emuButtonStarted(self): - b = self.emuButton - b.caption.set(u"Stop emulator...") - + self._row += 1 + self.createInfoMessagesLabel() + + def initMainWindow(self, mainWindow): + self.mainWindow = mainWindow + self.mainWindow.title('Knitting pattern uploader') + self.mainWindow.geometry("800x400") + self.mainWindow.grid() + self.mainWindow.grid_columnconfigure(1,weight=1) + self.mainWindow.resizable(True,True) + + def createDeviceWidgets(self): + label = Tkinter.Label(self.mainWindow, text=u"Device path (port):") + label.grid(column=0, row=self._row, sticky='W') + + entryText = Tkinter.StringVar() + self.mainWindow.deviceEntry = Tkinter.Entry(self.mainWindow, textvariable=entryText) + self.mainWindow.deviceEntry.grid(column=1,row=self._row,sticky='EW') + self.mainWindow.deviceEntry.entryText = entryText + + def createEmulatorButton(self): + caption = Tkinter.StringVar() + self.emuButton = Tkinter.Button(self.mainWindow, textvariable = caption, command = self.mainWindow.emuButtonClicked) + self.emuButton.caption = caption + self.emuButton.grid(column=2,row=self._row) + self.setEmuButtonStopped() + + def createDatFileWidgets(self): + label = Tkinter.Label(self.mainWindow, text=u"Dat file:") + label.grid(column=0, row=self._row, sticky='W') + + entryText = Tkinter.StringVar() + self.mainWindow.datFileEntry = Tkinter.Entry(self.mainWindow, textvariable=entryText) + self.mainWindow.datFileEntry.grid(column=1,row=self._row,sticky='EW') + self.mainWindow.datFileEntry.entryText = entryText + + self.reloadDatFileButton = Tkinter.Button(self.mainWindow, text=u"Reload file", command = self.mainWindow.reloadDatFileButtonClicked) + self.reloadDatFileButton.grid(column=2,row=self._row,sticky='EW') + + def createInfoMessagesLabel(self): + labelText = Tkinter.StringVar() + label = Tkinter.Label(self.mainWindow, anchor="w",fg="white",bg="blue",textvariable=labelText) + label.grid(column=0,row=self._row,columnspan=self._maxColumns,sticky='EW') + label.caption = labelText + self.mainWindow.infoLabel = label + + def createPatternsPanel(self): + patternFrame = Tkinter.Frame(self.mainWindow) + patternFrame.grid(column=0, row=self._row, columnspan = self._maxColumns,sticky='EW') + patternFrame.grid_columnconfigure(0,weight=1) + patternFrame.grid_columnconfigure(1,weight=1) + + listvar = Tkinter.StringVar() + lb = Tkinter.Listbox(patternFrame, listvariable=listvar, exportselection=0, width=50) + lb.items = ListboxVar(lb, listvar) + lb.grid(column=0, row=0, sticky='EW') + lb.bind('<>', self.mainWindow.patternSelected) + self.mainWindow.patternListBox = lb; + + pc = ExtendedCanvas(patternFrame, bg='white') + pc.grid(column=1, row=0, sticky='EW') + self.mainWindow.patternCanvas = pc + + def setEmuButtonStopped(self): + b = self.emuButton + b.caption.set(u"Start emulator...") + + def setEmuButtonStarted(self): + b = self.emuButton + b.caption.set(u"Stop emulator...") + +class ExtendedCanvas(Tkinter.Canvas): + def getWidth(self): + return int(self.cget('width')) + + def getHeight(self): + return int(self.cget('height')) + + def clear(self): + self.create_rectangle(0,0,self.getWidth(),self.getHeight(), width=0, fill=self.cget('bg')) + +class ListboxVar: + def __init__(self, listbox, stringvar): + self._stringvar = stringvar + self._listbox = listbox + + def set(self, list): + self._listbox.delete(0, Tkinter.END) + for item in list: + self._listbox.insert(Tkinter.END, item) diff --git a/app/pdd/PDDemulate.py b/app/pdd/PDDemulate.py deleted file mode 100644 index 0033dbd..0000000 --- a/app/pdd/PDDemulate.py +++ /dev/null @@ -1,658 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2009 Steve Conklin -# steve at conklinhouse dot com -# -# 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 2 -# 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, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -# This software emulates the external floppy disk drive used -# by the Brother Electroknit KH-930E computerized knitting machine. -# It may work for other models, but has only been tested with the -# Brother KH-930E -# -# This emulates the disk drive and stores the saved data from -# the knitting machine on the linux file system. It does not -# read or write floppy disks. -# -# The disk drive used by the brother knitting machine is the same -# as a Tandy PDD1 drive. This software does not support the entire -# command API of the PDD1, only what is required for the knitting -# machine. -# - -# -# Notes about data storage: -# -# The external floppy disk is formatted with 80 sectors of 1024 -# bytes each. These sectors are numbered (internally) from 0-79. -# When starting this emulator, a base directory is specified. -# In this directory the emulator creates 80 files, one for each -# sector. These are kept sync'd with the emulator's internal -# storage of the same sectors. For each sector, there are two -# files, nn.dat, and nn.id, where 00 <= nn <= 79. -# -# The knitting machine uses two sectors for each saved set of -# information, which are referred to in the knitting machine -# manual as 'tracks' (which they were on the floppy disk). Each -# pair of even/odd numbered sectors is a track. Tracks are -# numbered 1-40. The knitting machine always writes sectors -# in even/odd pairs, and when the odd sector is written, both -# sectors are concatenated to a file named fileqq.dat, where -# qq is the sector number. -# - -# The Knitting machine does not parse the returned hex ascii values -# unless they are ALL UPPER CASE. Lower case characters a-f appear -# to parse az zeros. - -# You will need the (very nice) pySerial module, found here: -# http://pyserial.wiki.sourceforge.net/pySerial - -import sys -import os -import os.path -import string -from array import * -import serial - -version = '1.0' - -# -# Note that this code makes a fundamental assumption which -# is only true for the disk format used by the brother knitting -# machine, which is that there is only one logical sector (LS) per -# physical sector (PS). The PS size is fixed at 1280 bytes, and -# the brother uses a LS size of 1024 bytes, so only one can fit. -# - -class DiskSector(): - def __init__(self, fn): - self.sectorSz = 1024 - self.idSz = 12 - self.data = '' - self.id = '' - #self.id = array('c') - - dfn = fn + ".dat" - idfn = fn + ".id" - - try: - try: - self.df = open(dfn, 'r+') - except IOError: - self.df = open(dfn, 'w') - - try: - self.idf = open(idfn, 'r+') - except IOError: - self.idf = open(idfn, 'w') - - dfs = os.path.getsize(dfn) - idfs = os.path.getsize(idfn) - - except: - print 'Unable to open files using base name <%s>' % fn - raise - - try: - if dfs == 0: - # New or empty file - self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) - self.writeDFile() - elif dfs == self.sectorSz: - # Existing file - self.data = self.df.read(self.sectorSz) - else: - print 'Found a data file <%s> with the wrong size' % dfn - raise IOError - except: - print 'Unable to handle data file <%s>' % fn - raise - - try: - if idfs == 0: - # New or empty file - self.id = ''.join([chr(0) for num in xrange(self.idSz)]) - self.writeIdFile() - elif idfs == self.idSz: - # Existing file - self.id = self.idf.read(self.idSz) - else: - print 'Found an ID file <%s> with the wrong size, is %d should be %d' % (idfn, idfs, self.idSz) - raise IOError - except: - print 'Unable to handle id file <%s>' % fn - raise - - return - - def __del__(self): - return - - def format(self): - self.data = ''.join([chr(0) for num in xrange(self.sectorSz)]) - self.writeDFile() - self.id = ''.join([chr(0) for num in xrange(self.idSz)]) - self.writeIdFile() - - def writeDFile(self): - self.df.seek(0) - self.df.write(self.data) - self.df.flush() - return - - def writeIdFile(self): - self.idf.seek(0) - self.idf.write(self.id) - self.idf.flush() - return - - def read(self, length): - if length != self.sectorSz: - print 'Error, read of %d bytes when expecting %d' % (length, self.sectorSz) - raise IOError - return self.data - - def write(self, indata): - if len(indata) != self.sectorSz: - print 'Error, write of %d bytes when expecting %d' % (len(indata), self.sectorSz) - raise IOError - self.data = indata - self.writeDFile() - return - - def getSectorId(self): - return self.id - - def setSectorId(self, newid): - if len(newid) != self.idSz: - print 'Error, bad id length of %d bytes when expecting %d' % (len(newid), self.id) - raise IOError - self.id = newid - self.writeIdFile() - print 'Wrote New ID: ', - self.dumpId() - return - - def dumpId(self): - for i in self.id: - print '%02X ' % ord(i), - print - -class Disk(): - """ - Fields: - self.lastDatFilePath : string - """ - - def __init__(self, basename): - self.numSectors = 80 - self.Sectors = [] - self.filespath = "" - "" - self.lastDatFilePath = None - # Set up disk Files and internal buffers - - # if absolute path, just accept it - if os.path.isabs(basename): - dirpath = basename - else: - dirpath = os.path.abspath(basename) - - if os.path.exists(dirpath): - if not os.access(dirpath, os.R_OK | os.W_OK): - print 'Directory <%s> exists but cannot be accessed, check permissions' % dirpath - raise IOError - elif not os.path.isdir(dirpath): - print 'Specified path <%s> exists but is not a directory' % dirpath - raise IOError - else: - try: - os.mkdir(dirpath) - except: - print 'Unable to create directory <%s>' % dirpath - raise IOError - - self.filespath = dirpath - # we have a directory now - set up disk sectors - for i in range(self.numSectors): - fname = os.path.join(dirpath, '%02d' % i) - ds = DiskSector(fname) - self.Sectors.append(ds) - return - - def __del__(self): - return - - def format(self): - for i in range(self.numSectors): - self.Sectors[i].format() - return - - def findSectorID(self, psn, id): - for i in range(psn, self.numSectors): - sid = self.Sectors[i].getSectorId() - if id == sid: - return '00' + '%02X' % i + '0000' - return '40000000' - - def getSectorID(self, psn): - return self.Sectors[psn].getSectorId() - - def setSectorID(self, psn, id): - self.Sectors[psn].setSectorId(id) - return - - def writeSector(self, psn, lsn, indata): - self.Sectors[psn].write(indata) - if psn % 2: - filenum = ((psn-1)/2)+1 - filename = 'file-%02d.dat' % filenum - # we wrote an odd sector, so create the - # associated file - fn1 = os.path.join(self.filespath, '%02d.dat' % (psn-1)) - fn2 = os.path.join(self.filespath, '%02d.dat' % psn) - outfn = os.path.join(self.filespath, filename) - cmd = 'cat %s %s > %s' % (fn1, fn2, outfn) - os.system(cmd) - self.lastDatFilePath = outfn - return - - def readSector(self, psn, lsn): - return self.Sectors[psn].read(1024) - -class PDDemulator(): - - def __init__(self, basename): - self.listeners = [] # list of PDDEmulatorListener - self.verbose = True - self.noserial = False - self.ser = None - self.disk = Disk(basename) - self.FDCmode = False - # bytes per logical sector - self.bpls = 1024 - self.formatLength = {'0':64, '1':80, '2': 128, '3': 256, '4': 512, '5': 1024, '6': 1280} - return - - def __del__(self): - return - - def open(self, cport='/dev/ttyUSB0'): - if self.noserial is False: - self.ser = serial.Serial(port=cport, baudrate=9600, parity='N', stopbits=1, timeout=1, xonxoff=0, rtscts=0, dsrdtr=0) -# self.ser.setRTS(True) - if self.ser == None: - print 'Unable to open serial device %s' % cport - raise IOError - return - - def close(self): - if self.noserial is not False: - if ser: - ser.close() - return - - def dumpchars(self): - num = 1 - while 1: - inc = self.ser.read() - if len(inc) != 0: - print 'flushed 0x%02X (%d)' % (ord(inc), num) - num = num + 1 - else: - break - return - - def readsomechars(self, num): - sch = self.ser.read(num) - return sch - - def readchar(self): - inc = '' - while len(inc) == 0: - inc = self.ser.read() - return inc - - def writebytes(self, bytes): - self.ser.write(bytes) - return - - def readFDDRequest(self): - inbuf = [] - # read through a carriage return - # parameters are seperated by commas - while 1: - inc = self.readchar() - if inc == '\r': - break - elif inc == ' ': - continue - else: - inbuf.append(inc) - - all = string.join(inbuf, '') - rv = all.split(',') - return rv - - def getPsnLsn(self, info): - psn = 0 - lsn = 1 - if len(info) >= 1 and info[0] != '': - val = int(info[0]) - if psn <= 79: - psn = val - if len(info) > 1 and info[1] != '': - val = int(info[0]) - return psn, lsn - - def readOpmodeRequest(self, req): - buff = array('b') - sum = req - reqlen = ord(self.readchar()) - buff.append(reqlen) - sum = sum + reqlen - - for x in range(reqlen, 0, -1): - rb = ord(self.readchar()) - buff.append(rb) - sum = sum + rb - - # calculate ckecksum - sum = sum % 0x100 - sum = sum ^ 0xFF - - cksum = ord(self.readchar()) - - if cksum == sum: - return buff - else: - if self.verbose: - print 'Checksum mismatch!!' - return None - - def handleRequests(self): - synced = False - while True: - self.handleRequest() - # never returns - return - - def handleRequest(self): - if self.ser.inWaiting() == 0: - return - inc = self.readchar() - if self.FDCmode: - self.handleFDCmodeRequest(inc) - else: - # in OpMode, look for ZZ - #inc = self.readchar() - if inc != 'Z': - return - inc = self.readchar() - if inc == 'Z': - self.handleOpModeRequest() - - def handleOpModeRequest(self): - req = ord(self.ser.read()) - print 'Request: 0X%02X' % req - if req == 0x08: - # Change to FDD emulation mode (no data returned) - inbuf = self.readOpmodeRequest(req) - if inbuf != None: - # Change Modes, leave any incoming serial data in buffer - self.FDCmode = True - else: - print 'Invalid OpMode request code 0X%02X received' % req - return - - def handleFDCmodeRequest(self, cmd): - # Commands may be followed by an optional space - # PSN (physical sector) range 0-79 - # LSN (logical sector) range 0-(number of logical sectors in a physical sector) - # LSN defaults to 1 if not supplied - # - # Result code information (verbatim from the Tandy reference): - # - # After the drive receives a command in FDC-emulation mode, it transmits - # 8 byte characters which represent 4 bytes of status code in hexadecimal. - # - # * The first and second bytes contain the error status. A value of '00' - # indicates that no error occurred - # - # * The third and fourth bytes usually contain the number of the physical - # sector where data is kept in the buffer - # - # For the D, F, and S commands, the contents of these bytes are different. - # See the command descriptions in these cases. - # - # * The fifth-eighth bytes usual show the logical sector length of the data - # kept in the RAM buffer, except the third and fourth digits are 'FF' - # - # In the case of an S, C, or M command -- or an F command that ends in - # an error -- the bytes contain '0000' - # - - if cmd == '\r': - return - - if cmd == 'Z': - # Hmmm, looks like we got the start of an Opmode Request - inc = self.readchar() - if inc == 'Z': - # definitely! - print 'Detected Opmode Request in FDC Mode, switching to OpMode' - self.FDCmode = False - self.handleOpModeRequest() - - elif cmd == 'M': - # apparently not used by brother knitting machine - print 'FDC Change Modes' - raise - # following parameter - 0=FDC, 1=Operating - - elif cmd == 'D': - # apparently not used by brother knitting machine - print 'FDC Check Device' - raise - # Sends result in third and fourth bytes of result code - # See doc - return zero for disk installed and not swapped - - elif cmd == 'F'or cmd == 'G': - #rint 'FDC Format', - info = self.readFDDRequest() - - if len(info) != 1: - print 'wrong number of params (%d) received, assuming 1024 bytes per sector' % len(info) - bps = 1024 - else: - try: - bps = self.formatLength[info[0]] - except KeyError: - print 'Invalid code %c for format, assuming 1024 bytes per sector' % info[0] - bps = 1024 - # we assume 1024 because that's what the brother machine uses - if self.bpls != bps: - print 'Bad news, differing sector sizes' - self.bpls = bps - - self.disk.format() - - # But this is probably more correct - self.writebytes('00000000') - - # After a format, we always start out with OPMode again - self.FDCmode = False - - elif cmd == 'A': - # Followed by physical sector number (0-79), defaults to 0 - # returns ID data, not sector data - info = self.readFDDRequest() - psn, lsn = self.getPsnLsn(info) - print 'FDC Read ID Section %d' % psn - - try: - id = self.disk.getSectorID(psn) - except: - print 'Error getting Sector ID %d, quitting' % psn - self.writebytes('80000000') - raise - - self.writebytes('00' + '%02X' % psn + '0000') - - # see whether to send data - go = self.readchar() - if go == '\r': - self.writebytes(id) - - elif cmd == 'R': - # Followed by Physical Sector Number PSN and Logical Sector Number LSN - info = self.readFDDRequest() - psn, lsn = self.getPsnLsn(info) - print 'FDC Read one Logical Sector %d' % psn - - try: - sd = self.disk.readSector(psn, lsn) - except: - print 'Failed to read Sector %d, quitting' % psn - self.writebytes('80000000') - raise - - self.writebytes('00' + '%02X' % psn + '0000') - - # see whether to send data - go = self.readchar() - if go == '\r': - self.writebytes(sd) - - elif cmd == 'S': - # We receive (optionally) PSN, (optionally) LSN - # This is not documented well at all in the manual - # What is expected is that all sectors will be searched - # and the sector number of the first matching sector - # will be returned. The brother machine always sends - # PSN = 0, so it is unknown whether searching should - # start at Sector 0 or at the PSN sector - info = self.readFDDRequest() - psn, lsn = self.getPsnLsn(info) - print 'FDC Search ID Section %d' % psn - - # Now we must send status (success) - self.writebytes('00' + '%02X' % psn + '0000') - - #self.writebytes('00000000') - - # we receive 12 bytes here - # compare with the specified sector (formatted is apparently zeros) - id = self.readsomechars(12) - print 'checking ID for sector %d' % psn - - try: - status = self.disk.findSectorID(psn, id) - except: - print "FAIL" - status = '30000000' - raise - - print 'returning %s' % status - # guessing - doc is unclear, but says that S always ends in 0000 - # MATCH 00000000 - # MATCH 02000000 - # infinite retries 10000000 - # infinite retries 20000000 - # blinking error 30000000 - # blinking error 40000000 - # infinite retries 50000000 - # infinite retries 60000000 - # infinite retries 70000000 - # infinite retries 80000000 - - self.writebytes(status) - - # Stay in FDC mode - - elif cmd == 'B' or cmd == 'C': - # Followed by PSN 0-79, defaults to 0 - # When received, send result status, if not error, wait - # for data to be written, then after write, send status again - info = self.readFDDRequest() - psn, lsn = self.getPsnLsn(info) - print 'FDC Write ID section %d' % psn - - self.writebytes('00' + '%02X' % psn + '0000') - - id = self.readsomechars(12) - - try: - self.disk.setSectorID(psn, id) - except: - print 'Failed to write ID for sector %d, quitting' % psn - self.writebytes('80000000') - raise - - self.writebytes('00' + '%02X' % psn + '0000') - - elif cmd == 'W' or cmd == 'X': - info = self.readFDDRequest() - psn, lsn = self.getPsnLsn(info) - print 'FDC Write logical sector %d' % psn - - # Now we must send status (success) - self.writebytes('00' + '%02X' % psn + '0000') - - indata = self.readsomechars(1024) - try: - self.disk.writeSector(psn, lsn, indata) - for l in self.listeners: - l.dataReceived(self.disk.lastDatFilePath) - except: - print 'Failed to write data for sector %d, quitting' % psn - self.writebytes('80000000') - raise - - self.writebytes('00' + '%02X' % psn + '0000') - - else: - print 'Unknown FDC command <0x02%X> received' % ord(cmd) - - # return to Operational Mode - return - -class PDDEmulatorListener: - def dataReceived(self, fullFilePath): - pass - -# meat and potatos here - -if __name__ == "__main__": - if len(sys.argv) < 3: - print '%s version %s' % (sys.argv[0], version) - print 'Usage: %s basedir serialdevice' % sys.argv[0] - sys.exit() - - print 'Preparing . . . Please Wait' - emu = PDDemulator(sys.argv[1]) - - emu.open(cport=sys.argv[2]) - - print 'PDDtmulate Version 1.1 Ready!' - try: - while 1: - emu.handleRequests() - except (KeyboardInterrupt): - pass - - emu.close() diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py index 60d1fae..a0c3922 100644 --- a/app/tkapp/KnittingApp.py +++ b/app/tkapp/KnittingApp.py @@ -3,94 +3,138 @@ from Config import Config from Messages import Messages -from gui.Gui import Gui -from pdd.PDDemulate import PDDemulator -from pdd.PDDemulate import PDDEmulatorListener +from app.gui.Gui import Gui +from PDDemulate import PDDemulator +from PDDemulate import PDDEmulatorListener +from dumppattern import PatternDumper import Tkinter class KnittingApp(Tkinter.Tk): - def __init__(self,parent=None): - Tkinter.Tk.__init__(self,parent) - self.parent = parent - self.initialize() - #self.startEmulator() - - def initialize(self): - self.msg = Messages(self) - self._cfg = None - self.gui = Gui() - self.gui.initializeMainWindow(self) - self.deviceEntry.entryText.set(self.getConfig().device) - self.datFileEntry.entryText.set(self.getConfig().datFile) - self.initEmulator() - - def initEmulator(self): - self.emu = PDDemulator(self.getConfig().imgdir) - self.emu.listeners.append(PDDListener(self)) - self.setEmulatorStarted(False) - - def emuButtonClicked(self): - self.getConfig().device = self.deviceEntry.entryText.get() - if self.emu.started: - self.stopEmulator() - else: - self.startEmulator() - - def startEmulator(self): - self.msg.showInfo('Preparing emulator. . . Please Wait') - try: - port = self.getConfig().device - self.emu.open(cport=port) - self.msg.showInfo('PDDemulate Version 1.1 Ready!') - self.setEmulatorStarted(True) - self.after_idle(self.emulatorLoop) - except Exception, e: - self.msg.showError('Ensure that TFDI cable is connected to port ' + port + '\n\nError: ' + str(e)) - self.setEmulatorStarted(False) - - def emulatorLoop(self): - if self.emu.started: - self.emu.handleRequest() - self.after_idle(self.emulatorLoop) - - def stopEmulator(self): - if self.emu is not None: - self.emu.close() - self.msg.showInfo('PDDemulate stopped.') - self.setEmulatorStarted(False) - self.initEmulator() - - def quitApplication(self): - self.stopEmulator() - self.after_idle(self.quit) - - def setEmulatorStarted(self, started): - self.emu.started = started - if started: - self.gui.emuButtonStarted() - else: - self.gui.emuButtonStopped() - - def getConfig(self): - if self._cfg is None: - self._cfg = Config() - if not hasattr(self._cfg, "device"): - self._cfg.device = u"" - if not hasattr(self._cfg, "datFile"): - self._cfg.datFile = u"" - return self._cfg - + def __init__(self,parent=None): + Tkinter.Tk.__init__(self,parent) + self.parent = parent + self.initialize() + #self.startEmulator() + + def initialize(self): + self.msg = Messages(self) + self.patterns = None + self._cfg = None + self.currentDatFile = None + self.patternDumper = PatternDumper() + self.patternDumper.printInfoCallback = self.msg.showInfo + self.gui = Gui() + self.gui.initializeMainWindow(self) + self.deviceEntry.entryText.set(self.getConfig().device) + self.datFileEntry.entryText.set(self.getConfig().datFile) + self.initEmulator() + + def initEmulator(self): + self.emu = PDDemulator(self.getConfig().imgdir) + self.emu.listeners.append(PDDListener(self)) + self.setEmulatorStarted(False) + + def emuButtonClicked(self): + self.getConfig().device = self.deviceEntry.entryText.get() + if self.emu.started: + self.stopEmulator() + else: + self.startEmulator() + + def startEmulator(self): + self.msg.showInfo('Preparing emulator. . . Please Wait') + try: + port = self.getConfig().device + self.emu.open(cport=port) + self.msg.showInfo('PDDemulate Version 1.1 Ready!') + self.setEmulatorStarted(True) + self.after_idle(self.emulatorLoop) + except Exception, e: + self.msg.showError('Ensure that TFDI cable is connected to port ' + port + '\n\nError: ' + str(e)) + self.setEmulatorStarted(False) + + def emulatorLoop(self): + if self.emu.started: + self.emu.handleRequest() + self.after_idle(self.emulatorLoop) + + def stopEmulator(self): + if self.emu is not None: + self.emu.close() + self.msg.showInfo('PDDemulate stopped.') + self.setEmulatorStarted(False) + self.initEmulator() + + def quitApplication(self): + self.stopEmulator() + self.after_idle(self.quit) + + def setEmulatorStarted(self, started): + self.emu.started = started + if started: + self.gui.setEmuButtonStarted() + else: + self.gui.setEmuButtonStopped() + + def getConfig(self): + if self._cfg is None: + self._cfg = Config() + if not hasattr(self._cfg, "device"): + self._cfg.device = u"" + if not hasattr(self._cfg, "datFile"): + self._cfg.datFile = u"" + return self._cfg + + def reloadPatternFile(self, pathToFile = None): + if pathToFile is None: + pathToFile = self.datFileEntry.entryText.get() + self.currentDatFile = pathToFile + try: + result = self.patternDumper.dumppattern([pathToFile]) + self.patterns = result.patterns + listBoxModel = [] + for p in self.patterns: + listBoxModel.append("Pattern number (" + str(p["number"]) + ") (rows x stitches: " + str(p["rows"]) + " x " + str(p["stitches"]) + ")" ) + self.patternListBox.items.set(listBoxModel) + self.patternListBox.selection_set(0) + self.patternCanvas.create_text(30,30,text='TEXT') + except IOError as e: + self.msg.showError('Could not open pattern file %s' % pathToFile + '\n' + str(e)) + + def reloadDatFileButtonClicked(self): + self.reloadPatternFile() + + def patternSelected(self, evt): + w = evt.widget + index = int(w.curselection()[0]) + value = self.patterns[index] + self.patternCanvas.clear() + self.patternCanvas.create_text(30,30,text='Pattern no: ' + str(value['number']), anchor=Tkinter.NW) + result = self.patternDumper.dumppattern([self.currentDatFile, str(value['number'])]) + self.printPatternOnCanvas(result.pattern) + + def printPatternOnCanvas(self, pattern): + patternWidth = len(pattern) + patternHeight = len(pattern[0]) + bitWidth = self.patternCanvas.getWidth() / patternWidth; + bitHeight = self.patternCanvas.getHeight() / patternHeight; + self.patternCanvas.clear() + for row in range(len(pattern)): + for stitch in range(len(pattern[row])): + if(pattern[row][stitch]) == 0: + self.patternCanvas.create_rectangle(stitch * bitWidth,row * bitHeight,(stitch+1) * bitWidth,(row+1) * bitHeight, width=0, fill='black') class PDDListener(PDDEmulatorListener): - def __init__(self, app): - self.app = app + def __init__(self, app): + self.app = app - def dataReceived(self, fullFilePath): - self.app.datFileEntry.entryText.set(fullFilePath) - - + def dataReceived(self, fullFilePath): + self.app.datFileEntry.entryText.set(fullFilePath) + self.reloadPatternFile(fullFilePath) + + if __name__ == "__main__": - app = KnittingApp() - app.mainloop() \ No newline at end of file + app = KnittingApp() + app.mainloop() \ No newline at end of file diff --git a/dumppattern.py b/dumppattern.py index 6694b99..f8fa1f6 100644 --- a/dumppattern.py +++ b/dumppattern.py @@ -22,6 +22,8 @@ DEBUG = 0 +version = '1.0' + ########## def roundeven(val): @@ -55,148 +57,189 @@ def bytesForMemo(rows): ############## +class PatternDumper: -version = '1.0' + def __init__(self): + self.printInfoCallback = self.printInfo -if len(sys.argv) < 2: - print 'Usage: %s file [patternnum]' % sys.argv[0] - print 'Dumps user programs (901-999) from brother data files' - sys.exit() - -if len(sys.argv) == 3: - patt = int(sys.argv[2]) -else: - patt = 0 - -bf = brother.brotherFile(sys.argv[1]) - -if patt == 0: - pats = bf.getPatterns() - print 'Pattern Stitches Rows' - for pat in pats: - print ' %3d %3d %3d' % (pat["number"], pat["stitches"], pat["rows"]) - - if DEBUG: - print "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+" - print "Data file" - print "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+" - - # first dump the 99 'pattern id' blocks - for i in range(99): - print "program entry",i - # each block is 7 bytes - bytenum = i*7 - - pattused = bf.getIndexedByte(bytenum) - print "\t",hex(bytenum),": ",hex(pattused), - if (pattused == 1): - print "\t(used)" - else: - print "\t(unused)" - #print "\t-skipped-" - #continue - bytenum += 1 - - unk1 = bf.getIndexedByte(bytenum) - print "\t",hex(bytenum),": ",hex(unk1),"\t(unknown)" - bytenum += 1 - - rows100 = bf.getIndexedByte(bytenum) - print "\t",hex(bytenum),": ",hex(rows100),"\t(rows = ", (rows100 >> 4)*100, " + ", (rows100 & 0xF)*10 - bytenum += 1 - - rows1 = bf.getIndexedByte(bytenum) - print "\t",hex(bytenum),": ",hex(rows1),"\t\t+ ", (rows1 >> 4), " stiches = ", (rows1 & 0xF)*100,"+" - bytenum += 1 - - stitches10 = bf.getIndexedByte(bytenum) - print "\t",hex(bytenum),": ",hex(stitches10),"\t\t+ ", (stitches10 >> 4)*10, " +", (stitches10 & 0xF),")" - bytenum += 1 - - prog100 = bf.getIndexedByte(bytenum) - print "\t",hex(bytenum),": ",hex(prog100),"\t(unknown , prog# = ", (prog100&0xF) * 100,"+" - bytenum += 1 - - prog10 = bf.getIndexedByte(bytenum) - print "\t",hex(bytenum),": ",hex(prog10),"\t\t + ", (prog10>>4) * 10,"+",(prog10&0xF),")" - bytenum += 1 - - print "============================================" - print "Program memory grows -up-" - # now we're onto data data - - # dump the first program - pointer = 0x6DF # this is the 'bottom' of the memory - for i in range(99): - # of course, not all patterns will get dumped - pattused = bf.getIndexedByte(i*7) - if (pattused != 1): - # :( - break - # otherwise its a valid pattern - print "pattern bank #", i - # calc pattern size - rows100 = bf.getIndexedByte(i*7 + 2) - rows1 = bf.getIndexedByte(i*7 + 3) - stitches10 = bf.getIndexedByte(i*7 + 4) - - rows = (rows100 >> 4)*100 + (rows100 & 0xF)*10 + (rows1 >> 4); - stitches = (rows1 & 0xF)*100 + (stitches10 >> 4)*10 + (stitches10 & 0xF) - print "rows = ", rows, "stitches = ", stitches -# print "total nibs per row = ", nibblesPerRow(stitches) - - - # dump the memo data - print "memo length =",bytesForMemo(rows) - for i in range (bytesForMemo(rows)): - b = pointer - i - print "\t",hex(b),": ",hex(bf.getIndexedByte(b)) - pointer -= bytesForMemo(rows) - - print "pattern length = ", bytesPerPattern(stitches, rows) - for i in range (bytesPerPattern(stitches, rows)): - b = pointer - i - print "\t",hex(b),": ",hex(bf.getIndexedByte(b)), - for j in range(8): - if (bf.getIndexedByte(b) & (1<> 4), " stiches = ", (rows1 & 0xF)*100,"+" + bytenum += 1 + + stitches10 = bf.getIndexedByte(bytenum) + print "\t",hex(bytenum),": ",hex(stitches10),"\t\t+ ", (stitches10 >> 4)*10, " +", (stitches10 & 0xF),")" + bytenum += 1 + + prog100 = bf.getIndexedByte(bytenum) + print "\t",hex(bytenum),": ",hex(prog100),"\t(unknown , prog# = ", (prog100&0xF) * 100,"+" + bytenum += 1 + + prog10 = bf.getIndexedByte(bytenum) + print "\t",hex(bytenum),": ",hex(prog10),"\t\t + ", (prog10>>4) * 10,"+",(prog10&0xF),")" + bytenum += 1 + + print "============================================" + print "Program memory grows -up-" + # now we're onto data data + + # dump the first program + pointer = 0x6DF # this is the 'bottom' of the memory + for i in range(99): + # of course, not all patterns will get dumped + pattused = bf.getIndexedByte(i*7) + if (pattused != 1): + # :( + break + # otherwise its a valid pattern + print "pattern bank #", i + # calc pattern size + rows100 = bf.getIndexedByte(i*7 + 2) + rows1 = bf.getIndexedByte(i*7 + 3) + stitches10 = bf.getIndexedByte(i*7 + 4) + + rows = (rows100 >> 4)*100 + (rows100 & 0xF)*10 + (rows1 >> 4); + stitches = (rows1 & 0xF)*100 + (stitches10 >> 4)*10 + (stitches10 & 0xF) + print "rows = ", rows, "stitches = ", stitches + # print "total nibs per row = ", nibblesPerRow(stitches) + + + # dump the memo data + print "memo length =",bytesForMemo(rows) + for i in range (bytesForMemo(rows)): + b = pointer - i + print "\t",hex(b),": ",hex(bf.getIndexedByte(b)) + pointer -= bytesForMemo(rows) + + print "pattern length = ", bytesPerPattern(stitches, rows) + for i in range (bytesPerPattern(stitches, rows)): + b = pointer - i + print "\t",hex(b),": ",hex(bf.getIndexedByte(b)), + for j in range(8): + if (bf.getIndexedByte(b) & (1< Date: Mon, 29 Jul 2013 00:34:17 +0200 Subject: [PATCH 09/20] Scrollbar to list of patterns, printed pattern rsizes after window resize --- app/gui/Gui.py | 37 +++++++++++----- app/tkapp/Config.py | 15 ++++--- app/tkapp/KnittingApp.py | 94 +++++++++++++++++++++++++++++----------- app/tkapp/Messages.py | 36 ++++++++------- dumppattern.py | 2 +- 5 files changed, 124 insertions(+), 60 deletions(-) diff --git a/app/gui/Gui.py b/app/gui/Gui.py index a3476e3..d043a66 100644 --- a/app/gui/Gui.py +++ b/app/gui/Gui.py @@ -5,6 +5,7 @@ def initializeMainWindow(self,w): self.initMainWindow(w) self._maxColumns = 1000 + self._maxRows = 1000 self._row = 0 self.createDeviceWidgets() self.createEmulatorButton() @@ -21,7 +22,7 @@ def initializeMainWindow(self,w): def initMainWindow(self, mainWindow): self.mainWindow = mainWindow self.mainWindow.title('Knitting pattern uploader') - self.mainWindow.geometry("800x400") + self.mainWindow.geometry("600x400") self.mainWindow.grid() self.mainWindow.grid_columnconfigure(1,weight=1) self.mainWindow.resizable(True,True) @@ -63,21 +64,33 @@ def createInfoMessagesLabel(self): def createPatternsPanel(self): patternFrame = Tkinter.Frame(self.mainWindow) - patternFrame.grid(column=0, row=self._row, columnspan = self._maxColumns,sticky='EW') + patternFrame.grid(column=0, row=self._row, columnspan = self._maxColumns,sticky='EWNS') + self.mainWindow.grid_rowconfigure(self._row,weight=1) patternFrame.grid_columnconfigure(0,weight=1) patternFrame.grid_columnconfigure(1,weight=1) + patternFrame.grid_rowconfigure(1,weight=1) + listboxFrame = Tkinter.Frame(patternFrame) + listboxFrame.grid(column=0, row=0, sticky='EWNS', rowspan=self._maxRows) + scrollbar = Tkinter.Scrollbar(listboxFrame, orient=Tkinter.VERTICAL) listvar = Tkinter.StringVar() - lb = Tkinter.Listbox(patternFrame, listvariable=listvar, exportselection=0, width=50) + lb = Tkinter.Listbox(listboxFrame, listvariable=listvar, exportselection=0, width=50, yscrollcommand=scrollbar.set) + scrollbar.config(command=lb.yview) lb.items = ListboxVar(lb, listvar) - lb.grid(column=0, row=0, sticky='EW') - lb.bind('<>', self.mainWindow.patternSelected) + scrollbar.pack(side=Tkinter.RIGHT, fill=Tkinter.Y) + lb.pack(side=Tkinter.LEFT, fill=Tkinter.BOTH, expand=1) self.mainWindow.patternListBox = lb; + + textvar = Tkinter.StringVar() + label = Tkinter.Label(patternFrame, anchor="w", textvariable = textvar) + label.grid(column=1, row=0, sticky='EW') + label.caption = textvar + self.mainWindow.patternTitle = label pc = ExtendedCanvas(patternFrame, bg='white') - pc.grid(column=1, row=0, sticky='EW') + pc.grid(column=1, row=1, sticky='EWNS') self.mainWindow.patternCanvas = pc - + def setEmuButtonStopped(self): b = self.emuButton b.caption.set(u"Start emulator...") @@ -87,14 +100,18 @@ def setEmuButtonStarted(self): b.caption.set(u"Stop emulator...") class ExtendedCanvas(Tkinter.Canvas): + def getWidth(self): - return int(self.cget('width')) + w = self.winfo_width() + return w def getHeight(self): - return int(self.cget('height')) + h = self.winfo_height() + return h def clear(self): - self.create_rectangle(0,0,self.getWidth(),self.getHeight(), width=0, fill=self.cget('bg')) + maxsize = 10000 + self.create_rectangle(0,0,maxsize, maxsize, width=0, fill=self.cget('bg')) class ListboxVar: def __init__(self, listbox, stringvar): diff --git a/app/tkapp/Config.py b/app/tkapp/Config.py index 806fcda..7160f52 100644 --- a/app/tkapp/Config.py +++ b/app/tkapp/Config.py @@ -1,10 +1,11 @@ import os class Config: - def __init__(self): - self.imgdir = "img"; - if os.sys.platform == 'win32': - self.device = "com34" - self.datFile = 'c:\\Documents and Settings\\ondro\\VirtualBox shared folder\\knitting\\app\\img\\file-01.dat' - else: - self.device = "/dev/ttyUSB0" \ No newline at end of file + def __init__(self): + self.imgdir = "img"; + if os.sys.platform == 'win32': + self.device = "com34" + self.datFile = 'c:\\Documents and Settings\\ondro\\VirtualBox shared folder\\knitting\\app\\img\\file-01.dat' + self.simulateEmulator = True + else: + self.device = "/dev/ttyUSB0" \ No newline at end of file diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py index a0c3922..651d387 100644 --- a/app/tkapp/KnittingApp.py +++ b/app/tkapp/KnittingApp.py @@ -19,17 +19,21 @@ def __init__(self,parent=None): def initialize(self): self.msg = Messages(self) - self.patterns = None + self.patterns = [] + self.pattern = None self._cfg = None self.currentDatFile = None self.patternDumper = PatternDumper() self.patternDumper.printInfoCallback = self.msg.showInfo self.gui = Gui() self.gui.initializeMainWindow(self) + self.patternListBox.bind('<>', self.patternSelected) + self.after_idle(self.canvasConfigured) self.deviceEntry.entryText.set(self.getConfig().device) self.datFileEntry.entryText.set(self.getConfig().datFile) self.initEmulator() - + self.after_idle(self.reloadPatternFile) + def initEmulator(self): self.emu = PDDemulator(self.getConfig().imgdir) self.emu.listeners.append(PDDListener(self)) @@ -44,15 +48,20 @@ def emuButtonClicked(self): def startEmulator(self): self.msg.showInfo('Preparing emulator. . . Please Wait') - try: - port = self.getConfig().device - self.emu.open(cport=port) - self.msg.showInfo('PDDemulate Version 1.1 Ready!') + + if self.getConfig().simulateEmulator: + self.msg.showInfo('Simulating emulator, emulator is not started...') self.setEmulatorStarted(True) - self.after_idle(self.emulatorLoop) - except Exception, e: - self.msg.showError('Ensure that TFDI cable is connected to port ' + port + '\n\nError: ' + str(e)) - self.setEmulatorStarted(False) + else: + try: + port = self.getConfig().device + self.emu.open(cport=port) + self.msg.showInfo('PDDemulate Version 1.1 Ready!') + self.setEmulatorStarted(True) + self.after_idle(self.emulatorLoop) + except Exception, e: + self.msg.showError('Ensure that TFDI cable is connected to port ' + port + '\n\nError: ' + str(e)) + self.setEmulatorStarted(False) def emulatorLoop(self): if self.emu.started: @@ -78,27 +87,36 @@ def setEmulatorStarted(self, started): self.gui.setEmuButtonStopped() def getConfig(self): - if self._cfg is None: - self._cfg = Config() - if not hasattr(self._cfg, "device"): - self._cfg.device = u"" - if not hasattr(self._cfg, "datFile"): - self._cfg.datFile = u"" - return self._cfg + cfg = self._cfg + if cfg is None: + self._cfg = cfg = Config() + if not hasattr(cfg, "device"): + cfg.device = u"" + if not hasattr(cfg, "datFile"): + cfg.datFile = u"" + if not hasattr(cfg, "simulateEmulator"): + cfg.simulateEmulator = False + return cfg def reloadPatternFile(self, pathToFile = None): - if pathToFile is None: + if not pathToFile: pathToFile = self.datFileEntry.entryText.get() + if not pathToFile: + return self.currentDatFile = pathToFile try: result = self.patternDumper.dumppattern([pathToFile]) self.patterns = result.patterns listBoxModel = [] for p in self.patterns: - listBoxModel.append("Pattern number (" + str(p["number"]) + ") (rows x stitches: " + str(p["rows"]) + " x " + str(p["stitches"]) + ")" ) + listBoxModel.append(self.getPatternTitle(p)) self.patternListBox.items.set(listBoxModel) - self.patternListBox.selection_set(0) - self.patternCanvas.create_text(30,30,text='TEXT') + if (len(listBoxModel) > 0): + selected_index = 0 + self.patternListBox.selection_set(selected_index) + self.displayPattern(self.patterns[selected_index]) + else: + self.displayPattern(None) except IOError as e: self.msg.showError('Could not open pattern file %s' % pathToFile + '\n' + str(e)) @@ -107,13 +125,31 @@ def reloadDatFileButtonClicked(self): def patternSelected(self, evt): w = evt.widget - index = int(w.curselection()[0]) - value = self.patterns[index] + sel = w.curselection() + if len(sel) > 0: + index = int(w.curselection()[0]) + pattern = self.patterns[index] + else: + pattern = None + self.displayPattern(pattern) + + def displayPattern(self, pattern=None): + if not pattern: + pattern = self.pattern self.patternCanvas.clear() - self.patternCanvas.create_text(30,30,text='Pattern no: ' + str(value['number']), anchor=Tkinter.NW) - result = self.patternDumper.dumppattern([self.currentDatFile, str(value['number'])]) - self.printPatternOnCanvas(result.pattern) + self.patternTitle.caption.set(self.getPatternTitle(pattern)) + if pattern: + result = self.patternDumper.dumppattern([self.currentDatFile, str(pattern['number'])]) + self.printPatternOnCanvas(result.pattern) + self.pattern = pattern + def getPatternTitle(self, pattern): + p = pattern + if p: + return 'Pattern no: ' + str(p['number']) + " (rows x stitches: " + str(p["rows"]) + " x " + str(p["stitches"]) + ")" + else: + return 'No pattern' + def printPatternOnCanvas(self, pattern): patternWidth = len(pattern) patternHeight = len(pattern[0]) @@ -124,6 +160,12 @@ def printPatternOnCanvas(self, pattern): for stitch in range(len(pattern[row])): if(pattern[row][stitch]) == 0: self.patternCanvas.create_rectangle(stitch * bitWidth,row * bitHeight,(stitch+1) * bitWidth,(row+1) * bitHeight, width=0, fill='black') + + def canvasConfigured(self): + self.msg.displayMessages = False + self.displayPattern() + self.msg.displayMessages = True + self.after(100, self.canvasConfigured) class PDDListener(PDDEmulatorListener): diff --git a/app/tkapp/Messages.py b/app/tkapp/Messages.py index 42de04d..41cf786 100644 --- a/app/tkapp/Messages.py +++ b/app/tkapp/Messages.py @@ -2,19 +2,23 @@ import sys class Messages: - def __init__(self, knittinApp): - self.app = knittinApp - - def showError(self, msg): - self.clear() - sys.stderr.write('Error: ' + str(msg) + '\n') - mb.showerror('Error:', str(msg)) - - def showInfo(self, msg): - self.clear() - print msg - self.app.infoLabel.caption.set('Info: ' + str(msg)) - - def clear(self): - self.app.infoLabel.caption.set('') - \ No newline at end of file + def __init__(self, knittinApp): + self.app = knittinApp + self.displayMessages = True + + def showError(self, msg): + if self.displayMessages: + self.clear() + sys.stderr.write('Error: ' + str(msg) + '\n') + mb.showerror('Error:', str(msg)) + + def showInfo(self, msg): + if self.displayMessages: + self.clear() + print msg + self.app.infoLabel.caption.set('Info: ' + str(msg)) + + def clear(self): + if self.displayMessages: + self.app.infoLabel.caption.set('') + \ No newline at end of file diff --git a/dumppattern.py b/dumppattern.py index f8fa1f6..5a85ee6 100644 --- a/dumppattern.py +++ b/dumppattern.py @@ -209,7 +209,7 @@ def __init__(self, patternNumber): class Result: def __init__(self): self.patterns = None # list of pat["number"], pat["stitches"], pat["rows"] - self.pattern = None # pattern object + self.pattern = None # pattern object: array of [rows][stitches] if __name__ == "__main__": From d4bb8dd23f7298db0d7c22d16da7556089fcd7ce Mon Sep 17 00:00:00 2001 From: ondro Date: Wed, 31 Jul 2013 01:06:22 +0200 Subject: [PATCH 10/20] Inserting bitmap --- app/gui/Gui.py | 20 ++- app/tkapp/KnittingApp.py | 63 ++++++++- app/tkapp/Messages.py | 6 + insertpattern.py | 296 ++++++++++++++++++++++----------------- 4 files changed, 243 insertions(+), 142 deletions(-) diff --git a/app/gui/Gui.py b/app/gui/Gui.py index d043a66..2058a84 100644 --- a/app/gui/Gui.py +++ b/app/gui/Gui.py @@ -24,7 +24,8 @@ def initMainWindow(self, mainWindow): self.mainWindow.title('Knitting pattern uploader') self.mainWindow.geometry("600x400") self.mainWindow.grid() - self.mainWindow.grid_columnconfigure(1,weight=1) + self.mainWindow.grid_columnconfigure(1,weight=10) + self.mainWindow.grid_columnconfigure(2,weight=1) self.mainWindow.resizable(True,True) def createDeviceWidgets(self): @@ -40,9 +41,12 @@ def createEmulatorButton(self): caption = Tkinter.StringVar() self.emuButton = Tkinter.Button(self.mainWindow, textvariable = caption, command = self.mainWindow.emuButtonClicked) self.emuButton.caption = caption - self.emuButton.grid(column=2,row=self._row) + self.emuButton.grid(column=2,row=self._row, columnspan=2, sticky='EW') self.setEmuButtonStopped() + but = Tkinter.Button(self.mainWindow, text = u'Help...', command = self.mainWindow.helpButtonClicked) + but.grid(column=4,row=self._row, sticky='E') + def createDatFileWidgets(self): label = Tkinter.Label(self.mainWindow, text=u"Dat file:") label.grid(column=0, row=self._row, sticky='W') @@ -52,8 +56,11 @@ def createDatFileWidgets(self): self.mainWindow.datFileEntry.grid(column=1,row=self._row,sticky='EW') self.mainWindow.datFileEntry.entryText = entryText + self.chooseDatFileButton = Tkinter.Button(self.mainWindow, text=u"...", command = self.mainWindow.chooseDatFileButtonClicked) + self.chooseDatFileButton.grid(column=2,row=self._row,sticky='W') + self.reloadDatFileButton = Tkinter.Button(self.mainWindow, text=u"Reload file", command = self.mainWindow.reloadDatFileButtonClicked) - self.reloadDatFileButton.grid(column=2,row=self._row,sticky='EW') + self.reloadDatFileButton.grid(column=3,row=self._row,sticky='EW') def createInfoMessagesLabel(self): labelText = Tkinter.StringVar() @@ -87,8 +94,11 @@ def createPatternsPanel(self): label.caption = textvar self.mainWindow.patternTitle = label + self.insertBitmapButton = Tkinter.Button(patternFrame, text=u"Insert bitmap...", command = self.mainWindow.insertBitmapButtonClicked) + self.insertBitmapButton.grid(column=2,row=0,sticky='EW') + pc = ExtendedCanvas(patternFrame, bg='white') - pc.grid(column=1, row=1, sticky='EWNS') + pc.grid(column=1, row=1, sticky='EWNS', columnspan=2) self.mainWindow.patternCanvas = pc def setEmuButtonStopped(self): @@ -97,7 +107,7 @@ def setEmuButtonStopped(self): def setEmuButtonStarted(self): b = self.emuButton - b.caption.set(u"Stop emulator...") + b.caption.set(u"...stop emulator") class ExtendedCanvas(Tkinter.Canvas): diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py index 651d387..3ae12a8 100644 --- a/app/tkapp/KnittingApp.py +++ b/app/tkapp/KnittingApp.py @@ -7,7 +7,9 @@ from PDDemulate import PDDemulator from PDDemulate import PDDEmulatorListener from dumppattern import PatternDumper +from insertpattern import PatternInserter import Tkinter +import tkFileDialog class KnittingApp(Tkinter.Tk): @@ -23,10 +25,10 @@ def initialize(self): self.pattern = None self._cfg = None self.currentDatFile = None - self.patternDumper = PatternDumper() - self.patternDumper.printInfoCallback = self.msg.showInfo + self.initializeUtilities() self.gui = Gui() self.gui.initializeMainWindow(self) + self.updatePatternCanvasLastSize() self.patternListBox.bind('<>', self.patternSelected) self.after_idle(self.canvasConfigured) self.deviceEntry.entryText.set(self.getConfig().device) @@ -34,6 +36,13 @@ def initialize(self): self.initEmulator() self.after_idle(self.reloadPatternFile) + def initializeUtilities(self): + self.patternDumper = PatternDumper() + self.patternDumper.printInfoCallback = self.msg.showInfo + self.patternInserter = PatternInserter() + self.patternInserter.printInfoCallback = self.msg.showInfo + self.patternInserter.printErrorCallback = self.msg.showError + def initEmulator(self): self.emu = PDDemulator(self.getConfig().imgdir) self.emu.listeners.append(PDDListener(self)) @@ -101,6 +110,9 @@ def getConfig(self): def reloadPatternFile(self, pathToFile = None): if not pathToFile: pathToFile = self.datFileEntry.entryText.get() + else: + self.datFileEntry.entryText.set(pathToFile) + if not pathToFile: return self.currentDatFile = pathToFile @@ -120,14 +132,29 @@ def reloadPatternFile(self, pathToFile = None): except IOError as e: self.msg.showError('Could not open pattern file %s' % pathToFile + '\n' + str(e)) + def helpButtonClicked(self): + helpMsg = '''Commands to execute on Knitting machine: + +552: Download patterns from machine to computer +551: Upload patterns from computer to machine +''' + self.msg.showMoreInfo(helpMsg) + def reloadDatFileButtonClicked(self): self.reloadPatternFile() + + def chooseDatFileButtonClicked(self): + filePath = tkFileDialog.askopenfilename(filetypes=[('DAT file', '*.dat')], initialfile=self.datFileEntry.entryText.get(), + title='Choose dat file with patterns...') + if len(filePath) > 0: + self.msg.showInfo('Opened dat file ' + filePath) + self.reloadPatternFile(filePath) def patternSelected(self, evt): w = evt.widget sel = w.curselection() if len(sel) > 0: - index = int(w.curselection()[0]) + index = int(sel[0]) pattern = self.patterns[index] else: pattern = None @@ -161,11 +188,34 @@ def printPatternOnCanvas(self, pattern): if(pattern[row][stitch]) == 0: self.patternCanvas.create_rectangle(stitch * bitWidth,row * bitHeight,(stitch+1) * bitWidth,(row+1) * bitHeight, width=0, fill='black') + def updatePatternCanvasLastSize(self): + self.patternCanvas.lastWidth = self.patternCanvas.getWidth() + self.patternCanvas.lastHeight = self.patternCanvas.getHeight() + def canvasConfigured(self): - self.msg.displayMessages = False - self.displayPattern() - self.msg.displayMessages = True + if self.patternCanvas.lastWidth != self.patternCanvas.getWidth() or self.patternCanvas.lastHeight != self.patternCanvas.getHeight(): + self.msg.displayMessages = False + self.updatePatternCanvasLastSize() + self.displayPattern() + self.msg.displayMessages = True self.after(100, self.canvasConfigured) + + def insertBitmapButtonClicked(self): + sel = self.patternListBox.curselection() + if len(sel) == 0: + self.msg.showError('Target pattern for insertion must be selected!') + return + index = int(sel[0]) + pattern = self.patterns[index] + filePath = tkFileDialog.askopenfilename(filetypes=[('2-color Bitmap', '*.bmp')], + title='Choose bitmap file to insert...') + if len(filePath) > 0: + self.insertBitmap(filePath, pattern["number"]) + + def insertBitmap(self, bitmapFile, patternNumber): + self.msg.showInfo('Inserting dat file %s to pattern number %d' % (bitmapFile, patternNumber)) + oldBrotherFile = self.currentDatFile + self.patternInserter.insertPattern(oldBrotherFile, patternNumber, bitmapFile, 'myfile.dat') class PDDListener(PDDEmulatorListener): @@ -173,7 +223,6 @@ def __init__(self, app): self.app = app def dataReceived(self, fullFilePath): - self.app.datFileEntry.entryText.set(fullFilePath) self.reloadPatternFile(fullFilePath) diff --git a/app/tkapp/Messages.py b/app/tkapp/Messages.py index 41cf786..92976de 100644 --- a/app/tkapp/Messages.py +++ b/app/tkapp/Messages.py @@ -11,6 +11,12 @@ def showError(self, msg): self.clear() sys.stderr.write('Error: ' + str(msg) + '\n') mb.showerror('Error:', str(msg)) + + def showMoreInfo(self, msg): + if self.displayMessages: + self.clear() + print msg + mb.showinfo('Info:',str(msg)) def showInfo(self, msg): if self.displayMessages: diff --git a/insertpattern.py b/insertpattern.py index c6df7b7..14c8870 100644 --- a/insertpattern.py +++ b/insertpattern.py @@ -24,6 +24,8 @@ TheImage = None +version = '1.0' + ################## def roundeven(val): @@ -57,149 +59,183 @@ def bytesForMemo(rows): ############## +class PatternInserter: + def __init__(self): + self.printInfoCallback = self.printInfo + self.printErrorCallback = self.printError + self.printPatternCallback = self.printPattern + + def printInfo(self, printMsg): + print printMsg -version = '1.0' + def printError(self, printMsg): + print printMsg -if len(sys.argv) < 5: - print 'Usage: %s oldbrotherfile pattern# image.bmp newbrotherfile' % sys.argv[0] - sys.exit() + def printPattern(self, printMsg): + sys.stdout.write(printMsg) + def insertPattern(self, oldbrotherfile, pattnum, imgfile, newbrotherfile): -bf = brother.brotherFile(sys.argv[1]) -pattnum = sys.argv[2] -imgfile = sys.argv[3] + bf = brother.brotherFile(oldbrotherfile) + pats = bf.getPatterns() -pats = bf.getPatterns() + # ok got a bank, now lets figure out how big this thing we want to insert is + TheImage = Image.open(imgfile) + TheImage.load() -# ok got a bank, now lets figure out how big this thing we want to insert is -TheImage = Image.open(imgfile) -TheImage.load() + im_size = TheImage.size + width = im_size[0] + self.printInfoCallback( "width:" + str(width)) + height = im_size[1] + self.printInfoCallback( "height:" + str(height)) -im_size = TheImage.size -width = im_size[0] -print "width:",width -height = im_size[1] -print "height:", height + # find the program entry + thePattern = None -# find the program entry -thePattern = None + for pat in pats: + if (int(pat["number"]) == int(pattnum)): + #print "found it!" + thePattern = pat + if (thePattern == None): + raise PatternNotFoundException(pattnum) -for pat in pats: - if (int(pat["number"]) == int(pattnum)): - #print "found it!" - thePattern = pat -if (thePattern == None): - print "Pattern #",pattnum,"not found!" - exit(0) + if (height != thePattern["rows"] or width != thePattern["stitches"]): + raise InserterException("Pattern is the wrong size, the BMP is ",height,"x",width,"and the pattern is ",thePattern["rows"], "x", thePattern["stitches"]) -if (height != thePattern["rows"] or width != thePattern["stitches"]): - print "Pattern is the wrong size, the BMP is ",height,"x",width,"and the pattern is ",thePattern["rows"], "x", thePattern["stitches"] - exit(0) + # debugging stuff here + x = 0 + y = 0 -# debugging stuff here -x = 0 -y = 0 - -x = width - 1 -while x > 0: - value = TheImage.getpixel((x,y)) - if value: - sys.stdout.write('* ') - else: - sys.stdout.write(' ') - #sys.stdout.write(str(value)) - x = x-1 - if x == 0: #did we hit the end of the line? - y = y+1 x = width - 1 - print " " - if y == height: - break -# debugging stuff done - -# now to make the actual, yknow memo+pattern data - -# the memo seems to be always blank. i have no idea really -memoentry = [] -for i in range(bytesForMemo(height)): - memoentry.append(0x0) - -# now for actual real live pattern data! -pattmemnibs = [] -for r in range(height): - row = [] # we'll chunk in bits and then put em into nibbles - for s in range(width): - value = TheImage.getpixel((width-s-1,height-r-1)) - if (value != 0): - row.append(1) - else: - row.append(0) - #print row - # turn it into nibz - for s in range(roundfour(width) / 4): - n = 0 - for nibs in range(4): - #print "row size = ", len(row), "index = ",s*4+nibs - - if (len(row) == (s*4+nibs)): - break # padding! + while x > 0: + value = TheImage.getpixel((x,y)) + if value: + self.printPattern('* ') + else: + self.printPattern(' ') + #sys.stdout.write(str(value)) + x = x-1 + if x == 0: #did we hit the end of the line? + y = y+1 + x = width - 1 + print " " + if y == height: + break + # debugging stuff done + + # now to make the actual, yknow memo+pattern data + + # the memo seems to be always blank. i have no idea really + memoentry = [] + for i in range(bytesForMemo(height)): + memoentry.append(0x0) + + # now for actual real live pattern data! + pattmemnibs = [] + for r in range(height): + row = [] # we'll chunk in bits and then put em into nibbles + for s in range(width): + value = TheImage.getpixel((width-s-1,height-r-1)) + if (value != 0): + row.append(1) + else: + row.append(0) + #print row + # turn it into nibz + for s in range(roundfour(width) / 4): + n = 0 + for nibs in range(4): + #print "row size = ", len(row), "index = ",s*4+nibs + + if (len(row) == (s*4+nibs)): + break # padding! + + if (row[s*4 + nibs]): + n |= 1 << nibs + pattmemnibs.append(n) + #print hex(n), + + + if (len(pattmemnibs) % 2): + # odd nibbles, buffer to a byte + pattmemnibs.append(0x0) + + #print len(pattmemnibs), "nibbles of data" + + # turn into bytes + pattmem = [] + for i in range (len(pattmemnibs) / 2): + pattmem.append( pattmemnibs[i*2] | (pattmemnibs[i*2 + 1] << 4)) + + #print map(hex, pattmem) + # whew. + + + # now to insert this data into the file + + # now we have to figure out the -end- of the last pattern is + endaddr = 0x6df + + beginaddr = thePattern["pattend"] + endaddr = beginaddr + bytesForMemo(height) + len(pattmem) + self.printInfoCallback("beginning will be at " + str(hex(beginaddr)) + ", end at " + str(hex(endaddr))) + + # Note - It's note certain that in all cases this collision test is needed. What's happening + # when you write below this address (as the pattern grows downward in memory) in that you begin + # to overwrite the pattern index data that starts at low memory. Since you overwrite the info + # for highest memory numbers first, you may be able to get away with it as long as you don't + # attempt to use higher memories. + # Steve + + if beginaddr <= 0x2B8: + self.printErrorCallback("Sorry, this will collide with the pattern entry data since %s is <= 0x2B8!" % hex(beginaddr)) + #exit + + # write the memo and pattern entry from the -end- to the -beginning- (up!) + for i in range(len(memoentry)): + bf.setIndexedByte(endaddr, 0) + endaddr -= 1 + + for i in range(len(pattmem)): + bf.setIndexedByte(endaddr, pattmem[i]) + endaddr -= 1 + + # push the data to a file + outfile = open(newbrotherfile, 'wb') + + d = bf.getFullData() + outfile.write(d) + outfile.close() + +class InserterException(Exception): + def getMessage(self): + msg = '' + for arg in self.args: + if msg != '': + msg += ' ' + msg += str(arg) - if (row[s*4 + nibs]): - n |= 1 << nibs - pattmemnibs.append(n) - #print hex(n), - - -if (len(pattmemnibs) % 2): - # odd nibbles, buffer to a byte - pattmemnibs.append(0x0) - -#print len(pattmemnibs), "nibbles of data" - -# turn into bytes -pattmem = [] -for i in range (len(pattmemnibs) / 2): - pattmem.append( pattmemnibs[i*2] | (pattmemnibs[i*2 + 1] << 4)) - -#print map(hex, pattmem) -# whew. - - -# now to insert this data into the file - -# now we have to figure out the -end- of the last pattern is -endaddr = 0x6df - -beginaddr = thePattern["pattend"] -endaddr = beginaddr + bytesForMemo(height) + len(pattmem) -print "beginning will be at ", hex(beginaddr), "end at", hex(endaddr) - -# Note - It's note certain that in all cases this collision test is needed. What's happening -# when you write below this address (as the pattern grows downward in memory) in that you begin -# to overwrite the pattern index data that starts at low memory. Since you overwrite the info -# for highest memory numbers first, you may be able to get away with it as long as you don't -# attempt to use higher memories. -# Steve - -if beginaddr <= 0x2B8: - print "sorry, this will collide with the pattern entry data since %s is <= 0x2B8!" % hex(beginaddr) - #exit - -# write the memo and pattern entry from the -end- to the -beginning- (up!) -for i in range(len(memoentry)): - bf.setIndexedByte(endaddr, 0) - endaddr -= 1 - -for i in range(len(pattmem)): - bf.setIndexedByte(endaddr, pattmem[i]) - endaddr -= 1 - -# push the data to a file -outfile = open(sys.argv[4], 'wb') - -d = bf.getFullData() -outfile.write(d) -outfile.close() + return msg + +class PatternNotFoundException(InserterException): + def __init__(self, patternNumber): + self.patternNumber = patternNumber + + +if __name__ == "__main__": + if len(sys.argv) < 5: + print 'Usage: %s oldbrotherfile pattern# image.bmp newbrotherfile' % sys.argv[0] + sys.exit() + inserter = PatternInserter() + argv = sys.argv + try: + inserter.insertPattern(argv[1],argv[2],argv[3],argv[4]) + except PatternNotFoundException as e: + print 'ERROR: Pattern %d not found' % e.patternNumber + sys.exit(1) + except InserterException as e: + print 'ERROR: ',e.getMessage() + sys.exit(1) From fcb1275ebee04a1157d1e948f671a983821c80b3 Mon Sep 17 00:00:00 2001 From: ondro Date: Sat, 3 Aug 2013 12:17:17 +0200 Subject: [PATCH 11/20] Insert pattern to load to machine --- app/gui/Gui.py | 4 ++ app/tkapp/KnittingApp.py | 116 +++++++++++++++++++++++++++++++-------- app/tkapp/Messages.py | 5 ++ 3 files changed, 102 insertions(+), 23 deletions(-) diff --git a/app/gui/Gui.py b/app/gui/Gui.py index 2058a84..d1004b7 100644 --- a/app/gui/Gui.py +++ b/app/gui/Gui.py @@ -62,6 +62,10 @@ def createDatFileWidgets(self): self.reloadDatFileButton = Tkinter.Button(self.mainWindow, text=u"Reload file", command = self.mainWindow.reloadDatFileButtonClicked) self.reloadDatFileButton.grid(column=3,row=self._row,sticky='EW') + but = Tkinter.Button(self.mainWindow, text=u"Store track", command = self.mainWindow.storeTrackButtonClicked) + but.grid(column=4,row=self._row,sticky='EW') + self.storeTrackButton = but + def createInfoMessagesLabel(self): labelText = Tkinter.StringVar() label = Tkinter.Label(self.mainWindow, anchor="w",fg="white",bg="blue",textvariable=labelText) diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py index 3ae12a8..d5355e7 100644 --- a/app/tkapp/KnittingApp.py +++ b/app/tkapp/KnittingApp.py @@ -10,6 +10,8 @@ from insertpattern import PatternInserter import Tkinter import tkFileDialog +import os +import os.path class KnittingApp(Tkinter.Tk): @@ -23,9 +25,11 @@ def initialize(self): self.msg = Messages(self) self.patterns = [] self.pattern = None - self._cfg = None self.currentDatFile = None + + self.initConfig() self.initializeUtilities() + self.gui = Gui() self.gui.initializeMainWindow(self) self.updatePatternCanvasLastSize() @@ -33,6 +37,7 @@ def initialize(self): self.after_idle(self.canvasConfigured) self.deviceEntry.entryText.set(self.getConfig().device) self.datFileEntry.entryText.set(self.getConfig().datFile) + self.initEmulator() self.after_idle(self.reloadPatternFile) @@ -46,6 +51,7 @@ def initializeUtilities(self): def initEmulator(self): self.emu = PDDemulator(self.getConfig().imgdir) self.emu.listeners.append(PDDListener(self)) + #self.emu = lambda: 1 self.setEmulatorStarted(False) def emuButtonClicked(self): @@ -75,7 +81,8 @@ def startEmulator(self): def emulatorLoop(self): if self.emu.started: self.emu.handleRequest() - self.after_idle(self.emulatorLoop) + # repeated call to after_idle() caused all window dialogs to hang out application, using after() each 10 milliseconds + self.after(10,self.emulatorLoop) def stopEmulator(self): if self.emu is not None: @@ -96,16 +103,17 @@ def setEmulatorStarted(self, started): self.gui.setEmuButtonStopped() def getConfig(self): - cfg = self._cfg - if cfg is None: - self._cfg = cfg = Config() - if not hasattr(cfg, "device"): - cfg.device = u"" - if not hasattr(cfg, "datFile"): - cfg.datFile = u"" - if not hasattr(cfg, "simulateEmulator"): - cfg.simulateEmulator = False - return cfg + return self.config + + def initConfig(self): + cfg = Config() + if not hasattr(cfg, "device"): + cfg.device = u"" + if not hasattr(cfg, "datFile"): + cfg.datFile = u"" + if not hasattr(cfg, "simulateEmulator"): + cfg.simulateEmulator = False + self.config = cfg def reloadPatternFile(self, pathToFile = None): if not pathToFile: @@ -122,26 +130,69 @@ def reloadPatternFile(self, pathToFile = None): listBoxModel = [] for p in self.patterns: listBoxModel.append(self.getPatternTitle(p)) + selectedIndex = self.getSelectedPatternIndex() self.patternListBox.items.set(listBoxModel) - if (len(listBoxModel) > 0): - selected_index = 0 - self.patternListBox.selection_set(selected_index) - self.displayPattern(self.patterns[selected_index]) - else: - self.displayPattern(None) + self.setSelectedPatternIndex(selectedIndex) except IOError as e: self.msg.showError('Could not open pattern file %s' % pathToFile + '\n' + str(e)) + + def storeTrack(self, pathToFile = None): + if not pathToFile: + pathToFile = self.datFileEntry.entryText.get() + self.msg.showInfo('Storing tracks for file ' + pathToFile) + trackFile1, trackFile2 = "00.dat", "01.dat" + trackPath1 = os.path.join(self.config.imgdir, trackFile1) + trackPath2 = os.path.join(self.config.imgdir, trackFile2) + trackSize = 1024 + + startEmu = self.emu.started + if startEmu: + self.stopEmulator() + + infile = track0file = track1file = None + + infile = open(pathToFile, 'rb') + + try: + track0file = open(trackPath1, 'wb') + track1file = open(trackPath2, 'wb') + + t0dat = infile.read(trackSize) + t1dat = infile.read(trackSize) + + track0file.write(t0dat) + track1file.write(t1dat) + self.msg.showInfo('Stored file to tracks ' + trackFile1 + ' and ' + trackFile2 + ' in config.imgdir') + except Exception as e: + self.msg.showError(str(e)) + finally: + if infile: + self.msg.showDebug("Closing infile...") + infile.close + if track0file: + self.msg.showDebug("Closing track0file...") + track0file.close() + if track1file: + self.msg.showDebug("Closing track1file...") + track1file.close() + if startEmu: + self.startEmulator() def helpButtonClicked(self): helpMsg = '''Commands to execute on Knitting machine: 552: Download patterns from machine to computer 551: Upload patterns from computer to machine + - before this, make sure that you stored file to track + - afterfards pressing 551, press 1 to load track with inserted patterns ''' self.msg.showMoreInfo(helpMsg) def reloadDatFileButtonClicked(self): self.reloadPatternFile() + + def storeTrackButtonClicked(self): + self.storeTrack() def chooseDatFileButtonClicked(self): filePath = tkFileDialog.askopenfilename(filetypes=[('DAT file', '*.dat')], initialfile=self.datFileEntry.entryText.get(), @@ -152,14 +203,32 @@ def chooseDatFileButtonClicked(self): def patternSelected(self, evt): w = evt.widget - sel = w.curselection() - if len(sel) > 0: - index = int(sel[0]) + index = self.getSelectedPatternIndex() + if index is not None: pattern = self.patterns[index] else: pattern = None self.displayPattern(pattern) + def getSelectedPatternIndex(self): + sel = self.patternListBox.curselection() + if len(sel) > 0: + return int(sel[0]) + else: + return None + + def setSelectedPatternIndex(self, index): + lb = self.patternListBox + if index is None: + index = 0 + if lb.size() == 0: + self.displayPattern(None) + return + if index > lb.size(): + index = 0 + self.patternListBox.selection_set(index) + self.displayPattern(self.patterns[index]) + def displayPattern(self, pattern=None): if not pattern: pattern = self.pattern @@ -215,7 +284,8 @@ def insertBitmapButtonClicked(self): def insertBitmap(self, bitmapFile, patternNumber): self.msg.showInfo('Inserting dat file %s to pattern number %d' % (bitmapFile, patternNumber)) oldBrotherFile = self.currentDatFile - self.patternInserter.insertPattern(oldBrotherFile, patternNumber, bitmapFile, 'myfile.dat') + self.patternInserter.insertPattern(oldBrotherFile, patternNumber, bitmapFile, oldBrotherFile) + self.reloadPatternFile() class PDDListener(PDDEmulatorListener): @@ -223,7 +293,7 @@ def __init__(self, app): self.app = app def dataReceived(self, fullFilePath): - self.reloadPatternFile(fullFilePath) + self.app.reloadPatternFile(fullFilePath) if __name__ == "__main__": diff --git a/app/tkapp/Messages.py b/app/tkapp/Messages.py index 92976de..2902cf3 100644 --- a/app/tkapp/Messages.py +++ b/app/tkapp/Messages.py @@ -5,6 +5,7 @@ class Messages: def __init__(self, knittinApp): self.app = knittinApp self.displayMessages = True + self.debug = True def showError(self, msg): if self.displayMessages: @@ -24,6 +25,10 @@ def showInfo(self, msg): print msg self.app.infoLabel.caption.set('Info: ' + str(msg)) + def showDebug(self, msg): + if self.debug: + print "DEBUG:", msg + def clear(self): if self.displayMessages: self.app.infoLabel.caption.set('') From 930b546750e1126cf6a2489875b9f12620c3dd8a Mon Sep 17 00:00:00 2001 From: ondro Date: Wed, 27 Nov 2013 00:40:55 +0100 Subject: [PATCH 12/20] Fix of display of patterns, added grid in pattern display --- app/tkapp/KnittingApp.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py index d5355e7..a89902f 100644 --- a/app/tkapp/KnittingApp.py +++ b/app/tkapp/KnittingApp.py @@ -247,15 +247,28 @@ def getPatternTitle(self, pattern): return 'No pattern' def printPatternOnCanvas(self, pattern): - patternWidth = len(pattern) - patternHeight = len(pattern[0]) +# pattern = [] +# for x in range(8): +# row = [] +# for y in range(13): +# row.append((y % 2 + x % 2) % 2) +# pattern.append(row) + patternHeight = len(pattern) + patternWidth = len(pattern[0]) bitWidth = self.patternCanvas.getWidth() / patternWidth; bitHeight = self.patternCanvas.getHeight() / patternHeight; + bitWidth = min(bitWidth, bitHeight) + bitHeight = bitWidth self.patternCanvas.clear() for row in range(len(pattern)): for stitch in range(len(pattern[row])): - if(pattern[row][stitch]) == 0: - self.patternCanvas.create_rectangle(stitch * bitWidth,row * bitHeight,(stitch+1) * bitWidth,(row+1) * bitHeight, width=0, fill='black') + if (pattern[row][stitch]) == 1: + fill='black' + border='white' + else: + fill='white' + border='black' + self.patternCanvas.create_rectangle(stitch * bitWidth,row * bitHeight,(stitch+1) * bitWidth,(row+1) * bitHeight, width=1, fill=fill, outline=border) def updatePatternCanvasLastSize(self): self.patternCanvas.lastWidth = self.patternCanvas.getWidth() From 447fc936166b8dc18efa8c5a4fb9f91b0cb9f0fd Mon Sep 17 00:00:00 2001 From: ondro Date: Fri, 29 Nov 2013 01:56:49 +0100 Subject: [PATCH 13/20] Fix for kh940 (tested only pattern display yet, not pattern insert) Export of bmp into file --- app/gui/Gui.py | 9 ++++-- app/tkapp/Config.py | 3 +- app/tkapp/KnittingApp.py | 63 ++++++++++++++++++++++++++++++++++++---- brother.py | 19 +++++++----- 4 files changed, 77 insertions(+), 17 deletions(-) diff --git a/app/gui/Gui.py b/app/gui/Gui.py index d1004b7..1c9a2c6 100644 --- a/app/gui/Gui.py +++ b/app/gui/Gui.py @@ -77,7 +77,7 @@ def createPatternsPanel(self): patternFrame = Tkinter.Frame(self.mainWindow) patternFrame.grid(column=0, row=self._row, columnspan = self._maxColumns,sticky='EWNS') self.mainWindow.grid_rowconfigure(self._row,weight=1) - patternFrame.grid_columnconfigure(0,weight=1) + patternFrame.grid_columnconfigure(0,weight=0) patternFrame.grid_columnconfigure(1,weight=1) patternFrame.grid_rowconfigure(1,weight=1) @@ -85,7 +85,7 @@ def createPatternsPanel(self): listboxFrame.grid(column=0, row=0, sticky='EWNS', rowspan=self._maxRows) scrollbar = Tkinter.Scrollbar(listboxFrame, orient=Tkinter.VERTICAL) listvar = Tkinter.StringVar() - lb = Tkinter.Listbox(listboxFrame, listvariable=listvar, exportselection=0, width=50, yscrollcommand=scrollbar.set) + lb = Tkinter.Listbox(listboxFrame, listvariable=listvar, exportselection=0, width=40, yscrollcommand=scrollbar.set) scrollbar.config(command=lb.yview) lb.items = ListboxVar(lb, listvar) scrollbar.pack(side=Tkinter.RIGHT, fill=Tkinter.Y) @@ -101,8 +101,11 @@ def createPatternsPanel(self): self.insertBitmapButton = Tkinter.Button(patternFrame, text=u"Insert bitmap...", command = self.mainWindow.insertBitmapButtonClicked) self.insertBitmapButton.grid(column=2,row=0,sticky='EW') + self.exportBitmapButton = Tkinter.Button(patternFrame, text=u"Export bitmap...", command = self.mainWindow.exportBitmapButtonClicked) + self.exportBitmapButton.grid(column=3,row=0,sticky='EW') + pc = ExtendedCanvas(patternFrame, bg='white') - pc.grid(column=1, row=1, sticky='EWNS', columnspan=2) + pc.grid(column=1, row=1, sticky='EWNS', columnspan=3) self.mainWindow.patternCanvas = pc def setEmuButtonStopped(self): diff --git a/app/tkapp/Config.py b/app/tkapp/Config.py index 7160f52..be53d43 100644 --- a/app/tkapp/Config.py +++ b/app/tkapp/Config.py @@ -5,7 +5,8 @@ def __init__(self): self.imgdir = "img"; if os.sys.platform == 'win32': self.device = "com34" - self.datFile = 'c:\\Documents and Settings\\ondro\\VirtualBox shared folder\\knitting\\app\\img\\file-01.dat' +# self.datFile = 'C:/Documents and Settings/ondro/VirtualBox shared folder/knitting/knitting_machine-master/img/zaloha vzory stroj python.dat' + self.datFile = 'C:/Documents and Settings/ondro/VirtualBox shared folder/knitting/knitting_machine-master/file-06.dat' self.simulateEmulator = True else: self.device = "/dev/ttyUSB0" \ No newline at end of file diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py index a89902f..e8da816 100644 --- a/app/tkapp/KnittingApp.py +++ b/app/tkapp/KnittingApp.py @@ -12,6 +12,8 @@ import tkFileDialog import os import os.path +import Image +import itertools class KnittingApp(Tkinter.Tk): @@ -255,21 +257,44 @@ def printPatternOnCanvas(self, pattern): # pattern.append(row) patternHeight = len(pattern) patternWidth = len(pattern[0]) - bitWidth = self.patternCanvas.getWidth() / patternWidth; - bitHeight = self.patternCanvas.getHeight() / patternHeight; + marginx, marginy = 10, 10 + bitWidth = (self.patternCanvas.getWidth() - marginx) / (patternWidth); + bitHeight = (self.patternCanvas.getHeight() - marginy)/ (patternHeight); bitWidth = min(bitWidth, bitHeight) bitHeight = bitWidth + self._printPatternBody(pattern, marginx, marginy, bitWidth, bitHeight) + secCoordbig, secCoordsmall, secCoord2 = 0, marginy / 2, marginy + step, bigStep = 5, 10 + for i in xrange(0, patternWidth+1, step): + xCoord = marginx + i * bitWidth + yCoord = marginx + i * bitHeight + secCoord = secCoordbig if i % bigStep == 0 else secCoordsmall + self.patternCanvas.create_line(xCoord, secCoord, xCoord, secCoord2) + self.patternCanvas.create_line(secCoord, yCoord, secCoord2, yCoord) + + def _printPatternBody(self, pattern, patternPosx, patternPosy, bitWidth, bitHeight): + patternHeight = len(pattern) + patternWidth = len(pattern[0]) self.patternCanvas.clear() - for row in range(len(pattern)): - for stitch in range(len(pattern[row])): + for row in xrange(patternHeight): + for stitch in xrange(patternWidth): if (pattern[row][stitch]) == 1: fill='black' border='white' + #border=fill else: fill='white' border='black' - self.patternCanvas.create_rectangle(stitch * bitWidth,row * bitHeight,(stitch+1) * bitWidth,(row+1) * bitHeight, width=1, fill=fill, outline=border) - + #border=fill + row = patternHeight - row - 1 + self.patternCanvas.create_rectangle(patternPosx + stitch * bitWidth, patternPosy + row * bitHeight, + patternPosx + (stitch+1) * bitWidth, patternPosy + (row+1) * bitHeight, width=1, fill=fill, outline=border) + # pattern border + self.patternCanvas.create_rectangle(patternPosx, patternPosy, + patternPosx + (patternWidth) * bitWidth, patternPosy + (patternHeight) * bitHeight, width=1, outline='black') + + + def updatePatternCanvasLastSize(self): self.patternCanvas.lastWidth = self.patternCanvas.getWidth() self.patternCanvas.lastHeight = self.patternCanvas.getHeight() @@ -293,6 +318,32 @@ def insertBitmapButtonClicked(self): title='Choose bitmap file to insert...') if len(filePath) > 0: self.insertBitmap(filePath, pattern["number"]) + + def exportBitmapButtonClicked(self): + sel = self.patternListBox.curselection() + if len(sel) == 0: + self.msg.showError('Target pattern for saving must be selected!') + return + index = int(sel[0]) + pattern = self.patterns[index] + filePath = tkFileDialog.asksaveasfilename(filetypes=[('2-color Bitmap', '*.bmp')], + title='Save as a bitmap file...') + if len(filePath) > 0: + patternNumber = pattern['number'] + self.msg.showInfo('Saving pattern number %d as bmp file %s' % (patternNumber, filePath)) + result = self.patternDumper.dumppattern([self.currentDatFile, str(patternNumber)]) + pattern = result.pattern + patternHeight = len(pattern) + patternWidth = len(pattern[0]) + img = Image.new('RGB', (patternWidth, patternHeight), None) + for x in xrange(patternWidth): + for y in xrange(patternHeight): + color = (0,0,0) if pattern[patternHeight - y - 1][x] == 1 else (255,255,255) + img.putpixel((x,y), color) + img = img.convert('1') + img.save(filePath, 'BMP') + self.msg.showInfo('Saved pattern number %d as bmp file %s' % (patternNumber, filePath)) + def insertBitmap(self, bitmapFile, patternNumber): self.msg.showInfo('Inserting dat file %s to pattern number %d' % (bitmapFile, patternNumber)) diff --git a/brother.py b/brother.py index 2790532..ceaac81 100644 --- a/brother.py +++ b/brother.py @@ -23,11 +23,13 @@ #import os.path #import string from array import * +import ctypes __version__ = '1.0' +machineFormat = 'kh940' # Some file location constants -initPatternOffset = 0x06DF # programmed patterns start here, grow down +initPatternOffset = 0x06DF + 288 if machineFormat == 'kh940' else 0x06DF # programmed patterns start here, grow down currentPatternAddr = 0x07EA # stored in MSN and following byte currentRowAddr = 0x06FF nextRowAddr = 0x072F @@ -168,7 +170,6 @@ def getRowData(self, pattOffset, stitches, rownumber): if stitches: row.append((nib & 0x08) >> 3) stitches = stitches - 1 - return row def getPatterns(self, patternNumber = None): @@ -214,22 +215,26 @@ def getPatterns(self, patternNumber = None): print 'Unk = %d, Unknown = 0x%02X (%d)' % (unk, unknown, unknown) if flag != 0: # valid entry + if machineFormat == 'kh940': + pptr = + initPatternOffset - ((flag << 8) + unknown) memoff = pptr if self.verbose: - print "Memo #",patno, "offset ", hex(memoff) - patoff = pptr - bytesForMemo(rows) + print "Memo #",patno, "offset ", memoff + bytes = bytesForMemo(rows) + patoff = pptr - bytes if self.verbose: - print "Pattern #",patno, "offset ", hex(patoff) + print "Pattern #",patno, "offset ", patoff pptr = pptr - bytesPerPatternAndMemo(stitches, rows) + if patno in (905,908): + pptr += 0 if self.verbose: print "Ending offset ", hex(pptr) - # TODO figure out how to calculate pattern length - #pptr = pptr - something if patternNumber: if patternNumber == patno: patlist.append({'number':patno, 'stitches':stitches, 'rows':rows, 'memo':memoff, 'pattern':patoff, 'pattend':pptr}) else: patlist.append({'number':patno, 'stitches':stitches, 'rows':rows, 'memo':memoff, 'pattern':patoff, 'pattend':pptr}) + print "Append pattern:",'number',patno, 'stitches',stitches, 'rows',rows, 'memo',memoff, 'pattern',patoff, 'bytesPattern',bytesPerPatternAndMemo(stitches, rows), 'bytesMemo',bytesForMemo(rows), 'flag', flag else: break return patlist From c0ecc0dba3c2a3f888ebdb738707b6041e8fd5e3 Mon Sep 17 00:00:00 2001 From: ondro Date: Fri, 29 Nov 2013 02:34:46 +0100 Subject: [PATCH 14/20] Black in bmp is inserted as black into machine (not inverse) --- brother.py | 9 ++++++--- insertpattern.py | 29 +++++++++++++---------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/brother.py b/brother.py index ceaac81..afb494c 100644 --- a/brother.py +++ b/brother.py @@ -26,10 +26,11 @@ import ctypes __version__ = '1.0' -machineFormat = 'kh940' + +MACHINE_FORMAT = 'FirstLady' # Some file location constants -initPatternOffset = 0x06DF + 288 if machineFormat == 'kh940' else 0x06DF # programmed patterns start here, grow down +initPatternOffset = 0x06DF + 288 if MACHINE_FORMAT == 'FirstLady' else 0x06DF # programmed patterns start here, grow down currentPatternAddr = 0x07EA # stored in MSN and following byte currentRowAddr = 0x06FF nextRowAddr = 0x072F @@ -92,6 +93,8 @@ def bytesPerPatternAndMemo(stitches, rows): class brotherFile(object): + machineFormat = MACHINE_FORMAT + def __init__(self, fn): self.dfn = None self.verbose = False @@ -215,7 +218,7 @@ def getPatterns(self, patternNumber = None): print 'Unk = %d, Unknown = 0x%02X (%d)' % (unk, unknown, unknown) if flag != 0: # valid entry - if machineFormat == 'kh940': + if self.machineFormat == 'FirstLady': pptr = + initPatternOffset - ((flag << 8) + unknown) memoff = pptr if self.verbose: diff --git a/insertpattern.py b/insertpattern.py index 14c8870..9ebb221 100644 --- a/insertpattern.py +++ b/insertpattern.py @@ -110,20 +110,15 @@ def insertPattern(self, oldbrotherfile, pattnum, imgfile, newbrotherfile): y = 0 x = width - 1 - while x > 0: - value = TheImage.getpixel((x,y)) - if value: - self.printPattern('* ') - else: - self.printPattern(' ') - #sys.stdout.write(str(value)) - x = x-1 - if x == 0: #did we hit the end of the line? - y = y+1 - x = width - 1 - print " " - if y == height: - break + for y in xrange(height): + for x in xrange(width): + value = TheImage.getpixel((x,y)) + if value: + self.printPattern('* ') + else: + self.printPattern(' ') + print " " + # debugging stuff done # now to make the actual, yknow memo+pattern data @@ -138,8 +133,10 @@ def insertPattern(self, oldbrotherfile, pattnum, imgfile, newbrotherfile): for r in range(height): row = [] # we'll chunk in bits and then put em into nibbles for s in range(width): - value = TheImage.getpixel((width-s-1,height-r-1)) - if (value != 0): + x = s if bf.machineFormat == 'FirstLady' else width-s-1 + value = TheImage.getpixel((x,height-r-1)) + isBlack = (value == 0) if bf.machineFormat == 'FirstLady' else (value != 0) + if (isBlack): row.append(1) else: row.append(0) From ea9a2dec7c24dbd5a1b5b5120782d013b827c27f Mon Sep 17 00:00:00 2001 From: ondro Date: Fri, 29 Nov 2013 23:31:32 +0100 Subject: [PATCH 15/20] Generalized new algorithm to work with any memory size. Fixed displaying of markers on sides of the image --- app/tkapp/KnittingApp.py | 13 +- brother.py | 24 +-- doc/kh940_format.txt | 315 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+), 14 deletions(-) create mode 100644 doc/kh940_format.txt diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py index e8da816..4c3a6c3 100644 --- a/app/tkapp/KnittingApp.py +++ b/app/tkapp/KnittingApp.py @@ -265,12 +265,14 @@ def printPatternOnCanvas(self, pattern): self._printPatternBody(pattern, marginx, marginy, bitWidth, bitHeight) secCoordbig, secCoordsmall, secCoord2 = 0, marginy / 2, marginy step, bigStep = 5, 10 - for i in xrange(0, patternWidth+1, step): - xCoord = marginx + i * bitWidth - yCoord = marginx + i * bitHeight + for i in xrange(0, max(patternWidth, patternHeight)+1, step): secCoord = secCoordbig if i % bigStep == 0 else secCoordsmall - self.patternCanvas.create_line(xCoord, secCoord, xCoord, secCoord2) - self.patternCanvas.create_line(secCoord, yCoord, secCoord2, yCoord) + if i < patternWidth: + xCoord = marginx + i * bitWidth + self.patternCanvas.create_line(xCoord, secCoord, xCoord, secCoord2) + if i < patternHeight: + yCoord = marginx + i * bitHeight + self.patternCanvas.create_line(secCoord, yCoord, secCoord2, yCoord) def _printPatternBody(self, pattern, patternPosx, patternPosy, bitWidth, bitHeight): patternHeight = len(pattern) @@ -287,6 +289,7 @@ def _printPatternBody(self, pattern, patternPosx, patternPosy, bitWidth, bitHeig border='black' #border=fill row = patternHeight - row - 1 + #stitch = patternWidth - stitch - 1 self.patternCanvas.create_rectangle(patternPosx + stitch * bitWidth, patternPosy + row * bitHeight, patternPosx + (stitch+1) * bitWidth, patternPosy + (row+1) * bitHeight, width=1, fill=fill, outline=border) # pattern border diff --git a/brother.py b/brother.py index afb494c..a2d47aa 100644 --- a/brother.py +++ b/brother.py @@ -27,10 +27,11 @@ __version__ = '1.0' -MACHINE_FORMAT = 'FirstLady' +methodWithPointers = False +methodWithPointers = True # uncomment this to use new, more precise method of finding patterns in the dat file, based on kh940 format documentation from https://github.com/stg/knittington/blob/master/doc/kh940_format.txt (should work with all kh930 and kh940 models) # Some file location constants -initPatternOffset = 0x06DF + 288 if MACHINE_FORMAT == 'FirstLady' else 0x06DF # programmed patterns start here, grow down +initPatternOffset = 0x06DF # programmed patterns start here, grow down currentPatternAddr = 0x07EA # stored in MSN and following byte currentRowAddr = 0x06FF nextRowAddr = 0x072F @@ -93,8 +94,6 @@ def bytesPerPatternAndMemo(stitches, rows): class brotherFile(object): - machineFormat = MACHINE_FORMAT - def __init__(self, fn): self.dfn = None self.verbose = False @@ -109,10 +108,18 @@ def __init__(self, fn): print 'Unable to open brother file <%s>' % fn raise try: - self.data = self.df.read(2048) + if methodWithPointers: + self.data = self.df.read(-1) + else: + self.data = self.df.read(2048) self.df.close() + if len(self.data) == 0: + raise Exception() except: - print 'Unable to read 2048 bytes from file <%s>' % fn + if methodWithPointers: + print 'Unable to read 2048 bytes from file <%s>' % fn + else: + print 'Unable to read data from file <%s>' % fn raise self.dfn = fn return @@ -215,11 +222,10 @@ def getPatterns(self, patternNumber = None): # we have this entry if self.verbose: print ' Pattern %3d: %3d Rows, %3d Stitches - ' % (patno, rows, stitches) - print 'Unk = %d, Unknown = 0x%02X (%d)' % (unk, unknown, unknown) if flag != 0: # valid entry - if self.machineFormat == 'FirstLady': - pptr = + initPatternOffset - ((flag << 8) + unknown) + if methodWithPointers: + pptr = len(self.data) -1 - ((flag << 8) + unknown) memoff = pptr if self.verbose: print "Memo #",patno, "offset ", memoff diff --git a/doc/kh940_format.txt b/doc/kh940_format.txt new file mode 100644 index 0000000..d8538d8 --- /dev/null +++ b/doc/kh940_format.txt @@ -0,0 +1,315 @@ +== BROTHER ELECTROKNIT KH940 FLOPPY FILE/MEMORY STRUCTURE ============ +Source: https://github.com/stg/knittington/blob/master/doc/kh940_format.txt + +The machine uses the first 32 sectors on a floppy for storage. +For the entirety of this document, the contents of these sectors +are treated as a single coherent 32kB chunk of binary data with +big-endian format where applicable. + +Note that this is not likely an actual file format, rather a binary +dump of all contents in the external RAM memory of the machine. + +* When writing about offsets, they are from LAST_BYTE (end of file), + Example: The file address for offset of 0x0120 would be + 0x7EDF = 0x7FFF - 0x0120 + +** When writing about alternatives, it means that contents is not + entirely classified, and could be any of several alternatives. + Further investigation was not warranted because knowing the + exact specification is not necessary to build a functional + disk image. + +Offset Bytes Content + +0x0000 686 PATTERN_LIST + + Contains array of *up to* 98 pattern descriptors. + Unused items are filled with 0x55 except for FINHDR which + also needs XX=0 and PTN_NUM=next unused id. + + Patterns are not always stored in linear order, ie the list + may be 901, 904, 902, 903. This happens when patterns are + deleted in the machine. + + Content Address (Offset* from header) + /---------------\ <- 0x0000 + | Header 901 | + \---------------/ <- 0x0006 + + /---------------\ <- 0x0007 + | Header 902 | + \---------------/ <- 0x000D + + /---------------\ <- 0x000E + | FINHDR | + \---------------/ <- 0x0014 + + /---------------\ <- 0x0015 <- CONTROL_DATA 0x10 points here + . . . + . . . + | Unused memory | + \---------------/ <- 0x02AD + + Each entry is 7 bytes: + + / DATA_OFFSET \ / HEIGHT \/ WIDTH \ /XX\/ PTN_NUM \ + OOOOOOOO-OOOOOOOO-HHHHHHHH-HHHHWWWW-WWWWWWWW-0000NNNN-NNNNNNNN + \byte 0/ \byte 1/ \byte 2/ \byte 3/ \byte 4/ \byte 5/ \byte 6/ + + DATA_OFFSET + Format: Binary 16bit unsigned + Data: Offset* to last byte of pattern data + + HEIGHT + Format: BCD 3nibbles/digits + Data: Heigh/number of rows + + WIDTH + Format: BCD 3nibbles/digits + Data: Width/number of stitches + + XX + Always a 0x0 nibble + + PTN_NUM + Format: BCD 3nibbles/digits + Data: Pattern number 0x901 to 0x998 + + +0x02AE 31794 PATTERN_MEMORY + + Pattern memory is arranged much as a stack and is written backwards + so that the first pattern has it's last byte in location 0x7EDF. + + Example: + + Content Address (Offset* from header) + /---------------\ <- 0x02AE + . . . + . . . + | Unused memory | + \---------------/ <- 0x7ECF + + /---------------\ <- 0x7ED0 + | Pattern 902 | + \---------------/ <- 0x7EDB (0x0124) + + /---------------\ <- 0x7EDC + | Pattern 901 | + \---------------/ <- 0x7EDF (0x0120) + + There MAY be gaps between patterns read from the machine so do not + expect patterns to be arranged end-to-end: USE THE POINTERS! + + Unused memory is filled with either 0x55 or 0xAA. + + Each pattern is divided into two sections, arranged as: + + /---------------\ + | PatternDATA | + \---------------/ + + /---------------\ + | PatternMEMO | + \---------------/ <- Offset* points here + + MEMO + + Each row of the pattern may have a number assigned to it but + it is not significant for operation, consider it meta-data. + + Each such number requires one nibble for every row, ie HEIGHT/2. + This is rounded to the nearest byte, hence a pattern + containing 7 rows will have 4 bytes of MEMO data. + A pattern with 10 rows will have 5 bytes of MEMO data. + + This can be calculated as: + ceil(height/2) // where ceil() is round upwards + + Write as: 0x00 + + DATA + + Each row of the pattern requires WIDTH bits for storage and + this is rounded up to the nearest nibble by padding it with + 0-bits until the number of bits is a multiple of 4. + + A pattern that has 7 stitches will therefore require 2 bytes + per row. One that has 9 stiches will require 2.5 bytes per row. + + This means that rows may not be aligned on byte boundaries. + + Patterns are stored top-down, right-left. + This means the pattern is flipped right to left compared to the + actual knitted design once completed. + + The number of bytes used to store data can be calculated as: + ceil(ceil(width/4)*height/2) // where ceil() is round upwards + + The layout is best described via examples: + 0 = pad bits (binary 0) + X = stitch bits (binary 1) + - = no stitch bits (binary 0) + + Example 1 - 4x4 + ---- Row 0: 0x0 + -XX- Row 1: 0x6 + -XX- Row 2: 0x6 + ---- Row 3: 0x0 + Byte data (hex): 06 60 + + + Example 2 - 6x5 + 00X----X Row 0: 0x21 + 00------ Row 1: 0x00 + 00--XX-- Row 2: 0x0C + 00X----X Row 3: 0x21 + 00-XXXX- Row 4: 0x1E + Byte data (hex): 21 00 0C 21 1E + + Example 3 - 9x9 + 0000 Padding: 0x0 + 000XXXXXXXXX Row 0: 0x1FF + 000X-------X Row 1: 0x101 + 000X-XXXXX-X Row 2: 0x17D + 000X-X---X-X Row 3: 0x145 + 000X-X-X-X-X Row 4: 0x155 + 000X-X---X-X Row 5: 0x145 + 000X-XXXXX-X Row 6: 0x17D + 000X-------X Row 7: 0x101 + 000XXXXXXXXX Row 8: 0x1FF + Byte data (hex): 01 FF 10 11 7D 14 51 55 14 51 7D 10 11 FF + + Note for example 3 that there is a nibble of padding before the + design because the design does not round up to an even byte. + + +0x7EE0 7 AREA0 + + Seems to be completely unused. + + Content with patterns: 0x55 or 0xAA + Content after format: 0x55 + Write as: 0x55 + + +0x7EE7 25 AREA1 + + Contains unidentified bit pattern. + Best guess is working memory for pattern input. + + Content with patterns: several repetitions of an n-byte pattern + Content after format: 0x55 + Write as: 0x00 + + +0x7F00 23 CONTROL_DATA + + 0x00 2 PATTERN_PTR1 + Format: Binary 16-bit unsigned + Data: Offset* of next byte to be written by next pattern input + Write as: Offset of first byte of last pattern + 1 + + 0x02 2 UNK1 + Format: Binary 16-bit unsigned + Data: Unknown, 0x0001 with patterns, 0x0000 after format + Write as: 0x0001 + + 0x04 2 PATTERN_PTR0 + Format: Binary 16-bit unsigned + Data: Same as BYTE 0-1 with patterns, 0x0000 after format + Write as: Offset of first byte of last pattern + 1 + + 0x06 2 LAST_BOTTOM + Format: Binary 16-bit unsigned + Data: Alt1**: Offset to last byte of currently active pattern + Alt2**: Offset to last byte of last created pattern + Write as: Offset to last byte of last created pattern + + 0x08 2 UNK2 + Data: Always 0x0000 + Write as: 0x0000 + + 0x0A 2 LAST_TOP + Format: Binary 16-bit unsigned + Data: Alt1**: Offset to first byte of currently active pattern + Alt2**: Offset to first byte of last created pattern + Write as: Offset to first byte of last created pattern + + 0x0C 4 UNK3 + Data: Unknown, 0x00008100 with patterns, 0x00000000 after format + Write as: 0x00008100 + + 0x10 2 HEADER_PTR + Format: Binary 16-bit unsigned + Data: Offset to end of pattern header list, 0x7FF9 after format + See PATTERN_LIST memory layout for exact pointer + Write as: 0x7FF9 + + 0x12 2 UNK_PTR + Format: Binary 16-bit unsigned + Data: Unknown offset + This was only present in one file, after a delete had + recently been made. Best guess is mem. move pointer. + Write as: 0x0000 + + 0x14 3 UNK4 + Data: Always 0x000000 + Write as: 0x000000 + + +0x7F17 25 AREA2 + + Contains unidentified bit pattern. + Best guess is working memory for pattern input. + + Content with patterns: several repetitions of an n-byte pattern + Content after format: 0x55 + Write as: 0x00 + + +0x7F30 186 AREA3 + + Seems to be completely unused. + + Content with patterns: 0x00 + Content after format : 0x00 + Write as: 0x00 + + +0x7FEA 2 LOADED_PATTERN + + Alt1**: Contains currently active pattern number. + Alt2**: Contains last created pattern number. + + Content after format : 0x1000 + Write as: Last created pattern number + + [XX]/ PTN_NUM \ + 0001NNNN-NNNNNNNN + \byte 0/ \byte 1/ + + XX + Always a 0x1 nibble + + PTN_NUM + Format: BCD 3nibbles/digits + Data: Pattern number + + +0x7FEC 19 AREA4 + + Seems to be completely unused. + + Content with patterns: 0x00 + Content after format : 0x00 + Write as: 0x00 + + +0x7FFF 1 LAST_BYTE + + Contains unidentified byte. + + Content with patterns: Different every time + Content after format: 0x00 + Write as: 0x02 From d8fcc9ebed6d5d2a24893a170331ec4ef9af8cbd Mon Sep 17 00:00:00 2001 From: ondro Date: Sat, 30 Nov 2013 01:26:42 +0100 Subject: [PATCH 16/20] Removed rubbish code --- brother.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/brother.py b/brother.py index a2d47aa..86fcfa4 100644 --- a/brother.py +++ b/brother.py @@ -229,13 +229,10 @@ def getPatterns(self, patternNumber = None): memoff = pptr if self.verbose: print "Memo #",patno, "offset ", memoff - bytes = bytesForMemo(rows) - patoff = pptr - bytes + patoff = pptr - bytesForMemo(rows) if self.verbose: print "Pattern #",patno, "offset ", patoff pptr = pptr - bytesPerPatternAndMemo(stitches, rows) - if patno in (905,908): - pptr += 0 if self.verbose: print "Ending offset ", hex(pptr) if patternNumber: @@ -243,7 +240,6 @@ def getPatterns(self, patternNumber = None): patlist.append({'number':patno, 'stitches':stitches, 'rows':rows, 'memo':memoff, 'pattern':patoff, 'pattend':pptr}) else: patlist.append({'number':patno, 'stitches':stitches, 'rows':rows, 'memo':memoff, 'pattern':patoff, 'pattend':pptr}) - print "Append pattern:",'number',patno, 'stitches',stitches, 'rows',rows, 'memo',memoff, 'pattern',patoff, 'bytesPattern',bytesPerPatternAndMemo(stitches, rows), 'bytesMemo',bytesForMemo(rows), 'flag', flag else: break return patlist From ce7c02b3f2bea8795cdceb5c5feb29c99c804995 Mon Sep 17 00:00:00 2001 From: ondro Date: Sat, 30 Nov 2013 01:32:35 +0100 Subject: [PATCH 17/20] import convenience functions from brother module --- dumppattern.py | 36 +++--------------------------------- insertpattern.py | 36 +++--------------------------------- 2 files changed, 6 insertions(+), 66 deletions(-) diff --git a/dumppattern.py b/dumppattern.py index 5a85ee6..d9412b5 100644 --- a/dumppattern.py +++ b/dumppattern.py @@ -20,43 +20,13 @@ import sys import brother +# import convenience functions from brother module +from brother import roundeven, roundfour, roundeight, nibblesPerRow, bytesPerPattern, bytesForMemo + DEBUG = 0 version = '1.0' -########## - -def roundeven(val): - return (val+(val%2)) - -def roundeight(val): - if val % 8: - return val + (8-(val%8)) - else: - return val - -def roundfour(val): - if val % 4: - return val + (4-(val%4)) - else: - return val - -def nibblesPerRow(stitches): - # there are four stitches per nibble - # each row is nibble aligned - return(roundfour(stitches)/4) - -def bytesPerPattern(stitches, rows): - nibbs = rows * nibblesPerRow(stitches) - bytes = roundeven(nibbs)/2 - return bytes - -def bytesForMemo(rows): - bytes = roundeven(rows)/2 - return bytes - -############## - class PatternDumper: def __init__(self): diff --git a/insertpattern.py b/insertpattern.py index 9ebb221..12f6b10 100644 --- a/insertpattern.py +++ b/insertpattern.py @@ -22,43 +22,13 @@ import Image import array +# import convenience functions from brother module +from brother import roundeven, roundfour, roundeight, nibblesPerRow, bytesPerPattern, bytesForMemo + TheImage = None version = '1.0' -################## - -def roundeven(val): - return (val+(val%2)) - -def roundeight(val): - if val % 8: - return val + (8-(val%8)) - else: - return val - -def roundfour(val): - if val % 4: - return val + (4-(val%4)) - else: - return val - -def nibblesPerRow(stitches): - # there are four stitches per nibble - # each row is nibble aligned - return(roundfour(stitches)/4) - -def bytesPerPattern(stitches, rows): - nibbs = rows * nibblesPerRow(stitches) - bytes = roundeven(nibbs)/2 - return bytes - -def bytesForMemo(rows): - bytes = roundeven(rows)/2 - return bytes - -############## - class PatternInserter: def __init__(self): self.printInfoCallback = self.printInfo From 6b840094b9c67525a4d3feabcc66976d3de25314 Mon Sep 17 00:00:00 2001 From: ondro Date: Sat, 30 Nov 2013 02:12:47 +0100 Subject: [PATCH 18/20] Added some documentation about my changes --- Changelog | 3 +++ README | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/Changelog b/Changelog index 9d79ead..e9bc0d7 100644 --- a/Changelog +++ b/Changelog @@ -1,4 +1,7 @@ ====================================================================== +Nov 29, 2013 +Added new graphical interface. +Added new algorithm for reading patterns based in pointers (should work with most brother machines) Feb 19, 2012 Renamed PDDemulate-1.0.py to PDDemulate.py, and put the version number in the script. Print version number on script start diff --git a/README b/README index d523fce..c6599cc 100644 --- a/README +++ b/README @@ -1,5 +1,13 @@ Please see the Changelog file for the latest changes +=== QUICK START === + +Execute script guimain.py to start graphical application. +This application can start floppy disk emulator and provides means to download/upload patterns and modify them. +It is based on existing python scripts, which were slightly modified for better user experience in graphical application. + +================= + These files are related to the Brother KH-930E knitting machine, and other similar models. === NOTE === From 4ce469361612f4f05fa59fd57a1da8a46258c0cc Mon Sep 17 00:00:00 2001 From: ondro Date: Sat, 30 Nov 2013 02:26:00 +0100 Subject: [PATCH 19/20] moved document into common docs folder --- doc/kh940_format.txt | 315 ------------------------------------------- 1 file changed, 315 deletions(-) delete mode 100644 doc/kh940_format.txt diff --git a/doc/kh940_format.txt b/doc/kh940_format.txt deleted file mode 100644 index d8538d8..0000000 --- a/doc/kh940_format.txt +++ /dev/null @@ -1,315 +0,0 @@ -== BROTHER ELECTROKNIT KH940 FLOPPY FILE/MEMORY STRUCTURE ============ -Source: https://github.com/stg/knittington/blob/master/doc/kh940_format.txt - -The machine uses the first 32 sectors on a floppy for storage. -For the entirety of this document, the contents of these sectors -are treated as a single coherent 32kB chunk of binary data with -big-endian format where applicable. - -Note that this is not likely an actual file format, rather a binary -dump of all contents in the external RAM memory of the machine. - -* When writing about offsets, they are from LAST_BYTE (end of file), - Example: The file address for offset of 0x0120 would be - 0x7EDF = 0x7FFF - 0x0120 - -** When writing about alternatives, it means that contents is not - entirely classified, and could be any of several alternatives. - Further investigation was not warranted because knowing the - exact specification is not necessary to build a functional - disk image. - -Offset Bytes Content - -0x0000 686 PATTERN_LIST - - Contains array of *up to* 98 pattern descriptors. - Unused items are filled with 0x55 except for FINHDR which - also needs XX=0 and PTN_NUM=next unused id. - - Patterns are not always stored in linear order, ie the list - may be 901, 904, 902, 903. This happens when patterns are - deleted in the machine. - - Content Address (Offset* from header) - /---------------\ <- 0x0000 - | Header 901 | - \---------------/ <- 0x0006 - - /---------------\ <- 0x0007 - | Header 902 | - \---------------/ <- 0x000D - - /---------------\ <- 0x000E - | FINHDR | - \---------------/ <- 0x0014 - - /---------------\ <- 0x0015 <- CONTROL_DATA 0x10 points here - . . . - . . . - | Unused memory | - \---------------/ <- 0x02AD - - Each entry is 7 bytes: - - / DATA_OFFSET \ / HEIGHT \/ WIDTH \ /XX\/ PTN_NUM \ - OOOOOOOO-OOOOOOOO-HHHHHHHH-HHHHWWWW-WWWWWWWW-0000NNNN-NNNNNNNN - \byte 0/ \byte 1/ \byte 2/ \byte 3/ \byte 4/ \byte 5/ \byte 6/ - - DATA_OFFSET - Format: Binary 16bit unsigned - Data: Offset* to last byte of pattern data - - HEIGHT - Format: BCD 3nibbles/digits - Data: Heigh/number of rows - - WIDTH - Format: BCD 3nibbles/digits - Data: Width/number of stitches - - XX - Always a 0x0 nibble - - PTN_NUM - Format: BCD 3nibbles/digits - Data: Pattern number 0x901 to 0x998 - - -0x02AE 31794 PATTERN_MEMORY - - Pattern memory is arranged much as a stack and is written backwards - so that the first pattern has it's last byte in location 0x7EDF. - - Example: - - Content Address (Offset* from header) - /---------------\ <- 0x02AE - . . . - . . . - | Unused memory | - \---------------/ <- 0x7ECF - - /---------------\ <- 0x7ED0 - | Pattern 902 | - \---------------/ <- 0x7EDB (0x0124) - - /---------------\ <- 0x7EDC - | Pattern 901 | - \---------------/ <- 0x7EDF (0x0120) - - There MAY be gaps between patterns read from the machine so do not - expect patterns to be arranged end-to-end: USE THE POINTERS! - - Unused memory is filled with either 0x55 or 0xAA. - - Each pattern is divided into two sections, arranged as: - - /---------------\ - | PatternDATA | - \---------------/ - - /---------------\ - | PatternMEMO | - \---------------/ <- Offset* points here - - MEMO - - Each row of the pattern may have a number assigned to it but - it is not significant for operation, consider it meta-data. - - Each such number requires one nibble for every row, ie HEIGHT/2. - This is rounded to the nearest byte, hence a pattern - containing 7 rows will have 4 bytes of MEMO data. - A pattern with 10 rows will have 5 bytes of MEMO data. - - This can be calculated as: - ceil(height/2) // where ceil() is round upwards - - Write as: 0x00 - - DATA - - Each row of the pattern requires WIDTH bits for storage and - this is rounded up to the nearest nibble by padding it with - 0-bits until the number of bits is a multiple of 4. - - A pattern that has 7 stitches will therefore require 2 bytes - per row. One that has 9 stiches will require 2.5 bytes per row. - - This means that rows may not be aligned on byte boundaries. - - Patterns are stored top-down, right-left. - This means the pattern is flipped right to left compared to the - actual knitted design once completed. - - The number of bytes used to store data can be calculated as: - ceil(ceil(width/4)*height/2) // where ceil() is round upwards - - The layout is best described via examples: - 0 = pad bits (binary 0) - X = stitch bits (binary 1) - - = no stitch bits (binary 0) - - Example 1 - 4x4 - ---- Row 0: 0x0 - -XX- Row 1: 0x6 - -XX- Row 2: 0x6 - ---- Row 3: 0x0 - Byte data (hex): 06 60 - - - Example 2 - 6x5 - 00X----X Row 0: 0x21 - 00------ Row 1: 0x00 - 00--XX-- Row 2: 0x0C - 00X----X Row 3: 0x21 - 00-XXXX- Row 4: 0x1E - Byte data (hex): 21 00 0C 21 1E - - Example 3 - 9x9 - 0000 Padding: 0x0 - 000XXXXXXXXX Row 0: 0x1FF - 000X-------X Row 1: 0x101 - 000X-XXXXX-X Row 2: 0x17D - 000X-X---X-X Row 3: 0x145 - 000X-X-X-X-X Row 4: 0x155 - 000X-X---X-X Row 5: 0x145 - 000X-XXXXX-X Row 6: 0x17D - 000X-------X Row 7: 0x101 - 000XXXXXXXXX Row 8: 0x1FF - Byte data (hex): 01 FF 10 11 7D 14 51 55 14 51 7D 10 11 FF - - Note for example 3 that there is a nibble of padding before the - design because the design does not round up to an even byte. - - -0x7EE0 7 AREA0 - - Seems to be completely unused. - - Content with patterns: 0x55 or 0xAA - Content after format: 0x55 - Write as: 0x55 - - -0x7EE7 25 AREA1 - - Contains unidentified bit pattern. - Best guess is working memory for pattern input. - - Content with patterns: several repetitions of an n-byte pattern - Content after format: 0x55 - Write as: 0x00 - - -0x7F00 23 CONTROL_DATA - - 0x00 2 PATTERN_PTR1 - Format: Binary 16-bit unsigned - Data: Offset* of next byte to be written by next pattern input - Write as: Offset of first byte of last pattern + 1 - - 0x02 2 UNK1 - Format: Binary 16-bit unsigned - Data: Unknown, 0x0001 with patterns, 0x0000 after format - Write as: 0x0001 - - 0x04 2 PATTERN_PTR0 - Format: Binary 16-bit unsigned - Data: Same as BYTE 0-1 with patterns, 0x0000 after format - Write as: Offset of first byte of last pattern + 1 - - 0x06 2 LAST_BOTTOM - Format: Binary 16-bit unsigned - Data: Alt1**: Offset to last byte of currently active pattern - Alt2**: Offset to last byte of last created pattern - Write as: Offset to last byte of last created pattern - - 0x08 2 UNK2 - Data: Always 0x0000 - Write as: 0x0000 - - 0x0A 2 LAST_TOP - Format: Binary 16-bit unsigned - Data: Alt1**: Offset to first byte of currently active pattern - Alt2**: Offset to first byte of last created pattern - Write as: Offset to first byte of last created pattern - - 0x0C 4 UNK3 - Data: Unknown, 0x00008100 with patterns, 0x00000000 after format - Write as: 0x00008100 - - 0x10 2 HEADER_PTR - Format: Binary 16-bit unsigned - Data: Offset to end of pattern header list, 0x7FF9 after format - See PATTERN_LIST memory layout for exact pointer - Write as: 0x7FF9 - - 0x12 2 UNK_PTR - Format: Binary 16-bit unsigned - Data: Unknown offset - This was only present in one file, after a delete had - recently been made. Best guess is mem. move pointer. - Write as: 0x0000 - - 0x14 3 UNK4 - Data: Always 0x000000 - Write as: 0x000000 - - -0x7F17 25 AREA2 - - Contains unidentified bit pattern. - Best guess is working memory for pattern input. - - Content with patterns: several repetitions of an n-byte pattern - Content after format: 0x55 - Write as: 0x00 - - -0x7F30 186 AREA3 - - Seems to be completely unused. - - Content with patterns: 0x00 - Content after format : 0x00 - Write as: 0x00 - - -0x7FEA 2 LOADED_PATTERN - - Alt1**: Contains currently active pattern number. - Alt2**: Contains last created pattern number. - - Content after format : 0x1000 - Write as: Last created pattern number - - [XX]/ PTN_NUM \ - 0001NNNN-NNNNNNNN - \byte 0/ \byte 1/ - - XX - Always a 0x1 nibble - - PTN_NUM - Format: BCD 3nibbles/digits - Data: Pattern number - - -0x7FEC 19 AREA4 - - Seems to be completely unused. - - Content with patterns: 0x00 - Content after format : 0x00 - Write as: 0x00 - - -0x7FFF 1 LAST_BYTE - - Contains unidentified byte. - - Content with patterns: Different every time - Content after format: 0x00 - Write as: 0x02 From 59be3ab926f0a79afd41b34c3778dfe7dcf13916 Mon Sep 17 00:00:00 2001 From: ondro Date: Sat, 30 Nov 2013 19:13:36 +0100 Subject: [PATCH 20/20] Fixed insert pattern script, fixed emulator putting CUP to 100% when listening to machine --- PDDemulate.py | 8 +++++--- app/tkapp/KnittingApp.py | 6 +++--- insertpattern.py | 6 +++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/PDDemulate.py b/PDDemulate.py index d749681..f5293eb 100644 --- a/PDDemulate.py +++ b/PDDemulate.py @@ -391,9 +391,10 @@ def handleRequests(self): # never returns return - def handleRequest(self): - if self.ser.inWaiting() == 0: - return + def handleRequest(self, blocking = True): + if not blocking: + if self.ser.inWaiting() == 0: + return inc = self.readchar() if self.FDCmode: self.handleFDCmodeRequest(inc) @@ -618,6 +619,7 @@ def handleFDCmodeRequest(self, cmd): self.disk.writeSector(psn, lsn, indata) for l in self.listeners: l.dataReceived(self.disk.lastDatFilePath) + print 'Saved data in dat file: ', self.disk.lastDatFilePath except: print 'Failed to write data for sector %d, quitting' % psn self.writebytes('80000000') diff --git a/app/tkapp/KnittingApp.py b/app/tkapp/KnittingApp.py index 4c3a6c3..b63b801 100644 --- a/app/tkapp/KnittingApp.py +++ b/app/tkapp/KnittingApp.py @@ -82,9 +82,9 @@ def startEmulator(self): def emulatorLoop(self): if self.emu.started: - self.emu.handleRequest() + self.emu.handleRequest(False) # repeated call to after_idle() caused all window dialogs to hang out application, using after() each 10 milliseconds - self.after(10,self.emulatorLoop) + self.after(100,self.emulatorLoop) def stopEmulator(self): if self.emu is not None: @@ -164,7 +164,7 @@ def storeTrack(self, pathToFile = None): track0file.write(t0dat) track1file.write(t1dat) - self.msg.showInfo('Stored file to tracks ' + trackFile1 + ' and ' + trackFile2 + ' in config.imgdir') + self.msg.showInfo('Stored file to tracks ' + trackFile1 + ' and ' + trackFile2 + ' in ' + self.config.imgdir) except Exception as e: self.msg.showError(str(e)) finally: diff --git a/insertpattern.py b/insertpattern.py index 12f6b10..212ba6a 100644 --- a/insertpattern.py +++ b/insertpattern.py @@ -23,7 +23,7 @@ import array # import convenience functions from brother module -from brother import roundeven, roundfour, roundeight, nibblesPerRow, bytesPerPattern, bytesForMemo +from brother import roundeven, roundfour, roundeight, nibblesPerRow, bytesPerPattern, bytesForMemo, methodWithPointers TheImage = None @@ -103,9 +103,9 @@ def insertPattern(self, oldbrotherfile, pattnum, imgfile, newbrotherfile): for r in range(height): row = [] # we'll chunk in bits and then put em into nibbles for s in range(width): - x = s if bf.machineFormat == 'FirstLady' else width-s-1 + x = s if methodWithPointers else width-s-1 value = TheImage.getpixel((x,height-r-1)) - isBlack = (value == 0) if bf.machineFormat == 'FirstLady' else (value != 0) + isBlack = (value == 0) if methodWithPointers else (value != 0) if (isBlack): row.append(1) else: