Skip to content

weather-bridge-90005 Integration Guide

Capability Overview

weather-bridge-90005 is integrated with ThinkLink through Source file does not provide it in Source file does not provide it mode. This guide documents the current deployable artifacts and their source-traceable versions.

ItemValue
Projectweather-bridge-90005
VendorSource file does not provide it
Modelweather-bridge-90005
Business CodeNot applicable
Integration ModeSource file does not provide it
ProtocolSource file does not provide it

Model catalog

Search for these models in ThinkLink by name or ID.

Model typeNameid_namePlatform model ID
RPC[WEATHER FETCH] 90005weather_fetch_90005119139033283457025
RPC[WEATHER CONFIG] 90005weather_set_config_90005119139033283457024
TemplateWEATHER-VIRTUAL120034851645452288
Thing Model[WEATHER] Current & Forecast 90005weather_current_90005119139033283457026

Operations Skill

Use create-weather to deploy or debug configuration, fetches, the customMqtt-to-AS forwarder, telemetry, and forecast readback. It protects API keys and identifies the first failed boundary in the data path.

Features and Application Scope

This project covers weather-bridge-90005 through Source file does not provide it. Its capability boundary, fields, and deployable models follow the current descriptor, requirements, and model sources.

Deployment and Data Acquisition

| DTU | Not applicable | | Power | Source file does not provide it | | LoRaWAN Class | Source file does not provide it | | Interface | Source file does not provide it | | Baud Rate | Not applicable |

Telemetry and Register Definitions

The source does not provide a standalone telemetry table; use the current thing models listed in the model inventory.

Parameter Definitions

No project-specific parameters are defined.

Requirements Evidence

The following key facts were extracted from the requirements source; line references support verification.

CategorySource FactSource
Deployment> platform | 90005 | docs/superpowers/specs/2026-06-15-weather-virtual-device-design.mdrequirement/requirements.md:3
Protocol/Database_url string dftValue https://api.openweathermap.orgrequirement/requirements.md:66
Protocol/Data2026-06-15 = OWM current + 5day/3h forecast 3hrequirement/requirements.md:101
Deployment2026-06-16 shared_attrs.forecast 3 3h ~24 + ts 「 」 f3/d1 ts telemetry 35→11 24 PUBLIC SYSADMIN _commitMessagerequirement/requirements.md:105

Thing Model and RPC Code

RPC: [WEATHER FETCH] 90005

id_name: weather_fetch_90005 · ID: 119139033283457025

javascript
// weather_fetch_90005 — 虚拟气象设备「取数」RPC
// 读 device.server_attrs 的气象配置 → axios.get(OpenWeatherMap current + 5day/3h forecast)
// → 归一成 {t: 实时遥测, s: 文本 + forecast 预报序列} → customMqtt 动作发布到固定 topic
//   /v32/{tenant}/tkl/up/customMqtt/{eui}
// 关键约定:遥测 t 只放实时获取的实时值(历史化成趋势);预报放 s.forecast(带时间戳序列,
//   每周期整体刷新、不留历史),供前端把历史遥测曲线 + 未来 3 天预报序列按 ts 拼成连续曲线。
// 由「天气→AS」转发器消费后转成 AS 上行 → 设备 payload_parser 落成遥测(设备化)。
//
// 可被排程(KB-27):无交互表单参数,运行时只读 device.server_attrs + axios。
// 平台把本函数体放进 `async function rpcRunner(){ <body> }` 执行,故 body 内可直接 top-level await;
// 平台提供 axios/device/params/logger/org_params/RPCHelper/Utils/BufferTKL 等脚本全局变量。
//   注:本地 `node` 跑会因 axios 未定义而抛错(axios 由平台注入),仅供 node --check 结构校验。
import { RPCHelper, Utils, BufferTKL } from '#tklHelper';

