Add azimuth, elevation and antenna information (#979)

* Support antenna selection and allow heading to be specified

* More antennas

* Heading -> Azimuth

* Ubiquiti's catalog

* Add Mikrotik builtin antennas

* Some Ubiquiti builtins

* JP Performance Antennas

* Added Altelix

* More Altelix

* More radios

* Add elevation

* Add generic omnis, sectors and dishes

* Improve selection

* Improve display for non-wireless devices

* Improve omni and builtin setup

* mode -> model typo in antennas.json

* Typos

* Fix bad gateway when saving

* Add antenna height above ground level.
Fix more bad gateways

* Update antennas.json

* More radio antennas

* More antennas
This commit is contained in:
Tim Wilkinson 2023-12-06 12:30:23 -08:00 committed by GitHub
parent 6d15dfb869
commit aae5e14655
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1152 additions and 113 deletions

398
files/etc/antennas.json Executable file
View File

@ -0,0 +1,398 @@
{
"900MHz": [
{
"model": "unknown",
"description": "Unknown"
},
{
"model": "AM-9M13-120",
"description": "airMAX 900 MHz, 13 dBi, 120° Sector",
"gain": 13,
"beamwidth": 120
},
{
"model": "NB-OD9",
"description": "airMAX NanoBridge OD9 Offset Dish, 11 dBi",
"gain": 11,
"beamwidth": 10
},
{
"model": "Generic-Omni",
"description": "Generic Omni",
"gain": 0,
"beamwidth": 360
}
],
"2.4GHz": [
{
"model": "unknown",
"description": "Unknown"
},
{
"model": "AMO-2G10",
"description": "airMAX 2.4 GHz, 10 dBi Omni",
"gain": 10,
"beamwidth": 360
},
{
"model": "AMO-2G13",
"description": "airMAX 2.4 GHz, 13 dBi Omni",
"gain": 13,
"beamwidth": 360
},
{
"model": "AM-V2G-Ti-15",
"description": "airMAX Sector 2.4 GHz Titanium, 15 dBi 120° Sector",
"gain": 15,
"beamwidth": 120
},
{
"model": "AM-V2G-Ti-16",
"description": "airMAX Sector 2.4 GHz Titanium, 16 dBi 90° Sector",
"gain": 16,
"beamwidth": 90
},
{
"model": "AM-V2G-Ti-17",
"description": "airMAX Sector 2.4 GHz Titanium, 17 dBi 60° Sector",
"gain": 17,
"beamwidth": 60
},
{
"model": "AM-2G15-120",
"description": "airMAX 2.4 GHz, 15 dBi, 120° Sector",
"gain": 15,
"beamwidth": 120
},
{
"model": "AM-2G16-90",
"description": "airMAX 2.4 GHz, 16 dBi, 90° Sector",
"gain": 16,
"beamwidth": 90
},
{
"model": "AF-2G24-S45",
"description": "airFiber X 2.4 GHz, 24 dBi 6.6°, Slant 45 Dish",
"gain": 24,
"beamwidth": 6.6
},
{
"model": "UB24EWIFIDBI",
"description": "KP Performance Antenna, 19 dBi 12° Dish",
"gain": 19,
"beamwidth": 12
},
{
"model": "AS24G17B90MS-KU",
"description": "Altelix 17 dBi 90° Sector",
"gain": 17,
"beamwidth": 90
},
{
"model": "TL-ANT2424MD",
"description": "TP-Link 24 dBi 6° Dish",
"gain": 24,
"beamwidth": 6
},
{
"model": "Generic-Omni",
"description": "Generic Omni",
"gain": 0,
"beamwidth": 360
},
{
"model": "Generic-Sector-120",
"description": "Generic 120° Sector",
"gain": 0,
"beamwidth": 90
},
{
"model": "Generic-Sector-90",
"description": "Generic 90° Sector",
"gain": 0,
"beamwidth": 90
},
{
"model": "Generic-Sector-60",
"description": "Generic 90° Sector",
"gain": 0,
"beamwidth": 60
},
{
"model": "Generic-Sector-45",
"description": "Generic 90° Sector",
"gain": 0,
"beamwidth": 45
},
{
"model": "Generic-Dish-10",
"description": "Generic 10° Dish",
"gain": 0,
"beamwidth": 10
},
{
"model": "Generic-Dish-6",
"description": "Generic 6° Dish",
"gain": 0,
"beamwidth": 6
},
{
"model": "Generic-Dish-3",
"description": "Generic 3° Dish",
"gain": 0,
"beamwidth": 3
}
],
"3GHz": [
{
"model": "unknown",
"description": "Unknown"
},
{
"model": "UB24EWIFIDBI",
"description": "KP Performance Antenna, 21 dBi 9° Dish",
"gain": 21,
"beamwidth": 9
},
{
"model": "Generic-Omni",
"description": "Generic Omni",
"gain": 0,
"beamwidth": 360
},
{
"model": "Generic-Sector-120",
"description": "Generic 120° Sector",
"gain": 0,
"beamwidth": 90
},
{
"model": "Generic-Sector-90",
"description": "Generic 90° Sector",
"gain": 0,
"beamwidth": 90
},
{
"model": "Generic-Sector-60",
"description": "Generic 90° Sector",
"gain": 0,
"beamwidth": 60
},
{
"model": "Generic-Sector-45",
"description": "Generic 90° Sector",
"gain": 0,
"beamwidth": 45
},
{
"model": "Generic-Dish-10",
"description": "Generic 10° Dish",
"gain": 0,
"beamwidth": 10
},
{
"model": "Generic-Dish-6",
"description": "Generic 6° Dish",
"gain": 0,
"beamwidth": 6
},
{
"model": "Generic-Dish-3",
"description": "Generic 3° Dish",
"gain": 0,
"beamwidth": 3
}
],
"5GHz": [
{
"model": "unknown",
"description": "Unknown"
},
{
"model": "AMO-5G10",
"description": "airMAX 5 GHz, 10 dBi Omni",
"gain": 10,
"beamwidth": 360
},
{
"model": "AMO-5G13",
"description": "airMAX 5 GHz, 13 dBi Omni",
"gain": 10,
"beamwidth": 360
},
{
"model": "AM-5G16-120",
"description": "airMAX 5 GHz, 16 dBi 120° Sector",
"gain": 16,
"beamwidth": 120
},
{
"model": "AM-5G17-90",
"description": "airMAX 5 GHz, 17 dBi 90° Sector",
"gain": 17,
"beamwidth": 90
},
{
"model": "AM-5G19-120",
"description": "airMAX 5 GHz, 19 dBi 120° Sector",
"gain": 19,
"beamwidth": 120
},
{
"model": "AM-5G20-90",
"description": "airMAX 5 GHz, 20 dBi 90° Sector",
"gain": 20,
"beamwidth": 90
},
{
"model": "AM-5AC21-60",
"description": "airMAX AC 5 GHz, 21 dBi, 60° Sector",
"gain": 21,
"beamwidth": 60
},
{
"model": "AM-5AC22-45",
"description": "airMAX AC 5 GHz, 22 dBi, 45° Sector",
"gain": 22,
"beamwidth": 45
},
{
"model": "AP-5AC-90-HD",
"description": "airPRISM 3x30° HD Sector",
"gain": 22,
"beamwidth": 30
},
{
"model": "AF-5G23-S45",
"description": "airFiber X 5 GHz, 23 dBi 10°, Slant 45 Dish",
"gain": 23,
"beamwidth": 10
},
{
"model": "AF-5G30-S45",
"description": "airFiber X 5 GHz, 30 dBi 5.8°, Slant 45 Dish",
"gain": 30,
"beamwidth": 5.8
},
{
"model": "AF-5G34-S45",
"description": "airFiber X 5 GHz, 34 dBi 3°, Slant 45 Dish",
"gain": 34,
"beamwidth": 3
},
{
"model": "RD-5G30",
"description": "airMAX AC 5 GHz, 30 dBi 5° RocketDish",
"gain": 30,
"beamwidth": 5
},
{
"model": "RD-5G31-AC",
"description": "airMAX AC 5 GHz, 31 dBi 4° RocketDish",
"gain": 31,
"beamwidth": 4
},
{
"model": "RD-5G34",
"description": "airMAX AC 5 GHz, 34 dBi 3° RocketDish",
"gain": 34,
"beamwidth": 3
},
{
"model": "Horn-5-90",
"description": "airMAX PrismStation Horn, 13 dBi 90°",
"gain": 13,
"beamwidth": 90
},
{
"model": "Horn-5-60",
"description": "airMAX PrismStation Horn, 16 dBi 60°",
"gain": 16,
"beamwidth": 60
},
{
"model": "Horn-5-45",
"description": "airMAX PrismStation Horn, 15.5 dBi 45°",
"gain": 15.5,
"beamwidth": 45
},
{
"model": "Horn-5-30",
"description": "airMAX PrismStation Horn, 19 dBi 30°",
"gain": 19,
"beamwidth": 30
},
{
"model": "STH-30-USMA",
"description": "RF Elements StarterHorn™ 30 USMA, 18 dBi 30° Horn",
"gain": 18,
"beamwidth": 30
},
{
"model": "UB24EWIFIDBI",
"description": "KP Performance Antenna, 25 dBi 6° Dish",
"gain": 25,
"beamwidth": 6
},
{
"model": "AS5158G18B65Ms-NF",
"description": "Altelix 18 dBi 65° Sector",
"gain": 18,
"beamwidth": 65
},
{
"model": "TL-ANT5830MD",
"description": "TP-Link 30 dBi 6° Dish",
"gain": 30,
"beamwidth": 6
},
{
"model": "Generic-Omni",
"description": "Generic Omni",
"gain": 0,
"beamwidth": 360
},
{
"model": "Generic-Sector-120",
"description": "Generic 120° Sector",
"gain": 0,
"beamwidth": 90
},
{
"model": "Generic-Sector-90",
"description": "Generic 90° Sector",
"gain": 0,
"beamwidth": 90
},
{
"model": "Generic-Sector-60",
"description": "Generic 60° Sector",
"gain": 0,
"beamwidth": 60
},
{
"model": "Generic-Sector-45",
"description": "Generic 45° Sector",
"gain": 0,
"beamwidth": 45
},
{
"model": "Generic-Dish-10",
"description": "Generic 10° Dish",
"gain": 0,
"beamwidth": 10
},
{
"model": "Generic-Dish-6",
"description": "Generic 6° Dish",
"gain": 0,
"beamwidth": 6
},
{
"model": "Generic-Dish-3",
"description": "Generic 3° Dish",
"gain": 0,
"beamwidth": 3
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -42,6 +42,7 @@ local hardware = {}
local radio_json = nil
local board_json = nil
local channels_cache = {}
local antennas_cache = {}
function hardware.get_board()
if not board_json then
@ -368,6 +369,50 @@ function hardware.get_rfchannels(wifiintf)
return channels
end
function hardware.get_antennas(wifiintf)
local ants = antennas_cache[wifiintf]
if not ants then
local radio = hardware.get_radio_intf(wifiintf)
if radio and radio.antenna then
if radio.antenna == "external" then
local dchan = hardware.get_default_channel(wifiintf)
if dchan and dchan.band then
local f = io.open("/etc/antennas.json")
if f then
ants = json.parse(f:read("*a"))
f:close()
ants = ants[dchan.band]
end
end
else
radio.antenna.builtin = true
ants = { radio.antenna }
end
end
antennas_cache[wifiintf] = ants
end
return ants
end
function hardware.get_current_antenna(wifiintf)
local ants = hardware.get_antennas(wifiintf)
if ants then
if #ants == 1 then
return ants[1]
end
local antenna = uci.cursor():get("aredn", "@location[0]", "antenna")
if antenna then
for _, ant in ipairs(ants)
do
if ant.model == antenna then
return ant
end
end
end
end
return nil
end
function hardware.supported()
return hardware.get_radio() and true or false
end

View File

@ -120,6 +120,30 @@ function model.getGridSquare()
return loc[1]['gridsquare']
end
-------------------------------------
-- Returns antenna azimuth
-------------------------------------
function model.getAzimuth()
loc=aredn_uci.getUciConfType("aredn", "location")
return loc[1]['azimuth']
end
-------------------------------------
-- Returns antenna elevation
-------------------------------------
function model.getElevation()
loc=aredn_uci.getUciConfType("aredn", "location")
return loc[1]['elevation']
end
-------------------------------------
-- Returns antenna height
-------------------------------------
function model.getHeight()
loc=aredn_uci.getUciConfType("aredn", "location")
return loc[1]['height']
end
-------------------------------------
-- Returns AREDN Alert (if exists)
-------------------------------------

View File

@ -472,6 +472,47 @@ if (parms.button_updatelocation or parms.button_save) then
out("Lat/lon purged.")
end
end
-- process antenna, azimuth and elevation
if wifi_enable == "1" then
local antenna = parms.antenna or ""
if (cursora:get("aredn", "@location[0]", "antenna") or "") ~= antenna then
cursora:set("aredn", "@location[0]", "antenna", antenna)
cursorb:set("aredn", "@location[0]", "antenna", antenna)
end
if (cursora:get("aredn", "@location[0]", "azimuth") or "") ~= parms.azimuth then
local ant = aredn.hardware.get_current_antenna(wifi_intf)
if ant and ant.beamwidth == 360 then
parms.azimuth = ""
end
local azimuth = tonumber(parms.azimuth)
if parms.azimuth == "" or (azimuth and azimuth >= 0 and azimuth < 360) then
cursora:set("aredn", "@location[0]", "azimuth", parms.azimuth)
cursorb:set("aredn", "@location[0]", "azimuth", parms.azimuth)
elseif parms.azimuth then
err("ERROR: Azimuth value must be blank or between 0 and 360.")
end
end
if (cursora:get("aredn", "@location[0]", "elevation") or "") ~= parms.elevation then
local elevation = tonumber(parms.elevation)
if parms.elevation == "" or (elevation and elevation >= -180 and elevation <= 180) then
cursora:set("aredn", "@location[0]", "elevation", parms.elevation)
cursorb:set("aredn", "@location[0]", "elevation", parms.elevation)
elseif parms.elevation then
err("ERROR: Elevation value must be blank or between -180 and 180.")
end
end
if (cursora:get("aredn", "@location[0]", "height") or "") ~= parms.height then
local height = tonumber(parms.height)
if parms.height == "" or (height and height >= 0 and height <= 500) then
cursora:set("aredn", "@location[0]", "height", parms.height)
cursorb:set("aredn", "@location[0]", "height", parms.height)
elseif parms.height then
err("ERROR: Height value must be blank or between 0 and 500.")
end
end
end
cursora:commit("aredn")
cursorb:commit("aredn")
cursor = cursora
@ -490,6 +531,10 @@ gridsquare = cursor:get("aredn", "@location[0]", "gridsquare")
if not gridsquare then
gridsquare = ""
end
local azimuth = cursor:get("aredn", "@location[0]", "azimuth") or ""
local elevation = cursor:get("aredn", "@location[0]", "elevation") or ""
local height = cursor:get("aredn", "@location[0]", "height") or ""
local antenna = cursor:get("aredn", "@location[0]", "antenna") or ""
-- retrieve ntp_period
local cm = uci.cursor("/etc/config.mesh")
@ -1471,7 +1516,41 @@ html.print("<input type=submit name='button_updatelocation' value='Apply Locatio
html.print("&nbsp;<button " .. locdisabled .. " type='button' id='hideshowmap' value='show' onClick='toggleMap(this);'>Show Map</button>&nbsp;")
html.print("<input " .. locdisabled .. " type='submit' name='button_uploaddata' value='Upload data to AREDN Servers' />&nbsp;")
html.print("</td><tr><td align=left>Longitude</td><td><input type=text name=longitude size=10 value='" .. lon .. "' title='Longitude value (in decimal) (ie. -95.334454)' /></td><td align=left>Grid Square <input type=text name=gridsquare maxlength=6 size=6 value='" .. gridsquare .. "' title='Gridsquare value (ie. AB12cd)' /></td></tr><tr><td colspan=4><div id='map' style='height: 200px; display: none;'></div></td></tr><tr><td colspan=4><hr /></td></tr>")
html.print("</td><tr><td align=left>Longitude</td><td><input type=text name=longitude size=10 value='" .. lon .. "' title='Longitude value (in decimal) (ie. -95.334454)' /></td><td align=left>Grid Square <input type=text name=gridsquare maxlength=6 size=6 value='" .. gridsquare .. "' title='Gridsquare value (ie. AB12cd)' /></td></tr>")
if wifi_enable == "1" then
local ants = aredn.hardware.get_antennas(wifi_intf)
local changeh = true
local changea = true
if ants and #ants == 1 then
changea = false
antenna = ants[1].description
if ants[1].beamwidth == 360 then
changeh = false
end
end
if not changeh then
html.print("<tr><td align=left>Azimuth</td><td>-</td>")
else
html.print("<tr><td align=left>Azimuth</td><td><input type=text name=azimuth size=10 value='" .. azimuth .. "' title='azimuth (degrees)' /></td>")
end
if not changea then
html.print("<td align=left>Antenna " .. antenna .. "</td>")
elseif ants then
html.print("<td align=left>Antenna <select name=antenna>")
for _, ant in ipairs(ants)
do
html.print("<option value='" .. ant.model .. "'" .. (ant.model == antenna and " selected" or "") .. ">" .. ant.description .. "</option>")
end
html.print("</select></td>")
end
if not changeh then
html.print("</tr><tr><td align=left>Elevation</td><td>-</td>")
else
html.print("</tr><tr><td align=left>Elevation</td><td><input type=text name=elevation size=10 value='" .. elevation .. "' title='elevation above ground level (degrees)' /></td>")
end
html.print("<td align=left>Height <input type=text name=height size=10 value='" .. height .. "' title='height above ground level (feet)' /></tr>")
end
html.print("<tr><td colspan=4><div id='map' style='height: 200px; display: none;'></div></td></tr><tr><td colspan=4><hr /></td></tr>")
html.print("<tr><td>Timezone </td><td><select name=time_zone_name tabindex=10>")
for _,zone in ipairs(tz_db_names)
do

View File

@ -446,7 +446,20 @@ if config == "mesh" and not wifi_disabled then
end
end
col2[#col2 + 1] = "<th align=right><nobr>firmware version:</nobr><br><nobr>model:</nobr></th><td>" .. read_all("/etc/mesh-release") .. "<br>" .. (aredn.hardware.get_radio() or { name = "unknown" }).name .. "</td>"
local azimuth = cursor:get("aredn", "@location[0]", "azimuth")
if tonumber(azimuth) then
azimuth = azimuth .. "&deg;"
else
azimuth = nil
end
local antenna
if not wifi_disabled then
antenna = aredn.hardware.get_current_antenna(wifi_iface)
if antenna then
antenna = antenna.description
end
end
col2[#col2 + 1] = "<th align=right><nobr>firmware version:</nobr><br><nobr>model:</nobr>" .. (antenna and "<br><nobr>antenna:</nobr>" or "") .. (azimuth and "<br><nobr>azimuth:</nobr>" or "") .. "</th><td>" .. read_all("/etc/mesh-release") .. "<br>" .. (aredn.hardware.get_radio() or { name = "unknown" }).name .. "<br>" .. (antenna and antenna .. "<br>" or "") .. (azimuth or "") .. "</td>"
local sysinfo = nixio.sysinfo()
local uptime = string.format("%d:%02d", math.floor(sysinfo.uptime / 3600) % 24, math.floor(sysinfo.uptime / 60) % 60)

View File

@ -38,6 +38,7 @@ require("uci")
require("aredn.utils")
local aredn_info = require("aredn.info")
local aredn_olsr = require("aredn.olsr")
require("aredn.hardware")
require("aredn.http")
require("nixio")
local ipc = require("luci.ip")
@ -55,7 +56,7 @@ end
info={}
-- API version
info['api_version']="1.12"
info['api_version']="1.13"
-- NODE name
@ -99,6 +100,10 @@ if ( radio ~= nil and radio ~= "" ) then
info['meshrf']['channel']=aredn_info.getChannel(radio)
info['meshrf']['chanbw']=aredn_info.getChannelBW(radio)
info['meshrf']['freq']=aredn_info.getFreq(radio)
info['meshrf']['azimuth'] = aredn_info.getAzimuth()
info['meshrf']['elevation'] = aredn_info.getElevation()
info['meshrf']['height'] = aredn_info.getHeight()
info['meshrf']['antenna'] = aredn.hardware.get_current_antenna(radio)
else
info['meshrf']['status']="off"
end