Sno Station
Sno Station은 AI 에이전트를 하나의 팀으로 묶는 오픈소스 워크스테이션으로, 로컬 머신에서 에이전트 협업을 가능하게 함
지역 진도와 S파 남은 시간까지 제공하는 지진 경보를 Home Assistant에 바로 연동, 순수 파이썬, 의존성 없음
Earthquake warnings with local intensity and an S-wave countdown, straight into Home Assistant. Pure Python, zero dependencies.
플러그인Python · PowerShellGitHub @Juanipis온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Lightweight, autonomous client for receiving real-time earthquake alerts from Android AEAS via MCS. Built with Google Antigravity (AGY) & Gemini 3.8 Flash.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Earthquake warnings, straight into your smart home. One dependency-free Python file that follows official early warnings, worldwide and national quake feeds, and your own seismometer, then tells Home Assistant how hard it will shake at your place and how many seconds you have.
| Source | --sources | What it gives you | Speed | Where | Status |
|---|---|---|---|---|---|
| Official EEW via Wolfx (JMA, CENC, Sichuan, Fujian, Chongqing) | wolfx | Early warning with magnitude and location, revised as the quake grows | Seconds after origin, often before the S-wave | Japan, mainland China | ✅ Live |
| EMSC SeismicPortal | emsc | Every new or revised quake, pushed over WebSocket | Minutes (measured ~6–8 min) | Worldwide | ✅ Live |
| USGS real-time feed | usgs | Every quake of the last hour, polled every 60 s with HTTP 304 caching | Minutes | Worldwide | ✅ Live |
| SGC (Servicio Geológico Colombiano) | sgc | Colombia's national catalogue, polled every 30 s with HTTP 304 caching | Minutes | Colombia | ✅ Live, opt-in |
| Your own sensor (Raspberry Shake UDP datacast) | --shake-udp | On-site P-wave trigger (STA/LTA) | Seconds, no network in between | Anywhere you install one | 🧪 Tested with synthetic signals |
Android device via android_alert_listener.py | POST /android | Google's Android Earthquake Alert, captured from a real (or containerized) Android | Seconds after Google alerts the device | Where Google runs AEAS | 🧪 Tested with settings demo; paused in favor of direct socket |
Google MCS socket (mtalk.google.com:5228) | mcs | Direct Google AEAS push client with S2 Level 8 cell subscriptions (Field 29) + decoys | Seconds after origin | Worldwide (where AEAS operates) | 🛸 Live listening (Field 29 active; awaiting first real wire alert) |
Defaults: mcs,emsc,wolfx,usgs. In Colombia add sgc (the bridge suggests it when your base station is there).
For every event, wherever it comes from, the bridge works out your local impact:
Notification levels mirror Android's:
notice from estimated MMI 3.0 (--notice-mmi), like Android's "Be Aware".alert from estimated MMI 5.0 (--alert-mmi), like Android's "Take Action".The same quake reported by several sources produces one notification; you only hear about it again if it gets worse, and a cancelled early warning is announced as cancelled.
[!CAUTION] This is an independent open-source project, not a certified civil-protection warning system. Early warnings in seconds exist only where an agency publishes them (today: Japan and mainland China) or where you run your own sensor; elsewhere you get rapid reports minutes after the quake. Always keep official emergency alerts enabled on your phone.
Google's Android Earthquake Alerts System (AEAS) is the world's largest earthquake detection network, and it has no public API. Through progressive static and dynamic reverse engineering of Google Play Services (GMS 26.37.37), here is what we discovered:
EARStorage via PBKDF2/AES-CBC) revealed that Google does not filter alerts by polling reported device GPS coordinates. Instead, Android Play Services discretizes the device's location into an S2 Level 8 cell (~38 km × 38 km) and subscribes via Field 29 (repeated string topic = 29;) in the binary LoginRequest on mtalk.google.com:5228. It subscribes to ea.<s2_cell_token> (e.g. ea.8e443 for Medellín, ea.9662d for Santiago) along with 3 globally distributed random decoy cells for privacy.quake_listener.py now includes a complete pure-Python quadratic projection and Hilbert curve partitioner that maps any (lat, lon) to its S2 Level 8 hex token. When mcs connects, it automatically announces your primary cell topic plus 3 decoys in Field 29 without any external libraries.gmta Wire Payload Decoded. According to the decompiled Play Services code (no live earthquake captured yet), earthquake alerts arrive as GCM push data stanzas (category="com.google.android.gms" or sender location.quake.ealert) with an internal gmta protobuf (AlertBatch -> Alert -> EarthquakeWrap -> EarthquakeInfo). This exposes the epicenter coordinates, magnitude, depth, and critically, the earthquake origin timestamp (origin_time), enabling our detection desk to calculate exact, real-time S-wave arrival countdowns!tools/radar_sismos_global.py keeps a 22-node fleet online on a Raspberry Pi 4 (~46 MB RAM, one thread multiplexing 22 TLS sockets): one node per seismic hotspot (Chile, Philippines, Indonesia, Mexico, Turkey, Greece, Peru, Colombia, California, Taiwan, Panama). Each node also subscribes to 3 decoys — and since a synthetic node needs no privacy, the 66 decoys are chosen on purpose (Colombian cities and volcanoes, US hurricane and tornado country, Japan, the Philippines, India, Europe…) instead of falling at random in the ocean.The full protocol walkthrough is in docs/HOW_IT_WORKS.md; the container setup in docs/REDROID_SENTINEL.md.
socket, ssl, urllib, http.server). No pip install. The Android sentinel is a separate, optional add-on.304.estimated_mmi, mmi_label, distance_km, hypocentral_km and s_wave_eta_s, so automations can say "Shaking in 18 seconds".Launch a temporary bridge on port 8990. The web console connects to it automatically:
macOS / Linux:
curl -sSL https://raw.githubusercontent.com/Juanipis/quake-alert-listener/main/run.sh | bash
Windows (PowerShell):
irm https://raw.githubusercontent.com/Juanipis/quake-alert-listener/main/run.ps1 | iex
Any OS with Python 3.8+:
curl -sSL https://raw.githubusercontent.com/Juanipis/quake-alert-listener/main/quake_listener.py | python3 - --lat <your-lat> --lon <your-lon>
Docker:
docker run --rm -it -p 127.0.0.1:8990:8990 python:alpine sh -c "wget -qO- https://raw.githubusercontent.com/Juanipis/quake-alert-listener/main/quake_listener.py | python3 -"
Inside a container the REST API listens on 0.0.0.0 automatically so the published port works; 127.0.0.1: in -p keeps it off your LAN.
The run.sh / run.ps1 launchers estimate your location from your IP address (via ipapi.co) when you don't pass --lat/--lon.
git clone https://github.com/Juanipis/quake-alert-listener.git
cd quake-alert-listener
python3 quake_listener.py --lat <your-lat> --lon <your-lon> --name "Base Station" \
--webhook-url http://<home-assistant>:8123/api/webhook/quake_alert_local
Check that it works without waiting for a quake:
python3 quake_listener.py --simulate # decode a synthetic alert and preview the webhook body
curl -X POST http://127.0.0.1:8990/drill # send a level "drill" payload to your webhook
Docker Compose. Edit QUAKE_LAT, QUAKE_LON and QUAKE_WEBHOOK_URL in docker-compose.yml, then:
docker compose up -d --build
The image (~55 MB, Alpine):
/data, mode 0600);systemd (a Raspberry Pi without Docker). Use deploy/quake-listener.service, a hardened unit capped at 64 MB of RAM, together with deploy/quake-listener.env. Install steps are in the unit's header.
python3 -m unittest discover -s tests -v # offline, standard library only
50 tests cover the Google MCS protocol (checkin layout, login, stream acks, idle replies, port fallback), protobuf decoding, the intensity model, each agency's time format, the USGS/SGC parsers and conditional requests, the detection policy (levels, escalation, cross-source dedupe, cancellations), WebSocket framing including hostile peers, the STA/LTA trigger, and the HTTP guards including /android signatures. CI runs them on Python 3.8, 3.10, 3.12 and 3.13.
Every option can also be set with its environment variable (handy for Docker and systemd). An empty variable counts as unset.
| Parameter | Environment Variable | Default | Description |
|---|---|---|---|
--lat / --lon | QUAKE_LAT / QUAKE_LON | 0.0 | Your base station: intensity and countdowns are computed here |
--name | QUAKE_NAME | Base Station | Human-readable location name |
--sources | QUAKE_SOURCES | mcs,emsc,wolfx,usgs | Any of wolfx, emsc, usgs, sgc, mcs (--no-emsc drops emsc) |
--notice-mmi | QUAKE_NOTICE_MMI | 3.0 | Estimated intensity at your base station that triggers a notice |
--alert-mmi | QUAKE_ALERT_MMI | 5.0 | Estimated intensity that triggers an alert |
--min-magnitude | QUAKE_MIN_MAGNITUDE | 0 | Ignore smaller events whatever their estimated intensity (alias --emsc-min-mag) |
--max-distance-km | QUAKE_MAX_DISTANCE_KM | no limit | Ignore events farther than this (alias --emsc-radius-km) |
--webhook-url | QUAKE_WEBHOOK_URL | None | Where payloads are posted (e.g. Home Assistant), http:// or |
The bridge exposes a local REST API (http://127.0.0.1:8990/status) and posts a JSON payload to your webhook whenever an event is worth telling you about. An early warning looks like this (synthetic example):
{
"level": "alert",
"status": "early warning",
"source": "JMA EEW",
"kind": "eew",
"magnitude": 6.1,
"region": "Test Region",
"distance_km": 42.3,
"depth_km": 30.0,
"hypocentral_km": 51.9,
"estimated_mmi": 5.3,
"mmi_label": "V · moderate",
"s_wave_eta_s": 9.8,
"origin_time": "2026-01-01T00:00:00Z",
"place": "M6.1 · Test Region · 42.3 km from Base Station · est. MMI V · S-wave in 9 s",
"revision": 3,
"final": false
}
status tells you what kind of information it is:
status | Meaning |
|---|---|
early warning | Official EEW (Wolfx) |
rapid report | EMSC, USGS or SGC, minutes after the quake |
on-site trigger | Your own sensor |
early alert | Google's alert, captured from an Android device |
cancelled | A warning you already received was withdrawn |
Every field is documented in docs/HOW_IT_WORKS.md.
In the homeassistant/ directory you will find ready-to-use snippets:
configuration.yaml: REST sensors for the bridge status, feeds online and the last quake (with intensity and countdown attributes), plus drill/ping commands.automations.yaml: critical push with an S-wave countdown ("🚨 Shaking in 18 s"), cancellations, reports and drills.dashboard.yaml: a Lovelace card with live status and test buttons.Webhooks are sent from a background thread (so no source ever stalls) and retried up to 3 times on network errors or HTTP 5xx. With --webhook-secret, every request carries an X-Quake-Signature header: the hex HMAC-SHA256 of the raw body.
127.0.0.1, so only this machine can reach it. This works as-is when Home Assistant runs on the same host (including Docker with network_mode: host).--http-host 0.0.0.0 and point the REST sensors at the host's IP. Only do this on a network you trust: reading /status and triggering drills need no authentication.POST /android is authenticated: with --android-secret every request must be HMAC-signed; without a secret, only clients on the same machine are accepted, so nobody on your LAN can make the lights flash red.--allowed-origins (the official web app and localhost pages are allowed by default). Other sites get no CORS headers and 403 on POST endpoints, so a random page can't trigger a drill or read your location.Host header is a foreign domain name get 403. Reaching the bridge by IP, localhost or this machine's hostname works as usual; add other names with --allowed-hosts.0600). Webhook URLs are masked in logs and in /status, because Home Assistant webhook IDs act as passwords.Ctrl+C and SIGTERM (systemd, docker stop) close every connection and give in-flight webhooks up to 5 s to finish.This project was developed collaboratively using:
docs/HOW_IT_WORKS.md, the security hardening, the test suite, and the redesigned web app and logo.Earthquake data: Wolfx, relaying the Japan Meteorological Agency and the China Earthquake Networks Center; EMSC (CC BY 4.0); USGS (public domain); Servicio Geológico Colombiano. Intensity model: Allen, T. I., Wald, D. J. & Worden, C. B. (2012), Intensity attenuation for active crustal regions, J. Seismology 16, 409–433.
This is an independent, non-profit open-source project created for research, civil protection and home automation interoperability. It is not affiliated with, endorsed by, or sponsored by Google LLC or any of the agencies above. Android and Google are trademarks of Google LLC.
Distributed under the MIT License. See LICENSE for more information.
gmtagcms=crisisalertsCRISISpa:cmid:quake_listener.py used to treat any Play Services rawData message as an earthquake, so a hurricane alert would have triggered your quake automations, and it de-duplicated by persistent_id, which every crisis alert shares. It now logs crisis alerts without dispatching them and de-duplicates by persistent_id + payload.https://--webhook-secret | QUAKE_WEBHOOK_SECRET | None | HMAC-SHA256 key for signing payloads (prefer the env var: CLI args are visible in ps) |
--android-secret | QUAKE_ANDROID_SECRET | --webhook-secret | HMAC-SHA256 key that POST /android requests must be signed with. Without any secret, /android only accepts clients on this machine |
--http-port | QUAKE_HTTP_PORT | 8990 | Local REST API port |
--http-host | QUAKE_HTTP_HOST | 127.0.0.1 | Interface the REST API binds to (0.0.0.0 automatically inside Docker/Podman) |
--allowed-origins | QUAKE_ALLOWED_ORIGINS | https://juanipis.github.io | Browser origins allowed to use the REST API (* = any). Loopback origins and non-browser clients are always allowed |
--allowed-hosts | QUAKE_ALLOWED_HOSTS | None | Extra host names the REST API answers to. IPs, localhost and this machine's hostname always work (* disables the check) |
--no-http | - | False | Disable the REST API |
--shake-udp | QUAKE_SHAKE_UDP | off | Listen for a Raspberry Shake UDP datacast on [host:]port, e.g. 8888 |
--shake-channel | QUAKE_SHAKE_CHANNEL | auto | Channel to watch (first vertical one, e.g. EHZ/ENZ) |
--shake-sta-lta-on / --shake-sta-lta-off | QUAKE_SHAKE_STA_LTA_ON / _OFF | 4.0 / 1.5 | STA/LTA trigger and release ratios |
--shake-alert-counts | QUAKE_SHAKE_ALERT_COUNTS | 0 | Peak amplitude (counts) that makes an on-site trigger an alert (0 = always notice) |
--ping-interval | QUAKE_PING_INTERVAL | 120 | MCS heartbeat interval in seconds (clamped to 30–600) |
--device-type | QUAKE_DEVICE_TYPE | android | MCS identity profile: android or chrome |
--locale / --timezone | QUAKE_LOCALE / QUAKE_TIMEZONE | en_US / UTC | Announced by the android MCS checkin |
--credentials-file | QUAKE_CREDENTIALS_FILE | ~/.quake_device_credentials.json | Where the anonymous MCS identity is stored (mode 0600) |
--debug-frames | QUAKE_DEBUG_FRAMES | False | Log every non-heartbeat MCS frame (protocol research) |
--test-ping | - | - | One MCS connection + heartbeat round-trip, then exit |
--simulate | - | - | Decode a synthetic alert and preview the webhook payload |
--version | - | - | Print the version |