initial commit of theirs code

This commit is contained in:
ondro 2013-07-22 20:37:42 +02:00
commit c939773800
26 changed files with 4236 additions and 0 deletions

11
.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
# specific to knitting machine source
img
img.start
docs
test-data
# binary files
*.bmp
*.xcf
*.png
*.dat

6
Changelog Normal file
View file

@ -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.

633
PDDemu-debug.py Normal file
View file

@ -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()

637
PDDemulate.py Normal file
View file

@ -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()

60
README Normal file
View file

@ -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

226
addpattern.py Normal file
View file

@ -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()

380
brother.py Normal file
View file

@ -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()

202
dumppattern.py Normal file
View file

@ -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<<j)):
print "*",
else:
print " ",
print ""
# print it out in nibbles per row?
for row in range(rows):
for nibs in range(nibblesPerRow(stitches)):
n = bf.getIndexedNibble(pointer, nibblesPerRow(stitches)*row + nibs)
print hex(n),
for j in range(8):
if (n & (1<<j)):
print "*",
else:
print " ",
print ""
pointer -= bytesPerPattern(stitches, rows)
#for i in range (0x06DF, 99*7, -1):
# print "\t",hex(i),": ",hex(bf.getIndexedByte(i))
else:
print 'Searching for pattern number %d' % patt
pats = bf.getPatterns(patt)
if len(pats) == 0:
print 'pattern %d not found' % patt
else:
stitches = pats[0]["stitches"]
rows = pats[0]["rows"]
print '%3d Stitches, %3d Rows' % (stitches, rows)
pattern = bf.getPattern(patt)
for row in range(rows):
for stitch in range(stitches):
if(pattern[row][stitch]) == 0:
print ' ',
else:
print '*',
print

View file

@ -0,0 +1,53 @@
_____
| |
______|___|______
| | | | |
| 7 | 5 | 3 | 1 |
|___|___|___|___|
| | | | |
| 8 | 6 | 4 | 2 |
|___|___|___|___|
View into Disk Drive Connector
1 - Ground
2 - CTS (In)
3 - DTR (Out)
4 - RTS (Out)
5 - DSR (In)
6 - TXD (Out)
7 - RXD (In)
8 - N/C
=========================================================
FTDI Friend -
Notes:
1. - use progrmming tool to invert all signals
2. - cut and rejumper pads for Signal Logic Level = +5V
1 - Blk - Gnd
2 - Brn - In - CTS
3 - Red - Out - VCC
4 - Ora - Out - TXD
5 - Yel - In - RXD
6 - Grn - Out - RTS
========================================================
Connections:
Drive FTDI Friend
Gnd 1 -------- 1 Gnd
CTS 2 ---.----- 6 RTS
DSR 5 --/
RTS 4 --------- 2 CTS
TXD 6 --------- 5 RXD
RXD 7 --------- 4 TXD
============================================
Thre's no response after a command to change to FDC mode,
so you need to wait at least 9 mS for the RTS line on the
drive to respond before sending more characters.
====================================

328
experimental/FDif.py Normal file
View file

@ -0,0 +1,328 @@
#!/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
FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)])
def dump(src, length=16):
result=[]
for i in xrange(0, len(src), length):
s = src[i:i+length]
hexa = ' '.join(["%02X"%ord(x) for x in s])
printable = s.translate(FILTER)
result.append("%04X %-*s %s\n" % (i, length*3, hexa, printable))
return ''.join(result)
class PDD1():
def __init__(self):
self.verbose = False
self.commport = ''
self.ser = None
return
def __del__(self):
return
def open(self, cport='/dev/ttyUSB0', rate = 9600, par = 'N', stpbts=1, tmout=None, xx=0):
self.ser = serial.Serial(port = cport, baudrate = rate, parity = par, stopbits = stpbts, timeout = tmout, xonxoff=0, rtscts=1, dsrdtr=0)
if self.ser == None:
print 'Unable to open serial device %s' % cport
raise IOError
self.commtimeout = tmout
# Sometimes on powerup there are some characters in the buffer, purge them
self.dumpchars()
return
def close(self):
if self.ser is not None:
self.ser.close()
self.ser = None
return
def dumpchars(self):
num = 1
while self.ser.inWaiting():
inc = self.ser.read()
if len(inc) != 0:
print 'flushed 0x%02X (%d)' % (ord(inc), num)
num = num + 1
else:
break
return
def getFDCresponse(self, expected):
sch = self.ser.read(expected)
return sch
def isSuccess(self, FDCstatus):
if self.verbose:
print "isSuccess checking:"
print dump(FDCstatus)
if (FDCstatus[0] == '0') and (FDCstatus[1] == '0'):
print " SUCCESS"
print " Physical Sector = %c%c" % (FDCstatus[2], FDCstatus[3])
print " Logical Sector = %c%c%c%c" % (FDCstatus[4], FDCstatus[5],FDCstatus[6], FDCstatus[7])
else:
print " ***** ERROR ***** : %c%c" % (FDCstatus[0], FDCstatus[1])
print " Physical Sector = %c%c" % (FDCstatus[2], FDCstatus[3])
print " Logical Sector = %c%c%c%c" % (FDCstatus[4], FDCstatus[5],FDCstatus[6], FDCstatus[7])
if len(FDCstatus) != 8:
print "Status Bad Len"
return False
for sb in FDCstatus[0:2]:
if sb != '0':
return False
return True
# def readchar(self):
# inc = ''
# while len(inc) == 0:
# inc = self.ser.read()
# return inc
def writebytes(self, bytes):
self.ser.write(bytes)
return
def calcChecksum(self, string):
sum = 0
for schr in string:
sum = sum + ord(schr)
sum = sum % 0x100
sum = sum ^ 0xFF
return chr(sum)
# def __commandResponse(self, command):
# if self.verbose:
# pcmd = command.strip()
# print 'writing command ===> <%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

