Skip to main content

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

MethodReturnsRequires
getRawTSData(options?)Individual samples, newest firstoptions.startTs
getLatestTSData(options?)At most one samplenothing
getTSData(options?)Window statisticsoptions.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:

OptionsReturns
window onlyThe current, still-open window
window + dateOne completed window
window + startDate + endDateA 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.

These three are declared optional

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.