Rotel RSP-1570 Surround Sound processor - Remote control

Tags: #<Tag:0x00007f3f1ad1e6f0>

My legacy Rotel RSP-1570 Surround Sound processor has a RJ-45 (RS232) port, so to network enable it, I bought a BF430 Serial Ethernet Converter.

Made a Python script and it turns out working great.

Be aware that the script must be in the config-folder. It will nor work in the python_script or the custom_componet folder.

Sharing this so other who want to send Hex codes to a device can see how it can be done. I am not a developer. It has been a copy-and-paste work, so if you know a better way of doing this. Please enlighten me.

The command line/ shell command looks like this:

> # Switch - command line:
>   - platform: command_line 
>     switches:
>       rotel_rsp1570_mute_toggle:
>         command_on: "python3 /config/Rotel-RSP1570-remote.py 'unitCommand' 'Mute Toggle'" 
>         command_off: "python3 /config/Rotel-RSP1570-remote.py 'unitCommand' 'Mute Toggle'"
>         friendly_name: Rotel - Mute Toggle

# Shell command:
1570_mute_toggle: "python3 /config/Rotel-RSP1570-remote.py 'unitCommand' 'Mute Toggle'"

Remote control script:
##############################################################################
# ROTEL RSP-1570 - REMOTE - HOME THEATER PROCESSOR #
##############################################################################

import socket
import sys

ROTEL_MODEL =  'Rotel RSP-1570 remote control'

IPADDR = '192.168.0.11'
SERIALPORT = 50000
CONTROLPORT = 51857

def updateSensor(text):
    line1 = text[4:25]
    line2 = text[25:46]
    line3 = text[46:]

def sendHex(HexCode):
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect((IPADDR, SERIALPORT))
    s.send(bytes.fromhex(HexCode))
    display = s.recv(CONTROLPORT)
    s.close()
    updateSensor(display)

def unitCommand(action):
    switcher = {
        #POWER & VOLUME COMMANDS
        'Power Toggle'        : 'FE 03 A3 10 0A C0',
        'Power Off'           : 'FE 03 A3 10 4A 00',
        'Power On'            : 'FE 03 A3 10 4B 01',
        'Mute Toggle'         : 'FE 03 A3 10 1E D4',
        'Volum Up'            : 'FE 03 A3 10 0B C1',
        'Volum Down'          : 'FE 03 A3 10 0C C2',
        'Power Off All Zones' : 'FE 03 A3 10 71 27',
        #SOURCE SELECTION COMMANDS
        'Source Linn Sneaky'   : 'FE 03 A3 10 02 B8',  #Source CD
        'Source Tuner'         : 'FE 03 A3 10 03 B9',
        'Source Tape'          : 'FE 03 A3 10 04 BA',
        'Source TV'            : 'FE 03 A3 10 05 BB',  #Source Video 1
        'Source PS4'           : 'FE 03 A3 10 06 BC',  #Source Video 2
        'Source Video 3'       : 'FE 03 A3 10 07 BD',  #Source Video 3
        'Source Video 4'       : 'FE 03 A3 10 08 BE',  #Source Video 4
        'Source Video 5'       : 'FE 03 A3 10 09 BF',  #Source Video 5
        'Source Multi Input'   : 'FE 03 A3 10 15 CB',
        #SURROUND MODE COMMANDS
        'Stereo Bypass Toggle'         : 'FE 03 A3 10 11 C7',
        'Dolby 3 Stereo'               : 'FE 03 A3 10 12 C8',
        'Dolby PLIIx Mode Toggle'      : 'FE 03 A3 10 13 C9',
        'DSP Mode Toggle'              : 'FE 03 A3 10 14 CA',
        'Dolby 3 Stereo/PLIIxToggle'   : 'FE 03 A3 10 53 09',
        'dts Neo 6 Music/CinemaToggle' : 'FE 03 A3 10 54 0A',
        'DSP 1'                        : 'FE 03 A3 10 57 0D',
        'DSP 2'                        : 'FE 03 A3 10 58 0E',
        'DSP 3'                        : 'FE 03 A3 10 59 0F',
        'DSP 4'                        : 'FE 03 A3 10 5A 10',
        '5 Channel Stereo'             : 'FE 03 A3 10 5B 11',
        '7 Channel Stereo'             : 'FE 03 A3 10 5C 12',
        'Dolby PLIIx Cinema'           : 'FE 03 A3 10 5D 13',
        'Dolby PLIIx Music'            : 'FE 03 A3 10 5E 14',
        'Dolby PLIIx Game'             : 'FE 03 A3 10 74 2A',
        'Dolby Pro Logic'              : 'FE 03 A3 10 5F 15',
        'dts Neo:6 Music'              : 'FE 03 A3 10 60 16',
        'dts Neo:6 Cinema'             : 'FE 03 A3 10 61 17',
        'PLII Panorama Toggle'         : 'FE 03 A3 10 62 18',
        'PLII Dimension Up'            : 'FE 03 A3 10 63 19',
        'PLII Dimension Down'          : 'FE 03 A3 10 64 1A',
        'PLII Center Width Up'         : 'FE 03 A3 10 65 1B',
        'PLII Center Width Down'       : 'FE 03 A3 10 66 1C',
        'Dolby Digital EX Toggle'      : 'FE 03 A3 10 68 1E',
        'Next Surround Mode'           : 'FE 03 A3 10 22 D8',
        #TONE CONTROL COMMANDS
        'Treble Up'           : 'FE 03 A3 10 0D C3',
        'Treble Down'         : 'FE 03 A3 10 0E C4',
        'Bass Up'             : 'FE 03 A3 10 0F C5',
        'Bass Down'           : 'FE 03 A3 10 10 C6',
        'Tone Control Select' : 'FE 03 A3 10 67 1D',
        #OSD MENU COMMANDS
        'OSD Menu'     : 'FE 03 A3 10 18 CE',
        'Enter'        : 'FE 03 A3 10 19 CF',
        'Cursor Right' : 'FE 03 A3 10 1A D0',
        'Cursor Left'  : 'FE 03 A3 10 1B D1',
        'Cursor Up'    : 'FE 03 A3 10 1C D2',
        'Cursor Down'  : 'FE 03 A3 10 1D D3',
        #OTHER COMMANDS
        'Front Display On/Off' : 'FE 03 A3 10 52 08',
        'Party Mode Toggle'    : 'FE 03 A3 10 6E 24',
        #VOLUME DIRECT COMMANDS
        'Volume Min' : 'FE 03 A3 30 00 D6',
        'Volume 10'  : 'FE 03 A3 30 0A E0',
        'Volume 32'  : 'FE 03 A3 30 20 F6',
        'Volume 40'  : 'FE 03 A3 30 28 FD 01',
        'Volume 64'  : 'FE 03 A3 30 40 16'
    }
    HexCode = switcher.get(action, 'Error')
    if HexCode != 'Error':
      sendHex(HexCode)
    else:
      return False

