The thing is, I don’t know that you can expect an ACK unless you send a packet to the correct address.
Thinking on this further, I wonder if using a higher level language may make things easier. Perhaps CircuitPython or something.
Hmm, I might be wrong, but my impression was that the dymmy_reviever will create a “fake” response by emulating an endpoint? Think I have seen examples in the forum where the response turns up with the debug active but no dummy_reciever.
Nope, just connected the ESP directly to the amp using a modified RJ12.
Have the same idea, the amp will not respond if it is not the correct address.
Might be missing something fundamental, but the example they give in the beginning does not make sense to me:
My interpretation is that this is the address. Checking my amp it has the link address 1 set (I checked this using the menu). Do not understand the PROD_DEV_CODE above the table, cannot find any other references to this in the documentation, my impression was that this would be the addressing to use, but seems to be unrelated.
Right now I´m going back to square one, not sure if the pinout config for the RJ12 is correct, though that this was a standard for RS232, but googling this it seems to be all over the place, so have asked Lyngdorf for the correct pinout used.
The address is sent in the 2nd and 3rd bytes, so there are 65535 possible addresses.
The PRODUCT_DEV_CODE is returned by command 200, which puts it in byte 23 of the answer - see page 46.
The example given in the quote in your last post does exactly that. It sends C8 hex (200 dec) to device with address 0001 and in the return 07 is in byte 23. 7 tells that it is a TDA2200+ RoomP.
Thanks for the explanation. I think I might be in over my head .
Got the pin-out from Lyngdorf for the RS232 cable and have connected the cabling to the ESP32 according to this:
(my notes in blue)
The config for UART is as follows:
# UART Serial interface
uart:
baud_rate: 57600
tx_pin: GPIO17
rx_pin: GPIO16
rx_buffer_size: 256
data_bits: 8
parity: NONE
stop_bits: 1
debug:
direction: BOTH
dummy_receiver: false
sequence:
- lambda: UARTDebug::log_binary(direction, bytes, ',');
Not 100% on this conf. but verified the baud rate on the amp at least, the rest is more or less the defaults.
Set up a switch that send the following command:
#test serial hex input, request for dev info
switch:
- platform: uart
name: "Comm test"
data: [0x05, 0x01, 0x00, 0xc8, 0xce]
The amp reports having the “link address” 1, so I guess that 0x01 0x00 should be correct to address it.
This gives me the following log:
[21:22:46][VV][api.service:558]: on_switch_command_request: SwitchCommandRequest {
key: 3250616424
state: YES
}
[21:22:46][D][switch:013]: 'Comm test' Turning ON.
[21:22:46][D][switch:037]: 'Comm test': Sending state ON
[21:22:46][VV][api.service:156]: send_switch_state_response: SwitchStateResponse {
key: 3250616424
state: YES
}
[21:22:46][D][uart.switch:020]: 'Comm test': Sending data...
[21:22:46][D][switch:037]: 'Comm test': Sending state OFF
[21:22:46][VV][api.service:156]: send_switch_state_response: SwitchStateResponse {
key: 3250616424
state: NO
}
[21:22:46][D][uart_debug:196]: >>> 0b00000101 (0x05),0b00000001 (0x01),0b00000000 (0x00),0b11001000 (0xC8),0b11001110 (0xCE)
[21:22:50][VV][api.service:470]: on_ping_request: PingRequest {}
[21:22:50][VV][api.service:043]: send_ping_response: PingResponse {}
My expectation would be to get something back from the amp, but nothing. Just to test I also reversed the Tx/Rx connection, same result.
Any ideas on what to test to try to figure this out?
How? Looks from the manual that there is a method to set the baud rate on various devices (eg command code 204), but it doesn’t say a default. I would try 9600.
I really think you should be using a TTL/RS232 interface.
Have you tried the Show Address command?
The amp lets you set the baud rate using the standard interface (buttons) and display, so verified the rate and that is also where the “link address” is showed.
Looking to get hold of the original cable for RS232 from Lyndorf, I can then connect from my laptop and verify that it is working in default setup, can use portsniffer to look at the actual commands as well over the wire.
Will check the “show address” command, missed that one.
Also asked them to clarify the pinout on the RJ12 as well, realized late yesterday that it is possible to interpret in different ways.
@mwitt : I’m not sure whether you are still working on this, but I have a similar setup for my TDAi2200 (commands are different compared to SDAi but the way of serial control is identical)
My code (which creates switches in HA (should convert this to buttons once, they did not exist at the time of creating this):
esphome:
name: esp-lyngdorf-serial
includes:
- parse_serial_data.h
on_boot:
priority: -100
then:
- switch.turn_on: lyngdorf_get_setup_data #retrieve current amp states after reboot
esp32:
board: lolin32_lite
framework:
type: arduino
# Enable logging
logger:
#Retrieve '19' messages from log
on_message:
level: DEBUG
then:
- if:
condition:
lambda: 'return strstr(message,"<<<");'
then:
- text_sensor.template.publish:
id: lyngdorf_raw_msg #zoek naar level 5 tag uart_debug in string <<<
state: !lambda
return remove_up_to_arrows(message);
# return "Triggered on_message with level " + to_string(level) + ", tag " + tag + " and message " + message;
# Enable Home Assistant API
api:
ota:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Esphome-Web-XYZ"
password: "XYZXYZ"
captive_portal:
uart:
baud_rate: 9600
id: uart_lyngdorf
tx_pin:
number: 17
inverted: true
rx_pin:
number: 16
inverted: true
debug:
direction: BOTH
dummy_receiver: true
after:
delimiter: "\n"
sequence:
- lambda: UARTDebug::log_hex(direction, bytes, ',');
# To parse serial output and report to HA
text_sensor:
- platform: template
name: "RAW Lyngdorf message"
id: "lyngdorf_raw_msg"
icon: "mdi:card-text-outline"
binary_sensor:
- platform: status #Connection status of ESP
name: "Lyngdorf serial connection status"
##########
# Serial sent stuff and GPIO
##########
switch:
- platform: gpio
pin: #OnBoard led
number: 22
inverted: true
name: "Set Onboard LED"
id: onboard_led
- platform: uart
name: "Lyngdorf: Set Volume 30"
id: lyngdorf_volume_30
icon: "mdi:volume-plus"
data: [0x07, 0x01, 0x00, 0x73, 0x2c, 0x01, 0xA8] # return 02 aa
- platform: uart
name: "Lyngdorf: Set Digital 1"
id: lyngdorf_set_digital_1
icon: "mdi:import"
data: [0x06, 0x01, 0x00, 0x71, 0x01, 0x79] # return 02 aa
on_turn_off:
then:
- delay: 1s
- switch.turn_on: lyngdorf_get_setup_data
- platform: uart
name: "Lyngdorf: Set Digital 2"
id: lyngdorf_set_digital_2
icon: "mdi:import"
data: [0x06, 0x01, 0x00, 0x71, 0x02, 0x7A] # return 02 aa
on_turn_off:
then:
- delay: 1s
- switch.turn_on: lyngdorf_get_setup_data
- platform: uart
name: "Lyngdorf: Set Digital 3"
id: lyngdorf_set_digital_3
icon: "mdi:import"
data: [0x06, 0x01, 0x00, 0x71, 0x03, 0x7B] # return 02 aa
on_turn_off:
then:
- delay: 1s
- switch.turn_on: lyngdorf_get_setup_data
- platform: uart
name: "Lyngdorf: Set Digital 4"
id: lyngdorf_set_digital_4
icon: "mdi:import"
data: [0x06, 0x01, 0x00, 0x71, 0x04, 0x7C] # return 02 aa
on_turn_off:
then:
- delay: 1s
- switch.turn_on: lyngdorf_get_setup_data
- platform: uart
name: "Lyngdorf: Set Digital 5"
id: lyngdorf_set_digital_5
icon: "mdi:import"
data: [0x06, 0x01, 0x00, 0x71, 0x05, 0x7D] # return 02 aa
on_turn_off:
then:
- delay: 1s
- switch.turn_on: lyngdorf_get_setup_data
- platform: uart
name: "Lyngdorf: Set Power On"
id: lyngdorf_set_power_on
icon: "mdi:power-on"
data: [0x06, 0x01, 0x00, 0x75, 0x01, 0x7D] # return 02 aa
- platform: uart
name: "Lyngdorf: Set Power Off"
id: lyngdorf_set_power_off
icon: "mdi:power-off"
data: [0x06, 0x01, 0x00, 0x75, 0x00, 0x7C] # No return
- platform: uart
name: "Lyngdorf: Comm test"
id: lyngdorf_send_comm_test
icon: "mdi:test-tube"
data: [0x05, 0x01, 0x00, 0x01, 0x07] # return 02 aa
- platform: uart
name: "Lyngdorf: Get setup data"
id: lyngdorf_get_setup_data
icon: "mdi:cog"
data: [0x05, 0x01, 0x00, 0xC8, 0xCE] # Returns setup data
My ESP code, creates switches in HA (i should convert this to buttons once, they did not exist at the time of creating this) and puts the reply by amplifier into the raw message, this is parsed by a separate piece of code in AppDaemon (out of this I get information like power state, volume and selected input back into HA)
AppDaemon code:
def parse_Lyngdorf_state(self, NewData):
if (len(self.LyngdorfDataBuffer) > 0):
self.LyngdorfDataBuffer = self.LyngdorfDataBuffer + "," + NewData
else:
self.LyngdorfDataBuffer = NewData
while (len(self.LyngdorfDataBuffer) > 15):
state_array = self.LyngdorfDataBuffer.split(",")
if (state_array[0] == "19"):
#self.log("parsing 19, status message")
if (len(state_array) > 24): #25 parts in complete msg
self.parse_Lyngdorf_msg19(state_array)
self.LyngdorfDataBuffer = self.LyngdorfDataBuffer[75:len(self.LyngdorfDataBuffer)]
else:
self.log("message 19 too short")
break
elif (state_array[3] == "73"): #73: 07,..,..,73,C5,01,.. Volume, actual volume C5,01
if (len(state_array) > 6): #7 parts in complete msg
#self.log("Msg 73, volume, received")
self.parse_Lyngdorf_73_volume(state_array)
self.LyngdorfDataBuffer = self.LyngdorfDataBuffer[21:len(self.LyngdorfDataBuffer)]
else:
self.log("message 73 too short")
break
elif (state_array[3] == "74"): #74: 06,..,..,74,01,.. Mute, 1=on
if (len(state_array) > 5): #6 parts in complete msg
#self.log("Msg 74, mute, received")
self.parse_Lyngdorf_74_mute(state_array)
self.LyngdorfDataBuffer = self.LyngdorfDataBuffer[18:len(self.LyngdorfDataBuffer)]
else:
self.log("message 74 too short")
break
elif (state_array[3] == "75"): #75 06,..,..,75,00,.. Power, 0 is off
if (len(state_array) > 5): #6 parts in complete msg
#self.log("Msg 75, power, received")
self.parse_Lyngdorf_75_power(state_array)
self.LyngdorfDataBuffer = self.LyngdorfDataBuffer[18:len(self.LyngdorfDataBuffer)]
else:
self.log("message 75 too short")
break
elif (state_array[3] == "89"): #89 07,..,..,89,00,64,.. Delta volume -- IGNORE
if (len(state_array) > 6): #7 parts in complete msg
#self.log("Msg 89 received, ignoring volume delta message")
self.LyngdorfDataBuffer = self.LyngdorfDataBuffer[21:len(self.LyngdorfDataBuffer)]
else:
self.log("message 89 too short")
break
elif ( (state_array[0] == "02") and (state_array[1] == "AA")): #ACK message - ignore
self.LyngdorfDataBuffer = self.LyngdorfDataBuffer[6:len(self.LyngdorfDataBuffer)]
else:
self.log("Invalid message received from Lyngdorf " + self.LyngdorfDataBuffer)
#ignore all remaining
self.LyngdorfDataBuffer = ""
break
def parse_Lyngdorf_msg19(self, state_array):
if (len(state_array) >= 9): #only focus on parsed data max field=9
#Power (not ignored here, in case I accidentally mis a message), komt wel vaak als hij uit staat, niet als hij aan staat
#self.log("power " + state_array[1])
if (state_array[1] == "01"):
self.set_state("input_boolean.lyngdorf_power_state", state="on")
else:
self.set_state("input_boolean.lyngdorf_power_state", state="off")
#Volume
#ignore volume here, will be sent regularly (and has offset....)
#self.parse_Lyngdorf_volume(state_array[3], state_array[2])
#calculated_vol = ((256*(int(state_array[3], 16)) + (int(state_array[2], 16)) -100))/10
#self.set_state("input_text.lyngdorf_volume_state", state=str(calculated_vol))
#Mute
#ignore mute, will be sent regularly in seperate message
#self.log("mute " + state_array[4])
#if (state_array[4] == "01"):
# self.set_state("input_boolean.lyngdorf_mute_on_state", state="on")
#else:
# self.set_state("input_boolean.lyngdorf_mute_on_state", state="off")
#Input This is not sent regulary by Lyngdorf themselves
input=state_array[9]
if (input == "05"): #Order optimized for performance
self.set_state("input_text.lyngdorf_input_state", state="Digital 1")
elif (input == "09"):
self.set_state("input_text.lyngdorf_input_state", state="Digital 5")
elif (input == "06"):
self.set_state("input_text.lyngdorf_input_state", state="Digital 2")
elif (input == "07"):
self.set_state("input_text.lyngdorf_input_state", state="Digital 3")
elif (input == "08"):
self.set_state("input_text.lyngdorf_input_state", state="Digital 4")
elif (input == "01"):
self.set_state("input_text.lyngdorf_input_state", state="Analog 1")
elif (input == "02"):
self.set_state("input_text.lyngdorf_input_state", state="Analog 2")
elif (input == "03"):
self.set_state("input_text.lyngdorf_input_state", state="Analog 3")
elif (input == "04"):
self.set_state("input_text.lyngdorf_input_state", state="Analog 4")
else:
self.set_state("input_text.lyngdorf_input_state", state="unknown")
#0 19: nr of bytes n.i. (not interesting)
#1 01: 1 power on implemented
#2 26: 38 vol LSB implemented
#3 01: 1 vol MSB implemented
#4 00: MUTE (off) implemented
#5 5E:def vol LSB (dit moet samen 35 zijn?) n.i.
#6 01,def vol MSB n.i.
#7 52,max vol lsb n.i.
#8 03,max vol msb n.i.
#9 09,SRC (digital 5) implemented
#10 01,PReset 1 n.i. ???
#11 01,Display max intens n.i.
#12 00,POL n.i.
#13 00,P1 n.i.
#14 00,P2 n.i.
#15 00,P3 n.i.
#16 00,P4 n.i.
#17 00,Remote setting both n.i.
#18 00,remote no disable n.i.
#19 01,master n.i.
#20 00,balance n.i.
#21 00,version n.i.
#22 26,version n.i.
#23 07,dev code n.i.
#24 2E,checksum n.i.
def parse_Lyngdorf_73_volume(self, state_array): ##73: 07,..,..,73,C5,01,.. Volume, actual volume C5,01
#self.log("parsing 73 volume")
self.parse_Lyngdorf_volume(state_array[5], state_array[4])
def parse_Lyngdorf_74_mute(self, state_array): #74: 06,..,..,74,01,.. Mute, 1=on
#self.log("parsing 74 mute " + state_array[4])
if (state_array[4] == "01"):
self.set_state("input_boolean.lyngdorf_mute_on_state", state="on")
else:
self.set_state("input_boolean.lyngdorf_mute_on_state", state="off")
def parse_Lyngdorf_75_power(self, state_array): #75 06,..,..,75,00,.. Power, 0 is off right?
#self.log("parsing 75 power " + state_array[4])
if (state_array[4] == "01"):
self.set_state("input_boolean.lyngdorf_power_state", state="on")
else:
self.set_state("input_boolean.lyngdorf_power_state", state="off")
def parse_Lyngdorf_volume(self, MSB, LSB):
calculated_vol = ((256*(int(MSB, 16)) + (int(LSB, 16)) -100))/10
self.set_state("input_text.lyngdorf_volume_state", state=str(calculated_vol))
#self.log("calculated volume " + str(calculated_vol))