# Monitor devices

> Know when a product or Gateway needs attention: offline, low battery, jammed, or
> failing commands. This guide shows what Parklio tells you, what it doesn't, and how to
> combine realtime events with REST checks.

## Prerequisites

- An API token on a server; see [Authentication](/api/authentication).
- A realtime connection; see [Connecting](/realtime/connecting).

## What you can watch

| Signal | Where | Pushed in realtime? |
| --- | --- | --- |
| Product state, `battery_status`, `rssi`, `product_error` | `lot_change` (no `event_id`), and the product in REST | Yes, while a Gateway sees the product |
| Product online or offline | `status` in `lot_change`, and on [`GET /v2/products`](/api/endpoints/products-find-all) | Yes |
| Products with an error | [`GET /v2/products/errors`](/api/endpoints/products-find-all-product-errors) | (via `lot_change`) |
| Gateway online or offline | `gateway_status`, and `status` on [`GET /v2/gateways`](/api/endpoints/gateways-find-all) | Yes, for Gateways assigned to a lot |
| Failed commands | `gateway_error` | Yes |

## 1. Watch status updates

While a Gateway sees a product, it reports it regularly, and each report arrives as a
`lot_change` **without** `event_id`, twice, whether or not anything changed. When no
Gateway sees the product any more, or its Gateway disconnects, a `lot_change` with
`status` `offline` follows. Keep the last value per product and alert on changes; see
[Deduplicate status updates](/realtime/patterns#deduplicate-status-updates).

```ts title="watch-products.ts"
// alert() and resolve() stand for your alerting.
const last = new Map<number, { online: boolean; errorCode: string | null; low: boolean }>();

socket.on('lot_change', (raw: string) => {
  const { event_id, data } = JSON.parse(raw);
  if (event_id) return; // command results are handled elsewhere

  const online = data.status === 'online';
  const errorCode: string | null = data.product_error?.error_code ?? null;
  const low = isLowBattery(data.battery_status);
  const prev = last.get(data.id);
  last.set(data.id, { online, errorCode, low });

  if (!online && prev?.online) alert(data, 'offline');
  if (online && prev?.online === false) resolve(data, 'back online');
  if (errorCode && errorCode !== prev?.errorCode) alert(data, `error flags ${errorCode}`, data.product_error);
  if (!errorCode && prev?.errorCode) resolve(data, 'error cleared');
  if (low && !prev?.low) alert(data, `battery at ${data.battery_status}%`);
});

function isLowBattery(value: unknown) {
  // Only barriers report a battery level. 0–100 is percent; above 100 means grid power.
  return typeof value === 'number' && value <= 20;
}
```

### Online and offline

`status` in `lot_change` is `online` or `offline`. `battery_status` and `rssi` are
`null` while a product is offline.

Don't alert on a single offline event. Products can show `offline` briefly, for example
while their Gateway reconnects or during Parklio maintenance, until a Gateway sees them
again. Alert when a product hasn't come back online within a few minutes.

### Error flags

`product_error` is present while the device reports at least one problem, and gone when
none is left. Each flag is `0` or `1`:

| Flag | Products | Suggested action |
| --- | --- | --- |
| `jammed` | Barrier | Send someone on site. |
| `sensor_dirty` | Barrier | Clean the car sensor. |
| `battery_empty` | Barrier | Replace or charge the battery. |
| `pin_broken` | Barrier | See the product's manual. |
| `rtcc_invalid` | All | The device's clock isn't set; time-limited credentials fail until it is. Parklio sets it when a Gateway sees the device. |
| `master_key_not_set` | All | See the product's manual. |

For a list of every product that currently has an error, call
[`GET /v2/products/errors`](/api/endpoints/products-find-all-product-errors). It accepts
the same filters as the products list, such as `lot_id`.

### Battery

Only **barriers** report `battery_status`. It's a percentage from 0 to 100; Parklio
sets `battery_empty` below 15%. Values **above 100** mean the barrier runs on grid power,
not a battery level. Alert somewhere around 20% to leave time to act.

## 2. Watch Gateways

`gateway_status` tells you when a Gateway assigned to a lot connects or disconnects. It
identifies the Gateway by `uuid`; match it with the `uuid` field of
[`GET /v2/gateways`](/api/endpoints/gateways-find-all).

```ts title="watch-gateways.ts"
socket.on('gateway_status', (raw: string) => {
  const { uuid, status } = JSON.parse(raw);
  if (status === 'offline') alert({ uuid }, 'Gateway offline');
});
```

When a Gateway goes offline, the products only it could see become `offline` too, and
commands to them fail with `704`. To check all Gateways at once, for example after a
reconnect:

```bash title="Gateways that are offline"
curl -s "https://api.parklio.com/v2/gateways?filter=status:eq:offline" \
  -H "Authorization: Bearer $PARKLIO_API_TOKEN"
```

## 3. Watch failed commands

A run of `gateway_error` events for the same product, especially `5` (jammed), `7`
(sensor dirty) or repeated `"Timeout"`, is often the first sign of trouble on site.
Count them per product and alert above a threshold. See
[Remote control](/guides/remote-control#3-handle-failures).

## Alerting ideas

| Alert | Rule of thumb |
| --- | --- |
| Product offline | `lot_change` with `status` `offline`, not back within a few minutes. |
| Gateway offline | `gateway_status` offline, not back within a few minutes. |
| Low battery | Barrier `battery_status` ≤ 20. |
| Needs service | `jammed` or `sensor_dirty` set. |
| Commands failing | Three `gateway_error`s for one product within an hour. |

## Verify it worked

1. Your monitor logs a `lot_change` without `event_id` for each product within a few
   minutes of starting.
2. Unplug a Gateway (or switch off its network): you get `gateway_status` with
   `offline`, and a `lot_change` with `status` `offline` for each of its products.
3. Plug it back in: `gateway_status` `online`, and the products' `lot_change` events
   resume.

## Common errors

| Symptom | Cause | Fix |
| --- | --- | --- |
| No `lot_change` for a product at all | No Gateway sees it, or it's on a lot your token can't see. | Check its `status` and `lot_id`; see [Troubleshooting](/resources/troubleshooting). |
| Duplicate alerts | Status updates repeat, and each is sent twice. | Alert on changes only. |
| `battery_status` is `null` | The product is offline, or isn't a barrier. | Expected. |
| `battery_status` above 100 | The barrier is on grid power. | Not a battery level; ignore. |
| `gateway_status` for an unknown `uuid` | The Gateway isn't in your cached list. | Re-read `GET /v2/gateways`. |
| Missed changes after a disconnect | No replay. | Re-read [`GET /v2/products`](/api/endpoints/products-find-all) after every reconnect. |

## Next

- [Remote control](/guides/remote-control)
- [Events](/realtime/events)
