Appearance
Toilet visits by gender, from one door counter
The question: How many people used the Level 4 toilets today, and how many of them were women and how many men?
What it costs: one people counter on the shared corridor door and one motion (PIR) sensor inside one of the two toilets. Nothing is worn, nobody is filmed, and no sensor needs to know who anyone is.
| You need | Example | Where it goes |
|---|---|---|
| A line-crossing people counter | Minew MSR01-B | Over the corridor door that leads to both toilets, so everyone going to either side crosses it once |
| A PIR (motion) sensor | MOKO S01P | Inside the women's toilet, on the ceiling or high on the wall, pointing at the area just inside the door. Not over a cubicle |
| Optional: a second PIR | MOKO S01P, Aqara P1 | Inside the men's toilet. It turns the estimate into a cross-check (see Make it better) |
| A gateway that hears both | any Bluetooth gateway | Anywhere in range. The counter's place comes from the counter's own zone, not the gateway's |
Why it works
The counter knows how many went in, but not where. The PIR knows someone went into the women's side, but not how many. Put together:
- Everyone = the counter's entries
- Women = the PIR's visits
- Men = everyone − women
That subtraction is the whole trick. Any building where several rooms share one entrance can be split the same way: one counter at the shared door, one presence sensor in all but one of the rooms behind it.
1. Put the sensors in the right zones
- Devices → Devices, register both sensors (or let them auto-register).
- Give the counter the zone it guards, for example Toilet Hallway, and the PIR the zone it is in, L4 Female Toilet. A counter with no location writes nothing to the Entry / Exit report; the Devices page shows why.
- Name them for what they do: Count Sensor – Hallway to Toilet, PIR – L4 Female Toilet. The names appear in the rule and in the report.
- Walk through the door once and wave under the PIR. On the device's View panel the latest reading should show Entries (last report) going to 1 and Occupied going to 1.
2. Make a data table
System → Data tables → New table: see Data tables.
| Field | Value |
|---|---|
| Name | Toilet entries |
| Description | One row per corridor crossing (counter) and one per women's toilet visit (PIR). Men = entries − women. |
| Keep rows for | 365 days, if you want a year-on-year comparison |
| Columns | event (text) · gender (text) · entry_total (number) |
3. Two rules fill it
Rules → Add Rule, twice. Both use the check Sensor reading (any field) (how that works).
Rule A — corridor entry. Watches the counter.
| Setting | Value |
|---|---|
| Condition | field entry Changes (any change) |
| Repeat at most every | 30 sec (the shortest) |
| Action | Add a row to a data table → Toilet entries: event = hallway, entry_total = {{entry}} |
Rule B — women's toilet visit. Watches the PIR.
| Setting | Value |
|---|---|
| Condition | field occupied Becomes (changes to) 1 |
| Repeat at most every | 3 min (see the PIR fires more than once) |
| Action | Add a row to a data table → Toilet entries: event = toilet, gender = female |
The counter reports entries since its last report (1 when one person walks in, 0 on the reports in between), so storing the reading and adding the rows up gives the day's total.
Why Changes and not Greater than 0. Changes is an edge: it is checked the moment the reading changes, on every packet. Greater than 0 is a level, and a level rule on one sensor is checked at most once a minute, so a second entry inside that minute is never seen. Changes also fires when the reading drops back to 0, which writes a row with entry_total 0. Those rows add nothing to the sum; leave them. To be sure the figure is right, compare a day with Reports → Entry / Exit for the same counter: the two totals should match.
4. The report
Reports → Custom Reports → New Report, Source Table Toilet entries (the builder).
| Step | Set |
|---|---|
| Metric 1 | sum Entry total, call it Everyone |
| Metric 2 | count Records, only where Gender equals female, call it Women |
| Calculated column | Men = metric 1 minus metric 2 |
| Calculated column | Women % = metric 2 as % of metric 1 |
| Group by | Date (a daily table) or Hour (the busy hours) |
| Chart | Bar |
Save it. It stays in the Custom Reports list: press ▶, pick the dates, and Export CSV for a spreadsheet. (Custom reports cannot be emailed on a schedule yet; System › Schedule Report covers the built-in reports only.)
Reading it honestly
These are visits, not people. Someone who goes in twice is two. A cleaner going in and out counts too: if cleaners wear badges, the Toilet Hygiene app already knows when they are inside, and their cleans are listed there.
The counter counts crossings in the "in" direction. Mount it the way round the vendor marks as in, and check on a quiet day that entries and exits come close (Reports → Entry / Exit). A big, steady gap means the counter is mounted backwards, is too high, or the door is often propped open.
The PIR fires more than once per visit
A PIR says "occupied" when it sees movement and "empty" after its hold-off time. Someone who sits still in a cubicle for longer than the hold-off makes the PIR go empty and then occupied again when they move, and each of those is a new Becomes 1. In our own office the women's PIR fired three or four times within five minutes for one visit, which made "women" almost as large as "everyone" and "men" close to zero.
Repeat at most every is the fix: one row per 3 minutes at most. The price is that two women arriving within 3 minutes of each other are one row. Pick the interval for your toilet:
| Toilet | Repeat at most every |
|---|---|
| Small, one or two cubicles | 3–5 min |
| Large, busy at lunch | 1–2 min, and accept some double counting |
Check it the first week. If Men ever comes out negative for an hour, the PIR is overcounting: lengthen the interval.
Men is whatever is left over
Every error lands on Men: a missed PIR trigger adds a man, a double trigger takes one away, and a cleaner or a visit to a store room off the same corridor is counted as a man. That is why the next step is worth it.
Make it better
A PIR in the men's toilet too. Add Rule C, the same as Rule B with gender = male, watching the men's PIR. Now the report has three figures:
- Women = rows where gender is
female - Men = rows where gender is
male - Not accounted for = everyone − women − men (calculated column twice: Everyone minus Women, then that minus Men, as a second report or in the CSV)
Not accounted for near zero means both PIRs are counting well. A large positive number means a PIR is missing visits; a negative one means one is double counting. Without that check, you would never know.
One rule, any number of toilets. Write the rule on a label (for example Toilet PIR) instead of one device, store gender as text you fix per rule, and add toilet = {{zone_uid}} as a zone column. A new toilet then needs only its sensor labelled.
Feed Toilet Hygiene. Give each toilet its gender on its settings in the Toilet Hygiene app, and the counter's visits drive the dirty score and the Busy-hour auto clean there. This report is the extra layer: who used it, not only how many.
Or ask OAIA. In the assistant, type: "Count women and men for the Level 4 toilets. The hallway counter is 'Count Sensor – Hallway to Toilet' and there is a PIR in the female toilet." It proposes this table, both rules and the report, and builds them when you press Apply. See OAIA.
Questions and problems
No hallway rows. Check the counter has a location and zone on the Devices page, and that Rules shows Rule A's Fired count going up. A rule watching a field the sensor does not send never fires: the field is entry, not entry_delta.
Rows, but Entry total is empty. The action wrote {{value}} or a name into the number column on a rule whose first condition is not entry. Use {{entry}}.
The numbers on this report and on Entry / Exit differ by a few. The data table started when the rule was saved; Entry / Exit has counted since the counter was registered. Also, both reports cut days on your organisation's clock, but a rule switched off for a while leaves a gap only in the data table.