@apocaliss92/nodewitt

中文文档
1.4.2发布于 2026/07/02MITTypeScriptESM + CJS近 30 天下载 2048

Node.js/TypeScript client for Ecowitt weather stations (local poll + push)

原始使用文档
文档目录11 节
  1. Install
  2. Transports
  3. Usage
  4. Local poll
  5. Push listener
  6. Events
  7. The unified Sensor
  8. Diagnostic dump
  9. Sharing a dump to extend the sensor tables
  10. Acknowledgments
  11. License

nodewitt

Node.js/TypeScript client for Ecowitt weather stations — local poll + push, one unified sensor model.

Event-driven library — not a UI.

Install

npm install @apocaliss92/nodewitt

Transports

Two transports are implemented: the local-poll client and the HTTP push listener (an internal node:http server that receives the gateway's "Customized" x-www-form-urlencoded uploads and decodes them into the same SI-normalized readings). Both are exposed behind the single Ecowitt facade, which converges them onto one unified Sensor model with a typed event stream.

Usage

The Ecowitt facade is the only public entry point. Pick a transport with Ecowitt.createLocal (poll the gateway over HTTP) or Ecowitt.createListener (receive the gateway's push uploads), then drive it with the same lifecycle: start() / stop(), subscribe with on(...) / off(...) / once(...), and read a point-in-time snapshot with getStation() (or getSensors()).

stop() is terminal: it tears down the transport and removes all listeners, and the instance cannot be restarted. Create a new Ecowitt via createLocal / createListener to resume.

Local poll

import { Ecowitt } from '@apocaliss92/nodewitt';

const client = Ecowitt.createLocal({
  host: '192.168.20.181',
  // port: 80,                 // gateway HTTP port (default 80)
  // password: 'secret',       // newer firmware
  // pollIntervalMs: 60000,    // live-data poll interval (default 60000)
  // mappingIntervalMs: 600000 // sensor-map refresh interval (default 600000)
});

client.on('update', (sensors) => {
  for (const s of sensors) {
    console.log(s.id, s.quantity, s.value, s.unit, s.battery ?? '', s.batteryUnit ?? '');
  }
});
client.on('sensorChanged', (s) => console.log('changed', s.id, s.value));
client.on('snapshot', (snap) => console.log('sensors:', snap.sensors.length));
client.on('error', (err) => console.error('ecowitt error', err.message));

await client.start();
// ... later
console.log(client.getStation().sensors.length);
await client.stop();

Push listener

import { Ecowitt } from '@apocaliss92/nodewitt';

const client = Ecowitt.createListener({ port: 4199 }); // port: 0 for an ephemeral port
client.on('update', (sensors) => console.log('push update', sensors.length));

await client.start();
console.log('listening on', client.getAddress()); // point the gateway "Customized" upload here
// ... later
await client.stop();

Events

eventpayloadwhen
updateSensor[]the sensors whose value or battery changed on the latest ingest
sensorChangedSensorone sensor changed (emitted once per changed sensor)
snapshotStationSnapshotthe full station snapshot after every ingest
errorErrora transport/decoder error (never thrown into the consumer)
rawFrameRawFramea raw, undecoded transport frame (poll livedata / push form) for diagnostics

The unified Sensor

fieldmeaning
idstable identity within the station
hardwareId?sensor hardware id (poll only)
model? / channel?sensor model token / channel (1..8)
quantitytemperature | humidity | pressure | wind_speed | …
value / unit / rawSI value, SI unit, original string
battery? / batteryUnit?battery level — '%' (decoded percent) or 'V' (raw voltage)
signal?signal strength (poll, when known)
lastUpdatedepoch millis

Both transports converge on this one model: poll keys sensors by hardware id (stable across battery swaps); push keys by PASSKEY + channel. Values are SI internally with the raw string preserved. Batteries are surfaced exactly as the transport produced them (percent or volts) — never re-decoded.

Diagnostic dump

createDumper attaches a read-only diagnostic recorder to a live Ecowitt client. nodewitt issues no commands — the dumper only observes the event stream plus a sensor snapshot — so it can never change the gateway. It records what the station exposes during operation (sensor models, channels, measurement keys, battery encodings, and optionally raw frames) and exports an anonymized DeviceDump JSON in the shared cross-library dump format.

import { Ecowitt, createDumper } from '@apocaliss92/nodewitt';

const client = Ecowitt.createLocal({ host: '192.168.20.181' });
const dumper = createDumper(client, { captureRawFrames: true });
await client.start();
dumper.start();
// ...let the station report for a while (one or more poll ticks)...
dumper.stop();
const json = dumper.exportJson(); // anonymized, share-safe JSON
await client.stop();

The output is anonymized: the gateway mac / PASSKEY / SSID / IP / host / latitude / longitude and any value-borne secret are replaced with [redacted]. Raw frames are scrubbed both on capture and on export, so a PASSKEY or mac carried in a poll/push frame never reaches the JSON.

Sharing a dump to extend the sensor tables

Entries under observations.properties with a non-empty unmapped array name something nodewitt does not yet recognize:

  • model:<img> — a sensor model token (e.g. a brand-new wh99) absent from the protocol tables.
  • key:<rawKey> — a measurement/push key (e.g. a new hex id or firmware field) that classifyKey cannot map.
  • battery:<rawKey> — a battery key whose encoding none of the decoders can decode.

Attaching the anonymized JSON to a GitHub issue lets maintainers fold the new sensor type into protocol/sensor-models.ts / protocol/hex-ids.ts / protocol/battery.ts.

Note: captureRawFrames (off by default) is required to surface unknown raw measurement keys and undecodable batteries. The Station drops any reading it cannot classify, so those keys only appear in the raw poll/push frames — which the dumper captures (and redacts) only when this flag is on. The unmapped-model signal works without it.

Acknowledgments

The local-poll layer ports alexlenk/ecowitt_local (MIT). The push layer is an independent implementation of Ecowitt's documented push protocol, informed by garbled1/pyecowitt and garbled1/homeassistant_ecowitt (Apache-2.0) as protocol references. See LICENSE.

License

MIT.

版本与发布记录

@apocaliss92/nodewitt 共发布 10 个版本,首次发布于 2026/06/05;过去 12 个月发布了 10 个版本。

版本发布日期距今
1.4.2latest2026/07/024 个月前
1.4.12026/07/024 个月前
1.4.02026/07/024 个月前
1.3.02026/07/014 个月前
1.2.12026/07/014 个月前
1.2.02026/06/304 个月前

关于 @apocaliss92/nodewitt 的常见问题

@apocaliss92/nodewitt 是什么?

Node.js/TypeScript client for Ecowitt weather stations (local poll + push)

如何安装 @apocaliss92/nodewitt?

在项目目录运行 npm install @apocaliss92/nodewitt,或使用 pnpm add / yarn add / bun add。

@apocaliss92/nodewitt 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@apocaliss92/nodewitt 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@apocaliss92/nodewitt 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@apocaliss92/nodewitt 可以商用吗?

@apocaliss92/nodewitt 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@apocaliss92/nodewitt 的下载量有多少?

根据 npm 官方下载统计,@apocaliss92/nodewitt 在 2026/10/07 – 2026/11/05 这 30 天内被下载 2,048 次,日均约 68 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@apocaliss92/nodewitt 还在维护吗?

最新版本 1.4.2 发布于 2026/07/02(4 个月前);至今共发布 10 个版本,其中过去 12 个月发布了 10 个;首次发布于 2026/06/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。