Skip to main content

IoT Digital Signage Integration

The Internet of Things (IoT) transforms digital signage from static displays into intelligent, responsive communication systems. By connecting sensors, devices, and data sources, signage can react to real-world conditions in real-time. This guide covers the architecture, APIs, and implementation of IoT-enabled digital signage.

IoT Signage Architecture

System Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│ IoT Digital Signage Architecture │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Data Sources Processing Display │
│ ┌─────────────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Sensors │ │ │ │ │ │
│ │ • Temperature │────────┐ │ SignService │────────▶│ SignPlayer│ │
│ │ • Motion/PIR │ │ │ (Port 8094) │ │ │ │
│ │ • Light/Ambient │ │ │ │ └──────────┘ │
│ │ • Humidity │ │ │ • Event Queue │ │
│ │ • Air Quality │ ▼ │ • Data Store │ ┌──────────┐ │
│ └─────────────────┘ ┌─────────┐ │ • Webhooks │────────▶│ SignPlayer│ │
│ │ IoT │ │ │ │ │ │
│ ┌─────────────────┐ │ Gateway │──▶│ │ └──────────┘ │
│ │ Controllers │ │ (MQTT/ │ └──────────────┘ │
│ │ • Raspberry Pi │───▶│ HTTP) │ │ │
│ │ • Arduino │ └─────────┘ │ ┌──────────┐ │
│ │ • ESP32 │ │ │ SignStudio│ │
│ └─────────────────┘ │ │ (CMS) │ │
│ │ └──────────┘ │
│ ┌─────────────────┐ │ │ │
│ │ External APIs │ │ │ │
│ │ • Weather │───────────────────────┘ │ │
│ │ • Social Media │ │ │
│ │ • POS Systems │◀────────────────────────────────────────────┘ │
│ │ • Building Mgmt │ Bidirectional │
│ └─────────────────┘ Communication │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

Three Pillars of IoT Signage

PillarDescriptionExamples
SenseCollect data from environmentTemperature, motion, light, proximity sensors
ProcessAnalyze and route dataEvent filtering, threshold triggers, data transformation
DisplayReact with appropriate contentShow weather when temperature changes, greet when motion detected

SignService API Reference

Overview

SignService is the local API server running on SignPlayer that enables IoT integration. It provides endpoints for sending events, registering callbacks, and controlling playback.

Base URL: https://SIGNPLAYER_IP:8094

note

SignService uses self-signed HTTPS certificates. Include --insecure flag with curl or configure your client to accept self-signed certificates.

Send Event Endpoint

Send data to SignPlayer for display or processing.

POST /player/sendEvent?eventName={eventName}&store={0|1}
Content-Type: application/json

{
"key": "value",
"temperature": 72,
"message": "Hello World"
}

Parameters:

ParameterTypeRequiredDescription
eventNamestringYesUnique identifier for the event type
storeintegerNo1 = persist data, 0 = transient (default)

Example - Send Temperature Data:

curl --insecure -X POST \
"https://192.168.1.100:8094/player/sendEvent?eventName=temperature&store=1" \
-H "Content-Type: application/json" \
-d '{"value": 72, "unit": "F", "location": "Lobby"}'

Example - Send Custom Message:

curl --insecure -X POST \
"https://192.168.1.100:8094/player/sendEvent?eventName=welcome&store=0" \
-H "Content-Type: application/json" \
-d '{"name": "John Smith", "title": "Welcome to Our Office"}'

Register Event Callback

Register a webhook URL to receive events when SignPlayer triggers outbound actions.

POST /player/registerEvent?eventName={eventName}&url={callbackUrl}

Parameters:

ParameterTypeRequiredDescription
eventNamestringYesEvent to subscribe to
urlstringYesURL-encoded webhook endpoint

Example:

# URL encode the callback URL
CALLBACK_URL=$(python3 -c "import urllib.parse; print(urllib.parse.quote('http://myserver.com:5000/webhook'))")

curl --insecure -X POST \
"https://192.168.1.100:8094/player/registerEvent?eventName=buttonClick&url=$CALLBACK_URL"

Unregister Event Callback

Remove a previously registered webhook.

POST /player/unregisterEvent?eventName={eventName}&url={callbackUrl}

List Registered Events

Get all currently registered event callbacks.

GET /player/listEvents

Response:

{
"events": [
{
"eventName": "buttonClick",
"url": "http://myserver.com:5000/webhook"
},
{
"eventName": "sceneChange",
"url": "http://myserver.com:5000/scene-webhook"
}
]
}

Data Binding in SignStudio

Overview

Data binding connects IoT event data to visual elements in SignStudio. When events are received, bound elements automatically update.

Binding Syntax

Use double curly braces with the event name and property path:

{{eventName.propertyName}}

Examples:

{{temperature.value}}°F          →  72°F
{{welcome.name}} → John Smith
{{weather.current.condition}} → Sunny
{{inventory.product_123.stock}} → 45

Supported Element Types

ElementBinding Behavior
TextContent updates with bound value
ImageSource URL changes dynamically
VideoSource can change based on conditions
ShapeColor, size, visibility can be bound
ContainerShow/hide based on conditions

