Sistema de Alarma para HASS + Xiaomi Gateway

Hola a todos,

En esta entrada voy a detallar como montar un sistema de alarma completo combinando el Gateway de Xiaomi, sus sensores y HomeAssistant.

Voy a dar por sentado que ya tenemos el Gateway integrado en HASS y los sensores configurados.

Yo particularmente siempre trabajo por SSH directamente contra el servidor, así que para todo el proceso utilizaremos PuttY

Nos conectamos por SSH con nuestro usuario homeassistant e ingresamos en el directorio del entorno:

 

 

 

 

En nuestro configuration.yaml agregamos lo siguiente: (nano configuration.yaml)

alarm_control_panel: !include alarm.yaml
panel_custom: !include panel_custom.yaml

Guardamos (ctrl+o y ctrl+x)

Ahora deberemos crear los archivos que hemos indicado anteriormente.

nano alarm.yaml

platform: bwalarm
name: House

code: !secret alarm_code #Code, should consist of one or more digits ie 6482
panic_code: !secret panic_code #[OPTIONAL] Panic Code should consist of one or more digits ie 9876, it needs to be different to your standard alarm code. This enables a special panic mode. This can be used under duress to deactivate the alarm which would appear to the unseeing eye as deactivated however a special attribute [panic_mode] listed under the alarm_control_panel.[identifier] will change to ACTIVE. This status could be used in your automations to send a notification to someone else police/spouse/sibling/neighbour that you are under duress. To deactive this mode arm then disarm your alarm in the usual manner.

pending_time: 25 #Grace time in seconds to allow for exit and entry using Away mode
trigger_time: 600

alarm: automation.alarm_triggered
warning: automation.alarm_warning

clock: True #Optional – True enables a clock in the center of the status bar
perimeter_mode: False #Optional – True enables perimeter mode, this could be known as ‘Day Mode’ i.e. only arm the doors whilst there is someone using all floors
weather: True #Optional – Allows a weather summary to be displayed on the status bar. Dark Sky weather component must be enabled with the name sensor.dark_sky_summary

#### COLOURS ########### Use any HTML format
warning_colour: ‘orange’
pending_colour: ‘orange’
disarmed_colour: ‘#03A9F4’
armed_home_colour: ‘black’
armed_away_colour: ‘black’
triggered_colour: ‘red’

############# SENSOR GROUPS ########################
# Sensors in this group tigger the alarm immediately
immediate:
# – binary_sensor.door_window_sensor_158d0001a5e1a3
# – binary_sensor.door_window_sensor_158d0001a99eb0
– binary_sensor.door_window_sensor_xxxxxxxxxxxx

# Sensors in this group start the clock (pending_time) when tripped before the alarm is activated in ‘Away’ mode
delayed:
– binary_sensor.door_window_sensor_xxxxxxxx
– binary_sensor.door_window_sensor_158d0001a5e1a3
– binary_sensor.door_window_sensor_158d0001a99eb0

# Same as notathome but hopefully the title is more self explanatory. Can still use notathome for backwards compatibility
# Note sensors can exist in more than one group notice top_floor appears in two groups
homemodeignore:
– binary_sensor.door_window_sensor_xxxxxxxxx

# Use this group to automatically override the warning message on open sensors when setting ‘away’ mode. (I use this as I have a motion sensor at the front door)
override:
– binary_sensor.door_window_sensor_xxxxxxxxxxxxxxxxxxx

# This group is special and only effects ‘perimeter mode’. If perimeter_mode is enabled then any sensor in this group will trigger the alarm immediately if arm perimeter is set. There is no delayed group for this mode (unless requested as a feature of course!)
perimeter:
– binary_sensor.sensor_prueba

Mostrar Código

Y guardamos.

Ahora, nano panel_custom.yaml

– name: alarm
sidebar_title: Alarm
sidebar_icon: mdi:security-home
config:
alarmid: alarm_control_panel.house ## USE THE SAME ID AS USED IN YOUR ALARM.YAML

Mostrar Código

Y guardamos.

Ahora, creamos el directorio panels.

mkdir panels
cd panels
nano alarm.html

<script src=”/local/lib/jquery-3.2.1.min.js”></script>
<script src=”/local/lib/countdown360.js”></script>
<style>
@font-face {
font-family: ‘Pacifico’;
src: url(https://fonts.gstatic.com/s/pacifico/v12/Q_Z9mv4hySLTMoMjnk_rCfesZW2xOQ-xsNqO47m55DA.woff2) format(‘woff2’);
}
@font-face {
font-family: ‘Lobster’;
font-style: normal;
font-weight: 400;
src: local(‘Lobster Regular’), local(‘Lobster-Regular’), url(https://fonts.gstatic.com/s/lobster/v20/cycBf3mfbGkh66G5NhszPQ.woff2) format(‘woff2′);
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215;
}
</style>

<dom-module id=’ha-panel-alarm’>
<template>
<style include=”ha-style iron-flex iron-flex-factors”>
:host {
–countdown-timer-display: none;
–time-display: initial;
}

app-header-layout {
background-color: var(–primary-background-color);
}
app-header {
text-align: -webkit-center;
}
app-toolbar ha-menu-button + [main-title] {
margin-left: -50px;
}
#container ::slotted(.content) {
padding: 15px;
}
.shake{
animation: shake linear 1s;
animation-iteration-count: 1;
transform-origin: 50% 50%;
-webkit-animation: shake linear 1s;
-webkit-animation-iteration-count: 1;
-webkit-transform-origin: 50% 50%;
-moz-animation: shake linear 1s;
-moz-animation-iteration-count: 1;
-moz-transform-origin: 50% 50%;
-o-animation: shake linear 1s;
-o-animation-iteration-count: 1;
-o-transform-origin: 50% 50%;
-ms-animation: shake linear 1s;
-ms-animation-iteration-count: 1;
-ms-transform-origin: 50% 50%;
}

@keyframes shake{
0% {
transform: translate(0px,0px) ;
}
10% {
transform: translate(-10px,0px) ;
}
20% {
transform: translate(10px,0px) ;
}
30% {
transform: translate(-10px,0px) ;
}
40% {
transform: translate(10px,0px) ;
}
50% {
transform: translate(-10px,0px) ;
}
60% {
transform: translate(10px,0px) ;
}
70% {
transform: translate(-10px,0px) ;
}
80% {
transform: translate(10px,0px) ;
}
90% {
transform: translate(-10px,0px) ;
}
100% {
transform: translate(0px,0px) ;
}
}

ha-label-badge {
–ha-label-badge-color: rgb(223, 76, 30);
}
:host ::slotted(.badge-container.ha-label-badge) {
margin: 0px 12px;
}
.actions {
margin-left: 20px;
@apply(–layout-self-center);
}
.box {
padding-top: 10px;
padding-bottom: 10px;
display: flex;
justify-content: center; /* align horizontal */
align-items: center; /* align vertical */
}
.box-outer {
margin: 0 5px 10px 5px;
background-color: var(–paper-card-background-color);
border-radius: 3px;
@apply(–shadow-elevation-4dp);
}
.box-inner {
opacity: var(–dark-primary-opacity);
padding: 15px;
margin-right: 15px;
}
.box-sensors {
width: 100%;
}
.title {
font-size: large;
background-color: var(–primary-color);
text-align: -webkit-center;
font-weight: 400;
color: white;
padding: 15px 0px;
}

.summary {
margin: 0px auto 0px;
text-align: center;
}
/* xs < 768 */
@media screen and (max-width: 767px) {
.sign {
font-size: x-large;
}
#hour, #minute, #middle {
font-size: xx-large;
}
#meridiem {
font-size: 10px;
margin-left: -20px;
margin-right: 3px;
}
#weather-icon img {
width: 40px;
}
.weather {
justify-content: center;
flex-direction: column;
display: flex;
}
}
/* sm */
@media screen and (min-width: 768px) {
.sign {
font-size: xx-large;
}
#hour, #minute, #middle {
font-size: -webkit-xxx-large;
}
#meridiem {
font-size: small;
margin-left: -29px;
}
#weather-icon img {
width: 70px;
}
}

/* md */
@media screen and (min-width: 992px) {
}

