Reviving my Standing stats project

I have used an Ergotron standing desk for 5 years, then it broke, right out of warranty. Several years ago I started building a sensor device to track how much time I spend standing up versus sitting down in my chair. The hardware prototype finished, it got shelved since I found other fun things to do with my limited hobby-time.
Later I noticed that I was sitting down 99% of the time when working from home. To revive my healthy habit of working upright part of the time, I decided to pick up my parked project by re-printing the enclosure I made on my own printer and leveraging the power of esphome on and home assistant to finish the project after all.

When I first started this project, I experimented a bit with sensors and found two easily available sensors are suitable for the two distance measuring tasks; an ultrasonic distance sensor works fine for desk height, while an infrared triangulation distance sensor works better for the softer surfaces like clothes that don't reflect ultrasonic sound that well.
When I got into 3D printing, the first design I made (which also was the most complex for years to come) was an enclosure that can be mounted on the standing desk.
Esphome brings all necessary tools to the table to easily read the sensors values with networked software; it uses platformio as a build environment, has drivers for a lot of sensors, integrates with home assistant easily and runs on ESP8266/ESP32 wifi chipsets. It was very easy to gather sensor statistics with such a setup.
The mechanics
I designed an enclosure that holds all electronics and mounts nicely and easily at an ideal spot of the desk's body.

I designed it with ease of printing and assembly in mind.



The electronics and sensor software
I wired up a wemos D1 mini board with the sensors and neopixel LEDs.
substitutions:
hostname: standingstats
esphome:
name: ${hostname}
platform: ESP8266
board: nodemcuv2
on_boot:
priority: -10
then:
- light.turn_on:
id: ${hostname}_kleuren
brightness: 100%
wifi:
ssid: !secret ssid
password: !secret wlan
logger:
api:
password: !secret api
ota:
password: !secret ota
sensor:
- platform: ultrasonic
trigger_pin: D2
echo_pin: D1
name: "Desk Level"
update_interval: 5s
- platform: adc
pin: A0
name: "Presence"
update_interval: 5s
light:
- id: ${hostname}_kleuren
name: ${hostname}_kleuren
type: GRB
platform: neopixelbus
variant: WS2812
method: BIT_BANG
pin: D4
num_leds: 5
Home assistant integration and business logic
With the current setup, no 'edge computing' is done, and all business logic is implemented using home assistant automations and sensors definitions.

The following sensor configuration provides a 'virtual' (template) sensor that determines the desk state as 'standing', 'sitting' and 'away' based on the sensor readings.
- platform: template
sensors:
desk:
friendly_name: "desk"
value_template: >-
{% if float(states("sensor.presence")) < 0.1 -%}
away
{%- else -%}
{% if float(states("sensor.desk_level")) > 0.2 -%}
standing
{%- else -%}
sitting
{%- endif %}
{%- endif %}
The history_stats sensor platform provides quick statistics about another integration or platforms, using data from the history integration. I create two history_stats sensors that will sum up the time I spend sitting and standing for the current day.
- platform: history_stats
name: Standing today
entity_id: sensor.desk
state: 'standing'
type: time
start: '{{ now().replace(hour=0, minute=0, second=0) }}'
end: '{{ now() }}'
- platform: history_stats
name: Sitting today
entity_id: sensor.desk
state: 'sitting'
type: time
start: '{{ now().replace(hour=0, minute=0, second=0) }}'
end: '{{ now() }}'
I use an automation to have the color of neopixel LEDs reflect the desk state:
- alias: deskstatus
trigger:
- platform: state
entity_id: sensor.desk
action:
- choose:
- conditions:
- condition: state
entity_id: sensor.desk
state: "sitting"
sequence:
- service: light.turn_on
data:
entity_id: light.standingstats_kleuren
brightness: 255
color_name: red
- conditions:
- condition: state
entity_id: sensor.desk
state: "standing"
sequence:
- service: light.turn_on
data:
entity_id: light.standingstats_kleuren
brightness: 255
color_name: green
- conditions:
- condition: state
entity_id: sensor.desk
state: "away"
sequence:
- service: light.turn_on
data:
entity_id: light.standingstats_kleuren
brightness: 255
color_name: blue
It's easy to calculate the ratio of standing versus sitting:
- platform: template
name: standratio
friendly_name: "Percentage standing"
value_template: >-
{{ (100*float(states("sensor.Standing_today"))/ (float(states("sensor.Standing_today")) + float(states("sensor.Sitting_today"))))|int }}
unit_of_measurement: "%"
All I have to do now is create an automation that will notify me if I have been sitting down too long...
Status display
I have a hasp-lvgl switchplate on my desk, and I made a home assistant automation to show the sitting and standing time, along with an arc widget that shows the percentage I have been standing vs total time.

Screenshot of hasp-lvgl
The hasp lvgl jsonlines can probably be made simpler:
{"obj":"arc","id":29,"x":5,"y":200,"w":140,"h":100,"max":32767,"border_side":0,"type":0,"rotation":0,"start_angle":180,"end_angle":0,"start_angle1":180,"value_font":12,"value_ofs_x":-19,"value_ofs_y":-4,"bg_opa":0,"min":0}
{"obj":"label","id":31,"x":100,"y":210,"w":140,"h":30,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"txt":"-","text_font":22}
{"obj":"label","id":32,"x":100,"y":240,"w":140,"h":30,"bg_color":"#000000","border_color":"#C7BAA7","border_width":0,"txt":"-","text_font":22}
The home assistant automation publishes updates to the widget's identifiers:
- alias: plate_display_standingtoday
trigger:
- platform: state
entity_id: sensor.standing_today
- platform: state
entity_id: sensor.sitting_today
- platform: state
entity_id: sensor.standratio
- platform: mqtt
topic: "hasp/plate_c45378/LWT"
payload: "online"
action:
- service: mqtt.publish
data_template:
topic: "hasp/plate_c45378/command/p1b29.val"
payload: "{{ (states('sensor.standratio')|int )*327}}"
- service: mqtt.publish
data_template:
topic: "hasp/plate_c45378/command/p1b31.txt"
payload: "{{states('sensor.standing_today')|int}}h{{((float(states('sensor.standing_today'))%1)*60)|int}}m Standing"
- service: mqtt.publish
data_template:
topic: "hasp/plate_c45378/command/p1b32.txt"
payload: "{{states('sensor.sitting_today')|int}}h{{((float(states('sensor.sitting_today'))%1)*60)|int}}m Sitting"
Liked something? Worked on something similar? Let me know what you think on Mastodon!
You can use your Mastodon account to reply to this post.