Marantz AV Receiver serial component

Hello

I got help from retired and very excellent software designer to solve this problem. Now I can control wirelessly Marantz SR6003 via H-Link adapter.
Only three files must be changed a little.
init.py:
“”"
Marantz has an RS232 interface to control the receiver.

Not all receivers have all functions.
Functions can be found on in the xls file within this repository
"""

import codecs
import socket
from time import sleep
from marantz_receiver.marantz_commands import CMDS
import serial  # pylint: disable=import-error
import threading
import telnetlib
import logging
import time

DEFAULT_TIMEOUT = 0.5
DEFAULT_WRITE_TIMEOUT = 0.5

_LOGGER = logging.getLogger(__name__)

class MarantzReceiver(object):
    """Marantz receiver."""

    def __init__(self, host, port, timeout=DEFAULT_TIMEOUT, write_timeout=DEFAULT_WRITE_TIMEOUT):
         """Create TCP/IP connection."""
         self.socket = socket.create_connection((host, port))
         # socket.py line 691
         # def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None):
         self.lock = threading.Lock()
        
    def exec_command(self, domain, function, operator, value=None):
        """
        Write a command to the receiver and read the value it returns.
        The receiver will always return a value, also when setting a value.
        """
        raw_command = CMDS[domain][function]['cmd']
        if operator in CMDS[domain][function]['supported_operators']:
            if value is None:
                raise ValueError('No value provided')
            else:
                cmd = ''.join([raw_command, operator, str(value)])

        else:
            raise ValueError('Invalid operator provided %s' % operator)
        with self.lock:
#            if not self.ser.is_open:
#                self.ser.open()
#            self.ser.reset_input_buffer()
#            self.ser.reset_output_buffer()
    
            # Marantz uses the prefix @ and the suffix \r, so add those to the above cmd.
            final_command = ''.join(['@', cmd, '\r']).encode('utf-8')
            _LOGGER.debug('Send Command %s', final_command)
            self.socket.send(final_command)
    
            msg = self.socket.recv(256)

        _LOGGER.debug('Response msg %s', msg.decode())

        split_string = msg.decode().strip().split(':')

        _LOGGER.debug("Decoded split string %s", split_string)
        _LOGGER.debug("Original command: %s", raw_command)
        # Check return value contains the same command value as requested. Sometimes the marantz gets out of sync. Ignore if this is the case
        if split_string[0] != ('@' + raw_command):
            _LOGGER.debug("Send & Response command values dont match %s != %s - Ignoring returned value", split_string[0], '@' + raw_command )
            return None
        else:
             return split_string[1]
             # b'AMT:0\r will return 0

    def main_mute(self, operator, value=None):
        """Execute Main.Mute."""
        return self.exec_command('main', 'mute', operator, value)

    def main_power(self, operator, value=None):
        """Execute Main.Power."""
        return self.exec_command('main', 'power', operator, value)

    def main_volume(self, operator, value=None):
        """
        Execute Main.Volume.
        Returns int
        """
        vol_result = self.exec_command('main', 'volume', operator, value)
        if vol_result != None:
            return int(vol_result)

    def main_source(self, operator, value=None):
        """Execute Main.Source."""
        result = self.exec_command('main', 'source', operator, value)
        """
        The receiver often returns the source value twice. If so take the
        second value as the source, otherwise return original
        """
        if result != None and len(result) == 2:
            _LOGGER.debug("Source Result: %s", result[1])
            return result[1]
        else:
            return result

    def main_autostatus (self, operator, value=None):
        """
        Execute autostatus.
        Not currently used but will allow two-way communications in future

        Returns int
        """
        return int(self.exec_command('main', 'autostatus', operator, value))

marantz.py:
“”"
Support for interfacing with Marantz receivers through RS-232.

For more details about this platform, please refer to the documentation at

"""
import logging

import voluptuous as vol

from homeassistant.components.media_player import (
    SUPPORT_VOLUME_SET,
    SUPPORT_VOLUME_MUTE, SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
    SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, MediaPlayerDevice,
    PLATFORM_SCHEMA)