# Get function name and command from calling program
impFunk = sys.argv[1]
impCom  = sys.argv[2]
fm = impFunk + '("' + impCom +  '")'

eval(fm)
1 Like

Hi,

Wow - I this is a real coincidence - we’ve both been hacking our RSP-1570s at the same time!

Not sure whether this is of any interest but I’ve been working on a Home Assistant media player component for the RSP-1570 and so far it’s working quite well. Take a look at my home assistant customisations for more information. If yoiu want to look under the hood then the low level library is on PyPi and the code lives here.

I’m using a USB to serial converter whereas I think that you are using an ethernet to serial converter. I believe that pyserial-asyncio should be able to deal with this so it may work out of the box if you specify the appropriate device in configuration.yaml. Otherwise I may need to tweak the RotelAmpConn object in the underlying comms library. Happy to look at this if needed.

I’m not sure how many other people might be interested but I’m planning to contribute the component to the Home Assistant repository at some point. Just working through their stringent requirements before submitting a pull request. Any feedback at this stage would be gratefully received.

Regards,

Phil

What you have here is so cool!
Please try to add this to the Home Assistant repository.

Step 2 of my development, was setting up a sensor and pinging the Rotel for the Rotel display information and checking if the Rotel turned on or off. But I rather go with your solution.

I hope I can use your code with my Serial Ethernet Converter (BF430). But your code is way over my skill level.

Any change you could tell me how to adapt this to a serial ethernet converter?

Or even better, build into you code the option to use a serial ethernet converter. Using a serial ethernet converter (mine is wired only, but you can get a WiFi-version too) makes location of the Rotel and Home-Assistant-unit totally independent of each other.

Cheers!

Hi,

I was thinking about the serial to ethernet cable earlier. Under the hood, pyserial-asyncio uses the serial.serial_for_url() call to open the connection which does seem to support TCP/IP to serial converters. Have a look at the documentation here.

Hopefully that means that you could specify the device in your configuration.yaml file as something like socket://192.168.0.11:50000

