Search code examples
reverse-engineeringchecksumcrc

Reverse engineer UPS serial protocol: Checksum/CRC?


I received an APC SMC1000 UPS device that I want to use to power a microcontroller-based application, and perform some tasks before shutting down. The UPS has both a serial and USB port, but the serial port protocol does not seem to be documented. Since USB-host is not on the uC, I want to communicate with it using the serial port.

After some poking around using a Python script, the UPS starts talking and I can identify some information. However, eventually it seems to expect a message back before continuing, so I want to figure out how to do that.

The UPS always send 19 byte messages, where the first byte is an ID, next are 16 bytes of data, and then presumably 2 bytes of some unknown checksum or CRC.

[ Msg ID | 16 byte data | 2 byte checksum? ]

How to derive the CRC or checksum type? I tried already a few schemes and eventually used reveng without luck, on the following messages with ID 7f:

0x7f 0000000019c90013004e000001790000 3190
0x7f 0000000019ca0013004e000001790000 259b
0x7f 0000000019cb0013004e000001790000 19a6
0x7f 0000000019cc0013004e000001790000 0db1
0x7f 0000000019cd0013004e000001790000 01bc
0x7f 0000000019ce0013004e000001790000 f4c7
0x7f 0000000019cf0013004e000001790000 e8d2
0x7f 0000000019d00013004e000001790000 dcdd
0x7f 0000000019d10013004e000001790000 d0e8
0x7f 0000000019d20013004e000001790000 c4f3
0x7f 0000000019d30013004e000001790000 b8fe
0x7f 0000000019d40013004e000001790000 ac0e
0x7f 0000000019d50013004e000001790000 a015
0x7f 000000001c67001500530000017c0000 5eb8

Any helpful thoughts would be appreciated!


Solution

  • I spent way too much time on this. But hey, once you're going down in the rabbit hole...

    Thanks to @rcgldr, I avoided looking for CRC checks and decided to look up various simple checksums instead. Eventually I ended up with a Python script that runs various checksums on the samples above and compares it with the last 2 bytes. It was not successful until I added the first byte into the checksum, giving me a Fletcher's 8 bit checksum. I used the code from https://github.com/njaladan/hashpy for this.

    7f0000000019c90013004e000001790000 0x3190 PASS
    7f0000000019cb0013004e000001790000 0x19a6 PASS
    7f0000000019cc0013004e000001790000 0xdb1 PASS
    7f0000000019cd0013004e000001790000 0x1bc PASS
    7f0000000019ce0013004e000001790000 0xf4c7 PASS
    7f0000000019cf0013004e000001790000 0xe8d2 PASS
    7f0000000019d00013004e000001790000 0xdcdd PASS
    7f0000000019d10013004e000001790000 0xd0e8 PASS
    7f0000000019d20013004e000001790000 0xc4f3 PASS
    7f0000000019d30013004e000001790000 0xb8fe PASS
    7f0000000019d40013004e000001790000 0xac0a FAIL
    7f0000000019d50013004e000001790000 0xa015 PASS
    7f000000001c67001500530000017c0000 0x5eb8 PASS
    

    It also shows that one of the samples had an invalid checksum. Unfortunately, I get the impression that message 7f is some kind of challenge string to the host. Maybe I can also solve that puzzle :)

    Protocol IDs

    So far I have also identified several of the message IDs simply by inspecting the data and searching for values that were reported by APC's PowerChute.

    • 0x00: Header with protocol info (16 bytes)
      • byte 0: unknown (always 0A on the units I checked, maybe protocol version?)
      • byte 1: msg size
      • byte 2: available IDs
    • 0x40: Serial number (14 bytes)
    • 0x41: Unit name
    • 0x43: Unit type
    • 0x45: Firmware version 1
    • 0x46: Firmware version 2
    • 0x48: Battery replacement code
    • 0x49: Manufacturer
    • 0x7e: Part of challenge (to)
    • 0x7f: Part of challenge (from)

    Maybe I will update this list as I progress

    Update 9/11/2019

    I am now documenting my progress on https://sites.google.com/site/klaasdc/apc-smartups-decode