from homeassistant.const import (
    CONF_NAME, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv

#REQUIREMENTS = ['marantz_receiver==0.0.1']
#REQUIREMENTS = ['https://github.com/andrewpc/marantz_receiver/archive/0.0.0.1.zip']

_LOGGER = logging.getLogger(__name__)

DEFAULT_NAME = 'Marantz Receiver'
DEFAULT_MIN_VOLUME = -71
DEFAULT_MAX_VOLUME = -1

SUPPORT_MARANTZ = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
    SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_VOLUME_STEP | \
    SUPPORT_SELECT_SOURCE

#CONF_SERIAL_PORT = 'serial_port'
CONF_HOST = 'host'
CONF_PORT = 'port'
CONF_MIN_VOLUME = 'min_volume'
CONF_MAX_VOLUME = 'max_volume'
CONF_SOURCE_DICT = 'sources'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    #vol.Required(CONF_SERIAL_PORT): cv.string,
    vol.Required(CONF_HOST): cv.string,
    vol.Required(CONF_PORT): cv.string,
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
    vol.Optional(CONF_MIN_VOLUME, default=DEFAULT_MIN_VOLUME): int,
    vol.Optional(CONF_MAX_VOLUME, default=DEFAULT_MAX_VOLUME): int,
    vol.Optional(CONF_SOURCE_DICT, default={}): {cv.string: cv.string},
})


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the Marantz platform."""
    from marantz_receiver import MarantzReceiver
    add_devices([Marantz(
        config.get(CONF_NAME),
        MarantzReceiver(config.get(CONF_HOST), config.get(CONF_PORT)),
        config.get(CONF_MIN_VOLUME),
        config.get(CONF_MAX_VOLUME),
        config.get(CONF_SOURCE_DICT)
    )], True)


class Marantz(MediaPlayerDevice):
    """Representation of a Marantz Receiver."""

    def __init__(self, name, marantz_receiver, min_volume, max_volume, source_dict):
        """Initialize the Marantz Receiver device."""
        self._name = name
        self._marantz_receiver = marantz_receiver
        self._min_volume = min_volume
        self._max_volume = max_volume
        self._source_dict = source_dict
        self._reverse_mapping = {value: key for key, value in
                                 self._source_dict.items()}

        self._volume = self._state = self._mute = self._source = None

    def calc_volume(self, decibel):
        """
        Calculate the volume given the decibel.

        Return the volume (0..1).
        """
        return abs(self._min_volume - decibel) / abs(
            self._min_volume - self._max_volume)

    def calc_db(self, volume):
        """
        Calculate the decibel given the volume.

        Return the dB.
        """
        return self._min_volume + round(
            abs(self._min_volume - self._max_volume) * volume)

    @property
    def name(self):
        """Return the name of the device."""
        return self._name

    @property
    def state(self):
        """Return the state of the device."""
        return self._state

    def update(self):
        """Retrieve latest state."""
        if self._marantz_receiver.main_power(':', '?') == '1':
            self._state = STATE_OFF
        else:
            self._state = STATE_ON

        if self._marantz_receiver.main_mute(':', '?') == '1':
            self._mute = False
        else:
            self._mute = True

        volume_result = self._marantz_receiver.main_volume(':', '?')
        if (volume_result != None):
            self._volume = self.calc_volume(volume_result)
        self._source = self._source_dict.get(
            self._marantz_receiver.main_source(':', '?'))

    @property
    def volume_level(self):
        """Volume level of the media player (0..1)."""
        return self._volume

    @property
    def is_volume_muted(self):
        """Boolean if volume is currently muted."""
        return self._mute

    @property
    def supported_features(self):
        """Flag media player features that are supported."""
        return SUPPORT_MARANTZ

    def turn_off(self):
        """Turn the media player off."""
        self._marantz_receiver.main_power(':', '3')

    def turn_on(self):
        """Turn the media player on."""
        self._marantz_receiver.main_power(':', '2')

    def volume_up(self):
        """Volume up the media player."""
        self._marantz_receiver.main_volume(':', '1')

    def volume_down(self):
        """Volume down the media player."""
        self._marantz_receiver.main_volume(':', '2')

    def set_volume_level(self, volume):
        """Set volume level, range 0..1."""
        vol_calc = '0' + str(self.calc_db(volume))
        self._marantz_receiver.main_volume(':', vol_calc)

    def select_source(self, source):
        """Select input source."""
        self._marantz_receiver.main_source(':', self._reverse_mapping.get(source))

    @property
    def source(self):
        """Name of the current input source."""
        return self._source

    @property
    def source_list(self):
        """List of available input sources."""
        return sorted(list(self._reverse_mapping.keys()))

    def mute_volume(self, mute):
        """Mute (true) or unmute (false) media player."""
        if mute:
            self._marantz_receiver.main_mute(':', '2')
        else:
            self._marantz_receiver.main_mute(':', '1')

configuration.yaml:

media_player:
#  - platform: cast
  - platform: samsungtv
    host: 192.168.1.177
  - platform: marantz
    host: 192.168.1.151
    port: 8080
#    serial_port: /dev/ttyS3
    name: Marantz Receiver
    min_volume: -71
    max_volume: -1
    sources:
      1: 'TV'
      3: 'VCR'
      5: 'DSS'
      9: 'AUX'
      F: 'Tuner'
      G: 'FM'
      H: 'AM'

:finland: