parent
b9ed0bf0d9
commit
d5570bcec7
51
README.md
51
README.md
|
@ -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. |
|
||||
| `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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
28
adsms/db.py
28
adsms/db.py
|
@ -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()
|
||||
]
|
||||
|
|
10
convert.py
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…
Reference in New Issue
Block a user