Joshua Rutschmann před 6 roky
rodič
revize
ff679f1874

+ 54
- 23
config/configuration.yaml Zobrazit soubor

@@ -29,8 +29,17 @@ frontend:
29 29
 # Enables configuration UI
30 30
 config:
31 31
 
32
+image_processing:
33
+  - platform: tensorflow
34
+    source:
35
+      - entity_id: camera.webcam
36
+    model:
37
+      graph: /config/tensorflow/frozen_inference_graph.pb
38
+
39
+emulated_hue:
40
+      
32 41
 octoprint:
33
-  host: 192.168.1.163
42
+  host: 192.168.1.126
34 43
   port: 5000
35 44
   api_key: 4C627EC30E884B1D9B65A7058FC67925
36 45
 
@@ -75,14 +84,7 @@ input_number:
75 84
 
76 85
 device_tracker:
77 86
   - platform: fritz
78
-    host: 192.168.2.1
79
-    new_device_defaults:
80
-      track_new_devices: False
81
-      hide_if_away: False
82
-  - platform: luci
83 87
     host: 192.168.1.1
84
-    username: !secret luci_username
85
-    password: !secret luci_password
86 88
     new_device_defaults:
87 89
       track_new_devices: False
88 90
       hide_if_away: False
@@ -109,13 +111,28 @@ sensor:
109 111
       - temperature
110 112
       - humidity
111 113
   - platform: fritzbox_callmonitor
112
-    host: 192.168.2.1
114
+    host: 192.168.1.1
113 115
     name: Phone
114 116
     username: homeassistant
115 117
     password: !secret fritz_password
116 118
     phonebook: 0
117 119
     prefixes:
118 120
       - '+49'
121
+  - platform: mqtt  
122
+    state_topic: "bruh/sensordht"  
123
+    name: "SN1 Humidity"  
124
+    unit_of_measurement: "%"  
125
+    value_template: '{{ value_json.humidity | round(1) }}'  
126
+  - platform: mqtt  
127
+    state_topic: "bruh/sensordht"  
128
+    name: "SN1 Temperature"  
129
+    unit_of_measurement: "°F"  
130
+    value_template: '{{ value_json.temperature | round(1) }}'  
131
+  - platform: mqtt
132
+    state_topic: "bruh/sensordht"
133
+    name: "SN1 Real Feel"
134
+    unit_of_measurement: "°F"
135
+    value_template: '{{ value_json.heatIndex | round(1) }}'
119 136
 
120 137
 binary_sensor:
121 138
   - platform: rpi_gpio
@@ -148,8 +165,6 @@ history_graph:
148 165
       - sensor.octoprint_target_bed_temp
149 166
       - sensor.octoprint_target_tool0_temp
150 167
     hours_to_show: 1
151
-  
152
-  
153 168
 
154 169
 lock:
155 170
   - platform: template
@@ -167,10 +182,15 @@ lock:
167 182
 light:
168 183
   - platform: flux_led
169 184
     devices:
170
-      192.168.1.185:
185
+      192.168.1.105:
171 186
         name: Led Strip
172
-      192.168.1.131:
187
+      192.168.1.123:
173 188
         name: Led Strip Bed
189
+  - platform: group
190
+    name: LED Strips
191
+    entities: 
192
+      - light.led_strip
193
+      - light.led_strip_bed
174 194
   - platform: template
175 195
     lights:
176 196
       hdmi:
@@ -187,7 +207,7 @@ light:
187 207
         turn_off:
188 208
           service: switch.turn_off
189 209
           entity_id: switch.lightrelay
190
-  - platform: mqtt_json
210
+  - platform: mqtt
191 211
     name: "WS2812B Strip"
192 212
     state_topic: "bruh/porch"
193 213
     command_topic: "bruh/porch/set"
@@ -221,9 +241,19 @@ switch:
221 241
   - platform: wake_on_lan
222 242
     name: Desktop
223 243
     mac_address: "4C-ED-FB-94-9D-24"
224
-    host: 192.168.1.3
244
+    host: 192.168.1.10
225 245
     turn_off:
226 246
       service: shell_command.turn_off_desktop
