fireboard-api-client 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +276 -2
- package/dist/client.d.ts +198 -0
- package/dist/client.js +68 -0
- package/dist/device-cache.d.ts +11 -0
- package/dist/device-cache.js +40 -0
- package/dist/drive.d.ts +10 -0
- package/dist/drive.js +53 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +10 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/login.d.ts +6 -0
- package/dist/login.js +19 -0
- package/dist/request.d.ts +6 -0
- package/dist/request.js +56 -0
- package/dist/schemas.d.ts +212 -0
- package/dist/schemas.js +276 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.js +1 -0
- package/dist/validation.d.ts +4 -0
- package/dist/validation.js +29 -0
- package/package.json +55 -4
package/dist/drive.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { headers, request } from "./request.js";
|
|
2
|
+
import { encodeDeviceId } from "./validation.js";
|
|
3
|
+
export class DriveControls {
|
|
4
|
+
baseUrl;
|
|
5
|
+
#options;
|
|
6
|
+
constructor(baseUrl, options) {
|
|
7
|
+
this.baseUrl = baseUrl;
|
|
8
|
+
this.#options = options;
|
|
9
|
+
}
|
|
10
|
+
async send(uuid, payload) {
|
|
11
|
+
await request(`${this.baseUrl}/devices/${encodeDeviceId(uuid)}/mq.json`, {
|
|
12
|
+
method: "POST",
|
|
13
|
+
headers: {
|
|
14
|
+
...headers(this.#options, this.#options.token),
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
},
|
|
17
|
+
body: JSON.stringify({
|
|
18
|
+
topic: "device",
|
|
19
|
+
payload: { ...payload, request_type: "control" },
|
|
20
|
+
}),
|
|
21
|
+
}, this.#options);
|
|
22
|
+
}
|
|
23
|
+
async setSetpoint(uuid, options) {
|
|
24
|
+
encodeDeviceId(uuid);
|
|
25
|
+
if (!options || (options.setpoint === undefined && options.channel === undefined)) {
|
|
26
|
+
throw new TypeError("At least one of setpoint or channel is required");
|
|
27
|
+
}
|
|
28
|
+
if (options.setpoint !== undefined && !Number.isFinite(options.setpoint)) {
|
|
29
|
+
throw new TypeError("Setpoint must be a finite number");
|
|
30
|
+
}
|
|
31
|
+
if (options.channel !== undefined &&
|
|
32
|
+
(!Number.isSafeInteger(options.channel) || options.channel < 1)) {
|
|
33
|
+
throw new TypeError("Channel must be a positive integer");
|
|
34
|
+
}
|
|
35
|
+
const writes = [];
|
|
36
|
+
if (options.channel !== undefined) {
|
|
37
|
+
writes.push(this.send(uuid, { cc: String(options.channel) }));
|
|
38
|
+
}
|
|
39
|
+
if (options.setpoint !== undefined) {
|
|
40
|
+
writes.push(this.send(uuid, { t: "fan", setpoint: String(options.setpoint) }));
|
|
41
|
+
}
|
|
42
|
+
await Promise.all(writes);
|
|
43
|
+
}
|
|
44
|
+
async setSpeed(uuid, percent) {
|
|
45
|
+
if (!Number.isFinite(percent) || percent < 0 || percent > 100) {
|
|
46
|
+
throw new TypeError("Fan speed must be a number between 0 and 100");
|
|
47
|
+
}
|
|
48
|
+
await this.send(uuid, { t: "fan", p: String(percent / 100) });
|
|
49
|
+
}
|
|
50
|
+
async off(uuid) {
|
|
51
|
+
await this.send(uuid, { t: "fan", setpoint: "0" });
|
|
52
|
+
}
|
|
53
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type FireboardErrorCode = "AUTHENTICATION" | "RATE_LIMIT" | "API_UNAVAILABLE" | "INVALID_RESPONSE" | "TIMEOUT" | "NETWORK";
|
|
2
|
+
export declare class FireboardError extends Error {
|
|
3
|
+
readonly code: FireboardErrorCode;
|
|
4
|
+
readonly status?: number | undefined;
|
|
5
|
+
constructor(message: string, code: FireboardErrorCode, status?: number | undefined);
|
|
6
|
+
}
|
package/dist/errors.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { FireboardClient, DEFAULT_BASE_URL } from "./client.js";
|
|
2
|
+
export { DEFAULT_DEVICE_CACHE_TTL_MS } from "./device-cache.js";
|
|
3
|
+
export { login, DEFAULT_LOGIN_URL } from "./login.js";
|
|
4
|
+
export { DEFAULT_TIMEOUT_MS } from "./request.js";
|
|
5
|
+
export { FireboardError } from "./errors.js";
|
|
6
|
+
export type { FireboardErrorCode } from "./errors.js";
|
|
7
|
+
export type { FireboardClientOptions, RequestOptions, LoginOptions, DriveSetpointOptions, DeviceListResult, } from "./types.js";
|
|
8
|
+
export * from "./schemas.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { FireboardClient, DEFAULT_BASE_URL } from "./client.js";
|
|
2
|
+
export { DEFAULT_DEVICE_CACHE_TTL_MS } from "./device-cache.js";
|
|
3
|
+
export { login, DEFAULT_LOGIN_URL } from "./login.js";
|
|
4
|
+
export { DEFAULT_TIMEOUT_MS } from "./request.js";
|
|
5
|
+
export { FireboardError } from "./errors.js";
|
|
6
|
+
export * from "./schemas.js";
|
package/dist/login.d.ts
ADDED
package/dist/login.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { decode, headers, request } from "./request.js";
|
|
3
|
+
import { validateEndpoint } from "./validation.js";
|
|
4
|
+
export const DEFAULT_LOGIN_URL = "https://fireboard.io/api/rest-auth/login/";
|
|
5
|
+
export async function login(credentials, options = {}) {
|
|
6
|
+
if (typeof credentials?.username !== "string" ||
|
|
7
|
+
!credentials.username.trim() ||
|
|
8
|
+
typeof credentials.password !== "string" ||
|
|
9
|
+
!credentials.password) {
|
|
10
|
+
throw new TypeError("Username and password are required");
|
|
11
|
+
}
|
|
12
|
+
const loginUrl = validateEndpoint(options.loginUrl ?? DEFAULT_LOGIN_URL);
|
|
13
|
+
const response = await request(loginUrl, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: { ...headers(options), "Content-Type": "application/json" },
|
|
16
|
+
body: JSON.stringify(credentials),
|
|
17
|
+
}, options, true);
|
|
18
|
+
return (await decode(response, z.object({ key: z.string().min(1) }))).key;
|
|
19
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { RequestOptions } from "./types.js";
|
|
3
|
+
export declare const DEFAULT_TIMEOUT_MS = 15000;
|
|
4
|
+
export declare function request(url: string, init: RequestInit, options: RequestOptions, isLogin?: boolean): Promise<Response>;
|
|
5
|
+
export declare function decode<S extends z.ZodType>(response: Response, schema: S): Promise<z.output<S>>;
|
|
6
|
+
export declare function headers(options: RequestOptions, token?: string): Record<string, string>;
|
package/dist/request.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { FireboardError } from "./errors.js";
|
|
3
|
+
import { validateTiming } from "./validation.js";
|
|
4
|
+
export const DEFAULT_TIMEOUT_MS = 15_000;
|
|
5
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
6
|
+
const defaultUserAgent = `fireboard-api-client/${version}`;
|
|
7
|
+
export async function request(url, init, options, isLogin = false) {
|
|
8
|
+
const timeoutMs = validateTiming(options.timeoutMs ?? DEFAULT_TIMEOUT_MS, "timeoutMs");
|
|
9
|
+
const controller = new AbortController();
|
|
10
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
11
|
+
try {
|
|
12
|
+
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
13
|
+
...init,
|
|
14
|
+
signal: controller.signal,
|
|
15
|
+
redirect: "error",
|
|
16
|
+
});
|
|
17
|
+
if (response.status === 401 || (isLogin && response.status === 400)) {
|
|
18
|
+
throw new FireboardError("Invalid Fireboard token. Check your API key.", "AUTHENTICATION", response.status);
|
|
19
|
+
}
|
|
20
|
+
if (response.status === 429) {
|
|
21
|
+
throw new FireboardError("Fireboard REST API rate limit reached (17 calls / 5 min). Further requests are blocked for ~5 minutes. See: https://docs.fireboard.io/app/app-api/#rate-limits", "RATE_LIMIT", 429);
|
|
22
|
+
}
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new FireboardError("Fireboard API unavailable. Try again shortly.", "API_UNAVAILABLE", response.status);
|
|
25
|
+
}
|
|
26
|
+
// Read the body under the timeout; method callers validate the decoded JSON.
|
|
27
|
+
const body = await response.text();
|
|
28
|
+
return new Response(body || null, { status: response.status, headers: response.headers });
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error instanceof FireboardError) {
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
if (controller.signal.aborted) {
|
|
35
|
+
throw new FireboardError("Fireboard API request timed out.", "TIMEOUT");
|
|
36
|
+
}
|
|
37
|
+
throw new FireboardError("Could not connect to the Fireboard API.", "NETWORK");
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function decode(response, schema) {
|
|
44
|
+
try {
|
|
45
|
+
return schema.parse(await response.json());
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new FireboardError("Unexpected Fireboard API response.", "INVALID_RESPONSE", response.status);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function headers(options, token) {
|
|
52
|
+
return {
|
|
53
|
+
...(token ? { Authorization: `Token ${token}` } : {}),
|
|
54
|
+
"User-Agent": options.userAgent ?? defaultUserAgent,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const driveLogSchema: z.ZodObject<{
|
|
3
|
+
driveper: z.ZodNumber;
|
|
4
|
+
setpoint: z.ZodNumber;
|
|
5
|
+
modetype: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
6
|
+
tiedchannel: z.ZodNumber;
|
|
7
|
+
degreetype: z.ZodNumber;
|
|
8
|
+
created: z.ZodString;
|
|
9
|
+
}, z.core.$loose>;
|
|
10
|
+
declare const alertSchema: z.ZodObject<{
|
|
11
|
+
id: z.ZodNumber;
|
|
12
|
+
temp_min: z.ZodNumber;
|
|
13
|
+
temp_max: z.ZodNumber;
|
|
14
|
+
notify_app: z.ZodBoolean;
|
|
15
|
+
notify_email: z.ZodBoolean;
|
|
16
|
+
notify_sms: z.ZodBoolean;
|
|
17
|
+
time_start: z.ZodString;
|
|
18
|
+
time_stop: z.ZodString;
|
|
19
|
+
minutes_buffer: z.ZodNumber;
|
|
20
|
+
minutes_repeat: z.ZodNumber;
|
|
21
|
+
enabled: z.ZodBoolean;
|
|
22
|
+
created: z.ZodString;
|
|
23
|
+
channel: z.ZodNumber;
|
|
24
|
+
device_id: z.ZodNumber;
|
|
25
|
+
session_id: z.ZodNumber;
|
|
26
|
+
}, z.core.$loose>;
|
|
27
|
+
export declare const deviceSchema: z.ZodObject<{
|
|
28
|
+
uuid: z.ZodString;
|
|
29
|
+
id: z.ZodNumber;
|
|
30
|
+
title: z.ZodString;
|
|
31
|
+
channel_count: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
channels: z.ZodArray<z.ZodObject<{
|
|
33
|
+
id: z.ZodNumber;
|
|
34
|
+
channel: z.ZodNumber;
|
|
35
|
+
channel_label: z.ZodString;
|
|
36
|
+
current_temp: z.ZodOptional<z.ZodNumber>;
|
|
37
|
+
degreetype: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
38
|
+
enabled: z.ZodBoolean;
|
|
39
|
+
alerts: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
40
|
+
id: z.ZodNumber;
|
|
41
|
+
temp_min: z.ZodNumber;
|
|
42
|
+
temp_max: z.ZodNumber;
|
|
43
|
+
notify_app: z.ZodBoolean;
|
|
44
|
+
notify_email: z.ZodBoolean;
|
|
45
|
+
notify_sms: z.ZodBoolean;
|
|
46
|
+
time_start: z.ZodString;
|
|
47
|
+
time_stop: z.ZodString;
|
|
48
|
+
minutes_buffer: z.ZodNumber;
|
|
49
|
+
minutes_repeat: z.ZodNumber;
|
|
50
|
+
enabled: z.ZodBoolean;
|
|
51
|
+
created: z.ZodString;
|
|
52
|
+
channel: z.ZodNumber;
|
|
53
|
+
device_id: z.ZodNumber;
|
|
54
|
+
session_id: z.ZodNumber;
|
|
55
|
+
}, z.core.$loose>>>;
|
|
56
|
+
last_templog: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
57
|
+
temp: z.ZodNumber;
|
|
58
|
+
created: z.ZodString;
|
|
59
|
+
degreetype: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
60
|
+
}, z.core.$loose>>>;
|
|
61
|
+
}, z.core.$loose>>;
|
|
62
|
+
last_drivelog: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
63
|
+
driveper: z.ZodNumber;
|
|
64
|
+
setpoint: z.ZodNumber;
|
|
65
|
+
modetype: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
66
|
+
tiedchannel: z.ZodNumber;
|
|
67
|
+
degreetype: z.ZodNumber;
|
|
68
|
+
created: z.ZodString;
|
|
69
|
+
}, z.core.$loose>>>;
|
|
70
|
+
active: z.ZodOptional<z.ZodBoolean>;
|
|
71
|
+
last_battery_reading: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
72
|
+
model_name: z.ZodOptional<z.ZodString>;
|
|
73
|
+
created: z.ZodOptional<z.ZodString>;
|
|
74
|
+
hardware_id: z.ZodOptional<z.ZodString>;
|
|
75
|
+
latest_temps: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
76
|
+
device_log: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
77
|
+
last_templog: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
78
|
+
}, z.core.$loose>;
|
|
79
|
+
export declare const devicesResponseSchema: z.ZodArray<z.ZodObject<{
|
|
80
|
+
uuid: z.ZodString;
|
|
81
|
+
id: z.ZodNumber;
|
|
82
|
+
title: z.ZodString;
|
|
83
|
+
channel_count: z.ZodOptional<z.ZodNumber>;
|
|
84
|
+
channels: z.ZodArray<z.ZodObject<{
|
|
85
|
+
id: z.ZodNumber;
|
|
86
|
+
channel: z.ZodNumber;
|
|
87
|
+
channel_label: z.ZodString;
|
|
88
|
+
current_temp: z.ZodOptional<z.ZodNumber>;
|
|
89
|
+
degreetype: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
90
|
+
enabled: z.ZodBoolean;
|
|
91
|
+
alerts: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
92
|
+
id: z.ZodNumber;
|
|
93
|
+
temp_min: z.ZodNumber;
|
|
94
|
+
temp_max: z.ZodNumber;
|
|
95
|
+
notify_app: z.ZodBoolean;
|
|
96
|
+
notify_email: z.ZodBoolean;
|
|
97
|
+
notify_sms: z.ZodBoolean;
|
|
98
|
+
time_start: z.ZodString;
|
|
99
|
+
time_stop: z.ZodString;
|
|
100
|
+
minutes_buffer: z.ZodNumber;
|
|
101
|
+
minutes_repeat: z.ZodNumber;
|
|
102
|
+
enabled: z.ZodBoolean;
|
|
103
|
+
created: z.ZodString;
|
|
104
|
+
channel: z.ZodNumber;
|
|
105
|
+
device_id: z.ZodNumber;
|
|
106
|
+
session_id: z.ZodNumber;
|
|
107
|
+
}, z.core.$loose>>>;
|
|
108
|
+
last_templog: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
109
|
+
temp: z.ZodNumber;
|
|
110
|
+
created: z.ZodString;
|
|
111
|
+
degreetype: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
112
|
+
}, z.core.$loose>>>;
|
|
113
|
+
}, z.core.$loose>>;
|
|
114
|
+
last_drivelog: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
115
|
+
driveper: z.ZodNumber;
|
|
116
|
+
setpoint: z.ZodNumber;
|
|
117
|
+
modetype: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
118
|
+
tiedchannel: z.ZodNumber;
|
|
119
|
+
degreetype: z.ZodNumber;
|
|
120
|
+
created: z.ZodString;
|
|
121
|
+
}, z.core.$loose>>>;
|
|
122
|
+
active: z.ZodOptional<z.ZodBoolean>;
|
|
123
|
+
last_battery_reading: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
124
|
+
model_name: z.ZodOptional<z.ZodString>;
|
|
125
|
+
created: z.ZodOptional<z.ZodString>;
|
|
126
|
+
hardware_id: z.ZodOptional<z.ZodString>;
|
|
127
|
+
latest_temps: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
128
|
+
device_log: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
129
|
+
last_templog: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
130
|
+
}, z.core.$loose>>;
|
|
131
|
+
export declare const deviceTemperaturesResponseSchema: z.ZodJSONSchema;
|
|
132
|
+
export declare const driveLogResponseSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
133
|
+
driveper: z.ZodNumber;
|
|
134
|
+
setpoint: z.ZodNumber;
|
|
135
|
+
modetype: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
136
|
+
tiedchannel: z.ZodNumber;
|
|
137
|
+
degreetype: z.ZodNumber;
|
|
138
|
+
created: z.ZodString;
|
|
139
|
+
}, z.core.$loose>, z.ZodObject<{}, z.core.$strict>]>;
|
|
140
|
+
declare const sessionSummarySchema: z.ZodObject<{
|
|
141
|
+
id: z.ZodNumber;
|
|
142
|
+
title: z.ZodString;
|
|
143
|
+
description: z.ZodString;
|
|
144
|
+
start_time: z.ZodString;
|
|
145
|
+
end_time: z.ZodNullable<z.ZodString>;
|
|
146
|
+
device_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
147
|
+
devices: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
148
|
+
duration: z.ZodOptional<z.ZodString>;
|
|
149
|
+
created: z.ZodOptional<z.ZodString>;
|
|
150
|
+
owner: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
151
|
+
}, z.core.$loose>;
|
|
152
|
+
export declare const sessionsResponseSchema: z.ZodArray<z.ZodObject<{
|
|
153
|
+
id: z.ZodNumber;
|
|
154
|
+
title: z.ZodString;
|
|
155
|
+
description: z.ZodString;
|
|
156
|
+
start_time: z.ZodString;
|
|
157
|
+
end_time: z.ZodNullable<z.ZodString>;
|
|
158
|
+
device_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
159
|
+
devices: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
160
|
+
duration: z.ZodOptional<z.ZodString>;
|
|
161
|
+
created: z.ZodOptional<z.ZodString>;
|
|
162
|
+
owner: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
163
|
+
}, z.core.$loose>>;
|
|
164
|
+
export declare const sessionDetailSchema: z.ZodObject<{
|
|
165
|
+
id: z.ZodNumber;
|
|
166
|
+
title: z.ZodString;
|
|
167
|
+
description: z.ZodString;
|
|
168
|
+
start_time: z.ZodString;
|
|
169
|
+
end_time: z.ZodNullable<z.ZodString>;
|
|
170
|
+
devices: z.ZodArray<z.ZodObject<{
|
|
171
|
+
uuid: z.ZodString;
|
|
172
|
+
title: z.ZodString;
|
|
173
|
+
channels: z.ZodArray<z.ZodObject<{
|
|
174
|
+
channel_label: z.ZodString;
|
|
175
|
+
channel: z.ZodNumber;
|
|
176
|
+
}, z.core.$loose>>;
|
|
177
|
+
}, z.core.$loose>>;
|
|
178
|
+
notes: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
179
|
+
note_time: z.ZodString;
|
|
180
|
+
note_text: z.ZodString;
|
|
181
|
+
channel: z.ZodNullable<z.ZodNumber>;
|
|
182
|
+
device: z.ZodString;
|
|
183
|
+
}, z.core.$loose>>>;
|
|
184
|
+
duration: z.ZodOptional<z.ZodString>;
|
|
185
|
+
created: z.ZodOptional<z.ZodString>;
|
|
186
|
+
owner: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
187
|
+
}, z.core.$loose>;
|
|
188
|
+
export declare const chartChannelSchema: z.ZodObject<{
|
|
189
|
+
device: z.ZodString;
|
|
190
|
+
label: z.ZodString;
|
|
191
|
+
degreetype: z.ZodNumber;
|
|
192
|
+
channel_id: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>>;
|
|
193
|
+
x: z.ZodArray<z.ZodNumber>;
|
|
194
|
+
y: z.ZodArray<z.ZodNumber>;
|
|
195
|
+
}, z.core.$loose>;
|
|
196
|
+
export declare const chartResponseSchema: z.ZodArray<z.ZodObject<{
|
|
197
|
+
device: z.ZodString;
|
|
198
|
+
label: z.ZodString;
|
|
199
|
+
degreetype: z.ZodNumber;
|
|
200
|
+
channel_id: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>>;
|
|
201
|
+
x: z.ZodArray<z.ZodNumber>;
|
|
202
|
+
y: z.ZodArray<z.ZodNumber>;
|
|
203
|
+
}, z.core.$loose>>;
|
|
204
|
+
export type RawDevice = z.infer<typeof deviceSchema>;
|
|
205
|
+
export type RawAlert = z.infer<typeof alertSchema>;
|
|
206
|
+
export type RawSessionSummary = z.infer<typeof sessionSummarySchema>;
|
|
207
|
+
export type RawSessionDetail = z.infer<typeof sessionDetailSchema>;
|
|
208
|
+
export type RawChartChannel = z.infer<typeof chartChannelSchema>;
|
|
209
|
+
export type RawDriveLog = z.infer<typeof driveLogSchema>;
|
|
210
|
+
export type RawDriveLogResponse = z.infer<typeof driveLogResponseSchema>;
|
|
211
|
+
export type RawDeviceTemperatures = z.infer<typeof deviceTemperaturesResponseSchema>;
|
|
212
|
+
export {};
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Unused channel slots on a device may omit degreetype or send null.
|
|
3
|
+
// Nested field shapes are based on responses observed while building fireboard-mcp.
|
|
4
|
+
// The official API page does not specify their complete structure.
|
|
5
|
+
const nullableDegreeTypeSchema = z.number().nullable().optional();
|
|
6
|
+
// Represents a single drive controller log entry.
|
|
7
|
+
// Used both as a top-level response from GET /api/v1/devices/{uuid}/drivelog.json
|
|
8
|
+
// and nested inside device objects from GET /api/v1/devices.json.
|
|
9
|
+
//
|
|
10
|
+
// Illustrative excerpt (GET /api/v1/devices/{uuid}/drivelog.json):
|
|
11
|
+
// {
|
|
12
|
+
// "tiedchannel": 1,
|
|
13
|
+
// "setpoint": 107.0,
|
|
14
|
+
// "driveper": 0.0,
|
|
15
|
+
// "modetype": "Off", // can also be a number, e.g. 2
|
|
16
|
+
// "degreetype": 1,
|
|
17
|
+
// "created": "2024-01-01T12:00:00+00:00"
|
|
18
|
+
// }
|
|
19
|
+
const driveLogSchema = z.looseObject({
|
|
20
|
+
driveper: z.number(),
|
|
21
|
+
setpoint: z.number(),
|
|
22
|
+
modetype: z.union([z.string(), z.number()]), // string from /drivelog.json ("Off"), integer from /devices.json last_drivelog (2) — API inconsistency
|
|
23
|
+
tiedchannel: z.number(),
|
|
24
|
+
degreetype: z.number(),
|
|
25
|
+
created: z.string(),
|
|
26
|
+
});
|
|
27
|
+
// Represents a temperature alert configured on a channel.
|
|
28
|
+
// Nested inside channel objects from GET /api/v1/devices.json.
|
|
29
|
+
const alertSchema = z.looseObject({
|
|
30
|
+
id: z.number(),
|
|
31
|
+
temp_min: z.number(),
|
|
32
|
+
temp_max: z.number(),
|
|
33
|
+
notify_app: z.boolean(),
|
|
34
|
+
notify_email: z.boolean(),
|
|
35
|
+
notify_sms: z.boolean(),
|
|
36
|
+
time_start: z.string(),
|
|
37
|
+
time_stop: z.string(),
|
|
38
|
+
minutes_buffer: z.number(),
|
|
39
|
+
minutes_repeat: z.number(),
|
|
40
|
+
enabled: z.boolean(),
|
|
41
|
+
created: z.string(),
|
|
42
|
+
channel: z.number(),
|
|
43
|
+
device_id: z.number(),
|
|
44
|
+
session_id: z.number(),
|
|
45
|
+
});
|
|
46
|
+
// Represents a single temperature probe channel on a device.
|
|
47
|
+
// Nested inside device objects from GET /api/v1/devices.json.
|
|
48
|
+
//
|
|
49
|
+
// Illustrative channel excerpt (not a complete schema fixture):
|
|
50
|
+
// {
|
|
51
|
+
// "channel": 1,
|
|
52
|
+
// "channel_label": "Grill Temp",
|
|
53
|
+
// "current_temp": 107.6,
|
|
54
|
+
// "degreetype": 1,
|
|
55
|
+
// "enabled": true,
|
|
56
|
+
// "last_templog": {
|
|
57
|
+
// "temp": 107.6,
|
|
58
|
+
// "created": "2024-01-01T12:00:00Z",
|
|
59
|
+
// "degreetype": 1
|
|
60
|
+
// }
|
|
61
|
+
// }
|
|
62
|
+
const deviceChannelSchema = z.looseObject({
|
|
63
|
+
id: z.number(),
|
|
64
|
+
channel: z.number(),
|
|
65
|
+
channel_label: z.string(),
|
|
66
|
+
current_temp: z.number().optional(),
|
|
67
|
+
degreetype: nullableDegreeTypeSchema,
|
|
68
|
+
enabled: z.boolean(),
|
|
69
|
+
alerts: z.array(alertSchema).default([]),
|
|
70
|
+
last_templog: z
|
|
71
|
+
.looseObject({
|
|
72
|
+
temp: z.number(),
|
|
73
|
+
created: z.string(),
|
|
74
|
+
degreetype: nullableDegreeTypeSchema,
|
|
75
|
+
})
|
|
76
|
+
.nullable()
|
|
77
|
+
.optional(),
|
|
78
|
+
});
|
|
79
|
+
// Represents a single Fireboard device.
|
|
80
|
+
// Used as an element in GET /api/v1/devices.json.
|
|
81
|
+
//
|
|
82
|
+
// Illustrative excerpt (GET /api/v1/devices.json, first element):
|
|
83
|
+
// {
|
|
84
|
+
// "uuid": "abc12345-0000-0000-0000-000000000000",
|
|
85
|
+
// "title": "Big Green Egg",
|
|
86
|
+
// "channel_count": 3,
|
|
87
|
+
// "active": true,
|
|
88
|
+
// "channels": [ ... ], // see deviceChannelSchema
|
|
89
|
+
// "last_drivelog": { ... } // see driveLogSchema, null if no drive session
|
|
90
|
+
// }
|
|
91
|
+
export const deviceSchema = z.looseObject({
|
|
92
|
+
uuid: z.string(),
|
|
93
|
+
id: z.number(),
|
|
94
|
+
title: z.string(),
|
|
95
|
+
channel_count: z.number().optional(),
|
|
96
|
+
channels: z.array(deviceChannelSchema),
|
|
97
|
+
last_drivelog: driveLogSchema.nullable().optional(),
|
|
98
|
+
active: z.boolean().optional(),
|
|
99
|
+
last_battery_reading: z.number().nullable().optional(),
|
|
100
|
+
model_name: z.string().optional(),
|
|
101
|
+
created: z.string().optional(),
|
|
102
|
+
hardware_id: z.string().optional(),
|
|
103
|
+
latest_temps: z.array(z.unknown()).optional(),
|
|
104
|
+
device_log: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
105
|
+
last_templog: z.string().nullable().optional(),
|
|
106
|
+
});
|
|
107
|
+
// GET /api/v1/devices.json
|
|
108
|
+
// Returns all devices associated with the authenticated account.
|
|
109
|
+
//
|
|
110
|
+
// Illustrative response excerpt (truncated):
|
|
111
|
+
// [
|
|
112
|
+
// {
|
|
113
|
+
// "uuid": "abc12345-0000-0000-0000-000000000000",
|
|
114
|
+
// "title": "Big Green Egg",
|
|
115
|
+
// "channel_count": 3,
|
|
116
|
+
// "active": true,
|
|
117
|
+
// "channels": [
|
|
118
|
+
// { "channel": 1, "channel_label": "Grill Temp", "current_temp": 225.0, "degreetype": 1, "enabled": true,
|
|
119
|
+
// "last_templog": { "temp": 225.0, "created": "2024-01-01T12:00:00Z", "degreetype": 1 } },
|
|
120
|
+
// { "channel": 2, "channel_label": "Meat Temp", "current_temp": 145.0, "degreetype": 1, "enabled": true,
|
|
121
|
+
// "last_templog": { "temp": 145.0, "created": "2024-01-01T12:00:00Z", "degreetype": 1 } }
|
|
122
|
+
// ],
|
|
123
|
+
// "last_drivelog": { "driveper": 0.0, "setpoint": 225.0, "modetype": 2, "tiedchannel": 1,
|
|
124
|
+
// "degreetype": 1, "created": "2024-01-01T12:00:02Z" }
|
|
125
|
+
// }
|
|
126
|
+
// ]
|
|
127
|
+
export const devicesResponseSchema = z.array(deviceSchema);
|
|
128
|
+
// GET /api/v1/devices/{uuid}/temps.json
|
|
129
|
+
// Official docs specify the one-minute freshness cutoff, but not the response structure.
|
|
130
|
+
// Preserve JSON without inventing required field names or a top-level container shape.
|
|
131
|
+
export const deviceTemperaturesResponseSchema = z.json();
|
|
132
|
+
// GET /api/v1/devices/{uuid}/drivelog.json
|
|
133
|
+
// Official docs: returns the latest Drive log if it is less than one minute old.
|
|
134
|
+
// A single-object response and the fields above are based on observed responses;
|
|
135
|
+
// Observed with Drive disconnected or the device offline: HTTP 200 with {}.
|
|
136
|
+
// Keep an empty response unchanged, and reject incomplete non-empty logs.
|
|
137
|
+
export const driveLogResponseSchema = z.union([driveLogSchema, z.strictObject({})]);
|
|
138
|
+
// Represents a session summary as returned in the sessions list.
|
|
139
|
+
// Used as an element in GET /api/v1/sessions.json.
|
|
140
|
+
//
|
|
141
|
+
// Illustrative session summary excerpt:
|
|
142
|
+
// {
|
|
143
|
+
// "id": 12345678,
|
|
144
|
+
// "title": "Sat Jan 1 Session",
|
|
145
|
+
// "description": "Auto-created session",
|
|
146
|
+
// "start_time": "2024-01-01T10:00:00Z",
|
|
147
|
+
// "end_time": null,
|
|
148
|
+
// "device_ids": ["abc12345-0000-0000-0000-000000000000"]
|
|
149
|
+
// }
|
|
150
|
+
const sessionSummarySchema = z.looseObject({
|
|
151
|
+
id: z.number(),
|
|
152
|
+
title: z.string(),
|
|
153
|
+
description: z.string(),
|
|
154
|
+
start_time: z.string(),
|
|
155
|
+
end_time: z.string().nullable(),
|
|
156
|
+
// Observed list-response field; not listed in the official session properties.
|
|
157
|
+
device_ids: z.array(z.string()).optional(),
|
|
158
|
+
devices: z.array(z.unknown()).optional(),
|
|
159
|
+
duration: z.string().optional(),
|
|
160
|
+
created: z.string().optional(),
|
|
161
|
+
owner: z.record(z.string(), z.unknown()).optional(),
|
|
162
|
+
});
|
|
163
|
+
// GET /api/v1/sessions.json
|
|
164
|
+
// Returns sessions for the authenticated account. The official docs do not specify ordering.
|
|
165
|
+
//
|
|
166
|
+
// Illustrative response excerpt (two entries shown):
|
|
167
|
+
// [
|
|
168
|
+
// { "id": 12345678, "title": "Sat Jan 1 Session", "description": "Auto-created session",
|
|
169
|
+
// "start_time": "2024-01-01T10:00:00Z", "end_time": null,
|
|
170
|
+
// "device_ids": ["abc12345-0000-0000-0000-000000000000"] },
|
|
171
|
+
// { "id": 12345677, "title": "Mon May 25: Pulled Pork",
|
|
172
|
+
// "description": "User-initiated session",
|
|
173
|
+
// "start_time": "2024-01-01T08:00:00Z", "end_time": "2024-01-01T20:00:00Z",
|
|
174
|
+
// "device_ids": ["abc12345-0000-0000-0000-000000000000"] }
|
|
175
|
+
// ]
|
|
176
|
+
export const sessionsResponseSchema = z.array(sessionSummarySchema);
|
|
177
|
+
// Represents a single cook note attached to a session.
|
|
178
|
+
// Nested inside the notes array of a session detail response.
|
|
179
|
+
//
|
|
180
|
+
// Illustrative note excerpt:
|
|
181
|
+
// {
|
|
182
|
+
// "note_time": "2024-01-01T12:00:00Z",
|
|
183
|
+
// "note_text": "Added more fuel",
|
|
184
|
+
// "channel": 2, // null if not tied to a specific channel
|
|
185
|
+
// "device": "abc12345-0000-0000-0000-000000000000"
|
|
186
|
+
// }
|
|
187
|
+
const noteSchema = z.looseObject({
|
|
188
|
+
note_time: z.string(),
|
|
189
|
+
note_text: z.string(),
|
|
190
|
+
channel: z.number().nullable(),
|
|
191
|
+
device: z.string(),
|
|
192
|
+
});
|
|
193
|
+
// Represents a device summary nested inside a session detail response.
|
|
194
|
+
// Contains only the fields needed to identify the device and its channels.
|
|
195
|
+
const sessionDeviceSchema = z.looseObject({
|
|
196
|
+
uuid: z.string(),
|
|
197
|
+
title: z.string(),
|
|
198
|
+
channels: z.array(z.looseObject({
|
|
199
|
+
channel_label: z.string(),
|
|
200
|
+
channel: z.number(),
|
|
201
|
+
})),
|
|
202
|
+
});
|
|
203
|
+
// GET /api/v1/sessions/{id}.json
|
|
204
|
+
// Returns full detail for a single cooking session including devices and notes.
|
|
205
|
+
//
|
|
206
|
+
// Illustrative excerpt (GET /api/v1/sessions/12345678.json):
|
|
207
|
+
// {
|
|
208
|
+
// "id": 12345678,
|
|
209
|
+
// "title": "Sat Jan 1 Session",
|
|
210
|
+
// "description": "Auto-created session",
|
|
211
|
+
// "start_time": "2024-01-01T10:00:00Z",
|
|
212
|
+
// "end_time": null,
|
|
213
|
+
// "devices": [
|
|
214
|
+
// {
|
|
215
|
+
// "uuid": "abc12345-0000-0000-0000-000000000000",
|
|
216
|
+
// "title": "Big Green Egg",
|
|
217
|
+
// "channels": [
|
|
218
|
+
// { "channel": 1, "channel_label": "Grill Temp" },
|
|
219
|
+
// { "channel": 2, "channel_label": "Meat Temp" },
|
|
220
|
+
// { "channel": 3, "channel_label": "Channel 3" }
|
|
221
|
+
// ]
|
|
222
|
+
// }
|
|
223
|
+
// ],
|
|
224
|
+
// "notes": [
|
|
225
|
+
// { "note_time": "2024-01-01T12:00:00Z", "note_text": "Added more fuel",
|
|
226
|
+
// "channel": 2, "device": "abc12345-0000-0000-0000-000000000000" }
|
|
227
|
+
// ]
|
|
228
|
+
// }
|
|
229
|
+
export const sessionDetailSchema = z.looseObject({
|
|
230
|
+
id: z.number(),
|
|
231
|
+
title: z.string(),
|
|
232
|
+
description: z.string(),
|
|
233
|
+
start_time: z.string(),
|
|
234
|
+
end_time: z.string().nullable(),
|
|
235
|
+
devices: z.array(sessionDeviceSchema),
|
|
236
|
+
notes: z.array(noteSchema).default([]),
|
|
237
|
+
duration: z.string().optional(),
|
|
238
|
+
created: z.string().optional(),
|
|
239
|
+
owner: z.record(z.string(), z.unknown()).optional(),
|
|
240
|
+
});
|
|
241
|
+
// Represents a single channel's time-series temperature data within a session.
|
|
242
|
+
// Used as an element in GET /api/v1/sessions/{id}/chart.json.
|
|
243
|
+
//
|
|
244
|
+
// x: array of Unix timestamps (seconds since epoch)
|
|
245
|
+
// y: array of temperature readings corresponding to each timestamp
|
|
246
|
+
//
|
|
247
|
+
// Illustrative channel excerpt:
|
|
248
|
+
// {
|
|
249
|
+
// "device": "abc12345-0000-0000-0000-000000000000",
|
|
250
|
+
// "label": "Grill Temp",
|
|
251
|
+
// "degreetype": 1,
|
|
252
|
+
// "channel_id": "1",
|
|
253
|
+
// "x": [1704110400, 1704110460, ...],
|
|
254
|
+
// "y": [225.0, 226.1, ...]
|
|
255
|
+
// }
|
|
256
|
+
export const chartChannelSchema = z.looseObject({
|
|
257
|
+
device: z.string(),
|
|
258
|
+
label: z.string(),
|
|
259
|
+
degreetype: z.number(),
|
|
260
|
+
channel_id: z.union([z.string(), z.number()]).transform(String).optional(),
|
|
261
|
+
x: z.array(z.number()),
|
|
262
|
+
y: z.array(z.number()),
|
|
263
|
+
});
|
|
264
|
+
// GET /api/v1/sessions/{id}/chart.json
|
|
265
|
+
// Returns time-series temperature data for all channels in a session.
|
|
266
|
+
// Each entry represents a channel on a device. The docs describe x as Unix timestamps
|
|
267
|
+
// and y as temperature values or Drive %. They do not specify channel_id.
|
|
268
|
+
//
|
|
269
|
+
// Illustrative response excerpt (two channels, values truncated):
|
|
270
|
+
// [
|
|
271
|
+
// { "device": "abc12345-0000-0000-0000-000000000000", "label": "Grill Temp",
|
|
272
|
+
// "degreetype": 1, "channel_id": "1", "x": [1704110400, 1704110460], "y": [225.0, 226.1] },
|
|
273
|
+
// { "device": "abc12345-0000-0000-0000-000000000000", "label": "Meat Temp",
|
|
274
|
+
// "degreetype": 1, "channel_id": "2", "x": [1704110400, 1704110460], "y": [145.0, 145.3] }
|
|
275
|
+
// ]
|
|
276
|
+
export const chartResponseSchema = z.array(chartChannelSchema);
|