Update
This commit is contained in:
parent
12eea82c8f
commit
984fc55b20
@ -25,3 +25,18 @@ attic_window_open:
|
|||||||
notifiers:
|
notifiers:
|
||||||
- telegram_group
|
- telegram_group
|
||||||
- alexa_all
|
- alexa_all
|
||||||
|
|
||||||
|
garage_door_open:
|
||||||
|
name: Garagentor geöffnet
|
||||||
|
message: "Zur Info - Die *Garage* steht noch *offen*!"
|
||||||
|
done_message: "Zur Info - Die *Garage* ist wieder *geschlossen*."
|
||||||
|
entity_id: binary_sensor.lumi_garage_door
|
||||||
|
state: "on"
|
||||||
|
repeat:
|
||||||
|
- 10
|
||||||
|
- 30
|
||||||
|
can_acknowledge: true
|
||||||
|
skip_first: true
|
||||||
|
notifiers:
|
||||||
|
- telegram_group
|
||||||
|
- alexa_all
|
@ -9,6 +9,7 @@
|
|||||||
- light.kuchen_theke
|
- light.kuchen_theke
|
||||||
- light.esstisch
|
- light.esstisch
|
||||||
- light.office
|
- light.office
|
||||||
|
- light.onair
|
||||||
- switch.livingroom_stimmungslicht
|
- switch.livingroom_stimmungslicht
|
||||||
- switch.livingroom_music
|
- switch.livingroom_music
|
||||||
- switch.livingroom_netflix
|
- switch.livingroom_netflix
|
||||||
@ -19,6 +20,8 @@
|
|||||||
- switch.desktop_wol
|
- switch.desktop_wol
|
||||||
- switch.wallboard_display
|
- switch.wallboard_display
|
||||||
- switch.tplink1
|
- switch.tplink1
|
||||||
|
- switch.osram_plug_01_57b6060a_on_off
|
||||||
|
- switch.onair_lamp_recording
|
||||||
# include_domains:
|
# include_domains:
|
||||||
# - switch
|
# - switch
|
||||||
# exclude_entities:
|
# exclude_entities:
|
||||||
@ -43,6 +46,9 @@
|
|||||||
light.stimmungslicht:
|
light.stimmungslicht:
|
||||||
name: Stimmungslicht
|
name: Stimmungslicht
|
||||||
description: Wohnzimmer - Stimmungslicht
|
description: Wohnzimmer - Stimmungslicht
|
||||||
|
light.onair:
|
||||||
|
name: Studio-Treppe
|
||||||
|
description: Studio-Treppenlicht
|
||||||
switch.livingroom_music:
|
switch.livingroom_music:
|
||||||
name: Musik
|
name: Musik
|
||||||
description: Wohnzimmer - Musik
|
description: Wohnzimmer - Musik
|
||||||
@ -70,4 +76,10 @@
|
|||||||
switch.tplink1:
|
switch.tplink1:
|
||||||
name: Kamera
|
name: Kamera
|
||||||
description: Wohnzimmer Kamera
|
description: Wohnzimmer Kamera
|
||||||
|
switch.osram_plug_01_57b6060a_on_off:
|
||||||
|
name: Ring
|
||||||
|
description: Studio-Steckdose
|
||||||
|
switch.onair_lamp_recording:
|
||||||
|
name: Aufnahme
|
||||||
|
description: Studio-Treppenlicht Aufnahme-Modus
|
||||||
# ACHTUNG: WHITELIST EBENFALLS ERGÄNZEN!
|
# ACHTUNG: WHITELIST EBENFALLS ERGÄNZEN!
|
25
config/automations/landroid.yaml
Normal file
25
config/automations/landroid.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
- alias: Landroid mowing
|
||||||
|
trigger:
|
||||||
|
- platform: state
|
||||||
|
entity_id: sensor.landroid_hans_dieter_status
|
||||||
|
to: "Mowing"
|
||||||
|
action:
|
||||||
|
- service: notify.telegram_group
|
||||||
|
data_template:
|
||||||
|
message: "Achtung - *Hans-Dieter* *mäht* jetzt den Rasen!"
|
||||||
|
- service: notify.alexa_all
|
||||||
|
data_template:
|
||||||
|
message: "Achtung - Hans-Dieter maeht jetzt den Rasen!"
|
||||||
|
|
||||||
|
- alias: Landroid home
|
||||||
|
trigger:
|
||||||
|
- platform: state
|
||||||
|
entity_id: sensor.landroid_hans_dieter_status
|
||||||
|
to: "Home"
|
||||||
|
action:
|
||||||
|
- service: notify.telegram_group
|
||||||
|
data_template:
|
||||||
|
message: "Zur Info - *Hans-Dieter* ist nun wieder in seiner *Ladestation*."
|
||||||
|
- service: notify.alexa_all
|
||||||
|
data_template:
|
||||||
|
message: "Zur Info - Hans-Dieter ist nun wieder in seiner Ladestation."
|
34
config/automations/misc.yaml
Normal file
34
config/automations/misc.yaml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
- alias: Instagram Counter - Adjust Brightness
|
||||||
|
trigger:
|
||||||
|
platform: state
|
||||||
|
entity_id: input_number.instagram_counter_brightness
|
||||||
|
action:
|
||||||
|
- service: rest_command.instagram_counter_brightness
|
||||||
|
data_template:
|
||||||
|
value: "{{ trigger.to_state.state | int }}"
|
||||||
|
|
||||||
|
- alias: onAir Recording On
|
||||||
|
trigger:
|
||||||
|
platform: state
|
||||||
|
entity_id: input_boolean.onair_lamp_recording
|
||||||
|
to: 'on'
|
||||||
|
action:
|
||||||
|
- service: light.turn_on
|
||||||
|
data:
|
||||||
|
entity_id: light.onair
|
||||||
|
rgb_color: [255, 0, 0]
|
||||||
|
|
||||||
|
- alias: onAir Recording Off
|
||||||
|
trigger:
|
||||||
|
platform: state
|
||||||
|
entity_id: input_boolean.onair_lamp_recording
|
||||||
|
to: 'off'
|
||||||
|
action:
|
||||||
|
- service: light.turn_on
|
||||||
|
data:
|
||||||
|
entity_id: light.onair
|
||||||
|
rgb_color: [255, 255, 255]
|
||||||
|
- service: light.turn_on
|
||||||
|
data:
|
||||||
|
entity_id: light.onair
|
||||||
|
color_temp: 367
|
@ -5,3 +5,8 @@
|
|||||||
- platform: switch
|
- platform: switch
|
||||||
name: Stimmungslicht
|
name: Stimmungslicht
|
||||||
entity_id: switch.livingroom_stimmungslicht
|
entity_id: switch.livingroom_stimmungslicht
|
||||||
|
- platform: group
|
||||||
|
name: onAir
|
||||||
|
entities:
|
||||||
|
- light.innr_gu10_rgb_1
|
||||||
|
- light.innr_gu10_rgb_2
|
@ -77,20 +77,20 @@
|
|||||||
scan_interval: 300
|
scan_interval: 300
|
||||||
name: instagram_beauty
|
name: instagram_beauty
|
||||||
resource: !secret instagram_beauty
|
resource: !secret instagram_beauty
|
||||||
value_template: '{{ value_json.data.counts.followed_by }}'
|
value_template: '{{ value_json.followers_count }}'
|
||||||
unit_of_measurement: Followers
|
unit_of_measurement: Followers
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 900
|
scan_interval: 900
|
||||||
name: instagram_beauty_follows
|
name: instagram_beauty_follows
|
||||||
resource: !secret instagram_beauty
|
resource: !secret instagram_beauty
|
||||||
value_template: '{{ value_json.data.counts.follows }}'
|
value_template: '{{ value_json.follows_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 600
|
scan_interval: 600
|
||||||
name: instagram_beauty_media
|
name: instagram_beauty_media
|
||||||
resource: !secret instagram_beauty
|
resource: !secret instagram_beauty
|
||||||
value_template: '{{ value_json.data.counts.media }}'
|
value_template: '{{ value_json.media_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
|
|
||||||
# franky
|
# franky
|
||||||
@ -98,20 +98,20 @@
|
|||||||
scan_interval: 300
|
scan_interval: 300
|
||||||
name: instagram_franky
|
name: instagram_franky
|
||||||
resource: !secret instagram_franky
|
resource: !secret instagram_franky
|
||||||
value_template: '{{ value_json.data.counts.followed_by }}'
|
value_template: '{{ value_json.followers_count }}'
|
||||||
unit_of_measurement: Followers
|
unit_of_measurement: Followers
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 900
|
scan_interval: 900
|
||||||
name: instagram_franky_follows
|
name: instagram_franky_follows
|
||||||
resource: !secret instagram_franky
|
resource: !secret instagram_franky
|
||||||
value_template: '{{ value_json.data.counts.follows }}'
|
value_template: '{{ value_json.follows_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 600
|
scan_interval: 600
|
||||||
name: instagram_franky_media
|
name: instagram_franky_media
|
||||||
resource: !secret instagram_franky
|
resource: !secret instagram_franky
|
||||||
value_template: '{{ value_json.data.counts.media }}'
|
value_template: '{{ value_json.media_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
|
|
||||||
# fb
|
# fb
|
||||||
@ -119,20 +119,20 @@
|
|||||||
scan_interval: 300
|
scan_interval: 300
|
||||||
name: instagram_fb
|
name: instagram_fb
|
||||||
resource: !secret instagram_fb
|
resource: !secret instagram_fb
|
||||||
value_template: '{{ value_json.data.counts.followed_by }}'
|
value_template: '{{ value_json.followers_count }}'
|
||||||
unit_of_measurement: Followers
|
unit_of_measurement: Followers
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 900
|
scan_interval: 900
|
||||||
name: instagram_fb_follows
|
name: instagram_fb_follows
|
||||||
resource: !secret instagram_fb
|
resource: !secret instagram_fb
|
||||||
value_template: '{{ value_json.data.counts.follows }}'
|
value_template: '{{ value_json.follows_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 600
|
scan_interval: 600
|
||||||
name: instagram_fb_media
|
name: instagram_fb_media
|
||||||
resource: !secret instagram_fb
|
resource: !secret instagram_fb
|
||||||
value_template: '{{ value_json.data.counts.media }}'
|
value_template: '{{ value_json.media_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
|
|
||||||
# mtb
|
# mtb
|
||||||
@ -140,20 +140,20 @@
|
|||||||
scan_interval: 300
|
scan_interval: 300
|
||||||
name: instagram_mtb
|
name: instagram_mtb
|
||||||
resource: !secret instagram_mtb
|
resource: !secret instagram_mtb
|
||||||
value_template: '{{ value_json.data.counts.followed_by }}'
|
value_template: '{{ value_json.followers_count }}'
|
||||||
unit_of_measurement: Followers
|
unit_of_measurement: Followers
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 900
|
scan_interval: 900
|
||||||
name: instagram_mtb_follows
|
name: instagram_mtb_follows
|
||||||
resource: !secret instagram_mtb
|
resource: !secret instagram_mtb
|
||||||
value_template: '{{ value_json.data.counts.follows }}'
|
value_template: '{{ value_json.follows_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 600
|
scan_interval: 600
|
||||||
name: instagram_mtb_media
|
name: instagram_mtb_media
|
||||||
resource: !secret instagram_mtb
|
resource: !secret instagram_mtb
|
||||||
value_template: '{{ value_json.data.counts.media }}'
|
value_template: '{{ value_json.media_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
|
|
||||||
# medieval
|
# medieval
|
||||||
@ -161,20 +161,20 @@
|
|||||||
scan_interval: 300
|
scan_interval: 300
|
||||||
name: instagram_medieval
|
name: instagram_medieval
|
||||||
resource: !secret instagram_medieval
|
resource: !secret instagram_medieval
|
||||||
value_template: '{{ value_json.data.counts.followed_by }}'
|
value_template: '{{ value_json.followers_count }}'
|
||||||
unit_of_measurement: Followers
|
unit_of_measurement: Followers
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 900
|
scan_interval: 900
|
||||||
name: instagram_medieval_follows
|
name: instagram_medieval_follows
|
||||||
resource: !secret instagram_medieval
|
resource: !secret instagram_medieval
|
||||||
value_template: '{{ value_json.data.counts.follows }}'
|
value_template: '{{ value_json.follows_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 600
|
scan_interval: 600
|
||||||
name: instagram_medieval_media
|
name: instagram_medieval_media
|
||||||
resource: !secret instagram_medieval
|
resource: !secret instagram_medieval
|
||||||
value_template: '{{ value_json.data.counts.media }}'
|
value_template: '{{ value_json.media_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
|
|
||||||
# lotte
|
# lotte
|
||||||
@ -182,20 +182,20 @@
|
|||||||
scan_interval: 300
|
scan_interval: 300
|
||||||
name: instagram_lotte
|
name: instagram_lotte
|
||||||
resource: !secret instagram_lotte
|
resource: !secret instagram_lotte
|
||||||
value_template: '{{ value_json.data.counts.followed_by }}'
|
value_template: '{{ value_json.followers_count }}'
|
||||||
unit_of_measurement: Followers
|
unit_of_measurement: Followers
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 900
|
scan_interval: 900
|
||||||
name: instagram_lotte_follows
|
name: instagram_lotte_follows
|
||||||
resource: !secret instagram_lotte
|
resource: !secret instagram_lotte
|
||||||
value_template: '{{ value_json.data.counts.follows }}'
|
value_template: '{{ value_json.follows_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 600
|
scan_interval: 600
|
||||||
name: instagram_lotte_media
|
name: instagram_lotte_media
|
||||||
resource: !secret instagram_lotte
|
resource: !secret instagram_lotte
|
||||||
value_template: '{{ value_json.data.counts.media }}'
|
value_template: '{{ value_json.media_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
|
|
||||||
# codedwithlove
|
# codedwithlove
|
||||||
@ -203,20 +203,20 @@
|
|||||||
scan_interval: 300
|
scan_interval: 300
|
||||||
name: instagram_codedwithlove
|
name: instagram_codedwithlove
|
||||||
resource: !secret instagram_codedwithlove
|
resource: !secret instagram_codedwithlove
|
||||||
value_template: '{{ value_json.data.counts.followed_by }}'
|
value_template: '{{ value_json.followers_count }}'
|
||||||
unit_of_measurement: Followers
|
unit_of_measurement: Followers
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 900
|
scan_interval: 900
|
||||||
name: instagram_codedwithlove_follows
|
name: instagram_codedwithlove_follows
|
||||||
resource: !secret instagram_codedwithlove
|
resource: !secret instagram_codedwithlove
|
||||||
value_template: '{{ value_json.data.counts.follows }}'
|
value_template: '{{ value_json.follows_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
- platform: rest
|
- platform: rest
|
||||||
scan_interval: 600
|
scan_interval: 600
|
||||||
name: instagram_codedwithlove_media
|
name: instagram_codedwithlove_media
|
||||||
resource: !secret instagram_codedwithlove
|
resource: !secret instagram_codedwithlove
|
||||||
value_template: '{{ value_json.data.counts.media }}'
|
value_template: '{{ value_json.media_count }}'
|
||||||
force_update: true
|
force_update: true
|
||||||
|
|
||||||
# TikTok
|
# TikTok
|
||||||
|
@ -13,3 +13,12 @@
|
|||||||
command_or_param: !secret desktop_mac
|
command_or_param: !secret desktop_mac
|
||||||
turn_off:
|
turn_off:
|
||||||
service: script.dummy
|
service: script.dummy
|
||||||
|
|
||||||
|
onair_lamp_recording:
|
||||||
|
value_template: "{{ is_state('input_boolean.onair_lamp_recording', 'on') }}"
|
||||||
|
turn_on:
|
||||||
|
- service: input_boolean.turn_on
|
||||||
|
entity_id: input_boolean.onair_lamp_recording
|
||||||
|
turn_off:
|
||||||
|
- service: input_boolean.turn_off
|
||||||
|
entity_id: input_boolean.onair_lamp_recording
|
@ -16,3 +16,22 @@ cards:
|
|||||||
- type: light
|
- type: light
|
||||||
entity: light.esstisch
|
entity: light.esstisch
|
||||||
name: Esstisch
|
name: Esstisch
|
||||||
|
- type: light
|
||||||
|
entity: light.tint_rgb_gu10_1
|
||||||
|
name: Kinderbad
|
||||||
|
|
||||||
|
- type: vertical-stack
|
||||||
|
title: Studio
|
||||||
|
cards:
|
||||||
|
- type: light
|
||||||
|
entity: light.onair
|
||||||
|
name: Studio-Treppe
|
||||||
|
- type: entities
|
||||||
|
show_header_toggle: false
|
||||||
|
entities:
|
||||||
|
- entity: switch.onair_lamp_recording
|
||||||
|
name: Studio Aufnahme
|
||||||
|
icon: mdi:camera-rear
|
||||||
|
- entity: switch.osram_plug_01_57b6060a_on_off
|
||||||
|
name: Studio Ringlicht
|
||||||
|
icon: mdi:checkbox-blank-circle-outline
|
@ -35,7 +35,7 @@ http:
|
|||||||
ip_ban_enabled: true
|
ip_ban_enabled: true
|
||||||
login_attempts_threshold: 5
|
login_attempts_threshold: 5
|
||||||
use_x_forwarded_for: true
|
use_x_forwarded_for: true
|
||||||
base_url: https://hass.f-brinker.de
|
base_url: !secret url_base
|
||||||
trusted_proxies:
|
trusted_proxies:
|
||||||
- 127.0.0.1
|
- 127.0.0.1
|
||||||
- ::1
|
- ::1
|
||||||
@ -58,7 +58,7 @@ zha:
|
|||||||
emulated_roku:
|
emulated_roku:
|
||||||
servers:
|
servers:
|
||||||
- name: Home Assistant
|
- name: Home Assistant
|
||||||
listen_port: 8060
|
listen_port: !secret roku_port
|
||||||
|
|
||||||
mqtt:
|
mqtt:
|
||||||
broker: !secret mqtt_broker_ip
|
broker: !secret mqtt_broker_ip
|
||||||
@ -87,12 +87,6 @@ notify:
|
|||||||
- name: telegram_fb
|
- name: telegram_fb
|
||||||
platform: telegram
|
platform: telegram
|
||||||
chat_id: !secret telegram_chat_fb
|
chat_id: !secret telegram_chat_fb
|
||||||
- platform: command_line
|
|
||||||
name: alexa_kitchen
|
|
||||||
command: "/config/alexa_wrapper.sh -d 'Küche'"
|
|
||||||
- platform: command_line
|
|
||||||
name: alexa_livingroom
|
|
||||||
command: "/config/alexa_wrapper.sh -d 'Wohnzimmer'"
|
|
||||||
- platform: command_line
|
- platform: command_line
|
||||||
name: alexa_all
|
name: alexa_all
|
||||||
command: "/config/alexa_wrapper.sh -d 'ALL'"
|
command: "/config/alexa_wrapper.sh -d 'ALL'"
|
||||||
@ -141,10 +135,23 @@ ffmpeg:
|
|||||||
ffmpeg_bin: /usr/bin/ffmpeg
|
ffmpeg_bin: /usr/bin/ffmpeg
|
||||||
|
|
||||||
input_datetime:
|
input_datetime:
|
||||||
bedroom_alarm_clock_time:
|
bedroom_alarm_clock_time:
|
||||||
name: Wecker
|
name: Wecker
|
||||||
has_date: false
|
has_date: false
|
||||||
has_time: true
|
has_time: true
|
||||||
|
|
||||||
|
input_number:
|
||||||
|
instagram_counter_brightness:
|
||||||
|
name: Instagram Counter Brightness
|
||||||
|
initial: 15
|
||||||
|
min: 0
|
||||||
|
max: 15
|
||||||
|
step: 1
|
||||||
|
|
||||||
|
input_boolean:
|
||||||
|
onair_lamp_recording:
|
||||||
|
name: onAir Recording Status
|
||||||
|
initial: false
|
||||||
|
|
||||||
alarm_control_panel:
|
alarm_control_panel:
|
||||||
- platform: manual
|
- platform: manual
|
||||||
@ -152,22 +159,26 @@ alarm_control_panel:
|
|||||||
code: !secret alarm_code
|
code: !secret alarm_code
|
||||||
code_arm_required: true
|
code_arm_required: true
|
||||||
delay_time: 20
|
delay_time: 20
|
||||||
pending_time: 30
|
arming_time: 30
|
||||||
trigger_time: 120
|
trigger_time: 120
|
||||||
disarm_after_trigger: false
|
disarm_after_trigger: false
|
||||||
disarmed:
|
disarmed:
|
||||||
trigger_time: 0
|
trigger_time: 0
|
||||||
armed_home:
|
armed_home:
|
||||||
pending_time: 0
|
arming_time: 0
|
||||||
delay_time: 0
|
delay_time: 0
|
||||||
armed_night:
|
armed_night:
|
||||||
pending_time: 0
|
arming_time: 0
|
||||||
delay_time: 0
|
delay_time: 0
|
||||||
|
|
||||||
shell_command:
|
shell_command:
|
||||||
ssh: 'ssh -o "StrictHostKeyChecking=no" -i {{ sshkey }} {{ host }} -l {{ user }} -p {{ port }} {{ command_or_param }}'
|
ssh: 'ssh -o "StrictHostKeyChecking=no" -i {{ sshkey }} {{ host }} -l {{ user }} -p {{ port }} {{ command_or_param }}'
|
||||||
|
|
||||||
rest_command:
|
rest_command:
|
||||||
|
instagram_counter_brightness:
|
||||||
|
url: !secret url_instagram_counter_brightness
|
||||||
|
method: GET
|
||||||
|
payload: 'value={{ value }}'
|
||||||
shinobi_monitorstates:
|
shinobi_monitorstates:
|
||||||
url: "https://{{ host }}/{{ apikey }}/monitorStates/{{ group }}/{{ preset_name }}"
|
url: "https://{{ host }}/{{ apikey }}/monitorStates/{{ group }}/{{ preset_name }}"
|
||||||
|
|
||||||
@ -185,10 +196,10 @@ spotify:
|
|||||||
client_id: !secret spotify_client_id
|
client_id: !secret spotify_client_id
|
||||||
client_secret: !secret spotify_client_secret
|
client_secret: !secret spotify_client_secret
|
||||||
|
|
||||||
tplink:
|
#tplink:
|
||||||
discovery: false
|
# discovery: false
|
||||||
switch:
|
# switch:
|
||||||
- host: !secret tplink_ip
|
# - host: !secret tplink_ip
|
||||||
|
|
||||||
# External config files
|
# External config files
|
||||||
alert: !include_dir_merge_named config/alerts/
|
alert: !include_dir_merge_named config/alerts/
|
||||||
|
1
custom_components/reolink_dev/ReolinkPyPi/__init__.py
Normal file
1
custom_components/reolink_dev/ReolinkPyPi/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Reolink Camera component for HomeAssistant."""
|
Binary file not shown.
Binary file not shown.
278
custom_components/reolink_dev/ReolinkPyPi/camera.py
Normal file
278
custom_components/reolink_dev/ReolinkPyPi/camera.py
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
"""
|
||||||
|
Reolink Camera API
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class ReolinkApi(object):
|
||||||
|
def __init__(self, ip, channel):
|
||||||
|
self._url = "http://" + ip + "/cgi-bin/api.cgi"
|
||||||
|
self._ip = ip
|
||||||
|
self._channel = channel
|
||||||
|
self._token = None
|
||||||
|
self._motion_state = False
|
||||||
|
self._last_motion = 0
|
||||||
|
self._device_info = None
|
||||||
|
self._motion_state = None
|
||||||
|
self._ftp_state = None
|
||||||
|
self._email_state = None
|
||||||
|
self._ir_state = None
|
||||||
|
self._rtspport = None
|
||||||
|
self._rtmpport = None
|
||||||
|
self._ptzpresets = dict()
|
||||||
|
|
||||||
|
def status(self):
|
||||||
|
if self._token is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
param_channel = {"channel": self._channel}
|
||||||
|
body = [{"cmd": "GetDevInfo", "action":1, "param": param_channel},
|
||||||
|
{"cmd": "GetNetPort", "action": 1, "param": param_channel},
|
||||||
|
{"cmd": "GetFtp", "action": 1, "param": param_channel},
|
||||||
|
{"cmd": "GetEmail", "action": 1, "param": param_channel},
|
||||||
|
{"cmd": "GetIrLights", "action": 1, "param": param_channel},
|
||||||
|
{"cmd": "GetPtzPreset", "action": 1, "param": param_channel}]
|
||||||
|
|
||||||
|
param = {"token": self._token}
|
||||||
|
response = self.send(body, param)
|
||||||
|
|
||||||
|
try:
|
||||||
|
json_data = json.loads(response.text)
|
||||||
|
except:
|
||||||
|
_LOGGER.error(f"Error translating response to json")
|
||||||
|
return
|
||||||
|
|
||||||
|
for data in json_data:
|
||||||
|
try:
|
||||||
|
if data["cmd"] == "GetDevInfo":
|
||||||
|
self._device_info = data
|
||||||
|
|
||||||
|
elif data["cmd"] == "GetNetPort":
|
||||||
|
self._netport_settings = data
|
||||||
|
self._rtspport = data["value"]["NetPort"]["rtspPort"]
|
||||||
|
self._rtmpport = data["value"]["NetPort"]["rtmpPort"]
|
||||||
|
|
||||||
|
elif data["cmd"] == "GetFtp":
|
||||||
|
self._ftp_settings = data
|
||||||
|
if (data["value"]["Ftp"]["schedule"]["enable"] == 1):
|
||||||
|
self._ftp_state = True
|
||||||
|
else:
|
||||||
|
self._ftp_state = False
|
||||||
|
|
||||||
|
elif data["cmd"] == "GetEmail":
|
||||||
|
self._email_settings = data
|
||||||
|
if (data["value"]["Email"]["schedule"]["enable"] == 1):
|
||||||
|
self._email_state = True
|
||||||
|
else:
|
||||||
|
self._email_state = False
|
||||||
|
|
||||||
|
elif data["cmd"] == "GetIrLights":
|
||||||
|
self._ir_settings = data
|
||||||
|
if (data["value"]["IrLights"]["state"] == "Auto"):
|
||||||
|
self._ir_state = True
|
||||||
|
else:
|
||||||
|
self._ir_state = False
|
||||||
|
|
||||||
|
elif data["cmd"] == "GetPtzPreset":
|
||||||
|
self._ptzpresets_settings = data
|
||||||
|
for preset in data["value"]["PtzPreset"]:
|
||||||
|
if int(preset["enable"]) == 1:
|
||||||
|
preset_name = preset["name"]
|
||||||
|
preset_id = int(preset["id"])
|
||||||
|
self._ptzpresets[preset_name] = preset_id
|
||||||
|
_LOGGER.debug(f"Got preset {preset_name} with ID {preset_id}")
|
||||||
|
else:
|
||||||
|
_LOGGER.debug(f"Preset is not enabled: {preset}")
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
@property
|
||||||
|
def motion_state(self):
|
||||||
|
body = [{"cmd": "GetMdState", "action": 0, "param":{"channel":self._channel}}]
|
||||||
|
param = {"token": self._token}
|
||||||
|
|
||||||
|
response = self.send(body, param)
|
||||||
|
|
||||||
|
try:
|
||||||
|
json_data = json.loads(response.text)
|
||||||
|
|
||||||
|
if json_data is None:
|
||||||
|
_LOGGER.error(f"Unable to get Motion detection state at IP {self._ip}")
|
||||||
|
self._motion_state = False
|
||||||
|
return self._motion_state
|
||||||
|
|
||||||
|
if json_data[0]["value"]["state"] == 1:
|
||||||
|
self._motion_state = True
|
||||||
|
self._last_motion = datetime.datetime.now()
|
||||||
|
else:
|
||||||
|
self._motion_state = False
|
||||||
|
except:
|
||||||
|
self._motion_state = False
|
||||||
|
|
||||||
|
return self._motion_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def still_image(self):
|
||||||
|
response = self.send(None, f"?cmd=Snap&channel={self._channel}&token={self._token}", stream=True)
|
||||||
|
response.raw.decode_content = True
|
||||||
|
return response.raw
|
||||||
|
|
||||||
|
@property
|
||||||
|
def snapshot(self):
|
||||||
|
response = self.send(None, f"?cmd=Snap&channel={self._channel}&token={self._token}", stream=False)
|
||||||
|
return response.content
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ftp_state(self):
|
||||||
|
return self._ftp_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def email_state(self):
|
||||||
|
return self._email_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ir_state(self):
|
||||||
|
return self._ir_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rtmpport(self):
|
||||||
|
return self._rtmpport
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rtspport(self):
|
||||||
|
return self._rtspport
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_motion(self):
|
||||||
|
return self._last_motion
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ptzpresets(self):
|
||||||
|
return self._ptzpresets
|
||||||
|
|
||||||
|
def login(self, username, password):
|
||||||
|
body = [{"cmd": "Login", "action": 0, "param": {"User": {"userName": username, "password": password}}}]
|
||||||
|
param = {"cmd": "Login", "token": "null"}
|
||||||
|
|
||||||
|
response = self.send(body, param)
|
||||||
|
|
||||||
|
try:
|
||||||
|
json_data = json.loads(response.text)
|
||||||
|
except:
|
||||||
|
_LOGGER.error(f"Error translating login response to json")
|
||||||
|
return
|
||||||
|
|
||||||
|
if json_data is not None:
|
||||||
|
if json_data[0]["code"] == 0:
|
||||||
|
self._token = json_data[0]["value"]["Token"]["name"]
|
||||||
|
_LOGGER.info(f"Reolink camera logged in at IP {self._ip}")
|
||||||
|
else:
|
||||||
|
_LOGGER.error(f"Failed to login at IP {self._ip}. No token available")
|
||||||
|
else:
|
||||||
|
_LOGGER.error(f"Failed to login at IP {self._ip}. Connection error.")
|
||||||
|
|
||||||
|
def logout(self):
|
||||||
|
body = [{"cmd":"Logout","action":0,"param":{}}]
|
||||||
|
param = {"cmd": "Logout", "token": self._token}
|
||||||
|
|
||||||
|
self.send(body, param)
|
||||||
|
|
||||||
|
def set_ftp(self, enabled):
|
||||||
|
self.status()
|
||||||
|
|
||||||
|
if not self._ftp_settings:
|
||||||
|
_LOGGER.error("Error while fetching current FTP settings")
|
||||||
|
return
|
||||||
|
|
||||||
|
if enabled == True:
|
||||||
|
newValue = 1
|
||||||
|
else:
|
||||||
|
newValue = 0
|
||||||
|
|
||||||
|
body = [{"cmd":"SetFtp","action":0,"param": self._ftp_settings["value"] }]
|
||||||
|
body[0]["param"]["Ftp"]["schedule"]["enable"] = newValue
|
||||||
|
|
||||||
|
response = self.send(body, {"cmd": "SetFtp", "token": self._token} )
|
||||||
|
try:
|
||||||
|
json_data = json.loads(response.text)
|
||||||
|
if json_data[0]["value"]["rspCode"] == 200:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except:
|
||||||
|
_LOGGER.error(f"Error translating FTP response to json")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def set_email(self, enabled):
|
||||||
|
self.status()
|
||||||
|
|
||||||
|
if not self._email_settings:
|
||||||
|
_LOGGER.error("Error while fetching current email settings")
|
||||||
|
return
|
||||||
|
|
||||||
|
if enabled == True:
|
||||||
|
newValue = 1
|
||||||
|
else:
|
||||||
|
newValue = 0
|
||||||
|
|
||||||
|
body = [{"cmd":"SetEmail","action":0,"param": self._email_settings["value"] }]
|
||||||
|
body[0]["param"]["Email"]["schedule"]["enable"] = newValue
|
||||||
|
|
||||||
|
response = self.send(body, {"cmd": "SetEmail", "token": self._token} )
|
||||||
|
try:
|
||||||
|
json_data = json.loads(response.text)
|
||||||
|
if json_data[0]["value"]["rspCode"] == 200:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except:
|
||||||
|
_LOGGER.error(f"Error translating Email response to json")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def set_ir_lights(self, enabled):
|
||||||
|
self.status()
|
||||||
|
|
||||||
|
if not self._ir_settings:
|
||||||
|
_LOGGER.error("Error while fetching current IR light settings")
|
||||||
|
return
|
||||||
|
|
||||||
|
if enabled == True:
|
||||||
|
newValue = "Auto"
|
||||||
|
else:
|
||||||
|
newValue = "Off"
|
||||||
|
|
||||||
|
body = [{"cmd":"SetIrLights","action":0,"param": self._ir_settings["value"] }]
|
||||||
|
body[0]["param"]["IrLights"]["state"] = newValue
|
||||||
|
|
||||||
|
response = self.send(body, {"cmd": "SetIrLights", "token": self._token} )
|
||||||
|
try:
|
||||||
|
json_data = json.loads(response.text)
|
||||||
|
if json_data[0]["value"]["rspCode"] == 200:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
_LOGGER.error(f"Error translating IR Lights response to json")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send(self, body, param, stream=False):
|
||||||
|
try:
|
||||||
|
if (self._token is None and
|
||||||
|
(body is None or body[0]["cmd"] != "Login")):
|
||||||
|
_LOGGER.info(f"Reolink camera at IP {self._ip} is not logged in")
|
||||||
|
return
|
||||||
|
|
||||||
|
if body is None:
|
||||||
|
response = requests.get(self._url, params=param, stream=stream)
|
||||||
|
else:
|
||||||
|
response = requests.post(self._url, data=json.dumps(body), params=param)
|
||||||
|
|
||||||
|
return response
|
||||||
|
except Exception:
|
||||||
|
_LOGGER.error(f"Exception while calling Reolink camera API at ip {self._ip}")
|
||||||
|
return None
|
||||||
|
|
1
custom_components/reolink_dev/__init__.py
Normal file
1
custom_components/reolink_dev/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Reolink Camera component for HomeAssistant."""
|
Binary file not shown.
BIN
custom_components/reolink_dev/__pycache__/camera.cpython-37.pyc
Normal file
BIN
custom_components/reolink_dev/__pycache__/camera.cpython-37.pyc
Normal file
Binary file not shown.
312
custom_components/reolink_dev/camera.py
Normal file
312
custom_components/reolink_dev/camera.py
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
"""This component provides basic support for Reolink IP cameras."""
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
import voluptuous as vol
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA, SUPPORT_STREAM, ENTITY_IMAGE_URL
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
|
from haffmpeg.camera import CameraMjpeg
|
||||||
|
from homeassistant.components.ffmpeg import DATA_FFMPEG
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
|
||||||
|
from custom_components.reolink_dev.ReolinkPyPi.camera import ReolinkApi
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
STATE_MOTION = "motion"
|
||||||
|
STATE_NO_MOTION = "no_motion"
|
||||||
|
STATE_IDLE = "idle"
|
||||||
|
|
||||||
|
DEFAULT_NAME = "Reolink Camera"
|
||||||
|
DEFAULT_STREAM = "main"
|
||||||
|
DEFAULT_PROTOCOL = "rtmp"
|
||||||
|
DEFAULT_CHANNEL = 0
|
||||||
|
CONF_STREAM = "stream"
|
||||||
|
CONF_PROTOCOL = "protocol"
|
||||||
|
CONF_CHANNEL = "channel"
|
||||||
|
DOMAIN = "camera"
|
||||||
|
SERVICE_ENABLE_FTP = 'enable_ftp'
|
||||||
|
SERVICE_DISABLE_FTP = 'disable_ftp'
|
||||||
|
SERVICE_ENABLE_EMAIL = 'enable_email'
|
||||||
|
SERVICE_DISABLE_EMAIL = 'disable_email'
|
||||||
|
SERVICE_ENABLE_IR_LIGHTS = 'enable_ir_lights'
|
||||||
|
SERVICE_DISABLE_IR_LIGHTS = 'disable_ir_lights'
|
||||||
|
|
||||||
|
DEFAULT_BRAND = 'Reolink'
|
||||||
|
DOMAIN_DATA = 'reolink_devices'
|
||||||
|
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_HOST): cv.string,
|
||||||
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
|
vol.Required(CONF_USERNAME): cv.string,
|
||||||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_STREAM, default=DEFAULT_STREAM): vol.In(["main", "sub"]),
|
||||||
|
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.In(["rtmp", "rtsp"]),
|
||||||
|
vol.Optional(CONF_CHANNEL, default=DEFAULT_CHANNEL): cv.positive_int,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
|
"""Set up a Reolink IP Camera."""
|
||||||
|
|
||||||
|
host = config.get(CONF_HOST)
|
||||||
|
username = config.get(CONF_USERNAME)
|
||||||
|
password = config.get(CONF_PASSWORD)
|
||||||
|
stream = config.get(CONF_STREAM)
|
||||||
|
protocol = config.get(CONF_PROTOCOL)
|
||||||
|
channel = config.get(CONF_CHANNEL)
|
||||||
|
name = config.get(CONF_NAME)
|
||||||
|
|
||||||
|
session = ReolinkApi(host, channel)
|
||||||
|
session.login(username, password)
|
||||||
|
|
||||||
|
async_add_devices([ReolinkCamera(hass, session, host, username, password, stream, protocol, channel, name)], update_before_add=True)
|
||||||
|
|
||||||
|
# Event enable FTP
|
||||||
|
def handler_enable_ftp(call):
|
||||||
|
component = hass.data.get(DOMAIN)
|
||||||
|
entity = component.get_entity(call.data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
|
if entity:
|
||||||
|
entity.enable_ftp_upload()
|
||||||
|
hass.services.async_register(DOMAIN, SERVICE_ENABLE_FTP, handler_enable_ftp)
|
||||||
|
|
||||||
|
# Event disable FTP
|
||||||
|
def handler_disable_ftp(call):
|
||||||
|
component = hass.data.get(DOMAIN)
|
||||||
|
entity = component.get_entity(call.data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
|
if entity:
|
||||||
|
entity.disable_ftp_upload()
|
||||||
|
|
||||||
|
hass.services.async_register(DOMAIN, SERVICE_DISABLE_FTP, handler_disable_ftp)
|
||||||
|
|
||||||
|
# Event enable email
|
||||||
|
def handler_enable_email(call):
|
||||||
|
component = hass.data.get(DOMAIN)
|
||||||
|
entity = component.get_entity(call.data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
|
if entity:
|
||||||
|
entity.enable_email()
|
||||||
|
hass.services.async_register(DOMAIN, SERVICE_ENABLE_EMAIL, handler_enable_email)
|
||||||
|
|
||||||
|
# Event disable email
|
||||||
|
def handler_disable_email(call):
|
||||||
|
component = hass.data.get(DOMAIN)
|
||||||
|
entity = component.get_entity(call.data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
|
if entity:
|
||||||
|
entity.disable_email()
|
||||||
|
|
||||||
|
hass.services.async_register(DOMAIN, SERVICE_DISABLE_EMAIL, handler_disable_email)
|
||||||
|
|
||||||
|
# Event enable ir lights
|
||||||
|
def handler_enable_ir_lights(call):
|
||||||
|
component = hass.data.get(DOMAIN)
|
||||||
|
entity = component.get_entity(call.data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
|
if entity:
|
||||||
|
entity.enable_ir_lights()
|
||||||
|
hass.services.async_register(DOMAIN, SERVICE_ENABLE_IR_LIGHTS, handler_enable_ir_lights)
|
||||||
|
|
||||||
|
# Event disable ir lights
|
||||||
|
def handler_disable_ir_lights(call):
|
||||||
|
component = hass.data.get(DOMAIN)
|
||||||
|
entity = component.get_entity(call.data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
|
if entity:
|
||||||
|
entity.disable_ir_lights()
|
||||||
|
|
||||||
|
hass.services.async_register(DOMAIN, SERVICE_DISABLE_IR_LIGHTS, handler_disable_ir_lights)
|
||||||
|
|
||||||
|
|
||||||
|
class ReolinkCamera(Camera):
|
||||||
|
"""An implementation of a Reolink IP camera."""
|
||||||
|
|
||||||
|
def __init__(self, hass, session, host, username, password, stream, protocol, channel, name):
|
||||||
|
"""Initialize a Reolink camera."""
|
||||||
|
|
||||||
|
super().__init__()
|
||||||
|
self._host = host
|
||||||
|
self._username = username
|
||||||
|
self._password = password
|
||||||
|
self._stream = stream
|
||||||
|
self._protocol = protocol
|
||||||
|
self._channel = channel
|
||||||
|
self._name = name
|
||||||
|
self._reolinkSession = session
|
||||||
|
self._hass = hass
|
||||||
|
self._manager = self._hass.data[DATA_FFMPEG]
|
||||||
|
|
||||||
|
self._last_update = 0
|
||||||
|
self._last_image = None
|
||||||
|
self._last_motion = 0
|
||||||
|
self._ftp_state = None
|
||||||
|
self._email_state = None
|
||||||
|
self._ir_state = None
|
||||||
|
self._ptzpresets = dict()
|
||||||
|
self._state = STATE_IDLE
|
||||||
|
|
||||||
|
self._hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, self.disconnect)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state_attributes(self):
|
||||||
|
"""Return the camera state attributes."""
|
||||||
|
attrs = {"access_token": self.access_tokens[-1]}
|
||||||
|
|
||||||
|
if self._last_motion:
|
||||||
|
attrs["last_motion"] = self._last_motion
|
||||||
|
|
||||||
|
if self._last_update:
|
||||||
|
attrs["last_update"] = self._last_update
|
||||||
|
|
||||||
|
attrs["ftp_enabled"] = self._ftp_state
|
||||||
|
attrs["email_enabled"] = self._email_state
|
||||||
|
attrs["ir_lights_enabled"] = self._ir_state
|
||||||
|
attrs["ptzpresets"] = self._ptzpresets
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Return supported features."""
|
||||||
|
return SUPPORT_STREAM
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of this camera."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Polling needed for the device status."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ftp_state(self):
|
||||||
|
"""Camera Motion recording Status."""
|
||||||
|
return self._ftp_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def email_state(self):
|
||||||
|
"""Camera email Status."""
|
||||||
|
return self._email_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ptzpresets(self):
|
||||||
|
"""Camera PTZ presets list."""
|
||||||
|
return self._ptzpresets
|
||||||
|
|
||||||
|
async def stream_source(self):
|
||||||
|
"""Return the source of the stream."""
|
||||||
|
if self._protocol == "rtsp":
|
||||||
|
rtspChannel = f"{self._channel+1:02d}"
|
||||||
|
stream_source = f"rtsp://{self._username}:{self._password}@{self._host}:{self._reolinkSession.rtspport}/h264Preview_{rtspChannel}_{self._stream}"
|
||||||
|
else:
|
||||||
|
stream_source = f"rtmp://{self._host}:{self._reolinkSession.rtmpport}/bcs/channel{self._channel}_{self._stream}.bcs?channel={self._channel}&stream=0&user={self._username}&password={self._password}"
|
||||||
|
|
||||||
|
return stream_source
|
||||||
|
|
||||||
|
async def handle_async_mjpeg_stream(self, request):
|
||||||
|
"""Generate an HTTP MJPEG stream from the camera."""
|
||||||
|
stream_source = await self.stream_source()
|
||||||
|
|
||||||
|
stream = CameraMjpeg(self._manager.binary, loop=self._hass.loop)
|
||||||
|
await stream.open_camera(stream_source)
|
||||||
|
|
||||||
|
try:
|
||||||
|
stream_reader = await stream.get_reader()
|
||||||
|
return await async_aiohttp_proxy_stream(
|
||||||
|
self._hass,
|
||||||
|
request,
|
||||||
|
stream_reader,
|
||||||
|
self._manager.ffmpeg_stream_content_type,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
await stream.close()
|
||||||
|
|
||||||
|
def camera_image(self):
|
||||||
|
"""Return bytes of camera image."""
|
||||||
|
return self._reolinkSession.still_image
|
||||||
|
|
||||||
|
async def async_camera_image(self):
|
||||||
|
"""Return a still image response from the camera."""
|
||||||
|
return self._reolinkSession.snapshot
|
||||||
|
|
||||||
|
def enable_ftp_upload(self):
|
||||||
|
"""Enable motion ftp recording in camera."""
|
||||||
|
if self._reolinkSession.set_ftp(True):
|
||||||
|
self._ftp_state = True
|
||||||
|
self._hass.states.set(self.entity_id, self.state, self.state_attributes)
|
||||||
|
|
||||||
|
def disable_ftp_upload(self):
|
||||||
|
"""Disable motion ftp recording."""
|
||||||
|
if self._reolinkSession.set_ftp(False):
|
||||||
|
self._ftp_state = False
|
||||||
|
self._hass.states.set(self.entity_id, self.state, self.state_attributes)
|
||||||
|
|
||||||
|
def enable_email(self):
|
||||||
|
"""Enable email motion detection in camera."""
|
||||||
|
if self._reolinkSession.set_email(True):
|
||||||
|
self._email_state = True
|
||||||
|
self._hass.states.set(self.entity_id, self.state, self.state_attributes)
|
||||||
|
|
||||||
|
def disable_email(self):
|
||||||
|
"""Disable email motion detection."""
|
||||||
|
if self._reolinkSession.set_email(False):
|
||||||
|
self._email_state = False
|
||||||
|
self._hass.states.set(self.entity_id, self.state, self.state_attributes)
|
||||||
|
|
||||||
|
def enable_ir_lights(self):
|
||||||
|
"""Enable IR lights."""
|
||||||
|
if self._reolinkSession.set_ir_lights(True):
|
||||||
|
self._ir_state = True
|
||||||
|
self._hass.states.set(self.entity_id, self.state, self.state_attributes)
|
||||||
|
|
||||||
|
def disable_ir_lights(self):
|
||||||
|
"""Disable IR lights."""
|
||||||
|
if self._reolinkSession.set_ir_lights(False):
|
||||||
|
self._ir_state = False
|
||||||
|
self._hass.states.set(self.entity_id, self.state, self.state_attributes)
|
||||||
|
|
||||||
|
async def update_motion_state(self):
|
||||||
|
if self._reolinkSession.motion_state == True:
|
||||||
|
self._state = STATE_MOTION
|
||||||
|
self._last_motion = self._reolinkSession.last_motion
|
||||||
|
else:
|
||||||
|
self._state = STATE_NO_MOTION
|
||||||
|
|
||||||
|
async def update_status(self):
|
||||||
|
self._reolinkSession.status()
|
||||||
|
|
||||||
|
self._last_update = datetime.datetime.now()
|
||||||
|
self._ftp_state = self._reolinkSession.ftp_state
|
||||||
|
self._email_state = self._reolinkSession.email_state
|
||||||
|
self._ir_state = self._reolinkSession.ir_state
|
||||||
|
self._ptzpresets = self._reolinkSession.ptzpresets
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Update the data from the camera."""
|
||||||
|
try:
|
||||||
|
self._hass.loop.create_task(self.update_motion_state())
|
||||||
|
|
||||||
|
if (self._last_update == 0 or
|
||||||
|
(datetime.datetime.now() - self._last_update).total_seconds() >= 30):
|
||||||
|
self._hass.loop.create_task(self.update_status())
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
_LOGGER.error("Got exception while fetching the state: %s", ex)
|
||||||
|
|
||||||
|
def disconnect(self, event):
|
||||||
|
_LOGGER.info("Disconnecting from Reolink camera")
|
||||||
|
self._reolinkSession.logout()
|
8
custom_components/reolink_dev/manifest.json
Normal file
8
custom_components/reolink_dev/manifest.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"domain": "reolink_dev",
|
||||||
|
"name": "Reolink IP camera",
|
||||||
|
"documentation": "https://www.example.com",
|
||||||
|
"dependencies": ["ffmpeg"],
|
||||||
|
"codeowners": ["@fwestenberg"],
|
||||||
|
"requirements": ["aiosmtpd==1.2"]
|
||||||
|
}
|
41
custom_components/reolink_dev/services.yaml
Normal file
41
custom_components/reolink_dev/services.yaml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
enable_ftp:
|
||||||
|
description: Enable FTP upload on motion recording.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name of the Reolink camera entity to set.
|
||||||
|
example: 'camera.frontdoor'
|
||||||
|
|
||||||
|
disable_ftp:
|
||||||
|
description: Disable FTP upload on motion recording.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name of the Reolink camera entity to set.
|
||||||
|
example: 'camera.frontdoor'
|
||||||
|
|
||||||
|
enable_email:
|
||||||
|
description: Enable email functionality on motion detection.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name of the Reolink camera entity to set.
|
||||||
|
example: 'camera.frontdoor'
|
||||||
|
|
||||||
|
disable_email:
|
||||||
|
description: Disable email functionality on motion detection.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name of the Reolink camera entity to set.
|
||||||
|
example: 'camera.frontdoor'
|
||||||
|
|
||||||
|
enable_ir_lights:
|
||||||
|
description: Enable the infrared lights (nightvision) of the Reolink camera.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name of the Reolink camera entity to set.
|
||||||
|
example: 'camera.frontdoor'
|
||||||
|
|
||||||
|
disable_ir_lights:
|
||||||
|
description: Disable the infrared lights (nightvision) of the Reolink camera.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name of the Reolink camera entity to set.
|
||||||
|
example: 'camera.frontdoor'
|
@ -212,21 +212,21 @@ input_boolean:
|
|||||||
|
|
||||||
# Automations #######################################################
|
# Automations #######################################################
|
||||||
automation:
|
automation:
|
||||||
- id: "landroid_status_notify"
|
# - id: "landroid_status_notify"
|
||||||
alias: "Landroid Status Notification"
|
# alias: "Landroid Status Notification"
|
||||||
initial_state: true
|
# initial_state: true
|
||||||
trigger:
|
# trigger:
|
||||||
- platform: state
|
# - platform: state
|
||||||
entity_id: sensor.landroid_hans_dieter_status
|
# entity_id: sensor.landroid_hans_dieter_status
|
||||||
condition:
|
# condition:
|
||||||
- condition: template
|
# - condition: template
|
||||||
value_template: "{{ trigger.to_state.state != trigger.from_state.state }}"
|
# value_template: "{{ trigger.to_state.state != trigger.from_state.state }}"
|
||||||
action:
|
# action:
|
||||||
- service: persistent_notification.create
|
# - service: persistent_notification.create
|
||||||
data_template:
|
# data_template:
|
||||||
title: Lanroid Status
|
# title: Landroid Status
|
||||||
message: "{{ trigger.from_state.state }} -> {{ trigger.to_state.state }} - {{ states('sensor.date_time') }}"
|
# message: "{{ trigger.from_state.state }} -> {{ trigger.to_state.state }} - {{ states('sensor.date_time') }}"
|
||||||
|
#
|
||||||
- id: "landroid_error_notify"
|
- id: "landroid_error_notify"
|
||||||
alias: "Landroid Error Notification"
|
alias: "Landroid Error Notification"
|
||||||
initial_state: true
|
initial_state: true
|
||||||
@ -239,7 +239,7 @@ automation:
|
|||||||
action:
|
action:
|
||||||
- service: persistent_notification.create
|
- service: persistent_notification.create
|
||||||
data_template:
|
data_template:
|
||||||
title: Lanroid Status
|
title: Landroid Status
|
||||||
message: "{{ trigger.from_state.state }} -> {{ trigger.to_state.state }} - {{ states('sensor.date_time') }}"
|
message: "{{ trigger.from_state.state }} -> {{ trigger.to_state.state }} - {{ states('sensor.date_time') }}"
|
||||||
|
|
||||||
# Scripts ###########################################################
|
# Scripts ###########################################################
|
||||||
|
@ -8,12 +8,18 @@ adbkey:
|
|||||||
alexa_email:
|
alexa_email:
|
||||||
alexa_password:
|
alexa_password:
|
||||||
|
|
||||||
|
url_base:
|
||||||
|
|
||||||
url_chronograf:
|
url_chronograf:
|
||||||
url_esphome:
|
url_esphome:
|
||||||
|
|
||||||
url_haslave_livingroom:
|
url_haslave_livingroom:
|
||||||
url_haslave_office:
|
url_haslave_office:
|
||||||
|
|
||||||
|
url_instagram_counter_brightness:
|
||||||
|
|
||||||
|
roku_port:
|
||||||
|
|
||||||
# Zones
|
# Zones
|
||||||
home_lat:
|
home_lat:
|
||||||
home_long:
|
home_long:
|
||||||
@ -56,6 +62,13 @@ cam_livingroom_ip:
|
|||||||
cam_livingroom_user:
|
cam_livingroom_user:
|
||||||
cam_livingroom_password:
|
cam_livingroom_password:
|
||||||
|
|
||||||
|
# Robots
|
||||||
|
landroid_mail:
|
||||||
|
landroid_pass:
|
||||||
|
landroid_ip:
|
||||||
|
landroid_sn:
|
||||||
|
landroid_mac:
|
||||||
|
|
||||||
# Weather
|
# Weather
|
||||||
openweathermap:
|
openweathermap:
|
||||||
|
|
||||||
@ -84,17 +97,16 @@ telegram_chat_fb:
|
|||||||
telegram_chat_group:
|
telegram_chat_group:
|
||||||
|
|
||||||
# Social data
|
# Social data
|
||||||
# invite @ https:
|
# App https:
|
||||||
# accept @ https:
|
# Login & Copy the Instagram URLs https:
|
||||||
# wait a while so instagram registers that we are a sandbox user now...
|
|
||||||
# open and authorize https:
|
|
||||||
instagram_beauty:
|
instagram_beauty:
|
||||||
instagram_franky:
|
instagram_franky:
|
||||||
|
|
||||||
instagram_fb:
|
instagram_fb:
|
||||||
instagram_mtb:
|
instagram_mtb:
|
||||||
|
instagram_codedwithlove:
|
||||||
instagram_medieval:
|
instagram_medieval:
|
||||||
instagram_lotte:
|
instagram_lotte:
|
||||||
instagram_codedwithlove:
|
|
||||||
|
|
||||||
youtube_beauty:
|
youtube_beauty:
|
||||||
youtube_mtb:
|
youtube_mtb:
|
||||||
@ -103,3 +115,4 @@ bitly_blog_bb:
|
|||||||
bitly_instagram_bb:
|
bitly_instagram_bb:
|
||||||
bitly_impressum_bb:
|
bitly_impressum_bb:
|
||||||
bitly_youtube_bb:
|
bitly_youtube_bb:
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user