Skip to content

Instantly share code, notes, and snippets.

@sdabet
Last active September 1, 2023 12:17
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save sdabet/ac4d7711d1a529806cb7b695530b1fac to your computer and use it in GitHub Desktop.
Save sdabet/ac4d7711d1a529806cb7b695530b1fac to your computer and use it in GitHub Desktop.
Send simple HDMI-CEC commands from an Android app via Pulse-Eight's USB-CEC adapter
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.util.Log;
import com.felhr.usbserial.UsbSerialDevice;
import com.felhr.usbserial.UsbSerialInterface;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This class allows to send HDMI CEC commands through Pulse-Eight's USB-CEC adapter.
* Command syntax is inspired by libCEC (https://github.com/Pulse-Eight/libcec).
*
* It requires UsbSerial library (https://github.com/felHR85/UsbSerial)
*
* IMPORTANT: the provided {@link UsbDevice} must have been granted permission.
*
*/
public class UsbCecConnection implements UsbSerialInterface.UsbReadCallback {
private static final String LOG_TAG = UsbCecConnection.class.getSimpleName();
private static final byte MSG_START = (byte)255, MSG_END = (byte)254;
private enum MsgCode {
MSGCODE_NOTHING,
MSGCODE_PING,
MSGCODE_TIMEOUT_ERROR,
MSGCODE_HIGH_ERROR,
MSGCODE_LOW_ERROR,
MSGCODE_FRAME_START,
MSGCODE_FRAME_DATA,
MSGCODE_RECEIVE_FAILED,
MSGCODE_COMMAND_ACCEPTED,
MSGCODE_COMMAND_REJECTED,
MSGCODE_SET_ACK_MASK,
MSGCODE_TRANSMIT,
MSGCODE_TRANSMIT_EOM,
MSGCODE_TRANSMIT_IDLETIME,
MSGCODE_TRANSMIT_ACK_POLARITY,
MSGCODE_TRANSMIT_LINE_TIMEOUT,
MSGCODE_TRANSMIT_SUCCEEDED,
MSGCODE_TRANSMIT_FAILED_LINE,
MSGCODE_TRANSMIT_FAILED_ACK,
MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA,
MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE,
MSGCODE_FIRMWARE_VERSION,
MSGCODE_START_BOOTLOADER,
MSGCODE_GET_BUILDDATE,
MSGCODE_SET_CONTROLLED,
MSGCODE_GET_AUTO_ENABLED,
MSGCODE_SET_AUTO_ENABLED,
MSGCODE_GET_DEFAULT_LOGICAL_ADDRESS,
MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS,
MSGCODE_GET_LOGICAL_ADDRESS_MASK,
MSGCODE_SET_LOGICAL_ADDRESS_MASK,
MSGCODE_GET_PHYSICAL_ADDRESS,
MSGCODE_SET_PHYSICAL_ADDRESS,
MSGCODE_GET_DEVICE_TYPE,
MSGCODE_SET_DEVICE_TYPE,
MSGCODE_GET_HDMI_VERSION,
MSGCODE_SET_HDMI_VERSION,
MSGCODE_GET_OSD_NAME,
MSGCODE_SET_OSD_NAME,
MSGCODE_WRITE_EEPROM,
MSGCODE_GET_ADAPTER_TYPE,
MSGCODE_SET_ACTIVE_SOURCE,
MSGCODE_UNKNOWN
}
private final UsbSerialDevice usbSerialDevice;
private final List<Byte> currentPacket = new ArrayList<>();
public UsbCecConnection(UsbDevice device, UsbDeviceConnection usbConnection) {
usbSerialDevice = UsbSerialDevice.createUsbSerialDevice(device, usbConnection);
usbSerialDevice.open();
usbSerialDevice.read(this);
// Optional, just attempt to get some information
sendCommand(MsgCode.MSGCODE_FIRMWARE_VERSION);
sendCommand(MsgCode.MSGCODE_SET_CONTROLLED, 1);
sendCommand(MsgCode.MSGCODE_GET_OSD_NAME);
}
public void switchTvOn() {
sendCommand(MsgCode.MSGCODE_SET_CONTROLLED, 1);
sendCommand(MsgCode.MSGCODE_TRANSMIT_ACK_POLARITY, 0);
sendCommand(MsgCode.MSGCODE_TRANSMIT, 16);
sendCommand(MsgCode.MSGCODE_TRANSMIT_EOM, 4);
}
public void switchTvOff() {
sendCommand(MsgCode.MSGCODE_SET_CONTROLLED, 1);
sendCommand(MsgCode.MSGCODE_TRANSMIT_ACK_POLARITY, 0);
sendCommand(MsgCode.MSGCODE_TRANSMIT, 16);
sendCommand(MsgCode.MSGCODE_TRANSMIT_EOM, 54);
}
public void activeSource() {
sendCommand(MsgCode.MSGCODE_SET_CONTROLLED, 1);
// marking the adapter as active source
sendCommand(MsgCode.MSGCODE_SET_ACTIVE_SOURCE, 1);
// powering on 'TV'
sendCommand(MsgCode.MSGCODE_TRANSMIT_ACK_POLARITY, 0);
sendCommand(MsgCode.MSGCODE_TRANSMIT, 16);
sendCommand(MsgCode.MSGCODE_TRANSMIT_EOM, 4);
// active source
sendCommand(MsgCode.MSGCODE_TRANSMIT_ACK_POLARITY, 1);
sendCommand(MsgCode.MSGCODE_TRANSMIT, 31);
sendCommand(MsgCode.MSGCODE_TRANSMIT, 130);
sendCommand(MsgCode.MSGCODE_TRANSMIT, 16);
sendCommand(MsgCode.MSGCODE_TRANSMIT_EOM, 0);
}
@Override
public void onReceivedData(byte[] bytes) {
Log.v(LOG_TAG, "onReceivedData: " + Arrays.toString(bytes));
for(byte b : bytes) {
currentPacket.add(b);
if(b == MSG_END) {
byte[] packetBytes = new byte[currentPacket.size()];
for(int i=0; i<packetBytes.length; i++) { packetBytes[i] = currentPacket.get(i); }
onReceivedPacket(packetBytes);
currentPacket.clear();
}
}
}
private void onReceivedPacket(byte[] bytes) {
Log.d(LOG_TAG, "onReceivedPacket: " + Arrays.toString(bytes));
if(bytes.length <= 2) {
Log.w(LOG_TAG, "Invalid response: " + Arrays.toString(bytes));
}
else {
MsgCode msgCode = getMsgCode(bytes[1]);
byte[] params = new byte[bytes.length-3];
System.arraycopy(bytes, 2, params, 0, params.length);
onReceivedPacket(msgCode, params);
}
}
private void onReceivedPacket(MsgCode msgCode, byte[] params) {
Log.d(LOG_TAG, "onReceivedPacket: " + msgCode + ", " + Arrays.toString(params));
switch(msgCode) {
case MSGCODE_COMMAND_ACCEPTED:
Log.d(LOG_TAG, "ACCEPTED " + getMsgCode(params[0]));
break;
case MSGCODE_COMMAND_REJECTED:
Log.w(LOG_TAG, "REJECTED: " + getMsgCode(params[0]));
break;
case MSGCODE_FIRMWARE_VERSION:
int firmwareVersion = 255 * (params[0] & 0xFF) + (params[1] & 0xFF);
Log.i(LOG_TAG, "Firmware version is " + firmwareVersion);
break;
case MSGCODE_GET_OSD_NAME:
String osdName = new String(params);
Log.i(LOG_TAG, "OSD name is '" + osdName + "'");
break;
}
}
private void sendCommand(MsgCode code) {
sendCommand(code, new byte[0]);
}
private void sendCommand(MsgCode code, int param) {
sendCommand(code, new byte[] { (byte) param });
}
private void sendCommand(MsgCode code, byte[] params) {
Log.i(LOG_TAG, "sendCommand: " + code + ", " + Arrays.toString(params));
sendCommand((byte) code.ordinal(), params);
}
private void sendCommand(byte command, byte[] params) {
Log.d(LOG_TAG, "sendCommand: " + command + ", " + Arrays.toString(params));
byte[] data = new byte[3 + params.length];
data[0] = MSG_START;
data[1] = command;
System.arraycopy(params, 0, data, 2, params.length);
data[data.length-1] = MSG_END;
sendData(data);
}
private void sendData(byte[] data) {
Log.v(LOG_TAG, "sendData: " + Arrays.toString(data));
usbSerialDevice.write(data);
}
private static MsgCode getMsgCode(byte b) {
int i = b & 0xFF; // unsigned int
if(i < MsgCode.values().length) {
return MsgCode.values()[i];
}
else {
return MsgCode.MSGCODE_UNKNOWN;
}
}
}
@louisparks
Copy link

This was really helpful to me. Thank you. Is there a place I can find a bigger list of the codes? I can't seem to find the map of these to actual CEC codes.

@sdabet
Copy link
Author

sdabet commented Dec 20, 2017

You should probably take a look at libcec source code.

@gamenic-babin
Copy link

libcec library have support for android too ??

@mpiffari
Copy link

mpiffari commented Mar 24, 2022

@sdabet Very useful hint in your code snippet!

Is there an already Ready2Go example App for Android that implement and send this commands?
Furthermore, is necessary some Hardware between smartphone and HDMI-USB adapter?

@lukatavcer
Copy link

@sdabet Very useful hint in your code snippet!

Is there an already Ready2Go example App for Android that implement and send this commands? Furthermore, is necessary some Hardware between smartphone and HDMI-USB adapter?

Have you found any working examples?

@mpiffari
Copy link

mpiffari commented Aug 9, 2022

Hi @lukatavcer,
unfortunately no, I have not found any examples!

Are you trying to build your own app in order to send CEC command to TV?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment