iplayabc-iot-center-sdk
v0.1.0
Published
IoT Center SDK for Huawei cloud device commands with bootstrap config cache
Readme
iplayabc-iot-center-sdk
Client SDK for sending Huawei IoT device commands. Bootstrap configuration (Huawei AK/SK, product IDs) is fetched from iot-center and cached locally for disaster recovery.
Install
npm install iplayabc-iot-center-sdk iplayabc-speaker-iot-sdk| Package | Role |
|---------|------|
| iplayabc-iot-center-sdk | Bootstrap, cache, domain API |
| iplayabc-speaker-iot-sdk | Peer dependency — Huawei C2D sender |
| ioredis | Optional — Redis cache for wifi/power lists |
Monorepo local link (before publish):
"iplayabc-iot-center-sdk": "file:../iot-center-v3/iplayabc-iot-center-sdk"Requires Node.js >= 22.21.0.
Module formats
The package ships CJS and ESM:
| Field | File |
|-------|------|
| require() | dist/index.js |
| import | dist/index.mjs |
TypeScript resolves types via exports (index.d.ts / index.d.mts).
Quick start
import { IotCenterSdk, IotCenterSdkError, SdkErrorCode } from 'iplayabc-iot-center-sdk';
const sdk = new IotCenterSdk({
endpoint: 'https://iot.example.com', // iot-center root URL, no /api/v1 suffix
licenseKey: 'iot_xxxxxxxx', // License API Key from iot-center admin
});
await sdk.whenReady();
await sdk.volume.set('speaker-device-id', 5);
await sdk.wifi.add('speaker-device-id', { ssid: 'classroom', password: 'secret' });Lifecycle
| Event | Behavior |
|-------|----------|
| new IotCenterSdk(...) | Starts bootstrap immediately (internal ready promise) |
| await sdk.whenReady() | Waits for remote config or local fallback cache |
| Domain methods (sdk.wifi.add, etc.) | Auto-await ready before sending commands |
| Bootstrap fails with no cache | whenReady() rejects; subsequent calls fail |
Read-only state:
sdk.isReady // boolean — Huawei sender configured
sdk.configSource // 'remote' | 'cache' | null
sdk.configHash // string | null — from bootstrap responseConfig refresh
iot-center product settings can change without restarting your service.
Manual refresh
const result = await sdk.refreshConfig();
// { updated: boolean, configHash: string | null, configSource: 'remote' | 'cache' | null }Call this after iot-center updates Huawei credentials. When updated is true, the in-memory sender has been hot-swapped and the local bootstrap cache file was updated.
If the refresh request fails while the SDK is already ready, the current sender is kept and the call rejects.
Lazy refresh on auth failure
Command sends automatically retry once when Huawei returns an auth error (HTTP 401/403 or known IAM/APIGW error codes). The SDK calls refreshConfig() internally, then retries the same command.
You do not need extra code for AK/SK rotation in most cases. For immediate effect after an admin change, still call await sdk.refreshConfig() explicitly.
Options
type IotCenterSdkOptions = {
endpoint: string; // Required — iot-center base URL
licenseKey: string; // Required — Bearer token (iot_...)
bootstrapTimeoutMs?: number; // Default 10000
commandTimeoutMs?: number; // Reserved — not wired yet
configPollIntervalMs?: number; // Reserved — not implemented yet
cache?: {
dir?: string; // Default: os.tmpdir()/iot-center-sdk
redis?: {
url: string;
keyPrefix?: string; // Default: 'iot-center-sdk:'
};
};
};Cache behavior
| Cache | Storage | Purpose | |-------|---------|---------| | Bootstrap config | Always file | Huawei credentials; survives iot-center outage | | WiFi / power lists | File or Redis | Same short-circuit logic as iot-center |
- Set
cache.dirto a persistent path in production (do not rely on system temp alone). - Use Redis when running multiple SDK instances so wifi/power list caches stay consistent.
Bootstrap endpoint: GET {endpoint}/api/v1/iot/sdk/bootstrap with Authorization: Bearer {licenseKey}.
API reference
All methods take a Huawei device ID as the first argument.
WiFi — sdk.wifi
await sdk.wifi.list(deviceId); // Uses cache when non-empty
await sdk.wifi.add(deviceId, { ssid: 'classroom', password: 'secret' });
await sdk.wifi.remove(deviceId, { ssid: 'classroom' });
await sdk.wifi.switch(deviceId, { ssid: 'classroom' });Power schedules — sdk.power
await sdk.power.list(deviceId); // Uses cache when present
await sdk.power.add(deviceId, { on: 3600, off: 7200, day: [1, 2], enable: true });
await sdk.power.update(deviceId, schedule);
await sdk.power.remove(deviceId, schedule);PowerSchedule: on / off are Unix seconds; day: [] means a one-time task.
Volume — sdk.volume
await sdk.volume.set(deviceId, 5); // Integer 1–8Files — sdk.files
await sdk.files.list(deviceId);Audio — sdk.audio
await sdk.audio.play(deviceId);
await sdk.audio.pause(deviceId);
await sdk.audio.prev(deviceId);
await sdk.audio.next(deviceId);Shutdown — sdk.shutdown
await sdk.shutdown(deviceId);Report / firmware — sdk.report / sdk.firmware
await sdk.report.sync(deviceId);
await sdk.firmware.setAutoUpgrade(deviceId, true);
await sdk.firmware.upgrade(deviceId);Generic commands — sdk.command
await sdk.command.send(deviceId, {
serviceId: 'BasicData',
command: 'playControl',
payload: { action: 'PLAY' },
});
await sdk.command.sendBatch(
['device-1', 'device-2'],
{
serviceId: 'BasicData',
command: 'playControl',
payload: { action: 'PLAY' },
},
{ taskName: 'batch-play' },
);Error handling
import {
IotCenterSdkError,
SdkErrorCode,
DeviceCoreError,
DeviceCoreErrorCode,
} from 'iplayabc-iot-center-sdk';
try {
await sdk.wifi.add(deviceId, { ssid: 'home', password: 'secret' });
} catch (error) {
if (error instanceof IotCenterSdkError) {
// SDK_BOOTSTRAP_TIMEOUT, SDK_LICENSE_INVALID, SDK_NOT_READY, ...
console.error(error.code, error.message, error.statusCode);
}
if (error instanceof DeviceCoreError) {
// VALIDATION_ERROR, HUAWEI_COMMAND_FAILED (includes statusCode)
console.error(error.code, error.message);
}
}| Phase | Error class | Common codes |
|-------|-------------|--------------|
| Constructor | IotCenterSdkError | SDK_VALIDATION_ERROR |
| Bootstrap | IotCenterSdkError | SDK_BOOTSTRAP_TIMEOUT, SDK_LICENSE_INVALID, SDK_BOOTSTRAP_FAILED, SDK_PLATFORM_UNSUPPORTED |
| Validation | DeviceCoreError | VALIDATION_ERROR |
| Huawei C2D | DeviceCoreError | HUAWEI_COMMAND_FAILED |
NestJS integration
@Injectable()
export class IotSdkService implements OnModuleInit {
readonly sdk: IotCenterSdk;
constructor(private readonly config: ConfigService) {
this.sdk = new IotCenterSdk({
endpoint: config.getOrThrow('IOT_CENTER_ENDPOINT'),
licenseKey: config.getOrThrow('IOT_LICENSE_KEY'),
cache: {
dir: config.get('IOT_SDK_CACHE_DIR'),
redis: config.get('REDIS_URL')
? { url: config.get('REDIS_URL')! }
: undefined,
},
});
}
async onModuleInit(): Promise<void> {
await this.sdk.whenReady();
if (this.sdk.configSource === 'cache') {
// iot-center unreachable — using last known bootstrap config
}
}
}Use one SDK instance per process per license key (one product).
SDK vs iot-center HTTP
| | HTTP POST /api/v1/iot/device/... | SDK |
|---|-----------------------------------|-----|
| Auth | Bearer license key | Same (bootstrap only) |
| Command path | Center → DB lookup → Huawei | Direct to Huawei |
| Device ownership | Center validates license vs device | Not validated — caller must ensure device ID is valid |
| WiFi / power cache | MySQL | File or Redis |
| Center down | Commands unavailable | Commands work if bootstrap cache exists |
Use HTTP for: device registration, product/license management, strict license–device authorization.
Use SDK for: high-volume device commands from trusted backends (e.g. reading-server).
HTTP → SDK mapping
| HTTP | SDK |
|------|-----|
| POST .../volume/setting | sdk.volume.set(id, volume) |
| POST .../wifi/list etc. | sdk.wifi.list/add/remove/switch |
| POST .../power/list etc. | sdk.power.list/add/update/remove |
| POST .../files/list | sdk.files.list |
| POST .../audio/* | sdk.audio.play/pause/prev/next |
| POST .../shutdown | sdk.shutdown |
| POST .../report/sync | sdk.report.sync |
| POST .../firmware/* | sdk.firmware.setAutoUpgrade/upgrade |
| POST .../command | sdk.command.send / sendBatch |
Limitations
- Huawei only — non-Huawei products throw
SDK_PLATFORM_UNSUPPORTED. configPollIntervalMsis declared but not yet implemented (userefreshConfig()or rely on lazy auth refresh for now).sendBatchrequires devices under the same product app (Huawei sender constraint); SDK does not re-check DB ownership.- No
app_idin client config — resolved by iot-center during bootstrap.
Publish
Only iplayabc-iot-center-sdk is published to npm. iplayabc-iot-device-core stays in the monorepo for iot-center and is bundled into the SDK at build time.
yarn build:sdk
yarn test:sdk
cd iplayabc-iot-center-sdk
npm publish --registry=https://registry.npmjs.orgConsumers install:
npm install iplayabc-iot-center-sdk iplayabc-speaker-iot-sdkDevelopment (monorepo root)
yarn test:sdk
yarn build:sdk # bundles device-core into SDK dist via tsup