Dilbert66 / esphome-vistaECP

This is an implementation of an ESPHOME custom component and ESP Library to interface directly to a Safewatch/Honeywell/Ademco Vista 15/20 alarm system using the ECP interface and very inexpensive ESP8266/ESP32 modules .
GNU Lesser General Public License v2.1
124 stars 21 forks source link

RF door sensors reporting open at regular intervals #96

Open travisrroy opened 1 year ago

travisrroy commented 1 year ago

Problem

I updated to the latest dev branch since I had some issues with the ESP32 disconnecting randomly after unexpected power loss and now I have a new problem. Basically, each door sensor will report to home assistant that it has been opened when it has not every 72 minutes or so since the last time it falsely reported it. Maybe something to do with rfSerialLookup? I do like this new feature though.

Platform

Vista-20P, 6150RF, 6x 5816WMWH, ESP32, Home Assistant 2023.2.3

Config

#for documentation see project at https://github.com/Dilbert66/esphome-VistaECP
substitutions:
  systemName: "vistaalarm"
  accessCode: !secret access_code #Only comes into effect if needed for arming and quickarm is not set

  maxZones: "48" #maximum amount of zones that your panel supports
  maxPartitions: "3" #maximum amount of partitions that your panel supports

  rfSerialLookup: "0397567:09:80,0931570:10:80,0037117:11:80,0117340:12:80,0136928:13:80,0798385:14:80,0199136:15:80,0474834:16:80" # list of RF serial numbers with associated zone and bitmask. 
  #Format: "serial1#:zone1:mask1,serial2#:zone2:mask2"
  #Mask: hex value used to mask out open/close bit from RF returned value

  defaultPartition: "1"  

  #assign a new virtual keypad address to each active partition using programs *190 - *196
  #and enter it below.  For unused partitions, use 0 as the keypad address.
  keypadAddr1: "19" #partition 1 virtual keyapd
  keypadAddr2: "0" #partition 2 virtual keypad. set to 0 to disable
  keypadAddr3: "0" #partition 3 virtual keypad. set to 0 to disable

  ##esp32
  rxPin: "22" #GPIO pin to use for data receive (yellow line) 
  txPin: "21" #GPIO pin to use for data transmit (green line)
  monitorPin: "18" #GPIO pin to use for monitoring module traffic such as RF or Expanders . Set to -1 to disable

  ##esp8266
  # rxPin: "5" #GPIO pin to use for data receive (yellow line) 
  # txPin: "4" #GPIO pin to use for data transmit (green line)
  # monitorPin: "14" #GPIO pin to use for monitoring module traffic such as RF or Expanders . Set to -1 to disable

  # module addresses:
  # 07 4229 zone expander  zones 9-16
  # 08 4229 zone expander zones 17-24
  # 09 4229 zone expander zones 25-32
  # 10 4229 zone expander zones 33-40
  # 11 4229 zone expander zones 41 48
  # 12 4204 relay module  
  # 13 4204 relay module
  # 14 4204 relay module
  # 15 4204 relay module

  expanderAddr1: "8" # 1st zone expander emulator (4229) address to use . Set to 0 to disable. 
  expanderAddr2: "0" # 2nd expander emulator address to use . Set to 0 to disable. 

  relayAddr1: "0" # relay module emulation (4204) addresses. Set to 0 to disable
  relayAddr2: "0"
  relayAddr3: "0"
  relayAddr4: "0"

  TTL: "15000"  # time  to live  in ms for zone/fire status before expiring;
  quickArm: "false"
  lrrSupervisor: "true" # set to true if we don't have an LRR monitoring supervisor we can emulate one to get the statuses

esphome:
  name: $systemName
  platform: ESP32
  #board: nodemcu-32s
  board: mhetesp32devkit
  # platform: ESP8266
  # board: nodemcuv2

  # subdirectory where library *.h and *.cpp are placed 
  includes:
    - vistaEcpInterface/

