ESPHome Alarm Control Panel - compare "panel state" and string with lambda function

Hi there,

I was trying to get into the ESPHome Alarm Control Panel and started with the official “ESPHome Template Alarm Control Panel”.

So far … so good.

Now I wanted to add a feature:
In “ARMED_AWAY” state I want a red LED to blink every second
In “ARMED_HOME” state I want the red LED to blink every 5 seconds
In “DISARM” state I want a green LED to be on continuously

To do that I added a “interval component”:

interval:
  - interval: 1s
    then:
      - lambda: |-
          if (alarm_control_panel_state_to_string(id(acp1)->get_state()) == "ARMED_AWAY") {
            id(status_led_red).turn_on();
            delay(500);
            id(status_led_red).turn_off();
            delay(500);}
  - interval: 5s
    then:
      - lambda: |-
          if (alarm_control_panel_state_to_string(id(acp1)->get_state()) == "ARMED_HOME") {
            id(status_led_red).turn_on();
            delay(500);
            id(status_led_red).turn_off();
            delay(500);}

When I want to run it on the ESP32 I always get following error:
error: comparison between distinct pointer types 'const esphome::LogString*' and 'const char*' lacks a cast [-fpermissive]

I have tried it in a lot of different ways (casting to string, …) and searched the internet, but I can’t find any solution to solve the problem.

Thanks already for any helpful feedback

Kind regards

Why not to use existing alarm control panel automations:

1 Like

Hi Masterzz,

good point.
But how do I then use the “interval component” to let my red led blink in different sequences depending on the current arming state?

Thanks in advance.

Kind regards

Can start scripts, which will perform flashing.

Alarm Control Panel state is enum with following values:

enum AlarmControlPanelState : uint8_t {
  ACP_STATE_DISARMED = 0,
  ACP_STATE_ARMED_HOME = 1,
  ACP_STATE_ARMED_AWAY = 2,
  ACP_STATE_ARMED_NIGHT = 3,
  ACP_STATE_ARMED_VACATION = 4,
  ACP_STATE_ARMED_CUSTOM_BYPASS = 5,
  ACP_STATE_PENDING = 6,
  ACP_STATE_ARMING = 7,
  ACP_STATE_DISARMING = 8,
  ACP_STATE_TRIGGERED = 9
};

So, You can just compare .state with f.e. ACP_STATE_ARMED_HOME

