Sensor for computer usage?

Not sure if you found a solution for Windows 10, but for any linux users who comes across this thread later, you can use ‘xprintidle’ to check if your computer is idle or not.

apt-get install xprintidle

Here’s the command I use (you will need to replace USER with your username. It returns a 1 if the computer is active and 0 if it is idling for over 60,000ms (60 seconds).

idle=$(xprintidle); [ $idle -lt 60000 ] && echo 1 || echo 0

You could then have HA ssh into your computer, run the above command, and return the 0 or 1 to a binary sensor.

I use it like so:

  • ping the computer.
  • if the ping was successful:
    • ssh into computer and run the above command. It will return a 1 if active and a 0 if idling.
  • If ping was not successful then return a 0.
ping -W 1 -c 1 192.168.0.10 > /dev/null 2>&1 && /usr/bin/ssh  USER@IP_ADDRESS 'idle=$(export DISPLAY=:0 && sudo -u USER xprintidle); [ $idle -lt 60000 ] && echo 1 || echo 0' || echo 0
5 Likes

You could put a smart power switch and monitor power consumption? When the computer goes into sleep mode it should consume less power.

3 Likes

Assuming that you have complete control of the computers and you aren’t trying to give employees a reason to quit, how about using a Python keylogger program, and every time the Return key is hit, send an MQTT message.

Thanks for all the replies. I couldn’t find a ready solution for this so I have started to hack together my own application in C#. Work is still in progress.

Instead of keylogging there is a less intrusive method called GetLastInputInfo that can be used. It returns the tick of the last time the mouse or keyboard was used which serve my purpose perfectly.

Unfortunately GetLastInputInfo cannot be used from a service, so for now it lives in a small window that I just minimize. Once I got other things in place I will change it to a systray app instead.

image

The first version used a local http server, but since this required the app to either run as administrator or changing network and firewall settings I am now looking into publishing the info to a mqtt channel instead.

The http response was a simple json with a timestamp for last input and number of idle seconds, the mqtt message will probably need to add the client name to the message aswell.
{"lastActivityTimeStamp":"2018-09-30T12:37:51.3315053+02:00","idleTime":3}

I know this topic is already quite old, but I want to share a solution for Windows computers to detect if they are idle or not. I use this kind of information to turn the heating on when somebody works on the computer.

The main idea is to send a heartbeat from the computer when it is active.

  1. Create a MQTT binary sensor:
binary_sensor:
  - platform: mqtt
    name: "PC Activity"
    state_topic: "pc/activity/state"
    off_delay: 120
    device_class: connectivity

The hearbeat will be sent every 1 minute, and after 2 minutes the sensor will go back to off (off_delay).
2. Download and install EventGhost
2b. Configure EventGhost to automatically start with Windows
3. Add plugin “Timer”
4. Create new macro called “Active”
4a. Add events System.UnIdle, Main.OnInit, System.SessionUnlock
4b. Add command “Start new or control running timer”
4c. Enabled “Start timer”, set loops to “0” (infinite) and interval to “60” seconds.
4d. Give event name “heartbeatEvent”
5. Create new macro “Inactive”
5a. Add events System.Idle, Main.OnClose, System.Suspend
5b. Add command and chose “python script”:

import requests as req
headers = {'Authorization': 'Bearer <token>', 'content-type': 'application/json'}
url = 'http://<HA IP>:<HA PORT>/api/services/mqtt/publish'
data = '{"payload": "OFF", "topic":"pc/activity/state"}'
response = req.post(url, headers=headers, data=data)
print(response.text)

where <token> is the (“Long-Lived Access Token”) (see REST API for more information)
5c. Add command “Start new or control running timer”, select your timer and chose “Abort”
6. Add macro “Heartbeat”
6a. Add Timer.heartbeatEvent (from step 4d)
6b. Add Python script

import requests as req
headers = {'Authorization': 'Bearer <token>', 'content-type': 'application/json'}
url = 'http://<HA IP>:<HA PORT>/api/services/mqtt/publish'
data = '{"payload": "ON", "topic":"pc/activity/state"}'
response = req.post(url, headers=headers, data=data)
print(response.text)

Now, the MQTT sensor should become “connected” when you login or the computer wakes up and it becomes “disconnected” when you logout or don’t do anything for a certain amount of time. In theory, it would also work without the heartbeat since EventGhost sets the sensor to on and off. But to be sure that the “off” is not missing e.g. due to a power off, I added the delay_off to the MQTT sensor. Also the “Inactive” macro could be left out if you can wait for a maximum of 2 minutes more before you see the “disconnected” state in HA.

7 Likes

There is also IOT Link that has a lot of functionalities, not perfect but usable. Worth taking a look, an alternative to hand-rolling your own.

2 Likes

There’s also an existing ping integration:

Binary Sensor

The ping binary sensor platform allows you to use ping to send ICMP echo requests. This way you can check if a given host is online and determine the round trip times from your Home Assistant instance to that system.

Hi @ZzetT, may I ask your help with this? I have followed your steps as much as possible but I think I did not configure your step 4d and step 6 correctly. This is my Eventghost configuration, any help is appreciated.

Specifically, when you say ‘give event name heartbeatEvent’ do you mean to rename the timer from Active macro as heartbeatEvent? Also, the Heartbeat macro does not trigger. I dragged a Timer event from the logger to the macro and renamed it as Timer.heartbeatEvent, is that what you mean?

eg_ha

My HA uses nmap location services on local network to determine if LAN/WLAN is active on selected computer…
No need to monitor active usage :slight_smile:
Best. JR

This is how it looks for me (although I think I now named it “timingEvent”):

Yes, that’s much simpler. But I don’t want to have the heating on even if my PC is running and I’m not using it :wink:

I assume if You use PC You are near it: some kind of presence detection maybe?
Does it go to sleep when not used- if so then nmap helps as LAN goes inactive…
I use it to switch on my remote printer on/off- it is on another room/floor.
Best, JR

Thanks so much. I got it working.

I use IOT Link to monitor my son’s gaming PC.

It let’s me see all sorts of stuff about the PC, including CPU usage.

It includes the ability to see what’s being displayed within the active monitors Maybe you could use the images to detect changes on the screen (which presumably would correspond to keyboard / mouse movements)

I’m on Linux and I like the idea of @martokk to use xprintidle, but I think it would be better to let the computer report to homeassistant using a webhook whenever the state changes, instead of letting homeassistant poll the computer every X seconds. I solved it using a script that runs in the background which connects with a trigger based template sensor.

In home assistant I added the following to config/configuration.yml:

template:
  - trigger:
      - platform: webhook
        webhook_id: eaea48a1-30e3-47bf-a076-30f816f0d3d1
    binary_sensor:
      - name: "Computer in use"
        icon: "mdi:desktop-tower"
        unique_id: computer_in_use
        state: "{{ trigger.json.in_use }}"
        # The script reports the state every 5 mins. Set a timeout of 11 mins,
        # so that when 2 pings are missed the computer is considered idle.
        auto_off: 660

For the webhook ID I used a random UUID. You can generate your own here: uuid at DuckDuckGo

On the computer I created a script that will keep track of whether the computer is in use in a file stored in memory, and will update home assistant when the state changes. It resends the state every 5 minutes to account for missed updates. I had a few instances where the “off” state was not received by Home Assistant for some reason (e.g. laptop disconnected from wifi), and my lights stayed on all night. If Home Assistant doesn’t get pinged for 11 minutes it considers the computer to be off.

The script runs in a tight loop of 200 milliseconds, so it updates Home Assistant pretty much instantly when the computer is being used. I use this quick response because I am impatient, I want my lights on immediately when I touch the mouse, not with a 1 second delay :slight_smile:

Because the state is kept in memory the resource usage is negligible.

Script is in $HOME/.local/scripts/hass-report-usage.sh:

#!/bin/bash

# Abort if another instance is already running.
if [ -e /dev/shm/in_use.pid ]; then
  read PID < /dev/shm/in_use.pid
  if kill -0 $PID > /dev/null 2>&1; then
    echo >&2 "already running. aborting."
    exit 1
  fi
fi

trap "rm /dev/shm/in_use; curl --header \"Content-Type: application/json\" --request POST --data '{\"in_use\": 'false'}' http://homeassistant.lan:8123/api/webhook/eaea48a1-30e3-47bf-a076-30f816f0d3d1" EXIT

echo $$ > /dev/shm/in_use.pid

# Initially set the status to idle.
echo "false" > /dev/shm/in_use

# Store a timestamp 10 minutes in the past, to trigger an immediate update.
echo $(( `date +%s%3N` - 600000)) > /dev/shm/in_use.timestamp

# Consider computer idle after 5 minutes.
TIMEOUT=300000

while true; do
  if [ `xprintidle` -lt $TIMEOUT ]; then
    IN_USE=true
  else
    IN_USE=false
  fi

  # Report every 5 minutes.
  if [ `date +%s%3N` -gt $((`cat /dev/shm/in_use.timestamp` + $TIMEOUT)) ]; then
    REPORT=true
  else
    REPORT=false
  fi

  if [ `cat /dev/shm/in_use` != $IN_USE ]; then
    CHANGED=true
  else
    CHANGED=false
  fi

  if $REPORT || $CHANGED; then
    echo $IN_USE > /dev/shm/in_use
    echo `date +%s%3N` > /dev/shm/in_use.timestamp
    curl --header "Content-Type: application/json" --request POST --data '{"in_use": '$IN_USE'}' http://homeassistant.lan:8123/api/webhook/eaea48a1-30e3-47bf-a076-30f816f0d3d1
  fi

  sleep 0.2
done

Make the script executable:

$ chmod u+x ~/.local/scripts/hass-report-usage.sh

I trigger the script when X is started by adding the following line to ~/.xinitrc, somewhere before the final line which starts the window manager:

$HOME/.local/scripts/hass-report-usage.sh &

Laptops

If you are on a laptop you might want to create some additional triggers to report the machine as idle when it suspends/hibernates and back as active when it resumes.

Going to sleep

When the computer goes to suspend/hibernation/sleep, then what works well is to just send the HTTP request to report the computer as idle, without changing the flag in /dev/shm/in_use. I found this works more reliably, since several processes might run simultaneously when sleep mode is initiated. If the script is still active in the background it might toggle the status back to “active” a split second after being set to “idle”.

Execute this command when suspending:

curl --header "Content-Type: application/json" --request POST --data '{"in_use": 'false'}' http://homeassistant.lan:8123/api/webhook/eaea48a1-30e3-47bf-a076-30f816f0d3d1

Resuming

When resuming from sleep/hibernation a good tactic is to toggle the flag. When the script resumes its operations it will read the flag and send out the “active” state immediately.

Execute this command when resuming:

echo 'false' > /dev/shm/in_use

Example systemd service for suspend/resume

Save this in /etc/systemd/system/[email protected]. Enable it with sudo systemctl enable hass-suspend@myusername

Edit: this is not 100% reliable, if anyone finds a robust way of detecting suspend/resume please let me know.

[Unit]
Description=Inform Home Assistant that the computer is idle on suspend.
Before=sleep.target
StopWhenUnneeded=yes

[Service]
User=%I
Type=oneshot
RemainAfterExit=yes
Environment=DISPLAY=:0
ExecStart=curl --header "Content-Type: application/json" --request POST --data '{"in_use": 'false'}' http://homeassistant.lan:8123/api/webhook/eaea48a1-30e3-47bf-a076-30f816f0d3d1
ExecStop=bash -c 'sleep 1 && echo false > /dev/shm/in_use'

[Install]
WantedBy=sleep.target

Earlier attempts

I first tried to use a systemd timer as well as a oneshot service but this was not reliable since after a reboot the X session is not always available at the moment the service is initiated. This simple bash script with a 200 millisecond sleep works well.

edits:

  1. Change from a systemd timer to a startup script.
  2. Abort if another instance is running.
  3. Report the computer as being idle when the script is terminated (e.g. when powering off / rebooting).
  4. Add example systemd service for reporting usage status on suspend/resume.
  5. Change to a binary sensor.
  6. Update to new template trigger configuration format. Introduce a 6 minute timeout.
8 Likes

Care to share the solution for others with the same question?

I just followed these steps provided by @ZzetT.

and

1 Like

I used this until 2 days ago when I suddenly started getting these responses:

SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)

