diff --git a/files/usr/local/bin/mgr/rssi_monitor_ath10k.lua b/files/usr/local/bin/mgr/rssi_monitor_ath10k.lua
deleted file mode 100644
index 7d667154..00000000
--- a/files/usr/local/bin/mgr/rssi_monitor_ath10k.lua
+++ /dev/null
@@ -1,114 +0,0 @@
---[[
-
- Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
- Copyright (C) 2022 Tim Wilkinson
- See Contributors file for additional contributors
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation version 3 of the License.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
- Additional Terms:
-
- Additional use restrictions exist on the AREDN(TM) trademark and logo.
- See AREDNLicense.txt for more info.
-
- Attributions to the AREDN Project must be retained in the source code.
- If importing this code into a new or existing project attribution
- to the AREDN project must be added to the source code.
-
- You must not misrepresent the origin of the material contained within.
-
- Modified versions must be modified to attribute to the original source
- and be marked in reasonable ways as differentiate it from the original
- version
-
---]]
-
-local periodic_scan_tick = 5
-
-local wifiiface
-local phy
-
-function rssi_monitor_10k()
- if not string.match(get_ifname("wifi"), "^wlan") then
- exit_app()
- else
- wait_for_ticks(math.max(1, 120 - nixio.sysinfo().uptime))
-
- wifiiface = get_ifname("wifi")
-
- -- ath10k only
- phy = iwinfo.nl80211.phyname(wifiiface)
- if not phy or not nixio.fs.stat("/sys/kernel/debug/ieee80211/" .. phy .. "/ath10k") then
- exit_app()
- return
- end
-
- while true
- do
- run_monitor_10k()
- wait_for_ticks(60) -- 1 minute
- end
- end
-end
-
-local logfile = "/tmp/rssi_ath10k.log"
-
-if not file_exists(logfile) then
- io.open(logfile, "w+"):close()
-end
-
-local station_zero = 0
-local log = aredn.log.open(logfile, 16000)
-
-local function reset_network()
- local coverage
- local f = io.popen("iw " .. phy .. " info")
- if f then
- for line in f:lines()
- do
- coverage = tonumber(line:match("Coverage class: (%d+)"))
- if coverage then
- os.execute("iw " .. phy .. " set coverage 0 > /dev/null 2>&1")
- break
- end
- end
- f:close()
- end
- write_all("/sys/kernel/debug/ieee80211/" .. phy .. "/ath10k/simulate_fw_crash", "hw-restart")
- if coverage then
- os.execute("iw " .. phy .. " set coverage " .. coverage .. " > /dev/null 2>&1")
- end
-end
-
-function run_monitor_10k()
-
- local station_count = 0
- local stations = iwinfo.nl80211.assoclist(wifiiface)
- for mac, station in pairs(stations)
- do
- station_count = station_count + 1
- end
-
- if station_count ~= 0 then
- station_zero = periodic_scan_tick - 1
- else
- station_zero = station_zero + 1
- if math.mod(station_zero, periodic_scan_tick) == 0 then
- reset_network()
- log:write("No stations detected")
- log:flush()
- end
- end
-end
-
-return rssi_monitor_10k
diff --git a/files/usr/local/bin/mgr/rssi_monitor_ath9k.lua b/files/usr/local/bin/mgr/rssi_monitor_ath9k.lua
deleted file mode 100644
index 3ee3e217..00000000
--- a/files/usr/local/bin/mgr/rssi_monitor_ath9k.lua
+++ /dev/null
@@ -1,269 +0,0 @@
---[[
-
- Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
- Copyright (C) 2021 Tim Wilkinson
- Original Perl Copyright (C) 2015 Joe Ayers ae6xe@arrl.net
- See Contributors file for additional contributors
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation version 3 of the License.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
- Additional Terms:
-
- Additional use restrictions exist on the AREDN(TM) trademark and logo.
- See AREDNLicense.txt for more info.
-
- Attributions to the AREDN Project must be retained in the source code.
- If importing this code into a new or existing project attribution
- to the AREDN project must be added to the source code.
-
- You must not misrepresent the origin of the material contained within.
-
- Modified versions must be modified to attribute to the original source
- and be marked in reasonable ways as differentiate it from the original
- version
-
---]]
-
-local wifiiface
-local phy
-local multiple_ant = false
-
-function rssi_monitor_9k()
- if not string.match(get_ifname("wifi"), "^wlan") then
- exit_app()
- else
- wait_for_ticks(math.max(1, 120 - nixio.sysinfo().uptime))
-
- wifiiface = get_ifname("wifi")
- phy = iwinfo.nl80211.phyname(wifiiface)
-
- -- Supports ath9k
- if not phy or not nixio.fs.stat("/sys/kernel/debug/ieee80211/" .. phy .. "/ath9k") then
- exit_app()
- return
- end
-
- if read_all("/sys/kernel/debug/ieee80211/" .. phy .. "/ath9k/tx_chainmask"):chomp() ~= "1" then
- multiple_ant = true
- end
-
- while true
- do
- run_monitor_9k()
- wait_for_ticks(60) -- 1 minute
- end
- end
-end
-
-local datfile = "/tmp/rssi.dat"
-local logfile = "/tmp/rssi.log"
-
-if not file_exists(datfile) then
- io.open(datfile, "w+"):close()
-end
-if not file_exists(logfile) then
- io.open(logfile, "w+"):close()
-end
-
-local station_zero = 0
-local periodic_scan_tick = 5
-local log = aredn.log.open(logfile, 16000)
-
-local function reset_network()
- write_all("/sys/kernel/debug/ieee80211/" .. phy .. "/ath9k/reset", "1")
-end
-
-function run_monitor_9k()
-
- local now = nixio.sysinfo().uptime
-
- -- load history
- local rssi_hist = {}
- for line in io.lines(datfile) do
- local mac, ave_h, sd_h, ave_v, sd_v, num, last = string.match(line, "([0-9a-fA-F:]*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)")
- rssi_hist[mac] = {
- ave_h = ave_h,
- sd_h = sd_h,
- ave_v = ave_v,
- sd_v = sd_v,
- num = tonumber(num),
- last = last
- }
- end
-
- local ofdm_level = 0
- for i, line in ipairs(read_all("/sys/kernel/debug/ieee80211/" .. phy .. "/ath9k/ani"):splitNewLine())
- do
- ofdm_level = tonumber(string.match(line, "OFDM LEVEL: (.*)"))
- if ofdm_level then
- break
- end
- end
- local amac = nil
- local station_count = 0
- local rssi = get_rssi(wifiiface)
- for mac, info in pairs(rssi)
- do
- station_count = station_count + 1
- local rssih = rssi_hist[mac]
- if rssih and now - rssih.last < 3600 then
- local hit = 0
- local sdh3 = math.floor(rssih.sd_h * 3 + 0.5)
- if math.abs(rssih.ave_h - info.Hrssi) > sdh3 then
- hit = hit + 1
- end
- local sdv3 = math.floor(rssih.sd_v * 3 + 0.5)
- if math.abs(rssih.ave_v - info.Vrssi) > sdv3 and multiple_ant then
- hit = hit + 1
- end
- if rssih.num > 9 and ofdm_level <= 3 and hit > 0 then
- -- overly attenuated chain suspected
- local msg = string.format("Attenuated Suspect %s [%d] %f %f", mac, info.Hrssi, rssih.ave_h, rssih.sd_h)
- if multiple_ant then
- msg = msg .. string.format(" [%d] %f %f", info.Vrssi, rssih.ave_v, rssih.sd_v)
- end
- if not amac or rssi[amac].Hrssi < info.Hrssi then
- amac = mac
- end
- log:write(msg)
- else
- -- update statistics
- local ave_h = (rssih.ave_h * rssih.num + info.Hrssi) / (rssih.num + 1)
- local sd_h = math.sqrt(((rssih.num - 1) * rssih.sd_h * rssih.sd_h + (info.Hrssi - ave_h) * (info.Hrssi - rssih.ave_h)) / rssih.num)
- rssih.ave_h = ave_h
- rssih.sd_h = sd_h
- local ave_v = (rssih.ave_v * rssih.num + info.Vrssi) / (rssih.num + 1)
- local sd_v = math.sqrt(((rssih.num - 1) * rssih.sd_v * rssih.sd_v + (info.Vrssi - ave_v) * (info.Vrssi - rssih.ave_v)) / rssih.num)
- rssih.ave_v = ave_v
- rssih.sd_v = sd_v
- rssih.last = now
- if rssih.num < 60 then
- rssih.num = rssih.num + 1
- end
- end
- else
- rssi_hist[mac] = {
- ave_h = info.Hrssi,
- sd_h = 0,
- ave_v = info.Vrssi,
- sd_v = 0,
- num = 1,
- last = now
- }
- end
- end
-
- if amac then
- reset_network()
- wait_for_ticks(5)
- -- update time
- now = nixio.sysinfo().uptime
-
- local beforeh = rssi[amac].Hrssi
- local beforev = rssi[amac].Vrssi
- local arssi = get_rssi(wifiiface)
-
- if arssi[amac] then
- if multiple_ant then
- log:write(string.format("before %s [%d] [%d]", amac, beforeh, beforev))
- log:write(string.format("after %s [%d] [%d]", amac, arssi[amac].Hrssi, arssi[amac].Vrssi))
- else
- log:write(string.format("before %s [%d]", amac, beforeh))
- log:write(string.format("after %s [%d]", amac, arssi[amac].Hrssi))
- end
- if math.abs(beforeh - arssi[amac].Hrssi) <= 2 and math.abs(beforev - arssi[amac].Vrssi) <= 2 then
- -- false positive if within 2dB after reset
- log:write(string.format("%s Possible valid data point, adding to statistics", amac))
- local rssih = rssi_hist[amac]
- local ave_h = (rssih.ave_h * rssih.num + beforeh) / (rssih.num + 1)
- local sd_h = math.sqrt(((rssih.num - 1) * rssih.sd_h * rssih.sd_h + (beforeh - ave_h) * (beforeh - rssih.ave_h)) / rssih.num)
- rssih.ave_h = ave_h
- rssih.sd_h = sd_h
- local ave_v = (rssih.ave_v * rssih.num + beforeh) / (rssih.num + 1)
- local sd_v = math.sqrt(((rssih.num - 1) * rssih.sd_v * rssih.sd_v + (beforeh - ave_v) * (beforeh - rssih.ave_v)) / rssih.num)
- rssih.ave_v = ave_v
- rssih.sd_v = sd_v
- rssih.last = now
- if rssih.num < 60 then
- rssih.num = rssih.num + 1
- end
- end
- end
- else
- if station_count ~= 0 then
- station_zero = periodic_scan_tick - 1
- else
- station_zero = station_zero + 1
- if math.mod(station_zero, periodic_scan_tick) == 0 then
- reset_network()
- wait_for_ticks(5)
- log:write("No stations detected")
- end
- end
- end
-
- local f = io.open(datfile, "w")
- if f then
- for mac, hist in pairs(rssi_hist)
- do
- f:write(string.format("%s|%f|%f|%f|%f|%d|%s\n", mac, hist.ave_h, hist.sd_h, hist.ave_v, hist.sd_v, hist.num, hist.last))
- end
- f:close()
- end
-
- log:flush()
-end
-
-function get_rssi(wifiiface)
- if not multiple_ant then
- -- easy way
- local rssi = {}
- local stations = iwinfo.nl80211.assoclist(wifiiface)
- for mac, station in pairs(stations)
- do
- if station.signal ~= 0 then
- if station.signal < -95 then
- rssi[mac] = { Hrssi = -96, Vrssi = -96 }
- else
- rssi[mac] = { Hrssi = station.signal, Vrssi = station.signal }
- end
- end
- end
- return rssi
- else
- -- hard way
- local rssi = {}
- local f = io.popen("/usr/sbin/iw " .. wifiiface .. " station dump 2>&1")
- if f then
- local mac
- for line in f:lines()
- do
- local m = line:match("Station (%S+) %(on " .. wifiiface)
- if m then
- mac = m
- end
- local h, v = line:match("signal:.*%[(.+),%s(.+)%]")
- if mac and v then
- h = tonumber(h)
- v = tonumber(v)
- rssi[mac] = { Hrssi = h < -95 and -95 or h, Vrssi = v < -95 and -95 or v }
- mac = nil
- end
- end
- f:close()
- end
- return rssi
- end
-end
-
-return rssi_monitor_9k
diff --git a/files/usr/local/bin/mgr/station_monitor.lua b/files/usr/local/bin/mgr/station_monitor.lua
deleted file mode 100755
index 6dc95cc9..00000000
--- a/files/usr/local/bin/mgr/station_monitor.lua
+++ /dev/null
@@ -1,154 +0,0 @@
---[[
-
- Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
- Copyright (C) 2023 Tim Wilkinson
- See Contributors file for additional contributors
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation version 3 of the License.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
- Additional Terms:
-
- Additional use restrictions exist on the AREDN(TM) trademark and logo.
- See AREDNLicense.txt for more info.
-
- Attributions to the AREDN Project must be retained in the source code.
- If importing this code into a new or existing project attribution
- to the AREDN project must be added to the source code.
-
- You must not misrepresent the origin of the material contained within.
-
- Modified versions must be modified to attribute to the original source
- and be marked in reasonable ways as differentiate it from the original
- version
-
---]]
-
-local unresponsive_max = 5
-local unresponsive_report = 3
-local last = {}
-local wifiiface
-local frequency
-local ssid
-
-local IW = "/usr/sbin/iw"
-local ARPING = "/usr/sbin/arping"
-
-local logfile = "/tmp/station_monitor.log"
-if not file_exists(logfile) then
- io.open(logfile, "w+"):close()
-end
-local log = aredn.log.open(logfile, 8000)
-
-function rejoin_network()
- os.execute(IW .. " " .. wifiiface .. " ibss leave")
- os.execute(IW .. " " .. wifiiface .. " ibss join " .. ssid .. " " .. frequency .. " fixed-freq")
- log:write("Rejoining network")
- log:flush()
-end
-
-function station_monitor()
- if not string.match(get_ifname("wifi"), "^wlan") then
- exit_app()
- else
- wait_for_ticks(math.max(1, 120 - nixio.sysinfo().uptime))
-
- wifiiface = get_ifname("wifi")
- frequency = iwinfo.nl80211.frequency(wifiiface)
- ssid = iwinfo.nl80211.ssid(wifiiface)
-
- -- If frequency or ssid is missing (some kind of bad configuration) just exit this
- if not (frequency and ssid) then
- exit_app()
- return
- end
-
- -- Mikrotik AC hardware has some startup issues which we try to resolve
- -- by leaving and rejoining the network
- local boardid = aredn.hardware.get_board_id():lower()
- if boardid:match("mikrotik") and boardid:match("ac") then
- rejoin_network()
- end
-
- -- Only monitor if we have LQM information
- if uci.cursor():get("aredn", "@lqm[0]", "enable") ~= "1" then
- exit_app()
- return
- end
-
- while true
- do
- run_station_monitor()
- wait_for_ticks(60) -- 1 minute
- end
- end
-end
-
-function run_station_monitor()
-
- -- Use the LQM state to ignore nodes we dont care about
- local trackers = nil
- local f = io.open("/tmp/lqm.info")
- if f then
- local lqm = luci.jsonc.parse(f:read("*a"))
- f:close()
- trackers = lqm.trackers
- end
- local now = nixio.sysinfo().uptime
-
- -- Check each station to make sure we can broadcast and unicast to them
- local total = 0
- local old = last
- last = {}
- arptable(
- function (entry)
- if entry.Device == wifiiface then
- local ip = entry["IP address"]
- local mac = entry["HW address"] or ""
- -- Only consider nodes which have valid ip and macs, routable and not pending
- local tracker = { pending = 0, routable = true }
- if trackers then
- tracker = trackers[mac:upper()] or { pending = now, routable = false }
- end
- if entry["Flags"] ~= "0x0" and ip and mac ~= "" and tracker.routable and tracker.pending < now then
- -- Two arp pings - the first is broadcast, the second unicast
- for line in io.popen(ARPING .. " -c 2 -I " .. wifiiface .. " " .. ip):lines()
- do
- -- If we see exactly one response then we neeed to force the station to reassociate
- -- This indicates that broadcasts work, but unicasts dont
- if line:match("Received 1 response") then
- local val = (old[ip] or 0) + 1
- last[ip] = val
- if val > unresponsive_report then
- log:write("Possible unresponsive node: " .. ip .. " [" .. mac .. "]")
- log:flush()
- end
- if val > total then
- total = val
- end
- break
- end
- end
- end
- end
- end
- )
-
- -- If we find unresponsive nodes too often then we leave and rejoin the network
- -- to reset everything
- if total >= unresponsive_max then
- last = {}
- rejoin_network()
- end
-end
-
-return station_monitor
diff --git a/files/usr/local/bin/mgr/wireless_monitor.lua b/files/usr/local/bin/mgr/wireless_monitor.lua
new file mode 100755
index 00000000..8c1b64cf
--- /dev/null
+++ b/files/usr/local/bin/mgr/wireless_monitor.lua
@@ -0,0 +1,287 @@
+--[[
+
+ Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
+ Copyright (C) 2023 Tim Wilkinson
+ See Contributors file for additional contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ Additional Terms:
+
+ Additional use restrictions exist on the AREDN(TM) trademark and logo.
+ See AREDNLicense.txt for more info.
+
+ Attributions to the AREDN Project must be retained in the source code.
+ If importing this code into a new or existing project attribution
+ to the AREDN project must be added to the source code.
+
+ You must not misrepresent the origin of the material contained within.
+
+ Modified versions must be modified to attribute to the original source
+ and be marked in reasonable ways as differentiate it from the original
+ version
+
+--]]
+
+local ip = require("luci.ip")
+
+local IW = "/usr/sbin/iw"
+local ARPING = "/usr/sbin/arping"
+
+local M = {}
+
+local wifi
+local phy
+local chipset
+local frequency
+local ssid
+
+local action_limits = {
+ unresponsive_report = 3,
+ unresponsive_trigger1 = 5,
+ unresponsive_trigger2 = 10,
+ zero_trigger1 = 10 * 60, -- 10 minutes
+ zero_trigger2 = 30 * 60 -- 30 minutes
+}
+-- Start action state assuming the node is active and no actions are pending
+local action_state = {
+ done_scan1 = true,
+ done_scan2 = true,
+ done_rejoin1 = true,
+ done_rejoin2 = true
+}
+local unresponsive = {
+ max = 0,
+ ignore = 15,
+ stations = {}
+}
+local station_count = {
+ first_zero = 0,
+ first_nonzero = 0,
+ last_zero = 0,
+ last_nonzero = 0,
+ history = {},
+ history_limit = 120 -- 2 hours
+}
+
+-- Detect Mikrotik AC which requires special handling
+local mikrotik_ac = false
+local boardid = aredn.hardware.get_board_id():lower()
+if boardid:match("mikrotik") and boardid:match("ac") then
+ mikrotik_ac = true
+end
+
+local logfile = "/tmp/wireless_monitor.log"
+if not file_exists(logfile) then
+ io.open(logfile, "w+"):close()
+end
+local log = aredn.log.open(logfile, 8000)
+
+-- Various forms of network resets --
+
+function M.reset_network(mode)
+ log:write("reset_network: " .. mode)
+ if mode == "rejoin" then
+ -- Only observered on Mikrotik AC devices
+ if mikrotik_ac then
+ os.execute(IW .. " " .. wifi .. " ibss leave > /dev/null 2>&1")
+ os.execute(IW .. " " .. wifi .. " ibss join " .. ssid .. " " .. frequency .. " fixed-freq > /dev/null 2>&1")
+ else
+ log:write("-- ignoring (mikrotik ac only)")
+ end
+ elseif mode == "scan-quick" then
+ os.execite(IW .. " " .. wifi .. " scan freq " .. frequency .. " > /dev/null 2>&1")
+ elseif mode == "scan-all" then
+ os.execite(IW .. " " .. wifi .. " scan > /dev/null 2>&1")
+ os.execite(IW .. " " .. wifi .. " scan passive > /dev/null 2>&1")
+ elseif mode == "reset" then
+ if chipset == "ath9k" then
+ write_all("/sys/kernel/debug/ieee80211/" .. phy .. "/ath9k/reset", "1")
+ else
+ write_all("/sys/kernel/debug/ieee80211/" .. phy .. "/ath10k/simulate_fw_crash", "hw-restart")
+ end
+ else
+ log:write("-- unknown")
+ end
+end
+
+-- Monitor stations and detect if they become unresponsive --
+
+function M.monitor_unresponsive_stations()
+
+ local old = unresponsive.stations
+ unresponsive.stations = {}
+ unresponsive.max = 0
+
+ local now = nixio.sysinfo().uptime
+ arptable(
+ function (entry)
+ if entry.Device == wifi then
+ local ipaddr = entry["IP address"]
+ local mac = entry["HW address"] or ""
+ -- Only consider nodes which have valid ip and mac and routable
+ if ipaddr then
+ unresponsive.stations[ipaddr] = -1
+ local rt = ip.route(ipaddr)
+ if entry["Flags"] ~= "0x0" and mac ~= "" and rt and tostring(rt.gw) == ipaddr then
+ unresponsive.stations[ipaddr] = 0
+ -- The first ping is broadcast, the rest unicast
+ for line in io.popen(ARPING .. " -w 5 -I " .. wifi .. " " .. ipaddr):lines()
+ do
+ -- If we see exactly one response then broadcast works and unicast doesnt.
+ -- We neeed to force the station to reassociate
+ if line:match("^Received 1 response") then
+ local val = (old[ipaddr] or 0) + 1
+ unresponsive.stations[ipaddr] = val
+ if val < unresponsive.ignore then
+ if val > action_limits.unresponsive_report then
+ log:write("Possible unresponsive node: " .. ipaddr .. " [" .. mac .. "]")
+ end
+ if val > unresponsive.max then
+ unresponsive.max = val
+ end
+ end
+ break
+ end
+ end
+ end
+ end
+ end
+ end
+ )
+end
+
+-- Monitor number of connected stations --
+
+function M.monitor_station_count()
+ local count = 0
+ for mac, station in pairs(iwinfo.nl80211.assoclist(wifi))
+ do
+ count = count + 1
+ end
+ table.insert(station_count.history, 1, count)
+ while #station_count.history > station_count.history_limit
+ do
+ station_count.history[#station_count.history] = nil
+ end
+ local now = nixio.sysinfo().uptime
+ if count == 0 then
+ station_count.last_zero = now
+ if station_count.first_zero <= station_count.first_nonzero then
+ station_count.first_zero = now
+ end
+ else
+ station_count.last_nonzero = now
+ if station_count.first_nonzero <= station_count.first_zero then
+ station_count.first_nonzero = now
+ end
+ end
+end
+
+-- Take action depending on the monitor state
+
+function M.run_actions()
+ -- No action if we have stations and they're responsive
+ if station_count.last_nonzero > station_count.last_zero and unresponsive.max < action_limits.unresponsive_trigger1 then
+ action_state = {}
+ return
+ end
+
+ -- Otherwise ...
+
+ -- If network stations falls to zero when it was previously non-zero
+ if station_count.first_zero > station_count.first_nonzero then
+ if not action_state.done_scan1 and station_count.last_zero - station_count.first_zero > action_limits.zero_trigger1 then
+ M.reset_network("scan-quick")
+ action_state.done_scan1 = true
+ return
+ elseif not action_state.done_scan2 and station_count.last_zero - station_count.first_zero > action_limits.zero_trigger2 then
+ M.reset_network("scan-all")
+ action_state.done_scan2 = true
+ return
+ end
+ end
+
+ -- We are failing to ping stations we are associated with
+ if unresponsive.max >= action_limits.unresponsive_trigger1 and not action_state.done_rejoin1 then
+ M.reset_network("rejoin")
+ action_state.done_rejoin1 = true
+ return
+ elseif unresponsive.max >= action_limits.unresponsive_trigger2 and not action_state.done_rejoin2 then
+ M.reset_network("rejoin")
+ action_state.done_rejoin2 = true
+ return
+ end
+end
+
+function M.run_monitors()
+ M.monitor_unresponsive_stations()
+ M.monitor_station_count()
+end
+
+function M.save()
+ local f = io.open("/tmp/wireless_monitor.info", "w")
+ if f then
+ f:write(luci.jsonc.stringify({
+ now = nixio.sysinfo().uptime,
+ unresponsive = unresponsive,
+ station_count = station_count,
+ action_state = action_state
+ }, true))
+ f:close()
+ end
+end
+
+function M.start_monitor()
+ if not string.match(get_ifname("wifi"), "^wlan") then
+ exit_app()
+ return
+ end
+
+ wait_for_ticks(math.max(1, 120 - nixio.sysinfo().uptime))
+
+ -- Extract all the necessary wifi parameters
+ wifi = get_ifname("wifi")
+ phy = iwinfo.nl80211.phyname(wifi)
+ frequency = iwinfo.nl80211.frequency(wifi)
+ ssid = iwinfo.nl80211.ssid(wifi)
+ if not (phy and frequency and ssid) then
+ exit_app()
+ return
+ end
+
+ -- Select chipset
+ if nixio.fs.stat("/sys/kernel/debug/ieee80211/" .. phy .. "/ath9k") then
+ chipset = "ath9k"
+ elseif nixio.fs.stat("/sys/kernel/debug/ieee80211/" .. phy .. "/ath10k") then
+ chipset = "ath10k"
+ else
+ exit_app()
+ return
+ end
+
+ log:write("Monitoring wireless chipset: " .. chipset)
+
+ M.reset_network("rejoin")
+
+ while true
+ do
+ M.run_monitors()
+ M.run_actions()
+ M.save()
+ log:flush()
+ wait_for_ticks(60) -- 1 minute
+ end
+end
+
+return M.start_monitor
diff --git a/files/www/cgi-bin/supporttool b/files/www/cgi-bin/supporttool
index 7455eace..2e2b7a04 100755
--- a/files/www/cgi-bin/supporttool
+++ b/files/www/cgi-bin/supporttool
@@ -65,6 +65,8 @@ local files = {
"/tmp/manager.log.0",
"/tmp/AutoDistReset.log",
"/tmp/lqm.info",
+ "/tmp/wireless_monitor.info",
+ "/tmp/wireless_monitor.log",
"/tmp/sysinfo/board_name",
"/tmp/sysinfo/boardid",
"/tmp/sysinfo/hardware_mfg",