cukierrro/Straznik

GitHub: cukierrro/Straznik

面向波兰的非官方多源融合空中威胁早期预警系统,通过加权评分与级联传播机制在 Android 与 Web 端提供实时警戒通知。

Stars: 0 | Forks: 0

Strażnik # 守护者 **非官方的早期空中威胁预警** 融合针对波兰(特别是优先关注东部边境)的多个独立信号。 没有任何单一信号是决定性的——只有组合才能提高预警的可信度。 ### 📖 [**包含截图的完整用户手册 →**](https://cukierrro.github.io/Straznik/)
## 功能说明 应用程序从多个独立来源收集信号,为其分配分数,并针对每个省份在 30 分钟的窗口内分别进行求和。当达到 **≥ 2 分** 时,触发“提高警戒”(静默通知);达到 **≥ 4 分** 时,触发带有警报声的响亮警报。UI 始终显示完整的细分信息:包含哪些信号、来源以及具体分数。 | 信号 | 条件 | 分数 | |---|---|---| | **NEPTUN** | 物体(无人机/导弹/弹道导弹/航空炸弹/MiG-31K)航向指向波兰边境,距离 < 100 公里,可信度 **高** | **+3** | | **NEPTUN** | 同上,可信度 中/低 | **+1.5** | | **NEPTUN** | 与波兰接壤的乌克兰州发布官方空中警报 | **+1** | | **媒体/RSS** | 特定省份的媒体中出现关键词(警报、空袭、无人机等) | **+2** | | **RCB** | gov.pl/web/rcb 上发布新通告 | **+2** | | **ADS-B** | 特定省份上空的军机数量 > 过去 7 天 baseline 的 2 倍 | **+1** | | **PAŻP** | 特定省份上空出现新的活动区域(TSA/TRA/D) | **+1** | | **波罗的海国家(LT/LV/EE)媒体** | 波罗的海媒体报道的空中事件 → 影响波德拉谢省 + 瓦尔米亚-马祖里省 | **+1** | 媒体分类分为两个层级(`textmatch.py`):**强** 词(“鸣响”、“空中警报”、“击落”、“领空侵犯”等)单独即可触发;**弱** 词(“警报器”、“无人机”、“火箭”等)需要 ≥2 次不同的匹配;而行政管理/演习相关的上下文(“更换警报器”、“招标”、“警报器测试”等)将排除匹配。每类来源对总分的贡献是有限制的(媒体 ≤2,RCB ≤2,ADS-B ≤1,PAŻP ≤1)——关于同一事件的五篇文章仍然算作一次确认。冗余信号会在 UI 中显示,但其分数会被划掉。 传播是**级联**的:总分 ≥ 2 分的区域会将 40% 的分数传递给相邻区域,相邻区域再将该分数的 40% 传递给它们的邻居,依此类推。这通过最短路径(BFS)计算,直到贡献降至 0.1 分以下。因此,卢布林省 5 分的警报分布如下:相邻区域 +2.0(黄色阈值),第二圈 +0.8,第三圈 +0.3,第四圈 +0.1。东部的威胁会提高全国的警戒级别,但与距离成正比。每个贡献都是一个独立的信号“来自 X 省的转移(...,第 2 圈)”,因此永远不会与该区域本身的信号混淆。 ## 架构 ``` [Neptun WS] ──┐ ┌─ mapa 3D (przeglądarka) [RSS media] ──┤ backend FastAPI (PC) ├─ aplikacja Android (APK) [RCB gov.pl]──┼─► fuzja punktowa ─► SQLite ─► API ┤ [ADS-B mil] ──┤ progi 2 / 4 pkt ├─ ntfy (telefon) [PAŻP AUP*] ──┘ └─ Telegram / Web Push ``` Android 应用程序**默认在无后端的情况下运行**:`frontend/engine.js` 是融合逻辑在 JS 中的镜像副本,并且来自 WebView 的原生 HTTP 请求绕过了 CORS。后端是可选的,提供完整的 SQLite 日志、全天候的 ADS-B baseline、PAŻP 层以及 ntfy/Telegram/Web Push 通知。 \* PAŻP:airspace.pansa.pl 没有公开的 API 文档,但其地图使用公开的 GeoJSON 进行数据填充 —— `/map-configuration/uup` 和 `/map-configuration/aup`(地址由 `/meta/configuration` 指定)。采集器从那里获取区域的几何形状、预订窗口和高度限制,并通过基于区域质心的 point-in-polygon 算法计算其所属省份。 ## 运行后端(Windows) ``` cd backend py -m pip install -r requirements.txt copy .env.example .env # uzupełnij NTFY_TOPIC itd. py -m uvicorn app.main:app --host 0.0.0.0 --port 8600 --app-dir . ``` Dashboard:`http://localhost:8600` · API:`/api/state`,`/api/health`,`/api/docs` 在没有服务器的情况下测试 Neptun 层: ``` cd backend py -m app.cli neptun # strumień WS na żywo z oceną odległości/kursu do granicy PL py -m app.cli score # jednorazowy snapshot REST ``` 测试端到端融合: ``` curl -X POST http://localhost:8600/api/test-signal -H "Content-Type: application/json" -d "{\"voivodeship\":\"lubelskie\",\"points\":2.5,\"title\":\"test\"}" ``` 浏览器内嵌模式:`http://localhost:8600/?standalone=1` ## 通知 - **ntfy(推荐用于手机):** 安装 ntfy 应用程序,想一个长的随机主题,将其输入到 `.env`(`NTFY_TOPIC=`)中,并在应用程序中订阅。高等级消息将以 `urgent` 优先级发送。 - **Telegram:** `@BotFather` → `/newbot` → 将 token 放入 `.env`;向机器人发送消息,从 `https://api.telegram.org/bot/getUpdates` 读取 `chat_id`。 - **Web Push:** dashboard 上的 🔔 按钮(需要 `http://localhost` 或 HTTPS)。 ## 签署发布版 每个 APK 都必须签名,并且 Android 仅在更新使用与已安装版本**相同的密钥**签名时才会接受。Debug 构建使用公开已知密码(`android`)的 `debug.keystore` 密钥,工具可以重新生成它——无法依靠它来维持更新连续性,并且此时应用程序启用了 `debuggable` 标志并信任用户安装的证书。 **密钥只需创建一次。** 丢失它意味着用户在不卸载应用程序(并丢失设置)的情况下将无法安装任何后续版本,因此请备份文件并将密码保存在密码管理器中。 ``` cd android-app/android "C:/Program Files/Android/Android Studio/jbr/bin/keytool" -genkeypair -v -keystore straznik-release.jks -alias straznik -keyalg RSA -keysize 4096 -validity 10000 ``` 该命令将要求输入密码(两次)和所有者信息——填入项目名称即可,其余部分可以留空。然后: ``` cp keystore.properties.example keystore.properties ``` ……并在其中输入您的密码。文件 `keystore.properties`、`*.jks` 和 `*.keystore` 已包含在 `.gitignore` 中,因此不会进入代码库。 构建签名发布版: ``` cd android-app/android JAVA_HOME="C:/Program Files/Android/Android Studio/jbr" ./gradlew assembleRelease ``` 结果:`app/build/outputs/apk/release/app-release.apk`。验证签名: ``` "$ANDROID_HOME/build-tools/37.0.0/apksigner" verify --print-certs -v app/build/outputs/apk/release/app-release.apk ``` 如果没有 `keystore.properties`,发布版构建仍会执行,但 APK **不会被签名**——这是有意为之,以确保发布版永远不会带有 debug 密钥。 ## 重新构建 APK ``` cd android-app rm -rf www && cp -r ../frontend www && rm www/sw.js npx cap sync android cd android && JAVA_HOME="C:/Program Files/Android/Android Studio/jbr" ./gradlew assembleDebug ``` 结果:`android-app/android/app/build/outputs/apk/debug/app-debug.apk` (作为 `Straznik.apk` 复制到根目录)。 ## 结构 - `backend/app/collectors/` — neptun.py (WS+REST), rss_media.py, rcb.py, adsb.py, pansa.py - `backend/app/fusion.py` — 评分引擎(透明直观,非 ML) - `backend/app/geo.py` — 边界参考点、haversine 公式、航向评估 - `backend/data/straznik.db` — SQLite:完整的信号日志(时间戳、来源、分数、省份) - `frontend/` — 3D MapLibre GL 地图、信号面板、图例、“关于应用”屏幕 - `frontend/engine.js` — APK 的内置引擎(后端逻辑的镜像副本) - `android-app/` — Capacitor 包装器 (WebView) + Gradle 项目 - `android-app/android/app/src/main/java/pl/straznik/app/` — 原生层: `MonitorService`(后台监听),`AlarmActivity`(全屏警报), `Fusion` + `Sources`(后台融合和采集器),`BackgroundPlugin`(与 JS 的桥接) - `docs/` — 用户手册(GitHub Pages)和截图 - `scripts/build_cams.py` — 刷新摄像头列表 - `scripts/build_sounds.py` — 将警报声生成至 `res/raw/` ## 设备兼容性 `minSdk 24` (Android 7.0) — `targetSdk 36` (Android 16)。已处理的差异:从 API 26 开始的通知渠道,从 API 33 开始的 `POST_NOTIFICATIONS` 权限,从 API 34 开始强制要求的 `dataSync` 服务类型,以及从 API 23 开始的电池优化豁免。布局使用 `env(safe-area-inset-*)`,因此栏位不会被刘海或系统栏遮挡;在窄屏幕上,按钮会折叠为图标。已在 Pixel 7 模拟器(Android 14)上测试。 **后台监听**(`MonitorService.java`)是一项原生的 foreground service,它会自行轮询来源,并在屏幕熄灭时发出通知。Android 要求在此过程中保持持久的“监听已激活”通知(显示来源状态,例如 `Neptun✓ Media✓ RCB✓`)——这是系统的硬性要求。该服务会在手机重启以及打开应用程序时恢复运行(如果已启用监听但服务未运行——例如在强制停止之后),并请求禁用电池优化,因为小米/三星/华为可能会杀掉后台服务。 **全屏警报**(`AlarmActivity.java`)在红色级别下的工作方式类似于来电:点亮屏幕,显示在锁屏界面之上,闪烁(颜色和频率与 CSS 中的 `#alarm-overlay` 相同),循环播放警报声并持续震动,直到确认为止。它是原生的,不在 WebView 中——即使应用程序进程已死,它也必须立即出现。 从 Android 14 开始,`USE_FULL_SCREEN_INTENT` 权限不会自动授予闹钟和电话以外的应用程序。没有此权限,从后台启动 Activity 会被阻止(`BAL_BLOCK`),因此应用程序会请求在设置中授予权限(`⚙ → 🚨 允许全屏警报`),如果未获授权,它会通过 wake lock 进行补救:点亮屏幕,以便显示带有警报声的通知。 **声音**由 `scripts/build_sounds.py` 生成至 `res/raw/` —— 与打开的应用程序在 Web Audio 中合成的波形相同(黄色:740↔988 Hz 双音信号,红色:通过低通滤波器的 380↔860 Hz 调制警报声)。这使得后台的声音与前台完全一致。在 `app.js` 中更改声音后,请重新运行脚本。通知渠道带有版本后缀(`-v2`),因为一旦创建,渠道将忽略后续对声音的更改。 **后台监听覆盖范围:** 全部 16 个省份,与地图具有相同的级联效应。通知针对用户选择的区域(`Fusion.setHomeVoivodeship`,在保存设置时通过 JS 设置)——如果没有此过滤器,手机将会收到有关国家另一端事件的警报。如果未选择区域,服务默认监控四个边境省份。从文本识别省份涵盖全部 16 个(`VOIV_KEYWORDS` / `VOIV_KEYS`),并且一个波兰全国性的 Google News 频道覆盖了没有专属 feed 的地区。 `Fusion.java` 中的邻接表基于 `Sources.VOIVS` 的索引——如果更改省份的顺序,则需要重新计算。建议通过比较这两个来源的脚本来检查与 `config.VOIV_NEIGHBORS` 的一致性以及邻接的对称性。 已在 Pixel 7 模拟器(Android 14)上验证,在应用程序进程被杀且屏幕熄灭的情况下:黄色级别会发出通知,并且 **不会** 唤醒屏幕;红色级别会唤醒屏幕(`Asleep → Awake`)并将 `AlarmActivity` 置于顶层(`BAL_ALLOW_PENDING_INTENT`),确认后会停止声音和震动。级联效应已单独测试:卢布林省 5 分的事件会为设置了马佐夫舍省的用户触发“提高警戒:马佐夫舍省(2.0 分)”的通知,其细分显示为“来自卢布林省的转移(5.0 分,相邻区域)”。 ## 已知限制(刻意为之) - **NEPTUN 是一个 OSINT/众包聚合器,而不是雷达** —— UI 始终显示 `confidenceLevel` 和 `±uncertaintyKm`。UI 中包含强制要求的署名“数据:NEPTUN”。 - Neptun 显示的是**乌克兰上空**的威胁 —— 飞入波兰领空的物体会从数据源中消失;该系统旨在作为*预警*,而不是追踪。 - **ADS-B 无法看到乌克兰上空的航空器**(已验证:全球有 219 架军机时,乌克兰西部上空为 0 架)。乌克兰和俄罗斯军方不发射应答机信号,领空已关闭。但可以看到北约在波兰、罗马尼亚和波罗的海上空的 AWACS、加油机和运输机。这些是*希望*被看到的飞机的公开应答机信号——而不是对敌方目标的定位追踪。 - **Neptun 的飞行轨迹实际上是空的**(`trail` 字段包含 0-2 个点,通常是重复的),因此应用程序根据连续的位置观测构建自己的轨迹,并根据物体类别的典型速度推算 dead-reckoning。 - **仅限波兰的摄像头** —— 16 个省份的 641 个公共城市和旅游景点摄像头(worldcam.pl包括 582 个室外摄像头;每一个都在构建列表时通过下载最新图像进行了验证。我最初使用了 traxelektronik.pl 的道路摄像头——后来发现它们需要登录。您可以使用 `scripts/build_cams.py` 脚本刷新列表。我刻意没有接入乌克兰的摄像头:自 2022 年以来,当地禁止直播,因为这可以用来校正火力。 - ADS-B baseline 需要大约一周的采样收集时间,在此之前该层不会计分。 - 如果网站更改结构,RSS/抓取可能会损坏——状态显示在 LED 和 `/api/health` 中。 - **从标题识别省份是基于启发式的。** 它依赖于城市和地区的名称,因此会忽略与普通词汇冲突的名称(如 "piła"(锯)、"żary"(火)、"hel"(氦/海尔半岛)、"brzeg"(岸边)),并且在遇到相似名称时("Chełm" 和 "Chełmno","Radom" 和 "Radomsko"),列表顺序中的第一个匹配项优先。没有专属 RSS 频道的地区由一个波兰全国性的 Google News 查询覆盖,因此只有强关键字("空中警报"、"警报器鸣响")才会到达这些地区。 ## 许可证 [MIT](LICENSE) —— 您可以使用、更改和分发代码,但需保留作者信息。本软件按“原样”提供,不提供任何担保:这是一个非官方的辅助信息源,而不是应急系统。 ## 数据与署名 [NEPTUN](https://neptun.in.ua)(OSINT 聚合器) · adsb.lol · airspace.pansa.pl · gov.pl/RCB · 区域媒体 · worldcam.pl 摄像头 · 地图 © [CARTO](https://carto.com/attributions),© [OpenStreetMap](https://www.openstreetmap.org/copyright)
标签:Android应用, RSS, 公共安全, 后台面板检测, 数据可视化, 数据聚合, 波兰, 航空监测, 逆向工具, 预警系统