Godstrument/workers/world_air.py
monsterrobotparty 8deb65b4fe Godstrument: a multi-modal sensor-fusion instrument
Everything — your hand, the room, a satellite, the markets, the sky — becomes an
OSC control signal fused through a modulation matrix. Includes:
- hub + normalize (One-Euro) + matrix (curves/gates/quantize) + transform (tweaks/groups)
- 20+ workers: sim sensors, 8 keyless world feeds, ephemeris/almanac/clock (computed),
  time-warp replay (quakes/weather/db), and the dealgod market warehouse feed
- planetary orbital LFOs, SQLite recorder, city targeting
- live browser console: mute, group macros, drag-to-patch, inspector, Web MIDI

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 02:13:05 +10:00

79 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""A city's pollution turned into audible grit and glitch.
Real-time PM2.5/PM10/US-AQI from Open-Meteo's air-quality API for a chosen city.
Dirtier air -> more grit; spikes glitch the signal chain. Delhi by default.
"""
import argparse
import json
import time
import urllib.request
import urllib.error
from pythonosc.udp_client import SimpleUDPClient
NAME = "world_air"
POLL = 300.0 # seconds between API polls (respect the source)
REEMIT = 5.0 # re-emit last known values this often
USER_AGENT = "Godstrument/1.0 (+worker world_air)"
URL = ("https://air-quality-api.open-meteo.com/v1/air-quality"
"?latitude={lat}&longitude={lon}&current=pm2_5,pm10,us_aqi")
def fetch(lat, lon):
"""Return (pm25, pm10, aqi) as floats, or None on any failure."""
url = URL.format(lat=lat, lon=lon)
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=20) as resp:
data = json.loads(resp.read().decode("utf-8"))
cur = data["current"]
pm25 = float(cur["pm2_5"])
pm10 = float(cur["pm10"])
aqi = float(cur["us_aqi"])
return pm25, pm10, aqi
def main():
ap = argparse.ArgumentParser(description="Air-quality -> OSC grit worker.")
ap.add_argument("--host", default="127.0.0.1")
ap.add_argument("--port", type=int, default=9000)
ap.add_argument("--lat", type=float, default=28.61, help="latitude (default Delhi)")
ap.add_argument("--lon", type=float, default=77.20, help="longitude (default Delhi)")
args = ap.parse_args()
client = SimpleUDPClient(args.host, args.port)
print(f"[{NAME}] emitting /gs/air/... every {int(REEMIT)}s")
last = None # (pm25, pm10, aqi) once we have a reading
next_poll = 0.0 # poll immediately on startup
backoff = 5.0
while True:
now = time.monotonic()
if now >= next_poll:
try:
last = fetch(args.lat, args.lon)
next_poll = now + POLL
backoff = 5.0
except (urllib.error.URLError, urllib.error.HTTPError,
OSError, ValueError, KeyError, TypeError) as e:
print(f"[{NAME}] poll failed ({e}); retrying in {backoff:.0f}s")
next_poll = now + backoff
backoff = min(backoff * 2, POLL)
if last is not None:
pm25, pm10, aqi = last
try:
client.send_message("/gs/air/pm25", float(pm25))
client.send_message("/gs/air/pm10", float(pm10))
client.send_message("/gs/air/aqi", float(aqi))
except OSError as e:
print(f"[{NAME}] OSC send failed ({e}); continuing")
time.sleep(REEMIT)
if __name__ == "__main__":
main()