""" This module is an implementation of the SNAP communications protocol and is used for communications between the PC host and the RepRap machine. """ # Python module properties __author__ = "Stefan Blanke (greenarrow) (greenarrow@users.sourceforge.net)" __license__ = "GPL 3.0" __licence__ = """ pyRepRap 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 3 of the License, or (at your option) any later version. pyRepRap 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 pyRepRap. If not, see . """ import serial, exceptions, threading, time SYNC_BYTE = 0x54 PAYLOAD_OFFSET = 5 HDB1_OFFSET = 2 printOutgoing = False #printOutgoing = True printIncoming = False #printIncoming = True localAddress = 0 serialPort = None def openSerial( port = 0, rate = 19200, tout = 60 ): """Open serial port for SNAP RepRap communications""" global serialPort try: serialPort = serial.Serial( port, rate, timeout = tout ) except: raise RepRapError("You do not have permissions to use the serial port, try running as root") def closeSerial(): """Close serial port for SNAP RepRap communications""" serialPort.close() class _SNAPError(exceptions.Exception): """Exception class""" def __init__(self, msg): self.msg = msg return def __str__(self): return "A SNAP error has occured! " + str(self.msg) class Packet: """SNAP packet class""" def __init__(self, DAB = None, SAB = None, ACK = None, NAK = None, dataBytes = None): """Create SNAP packet""" self.bytes = [] self.encoded = False if DAB != None and SAB != None and ACK != None and NAK != None and dataBytes != None: self.SYNC = SYNC_BYTE self.DAB = DAB self.SAB = SAB self.ACK = ACK self.NAK = NAK self.dataBytes = dataBytes self.decoded = True else: self.SYNC = None self.decoded = False def send(self): """Send SNAP packet over serial conneciton (automatically encodes)""" # Encode packet self._encode() # Clear incoming packet buffer (if we havn't looked at the yet we never will) #Receiver.clearPackets() # Transimit packet self._sendBytes() if printOutgoing: self.printPacket("Outgoing") # Get ack packet and remove from buffer ack = getPacket(serialPort) if not ack._check(): raise _SNAPError("ACK packet checksum failed") ack._decode() # check that packet is an acknoledgement and that it is from the device we just messaged. if ack.ACK == 1 and ack.SAB == self.DAB: return else: # If somthing is wrong with the ACK, print the packet we sent, the one we recieved, then raise an exception self.printPacket("Failed") ack.printPacket("Failed ACK") if ack.ACK != 1 and ack.SAB == self.DAB: raise _SNAPError("ACK expected, packet is not an ACK") elif ack.SAB != self.DAB and ack.ACK == 1: raise _SNAPError("ACK packet is not from the expected module""") else: raise _SNAPError("ACK expected, packet is not an ACK and it is from the wrong module (it's all gone very wrong)") def getReply(self): """Returns the first packet from the receive buffer""" rep = getPacket(serialPort) if not rep._check(): raise _SNAPError("reply packet checksum failed") return rep def _encode(self): """Encode attributes into byte list (Packet.bytes)""" if not self.decoded: raise _SNAPError("Can't encode packet, data is missing") self.NDB = len(self.dataBytes) self.bytes = [] self.bytes.insert( 0, 0xFF & self.SYNC ) self.HDB1 = _makeHDB2(self.ACK, self.NAK) self.bytes.insert( 1, 0xFF & self.HDB1 ) self.HDB2 = _makeHDB1(self.NDB) self.bytes.insert( 2, 0xFF & self.HDB2 ) self.bytes.insert( 3, 0xFF & self.DAB ) self.bytes.insert( 4, 0xFF & self.SAB ) for d in self.dataBytes: self.bytes.append( 0xFF & d ) checksum = _Checksum() for d in self.bytes[1:]: checksum.addData(d) self.CRC = checksum.getResult() self.bytes.append( self.CRC ) self.encoded = True def _decode(self): """Decode byte list (Packet.bytes) to attributes""" if len(self.bytes) == 0: raise _SNAPError("Can't decode and empty packet") self.SYNC = self.bytes[0] self.HDB2 = self.bytes[1] self.HDB1 = self.bytes[2] self.DAB = self.bytes[3] self.SAB = self.bytes[4] self.NDB = _breakHDB1(self.HDB1) self.dataBytes = [] for d in self.bytes[5:5 + self.NDB]: self.dataBytes.append(d) self.CRC = self.bytes[5 + self.NDB::6 + self.NDB][0] numLeftoverBytes = len(self.bytes) - 6 - self.NDB self.leftoverBytes = self.bytes[6 + self.NDB:len(self.bytes)] if numLeftoverBytes > 0: #print "leftover bytes", numLeftoverBytes, self.leftoverBytes raise _SNAPError("Decoded packet has " + str(numLeftoverBytes) + " bytes left over") self.ACK, self.NAK = _breakHDB2(self.HDB2) self.bytes = self.bytes[:6 + self.NDB] self.decoded = True def _check(self): """Returns True if calculated checksum matches checksum attatched to packet, otherwise False""" newChecksum = _Checksum() for d in self.bytes[1:-1]: newChecksum.addData(d) testCRC = newChecksum.getResult() if testCRC == self.CRC: return True else: return False, testCRC, self.CRC def checkReply(self, expectedBytes, expectedCommand): """Checks that the correct number of data bytes have been recieved and that the reply is a reply to the sent command""" if len( self.dataBytes ) != expectedBytes: raise RepRapError("Reply contains wrong number of bytes") if self.dataBytes[0] != expectedCommand: raise RepRapError("Reply is for wrong command") return True def _sendBytes(self): """Transmit byte list (Packet.bytes) on serial conneciton""" if self.encoded == True: for d in self.bytes: serialPort.write(chr(d)) else: raise _SNAPError("Trying to send a packet that has not been encoded") #def _addByte(self, byte): # """Add a byte to the packet, is this obsolete?""" # self.bytes.append(byte) def _setBytes(self, bytes): """Set Packet.bytes contents""" self.bytes = bytes def printPacket(self, status): """Print contents of packet to screen""" if self.decoded == True: print "### Start", status, "SNAP Packet ###" print "Bytes", self.bytes if self.SYNC == 0x54: print "...Sync OK" else: print "...Sync Error" print "...Check: ", self._check() print "...DATA", self.dataBytes print "...CRC", self.CRC print "...SAB", self.SAB print "...DAB", self.DAB print "...HDB1", self.HDB1, ":" print "...........NDB", self.NDB print "...HDB2", self.HDB2, ":" print "...........ACK", self.ACK print "...........NAK", self.NAK print "### End", status, "SNAP Packet ###" else: raise _SNAPError("Trying to print a packet that has not been decoded") #this is done again in full decode, but needed here so num bytes to expect is known. def _getPacketLen(buffer): l = _breakHDB1( buffer[offset_hdb1] ) return l def getPacket(ser): bytes = [] # Read in 5 bytes while len(bytes) <= 4: byte = ser.read() if len(byte) > 0: # If this is the sync then clear the byte buffer. if byte == SYNC_BYTE: bytes = [] bytes.append( ord(byte) ) else: raise _SNAPError("Serial timeout") # Read the expected packet length from Header Byte 1 packetLength = _breakHDB1( bytes[HDB1_OFFSET] ) + PAYLOAD_OFFSET + 1 while len(bytes) < packetLength: byte = ser.read() if len(byte) > 0: bytes.append( ord(byte) ) else: raise _SNAPError("Serial timeout") # We have enough bytes, create packet object p = Packet() p._setBytes(bytes) p._decode() p._check() if printIncoming: p.printPacket("Incoming") if p.DAB == localAddress: return p else: raise _RepRapError( "Received packet bound for " + str(p.DAB) ) class _Checksum: """Class for checksum calculator""" def __init__(self): """Create checksum calculator""" self.crc = 0 def addData(self, data): """Add a byte""" #byte i = (byte)(data ^ self.crc) i = data ^ self.crc self.crc = 0 if((i & 1) != 0): self.crc ^= 0x5e if((i & 2) != 0): self.crc ^= 0xbc if((i & 4) != 0): self.crc ^= 0x61 if((i & 8) != 0): self.crc ^= 0xc2 if((i & 0x10) != 0): self.crc ^= 0x9d if((i & 0x20) != 0): self.crc ^= 0x23 if((i & 0x40) != 0): self.crc ^= 0x46 if((i & 0x80) != 0): self.crc ^= 0x8c return data def getResult(self): """Return the checksum as an integer""" return self.crc def _makeHDB2(ACK, NAK): """Encode Header Byte 2 (HDB2)""" SAB = 1 # Length of the Source Address Bytes, in Binary. RepRap currently only accepts source addresses of 1 byte length DAB = 1 # Length of the Destination Address Bytes, in Binary. RepRap currently only accepts destinations of 1 byte length PFB = 0 # Length of Protocol Flag Bytes. RepRap does not accept any protocol flag bytes, so this must be set to 00 HDB2val = ((DAB & 0x3) * pow(2,6)) | ((SAB & 0x3) * pow(2,4)) | ((PFB & 0x3) * pow(2,2)) | ((ACK & 0x1) * pow(2,1)) | (NAK & 0x1) return HDB2val def _breakHDB2(HDB2): """Decode Header Byte 2 (HDB2)""" ACK = (HDB2 & 0x2) / pow(2,1) NAK = (HDB2 & 0x1) return ACK, NAK def _makeHDB1(NDB): """Encode Header Byte 1 (HDB1)""" CMD = 0 # Command Mode Bit. Not implemented by RepRap and should be set to 0 EMD = 0x3 # Currently RepRap only implements 8-bit self.crc. this should be set to 011 HDB1val = ((CMD & 0x1) * pow(2,7)) | ((EMD & 0x7) * pow(2,4)) | (0xF & NDB) return HDB1val def _breakHDB1(HDB1): """Decode Header Byte 1 (HDB1)""" NDB = HDB1 & 0xF return NDB