This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

HTTP PUT image to 2N intercom
#1
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:
12345678910111213141516171819202122232425262728293031323334353637383940
-- /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
Reply
#2
Try this. filedata variable must contain your image in JPEG format. Use io.readfile() to read a local file.
Code:
123456789101112131415161718192021222324252627282930
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.
Reply
#3
I updated the filedata line 
Code:
1
filedata = 'io.readfile(/img/STOP_DNE_241x320.jpeg)' -- image data as a binary string
But I get this error now
Code:
1234567891011121314
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"   } }
Reply
#4
You converted the whole command into a string by placing the '' at the wrong position ..

Try: filedata = io.readfile(‘/img/STOP_DNE_241x320.jpeg')
Reply
#5
Ok, tried that now, new error.

Code:
1234
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
Reply
#6
Try this (assuming that you've uploaded the image to Vis. graphics > Image / backgrounds):
Code:
1
filedata = io.readfile(‘/www/scada/resources/img/STOP_DNE_241x320.jpeg')
Reply
#7
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 Smile

Code:
123456789
09.02.2023 15:38:52 * arg: 1   * number: 1 * arg: 2   * number: 200 * arg: 3   * string: {   "success" : true }
Reply
#8
Thank you so much!
Finally got it working with digest, it could problably be done prettier, but this works for me  Big Grin

Code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
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
Reply


Forum Jump: