Now reads config.yaml.

Configuration now comes from config.yaml.  Command line flags override
config.yaml values.

Added --config and --secrets to specify the path to config.yaml and
secrets.yaml.
This commit is contained in:
John Wiseman 2020-01-20 21:30:50 -08:00
parent 807b5d7d44
commit 074f7c77c3
3 changed files with 115 additions and 70 deletions

View File

@ -60,7 +60,7 @@
(let [url (->> [url
"lat" lat
"lon" lon
"dist" radius-nm]
"dist" (.toFixed radius-nm 1)]
(map str)
(string/join "/"))]
(p/let [http-result (util/http-get url {:headers {:api-auth api-key}})]
@ -69,15 +69,10 @@
result))))
;; We keep position reports going back this far.
(def max-history-age-ms (* 25 60 1000))
;; Given a vector of position history, removes old entries.
(defn prune-history [history now]
(let [h (filterv #(< (- now (:time %)) max-history-age-ms) history)]
(defn prune-history [history now config]
(let [h (filterv #(< (- now (:time %)) (:max-history-age-ms config)) history)]
h))
@ -109,9 +104,9 @@
updated-db))
(defn prune-histories [db now]
(defn prune-histories [db now config]
(reduce-kv (fn [m k v]
(assoc m k (update v :history prune-history now)))
(assoc m k (update v :history prune-history now config)))
{}
db))
@ -144,10 +139,10 @@
(first args))
(defn update-history-db [db new-data now]
(defn update-history-db [db new-data now config]
(-> db
(update-history-db-add-new-data new-data now)
(prune-histories now)
(prune-histories now config)
(prune-records now)))
@ -170,12 +165,6 @@
(/ (.getTime (js/Date.)) 1))
;; This is how many degrees of turning we need to see over
;; max-history-age-ms ms to consider it a potential circling aircraft.
(def curviness-threshold-degrees 1440)
(defn ac-desc [ac]
(str (:icao ac) " " (:lat ac) " " (:lon ac)
" #" (:registration ac) " " (:alt ac) " " (:curviness ac) " "
@ -194,15 +183,15 @@
image-path))
(defn circling? [ac]
(and (> (geo/flight-curviness (:history ac)) curviness-threshold-degrees)
(defn circling? [ac config]
(and (> (geo/flight-curviness (:history ac)) (:curviness-threshold-degrees config))
(> (:alt ac) 300)))
;; Returns a vector of two elements,
;; [updated-database potentially-circling-aircraft]
(defn detect-circles [db now]
(defn detect-circles [db now config]
(log-verbose "Detecting circles")
(loop [old-db (seq db)
new-db {}
@ -213,7 +202,7 @@
ac (assoc ac
:curviness curviness
:normalized-curviness (geo/flight-normalized-curviness (:history ac)))
currently-circling? (circling? ac)
currently-circling? (circling? ac config)
previously-circling? (:started-circling-time ac)]
(cond
(and currently-circling?
@ -371,11 +360,6 @@
recent-hist))
;; If the centroid of the aircraft's positions is less than this close
;; to an airport, then it's probably just doinf flight training.
(def minimum-airport-distance-km 2.5)
;;(def minimum-airport-distance-miles 0)
(defn process-potential-circle [ac config now]
(p/let [icao (:icao ac)
recent-positions (recent-history (:history ac))
@ -393,11 +377,11 @@
(log-info "%s: Closest airport is %s, distance: %s km"
(:icao ac) (:label airport-properties) (:distance airport-properties))
(log-info "%s: No airports nearby" (:icao ac)))
(if (and airport-properties (<= (:distance airport-properties) minimum-airport-distance-km))
(if (and airport-properties (<= (:distance airport-properties) (:minimum-airport-distance-km config)))
(log-info "%s: Filtering out because it's %s km (minimum is %s) from %s"
(:icao ac)
(:distance airport-properties)
minimum-airport-distance-km
(:minimum-airport-distance-km config)
(:label airport-properties)
())
(do
@ -458,55 +442,88 @@
(p/recur (rest acs))))))
(def history-db-path "advisory-circular.db")
(def secrets-path "secrets.yaml")
(def default-config
{
;; We keep position reports going back this far.
:max-history-age-ms (* 25 60 1000)
;; This is how many degrees of turning we need to see over
;; max-history-age-ms ms to consider it a potential circling
;; aircraft.
:curviness-threshold-degrees 1440
;; If the centroid of the aircraft's positions is less than this
;; close to an airport, then it's probably just doinf flight
;; training.
:minimum-airport-distance-km 2.5
:history-db-path "advisory-circular.db"
:twitter {:enabled? true}})
(defn build-config [secrets commander]
(-> (merge-with merge
secrets
{:adsbx {:url (.-adsbxUrl commander)}}
{:twitter {:enabled? (.-tweeting commander)}}
{:pelias {:url (.-peliasUrl commander)}})
(assoc :basestation-sqb (.-basestationSqb commander)
:lat (.-lat commander)
:lon (.-lon commander)
:radius-nm (.-radius commander))))
(defn build-config-from-commander [commander]
(cond-> {}
(.-adsbxUrl commander)
(assoc-in [:adsbx :url] (.-adsbxUrl commander))
;; Note that we're distinguishing from the situation where
;; --tweeting or --no-tweeting is supplied, in which case the
;; tweeting field will be true or false, from the situation where
;; it's not suppled, in which case it will be undefined/nil.
(not (nil? (.-tweeting commander)))
(assoc-in [:twitter :enabled?] (.-tweeting commander))
(.-peliasUrl commander)
(assoc-in [:pelias :url] (.-peliasUrl commander))
(.-basestationSqb commander)
(assoc :basestation-sqb (.-basestationSqb commander))
(.-lat commander)
(assoc :lat (.-lat commander))
(.-lon commander)
(assoc :lon (.-lon commander))
(.-radius commander)
(assoc :radius (.-radius commander))))
(defn build-config [config cli-config secrets]
(util/deep-merge default-config config cli-config secrets))
(defn main [& args]
(-> commander
(.requiredOption "--lat <lat>" "Latitude of the circle of region of interest" parse-number)
(.requiredOption "--lon <lat>" "Longitude of the circle of the region of interest" parse-number)
(.requiredOption "--adsbx-url <url>" "ADSBX API url")
(.requiredOption "--pelias-url <url>" "Base pelias geocoder URL")
(.option "--radius <radius>" "Radius of the circle of interest, in nautical miles" 20 parse-number)
(.option "--lat <lat>" "Latitude of the circle of region of interest" parse-number)
(.option "--lon <lat>" "Longitude of the circle of the region of interest" parse-number)
(.option "--adsbx-url <url>" "ADSBX API url")
(.option "--pelias-url <url>" "Base pelias geocoder URL")
(.option "--radius <radius>" "Radius of the circle of interest, in km" parse-number)
(.option "--basestation-sqb <path>" "Path to a basestation.sqb database file")
(.option "--no-tweeting" "Do not tweet.")
(.option "--tweeting" "Enables tweeting")
(.option "--no-tweeting" "Do not tweet")
(.option "--config <path>" "Path to the configuration yaml file" "config.yaml")
(.option "--secrets <path>" "Path to the secrets yaml file" "secrets.yaml")
(.parse (.-argv js/process)))
(let [start-time (current-time)]
(p/then (p/all [(read-history-db history-db-path)
(util/read-config secrets-path)])
(fn [[db secrets]]
(p/let [config (build-config secrets commander)
(p/let [base-config (util/read-config (.-config commander))
cli-config (build-config-from-commander commander)
secrets (util/read-config (.-secrets commander))
config (build-config base-config cli-config secrets)
_1 (pprint/pprint base-config)
_2 (pprint/pprint cli-config)
_3 (pprint/pprint config)
db (read-history-db (:history-db-path config))
data (get-adsbexchange-live-data
{:url (get-in config [:adsbx :url])
:api-key (get-in config [:adsbx :api-key])
:lat (:lat config)
:lon (:lon config)
:radius-nm (:radius-nm config)})
:radius-nm (* (:radius-km config) 0.539957)})
now (current-time)
[new-db potential-circles] (-> db
(update-history-db (:aircraft data) now)
(detect-circles now))]
(update-history-db (:aircraft data) now config)
(detect-circles now config))]
(p/do
(when potential-circles
(doseq [ac potential-circles]
(log-warn "%s: New circle detected: %s" (:icao ac) (ac-desc ac)))
(process-potential-circles potential-circles config now))
(write-history-db new-db history-db-path)
(write-history-db new-db (:history-db-path config))
(let [end-time (current-time)]
(log-info
"Completed processing in %f seconds: tracking %s aircraft; %s potential circles"
(/ (- end-time start-time) 1000)
(count new-db)
(count potential-circles)))))))))
(count potential-circles)))))))

View File

@ -17,13 +17,15 @@
(.readFile fs-promises path (clj->js options)))
;; Writes a file, returns a promise that resolves to no arguments on success.
;; Writes a file, returns a promise that resolves to no arguments on
;; success.
(defn write-file [path data options]
(.writeFile fs-promises path data (clj->js options)))
;; Reads a YAML config file. Returns a promise that resolves to the parsed YAML.
;; Reads a YAML config file. Returns a promise that resolves to the
;; parsed YAML.
(defn read-config [path]
(log-verbose "Reading config file %s" path)
@ -48,3 +50,19 @@
(p/let [result (.get request
(clj->js (merge {:url (str url) :gzip true} options)))]
result)))))
;; From https://github.com/puppetlabs/clj-kitchensink/blob/cfea4a16e4d2e15a2d391131a163b4eeb60d872e/src/puppetlabs/kitchensink/core.clj#L311-L332
(defn deep-merge
"Deeply merges maps so that nested maps are combined rather than replaced.
For example:
(deep-merge {:foo {:bar :baz}} {:foo {:fuzz :buzz}})
;;=> {:foo {:bar :baz, :fuzz :buzz}}
;; contrast with clojure.core/merge
(merge {:foo {:bar :baz}} {:foo {:fuzz :buzz}})
;;=> {:foo {:fuzz :quzz}} ; note how last value for :foo wins"
[& vs]
(if (every? map? vs)
(apply merge-with deep-merge vs)
(last vs)))

View File

@ -0,0 +1,10 @@
(ns lemondronor.circlebot.util-test
(:require [cljs.test :refer (deftest is testing)]
[lemondronor.circlebot.util :as util]))
(deftest deep-merge
(is (= (util/deep-merge
{:adsbx {:url "http://bar"}}
{:adsbx {:url "http://foo"}}
{:adsbx {:secret "123"}})
{:adsbx {:url "http://foo", :secret "123"}})))