diff --git a/design/api.md b/design/api.md index f48a2e9..8c5cf9e 100644 --- a/design/api.md +++ b/design/api.md @@ -24,10 +24,10 @@ input such as a burglar alarm system's zone status. All requests for JSON data should be sent with the header `Accept: application/json` (exactly). -### `/api/login` +### `POST /api/login` -A `POST` request on this URL should have an `application/x-www-form-urlencoded` -body containing `username` and `password` parameters. +The request should have an `application/x-www-form-urlencoded` body containing +`username` and `password` parameters. On successful authentication, the server will return an HTTP 204 (no content) with a `Set-Cookie` header for the `s` cookie, which is an opaque, HttpOnly @@ -37,19 +37,19 @@ If authentication or authorization fails, the server will return a HTTP 403 (forbidden) response. Currently the body will be a `text/plain` error message; future versions will likely be more sophisticated. -### `/api/logout` +### `POST /api/logout` -A `POST` request on this URL should have an `application/x-www-form-urlencoded` -body containing a `csrf` parameter copied from the `session.csrf` of the +The request should have an `application/x-www-form-urlencoded` body containing +a `csrf` parameter copied from the `session.csrf` of the top-level API request. On success, returns an HTTP 204 (no content) responses. On failure, returns a 4xx response with `text/plain` error message. -### `/api/` +### `GET /api/` -A `GET` request on this URL returns basic information about the server, -including all cameras. Valid request parameters: +Returns basic information about the server, including all cameras. Valid +request parameters: * `days`: a boolean indicating if the days parameter described below should be included. @@ -101,6 +101,7 @@ The `application/json` response will have a dict as follows: `direct` or `indirect`. See `db/schema.sql` for more description. * `type`: a UUID, expected to match one of `signalTypes`. * `days`: as in `cameras.streams.days` above. + **status: unimplemented** * `signalTypes`: a list of all known signal types. * `uuid`: in text format. * `states`: a map of all possible states of the enumeration to more @@ -190,9 +191,9 @@ Example response: } ``` -### `/api/cameras//` +### `GET /api/cameras//` -A GET returns information for the camera with the given URL. The information +Returns information for the camera with the given URL. Example response: @@ -224,9 +225,9 @@ Example response: } ``` -### `/api/cameras///recordings` +### `GET /api/cameras///recordings` -A GET returns information about recordings, in descending order. +Returns information about recordings, in descending order. Valid request parameters: @@ -308,11 +309,11 @@ Example response: } ``` -### `/api/cameras///view.mp4` +### `GET /api/cameras///view.mp4` -A GET returns a `.mp4` file, with an etag and support for range requests. The -MIME type will be `video/mp4`, with a `codecs` parameter as specified in [RFC -6381][rfc-6381]. +Returns a `.mp4` file, with an etag and support for range requests. The MIME +type will be `video/mp4`, with a `codecs` parameter as specified in +[RFC 6381][rfc-6381]. Expected query parameters: @@ -356,14 +357,14 @@ Example request URI to retrieve recording id 1, skipping its first 26 TODO: error behavior on missing segment. It should be a 404, likely with an `application/json` body describing what portion if any (still) exists. -### `/api/cameras///view.mp4.txt` +### `GET /api/cameras///view.mp4.txt` -A GET returns a `text/plain` debugging string for the `.mp4` generated by the +Returns a `text/plain` debugging string for the `.mp4` generated by the same URL minus the `.txt` suffix. -### `/api/cameras///view.m4s` +### `GET /api/cameras///view.m4s` -A GET returns a `.mp4` suitable for use as a [HTML5 Media Source Extensions +Returns a `.mp4` suitable for use as a [HTML5 Media Source Extensions media segment][media-segment]. The MIME type will be `video/mp4`, with a `codecs` parameter as specified in [RFC 6381][rfc-6381]. @@ -387,16 +388,16 @@ recording segment for several reasons: than one video sample entry, so a `.m4s` that uses more than one video sample entry can't be used. -### `/api/cameras///view.m4s.txt` +### `GET /api/cameras///view.m4s.txt` -A GET returns a `text/plain` debugging string for the `.mp4` generated by the -same URL minus the `.txt` suffix. +Returns a `text/plain` debugging string for the `.mp4` generated by the same +URL minus the `.txt` suffix. -### `/api/cameras///live.m4s` +### `GET /api/cameras///live.m4s` -A GET returns a `multipart/mixed` sequence of parts. An extra top-level -header, `X-Open-Id`, contains the `openId` which is assigned to all recordings -in this live stream. +Returns a `multipart/mixed` sequence of parts. An extra top-level header, +`X-Open-Id`, contains the `openId` which is assigned to all recordings in this +live stream. Each part is a `.mp4` media segment that starts with a key frame and contains all other frames which depend on that key frame. The following part headers @@ -463,21 +464,21 @@ following URLs, respectively: * `/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/view.m4s?s=5681@42.0-180002` * `/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/view.m4s?s=5681@42.180002-360004` -### `/api/init/.mp4` +### `GET /api/init/.mp4` -A GET returns a `.mp4` suitable for use as a [HTML5 Media Source Extensions +Returns a `.mp4` suitable for use as a [HTML5 Media Source Extensions initialization segment][init-segment]. The MIME type will be `video/mp4`, with a `codecs` parameter as specified in [RFC 6381][rfc-6381]. -### `/api/init/.mp4.txt` +### `GET /api/init/.mp4.txt` -A GET returns a `text/plain` debugging string for the `.mp4` generated by the +Returns a `text/plain` debugging string for the `.mp4` generated by the same URL minus the `.txt` suffix. -### `/api/signals` +### `GET /api/signals` -A GET returns an `application/json` response with state of every signal for -the requested timespan. +Returns an `application/json` response with state of every signal for the +requested timespan. Valid request parameters: @@ -522,6 +523,130 @@ This represents the following observations: 2. signal 1 entered state 2 (`on`) at time 130985424000000. 3. signal 1 entered state 1 (`off`) at time 130985418600000. +### `POST /api/signals` + +**status: unimplemented** + +Alters the state of a signal. + +Signal state can be broken into two parts: observed history and predicted +future. Observed history is written to the database on next flush. +Predicted future is only retained in RAM and returned on `GET` requests on the +near future. This avoids having to display the "unknown" state when it's very +likely that the state has not changed since the most recent update. + +A typical request will add observed history from the time of a recent previous +prediction until now, and add another prediction. Following `GET /api/signals` +requests will return the new prediction until a pre-determined expiration +time. The client typically will send a following request before this +expiration time so that history is recorded and the UI never displays +`unknown` when the signal is being actively managed. + +Some requests may instead backfill earlier history, such as when a video +analytics client starts up and analyzes all video segments recorded since it +last ran. These will specify beginning and end times for the observed history +and not make a prediction. + +The request should have an `application/json` body describing the change to +make. It should be a dict with these attributes: + +* `signalIds`: a list of signal ids to change. +* `observedStates`: (optional) a list (one entry per `signalIds` entry) of + observed states to set. +* `predictedStates`: (optional) a list (one entry per `signalIds` entry) + of predictions to make. If absent, assumed to match `observedStates`. + Only used if `predictedDur90k` is non-zero. +* `observedStartTime90k` (optional): if absent, assumed to be now. Otherwise + is typically a time from an earlier response. +* `observedEndTime90k` (optional): if absent, assumed to be now. +* `predictionDur90k` (optional): (only allowed when `endTime90k` is absent) + additional time in which the current state should seem to "linger" if no + further updates are received. a `GET /api/signals` request until this time + will reflect the curent + between the time this request + +The response will be an `application/json` body dict with the following +attributes: + +* `time90k`: the current time. When the request's `observedStartTime90k` + and/or `observedEndTime90k` were absent, this is needed to later upgrade + predictions to history seamlessly. + +Example request sequence: + +#### Request 1 + +The client responsible for reporting live driveway motion has just started. It +observes motion now. It records no history and predicts there will be motion +for the next minute. + +Request: + +```json +{ + 'signalIds': [1], + 'predictedStates': [2], + 'predictionDur90k': 5400000 +} +``` + +Response: + +```json +{ + 'time90k': 140067468000000 +} +``` + +#### Request 2 + +30 seconds later (half the prediction interval), the client still observes +motion. It records the previous prediction and predicts the motion will continue. + +Request: + +```json +{ + 'signalIds': [1], + 'observedStates': [2], + 'observedStartTime90k': 140067468000000, + 'predictionDur90k': 5400000 +} +``` + +Response: + +```json +{ + 'time90k': 140067470700000 +} +``` + +### Request 3 + +5 seconds later, the client observes motion has ended. It records the history +and predicts no more motion. + +Request: + +```json +{ + 'signalIds': [1], + 'observedStates': [2], + 'predictedStates': [1], + 'observedStartTime90k': 140067470700000, + 'predictionDur90k': 5400000 +} +``` + +Response: + +```json +{ + 'time90k': 140067471150000 +} +``` + [media-segment]: https://w3c.github.io/media-source/isobmff-byte-stream-format.html#iso-media-segments [init-segment]: https://w3c.github.io/media-source/isobmff-byte-stream-format.html#iso-init-segments [rfc-6381]: https://tools.ietf.org/html/rfc6381