| [casetta] New organization for cas.py |
[ Thread Index | Date Index | More lists.tuxfamily.org/casetta Archives ]
|
Hello, I have just finished to implement the new organization for cas.py described here : http://dev.filyb.info/casetta/ticket/22. cas.py now uses TransferableObjects (for example for programs, we have a TransferableProgram class) Take some coffee because, it is going to be a loooong (but important ! ) email :-P I defined some guidelines for the creation of such objects: - Each TransferableObject, which inherits of all the properties of an object defined in data.py (for example, TransferableProgram derives from Program) must have a class attribute called header_len (length of a header) - Moreover, the must have all of the following methods. These methods are called by the functions of devices_serial.py : - parse_header(header) : This method fills the object's metadata with information from the header and returns the expect length of the object. The expected length of an object (got from the header) is not stored in objects attributes because its real data length may change after. - checksum_needed(index, data_len) : returns 0 if no checksum if needed as this given index / returns 1 if a checksum should be performed (ie after receiving the sheet of a picture) / returns 2 if we should send 0x06 and ignore if the checksum is valid or not (useful for color screencapture) / returns 3 when index == data_len -1 if you want to do a final checksum, 0 else - set_raw_data(raw_data) : stores the received raw_data into the object. This method will often contain only the instruction : self.raw_data = raw_data but it can contain some specials modifications (for screencaptures for example). - __len__() : returns the real data length (usually len(self.raw_data)+2) - build_header() : returns a header for this TransferalbeObject - get_raw_data_list() : Returns all data parts which should be transferred. Even if it contains only one element, it must return a list. If an object is nor supposed to be transferred, it is allowed not to have these last three methods. See attachments for more details The reorganization has lots of advantages : - More in the spirit of Object Orient Programming / More respectful towards the encapsulation principle - Less data.__class__ instructions which, I think should be avoided - Less code in devices_serial.py / Cleaner file / More logical organization : devices_serial.py functions only handle the low level communication - It is now much easier to add support for the transfer of new data types : before it was necessary to modify almost all the functions of cas.py and modify some functions in devices_serial.py. Now you just have to create a class which respects the guidelines mentioned above. However, it has one drawback. send_data and receive_data functions of devices_serial now manipulate TransferableObjects. For example the receive_data functions does not return a normal object bu a TransferableObject (ie receive_data returns a TransferableProgram). This is not really a problem because all Transferable objects inherits of all the properties of normal Object. However, it is a problem for send_data because it is not able to send a Program (for example) but it can only send a TransferableProgram (or another TransferableObject). It is possible to transform a Program into a TransferableProgram. If prgm is a datacls.Program, you only have to do : prgm = TransferableProgram(prgm.name,prgm.raw_data,prgm.date,prgm.use_base). I did not commit that to the SVN repository because it is a VERY big change and I would like to have your approval before. I tested this new cas.py and I was able to send/receive programs,pictures, receive backups(I didn't test backup receipt), receive Screencaptures. If you have difficulties to sleep, you can read this email twice and I guarantee you will have a good night :-D Thank you for reading this long and boring (but important ;-) ) email. Bye, Ps : Just in case you didn't know I just inform you that the main server of tuxfamily.org is going to die so we may have problems to connect to the SVN : http://tuxfamily.info/?p=100 Ps2 : I am very proud because this time I didn't forget the attachments :-) --
Fabien ANDRE aka Xion345 Linux User #418689 -- fabien.andre.g@xxxxxxxxxx -- xion345@xxxxxxxxxxxxx World domination. Fast. ( Linus Torvalds, Not dated ) |
# -*- coding: utf-8 -*-
#
############################################################################
#
# (c) 2007 Florian Birée aka Thesa <florian@xxxxxxxxxx>
#
# Website: <URL:http://casetta.tuxfamily.org>
#
# Version 0.3.0
#
# changelog:
#
# # 0.3.0 version:
#
# o First release
# This code is hardly inspired by cafix softwares (cafix.sourceforge.com)
# A lot of thanks for cafix coders.
#
############################################################################
#
# 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., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
# http://www.gnu.org/copyleft/gpl.html
#
############################################################################
"""cas (CASIOLINK format) management module"""
__author__ = "Florian Birée"
__version__ = "0.3.0"
__copyright__ = "Copyright (c) 2007, Florian Birée"
__license__ = "GPL"
__revision__ = "$Revision: $"
# $Source: $
__date__ = "$Date: $"
import datetime
import errors
import data as datacls
# Constant
PICTURE_PALLET = ['o', 'b', 'g', 'w']
SCREEN_PALLET = ['b', 'g', 'w', 'o']
########## Restructuration Objets ###############
def calc_checksum(data):
crc = 0
for byte in data:
crc = (crc + ord(byte)) % 256
crc = (0 - crc) % 256
return chr(crc)
# Special Data class
class End(datacls.Data): ## FIXME need methods
"""Special data to end a transfer."""
dType = 'end'
header_len = 49
def __init__(self):
"""New end data"""
datacls.Data.__init__(self)
def parse_header(self, header):
pass
class TransferableProgram(datacls.Program):
header_len = 49
def __init__(self,name = '', raw_data = '', date = None, password = "", use_base = False):
datacls.Program.__init__(self, name, raw_data, date, password, use_base)
def __len__(self):
return len(self.raw_data) + 2
def build_header(self):
header = 'TXT\x00PG'
header += chr(self.__len__() / (256 ** 3))
header += chr((self.__len__() % 256 ** 3) / (256 ** 2))
header += chr((self.__len__() % 256 ** 2) / 256)
header += chr(self.__len__() % 256) # 0 -> 9
header += self.name[:8] + '\xff' * (8 - len(self.name[:8])) #10->17
header += '\xff' * 8
header += self.password[:8] + '\xff' * (8 - len(self.password[:8]))
if self.use_base:
header += 'BN'
else:
header += 'NL'
header += '\xff' * 12
header += calc_checksum(header)
return header
def get_raw_data_list(self):
return [self.raw_data]
def parse_header(self, header, add_date=True):
self.name = header[10:18].replace('\xff', '')
if add_date:
self.date = datetime.date.today()
# Password
self.password = header[26:34].replace('\xff', '')
# Base:
if header[34:36] == 'BN':
self.use_base = True
else:
self.use_base = False
return ord(header[6]) * (256 ** 3) + ord(header[7]) * (256 ** 2) + \
ord(header[8]) * 256 + ord(header[9]) - 2
def checksum_needed(self, index, data_len):
if index == (data_len-1):
return 3
else:
return 0
def set_raw_data(self, raw_data):
self.raw_data = raw_data
class TransferableBackup(datacls.Backup):
header_len = 49
def __init__(self, name = '', raw_data = '', date = None):
datacls.Backup.__init__(self, name, raw_data,date)
def __len__(self):
return len(self.raw_data) + 2
def build_header(self):
header = self.raw_data[2048: 2048 + 48]
header = header[:33] + '\x00\x10\x00\x00\x00\x00' + header[39:]
header += calc_checksum(header)
return header
def get_raw_data_list():
return [self.raw_data]
def parse_header(self, header, add_date=True):
self.name = header[10:18].replace('\xff', '')
if add_date:
self.date = datetime.date.today()
return ord(header[6]) * (256 ** 3) + ord(header[7]) * (256 ** 2) + \
ord(header[8]) * 256 + ord(header[9]) - 2
def checksum_needed(self, index, data_len):
if index==(data_len-1):
return 3
else:
return 0
def set_raw_data(self, raw_data):
self.raw_data = raw_data
## Pictures and Screencaptures
class TransferablePicture(datacls.Picture):
header_len = 49
data_len = 4112
def __init__(self, name = '', raw_data = '', date = None, pallet = None, \
color_byte = None):
datacls.Picture.__init__(self, name, raw_data, date, pallet, color_byte)
def __len__(self):
return self.data_len
def build_header(self):
header = 'IMG\x00PC'
header += '\x00\x40\x00\x80' # len ?
header += self.name
header += '\xff' * 8
header += 'DRUWF'
header += '\x00\x04\x00\x01' # len ?
header += '\xff' * 13
header += calc_checksum(header)
return header
def get_raw_data_list(self):
return [self.raw_data[0:1028],self.raw_data[1028:2056],self.raw_data[2056:3084],self.raw_data[3084:4112]]
def parse_header(self, header ,add_date=True):
self.name = self.name = header[10:18].replace('\xff', '')
if add_date:
self.date = datetime.date.today()
self.pallet = PICTURE_PALLET
self.color_byte = 3
return self.data_len
def checksum_needed(self, index, data_len):
if (index + 1) % 1028 == 0 and index != 0 and index + 1 < data_len:
return 1
elif index==(data_len-1):
return 3
else:
return 0
def set_raw_data(self, raw_data):
self.raw_data = raw_data
class TransferableMonoScreenCapture(datacls.ScreenCapture):
header_len = 39
data_len = 1024
name = "Capture"
def __init__(self, name = '', raw_data = '', date = None, pallet = None, \
color_byte = None):
datacls.ScreenCapture.__init__(self, name, raw_data, date, pallet, \
color_byte)
def __len__():
return self.data_len
def parse_header(self, header,add_date=True):
if add_date:
self.date = datetime.date.today()
self.pallet = SCREEN_PALLET
self.color_byte = 0
return self.data_len
def checksum_needed(self, index ,data_len):
if index == data_len:
return 3
else:
return 0
def set_raw_data(self, raw_data):
columns = [''] * 16
for index in range(len(raw_data)):
col = index / 64
columns[col] += raw_data[index]
for col in range(len(columns)):
byte_list = list(columns[col])
byte_list.reverse()
columns[col] = ''.join(byte_list)
black_sheet = ''.join(columns)
self.raw_data = '\x01' + black_sheet +\
'\x02' + '\x00' * 0x400 +\
'\x03' + '\x00' * 0x400 +\
'\x04' + '\x00' * 0x400
class TransferableColorScreenCapture(datacls.ScreenCapture):
header_len = 39
data_len = 3075
name = "Capture"
def __init__(self, name='', raw_data='', date = None, pallet=None, \
color_byte = None):
datacls.ScreenCapture.__init__(self, name, raw_data, date, pallet, \
color_byte)
def __len__():
return self.data_len
def parse_header(self, add_date=True):
if add_date == True:
self.date = datetime.date.today()
self.pallet = SCREEN_PALLET
self.color_byte = 0
return self.data_len
def checksum_needed(self, index, data_len):
if (index == 1024 or index == 2048):
return 2 # False checksum
elif index == data_len:
return 3
else:
return 0
def set_raw_data(self, raw_data):
self.raw_data=raw_data[:0x400 * 2 + 2] + '\x03' + '\x00' * 0x400 + raw_data[0x400 * 2 + 2:]
def get_header_format(header):
"""Return [header_len, header_type, sub_type]."""
sub_type = None
type_id = header[0:3]
sub_id = header[4:6]
if type_id == "MEM" and sub_id == "BU":
data = TransferableBackup()
elif type_id == "DD@":
data = TransferableMonoScreenCapture()
elif type_id == "DC@":
data = TransferableColorScreenCapture()
# elif type_id == "FNC":
# datatype = 4
# headerlen = 49
elif type_id == "IMG" and sub_id == "PC":
data = TransferablePicture()
elif type_id == "TXT" and sub_id == "PG":
data = TransferableProgram()
# elif type_id == "VAL":
# datatype = 8
# headerlen = 49
# elif type_id == "REQ":
# datatype = 7
# headerlen = 49
elif type_id == "END":
data = End()
else:
return "unknown",49
return data, data.header_len
## Q: Are we able to send screencaptures ?
### A: Apparently not !
## Add support for screencaptures - Done
## Add header checksum calculation - Done
## Add data.name in fillmetadata - Done
## Add get_raw_data_list method
## Add __len__
### Au moment de l'appel de get_data_len, on a le header complet !
### Autant appeler fillmetadata directment
### Renommer fillmetadata en parse_header
## Liste des méthodes :
## Envoi :
## - __len__ - OK
## - build_header - OK
## - get_raw_data_list - OK
## Reception :
## - parse_header - OK
## - check_needed - OK
## - store_raw_data - OK
#################################################
# File format management classes
class CasFile:
"""Cas (Casiolink) file format manager."""
name = 'cas'
managed_data = [datacls.Program, datacls.Backup, datacls.Picture, \
datacls.ScreenCapture]
read = True
write = False
list_ext = ['cas']
def __init__(self, filename = None):
"""Make a cas file object with filename"""
self.filename = filename
def open_file(self):
"""Open a file in the cas Format"""
# Build the FileData
file_data = datacls.FileData()
cas_file = open(self.filename, 'r')
ctn = cas_file.read()
cas_file.close()
# Init var
index = 0
is_header = True
header = ""
header_len = 100
data = None
data_len = 100
raw_data = ""
# Loop on bytes in ctn
while index < len(ctn):
if is_header and ctn[index] != ':' and len(header) < header_len:
header += ctn[index]
if len(header) == 7:
# Get metadata
header_len, data_type, sub_type = \
get_header_format(header)
elif is_header and len(header) == header_len:
# Crc byte, init the data record
is_header = False
if data_type == 'end':
break
data_len = get_data_len(header, data_type, sub_type)
# Make the data
if data_type == 'program':
data = file_data.new_record(datacls.Program)
elif data_type == 'backup':
data = file_data.new_record(datacls.Backup)
elif data_type == 'picture':
data = file_data.new_record(datacls.Picture)
elif data_type == 'ScreenCapture':
data = file_data.new_record(datacls.ScreenCapture)
else:
data = file_data.new_record(datacls.Data)
fill_metadata(data, header, False)
elif not is_header and len(raw_data) == 0 and ctn[index] == ':':
# Not header, first :
pass
elif not is_header and len(raw_data) < data_len:
# Raw_data
if data_type == 'screencapture' and sub_type == 'color' \
and (len(raw_data) == 1024 or len(raw_data) == 2048):
# Jump two bytes (checksum and :)
index += 2
if data_type == 'picture' and (len(raw_data) + 1) % 1028 == 0 \
and len(raw_data) != 0 and len(raw_data) + 1 < data_len:
# Jump two bytes (checksum and :)
index += 2
raw_data += ctn[index]
elif not is_header and len(raw_data) == data_len:
# Data Crc, save raw_data (and make some convertions)
if data_type == 'screencapture' and sub_type == 'color':
data.raw_data = color_screencapture_to_raw(raw_data)
elif data_type == 'screencapture' and sub_type == 'mono':
data.raw_data = mono_screencapture_to_raw(raw_data)
else:
data.raw_data = raw_data
# Init vars
is_header = True
header = ""
header_len = 100
data = None
raw_data = ""
index += 1
return file_data
# -*- coding: utf-8 -*-
#
############################################################################
#
# (c) 2007 Florian Birée aka Thesa <florian@xxxxxxxxxx>
#
# Website: <URL:http://casetta.tuxfamily.org>
#
# Version 0.3.0
#
# changelog:
#
# # 0.3.0 version:
#
# o First release
# This code is hardly inspired by cafix softwares (cafix.sourceforge.com)
# A lot of thanks for cafix coders.
#
############################################################################
#
# 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., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
# http://www.gnu.org/copyleft/gpl.html
#
############################################################################
"""Internal serial tansfer tool"""
__author__ = "Florian Birée"
__version__ = "0.3.0"
__copyright__ = "Copyright (c) 2007, Florian Birée"
__license__ = "GPL"
__revision__ = "$Revision: $"
# $Source: $
__date__ = "$Date: $"
import serial
import time
import sys
import cas
import errors
import data as datacls
# Default functions
def default_status(data_name, current_size, total_size, is_header = False):
"""Print the status of a transfert on the standard output."""
percent = ((current_size * 100) / total_size)
if is_header:
progress_line = 'Header '
else:
progress_line = data_name + ' ' * (9 - len(data_name))
progress_line += '[' + '=' * (percent / 2)
progress_line += ' ' * (50 - (percent / 2)) + '] '
progress_line += '(%i%%) %i/%i' % (percent, current_size, total_size)
if current_size != 0:
progress_line = '\r' + progress_line
sys.stdout.write(progress_line)
sys.stdout.flush()
if current_size >= total_size:
sys.stdout.write('\n')
def default_overwrite(data_name):
"""Ask the user if he want to overwrite data_name on the calculator."""
user_rep = raw_input('Overwrite %s (Y/N): ' % data_name)
return user_rep.lower().startswith('y')
# Serial management classes
class Connexion:
"""Casetta serial transfer tool."""
name = 'serial'
managed_data = [datacls.Program, datacls.Backup, datacls.Picture, \
datacls.ScreenCapture]
def __init__(self, serial_port = 0, status = default_status, \
overwrite = default_overwrite):
"""Initial configuration of the serial port
status is a function which is runned as status(current_data_name,
current_size, total_size, is_header) each time the status change.
overwrite is a function which is runned as overwrite(data_name) and
which must return True or False.
"""
if type(serial_port) == str and serial_port.isdigit():
serial_port = int(serial_port)
self.serial = serial.Serial(
port=serial_port,
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=5, # or None
xonxoff=0, #0
rtscts=0) #(o:1)0
self.status = status
self.overwrite = overwrite
self.cancel = False
def check_cancel(self):
"""If the cancel flag is set, unset it and raise TransferAborted"""
if self.cancel:
self.cancel = False
raise errors.TransferAborted()
### Low level serial functions
def get_byte(self):
"""Read a single byte"""
byte = self.serial.read()
return byte
def send_byte(self, byte):
"""Send a single byte"""
self.serial.write(byte)
def send_string(self, string, status = None, data_name = '', \
is_header = False):
"""Send a string of bytes"""
for index in range(len(string)):
self.check_cancel()
self.send_byte(string[index])
time.sleep(0.005)
if status != None:
status(data_name, index, len(string) - 1, is_header)
### Send system
def send(self, data_list):
"""Send all data in data_list
Warning: a backup must be the only data to send
"""
while not self.calc_is_ready_to_receive():
self.check_cancel()
for data in data_list:
self.send_data(data)
if data_list[-1].dType != "backup":
# Send the END header
self.send_header(cas.End())
def calc_is_ready_to_receive(self):
"""Return if the calculator is ready to receive some data"""
interactive = False
if interactive:
code = '\x06'
else:
code = '\x16'
self.send_byte(code)
byte_rep = self.get_byte()
if interactive and byte_rep == '\x06':
return True
elif not interactive and byte_rep == '\x13':
return True
else:
return False
def send_data(self, data):
"""Send a data to the calc"""
self.send_header(data)
response = self.get_byte()
if response == '\x06':
# Calculator accept the header
pass
elif response == '\x24':
raise errors.DataNoManaged([data.__class__])
elif response == '\x2b':
raise errors.ChecksumError()
elif response == '\x21':
#File already exist in the calculator.
if self.overwrite(data.name) :
self.send_byte('\x06')
sec_resp = self.get_byte()
if sec_resp != '\x06':
raise errors.CannotOverwrite(data.name)
# Calculator accept to overwrite.
else:
# send abort code
self.send_byte('\x15')
sec_resp = self.get_byte()
if sec_resp != '\x06':
raise errors.BadAnswerFromCalc(sec_resp)
raise errors.TransferAborted()
elif response == '\x51':
raise errors.HeaderError()
else:
raise errors.BadAnswerFromCalc(response)
# Gets the data list to be transfered
raw_data_list = data.get_raw_data_list()
for raw_data in raw_data_list:
# CRC calcul
crc = 0
for byte in raw_data:
crc += ord(byte)
crc = (abs(255 - (crc % 256)) + 1) % 256
# Other way to get the crc - maybe better
#crc = 0
#for byte in raw_data:
# crc += ord(byte)
# if crc > 255:
# crc -= 256
#crc = 256 - crc
# Now sending the data
self.send_string('\x3a' + raw_data, self.status, data.name, False)
# All data sent, let the calc check the CRC
self.send_byte(chr(crc))
response = self.get_byte()
if response != '\x06':
raise errors.ChecksumError()
return
def send_header(self, data):
"""Send the header corresponding to data"""
header = data.build_header()
if data.dType == "end":
self.send_string('\x3a' + header, self.status, 'End', False)
else:
self.send_string('\x3a' + header, self.status, data.name, True)
return
### Receive system
def receive(self):
"""Return a file_data with all data receive from the calc."""
file_data = datacls.FileData()
while not self.calc_is_ready_to_send():
self.check_cancel()
# Starts to receive the data
while True:
data = self.receive_data()
if data.dType == "end":
break
# Adds the data to the file
file_data.data.append(data)
if data.dType == "backup" or \
data.dType == "screencapture":
break
return file_data
def calc_is_ready_to_send(self):
"""Waiting for receiving data"""
byte = self.get_byte()
if byte == '\x15':
# Received request for interactive handshake (0x15)
# -- responding with 0x13
self.send_byte("\x13")
return True
elif byte == '\x16':
# Received request for noninteractive handshake (0x16)
# -- responding with 0x13
self.send_byte("\x13")
return True
else:
return False
def receive_data(self):
"""Receive a data"""
# Get header
self.status('unknown', 0, 49, True)
data, data_len = self.get_header()
self.status('unknown', 49, 49, True)
if data.dType == 'end':
# End of the transfer : no data
return cas.End()
crc = 0
raw_data = ''
# Starts the receive loop
for index in range(data_len):
self.check_cancel()
byte = self.get_byte()
crc = crc + ord(byte)
# Do we need to perform a checksum at this given index ?
if data.checksum_needed(index, data_len) == 2:
# Pretends the cheksum we got is valid
# Does not do real checksum check
resp2 = self.get_byte()
self.send_byte('\x06')
resp2 = self.get_byte()
crc = 0
if data.checksum_needed(index, data_len) == 1:
# Real checksum check of the data part (sheet of a picture,
# Value of a matrix)
newcrc = ord(self.get_byte())
crc = abs(255 - (crc % 256)) + 1
if not newcrc == crc :
raise errors.ChecksumError()
crc = 0
self.send_byte('\x06')
resp2 = self.get_byte()
if not resp2 == '\x3a':
raise errors.BadAnswerFromCalc(resp2)
#if data_type == 1 and bytesproc + 1 == 62684:
# print 'byte 62684 of a backup : send 0x06'
# send_byte('\x06')
raw_data += byte
self.status(data.name, index, data_len - 1, False)
# Receipt of raw_data is finished
if data.checksum_needed(index, data_len) == 3:
# Final checksum
# Should be done for any data type
# except for those who frequently generate errors
# such as color screencaptures.
newcrc = ord(self.get_byte())
crc = abs(255 - (crc % 256)) + 1
if not newcrc == crc:
#and data_type != 'screencapture' and \
# sub_type != 'color':
# Warning: the crc check is not done for color screencapture
# because the crc of color screencapture is never
# valid.
raise errors.ChecksumError()
self.send_byte('\x06')
# Stores received raw data into the object
data.set_raw_data(raw_data)
return data
def get_header(self):
"""Return [header, header_type]"""
byte = self.get_byte()
if not byte == '\x3a':
raise errors.HeaderError()
header = ''
total_bytes = 49 # Default header size
cur_byte = 0
h_crc = 0
while cur_byte < total_bytes:
byte = self.get_byte()
header += byte
if cur_byte == 7:
data, header_len = cas.get_header_format(header)
total_bytes = header_len
cur_byte += 1
if cur_byte < total_bytes:
h_crc = (h_crc + ord(byte)) % 256
h_crc = (0 - h_crc) % 256
if h_crc != ord(byte) :
raise errors.ChecksumError()
# Check if we know the header type
if data == "unknown":
self.send_byte("\x00")
raise errors.HeaderError()
# Accepts the header
self.send_byte("\x06")
if not self.get_byte() == '\x3a':
raise errors.BadAnswerFromCalc()
return data, data.parse_header(header)
| Mail converted by MHonArc 2.6.19+ | http://listengine.tuxfamily.org/ |