an adventure to activate energy management control in apcupsd

I have an Back-UPS ES 700

A moment in 2018 with the ups out of use because it needed a new battery gives an opportuntiy to see what features are missing

Have a machine with libvirt and kvm, get a guest and licence a proprietary operating system

Using it virtualised is permitted inside a trusted hypervisor like kvm is permitted and adviseable.

Install powerchute for windows and notice that it can toggle energy management and change settings, that which neither apcupsd and nut do not seem to support. Update, latest nut in git ff860090 - ff860097

Energy management means that the controlled outlets will be on only if the master outlet draws more than a set number of watts.

Thus run powerchute in a windows guest under kvm and attach wireshark in usb debug mode to get the instructions issued to the ups...

Tracing sets with wireshark

Wireshark reveals ups gets sent urb control messages with a REPORT ID and some data, it is quickly noticed that the report id is present as the first octet of the "value"

We can compare apcupsd and powerchute

descriptionreport idvalue
with powerchute alarm on0x03780x7802
with powerchute alarm off0x03780x7801
with apcupsd alarm on0x03180x1802
with apcupsd alarm off0x03180x1801
line sensitivity high0x03350x3502
line sensitivity med0x03350x3501
line sensitivity low0x03350x3500
energy management enable first sets0x38f8f01
energy management enable second sets0x38d8d19
energy management disable first sets0x38f8f00
energy management disable then sets0x38d8d19
energy management set 10watts then sets0x38d8d0a
energy management set 25watts then sets0x38d8d19
energy management set 60watts then sets0x38d8d3c
energy management set custom min is 10watts then sets0x38d8d0a
energy management set custom max is 80watts then sets0x38d8d50
for 0x1337 sent in decimal seconds as ontime0x03910x913713
for 0x55aa sent in decimal seconds as offtime0x03920x92aa55
turn power on now sends (presume sets ontime zero)0x03910x910000
turn power on now then sends (presume sets ontime back to gui setting of 0x13370x03910x913713

The powerchute gui lets user pick watts of between 10 and 80 inclusive and times of between 1 and 65535 seconds

Rewriting reportid into usb hid usages

Then found, when trying to set apctest to do energy management, "report id" is not hardcoded into apcupsd, instead we have to find a value called "usage":

The resolution was found by replugging the UPS usb cable, the ups dumps a mapping table of "report id" to "usage"

I firstly tried usbhid-dump --model=051d:0002 just a big block of hex

Then success, save a wireshark pcap of the device being plugged in, tshark -x -r plugin.pcap -T json usbhid | grep "usbhid.item.local.usage_raw\|"

To correlate, the "usages" and reportids of features supported by both powerchute and apcupsd were compared.

When reading this, notice that the usage, that is the last octet in FF8600XX is preceeded by the corresponding report id, which is the operation code octet sent to the ups over usb hid protocol.

This is in addition to 008400XX and 008500XX with USB-IF defined values usage tables for power devices

"": "8c",
"usbhid.item.local.usage_raw": "91",
"": "8d",
"usbhid.item.local.usage_raw": "92",
"": "8e",
"usbhid.item.local.usage_raw": "93",
"": "8f",
"usbhid.item.local.usage_raw": "94",
"": "90",
"usbhid.item.local.usage_raw": "95",
"": "91",
"usbhid.item.local.usage_raw": "96",
"": "92",
"usbhid.item.local.usage_raw": "97",

We have at last some lines for src/drivers/usb/usb.c they are inserted into _known_info[]

The CI_ items are completely made up, they are internal to apcupsd, add them to the table in include/defines.h

I know CI_energy_master_enable is volatile as there is a button on the ups to enable or not energy management, also the threshold can be set by holding the button in until the ups beeps thrice.

  1. {CI_ENERGY_THRESHOLD, 0xFF860092, P_ANY, P_ANY, T_NONE, true},
  2. {CI_ENERGY_MASTER_ENABLE, 0xFF860094, P_ANY, P_ANY, T_NONE, true},
  3. {CI_ENERGY_ONDELAY, 0xFF860096, P_ANY, P_ANY, T_NONE, true},
  4. {CI_ENERGY_OFFDELAY, 0xFF860097, P_ANY, P_ANY, T_NONE, true},

At this point I copy the part of apctest.c that does the ups alarm and have it control the energy management.

It works, can theoretically let do everything that powerchute lets us, in powerchute seconds defaults to 4 and watts to 25.

Being adventurous, I notice that usages 90,91,93 and 95 are defined, though powerchute does not call them. 90 turns out to be unreadable, 91 and 95 return 1, and 93 return 0

I plug in an 18w rated soldering iron into the master port and notice that usage 93 returns 18. Great, this means 93 can be used to measure power draw on tha master outlet.

Doing the hold button until beep thrice reveals the ups adds 10 watts to usage 0xFF860093 and stores it in usage 0xFF860092

  1. {CI_ENERGY_91, 0xFF860091, P_ANY, P_ANY, T_NONE, true},
  2. {CI_ENERGY_DRAW_MASTER, 0xFF860093, P_ANY, P_ANY, T_NONE, true},
  3. {CI_ENERGY_95, 0xFF860095, P_ANY, P_ANY, T_NONE, true},

ACPI virtual battery for QEMU KVM

I have seen the virtual battery on the Internet, no code and not for kvm.

The ultimate idea is to have guests see the status of host UPS as if it is an ACPI battery.

See if I can implement it, so rip acpi tables from qemu, host, server and laptop, then disassemble them via iasl.

We obtain tables from /sys/firmware/acpi/tables generally linux lets root read them, so copy them to somewhere like /tmp/ and feed to iasl to get .dsl files (disassembled).

The most interesting of these are the DSDT (differentiated services table) and any SSDT (supplimentary services table)

Prototype ACPI materials can be built in .asl files, copying from interesting .dsl files and ACPI standards documents. iasl then compiles the .asl source code into .aml to be fed to KVM

The laptop provides tables describing power supply and laptop, cross reference with new and older acpi docs.

I found that you have one DSDT and multiple SSDT tables, and the easiest is to put the virtual battery in an SSDT, beucause the DSDT is built dynamically by QEMU.

This is seen by GNU/Linux upon each boot, a Windows 10 guest required a Reset this PC action to make it re-read the ACPI tables. It is not unexpected as the ACPI tables are not normally expected to change, and a full implementation reads and writes io-ports to obtain battery status from the hypervisor.

Start out with a static battery in various states of charge.

Change the values and recompile to see scenarios like fully charged and nearly empty battery, notice that GNOME does nearly immediate shutdown at 2%.

Full implementation needs a few more things.

I do think it best to use the _BST example more or less verbatim out of the latest acpi specification, althoug dumping DSDT and SDST out of as many machines and hypervisors as can lay my hands on for comparison is also important. As these may be copyrighted I would not republish those.

Further work would be to have the other tables such as _BIF call the hypervisor, so that the battery can be renamed live.

Theres a few reasons.

Guests may not even bother to implement an ACPI parser, or for that matter have some bugs, and just bang on the ioports where it thinks the battery is, so make it look identical and do the real intelligence hypervisor-side, so keep the tables simple.

and, the hypervisor can be tested by getting hexedit open and pointing it at /dev/port inside the guest.

First try, writes to the ports go to userspace, reads always return 0xe9.

Maybe qemu need modifying to get it working.

It appears necessary to modify QEMU to get read and write ioport support for this, it is a big program so whilst ACPI answer work could be done inside, it would be more agile to implement as an external process so that QEMU does not need repeated recompilation to "try things". Using the debugcon driver as a basis:

  1. -chardev socket,mux=on,id=fakeacpi,path=/tmp/fakeacpi
  2. -device isa-debugcon,iobase=0x62,chardev=fakeacpi
  3. -device isa-debugcon,iobase=0x66,chardev=fakeacpi