Strange error - NETWORK ERROR: “HTTP CLIENT ERROR

This is a new one for me.

I have a scheduled job that waters the plants and this morning I had this error message which appeared part the way through the execution of the sequence.

The log file said
NETWORK ERROR: “HTTP CLIENT ERROR - See log for details”

2 things come to mind.
1 - What log file is referenced here as this line was taken from the FB log in the web interface.
2 - If the script runs locally, why or how can there be a network error.

How do I start to debug this?

Is the watering based on curves? Maybe for each plant it will do a lookup for the curves value?

A solution (redesign) would be to have a local synced copy of the database per user. I build these kind of systems in the past using couchdb and pouchdb. ‘offline first’, but that would be a really big effort on the software side.

1 Like

No. I have not dived into the curves at the moment as I could not find any source to download the curves and I have too many other issues with FB to consider tackling that.

The watering is just 3 seconds per plant.
I was talking to @jsimmonds about my concerns over the internet aspect of the FB operations. In my mind, a local script should be just that and have Zero dependencies on the internet. That does not seem to be the case at the moment as indicated by the error above. Log files are a little thin and in my mind need a little bit of a redesign as the toggles do not really capture the logging output needs. It is good to capture starts and stops and steps but the raw log option does not seem to do anything and I am struggling to do deeper investigations without detailed log files. You need like a flight recorder type thing that captures deeper activities as well as the higher level logs.

I think we need a shared library of plants with FB parameters (growth and water curves etc) that can be used rather than all of us trying to work it out for ourselves.

It may end up being an additional library that extends the existing plant library and supplements the plants with additional parameters. The existing plant library does not seem to be an active project, or maybe I have misread what I saw.

It has happened again…

The logging say see log files but what log files should I be looking at and also, as this was a scheduled local script, why is there a network connection involved. Where do I start to unpick this?

Start with the steps in that Water All sequence. There must be Lua code in there doing http() or api() calls.

I’ll see where that very LOUD error message arises if you like ?

edit

The FarmBot Web API servers are fairly heavily request-rate-limited . . if you’re doing requests across a large collection of things you need to control that rate to something under 3/sec.

1 Like

Thanks @jsimmonds
You are right. The code is this

local points = api({method = “GET”, url = “/api/points”})

Looking this up in the LUA library the code is simple but it has no error handling wrapper.

I do not know LUA language but always willing to have a go.

Original code
local points = api({method = "GET", url = "/api/points"})

Watering code incorporating safety checks and retries


local watering_time = variable("Watering Time (Seconds)")
start_time = os.time() * 1000

-- New code

theNumberOfAllowableNetworkTries = 3
theSecondsDelayBetweenRetries = 5
failedToGetPoints = 1

while( theNumberOfAllowableNetworkTries > 0 )
do
	theNumberOfAllowableNetworkTries = theNumberOfAllowableNetworkTries -1
	local points = api({method = "GET", url = "/api/points"})

  	if result then
		toast("Plant list obtained", "info")
        theNumberOfAllowableNetworkTries = 0
		failedToGetPoints = 0
	else
		toast("Error - Unable to obtain list of plants (Retrying)" , "warn")
		wait (theSecondsDelayBetweenRetries * 1000)
		theNumberOfAllowableNetworkTries = theNumberOfAllowableNetworkTries -1
	end
end

if failedToGetPoints then
	toast("Fatal Error - Unable to obtain list of plants (EXIT)", "error")
	exit (1) 
end

-- New End code 

local plants = {}

for k, v in pairs(points) do
    if v.pointer_type == "Plant" then
        table.insert(plants, {name = v.name, x = v.x, y = v.y})
    end
end

table.sort(plants, function(l, r)
    -- "close enough" approximation.
    if math.abs(l.x - r.x) < 150 then
        return l.y < r.y
    else
        return l.x < r.x
    end
end)

count = 0
total = #plants
job = "Watering all " .. total .. " plants"

send_message(
    "info",
    "Watering all " .. total .. " plants for " .. watering_time .. " seconds each",
    "toast")

for k, v in pairs(plants) do
    coordinates = "(" .. v.x .. ", " .. v.y .. ")"
    set_job_progress(job, {
        percent = 100 * (count) / total,
        status = "Moving to " .. (v.name or "plant") .. " at " .. coordinates,
        time = start_time
    })
    move_absolute(v.x, v.y, 0)
    set_job_progress(job, {
        percent = 100 * (count + 0.5) / total,
        status = "Watering " .. (v.name or "plant") .. " for " .. watering_time .. " seconds",
        time = start_time
    })
    write_pin(8, "digital", 1)
    wait(watering_time * 1000)
    write_pin(8, "digital", 0)
    count = count + 1
end

set_job_progress(job, {
    percent = 100,
    status = "Complete",
    time = start_time
})

