def fakeProcessNoScanner(self): """ Simulates arriving CAN frames """ printT("Entered %s.fakeProcessNoScanner()" % (type(self).__name__, )) from random import randint fakeIdentifiers = {0x020, 0x220, 0x221, 0x560, 0x56a, 0x56b} # random identifiers counterLoop = 0 while True: durationCreation = 0. durationDispatch = 0. for canFrameIdentifier in fakeIdentifiers: canFrameData = bytearray(b"\x00\x00\x00\x00\x00") canFrameData.append(canFrameIdentifier % 256) canFrameData.append(randint(0, 0xFF)) canFrameData.append(counterLoop % 256) timewatchStart = perf_counter() canFrame = CANFrame( identifier=canFrameIdentifier, isExtended=False, isRTR=False, DLC=8, data=canFrameData, ) durationCreation += perf_counter() - timewatchStart timewatchStart = perf_counter() self.handleNewFrame(canFrame) durationDispatch += perf_counter() - timewatchStart sleep(0.001) counterLoop += 1
def run(self): httpd = ThreadedHTTPServer((self.ipAddress, self.tcpPort), OBDRelayHTTPRequestHandler) httpd.thread = self printT("OBDRelayHTTPServerThread started:", self.ipAddress, self.tcpPort) httpd.serve_forever()
def setParameters(self, parameters): self.parametersLock.acquire() obdLogOutputData = parameters["obdLogOutputData"] if obdLogOutputData != self.logOutputData: self.logOutputData = obdLogOutputData # Close any open file: if self.logOutputDataFile is not None: try: self.logOutputDataFile.close() except: pass self.logOutputDataFile = None # Attempt to open file: if self.logOutputData is not None: try: self.logOutputDataFile = open(self.logOutputData, mode="wb") except Exception as e: self.logOutputDataFile = None printT("Unable to open the obdLogOutputData file:", e) # Cleanup for clean start: self.logOutputDataColumns = {} self.logOutputDataColumnsOrder = [] self.logOutputDataCompact = parameters["obdLogOutputDataCompact"] self.parametersLock.release()
def sendPong(self, content): # thread-safe # Untested rawFrame = bytearray([0b10000000 | WebSocket.OPCODE_PONG]) rawFrame.extend([len(content)]) rawFrame.extend(content) self.sendMessageRaw(self, rawFrame) printT("Sent a PONG") # debug
def stopMonitoring( self, stopMonitoringReason ): """ Interrupts ATMA and displays the reason """ if self.stopMonitoringAttempts>self.stopMonitoringMaxAttempts: raise ConnectionError( "Failed %u times to interrupt ATMA"%(self.stopMonitoringAttempts,) ) self.write( b"\x0D" ) self.stopMonitoringAttempts += 1 if stopMonitoringReason: printT( "Interrupted monitoring: "+stopMonitoringReason )
def reloadParameters(): if execfileIfNeeded(parametersFile, parameters, parametersFileInfo): for httpBinding in parameters["httpBindings"]: for httpd in httpServers: httpdParameters = httpd.getParameters() # Reload HTTP parameters for the HTTP server matching address & port: if httpBinding["address"] == httpdParameters[ "ipAddress"] and httpBinding[ "port"] == httpdParameters["tcpPort"]: break printT("[main.py] Parameters have been reloaded.")
def reloadSequence(self): global sequenceFile global outputList global outputListLock if execfileIfNeeded(sequenceFile, {"obd": self}, self.sequenceFileInfo): with outputListLock: outputList.clear() # erase any obsolete JSON data printT("The OBD sequence has been reloaded.") if self.logger is not None: parameters = {} execfile(parametersFile, parameters) self.logger.setParameters(parameters)
def run(self): while not self.terminating: self.continueProcessLock.acquire() presentPendingData = True with self.parametersLock: while presentPendingData: with self.pendingDataLock: pendingData = self.pendingData self.pendingData = [] # cleanup presentPendingData = len(pendingData) != 0 if self.netOutputSocket: for frame in pendingData: try: self.netOutputSocket.sendto( canEthPacket(frame), self.canEthUdpDst) except OSError as e: # network error silently discarded pass except Exception as e: printT("CAN-ETH converter error, stopping:", format_exc()) try: netOutputSocket = self.netOutputSocket self.netOutputSocket = None netOutputSocket.close() except: pass break packet = None if self.logOutputDataFile: for frame in pendingData: try: self.logOutputDataFile.write( pcaprec_CAN(frame)) except Exception as e: printT("LibPCAP logging error, stopping:", format_exc()) try: logOutputDataFile = self.logOutputDataFile self.logOutputDataFile = None logOutputDataFile.close() except: pass try: self.logOutputDataFile.close() # properly flush & close except: pass
def read(self, size=1): result = self.ser.read(size) if len(result) != 0: if self.serialShowReceivedBytes: if not OBDRelayELM327Thread.lastShownReceived: printT("ELM327 :") if result == b"\x0D": print("") # new line elif result == b"\x0A": pass else: sys.stdout.write(result.decode("latin_1")) OBDRelayELM327Thread.lastShownReceived = True else: if self.serialShowReceivedBytes: printT("ELM327 : <timeout>") OBDRelayELM327Thread.lastShownReceived = False return result
def read(self, *, minReadCount=1, retryDelayIfEmpty=None): """ Read 1 byte from the input buffer Reduced number of I/O operations (CPU optimization) - minReadCount: wait for this number of bytes or a timeout (not a length guarantee) - retryDelayIfEmpty: effective timeout (in seconds) when the timeout of the I/O operation is short (low delay) """ if self.serialLocalBufferEnabled: ''' Optimiser encore : en cas de gros débit, c'est le manque de puissance CPU qui fera que le buffet de réception n'est pas vide, donc on ne résout pas l'utilisation CPU excessive ; on évite seulement les pertes de données. Il faut par exemple ajouter un temps d'attente paramétré si self.ser.in_waiting < minReadCount (très court, de valeur éventuellement calculée par rapport au débit => durée d'envoi d'octets). Attention, le code qui suit la lecture est lui aussi intensif, puisqu'il n'y avait plus d'erreurs de décodage quand la trame est mise à False. ''' if not self.readBuffer: # The buffer is empty, fetching new bytes. # The awaiting bytes count is not unlimited so there should be no excessive memory usage. if retryDelayIfEmpty is not None: readStartedAt = perf_counter() while True: newBytes = self.ser.read( max(minReadCount, self.ser.in_waiting)) if newBytes: # bytes received: append new bytes self.readBuffer.extend(newBytes) break elif retryDelayIfEmpty is None \ or ( perf_counter()-readStartedAt )>retryDelayIfEmpty: # nothing received break if self.readBuffer: # The buffer contains something, taking out the first byte. result = bytes([self.readBuffer.pop(0)]) else: # The buffer contains nothing. result = b"" else: result = self.ser.read() if self.serialShowReceivedBytes: if len(result) != 0: printT("ELM327 :", result.decode("ascii", "replace")) else: printT("ELM327 : <timeout>") return result
def reloadParameters(self): global parametersFile parameters = {} if execfileIfNeeded(parametersFile, parameters, self.parametersFileInfo): self.serialPort = parameters["serialPort"] self.serialBaudRateInitial = parameters["serialBaudRateInitial"] self.serialBaudRateDesired = parameters["serialBaudRateDesired"] self.serialBaudRateDesiredForce = parameters[ "serialBaudRateDesiredForce"] self.serialTimeoutWhileOBD = parameters["serialTimeoutWhileOBD"] self.serialShowSentBytes = parameters["serialShowSentBytes"] self.serialShowReceivedBytes = parameters[ "serialShowReceivedBytes"] self.scannerATSP = parameters["ATSP"] # OBD bus selection self.obdShowIncorrectResult = parameters["obdShowIncorrectResult"] scannerATBRD = round(4000000 / self.serialBaudRateDesired) if scannerATBRD > 0xFF: printT( "The parameter serialBaudRateDesired is set to an insufficient value!" ) self.scannerATBRD = b"ATBRD" + bytes( "%.2X" % round(4000000 / self.serialBaudRateDesired), "ascii") + b"\x0D" # desired baudrate obdCanFrameReq11 = parameters["obdCanFrameReq11"] obdCanFrameReq29 = parameters["obdCanFrameReq29"] self.obdCanFramePrep11 = self.obdCanFramePrepLoaded[ obdCanFrameReq11] self.obdCanFramePrep29 = self.obdCanFramePrepLoaded[ obdCanFrameReq29] self.obdCanFrameReq11 = self.obdCanFrameReqLoaded[obdCanFrameReq11] self.obdCanFrameReq29 = self.obdCanFrameReqLoaded[obdCanFrameReq29] self.obdCanFrameCleanup11 = self.obdCanFrameCleanupLoaded[ obdCanFrameReq11] self.obdCanFrameCleanup29 = self.obdCanFrameCleanupLoaded[ obdCanFrameReq29] if self.logger is not None: self.logger.setParameters(parameters) printT("[OBDRelayELM327.py] Parameters have been reloaded.")
def do_GET(self): f = None try: f = self.send_head() self.copyfile(f, self.wfile) except ConnectionError: return finally: if f is not None: f.close() del f if self.runWebSocket: try: ws = self.webSocketClass(self) ws.run() except ConnectionError: pass except StopIteration: pass except WebSocketBadRequest as e: printT(repr(e)) return
def run(self): canSource = self.canSource self.pendingFrames = [] while True: self.continueProcessLock.acquire() presentPendingFrames = True while presentPendingFrames: self.pendingFramesLock.acquire() pendingFrames = self.pendingFrames self.pendingFrames = [] # cleanup self.pendingFramesLock.release() presentPendingFrames = len(pendingFrames) != 0 for frame in pendingFrames: try: if canSource.passesFilters( frame): # thread-safety required WebSocket_frames.broadcastFrame(frame) if canSource.canExporter is not None: canSource.canExporter.logFrame(frame) except Exception as asyncException: # This exception will be raised on next frame income, with an incorrect stack trace. self.asyncException = asyncException printT(format_exc())
def selectATPB_rate(self, expectedRate): """ Select the most suitable bitrate for USER1 (B) protocol """ try: from math import inf except ImportError: inf = float("inf") expectedRate = float(expectedRate) # Calculate all possible rates rates = {} for mul8by7 in (False, True): ratio = 1. if mul8by7: ratio = 8. / 7. for divider in range(1, 0x41): # dividers from 1 to 0x40 ATPB = divider if mul8by7: ATPB |= 0b0001000000000000 rates[ATPB] = (500. * ratio / float(divider), mul8by7) # Select the closest rate: selGap = inf selRate = 0. selATPB = None selMul8by7 = None for rateInfo in rates.items(): ATPB = rateInfo[0] rate = rateInfo[1][0] mul8by7 = rateInfo[1][1] gap = abs(rate - expectedRate) if gap < selGap: selGap = gap selRate = rate selATPB = ATPB selMul8by7 = mul8by7 printT("Selected USER1 CAN rate: %.2f kb/s" % (selRate, )) self.User1Mul8by7 = selMul8by7 return selATPB
def run(self): while not self.terminating: self.continueProcessLock.acquire() presentPendingData = True self.parametersLock.acquire() while presentPendingData: self.pendingDataLock.acquire() pendingData = self.pendingData self.pendingData = [] # cleanup self.pendingDataLock.release() presentPendingData = len(pendingData) != 0 for onePendingData in pendingData: key = onePendingData[0] outputData = onePendingData[1] dataDateTime = onePendingData[2] try: if key not in self.logOutputDataColumns: self.logOutputDataColumns[key] = True self.logOutputDataColumnsOrder = list( self.logOutputDataColumns.keys()) self.logOutputDataColumnsOrder.sort() self.logOutputDataFile.seek(0) self.logOutputDataFile.truncate() self.logOutputDataFile.write( b'"Time","Elapsed","Updated","' + b'","'.join(self.logOutputDataColumnsOrder) + b'"\x0D\x0A') self.startDateTime = datetime.now() CSVDataColumns = [ b'"' + str(dataDateTime).encode("ascii", "replace") + b'"', str((dataDateTime - self.startDateTime ).total_seconds()).encode("ascii"), ] if self.logOutputDataCompact: CSVDataColumns.append(b'') else: CSVDataColumns.append(b'"' + key + b'"') for key1 in self.logOutputDataColumnsOrder: dataValue = outputData dataType = type(dataValue) CSVDataColumn = b'"#"' if self.logOutputDataCompact and key1 != key: CSVDataColumn = b'' else: try: if dataType is float or dataType is int: CSVDataColumn = str(dataValue).encode( "ascii") elif dataType is bool: if dataValue: CSVDataColumn = b'1' else: CSVDataColumn = b'0' elif dataValue is None: CSVDataColumn = b'""' else: if dataType is bytes or dataType is bytearray: pass else: dataValue = str(dataValue).encode( "utf_8", "replace") CSVDataColumn = b'"' + dataValue.replace( b'"', b"'") + b'"' except: pass CSVDataColumns.append(CSVDataColumn) self.logOutputDataFile.write( b','.join(CSVDataColumns) + b'\x0D\x0A') except Exception as e: printT("Logging error, stopping:", e) try: logOutputDataFile = self.logOutputDataFile self.logOutputDataFile = None logOutputDataFile.close() except: pass self.parametersLock.release() try: self.logOutputDataFile.close() # properly flush & close with except: pass
def reloadParameters(self): parameters = {} if execfileIfNeeded(parametersFile, parameters, self.parametersFileInfo): ### Serial port ### self.serialPort = parameters["serialPort"] self.serialBaudRateInitial = parameters["serialBaudRateInitial"] self.serialBaudRateDesired = parameters["serialBaudRateDesired"] self.serialBaudRateDesiredForce = parameters[ "serialBaudRateDesiredForce"] self.serialLocalBufferEnabled = parameters[ "serialLocalBufferEnabled"] if self.serialLocalBufferEnabled: self.serialLocalBufferAccuATMA = parameters[ "serialLocalBufferAccuATMA"] self.serialLocalBufferMinFillATMA = parameters[ "serialLocalBufferMinFillATMA"] self.serialLocalBufferWaitTimeATMA = parameters[ "serialLocalBufferWaitTimeATMA"] else: self.serialLocalBufferAccuATMA = None self.serialLocalBufferMinFillATMA = None self.serialLocalBufferWaitTimeATMA = None self.serialShowSentBytes = parameters["serialShowSentBytes"] self.serialShowReceivedBytes = parameters[ "serialShowReceivedBytes"] ### CAN bus ### self.testObdCompliant = parameters["canBusTestObdCompliant"] self.scannerATSP = parameters["ATSP"] #self.obdShowIncorrectResult = parameters["obdShowIncorrectResult"] scannerATBRD = round(4000000 / self.serialBaudRateDesired) if scannerATBRD > 0xFF: printT( "The parameter serialBaudRateDesired is set to an insufficient value!" ) self.scannerATBRD = b"ATBRD" + bytes( "%.2X" % round(4000000 / self.serialBaudRateDesired), "ascii") + b"\x0D" # desired baudrate if self.canExporter is not None: self.canExporter.setParameters(parameters) self.allowMixedIdentifiers = parameters[ "canBusAllowMixedIdentifiers"] self.inactivityTimeout = parameters["canBusInactivityTimeout"] if DEBUG_DISCONNECTED_CAN_BUS: self.inactivityTimeout = 20. self.stopMonitoringWait = parameters["canBusStopMonitoringWait"] self.stopMonitoringMaxAttempts = parameters[ "canBusStopMonitoringMaxAttempts"] self.maxStraightInvalidFrames = parameters[ "canBusMaxStraightInvalidFrames"] self.maskOver = parameters["canBusMaskOver"] if self.maskOver is None: self.maskOver = 0x1FFFFFFF # USER1 CAN bus specification: scannerATPB = 0 if parameters["ATPB_11bit"]: scannerATPB |= 0b1000000000000000 if parameters["ATPB_variableDataLength"]: scannerATPB |= 0b0100000000000000 if self.allowMixedIdentifiers: scannerATPB |= 0b0010000000000000 ATPB_dataFormat = parameters["ATPB_dataFormat"] if ATPB_dataFormat == None: pass elif ATPB_dataFormat == "ISO 15765-4": scannerATPB |= 0b0000000100000000 elif ATPB_dataFormat == "SAE J1939": scannerATPB |= 0b0000001000000000 else: raise NotImplementedError('Unknown CAN data format "%s"' % (ATPB_dataFormat, )) scannerATPB |= self.selectATPB_rate(parameters["ATPB_rate"]) self.scannerATPB = ("%.4X" % (scannerATPB, )).encode("ascii") printT("[CANCaptureELM327.py] Parameters have been reloaded.")
def run(self): self.reloadParameters() self.reloadSequence() lastReloadAttempt = int(time()) self.lastPid = -1 if DEBUG_DISCONNECTED_SCANNER: self.fakeProcessNoScanner() self.ser = serial.Serial(port=None, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, xonxoff=False, rtscts=False, write_timeout=None, dsrdtr=False, inter_byte_timeout=None) self.ser.exclusive = True # silently fails if "exclusive" does not exist isFirstAttempt = True while True: setConsoleColorWindows("4F") setConsoleTitle("ELM327 CAN " + self.serialPort + ": Disconnected") if self.ser.is_open: self.ser.close() if not isFirstAttempt: sleep(1) try: # Startup if not self.ser.is_open: # Configure and open the serial port self.reloadParameters() printT("New connection to " + self.serialPort + "...") self.ser.port = self.serialPort self.ser.open() # Communication attempt self.ser.baudrate = self.serialBaudRateInitial self.ser.timeout = 0.5 connectionConfirmed = False while not connectionConfirmed: self.write(b"ATH\x0D") # command that does nothing connectionConfirmed = self.waitForPrompt( noSilentTest=True) # Alternate between initial and desired baudrates if not connectionConfirmed: self.reloadParameters() if self.ser.baudrate == self.serialBaudRateInitial: self.ser.baudrate = self.serialBaudRateDesired else: self.ser.baudrate = self.serialBaudRateInitial printT("Connection works at " + str(self.ser.baudrate) + " b/s") # Reset if self.ser.baudrate == self.serialBaudRateDesired: # Note: on my Icar01 ELM327 V1.5, ATWS resets the baud rate. This is a workaround. self.write(b"ATD\x0D") else: self.write(b"ATWS\x0D") self.ser.timeout = 5 self.waitForPrompt("No prompt after ATWS or ATD!" ) # resets the timeout to 0.5 # TODO - alternate rate instead of keeping serialBaudRateInitial # Apply output parameters self.write(b"ATE0\x0D") # no echo self.waitForPrompt("No prompt after ATE0!") self.write(b"ATL0\x0D") # no <LF> after <CR> self.waitForPrompt("No prompt after ATL0!") self.write(b"ATS0\x0D") # no spaces self.waitForPrompt("No prompt after ATS0!") self.write( b"ATR1\x0D") # wait for response after sending message self.waitForPrompt("No prompt after ATR1!") self.write( b"ATD1\x0D" ) # display DLC of CAN frames between identifier & data self.waitForPrompt("No prompt after ATD1!") self.write(b"ATCAF0\x0D") # raw CAN data formatting self.waitForPrompt("No prompt after ATCAF0!") self.write( b"ATH1\x0D" ) # send CAN headers (otherwise identifiers not shown!) self.waitForPrompt("No prompt after ATH1!") self.write( b"ATJHF0\x0D" ) # send J1939 SAE identifiers as ordinary CAN identifiers self.waitForPrompt("No prompt after ATJHF0!") self.write( b"ATJS\x0D") # disable byte-reordering for J1939 SAE self.waitForPrompt("No prompt after ATJS!") # Setup the CAN bus characteristics self.write(b"ATCSM1\x0D") # CAN spy mode self.waitForPrompt("No prompt after ATCSM1!") self.write( b"ATPB" + self.scannerATPB + b"\x0D" ) # configuration of the USER1 CAN bus specification self.waitForPrompt("No prompt after ATPB!") self.write( b"ATSP" + self.scannerATSP + b"\x0D") # selection of the CAN bus specification self.waitForPrompt("No prompt after ATSP!") # Attempt to contact the ECU printT("Contacting the ECU...") self.ser.timeout = max(MAX_OBD_NEGOCIATION_TIME, self.inactivityTimeout ) # very conservative (1st request) if not DEBUG_DISCONNECTED_CAN_BUS: if self.testObdCompliant: busConnectionConfirmed = False while not busConnectionConfirmed: self.write(b"0100\x0D") # test OBD request busConfirmAnswer = self.readAnwer( "No prompt after 0100!") if "ERROR" in busConfirmAnswer: # BUS ERROR or CAN ERROR continue elif "UNABLE" in busConfirmAnswer: # UNABLE TO CONNECT continue else: busConnectionConfirmed = True # Get the found bus information self.ser.timeout = 0.5 self.write(b"ATDP\x0D") # return active bus protocol busSpecification = self.readAnwer("No prompt after ATDP!") busSpecificationPieces = self.busSpecificationRegex.search( busSpecification) if busSpecificationPieces: self.canBusFamily = busSpecificationPieces.group(1) self.canBusIsExtended = ( busSpecificationPieces.group(2) == "29") self.canBusRate = float( busSpecificationPieces.group(3)) if "USER1" in self.canBusFamily and self.User1Mul8by7: self.canBusRate *= 8. / 7. # ATDP reports the rate unaffected by 8/7 printT("Connected to CAN bus: %u-bit, %.2f kb/s (%s)" % (self.canBusIsExtended and 29 or 11, self.canBusRate, self.canBusFamily)) else: raise Exception( "The found OBD bus does not seem to be a CAN bus: %s" % (busSpecification, )) # Apply the desired baudrate if not self.applyDesiredBaudRate(): if self.serialBaudRateDesiredForce: raise Exception( "The desired baud rate could not be selected!") printT("Connection established at " + str(self.ser.baudrate) + " b/s") setConsoleColorWindows("2F") setConsoleTitle("ELM327 " + self.serialPort + " CAN: " + str(self.ser.baudrate) + " b/s") # Read CAN frames until thread exit counter = 0 # counts the number of straight monitoring episodes self.filter1RemoteChanged = True # unknown state: re-apply ELM327-side filter while True: try: if self.filter1RemoteChanged: with self.filter1RemoteLock: self.write(b"ATCF " + bytes( "%.8X" % (self.filter1RemoteResult & self.maskOver, ), "ascii") + b"\x0D") self.waitForPrompt("No prompt after ATCF!") self.write(b"ATCM " + bytes( "%.8X" % (self.filter1RemoteMask & self.maskOver, ), "ascii") + b"\x0D") self.waitForPrompt("No prompt after ATCM!") self.filter1RemoteChanged = False self.straightInvalidFramesCount = 0 self.write( b"ATMA\x0D" ) # start monitoring (may fail with "?" error, no particular handling needed) self.stopMonitoringAttempts = 0 stopMonitoringNextAttempt = None while True: # Live-refresh of configuration: if int(time()) != lastReloadAttempt: if counter % 2 == 0: self.reloadSequence() else: self.reloadParameters() lastReloadAttempt = int(time()) # Read the next frame frame = self.readFrame() stopMonitoringReason = None # if set, monitoring is stopped if self.filter1RemoteChanged: stopMonitoringReason = "Changed identifier mask" if frame is None: # nothing read stopMonitoringReason = "Nothing received while monitoring" elif frame == False: # invalid frame pass else: self.handleNewFrame(frame) if stopMonitoringReason: stopMonitoringTime = perf_counter() if stopMonitoringNextAttempt is None \ or stopMonitoringTime>=stopMonitoringNextAttempt: self.stopMonitoring(stopMonitoringReason) stopMonitoringNextAttempt = stopMonitoringTime + self.stopMonitoringWait except BaseException as e: if type(e) in (MemoryError, InterruptedError, ConnectionAbortedError): printT(e) else: raise e # Update the sequence counter: counter = counter + 1 except serial.SerialException as e: printT(e) isFirstAttempt = False except: printT(format_exc()) isFirstAttempt = False
def send_head(self, headersOnly=False): headers = {} currentThread = threading.currentThread() currentThread.name = "%s %s" % (self.command, self.path) path = self.path.split("?", 1)[0] if path == "/frames.ws": self.webSocketClass = WebSocket_frames info = WebSocket.prepareHeaders(self) encoded = info["encoded"] response = info["response"] contentType = info["contentType"] headers = info["headers"] elif path == "/threads.txt": contentLines = [b"List of known running threads:"] currentThread = threading.currentThread() for thread in threading.enumerate(): if thread is currentThread: contentLines.append( ("%5u: This request" % (thread.ident or 0, )).encode( "utf_8", errors="replace")) else: threadModuleName = str(thread.run.__func__.__module__) threadClassName = type(thread).__name__ threadTargetClassName = None if type( thread ) is threading.Thread: # is not of a meaningful class try: threadTargetClass = type(thread._target.__self__) threadModuleName = str( threadTargetClass.__module__) threadClassName = str(threadTargetClass.__name__) except: pass contentLines.append(("%5u: %s.%s(%s, %s) [%s]" % ( thread.ident or 0, threadModuleName, threadClassName, str(thread._args)[1:-1], str(thread._kwargs)[1:-1], thread.name, )).encode("utf_8", errors="replace")) contentLines.append( b"\r\nOn Windows the CPU usage can be monitored for each thread using SysInternals Autoruns." ) encoded = b"\r\n".join(contentLines) response = HTTPStatus.OK contentType = "text/plain" elif path[:5] == "/api/": contentType = "application/json" response = HTTPStatus.OK errorString = None data = {} canBus = self.server.thread.canBus try: postData = None if self.command == "POST": postContentLength = None try: postContentLength = int( self.headers.get("Content-Length")) except TypeError: pass except ValueError: pass if postContentLength is None or postContentLength < 0: raise StatusLengthRequired() if postContentLength > self.maxPostLength: raise StatusPayloadTooLarge() postPayload = self.rfile.read(postContentLength) postContentType = self.headers.get( "Content-Type", "application/x-www-form-urlencoded") if postContentType[:16] == "application/json": try: postData = json_loads(postPayload.decode("utf_8")) except ValueError: pass elif postContentType[: 33] == "application/x-www-form-urlencoded": postData_ = None try: postData_ = parse_qs(postPayload.decode("ascii"), keep_blank_values=True, strict_parsing=True) except ValueError: pass if postData_ is not None: postData = {} for pair in postData_.items(): # dict of lists => dict of {str and lists} if pair[0][-2:] == "[]": # explicit list postData[pair[0][:-2]] = pair[1] elif len(pair[1]) == 1: # single str postData[pair[0]] = pair[1][0] else: # implicit list postData[pair[0]] = pair[1] del postData_ if postData is None: raise StatusBadRequest() # Note: each POST data can be str or list or int! if path == "/api/filter1/installByMask": if postData is None: raise StatusMethodNotAllowed() try: mask = self.postFieldToIdentifier(postData["mask"]) maskingResult = self.postFieldToIdentifier( postData["maskingResult"]) except KeyError: raise StatusBadRequest() canBus.setFilter1Remote(mask, maskingResult) elif path == "/api/filter1/installByIds": if postData is None: raise StatusMethodNotAllowed() try: whitelist = self.postFieldToIdentifiersSet( postData, "whitelist") except KeyError: raise StatusBadRequest() if whitelist is None: # only vanish whitelist canBus.setFilter1Local(None) else: # set filter as well canBus.setFilter1(whitelist) elif path == "/api/filter1/getInstalled": maskInfo = canBus.getFilter1Remote() data["mask"] = maskInfo[0] data["maskingResult"] = maskInfo[1] del maskInfo whitelist = canBus.getFilter1Local() whitelist = (whitelist is not None) and list(whitelist) or None data["whitelist"] = whitelist del whitelist elif path == "/api/filter1/reset": if postData is None: raise StatusMethodNotAllowed() canBus.setFilter1(None) elif path == "/api/filter2/setExcluded": if postData is None: raise StatusMethodNotAllowed() try: blacklist = self.postFieldToIdentifiersSet( postData, "blacklist") except KeyError: raise StatusBadRequest() canBus.setFilter2(blacklist) elif path == "/api/filter2/getExcluded": blacklist = canBus.getFilter2() blacklist = (blacklist is not None) and list(blacklist) or None data["blacklist"] = blacklist del blacklist elif path == "/api/filter2/reset": if postData is None: raise StatusMethodNotAllowed() canBus.setFilter2(None) elif path == "/api/setInactivityTimeout": if postData is None: raise StatusMethodNotAllowed() try: inactivityTimeout = float(postData["timeout"]) except TypeError: raise StatusBadRequest() except ValueError: raise StatusBadRequest() if inactivityTimeout <= 0.: raise StatusBadRequest() canBus.inactivityTimeout = inactivityTimeout elif path == "/api/getInactivityTimeout": data["timeout"] = canBus.inactivityTimeout else: raise StatusNotFound() except Status as e: response = e.status errorString = e.text except Exception as e: printT(format_exc()) response = HTTPStatus.INTERNAL_SERVER_ERROR errorString = repr(e) del canBus if response == HTTPStatus.OK: data["status"] = 200 else: data = { "status": int(response), "error": errorString, } encoded = json_dumps(data).encode("utf_8", errors="replace") elif path in self.staticFiles: diskFileExcept = None try: diskFile = open(self.staticFiles[path], "rb") encoded = diskFile.read() diskFile.close() response = HTTPStatus.OK ext_4 = path[-5:].lower() ext_3 = ext_4[-4:] ext_2 = ext_4[-3:] contentType = "application/octet-stream" if ext_3 == ".htm" or ext_4 == ".html": contentType = "text/html; charset=utf-8" elif ext_2 == ".js": contentType = "text/javascript" elif ext_3 == ".css": contentType = "text/css" except FileNotFoundError as e: diskFileExcept = e response = HTTPStatus.NOT_FOUND except PermissionError as e: diskFileExcept = e response = HTTPStatus.FORBIDDEN except Exception as e: printT(format_exc()) diskFileExcept = e response = HTTPStatus.INTERNAL_SERVER_ERROR if diskFileExcept: encoded = repr(diskFileExcept).encode("utf_8", "replace") contentType = "text/plain" else: encoded = b"Not found" response = HTTPStatus.NOT_FOUND contentType = "text/plain" f = BytesIO() f.write(encoded) f.seek(0) self.send_response(response) if contentType is not None: self.send_header("Content-type", contentType) if headersOnly: self.send_header("Content-Length", "0") else: self.send_header("Content-Length", str(len(encoded))) for header in headers.items(): self.send_header(header[0], header[1]) self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() return f
def applyDesiredBaudRate(self): if self.ser.baudrate != self.serialBaudRateDesired: printT("Switching baud rate (", self.scannerATBRD, ")...") self.write(b"ATBRT00\x0D") self.waitForPrompt("No prompt after ATBRT00!") self.write(self.scannerATBRD) self.ser.timeout = 2 receivedO = False receivedOK = False unsupported = False newByte = None # Wait for "OK" for numByte in range(8): newByte = self.read() if len(newByte) == 0 or newByte == b'>': raise Exception( "No answer or invalid answer while applying the desired baudrate!" ) elif newByte == b'?': # unsupported printT( "This chip version does not support changing the serial link bitrate, or wrong argument in " + self.scannerATBRD.decode("ascii") + ".") self.ser.timeout = 0.5 unsupported = True self.waitForPrompt("No prompt after unsupported ATBRD!") break elif newByte == b'O': receivedO = True elif newByte == b'K': if receivedO: receivedOK = True break else: receivedO = False if unsupported: return False elif not receivedOK: raise Exception( "Invalid answer while applying the desired baudrate!") # Switch baudrate self.ser.baudrate = self.serialBaudRateDesired # Wait for "ELM327" (without order checking) unsupported = False receivedStepsATI = { b'E': False, b'L': False, b'M': False, b'3': False, b'2': False, b'7': False, } receivedATI = False for numByte in range(8): newByte = self.read() if len(newByte) == 0: unsupported = True break elif newByte == b'7': receivedStepsATI[newByte] = True for byte in receivedStepsATI.keys(): if not receivedStepsATI[byte]: unsupported = True if not unsupported: receivedATI = True else: self.waitForPrompt() self.ser.timeout = 0.5 break elif newByte in receivedStepsATI: receivedStepsATI[newByte] = True else: for byte in receivedStepsATI.keys(): receivedStepsATI[byte] = False # Wait for <CR> receivedCR = False if receivedATI and not unsupported: for numByte in range(8): newByte = self.read() if newByte == b"\x0D": receivedCR = True break if (not receivedATI) or (not receivedCR) or unsupported: printT( "The communication did not work after applying the desired baudrate!" ) self.ser.baudrate = self.serialBaudRateInitial self.waitForPrompt() self.ser.timeout = 0.5 return False # Send confirmation self.write(b"\x0D") self.ser.timeout = 0.5 # Wait for prompt and reset waiting delay self.waitForPrompt("No prompt after setting the desired baudrate!") self.write(b"ATBRT0F\x0D") self.waitForPrompt("No prompt after ATBRT0F!") return True
def readFrame(self, maxBytesPerLine=128): """ Read a line that should contain a frame (during ATMA operation) Return a CANFrame on success Return False if invalid frame Return None if nothing read Throws a MemoryError if received "BUFFER FULL" Throws a InterruptedError if received "STOPPED" Throws a ConnectionAbortedError if prompt received in other cases Throws a ValueError if invalid bytes received Throws a ChildProcessError if unexpected reboot Bug: When ATMA is interrupted, frames can be truncated but still seem valid (especially 29-bit-identifier frames decoded as 11-bit ones). """ canFrame = None # Read an incoming line if self.serialLocalBufferAccuATMA: # short I/O timeout (several attempts until effective timeout) self.ser.timeout = self.serialLocalBufferWaitTimeATMA else: # I/O timeout = effective timeout self.ser.timeout = self.inactivityTimeout currentLine = bytearray() timedOutEmpty = False try: for numByte in range(maxBytesPerLine): if self.serialLocalBufferAccuATMA: newByte = self.read( minReadCount=self.serialLocalBufferMinFillATMA, retryDelayIfEmpty=self.inactivityTimeout) else: newByte = self.read() if len(newByte) == 0: if len(currentLine) == 0: timedOutEmpty = True #printT( "readFrame() - len( newByte )==0" ) # debug break newByteInt = newByte[0] if newByte == b'\x0D': break # end of line elif newByte == b'>': raise ConnectionAbortedError( "ELM327: Unexpected prompt during ATMA reading") elif newByte == b'\x00' or newByte == b' ': pass # ignore spaces and nulls elif newByteInt > 0x00 and newByteInt < 0x80: currentLine.extend(newByte) else: raise ValueError( "ELM327: Received an illegal byte during ATMA processing!" ) else: # exceeded max length raise ValueError( "ELM327: Received too many bytes in a single line during ATMA processing!" ) # Decode the line if not timedOutEmpty: # Note: spaces are removed, messages are spaceless. # Note: receiving a 29-bit frame on an 11-bit CAN bus & vice-versa are not documented but seem to be accepted, so they are handled as they come. if len(currentLine) == 0: canFrame = False # nothing on the line (like corrupted frame) printT("ELM327: Received an empty ATMA line") elif b"ELM327" in currentLine: raise ChildProcessError( "ELM327: Detected a reboot during ATMA processing!") elif b"BUFFERFULL" in currentLine: raise MemoryError( "ELM327: Received a BUFFER FULL alert during ATMA processing!" ) elif b"STOPPED" in currentLine: raise InterruptedError( "ELM327: Received a STOPPED alert during ATMA processing!" ) elif b"<RXERROR" in currentLine: canFrame = False # corrupted frame (ignored frame) elif b"<DATAERROR" in currentLine: # Warning: incorrect CRCs may produce "DATA ERROR" but no way to distinguish them! pass if canFrame is None: # frame not corrupted canFrameIsRTR = False if b"RTR" in currentLine: canFrameIsRTR = True # Try parsing the frame with both identifier lengths for canFrameIsExtended in self.allowMixedIdentifiers and ( self.canBusIsExtended, not self.canBusIsExtended ) or (self.canBusIsExtended, ): canFrameRegex = canFrameIsExtended and self.canFrameRegex29 or self.canFrameRegex11 canFramePieces = canFrameRegex.match(currentLine) if canFramePieces: canFrameData = None try: if canFrameIsRTR: canFrameData = b'' else: canFrameData = bytes.fromhex( canFramePieces.group(4).decode( "ascii", "replace")) # Note: canFrameData must have a maximum of 8 bytes, otherwise readings are incorrect. except ValueError: # The 11-bit identifier has an odd number of digits. # The 29-bit identifier has an even number of digits. # This exception is raised for wrong identifier sizes + for some incomplete lines. continue # incomplete byte in frame (ignored frame) canFrameIdentifier = int(canFramePieces.group(1), base=16) if canFrameIdentifier > (canFrameIsExtended and 0x1FFFFFFF or 0x7FF): continue # invalid identifier (over maximum) elif canFrameIdentifier < 0: continue # invalid identifier (negative) canFrameDLC = int(canFramePieces.group(2), base=16) if not canFrameIsRTR: canFrameLengthActual = len(canFrameData) if canFrameLengthActual > 8: continue # impossible frame containing more than 8 bytes (ignored frame) canFrameLength = ( canFrameDLC <= 8 ) and canFrameDLC or 8 # DLC, truncated to 8 bytes (max transmitted length) if canFrameLengthActual < canFrameLength: continue # DLC comparison: missing bytes in frame (ignored frame) if canFrameLengthActual > canFrameLength: continue # impossible: data length longer than DLC canFrame = CANFrame( identifier=canFrameIdentifier, isExtended=canFrameIsExtended, isRTR=canFrameIsRTR, DLC=canFrameDLC, data=canFrameData, ) # canFrame = False # debug: pretend malformed frame instead of processing it break else: canFrame = False printT("ELM327: Cannot decode ATMA line: %s" % (str(currentLine), )) finally: exceptionType = exc_info()[0] if exceptionType is None: # Will continue: ready for next call of this function self.straightInvalidFramesCount = 0 if timedOutEmpty: # Will recover (nothing received): restore timeout self.ser.timeout = 0.5 elif exceptionType == MemoryError or exceptionType == InterruptedError: # Will recover (no problem): restore timeout + wait for prompt promptTimeoutMessage = None if exceptionType == MemoryError: promptTimeoutMessage = "ELM327: No prompt after BUFFER FULL alert during ATMA processing!" elif exceptionType == InterruptedError: promptTimeoutMessage = "ELM327: No prompt after STOPPED alert during ATMA processing!" self.ser.timeout = 0.5 # The prompt may show up anywhere, wait for it when not included in currentLine: if b'>' not in currentLine: self.waitForPrompt(promptTimeoutMessage, 64, noSilentTest=True) elif exceptionType == ValueError: # Will recover (invalid bytes received): restore timeout + wait for prompt if self.straightInvalidFramesCount > self.maxStraightInvalidFrames: self.stopMonitoring( "Received more than %u invalid CAN frames in a row" % (self.straightInvalidFramesCount)) self.straightInvalidFramesCount = 0 self.ser.timeout = 0.5 self.waitForPrompt( "ELM327: No prompt received after stopping ATMA (ValueError)", 16777215) self.straightInvalidFramesCount += 1 elif exceptionType == ConnectionAbortedError: # Will recover (prompt received): restore timeout self.ser.timeout = 0.5 else: # Impossible recover: restore timeout just in case self.ser.timeout = 0.5 return canFrame
def handleOBDResult(self, resultLine): resultLineBytes = None try: resultLineBytes = bytes.fromhex(resultLine) except: if resultLine == "STOPPED": # The OBD interface was not ready, let it cool down for a while... printT("Received a STOPPED alert") self.write(b"0100\x0D" ) # retry initiating communication to the OBD bus self.ser.timeout = max( MAX_OBD_NEGOCIATION_TIME, self.serialTimeoutWhileOBD) # very conservative if not self.waitForPrompt(): printT("Prompt not received after STOPPED!" ) # no exception: case handled naturally self.ser.timeout = self.serialTimeoutWhileOBD return else: if self.obdShowIncorrectResult: printT("Incorrect OBD result (PID " + ("0x%.2X" % self.lastPid) + "): " + resultLine) if resultLineBytes is not None: resultType = resultLineBytes[0:1] if resultType == b"\x41": resultPid = resultLineBytes[1] resultData = resultLineBytes[2:( 2 + self.pidResponseLengths[resultPid])] if not self.pidResponseReturnByteArrays[resultPid]: resultData = int.from_bytes(resultData, 'big') callback = self.pidResponseCallbacks.get(resultPid) if callback is not None: try: callback(resultPid, resultData) except: printT(format_exc()) self.lastResponseDatas[ resultPid] = resultData # memorizing to make the value available from other callbacks elif resultType == b"\x7F": # The vehicle reported something. If unsupported then fix the sequence. # Unsupported PIDs may report this or time out. printT("The ECU reported a 7F code for PID " + ("0x%.2X" % self.lastPid) + "): ") else: printT("Unexpected OBD result type in: " + resultLine)
def write(self, data): if self.serialShowSentBytes: printT(" PC :", data.decode("ascii", "replace")) return self.ser.write(data)
def write(self, data): if self.serialShowSentBytes: printT(" PC :", data.decode("ascii", "replace")) OBDRelayELM327Thread.lastShownReceived = False return self.ser.write(data)
def readFrame(self): # blocking, non-thread-safe fragmentedOpcode = self.fragmentedOpcode readBytes = self._readBytes(2) flagFin = (readBytes[0] & 0b10000000) != 0 if flagFin: self.fragmentedOpcode = None #flagRSV1 = ( readBytes[0]&0b01000000 )!=0 #flagRSV2 = ( readBytes[0]&0b00100000 )!=0 #flagRSV3 = ( readBytes[0]&0b00010000 )!=0 headerOpcode = readBytes[0] & 0b00001111 messageOpcode = headerOpcode fragmented = (fragmentedOpcode is not None) isDataFrame = (headerOpcode & 0b1000) == 0 if headerOpcode != WebSocket.OPCODE_FRAGMENT_CONTINUATION: if fragmentedOpcode is not None: raise WebSocketBadRequest( "WebSocket new message frame received with unfinished fragmented message" ) if headerOpcode == WebSocket.OPCODE_FRAGMENT_CONTINUATION: if (not fragmented) or (self.fragments is None): raise WebSocketBadRequest( "WebSocket fragment continuation frame received with no starting frame" ) messageOpcode = fragmentedOpcode elif headerOpcode == WebSocket.OPCODE_TEXT: if not self.allowFramesText: raise WebSocketBadRequest( "Text WebSocket frames are not allowed") elif headerOpcode == WebSocket.OPCODE_BINARY: if not self.allowFramesBinary: raise WebSocketBadRequest( "Binary WebSocket frames are not allowed") elif isDataFrame: raise WebSocketBadRequest( "Unknown WebSocket data frames are not supported") if isDataFrame: if not flagFin: # Setup a new unfinished message: self.fragmentedOpcode = headerOpcode self.fragments = None # start a new fresh list of fragments self.fragmentsTotalLen = 0 fragmented = True flagMask = (readBytes[1] & 0b10000000) != 0 if not flagMask: raise WebSocketBadRequest( "The Mask bit is mandatory but unset in an incoming WebSocket frame" ) headerPayloadLen = readBytes[1] & 0b01111111 if not isDataFrame: if headerPayloadLen >= 126 or not flagFin: raise WebSocketBadRequest( "Fragmented WebSocket control frame received") readBytes = None if headerPayloadLen == 126: readBytes = self._readBytes(2) headerPayloadLen = int.from_bytes(readBytes, 'big', signed=False) elif headerPayloadLen == 127: readBytes = self._readBytes(8) headerPayloadLen = int.from_bytes(readBytes, 'big', signed=False) if self.maxReceivedLen and (headerPayloadLen + self.fragmentsTotalLen ) > self.maxReceivedLen: raise WebSocketBadRequest( "Received a WebSocket message exceeding the maximum allowed message length" ) readBytes = None headerMaskingKey = None if flagMask: readBytes = self._readBytes(4) headerMaskingKey = readBytes readBytes = None if headerMaskingKey is not None: readBytes = self._readBytes(headerPayloadLen) data = bytearray(readBytes) for byteInfo in enumerate(data): k = byteInfo[0] data[k] = byteInfo[1] ^ headerMaskingKey[k % 4] if fragmented: if self.fragments is None: self.fragments = [] self.fragmentsTotalLen = 0 self.fragments.append(data) self.fragmentsTotalLen += headerPayloadLen if isDataFrame: if flagFin: if fragmented: data = bytearray.join(bytearray(), self.fragments) self.fragments = None self.fragmentsTotalLen = 0 try: self.handleMessage(data) except: printT(format_exc()) elif headerOpcode == WebSocket.OPCODE_PING: self.sendPong(data) elif headerOpcode == WebSocket.OPCODE_CLOSE: raise StopIteration("WebSocket graceful connection termination")
def setParameters(self, parameters): with self.parametersLock: pcapOutputFile = parameters["pcapOutputFile"] if pcapOutputFile != self.logOutputDataFileName: self.logOutputDataFileName = pcapOutputFile # Close any open file: if self.logOutputDataFile is not None: try: self.logOutputDataFile.close() except: pass self.logOutputDataFile = None # Attempt to open file: if self.logOutputDataFileName is not None: try: self.logOutputDataFile = open( self.logOutputDataFileName, mode="wb") self.logOutputDataFile.write(pcap_hdr_t) except Exception as e: self.logOutputDataFile = None printT("Unable to open the pcapOutputFile file:", e) # Cleanup for clean start: if socket: canEthUdpEnabled = parameters.get("canEthUdpEnabled", False) canEthUdpIpVersion = parameters.get("canEthUdpIpVersion", 4) if canEthUdpIpVersion == 6: canEthUdpIpVersion = socket.AF_INET6 elif canEthUdpIpVersion == 4: canEthUdpIpVersion = socket.AF_INET canEthUdpAddrSrc = parameters.get("canEthUdpAddrSrc", None) canEthUdpPortSrc = parameters.get("canEthUdpPortSrc", None) canEthUdpAddrDst = parameters.get( "canEthUdpAddrDst", canEthUdpIpVersion == socket.AF_INET6 and "::1" or "127.0.0.1") canEthUdpPortDst = parameters.get("canEthUdpPortDst", 11898) netOutputConfig = ( canEthUdpEnabled, canEthUdpIpVersion, canEthUdpAddrSrc, canEthUdpPortSrc, canEthUdpAddrDst, canEthUdpPortDst, ) if not self.netOutputSocket or netOutputConfig != self.netOutputConfig: if self.netOutputSocket: self.netOutputSocket.close() self.netOutputSocket = None if canEthUdpEnabled: try: netOutputSocket = None netOutputSocket = socket.socket( canEthUdpIpVersion, socket.SOCK_DGRAM) netOutputSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if canEthUdpAddrSrc and canEthUdpPortSrc: netOutputSocket.bind( (canEthUdpAddrSrc, canEthUdpPortSrc)) self.canEthUdpDst = (canEthUdpAddrDst, canEthUdpPortDst) self.netOutputSocket = netOutputSocket except: printT(format_exc()) try: netOutputSocket.close() except: pass self.netOutputConfig = netOutputConfig
def run(self): self.reloadParameters() self.reloadSequence() self.lastPid = -1 self.ser = serial.Serial(port=None, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, xonxoff=False, rtscts=False, write_timeout=None, dsrdtr=False, inter_byte_timeout=None) self.ser.exclusive = True # silently fails if "exclusive" does not exist isFirstAttempt = True while True: setConsoleColorWindows("4F") setConsoleTitle("ELM327: Disconnected") if self.ser.is_open: self.ser.close() if not isFirstAttempt: sleep(1) try: # Startup if not self.ser.is_open: # Configure and open the serial port self.reloadParameters() printT("New connection to " + self.serialPort + "...") self.ser.port = self.serialPort self.ser.open() # Communication attempt self.ser.baudrate = self.serialBaudRateInitial self.ser.timeout = 0.5 printT("Contacting the ELM327 chip...") connectionConfirmed = False while not connectionConfirmed: self.write(b"ATH\x0D") # command that does nothing connectionConfirmed = self.waitForPrompt() # Alternate between initial and desired baudrates if not connectionConfirmed: self.reloadParameters() if self.ser.baudrate == self.serialBaudRateInitial: self.ser.baudrate = self.serialBaudRateDesired else: self.ser.baudrate = self.serialBaudRateInitial printT("Connection works at " + str(self.ser.baudrate) + " b/s") # Reset if self.ser.baudrate == self.serialBaudRateDesired: # Note: on my Icar01 ELM327 V1.5, ATWS resets the baud rate. This is a workaround. self.write(b"ATD\x0D") else: self.write(b"ATWS\x0D") self.ser.timeout = 5 self.waitForPrompt("No prompt after ATWS or ATD!" ) # resets the timeout to 0.5 # Apply parameters (except new baudrate) self.write(b"ATE0\x0D") # no echo self.waitForPrompt("No prompt after ATE0!") self.write(b"ATL0\x0D") # no <LF> after <CR> self.waitForPrompt("No prompt after ATL0!") self.write(b"ATS0\x0D") # no spaces self.waitForPrompt("No prompt after ATS0!") self.write(b"ATCAF1\x0D") # display only the OBD payload self.waitForPrompt("No prompt after ATCAF1!") self.write(b"ATSP" + self.scannerATSP + b"\x0D") # selection of the OBD bus self.waitForPrompt("No prompt after ATSP!") self.write(b"0100\x0D" ) # try initiating communication to the OBD bus self.ser.timeout = max( MAX_OBD_NEGOCIATION_TIME, self.serialTimeoutWhileOBD) # very conservative self.waitForPrompt( "No prompt after initial 0100! Is the ignition on?") self.ser.timeout = 5 # Apply the desired baudrate if not self.applyDesiredBaudRate(): if self.serialBaudRateDesiredForce: raise Exception( "The desired baud rate could not be selected!") printT("Connection established at " + str(self.ser.baudrate) + " b/s") setConsoleColorWindows("2F") setConsoleTitle("ELM327: " + str(self.ser.baudrate) + " b/s") # Read OBD information until thread exit straightErrorCount = 0 counter = 0 # counts the number of executed sequences self.ser.timeout = max(MAX_OBD_NEGOCIATION_TIME, self.serialTimeoutWhileOBD ) # very conservative (1st request) isFirstRequest = True isReadingCan29 = False isReadingCan11 = False while True: # Send and handle requests from the configured sequence: for pid in self.sequence: self.ser.reset_input_buffer() if isinstance(pid, self.CanFrameRequest): # Reading of a CAN frame identifier: req = pid if req.identifierBits == 29: if not isReadingCan29: self.obdCanFramePrep29(self) isReadingCan29 = True resultData = self.obdCanFrameReq29(self, req) else: if not isReadingCan11: self.obdCanFramePrep11(self) isReadingCan11 = True resultData = self.obdCanFrameReq11(self, req) if resultData: callback = self.pidResponseCallbacks.get(req) if callback is not None: try: callback(req.identifier, resultData) except: printT(format_exc()) callback = None req = None resultData = None else: # Reading of an OBD PID: if isReadingCan29 or isReadingCan11: if self.obdCanFrameCleanup29 == self.obdCanFrameCleanup11: self.obdCanFrameCleanup29(self) else: if isReadingCan29: self.obdCanFrameCleanup29(self) if isReadingCan11: self.obdCanFrameCleanup11(self) isReadingCan29 = False isReadingCan11 = False self.write(self.pidToCommand[pid]) self.lastPid = pid line = self.readAnwer() if line == False: straightErrorCount = straightErrorCount + 1 else: straightErrorCount = 0 self.handleOBDResult(line) if isFirstRequest: isFirstRequest = False self.ser.timeout = self.serialTimeoutWhileOBD # Live-refresh of configuration: if counter % 2 == 0: self.reloadSequence() else: self.reloadParameters() # Handle erroring communication: if straightErrorCount >= len(self.sequence): raise Exception( "Unable to communicate on the serial port anymore!" ) # Update the sequence counter: counter = counter + 1 except serial.SerialException as e: printT(e) isFirstAttempt = False except: printT(format_exc()) isFirstAttempt = False
def callback(pid, data): global obd obd.setCurrentOutputData(b"throttlePosition", (data - min) * coef) # no unit printT("ACCELERATOR =", data)
def reloadSequence(self): if execfileIfNeeded(sequenceFile, {"canBus": self}, self.sequenceFileInfo): printT("The CAN sequence has been reloaded.")
def handleMessage(self, data): printT("Received:", data)
del httpd # Reload the parameters from utility import printT def reloadParameters(): if execfileIfNeeded(parametersFile, parameters, parametersFileInfo): for httpBinding in parameters["httpBindings"]: for httpd in httpServers: httpdParameters = httpd.getParameters() # Reload HTTP parameters for the HTTP server matching address & port: if httpBinding["address"] == httpdParameters[ "ipAddress"] and httpBinding[ "port"] == httpdParameters["tcpPort"]: break printT("[main.py] Parameters have been reloaded.") # Main work in an endless loop try: from time import sleep while True: sleep(3) reloadParameters() except KeyboardInterrupt: printT("Exiting...") canExporter.terminate() except BaseException as e: printT(e)