I did this for the laundry machines in two of our buildings (coin counts), so here’s what worked for me even though I’m no programmer.
Send a running total per column, every few minutes, as plain numbers:
{"vend.col1": 1834, "vend.col2": 977, "vend.col3": 2210, ...}
Then on the chart card pick the aggregate “increase”. It’s made for counters: each bucket shows how much the counter went up, and if the value goes down because the board rebooted, it counts from zero instead of showing a huge negative. So a reboot costs you nothing as long as you don’t reboot twice inside one bucket and land above the old value.
Sending {“col3”: 1} per vend also works with “sum”, but if a message is lost the count is gone for good. With totals the next message catches up.
One thing that tripped me: day buckets are UTC days. We’re on Eastern time, so my “per day” bars were split at 7 or 8 pm. For local days use hourly buckets and add up the hours, or just live with it.
For “probably empty”: store the total at refill time (I write a server attribute per column on Monday from the device page) and alert when total minus that passes 38 or so. I do that part in a flow because an alarm condition can’t do arithmetic between two values.
Page is Aggregates and rollups.

