Browse Source

Add geofencing and API support

Closes #8
main
Samuel Sloniker 9 months ago
parent
commit
d5570bcec7
  1. 63
      README.md
  2. 39
      adsms/__init__.py
  3. 34
      adsms/db.py
  4. 10
      convert.py

63
README.md

@ -12,38 +12,67 @@ Copy the configuration file, make any necessary changes, and run:
## Configuration file
* `textbelt_key`: your [Textbelt](https://textbelt.com) API key
* `data`: a URL to a readsb/tar1090 `aircraft.json` endpoint
All values are required. All are strings unless otherwise specified. All times
are in seconds.
* `textbelt_key`: your [Textbelt](https://textbelt.com) API key. This is always
required, but can be set to an invalid value if you do not plan to use SMS.
* `data`: a URL to a readsb/tar1090 `aircraft.json` endpoint (for a local
tracker), or an [adsb.one](https://adsb.one)-style `/hex/` API endpoint. For
adsb.one, this should be `https://api.adsb.one/v2/hex/`. The final slash is
important.
* `codes` (boolean): whether or not to append ICAO codes to the URL (`false`
for local tracker; `true` for adsb.one API)
* `tracker`: a URL to a tar1090 tracker (e.g. https://globe.theairtraffic.com/)
* `database`: an SQLite file in which to store subscriptions
* `pid_file`: path to which to write the PID (set to empty string to not write
a PID file)
* `max_age`: maximum age of aircraft pings in seconds; pings older than this
will be ignored
* `min_disappearance`: the minimum time in seconds for which an aircraft must
go "off the radar" before disappearing for new pings to trigger notifications
again
* `max_age`: maximum age of aircraft pings; pings older than this will be
ignored
* `min_disappearance`: the minimum time for which an aircraft must go "off the
radar" before disappearing for new pings to trigger notifications again
* `delay`: time to wait after processing all rules before running the loop
again
## Database Schema
`adsms` uses a SQLite database to store subscriptions and information about tracked aircraft. Currently, the only table is `subscriptions`.
`adsms` uses a SQLite database to store subscriptions and information about
tracked aircraft. Currently, the only table is `subscriptions`.
### `subscriptions`
The `subscriptions` table has the following columns:
| Column Name | Data Type | Description |
| ------------- | --------- | -------------------------------------------------------------- |
| `rowid` | INTEGER | Unique identifier for the subscription. |
| `phone` | TEXT | Phone number to receive notifications for this subscription. |
| `icao` | TEXT | ICAO address of the aircraft to track. |
| `description` | TEXT | Description of the aircraft being tracked. |
| `last_seen` | INTEGER | Timestamp of the last time this aircraft was seen by the system.|
| Column Name | Data Type | Description |
| ------------- | --------- | ---------------------------------------------------------------- |
| `rowid` | INTEGER | Unique identifier for the subscription. |
| `phone` | TEXT | Identifier to receive notifications for this subscription. |
| `icao` | TEXT | ICAO address of the aircraft to track. |
| `description` | TEXT | Description of the aircraft being tracked. |
| `last_seen` | INTEGER | Timestamp of the last time this aircraft was seen by the system. |
| `platform` | TEXT | The method by which to send the message. |
| `min_lat` | REAL | The minimum latitude of the geofence. |
| `min_lon` | REAL | The minimum longitude of the geofence. |
| `max_lat` | REAL | The maximum latitude of the geofence. |
| `max_lon` | REAL | The maximum longitude of the geofence. |
This table stores information about each subscription, including the contact
information to send notifications to, the ICAO address of the aircraft to
track, a description of the aircraft, and the last time it was seen by the
system.
adsms can send messages by SMS using [Textbelt](https://textbelt.com) or by
Discord using webhooks. For SMS, use `textbelt` for `platform` and the phone
number for `phone`; for Discord, use `discord_webhook` for `platform` and the
webhook URL for `phone`. (The field is called `phone` because adsms originally
only supported SMS.)
When adding new entries, set `last_seen` to 0.
This table stores information about each subscription, including the phone number to send notifications to, the ICAO address of the aircraft to track, a description of the aircraft, and the last time it was seen by the system.
If you want to notify whenever an aircraft is seen anywhere, use -90 for
`min_lat`, -180 for `min_lon`, 90 for `max_lat`, and 180 for `max_lon`. This
will cover the entire globe.
## Use of ChatGPT
Portions of both this README and the `adsms` code have been partially written with ChatGPT.
Portions of both this README and the `adsms` code have been partially written with ChatGPT.

39
adsms/__init__.py

@ -22,9 +22,25 @@ def send_text_message(phone, message, key):
def process_subscriptions(con, config, data):
subscriptions = db.get_subscriptions(con)
print(subscriptions)
for sub_id, phone, icao, description, last_seen, platform in subscriptions:
if icao in data and data[icao]["seen"] < config["max_age"]:
for (
sub_id,
phone,
icao,
description,
last_seen,
platform,
min_lat,
min_lon,
max_lat,
max_lon,
) in subscriptions:
if (
icao in data
and data[icao]["seen_pos"] < config["max_age"]
and min_lat <= data[icao]["lat"] <= max_lat
and min_lon <= data[icao]["lon"] <= max_lon
):
if last_seen + config["min_disappearance"] < time.time():
message = f"{description}\n{config['tracker']}?icao={icao}"
@ -44,18 +60,27 @@ def process_subscriptions(con, config, data):
con.commit()
def get_current_data(config):
def get_current_data(con, config):
# return {"78007e": {"seen": 0}}
response = requests.get(config["data"])
planes = response.json()["aircraft"]
print(
config["data"]
+ (",".join(db.get_all_icao(con)) if config["codes"] else "")
)
response = requests.get(
config["data"]
+ (",".join(db.get_all_icao(con)) if config["codes"] else "")
).json()
planes = response.get("aircraft", response["ac"])
return {plane["hex"]: plane for plane in planes}
def run(config):
con = adsms.db.load_database(config["database"])
print(db.get_all_icao(con))
while True:
data = get_current_data(config)
data = get_current_data(con, config)
process_subscriptions(con, config, data)

34
adsms/db.py

@ -11,6 +11,10 @@ Subscription = collections.namedtuple(
"description",
"last_seen",
"platform",
"min_lat",
"min_lon",
"max_lat",
"max_lon",
],
)
@ -18,16 +22,21 @@ Subscription = collections.namedtuple(
def load_database(file_name):
con = sqlite3.connect(file_name)
cur = con.execute(
con.execute(
"CREATE TABLE IF NOT EXISTS subscriptions(phone VARCHAR, icao VARCHAR, description VARCHAR, last_seen INTEGER)"
)
try:
cur.execute(
"ALTER TABLE subscriptions ADD COLUMN platform DEFAULT 'textbelt'"
)
except sqlite3.OperationalError:
pass
for query in [
"ALTER TABLE subscriptions ADD COLUMN platform VARCHAR DEFAULT 'textbelt'",
"ALTER TABLE subscriptions ADD COLUMN min_lat REAL DEFAULT -90",
"ALTER TABLE subscriptions ADD COLUMN min_lon REAL DEFAULT -180",
"ALTER TABLE subscriptions ADD COLUMN max_lat REAL DEFAULT 90",
"ALTER TABLE subscriptions ADD COLUMN max_lon REAL DEFAULT 180",
]:
try:
con.execute(query)
except sqlite3.OperationalError:
pass
con.commit()
@ -43,6 +52,15 @@ def update_last_seen_time(con, sub_id):
def get_subscriptions(con):
for subscription in con.execute(
"SELECT rowid, phone, icao, description, last_seen, platform FROM subscriptions"
"SELECT rowid, phone, icao, description, last_seen, platform, min_lat, min_lon, max_lat, max_lon FROM subscriptions"
).fetchall():
yield Subscription(*subscription)
def get_all_icao(con):
return [
i[0]
for i in con.execute(
"SELECT DISTINCT icao FROM subscriptions"
).fetchall()
]

10
convert.py

@ -1,10 +0,0 @@
import sqlite3
import sys
con = sqlite3.connect(sys.argv[1])
con.execute(
"ALTER TABLE subscriptions ADD COLUMN platform VARCHAR DEFAULT 'textbelt'"
)
con.commit()
Loading…
Cancel
Save