wifi:
  networks:
  - ssid: !secret wifi_ssid
    password: !secret wifi_password

  ap:
    ssid: "$systemName"
    password: !secret wifi_password_backup

logger:
  baud_rate: 115200
  level: DEBUG

api:
  encryption:
    key: !secret encryption_key

ota:
  password: !secret ota_password
  safe_mode: True
  on_begin:
    - lambda: |-
        disconnectVista();

status_led:
  pin:
    number: GPIO2
    inverted: yes

time:
  - platform: homeassistant
    id: local_time

custom_component:
- lambda: |-
    auto VistaECP = new vistaECPHome($keypadAddr1,$rxPin,$txPin,$monitorPin,$maxZones,$maxPartitions);
    VistaECP->defaultPartition=$defaultPartition;
    VistaECP->partitionKeypads[1]=$keypadAddr1;
    VistaECP->partitionKeypads[2]=$keypadAddr2;
    VistaECP->partitionKeypads[3]=$keypadAddr3;
    VistaECP->rfSerialLookup="$rfSerialLookup"; 
    VistaECP->accessCode="$accessCode";
    VistaECP->quickArm=$quickArm;
    VistaECP->expanderAddr1=$expanderAddr1; //zone expander
    VistaECP->expanderAddr2=$expanderAddr2;
    VistaECP->relayAddr1=$relayAddr1; //relay module 
    VistaECP->relayAddr2=$relayAddr2;
    VistaECP->relayAddr3=$relayAddr3; 
    VistaECP->relayAddr4=$relayAddr4;     
    VistaECP->lrrSupervisor=$lrrSupervisor;
    VistaECP->TTL=$TTL;
    VistaECP->debug=1;

    VistaECP->onSystemStatusChange([&](std::string statusCode,uint8_t partition) {
      switch(partition) {
        case 1: id(system_status).publish_state(statusCode);break;
        case 2: break;
        case 3: break;
        default: break;
      }
    });

    VistaECP->onLine1DisplayChange([&](std::string msg,uint8_t partition) {
      switch(partition) {    
        case 1: id(l1).publish_state(msg); break;
        case 2: break;
        case 3: break;
        default: break;
      }
    });  
    VistaECP->onLine2DisplayChange([&](std::string msg,uint8_t partition) {
      switch(partition) {    
        case 1: id(l2).publish_state(msg); break;
        case 2: break;
        case 3: break;
        default: break;
      }
    }); 
    VistaECP->onBeepsChange([&](std::string beeps,uint8_t partition) {
      switch(partition) {        
        case 1: id(beep1).publish_state(beeps); break;
        case 2: break;
        case 3: break;
        default: break;
      }
    });
        VistaECP->onZoneExtendedStatusChange([&](std::string msg) {
        id(zoneExtended).publish_state(msg); 
    }); 

    VistaECP->onLrrMsgChange([&](std::string msg) {
        id(m1).publish_state(msg); 
    });    

    VistaECP->onRfMsgChange([&](std::string msg) {
        id(rf1).publish_state(msg); 
    });

    VistaECP->onStatusChange([&](sysState led,bool open,uint8_t partition) {
      switch(partition) {          
        case 1: 
          switch(led) {
            case sfire: id(fire).publish_state(open);break;
            case salarm: id(alarm1).publish_state(open);break;
            case strouble: id(trouble).publish_state(open);break;
            case sarmedstay: id(stay).publish_state(open);break;
            case sarmedaway: id(away).publish_state(open);break;
            case sinstant: id(instant).publish_state(open);break; 
            case sready: id(ready).publish_state(open);break; 
            case sac: id(ac).publish_state(open);break;          
            case sbypass: id(bypass).publish_state(open);break;  
            case schime: id(chime).publish_state(open);break;
            case sbat: id(bat).publish_state(open);break;
            case sarmednight: id(night).publish_state(open);break;  
            case sarmed: id(armed).publish_state(open);break;                  
            default: break;
          };
          break;
        case 2: break;
        case 3: break;
        default: break;
      }
    });

    VistaECP->onZoneStatusChange([&](uint8_t zone, std::string open) {
      switch (zone) {
        case 9: id(z9).publish_state(open); break;
        case 10: id(z10).publish_state(open); break;
        case 11: id(z11).publish_state(open); break;
        case 12: id(z12).publish_state(open); break;
        case 13: id(z13).publish_state(open); break;
        case 14: id(z14).publish_state(open); break;
        case 15: id(z15).publish_state(open); break;
        case 16: id(z16).publish_state(open); break;

        // Emulated Zone
        case 17: id(z17).publish_state(open); break;
        // case 25: id(z25).publish_state(open); break;

        default: break; 
      }
    }); //you can add more zones above . Also add the text sensor entry below

    VistaECP->onRelayStatusChange([&](uint8_t addr,uint8_t zone,bool open) {
    switch(addr) {
      case 12: 
        switch (zone) {
          case 1: id(r1).publish_state(open); break;
          case 2: id(r2).publish_state(open); break;
        }
        break;
      case 13: break;
      default: break;
      }
    });  //add as many case and switch statements as needed to control your binary sensor outputs      
    return {VistaECP};

