[ST Edge] - Local HTTP Requests

I have a number of DTHs that use the groovy HubAction routine to perform an HTTP GET/POST to control devices around my home.

What is the simplest equivalent in the Lua Edge Driver world?

Is there a http routine I can call from my Command Handlers?

Many thanks Tim

1 Like

Hey, @Tim99

You can use the socket.http to build your HTTP Requests, and in case you’re expecting specific information from these devices, use the ltn12 module to sink the response data into a table. For example:

local http = require('socket.http')
local ltn12 = require("ltn12")

local res_payload = {}
local _, code = http.request({
  url = '192.168.x.xxx',
  sink = ltn12.sink.table(res_payload),
  method='GET'
})

print(table.concat(json_payload))
1 Like

Hi all, I am making progress in my quest to replace my HubAction groovy based DTH with an Edge Driver.
I am trying to make an Http call to the server but get the following error.
Http Response = [string “socket”]:1301: Permission Denied
Any thoughts?

I am using the following code in the command handler
local res_payload = {}
local _, code = http.request({
url = ‘url = ‘http://192.168.188.121:80/api/callAction?deviceID=101&name=turnOn’’,
sink = ltn12.sink.table(res_payload),
method=‘GET’
})
log.info("Http Response = ", code)
print(table.concat(res_payload))

Some of my servers require Basic Authentication to access their resources.

From what I read the encoded username and password needs to be included in the http request headers. Something like this:
headers = { authentication = “Basic " … (mime.b64(username …”:" … password)) }

However when I require the mine library like this
local mime = require(“mime”)

I get the following error:
Lua: runtime error: [string “init.lua”]:12: Module ‘mime’ not found

Is the mime library missing from the smartthings environment or is there another way?

Nice catch, that would explain the Permission Denied error response.


Import the base64 from the st module, i.e.:

local base64 = require 'st.base64'

local basic_auth = base64.encode( ... )

I have added Basic Authentication to the http.request but still get the following error.

[string “socket”]:1301: Permission Denied

It feels like an environment problem, I don’t believe its related to the http server.

local function httpGET(HCcommand)
local username = “xxxxxx”
local password = “xxxxxx”

local res_payload = {}
client, code, headers, status = http.request({
url = “http://192.168.188.121:80/api/callAction?deviceID=101&name=turnOn”,
sink = ltn12.sink.table(res_payload),
method =“GET”,
headers = { authentication = “Basic " … (base64.encode(username …”:" … password)) }
})
log.info("Http client = ", client)
log.info("Http code = ", code)
log.info("Http headers = ", headers)
log.info("Http status = ", status)

Results in:

Http client =
Http code = [string “socket”]:1301: Permission Denied
Http headers =
Http status =

Hmm… interesting!

Is this a server you’re running on your computer? I’d like to replicate the issue and it would be awesome if you could give me a few details on your environment.


UPDATE

@Tim99 Reviewing your implementation I just realized that you’re setting your authorization heather as it would work on other libraries (such as axios), hence, try passing it as follows:

local res_payload = {}
local _, code = http.request({
  url = 'http://192.168.x.xxx',
  sink = ltn12.sink.table(res_payload),
  method='GET',
  headers={
    ['Authorization'] = 'Basic '..base64.encode( ... )
  }
})

LIFT OFF!
@erickv thanks very much for pointing that out. I have now a fully working ON/OFF/SetLevel/SetColor Edge Driver using the http.request with Basic Authorization to remotely call my Fibaro Home Centre lighting Subsystem. Here is the code which may be useful for others.

CALL TO ROUTINE TO SWITCH ON LIGHT
local success, responseBody = command_handler.send_lan_command(
http://192.168.188.121’,
‘GET’,
‘api/callAction?deviceID=101&name=turnOn’,
{})

HTTP CALL FUNCTION
function command_handler.send_lan_command(url, method, path, body)
local dest_url = url…’/’…path
local res_body = {}
local username = ‘xxxxxx’
local password = ‘xxxxxxxxxxxx’

– HTTP Request
local _, code = http.request({
method=method,
url=dest_url,
sink=ltn12.sink.table(res_body),
headers={
[‘Content-Type’] = ‘application/x-www-urlencoded’,
[‘Authorization’] = 'Basic ’ … (base64.encode(username … ‘:’ … password ))
}})
log.info(‘code=’, code)
– Handle response
if code == 200 or code == 202 then
return true, res_body
end
return false, nil
end

1 Like

Awesome, @Tim99!

I’m glad that you’re now able to interact with your Smart Home environment through drivers.

Remember that if you want to share your driver with the community, you can distribute a shared channel so others can install your driver (more info here).

My ST Edge driver is now working just fine. I can turn lights on & off in the Fibaro lighting sub-system, from Smartthings.

Next I would like to implement inbound commands into Smartthings from the lighting sub-system.
I am using the server code below from the lightbulb-lan-esp8266 sample as a starting point.

What is the Http.request call the Lighting sub-system needs to make to execute code in my edge driver.
I’m trying a POST to ttp://192.168.188.22:39500/push-state with no autthorization at the moment. I get a code 202 returned, but no messages from live logging. Any thoughts?

local lux = require(‘luxure’)
local cosock = require(‘cosock’).socket
local json = require(‘dkjson’)
local log = require(‘log’)

local hub_server = {}

function hub_server.start(driver)
local server = lux.Server.new_with(cosock.tcp(), {env=‘debug’})

– Register server
driver:register_channel_handler(server.sock, function ()
server:tick()
end)

– Endpoint
log.info(“SERVER ENDPOINT”)

server:post(’/push-state’, function (req, res)

log.info("SERVER:POST")

local body = json.decode(req:get_body())

local device = driver:get_device_info(body.uuid)
if body.switch then
  driver:on_off(device, body.switch)
elseif body.level then
  driver:set_level(device, tonumber(body.level))
end
res:send('HTTP/1.1 200 OK')

end)

log.info(“SERVER LISTEN”)
server:listen()
driver.server = server
end

return hub_server

@Tim99 Maybe your Hub is receiving the request, but it isn’t handling it properly… or the request information is not accurate.

Are you pointing to the PORT that the driver automatically defines for the server? you can log this as driver.server.port.

Also, if you followed the whole implementation, you’ll notice that the IP and PORT are being shared with the device as soon as it gets integrated (see reference).

@erickv your are a star AGAIN!
The problem was that currently we cannot specify a Listener port. It is randomly chosen each time the driver is run. Which of course is a pain. Is this going to change in Production?

My device does not support SSDP, so my workaround plan is to retrieve the port being used via the variable driver.server.port, send this Port value to my device, which the device can use to POST back HTTP calls to the hub.

1 Like

@Tim99 Awesome! I’m very glad that you’re getting positive results with your integration.

Unfortunately, this is no going to change in the near future, because it isn’t a huge blocker and can be addressed within or independently of discovery workflows (just as you did).