Actually… Samsung removed the messagebox service from their TV sets starting with the the D-series models… The B-Series through C-series have it (verified with my UN55C5000), but it E-Series does not (verified with my UN40EH5300).
There is a way to determine if the set supports it or not… send a UPNP SSDP M-SEARCH packet to the multicast broadcast address (239.255.255.250) on port 1900… The TV will respond with the devices/services that it supports… MediaRenderer, AVTransport and RemoteControlReceiver for most models, and additionally either MessageBoxService and/or PersonalMessageReceiver (and I’ve also seen reference to a UPNP PersonalMessageTest for very early models with beta firmware)…
The code to do the SSDP search is:
local STVR_DEV = "urn:samsung.com:device:PersonalMessageReceiver:1" -- MessageBoxDevice
local STVR_DEV = "urn:samsung.com:device:RemoteControlReceiver:1" -- remote control device
local STVS_DEV = "urn:samsung.com:device:MainTVServer2:1" - digital media renderer device
local STVR_SVC = "urn:samsung.com:service:MultiScreenService:1" - screenshare - used by Samsung TV app framework
local STVS_SVC = "urn:samsung.com:service:MainTVAgent2:1" - digital media renderer service
local STVM_SVC = "urn:samsung.com:service:MessageBoxService:1" - MessageBox Service
function doSsdpSearch(searchFilter, retries)
-- do an ssdp search - get the response and retrieve the SCPD file
-- returns contents, DeviceURL, DeviceIpAddress, DeviceIpPort
local count = retries
local http = require("socket.http")
debug("doSsdpSearch - Search for '"..searchFilter.."' -------------------------------------------------------")
local ssdpPacketTable = {
[1] = "M-SEARCH * HTTP/1.1\r\n",
[2]= "Host: 239.255.255.250:1900\r\n",
[3] = "MAN: \"ssdp:discover\"\r\n",
[4] = "MX: 10\r\n",
[5] = "ST:"..searchFilter.."\r\n",
[6] = "CONTENT-LENGTH: 0\r\n",
[7] = "\r\n"
}
local ssdpPacket = table.concat(ssdpPacketTable )
repeat
local udp = assert(socket.udp())
udp:setoption("broadcast",true)
udp:settimeout(10)
assert(udp:sendto(ssdpPacket, "239.255.255.250", 1900))
address = udp:receive()
udp:close()
count = count - 1
debug("doSsdpSearch - Try # ".. (retries - count).." - received '"..(address or "").."'")
until ( (count == 0) or (address ~= "") )
if (address ~= "") then
location = string.match(address, "[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:%s*(.-)\r?\n")
debug("doSsdpSearch - received location = '"..(location or "").."'")
ipAddress = string.match(location,"http%:%/%/(.-)%/")
if ipAddress ~= nil and string.match(ipAddress, ":") then
ipPort = string.match(ipAddress, ":(.*)")
ipAddress = string.match(ipAddress, "(.*):")
else
ipPort = "80"
end
local contents,response=http.request(location)
response = string.match(response,"[%s+]?%d+")
debug("doSsdpSearch - Found device at '"..ipAddress.."' port '"..ipPort.."'")
return response, contents, location, ipAddress, ipPort
else
return "404", "", "", "", ""
end
end
local searchResponse = doSsdpSearch("*", 5) -- search for all uPNP devices, try 5 times
searchResponse = doSsdpSearch(STVM_SVC, retries) -- search for the Samsung messageboxservice only - 5 treies
One thing to remember… If you are talking to the uPNP services on a Samsung TV… It only understands SOAP… so you need to speak SOAP to it… If it does not understand what you are asking it, it will (and does) ignore you… MOST malformed requests (passing the headers to luasockets as a string is malformed) are silently ignored.
like this:
function SendSoapRequest(DeviceIpAddress, DeviceIpPort, SERVICE, Soap_URL, soap_action, soap_arguments)
-- Soap_URL is the control URL for the service you are targeting (obtained from the SCPD file)
local http = require("socket.http")
local ltn12 = require"ltn12"
local h_ds = "\045"
local h_lt = "\060"
local h_qm = "\063"
local h_gt = "\062"
local h_qt = "\034"
local req = h_lt..h_qm.."xml version="..h_qt..'1.0'..h_qt.." encoding="..h_qt..'UTF-8'..h_qt..h_qm..h_gt
req = req .. h_lt.."s:Envelope s:encodingStyle="..h_qt.."http://schemas.xmlsoap.org/soap/envelope/"..h_qt.." xmlns:s="..h_qt.."http://schemas.xmlsoap.org/soap/envelope/"..h_qt..h_gt
req = req .. h_lt.."s:Body"..h_gt
req = req .. h_lt.."u:" .. soap_action .. " xmlns:u="..h_qt.. SERVICE .. h_qt..h_gt
for aName, aValue in pairs(soap_arguments) do
req = req .. h_lt .. aName .. h_gt .. aValue .. h_lt.."/" .. aName .. h_gt
end
req = req .. h_lt.."/u:" .. soap_action .. h_gt..h_lt.."/s:Body"..h_gt..h_lt.."/s:Envelope"..h_gt
local reqHeaders = {
["HOST"] = DeviceIpAddress..":"..DeviceIpPort,
["USER-AGENT"] = "DLNADOC/1.50 SEC_HHP_MiCasaVerde /1.0",
["SOAPACTION"] = h_qt .. SERVICE.."#"..soap_action .. h_qt,
["Content-Type"] = "text/xml;charset="..h_qt.."utf-8"..h_qt,
["Content-Length"] = tostring(#req)
}
debug("SendSoapRequest - Headers:\n"..print_table(reqHeaders).."\n")
debug("SendSoapRequest - Request:\n"..req.."\n")
local respBody = {}
local rHeaders, rCode, rError = http.request({
method = "POST",
url = Soap_URL,
headers = reqHeaders,
source = ltn12.source.string(req),
sink = ltn12.sink.table(respBody)
})
if ((rCode == nil) or (rCode == "")) then
log("SendSoapRequest - Request FAILED!!")
end
if (soap_action:sub(1,3) == "Get") then
-- if the command is a get command, return the command response (the SCPD file)
return respBody
end
end
Good Luck… 8-}
PS: This IS working (well… was working… I slightly modified it to be standalone rather than part of a plugin) code…