<commit_before>"""
Python module presenting an API to an ELM327 serial interface
(C) 2015 <NAME> <<EMAIL>>
http://github.com/fwaggle/pyELM327

Please see License.txt and Readme.md.
"""

import serial, time, pprint, re
import pids

pidlist = pids.__pids


class ELM327(object):
	"""
	ELM327 Class

	Meta-class for abstracting ELM327 device.
	"""
	__ser = None # pySerial object.
	__readBuffer = ''
	__debug = 0

	id = None


	def __init__(self, port, debug=0, baud=38400, rtscts=0, xonxoff=0):
		self.__debug = debug
		self.__ser = serial.Serial(port, baud, timeout=5, rtscts=rtscts, xonxoff=xonxoff)
		self.reset()

	def reset(self, warm=0):
		"""
		Try to put the ELM327 device into a known state, by resetting it then 
		turning off echos.

		If the "warm" parameter is non-zero, don't do a full reset - useful for
		keeping custom baud rates and so on.
		"""

		# 'ATZ' will, depending on the status of the device's echo setting
		# return either just the device ID, or 'ATZ\r' followed by the ID.
		# 'ATWS' is preferred if we've changed the baud rate because it doesn't
		# reset all the things.
		if warm:
			self.write('ATWS', nowait=1)
		else:
			self.write('ATZ', nowait=1)
		self.id = self.expect('^ELM327') # Expecting 'ELM327 v1.5'

		# turn off echos
		self.write('ATE0')
		self.expect('^OK') # should be 'OK'
		self.expect('>')

		# Set protocol == AUTO for a sensible default
		# My holden is then AUTO, SAE J1850 VPW
		self.write('ATSP 0')
		self.expect('^OK')

	def tryBaudrate(self, rate=38400):
		"""
		Try a faster baud rate for the PC->ELM327 connection. Depending on your hardware,
		this may not work - for example Bluetooth adaptors are notoriously shit.

		Note that you can't specify 9600 baud - that requires a divisor higher than FF.

		If your device starts in 9600 baud (a hardware pin setting), simply init the ELM327
		class with baudrate=9600 and if higher baud rates don't work reset it with ATZ. 
		If it starts in 38400 baud, and you don't get a stable connection, throw the device
		away and buy a better one.
		"""
		
		# Select appropriate divisor - higher rates may cause a '?' response from ELM
		if rate == 38400:
			divisor = '69'
		elif rate == 57600:
			divisor = '45'
		elif rate == 115200:
			divisor = '23'
		elif rate == 500000:
			divisor = '08'
		elif rate == 666700:
			divisor = '06'
		else:
			raise Exception('Baud rate not implemented')

		# set the baud rate timeout - my python code isn't fast enough for 75ms
		# without getting the response in the old baud rate
		self.write('ATBRT 00')
		result = self.expect('^OK')
		if result != 'OK':
			raise Exception('Couldn\'t set Baud Rate Timeout (AT BRT)')

		# try to set the baud rate to 115200
		self.write('ATBRD ' + divisor)
		result = self.expect('^OK')
		if result != 'OK':
			raise Exception('Couldn\'t set Baud Rate Divisor (AT BRD)')
		self.baudrate = rate
		
		# we should get ELM327 at the new baud rate if it worked.
		result = self.expect('^ELM327')

		print "Res: %s" % result

		# if we get header at new baud rate, ELM is expecting CR at new baud rate.
		self.write('', 1)
		print "result: %s" % result

	@property
	def baudrate(self):
	    return self.__ser.baudrate

	@baudrate.setter
	def baudrate(self, rate):
		self.__ser.baudrate = rate

	def close(self):
		self.__ser.close()

	def fetchProtocol(self):
		"""
		Describe the Protocol used by the ELM327, most of the time this will return AUTO
		for no good reason.
		"""
		self.write('ATDP')
		result = self.expect('^(.+)$')
		return result

	def empty(self):
		"""
		Empty the read buffer - ensures we don't leave data in the way
		if a cheap-shit ELM327 clone doesn't respond within 1 second.

		This function is deprecated as our buffer code is a lot nicer now.
		"""
		self.__ser.flushInput()
		self.__readBuffer = '>' # kludge, I can't find a noop in the ELM327 commandset

	def __enter__(self):
		return self

	def __exit__(self, exception_type, exception_value, traceback):
		self.close()

	def write(self, data, nowait=None):
		"""
		Send raw data to the ELM327. For most features this shouldn't be necessary.
		"""

		if self.__debug:
			print (">>> %s" % data)

		if nowait == None:
			if self.__debug:
				print "DEBUG: Waiting for '>'"
			self.expect('>')

		self.__ser.flushOutput()
		self.__ser.write(data + '\r')

	def expect(self, pattern):
		if self.__debug:
			print "Expect: '%s'" % pattern
		while True:
			n = self.__ser.inWaiting()
			if n > 0:
				self.__readBuffer += self.__ser.read(n)

			if self.__readBuffer.count('\r') > 0:
				lines = self.__readBuffer.split('\r')

				if self.__debug:
					pprint.pprint(lines)

				for l in lines:
					# chomp beginning '>' if present
					if len(l) > 1 and l[0] == '>':
						l = l[1:]

					# drop each line from the read buffer as we process it.
					self.__readBuffer = self.__readBuffer[self.__readBuffer.find('\r')+1:]

					if re.search('^UNABLE TO CONNECT', l):
						raise Exception('UNABLE TO CONNECT')
					if re.search('^NO DATA', l):
						return None
					if re.search('^STOPPED', l):
						raise Exception('STOPPED')
					if re.search('^\?', l):
						raise Exception('UNKNOWN COMMAND')
					if re.search(pattern, l):
						return l
			
			if (self.__readBuffer == '>' or self.__readBuffer == '\r>') and pattern == '>':
				self.__readBuffer = ''
				return '>'

			time.sleep(0.1)

	def fetchBatteryLevel(self):
		"""
		Fetch the battery level from the ELM327.
		"""
		self.write('AT RV')
		result = self.expect('^[0-9\.]+V')
		return result

	def fetchSupportedPIDsLive(self):
		"""
		Fetch a list of supported PIDs from Live Data (Mode 01)

		Returns a dictionary where the indexes are PIDs and the value
		is either 0 or 1 depending on whether it's supported or not.

		Note we don't actually report what PIDs the ECU supports,
		we only report the PIDs that the ECU *and* the library supports.
		"""
		global pidlist # Nasty, but I don't know a better way yet

		# send request
		self.write('0100')
		result = self.expect('^41 ')
		#result = '41 BE 1F A8 13' # test data from Wikipedia
		result = result[3:] # chomp response header

		flags = int(result.replace(' ', ''), 16) # convert to integer
		supported = dict()

		for flag in range(31, -1, -1): # abomination!
			enabled = flags & (1 << (flag))
			if enabled > 0 and 32-flag in pidlist[01]:
				supported[("%02X" % (32-flag))] = 1
			#else:
			#	supported[32-flag] = 0

		return supported

	def fetchLiveData(self, reqPID):
		"""
		Fetch Live Data at the requested PID from ECU.

		Will raise an exception if the PID is unsupported by the library,
		but doesn't check it's supported by the ECU - you'll get "NO DATA"
		if that's the case.
		"""
		global pidlist # Nasty, but I don't know a better way yet
		
		if reqPID not in pidlist[0x01]:
			raise KeyError('Unsupported PID 0x%02x' % reqPID)

		pid = pidlist[0x01][reqPID]

		# Request the data
		self.write('01%02x1' % reqPID)
		result = self.expect('^41 ')

		# Test Data
		# result = '41 0C 0E 5A '