/* lg */
@media screen and (min-width: 1200px) {
}
.sign {
margin-left: -20px;
}
.ok {
color: green;
}
.warning {
color: orange;
}
.danger {
color: red;
}
.name .active {
color: red;
}
div.box-header {
width: 33%;
}
div.countdown-timer {
display: var(–countdown-timer-display);
}

div.code {
text-transform: capitalize;
}

div.code iron-label {
font-size: x-large;
}

paper-button {
background: #6b8d9c;
color: #ffffff;
}
div.arm paper-button {
width: 100%;
height: 100px;
text-align: -webkit-center;
}
div.arm paper-button.big {
width: 68%;
}
div.arm paper-button.little {
width: 28%;
}

div.digits paper-button{
width: calc(25%);
height: 77px;
margin-top: 4px;
background: var(–primary-color);
color: var(–primary-background-color);
opacity: 0.9;
}
div.options {
padding-top: 10px;
}
div.options paper-button{
width: 96.5%;
}
.tappable {
cursor: pointer;
margin-top: 2px;
font-size: 1.2em;
}
.time {
color: var( –primary-color);
text-shadow: 1px 1px #6b8d9b;
display: var(–time-display);
}
#middle {
text-align: center;
padding-left: 8px;
margin-right: -5px;
}
#meridiem {
display: inline-block;
vertical-align: middle;
line-height: normal;
height: 100%;
color: #6b8d9b;
}
#weather {
color: var( –primary-color);
}
#weather-icon img {
color: var(–paper-card-background-color);
text-align: -webkit-center;
}
#weather-icon {
height: 100%;
}
.weather-summary span {
display: inline-block;
vertical-align: middle;
line-height: normal;
font-weight: bolder;
margin-left: 5px;
padding-right: 5px;
text-align: -webkit-center;
height: 100%;
font-family: ‘Lobster’;

}
#main-title-bar{
display: flex; /* establish flex container */
flex-direction: row; /* default value; can be omitted */
flex-wrap: nowrap; /* default value; can be omitted */
justify-content: space-between; /* switched from default (flex-start, see below) */
width: 100%;
margin-left: -35px;
}
#main-title-bar div{
}
#main-title-text{
width: 100%;
position: absolute;
margin-left: -25px;
}
#settings-icon{
width: 5%;
}
#settings{
height: calc(100vh – 64px);
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
width: 100%;
text-align: center;
}
#settings-title {
width: 50%;
line-height: calc(100vh – 64px);
color: var(–primary-color);
font-size: 70px;
text-shadow: 3px 3px #6b8d9b;
}
#settings-menu {
width: 50%;
background-color: var(–primary-color);
}
.onoffswitch {
position: relative; width: 90px;
margin-bottom: 25px;
-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
}
.onoffswitch-checkbox {
display: none;
}
.onoffswitch-label {
display: block; overflow: hidden; cursor: pointer;
border: 2px solid #999999; border-radius: 20px;
}
.onoffswitch-inner {
display: block; width: 200%; margin-left: -100%;
transition: margin 0.3s ease-in 0s;
}
.onoffswitch-inner:before, .onoffswitch-inner:after {
display: block; float: left; width: 50%; height: 30px; padding: 0; line-height: 30px;
font-size: 14px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
box-sizing: border-box;
}
.onoffswitch-inner:before {
content: “ON”;
padding-left: 10px;
background-color: var(–primary-color); color: #FFFFFF;
text-align: left;
}
.onoffswitch-inner:after {
content: “OFF”;
padding-right: 10px;
background-color: #EEEEEE; color: #999999;
text-align: right;
}
.onoffswitch-switch {
display: block; width: 22px; margin: 4px;
background: #FFFFFF;
position: absolute; top: 0; bottom: 0;
right: 56px;
border: 2px solid #999999; border-radius: 20px;
transition: all 0.3s ease-in 0s;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
margin-left: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
right: 0px;
}
.blur {
-webkit-filter: blur(10px);
filter: blur(10px);
}
button {
background-color: #03a9f4;
border-width: 0;
color: white;
}
.hide {

}
.hide-bar {
margin-top: -64px;
height: calc(100vh + 64px);
}
#eyecandy {
height: 100vh;
background: linear-gradient(270deg, #050505, #15305d, #4f5258);
background-size: 400% 400%;
-webkit-animation: backgroundAnimation 30s ease infinite;
-moz-animation: backgroundAnimation 30s ease infinite;
animation: backgroundAnimation 30s ease infinite;
}
@-webkit-keyframes backgroundAnimation {
0%{background-position:0% 50%}
50%{background-position:100% 50%}
100%{background-position:0% 50%}
}
@-moz-keyframes backgroundAnimation {
0%{background-position:0% 50%}
50%{background-position:100% 50%}
100%{background-position:0% 50%}
}
@keyframes backgroundAnimation {
0%{background-position:0% 50%}
50%{background-position:100% 50%}
100%{background-position:0% 50%}
}
.ecTitleDiv {

padding-top: 20vh;
}

.ecTitle {
display: block;
margin-bottom: 10px;
font-family: “Avant Garde”, Avantgarde, “Century Gothic”, CenturyGothic, “AppleGothic”, sans-serif;
font-size: -webkit-xxx-large;
text-align: center;
text-transform: uppercase;
text-rendering: optimizeLegibility;
color: #e0dfdc;
letter-spacing: .1em;
text-shadow: 0 -1px 0 #fff, 0 1px 0 #2e2e2e, 0 2px 0 #2c2c2c, 0 3px 0 #2a2a2a, 0 4px 0 #282828, 0 5px 0 #262626, 0 6px 0 #242424, 0 7px 0 #222, 0 8px 0 #202020, 0 9px 0 #1e1e1e, 0 10px 0 #1c1c1c, 0 11px 0 #1a1a1a, 0 12px 0 #181818, 0 13px 0 #161616, 0 14px 0 #141414, 0 15px 0 #121212, 0 22px 30px rgba(0, 0, 0, 0.9);

}
.ecAlarm {
display: block;
margin-bottom: 10px;
padding-top: 44vh;
font-family: “Avant Garde”, Avantgarde, “Century Gothic”, CenturyGothic, “AppleGothic”, sans-serif;
font-size: xx-large;
text-align: center;
text-transform: uppercase;
text-rendering: optimizeLegibility;
color: #e0dfdc;
letter-spacing: .1em;
}

.remove {
display: none !important;
}

#tri {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, 100%);
width: 50px;
height: 50px;
}
#tri .side {
width: 50px;
height: 50px;
position: absolute;
transform-origin: center center;
}
#tri .side:nth-child(1) {
transform: translateX(-100px);
}
#tri .side:nth-child(2) {
transform: translateX(0px);
}
#tri .side:nth-child(3) {
transform: translateX(100px);
}
#tri .side:nth-child(2) {
transform: translateY(-150px);
}
#tri .ring {
width: 50px;
height: 50px;
position: absolute;
background-color: black;
animation-name: rotate, cromatic;
animation-duration: 3s, 3s;
animation-timing-function: linear, linear;
animation-iteration-count: infinite, infinite;
opacity: 0.5;
box-shadow: 0 0 30px black;
border-radius: 20%;
}

.side:nth-child(1) .ring:nth-child(0) {
margin: 0 0 0 0px;
z-index: 0;
animation-delay: 0s;
}

.side:nth-child(1) .ring:nth-child(1) {
margin: 0 0 0 10px;
z-index: 1;
animation-delay: 0.05s;
}

.side:nth-child(1) .ring:nth-child(2) {
margin: 0 0 0 20px;
z-index: 2;
animation-delay: 0.1s;
}

.side:nth-child(1) .ring:nth-child(3) {
margin: 0 0 0 30px;
z-index: 3;
animation-delay: 0.15s;
}

.side:nth-child(1) .ring:nth-child(4) {
margin: 0 0 0 40px;
z-index: 4;
animation-delay: 0.2s;
}

.side:nth-child(1) .ring:nth-child(5) {
margin: 0 0 0 50px;
z-index: 5;
animation-delay: 0.25s;
}

.side:nth-child(1) .ring:nth-child(6) {
margin: 0 0 0 60px;
z-index: 6;
animation-delay: 0.3s;
}

.side:nth-child(1) .ring:nth-child(7) {
margin: 0 0 0 70px;
z-index: 7;
animation-delay: 0.35s;
}