binary_sensor:
#- platform: gpio  #example use of pin d8 as a zone trigger port for the emulated zone expander
#  pin: D8
#  id: pind8
#  device_class: window
  # on_press:       #zone,on/off
  #   - lambda: |-
  #       vista.setExpFault(17,1); 
#  on_release:
#    - lambda: |-
  #       vista.setExpFault(17,0);  

    #system status indicator definitions
  - platform: template
    id: trouble
    name: "$systemName Trouble"
    #device_class: problem

  - platform: template
    id: bypass
    name: "$systemName Bypass"

  - platform: template
    id: away
    name: "$systemName Away"

  - platform: template
    id: armed
    name: "$systemName Armed"    

  - platform: template
    id: stay
    name: "$systemName Stay"

  - platform: template
    id: instant
    name: "$systemName Instant"

  - platform: template
    id: night
    name: "$systemName Night"

  - platform: template
    id: ac
    name: "$systemName AC"
    device_class: plug  

  - platform: template
    id: chime
    name: "$systemName Chime"

  - platform: template
    id: alarm1
    name: "$systemName Alarm"

  - platform: template
    id: bat
    name: "$systemName Battery"
    device_class: problem

  - platform: template
    id: fire
    device_class: smoke
    name: "$systemName Fire"

  - platform: template
    id: ready
    name: "$systemName Ready"
    #device_class: problem

    #relay module channels add as many as you need.  To hide, comment out the name: attribute
  - platform: template
    id: r1
    # name: "$systemName Relay1"

  - platform: template
    id: r2
    # name: "$systemName Relay2"   

    #zone definitions.  Add more (also add to the switch statment above). To hide, comment out the name: attribute
text_sensor:
    #zone definitions
  - platform: template
    id: z9
    name: "$systemName Front Door"

  - platform: template
    id: z10
    name: "$systemName Patio Door"

  - platform: template
    id: z11
    name: "$systemName Garage Door"

  - platform: template
    id: z12
    name: "$systemName Basement Door"

  - platform: template
    id: z13
    name: "$systemName Main Motion"

  - platform: template
    id: z14
    name: "$systemName Smoke Detector"  
  - platform: template
    id: z15
    name: "$systemName Garage Man Door"

  - platform: template
    id: z16
    name: "$systemName Garage Basement Door"

  - platform: template
    id: z17
    name: "$systemName Test"

    #system status 
  - platform: template
    id: system_status
    name: "$systemName System Status"
    icon: "mdi:shield"

  - platform: template
    id: m1
    name: "$systemName Lrr Msg"
    icon: "mdi:alert-box"

  - platform: template
    id: rf1
    name: "$systemName RF Msg"
    icon: "mdi:alert-box"    

  - platform: template
    id: l1
    name: "$systemName Line1"

  - platform: template
    id: l2
    name: "$systemName Line2"

  - platform: template
    id: beep1
    name: "$systemName Beeps"  

  - platform: template
    id: zoneExtended
    name: "$systemName Zone Status"    