Would be good if you could try that. Otherwise, if I get time later in the week I will try to emulate a serial port over TCP/IP and let you know how it goes.

Regards,

Phil

Hi,

By the way, a lightweight way to play with this would be to pip install the rsp1750serial package and then download the example scripts from here. If you edit line 43 in the example_runner.py file to:

    serial_port = "socket://192.168.0.11:50000"

…then example1.py and example2.py should hopefully work.

Regards,

Phil

I am happy to test this out.

Back to my skill level…how do I pip install the rsp1750serial package (I am on HassOS 2.11)?

Hi,

Ah, I see. I was expecting that you would have a PC or Pi that you could run python3 on.

I’ve just done some experiments and I think that my suggestion above will work. Here’s what I did.

  • Firstly, I ran a TCP/IP to serial bridge on the Raspberry Pi that is actually connected to the Rotel.
  • Then I installed the example scripts onto my PC and edited line 43 in example_runner.py to use the socket protocol as suggested above. It worked perfectly.

Next, I happen to have a VirtualBox image of HassOS (version 1.13) on my PC so I had a go at installing the custom component on there and that worked too. If you are feeling brave then you might want to try the following but I strongly suggest doing a backup first!

I downloaded a copy of my custom component from GitHub. The easiest way for you to do this would be to:

  • Navigate here
  • Open __init__.py
  • Right click the ‘Raw’ button on the right hand side and ‘Save As’ to a file on your computer.
  • Hit the back button and then do the same for media_player.py

Next, in HassOS I carried out the following steps in the configurator in the config folder:

  • Created a folder custom_components
  • Changed folder to custom_components and created a subfolder rotel_rsp1570
  • Changed folder to rotel_rsp1570
  • Uploaded __init__.py and media_player.py
  • Navigate back up two levels to the folder containing configuration.yaml
  • Edit configuration.yaml and add the following to your media_player section (E.g. if you already have a media_player section then add the lines from platform downwards into it. If you don’t have a media_player section then insert the whole thing.)
media_player:
- platform: rotel_rsp1570
  device: socket://192.168.0.11:50000
  • Configuration->General->Check Config
  • Reboot HassOS. On my machine I did this by going to Hass.io on the left and then SYSTEM at the top and then REBOOT in the ‘Host System’ box.

After this you should see the Rotel Media player card on the Overview page. If it is off and the player is on then just click on the power on button and it will then stay in sync. From the “…” button you should be able to set the volume, mute, change the source etc. Note that you would ideally add a source_map entry to the config above but let’s worry about that later.

If it doesn’t work then I suggest to inspect home-assistant.log in Configurator and hopefully any typos or other issues will be visible. You could also inspect the log in Hass.io->SYSTEM.

OK, so if you got this far then try this. Add the following to the sensor entry in configuration.yaml. As before, if you already have a sensor entry then merge this into it.

sensor:
  - platform: template
    sensors:
      rsp1570_source:
        friendly_name: "Source"
        entity_id: media_player.rotel_rsp_1570
        value_template: "{{ state_attr('media_player.rotel_rsp_1570', 'source') }}"
        icon_template: mdi:video-input-hdmi
      rsp1570_volume:
        friendly_name: "Volume"
        entity_id: media_player.rotel_rsp_1570
        value_template: "{{ state_attr('media_player.rotel_rsp_1570', 'volume') }}"
        icon_template: mdi:volume-high
      rsp1570_is_muted:
        friendly_name: "Is Muted"
        entity_id: media_player.rotel_rsp_1570
        value_template: "{{ state_attr('media_player.rotel_rsp_1570', 'is_volume_muted') }}"
        icon_template: mdi:volume-mute
      rsp1570_info:
        friendly_name: "Info"
        entity_id: media_player.rotel_rsp_1570
        value_template: "{{ state_attr('media_player.rotel_rsp_1570', 'info') }}"
        icon_template: mdi:surround-sound

Next, add the following to groups.yaml:

rotel_rsp1570:
  name: Rotel RSP-1570
  control: hidden
  entities:
  - sensor.rsp1570_source
  - sensor.rsp1570_info
  - sensor.rsp1570_volume
  - sensor.rsp1570_is_muted

Now restart Home Assistant:

  • Configuration->General->Check Config
  • Server Management->Restart

Now you should see a card summarising what’s on the front display of the processor.

Hope this helps.

Regards,

Phil

Hi,

FYI, I just updated my VM to HassOS 2.11 and Home Assistant 0.91.2 and it all still works.

