initial commit of theirs code
This commit is contained in:
commit
c939773800
26 changed files with 4236 additions and 0 deletions
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal 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
6
Changelog
Normal 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
633
PDDemu-debug.py
Normal 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
637
PDDemulate.py
Normal 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
60
README
Normal 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
226
addpattern.py
Normal 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
380
brother.py
Normal 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
202
dumppattern.py
Normal 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
|
||||
|
||||
53
experimental/FB100-Notes.txt
Normal file
53
experimental/FB100-Notes.txt
Normal 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
328
experimental/FDif.py
Normal 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
82
experimental/driveread.py
Normal 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
1
file-analysis/README
Normal 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
100
file-analysis/adumper.py
Normal 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
451
file-analysis/bdump.c
Normal 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
463
file-analysis/brother.py
Normal 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
46
file-analysis/compare.py
Normal 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
121
file-analysis/diff.py
Normal 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
59
file-analysis/dumprows.py
Normal 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
|
||||
|
||||
13
file-analysis/filecombine.sh
Normal file
13
file-analysis/filecombine.sh
Normal 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
68
file-analysis/makecsv.py
Normal 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 ""
|
||||
BIN
file-analysis/motif-memory.ods
Normal file
BIN
file-analysis/motif-memory.ods
Normal file
Binary file not shown.
BIN
file-analysis/unknowns.ods
Normal file
BIN
file-analysis/unknowns.ods
Normal file
Binary file not shown.
205
insertpattern.py
Normal file
205
insertpattern.py
Normal 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
40
process_image.py
Normal 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
26
splitfile2track.py
Normal 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()
|
||||
25
textconversion/maketext.sh
Normal file
25
textconversion/maketext.sh
Normal 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
|
||||
|
||||
Loading…
Reference in a new issue