switch:
  - platform: template
    name: "$systemName Connection"
    id: connection_status_switch
    lambda: |-
      return vista.keybusConnected;
    icon: "mdi:shield-link-variant"
    turn_on_action:
      - switch.toggle: restart_switch
    turn_off_action:
      - lambda: |-
          disconnectVista();
  - platform: restart
    id: restart_switch

Logs

I managed to catch these 3 logs from ESPHome.
image

Dilbert66 commented 1 year ago

Well, I can see that the panel is sending RF device statuses with the status of 0x84 and since your bit mask is set to 0x80, it sees it as an open. Do you have a status with a close event so we can determine what the correct bit mask should be.

Dilbert66 commented 1 year ago

I think the heartbeat that rf devices send every 60 to 90 minutes is causing the issue. I'll check my code in how it handles the mask.

Dilbert66 commented 1 year ago

I've pushed a small update on dev. Just an update to vistalarm.h that has it ignoring RF heartbeat status messages for zone updates. See if that addresses your issue. I'd still like to see a close RF status as well to see if there isnt anything else at play here.

travisrroy commented 1 year ago

Ok, so I haven't updated yet, but I collected these logs. I'm not sure if they have any specific close events in them.

Door 1 Event 1 image
Event 2 (I believe this is me actually opening the door) image Event 3 image

Door 2 image image

Dilbert66 commented 1 year ago

Ok, I see the issue. You should be using 0x20 as the mask and not 0x80. With rf sensors there are 4 possible loops that can be used and the corresponding masks would be 0x80, 0x40, 0x20 and 0x10. Your's has two loops defined. 0x80 is always on and 0x20 is what determine the open/close status (giving a on value of 0xa0 on and 0x80 off). I don't know the logic at this time as to how the loops are selected. The rf serial lookup is still experimental at this time. So change the mask value (3rd entry of each record) of each sensor to 0x20.

travisrroy commented 1 year ago

Ok. It looks like changing the mask fixed the issue. One question about the rf serial lookup is would I figure out the mask for motion sensors and smoke detectors?

Dilbert66 commented 1 year ago

Only way is to trigger then reset them and see what the value changes to in the RFX line in the log. It's a guessing game as I don't know the logic that determines it. I don't have enough data from other users to compare. I also do not have any rf sensors set up so can't test myself. I'm only working off a bench test system as far as the vista is concerned.

travisrroy commented 1 year ago

Ok, I tested the smoke detector. B0 when triggered and 00 when disarmed. Not sure how to determine the mask for that. Still waiting/trying to trigger the motion detector.

Edit: For documentation sake, I believe the smoke detector is a 5806W3.

travisrroy commented 1 year ago

Ok, so the PIR motion produces an 80 when triggered and 00 when cleared.

Dilbert66 commented 1 year ago

ok, Great. From what I see you can use 0x80 mask for the PIR and smoke detectors as they all set bit 7 (0x80) on and off. For the door sensors, you use 0x20 for the mask as bit 7 (0x80) of the status seems to always be on but bit 5 (0x20) is the one that toggles to indicate open/close status. Interesting though that the smoke sensor set bit 7, 5 and 4 on for active and 0 for idle. You only need to test for one of those bits so might as well use bit 7(0x80)

travisrroy commented 1 year ago

So setting the proper mask worked great. When I updated to the latest commit (75d02a6), I got a warning in the compiler and the doors were no longer working in HA. I downgraded back to 21fa8b5 and it works great again.

Dilbert66 commented 9 months ago

FYI, i've changed the way the RF devices are entered in the yaml to make it easier. It will use whatever info you have setup from program *56 for loop# and zone#. No need to worry about masks. Just enter the serial:loop#:zone#. You can see this document for default loop numbers for various devices: https://advancedsecurityllc.com/wp-content/uploads/5800%20Wireless%20Device%20List.pdf