|
|
AIWROK天气API采集定时监控
- /**
- * 案例16:天气预报定时采集入库(纯 printl 日志版,输出到 H5 日志终端)
- *
- *经典实用案例"天气API采集/定时监控",转换成 AIWROK 实现:
- * okHttp 请求 Open-Meteo 免费天气接口(https 纯 JSON,无需注册、无需Key)
- * → 解析当前温度/湿度/风速/天气码 → SQLite 入库(weather_records 表)
- * → printl 日志(由 LogBridge 打进 H5 终端,紫色主题,教学演示更漂亮)
- *
- * 与案例15 同一套路:"网络API + 数据库 + 定时器"型自动化,
- * 全程不找图、不点击、不依赖无障碍/代理模式,也没有悬浮窗,手机后台长期挂机即可。
- *
- * 选 Open-Meteo 接口的原因:
- * 1. 完全免费、不用申请 Key,返回标准 JSON,最适合教学演示;
- * 2. 一次请求就能拿到温度/湿度/风速/天气码多个字段,入库后能做趋势分析;
- * 3. 换城市只改配置区经纬度两个数字,逻辑一行不用动。
- *
- * 报警规则(改配置区数字即可,触发时日志打 ⚠):
- * - 天气码 >= 45(雾/雨/雪/雷阵雨等) → 恶劣天气报警;
- * - 与上一轮温度差绝对值 >= 温度骤变阈值 → 温度骤变报警。
- */
- // ========== 配置区(改这里) ==========
- var 城市名称 = "上海";
- var 纬度 = 31.23; // 换城市改这两个数字(北京 39.90/116.41,广州 23.13/113.26)
- var 经度 = 121.47;
- var 采集间隔秒 = 30; // 每 30 秒采一轮(录像教学用;长期挂机可改回 300)
- var 温度骤变阈值 = 3.0; // 与上一轮温差 >= 3℃ 触发报警
- var 恶劣天气码下限 = 45; // 天气码 >= 45 视为恶劣天气(雾/雨/雪/雷暴)
- var SQLite路径 = "/sdcard/天气预报.db";
- var 表名 = "weather_records";
- // ========== 全局变量顶部声明 ==========
- var gStop = false;
- var 数据库连接 = null;
- var 数据库可用 = false;
- var 累计入库条数 = 0;
- var 上一轮温度 = null; // 首轮没有对比基准,不报骤变
- // ========== importClass 只能放最外层 ==========
- importClass(android.database.sqlite.SQLiteDatabase);
- importClass(java.io.File);
- // ========== 工具函数(先定义后调用) ==========
- // 连接 SQLite(零配置;如需远程库可仿照案例14扩展 mysql 分支)
- function dbConnect() {
- try {
- 数据库连接 = SQLiteDatabase.openOrCreateDatabase(new File(SQLite路径), null);
- 数据库可用 = true;
- printl("[DB] ✅ SQLite 已打开: " + SQLite路径);
- } catch (e) {
- 数据库可用 = false;
- printl("[DB] ⚠ 打开失败(本轮只打印不入库): " + e);
- }
- }
- // 建表(存在则跳过):temperature/humidity/wind/weather_code/collected_at
- function dbEnsureTable() {
- if (!数据库可用) return;
- try {
- var sql = "CREATE TABLE IF NOT EXISTS " + 表名 + " (" +
- "id INTEGER PRIMARY KEY AUTOINCREMENT," +
- "temperature REAL, humidity REAL, wind REAL, weather_code INTEGER, collected_at INTEGER)";
- 数据库连接.execSQL(sql);
- printl("[DB] ✅ 表已就绪: " + 表名);
- } catch (e) {
- printl("[DB] ⚠ 建表失败: " + e);
- 数据库可用 = false;
- }
- }
- // 单轮天气数据入库
- function dbInsertWeather(温度, 湿度, 风速, 天气码) {
- if (!数据库可用) return false;
- try {
- var stmt = 数据库连接.compileStatement("INSERT INTO " + 表名 +
- " (temperature, humidity, wind, weather_code, collected_at) VALUES (?,?,?,?,?)");
- stmt.bindDouble(1, 温度);
- stmt.bindDouble(2, 湿度);
- stmt.bindDouble(3, 风速);
- stmt.bindLong(4, 天气码);
- stmt.bindLong(5, time.nowStamp()); // nowStamp=毫秒数字;time.now()是格式化字符串不能bindLong
- stmt.executeInsert();
- stmt.close();
- return true;
- } catch (e) {
- printl("[DB] ❌ 入库失败: " + e);
- return false;
- }
- }
- function dbClose() {
- if (数据库可用 && 数据库连接 != null) {
- try { 数据库连接.close(); } catch (e) {}
- 数据库连接 = null;
- }
- }
- // WMO 天气码 → 中文描述(接口返回的是数字码,入库后也靠它翻译)
- function 天气码描述(code) {
- if (code === 0) return "晴";
- if (code === 1) return "基本晴";
- if (code === 2) return "局部多云";
- if (code === 3) return "阴天";
- if (code === 45 || code === 48) return "雾";
- if (code >= 51 && code <= 57) return "毛毛雨";
- if (code >= 61 && code <= 67) return "雨";
- if (code >= 71 && code <= 77) return "雪";
- if (code >= 80 && code <= 82) return "阵雨";
- if (code === 85 || code === 86) return "阵雪";
- if (code >= 95) return "雷暴";
- return "未知(" + code + ")";
- }
- // 拉取一次当前天气:返回对象 {温度,湿度,风速,天气码},失败返回 null
- function 拉取天气() {
- var url = "https://api.open-meteo.com/v1/forecast?latitude=" + 纬度 +
- "&longitude=" + 经度 +
- "¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m" +
- "&timezone=Asia/Shanghai";
- try {
- printl("📡 正在请求天气接口(" + 城市名称 + " Open-Meteo 免Key)...");
- var http = new okHttp();
- http.setTimeOut(5000, 8000, 5000); // 必须设超时,防网络挂死(踩坑经验)
- printl("📡 等待接口返回(最长8秒)...");
- var text = "" + http.get(url);
- printl("📡 接口已返回,开始解析 JSON...");
- var obj = JSON.parse(text);
- if (!obj || !obj.current) {
- printl("[API] ⚠ 接口无数据: " + text.substring(0, 120));
- return null;
- }
- var cur = obj.current;
- var 温度 = parseFloat(cur.temperature_2m);
- var 湿度 = parseFloat(cur.relative_humidity_2m);
- var 风速 = parseFloat(cur.wind_speed_10m);
- var 天气码 = parseInt(cur.weather_code);
- if (isNaN(温度) || isNaN(天气码)) {
- printl("[API] ⚠ 关键字段解析失败: " + text.substring(0, 120));
- return null;
- }
- if (isNaN(湿度)) 湿度 = 0;
- if (isNaN(风速)) 风速 = 0;
- printl("✅ 解析完成:" + 城市名称 + " 温度 " + 温度.toFixed(1) + "℃ 湿度 " + 湿度 +
- "% 风速 " + 风速 + "km/h 天气 " + 天气码描述(天气码));
- return { 温度: 温度, 湿度: 湿度, 风速: 风速, 天气码: 天气码 };
- } catch (e) {
- printl("[API] ❌ 拉取失败: " + e);
- return null;
- }
- }
- // 逐条完整打印采集结果(演示用:接口返回的每个字段都打出来)
- function 打印采集结果(数据) {
- printl("📋 本轮采集结果(" + 城市名称 + "):");
- printl(" [1] 温度 : " + 数据.温度.toFixed(1) + " ℃");
- printl(" [2] 湿度 : " + 数据.湿度 + " %");
- printl(" [3] 风速 : " + 数据.风速.toFixed(1) + " km/h");
- printl(" [4] 天气码 : " + 数据.天气码 + "(" + 天气码描述(数据.天气码) + ")");
- printl(" [5] 采集时间: " + time.StampToTime(time.nowStamp()));
- }
- // 报警检查:恶劣天气码 或 与上一轮温差超阈值,触发时打 ⚠ 日志
- function 检查报警(数据) {
- // 规则1:恶劣天气码报警
- if (数据.天气码 >= 恶劣天气码下限) {
- printl("⚠ 报警:" + 城市名称 + " 恶劣天气(天气码 " + 数据.天气码 + " " +
- 天气码描述(数据.天气码) + "),阈值 >= " + 恶劣天气码下限);
- }
- // 规则2:与上一轮温差超阈值报警(首轮无基准不报)
- if (上一轮温度 != null && Math.abs(数据.温度 - 上一轮温度) >= 温度骤变阈值) {
- var 差值 = 数据.温度 - 上一轮温度;
- var 符号 = 差值 >= 0 ? "+" : "";
- printl("⚠ 报警:温度骤变 " + 符号 + 差值.toFixed(1) + "℃(上一轮 " +
- 上一轮温度.toFixed(1) + "℃ → 本轮 " + 数据.温度.toFixed(1) + "℃),阈值 " + 温度骤变阈值 + "℃");
- }
- 上一轮温度 = 数据.温度;
- }
- // ========== 主流程 ==========
- printl("===== 案例16 天气定时采集入库 启动 =====");
- // 步骤0:连库建表
- dbConnect();
- dbEnsureTable();
- printl("⚙ 配置:" + 城市名称 + "(" + 纬度 + "," + 经度 + ") · 采集间隔 " + 采集间隔秒 +
- " 秒 · 骤变阈值 " + 温度骤变阈值 + "℃ · 恶劣天气码 >= " + 恶劣天气码下限);
- // 停止按钮清理(顺序:先置标志,stopAllRuning 放最后)
- event.onStopBtnEvent(function () {
- gStop = true;
- dbClose();
- runTime.stopAllRuning();
- });
- // 步骤1:定时循环采集
- var 轮次 = 0;
- while (!gStop) {
- 轮次++;
- printl("\n---- 第 " + 轮次 + " 轮采集 ----");
- var 天气数据 = 拉取天气();
- if (天气数据 != null) {
- // 1) 逐条完整打印采集结果(演示重点:接口返回的字段全打出来)
- 打印采集结果(天气数据);
- // 2) 入库
- printl("💾 写入数据库(表: " + 表名 + ")...");
- if (dbInsertWeather(天气数据.温度, 天气数据.湿度, 天气数据.风速, 天气数据.天气码)) {
- 累计入库条数++;
- printl(" ✓ 已入库: " + 城市名称 + " 温度 " + 天气数据.温度.toFixed(1) + "℃ 天气 " +
- 天气码描述(天气数据.天气码));
- printl("[DB] 本轮入库 1 条 | 累计 " + 累计入库条数 + " 条 | 库文件: " + SQLite路径);
- } else {
- printl(" ✗ 入库失败,本轮数据未落库");
- }
- // 3) 报警检查
- 检查报警(天气数据);
- } else {
- printl("⚠ 本轮无数据,跳过入库(" + time.StampToTime(time.nowStamp()) + ")");
- }
- // 分片等待下一轮:每 5 秒打一条进度日志(画面不空,方便观察/录像)
- printl("⏳ 本轮全部完成," + 采集间隔秒 + " 秒后自动开始下一轮");
- var 剩余 = 采集间隔秒;
- while (剩余 > 0 && !gStop) {
- var 步长 = 剩余 >= 5 ? 5 : 剩余;
- printl("⏳ 等待下一轮...剩余 " + 剩余 + " 秒(期间脚本在待机,到点自动拉天气→入库)");
- sleep.second(秒 = 步长);
- 剩余 = 剩余 - 步长;
- }
- }
- dbClose();
- printl("===== 流程结束:共 " + 轮次 + " 轮,累计入库 " + 累计入库条数 + " 条 =====");
复制代码
|
|