Homeserver Power Monitoring

As mentioned in the introduction to the homeclimate monitoring project, I run a small unRAID homeserver with a couple of disks to provide some services in my local network. Besides monitoring the server itself with the TIG stack (Telegraf, InfluxDB, Grafana), it is also interesting to monitor power consumption to find out what the server costs aside from the initial cost for the hardware.

Self-made solutions operating on mains voltages is always tricky and the potential risks are large. Luckily, there are a number of cheap and easy commercial options. I opted for the "TP-Link Kasa smart plug HS110" which is available online for typically less than 20€. The HS110 can monitor voltage, current, power and turn on/off the integrated outlet remotely. It also has smart home feature (e.g. Alexa skills, Google Assistant, IFTTT) but I don't need nor want those. Still, it is one of the cheapest and also easiest solutions for power monitoring of plugged devices.

Reading the TP-Link HS110

The HS110 smart plug is meant to be set up with the Kasa app on iOS/Android devices and then controlled through the Kasa app or other smart home applications. The security analysis company SoftScheck analyzed the HS110 a few years ago (Reverse Engineering the TP-Link HS110) and found insecurities in the local communication protocolls. The communication between smart plug and client works via human readable JSON is "encrypted" with a simple autokey XOR cipher. Communicating with the smart plug is therefore possible with almost any device in the local network and easy to set up. This makes the HS1x0 family of smart plugs ideal for makers and hobby hackers. The virtually unrestricted access to the plug is not much of an issue when used in a restricted private network at home.

SoftScheck also provides a python implementation of the reverse engineered communation protocoll on GitHub. It offers options to read and controll the plug. Since I only want to monitor but not controll the smart plug, I wrote my own implementation following the structure of the homeclimate scripts: see on GitHub.

The general structure of the script is as follows.

client = InfluxDBClient(host=influxIP, port=8086, username=user, password=pass, database='telegraf')

def encrypt(string):
    """
    Encrypt the TP-Link Smart Home Protocoll: XOR Autokey Cipher with starting key = 171
    This follows: https://github.com/softScheck/tplink-smartplug
    """
    from struct import pack
    key = 171
    result = pack('>I', len(string))
    for i in string:
        a = key ^ ord(i)
        key = a
        result += bytes([a])
    return result

def decrypt(string):
    """
    Decrypt the TP-Link Smart Home Protocoll: XOR Autokey Cipher with starting key = 171
    This follows: https://github.com/softScheck/tplink-smartplug
    """
    key = 171
    result = ""
    for i in string:
        a = key ^ i
        key = i
        result += chr(a)
    return result

def poll_HS110(ip,port):
    """
    connect to HS110, send payload and receive power data
    """
    import socket
    try:
        sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock_tcp.settimeout(int(10))
        sock_tcp.connect((ip, port))
        sock_tcp.settimeout(None)
        sock_tcp.send(encrypt('{"emeter":{"get_realtime":{}}}'))
        data = sock_tcp.recv(2048)
        sock_tcp.close()
        return data
    except:
        raise ConnectionError("Could not connect to HS110 at IP "+str(ip)+" on port "+str(port))

def decrypt_power(data):
    """
    decrypt power data and convert to Volts, Ampere, Watt, kWh
    """
    import json
    try:
        decrypted = decrypt(data[4:])
        decrypt_dict = json.loads(decrypted)
        return {'voltage':      decrypt_dict['emeter']['get_realtime']['voltage_mv']/1000,    # V
                'current':      decrypt_dict['emeter']['get_realtime']['current_ma']/1000,    # A
                'power':        decrypt_dict['emeter']['get_realtime']['power_mw']/1000,      # W
                'energy_total': decrypt_dict['emeter']['get_realtime']['total_wh']/1000,      # kWh
                'error_code':   decrypt_dict['emeter']['get_realtime']['err_code']
               }
    except:
        raise TypeError("Could not decrypt returned data.")

def read_sensor():
    ...
    return [{'measurement': 'power',
             'tags': {'sensor': 'HS110'},
             'time': polltime,
             'fields': data
            }]


# continuously take data
while True:
    write_database(client = client,
                   data   = read_sensor()
                  )
    time.sleep(sample_time)

Ideally, the Telegraf instance that runs on the homeserver would also run the python polling script and send the received data to InfluxDB. However, I have Telgraf running in the official docker container which does not provide python. I really do not want the rewrite the python code as a shell script, so the next best option for me is to run the polling script on the Raspberry Pi that gathers the homeclimate data. Another systemd service controlls the automatic execution at startup.

[Unit]
Description=homeclimate TP-Link HS110 power monitoring
After=influxdb.service
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=5
User=root
ExecStart=/usr/bin/python3 /home/pi/homeclimate/scripts/hs110.py

[Install]
WantedBy=multi-user.target

Show the data in Grafana

Once the data is getting sent to InfluxDB, plotting with Grafana isstraight forward (see homeclimate V1 for a detailed description).

Homeserver power data shown in Grafana

A typical graph for my setup

The data of interest to me are the current power draw (orange) and the evolution over some time, here 24 hours. You can see that my setup idles at ~27 W with a few spikes to ~30 W. More power over an extended period of time indicates usage, e.g., data synchronisation through Nextcloud which can spin up multiple disks (~13:00h, ~9:30h), watching content with Plex (22:00h) or scheduled maintenence tasks (Unraid mover at 3:00h). The power monitoring has not been running long, so the consumed energy is still low at 8.7 kWh.

Homeserver power data shown in Grafana

Not immediately relevant but nonetheless interesting is monitoring voltage (blue) and current (red). The current graph does not provide much new information because in the simplest case power equals voltage times current and the voltage is pretty much constant. It is nice to confirm, though, that the smart plug is far from its design max current. For other applications this might be something to look out for. The voltage graph on the other hand is more interesting. The voltage provided by the grid is not constant but varies along with the AC frequency. This is to even out slight mismatches between power supply by power plants and demand by households and industry. If the demand increases but the supply is not ramped up simultaneously, the required additional energy is taken out of "stored" energy in the power plant. For example, steam turbines in coal or nuclear power plants can store substantial amount of energy in the rotating turbine blades/drive shaft/... Increased power demand slows the rotation down and causes a decrease in the AC frequency and voltage. The inverse happens when supply exceeds demand. As a result, voltage and frequency fluctuate continously. Larger changes happen when blocks or whole power plants are connected to or disconnected from the grid. Although, I have known this for a long time, it's still cool to measure it myself now.