The one thing that I’ve noticed is that Home Assistant needs to be rebooted if the TCP/IP connection is interrupted. I might add a way to reconnect if that turns out to be a problem.

Cheers,

Phil

Hi Guys, interesting that there’s a bunch of Rotel stuff going on just now…

I have an RSX-1550, same RJ45 serial connection.

I used the serial capability on a Tasmota-flashed WEMOS D1 Mini ESP8266 connected to a MAX3232-based TTL to RS232 converter, plugged into the RJ-45 to establish control from HomeAssistant via MQTT over Wifi (just done last night!). I used an input slider for volume (with a template to generate the hex codes for each volume step 0 - 100), and switches for other functions (which are fixed hex codes and therefore easier to manage). So far it works far better than the IR blaster I was using and costs less than $10.

The ability to parse the return data from the Rotel is interesting since what I get back looks mostly like gibberish (it is ASCII text mixed with hex characters and seems to be a stream of whats scrolling across the screen on the front of the unit). I can (although I haven’t bothered) pass the return data back to HA via MQTT.

Happy to work with you guys on this or share more information, although the coding is beyond my abilities…

Brandon

@pp81381
We are getting near, but I am not all the way there.
Any idea what’s going on?

Installed the customer files (removed of my own Rotel code). In the configuration-file added logging and:

media_player:
- platform: rotel_rsp1570
  device: socket://192.168.0.11:50000
  source_map:
    LINN: CD
    TV GET: VIDEO 1
    PS4: VIDEO 2

The result was this:

The State is “off”, when the unit is “on”.

In your code the line ' CD': 'SOURCE_CD' has an space befor the letter C, I removed the space.

I got two warnings in the HA-log:

And there are some Rotel errors in the HA-log:

Hi @Tomahawk,

That’s interesting.

Don’t worry about the two warnings - HA always shows when custom components are loaded.

The media player object state will sync with the actual device upon receipt of the first message. I assume that normally the player will be off when Home Assistant is started so I just default it to off. Once we get it working, try pressing the power button on the media player card and it should sync up.

Looking at the log, the entity has definitely been intialised correctly and it even looks to me as though it connected OK.

2019-04-10 18:43:00 INFO (MainThread) [homeassistant.components.media_player] Setting up media_player.rotel_rsp1570
2019-04-10 18:43:01 DEBUG (MainThread) [custom_components.rotel_rsp1570.media_player] CONF_SOURCE_MAP: OrderedDict([('LINN', 'CD'), ('TV GET', 'VIDEO 1'), ('PS4', 'VIDEO 2')])
2019-04-10 18:43:01 DEBUG (MainThread) [custom_components.rotel_rsp1570.media_player] Registered for HASS stop event
2019-04-10 18:43:01 DEBUG (MainThread) [custom_components.rotel_rsp1570.media_player] Sources to select: {'LINN': 'SOURCE_CD', 'TV GET': 'SOURCE_VIDEO_1', 'PS4': 'SOURCE_VIDEO_2', 'TUNER': 'SOURCE_TUNER', 'TAPE': 'SOURCE_TAPE', 'VIDEO 3': 'SOURCE_VIDEO_3', 'VIDEO 4': 'SOURCE_VIDEO_4', 'VIDEO 5': 'SOURCE_VIDEO_5', 'MULTI': 'SOURCE_MULTI_INPUT'}
2019-04-10 18:43:01 DEBUG (MainThread) [custom_components.rotel_rsp1570.media_player] Message reader started.

It also looks like the source mapping is working fine. On my Rotel I do have a space ahead of CD. Maybe I put that in there by accident one day. Maybe I will change the way the mapping is initialised to avoid any problems like that.

This is where it all goes wrong I think - 40 seconds or so later.

2019-04-10 18:44:41 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/config/deps/lib/python3.7/site-packages/serial/urlhandler/protocol_socket.py", line 169, in read
    raise SerialException('socket disconnected')
serial.serialutil.SerialException: socket disconnected

Clutching at straws here but could there have been some sort of interruption to the connection?

Please also double check the serial settings on your TCP/IP to serial converter. They need to be 115200 baudrate, no parity, 8 data bits, 1 stop bit, no handshaking.

The volume level errors in the log seem to be related to the openhome media player component. Do you still get those if you comment out the Rotel component? For now I’ll assume that they are unrelated. (Another thing you could try is to temporarily comment out the openhome component and see whether the Rotel one works then.)

