How to:Mythtv Mythfrontend HA remote control

Background
The following are the few simple steps required to control a mythtv frontend from the Home Assistant interface.

Mythfrontend Configuration
If you have a mythtv frontend the only thing you need to do to control it from HA is to enable the network control from the front end setup options.

You just need to check the uncheck box in the above picture and save the change.

HA bash script
I use a linux command line script that handles the communications with the Mythfrontend user interface. To make this work you need to add the following to your HA configuration.yaml file:

shell_command:
    send_myth_ctl: '/config/shell_cmds/send_myth_control {{ device }} {{ cmd }}'

After saving the update you have to restart HA core for it to read a new shell_command.
In my default HA directory (/usr/share/hassio/homeassistant), this is the same directory that hold the configuration.yaml file, I created the sub directory shell_cmds. In this directory I create the file send_myth_control with the following contents:

#!/bin/bash
#echo Parms are $1 $2>> myth.out
if [ $1 == "family_room_myth" ]; then
	ip="192.168.0.8"
        port="6546"
elif [ $1 == "back_bed_myth" ]; then
	ip="192.168.0.19"
        port="6546"
elif [ $1 == "basement_myth" ]; then
	ip="192.168.0.8"
        port="6548"
else
        echo Myth device not known $1 >> myth.out
        exit
fi

# Make sure shared memory variable exist for tracking playback speed
if [ ! -f "/dev/shm/$1" ]; then
    echo 1 > /dev/shm/$1
fi

if [ ! -z "$2" ]; then
    #echo run stop_client on $ip
    IFS='-'
    #Read the split words into an array based on dash delimiter
    read -a fields <<< "$2"

    if [[ "${fields[0]}" = PS ]]; then
        curspeed=`cat /dev/shm/$1`
        # echo Current speed is $curspeed >> myth.out
        if [[ "${fields[1]}" = P ]]; then
            if [[ $curspeed = 1 ]]; then
                curspeed=0
            else
                curspeed=1
            fi
        elif [[ "${fields[1]}" = F ]]; then
            if [[ $curspeed < 1 ]]; then
                curspeed=2
            else
                ((curspeed++1))
                if [[ $curspeed > 5 ]]; then
                    curspeed=5
                fi
            fi
        elif [[ "${fields[1]}" = R ]]; then
            curspeed="1/2"
        fi
        # echo "echo play speed "$curspeed"x| nc $ip $port -w 0" >> myth.out
        echo "play speed "$curspeed"x"| nc $ip $port -w 0
        echo $curspeed > /dev/shm/$1
    elif [[ "${fields[0]}" = KEY ]]; then
        echo key ${fields[1]} | nc $ip $port -w 0
    else
        #echo "echo key ${fields[1]} | nc $ip $port -w 0" >> myth.out
        echo jump ${fields[1]} | nc $ip $port -w 0
    fi
fi

At the top of the script you’ll need to update the “ip” line for each mythfrontend you’re working with. Port 6546 is the standard port so you will not need to modify this unless you’ve modified it on for the Mythfrontend.
Home Assistant Configuration
On HA you need to pull the “TV Remote Card” from HACS.
You edit your HA Dashboard, select "Add Card, and then select Manual.
Replace the contents that show up with:

type: custom:tv-card
entity: sun.sun
name: Family Room Mythtv
tv: true
back:
  service: shell_command.send_myth_ctl
  service_data:
    device: family_room_myth
    cmd: KEY-escape
info:
  service: shell_command.send_myth_ctl
  service_data:
    device: family_room_myth
    cmd: KEY-I
home:
  service: shell_command.send_myth_ctl
  service_data:
    device: family_room_myth
    cmd: JUMP-mainmenu
select:
  service: shell_command.send_myth_ctl
  service_data:
    device: family_room_myth
    cmd: KEY-return
left:
  service: shell_command.send_myth_ctl
  service_data:
    device: family_room_myth
    cmd: KEY-left
right:
  service: shell_command.send_myth_ctl
  service_data:
    device: family_room_myth
    cmd: KEY-right
up:
  service: shell_command.send_myth_ctl
  service_data:
    device: family_room_myth
    cmd: KEY-up
down:
  service: shell_command.send_myth_ctl
  service_data:
    device: family_room_myth
    cmd: KEY-down
volume_up:
  service: media_player.volume_up
  service_data:
    entity_id: media_player.lg_webos_tv_oled55c1pub
