Cloud version, requires approval

This commit is contained in:
2026-06-04 22:48:26 +10:00
parent 890feae060
commit 71fc3abb90
2 changed files with 136 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
// bluetti.mjs — Bluetti Open Platform (cloud) client, signed per the official spec.
//
// Node 18+ only (built-in fetch + node:crypto; no npm install):
// BLUETTI_APPKEY=xxx BLUETTI_APPSECRET=yyy BLUETTI_SN=EL300... node bluetti.mjs
//
// Auth (from open.bluetti.com OpenAPI "Signature" section):
// Authorization = SHA-256( "appKey=<k>&appSecret=<s>&nonceStr=<n>&timeStamp=<t>" )
// sent with headers: x-app-key=<k>, ETag=<nonceStr>, Date=<timeStamp(sec)>.
// The AppSecret is hashed in, never transmitted.
//
// Register an app + request the IoT/telemetry API permission at
// https://open.bluetti.com/developers — and the device SN must be authorized to
// your account, or the telemetry endpoints return a permission error.
import { createHash, randomBytes } from 'node:crypto';
const BASE = process.env.BLUETTI_BASE || 'https://open.bluetti.com';
const APP_KEY = process.env.BLUETTI_APPKEY || process.env.BLUETTI_ID;
const APP_SECRET = process.env.BLUETTI_APPSECRET || process.env.BLUETTI_SECRET;
const DEVICE_SN = process.env.BLUETTI_SN || 'EL3002546110146262';
if (!APP_KEY || !APP_SECRET) {
console.error('Set BLUETTI_APPKEY and BLUETTI_APPSECRET (from open.bluetti.com).');
process.exit(1);
}
function authHeaders() {
const timeStamp = String(Math.floor(Date.now() / 1000));
const nonceStr = randomBytes(16).toString('hex');
const signStr = `appKey=${APP_KEY}&appSecret=${APP_SECRET}&nonceStr=${nonceStr}&timeStamp=${timeStamp}`;
const signature = createHash('sha256').update(signStr).digest('hex').toUpperCase();
return {
'x-app-key': APP_KEY,
'ETag': nonceStr,
'Date': timeStamp,
'Authorization': signature,
'Content-Type': 'application/json',
'Accept-Language': 'en_US',
};
}
// POST a JSON body to an /open/... path; returns the UnifyResponse `data`.
async function call(path, body = {}) {
const r = await fetch(`${BASE}${path}`, {
method: 'POST',
headers: authHeaders(),
body: JSON.stringify(body),
});
const text = await r.text();
let j;
try { j = JSON.parse(text); } catch { throw new Error(`HTTP ${r.status}: ${text}`); }
// UnifyResponse: msgCode 0 == success
if (j.msgCode !== 0 && j.code !== 200) {
throw new Error(`API error msgCode=${j.msgCode} code=${j.code}: ${j.message || text}`);
}
return j.data;
}
// List the telemetry devices bound to this account.
function listDevices(pageNo = 1, pageSize = 20) {
return call('/open/bluiotdata/device/telemetry/v1/userTelemetryDeviceList', { pageNo, pageSize });
}
// Reported (telemetry) data for one device over a time window (timestamps in ms).
function reportedData(deviceSn, beginTimestamp, endTimestamp, pageNo = 1, pageSize = 20) {
return call('/open/bluiotdata/device/telemetry/v1/telemetryDeviceReportedData',
{ deviceSn, beginTimestamp, endTimestamp, pageNo, pageSize });
}
const devices = await listDevices();
console.log('Devices:', JSON.stringify(devices, null, 2));
const now = Date.now();
const data = await reportedData(DEVICE_SN, now - 10 * 60 * 1000, now);
console.log(`\nReported data for ${DEVICE_SN}:`);
console.log(JSON.stringify(data, null, 2));