.side:nth-child(1) .ring:nth-child(8) {
margin: 0 0 0 80px;
z-index: 8;
animation-delay: 0.4s;
}

.side:nth-child(1) .ring:nth-child(9) {
margin: 0 0 0 90px;
z-index: 9;
animation-delay: 0.45s;
}

.side:nth-child(1) .ring:nth-child(10) {
margin: 0 0 0 100px;
z-index: 10;
animation-delay: 0.5s;
}

.side:nth-child(2) .ring:nth-child(0) {
margin: 0px 0 0 0px;
z-index: 20;
animation-delay: 2s;
}

.side:nth-child(2) .ring:nth-child(1) {
margin: 7.5px 0 0 -5px;
z-index: 19;
animation-delay: 2.05s;
}

.side:nth-child(2) .ring:nth-child(2) {
margin: 15px 0 0 -10px;
z-index: 18;
animation-delay: 2.1s;
}

.side:nth-child(2) .ring:nth-child(3) {
margin: 22.5px 0 0 -15px;
z-index: 17;
animation-delay: 2.15s;
}

.side:nth-child(2) .ring:nth-child(4) {
margin: 30px 0 0 -20px;
z-index: 16;
animation-delay: 2.2s;
}

.side:nth-child(2) .ring:nth-child(5) {
margin: 37.5px 0 0 -25px;
z-index: 15;
animation-delay: 2.25s;
}

.side:nth-child(2) .ring:nth-child(6) {
margin: 45px 0 0 -30px;
z-index: 14;
animation-delay: 2.3s;
}

.side:nth-child(2) .ring:nth-child(7) {
margin: 52.5px 0 0 -35px;
z-index: 13;
animation-delay: 2.35s;
}

.side:nth-child(2) .ring:nth-child(8) {
margin: 60px 0 0 -40px;
z-index: 12;
animation-delay: 2.4s;
}

.side:nth-child(2) .ring:nth-child(9) {
margin: 67.5px 0 0 -45px;
z-index: 11;
animation-delay: 2.45s;
}

.side:nth-child(2) .ring:nth-child(10) {
margin: 75px 0 0 -50px;
z-index: 10;
animation-delay: 2.5s;
}

.side:nth-child(3) .ring:nth-child(0) {
margin: 0px 0 0 0px;
z-index: 0;
animation-delay: 1s;
}

.side:nth-child(3) .ring:nth-child(1) {
margin: -7.5px 0 0 -5px;
z-index: 1;
animation-delay: 1.05s;
}

.side:nth-child(3) .ring:nth-child(2) {
margin: -15px 0 0 -10px;
z-index: 2;
animation-delay: 1.1s;
}

.side:nth-child(3) .ring:nth-child(3) {
margin: -22.5px 0 0 -15px;
z-index: 3;
animation-delay: 1.15s;
}

.side:nth-child(3) .ring:nth-child(4) {
margin: -30px 0 0 -20px;
z-index: 4;
animation-delay: 1.2s;
}

.side:nth-child(3) .ring:nth-child(5) {
margin: -37.5px 0 0 -25px;
z-index: 5;
animation-delay: 1.25s;
}

.side:nth-child(3) .ring:nth-child(6) {
margin: -45px 0 0 -30px;
z-index: 6;
animation-delay: 1.3s;
}

.side:nth-child(3) .ring:nth-child(7) {
margin: -52.5px 0 0 -35px;
z-index: 7;
animation-delay: 1.35s;
}

.side:nth-child(3) .ring:nth-child(8) {
margin: -60px 0 0 -40px;
z-index: 8;
animation-delay: 1.4s;
}

.side:nth-child(3) .ring:nth-child(9) {
margin: -67.5px 0 0 -45px;
z-index: 9;
animation-delay: 1.45s;
}

.side:nth-child(3) .ring:nth-child(10) {
margin: -75px 0 0 -50px;
z-index: 10;
animation-delay: 1.5s;
}

@keyframes rotate1 {
to {
transform: rotate(360deg);
}
}
@keyframes cromatic {
0% {
background-color: #870000;
}
30% {
background-color: #290f6b;
}
60% {
background-color: #290f6b;
}
100% {
background-color: #870000;
}
}
.view {
position: relative;
top: 160px;
left: 0;
right: 0;
bottom: 0;
-webkit-perspective: 400;
perspective: 400;
}
.plane {
width: 120px;
height: 120px;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
}
.plane.main {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
-webkit-transform: rotateX(60deg) rotateZ(-30deg);
transform: rotateX(60deg) rotateZ(-30deg);
-webkit-animation: rotate 20s infinite linear;
animation: rotate 20s infinite linear;
}
.plane.main .circle {
width: 120px;
height: 120px;
position: absolute;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
border-radius: 100%;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-shadow: 0 0 60px white, inset 0 0 60px white;
box-shadow: 0 0 60px white, inset 0 0 60px white;
}
.plane.main .circle:nth-child(1) {
-webkit-transform: rotateZ(72deg) rotateX(63.435deg);
transform: rotateZ(72deg) rotateX(63.435deg);
}
.plane.main .circle:nth-child(2) {
-webkit-transform: rotateZ(144deg) rotateX(63.435deg);
transform: rotateZ(144deg) rotateX(63.435deg);
}
.plane.main .circle:nth-child(3) {
-webkit-transform: rotateZ(216deg) rotateX(63.435deg);
transform: rotateZ(216deg) rotateX(63.435deg);
}
.plane.main .circle:nth-child(4) {
-webkit-transform: rotateZ(288deg) rotateX(63.435deg);
transform: rotateZ(288deg) rotateX(63.435deg);
}
.plane.main .circle:nth-child(5) {
-webkit-transform: rotateZ(360deg) rotateX(63.435deg);
transform: rotateZ(360deg) rotateX(63.435deg);
}

@-webkit-keyframes rotate {
0% {
-webkit-transform: rotateX(0) rotateY(0) rotateZ(0);
transform: rotateX(0) rotateY(0) rotateZ(0);
}
100% {
-webkit-transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg);
transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg);
}
}

@keyframes rotate {
0% {
-webkit-transform: rotateX(0) rotateY(0) rotateZ(0);
transform: rotateX(0) rotateY(0) rotateZ(0);
}
100% {
-webkit-transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg);
transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg);
}
}
</style>
<app-header-layout has-scrolling-region>
<app-header slot=”header” fixed>
<app-toolbar>
<ha-menu-button narrow='[[narrow]]’ show-menu='[[showMenu]]’></ha-menu-button>
<div id=”main-title-bar”>
<div></div>
<div main-title id=”main-title-text”><iron-icon icon=’mdi:security-home’></iron-icon>&nbsp;&nbsp;Alarma de Casa</div>
<div id=”settings-icon” style=”display: none;”><button on-tap=’toggleSettings’><iron-icon icon=’mdi:settings’></iron-icon></button></div>

</div>
</app-toolbar>
</app-header>

<div class=’content’>
<div id=’alarm-panel’>
<div class=’horizontal layout center-justified’>
<div class=’box zone-card box-header’>
<ha-label-badge icon='[[computeIcon(alarm.state)]]’ label='[[computeLabel(alarm.state)]]’ description='[[alarm.entityDisplay]]’></ha-label-badge>
</div>

<div class=’box zone-card box-header’ >
<template is=’dom-if’ if='[[alarm.attributes.clock]]’>
<div id=’time’ class=’time’>
<span id=”hour”>[[hours]]</span>
<span id=”middle”>:
<span id=”meridiem”>[[meridiem]]</span>
</span>
<span id=”minute”>[[minutes]]</span>
</div>
</template>
<div id=’countdown’ class=’countdown-timer’></div>
</div>

<div class=’box zone-card box-header weather’>
<template is=’dom-if’ if='[[alarm.attributes.weather]]’>
<div id=’weather-icon’ class=’weather-summary’>
<span>[[temp]] &#176;C</span>
<img src=[[weather.attributes.entity_picture]]></img>
</div>
<div class=’weather-summary’>
<span>[[weather.state]]</span>
</div>
</template>
</div>

</div>
<template is=’dom-if’ if='[[isdisarmed(alarm)]]’>
<div class=’statecard’>
<div class=’title arm’>
<iron-label><iron-icon icon=’mdi:key’></iron-icon>&nbsp;&nbsp;Alarma Desactivada</iron-label>
</div>