Conditional Display

Use binding with visibility conditions:

// In SignStudio scene configuration
{
"element": "alert-banner",
"visible": "{{temperature.value}} > 90",
"content": "High Temperature Alert: {{temperature.value}}°F"
}

Nested Data Binding

Access nested JSON properties:

// Incoming event data
{
"eventName": "salesData",
"data": {
"today": {
"revenue": 15420,
"transactions": 234
},
"month": {
"revenue": 342500
}
}
}
// Binding in SignStudio
Today's Revenue: ${{salesData.today.revenue}}
Transactions: {{salesData.today.transactions}}
Monthly Total: ${{salesData.month.revenue}}

Common IoT Integrations

Temperature & Environmental Sensors

# Python example: DHT22 temperature/humidity sensor
import requests
import Adafruit_DHT

DHT_SENSOR = Adafruit_DHT.DHT22
DHT_PIN = 4
SIGNPLAYER_URL = "https://192.168.1.100:8094"

def send_environmental_data():
humidity, temperature = Adafruit_DHT.read_retry(DHT_SENSOR, DHT_PIN)

if humidity is not None and temperature is not None:
# Convert to Fahrenheit
temp_f = temperature * 9/5 + 32

data = {
"temperature": round(temp_f, 1),
"humidity": round(humidity, 1),
"timestamp": datetime.now().isoformat()
}

requests.post(
f"{SIGNPLAYER_URL}/player/sendEvent?eventName=environment&store=1",
json=data,
verify=False # For self-signed cert
)

# Run every 60 seconds
import schedule
schedule.every(60).seconds.do(send_environmental_data)

Motion Detection / Presence Sensing

# PIR motion sensor integration
import RPi.GPIO as GPIO
import requests
import time

PIR_PIN = 17
SIGNPLAYER_URL = "https://192.168.1.100:8094"

GPIO.setmode(GPIO.BCM)
GPIO.setup(PIR_PIN, GPIO.IN)

last_motion_time = 0
COOLDOWN = 30 # seconds

def motion_detected(channel):
global last_motion_time
current_time = time.time()

if current_time - last_motion_time > COOLDOWN:
last_motion_time = current_time

requests.post(
f"{SIGNPLAYER_URL}/player/sendEvent?eventName=motion",
json={
"detected": True,
"location": "entrance",
"timestamp": time.strftime("%H:%M:%S")
},
verify=False
)

GPIO.add_event_detect(PIR_PIN, GPIO.RISING, callback=motion_detected)

# Keep script running
while True:
time.sleep(1)

QR Code / NFC Reader Integration

# USB QR code scanner integration
import evdev
import requests

SIGNPLAYER_URL = "https://192.168.1.100:8094"

# Find QR scanner device
devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
scanner = None
for device in devices:
if "Scanner" in device.name or "Barcode" in device.name:
scanner = device
break

if scanner:
scanned_data = ""

for event in scanner.read_loop():
if event.type == evdev.ecodes.EV_KEY:
key_event = evdev.categorize(event)

if key_event.keystate == 1: # Key down
if key_event.keycode == "KEY_ENTER":
# QR code complete
if scanned_data:
requests.post(
f"{SIGNPLAYER_URL}/player/sendEvent?eventName=qrScan",
json={
"data": scanned_data,
"timestamp": time.time()
},
verify=False
)
scanned_data = ""
else:
# Accumulate characters
char = key_event.keycode.replace("KEY_", "")
scanned_data += char.lower()

External API Integration

# Weather API integration
import requests
import schedule

WEATHER_API_KEY = "your_api_key"
LOCATION = "New York,US"
SIGNPLAYER_URL = "https://192.168.1.100:8094"

def update_weather():
response = requests.get(
f"https://api.openweathermap.org/data/2.5/weather",
params={
"q": LOCATION,
"appid": WEATHER_API_KEY,
"units": "imperial"
}
)

if response.status_code == 200:
data = response.json()

weather_data = {
"temperature": round(data["main"]["temp"]),
"feels_like": round(data["main"]["feels_like"]),
"humidity": data["main"]["humidity"],
"condition": data["weather"][0]["main"],
"description": data["weather"][0]["description"],
"icon": data["weather"][0]["icon"],
"wind_speed": round(data["wind"]["speed"]),
"location": LOCATION
}

requests.post(
f"{SIGNPLAYER_URL}/player/sendEvent?eventName=weather&store=1",
json=weather_data,
verify=False
)

# Update every 15 minutes
schedule.every(15).minutes.do(update_weather)

Event-Driven Content Triggers

Scene Switching Based on Events

Configure SignStudio to automatically switch scenes based on incoming events:

// Scene trigger configuration
const sceneTriggers = [
{
eventName: "motion",
condition: "data.detected === true",
action: "switchScene",
targetScene: "welcome-scene",
duration: 30 // seconds
},
{
eventName: "temperature",
condition: "data.value > 90",
action: "showOverlay",
targetOverlay: "heat-warning",
priority: "high"
},
{
eventName: "emergency",
condition: "data.type === 'fire'",
action: "emergencyOverride",
targetScene: "evacuation",
priority: "critical"
}
];