<commit_msg>Add remainder of mode 01 PIDs to fetchSupportedPIDsLive().<commit_after>"""
Python module presenting an API to an ELM327 serial interface
(C) 2015 <NAME> <<EMAIL>>
http://github.com/fwaggle/pyELM327

Please see License.txt and Readme.md.
"""

import serial, time, pprint, re
import pids

pidlist = pids.__pids


class ELM327(object):
	"""
	ELM327 Class

	Meta-class for abstracting ELM327 device.
	"""
	__ser = None # pySerial object.
	__readBuffer = ''
	__debug = 0

	id = None


	def __init__(self, port, debug=0, baud=38400, rtscts=0, xonxoff=0):
		self.__debug = debug
		self.__ser = serial.Serial(port, baud, timeout=5, rtscts=rtscts, xonxoff=xonxoff)
		self.reset()

	def reset(self, warm=0):
		"""
		Try to put the ELM327 device into a known state, by resetting it then 
		turning off echos.

		If the "warm" parameter is non-zero, don't do a full reset - useful for
		keeping custom baud rates and so on.
		"""

		# 'ATZ' will, depending on the status of the device's echo setting
		# return either just the device ID, or 'ATZ\r' followed by the ID.
		# 'ATWS' is preferred if we've changed the baud rate because it doesn't
		# reset all the things.
		if warm:
			self.write('ATWS', nowait=1)
		else:
			self.write('ATZ', nowait=1)
		self.id = self.expect('^ELM327') # Expecting 'ELM327 v1.5'

		# turn off echos
		self.write('ATE0')
		self.expect('^OK') # should be 'OK'
		self.expect('>')

		# Set protocol == AUTO for a sensible default
		# My holden is then AUTO, SAE J1850 VPW
		self.write('ATSP 0')
		self.expect('^OK')

	def tryBaudrate(self, rate=38400):
		"""
		Try a faster baud rate for the PC->ELM327 connection. Depending on your hardware,
		this may not work - for example Bluetooth adaptors are notoriously shit.

		Note that you can't specify 9600 baud - that requires a divisor higher than FF.

		If your device starts in 9600 baud (a hardware pin setting), simply init the ELM327
		class with baudrate=9600 and if higher baud rates don't work reset it with ATZ. 
		If it starts in 38400 baud, and you don't get a stable connection, throw the device
		away and buy a better one.
		"""
		
		# Select appropriate divisor - higher rates may cause a '?' response from ELM
		if rate == 38400:
			divisor = '69'
		elif rate == 57600:
			divisor = '45'
		elif rate == 115200:
			divisor = '23'
		elif rate == 500000:
			divisor = '08'
		elif rate == 666700:
			divisor = '06'
		else:
			raise Exception('Baud rate not implemented')

		# set the baud rate timeout - my python code isn't fast enough for 75ms
		# without getting the response in the old baud rate
		self.write('ATBRT 00')
		result = self.expect('^OK')
		if result != 'OK':
			raise Exception('Couldn\'t set Baud Rate Timeout (AT BRT)')

		# try to set the baud rate to 115200
		self.write('ATBRD ' + divisor)
		result = self.expect('^OK')
		if result != 'OK':
			raise Exception('Couldn\'t set Baud Rate Divisor (AT BRD)')
		self.baudrate = rate
		
		# we should get ELM327 at the new baud rate if it worked.
		result = self.expect('^ELM327')

		print "Res: %s" % result

		# if we get header at new baud rate, ELM is expecting CR at new baud rate.
		self.write('', 1)
		print "result: %s" % result

	@property
	def baudrate(self):
	    return self.__ser.baudrate

	@baudrate.setter
	def baudrate(self, rate):
		self.__ser.baudrate = rate

	def close(self):
		self.__ser.close()

	def fetchProtocol(self):
		"""
		Describe the Protocol used by the ELM327, most of the time this will return AUTO
		for no good reason.
		"""
		self.write('ATDP')
		result = self.expect('^(.+)$')
		return result

	def empty(self):
		"""
		Empty the read buffer - ensures we don't leave data in the way
		if a cheap-shit ELM327 clone doesn't respond within 1 second.

		This function is deprecated as our buffer code is a lot nicer now.
		"""
		self.__ser.flushInput()
		self.__readBuffer = '>' # kludge, I can't find a noop in the ELM327 commandset

	def __enter__(self):
		return self

	def __exit__(self, exception_type, exception_value, traceback):
		self.close()

	def write(self, data, nowait=None):
		"""
		Send raw data to the ELM327. For most features this shouldn't be necessary.
		"""

		if self.__debug:
			print (">>> %s" % data)

		if nowait == None:
			if self.__debug:
				print "DEBUG: Waiting for '>'"
			self.expect('>')

		self.__ser.flushOutput()
		self.__ser.write(data + '\r')

	def expect(self, pattern):
		if self.__debug:
			print "Expect: '%s'" % pattern
		while True:
			n = self.__ser.inWaiting()
			if n > 0:
				self.__readBuffer += self.__ser.read(n)

			if self.__readBuffer.count('\r') > 0:
				lines = self.__readBuffer.split('\r')

				if self.__debug:
					pprint.pprint(lines)

				for l in lines:
					# chomp beginning '>' if present
					if len(l) > 1 and l[0] == '>':
						l = l[1:]

					# drop each line from the read buffer as we process it.
					self.__readBuffer = self.__readBuffer[self.__readBuffer.find('\r')+1:]

					if re.search('^UNABLE TO CONNECT', l):
						raise Exception('UNABLE TO CONNECT')
					if re.search('^NO DATA', l):
						return None
					if re.search('^STOPPED', l):
						raise Exception('STOPPED')
					if re.search('^\?', l):
						raise Exception('UNKNOWN COMMAND')
					if re.search(pattern, l):
						return l
			
			if (self.__readBuffer == '>' or self.__readBuffer == '\r>') and pattern == '>':
				self.__readBuffer = ''
				return '>'

			time.sleep(0.1)

	def fetchBatteryLevel(self):
		"""
		Fetch the battery level from the ELM327.
		"""
		self.write('AT RV')
		result = self.expect('^[0-9\.]+V')
		return result

	def fetchSupportedPIDsLive(self):
		"""
		Fetch a list of supported PIDs from Live Data (Mode 01)

		Returns a dictionary where the indexes are PIDs and the value
		is either 0 or 1 depending on whether it's supported or not.

		Note we don't actually report what PIDs the ECU supports,
		we only report the PIDs that the ECU *and* the library supports.
		"""
		global pidlist # Nasty, but I don't know a better way yet
		supported = dict()

		# send request for first batch
		for i in range(0, 0x81, 32):
			print i
			self.write('01 %2X' % i)
			result = self.expect('^41 ')
			#result = '%2X BE 1F A8 13' % i # test data from Wikipedia
			result = result[3:] # chomp response header

			flags = int(result.replace(' ', ''), 16) # convert to integer

			for flag in range(31, -1, -1): # abomination!
				enabled = flags & (1 << (flag))
				if enabled > 0 and (32-flag) + i in pidlist[01]:
					supported[("%02X" % ((32-flag) + i))] = 1

		return supported

	def fetchLiveData(self, reqPID):
		"""
		Fetch Live Data at the requested PID from ECU.

		Will raise an exception if the PID is unsupported by the library,
		but doesn't check it's supported by the ECU - you'll get "NO DATA"
		if that's the case.
		"""
		global pidlist # Nasty, but I don't know a better way yet
		
		if reqPID not in pidlist[0x01]:
			raise KeyError('Unsupported PID 0x%02x' % reqPID)

		pid = pidlist[0x01][reqPID]

		# Request the data
		self.write('01%02x1' % reqPID)
		result = self.expect('^41 ')

		# Test Data
		# result = '41 0C 0E 5A '

{'fraction_non_alphanumeric': 0.11973282171534143, 'fraction_numerical': 0.030910253677254317, 'mean_word_length': 3.187444213031836, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2174623', 'n_tokens_mistral': 5743, 'n_tokens_neox': 4704, 'n_words': 1744}