<div class=’box arm zone-card’>
<template is=’dom-if’ if='[[alarm.attributes.perimeter_mode]]’>
<paper-button id=’arm-perimeter’ on-tap=’callService’ data-call=’alarm_arm_night’ raised><iron-icon icon=’mdi:lock-outline’></iron-icon>&nbsp;Perimeter Mode</paper-button>
</template>
<paper-button id=’arm-home’ on-tap=’callService’ data-call=’alarm_arm_home’ raised><iron-icon icon=’mdi:lock-outline’></iron-icon>&nbsp;Modo Noche</paper-button>
<paper-button id=’arm-away’ on-tap=’callService’ data-call=’alarm_arm_away’ raised><iron-icon icon=’mdi:lock’></iron-icon>&nbsp;Modo Fuera de Casa</paper-button>
</div>
</div>
</template>

<template is=’dom-if’ if='[[!isdisarmed(alarm)]]’>
<div class=’statecard’>
<div class=’box-outer’>
<div class=’title code’>
<iron-label>[[computeLabel(alarm.state)]] Mode Activated&nbsp;<iron-icon icon=’mdi:key’></iron-icon>&nbsp;&nbsp;[[code]]</iron-label>
</div>
<div class=’horizontal layout center-justified’>
<div style=’width:100%’>
<div class=’digits horizontal layout center-justified’>
<paper-button on-tap=’callcode’ data-digit=1 raised>1</paper-button>
<paper-button on-tap=’callcode’ data-digit=2 raised>2</paper-button>
<paper-button on-tap=’callcode’ data-digit=3 raised>3</paper-button>
<paper-button class=’disarm’ on-tap=’callService’ data-call=’alarm_disarm’ raised>Desarmar</paper-button>
</div>
<div class=’digits horizontal layout center-justified’>
<paper-button on-tap=’callcode’ data-digit=4 raised>4</paper-button>
<paper-button on-tap=’callcode’ data-digit=5 raised>5</paper-button>
<paper-button on-tap=’callcode’ data-digit=6 raised>6</paper-button>
<paper-button class=’clear’ on-tap=’callclearcode’ raised>Borrar</paper-button>
</div>
<div class=’digits horizontal layout center-justified’>
<paper-button on-tap=’callcode’ data-digit=7 raised>7</paper-button>
<paper-button on-tap=’callcode’ data-digit=8 raised>8</paper-button>
<paper-button on-tap=’callcode’ data-digit=9 raised>9</paper-button>
<paper-button on-tap=’callcode’ data-digit=0 raised>0</paper-button>
</div>
</div>
</div>
</div>
</div>
</template>

<div class=’statecard’>

<template is=’dom-if’ if='[[opencount]]’>
<div class=’box-outer’ style=’max-width: 100%;’ >
<div class=’title’ style=’ background-color: orange;’ >[[opencount]]x Open Sensors</div>
<div class=’box-inner’>
<template is=’dom-repeat’ items='[[opensensors(allsensors)]]’ as=’entity’>
<state-card-display state-obj=”[[entity]]” hass='[[hass]]’></state-card-display>
</template>
</div>
</div>
</template>

<div class=’horizontal layout center-justified’>

<template is=’dom-if’ if='[[isperimeter(alarm)]]’>
<div class=’box-outer box-sensors’>
<div class=’title’>Sensores Inmediatos</div>
<div class=’box-inner’>
<template is=’dom-repeat’ items='[[perimeter]]’ as=’entity’>
<state-card-display state-obj=”[[entity]]” hass='[[hass]]’></state-card-display>
</template>
</div>
</div>

</template>

<template is=’dom-if’ if='[[!isperimeter(alarm)]]’>
<template is=’dom-if’ if='[[!isdisarmed(alarm)]]’>
<div class=’box-outer box-sensors’>
<div class=’title’>Sensores Retrasados</div>
<div class=’box-inner’>
<template is=’dom-repeat’ items='[[delayed]]’ as=’entity’>
<state-card-display state-obj=”[[entity]]” hass='[[hass]]’></state-card-display>
</template>
</div>
</div>

<div class=’box-outer box-sensors’>
<div class=’title’>Sensores Inmediatos</div>
<div class=’box-inner’>
<template is=’dom-repeat’ items='[[immediate]]’ as=’entity’>
<state-card-display state-obj=”[[entity]]” hass='[[hass]]’></state-card-display>
</template>
</div>
</div>
</template>
</template>

</div>

<div class=’box-outer’ style=’max-width: 100%;’ >
<template is=’dom-if’ if='[[isdisarmed(alarm)]]’>
<div class=’title’>Todos los Sensores</div>
</template>
<template is=’dom-if’ if='[[!isdisarmed(alarm)]]’>
<div class=’title’>Sensores Inactivos</div>
</template>
<div class=’box-inner’>

<template is=’dom-if’ if='[[isdisarmed(alarm)]]’>
<template is=’dom-repeat’ items='[[opensensors(allsensors)]]’ as=’entity’>
<state-card-display state-obj=”[[entity]]” hass='[[hass]]’></state-card-display>
</template>
<template is=’dom-repeat’ items='[[closedsensors(allsensors)]]’ as=’entity’>
<state-card-display state-obj=”[[entity]]” hass='[[hass]]’></state-card-display>
</template>
</template>

<template is=’dom-if’ if='[[!isdisarmed(alarm)]]’>
<template is=’dom-repeat’ items='[[ignored]]’ as=’entity’>
<state-card-display state-obj=”[[entity]]” hass='[[hass]]’></state-card-display>
</template>
</template>
</div>
</div>
</div>
</div>
<div id=”settings” class=’remove’>
<div id=”settings-title”>Settings</div>
<div id=”settings-menu”>
<ul class=”settings”>
<div class=”onoffswitch”>
<input type=”checkbox” name=”onoffswitch” class=”onoffswitch-checkbox” id=”screensaverSwitch” checked>
<label class=”onoffswitch-label” for=”screensaverSwitch”>
<span class=”onoffswitch-inner”></span>
<span class=”onoffswitch-switch”></span>
</label>
</div>
<div class=”onoffswitch”>
<input type=”checkbox” name=”onoffswitch” class=”onoffswitch-checkbox” id=”otherSwitch” checked>
<label class=”onoffswitch-label” for=”otherSwitch”>
<span class=”onoffswitch-inner”></span>
<span class=”onoffswitch-switch”></span>
</label>
</div>

</div>
</div>
<div id=’eyecandy’ class=’remove’>
<div class=”ecTitleDiv”>
<span class=”ecTitle”>Calvert</span><br /><span class=”ecTitle”>Residence</span>
</div>
<div class=”view”>
<div class=”plane main”>
<div class=”circle”></div>
<div class=”circle”></div>
<div class=”circle”></div>
<div class=”circle”></div>
<div class=”circle”></div>
<div class=”circle”></div>
</div>
</div>
<div class=”ecAlarm”>
<span>Disarmed</span>
</div>

</div>
</div>

</app-header-layout>
</template>
</dom-module>

<script>
Polymer({
is: ‘ha-panel-alarm’,
attached() {
this.onAttached();
},
ready() {
//if (this.screensaver){
// this.setupEyeCandy();
// }
},
properties: {
// things that appear in all the other panels
hass: { type: Object },
panel: { type: Object },
narrow: { type: Boolean, value: false },
showMenu: { type: Boolean, value: false },
// things specific to this alarm panel
alarm: { type: Object, observer: ‘changeTheme’ },
immediate: { type: Array, computed: ‘computeSensors(hass, alarm.attributes.immediate)’ },
delayed: { type: Array, computed: ‘computeSensors(hass, alarm.attributes.delayed)’ },
ignored: { type: Array, computed: ‘computeSensors(hass, alarm.attributes.ignored)’ },
perimeter: { type: Array, computed: ‘computeSensors(hass, alarm.attributes.perimeter)’ },
allsensors: { type: Array, computed: ‘computeSensors(hass, alarm.attributes.allsensors)’ },
overrideSensors: { type: Array, computed: ‘computeSensors(hass, alarm.attributes.override)’ },
weather: { type: String },
temp: { type: String },
code: { type: String, value: ” },
opencount: { type: Number, value: 0 },
timeoutID: { type: Number },
hours: { type: String, value: ‘??’ },
minutes: { type: String, value: ‘??’ },
meridiem: { type: String, value: ‘?M’ },
// Hold our unobservers
cleanup: { type: Array, value: [] },
attemptedArm: { type: Boolean, value: false },
screensaver: { type: Boolean, value: false },
settings: { type: Boolean, value: false },
},

changeTheme: function() {
// console.log(this.alarm.attributes.warning_colour);
this.updateStyles({‘–countdown-timer-display’: ‘none’});
this.updateStyles({‘–time-display’: ‘initial’});
if (this.alarm.state == ‘disarmed’) {
if (this.attemptedArm == true) { this.resetButtons(); }
this.updateStyles({‘–primary-color’: this.alarm.attributes.disarmed_colour});
} else if (this.alarm.state == ‘armed_away’) {
this.updateStyles({‘–primary-color’: this.alarm.attributes.armed_away_colour});
} else if ( this.alarm.state == ‘armed_home’) {
this.updateStyles({‘–primary-color’: this.alarm.attributes.armed_home_colour});
} else if ( this.alarm.state == ‘armed_perimeter’) {
this.updateStyles({‘–primary-color’: this.alarm.attributes.armed_home_colour}); //SORT THIS OUT
} else if (this.alarm.state == ‘pending’) {
this.updateStyles({‘–primary-color’: this.alarm.attributes.pending_colour});
this.updateStyles({‘–countdown-timer-display’: ‘initial’});
this.updateStyles({‘–time-display’: ‘none’});
this.loadTimer();
} else if (this.alarm.state == ‘warning’) {
this.updateStyles({‘–primary-color’: this.alarm.attributes.warning_colour});
this.loadTimer();
} else if (this.alarm.state == ‘triggered’) {
this.updateStyles({‘–primary-color’: this.alarm.attributes.triggered_colour});
}
},
loadTimer: function() {
var countdownDiv = Polymer.dom(this.root).querySelector(‘#countdown’);
var countdown = $(countdownDiv).countdown360({
radius : 50,
seconds : this.alarm.attributes.countdown_time,
fontColor : ‘#FFFFFF’,
autostart : false,
label : false,
smooth : true
//onComplete : function () { console.log(‘done’) }
});
countdown.stop();
countdown.start();
},
onAttached: function() {
if (this.alarm.attributes.clock) {
this.updateTime();
}
if (this.alarm.attributes.weather) {
this.getWeather();
}
},
//————————————————————–//
//—————–EYE CANDY FUNCTION—————————//
//————————————————————–//
setupEyeCandy: function () {
this.addEventListener(“mousemove”, this.resetTimer);
this.addEventListener(“mousedown”, this.resetTimer);
this.addEventListener(“keypress”, this.resetTimer);
this.addEventListener(“DOMMouseScroll”, this.resetTimer);
this.addEventListener(“mousewheel”, this.resetTimer);
this.addEventListener(“touchmove”, this.resetTimer);
this.addEventListener(“MSPointerMove”, this.resetTimer);

this.startTimer();
},
startTimer: function() {
this.timeoutID = this.async(this.goInactive,8000);
},
resetTimer: function () {
clearTimeout(this.timeoutID);
this.goActive();
},
goActive: function() {
var alarmContent = Polymer.dom(this.root).querySelector(‘#alarm-panel’);
alarmContent.classList.remove(‘remove’);
var eyecandyContent = Polymer.dom(this.root).querySelector(‘#eyecandy’);
eyecandyContent.classList.add(‘remove’);
var appHeader = Polymer.dom(this.root).querySelector(‘app-header-layout’);
appHeader.classList.remove(‘hide-bar’);
var settingsContent = Polymer.dom(this.root).querySelector(‘#settings’);
settingsContent.classList.add(‘remove’);

this.startTimer();
},
goInactive: function () {
var alarmContent = Polymer.dom(this.root).querySelector(‘#alarm-panel’);
alarmContent.classList.add(‘remove’);
var eyecandyContent = Polymer.dom(this.root).querySelector(‘#eyecandy’);
eyecandyContent.classList.remove(‘remove’);
var appHeader = Polymer.dom(this.root).querySelector(‘app-header-layout’);
appHeader.classList.add(‘hide-bar’);
var settingsContent = Polymer.dom(this.root).querySelector(‘#settings’);
settingsContent.classList.remove(‘remove’);
},
//————————————————————–//
//—————–EYE CANDY FUNCTION—————————//
//————————————————————–//
getWeather: function() {
this.weather = this.hass.states[‘sensor.clima_actual’];
this.temp = Math.ceil((this.hass.states[‘sensor.yr_temperature’]).state);
},
toggleSettings() {
var settingsContent = Polymer.dom(this.root).querySelector(‘#settings’);
var alarmContent = Polymer.dom(this.root).querySelector(‘#alarm-panel’);
if (this.settings) {
alarmContent.classList.remove(‘remove’);
settingsContent.classList.add(‘remove’);
this.settings = false;
} else {
alarmContent.classList.add(‘remove’);
settingsContent.classList.remove(‘remove’);
this.settings = true;
}
},
updateTime: function() {
var timeNow = new Date();
var hours = timeNow.getHours();
var minutes = timeNow.getMinutes();
var meridiem = (hours >= 12) ? ” P.M” : ” A.M”;
this.hours = (“0” + hours).slice(-2);
this.minutes = (“0” + minutes).slice(-2);
this.meridiem = meridiem;
this.async(this.updateTime,1000);
},
// Polymer observers definition
observers: [ ‘onPanelUpdate(hass, panel)’ ],
wtf: function (e) { debugger; return e; },
// Helpers to figure out what to display
isdisarmed: function(alarm) { return alarm.state == “disarmed”; },
isperimeter: function(alarm) { return alarm.state == “armed_perimeter”; },
issecure: function(all) { return this.opensensors(all).length == 0; },
opensensors: function(all) {
if (all == false) {
return false;
} else {
ret = all.filter(function (e) { return [‘on’, ‘open’, ‘true’, ‘detected’, ‘unlocked’].indexOf(e.state.toLowerCase()) > -1; });
this.opencount = ret.length;
return ret;
}
},
closedsensors: function(all) {
if (all == false) {
return false;
} else {
ret = all.filter(function (e) { return [‘off’, ‘closed’, ‘false’, ‘undetected’, ‘locked’].indexOf(e.state.toLowerCase()) > -1; });
return ret;
}
},
computeSensors: function(hass, ids) {
if (ids == undefined) {
// Control the exception when this.alarm is not ready
return false;
} else {
return ids.map(function (key) { return hass.states[key]; }).filter(function (e) { return e != undefined; });
}
},
computeIcon: function (state) {
switch (state) {
case ‘disarmed’: return ‘mdi:shield-outline’;
case ‘armed_away’: return ‘mdi:security-home’;
case ‘armed_home’: return ‘mdi:security-home’;
case ‘pending’: return ‘mdi:walk’;
case ‘warning’: return ‘mdi:run’;
case ‘triggered’: return ‘mdi:alert-circle’;
case ‘armed_perimeter’: return ‘mdi:security-home’;
}
return ‘mdi:help’;
},
computeLabel: function (state) {
switch (state) {
case ‘disarmed’: return ‘off’;
case ‘armed_away’: return ‘away’;
case ‘armed_home’: return ‘home’;
case ‘pending’: return ‘leave’;
case ‘warning’: return ‘warn’;
case ‘triggered’: return ‘alarm’;
case ‘armed_perimeter’: return ‘perimeter’;
}
return state;
},
// Responding to user inputs
callcode: function(ev) {
ev.stopPropagation();
var digit = ev.target.getAttribute(‘data-digit’);
this.code = this.code + digit;
//console.log(this.code);
},
callclearcode: function(ev) {
ev.stopPropagation();
this.code = ”;
//console.log(‘Code Cleared: ‘ + this.code);
},
callService: function(ev) {
ev.stopPropagation();
var call = ev.target.getAttribute(‘data-call’);
if (call == ‘cancel’) { //cancel alarm set and return page to default settings
this.resetButtons();
} else if (ev.target.getAttribute(‘data-override’)) { //Override of open sensors has been pressed so activate alarm
this.hass.callService(‘alarm_control_panel’, call, {‘entity_id’: this.alarm.entityId, ‘code’: this.code})
} else if ((call == ‘alarm_arm_home’ || call == ‘alarm_arm_away’ || call == ‘alarm_arm_night’) && this.checkOpenSensors(call)) { //Check if trying to activate home with open sensors. If so ping the warning message
console.log(‘WARNING open sensors’);
} else { //No open sensors or another service call
this.hass.callService(‘alarm_control_panel’, call, {‘entity_id’: this.alarm.entityId, ‘code’: this.code});
this.code = ”;
}
},
checkOpenSensors: function(call) {
if (this.opencount == 0) return false; //check to see how many open sensors there are, if none arm alarm

for (var sensor in this.allsensors) { //check to see if the open sensor is one to ignore, if so arm alarm
//is it open?
if ([‘on’, ‘open’, ‘true’, ‘detected’, ‘unlocked’].indexOf(this.allsensors[sensor].state.toLowerCase()) > -1) { //yes
//is it in the override list?
if (this.overrideSensors.indexOf(this.allsensors[sensor]) == -1) { //No: return so display message
this.attemptedArm = true; //display the warning message and shake

var btnArmHome = Polymer.dom(this.root).querySelector(‘#arm-home’);
var btnArmAway = Polymer.dom(this.root).querySelector(‘#arm-away’);

if (this.alarm.attributes.perimeter_mode) {
var btnArmPerimeter = Polymer.dom(this.root).querySelector(‘#arm-perimeter’);
btnArmPerimeter.classList.add(‘remove’);
}
btnArmHome.classList.add(‘big’);
btnArmAway.classList.add(‘little’);

this.override = document.createAttribute(“data-override”);
this.override.value = “true”;
btnArmHome.setAttributeNode(this.override);

var arm_mode = document.createAttribute(“data-call”);
arm_mode.value = call;
btnArmHome.setAttributeNode(arm_mode);

var arm_mode2 = document.createAttribute(“data-call”);
arm_mode2.value = ‘cancel’;
btnArmAway.setAttributeNode(arm_mode2);
btnArmHome.innerHTML = ‘Override Sensors and Arm Alarm’;
btnArmAway.innerHTML = ‘Cancel Arm’;

var pageContent = Polymer.dom(this.root).querySelector(‘.content’);
pageContent.classList.add(‘shake’);

return true;

}
}
}
},
resetButtons: function() {
this.attemptedArm = false;

var pageContent = Polymer.dom(this.root).querySelector(‘.content’);
pageContent.classList.remove(‘shake’);

var btnArmHome = Polymer.dom(this.root).querySelector(‘#arm-home’);
var btnArmAway = Polymer.dom(this.root).querySelector(‘#arm-away’);
if (this.alarm.attributes.perimeter_mode) {
var btnArmPerimeter = Polymer.dom(this.root).querySelector(‘#arm-perimeter’);
btnArmPerimeter.classList.remove(‘remove’);
}
btnArmHome.classList.remove(‘big’);
btnArmAway.classList.remove(‘little’);

//var override = document.createAttribute(“data-override”);
btnArmHome.removeAttributeNode(this.override);

var arm_mode = document.createAttribute(“data-call”);
arm_mode.value = ‘alarm_arm_home’;
btnArmHome.setAttributeNode(arm_mode);

var arm_mode2 = document.createAttribute(“data-call”);
arm_mode2.value = ‘alarm_arm_away’;
btnArmAway.setAttributeNode(arm_mode2);

btnArmHome.innerHTML = “<iron-icon icon=’mdi:lock-outline’></iron-icon>&nbsp;Home Mode”;
btnArmAway.innerHTML = “<iron-icon icon=’mdi:lock’></iron-icon>&nbsp;Away Mode”;

},
entityTapped: function (ev) {
ev.stopPropagation();
var entityId = ev.target.getAttribute(‘data-entity’);
this.fire(‘hass-more-info’, { entityId: entityId });
},
// Observer: polymer gaurantees that this won’t be called util hass and panel are both defined
onPanelUpdate: function(hass, panel) {
//console.log(‘onPanelUpdate’);
this.alarm = hass.states[panel.config.alarmid];
//console.log(‘Alarm is ‘, this.alarm);
},
});
</script>

Mostrar Código

Y guardamos.

 

Volvemos un directorio atrás

cd ../

y entramos en el directorio custom_components (si no existe lo creas)

cd custom_components

crear el directorio alarm_control_panel

mkdir alarm_control_panel

y crear el fichero bwalarm.py

nano bwalarm.py

“””
My take on the manual alarm control panel
“””
import asyncio
import datetime
import logging
import enum
import re
import voluptuous as vol
from operator import attrgetter

from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME,
CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER,
EVENT_STATE_CHANGED, EVENT_TIME_CHANGED,
STATE_ON)
from homeassistant.util.dt import utcnow as now
from homeassistant.helpers.event import async_track_point_in_time
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.components.switch as switch
import homeassistant.helpers.config_validation as cv

STATE_TRUE = ‘true’
STATE_UNLOCKED = ‘unlocked’
STATE_OPEN = ‘open’
STATE_DETECTED = ‘detected’

CONF_PANIC_CODE = ‘panic_code’
CONF_IMMEDIATE = ‘immediate’
CONF_DELAYED = ‘delayed’
CONF_IGNORE = ‘homemodeignore’
CONF_NOTATHOME = ‘notathome’
CONF_OVERRIDE = ‘override’
CONF_PERIMETER_MODE = ‘perimeter_mode’
CONF_PERIMETER = ‘perimeter’
CONF_ALARM = ‘alarm’
CONF_WARNING = ‘warning’

CONF_WARNING_COLOUR = ‘warning_colour’
CONF_PENDING_COLOUR = ‘pending_colour’
CONF_DISARMED_COLOUR = ‘disarmed_colour’
CONF_TRIGGERED_COLOUR = ‘triggered_colour’
CONF_ARMED_AWAY_COLOUR = ‘armed_away_colour’
CONF_ARMED_HOME_COLOUR = ‘armed_home_colour’

CONF_CLOCK = ‘clock’
CONF_WEATHER = ‘weather’

# Add a new state for the time after an delayed sensor and an actual alarm
STATE_ALARM_WARNING = ‘warning’
STATE_ALARM_ARMED_PERIMETER = ‘armed_perimeter’
class Events(enum.Enum):
ImmediateTrip = 1
DelayedTrip = 2
ArmHome = 3
ArmAway = 4
Timeout = 5
Disarm = 6
Trigger = 7
ArmPerimeter = 8

PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): ‘bwalarm’,
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_PENDING_TIME): vol.All(vol.Coerce(int), vol.Range(min=0)),
vol.Required(CONF_TRIGGER_TIME): vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Required(CONF_ALARM): cv.entity_id, # switch/group to turn on when alarming
vol.Required(CONF_WARNING): cv.entity_id, # switch/group to turn on when warning
vol.Optional(CONF_CODE): cv.string,
vol.Optional(CONF_PANIC_CODE): cv.string,
vol.Optional(CONF_IMMEDIATE): cv.entity_ids, # things that cause an immediate alarm
vol.Optional(CONF_DELAYED): cv.entity_ids, # things that allow a delay before alarm
vol.Optional(CONF_IGNORE): cv.entity_ids, # things that we ignore when at home
vol.Optional(CONF_NOTATHOME): cv.entity_ids, # things that we ignore when at home BACKWARDS COMPAT
vol.Optional(CONF_OVERRIDE): cv.entity_ids, # sensors that can be ignored if open when trying to set alarm in away mode
vol.Optional(CONF_PERIMETER_MODE): cv.boolean, # Enable perimeter mode?
vol.Optional(CONF_PERIMETER): cv.entity_ids, # things monitored under perimeter mode
vol.Optional(CONF_WARNING_COLOUR): cv.string, # Custom colour of warning display
vol.Optional(CONF_PENDING_COLOUR): cv.string, # Custom colour of pending display
vol.Optional(CONF_DISARMED_COLOUR): cv.string, # Custom colour of disarmed display
vol.Optional(CONF_TRIGGERED_COLOUR): cv.string, # Custom colour of triggered display
vol.Optional(CONF_ARMED_AWAY_COLOUR): cv.string, # Custom colour of armed away display
vol.Optional(CONF_ARMED_HOME_COLOUR): cv.string, # Custom colour of armed home display
vol.Optional(CONF_CLOCK): cv.boolean, # DIsplay clock on panel
vol.Optional(CONF_WEATHER): cv.boolean # DIsplay weather on panel
})

_LOGGER = logging.getLogger(__name__)

@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
alarm = BWAlarm(hass, config)
hass.bus.async_listen(EVENT_STATE_CHANGED, alarm.state_change_listener)
hass.bus.async_listen(EVENT_TIME_CHANGED, alarm.time_change_listener)
async_add_devices([alarm])

class BWAlarm(alarm.AlarmControlPanel):

def __init__(self, hass, config):
“”” Initalize the alarm system “””
self._hass = hass
self._name = config[CONF_NAME]
self._immediate = set(config.get(CONF_IMMEDIATE, []))
self._delayed = set(config.get(CONF_DELAYED, []))
self._ignore = set(config.get(CONF_IGNORE, []) if config.get(CONF_IGNORE, []) != [] else config.get(CONF_NOTATHOME, []))
self._override = set(config.get(CONF_OVERRIDE, []))
self._perimeter_mode = config.get(CONF_PERIMETER_MODE, False)
self._perimeter = set(config.get(CONF_PERIMETER, []))
self._allsensors = self._immediate | self._delayed | self._ignore
#self._allsensors = self._allinputs
self._alarm = config[CONF_ALARM]
self._warning = config[CONF_WARNING]
self._code = config[CONF_CODE] if config[CONF_CODE] else None
self._panic_code = config.get(CONF_PANIC_CODE, None)

self._countdown_time = config[CONF_PENDING_TIME]
self._pending_time = datetime.timedelta(seconds=config[CONF_PENDING_TIME])
self._trigger_time = datetime.timedelta(seconds=config[CONF_TRIGGER_TIME])

self._lasttrigger = “”
self._state = STATE_ALARM_DISARMED
self._returnto = STATE_ALARM_DISARMED
self._timeoutat = None

self._warning_colour = config.get(CONF_WARNING_COLOUR, ‘orange’)
self._pending_colour = config.get(CONF_PENDING_COLOUR, ‘orange’)
self._disarmed_colour = config.get(CONF_DISARMED_COLOUR, ‘#03A9F4’)
self._triggered_colour = config.get(CONF_TRIGGERED_COLOUR, ‘red’)
self._armed_away_colour = config.get(CONF_ARMED_AWAY_COLOUR, ‘black’)
self._armed_home_colour = config.get(CONF_ARMED_HOME_COLOUR, ‘black’)

self._panic_mode = “deactivated”

self._clock = config.get(CONF_CLOCK, False)
self._weather = config.get(CONF_WEATHER, False)

self.clearsignals()

### Alarm properties
@property
def should_poll(self) -> bool: return False
@property
def name(self) -> str: return self._name
@property
def changed_by(self) -> str: return self._lasttrigger
@property
def state(self) -> str: return self._state
@property
def device_state_attributes(self):
return {
‘immediate’: sorted(list(self.immediate)),
‘delayed’: sorted(list(self.delayed)),
‘override’: sorted(list(self._override)),
‘ignored’: sorted(list(self.ignored)),
‘allsensors’: sorted(list(self._allsensors)),
‘perimeter_mode’: self._perimeter_mode,
‘perimeter’: sorted(list(self._perimeter)),
‘changedby’: self.changed_by,
‘warning_colour’: self._warning_colour,
‘pending_colour’: self._pending_colour,
‘disarmed_colour’: self._disarmed_colour,
‘triggered_colour’: self._triggered_colour,
‘armed_home_colour’: self._armed_home_colour,
‘armed_away_colour’: self._armed_away_colour,
‘panic_mode’: self._panic_mode,
‘countdown_time’: self._countdown_time,
‘clock’: self._clock,
‘weather’: self._weather
}

### Actions from the outside world that affect us, turn into enum events for internal processing
def time_change_listener(self, eventignored):
“”” I just treat the time events as a periodic check, its simpler then (re-/un-)registration “””
if self._timeoutat is not None:
if now() > self._timeoutat:
self._timeoutat = None
self.process_event(Events.Timeout)

def state_change_listener(self, event):
“”” Something changed, we only care about things turning on at this point “””
new = event.data.get(‘new_state’, None)
if new is None:
return
if new.state.lower() == STATE_ON or new.state.lower() == STATE_TRUE or new.state.lower() == STATE_UNLOCKED or new.state.lower() == STATE_OPEN or new.state.lower() == STATE_DETECTED:
eid = event.data[‘entity_id’]
if eid in self.immediate:
self._lasttrigger = eid
self.process_event(Events.ImmediateTrip)
elif eid in self.delayed:
self._lasttrigger = eid
self.process_event(Events.DelayedTrip)

@property
def code_format(self):
“””One or more characters.”””
return None if self._code is None else ‘.+’

def alarm_disarm(self, code=None):

#If the provided code matches the panic alarm then deactivate the alarm but set the state of the panic mode to active.
if self._validate_panic_code(code):
self.process_event(Events.Disarm)
self._panic_mode = “ACTIVE”
# Let HA know that something changed
self.schedule_update_ha_state()
return

if not self._validate_code(code, STATE_ALARM_DISARMED):
return
self.process_event(Events.Disarm)

def alarm_arm_home(self, code):
self.process_event(Events.ArmHome)

def alarm_arm_away(self, code=None):
self.process_event(Events.ArmAway)

def alarm_arm_night(self, code=None):
self.process_event(Events.ArmPerimeter)

def alarm_trigger(self, code=None):
self.process_event(Events.Trigger)

### Internal processing
def setsignals(self, alarmMode):
“”” Figure out what to sense and how “””
if alarmMode == Events.ArmHome or alarmMode == Events.ArmAway:
self.immediate = self._immediate.copy()
self.delayed = self._delayed.copy()
if alarmMode == Events.ArmHome:
self.immediate -= self._ignore
self.delayed -= self._ignore
if alarmMode == Events.ArmPerimeter:
self.immediate = self._perimeter.copy()
self.ignored = self._allsensors – (self.immediate | self.delayed)

def clearsignals(self):
“”” Clear all our signals, we aren’t listening anymore “””
self._panic_mode = “deactivated”
self.immediate = set()
self.delayed = set()
self.ignored = self._allsensors.copy()

def process_event(self, event):
old = self._state

# Update state if applicable
if event == Events.Disarm:
self._state = STATE_ALARM_DISARMED
elif event == Events.Trigger:
self._state = STATE_ALARM_TRIGGERED
elif old == STATE_ALARM_DISARMED:
if event == Events.ArmHome: self._state = STATE_ALARM_ARMED_HOME
elif event == Events.ArmAway: self._state = STATE_ALARM_PENDING
elif event == Events.ArmPerimeter: self._state = STATE_ALARM_ARMED_PERIMETER
elif old == STATE_ALARM_PENDING:
if event == Events.Timeout: self._state = STATE_ALARM_ARMED_AWAY
elif old == STATE_ALARM_ARMED_HOME or \
old == STATE_ALARM_ARMED_AWAY or \
old == STATE_ALARM_ARMED_PERIMETER:
if event == Events.ImmediateTrip: self._state = STATE_ALARM_TRIGGERED
elif event == Events.DelayedTrip: self._state = STATE_ALARM_WARNING
elif old == STATE_ALARM_WARNING:
if event == Events.Timeout: self._state = STATE_ALARM_TRIGGERED
elif old == STATE_ALARM_TRIGGERED:
if event == Events.Timeout: self._state = self._returnto

new = self._state
if old != new:
_LOGGER.debug(“Alarm changing from {} to {}”.format(old, new))
# Things to do on entering state
if new == STATE_ALARM_WARNING:
_LOGGER.debug(“Turning on warning”)
switch.turn_on(self._hass, self._warning)
self._timeoutat = now() + self._pending_time
elif new == STATE_ALARM_TRIGGERED:
_LOGGER.debug(“Turning on alarm”)
switch.turn_on(self._hass, self._alarm)
self._timeoutat = now() + self._trigger_time
elif new == STATE_ALARM_PENDING:
_LOGGER.debug(“Pending user leaving house”)
switch.turn_on(self._hass, self._warning)
self._timeoutat = now() + self._pending_time
self._returnto = STATE_ALARM_ARMED_AWAY
self.setsignals(Events.ArmAway)
elif new == STATE_ALARM_ARMED_HOME:
self._returnto = new
self.setsignals(Events.ArmHome)
elif new == STATE_ALARM_ARMED_AWAY:
self._returnto = new
self.setsignals(Events.ArmAway)
elif new == STATE_ALARM_ARMED_PERIMETER:
self._returnto = new
self.setsignals(Events.ArmPerimeter)
elif new == STATE_ALARM_DISARMED:
self._returnto = new
self.clearsignals()

# Things to do on leaving state
if old == STATE_ALARM_WARNING or old == STATE_ALARM_PENDING:
_LOGGER.debug(“Turning off warning”)
switch.turn_off(self._hass, self._warning)
elif old == STATE_ALARM_TRIGGERED:
_LOGGER.debug(“Turning off alarm”)
switch.turn_off(self._hass, self._alarm)

# Let HA know that something changed
self.schedule_update_ha_state()

def _validate_code(self, code, state):
“””Validate given code.”””
check = self._code is None or code == self._code
if not check:
_LOGGER.debug(“Invalid code given for %s”, state)
return check

def _validate_panic_code(self, code):
“””Validate given code.”””
check = code == self._panic_code
if check:
_LOGGER.warning(“[ALARM] PANIC MODE ACTIVATED!!!”)
return check

Mostrar Código

Guardamos y listo, ya tenemos todos los ficheros necesarios, ahora simplemente faltaria crear la automatizacion y subir las voces al Gateway de Xiaomi mediante la APP Mi Home.

Los sonidos personalizados del GW de Xiaomi empiezan por el ID 10004, debereis ir llamandolos en el script de automatizacion segun el orden de vuestro GW.

Aqui os dejo mi Automation de ejemplo.

– id: alarm_armed_away
alias: ‘[Alarm] Away Mode Armed’
trigger:
– platform: state
entity_id: alarm_control_panel.house
to: ‘armed_away’
action:
– service: xiaomi_aqara.play_ringtone
data:
gw_mac: 34:ce:00:88:xx:xx
ringtone_id: 10010 # Alarma Activada.
ringtone_vol: 100
– service: switch.turn_on
entity_id: switch.cerrarentrada
– service: notify.telegram
data:
message: “* Alarma Activada*. Adios :)”
– service: climate.set_temperature
data:
entity_id: climate.aa_fujitsu
temperature: 23
operation_mode: Apagado
– service: input_boolean.turn_on
data:
entity_id: input_boolean.alarma_casa

– id: alarm_armed_home
alias: ‘[Alarm] Home Mode Armed’
trigger:
– platform: state
entity_id: alarm_control_panel.house
to: ‘armed_home’
action:
– service: xiaomi_aqara.play_ringtone
data:
gw_mac: 34:ce:00:88:xx:xx
ringtone_id: 10011 # Modo casa activado. Buenas noches
ringtone_vol: 50
– service: notify.telegram
data:
message: “*Modo noche activado*. Buenas noches.”
– service: input_boolean.turn_on
data:
entity_id: input_boolean.alarma_casa

– id: alarm_arming_away
alias: ‘[Alarm] Away Mode Arming’
trigger:
– platform: state
entity_id: alarm_control_panel.house
to: ‘pending’
action:
– service: xiaomi_aqara.play_ringtone
data:
gw_mac: 34:ce:00:88:xx:xx
ringtone_id: 10012 # Activando alarma, revisa que puertas y ventanas esten cerradas
ringtone_vol: 100
– delay:
seconds: 8
– service: xiaomi_aqara.play_ringtone
data:
gw_mac: 34:ce:00:88:xx:xx
ringtone_id: 10017 # Contador 22 Segundos
ringtone_vol: 100

– id: alarm_disarmed
alias: ‘[Alarm] Disarmed’
trigger:
– platform: state
entity_id: alarm_control_panel.house
to: ‘disarmed’
action:
– service: xiaomi_aqara.play_ringtone
data:
gw_mac: 34:ce:00:88:xx:xx
ringtone_id: 10014 # Alarma desactivada
ringtone_vol: 100
– service: input_boolean.turn_off
data:
entity_id: input_boolean.alarma_casa
– service: notify.telegram
data:
message: “Alarma *desactivada*”
– service: automation.turn_off
entity_id: automation.sirena1
– service: automation.turn_off
entity_id: automation.sirena2
– service: automation.turn_off
entity_id: automation.sirena3

– id: alarm_triggered
alias: ‘[Alarm] Triggered’
trigger:
– platform: state
entity_id: alarm_control_panel.house
to: ‘triggered’
action:
– service: automation.turn_on
entity_id: automation.sirena1
# – service: switch.turn_on
# entity_id: switch.siren_switch
– service: notify.telegram
data:
message: ‘HA SALTADO LA ALARMA!!! {{ states[states.alarm_control_panel.house.attributes.changed_by.split(“.”)[0]][ states.alarm_control_panel.house.attributes.changed_by.split(“.”)[1]].name }}’

– id: alarm_warning
alias: ‘[Alarm] Warning’
trigger:
– platform: state
entity_id: alarm_control_panel.house
to: ‘warning’
action:
– service: notify.telegram
data:
message: ‘ALARM Warning {{ states[states.alarm_control_panel.house.attributes.changed_by.split(“.”)[0]][ states.alarm_control_panel.house.attributes.changed_by.split(“.”)[1]].name }}’
– service: xiaomi_aqara.play_ringtone
data:
gw_mac: 34:ce:00:88:xx:xx
ringtone_id: 10013 # Se ha disparado la alarma, por favor desactivar.
ringtone_vol: 100

####################################################
################# P U L S A D O R #################
####################################################
– alias: “Alarma Casa on”
trigger:
– platform: state
entity_id: input_boolean.alarma_casa
to: ‘on’
action:
– service: alarm_control_panel.alarm_arm_away
data:
entity_id: alarm_control_panel.house
# code: !secret alarm_code

– alias: “Alarma Casa off”
trigger:
– platform: state
entity_id: input_boolean.alarma_casa
to: ‘off’
action:
– service: alarm_control_panel.alarm_disarm
data:
entity_id: alarm_control_panel.house
code: !secret alarm_code

####################################################
################# S I R E N A ######################
####################################################
– alias: “Sirena1”
initial_state: False
hide_entity: False
trigger:
– platform: state
entity_id: automation.sirena1
from: ‘off’
to: ‘on’
condition:
– condition: state
entity_id: alarm_control_panel.house
state: ‘triggered’
action:
– service: automation.turn_off
entity_id: automation.sirena3
– service: xiaomi_aqara.play_ringtone
data:
gw_mac: 34:ce:00:88:xx:xx
ringtone_id: 2
ringtone_vol: 100
– delay:
seconds: 4
– service: automation.turn_on
entity_id: automation.sirena2

– alias: “Sirena2”
initial_state: False
hide_entity: False
trigger:
– platform: state
entity_id: automation.sirena2
from: ‘off’
to: ‘on’
condition:
– condition: state
entity_id: alarm_control_panel.house
state: ‘triggered’
action:
– service: automation.turn_off
entity_id: automation.sirena1
– service: xiaomi_aqara.play_ringtone
data:
gw_mac: 34:ce:00:88:xx:xx
ringtone_id: 2
ringtone_vol: 100
– delay:
seconds: 4
– service: automation.turn_on
entity_id: automation.sirena3

– alias: “Sirena3”
initial_state: False
hide_entity: False
trigger:
– platform: state
entity_id: automation.sirena3
from: ‘off’
to: ‘on’
condition:
– condition: state
entity_id: alarm_control_panel.house
state: ‘triggered’
action:
– service: automation.turn_off
entity_id: automation.sirena2
– service: xiaomi_aqara.play_ringtone
data:
gw_mac: 34:ce:00:88:xx:xx
ringtone_id: 2
ringtone_vol: 100
– delay:
seconds: 4
– service: automation.turn_on
entity_id: automation.sirena1

Mostrar Código

Por ultimo os dejo un paquete con todos los ficheros y las Voces que tengo generadas para esa automatización.

 

Alarma HASS+XiaomiGW-ESP-ByRubenzori86

 

Si queréis mas información sobre esta alarma podéis visitar al creador de la misma en GitHub.

https://github.com/gazoscalvertos/Hass-Custom-Alarm

Deja un comentario