Service uart.write

Hello,

I’m aiming at controlling a Sony CD Player via the S-Link bus. I’m doing this using an Arduino Pro Mini as interface: the Arduino is connected to the S-Link bus and speaks to the ESP8266 that runs ESPHome via UART.

I have managed to setup sensors using the UARTex component and send commands to the CD Player using uart.write:

button:
  - platform: template
    name: "On"
    on_press:
      - uart.write: [0x90, 0x2E, 0x0A]
  - platform: template
    name: "Off"
    on_press:
      - uart.write: [0x90, 0x2F, 0x0A]

However, instead of setting up a button in the ESP yaml for each command that can be sent I would like to set up a service to be called from Home Assistant specifying the command to be sent.
I have looked at the uart.write action documentation, but I cannot figure out how to do this. Below is what I have came up with, but this does not even compile:

api:
  encryption:
    key: "xxxxxx"
  services:
    - service: s-link_cdp90
      variables: 
        command: int
      then:
      - uart.write: !lambda return '{0x90, command}';

I would need all uart.write to start with 0x90, that is the address of the CD Player on the bus, and then be followed by the command specified when calling the service on Home Assistant. The command itself can be 1 to 3 Hex bytes, for example:

  • 0x01 >> Stop
  • 0x50 0x02 0x03 >> Play disk #2 track #3

Could somebody please advise how to implement this?

See how I do it here: Serial Projector control with ESPHome

@tom_l thank you for start pointing me into the correct direction.

In my case the device takes raw hex value like [0x90, 0x2F, 0x0A], therefore I first need to do a conversion from the string received from HA.

I have tried the following lambda (the idea would be to create a vector that starts with 0x90, contains the converted string and ends with 0x0A):

api:
  encryption:
    key: "xxxxx"
  services:
    - service: write
      variables:
        command: string
      then:
        - uart.write:
            id: s_link
            data: !lambda |-
              std::int i;
              std::string command_str = command;
              std::int commandBytes[command_str.length() / 2 + 2];
              commandBytes[0] = 0x90;
              for (i = 0; i < sizeof(commandBytes)-2; ++i) {
                std::string hexByte = command_str.substr(2 * i, 2 * i + 2);
                commandBytes[i+1] = strtol(hexByte.c_str(), NULL, 16);
              }
              commandBytes[i+2] = 0x0A;
              return commandBytes;

But I get the following error when trying to compile. It seems I’m doing something wrong when declaring the variables in the lambda?

/config/esphome/esp8266-test.yaml: In lambda function:
/config/esphome/esp8266-test.yaml:30:12: error: expected unqualified-id before 'int'
   30 |               std::int i;
      |            ^  
/config/esphome/esp8266-test.yaml:32:12: error: expected unqualified-id before 'int'
   32 |               std::int commandBytes[command_str.length() / 2 + 2];
      |            ^  
/config/esphome/esp8266-test.yaml:33:7: error: 'commandBytes' was not declared in this scope; did you mean 'command_str'?
   33 |               commandBytes[0] = 0x90;
      |       ^       ~~~~
      |       command_str
/config/esphome/esp8266-test.yaml:34:12: error: 'i' was not declared in this scope
   34 |               for (i = 0; i < sizeof(commandBytes)-2; ++i) {
      |            ^
/config/esphome/esp8266-test.yaml:38:20: error: 'i' was not declared in this scope
   38 |               commandBytes[i+2] = 0x0A;
      |                    ^
*** [.pioenvs/esp8266-test/src/main.cpp.o] Error 1
========================== [FAILED] Took 7.39 seconds ==========================

Ok, I might have made some progress… seems the variables need to be declared as unit8_t. So have updated my lambda as follows:

  services:
    - service: write
      variables:
        command: string
      then:
        - uart.write:
            id: s_link
            data: !lambda |-
              std::uint8_t i;
              std::string command_str = command;
              std::uint8_t commandBytes[command_str.length() / 2 + 2];
              commandBytes[0] = 0x90;
              for (i = 0; i < sizeof(commandBytes)-2; ++i) {
                std::string hexByte = command_str.substr(2 * i, 2 * i + 2);
                commandBytes[i+1] = strtol(hexByte.c_str(), NULL, 16);
              }
              commandBytes[i+2] = 0x0A;
              return commandBytes;

However, now when I’m trying to compile I get the following error:

Compiling .pioenvs/esp8266-test/src/main.cpp.o
/config/esphome/esp8266-test.yaml: In lambda function:
/config/esphome/esp8266-test.yaml:39:14: error: could not convert 'commandBytes' from 'uint8_t [(<anonymous> + 1)]' {aka 'unsigned char [(<anonymous> + 1)]'} to 'std::vector<unsigned char>'
   39 |               return commandBytes;
      |              ^~~~~~~~~~~~
      |              |
      |              uint8_t [(<anonymous> + 1)] {aka unsigned char [(<anonymous> + 1)]}
*** [.pioenvs/esp8266-test/src/main.cpp.o] Error 1
========================== [FAILED] Took 8.43 seconds ==========================

Now this is confusing me as the documentation for UART states that in case of template return type is std::vector<uint8_t>:

on_...:
  - uart.write: 'Hello World'

  # For escape characters, you must use double quotes!
  - uart.write: "Hello World\r\n"

  # Raw data
  - uart.write: [0x00, 0x20, 0x42]

  # Templated, return type is std::vector<uint8_t>
  - uart.write: !lambda
      return {0x00, 0x20, 0x42};

  # in case you need to specify the uart id
  - uart.write:
      id: my_second_uart
      data: 'other data'

In an array like commandBytes different from a Vector?