Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read Errors #8

Open
theroof24 opened this issue Jul 2, 2022 · 4 comments
Open

Read Errors #8

theroof24 opened this issue Jul 2, 2022 · 4 comments

Comments

@theroof24
Copy link

When code is run a read error is printed. If code in modified then it shows the checksum is always off by 3 and any outputted data is garbage.

@theroof24
Copy link
Author

image

@theroof24
Copy link
Author

image

@theroof24
Copy link
Author

theroof24 commented Jul 2, 2022

Additional note: fan set speed function works
And code was recompiled for python 3 on Raspberry Pi 3B

@theroof24
Copy link
Author

theroof24 commented Jul 2, 2022

This is the new code created to work on python3.9.2 on Thonny 3.3.14


import time
import traceback
from smbus2 import SMBus, i2c_msg  # pip install smbus2

class PowerSupply:
    def __init__(self, i2c_bus=1, address=7):  # address 0..7 reflecting the i2c address select bits on the PSU edge connector
        self.i2c = SMBus(i2c_bus)  # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
        self.address = 0x58 + address
        self.EE_address = 0x50 + address

        self.numReg = 0x58 // 2  # literally just 44
        self.lastReg = [0 for _ in range(self.numReg)]
        self.minReg = [0xffff for _ in range(self.numReg)]
        self.maxReg = [0 for _ in range(self.numReg)]

    # not very interesting - read the 24c02 eeprom (you can write it too)
    def read_eeprom(self):
        pos = 0
        data = ""
        while pos < 256:  # fixme
            data += self.i2c.read_i2c_block_data(self.EE_address, pos, 32)
            pos += 32
        print(" ".join(["%02x" % ord(d) for d in data]))

    def read_var(self, address, write_ints, read_count):
        # https://github.com/kplindegaard/smbus2  'dual i2c_rdrw'
        write = i2c_msg.write(address, write_ints)
        # if read_count:
        read = i2c_msg.read(address, read_count)
        self.i2c.i2c_rdwr(write, read)
        return [chr(n) for n in list(read)]

    def write_var(self, address, bytes_):
        as_ints = [ord(d) for d in bytes_]
        self.i2c.write_i2c_block_data(address, as_ints[0], as_ints[1:])

    # the most useful
    def read_dps1200(self, reg, count):
        cs = reg + (self.address << 1)
        reg_cs = ((0xff - cs) + 1) & 0xff  # this is the 'secret sauce' - if you don't add the checksum byte when reading a register the PSU will play dumb
        # checksum is [ i2c_address, reg_address, checksum ] where checksum is as above.
        write_ints = [reg, reg_cs]  # send register plus checksum
        # this should write [address,register,checksum] and then read two bytes (send address+read bit, read lsb, read msb)
        # note this device doesn't require you to use a "repeated start" I2C condition - you can just do start/write/stop;start/read/stop
        return self.read_var(self.address, write_ints, count)

    # Writable registers/cmds:
    # Nothing very interesting discovered so far; you can control the fan speed. Not yet discovered how to turn
    # the 12v output on/off in software.
    #
    # Random notes below:
    #
    # "interesting" write cmds;
    # 0x35  (if msb+lsb==0 clear c4+c5)
    #
    # 0x3b - checks bit 5 of lsb of write data, if set does (stuff), if bit 6 set does (other stuff), low 5 bits
    # stored @ 0xc8 (read with 0x3a)
    #        b0..2= 1=(see 0x344); sets 'fan_related' to 9000
    #               2=(see 0x190a) sets 'surprise_more_flags bit 6'
    #        b3=
    #        b4=
    #        b5=
    #        b6=
    #
    # ..cmds that write to ram..
    # 0x31 - if data=0 sets i2c_flags1 2 & 7 (0x2d8) = resets total_watts and uptime_secs
    # 0x33 - if data=0 resets MaxInputWatts
    # 0x35 - if data=0 resets MaxInputCurrent
    # 0x37 - if data=0 resets MaxRecordedCurrent
    # 0x3b - sets yet_more_flags:5, checks write data lsb:5
    # 0x3d - something like set min fan speed
    # 0x40  (writes 0xe5)
    # 0x41  (writes 0xd4)   <<d4= fan speed control - write 0x4000 to 0x40 = full speed fan (sets 'surprise_mnore_flags:5)')
    # 0x45  sets some voltage threshold if written  (sets a4:1)
    # 0x47  sets some other threshold when written (sets a4:2) -
    # 0x49  sets a4:3
    # 0x4b  sets a4:4
    # 50/51  (writes 0xee/ef) - default is 3200
    # 52/53 (0xa5/6) - some temp threshold
    # 54/55 (0xa7/8)  (sets some_major_flags:5) - eeprom related
    # 56/57 (0xa9/a)  (a9 is EEPROM read address with cmd 57)

    def write_dps1200(self, reg, value):
        val_lsb = value & 0xff
        val_msb = value >> 8
        cs = (self.address << 1) + reg + val_lsb + val_msb
        reg_cs = ((0xff - cs) + 1) & 0xff  # the checksum is the 'secret sauce'
        write_ints = [reg, val_lsb, val_msb, reg_cs]  # write these 4 bytes to i2c
        bytes_ = "".join([chr(n) for n in write_ints])
        return self.write_var(self.address, bytes_)

    def test_write(self):
        # try fuzzing things to see if we can find power on/off.. (not yet) 0x40 controls fan speed (bitfield)
        # for n in [0x35,0x3b,0x40,0x50,0x52,0x54,0x56]:
        for n in [0x35, 0x3b, 0x50, 0x52, 0x54, 0x56]:
            # for n in [0x40]:
            for b in range(16):
                value = (1 << b) - 1
                print("%02x : %04x" % (n, value))
                self.write_dps1200(n, value)
                time.sleep(0.5)

    # Readable registers - some of these are slightly guessed - comments welcome if you figure something new out or have a correction.
    REGS = {
        # note when looking at PIC disasm table; "lookup_ram_to_read_for_cmd", below numbers are <<1
        # the second arg is the scale factor
        0x01: ["FLAGS", 0],  # not sure but includes e.g. "power good"
        0x04: ["INPUT_VOLTAGE", 32.0],  # e.g. 120 (volts)
        0x05: ["AMPS_IN", 128.0],
        0x06: ["WATTS_IN", 2.0],
        0x07: ["OUTPUT_VOLTAGE", 254.5],
        # pretty sure this is right; unclear why scale is /254.5 not /256 but it's wrong - can't see how they'd not be measuring this to high precision
        0x08: ["AMPS_OUT", 128.0],
        # rather inaccurate at low output <10A (reads under) - appears to have internal load for stability so always reads about 1.5 even open circuit
        0x09: ["WATTS_OUT", 2.0],
        0x0d: ["TEMP1_INTAKE_FAHRENHEIT", 32.0],  # this is a guess - may be C or F but F looks more right
        0x0e: ["TEMP2_INTERNAL_FAHRENHEIT", 32.0],
        0x0f: ["FAN_SPEED_RPM", 1],
        # total guess at scale but this is def fan speed it may be counting ticks from the fan sensor which seem to be typically 2 ticks/revolution
        0x1a: ["?flags", 0],  # unknown (from disassembly)
        0x1b: ["?voltage", 1],  # unknown (from disassembly)
        (0x2c >> 1): ["WATT_SECONDS_IN", -4.0],
        # this is a special case; uses two consecutive regs to make a 32-bit value (the minus scale factor is a flag for that)
        (0x30 >> 1): ["ON_SECONDS", 2.0],
        (0x32 >> 1): ["PEAK_WATTS_IN", 2.0],
        (0x34 >> 1): ["MIN_AMPS_IN", 128.0],
        (0x36 >> 1): ["PEAK_AMPS_OUT", 128.0],
        (0x3A >> 1): ["COOL_FLAGS1", 0],  # unknown (from disassembly)
        (0x3c >> 1): ["COOL_FLAGS2", 0],  # unknown (from disassembly)
        (0x40 >> 1): ["FAN_TARGET_RPM", 1],  # unknown (from disassembly)
        (0x44 >> 1): ["VOLTAGE_THRESHOLD_1", 1],  # unknown (from disassembly)
        (0x46 >> 1): ["VOLTAGE_THRESHOLD_2", 1],  # unknown (from disassembly)
        (0x50 >> 1): ["MAYBE_UNDERVOLTAGE_THRESH", 32.0],  # unknown (from disassembly)
        (0x52 >> 1): ["MAYBE_OVERVOLTAGE_THRESH", 32.0],  # unknown (from disassembly)
        # reading 0x57 reads internal EEPROM space in CPU (just logging info, e.g. hours in use)
    }

    def read_dps1200_register(self, reg):
        data = self.read_dps1200(reg << 1, 3)  # if low bit set returns zeros (so use even # cmds)
        # check checksum (why not)
        reply_cs = 0
        for d in data:
            reply_cs += ord(d)
        reply_cs = ((0xff - reply_cs) + 1) & 0xff  # check reply checksum (not really required)
        
        data = data[:-1]
        value = ord(data[0]) | ord(data[1]) << 8
        return value

    def read(self):
        for n in range(self.numReg):
            try:
                value = self.read_dps1200_register(n)
                self.minReg[n] = min(self.minReg[n], value)
                self.maxReg[n] = max(self.maxReg[n], value)
                name = ""
                if n in self.REGS:
                    name, scale = self.REGS[n]
                    if scale < 0:
                        scale = -scale
                        value += self.read_dps1200_register(n + 1) << 16
                else:
                    scale = 1
                print("%02x\t%04x\t" % (n << 1, value))
                if scale:
                    print("%d\t%d\t%d\t(%d)\t%.3f\t%s" % (
                        value, self.minReg[n], self.maxReg[n], self.maxReg[n] - self.minReg[n], value / scale, name))
                else:
                    print("%s\t%s" % (bin(value), name))
            except Exception as ex:
                print("r %02x er %s" % (n, ex))
                print(traceback.format_exc())
        return
        # addr = self.address

    def force_fan_rpm(self, rpm):
        # sets (forces) fan speed; lowest it will accept is 3300rpm conversely, 16000RPM is very, very fast indeed.
        # You probably (untested) have to keep writing this regularly if you want to override what the firmware wants
        # to do based on its temp sensors
        self.write_dps1200(0x40, rpm)

ps = PowerSupply(i2c_bus=1, address=7)
# ps.readEEPROM() 

#ps.read_dps1200(0x04, 1)  # test - don't do this unless you really want to override the fan for some reason
##s.force_fan_rpm(1000)
ps.read()
#while True:
    #ps.force_fan_rpm(1000)
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant