├── eeprom.bin ├── eeprom_notwork.bin ├── bat_exec.py ├── bat_hack.py ├── report_notwork ├── report_work ├── bat_write_eeprom.py ├── README.md ├── bat_read_eeprom.py └── bat_report.py /eeprom.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noolex/lenovo_battery_repair/HEAD/eeprom.bin -------------------------------------------------------------------------------- /eeprom_notwork.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noolex/lenovo_battery_repair/HEAD/eeprom_notwork.bin -------------------------------------------------------------------------------- /bat_exec.py: -------------------------------------------------------------------------------- 1 | import smbus2 2 | import sys 3 | 4 | if len(sys.argv) < 3: 5 | print("Error invalid arguments") 6 | print ("bat_exec [i2c-port-num] [i2c-hex-address]") 7 | exit() 8 | 9 | port = int(sys.argv[1]) 10 | dev = int(sys.argv[2], base=16) 11 | 12 | bus = smbus2.SMBus(port) 13 | 14 | bus.write_byte(dev, 0x08) 15 | print("Start OK.") 16 | bus.close() -------------------------------------------------------------------------------- /bat_hack.py: -------------------------------------------------------------------------------- 1 | import smbus2 2 | import sys 3 | 4 | if len(sys.argv) < 3: 5 | print("Error invalid arguments") 6 | print ("python3 bat_hack.py [i2c-port-num] [i2c-hex-address]") 7 | exit() 8 | 9 | port = int(sys.argv[1]) 10 | dev = int(sys.argv[2], base=16) 11 | 12 | bus = smbus2.SMBus(port) 13 | 14 | bus.write_word_data(dev, 0x71, 0x0214) 15 | print("Send 0x0214 -> 0x71") 16 | val = bus.read_word_data(dev, 0x73) 17 | print("Value from 0x73", val) 18 | val = 0x10000 - val 19 | print("First pswd:", val) 20 | bus.write_word_data(dev, 0x71, val) 21 | print("Send", val, "->0x71") 22 | bus.write_word_data(dev, 0x70, 0x0517) 23 | print("Send 0x0517->0x70") 24 | print("Hacking Ok") 25 | bus.close() -------------------------------------------------------------------------------- /report_notwork: -------------------------------------------------------------------------------- 1 | ------------------------------------------------- 2 | 3 | Manufacturer Name: SANYO 4 | Device Name: LNV-45N1175 5 | Device Chemistry: LION 6 | Serial Number: 0x2fc9 7 | Manufacture Date: 2013.07.11 8 | Manufacturer Access: 0x5001 9 | Remaining Capacity Alarm: 940 mAh(/10mWh) 10 | Remaining Time Alarm: 10 min 11 | Battery Mode: 0x8000 12 | At Rate: 0 mAh(/10mWh) 13 | At Rate Time To Full: 65535 min 14 | At Rate Time To Empty: 65535 min 15 | At Rate OK: 65535 16 | Temperature: 26.150000000000034 degC 17 | Voltage: 12332 mV 18 | Current: 0 mA 19 | Average Current: 0 mA 20 | Max Error: 0 % 21 | Relative State Of Charge: 0 % 22 | Absolute State Of Charge: 0 % 23 | Remaining Capacity: 0 mAh(/10mWh) 24 | Full Charge Capacity: 8894 mAh(/10mWh) 25 | Run Time To Empty: 0 min 26 | Average Time To Empty: 0 min 27 | Average Time To Full: 65535 min 28 | Charging Current: 0 mA 29 | Charging Voltage: 0 mV 30 | Battery Status: 0x3d0 31 | Cycle Count: 0 32 | Manufacturer Data: 33 | Cells voltage: 0 4112 4099 4121 mV 34 | -------------------------------------------------------------------------------- /report_work: -------------------------------------------------------------------------------- 1 | ------------------------------------------------- 2 | 3 | Manufacturer Name: SANYO 4 | Device Name: LNV-45N1175 5 | Device Chemistry: LION 6 | Serial Number: 0x2fc9 7 | Manufacture Date: 2013.07.11 8 | Manufacturer Access: 0x18 9 | Remaining Capacity Alarm: 940 mAh(/10mWh) 10 | Remaining Time Alarm: 10 min 11 | Battery Mode: 0x8000 12 | At Rate: 0 mAh(/10mWh) 13 | At Rate Time To Full: 65535 min 14 | At Rate Time To Empty: 65535 min 15 | At Rate OK: 65535 16 | Temperature: 26.350000000000023 degC 17 | Voltage: 12334 mV 18 | Current: 0 mA 19 | Average Current: 0 mA 20 | Max Error: 0 % 21 | Relative State Of Charge: 0 % 22 | Absolute State Of Charge: 0 % 23 | Remaining Capacity: 0 mAh(/10mWh) 24 | Full Charge Capacity: 8894 mAh(/10mWh) 25 | Run Time To Empty: 0 min 26 | Average Time To Empty: 0 min 27 | Average Time To Full: 65535 min 28 | Charging Current: 4125 mA 29 | Charging Voltage: 12600 mV 30 | Battery Status: 0x3d0 31 | Cycle Count: 0 32 | Manufacturer Data: 33 | Cells voltage: 0 4113 4099 4122 mV 34 | -------------------------------------------------------------------------------- /bat_write_eeprom.py: -------------------------------------------------------------------------------- 1 | import smbus2 2 | import sys 3 | from time import sleep 4 | 5 | CMD_WRITE_EEPROM_BLOCK = 0x10 6 | CMD_ERASE_EEPROM_FLASH = 0x12 7 | DATA_ERASE_CONFIRM = 0x83DE 8 | 9 | def writeEepromBlock(bus, dev, blocknum, data): 10 | wrd = [CMD_WRITE_EEPROM_BLOCK, len(data) + 1, blocknum] + data 11 | msg = smbus2.i2c_msg.write(dev, wrd) 12 | bus.i2c_rdwr(msg) 13 | print("Write block", blocknum) 14 | sleep(0.5) 15 | return len(data) 16 | 17 | if len(sys.argv) < 4: 18 | print("Error invalid arguments") 19 | print ("python3 bat_write_eeprom.py [i2c-port-num] [i2c-hex-address] [file_name_to_write]") 20 | exit() 21 | 22 | port = int(sys.argv[1]) 23 | dev = int(sys.argv[2], base=16) 24 | name = sys.argv[3] 25 | 26 | bus = smbus2.SMBus(port) 27 | 28 | fin = open(name, "rb") 29 | if fin == None: 30 | print("File not found") 31 | exit(0) 32 | 33 | print("Erase eeprom...", end='') 34 | bus.write_word_data(dev, CMD_ERASE_EEPROM_FLASH,DATA_ERASE_CONFIRM); 35 | sleep(1) 36 | print("OK") 37 | 38 | fullsz = 0 39 | for i in range(0, 62): 40 | data = fin.read(32) 41 | fullsz += writeEepromBlock(bus, dev, i, list(data)) 42 | 43 | print("Writing", fullsz, "bytes") 44 | fin.close() 45 | bus.close() 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lenovo_battery_repair 2 | Scripts for repair lenovo battery LNV-45N1175 from ThinkPad x230 on bq8030 controller 3 | 4 | Connect battery to VGA (D-Sub): 5 | Battery connector: | + | + | |SCL|SDA|unkn|GND|GND| 6 | VGA connector pins: 5-GND, 12-SDA, 15-SCL 7 | 8 | Detecting i2c port using i2c-tools and get port number of VGA I2C device 9 | ``` 10 | $i2cdetect -l 11 | ``` 12 | Detect i2c-hex-address for connected battery. And find bat-address by connect-disconnect battery 13 | ``` 14 | $i2cdetect -y [portnum] 15 | ``` 16 | Get report about battery 17 | ``` 18 | $python3 bat_report.py [portnum] [i2c-hex-address] 19 | ``` 20 | Hack bat for access to eeprom 21 | ``` 22 | $python3 bat_hack.py [portnum] [i2c-hex-address] 23 | ``` 24 | Read eeprom 25 | ``` 26 | $python3 bat_read_eeprom.py [portnum] [i2c-hex-address] [eeprom-filename] 27 | ``` 28 | Correct eeprom, I don't know how.... See below 29 | 30 | Write eeprom 31 | ``` 32 | $python3 bat_write_eeprom.py [portnum] [i2c-hex-address] [eeprom-filename] 33 | ``` 34 | After that don't forget start battery cntroller 35 | ``` 36 | $python3 bat_exec.py [portnum] [i2c-hex-address] 37 | ``` 38 | ### Thanks for Viktor: 39 | http://www.karosium.com/2016/08/hacking-bq8030-with-sanyo-firmware.html 40 | https://github.com/karosium/smbusb 41 | 42 | ## Correct EEPROM file 43 | 44 | For **my** battery I correct eeprom: 45 | 1) reset Charge Cycles: 46 | [0x500-0x501] set 0x0000 47 | [0x600-0x601] set 0x0000 48 | 3) [0x5A8] change 0x80 -> 0x00 49 | 2) [0x668] change 0x80 -> 0x00 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /bat_read_eeprom.py: -------------------------------------------------------------------------------- 1 | import smbus2 2 | import sys 3 | 4 | CMD_SET_EEPROM_ADDRESS = 0x09 5 | CMD_READ_EEPROM_BLOCK = 0x0c 6 | EEPROM_BLOCKSZ = 0x20 7 | EEPROM_BASE_ADDR = 0x4000 8 | EEPROM_BLOCK_COUNT = 64 9 | 10 | def readBlock(bus, dev, cmd, sz): 11 | cmdmsg = smbus2.i2c_msg.write(dev, [cmd]) 12 | msg = smbus2.i2c_msg.read(dev, sz + 1) 13 | bus.i2c_rdwr(cmdmsg, msg) 14 | data = list(msg) 15 | fulldata = data[1:] 16 | return fulldata 17 | 18 | def readEepromBlock(bus, dev, blocknum): 19 | try: 20 | bus.write_word_data(dev, CMD_SET_EEPROM_ADDRESS, EEPROM_BASE_ADDR+(blocknum*32)); 21 | except: 22 | print("Error write word data") 23 | return None 24 | data = readBlock(bus, dev, CMD_READ_EEPROM_BLOCK, 32) 25 | return data 26 | 27 | if len(sys.argv) < 4: 28 | print("Error invalid arguments") 29 | print ("bat_read_eeprom [i2c-port-num] [i2c-hex-address] [file_name_to_write]") 30 | exit() 31 | 32 | port = int(sys.argv[1]) 33 | dev = int(sys.argv[2], base=16) 34 | name = sys.argv[3] 35 | 36 | bus = smbus2.SMBus(port) 37 | 38 | fout = open(name, "wb") 39 | fullsz = 0 40 | 41 | for i in range(0, EEPROM_BLOCK_COUNT): 42 | data = readEepromBlock(bus, dev, i) 43 | 44 | if data == None: 45 | print("Error read block", i) 46 | exit() 47 | if len(data) != EEPROM_BLOCKSZ: 48 | print("Error data len", len(data)) 49 | exit() 50 | 51 | fullsz += len(data) 52 | fout.write(bytes(data)) 53 | print("Read", i, "block") 54 | print("Writing", fullsz, "bytes to", name) 55 | fout.close() 56 | bus.close() 57 | -------------------------------------------------------------------------------- /bat_report.py: -------------------------------------------------------------------------------- 1 | import smbus2 2 | import sys 3 | import struct 4 | 5 | def getstring(b) : 6 | i = 0 7 | for k in b: 8 | if k == 0: 9 | break 10 | else: 11 | i += 1 12 | return bytes(b[1:i]).decode("utf8") 13 | 14 | def rd_string(bus, devaddr, cmd): 15 | len = 1 16 | str = "None" 17 | try: 18 | len = bus.read_byte_data(devaddr, cmd) 19 | except: 20 | return "Error read len" 21 | if len: 22 | try: 23 | data = bus.read_i2c_block_data(devaddr, cmd, len+1) 24 | str = getstring(data) 25 | except: 26 | return "Error read block" 27 | return str 28 | 29 | def rd_word(bus, devaddr, cmd): 30 | str = "None" 31 | try: 32 | val = bus.read_word_data(devaddr, cmd) 33 | str = "0x{0:x}".format(val) 34 | except: 35 | return "Error read word" 36 | return str 37 | 38 | def rd_word_val(bus, devaddr, cmd): 39 | val = -1 40 | try: 41 | val = bus.read_word_data(devaddr, cmd) 42 | except: 43 | val = -1 44 | return val 45 | 46 | # ==== main scketch ===== 47 | 48 | if len(sys.argv) < 3: 49 | print("Error invalid arguments") 50 | print ("python3 bat_report [i2c-port-num] [i2c-hex-address]") 51 | exit() 52 | 53 | port = int(sys.argv[1]) 54 | dev = int(sys.argv[2], base=16) 55 | 56 | bus = smbus2.SMBus(port) 57 | 58 | #Todo enable PEC 59 | #SMBEnablePEC(1); 60 | print("-------------------------------------------------\n"); 61 | print("Manufacturer Name:", rd_string(bus, dev, 0x20)); 62 | print("Device Name:", rd_string(bus, dev, 0x21)); 63 | print("Device Chemistry:", rd_string(bus, dev, 0x22)); 64 | print("Serial Number:", rd_word(bus, dev, 0x1c)) 65 | 66 | val = rd_word_val(bus, dev, 0x1b); 67 | print("Manufacture Date:", end=' ') 68 | if val >= 0: 69 | print("{:d}.{:02d}.{:02d}".format(1980+(val>>9), val>>5&0xF, val&0x1F)) 70 | else: 71 | print("Error") 72 | 73 | print("Manufacturer Access:", rd_word(bus, dev, 0x00)); 74 | print("Remaining Capacity Alarm:", rd_word_val(bus, dev, 0x01),"mAh(/10mWh)") 75 | print("Remaining Time Alarm:", rd_word_val(bus, dev, 0x02), "min") 76 | print("Battery Mode:",rd_word(bus, dev, 0x03)) 77 | print("At Rate:", rd_word_val(bus, dev, 0x04), "mAh(/10mWh)") 78 | print("At Rate Time To Full:", rd_word_val(bus, dev, 0x05), "min") 79 | print("At Rate Time To Empty:", rd_word_val(bus, dev, 0x06), "min") 80 | print("At Rate OK:", rd_word_val(bus, dev, 0x07)) 81 | 82 | t = float(rd_word_val(bus, dev, 0x08)) 83 | print("Temperature:", t*0.1-273.15,"degC") 84 | 85 | print("Voltage:", rd_word_val(bus, dev, 0x09), "mV") 86 | print("Current:", rd_word_val(bus, dev, 0x0a), "mA") 87 | print("Average Current:", rd_word_val(bus, dev, 0x0b), "mA") 88 | print("Max Error:", rd_word_val(bus, dev, 0x0c), "%") 89 | print("Relative State Of Charge:", rd_word_val(bus, dev, 0x0d),"%") 90 | print("Absolute State Of Charge:", rd_word_val(bus, dev, 0x0e), "%") 91 | print("Remaining Capacity:", rd_word_val(bus, dev, 0x0f), "mAh(/10mWh)") 92 | print("Full Charge Capacity:", rd_word_val(bus, dev, 0x10), "mAh(/10mWh)") 93 | print("Run Time To Empty:", rd_word_val(bus, dev, 0x11), "min") 94 | print("Average Time To Empty:", rd_word_val(bus, dev, 0x12), "min") 95 | print("Average Time To Full:", rd_word_val(bus, dev, 0x13), "min") 96 | print("Charging Current:", rd_word_val(bus, dev, 0x14), "mA") 97 | print("Charging Voltage:", rd_word_val(bus, dev, 0x15), "mV") 98 | print("Battery Status:", rd_word(bus, dev, 0x16)) 99 | print("Cycle Count:", rd_word_val(bus, dev, 0x17)) 100 | 101 | print("Manufacturer Data: ") 102 | #get lenovo data 103 | len = bus.read_byte_data(dev, 0x23) 104 | data = bus.read_i2c_block_data(dev, 0x23, len+1) 105 | if len == 14: 106 | cells = struct.unpack("<4x4H2x", bytes(data[1:])) 107 | print("Cells voltage:", cells[0], cells[1], cells[2], cells[3], "mV") 108 | else: 109 | print(data) 110 | --------------------------------------------------------------------------------