Posts: 5
Threads: 1
Joined: Feb 2023
Reputation:
0
07.02.2023, 23:57
(This post was last modified: 08.02.2023, 09:13 by coolaew.)
Hello!
Using a SE spaceLYnk I'm trying to send an image to a 2N IP Verso intercom when the firealarm is activated and remove the image when the fire alarm resets.
The intercom uses HTTPS and Digest authentication.
I've got it working in postman but not from the SpaceLYnk.
I only got the DELETE part working with basic authentication.
Code: -- /img/STOP_DNE_241x320.png (I want to use this)
-- /img/fire_241x320.png (Alternate image for testing)
fire = grp.getvalue('32/1/1')
--log(fire)
if fire == true then
log('FIREALARM ACTIVE')
-- Send image to intercom
-- https://10.0.0.230/api/display/image?display=ext1
elseif fire == false then
log('No fire')
-- Delete image from intercom
-- https://10.0.0.230/api/display/image?display=ext1
local https = require("ssl.https")
local ltn12 = require("ltn12")
local response_body = {}
local request_body = ""
local res, code, response_headers, status = https.request{
url = "https://test:test@10.0.0.230/api/display/image?display=ext1",
method = "DELETE",
source = ltn12.source.string(request_body),
sink = ltn12.sink.table(response_body),
protocol = "tlsv1_2"
}.
if code == 200 then
log("Image deleted successfully!")
else
log("Error deleting image: "..code)
end
else
log('ERROR')
end
I found some threads here about digest authentication and image handling, but I'm not experienced with coding so I was not able to put the code together from what I found to do what I need.
I have saved the image to /img/ which i created in the root of the ftp server logged in as ftp.
Here's a link to the 2N API documentation, part 5.9.2 is about sending images and clearing the display.
https://wiki.2n.com/hip/hapi/latest/en
Posts: 7865
Threads: 42
Joined: Jun 2015
Reputation:
450
Try this. filedata variable must contain your image in JPEG format. Use io.readfile() to read a local file.
Code: require('ltn12')
require('ssl.https')
boundary = os.date('%d%m%Y%H%M%S')
filedata = '...' -- image data as a binary string
body = table.concat({
'--' .. boundary,
'Content-Disposition: form-data; name="blob-image"; filename="picture.jpeg"',
'Content-Type: image/jpeg',
'',
filedata,
'--' .. boundary .. '--',
''
}, '\r\n')
resp = {}
res, code, response_headers, status = ssl.https.request({
url = 'https://test:test@10.0.0.230/api/display/image?display=ext1',
sink = ltn12.sink.table(resp),
method = 'PUT',
source = ltn12.source.string(body),
headers = {
['content-length'] = #body,
['content-type'] = 'multipart/form-data; boundary=' .. boundary
}
})
log(res, code, table.concat(resp))
For digest auth you can use this example: https://forum.logicmachine.net/showthrea...64#pid9364
Replace ssl.https.request in the above code with request from the digest code.
Posts: 5
Threads: 1
Joined: Feb 2023
Reputation:
0
I updated the filedata line
Code: filedata = 'io.readfile(/img/STOP_DNE_241x320.jpeg)' -- image data as a binary string
But I get this error now
Code: 08.02.2023 22:19:12
* arg: 1
* number: 1
* arg: 2
* number: 200
* arg: 3
* string: {
"success" : false,
"error" : {
"code" : 12,
"param" : "blob-image",
"description" : "invalid parameter value"
}
}
Posts: 1776
Threads: 6
Joined: Jul 2015
Reputation:
118
You converted the whole command into a string by placing the '' at the wrong position ..
Try: filedata = io.readfile(‘/img/STOP_DNE_241x320.jpeg')
Posts: 5
Threads: 1
Joined: Feb 2023
Reputation:
0
09.02.2023, 05:26
(This post was last modified: 09.02.2023, 05:43 by coolaew.)
Ok, tried that now, new error.
Code: 09.02.2023 06:25:54
User script:19: invalid value (nil) at index 5 in table for 'concat'
stack traceback:
[C]: in function 'concat'
I'm suspecting that either the path to the file is wrong or there's a permission missing somewhere.
I've tried using /img/FILE and /data/ftp/img/FILE
Posts: 7865
Threads: 42
Joined: Jun 2015
Reputation:
450
Try this (assuming that you've uploaded the image to Vis. graphics > Image / backgrounds):
Code: filedata = io.readfile(‘/www/scada/resources/img/STOP_DNE_241x320.jpeg')
Posts: 5
Threads: 1
Joined: Feb 2023
Reputation:
0
Thanks! Finally got it working.
It might have been your suggestion, or it might have been my typo in the filename that fixed it... 241/214...
Now I just have to get digest auth working and I'm all set
Code: 09.02.2023 15:38:52
* arg: 1
* number: 1
* arg: 2
* number: 200
* arg: 3
* string: {
"success" : true
}
Posts: 5
Threads: 1
Joined: Feb 2023
Reputation:
0
Thank you so much!
Finally got it working with digest, it could problably be done prettier, but this works for me
Code: fire = grp.getvalue("32/1/1")
if fire == true then -- Uploads image to intercom
log("FIREALARM ACTIVE")
local skthttp = require("socket.http")
local skturl = require("socket.url")
local ltn12 = require("ltn12")
local md5sum = require("encdec").md5
boundary = os.date("%d%m%Y%H%M%S")
filedata = io.readfile("/www/scada/resources/img/STOP_DNE_214x320.jpeg") -- image data as a binary string
body =
table.concat(
{
"--" .. boundary,
'Content-Disposition: form-data; name="blob-image"; filename="picture.jpeg"',
"Content-Type: image/jpeg",
"",
filedata,
"--" .. boundary .. "--",
""
},
"\r\n"
)
resp = {}
local hash = function(...)
return md5sum(table.concat({...}, ":"))
end
local parse_header = function(header)
local result = {}
for key, value in (header .. ","):gmatch("(%w+)=(.-),") do
if value:sub(1, 1) == '"' then -- strip quotes
result[key:lower()] = value:sub(2, -2)
else
result[key:lower()] = value
end
end
return result
end
local make_digest_header = function(headers)
local digest = {}
for _, header in ipairs(headers) do
if not header.unquote then
header[2] = '"' .. header[2] .. '"'
end
digest[#digest + 1] = header[1] .. "=" .. header[2]
end
return "Digest " .. table.concat(digest, ", ")
end
local _request = function(req)
if not req.url then
return nil, "missing url"
end
local url = skturl.parse(req.url)
local user, password = url.user, url.password
local sink = req.sink
if not user or not password then
return nil, "missing credentials in url"
end
url.user, url.password, url.authority, url.userinfo = nil, nil, nil, nil
req.url = skturl.build(url)
local source
if req.source then
local chunks = {}
local capture = function(chunk)
if chunk then
chunks[#chunks + 1] = chunk
end
return chunk
end
local chunk_id = 0
source = function()
chunk_id = chunk_id + 1
return chunks[chunk_id]
end
req.source = ltn12.source.chain(req.source, capture)
end
req.sink = nil
local body, code, hdrs = skthttp.request(req)
if code == 401 and hdrs["www-authenticate"] then
local ht = parse_header(hdrs["www-authenticate"])
if not ht.realm or not ht.nonce then
return nil, "missing realm/nonce from response"
end
local qop = ht.qop
if qop and qop ~= "auth" then
return nil, "unsupported qop " .. tostring(qop)
end
if ht.algorithm and ht.algorithm:lower() ~= "md5" then
return nil, "unsupported algo " .. tostring(ht.algorithm)
end
local nc = "00000001"
local cnonce = string.format("%08x", os.time())
local uri = skturl.build({path = url.path, query = url.query})
local method = req.method or "GET"
local response = hash(hash(user, ht.realm, password), ht.nonce, nc, cnonce, "auth", hash(method, uri))
req.headers = req.headers or {}
local auth = {
{"username", user},
{"realm", ht.realm},
{"nonce", ht.nonce},
{"uri", uri},
{"cnonce", cnonce},
{"nc", nc, unquote = true},
{"qop", "auth"},
{"algorithm", "MD5"},
{"response", response}
}
if ht.opaque then
table.insert(auth, {"opaque", ht.opaque})
end
req.headers.authorization = make_digest_header(auth)
if not req.headers.cookie and hdrs["set-cookie"] then
-- not really correct but enough for httpbin
local cookie = (hdrs["set-cookie"] .. ";"):match("(.-=.-)[;,]")
if cookie then
req.headers.cookie = "$Version: 0; " .. cookie .. ";"
end
end
if req.source then
req.source = source
end
req.sink = sink
body, code, hdrs = skthttp.request(req)
end
return body, code, hdrs
end
local request = function(url)
local t = type(url)
if t == "table" then
return _request(table.clone(url))
elseif t == "string" then
local req = {}
local _, code, headers = _request({url = url, sink = ltn12.sink.table(req)})
return table.concat(req), code, headers
end
end
res, code, response_headers, status =
request(
{
url = "https://test:test@10.0.0.230/api/display/image?display=ext1",
sink = ltn12.sink.table(resp),
method = "PUT",
source = ltn12.source.string(body),
headers = {
["content-length"] = #body,
["content-type"] = "multipart/form-data; boundary=" .. boundary
}
}
)
log(res, code, table.concat(resp))
elseif fire == false then -- Removes image from intercom
log("Firealarm reset")
local skthttp = require("socket.http")
local skturl = require("socket.url")
local ltn12 = require("ltn12")
local md5sum = require("encdec").md5
local hash = function(...)
return md5sum(table.concat({...}, ":"))
end
local parse_header = function(header)
local result = {}
for key, value in (header .. ","):gmatch("(%w+)=(.-),") do
if value:sub(1, 1) == '"' then -- strip quotes
result[key:lower()] = value:sub(2, -2)
else
result[key:lower()] = value
end
end
return result
end
local make_digest_header = function(headers)
local digest = {}
for _, header in ipairs(headers) do
if not header.unquote then
header[2] = '"' .. header[2] .. '"'
end
digest[#digest + 1] = header[1] .. "=" .. header[2]
end
return "Digest " .. table.concat(digest, ", ")
end
local _request = function(req)
if not req.url then
return nil, "missing url"
end
local url = skturl.parse(req.url)
local user, password = url.user, url.password
local sink = req.sink
if not user or not password then
return nil, "missing credentials in url"
end
url.user, url.password, url.authority, url.userinfo = nil, nil, nil, nil
req.url = skturl.build(url)
local source
if req.source then
local chunks = {}
local capture = function(chunk)
if chunk then
chunks[#chunks + 1] = chunk
end
return chunk
end
local chunk_id = 0
source = function()
chunk_id = chunk_id + 1
return chunks[chunk_id]
end
req.source = ltn12.source.chain(req.source, capture)
end
req.sink = nil
local body, code, hdrs = skthttp.request(req)
if code == 401 and hdrs["www-authenticate"] then
local ht = parse_header(hdrs["www-authenticate"])
if not ht.realm or not ht.nonce then
return nil, "missing realm/nonce from response"
end
local qop = ht.qop
if qop and qop ~= "auth" then
return nil, "unsupported qop " .. tostring(qop)
end
if ht.algorithm and ht.algorithm:lower() ~= "md5" then
return nil, "unsupported algo " .. tostring(ht.algorithm)
end
local nc = "00000001"
local cnonce = string.format("%08x", os.time())
local uri = skturl.build({path = url.path, query = url.query})
local method = req.method or "GET"
local response = hash(hash(user, ht.realm, password), ht.nonce, nc, cnonce, "auth", hash(method, uri))
req.headers = req.headers or {}
local auth = {
{"username", user},
{"realm", ht.realm},
{"nonce", ht.nonce},
{"uri", uri},
{"cnonce", cnonce},
{"nc", nc, unquote = true},
{"qop", "auth"},
{"algorithm", "MD5"},
{"response", response}
}
if ht.opaque then
table.insert(auth, {"opaque", ht.opaque})
end
req.headers.authorization = make_digest_header(auth)
if not req.headers.cookie and hdrs["set-cookie"] then
-- not really correct but enough for httpbin
local cookie = (hdrs["set-cookie"] .. ";"):match("(.-=.-)[;,]")
if cookie then
req.headers.cookie = "$Version: 0; " .. cookie .. ";"
end
end
if req.source then
req.source = source
end
req.sink = sink
body, code, hdrs = skthttp.request(req)
end
return body, code, hdrs
end
local request = function(url)
local t = type(url)
if t == "table" then
return _request(table.clone(url))
elseif t == "string" then
local req = {}
local _, code, headers = _request({url = url, sink = ltn12.sink.table(req)})
return table.concat(req), code, headers
end
end
local response_body = {}
local request_body = ""
local res, code, response_headers, status =
request {
url = "https://test:test@10.0.0.230/api/display/image?display=ext1",
method = "DELETE",
source = ltn12.source.string(request_body),
sink = ltn12.sink.table(response_body),
protocol = "tlsv1_2"
}
if code == 200 then
log("Image deleted successfully!")
else
log("Error deleting image: " .. code)
end
else
log("ERROR")
end
|