Controlling A Cruise Ship’s Massive Video Display with Python

February 27, 2018
Written by
Sasa Buklijas
Contributor
Opinions expressed by Twilio contributors are their own

pasted image 0 (1)

In this article you will learn how to control an NEC display via RS232 or UTP connection with the official nec-pd-sdk Python package from NEC. Reasons for controlling NEC displays programmatically vary from automatic turning them on and off, scheduling, monitoring, and more.

I became interested in controlling NEC displays programmatically when I was building a virtual waterfall system and had issues with one of the displays. I needed to restart one manually every few days when it entered an error state and displayed only a black picture.

Next, I will explain where and how this NEC display was used and then we’ll dive straight into the code.

The system where code from this article is in use. The top 5 displays are shown.

General Overview of a Virtual Waterfall System

I was responsible for the maintenance of a 17 meter tall virtual waterfall on a cruise ship:

There are 34 NEC X551UN displays in the setup. Each display has a dedicated Linux PC for playback. Playback is delivered from the Linux PC to the NEC display via SDI, and each display has a DVI to HDMI active converter. An active converter means that the converter also supplies power.

Each Linux PC plays video for only one display. One additional Windows PC is responsible for synchronizing playout of all of the Linux PCs.

Installing the nec-pd-sdk Python Package

I recommended always using a virtual environment when developing with Python.

To install nec-pd-sdk with pip, run:

pip install nec-pd-sdk

Let’s Write Some Code

The entire display program is around 100 lines. Let’s go over it step by step.

We begin by importing the libraries we’ll need:

from __future__ import print_function
import time
import os
import datetime
import sys
import json
from nec_pd_sdk.nec_pd_sdk import NECPD
from nec_pd_sdk.protocol import PDError
from nec_pd_sdk.constants import PD_POWER_STATES

The code is compatible with Python version 2.7 and later. We add print syntax compatibility by importing print_function from the __future__ package.

The next lines import modules from the standard library and the last 3 lines import from the nec-pd-sdk package.

We have three custom functions to make code more readable:

def touch(file_name, times=None):
    """Make file with file_name"""
    with open(file_name, 'a'):
        os.utime(file_name, times)

def get_display_list():
    """Get display configuration"""
    with open('display_config.json') as json_data_file:
            data = json.load(json_data_file)
    return data["display_list"]

def get_screen_state(pd):
    """Return screen status and diagnosis as dictionary"""
    state = pd.command_power_status_read()
    text_list = pd.helper_self_diagnosis_status_text()
    return {'status': state, 'diagnosis': text_list}

touch is similar to the touch unix command; it will make an empty file. We are using files with specific names as notifications.

The IP addresses of the NEC displays are stored in a JSON file with the name display_config.json. So if we want to add or remove some displays we do not need to change source code, just one JSON configuration file.

In our current directory, let’s create a sample display_config.json:

{
    "display_list":[
        "192.168.1.52",
        "192.168.1.13"
    ]
}

The get_display_list function reads display_config.json and returns the content of display_list as a Python list.

The get_screen_state function takes an established connection to a display and returns a Python dictionary state and diagnostics. The state, or status is either on or off. The diagnostic message, or diagnosis is normal or an error description (eg. No signal; )

def main():
    displays_with_error = []

    for display in get_display_list():
        try:
            pd = NECPD.open(display)

            try:
                monitor_id = 1
                pd.helper_set_destination_monitor_id(monitor_id)
                
                screen_state = get_screen_state(pd)
                if screen_state['diagnosis'] == 'No signal; ':
                    displays_with_error.append(display)
                    pd.command_power_status_set(PD_POWER_STATES['Off'])
            finally:
                pd.close()

        except PDError as msg:
            print("PDError:", msg)

displays_with_error is a list of all displays that had an error. In that function, we loop through all displays and if a display is in error state it is added to this list (and turned off).

With the for loop we access each display from the get_display_list function.

Inside try we open a connection to a display with NECPD.open(display). If there is some exception, we use except PDError as msg to display the error.

After that we have try with final for closing the connection to the display.

Inside of try with monitor_id = 1; pd.helper_set_destination_monitor_id(monitor_id) we are accessing the first display in pd. In our setup we have one network UTP Cat5e cable connected to each individual display so each monitor_id is always 1. A NEC display setup can also be done as a daisy chain, and then each display will have a different id. In our setup we do not use daisy-chaining, so we know display id is always 1.

In the loop we are getting displays and testing them for errors. If an error is detected, we add the display to the displays_with_error list and turn off the display with pd.command_power_status_set(PD_POWER_STATES['Off']). Visually, if a display is showing only a black screen the display is in an error state.

if len(displays_with_error) > 0:
        FIVE_MINUTES = 5 * 60
        time.sleep(FIVE_MINUTES)

        for display in displays_with_error():
            try:
                pd = NECPD.open(display)

                try:
                    monitor_id = 1
                    pd.helper_set_destination_monitor_id(monitor_id)
                    pd.command_power_status_set(PD_POWER_STATES['On'])
                finally:
                    pd.close()

            except PDError as msg:
                print("PDError:", msg)

If at least one display had errors, then we will wait for 5 minutes with time.sleep(), calling time.sleep(FIVE_MINUTES).

As you recall, we already turned off all displays with errors. From my experience, 5 minutes of having a display off is enough to clear all the errors, so from the next ‘on’ command display will work well.

After that, we have similar code for connecting to each display with monitor_id = 1. The major difference is now we are sending the command to turn a display on with pd.command_power_status_set(PD_POWER_STATES['On'])

date_log = datetime.datetime.today().strftime('%Y%m%d-%H%M%S')
for display in displays_with_error:
    last_part_of_ip_address = display.split('.')[-1]
    log_file = date_log + '_display_' + last_part_of_ip_address + '_had_error.txt'
    touch(log_file)

This code appears in the same ‘if’ block and so we can include some code for notifications.

This code is executed every hour on the computer in the server room. We needed some system of notifications to know when and which displays had errors. Because this computer is not connected to internet or rest of office network, we can not send an email or use Twilio’s SMS service.

One practical solution was to use files as error notifications. Our file names have the date, time and number of displays that had an error. E.g.: 20171109-111512_display_52_had_error.txt

So, every few days when we checked for errors we’d know when and which displays had errors that required a restart.

Testing the Code

For all the current code to be executed, the NEC display must be in No Signal; state.

In order to test turning a display ON and OFF, add the line displays_with_error = [192.168.1.52] before the if len(displays_with_error) > 0: line (where 192.168.1.52 is the IP address of an NEC display).

The bottom 12 displays from the real installation.

Controlling NEC Displays with Python

This is just one example of how the nec-pd-sdk package can be used.

Everything that can be done manually with an NEC display can also be done programmatically with Python and nec-pd-sdk.

If you’ve automated an installation with NEC displays in the past, please share your examples.

Sasa Buklijas can be contacted via email through his website at http://buklijas.info/blog/.

 

Editor: Want to see more of the incredible waterfall in action? Sasa sent over three videos of the installation. Enjoy!