volume_down:
  service: media_player.volume_down
  service_data:
    entity_id: media_player.lg_webos_tv_oled55c1pub
volume_mute:
  service: media_player.volume_mute
  service_data:
    entity_id: media_player.lg_webos_tv_oled55c1pub
    is_volume_muted: true
play:
  service: shell_command.send_myth_ctl
  service_data:
    device: family_room_myth
    cmd: PS-P
reverse:
  service: shell_command.send_myth_ctl
  service_data:
    device: family_room_myth
    cmd: PS-R
forward:
  service: shell_command.send_myth_ctl
  service_data:
    device: family_room_myth
    cmd: PS-F

In the above the name after the “device” lines needs to match the device name with the IP address in the command line script above.

That should do it and you’ll end up with this interface for controlling the mythfrontend:

3 Likes

In the post above the tv is an recent LG tv with the webOS operating system. There is a custom remote card that works great with the LG tv. All other devices connected to this tv, other than mythfrontend, can be controlled via HDMI CEC. As such there was no need for a number pad tided to the mythfrontend remote interface. In the basement we have a amplifier and Epson projector for watching movies. It has a firestick for accessing apps and a mythfrontend connection. So to watch live TV from cable you need a number pad to change channels. The amplifier and projector are old enough so they don’t have network based controls. Setting this area up took a little more effort. I already had LMS setup, with squeezelite running on a raspberry pi 3 to play music on the amplifier. Setting up LMS is documented here. This is the interface I now have in place to control media in my rec room.

I switch TV cards to this one as it works with this samsung integration that I have in my system. Both are available via HACS.

The following provides the full mythfrontend card with number pad, while also providing controls for the projector and amplifier. This is just the left hand side of the page above.

type: vertical-stack
cards:
  - type: horizontal-stack
    cards:
      - show_name: true
        show_icon: true
        type: custom:button-card
        size: 25%
        tap_action:
          action: call-service
          service: shell_command.pioneer_ctl
          service_data:
            cmd: MYTH
        icon: mdi:television-play
        name: Mythtv on
      - show_name: true
        show_icon: true
        type: custom:button-card
        size: 25%
        tap_action:
          action: call-service
          service: shell_command.pioneer_ctl
          service_data:
            cmd: FIRE
        name: Firestick On
        icon: mdi:fire-circle
      - show_name: true
        show_icon: true
        type: custom:button-card
        size: 25%
        tap_action:
          action: call-service
          service: shell_command.pioneer_ctl
          service_data:
            cmd: LMS
        icon: mdi:music
        name: Music On
      - show_name: true
        show_icon: true
        type: custom:button-card
        size: 25%
        tap_action:
          action: call-service
          service: shell_command.pioneer_ctl
          service_data:
            cmd: AMPPOWER
        icon: mdi:power-cycle
        name: Cycle AMP
  - type: horizontal-stack
    cards:
      - show_name: true
        show_icon: true
        type: custom:button-card
        size: 25%
        tap_action:
          action: call-service
          service: shell_command.pioneer_ctl
          service_data:
            cmd: POWER
        icon: mdi:power-plug-off
        name: All Off
      - show_name: true
        show_icon: true
        type: custom:button-card
        size: 25%
        tap_action:
          action: call-service
          service: shell_command.pioneer_ctl
          service_data:
            cmd: MUTE
        icon: mdi:volume-mute
        name: Mute
      - show_name: true
        show_icon: true
        type: custom:button-card
        size: 25%
        tap_action:
          action: call-service
          service: shell_command.pioneer_ctl
          service_data:
            cmd: DOWN
        icon: mdi:volume-minus
        name: Volume Down
      - show_name: true
        name: Volume Up
        show_icon: true
        type: custom:button-card
        size: 25%
        tap_action:
          action: call-service
          service: shell_command.pioneer_ctl
          service_data:
            cmd: UP
        icon: mdi:volume-plus
  - type: custom:tv-card
    entity: sun.sun
    title: Basement Mythtv
    tv: true
    enable_button_feedback: false
    channel_row:
      - return
      - menu
      - info
      - home
    navigation_row: buttons
    media_control_row:
      - rewind
      - play
      - fast_forward
    custom_keys:
      return:
        icon: mdi:arrow-left
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: KEY-escape
      menu:
        icon: mdi:menu
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: KEY-D
      info:
        icon: mdi:information-variant
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: KEY-I
      home:
        icon: mdi:home
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: JUMP-mainmenu
      enter:
        icon: mdi:checkbox-blank-circle
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: KEY-return
      left:
        icon: mdi:chevron-left
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: KEY-left
      right:
        icon: mdi:chevron-right
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: KEY-right
      up:
        icon: mdi:chevron-up
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: KEY-up
      down:
        icon: mdi:chevron-down
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: KEY-down
      volume_up:
        icon: mdi:volume-plus
        service: shell_command.pioneer_ctl
        service_data:
          cmd: UP
      play:
        icon: mdi:play-pause
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: PS-P
      rewind:
        icon: mdi:rewind
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: PS-R
      fast_forward:
        icon: mdi:fast-forward
        service: shell_command.send_myth_ctl
        service_data:
          device: basement_myth
          cmd: PS-F
  - type: vertical-stack
    cards:
      - type: grid
        cards:
          - type: custom:button-card
            size: 30%
            show_name: true
            show_icon: true
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: KEY-1
            icon: mdi:numeric-1
          - type: custom:button-card
            size: 30%
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: KEY-2
            icon: mdi:numeric-2
          - type: custom:button-card
            size: 30%
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: KEY-3
            icon: mdi:numeric-3
          - type: custom:button-card
            size: 30%
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: KEY-4
            icon: mdi:numeric-4
          - type: custom:button-card
            size: 30%
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: KEY-5
            icon: mdi:numeric-5
          - type: custom:button-card
            size: 30%
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: KEY-6
            icon: mdi:numeric-6
          - type: custom:button-card
            size: 30%
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: KEY-7
            icon: mdi:numeric-7
          - type: custom:button-card
            size: 30%
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: KEY-8
            icon: mdi:numeric-8
          - type: custom:button-card
            size: 30%
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: KEY-9
            icon: mdi:numeric-9
          - type: custom:button-card
            size: 30%
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: KEY-escape
            name: Cancel
            show_name: false
            icon: mdi:close
          - type: custom:button-card
            size: 30%
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: KEY-0
            icon: mdi:numeric-0
          - show_name: false
            show_icon: true
            type: custom:button-card
            size: 30%
            tap_action:
              action: call-service
              service: shell_command.send_myth_ctl
              service_data:
                device: basement_myth
                cmd: backspace
            name: Backspace
            icon: mdi:backspace-outline
        square: false

The modified shell_command.send_myth_ctl script accessed in the HA card above is here:

#!/bin/bash
echo Parms are $1 $2>> myth.out
if [ $1 == "family_room_myth" ]; then
        ip="192.168.0.8"
        port="6546"
elif [ $1 == "back_bed_myth" ]; then
        ip="192.168.0.4"
        port="6546"
elif [ $1 == "basement_myth" ]; then
        ip="192.168.0.8"
        port="6550"
else
        echo Myth device not known $1 >> myth.out
        exit
fi

# Make sure shared memory variable exist for tracking playback speed
if [ ! -f "/dev/shm/$1" ]; then
    echo 1 > /dev/shm/$1
fi

if [ ! -z "$2" ]; then
    #echo run stop_client on $ip
    IFS='-'
    #Read the split words into an array based on dash delimiter
    read -a fields <<< "$2"

    if [[ "${fields[0]}" = PS ]]; then
        curspeed=`cat /dev/shm/$1`
        # echo Current speed is $curspeed >> myth.out
        if [[ "${fields[1]}" = P ]]; then
            if [[ $curspeed = 1 ]]; then
                curspeed=0
            else
                curspeed=1
            fi
        elif [[ "${fields[1]}" = F ]]; then
            if [[ $curspeed < 1 ]]; then
                curspeed=2
            else
                ((curspeed++1))
                if [[ $curspeed > 5 ]]; then
                    curspeed=5
                fi
            fi
        elif [[ "${fields[1]}" = R ]]; then
            curspeed="1/2"
        fi
        echo "echo play speed "$curspeed"x| nc $ip $port -w 0" >> myth.out
        echo "play speed "$curspeed"x"| nc $ip $port -w 0
        echo $curspeed > /dev/shm/$1
    elif [[ "${fields[0]}" = KEY ]]; then
        echo key ${fields[1]} | nc $ip $port -w 0
    elif [[ "${fields[0]}" = CTL ]]; then
        if [[ "${fields[1]}" = SHUTDOWN ]]; then
            ssh -i /config/.ssh/id_rsa -o StrictHostKeyChecking=no brian@$ip sudo shutdown -h now
        elif [[ "${fields[1]}" = START_MYTH ]]; then
            ssh -i /config/.ssh/id_rsa -o StrictHostKeyChecking=no brian@$ip ./run_mythfrontend
        fi
    else
        #echo "echo key ${fields[1]} | nc $ip $port -w 0" >> myth.out
        echo jump ${fields[1]} | nc $ip $port -w 0
    fi
