找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 8|回复: 0

AIWROK天气API采集定时监控

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
AIWROK天气API采集定时监控

QQ截图20261020091503.png


QQ截图20261020091442.png


QQ截图20261019088881758.png

  1. /**
  2. * 案例16:天气预报定时采集入库(纯 printl 日志版,输出到 H5 日志终端)
  3. *
  4. *经典实用案例"天气API采集/定时监控",转换成 AIWROK 实现:
  5. *   okHttp 请求 Open-Meteo 免费天气接口(https 纯 JSON,无需注册、无需Key)
  6. *   → 解析当前温度/湿度/风速/天气码 → SQLite 入库(weather_records 表)
  7. *   → printl 日志(由 LogBridge 打进 H5 终端,紫色主题,教学演示更漂亮)
  8. *
  9. * 与案例15 同一套路:"网络API + 数据库 + 定时器"型自动化,
  10. * 全程不找图、不点击、不依赖无障碍/代理模式,也没有悬浮窗,手机后台长期挂机即可。
  11. *
  12. * 选 Open-Meteo 接口的原因:
  13. * 1. 完全免费、不用申请 Key,返回标准 JSON,最适合教学演示;
  14. * 2. 一次请求就能拿到温度/湿度/风速/天气码多个字段,入库后能做趋势分析;
  15. * 3. 换城市只改配置区经纬度两个数字,逻辑一行不用动。
  16. *
  17. * 报警规则(改配置区数字即可,触发时日志打 ⚠):
  18. * - 天气码 >= 45(雾/雨/雪/雷阵雨等) → 恶劣天气报警;
  19. * - 与上一轮温度差绝对值 >= 温度骤变阈值 → 温度骤变报警。
  20. */

  21. // ========== 配置区(改这里) ==========
  22. var 城市名称 = "上海";
  23. var 纬度 = 31.23;              // 换城市改这两个数字(北京 39.90/116.41,广州 23.13/113.26)
  24. var 经度 = 121.47;
  25. var 采集间隔秒 = 30;            // 每 30 秒采一轮(录像教学用;长期挂机可改回 300)
  26. var 温度骤变阈值 = 3.0;         // 与上一轮温差 >= 3℃ 触发报警
  27. var 恶劣天气码下限 = 45;        // 天气码 >= 45 视为恶劣天气(雾/雨/雪/雷暴)
  28. var SQLite路径 = "/sdcard/天气预报.db";
  29. var 表名 = "weather_records";

  30. // ========== 全局变量顶部声明 ==========
  31. var gStop = false;
  32. var 数据库连接 = null;
  33. var 数据库可用 = false;
  34. var 累计入库条数 = 0;
  35. var 上一轮温度 = null;          // 首轮没有对比基准,不报骤变

  36. // ========== importClass 只能放最外层 ==========
  37. importClass(android.database.sqlite.SQLiteDatabase);
  38. importClass(java.io.File);

  39. // ========== 工具函数(先定义后调用) ==========

  40. // 连接 SQLite(零配置;如需远程库可仿照案例14扩展 mysql 分支)
  41. function dbConnect() {
  42.     try {
  43.         数据库连接 = SQLiteDatabase.openOrCreateDatabase(new File(SQLite路径), null);
  44.         数据库可用 = true;
  45.         printl("[DB] ✅ SQLite 已打开: " + SQLite路径);
  46.     } catch (e) {
  47.         数据库可用 = false;
  48.         printl("[DB] ⚠ 打开失败(本轮只打印不入库): " + e);
  49.     }
  50. }

  51. // 建表(存在则跳过):temperature/humidity/wind/weather_code/collected_at
  52. function dbEnsureTable() {
  53.     if (!数据库可用) return;
  54.     try {
  55.         var sql = "CREATE TABLE IF NOT EXISTS " + 表名 + " (" +
  56.             "id INTEGER PRIMARY KEY AUTOINCREMENT," +
  57.             "temperature REAL, humidity REAL, wind REAL, weather_code INTEGER, collected_at INTEGER)";
  58.         数据库连接.execSQL(sql);
  59.         printl("[DB] ✅ 表已就绪: " + 表名);
  60.     } catch (e) {
  61.         printl("[DB] ⚠ 建表失败: " + e);
  62.         数据库可用 = false;
  63.     }
  64. }

  65. // 单轮天气数据入库
  66. function dbInsertWeather(温度, 湿度, 风速, 天气码) {
  67.     if (!数据库可用) return false;
  68.     try {
  69.         var stmt = 数据库连接.compileStatement("INSERT INTO " + 表名 +
  70.             " (temperature, humidity, wind, weather_code, collected_at) VALUES (?,?,?,?,?)");
  71.         stmt.bindDouble(1, 温度);
  72.         stmt.bindDouble(2, 湿度);
  73.         stmt.bindDouble(3, 风速);
  74.         stmt.bindLong(4, 天气码);
  75.         stmt.bindLong(5, time.nowStamp());   // nowStamp=毫秒数字;time.now()是格式化字符串不能bindLong
  76.         stmt.executeInsert();
  77.         stmt.close();
  78.         return true;
  79.     } catch (e) {
  80.         printl("[DB] ❌ 入库失败: " + e);
  81.         return false;
  82.     }
  83. }

  84. function dbClose() {
  85.     if (数据库可用 && 数据库连接 != null) {
  86.         try { 数据库连接.close(); } catch (e) {}
  87.         数据库连接 = null;
  88.     }
  89. }

  90. // WMO 天气码 → 中文描述(接口返回的是数字码,入库后也靠它翻译)
  91. function 天气码描述(code) {
  92.     if (code === 0) return "晴";
  93.     if (code === 1) return "基本晴";
  94.     if (code === 2) return "局部多云";
  95.     if (code === 3) return "阴天";
  96.     if (code === 45 || code === 48) return "雾";
  97.     if (code >= 51 && code <= 57) return "毛毛雨";
  98.     if (code >= 61 && code <= 67) return "雨";
  99.     if (code >= 71 && code <= 77) return "雪";
  100.     if (code >= 80 && code <= 82) return "阵雨";
  101.     if (code === 85 || code === 86) return "阵雪";
  102.     if (code >= 95) return "雷暴";
  103.     return "未知(" + code + ")";
  104. }

  105. // 拉取一次当前天气:返回对象 {温度,湿度,风速,天气码},失败返回 null
  106. function 拉取天气() {
  107.     var url = "https://api.open-meteo.com/v1/forecast?latitude=" + 纬度 +
  108.         "&longitude=" + 经度 +
  109.         "&current=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m" +
  110.         "&timezone=Asia/Shanghai";

  111.     try {
  112.         printl("📡 正在请求天气接口(" + 城市名称 + " Open-Meteo 免Key)...");
  113.         var http = new okHttp();
  114.         http.setTimeOut(5000, 8000, 5000);   // 必须设超时,防网络挂死(踩坑经验)
  115.         printl("📡 等待接口返回(最长8秒)...");
  116.         var text = "" + http.get(url);
  117.         printl("📡 接口已返回,开始解析 JSON...");
  118.         var obj = JSON.parse(text);
  119.         if (!obj || !obj.current) {
  120.             printl("[API] ⚠ 接口无数据: " + text.substring(0, 120));
  121.             return null;
  122.         }
  123.         var cur = obj.current;
  124.         var 温度 = parseFloat(cur.temperature_2m);
  125.         var 湿度 = parseFloat(cur.relative_humidity_2m);
  126.         var 风速 = parseFloat(cur.wind_speed_10m);
  127.         var 天气码 = parseInt(cur.weather_code);
  128.         if (isNaN(温度) || isNaN(天气码)) {
  129.             printl("[API] ⚠ 关键字段解析失败: " + text.substring(0, 120));
  130.             return null;
  131.         }
  132.         if (isNaN(湿度)) 湿度 = 0;
  133.         if (isNaN(风速)) 风速 = 0;
  134.         printl("✅ 解析完成:" + 城市名称 + " 温度 " + 温度.toFixed(1) + "℃ 湿度 " + 湿度 +
  135.             "% 风速 " + 风速 + "km/h 天气 " + 天气码描述(天气码));
  136.         return { 温度: 温度, 湿度: 湿度, 风速: 风速, 天气码: 天气码 };
  137.     } catch (e) {
  138.         printl("[API] ❌ 拉取失败: " + e);
  139.         return null;
  140.     }
  141. }

  142. // 逐条完整打印采集结果(演示用:接口返回的每个字段都打出来)
  143. function 打印采集结果(数据) {
  144.     printl("📋 本轮采集结果(" + 城市名称 + "):");
  145.     printl("   [1] 温度   : " + 数据.温度.toFixed(1) + " ℃");
  146.     printl("   [2] 湿度   : " + 数据.湿度 + " %");
  147.     printl("   [3] 风速   : " + 数据.风速.toFixed(1) + " km/h");
  148.     printl("   [4] 天气码 : " + 数据.天气码 + "(" + 天气码描述(数据.天气码) + ")");
  149.     printl("   [5] 采集时间: " + time.StampToTime(time.nowStamp()));
  150. }

  151. // 报警检查:恶劣天气码 或 与上一轮温差超阈值,触发时打 ⚠ 日志
  152. function 检查报警(数据) {
  153.     // 规则1:恶劣天气码报警
  154.     if (数据.天气码 >= 恶劣天气码下限) {
  155.         printl("⚠ 报警:" + 城市名称 + " 恶劣天气(天气码 " + 数据.天气码 + " " +
  156.             天气码描述(数据.天气码) + "),阈值 >= " + 恶劣天气码下限);
  157.     }
  158.     // 规则2:与上一轮温差超阈值报警(首轮无基准不报)
  159.     if (上一轮温度 != null && Math.abs(数据.温度 - 上一轮温度) >= 温度骤变阈值) {
  160.         var 差值 = 数据.温度 - 上一轮温度;
  161.         var 符号 = 差值 >= 0 ? "+" : "";
  162.         printl("⚠ 报警:温度骤变 " + 符号 + 差值.toFixed(1) + "℃(上一轮 " +
  163.             上一轮温度.toFixed(1) + "℃ → 本轮 " + 数据.温度.toFixed(1) + "℃),阈值 " + 温度骤变阈值 + "℃");
  164.     }
  165.     上一轮温度 = 数据.温度;
  166. }

  167. // ========== 主流程 ==========
  168. printl("===== 案例16 天气定时采集入库 启动 =====");

  169. // 步骤0:连库建表
  170. dbConnect();
  171. dbEnsureTable();
  172. printl("⚙ 配置:" + 城市名称 + "(" + 纬度 + "," + 经度 + ") · 采集间隔 " + 采集间隔秒 +
  173.     " 秒 · 骤变阈值 " + 温度骤变阈值 + "℃ · 恶劣天气码 >= " + 恶劣天气码下限);

  174. // 停止按钮清理(顺序:先置标志,stopAllRuning 放最后)
  175. event.onStopBtnEvent(function () {
  176.     gStop = true;
  177.     dbClose();
  178.     runTime.stopAllRuning();
  179. });

  180. // 步骤1:定时循环采集
  181. var 轮次 = 0;
  182. while (!gStop) {
  183.     轮次++;
  184.     printl("\n---- 第 " + 轮次 + " 轮采集 ----");

  185.     var 天气数据 = 拉取天气();
  186.     if (天气数据 != null) {
  187.         // 1) 逐条完整打印采集结果(演示重点:接口返回的字段全打出来)
  188.         打印采集结果(天气数据);

  189.         // 2) 入库
  190.         printl("💾 写入数据库(表: " + 表名 + ")...");
  191.         if (dbInsertWeather(天气数据.温度, 天气数据.湿度, 天气数据.风速, 天气数据.天气码)) {
  192.             累计入库条数++;
  193.             printl("   ✓ 已入库: " + 城市名称 + " 温度 " + 天气数据.温度.toFixed(1) + "℃ 天气 " +
  194.                 天气码描述(天气数据.天气码));
  195.             printl("[DB] 本轮入库 1 条 | 累计 " + 累计入库条数 + " 条 | 库文件: " + SQLite路径);
  196.         } else {
  197.             printl("   ✗ 入库失败,本轮数据未落库");
  198.         }

  199.         // 3) 报警检查
  200.         检查报警(天气数据);
  201.     } else {
  202.         printl("⚠ 本轮无数据,跳过入库(" + time.StampToTime(time.nowStamp()) + ")");
  203.     }

  204.     // 分片等待下一轮:每 5 秒打一条进度日志(画面不空,方便观察/录像)
  205.     printl("⏳ 本轮全部完成," + 采集间隔秒 + " 秒后自动开始下一轮");
  206.     var 剩余 = 采集间隔秒;
  207.     while (剩余 > 0 && !gStop) {
  208.         var 步长 = 剩余 >= 5 ? 5 : 剩余;
  209.         printl("⏳ 等待下一轮...剩余 " + 剩余 + " 秒(期间脚本在待机,到点自动拉天气→入库)");
  210.         sleep.second(秒 = 步长);
  211.         剩余 = 剩余 - 步长;
  212.     }
  213. }

  214. dbClose();
  215. printl("===== 流程结束:共 " + 轮次 + " 轮,累计入库 " + 累计入库条数 + " 条 =====");
复制代码


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|

GMT+8, 2026-10-21 07:52

宣传软件 · 信息发布软件 · b2b软件 · 广告发布软件

快速回复 返回顶部 返回列表