From b78a918d0aca0a4aa9d948646972eaff135fc0c0 Mon Sep 17 00:00:00 2001 From: LeonAir RC Date: Mon, 21 Oct 2024 10:31:27 +0200 Subject: [PATCH] added Free Glide League --- Free Glide League/fgleague.lua | 268 +++++++++++++++++++++++++ Thermal Assist/thlassist.lua | 2 +- repository/FreeGlideLeague/lang.jsn | 36 ++++ repository/apps.json | 107 ++++++++-- repository/doc/FreeGlideLeague-de.html | 13 ++ repository/doc/FreeGlideLeague-en.html | 13 ++ repository/fgleague.lc | Bin 0 -> 5933 bytes 7 files changed, 424 insertions(+), 15 deletions(-) create mode 100644 Free Glide League/fgleague.lua create mode 100644 repository/FreeGlideLeague/lang.jsn create mode 100644 repository/doc/FreeGlideLeague-de.html create mode 100644 repository/doc/FreeGlideLeague-en.html create mode 100644 repository/fgleague.lc diff --git a/Free Glide League/fgleague.lua b/Free Glide League/fgleague.lua new file mode 100644 index 0000000..d932338 --- /dev/null +++ b/Free Glide League/fgleague.lua @@ -0,0 +1,268 @@ +--[[ +Copyright (c) 2024 LeonAirRC +]] + +local enlSensorIndex +local altSensorIndex +local latSensorIndex +local lonSensorIndex +local startSwitch +local resetSwitch +local enlAlarmFile +local climbAlarmFile +local entryTime + +local gpsSensorLabels +local otherSensorLabels +local gpsSensorIDs +local gpsSensorParams +local otherSensorIDs +local otherSensorParams + +local lastGpsPoint +local lastAltitude +local lastLoopTime +local startTime +local distance +local firstEnlExceedTime + +local langContent = io.readall("Apps/FreeGlideLeague/lang.jsn") +assert(langContent ~= nil, "The file FreeGlideLeague/lang.jsn is missing") +langContent = json.decode(langContent) +local lang = langContent[system.getLocale()] or langContent["en"] +langContent = nil + +local ROW_HEIGHT = 24 +local TELEM_VALUE_Y_OFFSET = (ROW_HEIGHT - lcd.getTextHeight(FONT_BIG)) // 2 +local TELEM_UNIT_Y_OFFSET = (ROW_HEIGHT + lcd.getTextHeight(FONT_BIG)) // 2 - 1 - lcd.getTextHeight(FONT_NORMAL) +local TELEM_UNIT_WIDTH = lcd.getTextWidth(FONT_NORMAL, "km/h") + +local function onEnlSensorChanged(value) + enlSensorIndex = value - 1 + system.pSave("enl", enlSensorIndex) +end + +local function onAltSensorChanged(value) + altSensorIndex = value - 1 + system.pSave("alt", altSensorIndex) +end + +local function onLatSensorChanged(value) + latSensorIndex = value - 1 + system.pSave("lat", latSensorIndex) +end + +local function onLonSensorChanged(value) + lonSensorIndex = value - 1 + system.pSave("lon", lonSensorIndex) +end + +local function onStartSwitchChanged(value) + startSwitch = system.getInputsVal(value) ~= 0.0 and value or nil + system.pSave("startsw", startSwitch) +end + +local function onResetSwitchChanged(value) + resetSwitch = system.getInputsVal(value) ~= 0.0 and value or nil + system.pSave("resetsw", resetSwitch) +end + +local function onEnlAlarmFileChanged(value) + enlAlarmFile = value + system.pSave("enlalarm", enlAlarmFile) +end + +local function onClimbAlarmFileChanged(value) + climbAlarmFile = value + system.pSave("climbalarm", climbAlarmFile) +end + +local function onEntryTimeChanged(value) + entryTime = value + system.pSave("entrytime", entryTime) +end + +local function printTelemetryRow(row, width, label, value, unit) + local y = row * ROW_HEIGHT + value = value or "-" + lcd.drawText(6, y + TELEM_UNIT_Y_OFFSET, label, FONT_NORMAL) + lcd.drawText(width - TELEM_UNIT_WIDTH - lcd.getTextWidth(FONT_BIG, value) - 12, y + TELEM_VALUE_Y_OFFSET, value, FONT_BIG) + if unit then + lcd.drawText(width - TELEM_UNIT_WIDTH - 6, y + TELEM_UNIT_Y_OFFSET, unit, FONT_NORMAL) + end +end + +local function printTelemetry(width, _) + local flightDuration = startTime and (system.getTimeCounter() - startTime) / 1000 - entryTime or nil + local dist = startTime and string.format("%d", distance) or nil + local avgSpeed = (startTime and flightDuration > 0) and string.format("%.1f", distance * 3.6 / flightDuration) or nil + local altitude = altSensorIndex ~= 0 and system.getSensorValueByID(otherSensorIDs[altSensorIndex], otherSensorParams[altSensorIndex]) or nil + altitude = (altitude and altitude.valid) and string.format("%d", altitude.value) or nil + local time + if startTime and flightDuration >= 0 then + time = string.format("%d:%02d:%02d", flightDuration // 3600, (flightDuration // 60) % 60, flightDuration % 60) + elseif startTime then + time = string.format("%d", flightDuration) + end + printTelemetryRow(0, width, lang.distance, dist, "m") + printTelemetryRow(1, width, lang.avgSpeed, avgSpeed, "km/h") + printTelemetryRow(2, width, lang.altitude, altitude, "m") + printTelemetryRow(3, width, lang.time, time, nil) +end + +local function loop() + if resetSwitch and system.getInputsVal(resetSwitch) == 1 then + distance = 0 + lastAltitude = nil + lastGpsPoint = nil + firstEnlExceedTime = nil + startTime = nil + lastLoopTime = nil + return + end + if startTime == nil then + if startSwitch and system.getInputsVal(startSwitch) == 1 then + distance = 0 + lastAltitude = nil + lastGpsPoint = nil + firstEnlExceedTime = nil + startTime = system.getTimeCounter() + lastLoopTime = startTime + else + return + end + end + local enl = enlSensorIndex ~= 0 and system.getSensorValueByID(otherSensorIDs[enlSensorIndex], otherSensorParams[enlSensorIndex]) or nil + enl = (enl and enl.valid) and enl.value or nil + local altitude = altSensorIndex ~= 0 and system.getSensorValueByID(otherSensorIDs[altSensorIndex], otherSensorParams[altSensorIndex]) or nil + altitude = (altitude and altitude.valid) and altitude.value or nil + + local currentTime = system.getTimeCounter() + + if currentTime < startTime + 1000 * entryTime then + -- before start + if enl and enl > 300 then + system.playFile(enlAlarmFile, AUDIO_IMMEDIATE) + end + if lastAltitude and altitude and altitude > lastAltitude then + system.playFile(climbAlarmFile, AUDIO_IMMEDIATE) + end + else + -- after start + if enl and enl > 300 then + if firstEnlExceedTime == nil then + firstEnlExceedTime = currentTime + elseif currentTime >= firstEnlExceedTime + 5000 then + system.playFile(enlAlarmFile, AUDIO_IMMEDIATE) + firstEnlExceedTime = firstEnlExceedTime + 5000 + end + else + firstEnlExceedTime = nil + end + if lastLoopTime ~= nil and currentTime // 1000 > lastLoopTime // 1000 then + local gpsPoint = (latSensorIndex ~= 0 and lonSensorIndex ~= 0) and + gps.getPosition(gpsSensorIDs[latSensorIndex], gpsSensorParams[latSensorIndex], gpsSensorParams[lonSensorIndex]) + or nil + if lastGpsPoint == nil then + lastGpsPoint = gpsPoint + elseif gpsPoint ~= nil then + local distanceToLastPoint = gps.getDistance(lastGpsPoint, gpsPoint) + local kmBefore = distance // 1000 + distance = distance + distanceToLastPoint + local kmNow = distance // 1000 + if kmBefore < kmNow and kmNow >= 5 then + system.playNumber(kmNow, 0, "km", lang.distanceLabel) + end + lastGpsPoint = gpsPoint + end + end + end + + lastAltitude = altitude + lastLoopTime = currentTime +end + +local function initForm() + form.setTitle(lang.appName) + form.addRow(2) + form.addLabel({ label = lang.enlSensor }) + form.addSelectbox(otherSensorLabels, enlSensorIndex + 1, true, onEnlSensorChanged) + form.addRow(2) + form.addLabel({ label = lang.altSensor }) + form.addSelectbox(otherSensorLabels, altSensorIndex + 1, true, onAltSensorChanged) + form.addRow(2) + form.addLabel({ label = lang.gpsLatitude }) + form.addSelectbox(gpsSensorLabels, latSensorIndex + 1, true, onLatSensorChanged) + form.addRow(2) + form.addLabel({ label = lang.gpsLongitude }) + form.addSelectbox(gpsSensorLabels, lonSensorIndex + 1, true, onLonSensorChanged) + form.addRow(2) + form.addLabel({ label = lang.startSwitch }) + form.addInputbox(startSwitch, false, onStartSwitchChanged) + form.addRow(2) + form.addLabel({ label = lang.resetSwitch }) + form.addInputbox(resetSwitch, false, onResetSwitchChanged) + form.addRow(2) + form.addLabel({ label = lang.enlAlarmFile }) + form.addAudioFilebox(enlAlarmFile, onEnlAlarmFileChanged) + form.addRow(2) + form.addLabel({ label = lang.climbAlarmFile }) + form.addAudioFilebox(climbAlarmFile, onClimbAlarmFileChanged) + form.addRow(2) + form.addLabel({ label = lang.entryTime }) + form.addIntbox(entryTime, 1, 60, 10, 0, 1, onEntryTimeChanged, { label = "s" }) +end + +local function init() + gpsSensorLabels = { "..." } + otherSensorLabels = { "..." } + gpsSensorIDs = {} + gpsSensorParams = {} + otherSensorIDs = {} + otherSensorParams = {} + local sensors = system.getSensors() + for _, sensor in ipairs(sensors) do + if sensor.param ~= 0 and sensor.type == 9 then + gpsSensorLabels[#gpsSensorLabels + 1] = string.format("%s: %s", sensor.sensorName, sensor.label) + gpsSensorIDs[#gpsSensorIDs + 1] = sensor.id + gpsSensorParams[#gpsSensorParams + 1] = sensor.param + elseif sensor.param ~= 0 and sensor.type ~= 5 then + otherSensorLabels[#otherSensorLabels + 1] = string.format("%s: %s [%s]", sensor.sensorName, sensor.label, sensor.unit) + otherSensorIDs[#otherSensorIDs + 1] = sensor.id + otherSensorParams[#otherSensorParams + 1] = sensor.param + end + end + + enlSensorIndex = system.pLoad("enl", 0) + altSensorIndex = system.pLoad("alt", 0) + latSensorIndex = system.pLoad("lat", 0) + lonSensorIndex = system.pLoad("lon", 0) + startSwitch = system.pLoad("startsw") + resetSwitch = system.pLoad("resetsw") + enlAlarmFile = system.pLoad("enlalarm", "") + climbAlarmFile = system.pLoad("climbalarm", "") + entryTime = system.pLoad("entrytime", 10) + + if enlSensorIndex > #otherSensorIDs then + enlSensorIndex = 0 + end + if altSensorIndex > #otherSensorIDs then + altSensorIndex = 0 + end + if latSensorIndex > #gpsSensorIDs then + latSensorIndex = 0 + end + if lonSensorIndex > #gpsSensorIDs then + lonSensorIndex = 0 + end + + system.registerForm(1, MENU_APPS, lang.appName, initForm) + system.registerTelemetry(1, "Free Glide League", 4, printTelemetry) +end + +local function destroy() + system.unregisterTelemetry(1) +end + +collectgarbage() +return { init = init, loop = loop, destroy = destroy, author = "LeonAir RC", version = "1.0.0", name = lang.appName } \ No newline at end of file diff --git a/Thermal Assist/thlassist.lua b/Thermal Assist/thlassist.lua index c164bdb..fc3adc6 100644 --- a/Thermal Assist/thlassist.lua +++ b/Thermal Assist/thlassist.lua @@ -217,7 +217,7 @@ end ------------------------------------------------------------------------------------------------------- -- Announcement of the bearing and distance to the optimal point. --- The expected climb rate at that point is also annouced if the best-subsequence algorith is selected. +-- The expected climb rate at that point is also announced if the best-subsequence algorith is selected. ------------------------------------------------------------------------------------------------------- local function voiceOutput() if avgPoint and bestPoint then diff --git a/repository/FreeGlideLeague/lang.jsn b/repository/FreeGlideLeague/lang.jsn new file mode 100644 index 0000000..e457333 --- /dev/null +++ b/repository/FreeGlideLeague/lang.jsn @@ -0,0 +1,36 @@ +{ + "en": { + "appName": "Free Glide League", + "enlSensor": "ENL Sensor", + "altSensor": "Altitude Sensor", + "gpsLatitude": "GPS Latitude", + "gpsLongitude": "GPS Longitude", + "startSwitch": "Start Switch", + "resetSwitch": "Reset Switch", + "enlAlarmFile": "ENL Alarm", + "climbAlarmFile": "Climb Alarm", + "entryTime": "Entry time", + "distanceLabel": "Distance", + "distance": "Distance", + "avgSpeed": "Ø Speed", + "altitude": "Altitude", + "time": "Time" + }, + "de": { + "appName": "Free Glide League", + "enlSensor": "ENL Sensor", + "altSensor": "Höhe Sensor", + "gpsLatitude": "GPS Breitengrad", + "gpsLongitude": "GPS Längengrad", + "startSwitch": "Start Schalter", + "resetSwitch": "Reset Schalter", + "enlAlarmFile": "ENL Alarm", + "climbAlarmFile": "Steigen Alarm", + "entryTime": "Einflugzeit", + "distanceLabel": "Strecke", + "distance": "Strecke", + "avgSpeed": "Ø Geschwindigkeit", + "altitude": "Höhe", + "time": "Zeit" + } +} \ No newline at end of file diff --git a/repository/apps.json b/repository/apps.json index f43bdbd..8b0dc34 100644 --- a/repository/apps.json +++ b/repository/apps.json @@ -123,7 +123,11 @@ 677, 678, 679, - 680 + 680, + 652, + 653, + 3857, + 3858 ], "id": 0, "name": { @@ -132,7 +136,7 @@ "en": "Thermal Assistant" }, "previewIcon": "https://leonairrc.github.io/Jeti-Lua-Apps/repository/logo.png", - "releaseDate": "Do Apr 22 00:00:00 2021 GMT", + "releaseDate": "Do Apr 22 00:00:00 2021", "version": "1.4.2" }, { @@ -162,7 +166,11 @@ 677, 678, 679, - 680 + 680, + 652, + 653, + 3857, + 3858 ], "id": 0, "name": { @@ -171,7 +179,7 @@ "en": "Virtual Sensor" }, "previewIcon": "https://leonairrc.github.io/Jeti-Lua-Apps/repository/logo.png", - "releaseDate": "Do Apr 22 00:00:00 2021 GMT", + "releaseDate": "Do Apr 22 00:00:00 2021", "version": "1.4.1" }, { @@ -195,7 +203,11 @@ 677, 678, 679, - 680 + 680, + 652, + 653, + 3857, + 3858 ], "id": 0, "name": { @@ -204,7 +216,7 @@ "en": "Beep Editor" }, "previewIcon": "https://leonairrc.github.io/Jeti-Lua-Apps/repository/logo.png", - "releaseDate": "Do Apr 22 00:00:00 2021 GMT", + "releaseDate": "Do Apr 22 00:00:00 2021", "version": "1.2.1" }, { @@ -228,7 +240,11 @@ 677, 678, 679, - 680 + 680, + 652, + 653, + 3857, + 3858 ], "id": 0, "name": { @@ -237,7 +253,7 @@ "en": "Audio Player" }, "previewIcon": "https://leonairrc.github.io/Jeti-Lua-Apps/repository/logo.png", - "releaseDate": "Do Apr 22 00:00:00 2021 GMT", + "releaseDate": "Do Apr 22 00:00:00 2021", "version": "1.1" }, { @@ -261,7 +277,11 @@ 677, 678, 679, - 680 + 680, + 652, + 653, + 3857, + 3858 ], "id": 0, "name": { @@ -269,7 +289,7 @@ "en": "Vario Integrator" }, "previewIcon": "https://leonairrc.github.io/Jeti-Lua-Apps/repository/logo.png", - "releaseDate": "Do Apr 22 00:00:00 2021 GMT", + "releaseDate": "Do Apr 22 00:00:00 2021", "version": "1.1" }, { @@ -299,7 +319,11 @@ 677, 678, 679, - 680 + 680, + 652, + 653, + 3857, + 3858 ], "id": 0, "name": { @@ -331,7 +355,11 @@ 677, 678, 679, - 680 + 680, + 652, + 653, + 3857, + 3858 ], "id": 0, "name": { @@ -364,7 +392,11 @@ 677, 678, 679, - 680 + 680, + 652, + 653, + 3857, + 3858 ], "id": 0, "name": { @@ -375,6 +407,48 @@ "releaseDate": "Di Jun 1 00:00:00 2021", "version": "1.0" }, + { + "author": "LeonAir RC", + "description": { + "de": "https://leonairrc.github.io/Jeti-Lua-Apps/repository/doc/FreeGlideLeague-de.html", + "en": "https://leonairrc.github.io/Jeti-Lua-Apps/repository/doc/FreeGlideLeague-en.html" + }, + "files": [ + { + "destination": "Apps/fgleague.lc", + "hash": "b2a13a4620d9079d62a327d7a5c2edeec8c05761", + "size": 5933, + "url": "https://leonairrc.github.io/Jeti-Lua-Apps/repository/fgleague.lc" + }, + { + "destination": "Apps/FreeGlideLeague/lang.jsn", + "hash": "fb3ac43e5e78b644723b319fc238d776e9808826", + "size": 1045, + "url": "https://leonairrc.github.io/Jeti-Lua-Apps/repository/FreeGlideLeague/lang.jsn" + } + ], + "hw": [ + 674, + 675, + 676, + 677, + 678, + 679, + 680, + 652, + 653, + 3857, + 3858 + ], + "id": 0, + "name": { + "de": "Free Glide League", + "en": "Free Glide League" + }, + "previewIcon": "https://leonairrc.github.io/Jeti-Lua-Apps/repository/logo.png", + "releaseDate": "Mo Okt 21 00:00:00 2024", + "version": "1.0.0" + }, { "author": "LeonAir RC", "description": { @@ -395,10 +469,15 @@ 677, 678, 679, - 680 + 680, + 652, + 653, + 3857, + 3858 ], "id": 0, "name": { + "de": "DJM Timer", "en": "DJM Timer" }, "previewIcon": "https://leonairrc.github.io/Jeti-Lua-Apps/repository/logo.png", diff --git a/repository/doc/FreeGlideLeague-de.html b/repository/doc/FreeGlideLeague-de.html new file mode 100644 index 0000000..34f1ae9 --- /dev/null +++ b/repository/doc/FreeGlideLeague-de.html @@ -0,0 +1,13 @@ + + + + + Free Glide League + + +

Free Glide League

+

+ description coming soon... +

+ + diff --git a/repository/doc/FreeGlideLeague-en.html b/repository/doc/FreeGlideLeague-en.html new file mode 100644 index 0000000..34f1ae9 --- /dev/null +++ b/repository/doc/FreeGlideLeague-en.html @@ -0,0 +1,13 @@ + + + + + Free Glide League + + +

Free Glide League

+

+ description coming soon... +

+ + diff --git a/repository/fgleague.lc b/repository/fgleague.lc new file mode 100644 index 0000000000000000000000000000000000000000..fbd3b04efafdf2923cb919090ae80afe8964b754 GIT binary patch literal 5933 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 KqXV4Z{QMVF^w