if (id(acp1).state) == ACP_STATE_ARMED_HOME) {

ah, thanks :+1:
meanwhile I had a similar idea and did it like following:

script:
  # Blink every 5 seconds
  - id: blink_red_slow
    then:
      - while:
          condition:
            lambda: 'return true;'
          then:
            - lambda: |-
                ESP_LOGD("DEBUG", "Blink red LED ...");
            - output.turn_on: status_led_red
            - delay: 500ms
            - output.turn_off: status_led_red
            - delay: 500ms
            - delay: 4s
  # Blink every second
  - id: blink_red_fast
    then:
      - while:
          condition:
            lambda: 'return true;'
          then:
            - lambda: |-
                ESP_LOGD("DEBUG", "Blink red LED ...");
            - output.turn_on: status_led_red
            - delay: 500ms
            - output.turn_off: status_led_red
            - delay: 500ms

@Masterzz

now I also tried it with your suggestion:

but I always get following error:

error: 'class esphome::template_::TemplateAlarmControlPanel' has no member named 'state'
error: expected primary-expression before '==' token

I don’t really know how to handle these errors :frowning:

You right, my theoretical idea was not right. Checked component code.
By some reason component have no public state, but method get_state().

Just complied following example with no errors:

binary_sensor:
  - platform: template
    id: xxx
    lambda: return (id(acp1).get_state() == ACP_STATE_ARMED_HOME);

@Masterzz your right :+1:

→ this works, but you have to use ‘->’ instead of ‘.’ ==> id(acp1)->get_state()
But I only get an number (int) returned from this function call

But I still need to find a way to get the actual state (DISARMED, ARMED AWAY, ARMED HOME, …) as a string

I’ve tried in vain to use this function in numerous ways:

id(acp1)->get_state()

I just can’t cast the “LogString*” to a basic string.
I’ve tried so many different ways but until now I always failed :frowning: :frowning:

My goal is to send the status via MQTT like:

  on_state:
    then:
      - lambda: |-
          ESP_LOGD("DEBUG", "State change %s", alarm_control_panel_state_to_string(id(acp1)->get_state()));
      - mqtt.publish:
          topic: ${mqtt_topic}/status
          payload: !lambda |-
              return to_string(id(acp1).get_state());

what confuses me is, that in the “ESP_LOGD” function it works to use alarm_control_panel_state_to_string(id(acp1)->get_state()) but in the payload for the mqtt part it doesn’t.
The error is the above mentioned casting issue from “LogString*” to a basic scring:
Here’s the error message:

error: could not convert 'esphome::alarm_control_panel::alarm_control_panel_state_to_string(((int)acp1->esphome::template_::TemplateAlarmControlPanel::<anonymous>.esphome::alarm_control_panel::AlarmControlPanel::get_state()))' from 'const esphome::LogString*' to 'std::__cxx11::string' {aka 'std::__cxx11::basic_string<char>'}

I would very appreciate any help or hints on this particular issue!

Kind regards!

Found similar issue for Climate: climate_action_to_string() can't be used in a text sensor · Issue #2798 · esphome/issues · GitHub
May be it can help.

The issue is really pretty similar.
But the issue has been labeled as “stale” and is not solved.

Anyway, thanks for your support!

Requesting State and then converting to a string that is then used as the payload is very clean and efficient. I get that its sexy and clean but, if its not cooperating and throwing errors, you can also use if/else statements. Its not as sexy but, it gets the job done and its easier to come back and refine your automations in the future. For example if you want a new action(s) to trigger on certain alarm panel states, you just go add it to whichever ones you need. Sometimes keeping it simple isnt a bad thing.

on_arming:
    then:
           - mqtt.publish:
             topic: ${mqtt_topic}/status
         .   payload: ....."Arming"

  
on_disarming:
        then:
           - mqtt.publish:
             topic: ${mqtt_topic}/status
         .   payload: ..... "Disarming"

Or do something like this.


 on_state:  
      - if:
        condition: 
           alarm_control_panel.is_armed acp1
        then:
           - mqtt.publish:
             topic: ${mqtt_topic}/status
         .   payload: ....."Armed"

      - if:
        condition: 
           alarm_control_panel.triggered: acp1
        then:
           - mqtt.publish:
             topic: ${mqtt_topic}/status
         .   payload: ....."Alarm Triggered!"

@Fallingaway24 thanks, I appreciate your efforts in solving this topic! :slight_smile: :+1:

Meanwhile I’ve solved in a different way, due to other requirements.
I’ve solved by using a “script component” with a “switch case”:

script:
  - id: send_arm_state
    then:
      - mqtt.publish:
            topic: ${mqtt_topic}/arm/state
            payload: !lambda |-
                switch (id(acp1)->get_state()) {
                  case ACP_STATE_DISARMED:
                    return "DISARMED";
                  case ACP_STATE_ARMED_HOME:
                    return "ARMED HOME";
                  case ACP_STATE_ARMED_AWAY:
                    return "ARMED AWAY";
                  case ACP_STATE_TRIGGERED:
                    return "TRIGGERED";
                  default:
                    return "UNKNOWN";
                }

One of the main reasons to do it with a script is due to following requirement:

  • Current State should always be displayed in NodeRed as text

In case the NodeRed service has been restarted (due to e.g. reboot of the hardware) then the dashboard of NodeRed can’t show the current state of the alarm panel.
For this I added a mqtt.publish in NodeRed which is triggered when NodeRed starts.
This MQTT message is being received by the ESP and executes the above posted script.
I now always have the current State displayed in NodeRed.

Kind regards

So mine wasnt good enough for you? Are you saying im slow in the head?!?!

Lol JK. Thats awesome! Im familiar with using switch/case but, i dont think using it for this would have even crossed my mind so, its good it crossed someones mind.

Helping people ultimately helps me keep learning and retain information. You found a better solution than what i came up with but, as far as im concerned this is a Win for both of us because I learned something too.

Just a suggestion… I would avoid using “unknown” for a state option intentionally. It makes it hard to distinguish between a potential issue(unknown) that needs investigated and something harmless. I kind of think “Disarmed” would be a good fit. If it isnt any of the Armed modes then it is by default Disarmed and not unkown, right?

Thanks for being patient.

You’re right, “unknown” might not be a good default option. I guess I’ll change it to “not set” just to be able to distinguish if the desired state has been set or if there is a failure.