├── .HA_VERSION
├── .gitattributes
├── .gitignore
├── HASS Cheatsheet.md
├── HA_screenshot_1.png
├── README.md
├── alert.yaml
├── alexaIntentSchema.json
├── automation_lab.yaml
├── automations.yaml
├── binary_sensor.yaml
├── blueprints
├── automation
│ └── homeassistant
│ │ ├── motion_light.yaml
│ │ └── notify_leaving_zone.yaml
└── script
│ └── homeassistant
│ └── confirmable_notification.yaml
├── chromedriver.sh
├── configuration.yaml
├── cover.yaml
├── custom_components
├── aarlo
│ ├── __init__.py
│ ├── alarm_control_panel.py
│ ├── binary_sensor.py
│ ├── camera.py
│ ├── light.py
│ ├── manifest.json
│ ├── media_player.py
│ ├── pyaarlo
│ │ ├── __init__.py
│ │ ├── backend.py
│ │ ├── background.py
│ │ ├── base.py
│ │ ├── camera.py
│ │ ├── cfg.py
│ │ ├── constant.py
│ │ ├── device.py
│ │ ├── doorbell.py
│ │ ├── light.py
│ │ ├── media.py
│ │ ├── sseclient.py
│ │ ├── storage.py
│ │ ├── tfa.py
│ │ └── util.py
│ ├── sensor.py
│ ├── services.yaml
│ └── switch.py
├── alexa_media
│ ├── .translations
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ └── pl.json
│ ├── __init__.py
│ ├── alarm_control_panel.py
│ ├── alexa_entity.py
│ ├── alexa_media.py
│ ├── config_flow.py
│ ├── const.py
│ ├── helpers.py
│ ├── light.py
│ ├── manifest.json
│ ├── media_player.py
│ ├── notify.py
│ ├── sensor.py
│ ├── services.py
│ ├── services.yaml
│ ├── strings.json
│ └── switch.py
├── androidtv
│ ├── __init__.py
│ ├── manifest.json
│ ├── media_player.py
│ └── services.yaml
├── authenticated
│ ├── __init__.py
│ ├── const.py
│ ├── manifest.json
│ ├── providers.py
│ └── sensor.py
├── composite
│ ├── __init__.py
│ ├── const.py
│ └── device_tracker.py
├── enable_debug.py
├── futures_cnn
│ ├── __init__.py
│ └── sensor.py
├── google_scholar
│ ├── __init__.py
│ └── sensor.py
├── hacs
│ ├── __init__.py
│ ├── config_flow.py
│ ├── const.py
│ ├── manifest.json
│ ├── repositories
│ │ ├── __init__.py
│ │ ├── appdaemon.py
│ │ ├── integration.py
│ │ ├── plugin.py
│ │ ├── python_script.py
│ │ └── theme.py
│ └── sensor.py
├── moon_here
│ ├── __init__.py
│ └── sensor.py
├── personalcapital
│ ├── __init__.py
│ ├── manifest.json
│ └── sensor.py
├── samsungtv_custom
│ ├── __init__.py
│ ├── media_player.py
│ ├── samsungctl_080b
│ │ ├── RUN_ME.py
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ ├── application.py
│ │ ├── art_mode.py
│ │ ├── cec_control
│ │ │ ├── __init__.py
│ │ │ └── dispatcher.py
│ │ ├── config.py
│ │ ├── exceptions.py
│ │ ├── interactive.py
│ │ ├── key_mappings.py
│ │ ├── power_handler.py
│ │ ├── remote.py
│ │ ├── remote_encrypted
│ │ │ ├── __init__.py
│ │ │ ├── aes.py
│ │ │ ├── crypto.py
│ │ │ ├── keys.py
│ │ │ └── rijndael
│ │ │ │ ├── __init__.py
│ │ │ │ ├── constants.py
│ │ │ │ ├── paddings.py
│ │ │ │ └── rijndael.py
│ │ ├── remote_legacy.py
│ │ ├── remote_websocket.py
│ │ ├── upnp
│ │ │ ├── UPNP_Device
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __main__.py
│ │ │ │ ├── action.py
│ │ │ │ ├── adapter_addresses.py
│ │ │ │ ├── data_type.py
│ │ │ │ ├── discover.py
│ │ │ │ ├── embedded_device.py
│ │ │ │ ├── icon.py
│ │ │ │ ├── instance_singleton.py
│ │ │ │ ├── listen.py
│ │ │ │ ├── service.py
│ │ │ │ ├── upnp_class.py
│ │ │ │ └── xmlns.py
│ │ │ ├── __init__.py
│ │ │ └── discover.py
│ │ ├── utils.py
│ │ ├── wake_on_lan.py
│ │ └── websocket_base.py
│ ├── samsungctl_qled
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ ├── application.py
│ │ ├── exceptions.py
│ │ ├── interactive.py
│ │ ├── remote.py
│ │ ├── remote_legacy.py
│ │ ├── remote_websocket.py
│ │ └── upnp.py
│ └── samsungtvws
│ │ ├── __init__.py
│ │ └── remote.py
├── samsungtv_smart
│ ├── __init__.py
│ ├── api
│ │ ├── samsungws.py
│ │ ├── shortcuts.py
│ │ ├── smartthings.py
│ │ └── upnp.py
│ ├── config_flow.py
│ ├── const.py
│ ├── diagnostics.py
│ ├── logo.py
│ ├── media_player.py
│ └── services.yaml
├── samsungtv_tizen
│ ├── __init__.py
│ ├── exceptions.py
│ ├── media_player.py
│ ├── shortcuts.py
│ ├── smartthings.py
│ ├── upnp.py
│ └── websockets.py
├── sectorperformance
│ ├── __init__.py
│ └── sensor.py
├── tuya_v2
│ ├── __init__.py
│ ├── aes_cbc.py
│ ├── alarm_control_panel.py
│ ├── base.py
│ ├── binary_sensor.py
│ ├── climate.py
│ ├── config_flow.py
│ ├── const.py
│ ├── cover.py
│ ├── fan.py
│ ├── humidifier.py
│ ├── light.py
│ ├── number.py
│ ├── scene.py
│ ├── select.py
│ ├── sensor.py
│ ├── switch.py
│ └── vacuum.py
├── wyzeapi
│ ├── __init__.py
│ ├── alarm_control_panel.py
│ ├── binary_sensor.py
│ ├── climate.py
│ ├── config_flow.py
│ ├── const.py
│ ├── light.py
│ ├── lock.py
│ ├── sensor.py
│ ├── switch.py
│ └── token_manager.py
├── wyzesense
│ ├── __init__.py
│ ├── binary_sensor.py
│ ├── manifest.json
│ ├── services.yaml
│ └── wyzesense_custom.py
└── yahoo_earnings
│ ├── __init__.py
│ ├── manifest.json
│ └── sensor.py
├── customize.yaml
├── customize_glob.yaml
├── emulated_hue_ids.json
├── frontend.yaml
├── gitupdate.sh
├── groups.yaml
├── ha_ss_1.png
├── ha_ss_2.png
├── ha_ss_3.png
├── ha_ss_4.png
├── ha_ss_5.png
├── history.yaml
├── input_boolean.yaml
├── input_button.yaml
├── input_datetime.yaml
├── input_number.yaml
├── input_select.yaml
├── input_text.yaml
├── intent_script.yaml
├── ios.yaml
├── ip_bans.yaml
├── logbook.yaml
├── lovelace.yaml
├── packages
├── homebridge.yaml
├── opencv.yaml
└── synology_surveillance.yaml
├── python_scripts
├── dark_sky_friendly_names.py
├── fade_in_light.py
├── initialize_tracker.py
├── light_counter.py
├── meta_device_tracker.py
├── ring_download.py
├── shellies_discovery.py
└── toggle_state.py
├── recorder.yaml
├── scenes.yaml
├── scripts.yaml
├── sensor.yaml
├── shell_scripts
├── alexa_remote_control.sh
├── alexa_remote_control_plain.sh
├── alexa_wrapper.sh
├── facebox.sh
└── image_classification.sh
├── switches.yaml
├── themes
├── oxfordblue
│ └── oxfordblue.yaml
├── prev_themes.yaml
└── sweetpink
│ └── sweetpink.yaml
├── ui-lovelace.yaml
├── update.sh
├── update_files.sh
├── www
├── assets
│ └── images
│ │ ├── skullcanyon.jpeg
│ │ └── vintage-colors-blur.jpg
├── community
│ ├── button-card
│ │ ├── button-card.js
│ │ └── button-card.js.gz
│ ├── config-template-card
│ │ ├── config-template-card.js
│ │ └── config-template-card.js.gz
│ ├── list-card
│ │ ├── list-card.js
│ │ └── list-card.js.gz
│ ├── lovelace-auto-entities
│ │ ├── auto-entities.js
│ │ └── auto-entities.js.gz
│ ├── lovelace-card-mod
│ │ ├── card-mod.js
│ │ └── card-mod.js.gz
│ ├── lovelace-card-tools
│ │ ├── card-tools.js
│ │ └── card-tools.js.gz
│ ├── lovelace-hass-aarlo
│ │ ├── hass-aarlo.js
│ │ └── hass-aarlo.js.gz
│ ├── lovelace-layout-card
│ │ ├── layout-card.js
│ │ └── layout-card.js.gz
│ ├── lovelace-slider-entity-row
│ │ ├── slider-entity-row.js
│ │ └── slider-entity-row.js.gz
│ ├── mini-graph-card
│ │ ├── mini-graph-card-bundle.js
│ │ └── mini-graph-card-bundle.js.gz
│ ├── mini-media-player
│ │ ├── mini-media-player-bundle.js
│ │ └── mini-media-player-bundle.js.gz
│ ├── pc-card
│ │ ├── pc-card.js
│ │ └── pc-card.js.gz
│ ├── restriction-card
│ │ ├── restriction-card.js
│ │ └── restriction-card.js.gz
│ ├── secondaryinfo-entity-row
│ │ ├── secondaryinfo-entity-row.js
│ │ └── secondaryinfo-entity-row.js.gz
│ ├── simple-thermostat
│ │ ├── simple-thermostat.js
│ │ └── simple-thermostat.js.gz
│ └── unused-card
│ │ ├── unused-card.js
│ │ └── unused-card.js.gz
└── custom_ui
│ ├── canvas-gauge-card.js
│ ├── card-modder.js
│ ├── circle-sensor-card.js
│ ├── ext-weblink.js
│ ├── gap-card.js
│ ├── layout-card.js
│ └── weather-card.js
└── z-wave_relay.png
/.HA_VERSION:
--------------------------------------------------------------------------------
1 | 2021.1.0
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ## GITATTRIBUTES FOR WEB PROJECTS
2 | #
3 | # These settings are for any web project.
4 | #
5 | # Details per file setting:
6 | # text These files should be normalized (i.e. convert CRLF to LF).
7 | # binary These files are binary and should be left untouched.
8 | #
9 | # Note that binary is a macro for -text -diff.
10 | ######################################################################
11 |
12 | ## AUTO-DETECT - Handle line endings automatically for files detected
13 | ## as text and leave all files detected as binary untouched.
14 | ## This will handle all files NOT defined below.
15 | * text=auto
16 |
17 | ## SOURCE CODE
18 | *.bat text
19 | *.coffee text
20 | *.css text
21 | *.htm text
22 | *.html text
23 | *.inc text
24 | *.ini text
25 | *.js text
26 | *.jsx text
27 | *.json text
28 | *.less text
29 | *.php text
30 | *.pl text
31 | *.py text
32 | *.rb text
33 | *.sass text
34 | *.scm text
35 | *.scss text
36 | *.sh text
37 | *.sql text
38 | *.styl text
39 | *.ts text
40 | *.xml text
41 | *.xhtml text
42 |
43 | ## DOCUMENTATION
44 | *.markdown text
45 | *.md text
46 | *.mdwn text
47 | *.mdown text
48 | *.mkd text
49 | *.mkdn text
50 | *.mdtxt text
51 | *.mdtext text
52 | *.txt text
53 | AUTHORS text
54 | CHANGELOG text
55 | CHANGES text
56 | CONTRIBUTING text
57 | COPYING text
58 | INSTALL text
59 | license text
60 | LICENSE text
61 | NEWS text
62 | readme text
63 | *README* text
64 | TODO text
65 |
66 | ## TEMPLATES
67 | *.dot text
68 | *.ejs text
69 | *.haml text
70 | *.handlebars text
71 | *.hbs text
72 | *.hbt text
73 | *.jade text
74 | *.latte text
75 | *.mustache text
76 | *.phtml text
77 | *.tmpl text
78 |
79 | ## LINTERS
80 | .csslintrc text
81 | .eslintrc text
82 | .jscsrc text
83 | .jshintrc text
84 | .jshintignore text
85 | .stylelintrc text
86 |
87 | ## CONFIGS
88 | *.bowerrc text
89 | *.cnf text
90 | *.conf text
91 | *.config text
92 | .editorconfig text
93 | .gitattributes text
94 | .gitconfig text
95 | .gitignore text
96 | .htaccess text
97 | *.npmignore text
98 | *.yaml text
99 | *.yml text
100 | Makefile text
101 | makefile text
102 |
103 | ## HEROKU
104 | Procfile text
105 | .slugignore text
106 |
107 | ## GRAPHICS
108 | *.ai binary
109 | *.bmp binary
110 | *.eps binary
111 | *.gif binary
112 | *.ico binary
113 | *.jng binary
114 | *.jp2 binary
115 | *.jpg binary
116 | *.jpeg binary
117 | *.jpx binary
118 | *.jxr binary
119 | *.pdf binary
120 | *.png binary
121 | *.psb binary
122 | *.psd binary
123 | *.svg text
124 | *.svgz binary
125 | *.tif binary
126 | *.tiff binary
127 | *.wbmp binary
128 | *.webp binary
129 |
130 | ## AUDIO
131 | *.kar binary
132 | *.m4a binary
133 | *.mid binary
134 | *.midi binary
135 | *.mp3 binary
136 | *.ogg binary
137 | *.ra binary
138 |
139 | ## VIDEO
140 | *.3gpp binary
141 | *.3gp binary
142 | *.as binary
143 | *.asf binary
144 | *.asx binary
145 | *.fla binary
146 | *.flv binary
147 | *.m4v binary
148 | *.mng binary
149 | *.mov binary
150 | *.mp4 binary
151 | *.mpeg binary
152 | *.mpg binary
153 | *.swc binary
154 | *.swf binary
155 | *.webm binary
156 |
157 | ## ARCHIVES
158 | *.7z binary
159 | *.gz binary
160 | *.rar binary
161 | *.tar binary
162 | *.zip binary
163 |
164 | ## FONTS
165 | *.ttf binary
166 | *.eot binary
167 | *.otf binary
168 | *.woff binary
169 | *.woff2 binary
170 |
171 | ## EXECUTABLES
172 | *.exe binary
173 | *.pyc binary
174 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | # Python cache
5 | __pycache__
6 | *.pyc
7 |
8 | # Other
9 | *.uuid
10 | *.conf
11 | *.db
12 | *.db-journal
13 | *.log
14 | *.noload
15 | *.txt
16 | *.sqlite
17 | *.xml
18 | *.backup
19 | *.json
20 | life360.sh
21 | .ip_authenticated.yaml
22 | ip_bans.yaml
23 | .config_entries.json
24 | *.google.token
25 | .google.token
26 | .ring_cache.pickle
27 | .spotify-token-cache
28 | .storage
29 | abodepy_cache.pickle
30 | camera_recording.py
31 | home-assistant.env
32 | home-assistant.*
33 | known_devices.yaml
34 | entity_registry.yaml
35 | secrets.yaml
36 | google_calendars.yaml
37 | SERVICE_ACCOUNT.json
38 | components
39 | deps
40 | tts
41 | www/icons
42 | www/floorplans
43 | www/community
44 | custom_components/hacs
45 | downloads
46 | icloud
47 | dlib_faces
48 | dlib_nofaces
49 | dlib_known_faces
50 | dlib_unknown_faces
51 | .cloud
52 | *.pickle
53 | .pc-session
54 | google*.deb
55 | .homekit.state
56 |
--------------------------------------------------------------------------------
/HA_screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/HA_screenshot_1.png
--------------------------------------------------------------------------------
/alert.yaml:
--------------------------------------------------------------------------------
1 | abode_updates:
2 | name: Abode updates are off
3 | done_message: Abode updates enabled
4 | entity_id: input_boolean.abodeupdate
5 | state: 'off'
6 | repeat: 30
7 | can_acknowledge: True
8 | skip_first: True
9 | notifiers:
10 | - ios_abode_updates
11 | home_automation:
12 | name: Home automation is disabled
13 | done_message: Home automation enabled
14 | entity_id: input_boolean.homeautomation
15 | state: 'off'
16 | repeat: 30
17 | can_acknowledge: True
18 | skip_first: True
19 | notifiers:
20 | - ios_home_automation
21 |
--------------------------------------------------------------------------------
/blueprints/automation/homeassistant/motion_light.yaml:
--------------------------------------------------------------------------------
1 | blueprint:
2 | name: Motion-activated Light
3 | description: Turn on a light when motion is detected.
4 | domain: automation
5 | source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/motion_light.yaml
6 | input:
7 | motion_entity:
8 | name: Motion Sensor
9 | selector:
10 | entity:
11 | domain: binary_sensor
12 | device_class: motion
13 | light_target:
14 | name: Light
15 | selector:
16 | target:
17 | entity:
18 | domain: light
19 | no_motion_wait:
20 | name: Wait time
21 | description: Time to leave the light on after last motion is detected.
22 | default: 120
23 | selector:
24 | number:
25 | min: 0
26 | max: 3600
27 | unit_of_measurement: seconds
28 |
29 | # If motion is detected within the delay,
30 | # we restart the script.
31 | mode: restart
32 | max_exceeded: silent
33 |
34 | trigger:
35 | platform: state
36 | entity_id: !input motion_entity
37 | from: "off"
38 | to: "on"
39 |
40 | action:
41 | - service: light.turn_on
42 | target: !input light_target
43 | - wait_for_trigger:
44 | platform: state
45 | entity_id: !input motion_entity
46 | from: "on"
47 | to: "off"
48 | - delay: !input no_motion_wait
49 | - service: light.turn_off
50 | target: !input light_target
51 |
--------------------------------------------------------------------------------
/blueprints/automation/homeassistant/notify_leaving_zone.yaml:
--------------------------------------------------------------------------------
1 | blueprint:
2 | name: Zone Notification
3 | description: Send a notification to a device when a person leaves a specific zone.
4 | domain: automation
5 | source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml
6 | input:
7 | person_entity:
8 | name: Person
9 | selector:
10 | entity:
11 | domain: person
12 | zone_entity:
13 | name: Zone
14 | selector:
15 | entity:
16 | domain: zone
17 | notify_device:
18 | name: Device to notify
19 | description: Device needs to run the official Home Assistant app to receive notifications.
20 | selector:
21 | device:
22 | integration: mobile_app
23 |
24 | trigger:
25 | platform: state
26 | entity_id: !input person_entity
27 |
28 | variables:
29 | zone_entity: !input zone_entity
30 | # This is the state of the person when it's in this zone.
31 | zone_state: "{{ states[zone_entity].name }}"
32 | person_entity: !input person_entity
33 | person_name: "{{ states[person_entity].name }}"
34 |
35 | condition:
36 | condition: template
37 | value_template: "{{ trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}"
38 |
39 | action:
40 | domain: mobile_app
41 | type: notify
42 | device_id: !input notify_device
43 | message: "{{ person_name }} has left {{ zone_state }}"
44 |
--------------------------------------------------------------------------------
/blueprints/script/homeassistant/confirmable_notification.yaml:
--------------------------------------------------------------------------------
1 | blueprint:
2 | name: Confirmable Notification
3 | description: >-
4 | A script that sends an actionable notification with a confirmation before
5 | running the specified action.
6 | domain: script
7 | source_url: https://github.com/home-assistant/core/blob/master/homeassistant/components/script/blueprints/confirmable_notification.yaml
8 | input:
9 | notify_device:
10 | name: Device to notify
11 | description: Device needs to run the official Home Assistant app to receive notifications.
12 | selector:
13 | device:
14 | integration: mobile_app
15 | title:
16 | name: "Title"
17 | description: "The title of the button shown in the notification."
18 | default: ""
19 | selector:
20 | text:
21 | message:
22 | name: "Message"
23 | description: "The message body"
24 | selector:
25 | text:
26 | confirm_text:
27 | name: "Confirmation Text"
28 | description: "Text to show on the confirmation button"
29 | default: "Confirm"
30 | selector:
31 | text:
32 | confirm_action:
33 | name: "Confirmation Action"
34 | description: "Action to run when notification is confirmed"
35 | default: []
36 | selector:
37 | action:
38 | dismiss_text:
39 | name: "Dismiss Text"
40 | description: "Text to show on the dismiss button"
41 | default: "Dismiss"
42 | selector:
43 | text:
44 | dismiss_action:
45 | name: "Dismiss Action"
46 | description: "Action to run when notification is dismissed"
47 | default: []
48 | selector:
49 | action:
50 |
51 | mode: restart
52 |
53 | sequence:
54 | - alias: "Send notification"
55 | domain: mobile_app
56 | type: notify
57 | device_id: !input notify_device
58 | title: !input title
59 | message: !input message
60 | data:
61 | actions:
62 | - action: "CONFIRM"
63 | title: !input confirm_text
64 | - action: "DISMISS"
65 | title: !input dismiss_text
66 | - alias: "Awaiting response"
67 | wait_for_trigger:
68 | - platform: event
69 | event_type: mobile_app_notification_action
70 | - choose:
71 | - conditions: "{{ wait.trigger.event.data.action == 'CONFIRM' }}"
72 | sequence: !input confirm_action
73 | - conditions: "{{ wait.trigger.event.data.action == 'DISMISS' }}"
74 | sequence: !input dismiss_action
75 |
--------------------------------------------------------------------------------
/chromedriver.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # https://developers.supportbee.com/blog/setting-up-cucumber-to-run-with-Chrome-on-Linux/
3 | # https://gist.github.com/curtismcmullan/7be1a8c1c841a9d8db2c
4 | # http://stackoverflow.com/questions/10792403/how-do-i-get-chrome-working-with-selenium-using-php-webdriver
5 | # http://stackoverflow.com/questions/26133486/how-to-specify-binary-path-for-remote-chromedriver-in-codeception
6 | # http://stackoverflow.com/questions/40262682/how-to-run-selenium-3-x-with-chrome-driver-through-terminal
7 | # http://askubuntu.com/questions/760085/how-do-you-install-google-chrome-on-ubuntu-16-04
8 |
9 | # Versions
10 | CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`
11 | SELENIUM_STANDALONE_VERSION=3.4.0
12 | SELENIUM_SUBDIR=$(echo "$SELENIUM_STANDALONE_VERSION" | cut -d"." -f-2)
13 |
14 | # Remove existing downloads and binaries so we can start from scratch.
15 | sudo apt-get remove google-chrome-stable
16 | rm ~/selenium-server-standalone-*.jar
17 | rm ~/chromedriver_linux64.zip
18 | sudo rm /usr/local/bin/chromedriver
19 | sudo rm /usr/local/bin/selenium-server-standalone.jar
20 |
21 | # Install dependencies.
22 | sudo apt-get update
23 | sudo apt-get install -y unzip openjdk-8-jre-headless xvfb libxi6 libgconf-2-4
24 |
25 | # Install Chrome.
26 | sudo curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add
27 | sudo echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list
28 | sudo apt-get -y update
29 | sudo apt-get -y install google-chrome-stable
30 |
31 | # Install ChromeDriver.
32 | wget -N http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -P ~/
33 | unzip ~/chromedriver_linux64.zip -d ~/
34 | rm ~/chromedriver_linux64.zip
35 | sudo mv -f ~/chromedriver /usr/local/bin/chromedriver
36 | sudo chown root:root /usr/local/bin/chromedriver
37 | sudo chmod 0755 /usr/local/bin/chromedriver
38 |
39 | # Install Selenium.
40 | wget -N http://selenium-release.storage.googleapis.com/$SELENIUM_SUBDIR/selenium-server-standalone-$SELENIUM_STANDALONE_VERSION.jar -P ~/
41 | sudo mv -f ~/selenium-server-standalone-$SELENIUM_STANDALONE_VERSION.jar /usr/local/bin/selenium-server-standalone.jar
42 | sudo chown root:root /usr/local/bin/selenium-server-standalone.jar
43 | sudo chmod 0755 /usr/local/bin/selenium-server-standalone.jar
44 |
--------------------------------------------------------------------------------
/cover.yaml:
--------------------------------------------------------------------------------
1 | - platform: template
2 | covers:
3 | garagedoor:
4 | friendly_name: 'Garage Door'
5 | value_template: >
6 | {%- if states.binary_sensor.garage_door -%}
7 | {{ states.binary_sensor.garage_door.state == 'on' }}
8 | {%- else -%}
9 | true
10 | {%- endif -%}
11 | open_cover:
12 | service: switch.turn_on
13 | entity_id: switch.shelly_garage
14 | close_cover:
15 | service: switch.turn_on
16 | entity_id: switch.shelly_garage
17 | stop_cover:
18 | service: switch.turn_on
19 | entity_id: switch.shelly_garage
20 | icon_template: >
21 | {%- if states.binary_sensor.garage_door.state == 'on' -%}
22 | mdi:garage-open
23 | {%- else -%}
24 | mdi:garage
25 | {%- endif -%}
26 |
--------------------------------------------------------------------------------
/custom_components/aarlo/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "domain": "aarlo",
3 | "name": "Arlo Camera Support",
4 | "documentation": "https://github.com/twrecked/hass-aarlo/blob/master/README.md",
5 | "issue_tracker": "https://github.com/twrecked/hass-aarlo/issues",
6 | "dependencies": ["ffmpeg"],
7 | "codeowners": ["@twrecked"],
8 | "requirements": ["unidecode","cloudscraper>=1.2.58", "paho-mqtt"],
9 | "version": "0.7.2b7",
10 | "iot_class": "cloud_push"
11 | }
12 |
--------------------------------------------------------------------------------
/custom_components/aarlo/pyaarlo/light.py:
--------------------------------------------------------------------------------
1 | import pprint
2 |
3 | from .constant import BATTERY_KEY, BRIGHTNESS_KEY, LAMP_STATE_KEY, MOTION_DETECTED_KEY
4 | from .device import ArloChildDevice
5 |
6 |
7 | class ArloLight(ArloChildDevice):
8 | def __init__(self, name, arlo, attrs):
9 | """An Arlo Light.
10 |
11 | :param name: name of light
12 | :param arlo: controlling arlo instance
13 | :param attrs: initial attributes give by Arlo
14 | """
15 | super().__init__(name, arlo, attrs)
16 |
17 | @property
18 | def resource_type(self):
19 | return "lights"
20 |
21 | def _event_handler(self, resource, event):
22 | self._arlo.debug(self.name + " LIGHT got one " + resource)
23 |
24 | # pass on to lower layer
25 | super()._event_handler(resource, event)
26 |
27 | @property
28 | def is_on(self):
29 | return self._load(LAMP_STATE_KEY, "off") == "on"
30 |
31 | def turn_on(self, brightness=None, rgb=None):
32 | """Turn the light on.
33 |
34 | :param brightness: how bright to make the light
35 | :param rgb: what color to make the light
36 | """
37 | properties = {LAMP_STATE_KEY: "on"}
38 | if brightness is not None:
39 | properties[BRIGHTNESS_KEY] = brightness
40 | if rgb is not None:
41 | # properties["single"] = rgb_to_hex(rgb)
42 | pass
43 |
44 | self._arlo.debug("{} sending {}".format(self._name, pprint.pformat(properties)))
45 | self._arlo.be.notify(
46 | base=self.base_station,
47 | body={
48 | "action": "set",
49 | "properties": properties,
50 | "publishResponse": True,
51 | "resource": self.resource_id,
52 | },
53 | )
54 | return True
55 |
56 | def turn_off(self):
57 | """Turn the light off."""
58 | self._arlo.be.notify(
59 | base=self.base_station,
60 | body={
61 | "action": "set",
62 | "properties": {LAMP_STATE_KEY: "off"},
63 | "publishResponse": True,
64 | "resource": self.resource_id,
65 | },
66 | )
67 | return True
68 |
69 | def set_brightness(self, brightness):
70 | """Set the light brightness.
71 |
72 | :param brightness: brightness to use (0-255)
73 | """
74 | self._arlo.be.notify(
75 | base=self.base_station,
76 | body={
77 | "action": "set",
78 | "properties": {BRIGHTNESS_KEY: brightness},
79 | "publishResponse": True,
80 | "resource": self.resource_id,
81 | },
82 | )
83 | return True
84 |
85 | def has_capability(self, cap):
86 | if cap in (MOTION_DETECTED_KEY, BATTERY_KEY):
87 | return True
88 | return super().has_capability(cap)
89 |
--------------------------------------------------------------------------------
/custom_components/aarlo/pyaarlo/storage.py:
--------------------------------------------------------------------------------
1 | import fnmatch
2 | import pickle
3 | import pprint
4 | import threading
5 |
6 |
7 | class ArloStorage(object):
8 | def __init__(self, arlo):
9 | self._arlo = arlo
10 | self._state_file = self._arlo.cfg.state_file
11 | self.db = {}
12 | self.lock = threading.Lock()
13 | self.load()
14 |
15 | def _ekey(self, key):
16 | return key if not isinstance(key, list) else "/".join(key)
17 |
18 | def _keys_matching(self, key):
19 | mkeys = []
20 | ekey = self._ekey(key)
21 | for mkey in self.db:
22 | if fnmatch.fnmatch(mkey, ekey):
23 | mkeys.append(mkey)
24 | return mkeys
25 |
26 | def load(self):
27 | if self._state_file is not None:
28 | try:
29 | with self.lock:
30 | with open(self._state_file, "rb") as dump:
31 | self.db = pickle.load(dump)
32 | except Exception:
33 | self._arlo.debug("file not read")
34 |
35 | def save(self):
36 | if self._state_file is not None:
37 | try:
38 | with self.lock:
39 | with open(self._state_file, "wb") as dump:
40 | pickle.dump(self.db, dump)
41 | except Exception:
42 | self._arlo.warning("file not written")
43 |
44 | def file_name(self):
45 | return self._state_file
46 |
47 | def get(self, key, default=None):
48 | with self.lock:
49 | ekey = self._ekey(key)
50 | return self.db.get(ekey, default)
51 |
52 | def get_matching(self, key, default=None):
53 | with self.lock:
54 | gets = []
55 | for mkey in self._keys_matching(key):
56 | gets.append((mkey, self.db.get(mkey, default)))
57 | return gets
58 |
59 | def keys_matching(self, key):
60 | with self.lock:
61 | return self._keys_matching(key)
62 |
63 | def set(self, key, value):
64 | ekey = self._ekey(key)
65 | output = "set:" + ekey + "=" + str(value)
66 | self._arlo.debug(output[:80])
67 | with self.lock:
68 | self.db[ekey] = value
69 | return value
70 |
71 | def unset(self, key):
72 | with self.lock:
73 | del self.db[self._ekey(key)]
74 |
75 | def clear(self):
76 | with self.lock:
77 | self.db = {}
78 |
79 | def dump(self):
80 | with self.lock:
81 | pprint.pprint(self.db)
82 |
--------------------------------------------------------------------------------
/custom_components/aarlo/pyaarlo/util.py:
--------------------------------------------------------------------------------
1 | import base64
2 | import time
3 | from datetime import datetime, timezone
4 |
5 | import requests
6 |
7 |
8 | def utc_to_local(utc_dt):
9 | return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)
10 |
11 |
12 | def the_epoch():
13 | return utc_to_local(datetime.fromtimestamp(0, tz=timezone.utc))
14 |
15 |
16 | def arlotime_to_time(timestamp):
17 | """Convert Arlo timestamp to Unix timestamp."""
18 | return int(timestamp / 1000)
19 |
20 |
21 | def arlotime_to_datetime(timestamp):
22 | """Convert Arlo timestamp to Python datetime."""
23 | return utc_to_local(datetime.fromtimestamp(int(timestamp / 1000), tz=timezone.utc))
24 |
25 |
26 | def arlotime_strftime(timestamp, date_format="%Y-%m-%dT%H:%M:%S"):
27 | """Convert Arlo timestamp to time string."""
28 | return arlotime_to_datetime(timestamp).strftime(date_format)
29 |
30 |
31 | def time_to_arlotime(timestamp=None):
32 | """Convert Unix timestamp to Arlo timestamp."""
33 | if timestamp is None:
34 | timestamp = time.time()
35 | return int(timestamp * 1000)
36 |
37 |
38 | def now_strftime(date_format="%Y-%m-%dT%H:%M:%S"):
39 | """Convert now to time string."""
40 | return datetime.now().strftime(date_format)
41 |
42 |
43 | def days_until(when):
44 | now = datetime.now()
45 | when = datetime.utcfromtimestamp(when)
46 | if when <= now:
47 | return 0
48 | return (when - now).days
49 |
50 |
51 | def httptime_to_datetime(http_timestamp):
52 | """Convert HTTP timestamp to Python datetime."""
53 | return utc_to_local(datetime.strptime(http_timestamp, "%a, %d %b %Y %H:%M:%S GMT"))
54 |
55 |
56 | def httptime_strftime(http_timestamp, date_format="%Y-%m-%dT%H:%M:%S"):
57 | """Convert HTTP timestamp to time string."""
58 | return httptime_to_datetime(http_timestamp).strftime(date_format)
59 |
60 |
61 | def _http_get(url):
62 | """Download HTTP data."""
63 |
64 | if url is None:
65 | return None
66 |
67 | try:
68 | ret = requests.get(url)
69 | except requests.exceptions.SSLError:
70 | return None
71 | except Exception:
72 | return None
73 |
74 | if ret.status_code != 200:
75 | return None
76 | return ret
77 |
78 |
79 | def http_get(url, filename=None):
80 | """Download HTTP data."""
81 |
82 | ret = _http_get(url)
83 | if ret is None:
84 | return False
85 |
86 | if filename is None:
87 | return ret.content
88 |
89 | with open(filename, "wb") as data:
90 | data.write(ret.content)
91 | return True
92 |
93 |
94 | def http_get_img(url, ignore_date=False):
95 | """Download HTTP image data."""
96 |
97 | ret = _http_get(url)
98 | if ret is None:
99 | return None, datetime.now().astimezone()
100 |
101 | date = None
102 | if not ignore_date:
103 | date = ret.headers.get("Last-Modified", None)
104 | if date is not None:
105 | date = httptime_to_datetime(date)
106 | if date is None:
107 | date = datetime.now().astimezone()
108 |
109 | return ret.content, date
110 |
111 |
112 | def http_stream(url, chunk=4096):
113 | """Generate stream for a given record video.
114 |
115 | :param url: url of stream to read
116 | :param chunk: chunk bytes to read per time
117 | :returns generator object
118 | """
119 | ret = requests.get(url, stream=True)
120 | ret.raise_for_status()
121 | for data in ret.iter_content(chunk):
122 | yield data
123 |
124 |
125 | def rgb_to_hex(rgb):
126 | """Convert HA color to Arlo color."""
127 | return "#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2])
128 |
129 |
130 | def hex_to_rgb(h):
131 | """Convert Arlo color to HA color."""
132 | return {"red": int(h[1:3], 16), "green": int(h[3:5], 16), "blue": int(h[5:7], 16)}
133 |
134 |
135 | def to_b64(in_str):
136 | """Convert a string into a base64 string."""
137 | return base64.b64encode(in_str.encode()).decode()
138 |
--------------------------------------------------------------------------------
/custom_components/aarlo/services.yaml:
--------------------------------------------------------------------------------
1 | # Describes the format for available virtual services
2 |
3 | camera_request_snapshot:
4 | description: Request a camera takes a snapshot
5 | fields:
6 | entity_id:
7 | description: Name(s) of entities to use
8 | example: 'camera.aarlo_front_camera'
9 |
10 | camera_request_snapshot_to_file:
11 | description: Request a camera takes a snapshot and write it to a file
12 | fields:
13 | entity_id:
14 | description: Name(s) of entities to use
15 | example: 'camera.aarlo_front_camera'
16 | filename:
17 | description: string (required) where to save the snapshot
18 | example: 'snapshot.jpg'
19 |
20 | camera_request_video_to_file:
21 | description: Request a camera records a video and write it to a file
22 | fields:
23 | entity_id:
24 | description: Name(s) of entities to use
25 | example: 'camera.aarlo_front_camera'
26 | filename:
27 | description: string (required) where to save the video
28 | example: 'snapshot.mp4'
29 |
30 | camera_stop_activity:
31 | description: Stop all stream activity on a camera
32 | fields:
33 | entity_id:
34 | description: Name(s) of entities to use
35 | example: 'camera.aarlo_front_camera'
36 |
37 | camera_start_recording:
38 | description: Request a camera start recording to cloud
39 | fields:
40 | entity_id:
41 | description: Name(s) of entities to use
42 | example: 'camera.aarlo_front_camera'
43 | duration:
44 | description: duration to record for in seconds
45 | example: 30
46 |
47 | camera_stop_recording:
48 | description: Request a camera stop recording to cloud
49 | fields:
50 | entity_id:
51 | description: Name(s) of entities to use
52 | example: 'camera.aarlo_front_camera'
53 |
54 | alarm_set_mode:
55 | description: Set the mode of a base station.
56 | fields:
57 | entity_id:
58 | description: Name(s) of entities to use
59 | example: 'alarm_control_panel.aarlo_front'
60 | mode:
61 | description: mode or schedule to change to
62 | example: 'home'
63 |
64 | siren_on:
65 | description: Turn on a siren.
66 | fields:
67 | entity_id:
68 | description: name(s) of entities to use
69 | example: 'alarm_control_panel.aarlo_front'
70 | volume:
71 | description: volume to use
72 | example: 6
73 | duration:
74 | description: duration to turn on for in seconds
75 | example: 30
76 |
77 | sirens_on:
78 | description: Turn on all sirens.
79 | fields:
80 | volume:
81 | description: volume to use
82 | example: 6
83 | duration:
84 | description: duration to turn on for in seconds
85 | example: 30
86 |
87 | siren_off:
88 | description: Turn off a siren.
89 | fields:
90 | entity_id:
91 | description: Name(s) of entities to turn off
92 | example: 'alarm_control_panel.aarlo_front'
93 |
94 | sirens_off:
95 | description: Turn off all sirens.
96 |
97 | inject_response:
98 | description: Inject a json packet into the Arlo event stream
99 | fields:
100 | filename:
101 | description: File in /config containing json packet.
102 | example: cry-off.json
103 |
104 |
--------------------------------------------------------------------------------
/custom_components/alexa_media/alexa_media.py:
--------------------------------------------------------------------------------
1 | """
2 | Alexa Devices Base Class.
3 |
4 | SPDX-License-Identifier: Apache-2.0
5 |
6 | For more details about this platform, please refer to the documentation at
7 | https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639
8 | """
9 |
10 | import logging
11 | from typing import Dict, Text # noqa pylint: disable=unused-import
12 |
13 | from alexapy import AlexaAPI, hide_email
14 |
15 | from .const import DATA_ALEXAMEDIA
16 |
17 | _LOGGER = logging.getLogger(__name__)
18 |
19 |
20 | class AlexaMedia:
21 | """Implementation of Alexa Media Base object."""
22 |
23 | def __init__(self, device, login) -> None:
24 | # pylint: disable=unexpected-keyword-arg
25 | """Initialize the Alexa device."""
26 |
27 | # Class info
28 | self._login = login
29 | self.alexa_api = AlexaAPI(device, login)
30 | self.email = login.email
31 | self.account = hide_email(login.email)
32 |
33 | def check_login_changes(self):
34 | """Update Login object if it has changed."""
35 | # _LOGGER.debug("Checking if Login object has changed")
36 | try:
37 | login = self.hass.data[DATA_ALEXAMEDIA]["accounts"][self.email]["login_obj"]
38 | except (AttributeError, KeyError):
39 | return
40 | # _LOGGER.debug("Login object %s closed status: %s", login, login.session.closed)
41 | # _LOGGER.debug(
42 | # "Alexaapi %s closed status: %s",
43 | # self.alexa_api,
44 | # self.alexa_api._session.closed,
45 | # )
46 | if self.alexa_api.update_login(login):
47 | _LOGGER.debug("Login object has changed; updating")
48 | self._login = login
49 | self.email = login.email
50 | self.account = hide_email(login.email)
51 |
--------------------------------------------------------------------------------
/custom_components/alexa_media/const.py:
--------------------------------------------------------------------------------
1 | """
2 | Support to interface with Alexa Devices.
3 |
4 | SPDX-License-Identifier: Apache-2.0
5 |
6 | For more details about this platform, please refer to the documentation at
7 | https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639
8 | """
9 | from datetime import timedelta
10 |
11 | __version__ = "3.10.15"
12 | PROJECT_URL = "https://github.com/custom-components/alexa_media_player/"
13 | ISSUE_URL = f"{PROJECT_URL}issues"
14 |
15 | DOMAIN = "alexa_media"
16 | DATA_ALEXAMEDIA = "alexa_media"
17 |
18 | PLAY_SCAN_INTERVAL = 20
19 | SCAN_INTERVAL = timedelta(seconds=60)
20 | MIN_TIME_BETWEEN_SCANS = SCAN_INTERVAL
21 | MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
22 |
23 | ALEXA_COMPONENTS = [
24 | "media_player",
25 | ]
26 | DEPENDENT_ALEXA_COMPONENTS = [
27 | "notify",
28 | "switch",
29 | "sensor",
30 | "alarm_control_panel",
31 | "light",
32 | ]
33 |
34 | HTTP_COOKIE_HEADER = "# HTTP Cookie File"
35 | CONF_ACCOUNTS = "accounts"
36 | CONF_COOKIES_TXT = "cookies_txt"
37 | CONF_DEBUG = "debug"
38 | CONF_HASS_URL = "hass_url"
39 | CONF_INCLUDE_DEVICES = "include_devices"
40 | CONF_EXCLUDE_DEVICES = "exclude_devices"
41 | CONF_QUEUE_DELAY = "queue_delay"
42 | CONF_EXTENDED_ENTITY_DISCOVERY = "extended_entity_discovery"
43 | CONF_SECURITYCODE = "securitycode"
44 | CONF_OTPSECRET = "otp_secret"
45 | CONF_PROXY = "proxy"
46 | CONF_TOTP_REGISTER = "registered"
47 | CONF_OAUTH = "oauth"
48 | CONF_OAUTH_LOGIN = "oauth_login"
49 | DATA_LISTENER = "listener"
50 |
51 | EXCEPTION_TEMPLATE = "An exception of type {0} occurred. Arguments:\n{1!r}"
52 |
53 | DEFAULT_EXTENDED_ENTITY_DISCOVERY = False
54 | DEFAULT_QUEUE_DELAY = 1.5
55 | SERVICE_CLEAR_HISTORY = "clear_history"
56 | SERVICE_UPDATE_LAST_CALLED = "update_last_called"
57 | SERVICE_FORCE_LOGOUT = "force_logout"
58 |
59 | RECURRING_PATTERN = {
60 | None: "Never Repeat",
61 | "P1D": "Every day",
62 | "XXXX-WE": "Weekends",
63 | "XXXX-WD": "Weekdays",
64 | "XXXX-WXX-1": "Every Monday",
65 | "XXXX-WXX-2": "Every Tuesday",
66 | "XXXX-WXX-3": "Every Wednesday",
67 | "XXXX-WXX-4": "Every Thursday",
68 | "XXXX-WXX-5": "Every Friday",
69 | "XXXX-WXX-6": "Every Saturday",
70 | "XXXX-WXX-7": "Every Sunday",
71 | }
72 |
73 | RECURRING_PATTERN_ISO_SET = {
74 | None: {},
75 | "P1D": {1, 2, 3, 4, 5, 6, 7},
76 | "XXXX-WE": {6, 7},
77 | "XXXX-WD": {1, 2, 3, 4, 5},
78 | "XXXX-WXX-1": {1},
79 | "XXXX-WXX-2": {2},
80 | "XXXX-WXX-3": {3},
81 | "XXXX-WXX-4": {4},
82 | "XXXX-WXX-5": {5},
83 | "XXXX-WXX-6": {6},
84 | "XXXX-WXX-7": {7},
85 | }
86 |
87 | ATTR_MESSAGE = "message"
88 | ATTR_EMAIL = "email"
89 | ATTR_NUM_ENTRIES = "entries"
90 | STARTUP = """
91 | -------------------------------------------------------------------
92 | {}
93 | Version: {}
94 | This is a custom component
95 | If you have any issues with this you need to open an issue here:
96 | {}
97 | -------------------------------------------------------------------
98 | """.format(
99 | DOMAIN, __version__, ISSUE_URL
100 | )
101 |
102 | AUTH_CALLBACK_PATH = "/auth/alexamedia/callback"
103 | AUTH_CALLBACK_NAME = "auth:alexamedia:callback"
104 | AUTH_PROXY_PATH = "/auth/alexamedia/proxy"
105 | AUTH_PROXY_NAME = "auth:alexamedia:proxy"
106 |
--------------------------------------------------------------------------------
/custom_components/alexa_media/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "domain": "alexa_media",
3 | "name": "Alexa Media Player",
4 | "version": "3.10.15",
5 | "config_flow": true,
6 | "documentation": "https://github.com/custom-components/alexa_media_player/wiki",
7 | "issue_tracker": "https://github.com/custom-components/alexa_media_player/issues",
8 | "dependencies": ["persistent_notification", "http"],
9 | "codeowners": ["@alandtse", "@keatontaylor"],
10 | "requirements": ["alexapy==1.25.3", "packaging>=20.3", "wrapt>=1.12.1"],
11 | "iot_class": "cloud_polling"
12 | }
13 |
--------------------------------------------------------------------------------
/custom_components/alexa_media/services.yaml:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: Apache-2.0
2 | update_last_called:
3 | # Description of the service
4 | description: Forces update of last_called echo device for each Alexa account.
5 | # Different fields that your service accepts
6 | fields:
7 | # Key of the field
8 | email:
9 | # Description of the field
10 | description: List of Alexa accounts to update. If empty, will update all known accounts.
11 | # Example value that can be passed for this field
12 | example: "my_email@alexa.com"
13 |
14 | clear_history:
15 | # Description of the service
16 | description: Clear last entries from Alexa history for each Alexa account.
17 | # Different fields that your service accepts
18 | fields:
19 | # Key of the field
20 | email:
21 | # Description of the field
22 | description: List of Alexa accounts to update. If empty, will delete from all known accounts.
23 | # Example value that can be passed for this field
24 | example: "my_email@alexa.com"
25 | entries:
26 | # Description of the field
27 | description: Number of entries to clear from 1 to 50. If empty, clear 50.
28 | # Example value that can be passed for this field
29 | example: 50
30 |
31 | force_logout:
32 | # Description of the service
33 | description: Force logout of Alexa Login account and deletion of .pickle. Intended for debugging use.
34 | # Different fields that your service accepts
35 | fields:
36 | # Key of the field
37 | email:
38 | # Description of the field
39 | description: List of Alexa accounts to log out. If empty, will log out from all known accounts.
40 | # Example value that can be passed for this field
41 | example: "my_email@alexa.com"
42 |
--------------------------------------------------------------------------------
/custom_components/androidtv/__init__.py:
--------------------------------------------------------------------------------
1 | """Support for functionality to interact with Android TV/Fire TV devices."""
2 |
--------------------------------------------------------------------------------
/custom_components/androidtv/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "domain": "androidtv",
3 | "name": "Android TV",
4 | "documentation": "https://www.home-assistant.io/integrations/androidtv",
5 | "requirements": [
6 | "adb-shell[async]==0.2.1",
7 | "androidtv[async]==0.0.47",
8 | "pure-python-adb==0.2.2.dev0"
9 | ],
10 | "codeowners": ["@JeffLIrion"]
11 | }
12 |
--------------------------------------------------------------------------------
/custom_components/androidtv/services.yaml:
--------------------------------------------------------------------------------
1 | # Describes the format for available Android TV and Fire TV services
2 |
3 | adb_command:
4 | description: Send an ADB command to an Android TV / Fire TV device.
5 | fields:
6 | entity_id:
7 | description: Name(s) of Android TV / Fire TV entities.
8 | example: "media_player.android_tv_living_room"
9 | command:
10 | description: Either a key command or an ADB shell command.
11 | example: "HOME"
12 | download:
13 | description: Download a file from your Android TV / Fire TV device to your Home Assistant instance.
14 | fields:
15 | entity_id:
16 | description: Name of Android TV / Fire TV entity.
17 | example: "media_player.android_tv_living_room"
18 | device_path:
19 | description: The filepath on the Android TV / Fire TV device.
20 | example: "/storage/emulated/0/Download/example.txt"
21 | local_path:
22 | description: The filepath on your Home Assistant instance.
23 | example: "/config/www/example.txt"
24 | upload:
25 | description: Upload a file from your Home Assistant instance to an Android TV / Fire TV device.
26 | fields:
27 | entity_id:
28 | description: Name(s) of Android TV / Fire TV entities.
29 | example: "media_player.android_tv_living_room"
30 | device_path:
31 | description: The filepath on the Android TV / Fire TV device.
32 | example: "/storage/emulated/0/Download/example.txt"
33 | local_path:
34 | description: The filepath on your Home Assistant instance.
35 | example: "/config/www/example.txt"
36 | learn_sendevent:
37 | description: Translate a key press on a remote into ADB 'sendevent' commands. You must press one button on the remote within 8 seconds of calling this service.
38 | fields:
39 | entity_id:
40 | description: Name(s) of Android TV / Fire TV entities.
41 | example: "media_player.android_tv_living_room"
42 |
--------------------------------------------------------------------------------
/custom_components/authenticated/__init__.py:
--------------------------------------------------------------------------------
1 | """The authenticated component."""
2 |
3 |
4 | class AuthenticatedBaseException(Exception):
5 | """Base exception for Authenticated."""
6 |
--------------------------------------------------------------------------------
/custom_components/authenticated/const.py:
--------------------------------------------------------------------------------
1 | """Constants for authenticated."""
2 |
3 | DOMAIN = "authenticated"
4 | INTEGRATION_VERSION = "21.9.0"
5 | ISSUE_URL = "https://github.com/custom-components/authenticated/issues"
6 |
7 | STARTUP = f"""
8 | -------------------------------------------------------------------
9 | {DOMAIN}
10 | Version: {INTEGRATION_VERSION}
11 | This is a custom component
12 | If you have any issues with this you need to open an issue here:
13 | https://github.com/custom-components/authenticated/issues
14 | -------------------------------------------------------------------
15 | """
16 |
17 |
18 | CONF_NOTIFY = "enable_notification"
19 | CONF_EXCLUDE = "exclude"
20 | CONF_EXCLUDE_CLIENTS = "exclude_clients"
21 | CONF_PROVIDER = "provider"
22 | CONF_LOG_LOCATION = "log_location"
23 |
24 | OUTFILE = ".ip_authenticated.yaml"
25 |
--------------------------------------------------------------------------------
/custom_components/authenticated/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "domain": "authenticated",
3 | "name": "Authenticated",
4 | "version": "21.9.0",
5 | "iot_class": "local_polling",
6 | "documentation": "https://github.com/custom-components/authenticated",
7 | "issue_tracker": "https://github.com/custom-components/authenticated/issues",
8 | "codeowners": [
9 | "@ludeeus"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/custom_components/authenticated/providers.py:
--------------------------------------------------------------------------------
1 | """Providers"""
2 | import logging
3 | import requests
4 | from . import AuthenticatedBaseException
5 |
6 | _LOGGER = logging.getLogger(__name__)
7 |
8 | PROVIDERS = {}
9 |
10 |
11 | def register_provider(classname):
12 | """Decorator used to register providers."""
13 | PROVIDERS[classname.name] = classname
14 | return classname
15 |
16 |
17 | class GeoProvider:
18 | """GeoProvider class."""
19 |
20 | url = None
21 |
22 | def __init__(self, ipaddr):
23 | """Initialize."""
24 | self.result = {}
25 | self.ipaddr = ipaddr
26 |
27 | @property
28 | def country(self):
29 | """Return country name or None."""
30 | return self.result.get("country")
31 |
32 | @property
33 | def region(self):
34 | """Return region name or None."""
35 | return self.result.get("region")
36 |
37 | @property
38 | def city(self):
39 | """Return city name or None."""
40 | return self.result.get("city")
41 |
42 | @property
43 | def computed_result(self):
44 | """Return the computed result."""
45 | if self.result is not None:
46 | return {"country": self.country, "region": self.region, "city": self.city}
47 | return None
48 |
49 | def update_geo_info(self):
50 | """Update Geo Information."""
51 | self.result = {}
52 | try:
53 | api = self.url.format(self.ipaddr)
54 | header = {"user-agent": "Home Assistant/Python"}
55 | data = requests.get(api, headers=header, timeout=5).json()
56 |
57 | if data.get("error"):
58 | if data.get("reason") == "RateLimited":
59 | raise AuthenticatedBaseException(
60 | "RatelimitError, try a different provider."
61 | )
62 |
63 | elif data.get("status", "success") == "error":
64 | return
65 |
66 | elif data.get("reserved"):
67 | return
68 |
69 | elif data.get("status", "success") == "fail":
70 | raise AuthenticatedBaseException(
71 | "[{}] - {}".format(
72 | self.ipaddr, data.get("message", "Unknown error.")
73 | )
74 | )
75 |
76 | self.result = data
77 | self.parse_data()
78 | except AuthenticatedBaseException as exception:
79 | _LOGGER.error(exception)
80 | except requests.exceptions.ConnectionError:
81 | pass
82 |
83 | def parse_data(self):
84 | """Parse data from geoprovider."""
85 | self.result = self.result
86 |
87 |
88 | @register_provider
89 | class IPApi(GeoProvider):
90 | """IPApi class."""
91 |
92 | url = "https://ipapi.co/{}/json"
93 | name = "ipapi"
94 |
95 | @property
96 | def country(self):
97 | """Return country name or None."""
98 | return self.result.get("country_name")
99 |
100 |
101 | @register_provider
102 | class ExtremeIPLookup(GeoProvider):
103 | """IPApi class."""
104 |
105 | url = "https://extreme-ip-lookup.com/json/{}"
106 | name = "extreme"
107 |
108 |
109 | @register_provider
110 | class IPVigilante(GeoProvider):
111 | """IPVigilante class."""
112 |
113 | url = "https://ipvigilante.com/json/{}"
114 | name = "ipvigilante"
115 |
116 | def parse_data(self):
117 | """Parse data from geoprovider."""
118 | self.result = self.result.get("data", {})
119 |
120 | @property
121 | def country(self):
122 | """Return country name or None."""
123 | return self.result.get("country_name")
124 |
125 | @property
126 | def region(self):
127 | """Return region name or None."""
128 | return self.result.get("subdivision_1_name")
129 |
130 | @property
131 | def city(self):
132 | """Return city name or None."""
133 | return self.result.get("city_name")
134 |
--------------------------------------------------------------------------------
/custom_components/composite/__init__.py:
--------------------------------------------------------------------------------
1 | """Composite Device Tracker."""
2 | import asyncio
3 | import logging
4 |
5 | import voluptuous as vol
6 |
7 | from homeassistant.requirements import async_process_requirements, RequirementsNotFound
8 | from homeassistant.components.device_tracker import DOMAIN as DT_DOMAIN
9 | from homeassistant.const import CONF_PLATFORM
10 | import homeassistant.helpers.config_validation as cv
11 |
12 | from .const import CONF_TIME_AS, DOMAIN, TZ_DEVICE_LOCAL, TZ_DEVICE_UTC
13 |
14 | CONF_TZ_FINDER = "tz_finder"
15 | DEFAULT_TZ_FINDER = "timezonefinderL==4.0.2"
16 | CONF_TZ_FINDER_CLASS = "tz_finder_class"
17 | TZ_FINDER_CLASS_OPTS = ["TimezoneFinder", "TimezoneFinderL"]
18 |
19 | CONFIG_SCHEMA = vol.Schema(
20 | {
21 | vol.Optional(DOMAIN, default=dict): vol.Schema(
22 | {
23 | vol.Optional(CONF_TZ_FINDER, default=DEFAULT_TZ_FINDER): cv.string,
24 | vol.Optional(
25 | CONF_TZ_FINDER_CLASS, default=TZ_FINDER_CLASS_OPTS[0]
26 | ): vol.In(TZ_FINDER_CLASS_OPTS),
27 | }
28 | ),
29 | },
30 | extra=vol.ALLOW_EXTRA,
31 | )
32 |
33 | _LOGGER = logging.getLogger(__name__)
34 |
35 |
36 | def setup(hass, config):
37 | if any(
38 | conf[CONF_TIME_AS] in (TZ_DEVICE_UTC, TZ_DEVICE_LOCAL)
39 | for conf in (config.get(DT_DOMAIN) or [])
40 | if conf[CONF_PLATFORM] == DOMAIN
41 | ):
42 | pkg = config[DOMAIN][CONF_TZ_FINDER]
43 | try:
44 | asyncio.run_coroutine_threadsafe(
45 | async_process_requirements(
46 | hass, "{}.{}".format(DOMAIN, DT_DOMAIN), [pkg]
47 | ),
48 | hass.loop,
49 | ).result()
50 | except RequirementsNotFound:
51 | _LOGGER.debug("Process requirements failed: %s", pkg)
52 | return False
53 | else:
54 | _LOGGER.debug("Process requirements suceeded: %s", pkg)
55 |
56 | if pkg.split("==")[0].strip().endswith("L"):
57 | from timezonefinderL import TimezoneFinder
58 |
59 | tf = TimezoneFinder()
60 | elif config[DOMAIN][CONF_TZ_FINDER_CLASS] == "TimezoneFinder":
61 | from timezonefinder import TimezoneFinder
62 |
63 | tf = TimezoneFinder()
64 | else:
65 | from timezonefinder import TimezoneFinderL
66 |
67 | tf = TimezoneFinderL()
68 | hass.data[DOMAIN] = tf
69 |
70 | return True
71 |
--------------------------------------------------------------------------------
/custom_components/composite/const.py:
--------------------------------------------------------------------------------
1 | """Constants for Composite Integration."""
2 | DOMAIN = "composite"
3 |
4 | CONF_REQ_MOVEMENT = "require_movement"
5 | CONF_TIME_AS = "time_as"
6 |
7 | TZ_UTC = "utc"
8 | TZ_LOCAL = "local"
9 | TZ_DEVICE_UTC = "device_or_utc"
10 | TZ_DEVICE_LOCAL = "device_or_local"
11 | # First item in list is default.
12 | TIME_AS_OPTS = [TZ_UTC, TZ_LOCAL, TZ_DEVICE_UTC, TZ_DEVICE_LOCAL]
13 |
--------------------------------------------------------------------------------
/custom_components/enable_debug.py:
--------------------------------------------------------------------------------
1 | """
2 | Support for enabling debug mode in HASS.
3 |
4 | Just add the following to yoru configuration.yaml
5 | enable_debug:
6 | """
7 | DOMAIN = 'enable_debug'
8 |
9 | async def async_setup(hass, config):
10 | hass.loop.set_debug(True)
11 | return True
12 |
--------------------------------------------------------------------------------
/custom_components/futures_cnn/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/custom_components/futures_cnn/__init__.py
--------------------------------------------------------------------------------
/custom_components/google_scholar/__init__.py:
--------------------------------------------------------------------------------
1 | """The google_scholar sensor component."""
2 |
--------------------------------------------------------------------------------
/custom_components/google_scholar/sensor.py:
--------------------------------------------------------------------------------
1 | """
2 | @ Author : Alok Saboo
3 | @ Description : Obtain citations from Google Scholar
4 | """
5 |
6 | import logging
7 | from datetime import date, datetime, timedelta
8 |
9 | import homeassistant.helpers.config_validation as cv
10 | import voluptuous as vol
11 | from homeassistant.components.sensor import PLATFORM_SCHEMA
12 | from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
13 | from homeassistant.helpers.entity import Entity
14 | from homeassistant.util import Throttle
15 |
16 | _LOGGER = logging.getLogger(__name__)
17 |
18 | ATTRIBUTION = "Data provided by Google.com"
19 | DEFAULT_ICON = 'mdi:school'
20 |
21 | SCAN_INTERVAL = timedelta(hours=24)
22 | MIN_TIME_BETWEEN_UPDATES = timedelta(hours=24)
23 |
24 | CONF_AUTHOR = 'author'
25 | DEFAULT_NAME = 'Google Scholar'
26 |
27 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
28 | {
29 | vol.Required(CONF_AUTHOR): cv.string,
30 | vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
31 | }
32 | )
33 |
34 | def setup_platform(hass, config, add_devices, discovery_info=None):
35 | """Set up the Google Scholar sensor."""
36 | author = config.get(CONF_AUTHOR)
37 | if not author:
38 | msg = "Warning: No author configured."
39 | hass.components.persistent_notification.create(msg, "Sensor google_scholar")
40 | _LOGGER.warning(msg)
41 | return
42 | rest = GoogleScholarData(author)
43 | name = config.get(CONF_NAME)
44 | add_devices([GoogleScholarSensor(name, rest)], True)
45 |
46 |
47 | class GoogleScholarSensor(Entity):
48 | """Implementing the Google Scholar sensor."""
49 |
50 | def __init__(self, name, rest):
51 | """Initialize the sensor."""
52 | self._icon = DEFAULT_ICON
53 | self.rest = rest
54 | self._state = None
55 | self._name = name
56 |
57 | @property
58 | def name(self):
59 | """Return the name of the sensor."""
60 | return self._name.rstrip()
61 |
62 | @property
63 | def icon(self):
64 | """Return the icon to use in the frontend, if any."""
65 | icon = DEFAULT_ICON
66 | return icon
67 |
68 | @property
69 | def state(self):
70 | """Return the state of the sensor."""
71 | try:
72 | self._state = float(self.rest.data['Citations'])
73 | except (KeyError, TypeError):
74 | self._state = None
75 | return self._state
76 |
77 | @property
78 | def device_state_attributes(self):
79 | """Return the state attributes of the sensor."""
80 | attr = {}
81 | attr['Author'] = self.rest.data['Author']
82 | attr['h-index'] = self.rest.data['hindex']
83 | attr[ATTR_ATTRIBUTION] = ATTRIBUTION
84 | return attr
85 |
86 | @property
87 | def available(self):
88 | """Could the device be accessed during the last update call."""
89 | return self.rest.available
90 |
91 | def update(self):
92 | """Update current date."""
93 | self.rest.update()
94 |
95 | class GoogleScholarData(object):
96 | """Get data from yahoo.com."""
97 |
98 | def __init__(self, author):
99 | """Initialize the data object."""
100 | self._author = author
101 | self.data = None
102 | self.available = True
103 |
104 | @Throttle(MIN_TIME_BETWEEN_UPDATES)
105 | def update(self):
106 | """Get the latest data from Google Scholar."""
107 | import scholarly
108 | try:
109 | results_json = {}
110 | results_json['Author'] = self._author
111 | author = self._author
112 | # Retrieve the author's data, fill-in, and print
113 | search_query = scholarly.search_author(author)
114 | author = next(search_query).fill()
115 | results_json['Citations'] = author.citedby
116 | results_json['hindex'] = author.hindex
117 | self.data = results_json
118 | self.available = True
119 | except requests.exceptions.ConnectionError:
120 | _LOGGER.error("Connection error")
121 | self.data = None
122 | self.available = False
123 |
--------------------------------------------------------------------------------
/custom_components/hacs/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "codeowners": [
3 | "@ludeeus"
4 | ],
5 | "config_flow": true,
6 | "dependencies": [
7 | "http",
8 | "websocket_api",
9 | "frontend",
10 | "persistent_notification",
11 | "lovelace"
12 | ],
13 | "documentation": "https://hacs.xyz/docs/configuration/start",
14 | "domain": "hacs",
15 | "iot_class": "cloud_polling",
16 | "issue_tracker": "https://github.com/hacs/integration/issues",
17 | "name": "HACS",
18 | "requirements": [
19 | "aiogithubapi>=21.11.0"
20 | ],
21 | "version": "1.22.0"
22 | }
--------------------------------------------------------------------------------
/custom_components/hacs/repositories/__init__.py:
--------------------------------------------------------------------------------
1 | """Initialize repositories."""
2 | from __future__ import annotations
3 |
4 | from ..enums import HacsCategory
5 | from .appdaemon import HacsAppdaemonRepository
6 | from .base import HacsRepository
7 | from .integration import HacsIntegrationRepository
8 | from .netdaemon import HacsNetdaemonRepository
9 | from .plugin import HacsPluginRepository
10 | from .python_script import HacsPythonScriptRepository
11 | from .theme import HacsThemeRepository
12 |
13 | RERPOSITORY_CLASSES: dict[HacsCategory, HacsRepository] = {
14 | HacsCategory.THEME: HacsThemeRepository,
15 | HacsCategory.INTEGRATION: HacsIntegrationRepository,
16 | HacsCategory.PYTHON_SCRIPT: HacsPythonScriptRepository,
17 | HacsCategory.APPDAEMON: HacsAppdaemonRepository,
18 | HacsCategory.NETDAEMON: HacsNetdaemonRepository,
19 | HacsCategory.PLUGIN: HacsPluginRepository,
20 | }
21 |
--------------------------------------------------------------------------------
/custom_components/hacs/repositories/appdaemon.py:
--------------------------------------------------------------------------------
1 | """Class for appdaemon apps in HACS."""
2 | from __future__ import annotations
3 |
4 | from typing import TYPE_CHECKING
5 |
6 | from aiogithubapi import AIOGitHubAPIException
7 |
8 | from ..enums import HacsCategory
9 | from ..exceptions import HacsException
10 | from ..utils.decorator import concurrent
11 | from .base import HacsRepository
12 |
13 | if TYPE_CHECKING:
14 | from ..base import HacsBase
15 |
16 |
17 | class HacsAppdaemonRepository(HacsRepository):
18 | """Appdaemon apps in HACS."""
19 |
20 | def __init__(self, hacs: HacsBase, full_name: str):
21 | """Initialize."""
22 | super().__init__(hacs=hacs)
23 | self.data.full_name = full_name
24 | self.data.full_name_lower = full_name.lower()
25 | self.data.category = HacsCategory.APPDAEMON
26 | self.content.path.local = self.localpath
27 | self.content.path.remote = "apps"
28 |
29 | @property
30 | def localpath(self):
31 | """Return localpath."""
32 | return f"{self.hacs.core.config_path}/appdaemon/apps/{self.data.name}"
33 |
34 | async def validate_repository(self):
35 | """Validate."""
36 | await self.common_validate()
37 |
38 | # Custom step 1: Validate content.
39 | try:
40 | addir = await self.repository_object.get_contents("apps", self.ref)
41 | except AIOGitHubAPIException:
42 | raise HacsException(
43 | f"Repository structure for {self.ref.replace('tags/','')} is not compliant"
44 | ) from None
45 |
46 | if not isinstance(addir, list):
47 | self.validate.errors.append("Repository structure not compliant")
48 |
49 | self.content.path.remote = addir[0].path
50 | self.content.objects = await self.repository_object.get_contents(
51 | self.content.path.remote, self.ref
52 | )
53 |
54 | # Handle potential errors
55 | if self.validate.errors:
56 | for error in self.validate.errors:
57 | if not self.hacs.status.startup:
58 | self.logger.error("%s %s", self, error)
59 | return self.validate.success
60 |
61 | @concurrent(concurrenttasks=10, backoff_time=5)
62 | async def update_repository(self, ignore_issues=False, force=False):
63 | """Update."""
64 | if not await self.common_update(ignore_issues, force) and not force:
65 | return
66 |
67 | # Get appdaemon objects.
68 | if self.repository_manifest:
69 | if self.data.content_in_root:
70 | self.content.path.remote = ""
71 |
72 | if self.content.path.remote == "apps":
73 | addir = await self.repository_object.get_contents(self.content.path.remote, self.ref)
74 | self.content.path.remote = addir[0].path
75 | self.content.objects = await self.repository_object.get_contents(
76 | self.content.path.remote, self.ref
77 | )
78 |
79 | # Set local path
80 | self.content.path.local = self.localpath
81 |
--------------------------------------------------------------------------------
/custom_components/hacs/repositories/plugin.py:
--------------------------------------------------------------------------------
1 | """Class for plugins in HACS."""
2 | from __future__ import annotations
3 |
4 | import json
5 | from typing import TYPE_CHECKING
6 |
7 | from ..exceptions import HacsException
8 | from ..utils.decorator import concurrent
9 | from .base import HacsRepository
10 |
11 | if TYPE_CHECKING:
12 | from ..base import HacsBase
13 |
14 |
15 | class HacsPluginRepository(HacsRepository):
16 | """Plugins in HACS."""
17 |
18 | def __init__(self, hacs: HacsBase, full_name: str):
19 | """Initialize."""
20 | super().__init__(hacs=hacs)
21 | self.data.full_name = full_name
22 | self.data.full_name_lower = full_name.lower()
23 | self.data.file_name = None
24 | self.data.category = "plugin"
25 | self.content.path.local = self.localpath
26 |
27 | @property
28 | def localpath(self):
29 | """Return localpath."""
30 | return f"{self.hacs.core.config_path}/www/community/{self.data.full_name.split('/')[-1]}"
31 |
32 | async def validate_repository(self):
33 | """Validate."""
34 | # Run common validation steps.
35 | await self.common_validate()
36 |
37 | # Custom step 1: Validate content.
38 | self.update_filenames()
39 |
40 | if self.content.path.remote is None:
41 | raise HacsException(
42 | f"Repository structure for {self.ref.replace('tags/','')} is not compliant"
43 | )
44 |
45 | if self.content.path.remote == "release":
46 | self.content.single = True
47 |
48 | # Handle potential errors
49 | if self.validate.errors:
50 | for error in self.validate.errors:
51 | if not self.hacs.status.startup:
52 | self.logger.error("%s %s", self, error)
53 | return self.validate.success
54 |
55 | @concurrent(concurrenttasks=10, backoff_time=5)
56 | async def update_repository(self, ignore_issues=False, force=False):
57 | """Update."""
58 | if not await self.common_update(ignore_issues, force) and not force:
59 | return
60 |
61 | # Get plugin objects.
62 | self.update_filenames()
63 |
64 | if self.content.path.remote is None:
65 | self.validate.errors.append(
66 | f"Repository structure for {self.ref.replace('tags/','')} is not compliant"
67 | )
68 |
69 | if self.content.path.remote == "release":
70 | self.content.single = True
71 |
72 | async def get_package_content(self):
73 | """Get package content."""
74 | try:
75 | package = await self.repository_object.get_contents("package.json", self.ref)
76 | package = json.loads(package.content)
77 |
78 | if package:
79 | self.data.authors = package["author"]
80 | except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
81 | pass
82 |
83 | def update_filenames(self) -> None:
84 | """Get the filename to target."""
85 | possible_locations = ("",) if self.data.content_in_root else ("release", "dist", "")
86 |
87 | # Handler for plug requirement 3
88 | if self.data.filename:
89 | valid_filenames = (self.data.filename,)
90 | else:
91 | valid_filenames = (
92 | f"{self.data.name.replace('lovelace-', '')}.js",
93 | f"{self.data.name}.js",
94 | f"{self.data.name}.umd.js",
95 | f"{self.data.name}-bundle.js",
96 | )
97 |
98 | for location in possible_locations:
99 | if location == "release":
100 | if not self.releases.objects:
101 | continue
102 | release = self.releases.objects[0]
103 | if not release.assets:
104 | continue
105 | asset = release.assets[0]
106 | for filename in valid_filenames:
107 | if filename == asset.name:
108 | self.data.file_name = filename
109 | self.content.path.remote = "release"
110 | break
111 |
112 | else:
113 | for filename in valid_filenames:
114 | if f"{location+'/' if location else ''}{filename}" in [
115 | x.full_path for x in self.tree
116 | ]:
117 | self.data.file_name = filename.split("/")[-1]
118 | self.content.path.remote = location
119 | break
120 |
--------------------------------------------------------------------------------
/custom_components/hacs/repositories/python_script.py:
--------------------------------------------------------------------------------
1 | """Class for python_scripts in HACS."""
2 | from __future__ import annotations
3 |
4 | from typing import TYPE_CHECKING
5 |
6 | from ..enums import HacsCategory
7 | from ..exceptions import HacsException
8 | from ..utils.decorator import concurrent
9 | from .base import HacsRepository
10 |
11 | if TYPE_CHECKING:
12 | from ..base import HacsBase
13 |
14 |
15 | class HacsPythonScriptRepository(HacsRepository):
16 | """python_scripts in HACS."""
17 |
18 | category = "python_script"
19 |
20 | def __init__(self, hacs: HacsBase, full_name: str):
21 | """Initialize."""
22 | super().__init__(hacs=hacs)
23 | self.data.full_name = full_name
24 | self.data.full_name_lower = full_name.lower()
25 | self.data.category = HacsCategory.PYTHON_SCRIPT
26 | self.content.path.remote = "python_scripts"
27 | self.content.path.local = self.localpath
28 | self.content.single = True
29 |
30 | @property
31 | def localpath(self):
32 | """Return localpath."""
33 | return f"{self.hacs.core.config_path}/python_scripts"
34 |
35 | async def validate_repository(self):
36 | """Validate."""
37 | # Run common validation steps.
38 | await self.common_validate()
39 |
40 | # Custom step 1: Validate content.
41 | if self.data.content_in_root:
42 | self.content.path.remote = ""
43 |
44 | compliant = False
45 | for treefile in self.treefiles:
46 | if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith(".py"):
47 | compliant = True
48 | break
49 | if not compliant:
50 | raise HacsException(
51 | f"Repository structure for {self.ref.replace('tags/','')} is not compliant"
52 | )
53 |
54 | # Handle potential errors
55 | if self.validate.errors:
56 | for error in self.validate.errors:
57 | if not self.hacs.status.startup:
58 | self.logger.error("%s %s", self, error)
59 | return self.validate.success
60 |
61 | async def async_post_registration(self):
62 | """Registration."""
63 | # Set name
64 | self.update_filenames()
65 |
66 | @concurrent(concurrenttasks=10, backoff_time=5)
67 | async def update_repository(self, ignore_issues=False, force=False):
68 | """Update."""
69 | if not await self.common_update(ignore_issues, force) and not force:
70 | return
71 |
72 | # Get python_script objects.
73 | if self.data.content_in_root:
74 | self.content.path.remote = ""
75 |
76 | compliant = False
77 | for treefile in self.treefiles:
78 | if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith(".py"):
79 | compliant = True
80 | break
81 | if not compliant:
82 | raise HacsException(
83 | f"Repository structure for {self.ref.replace('tags/','')} is not compliant"
84 | )
85 |
86 | # Update name
87 | self.update_filenames()
88 |
89 | def update_filenames(self) -> None:
90 | """Get the filename to target."""
91 | for treefile in self.tree:
92 | if treefile.full_path.startswith(
93 | self.content.path.remote
94 | ) and treefile.full_path.endswith(".py"):
95 | self.data.file_name = treefile.filename
96 |
--------------------------------------------------------------------------------
/custom_components/hacs/repositories/theme.py:
--------------------------------------------------------------------------------
1 | """Class for themes in HACS."""
2 | from __future__ import annotations
3 |
4 | from typing import TYPE_CHECKING
5 |
6 | from ..enums import HacsCategory
7 | from ..exceptions import HacsException
8 | from ..utils.decorator import concurrent
9 | from .base import HacsRepository
10 |
11 | if TYPE_CHECKING:
12 | from ..base import HacsBase
13 |
14 |
15 | class HacsThemeRepository(HacsRepository):
16 | """Themes in HACS."""
17 |
18 | def __init__(self, hacs: HacsBase, full_name: str):
19 | """Initialize."""
20 | super().__init__(hacs=hacs)
21 | self.data.full_name = full_name
22 | self.data.full_name_lower = full_name.lower()
23 | self.data.category = HacsCategory.THEME
24 | self.content.path.remote = "themes"
25 | self.content.path.local = self.localpath
26 | self.content.single = False
27 |
28 | @property
29 | def localpath(self):
30 | """Return localpath."""
31 | return f"{self.hacs.core.config_path}/themes/{self.data.file_name.replace('.yaml', '')}"
32 |
33 | async def async_post_installation(self):
34 | """Run post installation steps."""
35 | try:
36 | await self.hacs.hass.services.async_call("frontend", "reload_themes", {})
37 | except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
38 | pass
39 |
40 | async def validate_repository(self):
41 | """Validate."""
42 | # Run common validation steps.
43 | await self.common_validate()
44 |
45 | # Custom step 1: Validate content.
46 | compliant = False
47 | for treefile in self.treefiles:
48 | if treefile.startswith("themes/") and treefile.endswith(".yaml"):
49 | compliant = True
50 | break
51 | if not compliant:
52 | raise HacsException(
53 | f"Repository structure for {self.ref.replace('tags/','')} is not compliant"
54 | )
55 |
56 | if self.data.content_in_root:
57 | self.content.path.remote = ""
58 |
59 | # Handle potential errors
60 | if self.validate.errors:
61 | for error in self.validate.errors:
62 | if not self.hacs.status.startup:
63 | self.logger.error("%s %s", self, error)
64 | return self.validate.success
65 |
66 | async def async_post_registration(self):
67 | """Registration."""
68 | # Set name
69 | self.update_filenames()
70 | self.content.path.local = self.localpath
71 |
72 | @concurrent(concurrenttasks=10, backoff_time=5)
73 | async def update_repository(self, ignore_issues=False, force=False):
74 | """Update."""
75 | if not await self.common_update(ignore_issues, force) and not force:
76 | return
77 |
78 | # Get theme objects.
79 | if self.data.content_in_root:
80 | self.content.path.remote = ""
81 |
82 | # Update name
83 | self.update_filenames()
84 | self.content.path.local = self.localpath
85 |
86 | def update_filenames(self) -> None:
87 | """Get the filename to target."""
88 | for treefile in self.tree:
89 | if treefile.full_path.startswith(
90 | self.content.path.remote
91 | ) and treefile.full_path.endswith(".yaml"):
92 | self.data.file_name = treefile.filename
93 |
--------------------------------------------------------------------------------
/custom_components/hacs/sensor.py:
--------------------------------------------------------------------------------
1 | """Sensor platform for HACS."""
2 | from __future__ import annotations
3 |
4 | from homeassistant.components.sensor import SensorEntity
5 | from homeassistant.core import callback
6 |
7 | from .base import HacsBase
8 | from .const import DOMAIN, NAME_SHORT
9 |
10 |
11 | async def async_setup_platform(hass, _config, async_add_entities, _discovery_info=None):
12 | """Setup sensor platform."""
13 | async_add_entities([HACSSensor(hacs=hass.data.get(DOMAIN))])
14 |
15 |
16 | async def async_setup_entry(hass, _config_entry, async_add_devices):
17 | """Setup sensor platform."""
18 | async_add_devices([HACSSensor(hacs=hass.data.get(DOMAIN))])
19 |
20 |
21 | class HACSSensor(SensorEntity):
22 | """HACS Sensor class."""
23 |
24 | _attr_should_poll = False
25 | _attr_unique_id = "0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd"
26 | _attr_name = "hacs"
27 | _attr_icon = "hacs:hacs"
28 | _attr_unit_of_measurement = "pending update(s)"
29 |
30 | def __init__(self, hacs: HacsBase) -> None:
31 | """Initialize."""
32 | self.hacs = hacs
33 | self._attr_native_value = None
34 |
35 | async def async_update(self) -> None:
36 | """Manual updates of the sensor."""
37 | self._update()
38 |
39 | @callback
40 | def _update_and_write_state(self, *_) -> None:
41 | """Update the sensor and write state."""
42 | self._update()
43 | self.async_write_ha_state()
44 |
45 | @property
46 | def device_info(self) -> dict[str, any]:
47 | """Return device information about HACS."""
48 | info = {
49 | "identifiers": {(DOMAIN, self.unique_id)},
50 | "name": NAME_SHORT,
51 | "manufacturer": "hacs.xyz",
52 | "model": "",
53 | "sw_version": str(self.hacs.version),
54 | "configuration_url": "homeassistant://hacs",
55 | }
56 | # LEGACY can be removed when min HA version is 2021.12
57 | if self.hacs.core.ha_version >= "2021.12.0b0":
58 | # pylint: disable=import-outside-toplevel
59 | from homeassistant.helpers.device_registry import DeviceEntryType
60 |
61 | info["entry_type"] = DeviceEntryType.SERVICE
62 | else:
63 | info["entry_type"] = "service"
64 | return info
65 |
66 | @callback
67 | def _update(self) -> None:
68 | """Update the sensor."""
69 |
70 | repositories = [
71 | repository
72 | for repository in self.hacs.repositories.list_all
73 | if repository.pending_update
74 | ]
75 | self._attr_native_value = len(repositories)
76 | self._attr_extra_state_attributes = {
77 | "repositories": [
78 | {
79 | "name": repository.data.full_name,
80 | "display_name": repository.display_name,
81 | "installed_version": repository.display_installed_version,
82 | "available_version": repository.display_available_version,
83 | }
84 | for repository in repositories
85 | ]
86 | }
87 |
88 | async def async_added_to_hass(self) -> None:
89 | """Register for status events."""
90 | self.async_on_remove(
91 | self.hass.bus.async_listen("hacs/repository", self._update_and_write_state)
92 | )
93 |
--------------------------------------------------------------------------------
/custom_components/moon_here/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/custom_components/moon_here/__init__.py
--------------------------------------------------------------------------------
/custom_components/personalcapital/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/custom_components/personalcapital/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "domain": "personalcapital",
3 | "name": "Personal Capital",
4 | "documentation": "https://github.com/custom-components/sensor.personalcapital/blob/master/README.md",
5 | "dependencies": [],
6 | "codeowners": ["@iantrich"],
7 | "requirements": ["git+https://github.com/sanghviharshit/personalcapital.git@master#sanghviharshit"],
8 | "version": "0.1.3"
9 | }
10 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/__init__.py:
--------------------------------------------------------------------------------
1 | """The samsungtv component."""
2 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Remote control Samsung televisions via TCP/IP connection"""
4 |
5 |
6 | __title__ = "samsungctl"
7 | __version__ = "0.8.65b"
8 | __url__ = "https://github.com/kdschlosser/samsungctl"
9 | __author__ = "Lauri Niskanen, Kevin Schlosser"
10 | __author_email__ = "kevin.g.schlosser@gmail.com"
11 | __license__ = "MIT"
12 |
13 | from . import utils # NOQA
14 | from .config import Config # NOQA
15 | from .remote import Remote # NOQA
16 |
17 |
18 | def discover(timeout=8):
19 | from .upnp.discover import discover as _discover
20 | res = list(_discover(timeout=timeout))
21 |
22 | return res
23 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/cec_control/dispatcher.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | class Dispatcher(object):
5 | __callbacks = {}
6 |
7 | def __init__(self):
8 | import sys
9 | mod = sys.modules[__name__]
10 | self.__dict__ = mod.__dict__
11 | sys.modules[__name__] = self
12 |
13 | def connect(self, callback, signal):
14 | if signal not in self.__callbacks:
15 | self.__callbacks[signal] = set()
16 | self.__callbacks[signal].add(callback)
17 |
18 | def disconnect(self, callback, signal):
19 | if signal in self.__callbacks:
20 | self.__callbacks[signal].discard(callback)
21 | if not len(self.__callbacks[signal]):
22 | del self.__callbacks[signal]
23 |
24 | def send(self, signal, sender, *args, **kwargs):
25 | if signal in self.__callbacks:
26 | for callback in self.__callbacks[signal]:
27 | callback(signal=signal, sender=sender, *args, **kwargs)
28 |
29 |
30 | dispatcher = Dispatcher()
31 | connect = dispatcher.connect
32 | disconnect = dispatcher.disconnect
33 | send = dispatcher.send
34 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/exceptions.py:
--------------------------------------------------------------------------------
1 | class SamsungTVError(Exception):
2 | """Samsung TV Exception Base Class."""
3 |
4 | def __init__(self, *args):
5 | self._args = args
6 |
7 | def __str__(self):
8 |
9 | if self._args:
10 | return self.__class__.__doc__ % self._args
11 |
12 | elif b'%s' in self.__class__.__doc__:
13 | args = tuple([''] * self.__class__.__doc__.count(b'%s'))
14 | return self.__class__.__doc__ % args
15 |
16 | return self.__class__.__doc__
17 |
18 |
19 | class AccessDenied(SamsungTVError):
20 | """Connection was denied."""
21 |
22 |
23 | class ConnectionClosed(SamsungTVError):
24 | """Connection was closed."""
25 |
26 |
27 | class UnhandledResponse(SamsungTVError):
28 | """Received unknown response."""
29 |
30 |
31 | class NoTVFound(SamsungTVError):
32 | """Unable to locate a TV."""
33 |
34 |
35 | class ConfigError(SamsungTVError):
36 | """Base class for config exceptions."""
37 |
38 |
39 | class ConfigPortError(ConfigError):
40 | """Unknown connection port %s."""
41 |
42 |
43 | class ConfigHostError(ConfigError):
44 | """Host (IP) not specified."""
45 |
46 |
47 | class ConfigUnknownMethod(ConfigError):
48 | """Unknown connection method %s."""
49 |
50 |
51 | class ConfigParseError(ConfigError):
52 | """Config data is not json formatted or is not a formatted flat file."""
53 |
54 |
55 | class ConfigLoadError(ConfigError):
56 | """Config path specified cannot be located."""
57 |
58 |
59 | class ConfigSavePathError(ConfigError):
60 | """Config save path %s is not valid."""
61 |
62 |
63 | class ConfigSaveError(ConfigError):
64 | """Error saving config."""
65 |
66 |
67 | class ConfigSavePathNotSpecified(ConfigError):
68 | """Config save path was not specified."""
69 |
70 |
71 | class ConfigParameterError(ConfigError):
72 | """Parameter %s is not a config parameter."""
73 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/power_handler.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import threading
4 | from . import wake_on_lan
5 |
6 |
7 | CEC_POWER_STATUS_ON = 0
8 | CEC_POWER_STATUS_STANDBY = 1
9 | CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON = 2
10 | CEC_POWER_STATUS_IN_TRANSITION_ON_TO_STANDBY = 3
11 | CEC_POWER_STATUS_UNKNOWN = 153
12 |
13 |
14 | class PowerHandler(object):
15 | def __init__(self, parent):
16 | self._parent = parent
17 | self._event = threading.Event()
18 | self._thread = None
19 | self._power_on = False
20 | self._power_off = False
21 | self._is_powering_off = False
22 | self._is_powering_on = False
23 |
24 | @property
25 | def is_powering_off(self):
26 | return self._is_powering_off
27 |
28 | @is_powering_off.setter
29 | def is_powering_off(self, value):
30 | self._is_powering_off = value
31 | self._event.set()
32 |
33 | @property
34 | def is_powering_on(self):
35 | return self._is_powering_on
36 |
37 | @is_powering_on.setter
38 | def is_powering_on(self, value):
39 | self._is_powering_on = value
40 | self._event.set()
41 |
42 | def __on(self):
43 | self.is_powering_on = True
44 |
45 | if self._parent._cec is not None:
46 | if self._parent._cec.tv.power not in (
47 | CEC_POWER_STATUS_ON,
48 | CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON
49 | ):
50 | self._parent._cec.tv.power = True
51 | self._event.wait(120.0)
52 | else:
53 | self._parent.open()
54 |
55 | elif self._parent.mac_address:
56 | count = 0
57 | while not self._event.isSet():
58 | if count == 60:
59 | break
60 |
61 | wake_on_lan.send_wol(self._parent.mac_address)
62 | self._event.wait(2.0)
63 | count += 1
64 |
65 | self._power_on = False
66 |
67 | if self._power_off:
68 | self.__off()
69 |
70 | def __off(self):
71 | self.is_powering_off = True
72 |
73 | if self._parent._cec is not None:
74 | if self._parent._cec.tv.power not in (
75 | CEC_POWER_STATUS_STANDBY,
76 | CEC_POWER_STATUS_IN_TRANSITION_ON_TO_STANDBY
77 | ):
78 | self._parent._cec.tv.power = False
79 |
80 | else:
81 | if self._parent.config.method == 'websocket':
82 | power_key = 'KEY_POWER'
83 | else:
84 | power_key = 'KEY_POWEROFF'
85 |
86 | self._parent._send_key(power_key)
87 |
88 | self._event.wait(120.0)
89 |
90 | self._power_off = False
91 |
92 | if self._power_on:
93 | self.__on()
94 |
95 | def power_off(self):
96 | if self.is_powering_on:
97 | self._power_off = True
98 | elif not self.is_powering_off:
99 | self._thread = threading.Thread(target=self.__off)
100 | self._thread.daemon = True
101 | self._thread.start()
102 |
103 | def power_on(self):
104 | if self.is_powering_off:
105 | self._power_on = True
106 | elif not self.is_powering_on:
107 | self._thread = threading.Thread(target=self.__on)
108 | self._thread.daemon = True
109 | self._thread.start()
110 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/remote.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import six
4 | # noinspection PyCompatibility
5 | from . import exceptions
6 | from .remote_legacy import RemoteLegacy
7 | from .remote_websocket import RemoteWebsocket
8 | from .remote_encrypted import RemoteEncrypted
9 | from .config import Config
10 | from .key_mappings import KEYS
11 |
12 |
13 | class KeyWrapper(object):
14 | def __init__(self, remote, key):
15 | self.remote = remote
16 | self.key = key
17 |
18 | def __call__(self):
19 | self.key(self.remote)
20 |
21 |
22 | class RemoteMeta(type):
23 |
24 | def __call__(cls, config):
25 |
26 | if isinstance(config, dict):
27 | config = Config(**config)
28 |
29 | if config.method == "legacy":
30 | remote = RemoteLegacy(config)
31 | elif config.method == "websocket":
32 | remote = RemoteWebsocket(config)
33 | elif config.method == "encrypted":
34 | remote = RemoteEncrypted(config)
35 | else:
36 | raise exceptions.ConfigUnknownMethod(config.method)
37 |
38 | for name, key in KEYS.items():
39 | remote.__dict__[name] = KeyWrapper(remote, key)
40 |
41 | return remote
42 |
43 |
44 | @six.add_metaclass(RemoteMeta)
45 | class Remote(object):
46 |
47 | def __init__(self, config):
48 | self.config = config
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/remote_encrypted/aes.py:
--------------------------------------------------------------------------------
1 | from Crypto.Cipher import AES as _AES
2 | import binascii
3 | from .keys import wbKey
4 |
5 | # Padding for the input string --not
6 | # related to encryption itself.
7 | BLOCK_SIZE = 16 # Bytes
8 | IV = b'\x00' * BLOCK_SIZE
9 |
10 |
11 | MODE_CBC = _AES.MODE_CBC
12 |
13 |
14 | def pad(s):
15 | return (
16 | s +
17 | (BLOCK_SIZE - len(s) % BLOCK_SIZE) *
18 | chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
19 | )
20 |
21 |
22 | def unpad(s):
23 | return s[:-ord(s[len(s) - 1:])]
24 |
25 |
26 | class AES:
27 | """
28 | Usage:
29 | c = AESCipher('password').encrypt('message')
30 | m = AESCipher('password').decrypt(c)
31 | Tested under Python 3 and PyCrypto 2.6.1.
32 | """
33 |
34 | def __init__(self, key, mode=_AES.MODE_ECB, *args):
35 | try:
36 | self.key = binascii.unhexlify(key)
37 | except (TypeError, binascii.Error):
38 | self.key = key
39 |
40 | self._mode = mode
41 | self._args = args
42 |
43 | @property
44 | def _cipher(self):
45 | if self._mode == MODE_CBC:
46 | cipher = _AES.new(self.key, self._mode, IV)
47 | else:
48 | cipher = _AES.new(self.key, self._mode, *self._args)
49 |
50 | return cipher
51 |
52 | def decrypt(self, enc, remove_padding=True, unhexlify=binascii.unhexlify):
53 | if unhexlify is not None:
54 | data = unhexlify(enc)
55 | else:
56 | data = enc
57 |
58 | data = self._cipher.decrypt(data)
59 |
60 | if remove_padding:
61 | return unpad(data)
62 | else:
63 | return data
64 |
65 | def encrypt(self, raw, add_padding=True, encoding="utf8"):
66 | if add_padding:
67 | data = pad(raw)
68 | else:
69 | data = raw
70 |
71 | if encoding is not None:
72 | data = data.encode(encoding)
73 |
74 | return self._cipher.encrypt(data)
75 |
76 |
77 | AES_CIPHER = AES(wbKey, MODE_CBC)
78 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/remote_encrypted/keys.py:
--------------------------------------------------------------------------------
1 | import binascii
2 |
3 | publicKey = (
4 | '2cb12bb2cbf7cec713c0fff7b59ae68a96784ae517f41d259a45d20556177c0ffe951ca60'
5 | 'ec03a990c9412619d1bee30adc7773088c5721664cffcedacf6d251cb4b76e2fd7aef09b3'
6 | 'ae9f9496ac8d94ed2b262eee37291c8b237e880cc7c021fb1be0881f3d0bffa4234d3b8e6'
7 | 'a61530c00473ce169c025f47fcc001d9b8051'
8 | )
9 | privateKey = (
10 | '2fd6334713816fae018cdee4656c5033a8d6b00e8eaea07b3624999242e96247112dcd019'
11 | 'c4191f4643c3ce1605002b2e506e7f1d1ef8d9b8044e46d37c0d5263216a87cd783aa1854'
12 | '90436c4a0cb2c524e15bc1bfeae703bcbc4b74a0540202e8d79cadaae85c6f9c218bc1107'
13 | 'd1f5b4b9bd87160e782f4e436eeb17485ab4d'
14 | )
15 | transKey = '6c9474469ddf7578f3e5ad8a4c703d99'
16 | wbKey = 'abbb120c09e7114243d1fa0102163b27'
17 | prime = (
18 | 'b361eb0ab01c3439f2c16ffda7b05e3e320701ebee3e249123c3586765fd5bf6c1dfa88bb'
19 | '6bb5da3fde74737cd88b6a26c5ca31d81d18e3515533d08df619317063224cf0943a2f29a'
20 | '5fe60c1c31ddf28334ed76a6478a1122fb24c4a94c8711617ddfe90cf02e643cd82d4748d'
21 | '6d4a7ca2f47d88563aa2baf6482e124acd7dd'
22 | )
23 |
24 |
25 | BN_PRIME = int(prime, 16)
26 | BN_PRIVATE_KEY = int(privateKey, 16)
27 | PUBLIC_KEY = binascii.unhexlify(publicKey)
28 | TRANS_KEY = binascii.unhexlify(transKey)
29 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/remote_encrypted/rijndael/__init__.py:
--------------------------------------------------------------------------------
1 | from .rijndael import Rijndael
2 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/remote_encrypted/rijndael/constants.py:
--------------------------------------------------------------------------------
1 |
2 | shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]],
3 | [[0, 0], [1, 5], [2, 4], [3, 3]],
4 | [[0, 0], [1, 7], [3, 5], [4, 4]]]
5 |
6 | # [key_size][block_size]
7 | num_rounds = {
8 | 16: {16: 3}
9 | }
10 |
11 | A = [[1, 1, 1, 1, 1, 0, 0, 0],
12 | [0, 1, 1, 1, 1, 1, 0, 0],
13 | [0, 0, 1, 1, 1, 1, 1, 0],
14 | [0, 0, 0, 1, 1, 1, 1, 1],
15 | [1, 0, 0, 0, 1, 1, 1, 1],
16 | [1, 1, 0, 0, 0, 1, 1, 1],
17 | [1, 1, 1, 0, 0, 0, 1, 1],
18 | [1, 1, 1, 1, 0, 0, 0, 1]]
19 |
20 | # produce log and a_log tables, needed for multiplying in the
21 | # field GF(2^m) (generator = 3)
22 | a_log = [1]
23 | for i in range(255):
24 | j = (a_log[-1] << 1) ^ a_log[-1]
25 | if j & 0x100 != 0:
26 | j ^= 0x11B
27 | a_log.append(j)
28 |
29 | log = [0] * 256
30 | for i in range(1, 255):
31 | log[a_log[i]] = i
32 |
33 |
34 | # multiply two elements of GF(2^m)
35 | def mul(a, b):
36 | if a == 0 or b == 0:
37 | return 0
38 | return a_log[(log[a & 0xFF] + log[b & 0xFF]) % 255]
39 |
40 |
41 | # substitution box based on F^{-1}(x)
42 | box = [[0] * 8 for i in range(256)]
43 | box[1][7] = 1
44 | for i in range(2, 256):
45 | j = a_log[255 - log[i]]
46 | for t in range(8):
47 | box[i][t] = (j >> (7 - t)) & 0x01
48 |
49 | B = [0, 1, 1, 0, 0, 0, 1, 1]
50 |
51 | # affine transform: box[i] <- B + A*box[i]
52 | cox = [[0] * 8 for i in range(256)]
53 | for i in range(256):
54 | for t in range(8):
55 | cox[i][t] = B[t]
56 | for j in range(8):
57 | cox[i][t] ^= A[t][j] * box[i][j]
58 |
59 | # S-boxes and inverse S-boxes
60 | S = [0] * 256
61 | Si = [0] * 256
62 | for i in range(256):
63 | S[i] = cox[i][0] << 7
64 | for t in range(1, 8):
65 | S[i] ^= cox[i][t] << (7-t)
66 | Si[S[i] & 0xFF] = i
67 |
68 | # T-boxes
69 | G = [
70 | [2, 1, 1, 3],
71 | [3, 2, 1, 1],
72 | [1, 3, 2, 1],
73 | [1, 1, 3, 2]
74 | ]
75 |
76 | AA = [[0] * 8 for i in range(4)]
77 |
78 | for i in range(4):
79 | for j in range(4):
80 | AA[i][j] = G[i][j]
81 | AA[i][i+4] = 1
82 |
83 | for i in range(4):
84 | pivot = AA[i][i]
85 | for j in range(8):
86 | if AA[i][j] != 0:
87 | AA[i][j] = a_log[
88 | (255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255
89 | ]
90 | for t in range(4):
91 | if i != t:
92 | for j in range(i+1, 8):
93 | AA[t][j] ^= mul(AA[i][j], AA[t][i])
94 | AA[t][i] = 0
95 |
96 | iG = [[0] * 4 for i in range(4)]
97 |
98 | for i in range(4):
99 | for j in range(4):
100 | iG[i][j] = AA[i][j + 4]
101 |
102 |
103 | def mul4(a, bs):
104 | if a == 0:
105 | return 0
106 | rr = 0
107 | for b in bs:
108 | rr <<= 8
109 | if b != 0:
110 | rr = rr | mul(a, b)
111 | return rr
112 |
113 |
114 | T1 = []
115 | T2 = []
116 | T3 = []
117 | T4 = []
118 | T5 = []
119 | T6 = []
120 | T7 = []
121 | T8 = []
122 | U1 = []
123 | U2 = []
124 | U3 = []
125 | U4 = []
126 |
127 | for t in range(256):
128 | s = S[t]
129 | T1.append(mul4(s, G[0]))
130 | T2.append(mul4(s, G[1]))
131 | T3.append(mul4(s, G[2]))
132 | T4.append(mul4(s, G[3]))
133 |
134 | s = Si[t]
135 | T5.append(mul4(s, iG[0]))
136 | T6.append(mul4(s, iG[1]))
137 | T7.append(mul4(s, iG[2]))
138 | T8.append(mul4(s, iG[3]))
139 |
140 | U1.append(mul4(t, iG[0]))
141 | U2.append(mul4(t, iG[1]))
142 | U3.append(mul4(t, iG[2]))
143 | U4.append(mul4(t, iG[3]))
144 |
145 | # round constants
146 | r_con = [1]
147 | r = 1
148 | for t in range(1, 30):
149 | r = mul(2, r)
150 | r_con.append(r)
151 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/remote_encrypted/rijndael/paddings.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | class PaddingBase:
4 | def __init__(self, block_size):
5 | self.block_size = block_size
6 |
7 | def encode(self, source):
8 | raise NotImplementedError
9 |
10 | def decode(self, source):
11 | raise NotImplementedError
12 |
13 |
14 | class ZeroPadding(PaddingBase):
15 | """
16 | Specified for hashes and MACs as Padding Method 1 in ISO/IEC 10118-1 and
17 | ISO/IEC 9797-1.
18 | """
19 |
20 | def encode(self, source):
21 | pad_size = (
22 | self.block_size -
23 | (
24 | (len(source) + self.block_size - 1) % self.block_size + 1
25 | )
26 | )
27 | return source + b'\0' * pad_size
28 |
29 | def decode(self, source):
30 | assert len(source) % self.block_size == 0
31 | offset = len(source)
32 | if offset == 0:
33 | return b''
34 | end = offset - self.block_size + 1
35 |
36 | while offset > end:
37 | offset -= 1
38 | if source[offset:offset+1] != b'\0':
39 | return source[:offset + 1]
40 |
41 |
42 | class Pkcs7Padding(PaddingBase):
43 | """
44 | Technique for padding a string as defined in RFC 2315, section 10.3,
45 | note #2
46 | """
47 |
48 | def encode(self, source):
49 | amount_to_pad = self.block_size - (len(source) % self.block_size)
50 | amount_to_pad = (
51 | self.block_size if amount_to_pad == 0 else amount_to_pad
52 | )
53 | pad = chr(amount_to_pad).encode()
54 | return source + pad * amount_to_pad
55 |
56 | def decode(self, source):
57 | return source[:-source[-1]]
58 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/upnp/UPNP_Device/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """UPNP device mapping tool"""
3 |
4 |
5 | import logging
6 | from logging import NullHandler
7 |
8 | logger = logging.getLogger(__name__)
9 | logger.addHandler(NullHandler())
10 | logging.basicConfig(format="%(message)s", level=None)
11 |
12 |
13 | from .discover import discover as _discover # NOQA
14 | from .listen import listen # NOQA
15 | from .upnp_class import UPNPObject # NOQA
16 |
17 |
18 | def discover(timeout=5, log_level=None, ips=(), dump=''):
19 | for addr, locations in _discover(timeout, log_level, ips, dump):
20 | yield UPNPObject(addr, locations, dump)
21 |
22 |
23 | __title__ = "UPNP_Device"
24 | __version__ = "0.1.0b"
25 | __url__ = "https://github.com/kdschlosser/UPNP_Device"
26 | __author__ = "Kevin Schlosser"
27 | __author_email__ = "kevin.g.schlosser@gmail.com"
28 | __all__ = (
29 | '__title__', '__version__', '__url__', '__author__', '__author_email__',
30 | 'discover'
31 | )
32 |
33 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/upnp/UPNP_Device/icon.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import requests
4 | import logging
5 | logger = logging.getLogger(__name__)
6 |
7 |
8 | class Icon(object):
9 |
10 | def __init__(self, parent, url, node):
11 | self.__parent = parent
12 | self.mime_type = None
13 | self.width = None
14 | self.height = None
15 | self.depth = None
16 | self.url = None
17 |
18 | for item in node:
19 | tag = item.tag
20 | try:
21 | text = int(item.text)
22 | except ValueError:
23 | text = item.text
24 |
25 | if tag == 'url':
26 | name = text.split('/')[-1]
27 | name = name.replace('.', '_')
28 | self.__name__ = name
29 | text = url + text
30 |
31 | setattr(self, tag, text)
32 |
33 | @property
34 | def data(self):
35 | content = requests.get(self.url).content
36 | return "".join(map(chr, list(content)))
37 |
38 | @property
39 | def access_point(self):
40 | return self.__parent.access_point + '.' + self.__name__
41 |
42 | def __str__(self, indent=''):
43 | output = TEMPLATE.format(
44 | indent=indent,
45 | access_point=self.access_point,
46 | name=self.__name__,
47 | mime_type=self.mime_type,
48 | width=self.width,
49 | height=self.height,
50 | depth=self.depth,
51 | url=self.url,
52 | )
53 |
54 | return output
55 |
56 | @property
57 | def as_dict(self):
58 | res = dict(
59 | name=self.__name__,
60 | mime_type=self.mime_type,
61 | width=self.width,
62 | height=self.height,
63 | depth=self.depth,
64 | url=self.url,
65 | )
66 | return res
67 |
68 |
69 | TEMPLATE = '''
70 | {indent}Icon name: {name}
71 | {indent}Access point: {access_point}
72 | {indent}----------------------------------------------
73 | {indent} Mime Type: {mime_type}
74 | {indent} Width: {width}
75 | {indent} Height: {height}
76 | {indent} Color Depth: {depth}
77 | {indent} URL: {url}
78 | '''
79 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/upnp/UPNP_Device/instance_singleton.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | class InstanceSingleton(type):
5 | _objects = {}
6 |
7 | def __call__(cls, id, *args, **kwargs):
8 |
9 | if id not in InstanceSingleton._objects:
10 | InstanceSingleton._objects[id] = (
11 | super(InstanceSingleton, cls).__call__(id, *args, **kwargs)
12 | )
13 | else:
14 | try:
15 | InstanceSingleton._objects[id](id, *args, **kwargs)
16 | except TypeError:
17 | pass
18 |
19 | return InstanceSingleton._objects[id]
20 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_080b/upnp/UPNP_Device/xmlns.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ENVELOPE_XMLNS = 'http://schemas.xmlsoap.org/soap/envelope/'
4 |
5 |
6 | def strip_xmlns(root):
7 | def iter_node(n):
8 | nsmap = n.nsmap
9 | for child in n:
10 | nsmap.update(iter_node(child))
11 | return nsmap
12 |
13 | xmlns = list('{' + item + '}' for item in iter_node(root).values())
14 |
15 | def strip_node(n):
16 | for item in xmlns:
17 | n.tag = n.tag.replace(item, '')
18 |
19 | for child in n[:]:
20 | try:
21 | strip_node(child)
22 | except AttributeError:
23 | n.remove(child)
24 | strip_node(root)
25 |
26 | return root
27 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_qled/__init__.py:
--------------------------------------------------------------------------------
1 | """Remote control Samsung televisions via TCP/IP connection"""
2 |
3 | from .remote import Remote
4 | from .application import Application
5 | from .upnp import Upnp
6 |
7 | __title__ = "samsungctl"
8 | __version__ = "0.8"
9 | __url__ = "https://github.com/giefca/samsungctl"
10 | __author__ = "Lauri Niskanen"
11 | __author_email__ = ""
12 | __license__ = "MIT"
13 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_qled/application.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import requests
4 |
5 | APP_URL_FORMAT = "http://{}:8001/api/v2/applications/{}"
6 |
7 | APPS = {'YouTube': '111299001912',
8 | 'Plex': '3201512006963',
9 | 'Prime Video': '3201512006785',
10 | 'Universal Guide': '3201710015067',
11 | 'Netflix': '11101200001',
12 | 'Apple TV': '3201807016597',
13 | 'Steam Link': '3201702011851',
14 | 'MyCANAL': '3201606009910',
15 | # 'Browser': 'org.tizen.browser',
16 | 'Spotify': '3201606009684',
17 | 'Molotov': '3201611011210',
18 | 'SmartThings': '3201710015016',
19 | 'e-Manual': '20182100010',
20 | 'Google Play': '3201601007250',
21 | 'Gallery': '3201710015037',
22 | 'Rakuten TV': '3201511006428',
23 | 'RMC Sport': '3201704012212',
24 | 'MYTF1 VOD': '3201905018355',
25 | 'Blacknut': '3201811017333',
26 | 'Facebook Watch': '11091000000',
27 | 'McAfee Security for TV': '3201612011418',
28 | 'OCS': '3201703012029',
29 | 'Playzer': '3201810017091'
30 | }
31 |
32 |
33 | class Application:
34 | """ Handle applications."""
35 | def __init__(self, config):
36 | self._ip = config['host']
37 |
38 | def state(self, app):
39 | """ Get the state of the app."""
40 | try:
41 | response = requests.get(APP_URL_FORMAT.format(self._ip, APPS[app]), timeout=0.2)
42 | return response.content.decode('utf-8')
43 | except:
44 | return """{"id":"","name":"","running":false,"version":"","visible":false}"""
45 |
46 | def is_running(self, app):
47 | """ Is the app running."""
48 | app_state = json.loads(self.state(app))
49 | return app_state['running']
50 |
51 | def is_visible(self, app):
52 | """ Is the app visible."""
53 | app_state = json.loads(self.state(app))
54 |
55 | if 'visible' in app_state:
56 | return app_state['visible']
57 |
58 | return False
59 |
60 | def start(self, app):
61 | """ Start an application."""
62 | return os.system("curl -X POST " + APP_URL_FORMAT.format(self._ip, APPS[app]))
63 |
64 | def stop(self, app):
65 | """ Stop an application."""
66 | return os.system("curl -X DELETE " + APP_URL_FORMAT.format(self._ip, APPS[app]))
67 |
68 | def current_app(self):
69 | """ Get the current visible app."""
70 | current_app = None
71 | for app in APPS:
72 | if (self.is_visible(app) is True):
73 | current_app = app
74 | return current_app
75 |
76 | def app_list(self):
77 | """ List running apps."""
78 | apps = []
79 | for app in APPS:
80 | if (self.is_running(app) is True):
81 | apps.append(app)
82 | return apps
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_qled/exceptions.py:
--------------------------------------------------------------------------------
1 | class AccessDenied(Exception):
2 | """Connection was denied."""
3 | pass
4 |
5 |
6 | class ConnectionClosed(Exception):
7 | """Connection was closed."""
8 | pass
9 |
10 |
11 | class UnhandledResponse(Exception):
12 | """Received unknown response."""
13 | pass
14 |
15 |
16 | class UnknownMethod(Exception):
17 | """Unknown method."""
18 | pass
19 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_qled/interactive.py:
--------------------------------------------------------------------------------
1 | import curses
2 |
3 |
4 | _mappings = [
5 | ["p", "KEY_POWEROFF", "P", "Power off"],
6 | ["KEY_UP", "KEY_UP", "Up", "Up"],
7 | ["KEY_DOWN", "KEY_DOWN", "Down", "Down"],
8 | ["KEY_LEFT", "KEY_LEFT", "Left", "Left"],
9 | ["KEY_RIGHT", "KEY_RIGHT", "Right", "Right"],
10 | ["KEY_PPAGE", "KEY_CHUP", "Page Up", "P Up"],
11 | ["KEY_NPAGE", "KEY_CHDOWN", "Page Down", "P Down"],
12 | ["\n", "KEY_ENTER", "Enter", "Enter"],
13 | ["KEY_BACKSPACE", "KEY_RETURN", "Backspace", "Return"],
14 | ["e", "KEY_EXIT", "E", "Exit"],
15 | ["h", "KEY_CONTENTS", "H", "Smart Hub"],
16 | ["l", "KEY_CH_LIST", "L", "Channel List"],
17 | ["m", "KEY_MENU", "M", "Menu"],
18 | ["s", "KEY_SOURCE", "S", "Source"],
19 | ["g", "KEY_GUIDE", "G", "Guide"],
20 | ["t", "KEY_TOOLS", "T", "Tools"],
21 | ["i", "KEY_INFO", "I", "Info"],
22 | ["z", "KEY_RED", "Z", "A / Red"],
23 | ["x", "KEY_GREEN", "X", "B / Green"],
24 | ["c", "KEY_YELLOW", "C", "C / Yellow"],
25 | ["v", "KEY_BLUE", "V", "D / Blue"],
26 | ["d", "KEY_PANNEL_CHDOWN", "D", "3D"],
27 | ["+", "KEY_VOLUP", "+", "Volume Up"],
28 | ["-", "KEY_VOLDOWN", "-", "Volume Down"],
29 | ["*", "KEY_MUTE", "*", "Mute"],
30 | ["0", "KEY_0", "0", "0"],
31 | ["1", "KEY_1", "1", "1"],
32 | ["2", "KEY_2", "2", "2"],
33 | ["3", "KEY_3", "3", "3"],
34 | ["4", "KEY_4", "4", "4"],
35 | ["5", "KEY_5", "5", "5"],
36 | ["6", "KEY_6", "6", "6"],
37 | ["7", "KEY_7", "7", "7"],
38 | ["8", "KEY_8", "8", "8"],
39 | ["9", "KEY_9", "9", "9"],
40 | ["KEY_F(1)", "KEY_DTV", "F1", "TV Source"],
41 | ["KEY_F(2)", "KEY_HDMI", "F2", "HDMI Source"],
42 | ]
43 |
44 |
45 | def run(remote):
46 | """Run interactive remote control application."""
47 | curses.wrapper(_control, remote)
48 |
49 |
50 | def _control(stdscr, remote):
51 | height, width = stdscr.getmaxyx()
52 |
53 | stdscr.addstr("Interactive mode, press 'Q' to exit.\n")
54 | stdscr.addstr("Key mappings:\n")
55 |
56 | column_len = max(len(mapping[2]) for mapping in _mappings) + 1
57 | mappings_dict = {}
58 | for mapping in _mappings:
59 | mappings_dict[mapping[0]] = mapping[1]
60 |
61 | row = stdscr.getyx()[0] + 2
62 | if row < height:
63 | line = " {}= {} ({})\n".format(mapping[2].ljust(column_len),
64 | mapping[3], mapping[1])
65 | stdscr.addstr(line)
66 | elif row == height:
67 | stdscr.addstr("[Terminal is too small to show all keys]\n")
68 |
69 | running = True
70 | while running:
71 | key = stdscr.getkey()
72 |
73 | if key == "q":
74 | running = False
75 |
76 | if key in mappings_dict:
77 | remote.control(mappings_dict[key])
78 |
79 | try:
80 | stdscr.addstr(".")
81 | except curses.error:
82 | stdscr.deleteln()
83 | stdscr.move(stdscr.getyx()[0], 0)
84 | stdscr.addstr(".")
85 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_qled/remote.py:
--------------------------------------------------------------------------------
1 | from . import exceptions
2 | from .remote_legacy import RemoteLegacy
3 | from .remote_websocket import RemoteWebsocket
4 | from .application import Application
5 |
6 |
7 | class Remote:
8 | def __init__(self, config):
9 | if config["method"] == "legacy":
10 | self.remote = RemoteLegacy(config)
11 | elif config["method"] == "websocket":
12 | self.remote = RemoteWebsocket(config)
13 | else:
14 | raise exceptions.UnknownMethod()
15 |
16 | def __enter__(self):
17 | return self.remote.__enter__()
18 |
19 | def __exit__(self, type, value, traceback):
20 | self.remote.__exit__(type, value, traceback)
21 |
22 | def close(self):
23 | return self.remote.close()
24 |
25 | def control(self, key):
26 | return self.remote.control(key)
27 |
28 | def get_installed_apps(self):
29 | return self.remote.get_installed_apps()
30 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_qled/remote_legacy.py:
--------------------------------------------------------------------------------
1 | import base64
2 | import logging
3 | import socket
4 | import time
5 |
6 | from . import exceptions
7 |
8 |
9 | class RemoteLegacy():
10 | """Object for remote control connection."""
11 |
12 | def __init__(self, config):
13 | if not config["port"]:
14 | config["port"] = 55000
15 |
16 | """Make a new connection."""
17 | self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
18 |
19 | if config["timeout"]:
20 | self.connection.settimeout(config["timeout"])
21 |
22 | self.connection.connect((config["host"], config["port"]))
23 |
24 | payload = b"\x64\x00" \
25 | + self._serialize_string(config["description"]) \
26 | + self._serialize_string(config["id"]) \
27 | + self._serialize_string(config["name"])
28 | packet = b"\x00\x00\x00" + self._serialize_string(payload, True)
29 |
30 | logging.info("Sending handshake.")
31 | self.connection.send(packet)
32 | self._read_response(True)
33 |
34 | def __enter__(self):
35 | return self
36 |
37 | def __exit__(self, type, value, traceback):
38 | self.close()
39 |
40 | def close(self):
41 | """Close the connection."""
42 | if self.connection:
43 | self.connection.close()
44 | self.connection = None
45 | logging.debug("Connection closed.")
46 |
47 | def control(self, key):
48 | """Send a control command."""
49 | if not self.connection:
50 | raise exceptions.ConnectionClosed()
51 |
52 | payload = b"\x00\x00\x00" + self._serialize_string(key)
53 | packet = b"\x00\x00\x00" + self._serialize_string(payload, True)
54 |
55 | logging.info("Sending control command: %s", key)
56 | self.connection.send(packet)
57 | self._read_response()
58 | time.sleep(self._key_interval)
59 |
60 | _key_interval = 0.2
61 |
62 | def _read_response(self, first_time=False):
63 | header = self.connection.recv(3)
64 | tv_name_len = int.from_bytes(header[1:3],
65 | byteorder="little")
66 | tv_name = self.connection.recv(tv_name_len)
67 |
68 | if first_time:
69 | logging.debug("Connected to '%s'.", tv_name.decode())
70 |
71 | response_len = int.from_bytes(self.connection.recv(2),
72 | byteorder="little")
73 | response = self.connection.recv(response_len)
74 |
75 | if len(response) == 0:
76 | self.close()
77 | raise exceptions.ConnectionClosed()
78 |
79 | if response == b"\x64\x00\x01\x00":
80 | logging.debug("Access granted.")
81 | return
82 | elif response == b"\x64\x00\x00\x00":
83 | raise exceptions.AccessDenied()
84 | elif response[0:1] == b"\x0a":
85 | if first_time:
86 | logging.warning("Waiting for authorization...")
87 | return self._read_response()
88 | elif response[0:1] == b"\x65":
89 | logging.warning("Authorization cancelled.")
90 | raise exceptions.AccessDenied()
91 | elif response == b"\x00\x00\x00\x00":
92 | logging.debug("Control accepted.")
93 | return
94 |
95 | raise exceptions.UnhandledResponse(response)
96 |
97 | @staticmethod
98 | def _serialize_string(string, raw=False):
99 | if isinstance(string, str):
100 | string = str.encode(string)
101 |
102 | if not raw:
103 | string = base64.b64encode(string)
104 |
105 | return bytes([len(string)]) + b"\x00" + string
106 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_qled/remote_websocket.py:
--------------------------------------------------------------------------------
1 | import base64
2 | import json
3 | import logging
4 | import socket
5 | import time
6 | import os
7 | import ssl
8 |
9 | from . import exceptions
10 |
11 |
12 | URL_FORMAT = "ws://{}:{}/api/v2/channels/samsung.remote.control?name={}"
13 | SSL_URL_FORMAT = "wss://{}:{}/api/v2/channels/samsung.remote.control?name={}"
14 |
15 |
16 | class RemoteWebsocket():
17 | """Object for remote control connection."""
18 |
19 | def __init__(self, config):
20 | import websocket
21 | self.token_file = os.path.dirname(os.path.realpath(__file__)) + "/token.txt"
22 |
23 | if not config["port"]:
24 | config["port"] = 8001
25 |
26 | if config["timeout"] == 0:
27 | config["timeout"] = None
28 |
29 | if config["port"] == 8001:
30 | url = URL_FORMAT.format(config["host"], config["port"],
31 | self._serialize_string(config["name"]))
32 |
33 | self.connection = websocket.create_connection(url, config["timeout"])
34 | elif config["port"] == 8002:
35 | url = SSL_URL_FORMAT.format(config["host"], config["port"],
36 | self._serialize_string(config["name"]))
37 | if os.path.isfile(self.token_file):
38 | with open(self.token_file, "r") as token_file:
39 | url += "&token=" + token_file.readline()
40 | self.connection = websocket.create_connection(url, config["timeout"], sslopt={"cert_reqs": ssl.CERT_NONE})
41 |
42 | self._read_response()
43 |
44 | def __enter__(self):
45 | return self
46 |
47 | def __exit__(self, type, value, traceback):
48 | self.close()
49 |
50 | def close(self):
51 | """Close the connection."""
52 | if self.connection:
53 | self.connection.close()
54 | self.connection = None
55 | logging.debug("Connection closed.")
56 |
57 | def control(self, key):
58 | """Send a control command."""
59 | if not self.connection:
60 | raise exceptions.ConnectionClosed()
61 |
62 | payload = json.dumps({
63 | "method": "ms.remote.control",
64 | "params": {
65 | "Cmd": "Click",
66 | "DataOfCmd": key,
67 | "Option": "false",
68 | "TypeOfRemote": "SendRemoteKey"
69 | }
70 | })
71 |
72 | logging.info("Sending control command: %s", key)
73 | self.connection.send(payload)
74 | time.sleep(self._key_interval)
75 |
76 | _key_interval = 0.2
77 |
78 | def _read_response(self):
79 | response = self.connection.recv()
80 | response = json.loads(response)
81 |
82 | if 'data' in response and 'token' in response["data"]:
83 | with open(self.token_file, "w") as token_file:
84 | token_file.write(response['data']["token"])
85 |
86 | if response["event"] != "ms.channel.connect":
87 | self.close()
88 | raise exceptions.UnhandledResponse(response)
89 |
90 | logging.debug("Access granted.")
91 |
92 | @staticmethod
93 | def _serialize_string(string):
94 | if isinstance(string, str):
95 | string = str.encode(string)
96 |
97 | return base64.b64encode(string).decode("utf-8")
98 |
99 | def get_installed_apps(self):
100 | """Send a control command."""
101 | if not self.connection:
102 | raise exceptions.ConnectionClosed()
103 |
104 | payload = json.dumps({
105 | "method": "ms.channel.emit",
106 | "params": {
107 | "data": "",
108 | "event": "ed.installedApp.get",
109 | # "event": "ed.edenApp.get",
110 | "to": "host"
111 | }
112 | })
113 |
114 | logging.info("Asking for installed apps.")
115 | self.connection.send(payload)
116 | data = json.loads(self.connection.recv())
117 | installed_apps = []
118 | for app in data['data']['data']:
119 | installed_apps.append([app['name'], app['appId']])
120 | return installed_apps
121 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungctl_qled/upnp.py:
--------------------------------------------------------------------------------
1 | import xml.etree.ElementTree as ET
2 | import requests
3 |
4 | class Upnp:
5 | def __init__(self, config):
6 | self._host = config['host']
7 | self.mute = False
8 | self.volume = 0
9 |
10 | def SOAPrequest(self, action, arguments, protocole):
11 | headers = {'SOAPAction': '"urn:schemas-upnp-org:service:{protocole}:1#{action}"'.format(action=action, protocole=protocole), 'content-type': 'text/xml'}
12 | body = """
13 |
14 |
15 |
16 | 0
17 | {arguments}
18 |
19 |
20 | """.format(action=action, arguments=arguments, protocole=protocole)
21 | response = None
22 | try:
23 | response = requests.post("http://{host}:9197/upnp/control/{protocole}1".format(host=self._host, protocole=protocole), data=body, headers=headers, timeout=0.2)
24 | response = response.content
25 | except:
26 | pass
27 | return response
28 |
29 | def get_volume(self):
30 | self.volume = 0
31 | response = self.SOAPrequest('GetVolume', "Master", 'RenderingControl')
32 | if (response is not None):
33 | volume_xml = response.decode('utf8')
34 | tree = ET.fromstring(volume_xml)
35 | for elem in tree.iter(tag='CurrentVolume'):
36 | self.volume = elem.text
37 | return self.volume
38 |
39 | def set_volume(self, volume):
40 | self.SOAPrequest('SetVolume', "Master{}".format(volume), 'RenderingControl')
41 |
42 | def get_mute(self):
43 | self.mute = False
44 | response = self.SOAPrequest('GetMute', "Master", 'RenderingControl')
45 | if (response is not None):
46 | mute_xml = response.decode('utf8')
47 | tree = ET.fromstring(mute_xml)
48 | for elem in tree.iter(tag='CurrentMute'):
49 | mute = elem.text
50 | if (int(mute) == 0):
51 | self.mute = False
52 | else:
53 | self.mute = True
54 | return self.mute
55 |
56 | def set_current_media(self, url):
57 | """ Set media to playback."""
58 | self.SOAPrequest('SetAVTransportURI', "{url}".format(url=url), 'AVTransport')
59 |
60 | def play(self):
61 | """ Play media that was already set as current."""
62 | self.SOAPrequest('Play', "1", 'AVTransport')
--------------------------------------------------------------------------------
/custom_components/samsungtv_custom/samsungtvws/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | SamsungTVWS - Samsung Smart TV WS API wrapper
3 |
4 | Copyright (C) 2019 Xchwarze
5 |
6 | This library is free software; you can redistribute it and/or
7 | modify it under the terms of the GNU Lesser General Public
8 | License as published by the Free Software Foundation; either
9 | version 2.1 of the License, or (at your option) any later version.
10 |
11 | This library is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public
17 | License along with this library; if not, write to the Free Software
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 | Boston, MA 02110-1335 USA
20 |
21 | """
22 | from .remote import SamsungTVWS
23 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_smart/api/shortcuts.py:
--------------------------------------------------------------------------------
1 | """
2 | SamsungTVWS - Samsung Smart TV WS API wrapper
3 |
4 | Copyright (C) 2019 Xchwarze
5 |
6 | This library is free software; you can redistribute it and/or
7 | modify it under the terms of the GNU Lesser General Public
8 | License as published by the Free Software Foundation; either
9 | version 2.1 of the License, or (at your option) any later version.
10 |
11 | This library is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public
17 | License along with this library; if not, write to the Free Software
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 | Boston, MA 02110-1335 USA
20 |
21 | """
22 |
23 |
24 | class SamsungTVShortcuts:
25 | def __init__(self, remote):
26 | self.remote = remote
27 |
28 | # power
29 | def power(self):
30 | self.remote.send_key('KEY_POWER')
31 |
32 | # menu
33 | def home(self):
34 | self.remote.send_key('KEY_HOME')
35 |
36 | def menu(self):
37 | self.remote.send_key('KEY_MENU')
38 |
39 | def source(self):
40 | self.remote.send_key('KEY_SOURCE')
41 |
42 | def guide(self):
43 | self.remote.send_key('KEY_GUIDE')
44 |
45 | def tools(self):
46 | self.remote.send_key('KEY_TOOLS')
47 |
48 | def info(self):
49 | self.remote.send_key('KEY_INFO')
50 |
51 | # navigation
52 | def up(self):
53 | self.remote.send_key('KEY_UP')
54 |
55 | def down(self):
56 | self.remote.send_key('KEY_DOWN')
57 |
58 | def left(self):
59 | self.remote.send_key('KEY_LEFT')
60 |
61 | def right(self):
62 | self.remote.send_key('KEY_RIGHT')
63 |
64 | def enter(self, count=1):
65 | self.remote.send_key('KEY_ENTER')
66 |
67 | def back(self):
68 | self.remote.send_key('KEY_RETURN')
69 |
70 | # channel
71 | def channel_list(self):
72 | self.remote.send_key('KEY_CH_LIST')
73 |
74 | def channel(self, ch):
75 | for c in str(ch):
76 | self.digit(c)
77 |
78 | self.enter()
79 |
80 | def digit(self, d):
81 | self.remote.send_key('KEY_' + d)
82 |
83 | def channel_up(self):
84 | self.remote.send_key('KEY_CHUP')
85 |
86 | def channel_down(self):
87 | self.remote.send_key('KEY_CHDOWN')
88 |
89 | # volume
90 | def volume_up(self):
91 | self.remote.send_key('KEY_VOLUP')
92 |
93 | def volume_down(self):
94 | self.remote.send_key('KEY_VOLDOWN')
95 |
96 | def mute(self):
97 | self.remote.send_key('KEY_MUTE')
98 |
99 | # extra
100 | def red(self):
101 | self.remote.send_key('KEY_RED')
102 |
103 | def green(self):
104 | self.remote.send_key('KEY_GREEN')
105 |
106 | def yellow(self):
107 | self.remote.send_key('KEY_YELLOW')
108 |
109 | def blue(self):
110 | self.remote.send_key('KEY_BLUE')
111 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_smart/api/upnp.py:
--------------------------------------------------------------------------------
1 | # Smartthings TV integration#
2 | from aiohttp import ClientSession
3 | import async_timeout
4 | from typing import Optional
5 | import xml.etree.ElementTree as ET
6 |
7 | DEFAULT_TIMEOUT = 0.2
8 |
9 |
10 | class upnp:
11 | def __init__(self, host, session: Optional[ClientSession] = None):
12 | self._host = host
13 | self._mute = False
14 | self._volume = 0
15 | self._connected = False
16 | if session:
17 | self._session = session
18 | self._managed_session = False
19 | else:
20 | self._session = ClientSession()
21 | self._managed_session = True
22 |
23 | def __enter__(self):
24 | return self
25 |
26 | async def _SOAPrequest(self, action, arguments, protocole):
27 | headers = {
28 | "SOAPAction": f'"urn:schemas-upnp-org:service:{protocole}:1#{action}"',
29 | "content-type": "text/xml",
30 | }
31 | body = f"""
32 |
33 |
34 |
35 | 0
36 | {arguments}
37 |
38 |
39 | """
40 | response = None
41 | try:
42 | async with async_timeout.timeout(DEFAULT_TIMEOUT):
43 | async with self._session.post(
44 | f"http://{self._host}:9197/upnp/control/{protocole}1",
45 | headers=headers,
46 | data=body,
47 | raise_for_status=True,
48 | ) as resp:
49 | response = await resp.content.read()
50 | self._connected = True
51 | except:
52 | self._connected = False
53 |
54 | return response
55 |
56 | @property
57 | def connected(self):
58 | return self._connected
59 |
60 | async def async_get_volume(self):
61 | response = await self._SOAPrequest(
62 | "GetVolume", "Master", "RenderingControl"
63 | )
64 | if response is not None:
65 | volume_xml = response.decode("utf8")
66 | tree = ET.fromstring(volume_xml)
67 | for elem in tree.iter(tag="CurrentVolume"):
68 | self._volume = elem.text
69 | return self._volume
70 |
71 | async def async_set_volume(self, volume):
72 | await self._SOAPrequest(
73 | "SetVolume",
74 | f"Master{volume}",
75 | "RenderingControl",
76 | )
77 |
78 | async def async_get_mute(self):
79 | response = await self._SOAPrequest(
80 | "GetMute", "Master", "RenderingControl"
81 | )
82 | if response is not None:
83 | # mute_xml = response.decode('utf8')
84 | tree = ET.fromstring(response.decode("utf8"))
85 | mute = 0
86 | for elem in tree.iter(tag="CurrentMute"):
87 | mute = elem.text
88 | if int(mute) == 0:
89 | self._mute = False
90 | else:
91 | self._mute = True
92 |
93 | return self._mute
94 |
95 | async def async_set_current_media(self, url):
96 | """ Set media to playback and play it."""
97 | try:
98 | await self._SOAPrequest(
99 | "SetAVTransportURI",
100 | f"{url}",
101 | "AVTransport",
102 | )
103 | await self._SOAPrequest("Play", "1", "AVTransport")
104 | except Exception:
105 | pass
106 |
107 | async def async_play(self):
108 | """ Play media that was already set as current."""
109 | try:
110 | await self._SOAPrequest("Play", "1", "AVTransport")
111 | except Exception:
112 | pass
113 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_smart/const.py:
--------------------------------------------------------------------------------
1 | """Constants for the samsungtv_smart integration."""
2 | from enum import Enum
3 |
4 |
5 | class AppLoadMethod(Enum):
6 | All = 1
7 | Default = 2
8 | NotLoad = 3
9 |
10 |
11 | class AppLaunchMethod(Enum):
12 | Standard = 1
13 | Remote = 2
14 | Rest = 3
15 |
16 |
17 | class PowerOnMethod(Enum):
18 | WOL = 1
19 | SmartThings = 2
20 |
21 |
22 | DOMAIN = "samsungtv_smart"
23 |
24 | MIN_HA_MAJ_VER = 2021
25 | MIN_HA_MIN_VER = 7
26 | __min_ha_version__ = f"{MIN_HA_MAJ_VER}.{MIN_HA_MIN_VER}.0"
27 |
28 | DATA_OPTIONS = "options"
29 | WS_PREFIX = "[Home Assistant]"
30 |
31 | ATTR_DEVICE_MAC = "device_mac"
32 | ATTR_DEVICE_MODEL = "device_model"
33 | ATTR_DEVICE_NAME = "device_name"
34 | ATTR_DEVICE_OS = "device_os"
35 |
36 | CONF_APP_LAUNCH_METHOD = "app_launch_method"
37 | CONF_APP_LIST = "app_list"
38 | CONF_APP_LOAD_METHOD = "app_load_method"
39 | CONF_CHANNEL_LIST = "channel_list"
40 | CONF_DEVICE_MODEL = "device_model"
41 | CONF_DEVICE_NAME = "device_name"
42 | CONF_DEVICE_OS = "device_os"
43 | CONF_DUMP_APPS = "dump_apps"
44 | CONF_EXT_POWER_ENTITY = "ext_power_entity"
45 | CONF_LOAD_ALL_APPS = "load_all_apps"
46 | CONF_LOGO_OPTION = "logo_option"
47 | CONF_PING_PORT = "ping_port"
48 | CONF_POWER_ON_DELAY = "power_on_delay"
49 | CONF_POWER_ON_METHOD = "power_on_method"
50 | CONF_SHOW_CHANNEL_NR = "show_channel_number"
51 | CONF_SOURCE_LIST = "source_list"
52 | CONF_SYNC_TURN_OFF = "sync_turn_off"
53 | CONF_SYNC_TURN_ON = "sync_turn_on"
54 | CONF_USE_MUTE_CHECK = "use_mute_check"
55 | CONF_USE_ST_CHANNEL_INFO = "use_st_channel_info"
56 | CONF_USE_ST_STATUS_INFO = "use_st_status_info"
57 | CONF_WOL_REPEAT = "wol_repeat"
58 | CONF_WS_NAME = "ws_name"
59 |
60 | # obsolete
61 | CONF_UPDATE_METHOD = "update_method"
62 | CONF_UPDATE_CUSTOM_PING_URL = "update_custom_ping_url"
63 | CONF_SCAN_APP_HTTP = "scan_app_http"
64 |
65 | DEFAULT_APP = "TV/HDMI"
66 | DEFAULT_PORT = 8001
67 | DEFAULT_POWER_ON_DELAY = 30
68 | DEFAULT_SOURCE_LIST = {"TV": "KEY_TV", "HDMI": "KEY_HDMI"}
69 | DEFAULT_TIMEOUT = 6
70 |
71 | MAX_WOL_REPEAT = 5
72 |
73 | RESULT_NOT_SUCCESSFUL = "not_successful"
74 | RESULT_NOT_SUPPORTED = "not_supported"
75 | RESULT_ST_DEVICE_USED = "st_device_used"
76 | RESULT_ST_DEVICE_NOT_FOUND = "st_device_not_found"
77 | RESULT_ST_MULTI_DEVICES = "st_multiple_device"
78 | RESULT_SUCCESS = "success"
79 | RESULT_WRONG_APIKEY = "wrong_api_key"
80 |
81 | SERVICE_SELECT_PICTURE_MODE = "select_picture_mode"
82 | SERVICE_SET_ART_MODE = "set_art_mode"
83 |
84 | SERVICE_TURN_OFF = "turn_off"
85 | SERVICE_TURN_ON = "turn_on"
86 |
87 | STD_APP_LIST = {
88 | # app_id: smartthings app id (if different and available)
89 | "org.tizen.browser": "", # Internet
90 | "11101200001": "RN1MCdNq8t.Netflix", # Netflix
91 | "3201907018807": "org.tizen.netflix-app", # Netflix (New)
92 | "111299001912": "9Ur5IzDKqV.TizenYouTube", # YouTube
93 | "3201512006785": "org.tizen.ignition", # Prime Video
94 | # "3201512006785": "evKhCgZelL.AmazonIgnitionLauncher2", # Prime Video
95 | "3201901017640": "MCmYXNxgcu.DisneyPlus", # Disney+
96 | "11091000000": "4ovn894vo9.Facebook", # Facebook
97 | "3201601007250": "QizQxC7CUf.PlayMovies", # Google Play
98 | "3201606009684": "rJeHak5zRg.Spotify", # Spotify
99 | }
100 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_smart/diagnostics.py:
--------------------------------------------------------------------------------
1 | """Diagnostics support for Samsung TV Smart."""
2 | from __future__ import annotations
3 |
4 | from homeassistant.components.diagnostics import REDACTED, async_redact_data
5 | from homeassistant.config_entries import ConfigEntry
6 | from homeassistant.const import CONF_API_KEY, CONF_ID, CONF_MAC
7 | from homeassistant.core import HomeAssistant, callback
8 | from homeassistant.helpers import device_registry as dr, entity_registry as er
9 |
10 | from .const import DOMAIN
11 |
12 |
13 | TO_REDACT = {CONF_API_KEY, CONF_MAC}
14 |
15 |
16 | async def async_get_config_entry_diagnostics(
17 | hass: HomeAssistant, entry: ConfigEntry
18 | ) -> dict:
19 | """Return diagnostics for a config entry."""
20 | diag_data = {"entry": async_redact_data(entry.as_dict(), TO_REDACT)}
21 |
22 | yaml_data = hass.data[DOMAIN].get(entry.unique_id, {})
23 | if yaml_data:
24 | diag_data["config_data"] = async_redact_data(yaml_data, TO_REDACT)
25 |
26 | device_id = entry.data.get(CONF_ID, entry.entry_id)
27 | hass_data = _async_device_ha_info(hass, device_id)
28 | if hass_data:
29 | diag_data["device"] = hass_data
30 |
31 | return diag_data
32 |
33 |
34 | @callback
35 | def _async_device_ha_info(
36 | hass: HomeAssistant, device_id: str
37 | ) -> dict | None:
38 | """Gather information how this TV device is represented in Home Assistant."""
39 |
40 | device_registry = dr.async_get(hass)
41 | entity_registry = er.async_get(hass)
42 | hass_device = device_registry.async_get_device(
43 | identifiers={(DOMAIN, device_id)}
44 | )
45 | if not hass_device:
46 | return None
47 |
48 | data = {
49 | "name": hass_device.name,
50 | "name_by_user": hass_device.name_by_user,
51 | "model": hass_device.model,
52 | "manufacturer": hass_device.manufacturer,
53 | "sw_version": hass_device.sw_version,
54 | "disabled": hass_device.disabled,
55 | "disabled_by": hass_device.disabled_by,
56 | "entities": {},
57 | }
58 |
59 | hass_entities = er.async_entries_for_device(
60 | entity_registry,
61 | device_id=hass_device.id,
62 | include_disabled_entities=True,
63 | )
64 |
65 | for entity_entry in hass_entities:
66 | if entity_entry.platform != DOMAIN:
67 | continue
68 | state = hass.states.get(entity_entry.entity_id)
69 | state_dict = None
70 | if state:
71 | state_dict = dict(state.as_dict())
72 | # The entity_id is already provided at root level.
73 | state_dict.pop("entity_id", None)
74 | # The context doesn't provide useful information in this case.
75 | state_dict.pop("context", None)
76 | # Redact the `entity_picture` attribute as it contains a token.
77 | if "entity_picture" in state_dict["attributes"]:
78 | state_dict["attributes"] = {
79 | **state_dict["attributes"],
80 | "entity_picture": REDACTED,
81 | }
82 |
83 | data["entities"][entity_entry.entity_id] = {
84 | "name": entity_entry.name,
85 | "original_name": entity_entry.original_name,
86 | "disabled": entity_entry.disabled,
87 | "disabled_by": entity_entry.disabled_by,
88 | "entity_category": entity_entry.entity_category,
89 | "device_class": entity_entry.device_class,
90 | "original_device_class": entity_entry.original_device_class,
91 | "icon": entity_entry.icon,
92 | "original_icon": entity_entry.original_icon,
93 | "unit_of_measurement": entity_entry.unit_of_measurement,
94 | "state": state_dict,
95 | }
96 |
97 | return data
98 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_smart/services.yaml:
--------------------------------------------------------------------------------
1 | select_picture_mode:
2 | description: Send to samsung TV the command to change picture mode.
3 | fields:
4 | entity_id:
5 | name: Entity Name
6 | description: Name of the target entity
7 | required: true
8 | example: "media_player.tv"
9 | selector:
10 | entity:
11 | integration: samsungtv_smart
12 | picture_mode:
13 | name: Picture Mode
14 | description:
15 | Name of the picture mode to switch to. Possible options
16 | can be found in the picture_mode_list state attribute.
17 | required: true
18 | example: "Standard"
19 | selector:
20 | text:
21 |
22 | set_art_mode:
23 | description: Send to samsung TV the command to set art mode.
24 | fields:
25 | entity_id:
26 | name: Entity Name
27 | description: Name of the target entity
28 | required: true
29 | example: "media_player.tv"
30 | selector:
31 | entity:
32 | integration: samsungtv_smart
33 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_tizen/__init__.py:
--------------------------------------------------------------------------------
1 | """The samsungtv component."""
2 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_tizen/exceptions.py:
--------------------------------------------------------------------------------
1 | class ConnectionFailure(Exception):
2 | """Error during connection."""
3 | pass
4 |
5 |
6 | class ResponseError(Exception):
7 | """Error in response."""
8 | pass
9 |
10 |
11 | class HttpApiError(Exception):
12 | """Error using HTTP API."""
13 | pass
14 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_tizen/shortcuts.py:
--------------------------------------------------------------------------------
1 | """
2 | SamsungTVWS - Samsung Smart TV WS API wrapper
3 |
4 | Copyright (C) 2019 Xchwarze
5 |
6 | This library is free software; you can redistribute it and/or
7 | modify it under the terms of the GNU Lesser General Public
8 | License as published by the Free Software Foundation; either
9 | version 2.1 of the License, or (at your option) any later version.
10 |
11 | This library is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public
17 | License along with this library; if not, write to the Free Software
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 | Boston, MA 02110-1335 USA
20 |
21 | """
22 |
23 |
24 | class SamsungTVShortcuts:
25 | def __init__(self, remote):
26 | self.remote = remote
27 |
28 | # power
29 | def power(self):
30 | self.remote.send_key('KEY_POWER')
31 |
32 | # menu
33 | def home(self):
34 | self.remote.send_key('KEY_HOME')
35 |
36 | def menu(self):
37 | self.remote.send_key('KEY_MENU')
38 |
39 | def source(self):
40 | self.remote.send_key('KEY_SOURCE')
41 |
42 | def guide(self):
43 | self.remote.send_key('KEY_GUIDE')
44 |
45 | def tools(self):
46 | self.remote.send_key('KEY_TOOLS')
47 |
48 | def info(self):
49 | self.remote.send_key('KEY_INFO')
50 |
51 | # navigation
52 | def up(self):
53 | self.remote.send_key('KEY_UP')
54 |
55 | def down(self):
56 | self.remote.send_key('KEY_DOWN')
57 |
58 | def left(self):
59 | self.remote.send_key('KEY_LEFT')
60 |
61 | def right(self):
62 | self.remote.send_key('KEY_RIGHT')
63 |
64 | def enter(self, count=1):
65 | self.remote.send_key('KEY_ENTER')
66 |
67 | def back(self):
68 | self.remote.send_key('KEY_RETURN')
69 |
70 | # channel
71 | def channel_list(self):
72 | self.remote.send_key('KEY_CH_LIST')
73 |
74 | def channel(self, ch):
75 | for c in str(ch):
76 | self.digit(c)
77 |
78 | self.enter()
79 |
80 | def digit(self, d):
81 | self.remote.send_key('KEY_' + d)
82 |
83 | def channel_up(self):
84 | self.remote.send_key('KEY_CHUP')
85 |
86 | def channel_down(self):
87 | self.remote.send_key('KEY_CHDOWN')
88 |
89 | # volume
90 | def volume_up(self):
91 | self.remote.send_key('KEY_VOLUP')
92 |
93 | def volume_down(self):
94 | self.remote.send_key('KEY_VOLDOWN')
95 |
96 | def mute(self):
97 | self.remote.send_key('KEY_MUTE')
98 |
99 | # extra
100 | def red(self):
101 | self.remote.send_key('KEY_RED')
102 |
103 | def green(self):
104 | self.remote.send_key('KEY_GREEN')
105 |
106 | def yellow(self):
107 | self.remote.send_key('KEY_YELLOW')
108 |
109 | def blue(self):
110 | self.remote.send_key('KEY_BLUE')
111 |
--------------------------------------------------------------------------------
/custom_components/samsungtv_tizen/upnp.py:
--------------------------------------------------------------------------------
1 | #Smartthings TV integration#
2 | import requests
3 | import xml.etree.ElementTree as ET
4 |
5 | class upnp:
6 |
7 | def __init__(self, host):
8 | self.host = host
9 | self.mute = False
10 | self.volume = 0
11 |
12 | def __enter__(self):
13 | return self
14 |
15 | def SOAPrequest(self, action, arguments, protocole):
16 | headers = {'SOAPAction': '"urn:schemas-upnp-org:service:{protocole}:1#{action}"'.format(action=action, protocole=protocole), 'content-type': 'text/xml'}
17 | body = """
18 |
19 |
20 |
21 | 0
22 | {arguments}
23 |
24 |
25 | """.format(action=action, arguments=arguments, protocole=protocole)
26 | response = None
27 | try:
28 | response = requests.post("http://{host}:9197/upnp/control/{protocole}1".format(host=self.host, protocole=protocole), data=body, headers=headers, timeout=0.1)
29 | response = response.content
30 | except:
31 | pass
32 | return response
33 |
34 | def get_volume(self):
35 | response = self.SOAPrequest('GetVolume', "Master", 'RenderingControl')
36 | if response is not None:
37 | volume_xml = response.decode('utf8')
38 | tree = ET.fromstring(volume_xml)
39 | for elem in tree.iter(tag='CurrentVolume'):
40 | self.volume = elem.text
41 | return self.volume
42 |
43 | def set_volume(self, volume):
44 | self.SOAPrequest('SetVolume', "Master{}".format(volume), 'RenderingControl')
45 |
46 | def get_mute(self):
47 | response = self.SOAPrequest('GetMute', "Master", 'RenderingControl')
48 | if response is not None:
49 | # mute_xml = response.decode('utf8')
50 | tree = ET.fromstring(response.decode('utf8'))
51 | mute = 0
52 | for elem in tree.iter(tag='CurrentMute'):
53 | mute = elem.text
54 | if (int(mute) == 0):
55 | self.mute = False
56 | else:
57 | self.mute = True
58 | return self.mute
59 |
60 | def set_current_media(self, url):
61 | """ Set media to playback and play it."""
62 | try:
63 | self.SOAPrequest('SetAVTransportURI', "{url}".format(url=url), 'AVTransport')
64 | self.SOAPrequest('Play', "1", 'AVTransport')
65 | except Exception:
66 | pass
67 |
68 | def play(self):
69 | """ Play media that was already set as current."""
70 | try:
71 | self.SOAPrequest('Play', "1", 'AVTransport')
72 | except Exception:
73 | pass
74 |
75 |
--------------------------------------------------------------------------------
/custom_components/sectorperformance/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/custom_components/sectorperformance/__init__.py
--------------------------------------------------------------------------------
/custom_components/tuya_v2/aes_cbc.py:
--------------------------------------------------------------------------------
1 | """AES-CBC encryption and decryption for account info."""
2 |
3 | import base64 as b64
4 | import json
5 | import random
6 | from binascii import a2b_hex, b2a_hex
7 |
8 | from Crypto.Cipher import AES
9 |
10 | AES_ACCOUNT_KEY = "o0o0o0"
11 | XOR_KEY = "00oo00"
12 | KEY_KEY = "oo00oo"
13 |
14 |
15 | class AesCBC:
16 |
17 | # random_16
18 | def random_16(self):
19 | str = ""
20 | return str.join(
21 | random.choice("abcdefghijklmnopqrstuvwxyz!@#$%^&*1234567890")
22 | for i in range(16)
23 | )
24 |
25 | # add_to_16
26 | def add_to_16(self, text):
27 | if len(text.encode("utf-8")) % 16:
28 | add = 16 - (len(text.encode("utf-8")) % 16)
29 | else:
30 | add = 0
31 | text = text + ("\0" * add)
32 | return text.encode("utf-8")
33 |
34 | # cbc_encryption
35 | def cbc_encrypt(self, key, iv, text):
36 | key = key.encode("utf-8")
37 | mode = AES.MODE_CBC
38 | iv = bytes(iv, encoding="utf8")
39 | text = self.add_to_16(text)
40 | cryptos = AES.new(key, mode, iv)
41 | cipher_text = cryptos.encrypt(text)
42 | return str(b2a_hex(cipher_text), encoding="utf-8")
43 |
44 | # cbc_decryption
45 | def cbc_decrypt(self, key, iv, text):
46 | key = key.encode("utf-8")
47 | iv = bytes(iv, encoding="utf8")
48 | mode = AES.MODE_CBC
49 | cryptos = AES.new(key, mode, iv)
50 | plain_text = cryptos.decrypt(a2b_hex(text))
51 | return bytes.decode(plain_text).rstrip("\0")
52 |
53 | # xor_encrypt
54 | def xor_encrypt(self, data, key):
55 | lkey = len(key)
56 | secret = []
57 | num = 0
58 | for each in data:
59 | if num >= lkey:
60 | num = num % lkey
61 | secret.append(chr(ord(each) ^ ord(key[num])))
62 | num += 1
63 | return b64.b64encode("".join(secret).encode()).decode()
64 |
65 | # xor_decrypt
66 | def xor_decrypt(self, secret, key):
67 | tips = b64.b64decode(secret.encode()).decode()
68 | lkey = len(key)
69 | secret = []
70 | num = 0
71 | for each in tips:
72 | if num >= lkey:
73 | num = num % lkey
74 | secret.append(chr(ord(each) ^ ord(key[num])))
75 | num += 1
76 | return "".join(secret)
77 |
78 | # json to dict
79 | def json_to_dict(self, json_str):
80 | return json.loads(json_str)
81 |
82 | # confuse str
83 | def b64_encrypt(self, text):
84 | return b64.b64encode(text.encode()).decode()
85 |
86 | # unconfuse str
87 | def b64_decrypt(self, text):
88 | return b64.b64decode(text).decode()
89 |
--------------------------------------------------------------------------------
/custom_components/tuya_v2/base.py:
--------------------------------------------------------------------------------
1 | """Tuya Home Assistant Base Device Model."""
2 | from __future__ import annotations
3 |
4 | from typing import Any
5 |
6 | from tuya_iot import TuyaDevice, TuyaDeviceManager
7 |
8 | from homeassistant.helpers.dispatcher import async_dispatcher_connect
9 | from homeassistant.helpers.entity import Entity
10 |
11 | from .const import DOMAIN, TUYA_HA_SIGNAL_UPDATE_ENTITY
12 |
13 |
14 | class TuyaHaEntity(Entity):
15 | """Tuya base device."""
16 |
17 | def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager) -> None:
18 | """Init TuyaHaEntity."""
19 | super().__init__()
20 |
21 | self.tuya_device = device
22 | self.tuya_device_manager = device_manager
23 |
24 | @staticmethod
25 | def remap(old_value, old_min, old_max, new_min, new_max):
26 | """Remap old_value to new_value."""
27 | new_value = ((old_value - old_min) / (old_max - old_min)) * (
28 | new_max - new_min
29 | ) + new_min
30 | return new_value
31 |
32 | @property
33 | def should_poll(self) -> bool:
34 | """Hass should not poll."""
35 | return False
36 |
37 | @property
38 | def unique_id(self) -> str | None:
39 | """Return a unique ID."""
40 | return f"ty{self.tuya_device.id}"
41 |
42 | @property
43 | def name(self) -> str | None:
44 | """Return Tuya device name."""
45 | return self.tuya_device.name
46 |
47 | @property
48 | def device_info(self):
49 | """Return a device description for device registry."""
50 | _device_info = {
51 | "identifiers": {(DOMAIN, f"{self.tuya_device.id}")},
52 | "manufacturer": "tuya",
53 | "name": self.tuya_device.name,
54 | "model": self.tuya_device.product_name,
55 | }
56 | return _device_info
57 |
58 | @property
59 | def available(self) -> bool:
60 | """Return if the device is available."""
61 | return self.tuya_device.online
62 |
63 | async def async_added_to_hass(self):
64 | """Call when entity is added to hass."""
65 | self.async_on_remove(
66 | async_dispatcher_connect(
67 | self.hass,
68 | f"{TUYA_HA_SIGNAL_UPDATE_ENTITY}_{self.tuya_device.id}",
69 | self.async_write_ha_state,
70 | )
71 | )
72 |
73 | def _send_command(self, commands: list[dict[str, Any]]) -> None:
74 | """Send command to the device."""
75 | self.tuya_device_manager.send_commands(self.tuya_device.id, commands)
76 |
--------------------------------------------------------------------------------
/custom_components/tuya_v2/const.py:
--------------------------------------------------------------------------------
1 | """Constants for the Tuya integration."""
2 |
3 | DOMAIN = "tuya_v2"
4 |
5 | CONF_PROJECT_TYPE = "tuya_project_type"
6 | CONF_ENDPOINT = "endpoint"
7 | CONF_ACCESS_ID = "access_id"
8 | CONF_ACCESS_SECRET = "access_secret"
9 | CONF_USERNAME = "username"
10 | CONF_PASSWORD = "password"
11 | CONF_COUNTRY_CODE = "country_code"
12 | CONF_APP_TYPE = "tuya_app_type"
13 |
14 | TUYA_DISCOVERY_NEW = "tuya_v2_discovery_new_{}"
15 | TUYA_DEVICE_MANAGER = "tuya_device_manager"
16 | TUYA_HOME_MANAGER = "tuya_home_manager"
17 | TUYA_MQTT_LISTENER = "tuya_mqtt_listener"
18 | TUYA_HA_TUYA_MAP = "tuya_ha_tuya_map"
19 | TUYA_HA_DEVICES = "tuya_ha_devices"
20 |
21 | TUYA_HA_SIGNAL_UPDATE_ENTITY = "tuya_entry_update"
22 |
23 | TUYA_PROJECT_TYPE_INDUSTY_SOLUTIONS = "Custom Development"
24 | TUYA_PROJECT_TYPE_SMART_HOME = "Smart Home PaaS"
25 |
26 | TUYA_PROJECT_TYPES = {
27 | TUYA_PROJECT_TYPE_SMART_HOME: 0,
28 | TUYA_PROJECT_TYPE_INDUSTY_SOLUTIONS: 1,
29 | }
30 |
31 | TUYA_ENDPOINTS = {
32 | "America": "https://openapi.tuyaus.com",
33 | "China": "https://openapi.tuyacn.com",
34 | "Europe": "https://openapi.tuyaeu.com",
35 | "India": "https://openapi.tuyain.com",
36 | "Eastern America": "https://openapi-ueaz.tuyaus.com",
37 | "Western Europe": "https://openapi-weaz.tuyaeu.com",
38 | }
39 |
40 | TUYA_APP_TYPES = {"TuyaSmart": "tuyaSmart", "Smart Life": "smartlife"}
41 |
42 | PLATFORMS = [
43 | "binary_sensor",
44 | "climate",
45 | "cover",
46 | "fan",
47 | "humidifier",
48 | "light",
49 | "number",
50 | "scene",
51 | "select",
52 | "sensor",
53 | "switch",
54 | "vacuum",
55 | ]
56 |
--------------------------------------------------------------------------------
/custom_components/tuya_v2/scene.py:
--------------------------------------------------------------------------------
1 | """Support for Tuya scenes."""
2 | from __future__ import annotations
3 |
4 | import logging
5 | from typing import Any
6 |
7 | from tuya_iot import TuyaHomeManager, TuyaScene
8 |
9 | from homeassistant.components.scene import Scene
10 | from homeassistant.config_entries import ConfigEntry
11 | from homeassistant.core import HomeAssistant
12 | from homeassistant.helpers.entity_platform import AddEntitiesCallback
13 |
14 | from .const import DOMAIN, TUYA_HOME_MANAGER
15 |
16 | _LOGGER = logging.getLogger(__name__)
17 |
18 |
19 | async def async_setup_entry(
20 | hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
21 | ) -> None:
22 | """Set up tuya scenes."""
23 | home_manager = hass.data[DOMAIN][entry.entry_id][TUYA_HOME_MANAGER]
24 | scenes = await hass.async_add_executor_job(home_manager.query_scenes)
25 | async_add_entities(TuyaHAScene(home_manager, scene) for scene in scenes)
26 |
27 |
28 | class TuyaHAScene(Scene):
29 | """Tuya Scene Remote."""
30 |
31 | def __init__(self, home_manager: TuyaHomeManager, scene: TuyaScene) -> None:
32 | """Init Tuya Scene."""
33 | super().__init__()
34 | self.home_manager = home_manager
35 | self.scene = scene
36 |
37 | @property
38 | def should_poll(self) -> bool:
39 | """Hass should not poll."""
40 | return False
41 |
42 | @property
43 | def unique_id(self) -> str | None:
44 | """Return a unique ID."""
45 | return f"tys{self.scene.scene_id}"
46 |
47 | @property
48 | def name(self) -> str | None:
49 | """Return Tuya scene name."""
50 | return self.scene.name
51 |
52 | @property
53 | def device_info(self):
54 | """Return a device description for device registry."""
55 | return {
56 | "identifiers": {(DOMAIN, f"{self.unique_id}")},
57 | "manufacturer": "tuya",
58 | "name": self.scene.name,
59 | "model": "Tuya Scene",
60 | }
61 |
62 | @property
63 | def available(self) -> bool:
64 | """Return if the scene is enabled."""
65 | return self.scene.enabled
66 |
67 | def activate(self, **kwargs: Any) -> None:
68 | """Activate the scene."""
69 | self.home_manager.trigger_scene(self.scene.home_id, self.scene.scene_id)
70 |
--------------------------------------------------------------------------------
/custom_components/tuya_v2/select.py:
--------------------------------------------------------------------------------
1 | """Support for Tuya Select entities."""
2 | from __future__ import annotations
3 |
4 | import json
5 | import logging
6 |
7 | from homeassistant.components.select import DOMAIN as DEVICE_DOMAIN
8 | from homeassistant.components.select import SelectEntity
9 | from homeassistant.config_entries import ConfigEntry
10 | from homeassistant.core import HomeAssistant, callback
11 | from homeassistant.helpers.dispatcher import async_dispatcher_connect
12 | from homeassistant.helpers.entity import Entity
13 | from tuya_iot import TuyaDevice, TuyaDeviceManager
14 |
15 | from .base import TuyaHaEntity
16 | from .const import (
17 | DOMAIN,
18 | TUYA_DEVICE_MANAGER,
19 | TUYA_DISCOVERY_NEW,
20 | TUYA_HA_DEVICES,
21 | TUYA_HA_TUYA_MAP,
22 | )
23 |
24 | _LOGGER = logging.getLogger(__name__)
25 |
26 | TUYA_SUPPORT_TYPE = {
27 | "xxj", # Diffuser
28 | "kfj", # Coffee Maker
29 | "sd", # Vacuum Robot
30 | }
31 |
32 | DPCODE_MODE = "mode"
33 | DPCODE_COUNTDOWN = "countdown"
34 | DPCODE_WORK_MODE = "work_mode"
35 | DPCODE_DIRECTIONCONTROL = "direction_control"
36 |
37 | # Coffee Maker
38 | # https://developer.tuya.com/en/docs/iot/f?id=K9gf4701ox167
39 | DPCODE_MATERIAL = "material"
40 | DPCODE_CONCENTRATIONSET = "concentration_set"
41 | DPCODE_CUPNUMBER = "cup_number"
42 |
43 |
44 | AUTO_GENERATE_DP_LIST = [
45 | DPCODE_MODE,
46 | DPCODE_COUNTDOWN,
47 | DPCODE_WORK_MODE,
48 | DPCODE_MATERIAL,
49 | DPCODE_CONCENTRATIONSET,
50 | DPCODE_CUPNUMBER,
51 | DPCODE_DIRECTIONCONTROL
52 | ]
53 |
54 |
55 | async def async_setup_entry(
56 | hass: HomeAssistant, entry: ConfigEntry, async_add_entities
57 | ) -> None:
58 | """Set up tuya select dynamically through tuya discovery."""
59 | _LOGGER.info("select init")
60 |
61 | hass.data[DOMAIN][entry.entry_id][TUYA_HA_TUYA_MAP][
62 | DEVICE_DOMAIN
63 | ] = TUYA_SUPPORT_TYPE
64 |
65 | @callback
66 | def async_discover_device(dev_ids):
67 | _LOGGER.info(f"select add-> {dev_ids}")
68 | if not dev_ids:
69 | return
70 | entities = _setup_entities(hass, entry, dev_ids)
71 | async_add_entities(entities)
72 |
73 | entry.async_on_unload(
74 | async_dispatcher_connect(
75 | hass, TUYA_DISCOVERY_NEW.format(DEVICE_DOMAIN), async_discover_device
76 | )
77 | )
78 |
79 | device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
80 | device_ids = []
81 | for (device_id, device) in device_manager.device_map.items():
82 | if device.category in TUYA_SUPPORT_TYPE:
83 | device_ids.append(device_id)
84 | async_discover_device(device_ids)
85 |
86 |
87 | def get_auto_generate_data_points(status) -> list:
88 | dps = []
89 | for data_point in AUTO_GENERATE_DP_LIST:
90 | if data_point in status:
91 | dps.append(data_point)
92 |
93 | return dps
94 |
95 |
96 | def _setup_entities(
97 | hass, entry: ConfigEntry, device_ids: list[str]
98 | ) -> list[Entity]:
99 | """Set up Tuya Select."""
100 | device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
101 | entities:list[Entity] = []
102 | for device_id in device_ids:
103 | device = device_manager.device_map[device_id]
104 | if device is None:
105 | continue
106 |
107 | for data_point in get_auto_generate_data_points(device.status):
108 | entities.append(TuyaHaSelect(device, device_manager, data_point))
109 | hass.data[DOMAIN][entry.entry_id][TUYA_HA_DEVICES].add(device_id)
110 | return entities
111 |
112 |
113 | class TuyaHaSelect(TuyaHaEntity, SelectEntity):
114 | """Tuya Select Device."""
115 |
116 | def __init__(
117 | self, device: TuyaDevice, device_manager: TuyaDeviceManager, code: str = ""
118 | ):
119 | self._code = code
120 | self._attr_current_option = None
121 | super().__init__(device, device_manager)
122 |
123 | @property
124 | def unique_id(self) -> str | None:
125 | return f"{super().unique_id}{self._code}"
126 |
127 | @property
128 | def name(self) -> str | None:
129 | """Return Tuya device name."""
130 | return self.tuya_device.name + self._code
131 |
132 | @property
133 | def current_option(self) -> str:
134 | return self.tuya_device.status.get(self._code, None)
135 |
136 | def select_option(self, option: str) -> None:
137 | self._send_command([{"code": self._code, "value": option}])
138 |
139 | @property
140 | def options(self) -> list:
141 | dp_range = json.loads(self.tuya_device.function.get(self._code).values)
142 | return dp_range.get("range", [])
143 |
--------------------------------------------------------------------------------
/custom_components/wyzeapi/const.py:
--------------------------------------------------------------------------------
1 | """Constants for the Wyze Home Assistant Integration integration."""
2 |
3 | DOMAIN = "wyzeapi"
4 | CONF_CLIENT = "wyzeapi_client"
5 |
6 | ACCESS_TOKEN = "access_token"
7 | REFRESH_TOKEN = "refresh_token"
8 | REFRESH_TIME = "refresh_time"
9 |
10 | WYZE_NOTIFICATION_TOGGLE = f"{DOMAIN}.wyze.notification.toggle"
11 |
12 | LOCK_UPDATED = f"{DOMAIN}.lock_updated"
13 | CAMERA_UPDATED = f"{DOMAIN}.camera_updated"
14 | # EVENT NAMES
15 | WYZE_CAMERA_EVENT = "wyze_camera_event"
16 |
--------------------------------------------------------------------------------
/custom_components/wyzeapi/token_manager.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from inspect import iscoroutinefunction
3 |
4 | from homeassistant.config_entries import ConfigEntry
5 | from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
6 | from homeassistant.core import HomeAssistant
7 | from homeassistant.exceptions import ConfigEntryAuthFailed
8 | from wyzeapy.exceptions import AccessTokenError, LoginError
9 | from wyzeapy.wyze_auth_lib import Token
10 |
11 | from .const import DOMAIN, ACCESS_TOKEN, REFRESH_TOKEN, REFRESH_TIME
12 |
13 | _LOGGER = logging.getLogger(__name__)
14 |
15 |
16 | class TokenManager:
17 | hass: HomeAssistant = None
18 | config_entry: ConfigEntry = None
19 |
20 | def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry):
21 | TokenManager.hass = hass
22 | TokenManager.config_entry = config_entry
23 |
24 | @staticmethod
25 | async def token_callback(token: Token = None):
26 | _LOGGER.debug("TokenManager: Received new token, updating config entry.")
27 | if TokenManager.hass.config_entries.async_entries(DOMAIN):
28 | for entry in TokenManager.hass.config_entries.async_entries(DOMAIN):
29 | TokenManager.hass.config_entries.async_update_entry(
30 | entry,
31 | data={
32 | CONF_USERNAME: entry.data.get(CONF_USERNAME),
33 | CONF_PASSWORD: entry.data.get(CONF_PASSWORD),
34 | ACCESS_TOKEN: token.access_token,
35 | REFRESH_TOKEN: token.refresh_token,
36 | REFRESH_TIME: str(token.refresh_time),
37 | },
38 | )
39 |
40 |
41 | def token_exception_handler(func):
42 | async def inner_function(*args, **kwargs):
43 | try:
44 | if iscoroutinefunction(func):
45 | await func(*args, **kwargs)
46 | else:
47 | func(*args, **kwargs)
48 | except (AccessTokenError, LoginError) as err:
49 | _LOGGER.error("TokenManager detected a login issue please re-login.")
50 | raise ConfigEntryAuthFailed("Unable to login, please re-login.") from err
51 |
52 | return inner_function
53 |
--------------------------------------------------------------------------------
/custom_components/wyzesense/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/custom_components/wyzesense/__init__.py
--------------------------------------------------------------------------------
/custom_components/wyzesense/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "domain": "wyzesense",
3 | "name": "Wyze Sense Component",
4 | "documentation": "https://github.com/kevinvincent/wyzesense",
5 | "requirements": ["wyzesense==0.0.4","retry==0.9.2"],
6 | "dependencies": [],
7 | "codeowners": ["@kevinvincent"]
8 | }
9 |
--------------------------------------------------------------------------------
/custom_components/wyzesense/services.yaml:
--------------------------------------------------------------------------------
1 | # Describes the format for available wyzesense services
2 |
3 | scan:
4 | description: Allow new devices to join for the next 30s
5 |
6 | remove:
7 | description: Remove a device
8 | fields:
9 | mac:
10 | description: MAC address of the node to remove
11 | example: "777A4656"
--------------------------------------------------------------------------------
/custom_components/yahoo_earnings/__init__.py:
--------------------------------------------------------------------------------
1 | """The yahoo_earnings sensor component."""
2 |
--------------------------------------------------------------------------------
/custom_components/yahoo_earnings/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "domain": "yahoo_earnings",
3 | "name": "yahoo_earnings",
4 | "documentation": "https://www.home-assistant.io/components/sensor",
5 | "requirements": ["random_user_agent==1.0.1", "lxml==4.4.2"],
6 | "dependencies": [],
7 | "version": "0.0.1",
8 | "codeowners": ["@arsaboo"]
9 | }
10 |
--------------------------------------------------------------------------------
/customize_glob.yaml:
--------------------------------------------------------------------------------
1 | input_boolean.*:
2 | templates:
3 | icon_color: >
4 | if (state === 'on') return 'rgb(251, 210, 41)'; return 'rgb(54, 95, 140)';
5 | sensor.*_futures_change:
6 | templates:
7 | icon_color: >
8 | if (state > 0) return 'rgb(50,205,50)'; else return 'rgb(178,34,34)';
9 | sensor.*_futures_change_pct:
10 | templates:
11 | icon_color: >
12 | if (state < -5) return 'rgb(151, 26, 30)';
13 | if (state < -4) return 'rgb(194, 39, 45)';
14 | if (state < -3) return 'rgb(236, 27, 33)';
15 | if (state < -2) return 'rgb(244, 101, 35)';
16 | if (state < -1) return 'rgb(248, 147, 29)';
17 | if (state < 0) return 'rgb(255, 194, 15)';
18 | if (state < 1) return 'rgb(202, 219, 43)';
19 | if (state < 2) return 'rgb(142, 198, 65)';
20 | if (state < 3) return 'rgb(106, 158, 47)';
21 | if (state < 4) return 'rgb(37, 145, 60)';
22 | return 'rgb(0, 111, 58)';
23 |
--------------------------------------------------------------------------------
/emulated_hue_ids.json:
--------------------------------------------------------------------------------
1 | {"6": "switch.wemoporch", "3": "group.living_room_lights", "9": "light.lifx5", "1": "input_boolean.startrecording", "8": "light.gateway_light_34ce00813670", "7": "switch.driveway", "2": "light.kitchen_lights", "5": "switch.garage_relay_switch", "4": "input_boolean.abodeupdate"}
--------------------------------------------------------------------------------
/frontend.yaml:
--------------------------------------------------------------------------------
1 | themes: !include_dir_merge_named themes
2 |
--------------------------------------------------------------------------------
/gitupdate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | git add .
4 | git status
5 | echo -n "Enter the Description for the Change: " [Minor Update]
6 | read CHANGE_MSG
7 | git commit -m "${CHANGE_MSG}"
8 | git push origin master
9 |
10 | exit
11 |
--------------------------------------------------------------------------------
/groups.yaml:
--------------------------------------------------------------------------------
1 | #################################################################
2 | ## Groups
3 | #################################################################
4 |
5 | Thermostats:
6 | - switch.downstairs_away
7 | - switch.bedroom_away
8 | - switch.upstairs_away
9 |
10 | Household:
11 | - device_tracker.meta_rashmi
12 | - device_tracker.meta_alok
13 | - device_tracker.meta_arnav
14 |
--------------------------------------------------------------------------------
/ha_ss_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/ha_ss_1.png
--------------------------------------------------------------------------------
/ha_ss_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/ha_ss_2.png
--------------------------------------------------------------------------------
/ha_ss_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/ha_ss_3.png
--------------------------------------------------------------------------------
/ha_ss_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/ha_ss_4.png
--------------------------------------------------------------------------------
/ha_ss_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/ha_ss_5.png
--------------------------------------------------------------------------------
/history.yaml:
--------------------------------------------------------------------------------
1 | use_include_order: True
2 | include:
3 | entities:
4 | - binary_sensor.garage_door
5 | - device_tracker.meta_rashmi
6 | - device_tracker.test_rashmi
7 | - person.rashmi
8 | - device_tracker.meta_alok
9 | - device_tracker.test_alok
10 | - person.alok
11 | - sensor.temperature_158d0001a35ba5
12 | - sensor.humidity_158d0001a35ba5
13 | - sensor.pressure_158d0001a35ba5
14 |
--------------------------------------------------------------------------------
/input_boolean.yaml:
--------------------------------------------------------------------------------
1 | #################################################################
2 | ## Input_boolean
3 | #################################################################
4 |
5 | dashbounty:
6 | name: Dash Bounty
7 | initial: off
8 | icon: mdi:security
9 | abodeupdate:
10 | name: Abode Updates
11 | icon: mdi:security
12 | homeautomation:
13 | name: Home Automation
14 | icon: mdi:home-automation
15 | startrecording:
16 | name: Camera Recording
17 | initial: off
18 | icon: mdi:record-rec
19 | tv:
20 | name: TV
21 | initial: off
22 | icon: mdi:television
23 | tvtime:
24 | name: TV Time
25 | initial: on
26 | icon: mdi:television-guide
27 | leeoalarm:
28 | name: Leeo Alarm
29 | initial: off
30 | icon: mdi:fire
31 | devmode:
32 | name: Developer Mode
33 | initial: off
34 | icon: mdi:tune
35 | sleeping:
36 | name: Sleeping
37 | initial: off
38 | icon: mdi:sleep
39 | lifxeffects:
40 | name: Lifx Colorloop
41 | initial: off
42 | icon: mdi:theme-light-dark
43 | master_occupied:
44 | name: Master Occupied
45 | initial: off
46 | icon: mdi:motion-sensor
47 |
--------------------------------------------------------------------------------
/input_button.yaml:
--------------------------------------------------------------------------------
1 | #################################################################
2 | ## Input_button
3 | #################################################################
4 | restart_pihole:
5 | name: Restart Pihole
6 | icon: mdi:restart
7 |
--------------------------------------------------------------------------------
/input_datetime.yaml:
--------------------------------------------------------------------------------
1 | #################################################################
2 | ## Input_datetime
3 | #################################################################
4 |
5 | master_time:
6 | name: Master timestamp
7 | has_time: true
8 | has_date: true
9 | upstairs_time:
10 | name: Upstairs timestamp
11 | has_time: true
12 | has_date: true
13 | downstairs_time:
14 | name: Downstairs timestamp
15 | has_time: true
16 | has_date: true
17 |
--------------------------------------------------------------------------------
/input_number.yaml:
--------------------------------------------------------------------------------
1 | #################################################################
2 | ## Input_number
3 | #################################################################
4 |
5 | portfoliovalue:
6 | icon: mdi:currency-usd
7 | name: Portfolio Value
8 | min: 1
9 | max: 5000000
10 | step: 0.01
11 | mode: box
12 | portfolioreturn:
13 | icon: mdi:chart-line-variant
14 | name: Portfolio Return
15 | min: 1
16 | max: 25
17 | portfoliochange:
18 | icon: mdi:currency-usd
19 | name: Portfolio Change
20 | min: -50000
21 | max: 50000
22 | step: 0.01
23 | mode: box
24 | harmonyvolume:
25 | icon: mdi:volume-high
26 | name: Volume
27 | initial: 30
28 | min: 1
29 | max: 100
30 | step: 1
31 | lifxbrightness:
32 | icon: mdi:brightness-6
33 | name: Brightness
34 | initial: 255
35 | min: 0
36 | max: 255
37 | step: 1
38 | lifxtransition:
39 | icon: mdi:timer
40 | name: Transition
41 | initial: 15
42 | min: 0
43 | max: 60
44 | step: 1
45 | lifxchange:
46 | icon: mdi:delta
47 | name: Change
48 | initial: 35
49 | min: 0
50 | max: 359
51 | step: 1
52 | lifxspread:
53 | icon: mdi:arrow-split-vertical
54 | name: Spread
55 | initial: 359
56 | min: 0
57 | max: 359
58 | step: 1
59 | master_prev_temp:
60 | icon: mdi:chart-line-variant
61 | name: Master Temperature
62 | min: 50
63 | max: 100
64 | upstairs_prev_temp:
65 | icon: mdi:chart-line-variant
66 | name: Upstairs Temperature
67 | min: 50
68 | max: 100
69 | downstairs_prev_temp:
70 | icon: mdi:chart-line-variant
71 | name: Downstairs Temperature
72 | min: 50
73 | max: 100
74 | master_prev_eff:
75 | icon: mdi:chart-line-variant
76 | name: Master Prev Efficiency
77 | min: 0
78 | max: 100
79 | upstairs_prev_eff:
80 | icon: mdi:chart-line-variant
81 | name: Upstairs Prev Efficiency
82 | min: 0
83 | max: 100
84 | downstairs_prev_eff:
85 | icon: mdi:chart-line-variant
86 | name: Downstairs Prev Efficiency
87 | min: 0
88 | max: 100
89 |
--------------------------------------------------------------------------------
/input_select.yaml:
--------------------------------------------------------------------------------
1 | abodestatus:
2 | name: Abode status
3 | options:
4 | - disarmed
5 | - armed_home
6 | - armed_away
7 | initial: disarmed
8 | icon: mdi:security
9 | livingroomharmony:
10 | name: Harmony Activity
11 | options:
12 | - PowerOff
13 | - Watch Fire TV
14 | - Youtube
15 | - SATV
16 | - Watch Apple TV
17 | initial: PowerOff
18 | icon: mdi:remote
19 | hdmiswitcher:
20 | name: HDMI Switcher
21 | options:
22 | - AppleTV
23 | - FireTV
24 | - Shield
25 | initial: Shield
26 | icon: mdi:remote
27 | hdmiinput:
28 | name: HDMI Input
29 | options:
30 | - InputHdmi1
31 | - InputHdmi2
32 | - InputHDMI3
33 | - InputHdmi4
34 | initial: InputHdmi4
35 | icon: mdi:remote
36 |
37 | current_theme:
38 | name: 'Current Theme'
39 | options:
40 | - 'default'
41 | - 'oxfordblue'
42 | - 'teal'
43 | - 'darkorange'
44 | - 'darkred'
45 | - 'darkcyan'
46 | - 'Google - Dark'
47 | - 'Google - Light'
48 | - 'sweetpink'
49 | initial: 'default'
50 | icon: 'mdi:palette'
51 |
--------------------------------------------------------------------------------
/input_text.yaml:
--------------------------------------------------------------------------------
1 | #################################################################
2 | ## Input_text
3 | #################################################################
4 |
5 | porch:
6 | name: Porch Camera Tags
7 | initial: Tags
8 | patio:
9 | name: Patio Camera Tags
10 | initial: Tags
11 | driveway:
12 | name: Driveway Camera Tags
13 | initial: Tags
14 | backyard:
15 | name: Backyard Camera Tags
16 | initial: Tags
17 | porchfaces:
18 | name: Porch Camera Faces
19 | initial: 0
20 | patiofaces:
21 | name: Patio Camera Faces
22 | initial: 0
23 | drivewayfaces:
24 | name: Driveway Camera Faces
25 | initial: 0
26 | backyardfaces:
27 | name: Backyard Camera Faces
28 | initial: 0
29 |
--------------------------------------------------------------------------------
/ios.yaml:
--------------------------------------------------------------------------------
1 | actions:
2 | - name: Skip Track
3 | background_color: "#000000"
4 | label:
5 | text: Skip Track
6 | color: "#ff0000"
7 | icon:
8 | icon: skip-next-circle-outline
9 | color: "#ffffff"
10 | - name: Temperature report
11 | background_color: "#000000"
12 | label:
13 | text: Report
14 | color: "#ff0000"
15 | icon:
16 | icon: temperature-fahrenheit
17 | color: "#ffffff"
18 | - name: Set lights
19 | background_color: "#000000"
20 | label:
21 | text: Set lights
22 | color: "#ff0000"
23 | icon:
24 | icon: lamp
25 | color: "#ffffff"
26 | - name: Set network
27 | background_color: "#000000"
28 | label:
29 | text: Set network
30 | color: "#ff0000"
31 | icon:
32 | icon: router-wireless-settings
33 | color: "#ffffff"
34 | push:
35 | categories:
36 | - name: Abode Alarm
37 | identifier: 'abode_alarm'
38 | actions:
39 | - identifier: 'SOUND_ALARM'
40 | title: 'Sound Alarm'
41 | activationMode: 'background'
42 | authenticationRequired: yes
43 | destructive: yes
44 | behavior: 'default'
45 | - identifier: 'DISARM_ABODE'
46 | title: 'Disarm Abode'
47 | activationMode: 'background'
48 | authenticationRequired: yes
49 | destructive: no
50 | behavior: 'default'
51 | - name: Abode Updates
52 | identifier: 'abode_updates'
53 | actions:
54 | - identifier: 'ENABLE_ABODE_UPDATES'
55 | title: 'Enable Abode Updates'
56 | activationMode: 'background'
57 | authenticationRequired: no
58 | destructive: yes
59 | behavior: 'default'
60 | - name: Home Automation
61 | identifier: 'home_automation'
62 | actions:
63 | - identifier: 'ENABLE_HOME_AUTOMATION'
64 | title: 'Enable Home Automation'
65 | activationMode: 'background'
66 | authenticationRequired: no
67 | destructive: yes
68 | behavior: 'default'
69 | # Apple Watch
70 | - name: Set lights
71 | identifier: set_lights
72 | actions:
73 | - identifier: MASTER
74 | title: Master
75 | - identifier: LIVING_ROOM
76 | title: Living room
77 | - identifier: KITCHEN
78 | title: Kitchen
79 | - name: Light options
80 | identifier: light_options
81 | actions:
82 | - identifier: "OFF"
83 | title: "Off"
84 | - identifier: "ON"
85 | title: "On"
86 |
87 | - name: Set network
88 | identifier: set_network
89 | actions:
90 | - identifier: RISHAANFIREHD
91 | title: Rishaan FireHD
92 | - identifier: ARNAV_IPAD
93 | title: Arnav iPad
94 | - identifier: LENOVO
95 | title: Lenovo
96 | - identifier: TOSHIBA_AIO
97 | title: ToshibaAIO
98 | - identifier: SAMMY_TV
99 | title: Sammy TV
100 | - identifier: PIHOLE_MAIN
101 | title: PiHole Main
102 | - identifier: PIHOLE_KIDS
103 | title: PiHole Kids
104 | - name: Device options
105 | identifier: device_options
106 | actions:
107 | - identifier: "OFF"
108 | title: "Off"
109 | - identifier: "ON"
110 | title: "On"
111 |
--------------------------------------------------------------------------------
/logbook.yaml:
--------------------------------------------------------------------------------
1 | include:
2 | domains:
3 | - alarm_control_panel
4 | - climate
5 | - cover
6 | - input_boolean
7 | - input_select
8 | - light
9 | - media_player
10 | - switch
11 | - person
12 | - logbook
13 | entities:
14 | - binary_sensor.alok_home
15 | - binary_sensor.back_door
16 | - binary_sensor.cube_158d0001035aa7
17 | - binary_sensor.door_window_sensor_158d0001bf26df
18 | - binary_sensor.front_door
19 | - binary_sensor.garage_entry_door
20 | - binary_sensor.key_fob
21 | - binary_sensor.motion_sensor_158d0001a1f2ab
22 | - binary_sensor.rashmi_home
23 | - binary_sensor.water_leak_sensor_158d0001d77800
24 | - device_tracker.meta_rashmi
25 | - device_tracker.meta_alok
26 | - device_tracker.rashmiphone_rashmiphone_2
27 | - device_tracker.rashmiappiphone_2
28 | - device_tracker.rashmiiphone
29 | - device_tracker.life360_sonu
30 | - device_tracker.275f08b3_dd14_459f_81b9_6c5b02c9b54e
31 | - device_tracker.alokphone_alokphone_2
32 | - device_tracker.alokiphone_3
33 | - device_tracker.alokiphone
34 | - device_tracker.life360_alok_saboo
35 | - device_tracker.alok_geofency
36 | - device_tracker.alok_composite
37 | - sensor.living_room
38 | - sensor.plex
39 | - sensor.season
40 | - sensor.speedtest_download
41 | - sensor.ssl_certificate_expiry
42 | - sensor.steps_alok
43 | - sensor.steps_rashmi
44 | - sensor.sonos_stereo
45 | - sensor.cube_last_action
46 | - switch.shelly_garage
47 | - automation.turn_off_master_lights
48 | - automation.turn_on_master_lights_on_motion
49 | - image_processing.opencv_porch
50 | - image_processing.opencv_patio
51 | - image_processing.opencv_driveway
52 | - image_processing.opencv_backyard
53 | - image_processing.tensorflow_porch
54 | - image_processing.tensorflow_patio
55 | - image_processing.tensorflow_driveway
56 | - image_processing.tensorflow_backyard
57 | exclude:
58 | domains:
59 | - sun
60 | - binary_sensor
61 | - camera
62 | - device_tracker
63 | - group
64 | - input_number
65 | - input_text
66 | - script
67 | - sensor
68 | - weather
69 | - zone
70 | - automation
71 | entities:
72 | - sensor.last_boot
73 | - input_boolean.master_occupied
74 | - light.master_1
75 | - light.master_2
76 | - light.master_3
77 | - sensor.date
78 | - input_number.harmonyvolume
79 | - light.lifx3
80 | - light.lifxnrguest
81 | - light.lifxnrkitchen
82 | - switch.auto_away
83 | - switch.auto_home
84 | - switch.backyardmotion
85 | - switch.drivewaymotion
86 | - switch.patiomotion
87 | - switch.porchmotion
88 | - switch.study_fan
89 |
--------------------------------------------------------------------------------
/lovelace.yaml:
--------------------------------------------------------------------------------
1 | mode: yaml
2 | resources:
3 | - url: /hacsfiles/weather-card/weather-card.js
4 | type: module
5 | - url: /hacsfiles/pc-card/pc-card.js
6 | type: module
7 | - url: /local/custom_ui/circle-sensor-card.js
8 | type: module
9 | - url: /hacsfiles/lovelace-thermostat-card/main.js
10 | type: module
11 | - url: /hacsfiles/button-card/button-card.js
12 | type: module
13 | - url: /hacsfiles/restriction-card/restriction-card.js
14 | type: module
15 | - url: https://unpkg.com/moment@2.22.2/moment.js
16 | type: js
17 | - url: /hacsfiles/lovelace-slider-entity-row/slider-entity-row.js
18 | type: js
19 | - url: /hacsfiles/mini-media-player/mini-media-player-bundle.js
20 | type: module
21 | - url: /hacsfiles/mini-graph-card/mini-graph-card-bundle.js
22 | type: module
23 | - url: /hacsfiles/lovelace-gap-card/gap-card.js
24 | type: module
25 | - url: /hacsfiles/lovelace-layout-card/layout-card.js
26 | type: module
27 | - url: /local/custom_ui/card-modder.js
28 | type: js
29 | - url: /hacsfiles/lovelace-card-mod/card-mod.js
30 | type: module
31 | - url: /hacsfiles/firetv-card/firetv-card.js
32 | type: module
33 | - url: /hacsfiles/tv-card/tv-card.js
34 | type: module
35 | - url: /hacsfiles/lovelace-auto-entities/auto-entities.js
36 | type: module
37 | - url: /hacsfiles/config-template-card/config-template-card.js
38 | type: module
39 | - url: /hacsfiles/secondaryinfo-entity-row/secondaryinfo-entity-row.js
40 | type: module
41 | - url: /hacsfiles/simple-thermostat/simple-thermostat.js
42 | type: module
43 | - url: /hacsfiles/lovelace-hass-aarlo/hass-aarlo.js
44 | type: module
45 | - url: /hacsfiles/list-card/list-card.js
46 | type: js
47 | - url: /hacsfiles/unused-card/unused-card.js
48 | type: module
49 | - url: /hacsfiles/lovelace-card-tools/card-tools.js
50 | type: module
51 | - url: /hacsfiles/lovelace-template-entity-row/template-entity-row.js
52 | type: module
53 | - url: /hacsfiles/harmony-card/harmony-card.js
54 | type: module
55 | - url: /hacsfiles/lovelace-multiple-entity-row/multiple-entity-row.js
56 | type: module
57 | - url: /hacsfiles/vertical-stack-in-card/vertical-stack-in-card.js
58 | type: module
59 | - url: /hacsfiles/banner-card/banner-card.js
60 | type: module
61 | - url: /hacsfiles/bar-card/bar-card.js
62 | type: module
63 | - url: /hacsfiles/uptime-card/uptime-card.js
64 | type: module
65 |
--------------------------------------------------------------------------------
/packages/homebridge.yaml:
--------------------------------------------------------------------------------
1 | ################################################################
2 | ## Package / Homebridge
3 | ################################################################
4 |
5 | ################################################
6 | ## Customize
7 | ################################################
8 |
9 | homeassistant:
10 | customize_domain:
11 | alarm_control_panel:
12 | homebridge_hidden: false
13 |
14 | binary_sensor:
15 | homebridge_hidden: true
16 |
17 | climate:
18 | homebridge_hidden: true
19 |
20 | cover:
21 | homebridge_hidden: false
22 |
23 | device_tracker:
24 | homebridge_hidden: true
25 |
26 | fan:
27 | homebridge_hidden: true
28 |
29 | group:
30 | homebridge_hidden: true
31 |
32 | input_boolean:
33 | homebridge_hidden: true
34 |
35 | light:
36 | homebridge_hidden: true
37 |
38 | lock:
39 | homebridge_hidden: false
40 |
41 | media_player:
42 | homebridge_hidden: true
43 |
44 | scene:
45 | homebridge_hidden: true
46 |
47 | remote:
48 | homebridge_hidden: true
49 |
50 | sensor:
51 | homebridge_hidden: true
52 |
53 | switch:
54 | homebridge_hidden: true
55 |
--------------------------------------------------------------------------------
/packages/opencv.yaml:
--------------------------------------------------------------------------------
1 | homeassistant:
2 | customize:
3 | sensor.secrets:
4 | base_url: !secret base_url
5 | image_processing.opencv_backyard:
6 | camera: camera.backyard
7 | image_processing.opencv_patio:
8 | camera: camera.patio
9 | image_processing.opencv_driveway:
10 | camera: camera.driveway
11 | image_processing.opencv_porch:
12 | camera: camera.porch
13 | image_processing.face_detected_backyard:
14 | camera: camera.backyard
15 | image_processing.face_detected_patio:
16 | camera: camera.patio
17 | image_processing.face_detected_driveway:
18 | camera: camera.driveway
19 | image_processing.face_detected_porch:
20 | camera: camera.porch
21 | automation:
22 | - id: log_opencv
23 | alias: 'OpenCV detection'
24 | initial_state: 'off'
25 | trigger:
26 | - platform: state
27 | entity_id:
28 | - image_processing.opencv_backyard
29 | - image_processing.opencv_patio
30 | - image_processing.opencv_driveway
31 | - image_processing.opencv_porch
32 | condition:
33 | - condition: template
34 | value_template: >
35 | {{ trigger.to_state.attributes.total_matches > 0 }}
36 | action:
37 | - service: logbook.log
38 | data_template:
39 | name: "OpenCV activity: "
40 | message: >-
41 | {{ dict(trigger.to_state.attributes)|tojson }}
42 | - service: notify.telegram
43 | data_template:
44 | title: "OpenCV: "
45 | message: >-
46 | {{ dict(trigger.to_state.attributes)|tojson }}
47 | data:
48 | photo:
49 | - url: >-
50 | {% set camera = trigger.to_state.attributes.camera.split('.')[1] %}
51 | {{ states.sensor.secrets.attributes.base_url ~ states.camera[camera].attributes.entity_picture }}
52 | caption: >-
53 | {{ trigger.to_state.attributes.matches.Face }}
54 | # - id: dlib_person_detected
55 | # alias: Dlib person detected
56 | # initial_state: 'on'
57 | # trigger:
58 | # platform: event
59 | # event_type:
60 | # - image_processing.face_detected_backyard
61 | # - image_processing.face_detected_driveway
62 | # - image_processing.face_detected_patio
63 | # - image_processing.face_detected_porch
64 | # action:
65 | # - service: logbook.log
66 | # data_template:
67 | # name: "Person Detected: "
68 | # message: >-
69 | # {{ dict(trigger.to_state.attributes)|tojson }}
70 | # - service: notify.telegram
71 | # data_template:
72 | # title: "Person Detected: "
73 | # message: >-
74 | # {{ dict(trigger.to_state.attributes)|tojson }}
75 | # data:
76 | # photo:
77 | # - url: >-
78 | # {% set camera = trigger.to_state.attributes.camera.split('.')[1] %}
79 | # {{ states.sensor.secrets.attributes.base_url ~ states.camera[camera].attributes.entity_picture }}
80 |
81 | sensor:
82 | - platform: template
83 | sensors:
84 | secrets:
85 | value_template: "Secrets Container"
86 |
--------------------------------------------------------------------------------
/packages/synology_surveillance.yaml:
--------------------------------------------------------------------------------
1 | #Create triggers in Action Rule and obtain the respective URLs. The format is:
2 | # ss_enable_home: 'http://IP_ADDRESS:5000/webapi/entry.cgi?api=SYNO.SurveillanceStation.ExternalEvent&method="Trigger"&version=1&eventId=1&eventName="This is external event1"&account="USER"&password="PASSWORD"'
3 | rest_command:
4 | ss_enable_home:
5 | url: !secret ss_enable_home
6 | method: get
7 | ss_disable_home:
8 | url: !secret ss_disable_home
9 | method: get
10 |
--------------------------------------------------------------------------------
/python_scripts/dark_sky_friendly_names.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Author: Alok R. Saboo
3 | # Description: This script takes the dark_sky forecast sensors and updates
4 | # their friendly_name to include the date and day.
5 | # https://github.com/arsaboo/homeassistant-config/blob/master/packages/weather.yaml
6 | ###############################################################################
7 | dark_sky_entities = ["sensor.forecast_1", "sensor.forecast_2", "sensor.forecast_3",
8 | "sensor.forecast_4", "sensor.forecast_5", "sensor.forecast_6",
9 | "sensor.forecast_7"]
10 | days = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]
11 |
12 | triggeredEntity = data.get('entity_id')
13 | # logger.warning("trigger is {}".format(triggeredEntity))
14 | now = datetime.datetime.now()
15 | today = now.weekday()
16 |
17 | if triggeredEntity is None:
18 | for entity_id in dark_sky_entities:
19 | # copy it's state
20 | state = hass.states.get(entity_id)
21 | newState = state.state
22 | forecastdays = int(entity_id.split('_')[1])
23 | day = datetime.timedelta(days = forecastdays)
24 | forecastdate = now + day
25 | newEntityPicture = state.attributes.get('entity_picture')
26 | if today + forecastdays > 6:
27 | newDay = days[today + forecastdays - 7]
28 | else:
29 | newDay = days[today + forecastdays]
30 | # Set states
31 | hass.states.set(entity_id, newState, {
32 | 'friendly_name': "{} ({}/{})".format(newDay, forecastdate.month, forecastdate.day),
33 | 'entity_picture': newEntityPicture,
34 | })
35 | else:
36 | state = hass.states.get(triggeredEntity)
37 | newState = state.state
38 | forecastdays = int(triggeredEntity.split('_')[1])
39 | day = datetime.timedelta(days = forecastdays)
40 | forecastdate = now + day
41 | newEntityPicture = state.attributes.get('entity_picture')
42 | if today + forecastdays > 6:
43 | newDay = days[today + forecastdays - 7]
44 | else:
45 | newDay = days[today + forecastdays]
46 | # Set states
47 | hass.states.set(triggeredEntity, newState, {
48 | 'friendly_name': "{} ({}/{})".format(newDay, forecastdate.month, forecastdate.day),
49 | 'entity_picture': newEntityPicture,
50 | })
51 |
--------------------------------------------------------------------------------
/python_scripts/fade_in_light.py:
--------------------------------------------------------------------------------
1 | # GE Dimmer Switch Connected to SmartThings via MQTT
2 | # Python script to turn it on over a period of X minutes
3 | entity_id = data.get('entity_id')
4 | sleep_delay = int(data.get('delay_in_sec'))
5 | start_level_pct = int(data.get('start_level_pct'))
6 | end_level_pct = int(data.get('end_level_pct'))
7 | step_pct = int(data.get('step_in_level_pct'))
8 |
9 | start_level = int(255*start_level_pct/100)
10 | end_level = int(255*end_level_pct/100)
11 | step = int(255*step_pct/100)
12 |
13 | new_level = start_level
14 | while new_level < end_level
15 | states = hass.states.get(entity_id)
16 | current_level = states.attributes.get('brightness') or 0
17 | if (current_level > new_level) :
18 | logger.info('Exiting Fade In')
19 | break;
20 | else :
21 | logger.info('Setting brightness of ' + str(entity_id) + ' from ' + current_level + ' to ' + new_level)
22 | data = { "entity_id" : entity_id, "brightness" : new_level }
23 | hass.services.call('light', 'turn_on', data)
24 | new_level = new_level + step
25 | time.sleep(sleep_delay)
26 |
--------------------------------------------------------------------------------
/python_scripts/initialize_tracker.py:
--------------------------------------------------------------------------------
1 | AlokFriendlyName = 'Alok Test Tracker'
2 | AlokEntityPicture = '/local/icons/Alok.png'
3 | AloktrackerName = 'device_tracker.test_alok'
4 |
5 | RashmiFriendlyName = 'Rashmi Test Tracker'
6 | RashmiEntityPicture = '/local/icons/Rashmi.png'
7 | RashmitrackerName = 'device_tracker.test_rashmi'
8 |
9 | AlokcurrentState = hass.states.get(AloktrackerName)
10 | AloknewStatus = AlokcurrentState.state
11 |
12 | RashmicurrentState = hass.states.get(RashmitrackerName)
13 | RashminewStatus = RashmicurrentState.state
14 |
15 | # Create device_tracker.test_alok entity
16 | hass.states.set(AloktrackerName, AloknewStatus, {
17 | 'friendly_name': AlokFriendlyName,
18 | 'entity_picture': AlokEntityPicture,
19 | 'show_last_changed': 'true'
20 | })
21 |
22 | # Create device_tracker.test_rashmi entity
23 | hass.states.set(RashmitrackerName, RashminewStatus, {
24 | 'friendly_name': RashmiFriendlyName,
25 | 'entity_picture': RashmiEntityPicture,
26 | 'show_last_changed': 'true'
27 | })
28 |
--------------------------------------------------------------------------------
/python_scripts/light_counter.py:
--------------------------------------------------------------------------------
1 | on = 0
2 | for entity_id in hass.states.entity_ids('light'):
3 | state = hass.states.get(entity_id)
4 | if state.state == 'on':
5 | on = on + 1
6 | hass.states.set('sensor.lights_on', on, {
7 | 'unit_of_measurement': 'lights',
8 | 'friendly_name': 'Lights On'
9 | })
10 |
--------------------------------------------------------------------------------
/python_scripts/meta_device_tracker.py:
--------------------------------------------------------------------------------
1 | # Combine multiple device trackers into one entity
2 | # You can call the script using the following:
3 | # - service: python_script.meta_device_tracker
4 | # data_template:
5 | # entity_id: '{{trigger.entity_id}}'
6 |
7 | # OPTIONS
8 | # List the trackers for each individual
9 | RashmiTrackers = ['device_tracker.rashmiiphone', 'device_tracker.rashmiphone_rashmiphone_2',
10 | 'device_tracker.rashmiappiphone_2', 'device_tracker.life360_sonu',
11 | 'device_tracker.275f08b3_dd14_459f_81b9_6c5b02c9b54e']
12 | AlokTrackers = ['device_tracker.alokiphone', 'device_tracker.alokphone_alokphone_2',
13 | 'device_tracker.alokiphone_3', 'device_tracker.life360_alok_saboo',
14 | 'device_tracker.elantrase_2', 'device_tracker.alok_geofency']
15 | ArnavTrackers = ['device_tracker.arnav_iphone_12',
16 | 'device_tracker.life360_arnav_saboo',
17 | 'device_tracker.arnav_geofency',
18 | 'bdevice_tracker.arnavphone_arnavphone',
19 | 'device_tracker.arnaviphone12pro']
20 | # Get the entity that triggered the automation
21 | triggeredEntity = data.get('entity_id')
22 |
23 | # Set friendly name and the metatracker name based on the entity that triggered
24 | if triggeredEntity in AlokTrackers:
25 | newFriendlyName = 'Alok Tracker'
26 | newEntityPicture = '/local/icons/Alok.png'
27 | metatrackerName = 'device_tracker.meta_alok'
28 | elif triggeredEntity in RashmiTrackers:
29 | newFriendlyName = 'Rashmi Tracker'
30 | newEntityPicture = '/local/icons/Rashmi.png'
31 | metatrackerName = 'device_tracker.meta_rashmi'
32 | elif triggeredEntity in ArnavTrackers:
33 | newFriendlyName = 'Arnav Tracker'
34 | newEntityPicture = '/local/icons/Arnav.png'
35 | metatrackerName = 'device_tracker.meta_arnav'
36 | else:
37 | newFriendlyName = None
38 | metatrackerName = None
39 |
40 | # Get current & new state
41 | newState = hass.states.get(triggeredEntity)
42 | currentState = hass.states.get(metatrackerName)
43 | # Get New data
44 | newSource = newState.attributes.get('source_type')
45 | newFriendlyName_temp = newState.attributes.get('friendly_name')
46 |
47 | # If GPS source, set new coordinates
48 | if newSource == 'gps':
49 | newLatitude = newState.attributes.get('latitude')
50 | newLongitude = newState.attributes.get('longitude')
51 | newgpsAccuracy = newState.attributes.get('gps_accuracy')
52 | # If not, keep last known coordinates
53 | elif newSource is not None and currentState.attributes.get('latitude') is not None:
54 | newLatitude = currentState.attributes.get('latitude')
55 | newLongitude = currentState.attributes.get('longitude')
56 | newgpsAccuracy = currentState.attributes.get('gps_accuracy')
57 | # Otherwise return null
58 | else:
59 | newLatitude = None
60 | newLongitude = None
61 | newgpsAccuracy = None
62 |
63 | # Get Battery
64 | if newState.attributes.get('battery') is not None:
65 | newBattery = newState.attributes.get('battery')
66 | elif currentState is not None and currentState.attributes.get('battery') is not None:
67 | newBattery = currentState.attributes.get('battery')
68 | else:
69 | newBattery = None
70 |
71 | # Get velocity
72 | if newState.attributes.get('velocity') is not None:
73 | newVelocity = newState.attributes.get('velocity')
74 | elif currentState is not None and currentState.attributes.get('velocity') is not None:
75 | newVelocity = currentState.attributes.get('velocity')
76 | else:
77 | newVelocity = None
78 |
79 | if newState.state is not None:
80 | newStatus = newState.state
81 | else:
82 | newStatus = currentState.state
83 |
84 | # Create device_tracker.meta entity
85 | hass.states.set(metatrackerName, newStatus, {
86 | 'friendly_name': newFriendlyName,
87 | 'entity_picture': newEntityPicture,
88 | 'source_type': newSource,
89 | 'battery': newBattery,
90 | 'gps_accuracy': newgpsAccuracy,
91 | 'latitude': newLatitude,
92 | 'longitude': newLongitude,
93 | 'velocity': newVelocity,
94 | 'update_source': triggeredEntity,
95 | 'show_last_changed': 'true'
96 | })
97 |
--------------------------------------------------------------------------------
/python_scripts/ring_download.py:
--------------------------------------------------------------------------------
1 | # obtain ring doorbell camera object
2 | # replace the camera.front_door by your camera entity
3 | ring_cam = hass.states.get('camera.front_door')
4 |
5 | subdir_name = 'ring_{}'.format(ring_cam.attributes.get('friendly_name').lower().replace(' ', '_'))
6 |
7 | # get video URL
8 | data = {
9 | 'url': ring_cam.attributes.get('video_url'),
10 | 'subdir': subdir_name,
11 | 'overwrite': True,
12 | 'filename': ring_cam.attributes.get('friendly_name').lower().replace(' ', '_')
13 | }
14 |
15 | # call downloader component to save the video
16 | hass.services.call('downloader', 'download_file', data)
17 |
--------------------------------------------------------------------------------
/python_scripts/toggle_state.py:
--------------------------------------------------------------------------------
1 | # Manually toggle state of a device_tracker entity
2 | # You can call the script using the following:
3 | # - service: python_script.toggle_state
4 | # data_template:
5 | # entity_id: '{{trigger.entity_id}}'
6 |
7 | # Get the entity that triggered the automation
8 | triggeredEntity = data.get('entity_id')
9 | currentState = hass.states.get(triggeredEntity)
10 |
11 | if currentState == 'home':
12 | newStatus = 'not_home'
13 | else:
14 | newStatus = 'home'
15 |
16 | hass.states.set(triggeredEntity, newStatus)
17 | logger.warning("Set %s to %s",triggeredEntity, newStatus)
18 |
--------------------------------------------------------------------------------
/recorder.yaml:
--------------------------------------------------------------------------------
1 | purge_keep_days: 5
2 | db_url: !secret recorder_db_url
3 | exclude:
4 | domains:
5 | - sun
6 | entities:
7 | - binary_sensor.backyard_motion
8 | - binary_sensor.driveway_motion
9 | - binary_sensor.patio_motion
10 | - binary_sensor.porch_motion
11 | - group.camera_sensors
12 | - input_select.livingroomharmony
13 | - sensor.living_room
14 | - sensor.dark_sky_summary
15 | - sensor.total_tv_time
16 | - zwave.garage_door_tilt_sensor
17 | - automation.change_leeo_color
18 | - automation.change_leeo_upstairs_color
19 | - automation.parse_life360_data
20 | - automation.update_dark_sky_friendly_names
21 | - automation.update_friendly_names_at_night
22 | - group.ring_doorbell
23 | - sensor.rpi_cpu_thermal_1_temperature
24 | - sensor.cold_flu_risk
25 | - sensor.curr_future
26 | - sensor.pollen_level
27 | - sensor.uptime
28 | - sensor.sonos_stereo
29 | - sensor.sonos_audio_in
30 | - sensor.ssl_certificate_expiry
31 | - switch.pihole
32 | - sensor.downstairs_hvac_runtime
33 | - sensor.upstairs_hvac_runtime
34 | - sensor.master_hvac_runtime
35 | - sensor.downstairs_temp_change
36 | - sensor.master_temp_change
37 | - sensor.upstairs_temp_change
38 | - sensor.pws_weather
39 | - sensor.dark_sky_hourly_summary
40 | - sensor.dark_sky_daily_summary
41 | - sensor.pws_wind_string
42 | - sensor.emulated_hue_names
43 | - sensor.usdinr
44 | - automation.populate_owntracks_data_from_life360
45 | - automation.update_device_meta_tracker
46 | - image_processing.opencv_backyard
47 | - image_processing.opencv_patio
48 | - image_processing.opencv_driveway
49 | - image_processing.opencv_porch
50 | - sensor.pihole_dns_queries_today
51 | - sensor.pihole_domains_blocked
52 | - sensor.pihole_ads_percentage_blocked_today
53 | - sensor.pihole_dns_unique_domains
54 | - sensor.10_year_treasury
55 | - input_boolean.frontdoor
56 | - input_boolean.backdoor
57 | - binary_sensor.garageentrydoor
58 | - input_text.patio
59 | - input_text.porch
60 | - input_text.driveway
61 | - input_text.backyard
62 | - sensor.new_cases_us
63 | - sensor.new_cases_india
64 | - sensor.new_cases_georgia
65 |
--------------------------------------------------------------------------------
/scenes.yaml:
--------------------------------------------------------------------------------
1 | #################################################################
2 | ## Scenes
3 | #################################################################
4 |
5 | - name: Colorloop
6 | entities:
7 | light.living_room_lights: on
8 | lifx_effect_colorloop
9 | - platform: lifx_cloud
10 | token: !secret lifx_key
11 |
--------------------------------------------------------------------------------
/shell_scripts/alexa_wrapper.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Amazon Alexa TTS Home Assistant Wrapper
4 | #
5 | # 2018-06-18: v0.1 initial release
6 | #
7 | # This script is intended to allow the Alexa Remote Control script
8 | # from Alex Lotzimmer to be used as a command line notify platform
9 | # in Home Assistant.
10 | #
11 | # Usage:
12 | # ./alexa_wrapper.sh -d "My Dot Name"
13 | #
14 | # Home Assistant will pass the message to the script via STDIN. The
15 | # Alexa Remote control script requires that spaces be replaced with
16 | # underscores.
17 | #
18 | # Installation:
19 | # Place alexa_wrapper.sh and alexa_remote_control.sh in your Home Assistant
20 | # config directory. In a shell type echo $PATH and replace the below PATH
21 | # variable with your values.
22 | #
23 | # Edit alexa_remote_control.sh with your credentials and
24 | # your location. Test that you can pull a list of devices with
25 | # ./alexa_remote_control.sh -a
26 | #
27 | # Add a command line notify component for each Alexa device
28 | # to Home Assistant as follows:
29 | #
30 | # notify:
31 | # - platform: command_line
32 | # name: 'My Dot Name'
33 | # command: "/config/alexa_wrapper -d 'My Dot Name'"
34 | #
35 | # You should then be able to call notify.my_dot_name from automations
36 | #
37 |
38 | PATH=/home/arsaboo/bin:/home/arsaboo/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
39 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
40 | ALEXA_REMOTE="$DIR/alexa_remote_control.sh"
41 |
42 | usage()
43 | {
44 | echo "$0 -d |ALL"
45 | }
46 |
47 | case "$1" in
48 | -d)
49 | if [ "${2#-}" != "${2}" -o -z "$2" ] ; then
50 | echo "ERROR: missing argument for ${1}"
51 | usage
52 | exit 1
53 | fi
54 | DEVICE=$2
55 | shift
56 | ;;
57 | *)
58 | echo "ERROR: unknown option ${1}"
59 | usage
60 | exit 1
61 | ;;
62 | esac
63 | shift
64 |
65 | read message
66 |
67 | formatted=${message// /_}
68 |
69 | $ALEXA_REMOTE -d "$DEVICE" -e speak:$formatted >> /dev/null
70 | exit 0
71 |
--------------------------------------------------------------------------------
/shell_scripts/facebox.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #Set variables
4 | facebox_endpoint=$(awk '/facebox_endpoint/ {print $2}' /config/secrets.yaml)
5 | password=$(awk '/http_password/ {print $2}' /config/secrets.yaml)
6 | cameraURL=$(grep "$1_camera_image_url" /config/secrets.yaml | awk '{print $2}')
7 | sensor_name=$(grep "$1_faces_sensor" /config/secrets.yaml | awk '{print $2}')
8 |
9 | tagbox=$(curl -s -H 'Content-Type: application/json' -d '{"url": "'"$cameraURL"'"}' $facebox_endpoint)
10 | tags=$(echo $tagbox | jq --raw-output '.facesCount')
11 |
12 | curl -X POST -H "x-ha-access: $password" \
13 | -H "Content-Type: application/json" \
14 | -d '{"state":"'"$tags"'" }' \
15 | "$sensor_name"
16 |
--------------------------------------------------------------------------------
/shell_scripts/image_classification.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #Set variables
4 | tagbox_endpoint=$(awk '/tagbox_endpoint/ {print $2}' /config/secrets.yaml)
5 | password=$(awk '/http_password/ {print $2}' /config/secrets.yaml)
6 | cameraURL=$(grep "$1_camera_image_url" /config/secrets.yaml | awk '{print $2}')
7 | sensor_name=$(grep "$1_tag_sensor" /config/secrets.yaml | awk '{print $2}')
8 |
9 | tagbox=$(curl -s -H 'Content-Type: application/json' -d '{"url": "'"$cameraURL"'"}' $tagbox_endpoint)
10 | tags=$(echo $tagbox | jq --raw-output '.tags | map(.tag) | join(", ")')
11 |
12 | curl -X POST -H "x-ha-access: $password" \
13 | -H "Content-Type: application/json" \
14 | -d '{"state":"'"$tags"'" }' \
15 | "$sensor_name"
16 |
--------------------------------------------------------------------------------
/themes/oxfordblue/oxfordblue.yaml:
--------------------------------------------------------------------------------
1 | oxfordblue:
2 | # Main colors
3 | ha-card-border-radius: '8px'
4 | primary-color: '#324d67' # Header
5 | accent-color: '#148e99' # Accent color
6 | dark-primary-color: '#243a52' # Hyperlinks
7 | light-primary-color: '#617d98' # Horizontal line in about
8 | # Text colors
9 | primary-text-color: '#102a42' # Primary text color, here is referencing dark-primary-color
10 | text-primary-color: '#ffffff' # Primary text color
11 | secondary-text-color: '#829ab0' # For secondary titles in more info boxes etc.
12 | disabled-text-color: '#dae2ec' # Disabled text color
13 | # Background colors
14 | primary-background-color: '#f1f5f8' # Settings background
15 | secondary-background-color: 'var(--primary-background-color)' # Main card UI background
16 | sidebar-background-color: 'var(--primary-background-color)'
17 | divider-color: 'var(--secondary-text-color)'
18 | # Table rows
19 | table-row-background-color: 'var(--primary-background-color)' # Table row
20 | table-row-alternative-background-color: '#e4eaf1' # Table row alternative
21 | # Nav Menu
22 | paper-listbox-color: '#102a42' # Navigation menu selection hoover
23 | paper-listbox-background-color: 'var(--primary-background-color)' # Navigation menu background
24 | paper-grey-50: 'var(--primary-text-color)'
25 | paper-grey-200: '#414A59' # Navigation menu selection
26 | # Paper card
27 | paper-card-header-color: 'var(--accent-color)' # Card header text color
28 | paper-card-background-color: '#FFF' # Card background color
29 | paper-dialog-background-color: '#FFF' # Card dialog background color
30 | paper-item-icon-color: 'var(--accent-color)' # Icon color
31 | paper-item-icon-active-color: '#F9C536' # Icon color active
32 | paper-item-icon_-_color: 'green'
33 | paper-item-selected_-_background-color: '#434954'# Popup item select
34 | paper-tabs-selection-bar-color: 'green'
35 | # Labels
36 | label-badge-red: 'var(--google-red-500)'
37 | label-badge-blue: 'var(--accent-color)' # References the brand color label badge border
38 | ha-label-badge-color: 'var(--accent-color)'
39 | label-badge-text-color: 'var(--primary-text-color)' # Now same as label badge border but that's a matter of taste
40 | label-badge-background-color: 'var(--primary-background-color)' # Same, but can also be set to transparent here
41 | # Switches
42 | paper-toggle-button-checked-button-color: 'var(--accent-color)'
43 | paper-toggle-button-checked-bar-color: 'var(--accent-color)'
44 | paper-toggle-button-checked-ink-color: 'var(--accent-color)'
45 | paper-toggle-button-unchecked-button-color: 'var(--disabled-text-color)'
46 | paper-toggle-button-unchecked-bar-color: 'var(--disabled-text-color)'
47 | paper-toggle-button-unchecked-ink-color: 'var(--disabled-text-color)'
48 | switch-checked-color: 'var(--accent-color)'
49 | switch-unchecked-color: 'var(--disabled-text-color)'
50 | switch-unchecked-button-color: 'var(--disabled-text-color)'
51 | switch-unchecked-track-color: 'var(--disabled-text-color)'
52 | # Sliders
53 | paper-slider-knob-color: 'var(--accent-color)'
54 | paper-slider-knob-start-color: 'var(--accent-color)'
55 | paper-slider-pin-color: 'var(--accent-color)'
56 | paper-slider-active-color: 'var(--accent-color)'
57 | paper-slider-container-color: 'linear-gradient(var(--primary-background-color), var(--secondary-background-color)) no-repeat'
58 | paper-slider-secondary-color: 'var(--secondary-background-color)'
59 | paper-slider-disabled-active-color: 'var(--disabled-text-color)'
60 | paper-slider-disabled-secondary-color: 'var(--disabled-text-color)'
61 | # Google colors
62 | google-red-500: '#db3236'
63 | google-green-500: '#39E949'
64 |
65 | # CUSTOM STYLES NOT PART OF CORE HOME ASSISTANT
66 | # HACS https://hacs.xyz/docs/basic/theming
67 | hacs-badge-color: 'var(--paper-item-icon-color)' # Used for icon colors and new items in the store
68 | hacs-status-installed: 'var(--light-primary-color)' # Controls the icon color for installed, up-to-date repositories
69 | hacs-status-pending-restart: 'var(--accent-color)' # Controls the icon color for installed repositories that are awaiting a Home Assistant restart
70 | hacs-status-pending-update: 'var(--accent-color)' # Controls the icon color for installed repositories that have an update available
71 | hacs-status-not-loaded: 'var(--disabled-text-color)' # Controls the icon color for repositories that is not loaded
72 | link-text-color: 'var(--accent-color)' # Used for href links in HACS
73 |
--------------------------------------------------------------------------------
/themes/sweetpink/sweetpink.yaml:
--------------------------------------------------------------------------------
1 | sweetpink:
2 | # used these colors
3 | light: "#dbebfb"
4 | dark: "#32334a"
5 | pink: "#e83e8c"
6 | gray: "#1d1e2f"
7 | gray-dark: "#11111d"
8 | indigo: "#6610f2"
9 | purple: "#6f42c1"
10 | teal: "#20c997"
11 | cyan: "#17a2b8"
12 |
13 | # could use these colors
14 | blue: "#007bff"
15 | red: "#dc3545"
16 | orange: "#fd7e14"
17 | yellow: "#ffc107"
18 | green: "#28a745"
19 | white: "#fff"
20 |
21 | primary-color: "var(--pink)"
22 | primary-background-color: "var(--dark)"
23 | primary-text-color: "var(--light)"
24 | light-primary-color: "var(--cyan)"
25 | accent-color: "var(--teal)"
26 |
27 | secondary-background-color: "var(--gray)"
28 | secondary-text-color: "var(--cyan)"
29 |
30 | disabled-text-color: "var(--gray)"
31 |
32 | divider-color: "rgba(255, 255, 255, 0.12)"
33 |
34 | paper-card-background-color: "var(--gray-dark)"
35 | paper-card-header-color: "#var(--paper-item-icon-color)"
36 | card-background-color: "var(--paper-card-background-color)"
37 |
38 | paper-item-icon-color: "var(--pink)"
39 | paper-listbox-background-color: "var(--indigo)"
40 | paper-listbox-color: "var(--light)"
41 |
42 | paper-grey-50: "var(--primary-text-color)"
43 | paper-grey-200: "var(-gray)"
44 | google-red-500: "var(--orange)"
45 |
46 | label-badge-background-color: "var(--secondary-background-color)"
47 | label-badge-text-color: "var(--text-primary-color)"
48 |
49 | paper-item-icon-active-color: "var(--cyan)"
50 | paper-item-icon_-_color: "var(--primary-text-color)"
51 |
52 | sidebar-icon-color: "var(--light)"
53 | sidebar-text-color: "var(--light)"
54 | sidebar-selected-background-color: "var(--cyan)"
55 | sidebar-selected-icon-color: "var(--light)"
56 | sidebar-selected-text-color: "var(--light)"
57 | sidebar-text_-_background: "var(--light)"
58 |
59 | paper-progress-active-color: "var(--pink)"
60 | paper-progress-container-color: "var(--purple)"
61 | paper-slider-knob-color: "var(--teal)"
62 | paper-slider-knob-start-color: "var(--purple)"
63 |
64 | switch-unchecked-color: "var(--teal)"
65 | switch-unchecked-button-color: "var(--teal)"
66 |
67 | # Themable CCS vars in HACS (https://hacs.xyz/docs/basic/theming)
68 | link-text-color: "var(--cyan)"
69 | hacs-status-installed: "var(--teal)"
70 | hacs-status-pending-restart: "var(--indigo)"
71 | hacs-status-pending-update: "var(--purple)"
72 | hacs-status-not-loaded: "var(--indigo)"
73 |
74 | paper-dialog-button-color: "var(--light)"
75 | iron-icon-fill-color: "var(--light)"
76 | update-heading: "var(--light)"
77 |
--------------------------------------------------------------------------------
/update.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function get_file {
4 | DOWNLOAD_PATH=${2}?raw=true
5 | FILE_NAME=$1
6 | if [ "${FILE_NAME:0:1}" = "/" ]; then
7 | SAVE_PATH=$FILE_NAME
8 | else
9 | SAVE_PATH=$3$FILE_NAME
10 | fi
11 | TMP_NAME=${1}.tmp
12 | echo "Getting $1"
13 | # wget $DOWNLOAD_PATH -q -O $TMP_NAME
14 | curl -s -q -L -o $TMP_NAME $DOWNLOAD_PATH
15 | rv=$?
16 | if [ $rv != 0 ]; then
17 | rm $TMP_NAME
18 | echo "Download failed with error $rv"
19 | exit
20 | fi
21 | diff ${SAVE_PATH} $TMP_NAME &>/dev/null
22 | if [ $? == 0 ]; then
23 | echo "File up to date."
24 | rm $TMP_NAME
25 | return 0
26 | else
27 | mv $TMP_NAME ${SAVE_PATH}
28 | if [ $1 == $0 ]; then
29 | chmod u+x $0
30 | echo "Restarting"
31 | $0
32 | exit $?
33 | else
34 | return 1
35 | fi
36 | fi
37 | }
38 |
39 | function get_file_and_gz {
40 | get_file $1 $2 $3
41 | r1=$?
42 | get_file ${1}.gz ${2}.gz $3
43 | r2=$?
44 | if (( $r1 != 0 || $r2 != 0 )); then
45 | return 1
46 | fi
47 | return 0
48 | }
49 |
50 | function check_dir {
51 | if [ ! -d $1 ]; then
52 | read -p "$1 dir not found. Create? (y/n): [n] " r
53 | r=${r:-n}
54 | if [[ $r == 'y' || $r == 'Y' ]]; then
55 | mkdir -p $1
56 | else
57 | exit
58 | fi
59 | fi
60 | }
61 |
62 | if [ ! -f configuration.yaml ]; then
63 | echo "There is no configuration.yaml in current dir. 'update.sh' should run from Homeassistant config dir"
64 | read -p "Are you sure you want to continue? (y/n): [n] " r
65 | r=${r:-n}
66 | if [[ $r == 'n' || $r == 'N' ]]; then
67 | exit
68 | fi
69 | fi
70 |
71 | get_file $0 https://github.com/andrey-git/home-assistant-custom-ui/blob/master/update.sh ./
72 |
73 |
74 | check_dir "www/custom_ui"
75 |
76 |
77 | get_file scripts.js.map https://github.com/andrey-git/home-assistant-custom-ui/blob/master/scripts.js.map www/custom_ui/
78 | get_file scripts.js.LICENSE https://github.com/andrey-git/home-assistant-custom-ui/blob/master/scripts.js.LICENSE www/custom_ui/
79 | get_file scripts-es5.js.map https://github.com/andrey-git/home-assistant-custom-ui/blob/master/scripts-es5.js.map www/custom_ui/
80 | get_file scripts-es5.js.LICENSE https://github.com/andrey-git/home-assistant-custom-ui/blob/master/scripts-es5.js.LICENSE www/custom_ui/
81 | get_file_and_gz state-card-custom-ui-es5.html https://github.com/andrey-git/home-assistant-custom-ui/blob/master/state-card-custom-ui-es5.html www/custom_ui/
82 | get_file_and_gz state-card-custom-ui.html https://github.com/andrey-git/home-assistant-custom-ui/blob/master/state-card-custom-ui.html www/custom_ui/
83 |
84 |
85 | if [ $? != 0 ]; then
86 | echo "Updated to Custom UI `grep -o -e '"[0-9][0-9][0-9]*"' www/custom_ui/state-card-custom-ui.html`"
87 | fi
88 |
89 |
90 | check_dir "custom_components/customizer"
91 |
92 | get_file __init__.py https://github.com/andrey-git/home-assistant-customizer/blob/master/customizer/__init__.py custom_components/customizer/
93 | get_file services.yaml https://github.com/andrey-git/home-assistant-customizer/blob/master/customizer/services.yaml custom_components/customizer/
94 |
--------------------------------------------------------------------------------
/update_files.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | wget -O /config/www/custom_ui/card-modder.js https://raw.githubusercontent.com/thomasloven/lovelace-card-modder/master/card-modder.js
4 | sudo chown -R homeassistant:homeassistant /config/
5 |
--------------------------------------------------------------------------------
/www/assets/images/skullcanyon.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/assets/images/skullcanyon.jpeg
--------------------------------------------------------------------------------
/www/assets/images/vintage-colors-blur.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/assets/images/vintage-colors-blur.jpg
--------------------------------------------------------------------------------
/www/community/button-card/button-card.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/button-card/button-card.js.gz
--------------------------------------------------------------------------------
/www/community/config-template-card/config-template-card.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/config-template-card/config-template-card.js.gz
--------------------------------------------------------------------------------
/www/community/list-card/list-card.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/list-card/list-card.js.gz
--------------------------------------------------------------------------------
/www/community/lovelace-auto-entities/auto-entities.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/lovelace-auto-entities/auto-entities.js.gz
--------------------------------------------------------------------------------
/www/community/lovelace-card-mod/card-mod.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/lovelace-card-mod/card-mod.js.gz
--------------------------------------------------------------------------------
/www/community/lovelace-card-tools/card-tools.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/lovelace-card-tools/card-tools.js.gz
--------------------------------------------------------------------------------
/www/community/lovelace-hass-aarlo/hass-aarlo.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/lovelace-hass-aarlo/hass-aarlo.js.gz
--------------------------------------------------------------------------------
/www/community/lovelace-layout-card/layout-card.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/lovelace-layout-card/layout-card.js.gz
--------------------------------------------------------------------------------
/www/community/lovelace-slider-entity-row/slider-entity-row.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/lovelace-slider-entity-row/slider-entity-row.js.gz
--------------------------------------------------------------------------------
/www/community/mini-graph-card/mini-graph-card-bundle.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/mini-graph-card/mini-graph-card-bundle.js.gz
--------------------------------------------------------------------------------
/www/community/mini-media-player/mini-media-player-bundle.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/mini-media-player/mini-media-player-bundle.js.gz
--------------------------------------------------------------------------------
/www/community/pc-card/pc-card.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/pc-card/pc-card.js.gz
--------------------------------------------------------------------------------
/www/community/restriction-card/restriction-card.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/restriction-card/restriction-card.js.gz
--------------------------------------------------------------------------------
/www/community/secondaryinfo-entity-row/secondaryinfo-entity-row.js:
--------------------------------------------------------------------------------
1 | customElements.whenDefined('card-tools').then(() => {
2 |
3 | class SecondaryInfoEntityRow extends cardTools.LitElement {
4 | version() { return "0.5"; }
5 |
6 | render() {
7 | return cardTools.LitHtml`
8 | ${this._wrappedElement}
9 | `;
10 | }
11 |
12 | setConfig(config) {
13 | cardTools.checkVersion(2.0);
14 | this._config = config;
15 | this._wrappedElement = this._createElement(config);
16 | this.requestUpdate();
17 | }
18 |
19 | set hass(hass) {
20 | this._hass = hass;
21 | this._stateObj = this._config.entity in hass.states ? hass.states[this._config.entity] : null;
22 | this._updateElement(this._wrappedElement, hass);
23 | }
24 |
25 | _createElement(config) {
26 | // Override the custom row type in order to create the 'standard' row for this entity
27 | let defaultRowConfig = Object.assign({}, config);
28 | delete defaultRowConfig.type;
29 | const element = cardTools.createEntityRow(defaultRowConfig);
30 | return element;
31 | }
32 |
33 | async _updateElement(wrappedElement, hass) {
34 | if (!wrappedElement) return;
35 |
36 | this._wrappedElement.hass = hass;
37 | await this._wrappedElement.updateComplete;
38 | await this._wrappedElement.shadowRoot.querySelector("hui-generic-entity-row");
39 | let secondaryInfoDiv = this._wrappedElement.shadowRoot.querySelector("hui-generic-entity-row").shadowRoot.querySelector(".secondary");
40 | if (secondaryInfoDiv && this._config.secondary_info) {
41 | let text;
42 | if (this._config.secondary_info.includes('{{') || this._config.secondary_info.includes('{%')) {
43 | text = await window.cardTools.parseTemplate(hass, this._config.secondary_info, {entity: this._config.entity})
44 | } else {
45 | text = window.cardTools.parseTemplate(this._config.secondary_info, {entity: this._config.entity});
46 | }
47 | secondaryInfoDiv.innerHTML = text;
48 | }
49 | }
50 | }
51 | customElements.define('secondaryinfo-entity-row', SecondaryInfoEntityRow);
52 |
53 | });
54 |
55 | setTimeout(() => {
56 | if (customElements.get('card-tools')) return;
57 | customElements.define('secondaryinfo-entity-row', class extends HTMLElement {
58 | setConfig() {
59 | throw new Error("Can't find card-tools. See https://github.com/thomasloven/lovelace-card-tools");
60 | }
61 | });
62 | }, 2000);
63 |
--------------------------------------------------------------------------------
/www/community/secondaryinfo-entity-row/secondaryinfo-entity-row.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/secondaryinfo-entity-row/secondaryinfo-entity-row.js.gz
--------------------------------------------------------------------------------
/www/community/simple-thermostat/simple-thermostat.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/simple-thermostat/simple-thermostat.js.gz
--------------------------------------------------------------------------------
/www/community/unused-card/unused-card.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/www/community/unused-card/unused-card.js.gz
--------------------------------------------------------------------------------
/www/custom_ui/ext-weblink.js:
--------------------------------------------------------------------------------
1 | class ExtWebLink extends HTMLElement {
2 | set hass(hass) {
3 | if (!this.config.icon) {
4 | this.config.icon = "mdi:home-assistant";
5 | }
6 | if (!this.config.entity) {
7 | var state = "";
8 | } else {
9 | if (hass.states[this.config.entity].attributes.unit_of_measurement) {
10 | var state = hass.states[this.config.entity].state+' '+hass.states[this.config.entity].attributes.unit_of_measurement;
11 | } else {
12 | var state = hass.states[this.config.entity].state
13 | }
14 | }
15 | if (this.config.name) {
16 | var name = this.config.name;
17 | } else {
18 | var name = hass.states[this.config.entity].attributes.friendly_name;
19 | }
20 | if (!this.config.url) {
21 | this.config.url = "#";
22 | }
23 | this.innerHTML =`
24 |
58 |
59 |
60 |
61 |
${name}
62 |
63 |
${state}
64 |
65 |
66 | `;
67 | }
68 | setConfig(config) {
69 | this.config = config;
70 | }
71 |
72 | getCardSize() {
73 | return 1;
74 | }
75 | }
76 | customElements.define('ext-weblink', ExtWebLink);
--------------------------------------------------------------------------------
/www/custom_ui/gap-card.js:
--------------------------------------------------------------------------------
1 | class GapCard extends HTMLElement {
2 |
3 | setConfig(config) {
4 | this.height = ('height' in config) ? config.height : 50;
5 | this.size = ('size' in config) ? config.size : Math.ceil(this.height/50);
6 | this.style.setProperty('height', this.height + 'px');
7 | }
8 |
9 | getCardSize() {
10 | return this.size;
11 | }
12 | }
13 |
14 | customElements.define('gap-card', GapCard);
15 |
--------------------------------------------------------------------------------
/z-wave_relay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arsaboo/homeassistant-config/949a0ef28911f0bd3832e61365e5b58731915b4b/z-wave_relay.png
--------------------------------------------------------------------------------