RoomMe - Presence Detector

Hi

Is anyone using the RoomMe api with Vera ?

The RoomMe API is a local interface that enables 3rd party systems to receive specific-people room entry and leave events over LAN communications.

​When a person enters a room, the API sends a Room Entry Event, with the name of the entering user as well as his/her control priority for the room.
​When a person leaves a room, the API sends a Room Exit Event, with the name of the user that left as well indication whether that user has been the last person in the room.

Events are sent from the RoomMe app on the user’s phone via WiFi to a local IP address of a controller / system. ​

The API document in PDF format, is here

The sensors will post the following via http to an IP:Port I specify in their configuration

Here’s an example of the json that is sent .

{"type": "Event","userId": 0, "userName": "Joe", "roomName": "Living Room", "sensorId": "98072D0B9B72", "event": {"name": "RoomEntry", "inControl": true}}

Another, this time for RoomExit…

{"type": "Event","userId": 0, "userName": "Joe", "roomName": "Living Room", "sensorId": "98072D0B9B72", "event": {"name": "RoomExit", "inControl": true}}

The powerup one is…

{"type": "Event","userId": 0, "userName": "Joe", "roomName": "Living Room", "sensorId": "98072D0B9B72", "event": {"name": "PowerUp", "Version":"1.9"}}

I like the idea of this system. The reviews on Amazon are a bit sketchy and at over $100 per room seems a bit pricy. While we usually carry our phones from room-to-room but this system relies on that.

1 Like

Interesting they are also on the UK Amazon site at £65 each. Pretty expensive for one in all your rooms however.

Multi System Reactor could be used to send http requests out to its API and query the json data, then store the result in a variable.

You could then have Reactor set the state of say a virtual switch or motion sensor in Vera based on the value of that variable.

That’s how I’d do it anyway.

“Wireless: TI-CC2640 Bluetooth Low Energy”

It’s nothing special just Bluetooth. Surely Bluetooth has a long enough range your phone could be connected to another room your not in, which one review I’ve just read says that.

“The device kept losing its connection, it didn’t seem to work well and it would get confused because it could see my smart phone on other rooms in the house since the Bluetooth was strong enough.”

Can you make your phone connect automatically to the nearest Bluetooth device? If you have a voice assistant in each room with Bluetooth you could probably do this for free using Tasker.

Agreed, although a bit that intrigued me is that it also supports Tizen Smart Watches, so you can potentially go phone-less with these.