247
+  - platform: mqtt
248
+    name: "Bedroom Switch"
249
+    state_topic: "bruh/wifirelay"
250
+    command_topic: "bruh/wifirelay/set"
251
+    payload_on: "ON"
252
+    payload_off: "OFF"
253
+    state_on: "ON"
254
+    state_off: "OFF"
255
+    optimistic: true
256
+    qos: 0
227 257
   - platform: rpi_rf
228 258
     gpio: 24
229 259
     switches:
@@ -266,9 +296,9 @@ cover:
266 296
           service: script.coverstop
267 297
 
268 298
 shell_command:
269
-  turn_off_desktop: 'echo net rpc shutdown --ipaddress 192.168.1.3 --user Joshu%CYNTWyZkRBFik | ssh pi@192.168.1.2'
270
-  turn_on_hdmi: 'echo vcgencmd display_power 1 | ssh pi@192.168.1.179'
271
-  turn_off_hdmi: 'echo vcgencmd display_power 0 | ssh pi@192.168.1.179'
299
+  turn_off_desktop: 'echo net rpc shutdown --ipaddress 192.168.1.10 --user Joshu%CYNTWyZkRBFik | ssh pi@192.168.1.2'
300
+  turn_on_hdmi: 'echo vcgencmd display_power 1 | ssh pi@192.168.1.4'
301
+  turn_off_hdmi: 'echo vcgencmd display_power 0 | ssh pi@192.168.1.4'
272 302
 
273 303
 api:
274 304
 
@@ -287,11 +317,12 @@ alexa:
287 317
       
288 318
 http:
289 319
   api_password: !secret http_password
290
-  base_url: https://home.rutschmann.tech
291
-  server_port: 443
292
-  ssl_profile: intermediate
293
-  ssl_certificate: /config/ssl/fullchain.pem
294
-  ssl_key: /config/ssl/privkey.pem
320
+  base_url: http://localhost:8123
321
+  #base_url: https://home.rutschmann.tech
322
+  #server_port: 443
323
+  #ssl_profile: intermediate
324
+  #ssl_certificate: /config/ssl/fullchain.pem
325
+  #ssl_key: /config/ssl/privkey.pem
295 326
 
296 327
 ifttt:
297 328
   key: !secret webhooks_key

+ 112
- 0
config/known_devices.yaml Zobrazit soubor

@@ -366,3 +366,115 @@ d0a637864f63:
366 366
   name: 600194b39ebb
367 367
   picture:
368 368
   track: false
369
+
370
+3095e3088e72:
371
+  hide_if_away: false
372
+  icon:
373
+  mac: 30:95:E3:08:8E:72
374
+  name: 3095e3088e72
375
+  picture:
376
+  track: false
377
+
378
+600194b3a6b9:
379
+  hide_if_away: false
380
+  icon:
381
+  mac: 60:01:94:B3:A6:B9
382
+  name: 600194b3a6b9
383
+  picture:
384
+  track: false
385
+
386
+8866a5db8007:
387
+  hide_if_away: false
388
+  icon:
389
+  mac: 88:66:A5:DB:80:07
390
+  name: 8866a5db8007
391
+  picture:
392
+  track: false
393
+
394
+archerc7:
395
+  hide_if_away: false
396
+  icon:
397
+  mac: 0C:80:63:E7:04:08
398
+  name: Archer-C7
399
+  picture:
400
+  track: false
401
+
402
+openwrt_2:
403
+  hide_if_away: false
404
+  icon:
405
+  mac: 0C:80:63:E7:04:07
406
+  name: OpenWrt
407
+  picture:
408
+  track: false
409
+
410
+pc1921681107:
411
+  hide_if_away: false
412
+  icon:
413
+  mac: F8:D1:11:8A:A2:D4
414
+  name: PC-192-168-1-107
415
+  picture:
416
+  track: false
417
+
418
+iphonelj:
419
+  hide_if_away: false
420
+  icon:
421
+  mac: 74:8D:08:C2:80:C7
422
+  name: iPhone-LJ
423
+  picture:
424
+  track: false
425
+
426
+pc_192_168_1_2:
427
+  hide_if_away: false
428
+  icon:
429
+  mac: 2E:95:18:52:3B:2A
430
+  name: PC-192-168-1-2
431
+  picture:
432
+  track: false
433
+
434
+esp_96f94f:
435
+  hide_if_away: false
436
+  icon:
437
+  mac: 84:0D:8E:96:F9:4F
438
+  name: ESP-96F94F
439
+  picture:
440
+  track: false
441
+
442
+esp_96d07a:
443
+  hide_if_away: false
444
+  icon:
445
+  mac: 84:0D:8E:96:D0:7A
446
+  name: ESP-96D07A
447
+  picture:
448
+  track: false
449
+
450
+pc_bc_6c_21_73_25_de:
451
+  hide_if_away: false
452
+  icon:
453
+  mac: BC:6C:21:73:25:DE
454
+  name: PC-BC-6C-21-73-25-DE
455
+  picture:
456
+  track: false
457
+
458
+huawei_p20_lite_2146e034c:
459
+  hide_if_away: false
460
+  icon:
461
+  mac: B4:CD:27:F4:B8:A3
462
+  name: HUAWEI-P20-lite-2146e034c
463
+  picture:
464
+  track: false
465
+
466
+desktop_g2k41f0:
467
+  hide_if_away: false
468
+  icon:
469
+  mac: 00:22:68:4A:24:9C
470
+  name: DESKTOP-G2K41F0
471
+  picture:
472
+  track: false
473
+
474
+pc_a4_d9_31_69_4b_eb:
475
+  hide_if_away: false
476
+  icon:
477
+  mac: A4:D9:31:69:4B:EB
478
+  name: PC-A4-D9-31-69-4B-EB
479
+  picture:
480
+  track: false