fi

On the RPI 3 running squeezelite I enabled infrared transmission in the /boot/config.txt file:

dtoverlay=gpio-ir-tx,gpio_pin=18

Then I connected an IR LED to pin 18 and a ground GPIO pin. This was done to control the pioneer amplifier via IR. This method of connecting the IR LED directly to the gpio pin doesn’t transmit very far,so I put the IR LED right in front of the amplifier. If I had a better IR transmitter I would have used it to control the Epson projector also. The Epson projector supports control communications over RS232. I attempted to use the RPI 3 com port connect to the projector’s RS232 interface but this didn’t work. I think the issue is that the RPI com port is at 3v and RS232 spec calls for 5-15v. I had a usb RS232 device sitting around so I plugged it into the squeezelite RPI and it works great talking and controlling the Epson projector. On HA I have this simple shell command “pioneer_ctl” to send commands over to the RPI.

#!/bin/bash
echo -n $1 | nc -w0 192.168.0.25 33333

If you look at the HA interface card above you’ll see this command being called. On the RPI I have a service named proj-amp-networkControl.py which handles these commands. It’s contents are here:

#!/usr/bin/python3
# License: Public Domain
from __future__ import division
import time
import sys
import socket
import os

# Uncomment to enable debug output.
#import logging
#logging.basicConfig(level=logging.DEBUG)

state="OFF"

# -- Main ----
HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 33333              # Arbitrary non-privileged port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen(1)

    try:
        while True:
            conn, addr = s.accept()
            with conn:
                print('Connected by', addr)
                while True:
                    data = conn.recv(1024).decode("ascii")
                    if not data: break
                    #print('Data: ',data)

                    if data == "POWER":
                        print("Command read is POWER")
                        # Turns piorneer off (cycle power)
                        os.system("sudo ir-ctl -S nec:0xa51c")
                        # Turn projector off
                        os.system("/home/pi/epson-proj-serial-ctl.py OFF")
                        state="OFF"
                    elif data == "AMPPOWER":
                        print("Command read is AMPPOWER")
                        # Cycle POWER on AMP and leave state alone
                        os.system("sudo ir-ctl -S nec:0xa51c")

                    elif data == "UP":
                        print("Command read is UP")
                        # Turn volume up
                        os.system("sudo ir-ctl -S nec:0xa50a")
                        time.sleep(.1)
                        os.system("sudo ir-ctl -S nec:0xa50a")

                    elif data == "DOWN":
                        print("Command read is DOWN")
                        # Turn volume down
                        os.system("sudo ir-ctl -S nec:0xa50b")
                        time.sleep(.1)
                        os.system("sudo ir-ctl -S nec:0xa50b")

                    elif data == "MUTE":
                        print("Command read is MUTE")
                        os.system("sudo ir-ctl -S nec:0xa512")

                    elif data == "FIRE":
                        print("Command read is FIRE")
                        # If state is off then turn on amp for audio
                        if state == "OFF":
                            os.system("sudo ir-ctl -S nec:0xa51c")

                        # Now turn projector on for HDMI1
                        os.system("/home/pi/epson-proj-serial-ctl.py ON HDMI1")
                        # set AMP to input connected to FIRESTICK (DVD)
                        os.system("sudo ir-ctl -S nec:0xa585")
                        # Mark state as watching Firestick
                        state="FIRE"

                    elif data == "MYTH":
                        print("Command read is MYTH")
                        # If state is off then turn on amp for audio
                        if state == "OFF":
                            os.system("sudo ir-ctl -S nec:0xa51c")

                        # turn projector on and set to PC input
                        os.system("/home/pi/epson-proj-serial-ctl.py ON PC")
                        # set pioneer to PC connected input so sound plays (DVR/BVR)
                        os.system("sudo ir-ctl -S nec:0xa589")
                        # Mark state as watching Mythtv
                        state="MYTH"


                    elif data == "LMS":
                        print("Command read is LMS")
                        # If state is off then turn on amp for audio
                        if state == "OFF":
                            os.system("sudo ir-ctl -S nec:0xa51c")
                            # since we did turn on projector will have to wait for audio to come up
                            time.sleep(20)

                        # turn off projector
                        os.system("/home/pi/epson-proj-serial-ctl.py OFF")
                        # Set piorneer to input for music (CD)
                        os.system("sudo ir-ctl -S nec:0xa54c")
                        # Mark state as listening to Logitect Media Server (squeezebox music)
                        state="LMS"

                conn.close()
                sys.stdout.flush()
    finally:
        print("All done")

