Voice Announcement Flight time

thats the plan. Thanks ever so much for all your help. It’s really really appreciated.

Hey Yuri just wanted to let you know the script is still throwing an error. It’s related to the Floor function.
Going to do more reading on the subject but wanted you to be aware.


So I did some testing with the code.
I added notes for line numbers so I could understand where the error was occurring. As well as extra commentary to understand what each line did.
I then commented out the call to get teh vehicle flight time and added a value of 60000 or one minute to the script to see if it reported to the GCS and it did. Proper voice announcement and all.

I then took the comment out for the line local ms = vehicle:get_flying_time_ms()
When I ran it again I got an error. IT seems that this call is “nil” if not flying. So I assume the if statement that follows will not work as it’s looking for less then 1 and not nil.
More reading to do.

local RUN_INTERVAL = 30000 --30 second interval 1
local MAV_SEVERITY_WARNING = 4 --set to w1 arning level 2
– 3
– 4
function flight_time()-- 5
–local ms = vehicle:get_flying_time_ms() – This looks correct 6
ms=60000 --7
–if ms < 1 then return flight_time, RUN_INTERVAL end – skip if we aren’t flying 8
local mins= math.floor(ms / 60000) – convert ms to minutes by dividing my 60000 then using floor to round it 9
gcs:send_text(MAV_SEVERITY_WARNING, tostring(mins) … " Minutes")-- send GCS a warning message of flight time converted to a string 10
return flight_time, RUN_INTERVAL --11
end --12
–13
return flight_time()–14

This was a slightly harder problem to solve than I expected. I’m not sure how to cast/convert a uint32_t value to a proper Lua number, so I wrote a string substitution routine to do some pseudo-rounding. I tested the following script under Copter 4.2.1-rc1, and it works well. Mission Planner gives a TTS warning every 30 seconds while flying. I think QGC will do similar.

local RUN_INTERVAL = 30000
local MAV_SEVERITY_WARNING = 4

function flight_time()
    local ms = vehicle:get_time_flying_ms()
    if ms < 500 then return flight_time, RUN_INTERVAL end  -- skip if we aren't flying

    local mins = tostring(ms / 6000)  -- this is minutes * 10 for rounding later

    -- brute force rounding of a uint32_t value (there is probably a better way)
    local strlen = string.len(mins)
    if strlen == 1 then
        mins = string.format('0.%s minutes', mins)
    else
        mins = string.format('%s.%s minutes', string.sub(mins, 1, strlen - 1), string.sub(mins, - 1))
    end

    -- send flight time in minutes to the GCS
    gcs:send_text(MAV_SEVERITY_WARNING, mins)
    return flight_time, RUN_INTERVAL
end

return flight_time()

And here’s a slightly more elegant way to do the same, converting the uint32_t first to a string, and then to a number.

local RUN_INTERVAL = 30000
local MAV_SEVERITY_WARNING = 4

function flight_time()
    local ms = vehicle:get_time_flying_ms()
    if ms < 500 then return flight_time, RUN_INTERVAL end  -- skip if we aren't flying

    local mins = tonumber(tostring(ms)) / 60000

    gcs:send_text(MAV_SEVERITY_WARNING, string.format('%.1f minutes', mins))
    return flight_time, RUN_INTERVAL
end

return flight_time()

@iampete, maybe we need a binding for tonumber() that takes a uint32_t value to avoid this kludge.

We have to int and to float functions eg

local mins = ms:tofloat() / 60000

ms:toint() should also work.

Aha! Thank you - that’s the missing link!

local RUN_INTERVAL = 30000
local MAV_SEVERITY_WARNING = 4

function flight_time()
    local ms = vehicle:get_time_flying_ms()
    if ms < 500 then return flight_time, RUN_INTERVAL end  -- skip if we aren't flying

    local mins = ms:tofloat() / 60000

    gcs:send_text(MAV_SEVERITY_WARNING, string.format('%.1f minutes', mins))
    return flight_time, RUN_INTERVAL
end

return flight_time()

Wow thanks guys.
I will give this a try.

1 Like

Hey Pete. Forgive me I am not a programmer…at least not in many many moons.
But wouldn’t tofloat convert the number to a floating point decimal value. Just trying to understand the function. IF it does then wouldn’t it create a time value or say 1.333 minutes.