Given the amount of time that it ran for before it failed I’m wondering whether it might have been working for those 40 seconds. I see that you have turned on debug logging so can you try the following: put the amplifier on mute and then restart Home Assistant. While on mute the amplifier sends a feedback message every time the display flashes so we may capture something interesting in the log before we lose the connection again.

If that doesn’t work then I’d suggest that I do send you instructions as to how to set up the examples and we can try to create a minimum failing configuration for me to debug.

Thanks very much for trying this. I really hope that we get it working.

Best Regards,

Phil

Hi @Brandon_Beierle

I did have a go at parsing the responses from the Rotel. Take a look at the code for the rsp1570serial package on GitHub..

The message envelope is decoded in protocol.py and the various types of message payload are decoded in messages.py.

Display line 1 is quite easy to parse and useful for the HA integration. However, I found that the second line of the display is used for too many things to usefully parse it so I ended up just returning it in a field called ‘info’. That said, I do try to parse the recording and zone sources when they are displayed - I thought that it might be interesting to send the zone button to the device several times to collect this information and then leave the device back in it’s normal state.

Regards,

Phil

Thanks Phil, I’l have a look. There is a ‘Display Refresh’ code you can send on the RSX 1550 that returns the source and volume level, and some garbage (to my eye) as well…the 1550 only has a one line display. Maybe that would work instead of sending the zone button multiple times to elicit a response.

Brandon

Of course now that I look at the hex spec sheet closer there is a definition of the response structure…duh.

Hi @Tomahawk,

I thought of something else that may help us debug this. Try setting your device as follows to get debug information from the socket layer:

  device: socket://192.168.0.11:50000?logging=debug

Regards,

Phil

Before I fry my RPI3/Hassio with restarts, are my BF430 Serial Ethernet Converter settings compatible with your code?

BF-430-1

Hi,

Yes, that looks good. However I think that I see your problem. Last time, the connection didn’t drop after 40 seconds as I originally said in my email. It was 1 min 40 seconds. Or 100 seconds: the connection idle timeout.

Can that be turned off? Perhaps by setting it to 0? Otherwise we won’t be able to listen continuously for messages on the serial port that weren’t in direct response to Home Assistant messages (12v triggers, the remote control or front panel buttons).

Regards,

Phil

And we have…a winner! : D

Looks like it works fine. I had to set the "Close connection…" to "0".

When pressing the buttons on the media_player card, the commands does not work with the Rotel(?)
So I was thinking, is there any point then using the media_card domain. Why not creating a rotel domain?
In the configuration.yaml stating the Rotel units like:

rotel:
  - RSP-1570
  - RSX-1550

The sensor is created like now, but the entity IDs becomes, rotel.rsp-1570 and rotel.rsx-1550.
Your code then also creates services for all the commands for the unit. Or just one service with two arguments. The unit name (RSP-1570) and the command (Mute Toggle). This way you code can check witch rotel unit to process and handle it accordingly.

Then it would be a breeze to use Lovelace to make a Rotel remote with all the functionality.

image

Here is the log, using the debugger, if any interest.

Hi,

Great. That’s good news!

I was just replying to the original version of your message and then noticed that you’d edited it. I assume that you found the commands that you were looking for in commands.py? Bear in mind that there are also commands to go directly to most settings rather than using SEL to cycle through them.

I’m surprised that the media player card doesn’t work for you. It does work for me. I’ll have a look at that log over the weekend to see whether anything jumps out at me.

I like the idea of adding a service to send an arbitrary remote command so I’ve put that on my list. I’m a bit busy this weekend so please bear with me.

In the meantime, note that the media player base class already defines some of the services you want such as turn_on , turn_off , toggle , volume_up , volume_down , volume_set , volume_mute. Here’s an example of an automation that I was playing with:

- alias: Rotel RSP-1570 Volume Up
  trigger:
    platform: event
    event_type: ir_command_received
    event_data:
      button_name: VOLUME_UP
  action:
    service: media_player.volume_up
    entity_id: media_player.rotel_rsp_1570

RSX-1550 support and multi-device support is probably a little bit further off but I have been thinking about how to restructure the underlying library to support multiple devices and devices of different types. Will let you know.

FYI, I was also going to add a service to reconnect to the serial port to save needing to restart hass if it gets disconnected.

Regards,

Phil

Hi,

FYI, I updated my development environment to the dev head earlier and found that there will be changes that break the component when 0.92 comes out. I’ve updated media_player.py and added manifest.json to handle this and I’ve checked that it’s back compatible with 0.91. No need to update yet unless you want to try it but be aware. There are no functional changes yet - I was just getting ready to add those services.

Regards,

Phil