Is there a way to obtain the ‘error code’ from this LUA code so the error can be properly handed by the calling sequence - Ideally with an IF statement?

@jsimmonds So John. How is my first piece of code? LOL. (I do like verbose variable names as it means you don’t need comments)

Hi @mvillion

That’s just 1 API request, so that’s not the source of this issue.

I doubt there is. Lua ends up calling into FBOS Elixir code. We need to go there.

This is the only api call in the code (that I can see)

local watering_time = variable("Watering Time (Seconds)")
start_time = os.time() * 1000

local points = api({method = "GET", url = "/api/points"})

local plants = {}

for k, v in pairs(points) do
    if v.pointer_type == "Plant" then
        table.insert(plants, {name = v.name, x = v.x, y = v.y})
    end
end

table.sort(plants, function(l, r)
    -- "close enough" approximation.
    if math.abs(l.x - r.x) < 150 then
        return l.y < r.y
    else
        return l.x < r.x
    end
end)

count = 0
total = #plants
job = "Watering all " .. total .. " plants"

send_message(
    "info",
    "Watering all " .. total .. " plants for " .. watering_time .. " seconds each",
    "toast")

for k, v in pairs(plants) do
    coordinates = "(" .. v.x .. ", " .. v.y .. ")"
    set_job_progress(job, {
        percent = 100 * (count) / total,
        status = "Moving to " .. (v.name or "plant") .. " at " .. coordinates,
        time = start_time
    })
    move_absolute(v.x, v.y, 0)
    set_job_progress(job, {
        percent = 100 * (count + 0.5) / total,
        status = "Watering " .. (v.name or "plant") .. " for " .. watering_time .. " seconds",
        time = start_time
    })
    write_pin(8, "digital", 1)
    wait(watering_time * 1000)
    write_pin(8, "digital", 0)
    count = count + 1
end

set_job_progress(job, {
    percent = 100,
    status = "Complete",
    time = start_time
})

And so it follows that the API is not local??

Plain API usually refers to the FarmBot Inc. Web API.
On GitHub the Lua api() function is in farmbot_os/priv/lua/api.lua at main · FarmBot/farmbot_os · GitHub
which also shows where that LOUD error message originates :slight_smile:

This all leads to my observations of non-local operations. I found an FB article that states that offline operations was not the primary consideration of the FB architecture. I think that creates many issues in reliability and dependency. Even the fact you need to got to the internet to find your plants is sub-optimal.

I think the internet has great value to store copies of the data and provide the user interface for programming but I would be pushing to allow executing to be local with no dependencies (Where possible such as ‘where are my plants’)

@jsimmonds Hi John

I have re-written the water LUA code to incorporate a verification loop to check 3 times over a minute (Every 20 seconds)

– New code


theNumberOfAllowableNetworkTries = 3
theSecondsDelayBetweenRetries = 20
failedToGetPoints = 1

while( theNumberOfAllowableNetworkTries > 0 )
do
	theNumberOfAllowableNetworkTries = theNumberOfAllowableNetworkTries -1
	local points = api({method = "GET", url = "/api/points"})

  	if result then
		toast("Plant list obtained", "info")
        theNumberOfAllowableNetworkTries = 0
		failedToGetPoints = 0
	else
		toast("Error - Unable to obtain list of plants (Retrying)" , "warn")
		wait (theSecondsDelayBetweenRetries * 1000)
		theNumberOfAllowableNetworkTries = theNumberOfAllowableNetworkTries -1
	end
end

if failedToGetPoints then
	toast("Fatal Error - Unable to obtain list of plants (EXIT)", "error")
	exit (1) 
end

-- New End code 

local plants = {}

for k, v in pairs(points) do
    if v.pointer_type == "Plant" then
        table.insert(plants, {name = v.name, x = v.x, y = v.y})
    end
end

table.sort(plants, function(l, r)
    -- "close enough" approximation.
    if math.abs(l.x - r.x) < 150 then
        return l.y < r.y
    else
        return l.x < r.x
    end
end)

count = 0
total = #plants
job = "Watering all " .. total .. " plants"

send_message(
    "info",
    "Watering all " .. total .. " plants for " .. watering_time .. " seconds each",
    "toast")

for k, v in pairs(plants) do
    coordinates = "(" .. v.x .. ", " .. v.y .. ")"
    set_job_progress(job, {
        percent = 100 * (count) / total,
        status = "Moving to " .. (v.name or "plant") .. " at " .. coordinates,
        time = start_time
    })
    move_absolute(v.x, v.y, 0)
    set_job_progress(job, {
        percent = 100 * (count + 0.5) / total,
        status = "Watering " .. (v.name or "plant") .. " for " .. watering_time .. " seconds",
        time = start_time
    })
    write_pin(8, "digital", 1)
    wait(watering_time * 1000)
    write_pin(8, "digital", 0)
    count = count + 1
end