82
experimental/driveread.py Normal file
View file

@ -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()

1
file-analysis/README Normal file
View file

@ -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.

100
file-analysis/adumper.py Normal file
View file

@ -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()

451
file-analysis/bdump.c Normal file
View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
// 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<NEEDLELEN; i++) {
pb = htons(*pp1);
//printf("0x%04X\n", pb);
for (j=15; j>=0; j--) {
if (pb & (1<<j))
printf("*");
else
printf(" ");
}
pp1++;
}
printf("\n");
}
void dumpNeedles(unsigned short *pp) {
void *pat = pp;
pat += NEEDLELEN;
/*
unsigned short *pp1 = pp;
unsigned short pb;
int i, j;
for (i=0; i<NEEDLELEN; i++) {
pb = htons(*pp1);
//printf("0x%04X\n", pb);
for (j=15; j>=0; j--) {
if (pb & (1<<j))
printf("*");
else
printf(" ");
}
pp1++;
}
*/
printf("\n");
}
/*
void dumpPattern(void *pat, int stitches, int rows) {
// TODO finish this - how are things padded for odd numbers of stitches?
return;
}
*/
int main (int argc, char *argv[]) {
FILE *fin;
char *infile;
// our struct - doesn't reflect file structure
typedef struct {
unsigned char pos;
unsigned char right;
unsigned char copies;
} motifstr, *motifptr;
// This 7 byte structure is the file format
// for the information about each custom
// pattern 901-999
typedef struct {
unsigned char status;
unsigned char unk1;
unsigned char rows_t:4;
unsigned char rows_h:4;
unsigned char stitches_h:4;
unsigned char rows_o:4;
unsigned char stitches_o:4;
unsigned char stitches_t:4;
unsigned char num_h:4;
unsigned char unk:4;
unsigned char num_o:4;
unsigned char num_t:4;
} pgminfo, *ppgminfo;
struct {
pgminfo pgms[99];
unsigned char head[1073];
unsigned short needles1[NEEDLELEN];
unsigned char afterfirst1[13];
unsigned char variations;
unsigned char afterfirst2[8];
unsigned short needles2[NEEDLELEN];
unsigned char tail1[186];
unsigned char selector[1];
unsigned char selfiller;
unsigned char motifdata[20];
} bd;
unsigned char selector;
motifstr motif[6];
int nummotifs;
unsigned char selone_pos;
unsigned char selone_right;
// misc variables
int readlen;
int i;
unsigned char cval, *cptr;
unsigned long foolong;
if (argc != 2) {
printf("usage: %s filename\n", argv[0]);
exit(-1);
}
infile = argv[1];
if (!(fin=fopen(infile, "r"))) {
printf("Unable to open file %s\n", argv[1]);
exit(-1);
}
printf("length of struct is %d\n", sizeof(bd));
readlen = fread(&bd, 1, sizeof(bd), fin);
if (readlen != sizeof(bd)) {
printf("Failed read, %d\n", readlen);
exit(-1);
}
fclose(fin);
printf("===== programs =====\n");
//for(i=0;i<100;i++) {
for(i=0;i<5;i++) {
ppgminfo pptr = &bd.pgms[i];
unsigned int ival;
//if (pptr->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);
}

463
file-analysis/brother.py Normal file
View file

@ -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()

46
file-analysis/compare.py Normal file
View file

@ -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()

121
file-analysis/diff.py Normal file
View file

@ -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

59
file-analysis/dumprows.py Normal file
View file

@ -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

View file

@ -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

68
file-analysis/makecsv.py Normal file
View file

@ -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 ""

Binary file not shown.

BIN
file-analysis/unknowns.ods Normal file

Binary file not shown.

205
insertpattern.py Normal file
View file

@ -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()

40
process_image.py Normal file
View file

@ -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

26
splitfile2track.py Normal file
View file

@ -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()

View file

@ -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