async function rpc_script({ device, params, alarms, logger, org_params }) {
    const num = (v) => (v == null || isNaN(Number(v)) ? null : Number(v));
    const pct = (v) => (v == null ? null : Math.round(Number(v) * 100)); // OWM pop 0-1 → 0-100%

    const cfg = device?.server_attrs || {};
    const source = cfg.source || 'openweathermap';

    // 缺配置不取数(KB-27/31:绝不空请求)
    if (!cfg.apikey) { logger && logger.systemWarn && logger.systemWarn('weather_fetch: missing apikey'); return null; }
    const hasLatLon = cfg.lat != null && cfg.lon != null;
    if (!hasLatLon && !cfg.city) { logger && logger.systemWarn && logger.systemWarn('weather_fetch: missing lat/lon and city'); return null; }

    // 首期只实现 openweathermap;预留 source 分支(qweather/cma/custom 后续增量)
    if (source !== 'openweathermap') { logger && logger.systemWarn && logger.systemWarn(`weather_fetch: source ${source} not implemented`); return null; }

    const base = (cfg.base_url || 'https://api.openweathermap.org').replace(/\/+$/, '');
    const common = { appid: cfg.apikey, units: cfg.units || 'metric', lang: cfg.lang || 'zh_cn' };
    const loc = hasLatLon ? { lat: cfg.lat, lon: cfg.lon } : { q: cfg.city };

    let curResp, fcResp;
    try {
        curResp = await axios.get(`${base}/data/2.5/weather`, { params: { ...common, ...loc } });
        fcResp = await axios.get(`${base}/data/2.5/forecast`, { params: { ...common, ...loc } });
    } catch (e) {
        logger && logger.systemError && logger.systemError(`weather_fetch http error: ${e && e.message}`, { status: e && e.response && e.response.status });
        return null; // 取数失败/超时/非 200 → 不写脏数据,下个周期重试
    }
    const c = curResp && curResp.data;
    const fd = fcResp && fcResp.data;
    if (!c || !fd) return null;

    const fetchedAt = new Date().toISOString();

    // ── 当前天气(遥测,全部是实时获取的实时值,历史化成趋势)──
    // 遥测只放实时数据:预报不进遥测(预报每周期会变,不应留历史点)。
    const t = {
        temp: num(c.main && c.main.temp),
        feels_like: num(c.main && c.main.feels_like),
        humidity: num(c.main && c.main.humidity),
        pressure: num(c.main && c.main.pressure),
        wind_speed: num(c.wind && c.wind.speed),
        wind_deg: num(c.wind && c.wind.deg),
        clouds: num(c.clouds && c.clouds.all),
        visibility: num(c.visibility),
        rain_1h: num(c.rain && c.rain['1h']) != null ? num(c.rain && c.rain['1h']) : 0,
        weather_code: num(c.weather && c.weather[0] && c.weather[0].id),
    };

    // ── 预报序列(放 shared_attrs,随遥测每周期实时刷新;整体覆盖、不留历史)──
    // 取未来 3 天(72h)内的 OWM 3h 预报点 → 带时间戳的序列,供前端把
    //   「历史遥测曲线(过去真实值)」与「未来 3 天预报序列」按 ts 直接拼成一条连续曲线。
    const list = Array.isArray(fd.list) ? fd.list : [];
    t.pop = list[0] ? pct(list[0].pop) : null; // 临近(当前时刻)降水概率取 forecast[0],属实时遥测

    const nowSec = Math.floor(new Date(fetchedAt).getTime() / 1000);
    const horizonSec = nowSec + 3 * 24 * 3600; // 未来 3 天
    const points = list
        .filter((e) => e && e.dt != null && Number(e.dt) <= horizonSec)
        .map((e) => ({
            ts: new Date(Number(e.dt) * 1000).toISOString(),
            temp: num(e.main && e.main.temp),
            pop: pct(e.pop),
            code: num(e.weather && e.weather[0] && e.weather[0].id),
        }));

    // ── 文本 + 预报序列属性(shared_attrs,不入遥测趋势)──
    const w0 = (c.weather && c.weather[0]) || {};
    const s = {
        weather_main: w0.main != null ? w0.main : null,
        weather_desc: w0.description != null ? w0.description : null,
        city: c.name != null ? c.name : (cfg.city || null),
        source: source,
        fetched_at: fetchedAt,
        // 顶层 key(mergeDeviceAttrs 顶层浅合并 → 每周期整体覆盖该 forecast 对象,永远是最新预报)
        forecast: { fetched_at: fetchedAt, points: points },
    };

    // ── 发布到固定 customMqtt topic,交「天气→AS」转发器转 AS 上行 ──
    return [{ sleepTimeMs: 0, type: 'customMqtt', dnMsg: { eui: device.eui, t: t, s: s } }];
}

