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
| Pillar | Description | Examples |
|---|---|---|
| Sense | Collect data from environment | Temperature, motion, light, proximity sensors |
| Process | Analyze and route data | Event filtering, threshold triggers, data transformation |
| Display | React with appropriate content | Show 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
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:
| Parameter | Type | Required | Description |
|---|---|---|---|
eventName | string | Yes | Unique identifier for the event type |
store | integer | No | 1 = 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
eventName | string | Yes | Event to subscribe to |
url | string | Yes | URL-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
| Element | Binding Behavior |
|---|---|
| Text | Content updates with bound value |
| Image | Source URL changes dynamically |
| Video | Source can change based on conditions |
| Shape | Color, size, visibility can be bound |
| Container | Show/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 Type | Recommended Interval | Rationale |
|---|---|---|
| Temperature | 60 seconds | Slow-changing, conserve resources |
| Weather | 15 minutes | API rate limits, slow changes |
| Motion | Instant (event-driven) | Responsiveness required |
| Social media | 5 minutes | Balance freshness vs. API limits |
| Sales data | 5-15 minutes | Near real-time without overload |
| Emergency alerts | Instant | Critical, 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.