I start this script by adding the following line into /etc/rc.local

su -c '/home/pi/proj-amp-networkControl.py 2>&1 > /dev/null &' pi

The user pi has to be in the dialout group to access the RS232 interface. The above script calls the
epson-proj-serial-ctl.py to implement the projector controls over RS232:

#!/usr/bin/env python

import sys
import time
import serial

ser = serial.Serial(
    port='/dev/ttyUSB0',
    baudrate = 9600,
    parity=serial.PARITY_NONE,
    stopbits=serial.STOPBITS_ONE,
    bytesize=serial.EIGHTBITS,
    write_timeout=1,
    xonxoff=False,
    rtscts=False,
    dsrdtr=False,
    timeout=1
)

arg_cnt = len(sys.argv)

if arg_cnt < 2:
    print("Usage:",sys.argv[0],"[ON|OFF]","[PC|HDMI1|HDMI2|COMPONENT|SVIDEO|VIDEO]")
    exit(0)

cmd = sys.argv[1]
if ((cmd != "ON" and cmd != "OFF") or (cmd == "ON" and arg_cnt < 3)):
    print("Usage:",sys.argv[0],"[ON|OFF]","[PC|HDMI1|HDMI2|COMPONENT|SVIDEO|VIDEO]")
    exit(0)

print("Good command", cmd)

#Clear anything that might be in input buffer
readlen=40
while readlen == 40:
    x=ser.read(40)
    readlen = len(x)

if cmd == "OFF":
    full_cmd = ("PWR OFF\r").encode("ascii")
    ser.write(full_cmd)

else: # Turning on projector if need be and setting input

    full_cmd = ("PWR?\r").encode("ascii")
    ser.write(full_cmd)

    x=ser.read(10)
    if len(x)>1:
        resp=(((x.decode("ascii")).split('\r'))[0]).split('=')[1]
        print("Response: ", resp )
        # 00 means projector is off so turn it on
        if resp == "00":
            print("Turning projector on")
            full_cmd = ("PWR ON\r").encode("ascii")
            ser.write(full_cmd)
            time.sleep(30)
            # Clear out read buffer again
            readlen=40
            while readlen == 40:
                x=ser.read(40)
                readlen = len(x)

            # Need to wait until projector reports power on when asked to set input
            full_cmd = ("PWR?\r").encode("ascii")
            done = False
            while not done:
                # Send command to check power
                ser.write(full_cmd)
                # read response
                x=ser.read(30)
                if len(x)>1:
                    try:
                        resp=(((x.decode("ascii")).split('\r'))[0]).split('=')[1]
                        print("Response: ", resp )
                        # 01 means projector is on
                        if resp == "02":
                            done = True
                    except Exception:
                        pass

    else:
        print("No response to PWR? command, will exit")
        exit(0)

    source = sys.argv[2]
    print("Need to set source to",source)

    if source == "PC":
        full_cmd = ("SOURCE 21\r").encode("ascii")
    elif source == "HDMI1":
        full_cmd = ("SOURCE 30\r").encode("ascii")
    elif source == "HDMI2":
        full_cmd = ("SOURCE A0\r").encode("ascii")
    elif source == "COMPONENT":
        full_cmd = ("SOURCE 14\r").encode("ascii")
    elif source == "SVIDEO":
        full_cmd = ("SOURCE 42\r").encode("ascii")
    elif source == "VIDEO":
        full_cmd = ("SOURCE 41\r").encode("ascii")
    else:
        print("Source",source,"not known")
        exit(0)

    ser.write(full_cmd)

With this I now have the ability to control the mythfrontend and control the amplifier and projector that provide the audio/visual.

1 Like