Time series
Time series is the recorded history of a parameter; the samples a device reported over time, and statistics computed over them. Three methods sit on ESPRMNeoDeviceParam for the common case, and three mirror them on ESPRMNeoNode for parameters you address by name.
Only parameters whose firmware declares time-series support have data. The SDK warns rather than throws when a parameter does not declare it, because the backend may still hold samples.
The three queries
| Method | Returns | Requires |
|---|---|---|
getRawTSData(options?) | Individual samples, newest first | options.startTs |
getLatestTSData(options?) | At most one sample | nothing |
getTSData(options?) | Window statistics | options.window |
All three return Promise<ESPRMNeoTSDataResult>.
Raw samples
const brightness = light.params.find((p) => p.id === "Brightness");
const raw = await brightness.getRawTSData({
startTs: Date.now() - 24 * 60 * 60 * 1000,
});
for (const point of raw.data) {
console.log(point.timestamp, point.value);
}
startTs is a Unix timestamp in milliseconds and is required; omitting it throws ESPAPICallValidationError. endTs defaults to now.
Each ESPRMNeoTSDataPoint carries timestamp, value, and optionally dataType, timezone and cumulative. Points come newest first.
The latest sample
const latest = await brightness.getLatestTSData();
const current = latest.data[0];
Takes no date options; only a dataType override is read. data is empty when the parameter has no samples yet, so index defensively.
Aggregates
const daily = await brightness.getTSData({ window: "daily" });
window is required and is one of hourly, daily, weekly or monthly. Three query modes:
| Options | Returns |
|---|---|
window only | The current, still-open window |
window + date | One completed window |
window + startDate + endDate | A range of completed windows |
date is YYYY-MM-DD, or YYYY-MM-DDTHH for an hourly window. Results land in aggregates rather than data:
const range = await brightness.getTSData({
window: "daily",
startDate: "2026-07-01",
endDate: "2026-07-31",
});
for (const entry of range.aggregates ?? []) {
console.log(entry.date, entry.windows.daily?.average);
}
Each entry's windows is keyed by window type, and each window carries count, sum, min, max, average, firstValue, lastValue, windowStart and windowEnd.
For a cumulative parameter such as an energy meter, read cumulativeValue, consumption within the window; rather than sum, which adds meter readings together and is meaningless. The isCumulative flag on the entry tells you which case you are in.
Pagination
let page = await brightness.getRawTSData({ startTs: from });
while (page.hasNext) {
page = await page.fetchNext();
}
hasNext is true when more pages exist, and fetchNext() is set only then; it repeats the same query with the next token. You can also paginate manually with pageSize and startKey, passing the previous result's nextKey. The backend default page size is 20.
Node-level equivalents
When you have a node but not a parameter instance, three methods take the device and parameter names:
const raw = await node.getCustomParamRawTSData?.("Light", "Brightness", {
startTs: from,
});
getCustomParamRawTSData, getCustomParamLatestTSData and getCustomParamTSData mirror the parameter-level methods with deviceName and paramName prepended. They resolve the parameter from the node's configuration and throw ESPAPICallValidationError when it is not found there.
They are typed with a ?, unlike every other node method, so TypeScript requires optional-call syntax — node.getCustomParamRawTSData?.(…). They are always present at runtime once the SDK is imported. Prefer the parameter-level methods where you have the instance.
Querying by explicit key
options.key bypasses configuration lookup entirely and sends a backend parameter key you supply. It exists for data models the lookup cannot resolve, and must be paired with dataType, passing key alone throws ESPAPICallValidationError.
A dataType mismatch returns no data rather than an error, which is why the SDK reads it from parameter metadata unless you override it. If a query comes back unexpectedly empty, check the data type before anything else.
Composing your own queries
Three fetchers are exported for flows the methods above do not cover, such as custom pagination across parameters:
import { fetchRawTSData, fetchLatestTSData, fetchAggregatedTSData } from "@espressif/rainmaker-neo-base-sdk";
const result = await fetchRawTSData({
groupId: node.groupId,
nodeId: node.nodeId,
key: "Light.Brightness",
dataType: "int",
options: { startTs: from },
});
Each takes a FetchTSDataConfig and returns the same ESPRMNeoTSDataResult. They are the primitives the methods above are built on, so validation and shaping behave identically.
Related
- Device control — the parameters being recorded
- Nodes — reaching a parameter instance
- Time series — the platform feature
- Types — the result and options interfaces
- Helpers — the exported fetchers