// ── 本地结构占位测试(axios 由平台注入,本地不可跑通;仅供 node --check 结构校验)──
rpc_script({
    device: { eui: '0123456789abcdef', tenant_code: 'PUBLIC', server_attrs: { apikey: 'TEST', lat: 36.07, lon: 120.38, source: 'openweathermap', units: 'metric', lang: 'zh_cn' } },
    params: {},
    alarms: [],
    logger: console,
    org_params: {},
}).then((r) => console.log(JSON.stringify(r, null, 2))).catch((e) => console.error(e));

RPC: [WEATHER CONFIG] 90005

id_name: weather_set_config_90005 · ID: 119139033283457024

javascript
// weather_set_config_90005 — 虚拟气象设备「配置」RPC(交互表单,写 server_attrs)
// 把数据源 / apikey / 取数地址 / 定位(经纬度或城市) / 单位 / 语言 / 预报地平线 写进 device.server_attrs。
// weather_fetch 运行时读这些配置取数。表单各项 default_from:"server_attrs" 回填当前值(KB-31 闭环)。
//
// server_attrs 为扁平顶层键(平台 mergeDeviceAttrs 顶层浅合并 → 只写本次提交的键,其余保留,KB-36 安全)。
// 配置 RPC 必须有非空脚本(KB-34):attr_field_name 只供表单回填,写回靠 rpc_script 的 modifyAttrs。
import { RPCHelper, Utils, BufferTKL } from '#tklHelper';

function rpc_script({ device, params, alarms, logger, org_params }) {
    // 读-改-写:从设备现存 server_attrs 播种,再叠加本次提交字段(缺省 null 跳过,KB-31/36)
    const sa = {};
    const set = (k) => { if (params[k] !== undefined && params[k] !== null && params[k] !== '') sa[k] = params[k]; };

    set('source');
    set('apikey');
    set('base_url');
    set('units');
    set('lang');
    set('forecast_window');
    set('city');
    // lat/lon 为数值:0 是合法经度,故只跳过 null/undefined(不按空串判定)
    if (params.lat !== undefined && params.lat !== null) sa.lat = Number(params.lat);
    if (params.lon !== undefined && params.lon !== null) sa.lon = Number(params.lon);

    if (Object.keys(sa).length === 0) return null;
    return [{ sleepTimeMs: 0, type: 'modifyAttrs', dnMsg: { server_attrs: sa } }];
}

// ── 本地结构占位测试(仅供 node --check 结构校验)──
console.log(rpc_script({
    device: { eui: '0123456789abcdef', tenant_code: 'PUBLIC', server_attrs: {} },
    params: { source: 'openweathermap', apikey: 'KEY', base_url: 'https://api.openweathermap.org', units: 'metric', lang: 'zh_cn', forecast_window: '24h', lat: 36.07, lon: 120.38, city: 'Qingdao' },
    alarms: [],
    logger: console,
    org_params: {},
}));

Thing Model: [WEATHER] Current & Forecast 90005

id_name: weather_current_90005 · ID: 119139033283457026

javascript
// weather_current_90005 — 虚拟气象设备遥测物模型(payload_parser_type = CUSTOMER)
// 数据不来自 LoRaWAN 上行,而来自「天气→AS」转发器注入的 AS 上行:
//   weather_fetch RPC 归一出 {t: 实时遥测, s: 文本 + forecast 预报序列} → 转发器置入 userdata.payload = base64(JSON) → AS 上行。
// 本 parser 解 base64 JSON,把 t 落成 telemetry_data(实时值,历史化成趋势)、
//   s 落成 shared_attrs(文本属性 + forecast 未来3天预报序列,每周期刷新、不入遥测趋势)。
// 查看曲线时:历史遥测(过去真实值)+ shared_attrs.forecast.points(未来3天最新预报)按时间戳拼成连续曲线。
//
// CUSTOMER 类型:平台提供 msg/Buffer/device,并调用解析函数;
//   body 必须 return {telemetry_data, shared_attrs}。无需 LoRaWAN bytes 解码(非二进制报文)。
import {BufferTKL, PayloadParser, RPCHelper, Utils} from '#tklHelper';

