import struct import sys import time import binascii import serial from serial.tools import list_ports class UbxStream(object): def __init__(self, dev=None): # pyserial 3.x has min requirement python2.7 # read() returns string in 2.7, bytes object otherwise if sys.version_info[0] < 3: if sys.version_info[1] < 7: raise ValueError('This library is based on pyserial v3.x. Python 2.7 or higher is required.') self._version = 2 self._ubox_synch = '\xb5b' else: self._version = 3 self._ubox_synch = ['b5', '62'] if(dev): self.dev = dev else: self.dev = serial.Serial(timeout=1) try: if(self._dev.open): self.baudrate = dev.baudrate except AttributeError: print("Serial Port is closed; open before using.") @property def dev(self): return self._dev @dev.setter def dev(self, dev): x = dev.__class__.__module__ + "." + dev.__class__.__name__ supported = ["serial.serialcli.Serial", "serial.serialwin32.Serial", "serial.serialposix.Serial", "serial.serialjava.Serial"] if(x in supported): self._dev = dev else: print("This connection is not supported") @property def baudrate(self): try: if(self._dev.open): return self._baudrate else: print("Port is closed.") except AttributeError: print("Serial connection has not been initialized or assigned a baudrate yet.") @baudrate.setter def baudrate(self,baudrate): y = UbxMessage('06','00', msg_type="tx", rate=baudrate, version=self._version) try: if(self.dev.writable): if(self.dev.write(y.msg)): time.sleep(1) # Sleep to make sure buffer gets written! self._baudrate = baudrate self._dev.baudrate = baudrate except AttributeError: print("Serial connection has not been initialized or assigned a port yet.") def detect_ports(self): ports = list(serial.tools.list_ports.comports()) if(len(ports) == 0): print("No ports detected") else: for i in ports: print(i.device) def read(self, timeout=3, reset=True): if(reset): self.dev.reset_input_buffer() now = time.time() counter = 0 while(time.time() - now) < timeout: if self._dev.in_waiting > 0: if counter < 2: try: s = self.dev.read() if self._version == 3: s = binascii.hexlify(s).decode('utf-8') if s == self._ubox_synch[counter]: counter += 1 elif s == self._ubox_synch[0]: counter = 1 else: counter = 0 except serial.serialutil.SerialException: print("Somethig went wrong") else: if self._version == 3: ubx_class = binascii.hexlify(self.dev.read()).decode('utf-8') ubx_id = binascii.hexlify(self.dev.read()).decode('utf-8') else: ubx_class = binascii.hexlify(self.dev.read()) ubx_id = binascii.hexlify(self.dev.read()) return UbxMessage(ubx_class, ubx_id, dev=self.dev, version=self._version) print("Connection timed out..") def enable_message(self, msgClass, msgId): msg = UbxMessage('06', '01', msg_type="tx", msgClass=msgClass, msgId=msgId, ioPorts=[0, 1, 0, 0, 0, 0]) self.dev.write(msg.msg) if(self.__confirmation()): return msg def disable_message(self, msgClass, msgId): #msg = UbxMessage('06', '01', msg_type="tx", msgClass=msgClass, msgId=msgId, ioPorts=[0, 0, 0, 0, 0, 0]) msg = UbxMessage('06', '01', msg_type="tx", msgClass=msgClass, msgId=msgId, ioPorts=[0, 0, 0, 0, 0, 0]) self.dev.write(msg.msg) if(self.__confirmation()): return msg def reset_config(self): clearMask, saveMask, loadMask, deviceMask = [255, 255, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [3] msg = UbxMessage('06','09', msg_type="tx", clearMask=clearMask, saveMask=saveMask, loadMask=loadMask, deviceMask=deviceMask) self.dev.write(msg.msg) if(self.__confirmation()): return msg def save_config(self): clearMask, saveMask, loadMask, deviceMask = [0, 0, 0, 0], [255, 255, 0, 0], [0, 0, 0, 0], [19] #clearMask, saveMask, loadMask, deviceMask = [0, 0, 0, 0], [255, 255, 0, 0], [0, 0, 0, 0], [255] msg = UbxMessage('06','09', msg_type="tx", clearMask=clearMask, saveMask=saveMask, loadMask=loadMask, deviceMask=deviceMask) self.dev.write(msg.msg) if(self.__confirmation()): return msg def load_config(self): clearMask, saveMask, loadMask, deviceMask = [0, 0, 0, 0], [0, 0, 0, 0], [255, 255, 0, 0], [19] msg = UbxMessage('06','09', msg_type="tx", clearMask=clearMask, saveMask=saveMask, loadMask=loadMask, deviceMask=deviceMask) self.dev.write(msg.msg) if(self.__confirmation()): return msg def nav_config(self, dynModel): msg = UbxMessage('06', '24', msg_type="tx", dynModel=dynModel) self.dev.write(msg.msg) if(self.__confirmation()): return msg # diables all NMEA messages by default using ubx class / ids # 0xF0 = 240 / 0xF1 = 241 # NMEA Messages: # class 0xF0: 0A, 09, 00, 01, 0D, 06, 02, 07, 03, 04, 41, 0F, 05, 08 # class 0xF1: 00, 03, 04 def disable_NMEA(self): classes = [240, 241] ids1 = [10, 9, 0, 1, 13, 6, 2, 7, 3, 4, 65, 15, 5, 8] ids2 = [0, 3, 4] counter = 0 string = "" for i in ids1: val = False while(val == False): val = self.disable_message(classes[0], i) if(val): string += "Class: {} ID: {} // ".format(classes[0], i) counter += 1 break for i in ids2: val = False while(val == False): val = self.disable_message(classes[1], i) if(val): string += "Class: {} ID: {} // ".format(classes[1], i) counter += 1 break print("Counter: {}".format(counter)) print(string) def __confirmation(self): now = time.time() while(time.time() - now < 5): answer = self.read(reset=False) try: if(answer): if(answer.ubx_class=='05' and answer.ubx_id=='01'): print("Acknowledged. CLS:{} ID:{}".format(answer.clsID, answer.msgID)) return True elif(answer.ubx_class=='05' and answer.ubx_id=='00'): print("Not acknowledged. CLS:{} ID:{}".format(answer.clsID, answer.msgID)) return False except AttributeError: pass print("Message not received") return False class UbxMessage(object): def __init__(self, ubx_class, ubx_id, msg_type="rx", **kwargs): if(msg_type == "rx"): self._version = kwargs["version"] print("Receiving") print("{} {}".format(ubx_class, ubx_id)) #NAV if(ubx_class == '01'): message = {'02': lambda: self.__ubx_NAV_POSLLH(kwargs["dev"]), '04': lambda: self.__ubx_NAV_DOP(kwargs["dev"]), '06': lambda: self.__ubx_NAV_SOL(kwargs["dev"]), '07': lambda: self.__ubx_NAV_PVT(kwargs["dev"])} message[ubx_id]() #RXM elif(ubx_class == '02'): print("") #INF elif(ubx_class == '04'): print("") #ACK elif(ubx_class == '05'): message = {'01': lambda: self.__ubx_ACK_ACK(kwargs["dev"]), '00': lambda: self.__ubx_ACK_NAK(kwargs["dev"])} message[ubx_id]() #CFG elif(ubx_class == '06'): print("") #UPD elif(ubx_class == '09'): print("") #MON elif(ubx_class == '0A'): print("") #AID elif(ubx_class == '0B'): print("") #TIM elif(ubx_class == '0D'): print("") #ESF elif(ubx_class == '10'): print("") #MGA elif(ubx_class == '13'): print("") #LOG elif(ubx_class == '21'): print("") #SEC elif(ubx_class == '27'): print("") #HNR elif(ubx_class == '28'): print("") else: print("Unsuported message class") raise TypeError elif(msg_type == "tx"): print("Transmitting") #NAV if(ubx_class == '01'): print("") #RXM elif(ubx_class == '02'): print("") #INF elif(ubx_class == '04'): print("") #ACK elif(ubx_class == '05'): print("") #CFG elif(ubx_class == '06'): print("{} {}".format(ubx_class, ubx_id)) message = {'00': lambda: self.__ubx_CFG_PRT(kwargs["rate"]), '01': lambda: self.__ubx_CFG_MSG(kwargs["msgClass"], kwargs["msgId"], kwargs["ioPorts"]), '09': lambda: self.__ubx_CFG_CFG(kwargs["clearMask"], kwargs["saveMask"], kwargs["loadMask"], kwargs["deviceMask"]), '24': lambda: self.__ubx_CFG_NAV5(kwargs["dynModel"]) } message[ubx_id]() #UPD elif(ubx_class == '09'): print("") #MON elif(ubx_class == '0A'): print("") #AID elif(ubx_class == '0B'): print("") #TIM elif(ubx_class == '0D'): print("") #ESF elif(ubx_class == '10'): print("") #MGA elif(ubx_class == '13'): print("") #LOG elif(ubx_class == '21'): print("") #SEC elif(ubx_class == '27'): print("") #HNR elif(ubx_class == '28'): print("") else: print("Unsuported message class") raise TypeError else: print("Message type not supported.") ## UBX-NAV 0x01 ## # time_of_week in ms / longitude in deg / latitude in deg # height ellipsoid in mm / height mean sea level mm # horizontal accuracy in mm / vertical accuracy in mm def __ubx_NAV_POSLLH(self, dev): payload = dev.read(size=30) payload_cpy = payload if self.__validate_checksum(1, 2, payload, dev): try: payload_cpy = payload_cpy[2:] # Remove padding (=) introduced by struct for processor optimization self.iTOW, self.lon, self.lat, self.height, self.hMSL, self.hAcc, self.vAcc = struct.unpack('=LllllLL', payload_cpy) self.ubx_class = '01' self.ubx_id = '02' except struct.error: print("{} {}".format(sys.exc_info()[0], sys.exc_info()[1])) # time_of_week in ms / Dilution of Precision # DOP is Dimensionless / scaled by factor 100 # Geometric / Position / Time / Vertical / Horizontal / Northing / Easting def __ubx_NAV_DOP(self, dev): payload = dev.read(size=20) payload_cpy = payload if self.__validate_checksum(1, 4, payload, dev): try: payload_cpy = payload_cpy[2:] self.iTOW, self.gDOP, self.pDOP, self.tDOP, self.vDOP, self.hDOP, self.nDOP, self.eDOP = struct.unpack('=L7H', payload_cpy) self.ubx_class = '01' self.ubx_id = '04' except struct.error: print("{} {}".format(sys.exc_info()[0], sys.exc_info()[1])) # Time_of_week in ms / Fractional time_of_week ns / Week number # GPS Fix (6 valid types depending on status) / Fix status flags (4 types) # ECEF X cm / ECEF Y cm / ECEF Z cm / Position Accuracy cm # ECEF-Velocity X cm/s / ECEF-Velocity Y cm/s / ECEF-Velocity Z cm/s # Speed Accuracy cm/s / Position DOP (scale 0.01) # reserved / number of SV's used / reserved def __ubx_NAV_SOL(self, dev): payload = dev.read(size=54) payload_cpy = payload if(self.__validate_checksum(1, 6, payload, dev)): try: payload_cpy = payload_cpy[2:] self.iTOW, self.fTOW, self.week, self.gpsFix, self.flags, self.ecefX, self.ecefY, self.ecefZ, self.pAcc, self.ecefVX, self.ecefVY, self.ecefVZ, self.sAcc, self.pDOP, reserved1, self.numSV, reserved21, reserved22, reserved23, reserved24 = struct.unpack('=LlhBB3lL3lLH6B', payload_cpy) self.ubx_class = '01' self.ubx_id = '06' except struct.error: print("{} {}".format(sys.exc_info()[0], sys.exc_info()[1])) # Time of week in ms / Year(UTC) / Month (UTC 1..12) / Day (1..31) # Hour (0..23) / Minute (0..59) / Seconds (0..60) / Validity Flags # Time Accuracy Estimate in ns / Fraction of second (-1e9..1e9) # GNSS Fix Type / Fix Status Flags / Other Flags / Number of Sattelites # Longitude in deg (1e-7) / Latitude in deg (1e-7) / Height Ellipsoid in mm # Height Sea Level in mm / Horizontal Accuracy in mm / Vertical Accuracy in mm # North Velocity mm s^-1 / East Velocity mm s^-1 / Down Velocity mm s^-1 # Ground speed in mm s^-1 / Heading of Motion in deg (1e-5) / Speed accuracy mm s^-1 # Heading accuracy estimate deg (1e-5) / Position DOP (0.01) / Reserved space # Heading of Vehicle in deg (1e-5) / Magnetic Declination in deg (1e-2) <-+ Accuracy in deg def __ubx_NAV_PVT(self, dev): payload = dev.read(size=94) payload_cpy = payload if(self.__validate_checksum(1, 7, payload, dev)): try: payload_cpy = payload[2:] self.iTOW, self.year, self.month, self.day, self.hour, self.minute, self.second, self.valid, self.tAcc, self.nano, self.fixType, self.flags, self.flags2, self.numSV, self.lon, self.lat, self.height, self.hMSL, self.hAcc, self.vAcc, self.velN, self.velE, self.velD, self.gSpeed, self.headMot, self.sAcc, self.headAcc, self.pDOP, reserved11, reserved12, reserved13, reserved14, reserved15, reserved16, self.headVeh, self.magDec, self.magAcc = struct.unpack('=LH5BBLlB2BB4l2L5lLLH6BlhH', payload_cpy) self.ubx_class = '01' self.ubx_id = '07' except struct.error: print("{} {}".format(sys.exc_info()[0], sys.exc_info()[1])) ## UBX-ACK 0x05 ## # UBX-ACK-ACK (0x05 0x01) def __ubx_ACK_ACK(self, dev): payload = dev.read(size=4) payload_cpy = payload if(self.__validate_checksum(5, 1, payload, dev)): try: payload_cpy = payload_cpy[2:] self.clsID, self.msgID = struct.unpack('=BB', payload_cpy) self.clsID, self.msgID = hex(self.clsID), hex(self.msgID) self.ubx_class = '05' self.ubx_id = '01' except struct.error: print("{} {}".format(sys.exc_info()[0], sys.exc_info()[1])) # UBX-ACK-NAK (0x05 0x00) def __ubx_ACK_NAK(self, dev): payload = dev.read(size=4) payload_cpy = payload if(self.__validate_checksum(5, 0, payload, dev)): try: payload_cpy = payload_cpy[2:] self.clsID, self.msgID = struct.unpack('=BB', payload_cpy) self.clsID, self.msgID = hex(self.clsID), hex(self.msgID) self.ubx_class = '05' self.ubx_id = '00' except struct.error: print("{} {}".format(sys.exc_info()[0], sys.exc_info()[1])) ## UBX-CFG 0x06 ## # UBX-CFG-PRT (0x06 0x00) # Only support for UART atm -> alter input to cover DDC(I2C) / USB / SPI def __ubx_CFG_PRT(self, rate): header, ubx_class, ubx_id, length, uart_port = 46434, 6, 0, 20, 1 rate = hex(rate) rate = rate[2:] while(len(rate) < 8): rate = '0' + rate rate1, rate2, rate3, rate4 = int(rate[-2:], 16), int(rate[-4:-2], 16), int(rate[2:4], 16), int(rate[:2], 16) payload = [length, 0, uart_port, 0, 0, 0, 208, 8, 0, 0, rate1, rate2, rate3, rate4, 7, 0, 3, 0, 0, 0, 0, 0] checksum = self.__calc_checksum(ubx_class, ubx_id, payload) payload = payload + checksum try: self.msg = struct.pack('>H26B', header, ubx_class, ubx_id, *payload) self.ubx_class = '06' self.ubx_id = '00' except struct.error: print("{} {}".format(sys.exc_info()[0], sys.exc_info()[1])) # UBX-CFG-MSG (0x06 0x01) def __ubx_CFG_MSG(self, msgClass, msgId, ioPorts): header, ubx_class, ubx_id, length = 46434, 6, 1, 8 payload = [length, 0, msgClass, msgId] + ioPorts checksum = self.__calc_checksum(ubx_class, ubx_id, payload) try: self.msg = struct.pack('>H14B', header, ubx_class, ubx_id, length, 0, msgClass, msgId, ioPorts[0], ioPorts[1], ioPorts[2], ioPorts[3], ioPorts[4], ioPorts[5], checksum[0], checksum[1]) self.ubx_class = '06' self.ubx_id = '01' except struct.error: print("{} {}".format(sys.exc_info()[0], sys.exc_info()[1])) ## UBX-CFG-CFG (0x06 0x09) def __ubx_CFG_CFG(self, clearMask, saveMask, loadMask, deviceMask): header, ubx_class, ubx_id, length = 46434, 6, 9, 13 payload = [length, 0] + clearMask + saveMask + loadMask + deviceMask checksum = self.__calc_checksum(ubx_class, ubx_id, payload) payload = payload + checksum try: self.msg = struct.pack('>H19B', header, ubx_class, ubx_id, *payload) self.ubx_class = '06' self.ubx_id = '09' except struct.error: print("{} {}".format(sys.exc_info()[0], sys.exc_info()[1])) ## UBX-CFG-NAV5 (0x06 0x24) # ! only currently configures dynModel ! <- Dynmodel = 6/7/8 Needed for high altitude 50km def __ubx_CFG_NAV5(self, dynModel): header, ubx_class, ubx_id, length = 46434, 6, 36, 36 body = [3, 0, 0, 0, 0, 16, 39, 0, 0, 5, 0, 250, 0, 250, 0, 100, 0, 44, 1, 0, 60, 0, 0, 0, 0, 200, 0, 0, 0, 0, 0, 0, 0] payload = [length, 0] + [255, 255] + [dynModel] + body checksum = self.__calc_checksum(ubx_class, ubx_id, payload) payload = payload + checksum try: self.msg = struct.pack('>H42B', header, ubx_class, ubx_id, *payload) self.ubx_class = '06' self.ubx_id = '24' except struct.error: print("{} {}".format(sys.exc_info()[0], sys.exc_info()[1])) def __calc_checksum(self, ubx_class, ubx_id, payload): check1 = (ubx_class + ubx_id) % 256 check2 = ((2*ubx_class) + ubx_id) % 256 for i in range(0, len(payload)): check1 = (check1 + payload[i]) % 256 check2 = (check1 + check2) % 256 result = [check1, check2] return result def __validate_checksum(self, ubx_class, ubx_id, payload, dev): check1 = (ubx_class + ubx_id) % 256 check2 = ((2*ubx_class) + ubx_id) % 256 if self._version == 3: chk1 = dev.read()[0] chk2 = dev.read()[0] for i in range(0, len(payload)): check1 = (check1 + payload[i]) % 256 check2 = (check1 + check2) % 256 else: chk1 = int(dev.read().encode('hex'), 16) chk2 = int(dev.read().encode('hex'), 16) for i in range(0, len(payload)): check1 = (check1 + int(payload[i].encode('hex'), 16)) % 256 check2 = (check1 + check2) % 256 if chk1==check1 and chk2==check2: return True else: print("Checksum is incorrect") return False