Source/credits: - https://github.com/EVNotify/EVNotiPi/blob/master/car/ioniq_bev.py Notes: - default timeout of 100ms on manual requests is insufficient - initial fields padding includes the UDS/OBD response header, i.e. 2 bytes for a type 21 request / 3 bytes for type 22 - CAN bus being switched off by the vehicle can be detected via CAN status turning error-passive Manual request examples: - obd can1 req dev -t1000 7e4 7ec 2101 - obd can1 req dev -t1000 7e4 7ec 2105 - obd can1 req dev -t1000 7e4 7ec 22b002 ------------------------------------------------------------------------------------------ """ Module for the Hyundai Ioniq Electric 28kWh """ from .car import Car from .isotp_decoder import IsoTpDecoder b2101 = bytes.fromhex('2101') b2102 = bytes.fromhex('2102') b2103 = bytes.fromhex('2103') b2104 = bytes.fromhex('2104') b2105 = bytes.fromhex('2105') b2180 = bytes.fromhex('2180') b22b002 = bytes.fromhex('22b002') Fields = ( {'cmd': b2101, 'canrx': 0x7ec, 'cantx': 0x7e4, 'fields': ( {'padding': 6}, {'name': 'SOC_BMS', 'width': 1, 'scale': .5}, {'name': 'availableChargePower', 'width': 2, 'scale': .01}, {'name': 'availableDischargePower', 'width': 2, 'scale': .01}, {'name': 'charging_bits', 'width': 1}, {'name': 'dcBatteryCurrent', 'width': 2, 'signed': True, 'scale': .1}, {'name': 'dcBatteryVoltage', 'width': 2, 'scale': .1}, {'name': 'batteryMaxTemperature', 'width': 1, 'signed': True}, {'name': 'batteryMinTemperature', 'width': 1, 'signed': True}, {'name': 'cellTemp%02d', 'idx': 1, 'cnt': 5, 'width': 1, 'signed': True}, {'padding': 1}, {'name': 'batteryInletTemperature', 'width': 1, 'signed': True}, {'padding': 4}, {'name': 'fanStatus', 'width': 1}, {'name': 'fanFeedback', 'width': 1, 'scale': 100}, {'name': 'auxBatteryVoltage', 'width': 1, 'scale': .1}, {'name': 'cumulativeChargeCurrent', 'width': 4, 'scale': .1}, {'name': 'cumulativeDischargeCurrent', 'width': 4, 'scale': .1}, {'name': 'cumulativeEnergyCharged', 'width': 4, 'scale': .1}, {'name': 'cumulativeEnergyDischarged', 'width': 4, 'scale': .1}, {'name': 'operatingTime', 'width': 4}, # seconds {'padding': 3}, {'name': 'driveMotorSpeed', 'width': 2, 'signed': True, 'offset': 0, 'scale': 1}, {'padding': 4}, # Len: 56 ) }, {'cmd': b2102, 'canrx': 0x7ec, 'cantx': 0x7e4, 'fields': ( {'padding': 6}, {'name': 'cellVoltage%02d', 'idx': 1, 'cnt': 32, 'width': 1, 'scale': .02}, # Len: 38 ) }, {'cmd': b2103, 'canrx': 0x7ec, 'cantx': 0x7e4, 'fields': ( {'padding': 6}, {'name': 'cellVoltage%02d', 'idx': 33, 'cnt': 32, 'width': 1, 'scale': .02}, # Len: 38 ) }, {'cmd': b2104, 'canrx': 0x7ec, 'cantx': 0x7e4, 'fields': ( {'padding': 6}, {'name': 'cellVoltage%02d', 'idx': 65, 'cnt': 32, 'width': 1, 'scale': .02}, # Len: 38 ) }, {'cmd': b2105, 'canrx': 0x7ec, 'cantx': 0x7e4, 'fields': ( {'padding': 11}, {'name': 'cellTemp%02d', 'idx': 6, 'cnt': 7, 'width': 1, 'signed': True}, {'padding': 9}, {'name': 'soh', 'width': 2, 'scale': .1}, {'padding': 4}, {'name': 'SOC_DISPLAY', 'width': 1, 'scale': .5}, {'padding': 11}, # Len: 45 ) }, {'cmd': b2180, 'canrx': 0x7ee, 'cantx': 0x7e6, 'fields': ( {'padding': 14}, {'name': 'externalTemperature', 'width': 1, 'scale': .5, 'offset': -40}, {'padding': 10}, # Len: 25 ) }, {'cmd': b22b002, 'canrx': 0x7ce, 'cantx': 0x7c6, 'optional': True, 'fields': ( {'padding': 9}, {'name': 'odo', 'width': 3}, {'padding': 3}, # Len: 15 ) }, {'computed': True, 'fields': ( {'name': 'dcBatteryPower', 'lambda': lambda d: d['dcBatteryCurrent'] * d['dcBatteryVoltage'] / 1000.0}, {'name': 'charging', 'lambda': lambda d: int(d['charging_bits'] & 0x80 != 0)}, {'name': 'normalChargePort', 'lambda': lambda d: int(d['charging_bits'] & 0x20 != 0)}, {'name': 'rapidChargePort', 'lambda': lambda d: int(d['charging_bits'] & 0x40 != 0)}, ) }, ) class IoniqBev(Car): """ Class for Ioniq Electric """ def __init__(self, config, dongle, watchdog, gps): Car.__init__(self, config, dongle, watchdog, gps) self._dongle.set_protocol('CAN_11_500') self._isotp = IsoTpDecoder(self._dongle, Fields) def read_dongle(self, data): """ Fetch data from CAN-bus and decode it. "data" needs to be a dictionary that will be modified with decoded data """ data.update(self.get_base_data()) data.update(self._isotp.get_data()) def get_base_data(self): return { "CAPACITY": 28, "SLOW_SPEED": 2.3, "NORMAL_SPEED": 4.6, "FAST_SPEED": 50.0 } def get_abrp_model(self): return 'hyundai:ioniq:17:28:other' def get_evn_model(self): return 'IONIQ_BEV'