Time-Based + Event Conditions

Combine scheduled content with IoT triggers:

// Combined trigger logic
const contentRules = {
// Morning greeting when motion detected
rule1: {
timeRange: "06:00-10:00",
event: "motion.detected",
content: "good-morning-scene"
},

// Show promotional content when few people present
rule2: {
timeRange: "10:00-17:00",
event: "occupancy.count < 5",
content: "promotional-loop"
},

// Evening wind-down content
rule3: {
timeRange: "17:00-20:00",
event: "motion.detected",
content: "evening-scene"
}
};

IoT Gateway Configuration

MQTT Broker Integration

For deployments with many sensors, use MQTT as a message broker:

# MQTT to SignService bridge
import paho.mqtt.client as mqtt
import requests
import json

MQTT_BROKER = "localhost"
MQTT_PORT = 1883
SIGNPLAYER_URL = "https://192.168.1.100:8094"

def on_message(client, userdata, message):
topic = message.topic
payload = json.loads(message.payload.decode())

# Convert MQTT topic to event name
# sensors/temperature/lobby -> temperature_lobby
event_name = topic.replace("/", "_").replace("sensors_", "")

requests.post(
f"{SIGNPLAYER_URL}/player/sendEvent?eventName={event_name}&store=1",
json=payload,
verify=False
)

client = mqtt.Client()
client.on_message = on_message
client.connect(MQTT_BROKER, MQTT_PORT)

# Subscribe to all sensor topics
client.subscribe("sensors/#")
client.loop_forever()

Node-RED Integration

Node-RED provides a visual flow-based programming interface for IoT:

// Node-RED flow for SignService integration
[
{
"id": "sensor-input",
"type": "mqtt in",
"topic": "sensors/temperature",
"broker": "mqtt-broker"
},
{
"id": "transform",
"type": "function",
"func": "msg.payload = { value: msg.payload.temp, unit: 'F' }; return msg;"
},
{
"id": "signservice-output",
"type": "http request",
"method": "POST",
"url": "https://192.168.1.100:8094/player/sendEvent?eventName=temperature&store=1",
"paytoqs": false,
"tls": "tls-config"
}
]

Best Practices

Data Refresh Rates

Data TypeRecommended IntervalRationale
Temperature60 secondsSlow-changing, conserve resources
Weather15 minutesAPI rate limits, slow changes
MotionInstant (event-driven)Responsiveness required
Social media5 minutesBalance freshness vs. API limits
Sales data5-15 minutesNear real-time without overload
Emergency alertsInstantCritical, immediate response

Error Handling

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_robust_session():
session = requests.Session()

retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504]
)

adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("https://", adapter)

return session

def send_event_with_retry(event_name, data):
session = create_robust_session()

try:
response = session.post(
f"{SIGNPLAYER_URL}/player/sendEvent?eventName={event_name}&store=1",
json=data,
verify=False,
timeout=10
)
response.raise_for_status()
return True
except requests.exceptions.RequestException as e:
print(f"Failed to send event: {e}")
# Log to local queue for retry
queue_for_retry(event_name, data)
return False

Security Considerations

# Secure IoT integration practices

# 1. Use environment variables for sensitive data
import os
SIGNPLAYER_URL = os.environ.get("SIGNPLAYER_URL")
API_KEY = os.environ.get("SIGNAGE_API_KEY")

# 2. Validate incoming data
def validate_sensor_data(data):
if "temperature" in data:
temp = data["temperature"]
if not isinstance(temp, (int, float)):
raise ValueError("Temperature must be numeric")
if temp < -50 or temp > 150:
raise ValueError("Temperature out of valid range")
return True

# 3. Rate limiting
from functools import wraps
import time

def rate_limit(max_per_minute):
interval = 60.0 / max_per_minute
last_called = [0.0]

def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
elapsed = time.time() - last_called[0]
wait = interval - elapsed
if wait > 0:
time.sleep(wait)
last_called[0] = time.time()
return func(*args, **kwargs)
return wrapper
return decorator

@rate_limit(max_per_minute=60)
def send_sensor_data(data):
# Implementation
pass

Troubleshooting

Common Issues

Events not appearing on display:

# 1. Verify SignService is running
curl --insecure https://192.168.1.100:8094/player/listEvents

# 2. Check event name matches binding
# Event name: "temperature"
# Binding must be: {{temperature.value}} not {{temp.value}}

# 3. Verify JSON is valid
echo '{"value": 72}' | python -m json.tool

# 4. Check network connectivity
ping 192.168.1.100

Webhook callbacks not firing:

# 1. Verify registration
curl --insecure https://192.168.1.100:8094/player/listEvents

# 2. Check callback URL is accessible from SignPlayer network
# 3. Ensure callback server is running and responding

# 4. Test callback endpoint directly
curl -X POST http://yourserver:5000/webhook \
-H "Content-Type: application/json" \
-d '{"test": "data"}'

Frequently Asked Questions


IoT integration transforms digital signage into responsive, intelligent communication systems. For implementation assistance with sensor integration and custom IoT solutions, explore the SignageStudio platform or contact MediaSignage.