Anyone have any clues as to why this suddenly started happening?

Given the timing that might be related to the Lets Encrypt thingy (don’t know the proper term) expiring DST Root CA X3 Expiration (September 2021) - Let's Encrypt

1 Like

Yeah that seems to be it…

The xprintidle solution from martokk is awesome, simple and very versatile… except for a tiny issue: getting SSH to run properly from the command_line integration seems to be a nightmare (sample complaint post from 2020).

  • You obviously can’t pass a password via argument to ssh
  • no clue if it’s ever possible to install sshpass in HA Core
  • SSH keys seem to be wiped with Core updates (reply at the same post)
    • I mean, if you ever figure out how to add a working SSH key; the SSH add-on don’t run at the same container which the integration will execute (reply at the same post, integration docs “hint”), so testing from the add-on and seeing it working fine makes you feel like a :clown_face:

Other solutions I’m leaning towards:

  • edit [THIS ONE WORKED!!!] installing some sort of HTTP server on my computer and make it spit out xprintidle whenever a given page is requested… so somehow HA can pick that up through, idk, curl? Nonetheless, that’s a lot of work which could be simply done via SSH lol
    1. I indeed installed Apache in my computer
    2. I added a cronjob to store xprintidle output in Apache’s folder: crontab -e; then add */1 * * * * DISPLAY=:0 xprintidle 2>&1 > /var/www/html/xprintidle.txt
      • 2-year later note: after reinstalling my OS, I lost the cronjob and tried to do it by hand, before finding this very own post. Turns out this can’t be done directly with PHP (e.g. <? passthru('xprintidle');) because that process won’t be executed by your actual user lol
    3. I added a command_line integration to HA which runs curl my-desktop-ip/xprintidle.txt :pray: Dirtiest solution possible, but… that’s what we have to put up with sometimes around Home Assistant “user-friendly” configurations? lol
  • some indirect trick with System Bridge
  • hope for something like HASS.Agent but for Linux lol