Add geofencing and API support

Closes #8
This commit is contained in:
Samuel Sloniker 2023-06-06 18:38:31 -07:00
parent b9ed0bf0d9
commit d5570bcec7
4 changed files with 104 additions and 42 deletions

View File

@ -12,37 +12,66 @@ 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. |
| `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.|
| `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 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.
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.
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

View File

@ -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)

View File

@ -11,6 +11,10 @@ Subscription = collections.namedtuple(
"description",
"last_seen",
"platform",
"min_lat",
"min_lon",
"max_lat",
"max_lon",
],
)
@ -18,14 +22,19 @@ 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)"
)
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:
cur.execute(
"ALTER TABLE subscriptions ADD COLUMN platform DEFAULT 'textbelt'"
)
con.execute(query)
except sqlite3.OperationalError:
pass
@ -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()
]

View File

@ -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()