I now have a couple installed and set up, one in the hallway and one in the kitchen, (as 9 times out of 10 people will pass under both sensors in the direction they’re travelling (so if I add to that an existing door sensor, I hopefully have something intriguing to work with)

FYI - I’m currently running them with Homebridge, to integrate with iOS more - https://www.npmjs.com/package/homebridge-roomme

Overall so far they seem to be working well, and you do need to fiddle with the settings and the entry/exit ranges to get to the sweet spot, (not perfect by any means), but an interesting approach and I like their potential

Thanks @cw-kid , there doesn’t seem to be a native http request/api facility, the RoomMe app will just send out any one of the earlier HTTP Post messages I shared.

For RoomMe to work with Vera, I need something set up/configured that is constantly listening on a specific port ready to accept and process the json that’s sent. E.g.

If I can get something to accept the api post, I should be able to extract the values to then set attributes

local json = require "dkjson"
local examplehttppost = [[

{"type": "Event","userId": 0,"userName": "Joe", "roomName": "Living Room", "sensorId": "98072D0B9B72", "event": {"name": "RoomEntry", "inControl": true}}
]]

local j = json.decode(examplehttppost)

local Type, userId, userName, sensorId, roomName, eventName, eventControl = j.type, j.userId, j.userName, j.roomName, j.sensorId, j.event.name, j.event.inControl

print(Type, userId, userName, roomName, sensorId, eventName, eventControl)

I see, so it’s their app that sends out a http request with the data to some end point you specify, so it’s one way Out.

I thought you could maybe send IN a http request to RoomMe API to query it etc.

Not sure then how to do it.

Maybe some of the Devs on here will know.

Thanks @cw-kid

I think I know what I need. - I believe I need Vera to be able to listen for any HTTP POST requests that are made by the RoomMe client. - hence I was just curious if anyone on this forum had anything already for these RoomMe sensors.

Regarding Reactor, I actually thought @togglebits might have what I needed with his lua websocket offering → GitHub - toggledbits/LuWS: A WebSocket client implementation for Luup (Vera and openLuup) systems, with optional async receive (responsive),

… but I can’t work out what to do as Ideally just want this to run the ‘listening’ command via the StartUp code , rather than a dedicated plugin.

HTTP POST events are not WebSockets. Vaguely related, but not enough so to be operationally equivalent. LuWS would not provide the functionality you are looking for here, unless their API also supports WebSockets.

Thanks @rigpapa

What exactly would we need for Vera to have a port constantly open to listen for, and process HTTP POSTs ?

By any chance do you have any examples that you could share, that we could use?

You would need to register a Luup request handler using luup.register_handler(). The handler itself is a function that receives certain parameters. This is usually done in a plugin but you can do it in startup Lua.

function RoomMeHandler( req, params, outputfmt )
end

luup.register_handler( 'RoomMeHandler', 'RoomMe' )

I haven’t provided any function implementation yet, and I’ll go into a bit of that in a moment, but let’s look at what’s here, starting at the bottom. The luup.register_handler() call registers a request handler for making Luup requests (which are HTTP requests). The first argument is the name of the function to handle the requests, which must be a global function. The second argument is the name/ID of the handler.

Once this registration has happened, a request to http://your-vera-ip:3480/data_request?id=lr_RoomMe will cause the named handler function to be called. Notice that the id parameter of this request/URL has lr_ (lowercase-L lowercase-R underscore) prepended to the ID we gave in the handler call. This is how the request is connected to the handler.

The handler is called when Luup’s internal web server receives a GET or POST request at the above URL. Within the function (your implementation), the most important part is the params argument, where you will receive the values of all parameters passed on the request as a Lua table. If the URL parameters were ?id=lr_RoomMe&room=Dining then we would expect to have params.room with a value of Dining. You can ignore the req and outputfmt arguments for this purpose.

On a POST request, the post body (in your case, the JSON payload) will be in params.post_data, and it will be a string that you have to parse, so you’ll need to load a JSON library. Once you have that, you can start processing the request.

function RoomMeHandler( req, params, outputfmt )
    local json = require( 'dkjson' ) -- typical/default JSON parser for Veras
    -- Parse the JSON data
    local data, pos, err = json.decode( params.post_data )
    if err then
        -- Signal requester there was a problem and log the error message and data received.
        luup.log( "RoomMeHandler invalid JSON data at " .. tostring(pos) .. ": " .. tostring(err), 1 )
        luup.log( data, 1 )
        return "Invalid request data", "text/plain"
    end
    if "RoomEntry" == data.event then
        -- Add your code here to handle a RoomEntry event
    elseif "RoomExit" == data.event then
        -- Add your code here to handle a RoomExit event
    else
        -- Unhandled event type (use of tostring() is defensive/guard nil)
        luup.log( "Ignoring unhandled event " .. toString( data.event ), 2 )
        return "Ignored unhandled event", "text/plain"
    end
    return "OK", "text/plain"
end

This skeleton should be filled in with your implementation. Note that the request handler function here also returns two values from every exit point – it must do this every time, success or error. The first value is a string, and the second value is the MIME type of the data in the string. In this case, I’ve just used simple plain text responses.

Great, thanks so much @rigpapa !

I’ve created a separate luup file for Vera, and linked the Lua StartUp handler to it

local roomme = require("xxRoomMe")
RoomMeHandler = roomme.RoomMeHandler
luup.register_handler( 'RoomMeHandler', 'RoomMe' )

And to test it , I’ve tried to use the following to simulate the RoomMe POST commands which I found here - > http://forum.micasaverde.com/index.php?topic=32035.0

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

-- Test
function sendRequest()
local path = "http://192.168.102.10:3480/data_request?id=lr_RoomMe"
  local payload = [[ {"type": "Event","userId": 0, "userName": "Joe", "roomName": "Living Room", "sensorId": "98072D0B9B72", "event": {"name": "RoomEntry", "inControl": true}} ]]
  local response_body = { }

  local res, code, response_headers, status = http.request
  {
    url = path,
    method = "POST",
    headers =
    {
--      ["Authorization"] = "Do I need an Authorization header?", 
      ["Content-Type"] = "application/json",
      ["Content-Length"] = payload:len()
    },
    source = ltn12.source.string(payload),
    sink = ltn12.sink.table(response_body)
  }
  luup.task('Response: = ' .. table.concat(response_body) .. ' code = ' .. code .. '   status = ' .. status,1,'Sample POST request with JSON data',-1)
end

sendRequest()

But unfortunately it’s not working, and I’m getting the following errors in the log. Any ideas ? What’s ‘pegmatch’?

01	07/27/21 20:50:39.918	LuaInterface::CallFunction_Request function RoomMeHandler name RoomMe failed /usr/lib/lua/dkjson.lua:693: bad argument #2 to 'pegmatch' (string expected, got nil) <0x74adc520>
02	07/27/21 20:50:39.918	JobHandler_LuaUPnP::REQ_Handler handler failure for lr_RoomMe <0x74adc520

Looks like an error on the handler. Can you post your handler code as well?

Sure, here you go…

module("xxRoomMe", package.seeall)

function RoomMeHandler( req, params, outputfmt )
    local json = require( 'dkjson' ) -- typical/default JSON parser for Veras
    -- Parse the JSON data
    local data, pos, err = json.decode( params.post_data )
    if err then
        -- Signal requester there was a problem and log the error message and data received.
        luup.log( "RoomMeHandler invalid JSON data at " .. tostring(pos) .. ": " .. tostring(err), 1 )
        luup.log( data, 1 )
        return "Invalid request data", "text/plain"
    end
    if "RoomEntry" == data.event then
		luup.log( data.event )
        -- Add your code here to handle a RoomEntry event
    elseif "RoomExit" == data.event then
		luup.log( data.event )
        -- Add your code here to handle a RoomExit event
    else
        -- Unhandled event type (use of tostring() is defensive/guard nil)
        luup.log( "Ignoring unhandled event " .. toString( data.event ), 2 )
        return "Ignored unhandled event", "text/plain"
    end
    return "OK", "text/plain"
end

Looks fine. Somehow the POST body is not making it in your request. I just tested this with Postman and it works fine.

Hummm, I’m drawing blanks here, I’ve just tried a different POST script, but it’s still the same result in the logs ?

local http = require("socket.http");
local ltn12 = require("ltn12");
local path = "http://192.168.102.10:3480/data_request?id=lr_RoomMe"
local body = [[ {"type": "Event","userId": 0, "userName": "Joe", "roomName": "Living Room", "sensorId": "98072D0B9B72", "event": {"name": "RoomEntry", "inControl": true}} ]]
local response_body = { }
local res, code, response_headers, status = http.request
{
    url = path,
    method = "POST",
    headers =
    {
          ["Content-Type"] = "application/json",
          ["Content-Length"] = body:len()
    },
    source = ltn12.source.string(body),
}

print(res, code, response_headers, status)

The print output was as follows

1     200     table: 0x236ea70     HTTP/1.1 200 OK

But the log still shows the same.

01	07/28/21 9:19:05.754	LuaInterface::CallFunction_Request function RoomMeHandler name RoomMe failed /usr/lib/lua/dkjson.lua:693: bad argument #2 to 'pegmatch' (string expected, got nil) <0x73e4a520>
02	07/28/21 9:19:05.754	JobHandler_LuaUPnP::REQ_Handler handler failure for lr_RoomMe <0x73e4a520>

Also what’s Postman ?

I added some additional debug content to the start of the RoomMeHandler function.

function RoomMeHandler( req, params, outputfmt )
	luup.log("RoomMe Starting")
	luup.log(req, params, outputfmt)
	luup.log("req = ", req)
	luup.log("params = ", params)
	luup.log("outputfmt = ", outputfmt)
    local json = require( 'dkjson' ) -- typical/default JSON parser for Vera
    ..

Which didn’t help much, other than perhaps confirming the handler function is not seeing/receiving the json ?

50	07/28/21 9:40:37.378	luup_log:0: RoomMe Starting <0x7462c520>
01	07/28/21 9:40:37.378	luup_log interface 0xe5b7d8 args 3 <0x7462c520>
01	07/28/21 9:40:37.379	LuaInterface::CallFunction_Request function RoomMeHandler name RoomMe failed /usr/lib/lua/dkjson.lua:693: bad argument #2 to 'pegmatch' (string expected, got nil) <0x7462c520>
02	07/28/21 9:40:37.380	JobHandler_LuaUPnP::REQ_Handler handler failure for lr_RoomMe <0x7462c520>

It’s actually not even logging based on your code vs the log snippet you posted. Or you posted the wrong snippet.

Your use of luup.log() is incorrect, so that may be why as well. The first argument should be the string to be logged. If you have any follow-on or inserted data, you have to stringify it and concatenate it; you can’t supply it as additional arguments as you have done. The second argument, which is optional, is a log level for the string (50 is debug and the default, 1 is error and will appear in red, 2 is warning and will appear in yellow, other values are used by Luup to differentiate logging from different subsystems).

Corrected:

	luup.log("RoomMe Starting")
	luup.log(tostring(req) .. ", " .. tostring(params) .. "," .. tostring(outputfmt))
	luup.log(table.concat( { tostring(req), tostring(params), tostring(outputfmt) }, ", " )) -- another way of doing the previous line
	luup.log("req = " .. tostring(req) )
	luup.log("params = " .. tostring( params) )

The next problem is that tables, like params will not render their contents, but rather something unhelpful like table (0xff3492). So for a table, you need to either write an expander, or use the JSON library to do it for you:

-- Load the JSON library first
local json = require( 'dkjson' )
luup.log("RoomMe Starting")
luup.log("params="..json.encode( params ))

This will properly dump the contents of params to the log, rather than the cryptic table identifier.

Thanks again @rigpapa

I’ve added the luup.log debug content to the handler function,

function RoomMeHandler( req, params, outputfmt )
	local json = require( 'dkjson' ) -- typical/default JSON parser for Veras
	luup.log("RoomMe Starting")
	luup.log(tostring(req) .. ", " .. tostring(params) .. "," .. tostring(outputfmt))
	luup.log(table.concat( { tostring(req), tostring(params), tostring(outputfmt) }, ", " )) -- another way of doing the previous line
	luup.log("req = " .. tostring(req) )
	luup.log("params = " .. tostring( params) )
    luup.log("params v2 ="..json.encode( params ))

and the log now shows the following …

50	07/28/21 19:42:09.353	luup_log:0: RoomMe Starting <0x74772520>
50	07/28/21 19:42:09.353	luup_log:0: RoomMe, table: 0xf45198, <0x74772520>
50	07/28/21 19:42:09.353	luup_log:0: RoomMe, table: 0xf45198,  <0x74772520>
50	07/28/21 19:42:09.354	luup_log:0: req = RoomMe <0x74772520>
50	07/28/21 19:42:09.354	luup_log:0: params = table: 0xf45198 <0x74772520>
50	07/28/21 19:42:09.355	luup_log:0: params v2 ={"{\"type\": \"Event\",\"userId\": 0, \"userName\": \"Joe\", \"roomName\": \"Living Room\", \"sensorId\": \"98072D0B9B72\", \"event\": {\"name\": \"RoomEntry\", \"inControl\": true}}":""} <0x74772520>
01	07/28/21 19:42:09.356	LuaInterface::CallFunction_Request function RoomMeHandler name RoomMe failed /usr/lib/lua/dkjson.lua:693: bad argument #2 to 'pegmatch' (string expected, got nil) <0x74772520>
02	07/28/21 19:42:09.356	JobHandler_LuaUPnP::REQ_Handler handler failure for lr_RoomMe <0x74772520>

So it seems the Room`me POST json is getting through, but overall it’s still failing with the same ‘pegmatch’ message error etc. Also any idea why all the backslashes were added to the json sent ?

Other observations

  1. if I paste the json captured with the backslashes into a json validator, it returns an error
{\"type\": \"Event\",\"userId\": 0, \"userName\": \"Joe\", \"roomName\": \"Living Room\", \"sensorId\": \"98072D0B9B72\", \"event\": {\"name\": \"RoomEntry\", \"inControl\": true}}

The error the validator reports is…

Error: Parse error on line 1:
{	\	"type\": \"Event\
--^
Expecting 'STRING', '}', got 'undefined'
  1. if I go direct to 192.168.102.10:3480/data_request?id=lr_RoomMe it returns a Handler failed message…