set_job_progress(job, {
    percent = 100,
    status = "Complete",
    time = start_time
})

@jsimmonds
It always failed to detect if the API has returned the correct data. Can you look at where I have gone wrong? This is my first attempt at coding LUA and I pinched the template from this

I would have thought that the API read would also have the same detection as the documentation says *

Returns nil if there was an error.

– Create a new point at (200, 200, 0) with a radius of 100
result = api({
method = “post”,
url = “/api/points”,
body = {
x = 200,
y = 200,
z = 0,
radius = 100,
pointer_type = “GenericPointer”
}
})

if result then
toast(“Point creation ok”, “debug”)
else
toast(“Error - See logs for details”, “error”)
end

Hi @mvillion copy/paste error I think. Check where result is used.

I don’t understand.

From what I can see, the commands are the same.

if result then

Whatever it is you can see, I can’t see it.

@mvillion maybe 2 lines of code will help . . where does result receive a value ?


	local points = api({method = "GET", url = "/api/points"})

  	if result then

You are right.

In a previous language I have used, result is a reserved word that is populated on the return of certain calls. I misread the documentation.

I have fixed the code, and it now runs, but there is another error I do not understand.

This is the code I have written
The ‘existing code’ works perfectly which call the API

The ‘new code’ fails with this error code

but they both call the same API request.

I cannot see what the difference is and I know the API is called as the toast message is displayed.

Any ideas? I am certain it is PEBCAK

local watering_time = variable("Watering Time (Seconds)")
start_time = os.time() * 1000


--Exisitng code
--  local points = api({method = "GET", url = "/api/points"})
--End of existing code



-- New code
theNumberOfAllowableNetworkTries = 3
theSecondsDelayBetweenRetries = 5
failedToGetPoints = 1

while( theNumberOfAllowableNetworkTries > 0 )
do
    theNumberOfAllowableNetworkTries = theNumberOfAllowableNetworkTries -1
    local points = api({method = "GET", url = "/api/points"})
     toast("Reading", "success")


    if points then
        toast("Plant list obtained", "info")
        theNumberOfAllowableNetworkTries = 0
        failedToGetPoints = 0
    else
        toast("Error - Unable to obtain list of plants (Retrying)", "warn")
        wait (theSecondsDelayBetweenRetries * 1000)
        theNumberOfAllowableNetworkTries = theNumberOfAllowableNetworkTries -1
    end
end

if (failedToGetPoints == 1) then
    toast("Fatal Error - Unable to obtain list of plants (EXIT)", "error")
    return
end
--End of new code



--All code below is original
local plants = {}

for k, v in pairs(points) do
    if v.pointer_type == "Plant" then
        table.insert(plants, {name = v.name, x = v.x, y = v.y})
    end
end

table.sort(plants, function(l, r)
    -- "close enough" approximation.
    if math.abs(l.x - r.x) < 150 then
        return l.y < r.y
    else
        return l.x < r.x
    end
end)

count = 0
total = #plants
job = "Watering all " .. total .. " plants"

send_message(
    "info",
    "Watering all " .. total .. " plants for " .. watering_time .. " seconds each",
    "toast")

for k, v in pairs(plants) do
    coordinates = "(" .. v.x .. ", " .. v.y .. ")"
    set_job_progress(job, {
        percent = 100 * (count) / total,
        status = "Moving to " .. (v.name or "plant") .. " at " .. coordinates,
        time = start_time
    })
    move_absolute(v.x, v.y, 0)
    set_job_progress(job, {
        percent = 100 * (count + 0.5) / total,
        status = "Watering " .. (v.name or "plant") .. " for " .. watering_time .. " seconds",
        time = start_time
    })
    write_pin(8, "digital", 1)
    wait(watering_time * 1000)
    write_pin(8, "digital", 0)
    count = count + 1
end

set_job_progress(job, {
    percent = 100,
    status = "Complete",
    time = start_time
})


I found the problem

in LUA the local statement keep the variable name contained to the block it was declared in.
As it was declared within the do, while loop (which was bad practice anyway) the variable scope was only until the end of the while loop thus when it moved to the next code block, the points variable no longer existed.

Below is the correct code construct. As the local declarations are outside of the while loop, they will continue into and through the code block.

local theNumberOfAllowableNetworkTries = 9
local theSecondsDelayBetweenRetries = 20
local failedToGetPoints = 1
local points

while( theNumberOfAllowableNetworkTries > 0 )
do
    theNumberOfAllowableNetworkTries = theNumberOfAllowableNetworkTries -1
    points = api({method = "GET", url = "/api/points"})
.....

I am going to keep this code in test and see if it stops the network timeouts I occasionally was getting as it now has a retry function. I have set the retry to every 20 seconds for 3 minutes which should be lomng enough if the FB drops Wifi. If it is longer than a 3 minute wifi outage, something else probably has gone wrong.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.