+ 249
- 0
config/www/circle-sensor-card.js Zobrazit soubor

@@ -0,0 +1,249 @@
1
+import {
2
+  LitElement, html
3
+} from 'https://unpkg.com/@polymer/lit-element@^0.5.2/lit-element.js?module';
4
+
5
+class CircleSensorCard extends LitElement {
6
+  static get properties() {
7
+    return {
8
+      hass: Object,
9
+      config: Object,
10
+      state: Object,
11
+      dashArray: String
12
+    }
13
+  }
14
+
15
+  _render({ state, dashArray, config }) {
16
+    return html`
17
+      <style>
18
+          :host {
19
+            cursor: pointer;
20
+          }
21
+
22
+          .container {
23
+            position: relative;
24
+            height: 100%;
25
+            display: flex;
26
+            flex-direction: column;
27
+          }
28
+
29
+          .labelContainer {
30
+            position: absolute;
31
+            top: 0;
32
+            left: 0;
33
+            width: 100%;
34
+            height: 100%;
35
+            display: flex;
36
+            flex-direction: column;
37
+            align-items: center;
38
+            justify-content: center;
39
+          }
40
+          
41
+          #label {
42
+            display: flex;
43
+            line-height: 1;
44
+          }
45
+          
46
+          #label.bold {
47
+            font-weight: bold;
48
+          }
49
+          
50
+          #label, #name {
51
+            margin: 1% 0;
52
+          }
53
+
54
+          .text, #name {
55
+            font-size: 100%;
56
+          }
57
+          
58
+          .unit {
59
+            font-size: 75%;
60
+          }
61
+
62
+      </style>
63
+      <div class="container" id="container" on-click="${() => this._click()}">
64
+        <svg viewbox="0 0 200 200" id="svg">
65
+          <circle id="circle" cx="50%" cy="50%" r="45%"
66
+            fill$="${config.fill || 'rgba(255, 255, 255, .75)'}"
67
+            stroke$="${config.stroke_color || '#03a9f4'}"
68
+            stroke-dasharray$="${dashArray}"
69
+            stroke-width$="${config.stroke_width || 6}" 
70
+            transform="rotate(-90 100 100)"/>
71
+        </svg>
72
+        <span class="labelContainer">
73
+          ${config.name != null ? html`<span id="name">${config.name}</span>` : ''}
74
+          <span id="label" class$="${!!config.name ? 'bold' : ''}">
75
+            <span class="text">
76
+              ${config.attribute ? state.attributes[config.attribute] : state.state}
77
+            </span>
78
+            <span class="unit">
79
+              ${config.show_max
80
+                ? html`&nbsp/ ${config.attribute_max ? state.attributes[config.attribute_max] : config.max}`
81
+                : (config.units ? config.units : state.attributes.unit_of_measurement)}
82
+            </span>
83
+          </span>
84
+        </span>
85
+      </div>
86
+    `;
87
+  }
88
+
89
+  _createRoot() {
90
+    const shadow = this.attachShadow({ mode: 'open' })
91
+    if (!this.config.show_card) {
92
+      return shadow;
93
+    }
94
+    const card = document.createElement('ha-card');
95
+    shadow.appendChild(card);
96
+    return card;
97
+  }
98
+
99
+  _didRender() {
100
+    this.circle = this._root.querySelector('#circle');
101
+    if (this.config) {
102
+      this._updateConfig();
103
+    }
104
+  }
105
+
106
+  setConfig(config) {
107
+    if (!config.entity) {
108
+      throw Error('No entity defined')
109
+    }
110
+    this.config = config;
111
+    if (this.circle) {
112
+      this._updateConfig();
113
+    }
114
+  }
115
+
116
+  getCardSize() {
117
+    return 3;
118
+  }
119
+
120
+  _updateConfig() {
121
+    const container = this._root.querySelector('.labelContainer');
122
+    container.style.color = 'var(--primary-text-color)';
123
+
124
+    if (this.config.font_style) {
125
+      Object.keys(this.config.font_style).forEach((prop) => {
126
+        container.style.setProperty(prop, this.config.font_style[prop]);
127
+      });
128
+    }
129
+  }
130
+
131
+  set hass(hass) {
132
+    this.state = hass.states[this.config.entity];
133
+
134
+    if (this.config.attribute) {
135
+      if (!this.state.attributes[this.config.attribute] ||
136
+          isNaN(this.state.attributes[this.config.attribute])) {
137
+        console.error(`Attribute [${this.config.attribute}] is not a number`);
138
+        return;
139
+      }
140
+    } else {
141
+      if (!this.state || isNaN(this.state.state)) {
142
+        console.error(`State is not a number`);
143
+        return;
144
+      }
145
+    }
146
+
147
+    const state = this.config.attribute
148
+      ? this.state.attributes[this.config.attribute]
149
+      : this.state.state;
150
+    const r = 200 * .45;
151
+    const min = this.config.min || 0;
152
+    const max = this.config.attribute_max
153
+      ? this.state.attributes[this.config.attribute_max]
154
+      : (this.config.max || 100);
155
+    const val = this._calculateValueBetween(min, max, state);
156
+    const score = val * 2 * Math.PI * r;
157
+    const total = 10 * r;
158
+    this.dashArray = `${score} ${total}`;
159
+
160
+    let colorStops = {};
161
+    colorStops[min] = this.config.stroke_color || '#03a9f4';
162
+    if (this.config.color_stops) {
163
+      Object.keys(this.config.color_stops).forEach((key) => {
164
+        colorStops[key] = this.config.color_stops[key];
165
+      });
166
+    }
167
+
168
+    if (this.circle) {
169
+      const stroke = this._calculateStrokeColor(state, colorStops);
170
+      this.circle.setAttribute('stroke', stroke);
171
+    }
172
+  }
173
+
174
+  _click() {
175
+    this._fire('hass-more-info', { entityId: this.config.entity });
176
+  }
177
+
178
+  _calculateStrokeColor(state, stops) {
179
+    const sortedStops = Object.keys(stops).map(n => Number(n)).sort((a, b) => a - b);
180
+    let start, end, val;
181
+    const l = sortedStops.length;
182
+    if (state <= sortedStops[0]) {
183
+      return stops[sortedStops[0]];
184
+    } else if (state >= sortedStops[l - 1]) {
185
+      return stops[sortedStops[l - 1]];
186
+    } else {
187
+      for (let i = 0; i < l - 1; i++) {
188
+        const s1 = sortedStops[i];
189
+        const s2 = sortedStops[i + 1];
190
+        if (state >= s1 && state < s2) {
191
+          [start, end] = [stops[s1], stops[s2]];
192
+          if (!this.config.gradient) {
193
+            return start;
194
+          }
195
+          val = this._calculateValueBetween(s1, s2, state);
196
+          break;
197
+        }
198
+      }
199
+    }
200
+    return this._getGradientValue(start, end, val);
201
+  }
202
+
203
+  _calculateValueBetween(start, end, val) {
204
+    return (val - start) / (end - start);
205
+  }
206
+
207
+  _getGradientValue(colorA, colorB, val) {
208
+    const v1 = 1 - val;
209
+    const v2 = val;
210
+    const decA = this._hexColorToDecimal(colorA);
211
+    const decB = this._hexColorToDecimal(colorB);
212
+    const rDec = Math.floor((decA[0] * v1) + (decB[0] * v2));
213
+    const gDec = Math.floor((decA[1] * v1) + (decB[1] * v2));
214
+    const bDec = Math.floor((decA[2] * v1) + (decB[2] * v2));
215
+    const rHex = this._padZero(rDec.toString(16));
216
+    const gHex = this._padZero(gDec.toString(16));
217
+    const bHex = this._padZero(bDec.toString(16));
218
+    return `#${rHex}${gHex}${bHex}`;
219
+  }
220
+
221
+  _hexColorToDecimal(color) {
222
+    let c = color.substr(1);
223
+    if (c.length === 3) {
224
+      c = `${c[0]}${c[0]}${c[1]}${c[1]}${c[2]}${c[2]}`;
225
+    }
226
+
227
+    const [r, g, b] = c.match(/.{2}/g);
228
+    return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)];
229
+  }
230
+
231
+  _padZero(val) {
232
+    if (val.length < 2) {
233
+      val = `0${val}`;
234
+    }
235
+    return val.substr(0, 2);
236
+  }
237
+
238
+  _fire(type, detail) {
239
+    const event = new Event(type, {
240
+      bubbles: true,
241
+      cancelable: false,
242
+      composed: true
243
+    });
244
+    event.detail = detail || {};
245
+    this.shadowRoot.dispatchEvent(event);
246
+    return event;
247
+  }
248
+}
249
+customElements.define('circle-sensor-card', CircleSensorCard);

+ 1899
- 0
config/www/circle-sensor-card.js.htm
Diff nebyl zobrazen, protože je příliš veliký
Zobrazit soubor


binární
config/www/david.jpg Zobrazit soubor


binární
config/www/margit.jpg Zobrazit soubor


binární
config/www/meinrad.jpg Zobrazit soubor


+ 179
- 0
config/www/surveillance-card.js Zobrazit soubor

@@ -0,0 +1,179 @@
1
+import {
2
+  LitElement, html
3
+} from 'https://unpkg.com/@polymer/lit-element@^0.5.2/lit-element.js?module';
4
+
5
+import { repeat } from 'https://unpkg.com/lit-html@0.10.2/lib/repeat.js?module';
6
+
7
+class SurveillanceCard extends LitElement {
8
+  /* eslint-disable indent,object-curly-newline */
9
+  _render({ imageSources, currentCamera, lastMotion, updateInterval }) {
10
+    const accessToken = currentCamera && this._hass.states[currentCamera].attributes.access_token;
11
+    const template = html`
12
+    <style>
13
+      .container {
14
+        height: 100%;
15
+        width: 100%;
16
+        display: flex;
17
+        align-items: stretch;
18
+        position: absolute;
19
+        background: #000;
20
+      }
21
+
22
+      .thumbs {
23
+        flex: 1;
24
+        overflow-y: auto;
25
+        position:relative;
26
+      }
27
+
28
+      .thumb > img {
29
+        width: 100%;
30
+        height: auto;
31
+        border: 1px solid var(--primary-color);
32
+      }
33
+
34
+      .thumb {
35
+        width: calc(100% - 9px);
36
+        padding: 2px 4px;
37
+        position: relative;
38
+      }
39
+
40
+      .thumb.motion > img {
41
+        border-color: var(--accent-color);
42
+      }
43
+
44
+      img {
45
+        display: block;
46
+      }
47
+
48
+      .mainImage {
49
+        flex: 3;
50
+        height: 100%;
51
+        position: relative;
52
+        display: flex;
53
+        align-items: center;
54
+        justify-content: center;
55
+        overflow: hidden;
56
+      }
57
+
58
+      .mainImage > img {
59
+        display: inline-block;
60
+        max-width: 100%;
61
+        max-height: 100%;
62
+      }
63
+
64
+      .loading {
65
+        color: #FFF;
66
+        text-align: center;
67
+        font-size: 1.2rem;
68
+        margin-top: 3rem;
69
+      }
70
+    </style>
71
+    <div class="container">
72
+      <div class="thumbs">
73
+        ${imageSources ? repeat(this.cameras, (camera) => {
74
+          const thumbClass = lastMotion && lastMotion === camera.motion ? 'thumb motion' : 'thumb';
75
+          const source = this.imageSources[camera.entity];
76
+          return html`
77
+            <div class$="${thumbClass}" on-click="${() => { this.currentCamera = camera.entity; }}">
78
+              <img src="${source || ''}" />
79
+            </div>
80
+          `;
81
+        }) : html`<div class="loading">Loading Cameras...</div>`}
82
+      </div>
83
+      <div class="mainImage">
84
+        <img src$="${currentCamera ? `/api/camera_proxy_stream/${currentCamera}?token=${accessToken}&interval=${updateInterval}` : ''}" />
85
+      </div>
86
+    </div>
87
+    `;
88
+
89
+    return template;
90
+  }
91
+  /* eslint-enable indent,object-curly-newline */
92
+
93
+  static get properties() {
94
+    return {
95
+      _hass: Object,
96
+      cameras: Array,
97
+      currentCamera: String,
98
+      imageSources: Object,
99
+      lastMotion: String,
100
+      thumbInterval: Number,
101
+      updateInterval: Number
102
+    };
103
+  }
104
+
105
+  setConfig(config) {
106
+    this.cameras = config.cameras.map(c => ({
107
+      entity: c.entity,
108
+      motion: c.motion_entity
109
+    }));
110
+    this.currentCamera = this.cameras[0].entity;
111
+    this.thumbInterval = (config.thumb_interval || 10) * 1000;
112
+    this.updateInterval = config.update_interval || 1;
113
+    this.focusMotion = config.focus_motion !== false;
114
+  }
115
+
116
+  set hass(hass) {
117
+    this._hass = hass;
118
+
119
+    for (const cam of this.cameras) {
120
+      const { motion } = cam;
121
+      if ((motion in hass.states) && hass.states[motion].state === 'on') {
122
+        if (this.focusMotion && this.lastMotion !== motion) {
123
+          this.currentCamera = cam.entity;
124
+        }
125
+        this.lastMotion = motion;
126
+        return;
127
+      }
128
+    }
129
+    this.lastMotion = null;
130
+  }
131
+
132
+  connectedCallback() {
133
+    super.connectedCallback();
134
+    this.thumbUpdater = setInterval(() => this._updateThumbs(), this.thumbInterval);
135
+  }
136
+
137
+  disconnectedCallback() {
138
+    super.disconnectedCallback();
139
+    clearInterval(this.thumbUpdater);
140
+    this.imageSources = null;
141
+    this.currentCamera = '';
142
+  }
143
+
144
+  _firstRendered() {
145
+    this._updateThumbs();
146
+  }
147
+
148
+  async _updateCameraImageSrc(entity) {
149
+    try {
150
+      const { content_type: contentType, content } = await this._hass.callWS({
151
+        type: 'camera_thumbnail',
152
+        entity_id: entity,
153
+      });
154
+
155
+      return {
156
+        entityId: entity,
157
+        src: `data:${contentType};base64, ${content}`
158
+      };
159
+    } catch (err) {
160
+      return {
161
+        entityId: entity,
162
+        src: null
163
+      };
164
+    }
165
+  }
166
+
167
+  _updateThumbs() {
168
+    const sources = {};
169
+    const promises = this.cameras.map(camera => this._updateCameraImageSrc(camera.entity));
170
+    Promise.all(promises).then((vals) => {
171
+      this.cameras.forEach((camera) => {
172
+        const target = vals.find(val => val.entityId === camera.entity);
173
+        sources[camera.entity] = target.src;
174
+      });
175
+      this.imageSources = sources;
176
+    });
177
+  }
178
+}
179
+customElements.define('surveillance-card', SurveillanceCard);

+ 1
- 1
docker-compose.yml Zobrazit soubor

@@ -2,7 +2,7 @@ version: '2.0'
2 2
 services:
3 3
   homeassistant:
4 4
     container_name: home-assistant
5
-    image: homeassistant/raspberrypi3-homeassistant
5
+    image: homeassistant/raspberrypi3-homeassistant:0.85.1
6 6
     restart: always
7 7
     devices:
8 8
       - /dev/video0:/dev/video0

Loading…
Zrušit
Uložit