Whereas the toint would create an integer value…so 1.333 would be 1 minute.
I could be all wrong and likely am. But wanted to check.

Also what does this “string.format(’%.1f minutes’, mins))” do. It looks like a string formatting function but I don’t’ get the 1F part.

Thanks again to both of you.

We were dealing with type inconsistencies. The vehicle:get_time_flying_ms() method returns a “userdata” object that is bound to the C++ uint32_t type (32 bit unsigned integer). As such, the Lua engine isn’t “smart” enough to see that as an arithmetic value, though you can get away with some basic operations based on some further bindings that have been defined for us.

Lua only has one type to represent arithmetic values called “number.” The :toint() and :tofloat() methods return integer or floating point representations of the uint32_t userdata object as a Lua number type.

So, for example, if ms = vehicle:get_time_flying_ms() stores a uint32_t value of 165000 in the ms variable, we can use ms:toint() to get a Lua number that has the same value, 165000. ms:tofloat() will get us 165000.0 (which is just semantics, really, but probably a little more “correct”).

Taking that example a bit farther, local mins = ms:tofloat() / 60000 will result in the number 2.75 stored in the mins variable. Then, string.format('%.1f minutes', mins)) converts mins to a formatted string with a single decimal place, resulting in a displayed value of “2.8 minutes.”

I hope that makes sense.

Wow
Thank you for that explanation. That makes so much sense.
Thank you Yuri. I really appreciate it.

To elaborate slightly more on why this is a problem. Were using single precision lua in AP. As Yuri said we only have one type in lua, a “number”. This is a floating point representation using 32bits. The return of the time function is a uint32 type again in 32 bits. This can represent every integer from 0 to 4294967295 exactly. If you try and represent 4294967295 as a float with 32 bits you get 4294967300, losing some accuracy, you only get 8 digits (it depends on the number). So in order to not loose accuracy you keep the number as a uint32 for as long as you can.

See wiki for more detail than you could possibly want:

1 Like

Interesting
Thank you Pete.

Hey guys I wanted to give you a quick update on the use of this script. The weather has been crappy so not had a chance to try it till this past weekend.
It works great. The only thing I don’t like is it reports time in decimal format…so 1.3 minutes. Thats a bit odd, but it works great.
I might have a look and see how to add battery percentage next.
Thank you both for your help with this.

Cheers

If you want to round to the nearest whole minute, make this change:

local RUN_INTERVAL = 30000
local MAV_SEVERITY_WARNING = 4

function flight_time()
    local ms = vehicle:get_time_flying_ms()
    if ms < 500 then return flight_time, RUN_INTERVAL end  -- skip if we aren't flying

    local mins = ms:tofloat() / 60000

    gcs:send_text(MAV_SEVERITY_WARNING, string.format('%.0f minutes', mins))
    return flight_time, RUN_INTERVAL
end

return flight_time()

Thank you I can easily make that change.
I assume then if I want to add battery info I just need to make a change like this.
local RUN_INTERVAL = 30000
local MAV_SEVERITY_WARNING = 4

function flight_time()
local ms = vehicle:get_time_flying_ms()
if ms < 500 then return flight_time, RUN_INTERVAL end – skip if we aren’t flying

local mins = ms:tofloat() / 60000
local BatCap = battery:capacity_remaining_pct(0)

gcs:send_text(MAV_SEVERITY_WARNING, string.format('%.0f minutes', mins))
gcs:send_text(0, BatCap) -- send the Battery Percentage
return flight_time, RUN_INTERVAL

end

return flight_time()

That would certainly report battery percentage, but it would just be a number with no context contained in a message of “emergency” severity. This might be better:

local RUN_INTERVAL = 30000
local MAV_SEVERITY_WARNING = 4

function flight_time()
    local ms = vehicle:get_time_flying_ms()
    if ms < 500 then return flight_time, RUN_INTERVAL end  -- skip if we aren't flying

    local mins = ms:tofloat() / 60000
    local batt = battery:capacity_remaining_pct(0)

    gcs:send_text(MAV_SEVERITY_WARNING, string.format('%.0f minutes, %d percent', mins, batt))
    return flight_time, RUN_INTERVAL
end

return flight_time()

Interesting.
Ok I will give that a try, Thanks man.
This is very interesting stuff