info-beamer

Proof of Play System Needed


#1

Hi all,

Is there a way to apply a monthly proof of play for assets that need to be displayed on info-beamer driven screens?

If we need to build one, where should we start?


#2

Nothing like this is built-in at the moment. Right now the best approach would be to modify the package you’re currently using to record “play” events. You could then send those to some external analytics server.

I would imagine something like elastic.co would for for analytics.

What kind of information are you looking for? I guess time/date, device id, asset name and duration at least?


#3

Thanks :slight_smile:

Exactly! looking to automate these info extraction. Looking for that little hint of how to modify the initial packages and where to start.


#4

Don‘t know of you are still looking for a solution to this Problem but maybe I could offer you some help. I wrote 2 versions of a playout logger. One working and in production using mongodb and one atm testing with elasticsearch. We use it to prove to customers that they got what they payed for. Also has an Export to .xls.


#5

What LUA code did you use to get current running asset? I’m trying to do this now.


#6

The basic idea

Logging the playback

Within the Lua code you exactly tell when an asset is playing: The usual packages all work like this:

  • Preload asset
  • Wait for correct moment to start
  • Play asset
  • Unload asset

For the HD Image/Video player, this is the code that does this for videos: https://github.com/info-beamer/package-hd-player/blob/master/node.lua#L374-L432

Basically you would then need to generate a “proof of play event” once the video starts playing. In the above case that would be this line: https://github.com/info-beamer/package-hd-player/blob/master/node.lua#L398

So what’s a “proof of play event”? Basically you’d probably want to generate something like:

save_proof_of_play{
     asset_id = asset_id, -- What are we playing
     duration = duration, -- .. for how long
     start = os.time(), -- unix timestamp of when playback started
     device = sys.getenv "SERIAL", -- the serial number of the device
     uuid = generate_some_uuid(), -- see below
}

You can get the asset_id and duration beforehand. They will be in config.json so you can just fetch them from there. You can fetch the serial number of the device using sys.getenv "SERIAL". If you prefer the device_id, you can fetch that from the __metadata entry in the generated config.json file.

The uuid is optional, but probably a good idea as it allows you to ensure an “at-most-once” delivery by simple throwing away duplicate play events at your data store backend.

Delivering the playback event to your backend

The save_proof_of_play above is a function. You’ll have to implement that somehow. The idea is to send out play events to an external process that then forwards these events to your backend system. The way to get data out of your Lua code into an external process is to use the TCP feature of info-beamer: An external program (more on this later) will connect to the info-beamer process using TCP. Your Lua code can then send data to this program. Here’s the simplest code that does just that:

local json = require "json"
local clients = {}

-- If a new TCP client connects, see if it tries to connect to the
-- "proof-of-play" path and if so, same a reference to it in the 
-- clients table.
node.event("connect", function(client, path)
    if path == "proof-of-play" then
        clients[client] = true
    end
end)

-- Client disconnected? Then remove our reference
node.event("disconnect", function(client)
   clients[client] = nil
end)

-- This is the function used above which sends events to a locally
-- running progam on your Pi.
local function save_proof_of_play(event)
    -- encode event to JSON
    local data = json.encode(event)
    
    -- send it to all connected clients
    for client, _ in pairs(clients) do
        node.client_write(client, data)
    end
end

Now everything is ready. And external package service can now connect to the info-beamer process using TCP and start receiving proof-of-play events. Here’s how this works from the command line. If you’re connected to your Pi using SSH, the following example shows how to use the included telnet client to connect to info-beamer:

info-beamer-4183873228 /space/root # telnet localhost 4444
Info Beamer PI 0.9.9-...... [pid 381/uptime 1672]. *help to get help.
*raw/root/proof-of-play
ok!
{"asset_id":1234,"duration":10,"start":1234567,"device":"4890", "uuid": "foobar"}

So just run telnet localhost 4444 to connect to info-beamer. You will be greeted by the second line in the above example. Then send *raw/root/proof-of-play and your telnet client will be connected to your Lua code and will receive proof-of-play events sent by the Lua code above.

Using the ibquery.py Python library makes this easy: Here’s code that also connects to info-beamer and directly forwards the received proof-of-play JSON to an external backend using a POST request:

import ibquery, requests
ib = ibquery.InfoBeamerQuery("127.0.0.1")
stream = ib.node('root/proof-of-play').io(raw=True)

for event in stream:
    requests.post("https://example.net/backend", data={"event": event.strip()})

That’s basically it. Events generated and sent using the save_proof_of_play end up being forwarded to the above Python code which then sends them to some external service. Of course how exactly you send those events depends on how your backend works. Right now this is the minimal code you’ll need. There is no real error handling, so events are lost of the network connection is down. For a more sophisticated system, you’d probably want to spool events in the package service and handle network errors by retrying the POST request. You could implement that by saving events to file(s) in the scratch directory.


#7

I‘m using a custom go binary which connects locally to infobeamer and then send out the data. That way i can change the transport mechanism. Basically what Florian statet above but switched ibquery for our custom logger binary. We used http first, switched to grpc, then twrip (like grpc but easier) and finally moved to something with using the nats.io library and gnatsd server.


#8

I guess in the future a simple system doing what I described above could be part of what info-beamer hosted offers by default. Most of what’s required for that is already there, so it wouldn’t be too difficult to add that. It’s mostly reasoning about how to best store these events and build a UI and the supporting code on the backend and OS.


#9

That would be useful and when integrated just a tick of a box away. Figuring out the presentation layer is still a thing that makes it more complicated. Salesdepartment loves excel files. Admins can live with kinaba/grafana charts and graphs or json responses.


#10

Well, I tried the solution you gave, but I just don’t know LUA enough (actually I don’t know it at all) to implement it. To make my problem easier I just decided to get people walking by with a motion sensor, asset running time isn’t as important, people traffic is more important.

I made this python service that sends data to an endpoint and it works, but how can I get the device_id in python without using LUA?

#!/usr/bin/python2.7
import requests
from hosted import device, node, config
config.restart_on_update()

device.gpio.monitor(config.pin)
for (pin, state) in device.gpio.poll_forever():
    deviceId = ?????????
    counter = 0
    while state == True:
        counter += 1
        r = requests.get('https://api.senddatatoendpoint.com?data='+ str(counter) + str(deviceId) )

Just need to get a proof of concept out to the clients so I can sell them on Infobeamer, the backend is already created :grin:


#11

The device_id should be accessible on config.metadata['device_id']

(It’s read from the __metadata value in config.json in the current directory)


#12

Yes it worked!!! Going to add this service and GPIO / API settings to HD Image/Video player options and figure out the best cadence to send data. Thanks so much!


#13

Nice. Looking forward to seeing what you built in action some day :slight_smile: