An important part of Thuis is integration of our Home Theater system. As the integration is quite extensive and consists of several components, this will be a 3-part blog series. In the first part we start with communicating to CEC-enabled devices from a Raspberry Pi. In the second part we will integrate CEC with the rest of Thuis, and make sure everything works properly together. In the third – and last – part of the Home Theater series we will add the support for Plex.

Home Cinema

CEC

HDMILet’s start with a short introduction of CEC itself. CEC stands for Consumer Electronics Control and is a feature of HDMI. CEC enables HDMI-devices to communicate with each other. In the ideal situation this means a user only needs one remote control to control all his devices: TV, AV receiver, Blu-ray player, etc. Unfortunately many manufacturers use their own variation of CEC and therefore in a lot of cases one still needs multiple remotes. To get an idea about the protocol have a look a CEC-O-MATIC, this is a great reference for all available commands!

The good news is that the GPU of the Raspberry Pi supports CEC out of the box!

libCEC

To be able to handle the different dialects of CEC, Pulse Eight developed libcec. It enables you to interact with other HDMI devices without having to worry about the communication overhead, handshaking and all the differences between manufacturers. In contrast to what I mentioned in Cooking up the nodes: Thuis Cookbook Raspbian Jessie nowadays provides version 3.0.1 in the Apt repository, so there is no need to use the version from Stretch anymore. I’ve updated the cookbook accordingly. Other than that provisioning the Raspberry Pi using Chef was straightforward.

libCEC comes with the tool cec-client. This basically gives you a terminal for CEC commands. When we execute cec-client you see it connecting to HDMI and collecting some information about other devices, then we can give it commands. For example we ask it for all devices currently connected with the scan command:

thuis-server-tv# cec-client -d 16 -t r
log level set to 16
== using device type 'recording device'
CEC Parser created - libCEC version 3.0.1
no serial port given. trying autodetect: 
 path:     Raspberry Pi
 com port: RPI

opening a connection to the CEC adapter...
DEBUG:   [              94]	Broadcast (F): osd name set to 'Broadcast'
DEBUG:   [              96]	InitHostCEC - vchiq_initialise succeeded
DEBUG:   [              98]	InitHostCEC - vchi_initialise succeeded
DEBUG:   [              99]	InitHostCEC - vchi_connect succeeded
DEBUG:   [             100]	logical address changed to Free use (e)
DEBUG:   [             102]	Open - vc_cec initialised
DEBUG:   [             105]	<< Broadcast (F) -> TV (0): POLL
// Receiving information from the TV
// ...
// Request information about all connected devices
scan
requesting CEC bus information ...
DEBUG:   [           41440]	<< Recorder 1 (1) -> Playback 1 (4): POLL
DEBUG:   [           41472]	>> POLL sent
DEBUG:   [           41473]	Playback 1 (4): device status changed into 'present'
// ...
CEC bus information
===================
device #0: TV
address:       0.0.0.0
active source: no
vendor:        Sony
osd string:    TV
CEC version:   1.4
power status:  on
language:      dut


device #1: Recorder 1
address:       1.6.0.0
active source: no
vendor:        Pulse Eight
osd string:    CECTester
CEC version:   1.4
power status:  on
language:      eng


device #4: Playback 1
address:       1.1.0.0
active source: yes
vendor:        Unknown
osd string:    Apple TV
CEC version:   1.4
power status:  on
language:      ???


device #5: Audio
address:       1.0.0.0
active source: no
vendor:        Denon
osd string:    AVR-X2000
CEC version:   1.4
power status:  on
language:      ???


currently active source: Playback 1 (4)

// indicates a comment added by me, // ... indicates output that was hidden as it’s not needed for understanding

As you can see currently 4 devices are connected to the bus, including the Raspberry Pi itself (device #1). The Apple TV is the currently active source. You can tell cec-client which output it should give with the -d parameter. We’ll use this for our integration by choosing -d 8, which just displays the traffic on the bus.

CEC-CDI

To integrate libCEC (or more specifically cec-client) with Java we have to write a wrapper around it. We’ll do that in a similar way as MQTT-CDI, so the Java code can observe events happening on the CEC-bus via a CDI observer. I wrote the initial version about a year ago and the full source code is available on my GitHub as Edubits/cec-cdi. It does not support the full CEC protocol yet, but most of the usual commands are available. For example you’re able to turn on and off your devices, and send UI commands like play, pause, volume up, etc. You can of course also monitor these same functions, so the app will for example know when you turn off the TV manually.

You can add CEC-CDI to your own project by adding the following dependency to your pom.xml:

<dependency>
    <groupId>nl.edubits.cec</groupId>
    <artifactId>cec-cdi</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

Monitoring what happens in the home theatre system can be done using CDI observers. Currently you can just add a qualifier for the source device, later I might also add some more sophisticated qualifiers such as the type of a command. When you’re interesting in all messages send from the TV you can observe them like this:

@ApplicationScoped
public class CecObserverBean {
    public void tvMessage(@Observes @CecSource(TV) Message message) {
        logger.info("Message received from TV: " + message);
    }
}

To turn the TV on you can send it the IMAGE_VIEW_ON message without any arguments, for putting it in standby you use the STANDBY command. In Java this looks as follows:

public class SendExample {
    @Inject
    private CecConnection connection;

    public void send() {
        // Send message from RECORDER1 (by default the device running this code) to the TV to turn on
        connection.sendMessage(new Message(RECORDER1, TV, IMAGE_VIEW_ON, Collections.emptyList(), ""));

        // Send message from RECORDER1 (by default the device running this code) to the TV to turn off
        connection.sendMessage(new Message(RECORDER1, TV, STANDBY, Collections.emptyList(), ""));
    }
}

ThuisServer-TV

Just like the Core application described in Core v2: A Java EE application, this will be a Java EE application running on WildFly. It includes CEC-CDI. The application itself is quite simple as it’s only function is bridging between CEC and MQTT. So we have two @ApplicationScoped beans observing events.

The CecObserverBean forwards specific messages from the CEC bus to MQTT. In the example it monitors the power state of the television. Note that my Sony television has its own dialect as well, depending on how the TV is turned off it reports the official STANDBY command or gives a vendor specific command. When turning on it’s supposed to report a certain command as well, but the Sony decides to skip it. That’s why – as workaround – I listen to REPORT_PHYSICAL_ADDRESS, which is a command it always gives during power on.

package nl.edubits.thuis.server.tv.cec;

@Startup
@ApplicationScoped
public class CecObserverBean {
	@Inject
	MqttService mqttService;

	public void tvMessage(@Observes @CecSource(TV) Message message) {
		if (message.getDestination() != BROADCAST && message.getDestination() != RECORDER1) {
			return;
		}

		switch (message.getOperator()) {
		case STANDBY:
			mqttService.publishMessage("Thuis/device/living/homeTheater/tv", "off");
			break;

		case REPORT_PHYSICAL_ADDRESS:
			mqttService.publishMessage("Thuis/device/living/homeTheater/tv", "on");
			break;

		case VENDOR_COMMAND_WITH_ID:
			if (message.getRawMessage().equals("0f:a0:08:00:46:00:09:00:01")
					|| message.getRawMessage().equals("0f:87:08:00:46")) {
				mqttService.publishMessage("Thuis/device/living/homeTheater/tv", "off");
			}
			break;

		default:
			break;
		}
	}
}

The opposite happens in the MqttObserverBean, which listens to MQTT messages and executes the corresponding CEC commands. Here we’ll turn the TV on and off and then ask the TV to report its power status back:

package nl.edubits.thuis.server.tv.mqtt;

@ApplicationScoped
public class MqttObserverBean {
	@Inject
	private CecConnection connection;

	public void onActionMessageTV(@Observes @MqttTopic("Thuis/device/living/homeTheater/tv/set") MqttMessage message) {
		switch(message.asText()) {
			case "on":
				connection.sendMessage(new Message(RECORDER1, TV, IMAGE_VIEW_ON, Collections.emptyList(), ""));
			case "off":
				connection.sendMessage(new Message(RECORDER1, TV, STANDBY, Collections.emptyList(), ""));
		}

		connection.sendMessage(new Message(RECORDER1, TV, REPORT_POWER_STATUS, Collections.emptyList(), ""));
	}
}

This concludes our implementation of the TV node. It’s now able to listen to other CEC-enabled devices, communicate with them and bridge this through MQTT messages. In part 2 we’ll take these MQTT messages, wrap them and create some scenes to turn everything on with a single button!

Home Theater part 1: CEC
Tagged on:                 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.