function payload_parser({ device, msg, thingModelId, noticeAttrs }) {
    let o;
    try {
        const raw = msg && msg.userdata && msg.userdata.payload;
        if (raw == null) return null;
        // 转发器以 base64(JSON.stringify({t,s})) 注入;兼容直接传对象的情形
        o = typeof raw === 'string' ? JSON.parse(Buffer.from(raw, 'base64').toString('utf8')) : raw;
    } catch (e) {
        return null; // 解析失败不写脏数据
    }
    if (!o || typeof o !== 'object') return null;
    let result = {
        telemetry_data: o.t || null,
        shared_attrs: o.s || null,
    };
    // ── rpcHook (设备对接通用规则) ──
    if (typeof RPCHelper.buildRpcHookAction === "function") {
      const __a = RPCHelper.buildRpcHookAction({ device, msg, thingModelId, idName: "weather_current_90005", name: "[WEATHER] Current & Forecast 90005", parsed: { telemetry_data: result.telemetry_data, server_attrs: result.server_attrs, shared_attrs: result.shared_attrs } });
      if (__a) { result.actions = result.actions || []; result.actions.push(__a); }
    }
    return result;
}

// ── 本地测试(仅供 node --check + 本地跑通验证)──
const _demo = { t: { temp: 21.3, humidity: 58, pressure: 1012, wind_speed: 3.1, weather_code: 800, pop: 10 }, s: { weather_main: 'Clear', weather_desc: '晴', city: 'Qingdao', source: 'openweathermap', fetched_at: '2026-06-15T00:00:00.000Z', forecast: { fetched_at: '2026-06-15T00:00:00.000Z', points: [{ ts: '2026-06-15T03:00:00.000Z', temp: 20.8, pop: 10, code: 800 }, { ts: '2026-06-15T06:00:00.000Z', temp: 24.0, pop: 0, code: 801 }] } } };
console.log(payload_parser({
    device: { eui: '0123456789abcdef', tenant_code: 'PUBLIC' },
    msg: { userdata: { port: 1, payload: Buffer.from(JSON.stringify(_demo)).toString('base64') } },
    thingModelId: '119139033283457026',
    noticeAttrs: {},
}));

Thing Model Field Definitions

Thing ModelFieldTypeUnitName
weather_current_90005tempnumber°CTemperature
weather_current_90005feels_likenumber°CFeels Like
weather_current_90005humiditynumber%Humidity
weather_current_90005pressurenumberhPaPressure
weather_current_90005wind_speednumberm/sWind Speed
weather_current_90005wind_degnumber°Wind Direction
weather_current_90005cloudsnumber%Cloudiness
weather_current_90005visibilitynumbermVisibility
weather_current_90005rain_1hnumbermmRain (1h)
weather_current_90005popnumber%Precipitation Probability
weather_current_90005weather_codenumberWeather Code

RPC Parameter Definitions

RPCParameterTypeDescriptionDefaultUnit
weather_set_config_90005sourcestringData Sourceopenweathermap
weather_set_config_90005apikeystringAPI Key
weather_set_config_90005base_urlstringBase URLhttps://api.openweathermap.org
weather_set_config_90005latnumberLatitude°
weather_set_config_90005lonnumberLongitude°
weather_set_config_90005citystringCity
weather_set_config_90005unitsstringUnitsmetric
weather_set_config_90005langstringLanguagezh_cn
weather_set_config_90005forecast_windowstringForecast Window24h

Trigger Definitions

No project-specific triggers are defined.

Third-Party Platform Subscription

MQTT Topic: /v32/{Organization Account}/tkl/up/telemetry/{eui}

json
{
  "eui": "6353012af10a9331",
  "active_time": "2026-07-13T08:35:48.000Z",
  "thingModelId": "Use the platform model ID from the model inventory",
  "thingModelIdName": "Use the current telemetry model id_name",
  "telemetry_data": {
    "snr": 13.5,
    "rssi": -51,
    "battery": 3.37
  }
}

Template Selection and Verification

Search ThinkLink by project name, business code, or the id_name and ID values in the model inventory. After deployment, use real uplinks to verify fields, units, RPCs, and trigger behavior.

Supplementary Notes

Requirements and decisions: requirement/requirements.md.