diff --git a/README b/README index 833d5a9..7c75f1d 100644 --- a/README +++ b/README @@ -1,72 +1,85 @@ Current content of the repository --------------------------------- ./bin/console Console that plugs into a NMEA multiplexer (or any TCP server). It displays the NMEA sentences. USAGE: ./console 39150 localhost or (equivalent) ./console 39150 --- ./bin/joiner Tool that sends the NMEA sentences from one server to another one. USAGE: ./joiner localhost 39150 localhost 39151 (This would send the traffic from the NMEA server on port 39150 to the server on port 39151) --- ./bin/multiplexer NMEA multiplexer. Each NMEA client that connects to the multiplexer will receive the NMEA traffic from other clients, and its NMEA sentences will be sent to other clients. USAGE: ./multiplexer 39150 localhost or (equivalent) ./multiplexer 39150 --- ./bin/pipe Pipe tool to connect 2 NMEA servers in both directions. Like a joiner but in both directions. USAGE: ./joiner localhost 39150 localhost 39151 --- ./tools/dpt Client NMEA client that sends random DPT sentences (with YX talker ID) every second. USAGE: ./dpt 39150 --- ./tools/boat Fake boat that waits for a RMB sentence from navigation (like OpenCPN), and sends RMC position sentences. When the navigation starts sending RMB messages, the fake boat will jump to the provided way point. This wokrs by activation a route in OpenCPN. USAGE: ./boat (it connects to server on port 39150 by default) --- ./tools/PetitBaigneur.py Fake AIS ship sending AIS type 5 and type 1 messages. USAGE: ./PetitBaigneur.py (it connects to server on port 39150 by default) + +--- + +./tools/MOB.py + +Simulate AIS Man Over Board + +--- + +./tools/CardNorth + +Simulate AIS Cardinal N + diff --git a/lib/aisMsg.py b/lib/aisMsg.py index 64d9a0d..24083cd 100755 --- a/lib/aisMsg.py +++ b/lib/aisMsg.py @@ -1,248 +1,359 @@ #!/usr/bin/python from bitBuf import bitBuf from nmea import NMEAaddChecksum # # AIS message # encodingTable = "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVW`abcdefghijklmnopqrstuvw" class AISmsg : def __init__(self) : self.type = 0 self.repeat = 0 self.mmsi = 0 self.bb = bitBuf() self.aisframe = '' self.padding = 0 def toAscii(self) : out = '' if self.bb.bit == 0 : self.bb.buff.pop() # delete last element for c in self.bb.buff : if c < 64 : out += encodingTable[c] return out # Encapsulate AIS frame in multiple NMEA frames def EncapInAIVDM(self) : multi = [] count = (len(self.aisframe)-1) / 60 # MAX_AIS=60 count += 1 segment = 0 for i in range(0, count) : subframe = self.aisframe[segment:segment+60] aivdm = "!AIVDM,"+str(count)+","+str(i+1)+",,A,"+subframe segment += 60 if i != count-1 : aivdm += ",0" else : aivdm += ","+str(self.padding) aivdm = NMEAaddChecksum(aivdm) multi.append(aivdm) return multi def Encode(self) : self._encode() return self.EncapInAIVDM() class AISmsg1(AISmsg) : UNDERWAY = 0 ANCHOR = 1 NOT_UNDER_COMMAND = 2 RESTRICTED_MANOEUVRABILITY = 3 CONSTRAINED_DRAUGHT = 4 MOORED = 5 AGROUND = 6 FISHING = 7 SAILING = 8 SART_ACTIVE = 14 SART_TEST = 15 HDG_NOT_AVAILABLE = 511 SPECIAL_MANOEUVRE_NOT_AVAIL = 0 SPECIAL_MANOEUVRE_NOT_ENGAGED = 1 SPECIAL_MANOEUVRE_ENGAGED = 2 def __init__(self) : AISmsg.__init__(self) self.type = 1 self.repeat = 0 self.mmsi = 0 self.navStatus = AISmsg1.UNDERWAY self.rot = 0.0 self.sog = 0.0 self.accuracy = 0 self.lon = 0.0 self.lat = 0.0 self.cog = 0.0 self.hdg = AISmsg1.HDG_NOT_AVAILABLE self.timeStamp = 0 self.specialManoeuvre = AISmsg1.SPECIAL_MANOEUVRE_NOT_AVAIL self.RAIM = 0 self.state = 0 def _encode(self) : self.bb = bitBuf() self.aisframe = '' self.padding = 0 self.bb.WriteByte (self.type, 6) self.bb.WriteByte (self.repeat, 2) - self.bb.WriteByte (self.mmsi, 30) + self.bb.WriteInteger (self.mmsi, 30) self.bb.WriteByte (self.navStatus, 4) self.bb.WriteByte (128, 8) self.bb.WriteInteger (int(self.sog * 10), 10) self.bb.WriteBit (self.accuracy) lon = int((self.lon * 600000) + 0.5) if self.lon < 0 : lon = (lon ^ 0x0fffffff) + 1 # 2-complement self.bb.WriteInteger (lon, 28) lat = int((self.lat * 600000) + 0.5) if self.lat < 0 : lat = (lat ^ 0x0fffffff) + 1 # 2-complement self.bb.WriteInteger (lat, 27) self.bb.WriteInteger (int(self.cog * 10), 12) self.bb.WriteInteger (int(self.hdg), 9) self.bb.WriteByte (self.timeStamp, 6) self.bb.WriteByte (self.specialManoeuvre, 2) self.bb.WriteByte (0, 3) # spare self.bb.WriteBit (self.RAIM) self.bb.WriteInteger (self.state, 19) #self.bb.info() self.aisframe = self.toAscii() self.padding = self.bb.Padding() class AISmsg5(AISmsg) : VERSION_M1371_1 = 0 VERSION_M1371_3 = 1 FISHING = 30 TOWING = 31 TOWING_200m = 32 DREDGING = 33 DIVING = 34 MILITARY = 35 SAILING = 36 PLEASURE = 37 HIGH_SPEED = 40 PILOT_VESSEL = 50 SAR_VESSEL = 51 TUG = 52 PORT_TENDER = 53 PASSENGER = 60 CARGO = 70 TANKER = 80 OTHER = 90 DEVICE_UNDEFINED = 0 DEVICE_GPS = 1 DEVICE_GLONASS = 2 DEVICE_LORANC = 4 DEVICE_GALILEO = 8 def __init__(self) : AISmsg.__init__(self) self.type = 5 self.version = self.VERSION_M1371_1 self.imo = 0 self.callSign = '' self.name = '' self.shipType = 0 self.to_bow = 0 self.to_stern = 0 self.to_port = 0 self.to_starboard = 0 self.deviceType = self.DEVICE_UNDEFINED self.ETAmonth = 0 self.ETAday = 0 self.ETAhour = 0 self.ETAminute = 0 self.draught = 0 self.destination = '' self.dte = 0 def _encode(self) : self.bb = bitBuf() self.aisframe = '' self.padding = 0 self.bb.WriteByte (self.type, 6) self.bb.WriteByte (self.repeat, 2) - self.bb.WriteByte (self.mmsi, 30) + self.bb.WriteInteger (self.mmsi, 30) self.bb.WriteByte (self.version, 2) self.bb.WriteInteger (self.imo, 30) self.bb.WriteString (self.callSign, 7) self.bb.WriteString (self.name, 20) self.bb.WriteByte (self.shipType, 8) self.bb.WriteInteger (self.to_bow, 9) self.bb.WriteInteger (self.to_stern, 9) self.bb.WriteByte (self.to_port, 6) self.bb.WriteByte (self.to_starboard, 6) self.bb.WriteByte (self.deviceType, 4) self.bb.WriteInteger (self.ETAmonth, 4) self.bb.WriteInteger (self.ETAday, 5) self.bb.WriteInteger (self.ETAhour, 5) self.bb.WriteInteger (self.ETAminute, 6) self.bb.WriteByte (self.draught * 10, 8) self.bb.WriteString (self.destination, 20) self.bb.WriteBit (self.dte) self.bb.WriteBit (0) # spare self.aisframe = self.toAscii() self.padding = self.bb.Padding() +class AISmsg21(AISmsg) : + + NOT_SPECIFIED = 0 + REFERENCE_POINT = 1 + RACON = 2 + FIXED_STRUCTURE_OFFSHORE = 3 + LIGHT_WITHOUT_SECTORS = 5 + LIGHT_WITH_SECTORS = 6 + LEADING_LIGHT_FRONT = 7 + LEADING_LIGHT_REAR = 8 + BEACON_CARDINAL_N = 9 + BEACON_CARDINAL_E = 10 + BEACON_CARDINAL_S = 11 + BEACON_CARDINAL_W = 12 + BEACON_PORT = 13 + BEACON_STARBOARD = 14 + BEACON_PREF_PORT = 15 + BEACON_PREF_STARBOARD = 16 + BEACON_ISOLATED = 17 + BEACON_SAFE_WATER = 18 + BEACON_SPECIAL_MARK = 19 + CARDINAL_N = 20 + CARDINAL_E = 21 + CARDINAL_S = 22 + CARDINAL_W = 23 + PORT_HAND_MARK = 24 + STARBOARD_HAND_MARK = 25 + PREF_CHANNEL_PORT = 26 + PREF_CHANNEL_STARBOARD = 27 + ISOLATED_DANGER = 28 + SAFE_WATER = 29 + SPECIAL_MARK = 30 + LIGHT_VESSEL = 31 + + def __init__(self) : + AISmsg.__init__(self) + self.type = 21 + self.AtoN_type = self.NOT_SPECIFIED + self.name = '' + self.accuracy = 1 + self.lon = 0.0 + self.lat = 0.0 + self.to_bow = 0 + self.to_stern = 0 + self.to_port = 0 + self.to_starboard = 0 + self.deviceType = 0 + self.timeStamp = 0 + self.offPosition = 0 + self.AtoN_status = 0 + self.RAIM = 0 + self.virtual_AtoN = 0 + self.assigned = 0 + self.spare = 0 + self.extension = '' + + def _encode(self) : + self.bb = bitBuf() + self.aisframe = '' + self.padding = 0 + + self.bb.WriteByte (self.type, 6) + self.bb.WriteByte (self.repeat, 2) + self.bb.WriteInteger (self.mmsi, 30) + + self.bb.WriteByte (self.AtoN_type, 5) + self.bb.WriteString (self.name, 20) + self.bb.WriteBit (self.accuracy) + + lon = int((self.lon * 600000) + 0.5) + if self.lon < 0 : + lon = (lon ^ 0x0fffffff) + 1 # 2-complement + self.bb.WriteInteger (lon, 28) + + lat = int((self.lat * 600000) + 0.5) + if self.lat < 0 : + lat = (lat ^ 0x0fffffff) + 1 # 2-complement + self.bb.WriteInteger (lat, 27) + + self.bb.WriteInteger (self.to_bow, 9) + self.bb.WriteInteger (self.to_stern, 9) + self.bb.WriteByte (self.to_port, 6) + self.bb.WriteByte (self.to_starboard, 6) + + self.bb.WriteByte (self.deviceType, 4) + self.bb.WriteByte (self.timeStamp, 6) + self.bb.WriteBit (self.offPosition) + self.bb.WriteByte (self.AtoN_status, 8) + self.bb.WriteBit (self.RAIM) + self.bb.WriteBit (self.virtual_AtoN) + self.bb.WriteBit (self.assigned) + self.bb.WriteBit (self.spare) + if len(self.extension) > 0 : + self.bb.WriteString (self.extension, len(self.extension)) + self.bb.WriteInteger (self.spare2, 6) + + self.aisframe = self.toAscii() + self.padding = self.bb.Padding() if __name__ == '__main__': obj = AISmsg5() obj.mmsi = 205344990 obj.callSign = 'ATC' obj.name = 'PETIT BAIGNEUR' obj.destination = 'MARSEILLE' - obj.to_bow = 50; - obj.to_stern = 20; - obj.to_port = 10; - obj.to_starboard = 10; - obj.draught = 10; + obj.to_bow = 50 + obj.to_stern = 20 + obj.to_port = 10 + obj.to_starboard = 10 + obj.draught = 10 multi = obj.Encode() for aivdm in multi : print aivdm obj = AISmsg1() obj.mmsi = 205344990 - obj.navStatus = AISmsg1.UNDERWAY; - obj.sog = 20; - obj.accuracy = 1; - obj.lat = 43.01666; - obj.lon = 6.0; - obj.cog = 110.7; - obj.hdg = AISmsg1.HDG_NOT_AVAILABLE; - obj.timeStamp = 40; - obj.specialManoeuvre = AISmsg1.SPECIAL_MANOEUVRE_NOT_AVAIL; - obj.RAIM = 1; - obj.state = 0; + obj.navStatus = AISmsg1.UNDERWAY + obj.sog = 20 + obj.accuracy = 1 + obj.lat = 43.01666 + obj.lon = 6.0 + obj.cog = 110.7 + obj.hdg = AISmsg1.HDG_NOT_AVAILABLE + obj.timeStamp = 40 + obj.specialManoeuvre = AISmsg1.SPECIAL_MANOEUVRE_NOT_AVAIL + obj.RAIM = 1 + obj.state = 0 + + multi = obj.Encode() + for aivdm in multi : + print aivdm + + obj = AISmsg21() + obj.mmsi = 101010100 + obj.AtoN_type = AISmsg21.BEACON_CARDINAL_N + obj.name = 'Nord' + obj.accuracy = 1 + obj.lat = 42.0 + 0.05 + obj.lon = 6.0 + obj.virtual_AtoN = 1 multi = obj.Encode() for aivdm in multi : print aivdm diff --git a/lib/bitBuf.py b/lib/bitBuf.py index d8f7cd3..483440d 100755 --- a/lib/bitBuf.py +++ b/lib/bitBuf.py @@ -1,86 +1,88 @@ #!/usr/bin/python # # 6-bit buffer # class bitBuf : def __init__(self) : self.buff = [] self.buff.append(0) self.byte = 0 self.bit = 0 self.nbbit = 0 def info(self) : print 'Nbbit : '+str(self.nbbit) print 'Nbbyte : '+str(len(self.buff)) for byte in self.buff : #print format(byte, 'b') print "{0:06b}".format(byte) def WriteBit(self, bit) : if bit == 1 : self.buff[self.byte] |= (1<<(5-self.bit)) else : self.buff[self.byte] &= ~(1<<(5-self.bit)) self.nbbit += 1 self.bit += 1 if self.bit == 6 : self.byte += 1 self.buff.append(0) self.bit = 0 def WriteByte(self, byte, count) : for i in range(0,count) : self.WriteBit ((byte >> (count-i-1)) & 0x1) def WriteInteger(self, val, count) : for i in range(0,count) : self.WriteBit ((val >> (count-i-1)) & 0x1) def WriteString(self, aString, count) : for c in aString : if count <= 0 : break aByte = ord(c) if aByte < 32: aByte = 32 elif aByte < 64 : aByte = aByte elif aByte < 96 : aByte = aByte - 64 + elif aByte < 128 : + aByte = aByte - 32 else : aByte = 32 self.WriteByte (aByte, 6) count -= 1 for i in range(0,count) : self.WriteByte (0, 6) def WriteBinary(self, payload, size) : for c in payload : if size <= 0 : break; self.WriteByte(c, 8) size -= 1 def Padding(self) : padd = self.nbbit % 6 if padd != 0 : return 6 - padd else : return padd if __name__ == '__main__': obj = bitBuf() obj.WriteBit(1) obj.WriteBit(0) obj.WriteBit(0) obj.WriteBit(1) obj.WriteInteger(7, 3) obj.info() diff --git a/tools/CardNorth b/tools/CardNorth new file mode 100755 index 0000000..6396be8 --- /dev/null +++ b/tools/CardNorth @@ -0,0 +1,26 @@ +#!/usr/bin/python + +import sys, socket + +sys.path.append ('../lib') +from aisMsg import * + +# Connect to multiplexer +client = socket.socket() +host = socket.gethostname() +client.connect(('0.0.0.0', 39150)) + + +obj = AISmsg21() +obj.mmsi = 101010100 +obj.AtoN_type = AISmsg21.BEACON_CARDINAL_N +obj.name = 'North' +obj.accuracy = 1 +obj.lat = 42.0 + 0.05 +obj.lon = 6.0 +obj.virtual_AtoN = 1 + +multi = obj.Encode() +for aivdm in multi : + print aivdm + client.send(aivdm) diff --git a/tools/MOB.py b/tools/MOB.py index 105f4ae..b7c5761 100755 --- a/tools/MOB.py +++ b/tools/MOB.py @@ -1,34 +1,34 @@ #!/usr/bin/python import time, math, sys, socket sys.path.append ('../lib') from aisMsg import * # Connect to multiplexer client = socket.socket() host = socket.gethostname() client.connect(('0.0.0.0', 39150)) obj = AISmsg1() obj.mmsi = 972010001 obj.navStatus = AISmsg1.SART_ACTIVE obj.accuracy = 1 obj.lat = 43.02 obj.lon = 6.1 obj.cog = 0 obj.hdg = AISmsg1.HDG_NOT_AVAILABLE obj.timeStamp = 40 obj.specialManoeuvre = AISmsg1.SPECIAL_MANOEUVRE_NOT_AVAIL obj.RAIM = 1 obj.state = 0 multi = obj.Encode() while True : for aivdm in multi : print aivdm client.send(aivdm) - time.sleep(10) + time.sleep(4)