Translate cURL HTTP POST to Lua ?

I’ve tried reading through forum posts and documentation, but I still don’t understand how to do this. Here is a cURL HTTP POST command to change a Radio Thermostat (WIFI) fan mode to On (API v1.3):

2.5.5 Set fan mode to ON The following curl command sets the fan mode to ON. $ curl -d '{"fmode":2}' http://192.168.1.101/tstat {?success?: 0}

So, the Vera wiki for HTTP POST
says:

Invoke HTTP URL with POST request (Method 3)

local http = require(“socket.http”)

– 5 Second timeout
http.TIMEOUT = 5

– The return parameters are in a different order from luup.inet.wget(…)
result, status = http.request(“http://192.168.0.113/runprocess.htm”, “run=run”)

So where does the parameter go to change fmode to 2?

I’m a complete novice at this. Thanks for any help.

Did you try the URL:
http://192.168.1.101/tstat?fmode=2

[quote=“RichardTSchaefer, post:2, topic:177867”]Did you try the URL:
http://192.168.1.101/tstat?fmode=2[/quote]
Surprisingly, I had actually tried that format. It returns an error:

{"error_msg":"Invalid HTTP API"}

Edit: If I just try the GET version of the command (http:///tstat/fmode), it works.

{"fmode":1}

(Testing in Firefox; not Lua)

OK, since this question didn’t go anywhere, I kept looking. I found that cURL is built into OpenWRT running Vera. The Lua os.execute([command]) works to start cURL. An example of setting your Radio Thermostat fan mode to on (Test Luup code):

os.execute(“curl -d ‘{"fmode":2}’ http:///tstat”)

This particular setting is built into the Radio Thermostat plug-in, but there are others, such as humidifier setting, that is not.

Did you try the SO version:
http - lua socket POST - Stack Overflow

or the one from the Sonos:
L_Sonos1.lua in trunk – Sonos Wireless HiFi Music Systems

Either will be better than using [tt]os.execute()[/tt], given the overheads of launching a shell process.

I’ve looked at posts like that, but they leave me pretty lost. There are a bunch of extra parameters and I have no idea what they do, or what to do with the single variable I’m trying to change. I wish there was a Google Translate for programming languages.

I will throw some more computer jargon around … apparently this device implements a RESTful api. That means they require a POST method to cause an action to happen or change a value.

POST base access is more complicated in LUA. There are more data structures to have to deal with (as opposed to a simple URL).

I think you just learned your first lesson in programming … Some times simple things are just hard!

Finding this post made me a little sick. I have been working with a Spark Core in another thread (http://forum.micasaverde.com/index.php/topic,17323.60.html), and just realized that what should be a simple HTTP call, won’t be. Spark API is a REST API…

So have my cURL command working perfectly when I issue from windows cURL, stuck on translating it for VERA…

Have a look at the Sonos code I linked above.

I does the requisite POST. You’ll need to do the work to formulate what to go into that POST body, since the Sonos uses XML in it’s POST Body.

REST is a pretty thin layer on top of HTTP. If you are familiar with HTTP then you should have no issue. I suspect that it is an unfamiliarity with the mechanics of HTTP that is the root problem.

When you want to make an HTTP request you need to be able to enumerate:

  • What method are you using? Say, POST.
  • What URL is the request going to? Say, http:// some host /some/path
  • What headers do you need? (Some are mandated by HTTP such as Content-Type and Content-Length. Those have to be included no matter what. But there might be extra headers. Or there might be none.)
  • What is the body of the request? (This might be a bunch of parameters in the form name=value&name=value, or it might be in some other format.)

Spell out those bits and we can show you where they need to go in the boilerplate Lua.

Man, I have tried everything. Spark Core documentation on the API only includes cURL, and I have tried everything to make a call work. Here is my best idea to date, which does not work…

[code]local url = require(“socket.url”)
local socket = require(“socket”)
local http = require(“socket.http”)
local ltn12 = require(“ltn12”)

http.TIMEOUT = 5

postBody = “access_token=[MY ACCESS TOKEN]”


– Execute the resulting URL, and collect the results as a Table

local resultTable = {}
local status, statusMsg = http.request{
url = “https://api.spark.io/v1/devices/[MY DEVICE NUMBER]/NeatoGo”,
sink = ltn12.sink.table(resultTable),
method = “POST”,
headers = {[“Accept”] = “/”,
[“Content-Length”] = postBody:len(),
[“Content-Type”] = “application/x-www-form-urlencoded”},
source = ltn12.source.string(postBody),
}
[/code]

According to the socket documentation [url=http://w3.impa.br/~diego/software/luasocket/http.html]http://w3.impa.br/~diego/software/luasocket/http.html[/url], a POST should be as easy as http.request(url [, body])

So to try that I have:

local status, statusMsg = http.request{ url = "https://api.spark.io/v1/devices/[Input Device ID]/NeatoGo", body= "access_token=[Input Access Token"}

Adding in luup.log("Status " … status … “Status Message” … statusMsg)

I see I am getting back a status of “1” and a statusMsg of “301”…?

HTTP 301 is “Moved Permanently”. The server is telling you that the Real URL is somewhere else. The Luasocket HTTP request function has a “redirect” option which you could try setting to true. If that works then Luasocket will automatically perform a second request to the Real URL.

Failing that, you can do it manually. Your code will need to read the Location header from the response, and repeat the request. Or you can see where it’s redirecting you to by running curl in verbose mode and taking note of the second request that curl is automatically doing for you.

Your (long-form) http.request() code snippet looks pretty good. Actual working code won’t look very different to that. The short-form one might not give you enough power to tweak the request to what the server exactly wants.

Does your access token contain any characters that are special to HTTP? Those characters are: % & < + space # ; /

I just tried redirect = 1 and redirect = true, but no change. Also, I did verbose on cURL and don’t see any redirect.

C:\Program Files\cURL\bin>curl -v https://api.spark.io/v1/devices/[My Device ID]/NeatoGo

  • Hostname was NOT found in DNS cache
  • Trying 54.86.91.207…
  • Connected to api.spark.io (54.86.91.207) port 443 (#0)

I have checked the URL and access token two dozen times and I know they are right.
I switched over to Lua for Windows and get the same error in SciTE. When I parse the table returned I get:

301 Moved Permanently

301 Moved Permanently


nginx/1.6.0

Access tokens and device IDs are hex - so only numeric + a-f…

If the URL is identical then perhaps there is still a difference in the HTTP headers. There’s an option in curl for showing the headers of the request and of the response. Perhaps Lua is supplying too many or too few or the wrong kinds of headers.

You’re using HTTPS so I can’t recommend sniffing the packets with Wireshark, unfortunately.

OK, I got it working in Lua for Windows. I had to add in the Luasec library… [url=https://github.com/brunoos/luasec/wiki]https://github.com/brunoos/luasec/wiki[/url]

[code]
local socket = require(“socket”)
local https = require(“ssl.https”)
local ltn12 = require(“ltn12”)

https.TIMEOUT = 5

local body, code, headers, status = https.request(“https://api.spark.io/v1/devices/[My Device]/[My Function]”, “access_token=[My Access Token]”)
print(status)[/code]

How do I add the library to Vera? Or might it already be there??

It’s there already.

[quote=“futzle, post:16, topic:177867”]If the URL is identical then perhaps there is still a difference in the HTTP headers. There’s an option in curl for showing the headers of the request and of the response. Perhaps Lua is supplying too many or too few or the wrong kinds of headers.

You’re using HTTPS so I can’t recommend sniffing the packets with Wireshark, unfortunately.[/quote]

@futzle, the Lua for Windows above works in Vera without modification. Many thanks for the idea of cunning cURL in verbose mode, which made me realize the SSL handshake was likely the redirect and hence I needed to move to the Luasec library…

[quote=“wilme2, post:19, topic:177867”][quote=“futzle, post:16, topic:177867”]If the URL is identical then perhaps there is still a difference in the HTTP headers. There’s an option in curl for showing the headers of the request and of the response. Perhaps Lua is supplying too many or too few or the wrong kinds of headers.

You’re using HTTPS so I can’t recommend sniffing the packets with Wireshark, unfortunately.[/quote]

@futzle, the Lua for Windows above works in Vera without modification. Many thanks for the idea of cunning cURL in verbose mode, which made me realize the SSL handshake was likely the redirect and hence I needed to move to the Luasec library…[/quote]

Hi Wilme

I have a few questions regarding this thread:
I am trying to do something similar. In stead of the function you call I would like to POST and GET the tinker app (out of the box) functions. At least the digitalwrite and digitalread ones. Does the code you provided apply to this as well?
Where do you actually add the code for your functions? Did you add a device or do you run it in a scene?

Any help would be appreciated. THanks