diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..c8d7c7ee6b2 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,262 @@ +# Python CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-python/ for more details +# +version: 2.1 + +executors: + + python: + parameters: + tag: + type: string + default: latest + docker: + - image: circleci/python:<< parameters.tag >> + - image: circleci/buildpack-deps:stretch + working_directory: ~/repo + +commands: + + docker-prereqs: + description: Set up docker prerequisite requirement + steps: + - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends + libudev-dev libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev + libswscale-dev libswresample-dev libavfilter-dev + + install-requirements: + description: Set up venv and install requirements python packages with cache support + parameters: + python: + type: string + default: latest + all: + description: pip install -r requirements_all.txt + type: boolean + default: false + test: + description: pip install -r requirements_test.txt + type: boolean + default: false + test_all: + description: pip install -r requirements_test_all.txt + type: boolean + default: false + steps: + - restore_cache: + keys: + - v1-<< parameters.python >>-{{ checksum "homeassistant/package_constraints.txt" }}-<<# parameters.all >>{{ checksum "requirements_all.txt" }}<>-<<# parameters.test >>{{ checksum "requirements_test.txt" }}<>-<<# parameters.test_all >>{{ checksum "requirements_test_all.txt" }}<> + - run: + name: install dependencies + command: | + python3 -m venv venv + . venv/bin/activate + pip install -q -U pip + pip install -q -U setuptools + <<# parameters.all >>pip install -q --progress-bar off -r requirements_all.txt -c homeassistant/package_constraints.txt<> + <<# parameters.test >>pip install -q --progress-bar off -r requirements_test.txt -c homeassistant/package_constraints.txt<> + <<# parameters.test_all >>pip install -q --progress-bar off -r requirements_test_all.txt -c homeassistant/package_constraints.txt<> + - save_cache: + paths: + - ./venv + key: v1-<< parameters.python >>-{{ checksum "homeassistant/package_constraints.txt" }}-<<# parameters.all >>{{ checksum "requirements_all.txt" }}<>-<<# parameters.test >>{{ checksum "requirements_test.txt" }}<>-<<# parameters.test_all >>{{ checksum "requirements_test_all.txt" }}<> + + install: + description: Install Home Assistant + steps: + - run: + name: install + command: | + . venv/bin/activate + pip install -q --progress-bar off -e . + +jobs: + + static-check: + executor: + name: python + tag: 3.5.5-stretch + + steps: + - checkout + - docker-prereqs + - install-requirements: + python: 3.5.5-stretch + test: true + + - run: + name: run static check + command: | + . venv/bin/activate + flake8 + + - run: + name: run static type check + command: | + . venv/bin/activate + TYPING_FILES=$(cat mypyrc) + mypy $TYPING_FILES + + - install + - run: + name: run gen_requirements_all + command: | + . venv/bin/activate + python script/gen_requirements_all.py validate + + pre-install-all-requirements: + executor: + name: python + tag: 3.5.5-stretch + + steps: + - checkout + - docker-prereqs + - install-requirements: + python: 3.5.5-stretch + all: true + test: true + + pylint: + executor: + name: python + tag: 3.5.5-stretch + parallelism: 2 + + steps: + - checkout + - docker-prereqs + - install-requirements: + python: 3.5.5-stretch + all: true + test: true + - install + + - run: + name: run pylint + command: | + . venv/bin/activate + PYFILES=$(circleci tests glob "homeassistant/**/*.py" | circleci tests split) + pylint ${PYFILES} + + pre-test: + parameters: + python: + type: string + executor: + name: python + tag: << parameters.python >> + + steps: + - checkout + - docker-prereqs + - install-requirements: + python: << parameters.python >> + test_all: true + + test: + parameters: + python: + type: string + executor: + name: python + tag: << parameters.python >> + parallelism: 2 + + steps: + - checkout + - docker-prereqs + - install-requirements: + python: << parameters.python >> + test_all: true + - install + + - run: + name: run tests + command: | + . venv/bin/activate + TESTFILES=$(circleci tests glob "tests/**/test_*.py" | circleci tests split --split-by=timings) + if [ -z "$CODE_COVERAGE" ]; then CC_SWITCH=""; else CC_SWITCH="--cov --cov-report html:htmlcov"; fi + pytest --timeout=9 --duration=10 --junitxml=test-reports/homeassistant/results.xml -qq -o junit_family=xunit2 -o junit_suite_name=homeassistant -o console_output_style=count -p no:sugar $CC_SWITCH -- ${TESTFILES} + script/check_dirty + + - store_test_results: + path: test-reports + + - store_artifacts: + path: htmlcov + destination: cov-reports + + - store_artifacts: + path: test-reports + destination: test-reports + + # This job use machine executor, e.g. classic CircleCI VM because we need both lokalise-cli and a Python runtime. + # Classic CircleCI included python 2.7.12 and python 3.5.2 managed by pyenv, the Python version may need change if + # CircleCI changed its VM in future. + upload-translations: + machine: true + + steps: + - checkout + + - run: + name: upload english translations + command: | + pyenv versions + pyenv global 3.5.2 + docker pull lokalise/lokalise-cli@sha256:2198814ebddfda56ee041a4b427521757dd57f75415ea9693696a64c550cef21 + script/translations_upload + +workflows: + version: 2 + build: + jobs: + - static-check + - pre-install-all-requirements: + requires: + - static-check + - pylint: + requires: + - pre-install-all-requirements + - pre-test: + name: pre-test 3.5.5 + requires: + - static-check + python: 3.5.5-stretch + - pre-test: + name: pre-test 3.6 + requires: + - static-check + python: 3.6-stretch + - pre-test: + name: pre-test 3.7 + requires: + - static-check + python: 3.7-stretch + - test: + name: test 3.5.5 + requires: + - pre-test 3.5.5 + python: 3.5.5-stretch + - test: + name: test 3.6 + requires: + - pre-test 3.6 + python: 3.6-stretch + - test: + name: test 3.7 + requires: + - pre-test 3.7 + python: 3.7-stretch + # CircleCI does not allow failure yet + # - test: + # name: test 3.8 + # python: 3.8-rc-stretch + - upload-translations: + requires: + - static-check + filters: + branches: + only: dev diff --git a/.coveragerc b/.coveragerc index b7f2961f14d..3cba8519314 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,164 +3,165 @@ source = homeassistant omit = homeassistant/__main__.py + homeassistant/helpers/signal.py + homeassistant/helpers/typing.py + homeassistant/monkey_patch.py homeassistant/scripts/*.py homeassistant/util/async.py - homeassistant/monkey_patch.py - homeassistant/helpers/typing.py - homeassistant/helpers/signal.py # omit pieces of code that rely on external devices being present homeassistant/components/abode/* + homeassistant/components/acer_projector/switch.py + homeassistant/components/actiontec/device_tracker.py homeassistant/components/ads/* - homeassistant/components/air_quality/nilu.py - homeassistant/components/air_quality/norway_air.py - homeassistant/components/air_quality/opensensemap.py - homeassistant/components/alarm_control_panel/alarmdotcom.py - homeassistant/components/alarm_control_panel/canary.py - homeassistant/components/alarm_control_panel/concord232.py - homeassistant/components/alarm_control_panel/ialarm.py + homeassistant/components/aftership/sensor.py + homeassistant/components/airvisual/sensor.py + homeassistant/components/aladdin_connect/cover.py homeassistant/components/alarm_control_panel/manual_mqtt.py - homeassistant/components/alarm_control_panel/nx584.py - homeassistant/components/alarm_control_panel/totalconnect.py - homeassistant/components/alarm_control_panel/yale_smart_alarm.py homeassistant/components/alarmdecoder/* + homeassistant/components/alarmdotcom/alarm_control_panel.py + homeassistant/components/alpha_vantage/sensor.py homeassistant/components/ambient_station/* homeassistant/components/amcrest/* homeassistant/components/android_ip_webcam/* homeassistant/components/androidtv/* + homeassistant/components/anel_pwrctrl/switch.py + homeassistant/components/anthemav/media_player.py homeassistant/components/apcupsd/* homeassistant/components/apiai/* homeassistant/components/apple_tv/* homeassistant/components/aqualogic/* + homeassistant/components/aquostv/media_player.py homeassistant/components/arduino/* + homeassistant/components/arest/binary_sensor.py + homeassistant/components/arest/sensor.py + homeassistant/components/arest/switch.py homeassistant/components/arlo/* + homeassistant/components/aruba/device_tracker.py + homeassistant/components/arwn/sensor.py + homeassistant/components/asterisk_cdr/mailbox.py homeassistant/components/asterisk_mbox/* + homeassistant/components/asuswrt/device_tracker.py homeassistant/components/august/* - homeassistant/components/axis/* + homeassistant/components/automatic/device_tracker.py + homeassistant/components/avion/light.py + homeassistant/components/aws_lambda/notify.py + homeassistant/components/aws_sns/notify.py + homeassistant/components/aws_sqs/notify.py homeassistant/components/bbb_gpio/* - homeassistant/components/binary_sensor/arest.py - homeassistant/components/binary_sensor/concord232.py - homeassistant/components/binary_sensor/flic.py - homeassistant/components/binary_sensor/hikvision.py - homeassistant/components/binary_sensor/iss.py - homeassistant/components/binary_sensor/mystrom.py - homeassistant/components/binary_sensor/ping.py - homeassistant/components/binary_sensor/rest.py - homeassistant/components/binary_sensor/tapsaff.py - homeassistant/components/binary_sensor/uptimerobot.py + homeassistant/components/bbox/device_tracker.py + homeassistant/components/bbox/sensor.py + homeassistant/components/bh1750/sensor.py + homeassistant/components/bitcoin/sensor.py homeassistant/components/blink/* + homeassistant/components/blinksticklight/light.py + homeassistant/components/blinkt/light.py + homeassistant/components/blockchain/sensor.py homeassistant/components/bloomsky/* + homeassistant/components/bluesound/media_player.py + homeassistant/components/bluetooth_le_tracker/device_tracker.py + homeassistant/components/bluetooth_tracker/device_tracker.py + homeassistant/components/bme280/sensor.py + homeassistant/components/bme680/sensor.py homeassistant/components/bmw_connected_drive/* + homeassistant/components/bom/sensor.py + homeassistant/components/bom/weather.py + homeassistant/components/braviatv/media_player.py + homeassistant/components/broadlink/sensor.py + homeassistant/components/broadlink/switch.py + homeassistant/components/brottsplatskartan/sensor.py homeassistant/components/browser/* - homeassistant/components/calendar/caldav.py - homeassistant/components/calendar/todoist.py - homeassistant/components/camera/bloomsky.py - homeassistant/components/camera/canary.py - homeassistant/components/camera/familyhub.py - homeassistant/components/camera/ffmpeg.py - homeassistant/components/camera/foscam.py - homeassistant/components/camera/mjpeg.py - homeassistant/components/camera/onvif.py - homeassistant/components/camera/proxy.py - homeassistant/components/camera/ring.py - homeassistant/components/camera/rpi_camera.py - homeassistant/components/camera/synology.py - homeassistant/components/camera/xeoma.py - homeassistant/components/camera/xiaomi.py - homeassistant/components/camera/yi.py + homeassistant/components/brunt/cover.py + homeassistant/components/bt_home_hub_5/device_tracker.py + homeassistant/components/bt_smarthub/device_tracker.py + homeassistant/components/buienradar/sensor.py + homeassistant/components/buienradar/weather.py + homeassistant/components/caldav/calendar.py + homeassistant/components/canary/alarm_control_panel.py + homeassistant/components/canary/camera.py homeassistant/components/cast/* + homeassistant/components/cert_expiry/sensor.py + homeassistant/components/channels/media_player.py + homeassistant/components/cisco_ios/device_tracker.py homeassistant/components/cisco_mobility_express/device_tracker.py - homeassistant/components/climate/coolmaster.py - homeassistant/components/climate/ephember.py - homeassistant/components/climate/eq3btsmart.py - homeassistant/components/climate/flexit.py - homeassistant/components/climate/heatmiser.py - homeassistant/components/climate/homematic.py - homeassistant/components/climate/honeywell.py - homeassistant/components/climate/knx.py - homeassistant/components/climate/mill.py - homeassistant/components/climate/oem.py - homeassistant/components/climate/proliphix.py - homeassistant/components/climate/radiotherm.py - homeassistant/components/climate/sensibo.py - homeassistant/components/climate/touchline.py - homeassistant/components/climate/venstar.py - homeassistant/components/climate/zhong_hong.py + homeassistant/components/ciscospark/notify.py + homeassistant/components/citybikes/sensor.py + homeassistant/components/clementine/media_player.py + homeassistant/components/clickatell/notify.py + homeassistant/components/clicksend/notify.py + homeassistant/components/clicksend_tts/notify.py homeassistant/components/cloudflare/* + homeassistant/components/cmus/media_player.py homeassistant/components/coinbase/* + homeassistant/components/comed_hourly_pricing/sensor.py homeassistant/components/comfoconnect/* - homeassistant/components/cover/aladdin_connect.py - homeassistant/components/cover/brunt.py - homeassistant/components/cover/garadget.py - homeassistant/components/cover/gogogate2.py - homeassistant/components/cover/homematic.py - homeassistant/components/cover/knx.py - homeassistant/components/cover/myq.py - homeassistant/components/cover/opengarage.py - homeassistant/components/cover/rpi_gpio.py - homeassistant/components/cover/scsgate.py + homeassistant/components/concord232/alarm_control_panel.py + homeassistant/components/concord232/binary_sensor.py + homeassistant/components/coolmaster/climate.py + homeassistant/components/cppm_tracker/device_tracker.py + homeassistant/components/cpuspeed/sensor.py + homeassistant/components/crimereports/sensor.py + homeassistant/components/cups/sensor.py + homeassistant/components/currencylayer/sensor.py homeassistant/components/daikin/* homeassistant/components/danfoss_air/* - homeassistant/components/device_tracker/actiontec.py - homeassistant/components/device_tracker/aruba.py - homeassistant/components/device_tracker/asuswrt.py - homeassistant/components/device_tracker/automatic.py - homeassistant/components/device_tracker/bbox.py - homeassistant/components/device_tracker/bluetooth_le_tracker.py - homeassistant/components/device_tracker/bluetooth_tracker.py - homeassistant/components/device_tracker/bt_home_hub_5.py - homeassistant/components/device_tracker/bt_smarthub.py - homeassistant/components/device_tracker/cisco_ios.py - homeassistant/components/cppm_tracker/device_tracker.py - homeassistant/components/device_tracker/ddwrt.py - homeassistant/components/device_tracker/fritz.py - homeassistant/components/device_tracker/google_maps.py - homeassistant/components/device_tracker/hitron_coda.py - homeassistant/components/device_tracker/huawei_router.py - homeassistant/components/device_tracker/icloud.py - homeassistant/components/device_tracker/keenetic_ndms2.py - homeassistant/components/device_tracker/linksys_ap.py - homeassistant/components/device_tracker/linksys_smart.py - homeassistant/components/device_tracker/luci.py - homeassistant/components/device_tracker/mikrotik.py - homeassistant/components/device_tracker/netgear.py - homeassistant/components/device_tracker/nmap_tracker.py - homeassistant/components/device_tracker/ping.py - homeassistant/components/device_tracker/quantum_gateway.py - homeassistant/components/device_tracker/ritassist.py - homeassistant/components/device_tracker/sky_hub.py - homeassistant/components/device_tracker/snmp.py - homeassistant/components/device_tracker/swisscom.py - homeassistant/components/device_tracker/synology_srm.py - homeassistant/components/device_tracker/tado.py - homeassistant/components/device_tracker/thomson.py - homeassistant/components/device_tracker/tile.py - homeassistant/components/device_tracker/tomato.py - homeassistant/components/device_tracker/tplink.py - homeassistant/components/device_tracker/traccar.py - homeassistant/components/device_tracker/trackr.py - homeassistant/components/device_tracker/ubee.py - homeassistant/components/device_tracker/ubus.py - homeassistant/components/device_tracker/xfinity.py + homeassistant/components/darksky/weather.py + homeassistant/components/ddwrt/device_tracker.py + homeassistant/components/decora/light.py + homeassistant/components/decora_wifi/light.py + homeassistant/components/deluge/sensor.py + homeassistant/components/deluge/switch.py + homeassistant/components/denon/media_player.py + homeassistant/components/denonavr/media_player.py + homeassistant/components/deutsche_bahn/sensor.py + homeassistant/components/dht/sensor.py homeassistant/components/digital_ocean/* + homeassistant/components/digitalloggers/switch.py + homeassistant/components/directv/media_player.py + homeassistant/components/discogs/sensor.py + homeassistant/components/discord/notify.py + homeassistant/components/dlib_face_detect/image_processing.py + homeassistant/components/dlib_face_identify/image_processing.py + homeassistant/components/dlink/switch.py + homeassistant/components/dlna_dmr/media_player.py + homeassistant/components/dnsip/sensor.py + homeassistant/components/domain_expiry/sensor.py homeassistant/components/dominos/* homeassistant/components/doorbird/* homeassistant/components/dovado/* homeassistant/components/downloader/* + homeassistant/components/dte_energy_bridge/sensor.py + homeassistant/components/dublin_bus_transport/sensor.py + homeassistant/components/duke_energy/sensor.py + homeassistant/components/dunehd/media_player.py + homeassistant/components/dwd_weather_warnings/sensor.py homeassistant/components/dweet/* + homeassistant/components/ebox/sensor.py homeassistant/components/ebusd/* homeassistant/components/ecoal_boiler/* homeassistant/components/ecobee/* + homeassistant/components/econet/water_heater.py homeassistant/components/ecovacs/* + homeassistant/components/eddystone_temperature/sensor.py + homeassistant/components/edimax/switch.py homeassistant/components/edp_redy/* homeassistant/components/egardia/* homeassistant/components/eight_sleep/* + homeassistant/components/eliqonline/sensor.py homeassistant/components/elkm1/* + homeassistant/components/emby/media_player.py + homeassistant/components/emoncms/sensor.py homeassistant/components/emoncms_history/* homeassistant/components/emulated_hue/upnp.py homeassistant/components/enigma2/media_player.py homeassistant/components/enocean/* + homeassistant/components/enphase_envoy/sensor.py + homeassistant/components/entur_public_transport/* + homeassistant/components/envirophat/sensor.py homeassistant/components/envisalink/* + homeassistant/components/ephember/climate.py + homeassistant/components/epson/media_player.py + homeassistant/components/eq3btsmart/climate.py homeassistant/components/esphome/__init__.py homeassistant/components/esphome/binary_sensor.py homeassistant/components/esphome/cover.py @@ -168,231 +169,298 @@ omit = homeassistant/components/esphome/light.py homeassistant/components/esphome/sensor.py homeassistant/components/esphome/switch.py + homeassistant/components/etherscan/sensor.py homeassistant/components/eufy/* + homeassistant/components/everlights/light.py homeassistant/components/evohome/* - homeassistant/components/fan/wemo.py + homeassistant/components/familyhub/camera.py homeassistant/components/fastdotcom/* + homeassistant/components/fedex/sensor.py + homeassistant/components/ffmpeg/camera.py homeassistant/components/fibaro/* + homeassistant/components/filesize/sensor.py + homeassistant/components/fints/sensor.py + homeassistant/components/fitbit/sensor.py + homeassistant/components/fixer/sensor.py + homeassistant/components/flexit/climate.py + homeassistant/components/flic/binary_sensor.py + homeassistant/components/flock/notify.py + homeassistant/components/flunearyou/sensor.py + homeassistant/components/flux_led/light.py + homeassistant/components/folder/sensor.py homeassistant/components/folder_watcher/* + homeassistant/components/foobot/sensor.py + homeassistant/components/foscam/camera.py homeassistant/components/foursquare/* + homeassistant/components/free_mobile/notify.py homeassistant/components/freebox/* + homeassistant/components/fritz/device_tracker.py homeassistant/components/fritzbox/* + homeassistant/components/fritzbox_callmonitor/sensor.py + homeassistant/components/fritzbox_netmonitor/sensor.py + homeassistant/components/fritzdect/switch.py + homeassistant/components/frontier_silicon/media_player.py + homeassistant/components/futurenow/light.py + homeassistant/components/garadget/cover.py homeassistant/components/gc100/* + homeassistant/components/gearbest/sensor.py + homeassistant/components/geizhals/sensor.py + homeassistant/components/github/sensor.py + homeassistant/components/gitlab_ci/sensor.py + homeassistant/components/gitter/sensor.py + homeassistant/components/glances/sensor.py + homeassistant/components/gntp/notify.py homeassistant/components/goalfeed/* + homeassistant/components/gogogate2/cover.py homeassistant/components/google/* + homeassistant/components/google_maps/device_tracker.py + homeassistant/components/google_travel_time/sensor.py homeassistant/components/googlehome/* + homeassistant/components/gpmdp/media_player.py + homeassistant/components/gpsd/sensor.py homeassistant/components/greeneye_monitor/* + homeassistant/components/greeneye_monitor/sensor.py + homeassistant/components/greenwave/light.py + homeassistant/components/group/notify.py + homeassistant/components/gstreamer/media_player.py + homeassistant/components/gtfs/sensor.py + homeassistant/components/gtt/sensor.py homeassistant/components/habitica/* - homeassistant/components/hangouts/__init__.py homeassistant/components/hangouts/* + homeassistant/components/hangouts/__init__.py homeassistant/components/hangouts/const.py homeassistant/components/hangouts/hangouts_bot.py homeassistant/components/hangouts/hangups_utils.py + homeassistant/components/harman_kardon_avr/media_player.py + homeassistant/components/harmony/remote.py + homeassistant/components/haveibeenpwned/sensor.py homeassistant/components/hdmi_cec/* + homeassistant/components/heatmiser/climate.py + homeassistant/components/hikvision/binary_sensor.py + homeassistant/components/hikvisioncam/switch.py + homeassistant/components/hipchat/notify.py + homeassistant/components/hitron_coda/device_tracker.py homeassistant/components/hive/* homeassistant/components/hlk_sw16/* homeassistant/components/homekit_controller/* homeassistant/components/homematic/* + homeassistant/components/homematic/climate.py + homeassistant/components/homematic/cover.py + homeassistant/components/homematic/notify.py homeassistant/components/homematicip_cloud/* homeassistant/components/homeworks/* + homeassistant/components/honeywell/climate.py + homeassistant/components/hook/switch.py + homeassistant/components/horizon/media_player.py + homeassistant/components/hp_ilo/sensor.py + homeassistant/components/htu21d/sensor.py homeassistant/components/huawei_lte/* + homeassistant/components/huawei_router/device_tracker.py + homeassistant/components/hue/light.py + homeassistant/components/hunterdouglas_powerview/scene.py homeassistant/components/hydrawise/* + homeassistant/components/hyperion/light.py + homeassistant/components/ialarm/alarm_control_panel.py + homeassistant/components/icloud/device_tracker.py homeassistant/components/idteck_prox/* homeassistant/components/ifttt/* + homeassistant/components/iglo/light.py homeassistant/components/ihc/* - homeassistant/components/image_processing/dlib_face_detect.py - homeassistant/components/image_processing/dlib_face_identify.py - homeassistant/components/image_processing/qrcode.py - homeassistant/components/image_processing/seven_segments.py - homeassistant/components/image_processing/tensorflow.py + homeassistant/components/iliad_italy/sensor.py + homeassistant/components/imap/sensor.py + homeassistant/components/imap_email_content/sensor.py + homeassistant/components/influxdb/sensor.py + homeassistant/components/insteon/* homeassistant/components/insteon_local/* homeassistant/components/insteon_plm/* - homeassistant/components/insteon/* homeassistant/components/ios/* homeassistant/components/iota/* homeassistant/components/iperf3/* + homeassistant/components/irish_rail_transport/sensor.py + homeassistant/components/iss/binary_sensor.py homeassistant/components/isy994/* + homeassistant/components/itach/remote.py + homeassistant/components/itunes/media_player.py homeassistant/components/joaoapps_join/* homeassistant/components/juicenet/* - homeassistant/components/keyboard_remote/* + homeassistant/components/kankun/switch.py + homeassistant/components/keenetic_ndms2/device_tracker.py homeassistant/components/keyboard/* + homeassistant/components/keyboard_remote/* homeassistant/components/kira/* + homeassistant/components/kiwi/lock.py homeassistant/components/knx/* + homeassistant/components/knx/climate.py + homeassistant/components/knx/cover.py + homeassistant/components/kodi/media_player.py + homeassistant/components/kodi/notify.py homeassistant/components/konnected/* + homeassistant/components/kwb/sensor.py + homeassistant/components/lacrosse/sensor.py homeassistant/components/lametric/* + homeassistant/components/lannouncer/notify.py + homeassistant/components/lastfm/sensor.py + homeassistant/components/launch_library/sensor.py homeassistant/components/lcn/* + homeassistant/components/lg_netcast/media_player.py + homeassistant/components/lg_soundbar/media_player.py homeassistant/components/lifx/* - homeassistant/components/light/avion.py - homeassistant/components/light/blinksticklight.py - homeassistant/components/light/blinkt.py - homeassistant/components/light/decora_wifi.py - homeassistant/components/light/decora.py - homeassistant/components/light/everlights.py - homeassistant/components/light/flux_led.py - homeassistant/components/light/futurenow.py - homeassistant/components/light/greenwave.py - homeassistant/components/light/hue.py - homeassistant/components/light/hyperion.py - homeassistant/components/light/iglo.py - homeassistant/components/light/lifx_legacy.py - homeassistant/components/light/limitlessled.py - homeassistant/components/light/lw12wifi.py - homeassistant/components/light/mystrom.py - homeassistant/components/light/nanoleaf.py - homeassistant/components/light/niko_home_control.py - homeassistant/components/light/opple.py - homeassistant/components/light/osramlightify.py - homeassistant/components/light/piglow.py - homeassistant/components/light/rpi_gpio_pwm.py - homeassistant/components/light/sensehat.py - homeassistant/components/light/tikteck.py - homeassistant/components/light/tplink.py - homeassistant/components/light/tradfri.py - homeassistant/components/light/x10.py - homeassistant/components/light/yeelight.py - homeassistant/components/light/yeelightsunflower.py - homeassistant/components/light/zengge.py + homeassistant/components/lifx_cloud/scene.py + homeassistant/components/lifx_legacy/light.py homeassistant/components/lightwave/* + homeassistant/components/limitlessled/light.py + homeassistant/components/linksys_ap/device_tracker.py + homeassistant/components/linksys_smart/device_tracker.py + homeassistant/components/linky/sensor.py homeassistant/components/linode/* + homeassistant/components/linux_battery/sensor.py homeassistant/components/lirc/* - homeassistant/components/lock/kiwi.py - homeassistant/components/lock/lockitron.py - homeassistant/components/lock/nello.py - homeassistant/components/lock/nuki.py - homeassistant/components/lock/sesame.py + homeassistant/components/liveboxplaytv/media_player.py + homeassistant/components/llamalab_automate/notify.py + homeassistant/components/lockitron/lock.py homeassistant/components/logi_circle/* + homeassistant/components/london_underground/sensor.py + homeassistant/components/loopenergy/sensor.py + homeassistant/components/luci/device_tracker.py homeassistant/components/luftdaten/* homeassistant/components/lupusec/* - homeassistant/components/lutron_caseta/* homeassistant/components/lutron/* - homeassistant/components/mailbox/asterisk_cdr.py + homeassistant/components/lutron_caseta/* + homeassistant/components/lw12wifi/light.py + homeassistant/components/lyft/sensor.py + homeassistant/components/magicseaweed/sensor.py homeassistant/components/mailgun/notify.py homeassistant/components/map/* + homeassistant/components/mastodon/notify.py homeassistant/components/matrix/* homeassistant/components/maxcube/* homeassistant/components/media_extractor/* - homeassistant/components/media_player/anthemav.py - homeassistant/components/media_player/aquostv.py - homeassistant/components/media_player/bluesound.py - homeassistant/components/media_player/braviatv.py - homeassistant/components/media_player/channels.py - homeassistant/components/media_player/clementine.py - homeassistant/components/media_player/cmus.py - homeassistant/components/media_player/denon.py - homeassistant/components/media_player/denonavr.py - homeassistant/components/media_player/directv.py - homeassistant/components/media_player/dlna_dmr.py - homeassistant/components/media_player/dunehd.py - homeassistant/components/media_player/emby.py - homeassistant/components/media_player/epson.py - homeassistant/components/media_player/frontier_silicon.py - homeassistant/components/media_player/gpmdp.py - homeassistant/components/media_player/gstreamer.py - homeassistant/components/media_player/harman_kardon_avr.py - homeassistant/components/media_player/horizon.py - homeassistant/components/media_player/itunes.py - homeassistant/components/media_player/kodi.py - homeassistant/components/media_player/lg_netcast.py - homeassistant/components/media_player/lg_soundbar.py - homeassistant/components/media_player/liveboxplaytv.py - homeassistant/components/media_player/mediaroom.py - homeassistant/components/media_player/mpchc.py - homeassistant/components/media_player/mpd.py - homeassistant/components/media_player/nad.py - homeassistant/components/media_player/nadtcp.py - homeassistant/components/media_player/onkyo.py - homeassistant/components/media_player/openhome.py - homeassistant/components/media_player/panasonic_bluray.py - homeassistant/components/media_player/panasonic_viera.py - homeassistant/components/media_player/pandora.py - homeassistant/components/media_player/philips_js.py - homeassistant/components/media_player/pioneer.py - homeassistant/components/media_player/pjlink.py - homeassistant/components/media_player/plex.py - homeassistant/components/media_player/russound_rio.py - homeassistant/components/media_player/russound_rnet.py - homeassistant/components/media_player/snapcast.py - homeassistant/components/media_player/songpal.py - homeassistant/components/media_player/spotify.py - homeassistant/components/media_player/squeezebox.py - homeassistant/components/media_player/ue_smart_radio.py - homeassistant/components/media_player/vizio.py - homeassistant/components/media_player/vlc.py - homeassistant/components/media_player/volumio.py - homeassistant/components/media_player/xiaomi_tv.py - homeassistant/components/media_player/yamaha_musiccast.py - homeassistant/components/media_player/yamaha.py - homeassistant/components/media_player/ziggo_mediabox_xl.py + homeassistant/components/mediaroom/media_player.py + homeassistant/components/message_bird/notify.py + homeassistant/components/met/weather.py homeassistant/components/meteo_france/* + homeassistant/components/metoffice/sensor.py + homeassistant/components/metoffice/weather.py + homeassistant/components/miflora/sensor.py + homeassistant/components/mikrotik/device_tracker.py + homeassistant/components/mill/climate.py + homeassistant/components/mitemp_bt/sensor.py + homeassistant/components/mjpeg/camera.py homeassistant/components/mobile_app/* homeassistant/components/mochad/* homeassistant/components/modbus/* + homeassistant/components/modem_callerid/sensor.py + homeassistant/components/mopar/* + homeassistant/components/mpchc/media_player.py + homeassistant/components/mpd/media_player.py + homeassistant/components/mqtt_room/sensor.py + homeassistant/components/mvglive/sensor.py homeassistant/components/mychevy/* homeassistant/components/mycroft/* + homeassistant/components/mycroft/notify.py + homeassistant/components/myq/cover.py homeassistant/components/mysensors/* + homeassistant/components/mystrom/binary_sensor.py + homeassistant/components/mystrom/light.py + homeassistant/components/mystrom/switch.py + homeassistant/components/nad/media_player.py + homeassistant/components/nadtcp/media_player.py + homeassistant/components/nanoleaf/light.py homeassistant/components/neato/* + homeassistant/components/nederlandse_spoorwegen/sensor.py + homeassistant/components/nello/lock.py homeassistant/components/nest/* homeassistant/components/netatmo/* + homeassistant/components/netatmo_public/sensor.py + homeassistant/components/netdata/sensor.py + homeassistant/components/netdata_public/sensor.py + homeassistant/components/netgear/device_tracker.py homeassistant/components/netgear_lte/* + homeassistant/components/netio/switch.py + homeassistant/components/neurio_energy/sensor.py + homeassistant/components/nfandroidtv/notify.py + homeassistant/components/niko_home_control/light.py + homeassistant/components/nilu/air_quality.py homeassistant/components/nissan_leaf/* - homeassistant/components/notify/aws_lambda.py - homeassistant/components/notify/aws_sns.py - homeassistant/components/notify/aws_sqs.py - homeassistant/components/notify/ciscospark.py - homeassistant/components/notify/clickatell.py - homeassistant/components/notify/clicksend_tts.py - homeassistant/components/notify/clicksend.py - homeassistant/components/notify/discord.py - homeassistant/components/notify/flock.py - homeassistant/components/notify/free_mobile.py - homeassistant/components/notify/gntp.py - homeassistant/components/notify/group.py - homeassistant/components/notify/hipchat.py - homeassistant/components/notify/homematic.py - homeassistant/components/notify/kodi.py - homeassistant/components/notify/lannouncer.py - homeassistant/components/notify/llamalab_automate.py - homeassistant/components/notify/mastodon.py - homeassistant/components/notify/message_bird.py - homeassistant/components/notify/mycroft.py - homeassistant/components/notify/nfandroidtv.py - homeassistant/components/notify/prowl.py - homeassistant/components/notify/pushbullet.py - homeassistant/components/notify/pushetta.py - homeassistant/components/notify/pushover.py - homeassistant/components/notify/pushsafer.py - homeassistant/components/notify/rest.py - homeassistant/components/notify/rocketchat.py - homeassistant/components/notify/sendgrid.py - homeassistant/components/notify/simplepush.py - homeassistant/components/notify/slack.py - homeassistant/components/notify/smtp.py - homeassistant/components/notify/stride.py - homeassistant/components/notify/synology_chat.py - homeassistant/components/notify/syslog.py - homeassistant/components/notify/telegram.py - homeassistant/components/notify/telstra.py - homeassistant/components/notify/twilio_call.py - homeassistant/components/notify/twilio_sms.py - homeassistant/components/notify/twitter.py - homeassistant/components/notify/xmpp.py + homeassistant/components/nmap_tracker/device_tracker.py + homeassistant/components/nmbs/sensor.py + homeassistant/components/noaa_tides/sensor.py + homeassistant/components/norway_air/air_quality.py + homeassistant/components/nsw_fuel_station/sensor.py homeassistant/components/nuimo_controller/* + homeassistant/components/nuki/lock.py + homeassistant/components/nut/sensor.py + homeassistant/components/nx584/alarm_control_panel.py + homeassistant/components/nzbget/sensor.py homeassistant/components/octoprint/* + homeassistant/components/oem/climate.py + homeassistant/components/ohmconnect/sensor.py + homeassistant/components/onewire/sensor.py + homeassistant/components/onkyo/media_player.py + homeassistant/components/onvif/camera.py homeassistant/components/opencv/* + homeassistant/components/openevse/sensor.py + homeassistant/components/openexchangerates/sensor.py + homeassistant/components/opengarage/cover.py + homeassistant/components/openhome/media_player.py + homeassistant/components/opensensemap/air_quality.py + homeassistant/components/opensky/sensor.py homeassistant/components/opentherm_gw/* homeassistant/components/openuv/__init__.py homeassistant/components/openuv/binary_sensor.py homeassistant/components/openuv/sensor.py + homeassistant/components/openweathermap/sensor.py + homeassistant/components/openweathermap/weather.py + homeassistant/components/opple/light.py + homeassistant/components/orvibo/switch.py + homeassistant/components/osramlightify/light.py + homeassistant/components/otp/sensor.py homeassistant/components/owlet/* + homeassistant/components/panasonic_bluray/media_player.py + homeassistant/components/panasonic_viera/media_player.py + homeassistant/components/pandora/media_player.py + homeassistant/components/pencom/switch.py + homeassistant/components/philips_js/media_player.py + homeassistant/components/pi_hole/sensor.py + homeassistant/components/piglow/light.py homeassistant/components/pilight/* + homeassistant/components/ping/binary_sensor.py + homeassistant/components/ping/device_tracker.py + homeassistant/components/pioneer/media_player.py + homeassistant/components/pjlink/media_player.py + homeassistant/components/plex/media_player.py + homeassistant/components/plex/sensor.py homeassistant/components/plum_lightpad/* + homeassistant/components/pocketcasts/sensor.py homeassistant/components/point/* + homeassistant/components/pollen/sensor.py + homeassistant/components/postnl/sensor.py + homeassistant/components/prezzibenzina/sensor.py + homeassistant/components/proliphix/climate.py homeassistant/components/prometheus/* + homeassistant/components/prowl/notify.py + homeassistant/components/proxy/camera.py homeassistant/components/ps4/__init__.py homeassistant/components/ps4/media_player.py + homeassistant/components/pulseaudio_loopback/switch.py + homeassistant/components/pushbullet/notify.py + homeassistant/components/pushbullet/sensor.py + homeassistant/components/pushetta/notify.py + homeassistant/components/pushover/notify.py + homeassistant/components/pushsafer/notify.py + homeassistant/components/pvoutput/sensor.py + homeassistant/components/pyload/sensor.py + homeassistant/components/qbittorrent/sensor.py + homeassistant/components/qnap/sensor.py + homeassistant/components/qrcode/image_processing.py + homeassistant/components/quantum_gateway/device_tracker.py homeassistant/components/qwikswitch/* homeassistant/components/rachio/* + homeassistant/components/radarr/sensor.py + homeassistant/components/radiotherm/climate.py homeassistant/components/rainbird/* + homeassistant/components/rainbird/sensor.py + homeassistant/components/rainbird/switch.py homeassistant/components/raincloud/* homeassistant/components/rainmachine/__init__.py homeassistant/components/rainmachine/binary_sensor.py @@ -400,281 +468,210 @@ omit = homeassistant/components/rainmachine/switch.py homeassistant/components/raspihats/* homeassistant/components/raspyrfm/* + homeassistant/components/recollect_waste/sensor.py + homeassistant/components/recswitch/switch.py homeassistant/components/reddit/* + homeassistant/components/rejseplanen/sensor.py homeassistant/components/remember_the_milk/__init__.py - homeassistant/components/remote/harmony.py - homeassistant/components/remote/itach.py + homeassistant/components/rest/binary_sensor.py + homeassistant/components/rest/notify.py + homeassistant/components/rest/switch.py homeassistant/components/rfxtrx/* + homeassistant/components/ring/camera.py + homeassistant/components/ripple/sensor.py + homeassistant/components/ritassist/device_tracker.py + homeassistant/components/rocketchat/notify.py homeassistant/components/roku/* + homeassistant/components/roomba/vacuum.py homeassistant/components/route53/* + homeassistant/components/rova/sensor.py + homeassistant/components/rpi_camera/camera.py homeassistant/components/rpi_gpio/* + homeassistant/components/rpi_gpio/cover.py + homeassistant/components/rpi_gpio_pwm/light.py homeassistant/components/rpi_pfio/* + homeassistant/components/rpi_rf/switch.py + homeassistant/components/rtorrent/sensor.py + homeassistant/components/russound_rio/media_player.py + homeassistant/components/russound_rnet/media_player.py + homeassistant/components/ruter/sensor.py homeassistant/components/sabnzbd/* homeassistant/components/satel_integra/* - homeassistant/components/scene/hunterdouglas_powerview.py - homeassistant/components/scene/lifx_cloud.py + homeassistant/components/scrape/sensor.py homeassistant/components/scsgate/* + homeassistant/components/scsgate/cover.py + homeassistant/components/sendgrid/notify.py homeassistant/components/sense/* - homeassistant/components/sensor/aftership.py - homeassistant/components/sensor/airvisual.py - homeassistant/components/sensor/alpha_vantage.py - homeassistant/components/sensor/arest.py - homeassistant/components/sensor/arwn.py - homeassistant/components/sensor/bbox.py - homeassistant/components/sensor/bh1750.py - homeassistant/components/sensor/bitcoin.py - homeassistant/components/sensor/blockchain.py - homeassistant/components/sensor/bme280.py - homeassistant/components/sensor/bme680.py - homeassistant/components/sensor/bom.py - homeassistant/components/sensor/broadlink.py - homeassistant/components/sensor/brottsplatskartan.py - homeassistant/components/sensor/buienradar.py - homeassistant/components/sensor/cert_expiry.py - homeassistant/components/sensor/citybikes.py - homeassistant/components/sensor/coinbase.py - homeassistant/components/sensor/comed_hourly_pricing.py - homeassistant/components/sensor/cpuspeed.py - homeassistant/components/sensor/crimereports.py - homeassistant/components/sensor/cups.py - homeassistant/components/sensor/currencylayer.py - homeassistant/components/sensor/deluge.py - homeassistant/components/sensor/deutsche_bahn.py - homeassistant/components/sensor/dht.py - homeassistant/components/sensor/discogs.py - homeassistant/components/sensor/dnsip.py - homeassistant/components/sensor/domain_expiry.py - homeassistant/components/sensor/dte_energy_bridge.py - homeassistant/components/sensor/dublin_bus_transport.py - homeassistant/components/sensor/duke_energy.py - homeassistant/components/sensor/dwd_weather_warnings.py - homeassistant/components/sensor/ebox.py - homeassistant/components/sensor/eddystone_temperature.py - homeassistant/components/sensor/eliqonline.py - homeassistant/components/sensor/emoncms.py - homeassistant/components/sensor/enphase_envoy.py - homeassistant/components/sensor/envirophat.py - homeassistant/components/sensor/etherscan.py - homeassistant/components/sensor/fedex.py - homeassistant/components/sensor/filesize.py - homeassistant/components/sensor/fints.py - homeassistant/components/sensor/fitbit.py - homeassistant/components/sensor/fixer.py - homeassistant/components/sensor/flunearyou.py - homeassistant/components/sensor/folder.py - homeassistant/components/sensor/foobot.py - homeassistant/components/sensor/fritzbox_callmonitor.py - homeassistant/components/sensor/fritzbox_netmonitor.py - homeassistant/components/sensor/gearbest.py - homeassistant/components/sensor/geizhals.py - homeassistant/components/sensor/github.py - homeassistant/components/sensor/gitlab_ci.py - homeassistant/components/sensor/gitter.py - homeassistant/components/sensor/glances.py - homeassistant/components/sensor/google_travel_time.py - homeassistant/components/sensor/gpsd.py - homeassistant/components/sensor/greeneye_monitor.py - homeassistant/components/sensor/gtfs.py - homeassistant/components/sensor/gtt.py - homeassistant/components/sensor/haveibeenpwned.py - homeassistant/components/sensor/hp_ilo.py - homeassistant/components/sensor/htu21d.py - homeassistant/components/sensor/iliad_italy.py - homeassistant/components/sensor/imap_email_content.py - homeassistant/components/sensor/imap.py - homeassistant/components/sensor/influxdb.py - homeassistant/components/sensor/irish_rail_transport.py - homeassistant/components/sensor/kwb.py - homeassistant/components/sensor/lacrosse.py - homeassistant/components/sensor/lastfm.py - homeassistant/components/sensor/launch_library.py - homeassistant/components/sensor/linky.py - homeassistant/components/sensor/linux_battery.py - homeassistant/components/sensor/london_underground.py - homeassistant/components/sensor/loopenergy.py - homeassistant/components/sensor/lyft.py - homeassistant/components/sensor/magicseaweed.py - homeassistant/components/sensor/metoffice.py - homeassistant/components/sensor/miflora.py - homeassistant/components/sensor/mitemp_bt.py - homeassistant/components/sensor/modem_callerid.py - homeassistant/components/sensor/mopar.py - homeassistant/components/sensor/mqtt_room.py - homeassistant/components/sensor/mvglive.py - homeassistant/components/sensor/nederlandse_spoorwegen.py - homeassistant/components/sensor/netatmo_public.py - homeassistant/components/sensor/netdata_public.py - homeassistant/components/sensor/netdata.py - homeassistant/components/sensor/neurio_energy.py - homeassistant/components/sensor/nmbs.py - homeassistant/components/sensor/noaa_tides.py - homeassistant/components/sensor/nsw_fuel_station.py - homeassistant/components/sensor/nut.py - homeassistant/components/sensor/nzbget.py - homeassistant/components/sensor/ohmconnect.py - homeassistant/components/sensor/onewire.py - homeassistant/components/sensor/openevse.py - homeassistant/components/sensor/openexchangerates.py - homeassistant/components/sensor/opensky.py - homeassistant/components/sensor/openweathermap.py - homeassistant/components/sensor/otp.py - homeassistant/components/sensor/pi_hole.py - homeassistant/components/sensor/plex.py - homeassistant/components/sensor/pocketcasts.py - homeassistant/components/sensor/pollen.py - homeassistant/components/sensor/postnl.py - homeassistant/components/sensor/prezzibenzina.py - homeassistant/components/sensor/pushbullet.py - homeassistant/components/sensor/pvoutput.py - homeassistant/components/sensor/pyload.py - homeassistant/components/sensor/qbittorrent.py - homeassistant/components/sensor/qnap.py - homeassistant/components/sensor/radarr.py - homeassistant/components/sensor/rainbird.py - homeassistant/components/sensor/recollect_waste.py - homeassistant/components/sensor/rejseplanen.py - homeassistant/components/sensor/ripple.py - homeassistant/components/sensor/rova.py - homeassistant/components/sensor/rtorrent.py - homeassistant/components/sensor/ruter.py - homeassistant/components/sensor/scrape.py - homeassistant/components/sensor/sensehat.py - homeassistant/components/sensor/serial_pm.py - homeassistant/components/sensor/serial.py - homeassistant/components/sensor/seventeentrack.py - homeassistant/components/sensor/shodan.py - homeassistant/components/sensor/sht31.py - homeassistant/components/sensor/sigfox.py - homeassistant/components/sensor/simulated.py - homeassistant/components/sensor/skybeacon.py - homeassistant/components/sensor/sma.py - homeassistant/components/sensor/snmp.py - homeassistant/components/sensor/sochain.py - homeassistant/components/sensor/socialblade.py - homeassistant/components/sensor/solaredge.py - homeassistant/components/sensor/sonarr.py - homeassistant/components/sensor/spotcrime.py - homeassistant/components/sensor/srp_energy.py - homeassistant/components/sensor/starlingbank.py - homeassistant/components/sensor/steam_online.py - homeassistant/components/sensor/supervisord.py - homeassistant/components/sensor/swiss_hydrological_data.py - homeassistant/components/sensor/swiss_public_transport.py - homeassistant/components/sensor/syncthru.py - homeassistant/components/sensor/synologydsm.py - homeassistant/components/sensor/systemmonitor.py - homeassistant/components/sensor/sytadin.py - homeassistant/components/sensor/tank_utility.py - homeassistant/components/sensor/tautulli.py - homeassistant/components/sensor/ted5000.py - homeassistant/components/sensor/temper.py - homeassistant/components/sensor/thermoworks_smoke.py - homeassistant/components/sensor/time_date.py - homeassistant/components/sensor/torque.py - homeassistant/components/sensor/trafikverket_weatherstation.py - homeassistant/components/sensor/travisci.py - homeassistant/components/sensor/twitch.py - homeassistant/components/sensor/uber.py - homeassistant/components/sensor/ups.py - homeassistant/components/sensor/uscis.py - homeassistant/components/sensor/vasttrafik.py - homeassistant/components/sensor/viaggiatreno.py - homeassistant/components/sensor/volkszaehler.py - homeassistant/components/sensor/waqi.py - homeassistant/components/sensor/waze_travel_time.py - homeassistant/components/sensor/whois.py - homeassistant/components/sensor/worldtidesinfo.py - homeassistant/components/sensor/worxlandroid.py - homeassistant/components/sensor/xbox_live.py - homeassistant/components/sensor/zamg.py - homeassistant/components/sensor/zestimate.py + homeassistant/components/sensehat/light.py + homeassistant/components/sensehat/sensor.py + homeassistant/components/sensibo/climate.py + homeassistant/components/serial/sensor.py + homeassistant/components/serial_pm/sensor.py + homeassistant/components/sesame/lock.py + homeassistant/components/seven_segments/image_processing.py + homeassistant/components/seventeentrack/sensor.py homeassistant/components/shiftr/* + homeassistant/components/shodan/sensor.py + homeassistant/components/sht31/sensor.py + homeassistant/components/sigfox/sensor.py + homeassistant/components/simplepush/notify.py homeassistant/components/simplisafe/__init__.py homeassistant/components/simplisafe/alarm_control_panel.py + homeassistant/components/simulated/sensor.py homeassistant/components/sisyphus/* + homeassistant/components/sky_hub/device_tracker.py + homeassistant/components/skybeacon/sensor.py homeassistant/components/skybell/* + homeassistant/components/slack/notify.py + homeassistant/components/sma/sensor.py homeassistant/components/smappee/* + homeassistant/components/smtp/notify.py + homeassistant/components/snapcast/media_player.py + homeassistant/components/snmp/device_tracker.py + homeassistant/components/snmp/sensor.py + homeassistant/components/snmp/switch.py + homeassistant/components/sochain/sensor.py + homeassistant/components/socialblade/sensor.py + homeassistant/components/solaredge/sensor.py + homeassistant/components/sonarr/sensor.py + homeassistant/components/songpal/media_player.py homeassistant/components/sonos/* + homeassistant/components/sony_projector/switch.py homeassistant/components/spc/* homeassistant/components/speedtestdotnet/* homeassistant/components/spider/* - homeassistant/components/switch/acer_projector.py - homeassistant/components/switch/anel_pwrctrl.py - homeassistant/components/switch/arest.py - homeassistant/components/switch/broadlink.py - homeassistant/components/switch/deluge.py - homeassistant/components/switch/digitalloggers.py - homeassistant/components/switch/dlink.py - homeassistant/components/switch/edimax.py - homeassistant/components/switch/fritzdect.py - homeassistant/components/switch/hikvisioncam.py - homeassistant/components/switch/hook.py - homeassistant/components/switch/kankun.py - homeassistant/components/switch/mystrom.py - homeassistant/components/switch/netio.py - homeassistant/components/switch/orvibo.py - homeassistant/components/switch/pencom.py - homeassistant/components/switch/pulseaudio_loopback.py - homeassistant/components/switch/rainbird.py - homeassistant/components/switch/recswitch.py - homeassistant/components/switch/rest.py - homeassistant/components/switch/rpi_rf.py - homeassistant/components/switch/snmp.py - homeassistant/components/switch/sony_projector.py - homeassistant/components/switch/switchbot.py - homeassistant/components/switch/switchmate.py - homeassistant/components/switch/telnet.py - homeassistant/components/switch/tplink.py - homeassistant/components/switch/vesync.py + homeassistant/components/spotcrime/sensor.py + homeassistant/components/spotify/media_player.py + homeassistant/components/squeezebox/media_player.py + homeassistant/components/srp_energy/sensor.py + homeassistant/components/starlingbank/sensor.py + homeassistant/components/steam_online/sensor.py + homeassistant/components/stride/notify.py + homeassistant/components/supervisord/sensor.py + homeassistant/components/swiss_hydrological_data/sensor.py + homeassistant/components/swiss_public_transport/sensor.py + homeassistant/components/swisscom/device_tracker.py + homeassistant/components/switchbot/switch.py + homeassistant/components/switchmate/switch.py + homeassistant/components/syncthru/sensor.py + homeassistant/components/synology/camera.py + homeassistant/components/synology_chat/notify.py + homeassistant/components/synology_srm/device_tracker.py + homeassistant/components/synologydsm/sensor.py + homeassistant/components/syslog/notify.py + homeassistant/components/systemmonitor/sensor.py + homeassistant/components/sytadin/sensor.py homeassistant/components/tado/* + homeassistant/components/tado/device_tracker.py homeassistant/components/tahoma/* + homeassistant/components/tank_utility/sensor.py + homeassistant/components/tapsaff/binary_sensor.py + homeassistant/components/tautulli/sensor.py + homeassistant/components/ted5000/sensor.py + homeassistant/components/telegram/notify.py homeassistant/components/telegram_bot/* homeassistant/components/tellduslive/* homeassistant/components/tellstick/* + homeassistant/components/telnet/switch.py + homeassistant/components/telstra/notify.py + homeassistant/components/temper/sensor.py + homeassistant/components/tensorflow/image_processing.py homeassistant/components/tesla/* + homeassistant/components/tfiac/climate.py + homeassistant/components/thermoworks_smoke/sensor.py homeassistant/components/thethingsnetwork/* homeassistant/components/thingspeak/* homeassistant/components/thinkingcleaner/* + homeassistant/components/thomson/device_tracker.py homeassistant/components/tibber/* + homeassistant/components/tikteck/light.py + homeassistant/components/tile/device_tracker.py + homeassistant/components/time_date/sensor.py + homeassistant/components/todoist/calendar.py homeassistant/components/tof/sensor.py + homeassistant/components/tomato/device_tracker.py homeassistant/components/toon/* + homeassistant/components/torque/sensor.py + homeassistant/components/totalconnect/alarm_control_panel.py + homeassistant/components/touchline/climate.py + homeassistant/components/tplink/device_tracker.py + homeassistant/components/tplink/light.py + homeassistant/components/tplink/switch.py homeassistant/components/tplink_lte/* + homeassistant/components/traccar/device_tracker.py + homeassistant/components/trackr/device_tracker.py homeassistant/components/tradfri/* + homeassistant/components/tradfri/light.py + homeassistant/components/trafikverket_weatherstation/sensor.py homeassistant/components/transmission/* + homeassistant/components/travisci/sensor.py homeassistant/components/tts/amazon_polly.py homeassistant/components/tts/baidu.py homeassistant/components/tts/microsoft.py homeassistant/components/tts/picotts.py homeassistant/components/tuya/* + homeassistant/components/twilio_call/notify.py + homeassistant/components/twilio_sms/notify.py + homeassistant/components/twitch/sensor.py + homeassistant/components/twitter/notify.py + homeassistant/components/ubee/device_tracker.py + homeassistant/components/uber/sensor.py + homeassistant/components/ubus/device_tracker.py + homeassistant/components/ue_smart_radio/media_player.py homeassistant/components/upcloud/* homeassistant/components/upnp/* + homeassistant/components/ups/sensor.py + homeassistant/components/uptimerobot/binary_sensor.py + homeassistant/components/uscis/sensor.py homeassistant/components/usps/* - homeassistant/components/vacuum/roomba.py + homeassistant/components/vasttrafik/sensor.py homeassistant/components/velbus/* homeassistant/components/velux/* + homeassistant/components/venstar/climate.py homeassistant/components/vera/* homeassistant/components/verisure/* + homeassistant/components/vesync/switch.py + homeassistant/components/viaggiatreno/sensor.py + homeassistant/components/vizio/media_player.py + homeassistant/components/vlc/media_player.py + homeassistant/components/volkszaehler/sensor.py + homeassistant/components/volumio/media_player.py homeassistant/components/volvooncall/* homeassistant/components/w800rf32/* - homeassistant/components/water_heater/econet.py + homeassistant/components/waqi/sensor.py homeassistant/components/waterfurnace/* homeassistant/components/watson_iot/* - homeassistant/components/weather/bom.py - homeassistant/components/weather/buienradar.py - homeassistant/components/weather/darksky.py - homeassistant/components/weather/met.py - homeassistant/components/weather/metoffice.py - homeassistant/components/weather/openweathermap.py - homeassistant/components/weather/zamg.py + homeassistant/components/waze_travel_time/sensor.py homeassistant/components/webostv/* homeassistant/components/wemo/* + homeassistant/components/wemo/fan.py + homeassistant/components/whois/sensor.py homeassistant/components/wink/* homeassistant/components/wirelesstag/* + homeassistant/components/worldtidesinfo/sensor.py + homeassistant/components/worxlandroid/sensor.py + homeassistant/components/x10/light.py + homeassistant/components/xbox_live/sensor.py + homeassistant/components/xeoma/camera.py + homeassistant/components/xfinity/device_tracker.py + homeassistant/components/xiaomi/camera.py homeassistant/components/xiaomi_aqara/* homeassistant/components/xiaomi_miio/* + homeassistant/components/xiaomi_tv/media_player.py + homeassistant/components/xmpp/notify.py homeassistant/components/xs1/* + homeassistant/components/yale_smart_alarm/alarm_control_panel.py + homeassistant/components/yamaha/media_player.py + homeassistant/components/yamaha_musiccast/media_player.py + homeassistant/components/yeelight/light.py + homeassistant/components/yeelightsunflower/light.py + homeassistant/components/yi/camera.py homeassistant/components/zabbix/* + homeassistant/components/zamg/sensor.py + homeassistant/components/zamg/weather.py + homeassistant/components/zengge/light.py homeassistant/components/zeroconf/* + homeassistant/components/zestimate/sensor.py homeassistant/components/zha/__init__.py homeassistant/components/zha/api.py homeassistant/components/zha/const.py @@ -687,7 +684,9 @@ omit = homeassistant/components/zha/entity.py homeassistant/components/zha/light.py homeassistant/components/zha/sensor.py + homeassistant/components/zhong_hong/climate.py homeassistant/components/zigbee/* + homeassistant/components/ziggo_mediabox_xl/media_player.py homeassistant/components/zoneminder/* homeassistant/components/zwave/util.py diff --git a/.gitignore b/.gitignore index 91b8d024aed..b486032c741 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ pip-log.txt .tox nosetests.xml htmlcov/ +test-reports/ # Translations *.mo diff --git a/CODEOWNERS b/CODEOWNERS index a795c4c3151..e9d7a652a66 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -18,7 +18,7 @@ homeassistant/components/frontend/* @home-assistant/core homeassistant/components/group/* @home-assistant/core homeassistant/components/history/* @home-assistant/core homeassistant/components/http/* @home-assistant/core -homeassistant/components/input_*.py @home-assistant/core +homeassistant/components/input_*/* @home-assistant/core homeassistant/components/introduction/* @home-assistant/core homeassistant/components/logger/* @home-assistant/core homeassistant/components/lovelace/* @home-assistant/core @@ -47,123 +47,58 @@ homeassistant/components/*/zwave.py @home-assistant/z-wave homeassistant/components/hassio/* @home-assistant/hassio # Individual platforms -homeassistant/components/alarm_control_panel/manual_mqtt.py @colinodell -homeassistant/components/binary_sensor/hikvision.py @mezz64 -homeassistant/components/binary_sensor/threshold.py @fabaff -homeassistant/components/binary_sensor/uptimerobot.py @ludeeus -homeassistant/components/camera/push.py @dgomes -homeassistant/components/camera/yi.py @bachya -homeassistant/components/climate/coolmaster.py @OnFreund -homeassistant/components/climate/ephember.py @ttroy50 -homeassistant/components/climate/eq3btsmart.py @rytilahti -homeassistant/components/climate/mill.py @danielhiversen -homeassistant/components/climate/sensibo.py @andrey-git -homeassistant/components/cover/brunt.py @eavanvalkenburg -homeassistant/components/cover/group.py @cdce8p -homeassistant/components/cover/template.py @PhracturedBlue -homeassistant/components/device_tracker/asuswrt.py @kennedyshead -homeassistant/components/device_tracker/automatic.py @armills -homeassistant/components/device_tracker/bt_smarthub.py @jxwolstenholme -homeassistant/components/device_tracker/huawei_router.py @abmantis -homeassistant/components/device_tracker/quantum_gateway.py @cisasteelersfan -homeassistant/components/device_tracker/tile.py @bachya -homeassistant/components/device_tracker/traccar.py @ludeeus -homeassistant/components/device_tracker/synology_srm.py @aerialls -homeassistant/components/device_tracker/xfinity.py @cisasteelersfan -homeassistant/components/light/lifx_legacy.py @amelchio -homeassistant/components/light/yeelight.py @rytilahti -homeassistant/components/light/yeelightsunflower.py @lindsaymarkward -homeassistant/components/lock/nello.py @pschmitt -homeassistant/components/lock/nuki.py @pschmitt -homeassistant/components/media_player/emby.py @mezz64 -homeassistant/components/media_player/kodi.py @armills -homeassistant/components/media_player/liveboxplaytv.py @pschmitt -homeassistant/components/media_player/mediaroom.py @dgomes -homeassistant/components/media_player/monoprice.py @etsinko -homeassistant/components/media_player/mpd.py @fabaff -homeassistant/components/media_player/xiaomi_tv.py @fattdev -homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth +homeassistant/components/notify/aws_lambda.py @robbiet480 +homeassistant/components/notify/aws_sns.py @robbiet480 +homeassistant/components/notify/aws_sqs.py @robbiet480 homeassistant/components/notify/file.py @fabaff homeassistant/components/notify/flock.py @fabaff +homeassistant/components/notify/gntp.py @robbiet480 +homeassistant/components/notify/html5.py @robbiet480 homeassistant/components/notify/mastodon.py @fabaff homeassistant/components/notify/smtp.py @fabaff homeassistant/components/notify/syslog.py @fabaff +homeassistant/components/notify/twilio_call.py @robbiet480 +homeassistant/components/notify/twilio_sms.py @robbiet480 homeassistant/components/notify/xmpp.py @fabaff homeassistant/components/notify/yessssms.py @flowolf -homeassistant/components/scene/lifx_cloud.py @amelchio -homeassistant/components/sensor/airvisual.py @bachya -homeassistant/components/sensor/alpha_vantage.py @fabaff -homeassistant/components/sensor/bitcoin.py @fabaff -homeassistant/components/sensor/cpuspeed.py @fabaff -homeassistant/components/sensor/cups.py @fabaff -homeassistant/components/sensor/darksky.py @fabaff -homeassistant/components/sensor/discogs.py @thibmaek -homeassistant/components/sensor/file.py @fabaff -homeassistant/components/sensor/filter.py @dgomes -homeassistant/components/sensor/fixer.py @fabaff -homeassistant/components/sensor/flunearyou.py @bachya -homeassistant/components/sensor/gearbest.py @HerrHofrat -homeassistant/components/sensor/gitter.py @fabaff -homeassistant/components/sensor/glances.py @fabaff -homeassistant/components/sensor/gpsd.py @fabaff -homeassistant/components/sensor/integration.py @dgomes -homeassistant/components/sensor/irish_rail_transport.py @ttroy50 -homeassistant/components/sensor/jewish_calendar.py @tsvi -homeassistant/components/sensor/launch_library.py @ludeeus -homeassistant/components/sensor/linux_battery.py @fabaff -homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel -homeassistant/components/sensor/min_max.py @fabaff -homeassistant/components/sensor/moon.py @fabaff -homeassistant/components/sensor/netdata.py @fabaff -homeassistant/components/sensor/nmbs.py @thibmaek -homeassistant/components/sensor/nsw_fuel_station.py @nickw444 -homeassistant/components/sensor/pi_hole.py @fabaff -homeassistant/components/sensor/pollen.py @bachya -homeassistant/components/sensor/pvoutput.py @fabaff -homeassistant/components/sensor/qnap.py @colinodell -homeassistant/components/sensor/ruter.py @ludeeus -homeassistant/components/sensor/scrape.py @fabaff -homeassistant/components/sensor/serial.py @fabaff -homeassistant/components/sensor/seventeentrack.py @bachya -homeassistant/components/sensor/shodan.py @fabaff -homeassistant/components/sensor/sma.py @kellerza -homeassistant/components/sensor/sql.py @dgomes -homeassistant/components/sensor/statistics.py @fabaff -homeassistant/components/sensor/swiss*.py @fabaff -homeassistant/components/sensor/sytadin.py @gautric -homeassistant/components/sensor/tautulli.py @ludeeus -homeassistant/components/sensor/time_date.py @fabaff -homeassistant/components/sensor/version.py @fabaff -homeassistant/components/sensor/waqi.py @andrey-git -homeassistant/components/sensor/worldclock.py @fabaff -homeassistant/components/switch/switchbot.py @danielhiversen -homeassistant/components/switch/switchmate.py @danielhiversen -homeassistant/components/vacuum/roomba.py @pschmitt -homeassistant/components/weather/__init__.py @fabaff -homeassistant/components/weather/darksky.py @fabaff -homeassistant/components/weather/demo.py @fabaff -homeassistant/components/weather/met.py @danielhiversen -homeassistant/components/weather/openweathermap.py @fabaff +homeassistant/components/tts/amazon_polly.py @robbiet480 # A +homeassistant/components/airvisual/sensor.py @bachya +homeassistant/components/alarm_control_panel/manual_mqtt.py @colinodell +homeassistant/components/alpha_vantage/sensor.py @fabaff homeassistant/components/ambient_station/* @bachya homeassistant/components/arduino/* @fabaff +homeassistant/components/arest/* @fabaff +homeassistant/components/asuswrt/device_tracker.py @kennedyshead +homeassistant/components/automatic/device_tracker.py @armills homeassistant/components/axis/* @kane610 -homeassistant/components/*/arest.py @fabaff # B +homeassistant/components/bitcoin/sensor.py @fabaff homeassistant/components/blink/* @fronzbot homeassistant/components/bmw_connected_drive/* @ChristianKuehnel -homeassistant/components/*/broadlink.py @danielhiversen +homeassistant/components/braviatv/media_player.py @robbiet480 +homeassistant/components/broadlink/* @danielhiversen +homeassistant/components/brunt/cover.py @eavanvalkenburg +homeassistant/components/bt_smarthub/device_tracker.py @jxwolstenholme # C homeassistant/components/cloudflare/* @ludeeus +homeassistant/components/coolmaster/climate.py @OnFreund homeassistant/components/counter/* @fabaff +homeassistant/components/cover/group.py @cdce8p +homeassistant/components/cpuspeed/sensor.py @fabaff +homeassistant/components/cups/sensor.py @fabaff # D homeassistant/components/daikin/* @fredrike @rofrantz +homeassistant/components/darksky/* @fabaff +homeassistant/components/discogs/sensor.py @thibmaek homeassistant/components/deconz/* @kane610 +homeassistant/components/demo/weather.py @fabaff homeassistant/components/digital_ocean/* @fabaff +homeassistant/components/doorbird/* @oblogic7 homeassistant/components/dweet/* @fabaff # E @@ -171,94 +106,185 @@ homeassistant/components/ecovacs/* @OverloadUT homeassistant/components/edp_redy/* @abmantis homeassistant/components/eight_sleep/* @mezz64 homeassistant/components/egardia/* @jeroenterheerdt -homeassistant/components/esphome/*.py @OttoWinter +homeassistant/components/emby/media_player.py @mezz64 +homeassistant/components/ephember/climate.py @ttroy50 +homeassistant/components/eq3btsmart/climate.py @rytilahti +homeassistant/components/esphome/* @OttoWinter # F -homeassistant/components/freebox/*.py @snoof85 +homeassistant/components/file/sensor.py @fabaff +homeassistant/components/filter/sensor.py @dgomes +homeassistant/components/fitbit/sensor.py @robbiet480 +homeassistant/components/fixer/sensor.py @fabaff +homeassistant/components/flunearyou/sensor.py @bachya +homeassistant/components/foursquare/* @robbiet480 +homeassistant/components/freebox/* @snoof85 # G +homeassistant/components/gearbest/sensor.py @HerrHofrat +homeassistant/components/gitter/sensor.py @fabaff +homeassistant/components/glances/sensor.py @fabaff +homeassistant/components/google_travel_time/sensor.py @robbiet480 homeassistant/components/googlehome/* @ludeeus +homeassistant/components/gpsd/sensor.py @fabaff +homeassistant/components/gtfs/sensor.py @robbiet480 # H homeassistant/components/harmony/* @ehendrix23 +homeassistant/components/hikvision/binary_sensor.py @mezz64 homeassistant/components/history_graph/* @andrey-git homeassistant/components/hive/* @Rendili @KJonline homeassistant/components/homekit/* @cdce8p homeassistant/components/huawei_lte/* @scop +homeassistant/components/huawei_router/device_tracker.py @abmantis # I -homeassistant/components/influx/* @fabaff +homeassistant/components/influxdb/* @fabaff +homeassistant/components/integration/sensor.py @dgomes +homeassistant/components/ios/* @robbiet480 homeassistant/components/ipma/* @dgomes +homeassistant/components/irish_rail_transport/sensor.py @ttroy50 + +# J +homeassistant/components/jewish_calendar/sensor.py @tsvi # K homeassistant/components/knx/* @Julius2342 +homeassistant/components/kodi/media_player.py @armills homeassistant/components/konnected/* @heythisisnate # L +homeassistant/components/lametric/notify.py @robbiet480 +homeassistant/components/launch_library/sensor.py @ludeeus homeassistant/components/lifx/* @amelchio +homeassistant/components/lifx_cloud/scene.py @amelchio +homeassistant/components/lifx_legacy/light.py @amelchio +homeassistant/components/linux_battery/sensor.py @fabaff +homeassistant/components/liveboxplaytv/media_player.py @pschmitt homeassistant/components/luftdaten/* @fabaff # M homeassistant/components/matrix/* @tinloaf +homeassistant/components/mediaroom/media_player.py @dgomes homeassistant/components/melissa/* @kennedyshead -homeassistant/components/*/melissa.py @kennedyshead -homeassistant/components/*/mystrom.py @fabaff +homeassistant/components/met/weather.py @danielhiversen +homeassistant/components/miflora/sensor.py @danielhiversen @ChristianKuehnel +homeassistant/components/mill/climate.py @danielhiversen +homeassistant/components/min_max/sensor.py @fabaff +homeassistant/components/mobile_app/* @robbiet480 +homeassistant/components/monoprice/media_player.py @etsinko +homeassistant/components/moon/sensor.py @fabaff +homeassistant/components/mpd/media_player.py @fabaff +homeassistant/components/mystrom/* @fabaff # N +homeassistant/components/nello/lock.py @pschmitt homeassistant/components/ness_alarm/* @nickw444 -homeassistant/components/*/ness_alarm.py @nickw444 +homeassistant/components/netdata/sensor.py @fabaff homeassistant/components/nissan_leaf/* @filcole +homeassistant/components/nmbs/sensor.py @thibmaek homeassistant/components/no_ip/* @fabaff +homeassistant/components/nuki/lock.py @pschmitt +homeassistant/components/nsw_fuel_station/sensor.py @nickw444 # O +homeassistant/components/ohmconnect/sensor.py @robbiet480 homeassistant/components/openuv/* @bachya +homeassistant/components/openweathermap/weather.py @fabaff +homeassistant/components/owlet/* @oblogic7 # P +homeassistant/components/pi_hole/sensor.py @fabaff homeassistant/components/plant/* @ChristianKuehnel homeassistant/components/point/* @fredrike +homeassistant/components/pollen/sensor.py @bachya +homeassistant/components/push/camera.py @dgomes +homeassistant/components/pvoutput/sensor.py @fabaff # Q +homeassistant/components/qnap/sensor.py @colinodell +homeassistant/components/quantum_gateway/device_tracker.py @cisasteelersfan homeassistant/components/qwikswitch/* @kellerza # R homeassistant/components/rainmachine/* @bachya +homeassistant/components/random/* @fabaff homeassistant/components/rfxtrx/* @danielhiversen -homeassistant/components/*/random.py @fabaff +homeassistant/components/rmvtransport/* @cgtobi +homeassistant/components/roomba/vacuum.py @pschmitt +homeassistant/components/ruter/sensor.py @ludeeus # S +homeassistant/components/scrape/sensor.py @fabaff +homeassistant/components/sensibo/climate.py @andrey-git +homeassistant/components/serial/sensor.py @fabaff +homeassistant/components/seventeentrack/sensor.py @bachya homeassistant/components/shiftr/* @fabaff +homeassistant/components/shodan/sensor.py @fabaff homeassistant/components/simplisafe/* @bachya +homeassistant/components/sma/sensor.py @kellerza homeassistant/components/smartthings/* @andrewsayre homeassistant/components/sonos/* @amelchio homeassistant/components/spaceapi/* @fabaff homeassistant/components/spider/* @peternijssen +homeassistant/components/sql/sensor.py @dgomes +homeassistant/components/statistics/sensor.py @fabaff +homeassistant/components/swiss_*/* @fabaff +homeassistant/components/switchbot/switch.py @danielhiversen +homeassistant/components/switchmate/switch.py @danielhiversen +homeassistant/components/synology_srm/device_tracker.py @aerialls +homeassistant/components/sytadin/sensor.py @gautric # T homeassistant/components/tahoma/* @philklei -homeassistant/components/tellduslive/*.py @fredrike +homeassistant/components/tautulli/sensor.py @ludeeus +homeassistant/components/tellduslive/* @fredrike +homeassistant/components/template/cover.py @PhracturedBlue homeassistant/components/tesla/* @zabuldon +homeassistant/components/tfiac/* @fredrike @mellado homeassistant/components/thethingsnetwork/* @fabaff +homeassistant/components/threshold/binary_sensor.py @fabaff homeassistant/components/tibber/* @danielhiversen -homeassistant/components/tplink/* @rytilahti -homeassistant/components/tradfri/* @ggravlingen +homeassistant/components/tile/device_tracker.py @bachya +homeassistant/components/time_date/sensor.py @fabaff homeassistant/components/toon/* @frenck +homeassistant/components/tplink/* @rytilahti +homeassistant/components/traccar/device_tracker.py @ludeeus +homeassistant/components/tradfri/* @ggravlingen # U +homeassistant/components/uber/sensor.py @robbiet480 homeassistant/components/unifi/* @kane610 homeassistant/components/upcloud/* @scop +homeassistant/components/upnp/* @robbiet480 +homeassistant/components/uptimerobot/binary_sensor.py @ludeeus homeassistant/components/utility_meter/* @dgomes # V homeassistant/components/velux/* @Julius2342 +homeassistant/components/version/sensor.py @fabaff # W +homeassistant/components/waqi/sensor.py @andrey-git +homeassistant/components/weather/__init__.py @fabaff homeassistant/components/wemo/* @sqldiablo +homeassistant/components/worldclock/sensor.py @fabaff # X +homeassistant/components/xfinity/device_tracker.py @cisasteelersfan homeassistant/components/xiaomi_aqara/* @danielhiversen @syssi homeassistant/components/xiaomi_miio/* @rytilahti @syssi +homeassistant/components/xiaomi_tv/media_player.py @fattdev + +# Y +homeassistant/components/yamaha_musiccast/* @jalmeroth +homeassistant/components/yeelight/* @rytilahti @zewelor +homeassistant/components/yeelightsunflower/light.py @lindsaymarkward +homeassistant/components/yi/camera.py @bachya # Z +homeassistant/components/zeroconf/* @robbiet480 +homeassistant/components/zha/* @dmulcahey @adminiuga homeassistant/components/zoneminder/* @rohankapoorcom # Other code diff --git a/README.rst b/README.rst index f231d6c5514..941a463fb37 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -Home Assistant |Build Status| |Coverage Status| |Chat Status| +Home Assistant |Build Status| |CI Status| |Coverage Status| |Chat Status| ================================================================================= Home Assistant is a home automation platform running on Python 3. It is able to track and control all devices at home and offer a platform for automating control. @@ -27,8 +27,10 @@ components `__ of our website for further help and information. -.. |Build Status| image:: https://travis-ci.org/home-assistant/home-assistant.svg?branch=master +.. |Build Status| image:: https://travis-ci.org/home-assistant/home-assistant.svg?branch=dev :target: https://travis-ci.org/home-assistant/home-assistant +.. |CI Status| image:: https://circleci.com/gh/home-assistant/home-assistant.svg?style=shield + :target: https://circleci.com/gh/home-assistant/home-assistant .. |Coverage Status| image:: https://img.shields.io/coveralls/home-assistant/home-assistant.svg :target: https://coveralls.io/r/home-assistant/home-assistant?branch=master .. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py index d0bc45c326a..e8161a2bfb6 100644 --- a/homeassistant/auth/providers/trusted_networks.py +++ b/homeassistant/auth/providers/trusted_networks.py @@ -18,8 +18,26 @@ from ..models import Credentials, UserMeta IPAddress = Union[IPv4Address, IPv6Address] IPNetwork = Union[IPv4Network, IPv6Network] +CONF_TRUSTED_NETWORKS = 'trusted_networks' +CONF_TRUSTED_USERS = 'trusted_users' +CONF_GROUP = 'group' +CONF_ALLOW_BYPASS_LOGIN = 'allow_bypass_login' + CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({ - vol.Required('trusted_networks'): vol.All(cv.ensure_list, [ip_network]) + vol.Required(CONF_TRUSTED_NETWORKS): vol.All( + cv.ensure_list, [ip_network] + ), + vol.Optional(CONF_TRUSTED_USERS, default={}): vol.Schema( + # we only validate the format of user_id or group_id + {ip_network: vol.All( + cv.ensure_list, + [vol.Or( + cv.uuid4_hex, + vol.Schema({vol.Required(CONF_GROUP): cv.uuid4_hex}), + )], + )} + ), + vol.Optional(CONF_ALLOW_BYPASS_LOGIN, default=False): cv.boolean, }, extra=vol.PREVENT_EXTRA) @@ -43,7 +61,12 @@ class TrustedNetworksAuthProvider(AuthProvider): @property def trusted_networks(self) -> List[IPNetwork]: """Return trusted networks.""" - return cast(List[IPNetwork], self.config['trusted_networks']) + return cast(List[IPNetwork], self.config[CONF_TRUSTED_NETWORKS]) + + @property + def trusted_users(self) -> Dict[IPNetwork, Any]: + """Return trusted users per network.""" + return cast(Dict[IPNetwork, Any], self.config[CONF_TRUSTED_USERS]) @property def support_mfa(self) -> bool: @@ -53,13 +76,34 @@ class TrustedNetworksAuthProvider(AuthProvider): async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: """Return a flow to login.""" assert context is not None + ip_addr = cast(IPAddress, context.get('ip_address')) users = await self.store.async_get_users() - available_users = {user.id: user.name - for user in users - if not user.system_generated and user.is_active} + available_users = [user for user in users + if not user.system_generated and user.is_active] + for ip_net, user_or_group_list in self.trusted_users.items(): + if ip_addr in ip_net: + user_list = [user_id for user_id in user_or_group_list + if isinstance(user_id, str)] + group_list = [group[CONF_GROUP] for group in user_or_group_list + if isinstance(group, dict)] + flattened_group_list = [group for sublist in group_list + for group in sublist] + available_users = [ + user for user in available_users + if (user.id in user_list or + any([group.id in flattened_group_list + for group in user.groups])) + ] + break return TrustedNetworksLoginFlow( - self, cast(IPAddress, context.get('ip_address')), available_users) + self, + ip_addr, + { + user.id: user.name for user in available_users + }, + self.config[CONF_ALLOW_BYPASS_LOGIN], + ) async def async_get_or_create_credentials( self, flow_result: Dict[str, str]) -> Credentials: @@ -109,11 +153,13 @@ class TrustedNetworksLoginFlow(LoginFlow): def __init__(self, auth_provider: TrustedNetworksAuthProvider, ip_addr: IPAddress, - available_users: Dict[str, Optional[str]]) -> None: + available_users: Dict[str, Optional[str]], + allow_bypass_login: bool) -> None: """Initialize the login flow.""" super().__init__(auth_provider) self._available_users = available_users self._ip_address = ip_addr + self._allow_bypass_login = allow_bypass_login async def async_step_init( self, user_input: Optional[Dict[str, str]] = None) \ @@ -131,6 +177,11 @@ class TrustedNetworksLoginFlow(LoginFlow): if user_input is not None: return await self.async_finish(user_input) + if self._allow_bypass_login and len(self._available_users) == 1: + return await self.async_finish({ + 'user': next(iter(self._available_users.keys())) + }) + return self.async_show_form( step_id='init', data_schema=vol.Schema({'user': vol.In(self._available_users)}), diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index d532d9cdb86..435ec317985 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -1,4 +1,5 @@ """Provide methods to bootstrap a Home Assistant instance.""" +import asyncio import logging import logging.handlers import os @@ -9,10 +10,10 @@ from typing import Any, Optional, Dict, Set import voluptuous as vol -from homeassistant import ( - core, config as conf_util, config_entries, components as core_components, - loader) -from homeassistant.components import persistent_notification +from homeassistant import core, config as conf_util, config_entries, loader +from homeassistant.components import ( + persistent_notification, homeassistant as core_component +) from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE from homeassistant.setup import async_setup_component from homeassistant.util.logging import AsyncHandler @@ -139,7 +140,7 @@ async def async_from_config_dict(config: Dict[str, Any], pass # setup components - res = await core_components.async_setup(hass, config) + res = await core_component.async_setup(hass, config) if not res: _LOGGER.error("Home Assistant core failed to initialize. " "Further initialization aborted") @@ -157,6 +158,12 @@ async def async_from_config_dict(config: Dict[str, Any], await hass.async_block_till_done() + # Kick off loading the registries. They don't need to be awaited. + asyncio.gather( + hass.helpers.device_registry.async_get_registry(), + hass.helpers.entity_registry.async_get_registry(), + hass.helpers.area_registry.async_get_registry()) + # stage 1 for component in components: if component in FIRST_INIT_COMPONENT: diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 533811e275d..88cd44f4bf2 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -7,33 +7,12 @@ Component design guidelines: format ".". - Each component should publish services only under its own domain. """ -import asyncio -import itertools as it import logging -from typing import Awaitable -import voluptuous as vol - -import homeassistant.core as ha -import homeassistant.config as conf_util -from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.service import async_extract_entity_ids -from homeassistant.helpers import intent -from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, - SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, - RESTART_EXIT_CODE) -from homeassistant.helpers import config_validation as cv +from homeassistant.core import split_entity_id _LOGGER = logging.getLogger(__name__) -SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' -SERVICE_CHECK_CONFIG = 'check_config' -SERVICE_UPDATE_ENTITY = 'update_entity' -SCHEMA_UPDATE_ENTITY = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids -}) - def is_on(hass, entity_id=None): """Load up the module to call the is_on method. @@ -46,7 +25,7 @@ def is_on(hass, entity_id=None): entity_ids = hass.states.entity_ids() for ent_id in entity_ids: - domain = ha.split_entity_id(ent_id)[0] + domain = split_entity_id(ent_id)[0] try: component = getattr(hass.components, domain) @@ -64,113 +43,3 @@ def is_on(hass, entity_id=None): return True return False - - -async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: - """Set up general services related to Home Assistant.""" - async def async_handle_turn_service(service): - """Handle calls to homeassistant.turn_on/off.""" - entity_ids = await async_extract_entity_ids(hass, service) - - # Generic turn on/off method requires entity id - if not entity_ids: - _LOGGER.error( - "homeassistant/%s cannot be called without entity_id", - service.service) - return - - # Group entity_ids by domain. groupby requires sorted data. - by_domain = it.groupby(sorted(entity_ids), - lambda item: ha.split_entity_id(item)[0]) - - tasks = [] - - for domain, ent_ids in by_domain: - # We want to block for all calls and only return when all calls - # have been processed. If a service does not exist it causes a 10 - # second delay while we're blocking waiting for a response. - # But services can be registered on other HA instances that are - # listening to the bus too. So as an in between solution, we'll - # block only if the service is defined in the current HA instance. - blocking = hass.services.has_service(domain, service.service) - - # Create a new dict for this call - data = dict(service.data) - - # ent_ids is a generator, convert it to a list. - data[ATTR_ENTITY_ID] = list(ent_ids) - - tasks.append(hass.services.async_call( - domain, service.service, data, blocking)) - - await asyncio.wait(tasks, loop=hass.loop) - - hass.services.async_register( - ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service) - hass.services.async_register( - ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service) - hass.services.async_register( - ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned {} on")) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF, - "Turned {} off")) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}")) - - async def async_handle_core_service(call): - """Service handler for handling core services.""" - if call.service == SERVICE_HOMEASSISTANT_STOP: - hass.async_create_task(hass.async_stop()) - return - - try: - errors = await conf_util.async_check_ha_config_file(hass) - except HomeAssistantError: - return - - if errors: - _LOGGER.error(errors) - hass.components.persistent_notification.async_create( - "Config error. See dev-info panel for details.", - "Config validating", "{0}.check_config".format(ha.DOMAIN)) - return - - if call.service == SERVICE_HOMEASSISTANT_RESTART: - hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE)) - - async def async_handle_update_service(call): - """Service handler for updating an entity.""" - tasks = [hass.helpers.entity_component.async_update_entity(entity) - for entity in call.data[ATTR_ENTITY_ID]] - - if tasks: - await asyncio.wait(tasks) - - hass.services.async_register( - ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service) - hass.services.async_register( - ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service) - hass.services.async_register( - ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service) - hass.services.async_register( - ha.DOMAIN, SERVICE_UPDATE_ENTITY, async_handle_update_service, - schema=SCHEMA_UPDATE_ENTITY) - - async def async_handle_reload_config(call): - """Service handler for reloading core config.""" - try: - conf = await conf_util.async_hass_config_yaml(hass) - except HomeAssistantError as err: - _LOGGER.error(err) - return - - # auth only processed during startup - await conf_util.async_process_ha_core_config( - hass, conf.get(ha.DOMAIN) or {}) - - hass.services.async_register( - ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config) - - return True diff --git a/homeassistant/components/abode/alarm_control_panel.py b/homeassistant/components/abode/alarm_control_panel.py index 838d09b73af..d7426e04166 100644 --- a/homeassistant/components/abode/alarm_control_panel.py +++ b/homeassistant/components/abode/alarm_control_panel.py @@ -2,12 +2,12 @@ import logging import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.abode import ATTRIBUTION, AbodeDevice -from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN from homeassistant.const import ( ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED) +from . import ATTRIBUTION, DOMAIN as ABODE_DOMAIN, AbodeDevice + DEPENDENCIES = ['abode'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/abode/binary_sensor.py b/homeassistant/components/abode/binary_sensor.py index 47baef1d7e5..874723420ed 100644 --- a/homeassistant/components/abode/binary_sensor.py +++ b/homeassistant/components/abode/binary_sensor.py @@ -1,10 +1,10 @@ """Support for Abode Security System binary sensors.""" import logging -from homeassistant.components.abode import (AbodeDevice, AbodeAutomation, - DOMAIN as ABODE_DOMAIN) from homeassistant.components.binary_sensor import BinarySensorDevice +from . import DOMAIN as ABODE_DOMAIN, AbodeAutomation, AbodeDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['abode'] diff --git a/homeassistant/components/abode/camera.py b/homeassistant/components/abode/camera.py index 99613d07c47..d37644eccc3 100644 --- a/homeassistant/components/abode/camera.py +++ b/homeassistant/components/abode/camera.py @@ -1,13 +1,14 @@ """Support for Abode Security System cameras.""" +from datetime import timedelta import logging -from datetime import timedelta import requests -from homeassistant.components.abode import AbodeDevice, DOMAIN as ABODE_DOMAIN from homeassistant.components.camera import Camera from homeassistant.util import Throttle +from . import DOMAIN as ABODE_DOMAIN, AbodeDevice + DEPENDENCIES = ['abode'] MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90) diff --git a/homeassistant/components/abode/cover.py b/homeassistant/components/abode/cover.py index 03d6219ebce..c40159164dc 100644 --- a/homeassistant/components/abode/cover.py +++ b/homeassistant/components/abode/cover.py @@ -1,9 +1,10 @@ """Support for Abode Security System covers.""" import logging -from homeassistant.components.abode import AbodeDevice, DOMAIN as ABODE_DOMAIN from homeassistant.components.cover import CoverDevice +from . import DOMAIN as ABODE_DOMAIN, AbodeDevice + DEPENDENCIES = ['abode'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/abode/light.py b/homeassistant/components/abode/light.py index aabf5fbccdc..9e88acce41f 100644 --- a/homeassistant/components/abode/light.py +++ b/homeassistant/components/abode/light.py @@ -1,13 +1,14 @@ """Support for Abode Security System lights.""" import logging from math import ceil -from homeassistant.components.abode import AbodeDevice, DOMAIN as ABODE_DOMAIN + from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_COLOR_TEMP, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, Light) + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, SUPPORT_COLOR_TEMP, Light) from homeassistant.util.color import ( color_temperature_kelvin_to_mired, color_temperature_mired_to_kelvin) +from . import DOMAIN as ABODE_DOMAIN, AbodeDevice DEPENDENCIES = ['abode'] diff --git a/homeassistant/components/abode/lock.py b/homeassistant/components/abode/lock.py index ce6634268e9..0f568a4ace2 100644 --- a/homeassistant/components/abode/lock.py +++ b/homeassistant/components/abode/lock.py @@ -1,9 +1,10 @@ """Support for Abode Security System locks.""" import logging -from homeassistant.components.abode import AbodeDevice, DOMAIN as ABODE_DOMAIN from homeassistant.components.lock import LockDevice +from . import DOMAIN as ABODE_DOMAIN, AbodeDevice + DEPENDENCIES = ['abode'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py index fa6cb9323bf..ef6941c76d8 100644 --- a/homeassistant/components/abode/sensor.py +++ b/homeassistant/components/abode/sensor.py @@ -1,10 +1,11 @@ """Support for Abode Security System sensors.""" import logging -from homeassistant.components.abode import AbodeDevice, DOMAIN as ABODE_DOMAIN from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE) +from . import DOMAIN as ABODE_DOMAIN, AbodeDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['abode'] diff --git a/homeassistant/components/abode/switch.py b/homeassistant/components/abode/switch.py index d5303a27cd2..3e3ce031855 100644 --- a/homeassistant/components/abode/switch.py +++ b/homeassistant/components/abode/switch.py @@ -1,10 +1,10 @@ """Support for Abode Security System switches.""" import logging -from homeassistant.components.abode import (AbodeDevice, AbodeAutomation, - DOMAIN as ABODE_DOMAIN) from homeassistant.components.switch import SwitchDevice +from . import DOMAIN as ABODE_DOMAIN, AbodeAutomation, AbodeDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['abode'] diff --git a/homeassistant/components/acer_projector/__init__.py b/homeassistant/components/acer_projector/__init__.py new file mode 100644 index 00000000000..39896d203b1 --- /dev/null +++ b/homeassistant/components/acer_projector/__init__.py @@ -0,0 +1 @@ +"""The acer_projector component.""" diff --git a/homeassistant/components/switch/acer_projector.py b/homeassistant/components/acer_projector/switch.py similarity index 100% rename from homeassistant/components/switch/acer_projector.py rename to homeassistant/components/acer_projector/switch.py diff --git a/homeassistant/components/actiontec/__init__.py b/homeassistant/components/actiontec/__init__.py new file mode 100644 index 00000000000..fa59cc87063 --- /dev/null +++ b/homeassistant/components/actiontec/__init__.py @@ -0,0 +1 @@ +"""The actiontec component.""" diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/actiontec/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/actiontec.py rename to homeassistant/components/actiontec/device_tracker.py diff --git a/homeassistant/components/ads/binary_sensor.py b/homeassistant/components/ads/binary_sensor.py index 6771e99cd77..3b935156d18 100644 --- a/homeassistant/components/ads/binary_sensor.py +++ b/homeassistant/components/ads/binary_sensor.py @@ -3,12 +3,13 @@ import logging import voluptuous as vol -from homeassistant.components.ads import CONF_ADS_VAR, DATA_ADS from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME import homeassistant.helpers.config_validation as cv +from . import CONF_ADS_VAR, DATA_ADS + _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'ADS binary sensor' diff --git a/homeassistant/components/ads/light.py b/homeassistant/components/ads/light.py index e5299821e39..c61fd813634 100644 --- a/homeassistant/components/ads/light.py +++ b/homeassistant/components/ads/light.py @@ -1,13 +1,15 @@ """Support for ADS light sources.""" import logging + import voluptuous as vol -from homeassistant.components.light import Light, ATTR_BRIGHTNESS, \ - SUPPORT_BRIGHTNESS, PLATFORM_SCHEMA + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light) from homeassistant.const import CONF_NAME -from homeassistant.components.ads import DATA_ADS, CONF_ADS_VAR, \ - CONF_ADS_VAR_BRIGHTNESS import homeassistant.helpers.config_validation as cv +from . import CONF_ADS_VAR, CONF_ADS_VAR_BRIGHTNESS, DATA_ADS + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['ads'] DEFAULT_NAME = 'ADS Light' diff --git a/homeassistant/components/ads/sensor.py b/homeassistant/components/ads/sensor.py index 4db6ca7dbca..118a669a7ad 100644 --- a/homeassistant/components/ads/sensor.py +++ b/homeassistant/components/ads/sensor.py @@ -4,13 +4,13 @@ import logging import voluptuous as vol from homeassistant.components import ads -from homeassistant.components.ads import ( - CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from . import CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR + _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "ADS sensor" diff --git a/homeassistant/components/ads/switch.py b/homeassistant/components/ads/switch.py index e3aee023f21..094b4552349 100644 --- a/homeassistant/components/ads/switch.py +++ b/homeassistant/components/ads/switch.py @@ -3,12 +3,13 @@ import logging import voluptuous as vol -from homeassistant.components.ads import CONF_ADS_VAR, DATA_ADS from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity +from . import CONF_ADS_VAR, DATA_ADS + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['ads'] diff --git a/homeassistant/components/aftership/__init__.py b/homeassistant/components/aftership/__init__.py new file mode 100644 index 00000000000..b063c919f18 --- /dev/null +++ b/homeassistant/components/aftership/__init__.py @@ -0,0 +1 @@ +"""The aftership component.""" diff --git a/homeassistant/components/sensor/aftership.py b/homeassistant/components/aftership/sensor.py similarity index 100% rename from homeassistant/components/sensor/aftership.py rename to homeassistant/components/aftership/sensor.py diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py new file mode 100644 index 00000000000..b1f79d17241 --- /dev/null +++ b/homeassistant/components/airvisual/__init__.py @@ -0,0 +1 @@ +"""The airvisual component.""" diff --git a/homeassistant/components/sensor/airvisual.py b/homeassistant/components/airvisual/sensor.py similarity index 100% rename from homeassistant/components/sensor/airvisual.py rename to homeassistant/components/airvisual/sensor.py diff --git a/homeassistant/components/aladdin_connect/__init__.py b/homeassistant/components/aladdin_connect/__init__.py new file mode 100644 index 00000000000..90196616dc5 --- /dev/null +++ b/homeassistant/components/aladdin_connect/__init__.py @@ -0,0 +1 @@ +"""The aladdin_connect component.""" diff --git a/homeassistant/components/cover/aladdin_connect.py b/homeassistant/components/aladdin_connect/cover.py similarity index 100% rename from homeassistant/components/cover/aladdin_connect.py rename to homeassistant/components/aladdin_connect/cover.py diff --git a/homeassistant/components/alarmdecoder/__init__.py b/homeassistant/components/alarmdecoder/__init__.py index 1f74d72809b..5b1296b39de 100644 --- a/homeassistant/components/alarmdecoder/__init__.py +++ b/homeassistant/components/alarmdecoder/__init__.py @@ -5,7 +5,7 @@ from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_HOST from homeassistant.helpers.discovery import load_platform from homeassistant.util import dt as dt_util from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA @@ -20,7 +20,6 @@ DATA_AD = 'alarmdecoder' CONF_DEVICE = 'device' CONF_DEVICE_BAUD = 'baudrate' -CONF_DEVICE_HOST = 'host' CONF_DEVICE_PATH = 'path' CONF_DEVICE_PORT = 'port' CONF_DEVICE_TYPE = 'type' @@ -55,7 +54,7 @@ SIGNAL_REL_MESSAGE = 'alarmdecoder.rel_message' DEVICE_SOCKET_SCHEMA = vol.Schema({ vol.Required(CONF_DEVICE_TYPE): 'socket', - vol.Optional(CONF_DEVICE_HOST, default=DEFAULT_DEVICE_HOST): cv.string, + vol.Optional(CONF_HOST, default=DEFAULT_DEVICE_HOST): cv.string, vol.Optional(CONF_DEVICE_PORT, default=DEFAULT_DEVICE_PORT): cv.port}) DEVICE_SERIAL_SCHEMA = vol.Schema({ @@ -165,7 +164,7 @@ def setup(hass, config): controller = False if device_type == 'socket': - host = device.get(CONF_DEVICE_HOST) + host = device.get(CONF_HOST) port = device.get(CONF_DEVICE_PORT) controller = AlarmDecoder(SocketDevice(interface=(host, port))) elif device_type == 'serial': diff --git a/homeassistant/components/alarmdecoder/alarm_control_panel.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py index cf26e42b056..d7eced933dd 100644 --- a/homeassistant/components/alarmdecoder/alarm_control_panel.py +++ b/homeassistant/components/alarmdecoder/alarm_control_panel.py @@ -4,12 +4,13 @@ import logging import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarmdecoder import DATA_AD, SIGNAL_PANEL_MESSAGE from homeassistant.const import ( ATTR_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) import homeassistant.helpers.config_validation as cv +from . import DATA_AD, SIGNAL_PANEL_MESSAGE + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['alarmdecoder'] diff --git a/homeassistant/components/alarmdecoder/binary_sensor.py b/homeassistant/components/alarmdecoder/binary_sensor.py index c5af6ea79cb..09e63b4d664 100644 --- a/homeassistant/components/alarmdecoder/binary_sensor.py +++ b/homeassistant/components/alarmdecoder/binary_sensor.py @@ -2,11 +2,11 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.alarmdecoder import ( - ZONE_SCHEMA, CONF_ZONES, CONF_ZONE_NAME, CONF_ZONE_TYPE, - CONF_ZONE_RFID, CONF_ZONE_LOOP, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE, - SIGNAL_RFX_MESSAGE, SIGNAL_REL_MESSAGE, CONF_RELAY_ADDR, - CONF_RELAY_CHAN) + +from . import ( + CONF_RELAY_ADDR, CONF_RELAY_CHAN, CONF_ZONE_LOOP, CONF_ZONE_NAME, + CONF_ZONE_RFID, CONF_ZONE_TYPE, CONF_ZONES, SIGNAL_REL_MESSAGE, + SIGNAL_RFX_MESSAGE, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE, ZONE_SCHEMA) DEPENDENCIES = ['alarmdecoder'] diff --git a/homeassistant/components/alarmdecoder/sensor.py b/homeassistant/components/alarmdecoder/sensor.py index b2f697ea83f..88371dad17a 100644 --- a/homeassistant/components/alarmdecoder/sensor.py +++ b/homeassistant/components/alarmdecoder/sensor.py @@ -2,7 +2,8 @@ import logging from homeassistant.helpers.entity import Entity -from homeassistant.components.alarmdecoder import (SIGNAL_PANEL_MESSAGE) + +from . import SIGNAL_PANEL_MESSAGE _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/alarmdotcom/__init__.py b/homeassistant/components/alarmdotcom/__init__.py new file mode 100644 index 00000000000..0a715230e9f --- /dev/null +++ b/homeassistant/components/alarmdotcom/__init__.py @@ -0,0 +1 @@ +"""The alarmdotcom component.""" diff --git a/homeassistant/components/alarm_control_panel/alarmdotcom.py b/homeassistant/components/alarmdotcom/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/alarmdotcom.py rename to homeassistant/components/alarmdotcom/alarm_control_panel.py diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index c87b2c3f624..e16a1d45ab7 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -65,6 +65,12 @@ API_THERMOSTAT_MODES = OrderedDict([ (climate.STATE_DRY, 'OFF'), ]) +PERCENTAGE_FAN_MAP = { + fan.SPEED_LOW: 33, + fan.SPEED_MEDIUM: 66, + fan.SPEED_HIGH: 100, +} + SMART_HOME_HTTP_ENDPOINT = '/api/alexa/smart_home' CONF_DESCRIPTION = 'description' @@ -580,6 +586,26 @@ class _AlexaPercentageController(_AlexaInterface): def name(self): return 'Alexa.PercentageController' + def properties_supported(self): + return [{'name': 'percentage'}] + + def properties_retrievable(self): + return True + + def get_property(self, name): + if name != 'percentage': + raise _UnsupportedProperty(name) + + if self.entity.domain == fan.DOMAIN: + speed = self.entity.attributes.get(fan.ATTR_SPEED) + + return PERCENTAGE_FAN_MAP.get(speed, 0) + + if self.entity.domain == cover.DOMAIN: + return self.entity.attributes.get(cover.ATTR_CURRENT_POSITION, 0) + + return 0 + class _AlexaSpeaker(_AlexaInterface): """Implements Alexa.Speaker. diff --git a/homeassistant/components/alpha_vantage/__init__.py b/homeassistant/components/alpha_vantage/__init__.py new file mode 100644 index 00000000000..f8220c2cb81 --- /dev/null +++ b/homeassistant/components/alpha_vantage/__init__.py @@ -0,0 +1 @@ +"""The alpha_vantage component.""" diff --git a/homeassistant/components/sensor/alpha_vantage.py b/homeassistant/components/alpha_vantage/sensor.py similarity index 100% rename from homeassistant/components/sensor/alpha_vantage.py rename to homeassistant/components/alpha_vantage/sensor.py diff --git a/homeassistant/components/amazon_polly/__init__.py b/homeassistant/components/amazon_polly/__init__.py new file mode 100644 index 00000000000..0fab4af43e6 --- /dev/null +++ b/homeassistant/components/amazon_polly/__init__.py @@ -0,0 +1 @@ +"""Support for Amazon Polly integration.""" diff --git a/homeassistant/components/tts/amazon_polly.py b/homeassistant/components/amazon_polly/tts.py similarity index 99% rename from homeassistant/components/tts/amazon_polly.py rename to homeassistant/components/amazon_polly/tts.py index 0102a1fec09..167cd9cfc78 100644 --- a/homeassistant/components/tts/amazon_polly.py +++ b/homeassistant/components/amazon_polly/tts.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol -from homeassistant.components.tts import Provider, PLATFORM_SCHEMA +from homeassistant.components.tts import PLATFORM_SCHEMA, Provider import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['boto3==1.9.16'] diff --git a/homeassistant/components/ambient_station/.translations/bg.json b/homeassistant/components/ambient_station/.translations/bg.json new file mode 100644 index 00000000000..2099038f004 --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/bg.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Application \u0438/\u0438\u043b\u0438 API \u043a\u043b\u044e\u0447\u044a\u0442 \u0432\u0435\u0447\u0435 \u0441\u0430 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0430\u043d\u0438", + "invalid_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447 \u0438/\u0438\u043b\u0438 Application \u043a\u043b\u044e\u0447", + "no_devices": "\u041d\u0435 \u0441\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043f\u0440\u043e\u0444\u0438\u043b\u0430" + }, + "step": { + "user": { + "data": { + "api_key": "API \u043a\u043b\u044e\u0447", + "app_key": "Application \u043a\u043b\u044e\u0447" + }, + "title": "\u041f\u043e\u043f\u044a\u043b\u043d\u0435\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\u0442\u0430 \u0441\u0438" + } + }, + "title": "\u0410\u0442\u043c\u043e\u0441\u0444\u0435\u0440\u043d\u0430 PWS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/zh-Hans.json b/homeassistant/components/ambient_station/.translations/zh-Hans.json new file mode 100644 index 00000000000..866c06316f1 --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/zh-Hans.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Application Key \u548c/\u6216 API Key \u5df2\u6ce8\u518c", + "invalid_key": "\u65e0\u6548\u7684 API \u5bc6\u94a5\u548c/\u6216 Application Key", + "no_devices": "\u6ca1\u6709\u5728\u5e10\u6237\u4e2d\u627e\u5230\u8bbe\u5907" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "app_key": "Application Key" + }, + "title": "\u586b\u5199\u60a8\u7684\u4fe1\u606f" + } + }, + "title": "Ambient PWS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 545415f9d5d..2383d011945 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -304,15 +304,26 @@ class AmbientStation: self.monitored_conditions = monitored_conditions self.stations = {} - async def ws_connect(self): - """Register handlers and connect to the websocket.""" + async def _attempt_connect(self): + """Attempt to connect to the socket (retrying later on fail).""" from aioambient.errors import WebsocketError + try: + await self.client.websocket.connect() + except WebsocketError as err: + _LOGGER.error("Error with the websocket connection: %s", err) + self._ws_reconnect_delay = min( + 2 * self._ws_reconnect_delay, 480) + async_call_later( + self._hass, self._ws_reconnect_delay, self.ws_connect) + + async def ws_connect(self): + """Register handlers and connect to the websocket.""" async def _ws_reconnect(event_time): """Forcibly disconnect from and reconnect to the websocket.""" _LOGGER.debug('Watchdog expired; forcing socket reconnection') await self.client.websocket.disconnect() - await self.client.websocket.connect() + await self._attempt_connect() def on_connect(): """Define a handler to fire when the websocket is connected.""" @@ -381,15 +392,7 @@ class AmbientStation: self.client.websocket.on_disconnect(on_disconnect) self.client.websocket.on_subscribed(on_subscribed) - try: - await self.client.websocket.connect() - except WebsocketError as err: - _LOGGER.error("Error with the websocket connection: %s", err) - - self._ws_reconnect_delay = min(2 * self._ws_reconnect_delay, 480) - - async_call_later( - self._hass, self._ws_reconnect_delay, self.ws_connect) + await self._attempt_connect() async def ws_disconnect(self): """Disconnect from the websocket.""" @@ -411,6 +414,13 @@ class AmbientWeatherEntity(Entity): self._state = None self._station_name = station_name + @property + def available(self): + """Return True if entity is available.""" + return bool( + self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get( + self._sensor_type)) + @property def device_info(self): """Return device registry information for this entity.""" diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py index 2defa032809..04a38901683 100644 --- a/homeassistant/components/ambient_station/binary_sensor.py +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -1,13 +1,13 @@ """Support for Ambient Weather Station binary sensors.""" import logging -from homeassistant.components.ambient_station import ( - SENSOR_TYPES, TYPE_BATT1, TYPE_BATT10, TYPE_BATT2, TYPE_BATT3, TYPE_BATT4, - TYPE_BATT5, TYPE_BATT6, TYPE_BATT7, TYPE_BATT8, TYPE_BATT9, TYPE_BATTOUT, - AmbientWeatherEntity) from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.const import ATTR_NAME +from . import ( + SENSOR_TYPES, TYPE_BATT1, TYPE_BATT2, TYPE_BATT3, TYPE_BATT4, TYPE_BATT5, + TYPE_BATT6, TYPE_BATT7, TYPE_BATT8, TYPE_BATT9, TYPE_BATT10, TYPE_BATTOUT, + AmbientWeatherEntity) from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_BINARY_SENSOR _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index fa3222bf0e4..b394dc558e6 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -1,10 +1,9 @@ """Support for Ambient Weather Station sensors.""" import logging -from homeassistant.components.ambient_station import ( - SENSOR_TYPES, AmbientWeatherEntity) from homeassistant.const import ATTR_NAME +from . import SENSOR_TYPES, AmbientWeatherEntity from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_SENSOR _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index b976c1bd9d3..9f43941505b 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -4,8 +4,6 @@ from datetime import timedelta import aiohttp import voluptuous as vol -from requests.exceptions import HTTPError, ConnectTimeout -from requests.exceptions import ConnectionError as ConnectError from homeassistant.const import ( CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, @@ -13,7 +11,8 @@ from homeassistant.const import ( from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['amcrest==1.2.5'] + +REQUIREMENTS = ['amcrest==1.2.7'] DEPENDENCIES = ['ffmpeg'] _LOGGER = logging.getLogger(__name__) @@ -27,6 +26,7 @@ DEFAULT_NAME = 'Amcrest Camera' DEFAULT_PORT = 80 DEFAULT_RESOLUTION = 'high' DEFAULT_STREAM_SOURCE = 'snapshot' +DEFAULT_ARGUMENTS = '-pred 1' TIMEOUT = 10 DATA_AMCREST = 'amcrest' @@ -78,7 +78,8 @@ CONFIG_SCHEMA = vol.Schema({ vol.All(vol.In(RESOLUTION_LIST)), vol.Optional(CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE): vol.All(vol.In(STREAM_SOURCE_LIST)), - vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string, + vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): + cv.string, vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period, vol.Optional(CONF_SENSORS): @@ -91,7 +92,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): """Set up the Amcrest IP Camera component.""" - from amcrest import AmcrestCamera + from amcrest import AmcrestCamera, AmcrestError hass.data[DATA_AMCREST] = {} amcrest_cams = config[DOMAIN] @@ -105,7 +106,7 @@ def setup(hass, config): # pylint: disable=pointless-statement camera.current_time - except (ConnectError, ConnectTimeout, HTTPError) as ex: + except AmcrestError as ex: _LOGGER.error("Unable to connect to Amcrest camera: %s", str(ex)) hass.components.persistent_notification.create( 'Error: {}
' diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index f6c507e73f4..63c2c720781 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -2,17 +2,15 @@ import asyncio import logging -from requests import RequestException -from urllib3.exceptions import ReadTimeoutError - -from homeassistant.components.amcrest import ( - DATA_AMCREST, STREAM_SOURCE_LIST, TIMEOUT) -from homeassistant.components.camera import Camera +from homeassistant.components.camera import ( + Camera, SUPPORT_STREAM) from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.const import CONF_NAME from homeassistant.helpers.aiohttp_client import ( - async_get_clientsession, async_aiohttp_proxy_web, - async_aiohttp_proxy_stream) + async_aiohttp_proxy_stream, async_aiohttp_proxy_web, + async_get_clientsession) + +from . import DATA_AMCREST, STREAM_SOURCE_LIST, TIMEOUT DEPENDENCIES = ['amcrest', 'ffmpeg'] @@ -51,13 +49,15 @@ class AmcrestCam(Camera): async def async_camera_image(self): """Return a still image response from the camera.""" + from amcrest import AmcrestError + async with self._snapshot_lock: try: # Send the request to snap a picture and return raw jpg data response = await self.hass.async_add_executor_job( self._camera.snapshot, self._resolution) return response.data - except (RequestException, ReadTimeoutError, ValueError) as error: + except AmcrestError as error: _LOGGER.error( 'Could not get camera image due to error %s', error) return None @@ -79,7 +79,7 @@ class AmcrestCam(Camera): self.hass, request, stream_coro) # streaming via ffmpeg - from haffmpeg import CameraMjpeg + from haffmpeg.camera import CameraMjpeg streaming_url = self._camera.rtsp_url(typeno=self._resolution) stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) @@ -87,8 +87,9 @@ class AmcrestCam(Camera): streaming_url, extra_cmd=self._ffmpeg_arguments) try: + stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream, + self.hass, request, stream_reader, self._ffmpeg.ffmpeg_stream_content_type) finally: await stream.close() @@ -98,6 +99,11 @@ class AmcrestCam(Camera): """Return the name of this camera.""" return self._name + @property + def supported_features(self): + """Return supported features.""" + return SUPPORT_STREAM + @property def stream_source(self): """Return the source of the stream.""" diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py index 4869dfffa6e..c721914c73c 100644 --- a/homeassistant/components/amcrest/sensor.py +++ b/homeassistant/components/amcrest/sensor.py @@ -2,9 +2,10 @@ from datetime import timedelta import logging -from homeassistant.components.amcrest import DATA_AMCREST, SENSORS -from homeassistant.helpers.entity import Entity from homeassistant.const import CONF_NAME, CONF_SENSORS +from homeassistant.helpers.entity import Entity + +from . import DATA_AMCREST, SENSORS DEPENDENCIES = ['amcrest'] diff --git a/homeassistant/components/amcrest/switch.py b/homeassistant/components/amcrest/switch.py index 3c1f03f0145..0bbd290b3ac 100644 --- a/homeassistant/components/amcrest/switch.py +++ b/homeassistant/components/amcrest/switch.py @@ -1,11 +1,11 @@ """Support for toggling Amcrest IP camera settings.""" import logging -from homeassistant.components.amcrest import DATA_AMCREST, SWITCHES -from homeassistant.const import ( - CONF_NAME, CONF_SWITCHES, STATE_OFF, STATE_ON) +from homeassistant.const import CONF_NAME, CONF_SWITCHES, STATE_OFF, STATE_ON from homeassistant.helpers.entity import ToggleEntity +from . import DATA_AMCREST, SWITCHES + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['amcrest'] diff --git a/homeassistant/components/android_ip_webcam/__init__.py b/homeassistant/components/android_ip_webcam/__init__.py index c5424b3d0fa..600efd55a16 100644 --- a/homeassistant/components/android_ip_webcam/__init__.py +++ b/homeassistant/components/android_ip_webcam/__init__.py @@ -18,7 +18,7 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util.dt import utcnow -from homeassistant.components.camera.mjpeg import ( +from homeassistant.components.mjpeg.camera import ( CONF_MJPEG_URL, CONF_STILL_IMAGE_URL) REQUIREMENTS = ['pydroid-ipcam==0.8'] diff --git a/homeassistant/components/android_ip_webcam/binary_sensor.py b/homeassistant/components/android_ip_webcam/binary_sensor.py index e33e22f3778..c058c44c503 100644 --- a/homeassistant/components/android_ip_webcam/binary_sensor.py +++ b/homeassistant/components/android_ip_webcam/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Android IP Webcam binary sensors.""" from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.android_ip_webcam import ( - KEY_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, CONF_NAME) + +from . import CONF_HOST, CONF_NAME, DATA_IP_WEBCAM, KEY_MAP, AndroidIPCamEntity DEPENDENCIES = ['android_ip_webcam'] diff --git a/homeassistant/components/android_ip_webcam/sensor.py b/homeassistant/components/android_ip_webcam/sensor.py index e98ce7951b8..4d29493d64f 100644 --- a/homeassistant/components/android_ip_webcam/sensor.py +++ b/homeassistant/components/android_ip_webcam/sensor.py @@ -1,9 +1,10 @@ """Support for Android IP Webcam sensors.""" -from homeassistant.components.android_ip_webcam import ( - KEY_MAP, ICON_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, - CONF_NAME, CONF_SENSORS) from homeassistant.helpers.icon import icon_for_battery_level +from . import ( + CONF_HOST, CONF_NAME, CONF_SENSORS, DATA_IP_WEBCAM, ICON_MAP, KEY_MAP, + AndroidIPCamEntity) + DEPENDENCIES = ['android_ip_webcam'] diff --git a/homeassistant/components/android_ip_webcam/switch.py b/homeassistant/components/android_ip_webcam/switch.py index 73a94acbcdd..0304c5747f7 100644 --- a/homeassistant/components/android_ip_webcam/switch.py +++ b/homeassistant/components/android_ip_webcam/switch.py @@ -1,8 +1,9 @@ """Support for Android IP Webcam settings.""" from homeassistant.components.switch import SwitchDevice -from homeassistant.components.android_ip_webcam import ( - KEY_MAP, ICON_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, - CONF_NAME, CONF_SWITCHES) + +from . import ( + CONF_HOST, CONF_NAME, CONF_SWITCHES, DATA_IP_WEBCAM, ICON_MAP, KEY_MAP, + AndroidIPCamEntity) DEPENDENCIES = ['android_ip_webcam'] diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index 1282a40cac5..5bce21f05a0 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -22,7 +22,7 @@ import homeassistant.helpers.config_validation as cv ANDROIDTV_DOMAIN = 'androidtv' -REQUIREMENTS = ['androidtv==0.0.12'] +REQUIREMENTS = ['androidtv==0.0.14'] _LOGGER = logging.getLogger(__name__) @@ -40,6 +40,8 @@ CONF_ADB_SERVER_IP = 'adb_server_ip' CONF_ADB_SERVER_PORT = 'adb_server_port' CONF_APPS = 'apps' CONF_GET_SOURCES = 'get_sources' +CONF_TURN_ON_COMMAND = 'turn_on_command' +CONF_TURN_OFF_COMMAND = 'turn_off_command' DEFAULT_NAME = 'Android TV' DEFAULT_PORT = 5555 @@ -59,27 +61,21 @@ SERVICE_ADB_COMMAND_SCHEMA = vol.Schema({ }) -def has_adb_files(value): - """Check that ADB key files exist.""" - priv_key = value - pub_key = '{}.pub'.format(value) - cv.isfile(pub_key) - return cv.isfile(priv_key) - - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): vol.In(DEVICE_CLASSES), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_ADBKEY): has_adb_files, + vol.Optional(CONF_ADBKEY): cv.isfile, vol.Optional(CONF_ADB_SERVER_IP): cv.string, vol.Optional(CONF_ADB_SERVER_PORT, default=DEFAULT_ADB_SERVER_PORT): cv.port, vol.Optional(CONF_GET_SOURCES, default=DEFAULT_GET_SOURCES): cv.boolean, vol.Optional(CONF_APPS, default=dict()): - vol.Schema({cv.string: cv.string}) + vol.Schema({cv.string: cv.string}), + vol.Optional(CONF_TURN_ON_COMMAND): cv.string, + vol.Optional(CONF_TURN_OFF_COMMAND): cv.string }) # Translate from `AndroidTV` / `FireTV` reported state to HA state. @@ -136,12 +132,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): else: if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV: device = AndroidTVDevice(aftv, config[CONF_NAME], - config[CONF_APPS]) + config[CONF_APPS], + config.get(CONF_TURN_ON_COMMAND), + config.get(CONF_TURN_OFF_COMMAND)) device_name = config[CONF_NAME] if CONF_NAME in config \ else 'Android TV' else: device = FireTVDevice(aftv, config[CONF_NAME], config[CONF_APPS], - config[CONF_GET_SOURCES]) + config[CONF_GET_SOURCES], + config.get(CONF_TURN_ON_COMMAND), + config.get(CONF_TURN_OFF_COMMAND)) device_name = config[CONF_NAME] if CONF_NAME in config \ else 'Fire TV' @@ -163,7 +163,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): output = target_device.adb_command(cmd) # log the output if there is any - if output: + if output and (not isinstance(output, str) or output.strip()): _LOGGER.info("Output of command '%s' from '%s': %s", cmd, target_device.entity_id, repr(output)) @@ -199,7 +199,8 @@ def adb_decorator(override_available=False): class ADBDevice(MediaPlayerDevice): """Representation of an Android TV or Fire TV device.""" - def __init__(self, aftv, name, apps): + def __init__(self, aftv, name, apps, turn_on_command, + turn_off_command): """Initialize the Android TV / Fire TV device.""" from androidtv.constants import APPS, KEYS @@ -209,6 +210,9 @@ class ADBDevice(MediaPlayerDevice): self._apps.update(apps) self._keys = KEYS + self.turn_on_command = turn_on_command + self.turn_off_command = turn_off_command + # ADB exceptions to catch if not self.aftv.adb_server_ip: # Using "python-adb" (Python ADB implementation) @@ -223,7 +227,7 @@ class ADBDevice(MediaPlayerDevice): TcpTimeoutException) else: # Using "pure-python-adb" (communicate with ADB server) - self.exceptions = (ConnectionResetError,) + self.exceptions = (ConnectionResetError, RuntimeError) # Property attributes self._available = self.aftv.available @@ -278,12 +282,18 @@ class ADBDevice(MediaPlayerDevice): @adb_decorator() def turn_on(self): """Turn on the device.""" - self.aftv.turn_on() + if self.turn_on_command: + self.aftv.adb_shell(self.turn_on_command) + else: + self.aftv.turn_on() @adb_decorator() def turn_off(self): """Turn off the device.""" - self.aftv.turn_off() + if self.turn_off_command: + self.aftv.adb_shell(self.turn_off_command) + else: + self.aftv.turn_off() @adb_decorator() def media_previous_track(self): @@ -311,9 +321,11 @@ class ADBDevice(MediaPlayerDevice): class AndroidTVDevice(ADBDevice): """Representation of an Android TV device.""" - def __init__(self, aftv, name, apps): + def __init__(self, aftv, name, apps, turn_on_command, + turn_off_command): """Initialize the Android TV device.""" - super().__init__(aftv, name, apps) + super().__init__(aftv, name, apps, turn_on_command, + turn_off_command) self._device = None self._muted = None @@ -392,9 +404,11 @@ class AndroidTVDevice(ADBDevice): class FireTVDevice(ADBDevice): """Representation of a Fire TV device.""" - def __init__(self, aftv, name, apps, get_sources): + def __init__(self, aftv, name, apps, get_sources, + turn_on_command, turn_off_command): """Initialize the Fire TV device.""" - super().__init__(aftv, name, apps) + super().__init__(aftv, name, apps, turn_on_command, + turn_off_command) self._get_sources = get_sources self._running_apps = None diff --git a/homeassistant/components/anel_pwrctrl/__init__.py b/homeassistant/components/anel_pwrctrl/__init__.py new file mode 100644 index 00000000000..bd06aa87b36 --- /dev/null +++ b/homeassistant/components/anel_pwrctrl/__init__.py @@ -0,0 +1 @@ +"""The anel_pwrctrl component.""" diff --git a/homeassistant/components/switch/anel_pwrctrl.py b/homeassistant/components/anel_pwrctrl/switch.py similarity index 100% rename from homeassistant/components/switch/anel_pwrctrl.py rename to homeassistant/components/anel_pwrctrl/switch.py diff --git a/homeassistant/components/anthemav/__init__.py b/homeassistant/components/anthemav/__init__.py new file mode 100644 index 00000000000..56b06e865c2 --- /dev/null +++ b/homeassistant/components/anthemav/__init__.py @@ -0,0 +1 @@ +"""The anthemav component.""" diff --git a/homeassistant/components/media_player/anthemav.py b/homeassistant/components/anthemav/media_player.py similarity index 100% rename from homeassistant/components/media_player/anthemav.py rename to homeassistant/components/anthemav/media_player.py diff --git a/homeassistant/components/api_streams/__init__.py b/homeassistant/components/api_streams/__init__.py new file mode 100644 index 00000000000..dba43061313 --- /dev/null +++ b/homeassistant/components/api_streams/__init__.py @@ -0,0 +1 @@ +"""The api_streams component.""" diff --git a/homeassistant/components/apns/__init__.py b/homeassistant/components/apns/__init__.py new file mode 100644 index 00000000000..9332b0d1ede --- /dev/null +++ b/homeassistant/components/apns/__init__.py @@ -0,0 +1 @@ +"""The apns component.""" diff --git a/homeassistant/components/notify/apns.py b/homeassistant/components/apns/notify.py similarity index 98% rename from homeassistant/components/notify/apns.py rename to homeassistant/components/apns/notify.py index 8fabfc3aefb..b2c6b63864f 100644 --- a/homeassistant/components/notify/apns.py +++ b/homeassistant/components/apns/notify.py @@ -9,13 +9,14 @@ import os import voluptuous as vol -from homeassistant.helpers.event import track_state_change from homeassistant.config import load_yaml_config_file -from homeassistant.components.notify import ( - ATTR_TARGET, ATTR_DATA, BaseNotificationService, DOMAIN, PLATFORM_SCHEMA) -from homeassistant.const import CONF_NAME, CONF_PLATFORM, ATTR_NAME -import homeassistant.helpers.config_validation as cv +from homeassistant.const import ATTR_NAME, CONF_NAME, CONF_PLATFORM from homeassistant.helpers import template as template_helper +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import track_state_change + +from homeassistant.components.notify import ( + ATTR_DATA, ATTR_TARGET, DOMAIN, PLATFORM_SCHEMA, BaseNotificationService) REQUIREMENTS = ['apns2==0.3.0'] diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index 03ac5bd2549..e00ce6ed13b 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -1,8 +1,6 @@ """Support for Apple TV media player.""" import logging -from homeassistant.components.apple_tv import ( - ATTR_ATV, ATTR_POWER, DATA_APPLE_TV, DATA_ENTITIES) from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, @@ -14,6 +12,8 @@ from homeassistant.const import ( from homeassistant.core import callback import homeassistant.util.dt as dt_util +from . import ATTR_ATV, ATTR_POWER, DATA_APPLE_TV, DATA_ENTITIES + DEPENDENCIES = ['apple_tv'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/apple_tv/remote.py b/homeassistant/components/apple_tv/remote.py index 2d80ded6861..25b500ac09d 100644 --- a/homeassistant/components/apple_tv/remote.py +++ b/homeassistant/components/apple_tv/remote.py @@ -1,8 +1,8 @@ """Remote control support for Apple TV.""" -from homeassistant.components.apple_tv import ( - ATTR_ATV, ATTR_POWER, DATA_APPLE_TV) from homeassistant.components import remote -from homeassistant.const import (CONF_NAME, CONF_HOST) +from homeassistant.const import CONF_HOST, CONF_NAME + +from . import ATTR_ATV, ATTR_POWER, DATA_APPLE_TV DEPENDENCIES = ['apple_tv'] diff --git a/homeassistant/components/aqualogic/sensor.py b/homeassistant/components/aqualogic/sensor.py index 9e061ba91bf..dc06a2127e9 100644 --- a/homeassistant/components/aqualogic/sensor.py +++ b/homeassistant/components/aqualogic/sensor.py @@ -4,12 +4,13 @@ import logging import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_MONITORED_CONDITIONS, - TEMP_CELSIUS, TEMP_FAHRENHEIT) +from homeassistant.const import ( + CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, TEMP_FAHRENHEIT) from homeassistant.core import callback -from homeassistant.helpers.entity import Entity -import homeassistant.components.aqualogic as aq import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity + +from . import DOMAIN, UPDATE_TOPIC _LOGGER = logging.getLogger(__name__) @@ -46,7 +47,7 @@ async def async_setup_platform( """Set up the sensor platform.""" sensors = [] - processor = hass.data[aq.DOMAIN] + processor = hass.data[DOMAIN] for sensor_type in config.get(CONF_MONITORED_CONDITIONS): sensors.append(AquaLogicSensor(processor, sensor_type)) @@ -95,7 +96,7 @@ class AquaLogicSensor(Entity): async def async_added_to_hass(self): """Register callbacks.""" self.hass.helpers.dispatcher.async_dispatcher_connect( - aq.UPDATE_TOPIC, self.async_update_callback) + UPDATE_TOPIC, self.async_update_callback) @callback def async_update_callback(self): diff --git a/homeassistant/components/aqualogic/switch.py b/homeassistant/components/aqualogic/switch.py index ee040fa1ba5..21e573f944b 100644 --- a/homeassistant/components/aqualogic/switch.py +++ b/homeassistant/components/aqualogic/switch.py @@ -3,11 +3,12 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.const import CONF_MONITORED_CONDITIONS from homeassistant.core import callback -import homeassistant.components.aqualogic as aq -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA -from homeassistant.const import (CONF_MONITORED_CONDITIONS) +import homeassistant.helpers.config_validation as cv + +from . import DOMAIN, UPDATE_TOPIC DEPENDENCIES = ['aqualogic'] @@ -37,7 +38,7 @@ async def async_setup_platform( """Set up the switch platform.""" switches = [] - processor = hass.data[aq.DOMAIN] + processor = hass.data[DOMAIN] for switch_type in config.get(CONF_MONITORED_CONDITIONS): switches.append(AquaLogicSwitch(processor, switch_type)) @@ -101,7 +102,7 @@ class AquaLogicSwitch(SwitchDevice): async def async_added_to_hass(self): """Register callbacks.""" self.hass.helpers.dispatcher.async_dispatcher_connect( - aq.UPDATE_TOPIC, self.async_update_callback) + UPDATE_TOPIC, self.async_update_callback) @callback def async_update_callback(self): diff --git a/homeassistant/components/aquostv/__init__.py b/homeassistant/components/aquostv/__init__.py new file mode 100644 index 00000000000..a7f39037fe1 --- /dev/null +++ b/homeassistant/components/aquostv/__init__.py @@ -0,0 +1 @@ +"""The aquostv component.""" diff --git a/homeassistant/components/media_player/aquostv.py b/homeassistant/components/aquostv/media_player.py similarity index 100% rename from homeassistant/components/media_player/aquostv.py rename to homeassistant/components/aquostv/media_player.py diff --git a/homeassistant/components/arest/__init__.py b/homeassistant/components/arest/__init__.py new file mode 100644 index 00000000000..37a104c08fe --- /dev/null +++ b/homeassistant/components/arest/__init__.py @@ -0,0 +1 @@ +"""The arest component.""" diff --git a/homeassistant/components/binary_sensor/arest.py b/homeassistant/components/arest/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/arest.py rename to homeassistant/components/arest/binary_sensor.py diff --git a/homeassistant/components/sensor/arest.py b/homeassistant/components/arest/sensor.py similarity index 100% rename from homeassistant/components/sensor/arest.py rename to homeassistant/components/arest/sensor.py diff --git a/homeassistant/components/switch/arest.py b/homeassistant/components/arest/switch.py similarity index 100% rename from homeassistant/components/switch/arest.py rename to homeassistant/components/arest/switch.py diff --git a/homeassistant/components/arlo/alarm_control_panel.py b/homeassistant/components/arlo/alarm_control_panel.py index 931dfa1b15d..3557ed125c6 100644 --- a/homeassistant/components/arlo/alarm_control_panel.py +++ b/homeassistant/components/arlo/alarm_control_panel.py @@ -3,16 +3,16 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.components.alarm_control_panel import ( - AlarmControlPanel, PLATFORM_SCHEMA) -from homeassistant.components.arlo import ( - DATA_ARLO, ATTRIBUTION, SIGNAL_UPDATE_ARLO) + PLATFORM_SCHEMA, AlarmControlPanel) from homeassistant.const import ( ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, - STATE_ALARM_DISARMED, STATE_ALARM_ARMED_NIGHT) + STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ATTRIBUTION, DATA_ARLO, SIGNAL_UPDATE_ARLO _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/arlo/camera.py b/homeassistant/components/arlo/camera.py index 6f20ecdadcd..d4b00f00625 100644 --- a/homeassistant/components/arlo/camera.py +++ b/homeassistant/components/arlo/camera.py @@ -3,16 +3,18 @@ import logging import voluptuous as vol -from homeassistant.core import callback -import homeassistant.helpers.config_validation as cv -from homeassistant.components.arlo import ( - DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO) -from homeassistant.components.camera import Camera, PLATFORM_SCHEMA +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.const import ATTR_BATTERY_LEVEL +from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect +from . import DATA_ARLO, DEFAULT_BRAND, SIGNAL_UPDATE_ARLO + +DEPENDENCIES = ['arlo', 'ffmpeg'] + _LOGGER = logging.getLogger(__name__) ARLO_MODE_ARMED = 'armed' @@ -28,8 +30,7 @@ ATTR_UNSEEN_VIDEOS = 'unseen_videos' ATTR_LAST_REFRESH = 'last_refresh' CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' - -DEPENDENCIES = ['arlo', 'ffmpeg'] +DEFAULT_ARGUMENTS = '-pred 1' POWERSAVE_MODE_MAPPING = { 1: 'best_battery_life', @@ -38,7 +39,7 @@ POWERSAVE_MODE_MAPPING = { } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string, + vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, }) @@ -83,7 +84,7 @@ class ArloCam(Camera): async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" - from haffmpeg import CameraMjpeg + from haffmpeg.camera import CameraMjpeg video = self._camera.last_video if not video: error_msg = \ @@ -97,8 +98,9 @@ class ArloCam(Camera): video.video_url, extra_cmd=self._ffmpeg_arguments) try: + stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream, + self.hass, request, stream_reader, self._ffmpeg.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/arlo/sensor.py b/homeassistant/components/arlo/sensor.py index 1c3cc933438..e08669eb80b 100644 --- a/homeassistant/components/arlo/sensor.py +++ b/homeassistant/components/arlo/sensor.py @@ -3,19 +3,18 @@ import logging import voluptuous as vol -from homeassistant.core import callback -import homeassistant.helpers.config_validation as cv -from homeassistant.components.arlo import ( - ATTRIBUTION, DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, - DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY) - + ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level +from . import ATTRIBUTION, DATA_ARLO, DEFAULT_BRAND, SIGNAL_UPDATE_ARLO + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['arlo'] diff --git a/homeassistant/components/aruba/__init__.py b/homeassistant/components/aruba/__init__.py new file mode 100644 index 00000000000..cd52f7310f3 --- /dev/null +++ b/homeassistant/components/aruba/__init__.py @@ -0,0 +1 @@ +"""The aruba component.""" diff --git a/homeassistant/components/device_tracker/aruba.py b/homeassistant/components/aruba/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/aruba.py rename to homeassistant/components/aruba/device_tracker.py diff --git a/homeassistant/components/arwn/__init__.py b/homeassistant/components/arwn/__init__.py new file mode 100644 index 00000000000..726f3dba5de --- /dev/null +++ b/homeassistant/components/arwn/__init__.py @@ -0,0 +1 @@ +"""The arwn component.""" diff --git a/homeassistant/components/sensor/arwn.py b/homeassistant/components/arwn/sensor.py similarity index 100% rename from homeassistant/components/sensor/arwn.py rename to homeassistant/components/arwn/sensor.py diff --git a/homeassistant/components/asterisk_cdr/__init__.py b/homeassistant/components/asterisk_cdr/__init__.py new file mode 100644 index 00000000000..d681a392c56 --- /dev/null +++ b/homeassistant/components/asterisk_cdr/__init__.py @@ -0,0 +1 @@ +"""The asterisk_cdr component.""" diff --git a/homeassistant/components/mailbox/asterisk_cdr.py b/homeassistant/components/asterisk_cdr/mailbox.py similarity index 100% rename from homeassistant/components/mailbox/asterisk_cdr.py rename to homeassistant/components/asterisk_cdr/mailbox.py diff --git a/homeassistant/components/asterisk_mbox/mailbox.py b/homeassistant/components/asterisk_mbox/mailbox.py index 9da4bd1a21a..a3e7c3f4d61 100644 --- a/homeassistant/components/asterisk_mbox/mailbox.py +++ b/homeassistant/components/asterisk_mbox/mailbox.py @@ -1,12 +1,13 @@ """Support for the Asterisk Voicemail interface.""" import logging -from homeassistant.components.asterisk_mbox import DOMAIN as ASTERISK_DOMAIN from homeassistant.components.mailbox import ( CONTENT_TYPE_MPEG, Mailbox, StreamError) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from . import DOMAIN as ASTERISK_DOMAIN + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['asterisk_mbox'] diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/asuswrt/device_tracker.py similarity index 97% rename from homeassistant/components/device_tracker/asuswrt.py rename to homeassistant/components/asuswrt/device_tracker.py index 4630c3730ca..f5c6dd4a42a 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -6,9 +6,10 @@ https://home-assistant.io/components/device_tracker.asuswrt/ """ import logging -from homeassistant.components.asuswrt import DATA_ASUSWRT from homeassistant.components.device_tracker import DeviceScanner +from . import DATA_ASUSWRT + DEPENDENCIES = ['asuswrt'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/asuswrt.py b/homeassistant/components/asuswrt/sensor.py similarity index 98% rename from homeassistant/components/sensor/asuswrt.py rename to homeassistant/components/asuswrt/sensor.py index 08e2ec27cb2..53d232862c6 100644 --- a/homeassistant/components/sensor/asuswrt.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -7,7 +7,8 @@ https://home-assistant.io/components/sensor.asuswrt/ import logging from homeassistant.helpers.entity import Entity -from homeassistant.components.asuswrt import DATA_ASUSWRT + +from . import DATA_ASUSWRT DEPENDENCIES = ['asuswrt'] diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index 1ad2d80cea8..3a69d41177d 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -1,9 +1,10 @@ """Support for August binary sensors.""" +from datetime import datetime, timedelta import logging -from datetime import timedelta, datetime -from homeassistant.components.august import DATA_AUGUST -from homeassistant.components.binary_sensor import (BinarySensorDevice) +from homeassistant.components.binary_sensor import BinarySensorDevice + +from . import DATA_AUGUST _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index 7420bb04067..53a9d78bc60 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -3,9 +3,10 @@ from datetime import timedelta import requests -from homeassistant.components.august import DATA_AUGUST, DEFAULT_TIMEOUT from homeassistant.components.camera import Camera +from . import DATA_AUGUST, DEFAULT_TIMEOUT + DEPENDENCIES = ['august'] SCAN_INTERVAL = timedelta(seconds=5) diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index ff355bbf87b..e112eaa2592 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -1,11 +1,12 @@ """Support for August lock.""" -import logging from datetime import timedelta +import logging -from homeassistant.components.august import DATA_AUGUST from homeassistant.components.lock import LockDevice from homeassistant.const import ATTR_BATTERY_LEVEL +from . import DATA_AUGUST + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['august'] diff --git a/homeassistant/components/aurora/__init__.py b/homeassistant/components/aurora/__init__.py new file mode 100644 index 00000000000..2b3caa06843 --- /dev/null +++ b/homeassistant/components/aurora/__init__.py @@ -0,0 +1 @@ +"""The aurora component.""" diff --git a/homeassistant/components/binary_sensor/aurora.py b/homeassistant/components/aurora/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/aurora.py rename to homeassistant/components/aurora/binary_sensor.py diff --git a/homeassistant/components/auth/.translations/bg.json b/homeassistant/components/auth/.translations/bg.json new file mode 100644 index 00000000000..63cf17f0b22 --- /dev/null +++ b/homeassistant/components/auth/.translations/bg.json @@ -0,0 +1,16 @@ +{ + "mfa_setup": { + "totp": { + "error": { + "invalid_code": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d \u043a\u043e\u0434, \u043c\u043e\u043b\u044f \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e. \u0410\u043a\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0432\u0430\u0442\u0435 \u0442\u0430\u0437\u0438 \u0433\u0440\u0435\u0448\u043a\u0430 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e, \u043c\u043e\u043b\u044f, \u0443\u0432\u0435\u0440\u0435\u0442\u0435 \u0441\u0435, \u0447\u0435 \u0447\u0430\u0441\u043e\u0432\u043d\u0438\u043a\u044a\u0442 \u043d\u0430 Home Assistant \u0435 \u0441\u0432\u0435\u0440\u0435\u043d." + }, + "step": { + "init": { + "description": "\u0417\u0430 \u0434\u0430 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u0442\u0435 \u0434\u0432\u0443\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u0432\u043e-\u0431\u0430\u0437\u0438\u0440\u0430\u043d\u0438 \u0435\u0434\u043d\u043e\u043a\u0440\u0430\u0442\u043d\u0438 \u043f\u0430\u0440\u043e\u043b\u0438, \u0441\u043a\u0430\u043d\u0438\u0440\u0430\u0439\u0442\u0435 QR \u043a\u043e\u0434\u0430 \u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u043a\u0430\u0442\u043e\u0440\u0430. \u0410\u043a\u043e \u043d\u044f\u043c\u0430\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0412\u0438 \u043f\u0440\u0435\u043f\u043e\u0440\u044a\u0447\u0432\u0430\u043c\u0435 \u0438\u043b\u0438 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u0438\u043b\u0438 [Authy](https://authy.com/).\n\n{qr_code}\n\n\u0421\u043b\u0435\u0434 \u0441\u043a\u0430\u043d\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043a\u043e\u0434\u0430, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 6-\u0442\u0435 \u0446\u0438\u0444\u0440\u0438 \u043e\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e \u0437\u0430 \u0434\u0430 \u043f\u043e\u0442\u0432\u044a\u0440\u0434\u0438\u0442\u0435 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d\u0435\u0442\u043e. \u0410\u043a\u043e \u0438\u043c\u0430\u0442\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u043f\u0440\u0438 \u0441\u043a\u0430\u043d\u0438\u0440\u0430\u043d\u0435\u0442\u043e \u043d\u0430 QR \u043a\u043e\u0434\u0430, \u043d\u0430\u043f\u0440\u0430\u0432\u0435\u0442\u0435 \u0440\u044a\u0447\u043d\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0441 \u043a\u043e\u0434 **`{code}`**.", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043d\u0430 \u0434\u0432\u0443\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u043a\u0430\u0446\u0438\u044f \u0447\u0440\u0435\u0437 TOTP" + } + }, + "title": "TOTP" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/auth/.translations/hu.json b/homeassistant/components/auth/.translations/hu.json index a2d132d9073..5e7b1835093 100644 --- a/homeassistant/components/auth/.translations/hu.json +++ b/homeassistant/components/auth/.translations/hu.json @@ -9,13 +9,15 @@ }, "step": { "init": { - "description": "V\u00e1lassz \u00e9rtes\u00edt\u00e9si szolg\u00e1ltat\u00e1st:", + "description": "K\u00e9rlek, v\u00e1lassz egyet az \u00e9rtes\u00edt\u00e9si szolg\u00e1ltat\u00e1sok k\u00f6z\u00fcl:", "title": "\u00c1ll\u00edtsa be az \u00e9rtes\u00edt\u00e9si \u00f6sszetev\u0151 \u00e1ltal megadott egyszeri jelsz\u00f3t" }, "setup": { + "description": "Az egyszeri jelsz\u00f3 el lett k\u00fcldve a(z) **notify.{notify_service}** szolg\u00e1ltat\u00e1ssal. K\u00e9rlek, add meg al\u00e1bb:", "title": "Be\u00e1ll\u00edt\u00e1s ellen\u0151rz\u00e9se" } - } + }, + "title": "Egyszeri Jelsz\u00f3 \u00c9rtes\u00edt\u00e9s" }, "totp": { "error": { diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py index 3a51cf8066f..c2f03341d20 100644 --- a/homeassistant/components/auth/login_flow.py +++ b/homeassistant/components/auth/login_flow.py @@ -81,7 +81,8 @@ from . import indieauth async def async_setup(hass, store_result): """Component to allow users to login.""" hass.http.register_view(AuthProvidersView) - hass.http.register_view(LoginFlowIndexView(hass.auth.login_flow)) + hass.http.register_view( + LoginFlowIndexView(hass.auth.login_flow, store_result)) hass.http.register_view( LoginFlowResourceView(hass.auth.login_flow, store_result)) @@ -142,9 +143,10 @@ class LoginFlowIndexView(HomeAssistantView): name = 'api:auth:login_flow' requires_auth = False - def __init__(self, flow_mgr): + def __init__(self, flow_mgr, store_result): """Initialize the flow manager index view.""" self._flow_mgr = flow_mgr + self._store_result = store_result async def get(self, request): """Do not allow index of flows in progress.""" @@ -179,6 +181,12 @@ class LoginFlowIndexView(HomeAssistantView): except data_entry_flow.UnknownStep: return self.json_message('Handler does not support init', 400) + if result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + result.pop('data') + result['result'] = self._store_result( + data['client_id'], result['result']) + return self.json(result) + return self.json(_prepare_result_json(result)) diff --git a/homeassistant/components/automatic/__init__.py b/homeassistant/components/automatic/__init__.py new file mode 100644 index 00000000000..8a1cae16f1e --- /dev/null +++ b/homeassistant/components/automatic/__init__.py @@ -0,0 +1 @@ +"""The automatic component.""" diff --git a/homeassistant/components/device_tracker/automatic.py b/homeassistant/components/automatic/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/automatic.py rename to homeassistant/components/automatic/device_tracker.py diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 5a7b19ce4e3..94776deefb0 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -6,20 +6,20 @@ import logging import voluptuous as vol -from homeassistant.setup import async_prepare_setup_platform -from homeassistant.core import CoreState, Context -from homeassistant.loader import bind_hass from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, - SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID, - EVENT_AUTOMATION_TRIGGERED, ATTR_NAME) + ATTR_ENTITY_ID, ATTR_NAME, CONF_ID, CONF_PLATFORM, + EVENT_AUTOMATION_TRIGGERED, EVENT_HOMEASSISTANT_START, SERVICE_RELOAD, + SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON) +from homeassistant.core import Context, CoreState from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import extract_domain_configs, script, condition +from homeassistant.helpers import condition, extract_domain_configs, script +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.loader import bind_hass +from homeassistant.setup import async_prepare_setup_platform from homeassistant.util.dt import utcnow -import homeassistant.helpers.config_validation as cv DOMAIN = 'automation' DEPENDENCIES = ['group'] @@ -54,9 +54,8 @@ _LOGGER = logging.getLogger(__name__) def _platform_validator(config): """Validate it is a valid platform.""" try: - platform = importlib.import_module( - 'homeassistant.components.automation.{}'.format( - config[CONF_PLATFORM])) + platform = importlib.import_module('.{}'.format(config[CONF_PLATFORM]), + __name__) except ImportError: raise vol.Invalid('Invalid platform specified') from None diff --git a/homeassistant/components/avion/__init__.py b/homeassistant/components/avion/__init__.py new file mode 100644 index 00000000000..79e88222545 --- /dev/null +++ b/homeassistant/components/avion/__init__.py @@ -0,0 +1 @@ +"""The avion component.""" diff --git a/homeassistant/components/light/avion.py b/homeassistant/components/avion/light.py similarity index 97% rename from homeassistant/components/light/avion.py rename to homeassistant/components/avion/light.py index 00fc4f33741..617198b2c8c 100644 --- a/homeassistant/components/light/avion.py +++ b/homeassistant/components/avion/light.py @@ -4,6 +4,7 @@ Support for Avion dimmers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.avion/ """ +import importlib import logging import time @@ -38,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up an Avion switch.""" # pylint: disable=no-member - import avion + avion = importlib.import_module('avion') lights = [] if CONF_USERNAME in config and CONF_PASSWORD in config: @@ -108,7 +109,7 @@ class AvionLight(Light): def set_state(self, brightness): """Set the state of this lamp to the provided brightness.""" # pylint: disable=no-member - import avion + avion = importlib.import_module('avion') # Bluetooth LE is unreliable, and the connection may drop at any # time. Make an effort to re-establish the link. diff --git a/homeassistant/components/awair/__init__.py b/homeassistant/components/awair/__init__.py new file mode 100644 index 00000000000..c9a08cb40b5 --- /dev/null +++ b/homeassistant/components/awair/__init__.py @@ -0,0 +1 @@ +"""The awair component.""" diff --git a/homeassistant/components/sensor/awair.py b/homeassistant/components/awair/sensor.py similarity index 100% rename from homeassistant/components/sensor/awair.py rename to homeassistant/components/awair/sensor.py diff --git a/homeassistant/components/aws/__init__.py b/homeassistant/components/aws/__init__.py new file mode 100644 index 00000000000..a15e56e9de8 --- /dev/null +++ b/homeassistant/components/aws/__init__.py @@ -0,0 +1,176 @@ +"""Support for Amazon Web Services (AWS).""" +import asyncio +import logging +from collections import OrderedDict + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import ATTR_CREDENTIALS, CONF_NAME, CONF_PROFILE_NAME +from homeassistant.helpers import config_validation as cv, discovery + +# Loading the config flow file will register the flow +from . import config_flow # noqa +from .const import ( + CONF_ACCESS_KEY_ID, + CONF_CONTEXT, + CONF_CREDENTIAL_NAME, + CONF_CREDENTIALS, + CONF_NOTIFY, + CONF_REGION, + CONF_SECRET_ACCESS_KEY, + CONF_SERVICE, + DATA_CONFIG, + DATA_HASS_CONFIG, + DATA_SESSIONS, + DOMAIN, +) + +REQUIREMENTS = ["aiobotocore==0.10.2"] + +_LOGGER = logging.getLogger(__name__) + +AWS_CREDENTIAL_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string, + vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string, + vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string, + } +) + +DEFAULT_CREDENTIAL = [{CONF_NAME: "default", CONF_PROFILE_NAME: "default"}] + +SUPPORTED_SERVICES = ["lambda", "sns", "sqs"] + +NOTIFY_PLATFORM_SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME): cv.string, + vol.Required(CONF_SERVICE): vol.All( + cv.string, vol.Lower, vol.In(SUPPORTED_SERVICES) + ), + vol.Required(CONF_REGION): vol.All(cv.string, vol.Lower), + vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string, + vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string, + vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string, + vol.Exclusive(CONF_CREDENTIAL_NAME, ATTR_CREDENTIALS): cv.string, + vol.Optional(CONF_CONTEXT): vol.Coerce(dict), + } +) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional( + CONF_CREDENTIALS, default=DEFAULT_CREDENTIAL + ): vol.All(cv.ensure_list, [AWS_CREDENTIAL_SCHEMA]), + vol.Optional(CONF_NOTIFY, default=[]): vol.All( + cv.ensure_list, [NOTIFY_PLATFORM_SCHEMA] + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + + +async def async_setup(hass, config): + """Set up AWS component.""" + hass.data[DATA_HASS_CONFIG] = config + + conf = config.get(DOMAIN) + if conf is None: + # create a default conf using default profile + conf = CONFIG_SCHEMA({ATTR_CREDENTIALS: DEFAULT_CREDENTIAL}) + + hass.data[DATA_CONFIG] = conf + hass.data[DATA_SESSIONS] = OrderedDict() + + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=conf + ) + ) + + return True + + +async def async_setup_entry(hass, entry): + """Load a config entry. + + Validate and save sessions per aws credential. + """ + config = hass.data.get(DATA_HASS_CONFIG) + conf = hass.data.get(DATA_CONFIG) + + if entry.source == config_entries.SOURCE_IMPORT: + if conf is None: + # user removed config from configuration.yaml, abort setup + hass.async_create_task( + hass.config_entries.async_remove(entry.entry_id) + ) + return False + + if conf != entry.data: + # user changed config from configuration.yaml, use conf to setup + hass.config_entries.async_update_entry(entry, data=conf) + + if conf is None: + conf = CONFIG_SCHEMA({DOMAIN: entry.data})[DOMAIN] + + # validate credentials and create sessions + validation = True + tasks = [] + for cred in conf[ATTR_CREDENTIALS]: + tasks.append(_validate_aws_credentials(hass, cred)) + if tasks: + results = await asyncio.gather(*tasks, return_exceptions=True) + for index, result in enumerate(results): + name = conf[ATTR_CREDENTIALS][index][CONF_NAME] + if isinstance(result, Exception): + _LOGGER.error( + "Validating credential [%s] failed: %s", + name, + result, + exc_info=result, + ) + validation = False + else: + hass.data[DATA_SESSIONS][name] = result + + # set up notify platform, no entry support for notify component yet, + # have to use discovery to load platform. + for notify_config in conf[CONF_NOTIFY]: + hass.async_create_task( + discovery.async_load_platform( + hass, "notify", DOMAIN, notify_config, config + ) + ) + + return validation + + +async def _validate_aws_credentials(hass, credential): + """Validate AWS credential config.""" + import aiobotocore + + aws_config = credential.copy() + del aws_config[CONF_NAME] + + profile = aws_config.get(CONF_PROFILE_NAME) + + if profile is not None: + session = aiobotocore.AioSession(profile=profile, loop=hass.loop) + del aws_config[CONF_PROFILE_NAME] + if CONF_ACCESS_KEY_ID in aws_config: + del aws_config[CONF_ACCESS_KEY_ID] + if CONF_SECRET_ACCESS_KEY in aws_config: + del aws_config[CONF_SECRET_ACCESS_KEY] + else: + session = aiobotocore.AioSession(loop=hass.loop) + + async with session.create_client("iam", **aws_config) as client: + await client.get_user() + + return session diff --git a/homeassistant/components/aws/config_flow.py b/homeassistant/components/aws/config_flow.py new file mode 100644 index 00000000000..c21f2a94137 --- /dev/null +++ b/homeassistant/components/aws/config_flow.py @@ -0,0 +1,22 @@ +"""Config flow for AWS component.""" + +from homeassistant import config_entries + +from .const import DOMAIN + + +@config_entries.HANDLERS.register(DOMAIN) +class AWSFlowHandler(config_entries.ConfigFlow): + """Handle a config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH + + async def async_step_import(self, user_input): + """Import a config entry.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + return self.async_create_entry( + title="configuration.yaml", data=user_input + ) diff --git a/homeassistant/components/aws/const.py b/homeassistant/components/aws/const.py new file mode 100644 index 00000000000..4fa88566934 --- /dev/null +++ b/homeassistant/components/aws/const.py @@ -0,0 +1,16 @@ +"""Constant for AWS component.""" +DOMAIN = "aws" + +DATA_CONFIG = "aws_config" +DATA_HASS_CONFIG = "aws_hass_config" +DATA_SESSIONS = "aws_sessions" + +CONF_ACCESS_KEY_ID = "aws_access_key_id" +CONF_CONTEXT = "context" +CONF_CREDENTIAL_NAME = "credential_name" +CONF_CREDENTIALS = 'credentials' +CONF_NOTIFY = "notify" +CONF_PROFILE_NAME = "profile_name" +CONF_REGION = "region_name" +CONF_SECRET_ACCESS_KEY = "aws_secret_access_key" +CONF_SERVICE = "service" diff --git a/homeassistant/components/aws/notify.py b/homeassistant/components/aws/notify.py new file mode 100644 index 00000000000..48b80b64ce2 --- /dev/null +++ b/homeassistant/components/aws/notify.py @@ -0,0 +1,243 @@ +"""AWS platform for notify component.""" +import asyncio +import base64 +import json +import logging + +from homeassistant.components.notify import ( + ATTR_TARGET, + ATTR_TITLE, + ATTR_TITLE_DEFAULT, + BaseNotificationService, +) +from homeassistant.const import CONF_PLATFORM, CONF_NAME +from homeassistant.helpers.json import JSONEncoder +from .const import ( + CONF_CONTEXT, + CONF_CREDENTIAL_NAME, + CONF_PROFILE_NAME, + CONF_REGION, + CONF_SERVICE, + DATA_SESSIONS, +) + +DEPENDENCIES = ["aws"] + +_LOGGER = logging.getLogger(__name__) + + +async def get_available_regions(hass, service): + """Get available regions for a service.""" + import aiobotocore + + session = aiobotocore.get_session() + # get_available_regions is not a coroutine since it does not perform + # network I/O. But it still perform file I/O heavily, so put it into + # an executor thread to unblock event loop + return await hass.async_add_executor_job( + session.get_available_regions, service + ) + + +async def async_get_service(hass, config, discovery_info=None): + """Get the AWS notification service.""" + if discovery_info is None: + _LOGGER.error('Please config aws notify platform in aws component') + return None + + import aiobotocore + + session = None + + conf = discovery_info + + service = conf[CONF_SERVICE] + region_name = conf[CONF_REGION] + + available_regions = await get_available_regions(hass, service) + if region_name not in available_regions: + _LOGGER.error( + "Region %s is not available for %s service, must in %s", + region_name, service, available_regions + ) + return None + + aws_config = conf.copy() + + del aws_config[CONF_SERVICE] + del aws_config[CONF_REGION] + if CONF_PLATFORM in aws_config: + del aws_config[CONF_PLATFORM] + if CONF_NAME in aws_config: + del aws_config[CONF_NAME] + if CONF_CONTEXT in aws_config: + del aws_config[CONF_CONTEXT] + + if not aws_config: + # no platform config, use the first aws component credential instead + if hass.data[DATA_SESSIONS]: + session = next(iter(hass.data[DATA_SESSIONS].values())) + else: + _LOGGER.error( + "Missing aws credential for %s", config[CONF_NAME] + ) + return None + + if session is None: + credential_name = aws_config.get(CONF_CREDENTIAL_NAME) + if credential_name is not None: + session = hass.data[DATA_SESSIONS].get(credential_name) + if session is None: + _LOGGER.warning( + "No available aws session for %s", credential_name + ) + del aws_config[CONF_CREDENTIAL_NAME] + + if session is None: + profile = aws_config.get(CONF_PROFILE_NAME) + if profile is not None: + session = aiobotocore.AioSession(profile=profile, loop=hass.loop) + del aws_config[CONF_PROFILE_NAME] + else: + session = aiobotocore.AioSession(loop=hass.loop) + + aws_config[CONF_REGION] = region_name + + if service == "lambda": + context_str = json.dumps( + {"custom": conf.get(CONF_CONTEXT, {})}, cls=JSONEncoder + ) + context_b64 = base64.b64encode(context_str.encode("utf-8")) + context = context_b64.decode("utf-8") + return AWSLambda(session, aws_config, context) + + if service == "sns": + return AWSSNS(session, aws_config) + + if service == "sqs": + return AWSSQS(session, aws_config) + + # should not reach here since service was checked in schema + return None + + +class AWSNotify(BaseNotificationService): + """Implement the notification service for the AWS service.""" + + def __init__(self, session, aws_config): + """Initialize the service.""" + self.session = session + self.aws_config = aws_config + + +class AWSLambda(AWSNotify): + """Implement the notification service for the AWS Lambda service.""" + + service = "lambda" + + def __init__(self, session, aws_config, context): + """Initialize the service.""" + super().__init__(session, aws_config) + self.context = context + + async def async_send_message(self, message="", **kwargs): + """Send notification to specified LAMBDA ARN.""" + if not kwargs.get(ATTR_TARGET): + _LOGGER.error("At least one target is required") + return + + cleaned_kwargs = {k: v for k, v in kwargs.items() if v is not None} + payload = {"message": message} + payload.update(cleaned_kwargs) + json_payload = json.dumps(payload) + + async with self.session.create_client( + self.service, **self.aws_config + ) as client: + tasks = [] + for target in kwargs.get(ATTR_TARGET, []): + tasks.append( + client.invoke( + FunctionName=target, + Payload=json_payload, + ClientContext=self.context, + ) + ) + + if tasks: + await asyncio.gather(*tasks) + + +class AWSSNS(AWSNotify): + """Implement the notification service for the AWS SNS service.""" + + service = "sns" + + async def async_send_message(self, message="", **kwargs): + """Send notification to specified SNS ARN.""" + if not kwargs.get(ATTR_TARGET): + _LOGGER.error("At least one target is required") + return + + message_attributes = { + k: {"StringValue": json.dumps(v), "DataType": "String"} + for k, v in kwargs.items() + if v is not None + } + subject = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) + + async with self.session.create_client( + self.service, **self.aws_config + ) as client: + tasks = [] + for target in kwargs.get(ATTR_TARGET, []): + tasks.append( + client.publish( + TargetArn=target, + Message=message, + Subject=subject, + MessageAttributes=message_attributes, + ) + ) + + if tasks: + await asyncio.gather(*tasks) + + +class AWSSQS(AWSNotify): + """Implement the notification service for the AWS SQS service.""" + + service = "sqs" + + async def async_send_message(self, message="", **kwargs): + """Send notification to specified SQS ARN.""" + if not kwargs.get(ATTR_TARGET): + _LOGGER.error("At least one target is required") + return + + cleaned_kwargs = {k: v for k, v in kwargs.items() if v is not None} + message_body = {"message": message} + message_body.update(cleaned_kwargs) + json_body = json.dumps(message_body) + message_attributes = {} + for key, val in cleaned_kwargs.items(): + message_attributes[key] = { + "StringValue": json.dumps(val), + "DataType": "String", + } + + async with self.session.create_client( + self.service, **self.aws_config + ) as client: + tasks = [] + for target in kwargs.get(ATTR_TARGET, []): + tasks.append( + client.send_message( + QueueUrl=target, + MessageBody=json_body, + MessageAttributes=message_attributes, + ) + ) + + if tasks: + await asyncio.gather(*tasks) diff --git a/homeassistant/components/aws_lambda/__init__.py b/homeassistant/components/aws_lambda/__init__.py new file mode 100644 index 00000000000..f6d86d02e93 --- /dev/null +++ b/homeassistant/components/aws_lambda/__init__.py @@ -0,0 +1 @@ +"""The aws_lambda component.""" diff --git a/homeassistant/components/notify/aws_lambda.py b/homeassistant/components/aws_lambda/notify.py similarity index 85% rename from homeassistant/components/notify/aws_lambda.py rename to homeassistant/components/aws_lambda/notify.py index 17df1ba8f5a..d7ebb40d19a 100644 --- a/homeassistant/components/notify/aws_lambda.py +++ b/homeassistant/components/aws_lambda/notify.py @@ -4,19 +4,19 @@ AWS Lambda platform for notify component. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.aws_lambda/ """ -import logging -import json import base64 +import json +import logging import voluptuous as vol -from homeassistant.const import ( - CONF_PLATFORM, CONF_NAME) -from homeassistant.components.notify import ( - ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) +from homeassistant.const import CONF_NAME, CONF_PLATFORM import homeassistant.helpers.config_validation as cv from homeassistant.helpers.json import JSONEncoder +from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA, + BaseNotificationService) + REQUIREMENTS = ['boto3==1.9.16'] _LOGGER = logging.getLogger(__name__) @@ -39,6 +39,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the AWS Lambda notification service.""" + _LOGGER.warning( + "aws_lambda notify platform is deprecated, please replace it" + " with aws component. This config will become invalid in version 0.92." + " See https://www.home-assistant.io/components/aws/ for details." + ) + context_str = json.dumps({'custom': config[CONF_CONTEXT]}, cls=JSONEncoder) context_b64 = base64.b64encode(context_str.encode('utf-8')) context = context_b64.decode('utf-8') diff --git a/homeassistant/components/aws_sns/__init__.py b/homeassistant/components/aws_sns/__init__.py new file mode 100644 index 00000000000..b51698ce0dc --- /dev/null +++ b/homeassistant/components/aws_sns/__init__.py @@ -0,0 +1 @@ +"""The aws_sns component.""" diff --git a/homeassistant/components/notify/aws_sns.py b/homeassistant/components/aws_sns/notify.py similarity index 86% rename from homeassistant/components/notify/aws_sns.py rename to homeassistant/components/aws_sns/notify.py index 065898bcb85..09018562cb8 100644 --- a/homeassistant/components/notify/aws_sns.py +++ b/homeassistant/components/aws_sns/notify.py @@ -4,18 +4,18 @@ AWS SNS platform for notify component. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.aws_sns/ """ -import logging import json +import logging import voluptuous as vol -from homeassistant.const import ( - CONF_PLATFORM, CONF_NAME) -from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_TARGET, PLATFORM_SCHEMA, - BaseNotificationService) +from homeassistant.const import CONF_NAME, CONF_PLATFORM import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import ( + ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, + BaseNotificationService) + _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ["boto3==1.9.16"] @@ -35,6 +35,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the AWS SNS notification service.""" + _LOGGER.warning( + "aws_sns notify platform is deprecated, please replace it" + " with aws component. This config will become invalid in version 0.92." + " See https://www.home-assistant.io/components/aws/ for details." + ) + import boto3 aws_config = config.copy() diff --git a/homeassistant/components/aws_sqs/__init__.py b/homeassistant/components/aws_sqs/__init__.py new file mode 100644 index 00000000000..79b29a76009 --- /dev/null +++ b/homeassistant/components/aws_sqs/__init__.py @@ -0,0 +1 @@ +"""The aws_sqs component.""" diff --git a/homeassistant/components/notify/aws_sqs.py b/homeassistant/components/aws_sqs/notify.py similarity index 84% rename from homeassistant/components/notify/aws_sqs.py rename to homeassistant/components/aws_sqs/notify.py index 78e71bde97a..eff9018bae9 100644 --- a/homeassistant/components/notify/aws_sqs.py +++ b/homeassistant/components/aws_sqs/notify.py @@ -4,17 +4,17 @@ AWS SQS platform for notify component. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.aws_sqs/ """ -import logging import json +import logging import voluptuous as vol -from homeassistant.const import ( - CONF_PLATFORM, CONF_NAME) -from homeassistant.components.notify import ( - ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) +from homeassistant.const import CONF_NAME, CONF_PLATFORM import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA, + BaseNotificationService) + _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ["boto3==1.9.16"] @@ -34,6 +34,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the AWS SQS notification service.""" + _LOGGER.warning( + "aws_sqs notify platform is deprecated, please replace it" + " with aws component. This config will become invalid in version 0.92." + " See https://www.home-assistant.io/components/aws/ for details." + ) + import boto3 aws_config = config.copy() diff --git a/homeassistant/components/axis/.translations/ca.json b/homeassistant/components/axis/.translations/ca.json new file mode 100644 index 00000000000..5e98dbf3418 --- /dev/null +++ b/homeassistant/components/axis/.translations/ca.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "bad_config_file": "Dades incorrectes del fitxer de configuraci\u00f3", + "link_local_address": "L'enlla\u00e7 d'adreces locals no est\u00e0 disponible" + }, + "error": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "device_unavailable": "El dispositiu no est\u00e0 disponible", + "faulty_credentials": "Credencials d'usuari incorrectes" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "password": "Contrasenya", + "port": "Port", + "username": "Nom d'usuari" + }, + "title": "Configuraci\u00f3 de dispositiu Axis" + } + }, + "title": "Dispositiu Axis" + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/.translations/da.json b/homeassistant/components/axis/.translations/da.json new file mode 100644 index 00000000000..4657d2fb355 --- /dev/null +++ b/homeassistant/components/axis/.translations/da.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Enheden er allerede konfigureret" + }, + "error": { + "already_configured": "Enheden er allerede konfigureret", + "device_unavailable": "Enheden er ikke tilg\u00e6ngelig", + "faulty_credentials": "Ugyldige legitimationsoplysninger" + }, + "step": { + "user": { + "data": { + "host": "V\u00e6rt", + "password": "Adgangskode", + "port": "Port", + "username": "Brugernavn" + }, + "title": "Konfigurer Axis enhed" + } + }, + "title": "Axis enhed" + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/.translations/de.json b/homeassistant/components/axis/.translations/de.json new file mode 100644 index 00000000000..c979068b922 --- /dev/null +++ b/homeassistant/components/axis/.translations/de.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "bad_config_file": "Fehlerhafte Daten aus der Konfigurationsdatei", + "link_local_address": "Link-local Adressen werden nicht unterst\u00fctzt" + }, + "error": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "device_unavailable": "Ger\u00e4t ist nicht verf\u00fcgbar", + "faulty_credentials": "Ung\u00fcltige Anmeldeinformationen" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + }, + "title": "Axis Ger\u00e4t einrichten" + } + }, + "title": "Axis Ger\u00e4t" + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/.translations/en.json b/homeassistant/components/axis/.translations/en.json new file mode 100644 index 00000000000..6c5933dfd97 --- /dev/null +++ b/homeassistant/components/axis/.translations/en.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured", + "bad_config_file": "Bad data from config file", + "link_local_address": "Link local addresses are not supported" + }, + "error": { + "already_configured": "Device is already configured", + "device_unavailable": "Device is not available", + "faulty_credentials": "Bad user credentials" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "port": "Port", + "username": "Username" + }, + "title": "Set up Axis device" + } + }, + "title": "Axis device" + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/.translations/hu.json b/homeassistant/components/axis/.translations/hu.json new file mode 100644 index 00000000000..cbf055e2fba --- /dev/null +++ b/homeassistant/components/axis/.translations/hu.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Hoszt" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/.translations/ko.json b/homeassistant/components/axis/.translations/ko.json new file mode 100644 index 00000000000..f1543afbae8 --- /dev/null +++ b/homeassistant/components/axis/.translations/ko.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "\uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "bad_config_file": "\uad6c\uc131 \ud30c\uc77c\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "link_local_address": "\ub85c\uceec \uc8fc\uc18c \uc5f0\uacb0\uc740 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" + }, + "error": { + "already_configured": "\uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "device_unavailable": "\uc7a5\uce58\ub97c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "faulty_credentials": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ud639\uc740 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "password": "\ube44\ubc00\ubc88\ud638", + "port": "\ud3ec\ud2b8", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "title": "Axis \uc7a5\uce58 \uc124\uc815" + } + }, + "title": "Axis \uc7a5\uce58" + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/.translations/lb.json b/homeassistant/components/axis/.translations/lb.json new file mode 100644 index 00000000000..e0f6ebc1553 --- /dev/null +++ b/homeassistant/components/axis/.translations/lb.json @@ -0,0 +1,21 @@ +{ + "config": { + "error": { + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "device_unavailable": "Apparat ass net erreechbar", + "faulty_credentials": "Ong\u00eblteg Login Informatioune" + }, + "step": { + "user": { + "data": { + "host": "Apparat", + "password": "Passwuert", + "port": "Port", + "username": "Benotzernumm" + }, + "title": "Axis Apparat ariichten" + } + }, + "title": "Axis Apparat" + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/.translations/pl.json b/homeassistant/components/axis/.translations/pl.json new file mode 100644 index 00000000000..7903dc63bf8 --- /dev/null +++ b/homeassistant/components/axis/.translations/pl.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "bad_config_file": "B\u0142\u0119dne dane z pliku konfiguracyjnego", + "link_local_address": "Po\u0142\u0105czenie lokalnego adresu nie jest obs\u0142ugiwane" + }, + "error": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "device_unavailable": "Urz\u0105dzenie jest niedost\u0119pne", + "faulty_credentials": "B\u0142\u0119dne dane uwierzytelniaj\u0105ce" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Has\u0142o", + "port": "Port", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Konfiguracja urz\u0105dzenia Axis" + } + }, + "title": "Urz\u0105dzenie Axis" + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/.translations/ru.json b/homeassistant/components/axis/.translations/ru.json new file mode 100644 index 00000000000..f303aa947ea --- /dev/null +++ b/homeassistant/components/axis/.translations/ru.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", + "bad_config_file": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438", + "link_local_address": "\u0421\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f" + }, + "error": { + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", + "device_unavailable": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e", + "faulty_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "title": "Axis" + } + }, + "title": "Axis" + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/.translations/sl.json b/homeassistant/components/axis/.translations/sl.json new file mode 100644 index 00000000000..41d29949873 --- /dev/null +++ b/homeassistant/components/axis/.translations/sl.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Naprava je \u017ee konfigurirana", + "bad_config_file": "Napa\u010dni podatki iz konfiguracijske datoteke", + "link_local_address": "Lokalni naslovi povezave niso podprti" + }, + "error": { + "already_configured": "Naprava je \u017ee konfigurirana", + "device_unavailable": "Naprava ni na voljo", + "faulty_credentials": "Napa\u010dni uporabni\u0161ki podatki" + }, + "step": { + "user": { + "data": { + "host": "Gostitelj", + "password": "Geslo", + "port": "Vrata", + "username": "Uporabni\u0161ko ime" + }, + "title": "Nastavite plo\u0161\u010dek" + } + }, + "title": "Plo\u0161\u010dek" + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/.translations/zh-Hant.json b/homeassistant/components/axis/.translations/zh-Hant.json new file mode 100644 index 00000000000..ac9f3ceb2b6 --- /dev/null +++ b/homeassistant/components/axis/.translations/zh-Hant.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "bad_config_file": "\u8a2d\u5b9a\u6a94\u6848\u8cc7\u6599\u7121\u6548", + "link_local_address": "\u4e0d\u652f\u63f4\u9023\u7d50\u672c\u5730\u7aef\u4f4d\u5740" + }, + "error": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "device_unavailable": "\u88dd\u7f6e\u7121\u6cd5\u4f7f\u7528", + "faulty_credentials": "\u4f7f\u7528\u8005\u6191\u8b49\u7121\u6548" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "password": "\u5bc6\u78bc", + "port": "\u901a\u8a0a\u57e0", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "title": "\u8a2d\u5b9a Axis \u88dd\u7f6e" + } + }, + "title": "Axis \u88dd\u7f6e" + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index df723272a7a..53087f2682c 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -1,262 +1,78 @@ """Support for Axis devices.""" -import logging import voluptuous as vol -from homeassistant.components.discovery import SERVICE_AXIS +from homeassistant import config_entries from homeassistant.const import ( - ATTR_LOCATION, CONF_EVENT, CONF_HOST, CONF_INCLUDE, - CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TRIGGER_TIME, CONF_USERNAME, + CONF_DEVICE, CONF_HOST, CONF_NAME, CONF_TRIGGER_TIME, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import discovery -from homeassistant.helpers.dispatcher import dispatcher_send -from homeassistant.util.json import load_json, save_json -REQUIREMENTS = ['axis==16'] +from .config_flow import configured_devices, DEVICE_SCHEMA +from .const import CONF_CAMERA, CONF_EVENTS, DEFAULT_TRIGGER_TIME, DOMAIN +from .device import AxisNetworkDevice, get_device -_LOGGER = logging.getLogger(__name__) - -DOMAIN = 'axis' -CONFIG_FILE = 'axis.conf' - -EVENT_TYPES = ['motion', 'vmd3', 'pir', 'sound', - 'daynight', 'tampering', 'input'] - -PLATFORMS = ['camera'] - -AXIS_INCLUDE = EVENT_TYPES + PLATFORMS - -AXIS_DEFAULT_HOST = '192.168.0.90' -AXIS_DEFAULT_USERNAME = 'root' -AXIS_DEFAULT_PASSWORD = 'pass' -DEFAULT_PORT = 80 - -DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_INCLUDE): - vol.All(cv.ensure_list, [vol.In(AXIS_INCLUDE)]), - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_HOST, default=AXIS_DEFAULT_HOST): cv.string, - vol.Optional(CONF_USERNAME, default=AXIS_DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, default=AXIS_DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_TRIGGER_TIME, default=0): cv.positive_int, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(ATTR_LOCATION, default=''): cv.string, -}) +REQUIREMENTS = ['axis==17'] CONFIG_SCHEMA = vol.Schema({ DOMAIN: cv.schema_with_slug_keys(DEVICE_SCHEMA), }, extra=vol.ALLOW_EXTRA) -SERVICE_VAPIX_CALL = 'vapix_call' -SERVICE_VAPIX_CALL_RESPONSE = 'vapix_call_response' -SERVICE_CGI = 'cgi' -SERVICE_ACTION = 'action' -SERVICE_PARAM = 'param' -SERVICE_DEFAULT_CGI = 'param.cgi' -SERVICE_DEFAULT_ACTION = 'update' -SERVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Required(SERVICE_PARAM): cv.string, - vol.Optional(SERVICE_CGI, default=SERVICE_DEFAULT_CGI): cv.string, - vol.Optional(SERVICE_ACTION, default=SERVICE_DEFAULT_ACTION): cv.string, -}) - - -def request_configuration(hass, config, name, host, serialnumber): - """Request configuration steps from the user.""" - configurator = hass.components.configurator - - def configuration_callback(callback_data): - """Call when configuration is submitted.""" - if CONF_INCLUDE not in callback_data: - configurator.notify_errors( - request_id, "Functionality mandatory.") - return False - - callback_data[CONF_INCLUDE] = callback_data[CONF_INCLUDE].split() - callback_data[CONF_HOST] = host - - if CONF_NAME not in callback_data: - callback_data[CONF_NAME] = name - - try: - device_config = DEVICE_SCHEMA(callback_data) - except vol.Invalid: - configurator.notify_errors( - request_id, "Bad input, please check spelling.") - return False - - if setup_device(hass, config, device_config): - config_file = load_json(hass.config.path(CONFIG_FILE)) - config_file[serialnumber] = dict(device_config) - save_json(hass.config.path(CONFIG_FILE), config_file) - configurator.request_done(request_id) - else: - configurator.notify_errors( - request_id, "Failed to register, please try again.") - return False - - title = '{} ({})'.format(name, host) - request_id = configurator.request_config( - title, configuration_callback, - description='Functionality: ' + str(AXIS_INCLUDE), - entity_picture="/static/images/logo_axis.png", - link_name='Axis platform documentation', - link_url='https://home-assistant.io/components/axis/', - submit_caption="Confirm", - fields=[ - {'id': CONF_NAME, - 'name': "Device name", - 'type': 'text'}, - {'id': CONF_USERNAME, - 'name': "User name", - 'type': 'text'}, - {'id': CONF_PASSWORD, - 'name': 'Password', - 'type': 'password'}, - {'id': CONF_INCLUDE, - 'name': "Device functionality (space separated list)", - 'type': 'text'}, - {'id': ATTR_LOCATION, - 'name': "Physical location of device (optional)", - 'type': 'text'}, - {'id': CONF_PORT, - 'name': "HTTP port (default=80)", - 'type': 'number'}, - {'id': CONF_TRIGGER_TIME, - 'name': "Sensor update interval (optional)", - 'type': 'number'}, - ] - ) - - -def setup(hass, config): +async def async_setup(hass, config): """Set up for Axis devices.""" - hass.data[DOMAIN] = {} - - def _shutdown(call): - """Stop the event stream on shutdown.""" - for serialnumber, device in hass.data[DOMAIN].items(): - _LOGGER.info("Stopping event stream for %s.", serialnumber) - device.stop() - - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown) - - def axis_device_discovered(service, discovery_info): - """Call when axis devices has been found.""" - host = discovery_info[CONF_HOST] - name = discovery_info['hostname'] - serialnumber = discovery_info['properties']['macaddress'] - - if serialnumber not in hass.data[DOMAIN]: - config_file = load_json(hass.config.path(CONFIG_FILE)) - if serialnumber in config_file: - # Device config previously saved to file - try: - device_config = DEVICE_SCHEMA(config_file[serialnumber]) - device_config[CONF_HOST] = host - except vol.Invalid as err: - _LOGGER.error("Bad data from %s. %s", CONFIG_FILE, err) - return False - if not setup_device(hass, config, device_config): - _LOGGER.error( - "Couldn't set up %s", device_config[CONF_NAME]) - else: - # New device, create configuration request for UI - request_configuration(hass, config, name, host, serialnumber) - else: - # Device already registered, but on a different IP - device = hass.data[DOMAIN][serialnumber] - device.config.host = host - dispatcher_send(hass, DOMAIN + '_' + device.name + '_new_ip', host) - - # Register discovery service - discovery.listen(hass, SERVICE_AXIS, axis_device_discovered) - if DOMAIN in config: - for device in config[DOMAIN]: - device_config = config[DOMAIN][device] + + for device_name, device_config in config[DOMAIN].items(): + if CONF_NAME not in device_config: - device_config[CONF_NAME] = device - if not setup_device(hass, config, device_config): - _LOGGER.error("Couldn't set up %s", device_config[CONF_NAME]) + device_config[CONF_NAME] = device_name - def vapix_service(call): - """Service to send a message.""" - for device in hass.data[DOMAIN].values(): - if device.name == call.data[CONF_NAME]: - response = device.vapix.do_request( - call.data[SERVICE_CGI], - call.data[SERVICE_ACTION], - call.data[SERVICE_PARAM]) - hass.bus.fire(SERVICE_VAPIX_CALL_RESPONSE, response) - return True - _LOGGER.info("Couldn't find device %s", call.data[CONF_NAME]) - return False + if device_config[CONF_HOST] not in configured_devices(hass): + hass.async_create_task(hass.config_entries.flow.async_init( + DOMAIN, context={'source': config_entries.SOURCE_IMPORT}, + data=device_config + )) - # Register service with Home Assistant. - hass.services.register( - DOMAIN, SERVICE_VAPIX_CALL, vapix_service, schema=SERVICE_SCHEMA) return True -def setup_device(hass, config, device_config): - """Set up an Axis device.""" - import axis +async def async_setup_entry(hass, config_entry): + """Set up the Axis component.""" + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} - def signal_callback(action, event): - """Call to configure events when initialized on event stream.""" - if action == 'add': - event_config = { - CONF_EVENT: event, - CONF_NAME: device_config[CONF_NAME], - ATTR_LOCATION: device_config[ATTR_LOCATION], - CONF_TRIGGER_TIME: device_config[CONF_TRIGGER_TIME] - } - component = event.event_platform - discovery.load_platform( - hass, component, DOMAIN, event_config, config) + if not config_entry.options: + await async_populate_options(hass, config_entry) - event_types = [ - event - for event in device_config[CONF_INCLUDE] - if event in EVENT_TYPES - ] + device = AxisNetworkDevice(hass, config_entry) - device = axis.AxisDevice( - loop=hass.loop, host=device_config[CONF_HOST], - username=device_config[CONF_USERNAME], - password=device_config[CONF_PASSWORD], - port=device_config[CONF_PORT], web_proto='http', - event_types=event_types, signal=signal_callback) - - try: - hass.data[DOMAIN][device.vapix.serial_number] = device - - except axis.Unauthorized: - _LOGGER.error("Credentials for %s are faulty", - device_config[CONF_HOST]) + if not await device.async_setup(): return False - except axis.RequestError: - return False + hass.data[DOMAIN][device.serial] = device - device.name = device_config[CONF_NAME] + await device.async_update_device_registry() - for component in device_config[CONF_INCLUDE]: - if component == 'camera': - camera_config = { - CONF_NAME: device_config[CONF_NAME], - CONF_HOST: device_config[CONF_HOST], - CONF_PORT: device_config[CONF_PORT], - CONF_USERNAME: device_config[CONF_USERNAME], - CONF_PASSWORD: device_config[CONF_PASSWORD] - } - discovery.load_platform( - hass, component, DOMAIN, camera_config, config) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.shutdown) - if event_types: - hass.add_job(device.start) return True + + +async def async_populate_options(hass, config_entry): + """Populate default options for device.""" + from axis.vapix import VAPIX_IMAGE_FORMAT + + device = await get_device(hass, config_entry.data[CONF_DEVICE]) + + supported_formats = device.vapix.get_param(VAPIX_IMAGE_FORMAT) + + camera = bool(supported_formats) + + options = { + CONF_CAMERA: camera, + CONF_EVENTS: True, + CONF_TRIGGER_TIME: DEFAULT_TRIGGER_TIME + } + + hass.config_entries.async_update_entry(config_entry, options=options) diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index 11014dc4bc9..6d373dd638f 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -1,75 +1,91 @@ """Support for Axis binary sensors.""" + from datetime import timedelta -import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.const import ( - ATTR_LOCATION, CONF_EVENT, CONF_NAME, CONF_TRIGGER_TIME) +from homeassistant.const import CONF_MAC, CONF_TRIGGER_TIME from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util.dt import utcnow -DEPENDENCIES = ['axis'] +from .const import DOMAIN as AXIS_DOMAIN, LOGGER -_LOGGER = logging.getLogger(__name__) +DEPENDENCIES = [AXIS_DOMAIN] -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Axis binary devices.""" - add_entities([AxisBinarySensor(discovery_info)], True) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up a Axis binary sensor.""" + serial_number = config_entry.data[CONF_MAC] + device = hass.data[AXIS_DOMAIN][serial_number] + + @callback + def async_add_sensor(event): + """Add binary sensor from Axis device.""" + async_add_entities([AxisBinarySensor(event, device)], True) + + device.listeners.append(async_dispatcher_connect( + hass, device.event_new_sensor, async_add_sensor)) class AxisBinarySensor(BinarySensorDevice): """Representation of a binary Axis event.""" - def __init__(self, event_config): + def __init__(self, event, device): """Initialize the Axis binary sensor.""" - self.axis_event = event_config[CONF_EVENT] - self.device_name = event_config[CONF_NAME] - self.location = event_config[ATTR_LOCATION] - self.delay = event_config[CONF_TRIGGER_TIME] + self.event = event + self.device = device + self.delay = device.config_entry.options[CONF_TRIGGER_TIME] self.remove_timer = None async def async_added_to_hass(self): """Subscribe sensors events.""" - self.axis_event.callback = self._update_callback + self.event.register_callback(self.update_callback) - def _update_callback(self): + def update_callback(self): """Update the sensor's state, if needed.""" + delay = self.device.config_entry.options[CONF_TRIGGER_TIME] + if self.remove_timer is not None: self.remove_timer() self.remove_timer = None - if self.delay == 0 or self.is_on: + if delay == 0 or self.is_on: self.schedule_update_ha_state() - else: # Run timer to delay updating the state - @callback - def _delay_update(now): - """Timer callback for sensor update.""" - _LOGGER.debug("%s called delayed (%s sec) update", - self.name, self.delay) - self.async_schedule_update_ha_state() - self.remove_timer = None + return - self.remove_timer = async_track_point_in_utc_time( - self.hass, _delay_update, - utcnow() + timedelta(seconds=self.delay)) + @callback + def _delay_update(now): + """Timer callback for sensor update.""" + LOGGER.debug("%s called delayed (%s sec) update", self.name, delay) + self.async_schedule_update_ha_state() + self.remove_timer = None + + self.remove_timer = async_track_point_in_utc_time( + self.hass, _delay_update, + utcnow() + timedelta(seconds=delay)) @property def is_on(self): """Return true if event is active.""" - return self.axis_event.is_tripped + return self.event.is_tripped @property def name(self): """Return the name of the event.""" - return '{}_{}_{}'.format( - self.device_name, self.axis_event.event_type, self.axis_event.id) + return '{} {} {}'.format( + self.device.name, self.event.event_type, self.event.id) @property def device_class(self): """Return the class of the event.""" - return self.axis_event.event_class + return self.event.event_class + + @property + def unique_id(self): + """Return a unique identifier for this device.""" + return '{}-{}-{}'.format( + self.device.serial, self.event.topic, self.event.id) @property def should_poll(self): @@ -77,10 +93,8 @@ class AxisBinarySensor(BinarySensorDevice): return False @property - def device_state_attributes(self): - """Return the state attributes of the event.""" - attr = {} - - attr[ATTR_LOCATION] = self.location - - return attr + def device_info(self): + """Return a device description for device registry.""" + return { + 'identifiers': {(AXIS_DOMAIN, self.device.serial)} + } diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index b9e969efec1..45801257d00 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -1,58 +1,71 @@ """Support for Axis camera streaming.""" -import logging -from homeassistant.components.camera.mjpeg import ( +from homeassistant.components.mjpeg.camera import ( CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera, filter_urllib3_logging) from homeassistant.const import ( - CONF_AUTHENTICATION, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION) -from homeassistant.helpers.dispatcher import dispatcher_connect + CONF_AUTHENTICATION, CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_NAME, + CONF_PASSWORD, CONF_PORT, CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION) +from homeassistant.helpers.dispatcher import async_dispatcher_connect -_LOGGER = logging.getLogger(__name__) +from .const import DOMAIN as AXIS_DOMAIN -DOMAIN = 'axis' -DEPENDENCIES = [DOMAIN] +DEPENDENCIES = [AXIS_DOMAIN] + +AXIS_IMAGE = 'http://{}:{}/axis-cgi/jpg/image.cgi' +AXIS_VIDEO = 'http://{}:{}/axis-cgi/mjpg/video.cgi' -def _get_image_url(host, port, mode): - """Set the URL to get the image.""" - if mode == 'mjpeg': - return 'http://{}:{}/axis-cgi/mjpg/video.cgi'.format(host, port) - if mode == 'single': - return 'http://{}:{}/axis-cgi/jpg/image.cgi'.format(host, port) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Axis camera.""" +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Axis camera video stream.""" filter_urllib3_logging() - camera_config = { - CONF_NAME: discovery_info[CONF_NAME], - CONF_USERNAME: discovery_info[CONF_USERNAME], - CONF_PASSWORD: discovery_info[CONF_PASSWORD], - CONF_MJPEG_URL: _get_image_url( - discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]), - 'mjpeg'), - CONF_STILL_IMAGE_URL: _get_image_url( - discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]), - 'single'), + serial_number = config_entry.data[CONF_MAC] + device = hass.data[AXIS_DOMAIN][serial_number] + + config = { + CONF_NAME: config_entry.data[CONF_NAME], + CONF_USERNAME: config_entry.data[CONF_DEVICE][CONF_USERNAME], + CONF_PASSWORD: config_entry.data[CONF_DEVICE][CONF_PASSWORD], + CONF_MJPEG_URL: AXIS_VIDEO.format( + config_entry.data[CONF_DEVICE][CONF_HOST], + config_entry.data[CONF_DEVICE][CONF_PORT]), + CONF_STILL_IMAGE_URL: AXIS_IMAGE.format( + config_entry.data[CONF_DEVICE][CONF_HOST], + config_entry.data[CONF_DEVICE][CONF_PORT]), CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, } - add_entities([AxisCamera( - hass, camera_config, str(discovery_info[CONF_PORT]))]) + async_add_entities([AxisCamera(config, device)]) class AxisCamera(MjpegCamera): """Representation of a Axis camera.""" - def __init__(self, hass, config, port): + def __init__(self, config, device): """Initialize Axis Communications camera component.""" super().__init__(config) - self.port = port - dispatcher_connect( - hass, DOMAIN + '_' + config[CONF_NAME] + '_new_ip', self._new_ip) + self.device_config = config + self.device = device + self.port = device.config_entry.data[CONF_DEVICE][CONF_PORT] + self.unsub_dispatcher = None + + async def async_added_to_hass(self): + """Subscribe camera events.""" + self.unsub_dispatcher = async_dispatcher_connect( + self.hass, 'axis_{}_new_ip'.format(self.device.name), self._new_ip) def _new_ip(self, host): """Set new IP for video stream.""" - self._mjpeg_url = _get_image_url(host, self.port, 'mjpeg') - self._still_image_url = _get_image_url(host, self.port, 'single') + self._mjpeg_url = AXIS_VIDEO.format(host, self.port) + self._still_image_url = AXIS_IMAGE.format(host, self.port) + + @property + def unique_id(self): + """Return a unique identifier for this device.""" + return '{}-camera'.format(self.device.serial) + + @property + def device_info(self): + """Return a device description for device registry.""" + return { + 'identifiers': {(AXIS_DOMAIN, self.device.serial)} + } diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py new file mode 100644 index 00000000000..24c286b140a --- /dev/null +++ b/homeassistant/components/axis/config_flow.py @@ -0,0 +1,202 @@ +"""Config flow to configure Axis devices.""" + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import ( + CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_NAME, CONF_PASSWORD, CONF_PORT, + CONF_USERNAME) +from homeassistant.core import callback +from homeassistant.helpers import config_validation as cv +from homeassistant.util.json import load_json + +from .const import CONF_MODEL, DOMAIN +from .device import get_device +from .errors import AlreadyConfigured, AuthenticationRequired, CannotConnect + +CONFIG_FILE = 'axis.conf' + +EVENT_TYPES = ['motion', 'vmd3', 'pir', 'sound', + 'daynight', 'tampering', 'input'] + +PLATFORMS = ['camera'] + +AXIS_INCLUDE = EVENT_TYPES + PLATFORMS + +AXIS_DEFAULT_HOST = '192.168.0.90' +AXIS_DEFAULT_USERNAME = 'root' +AXIS_DEFAULT_PASSWORD = 'pass' +DEFAULT_PORT = 80 + +DEVICE_SCHEMA = vol.Schema({ + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_HOST, default=AXIS_DEFAULT_HOST): cv.string, + vol.Optional(CONF_USERNAME, default=AXIS_DEFAULT_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD, default=AXIS_DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, +}, extra=vol.ALLOW_EXTRA) + + +@callback +def configured_devices(hass): + """Return a set of the configured devices.""" + return set(entry.data[CONF_DEVICE][CONF_HOST] for entry + in hass.config_entries.async_entries(DOMAIN)) + + +@config_entries.HANDLERS.register(DOMAIN) +class AxisFlowHandler(config_entries.ConfigFlow): + """Handle a Axis config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + def __init__(self): + """Initialize the Axis config flow.""" + self.device_config = {} + self.model = None + self.name = None + self.serial_number = None + + self.discovery_schema = {} + self.import_schema = {} + + async def async_step_user(self, user_input=None): + """Handle a Axis config flow start. + + Manage device specific parameters. + """ + from axis.vapix import VAPIX_MODEL_ID, VAPIX_SERIAL_NUMBER + errors = {} + + if user_input is not None: + try: + if user_input[CONF_HOST] in configured_devices(self.hass): + raise AlreadyConfigured + + self.device_config = { + CONF_HOST: user_input[CONF_HOST], + CONF_PORT: user_input[CONF_PORT], + CONF_USERNAME: user_input[CONF_USERNAME], + CONF_PASSWORD: user_input[CONF_PASSWORD] + } + device = await get_device(self.hass, self.device_config) + + self.serial_number = device.vapix.get_param( + VAPIX_SERIAL_NUMBER) + self.model = device.vapix.get_param(VAPIX_MODEL_ID) + + return await self._create_entry() + + except AlreadyConfigured: + errors['base'] = 'already_configured' + + except AuthenticationRequired: + errors['base'] = 'faulty_credentials' + + except CannotConnect: + errors['base'] = 'device_unavailable' + + data = self.import_schema or self.discovery_schema or { + vol.Required(CONF_HOST): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int + } + + return self.async_show_form( + step_id='user', + description_placeholders=self.device_config, + data_schema=vol.Schema(data), + errors=errors + ) + + async def _create_entry(self): + """Create entry for device. + + Generate a name to be used as a prefix for device entities. + """ + if self.name is None: + same_model = [ + entry.data[CONF_NAME] for entry + in self.hass.config_entries.async_entries(DOMAIN) + if entry.data[CONF_MODEL] == self.model + ] + + self.name = "{}".format(self.model) + for idx in range(len(same_model) + 1): + self.name = "{} {}".format(self.model, idx) + if self.name not in same_model: + break + + data = { + CONF_DEVICE: self.device_config, + CONF_NAME: self.name, + CONF_MAC: self.serial_number, + CONF_MODEL: self.model, + } + + title = "{} - {}".format(self.model, self.serial_number) + return self.async_create_entry( + title=title, + data=data + ) + + async def async_step_discovery(self, discovery_info): + """Prepare configuration for a discovered Axis device. + + This flow is triggered by the discovery component. + """ + if discovery_info[CONF_HOST] in configured_devices(self.hass): + return self.async_abort(reason='already_configured') + + if discovery_info[CONF_HOST].startswith('169.254'): + return self.async_abort(reason='link_local_address') + + config_file = await self.hass.async_add_executor_job( + load_json, self.hass.config.path(CONFIG_FILE)) + + serialnumber = discovery_info['properties']['macaddress'] + + if serialnumber not in config_file: + self.discovery_schema = { + vol.Required( + CONF_HOST, default=discovery_info[CONF_HOST]): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_PORT, default=discovery_info[CONF_PORT]): int + } + return await self.async_step_user() + + try: + device_config = DEVICE_SCHEMA(config_file[serialnumber]) + device_config[CONF_HOST] = discovery_info[CONF_HOST] + + if CONF_NAME not in device_config: + device_config[CONF_NAME] = discovery_info['hostname'] + + except vol.Invalid: + return self.async_abort(reason='bad_config_file') + + return await self.async_step_import(device_config) + + async def async_step_import(self, import_config): + """Import a Axis device as a config entry. + + This flow is triggered by `async_setup` for configured devices. + This flow is also triggered by `async_step_discovery`. + + This will execute for any Axis device that contains a complete + configuration. + """ + self.name = import_config[CONF_NAME] + + self.import_schema = { + vol.Required(CONF_HOST, default=import_config[CONF_HOST]): str, + vol.Required( + CONF_USERNAME, default=import_config[CONF_USERNAME]): str, + vol.Required( + CONF_PASSWORD, default=import_config[CONF_PASSWORD]): str, + vol.Required(CONF_PORT, default=import_config[CONF_PORT]): int + } + return await self.async_step_user(user_input=import_config) diff --git a/homeassistant/components/axis/const.py b/homeassistant/components/axis/const.py new file mode 100644 index 00000000000..c6cd6976129 --- /dev/null +++ b/homeassistant/components/axis/const.py @@ -0,0 +1,12 @@ +"""Constants for the Axis component.""" +import logging + +LOGGER = logging.getLogger('homeassistant.components.axis') + +DOMAIN = 'axis' + +CONF_CAMERA = 'camera' +CONF_EVENTS = 'events' +CONF_MODEL = 'model' + +DEFAULT_TRIGGER_TIME = 0 diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py new file mode 100644 index 00000000000..ffe48e5f733 --- /dev/null +++ b/homeassistant/components/axis/device.py @@ -0,0 +1,147 @@ +"""Axis network device abstraction.""" + +import asyncio +import async_timeout + +from homeassistant.const import ( + CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_NAME, CONF_PASSWORD, CONF_PORT, + CONF_USERNAME) +from homeassistant.core import callback +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.dispatcher import async_dispatcher_send + +from .const import CONF_CAMERA, CONF_EVENTS, CONF_MODEL, DOMAIN, LOGGER +from .errors import AuthenticationRequired, CannotConnect + + +class AxisNetworkDevice: + """Manages a Axis device.""" + + def __init__(self, hass, config_entry): + """Initialize the device.""" + self.hass = hass + self.config_entry = config_entry + self.available = True + + self.api = None + self.fw_version = None + self.product_type = None + + self.listeners = [] + + @property + def host(self): + """Return the host of this device.""" + return self.config_entry.data[CONF_DEVICE][CONF_HOST] + + @property + def model(self): + """Return the model of this device.""" + return self.config_entry.data[CONF_MODEL] + + @property + def name(self): + """Return the name of this device.""" + return self.config_entry.data[CONF_NAME] + + @property + def serial(self): + """Return the mac of this device.""" + return self.config_entry.data[CONF_MAC] + + async def async_update_device_registry(self): + """Update device registry.""" + device_registry = await \ + self.hass.helpers.device_registry.async_get_registry() + device_registry.async_get_or_create( + config_entry_id=self.config_entry.entry_id, + connections={(CONNECTION_NETWORK_MAC, self.serial)}, + identifiers={(DOMAIN, self.serial)}, + manufacturer='Axis Communications AB', + model="{} {}".format(self.model, self.product_type), + name=self.name, + sw_version=self.fw_version + ) + + async def async_setup(self): + """Set up the device.""" + from axis.vapix import VAPIX_FW_VERSION, VAPIX_PROD_TYPE + + hass = self.hass + + try: + self.api = await get_device( + hass, self.config_entry.data[CONF_DEVICE], + event_types='on', signal_callback=self.async_signal_callback) + + except CannotConnect: + raise ConfigEntryNotReady + + except Exception: # pylint: disable=broad-except + LOGGER.error( + 'Unknown error connecting with Axis device on %s', self.host) + return False + + self.fw_version = self.api.vapix.get_param(VAPIX_FW_VERSION) + self.product_type = self.api.vapix.get_param(VAPIX_PROD_TYPE) + + if self.config_entry.options[CONF_CAMERA]: + self.hass.async_create_task( + self.hass.config_entries.async_forward_entry_setup( + self.config_entry, 'camera')) + + if self.config_entry.options[CONF_EVENTS]: + self.hass.async_create_task( + self.hass.config_entries.async_forward_entry_setup( + self.config_entry, 'binary_sensor')) + self.api.start() + + return True + + @property + def event_new_sensor(self): + """Device specific event to signal new sensor available.""" + return 'axis_add_sensor_{}'.format(self.serial) + + @callback + def async_signal_callback(self, action, event): + """Call to configure events when initialized on event stream.""" + if action == 'add': + async_dispatcher_send(self.hass, self.event_new_sensor, event) + + @callback + def shutdown(self, event): + """Stop the event stream.""" + self.api.stop() + + +async def get_device(hass, config, event_types=None, signal_callback=None): + """Create a Axis device.""" + import axis + + device = axis.AxisDevice( + loop=hass.loop, host=config[CONF_HOST], + username=config[CONF_USERNAME], + password=config[CONF_PASSWORD], + port=config[CONF_PORT], web_proto='http', + event_types=event_types, signal=signal_callback) + + try: + with async_timeout.timeout(15): + await hass.async_add_executor_job(device.vapix.load_params) + return device + + except axis.Unauthorized: + LOGGER.warning("Connected to device at %s but not registered.", + config[CONF_HOST]) + raise AuthenticationRequired + + except (asyncio.TimeoutError, axis.RequestError): + LOGGER.error("Error connecting to the Axis device at %s", + config[CONF_HOST]) + raise CannotConnect + + except axis.AxisException: + LOGGER.exception('Unknown Axis communication error occurred') + raise AuthenticationRequired diff --git a/homeassistant/components/axis/errors.py b/homeassistant/components/axis/errors.py new file mode 100644 index 00000000000..56105b28b1b --- /dev/null +++ b/homeassistant/components/axis/errors.py @@ -0,0 +1,22 @@ +"""Errors for the Axis component.""" +from homeassistant.exceptions import HomeAssistantError + + +class AxisException(HomeAssistantError): + """Base class for Axis exceptions.""" + + +class AlreadyConfigured(AxisException): + """Device is already configured.""" + + +class AuthenticationRequired(AxisException): + """Unknown error occurred.""" + + +class CannotConnect(AxisException): + """Unable to connect to the device.""" + + +class UserLevel(AxisException): + """User level too low.""" diff --git a/homeassistant/components/axis/services.yaml b/homeassistant/components/axis/services.yaml deleted file mode 100644 index 03db5ce7af8..00000000000 --- a/homeassistant/components/axis/services.yaml +++ /dev/null @@ -1,15 +0,0 @@ -vapix_call: - description: Configure device using Vapix parameter management. - fields: - name: - description: Name of device to Configure. [Required] - example: M1065-W - cgi: - description: Which cgi to call on device. [Optional] Default is 'param.cgi' - example: 'applications/control.cgi' - action: - description: What type of call. [Optional] Default is 'update' - example: 'start' - param: - description: What parameter to operate on. [Required] - example: 'package=VideoMotionDetection' \ No newline at end of file diff --git a/homeassistant/components/axis/strings.json b/homeassistant/components/axis/strings.json new file mode 100644 index 00000000000..3c528dfbb16 --- /dev/null +++ b/homeassistant/components/axis/strings.json @@ -0,0 +1,26 @@ +{ + "config": { + "title": "Axis device", + "step": { + "user": { + "title": "Set up Axis device", + "data": { + "host": "Host", + "username": "Username", + "password": "Password", + "port": "Port" + } + } + }, + "error": { + "already_configured": "Device is already configured", + "device_unavailable": "Device is not available", + "faulty_credentials": "Bad user credentials" + }, + "abort": { + "already_configured": "Device is already configured", + "bad_config_file": "Bad data from config file", + "link_local_address": "Link local addresses are not supported" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baidu/__init__.py b/homeassistant/components/baidu/__init__.py new file mode 100644 index 00000000000..8a332cf52e1 --- /dev/null +++ b/homeassistant/components/baidu/__init__.py @@ -0,0 +1 @@ +"""Support for Baidu integration.""" diff --git a/homeassistant/components/tts/baidu.py b/homeassistant/components/baidu/tts.py similarity index 98% rename from homeassistant/components/tts/baidu.py rename to homeassistant/components/baidu/tts.py index 609f38454fe..07b69d41dfd 100644 --- a/homeassistant/components/tts/baidu.py +++ b/homeassistant/components/baidu/tts.py @@ -6,9 +6,10 @@ https://home-assistant.io/components/tts.baidu/ """ import logging + import voluptuous as vol -from homeassistant.components.tts import Provider, CONF_LANG, PLATFORM_SCHEMA +from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider from homeassistant.const import CONF_API_KEY import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/bayesian/__init__.py b/homeassistant/components/bayesian/__init__.py new file mode 100644 index 00000000000..971ff8427ac --- /dev/null +++ b/homeassistant/components/bayesian/__init__.py @@ -0,0 +1 @@ +"""The bayesian component.""" diff --git a/homeassistant/components/binary_sensor/bayesian.py b/homeassistant/components/bayesian/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/bayesian.py rename to homeassistant/components/bayesian/binary_sensor.py diff --git a/homeassistant/components/bbox/__init__.py b/homeassistant/components/bbox/__init__.py new file mode 100644 index 00000000000..8c3bbf0d57f --- /dev/null +++ b/homeassistant/components/bbox/__init__.py @@ -0,0 +1 @@ +"""The bbox component.""" diff --git a/homeassistant/components/device_tracker/bbox.py b/homeassistant/components/bbox/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/bbox.py rename to homeassistant/components/bbox/device_tracker.py diff --git a/homeassistant/components/sensor/bbox.py b/homeassistant/components/bbox/sensor.py similarity index 100% rename from homeassistant/components/sensor/bbox.py rename to homeassistant/components/bbox/sensor.py diff --git a/homeassistant/components/bh1750/__init__.py b/homeassistant/components/bh1750/__init__.py new file mode 100644 index 00000000000..ce7ecc65366 --- /dev/null +++ b/homeassistant/components/bh1750/__init__.py @@ -0,0 +1 @@ +"""The bh1750 component.""" diff --git a/homeassistant/components/sensor/bh1750.py b/homeassistant/components/bh1750/sensor.py similarity index 100% rename from homeassistant/components/sensor/bh1750.py rename to homeassistant/components/bh1750/sensor.py diff --git a/homeassistant/components/bitcoin/__init__.py b/homeassistant/components/bitcoin/__init__.py new file mode 100644 index 00000000000..cfdfb53c044 --- /dev/null +++ b/homeassistant/components/bitcoin/__init__.py @@ -0,0 +1 @@ +"""The bitcoin component.""" diff --git a/homeassistant/components/sensor/bitcoin.py b/homeassistant/components/bitcoin/sensor.py similarity index 100% rename from homeassistant/components/sensor/bitcoin.py rename to homeassistant/components/bitcoin/sensor.py diff --git a/homeassistant/components/blackbird/__init__.py b/homeassistant/components/blackbird/__init__.py new file mode 100644 index 00000000000..b901bda0469 --- /dev/null +++ b/homeassistant/components/blackbird/__init__.py @@ -0,0 +1 @@ +"""The blackbird component.""" diff --git a/homeassistant/components/media_player/blackbird.py b/homeassistant/components/blackbird/media_player.py similarity index 100% rename from homeassistant/components/media_player/blackbird.py rename to homeassistant/components/blackbird/media_player.py diff --git a/homeassistant/components/blink/alarm_control_panel.py b/homeassistant/components/blink/alarm_control_panel.py index b9bf4a5250f..75e645dff5f 100644 --- a/homeassistant/components/blink/alarm_control_panel.py +++ b/homeassistant/components/blink/alarm_control_panel.py @@ -2,10 +2,10 @@ import logging from homeassistant.components.alarm_control_panel import AlarmControlPanel -from homeassistant.components.blink import ( - BLINK_DATA, DEFAULT_ATTRIBUTION) from homeassistant.const import ( - ATTR_ATTRIBUTION, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY) + ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_DISARMED) + +from . import BLINK_DATA, DEFAULT_ATTRIBUTION _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/blink/binary_sensor.py b/homeassistant/components/blink/binary_sensor.py index fe0b95b1517..466b73caf5f 100644 --- a/homeassistant/components/blink/binary_sensor.py +++ b/homeassistant/components/blink/binary_sensor.py @@ -1,8 +1,9 @@ """Support for Blink system camera control.""" -from homeassistant.components.blink import BLINK_DATA, BINARY_SENSORS from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.const import CONF_MONITORED_CONDITIONS +from . import BINARY_SENSORS, BLINK_DATA + DEPENDENCIES = ['blink'] diff --git a/homeassistant/components/blink/camera.py b/homeassistant/components/blink/camera.py index 2e5c024d6e5..1da3080e3ff 100644 --- a/homeassistant/components/blink/camera.py +++ b/homeassistant/components/blink/camera.py @@ -1,9 +1,10 @@ """Support for Blink system camera.""" import logging -from homeassistant.components.blink import BLINK_DATA, DEFAULT_BRAND from homeassistant.components.camera import Camera +from . import BLINK_DATA, DEFAULT_BRAND + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['blink'] diff --git a/homeassistant/components/blink/sensor.py b/homeassistant/components/blink/sensor.py index c1fdf9252dd..0e97db9d7d4 100644 --- a/homeassistant/components/blink/sensor.py +++ b/homeassistant/components/blink/sensor.py @@ -1,9 +1,10 @@ """Support for Blink system camera sensors.""" import logging -from homeassistant.components.blink import BLINK_DATA, SENSORS -from homeassistant.helpers.entity import Entity from homeassistant.const import CONF_MONITORED_CONDITIONS +from homeassistant.helpers.entity import Entity + +from . import BLINK_DATA, SENSORS _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/blinksticklight/__init__.py b/homeassistant/components/blinksticklight/__init__.py new file mode 100644 index 00000000000..dd45fbcd690 --- /dev/null +++ b/homeassistant/components/blinksticklight/__init__.py @@ -0,0 +1 @@ +"""The blinksticklight component.""" diff --git a/homeassistant/components/light/blinksticklight.py b/homeassistant/components/blinksticklight/light.py similarity index 100% rename from homeassistant/components/light/blinksticklight.py rename to homeassistant/components/blinksticklight/light.py diff --git a/homeassistant/components/blinkt/__init__.py b/homeassistant/components/blinkt/__init__.py new file mode 100644 index 00000000000..0f61a211559 --- /dev/null +++ b/homeassistant/components/blinkt/__init__.py @@ -0,0 +1 @@ +"""The blinkt component.""" diff --git a/homeassistant/components/light/blinkt.py b/homeassistant/components/blinkt/light.py similarity index 98% rename from homeassistant/components/light/blinkt.py rename to homeassistant/components/blinkt/light.py index d8f819492a5..0704881bff9 100644 --- a/homeassistant/components/light/blinkt.py +++ b/homeassistant/components/blinkt/light.py @@ -4,6 +4,7 @@ Support for Blinkt! lights on Raspberry Pi. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.blinkt/ """ +import importlib import logging import voluptuous as vol @@ -31,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Blinkt Light platform.""" # pylint: disable=no-member - import blinkt + blinkt = importlib.import_module('blinkt') # ensure that the lights are off when exiting blinkt.set_clear_on_exit() diff --git a/homeassistant/components/blockchain/__init__.py b/homeassistant/components/blockchain/__init__.py new file mode 100644 index 00000000000..a8ee9884bba --- /dev/null +++ b/homeassistant/components/blockchain/__init__.py @@ -0,0 +1 @@ +"""The blockchain component.""" diff --git a/homeassistant/components/sensor/blockchain.py b/homeassistant/components/blockchain/sensor.py similarity index 100% rename from homeassistant/components/sensor/blockchain.py rename to homeassistant/components/blockchain/sensor.py diff --git a/homeassistant/components/bluesound/__init__.py b/homeassistant/components/bluesound/__init__.py new file mode 100644 index 00000000000..9dbe0f754fb --- /dev/null +++ b/homeassistant/components/bluesound/__init__.py @@ -0,0 +1 @@ +"""The bluesound component.""" diff --git a/homeassistant/components/media_player/bluesound.py b/homeassistant/components/bluesound/media_player.py similarity index 100% rename from homeassistant/components/media_player/bluesound.py rename to homeassistant/components/bluesound/media_player.py diff --git a/homeassistant/components/bluetooth_le_tracker/__init__.py b/homeassistant/components/bluetooth_le_tracker/__init__.py new file mode 100644 index 00000000000..d6886e1b356 --- /dev/null +++ b/homeassistant/components/bluetooth_le_tracker/__init__.py @@ -0,0 +1 @@ +"""The bluetooth_le_tracker component.""" diff --git a/homeassistant/components/device_tracker/bluetooth_le_tracker.py b/homeassistant/components/bluetooth_le_tracker/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/bluetooth_le_tracker.py rename to homeassistant/components/bluetooth_le_tracker/device_tracker.py diff --git a/homeassistant/components/bluetooth_tracker/__init__.py b/homeassistant/components/bluetooth_tracker/__init__.py new file mode 100644 index 00000000000..e58d5abab4a --- /dev/null +++ b/homeassistant/components/bluetooth_tracker/__init__.py @@ -0,0 +1 @@ +"""The bluetooth_tracker component.""" diff --git a/homeassistant/components/device_tracker/bluetooth_tracker.py b/homeassistant/components/bluetooth_tracker/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/bluetooth_tracker.py rename to homeassistant/components/bluetooth_tracker/device_tracker.py diff --git a/homeassistant/components/bme280/__init__.py b/homeassistant/components/bme280/__init__.py new file mode 100644 index 00000000000..87de36fdf02 --- /dev/null +++ b/homeassistant/components/bme280/__init__.py @@ -0,0 +1 @@ +"""The bme280 component.""" diff --git a/homeassistant/components/sensor/bme280.py b/homeassistant/components/bme280/sensor.py similarity index 100% rename from homeassistant/components/sensor/bme280.py rename to homeassistant/components/bme280/sensor.py diff --git a/homeassistant/components/bme680/__init__.py b/homeassistant/components/bme680/__init__.py new file mode 100644 index 00000000000..dc88286a603 --- /dev/null +++ b/homeassistant/components/bme680/__init__.py @@ -0,0 +1 @@ +"""The bme680 component.""" diff --git a/homeassistant/components/sensor/bme680.py b/homeassistant/components/bme680/sensor.py similarity index 99% rename from homeassistant/components/sensor/bme680.py rename to homeassistant/components/bme680/sensor.py index 2fa592bf864..8d620e459d0 100644 --- a/homeassistant/components/sensor/bme680.py +++ b/homeassistant/components/bme680/sensor.py @@ -7,6 +7,7 @@ Air Quality calculation based on humidity and volatile gas. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.bme680/ """ +import importlib import logging from time import time, sleep @@ -122,7 +123,7 @@ async def async_setup_platform(hass, config, async_add_entities, def _setup_bme680(config): """Set up and configure the BME680 sensor.""" from smbus import SMBus # pylint: disable=import-error - import bme680 + bme680 = importlib.import_module('bme680') sensor_handler = None sensor = None diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index 1843f647df8..deab157292d 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -2,9 +2,10 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN from homeassistant.const import LENGTH_KILOMETERS +from . import DOMAIN as BMW_DOMAIN + DEPENDENCIES = ['bmw_connected_drive'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py index 21121b069af..20e84e33e29 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -1,10 +1,10 @@ """Device tracker for BMW Connected Drive vehicles.""" import logging -from homeassistant.components.bmw_connected_drive import DOMAIN \ - as BMW_DOMAIN from homeassistant.util import slugify +from . import DOMAIN as BMW_DOMAIN + DEPENDENCIES = ['bmw_connected_drive'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index 8a5eddaa86a..fe646dcd1c9 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -1,10 +1,11 @@ """Support for BMW car locks with BMW ConnectedDrive.""" import logging -from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN from homeassistant.components.lock import LockDevice from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED +from . import DOMAIN as BMW_DOMAIN + DEPENDENCIES = ['bmw_connected_drive'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index a01142c53ed..03c03f01b4a 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -1,12 +1,13 @@ """Support for reading vehicle status from BMW connected drive portal.""" import logging -from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN +from homeassistant.const import ( + CONF_UNIT_SYSTEM_IMPERIAL, LENGTH_KILOMETERS, LENGTH_MILES, VOLUME_GALLONS, + VOLUME_LITERS) from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level -from homeassistant.const import (CONF_UNIT_SYSTEM_IMPERIAL, VOLUME_LITERS, - VOLUME_GALLONS, LENGTH_KILOMETERS, - LENGTH_MILES) + +from . import DOMAIN as BMW_DOMAIN DEPENDENCIES = ['bmw_connected_drive'] diff --git a/homeassistant/components/bom/__init__.py b/homeassistant/components/bom/__init__.py new file mode 100644 index 00000000000..7b83a5c981b --- /dev/null +++ b/homeassistant/components/bom/__init__.py @@ -0,0 +1 @@ +"""The bom component.""" diff --git a/homeassistant/components/sensor/bom.py b/homeassistant/components/bom/sensor.py similarity index 100% rename from homeassistant/components/sensor/bom.py rename to homeassistant/components/bom/sensor.py diff --git a/homeassistant/components/weather/bom.py b/homeassistant/components/bom/weather.py similarity index 98% rename from homeassistant/components/weather/bom.py rename to homeassistant/components/bom/weather.py index b05d5fc594d..2444192d87d 100644 --- a/homeassistant/components/weather/bom.py +++ b/homeassistant/components/bom/weather.py @@ -3,14 +3,15 @@ import logging import voluptuous as vol -# Reuse data and API logic from the sensor implementation -from homeassistant.components.sensor.bom import ( - CONF_STATION, BOMCurrentData, closest_station, validate_station) from homeassistant.components.weather import PLATFORM_SCHEMA, WeatherEntity from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS) from homeassistant.helpers import config_validation as cv +# Reuse data and API logic from the sensor implementation +from .sensor import ( + CONF_STATION, BOMCurrentData, closest_station, validate_station) + _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/braviatv/__init__.py b/homeassistant/components/braviatv/__init__.py new file mode 100644 index 00000000000..47c6f4cf24d --- /dev/null +++ b/homeassistant/components/braviatv/__init__.py @@ -0,0 +1 @@ +"""The braviatv component.""" diff --git a/homeassistant/components/media_player/braviatv.py b/homeassistant/components/braviatv/media_player.py similarity index 100% rename from homeassistant/components/media_player/braviatv.py rename to homeassistant/components/braviatv/media_player.py diff --git a/homeassistant/components/broadlink/__init__.py b/homeassistant/components/broadlink/__init__.py new file mode 100644 index 00000000000..5055c7fa597 --- /dev/null +++ b/homeassistant/components/broadlink/__init__.py @@ -0,0 +1 @@ +"""The broadlink component.""" diff --git a/homeassistant/components/sensor/broadlink.py b/homeassistant/components/broadlink/sensor.py similarity index 100% rename from homeassistant/components/sensor/broadlink.py rename to homeassistant/components/broadlink/sensor.py diff --git a/homeassistant/components/switch/broadlink.py b/homeassistant/components/broadlink/switch.py similarity index 100% rename from homeassistant/components/switch/broadlink.py rename to homeassistant/components/broadlink/switch.py diff --git a/homeassistant/components/brottsplatskartan/__init__.py b/homeassistant/components/brottsplatskartan/__init__.py new file mode 100644 index 00000000000..d519909b290 --- /dev/null +++ b/homeassistant/components/brottsplatskartan/__init__.py @@ -0,0 +1 @@ +"""The brottsplatskartan component.""" diff --git a/homeassistant/components/sensor/brottsplatskartan.py b/homeassistant/components/brottsplatskartan/sensor.py similarity index 100% rename from homeassistant/components/sensor/brottsplatskartan.py rename to homeassistant/components/brottsplatskartan/sensor.py diff --git a/homeassistant/components/brunt/__init__.py b/homeassistant/components/brunt/__init__.py new file mode 100644 index 00000000000..f89d57cdec1 --- /dev/null +++ b/homeassistant/components/brunt/__init__.py @@ -0,0 +1 @@ +"""The brunt component.""" diff --git a/homeassistant/components/cover/brunt.py b/homeassistant/components/brunt/cover.py similarity index 100% rename from homeassistant/components/cover/brunt.py rename to homeassistant/components/brunt/cover.py diff --git a/homeassistant/components/bt_home_hub_5/__init__.py b/homeassistant/components/bt_home_hub_5/__init__.py new file mode 100644 index 00000000000..54816d6556d --- /dev/null +++ b/homeassistant/components/bt_home_hub_5/__init__.py @@ -0,0 +1 @@ +"""The bt_home_hub_5 component.""" diff --git a/homeassistant/components/device_tracker/bt_home_hub_5.py b/homeassistant/components/bt_home_hub_5/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/bt_home_hub_5.py rename to homeassistant/components/bt_home_hub_5/device_tracker.py diff --git a/homeassistant/components/bt_smarthub/__init__.py b/homeassistant/components/bt_smarthub/__init__.py new file mode 100644 index 00000000000..07419a2bacd --- /dev/null +++ b/homeassistant/components/bt_smarthub/__init__.py @@ -0,0 +1 @@ +"""The bt_smarthub component.""" diff --git a/homeassistant/components/device_tracker/bt_smarthub.py b/homeassistant/components/bt_smarthub/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/bt_smarthub.py rename to homeassistant/components/bt_smarthub/device_tracker.py diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py new file mode 100644 index 00000000000..680351f9b81 --- /dev/null +++ b/homeassistant/components/buienradar/__init__.py @@ -0,0 +1 @@ +"""The buienradar component.""" diff --git a/homeassistant/components/sensor/buienradar.py b/homeassistant/components/buienradar/sensor.py similarity index 98% rename from homeassistant/components/sensor/buienradar.py rename to homeassistant/components/buienradar/sensor.py index 4ceb1b221c0..d144d84cbf8 100644 --- a/homeassistant/components/sensor/buienradar.py +++ b/homeassistant/components/buienradar/sensor.py @@ -8,19 +8,18 @@ import asyncio from datetime import datetime, timedelta import logging -import async_timeout import aiohttp +import async_timeout import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, - CONF_MONITORED_CONDITIONS, CONF_NAME, TEMP_CELSIUS) + ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, CONF_MONITORED_CONDITIONS, + CONF_NAME, TEMP_CELSIUS) from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import ( - async_track_point_in_utc_time) +from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util import dt as dt_util REQUIREMENTS = ['buienradar==0.91'] @@ -144,7 +143,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Create the buienradar sensor.""" - from homeassistant.components.weather.buienradar import DEFAULT_TIMEFRAME + from .weather import DEFAULT_TIMEFRAME latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) diff --git a/homeassistant/components/weather/buienradar.py b/homeassistant/components/buienradar/weather.py similarity index 98% rename from homeassistant/components/weather/buienradar.py rename to homeassistant/components/buienradar/weather.py index 31f51824146..86dcb229a78 100644 --- a/homeassistant/components/weather/buienradar.py +++ b/homeassistant/components/buienradar/weather.py @@ -3,8 +3,6 @@ import logging import voluptuous as vol -# Reuse data and API logic from the sensor implementation -from homeassistant.components.sensor.buienradar import BrData from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, PLATFORM_SCHEMA, WeatherEntity) @@ -12,6 +10,9 @@ from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS) from homeassistant.helpers import config_validation as cv +# Reuse data and API logic from the sensor implementation +from .sensor import BrData + REQUIREMENTS = ['buienradar==0.91'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/caldav/__init__.py b/homeassistant/components/caldav/__init__.py new file mode 100644 index 00000000000..6fe9a8d4d19 --- /dev/null +++ b/homeassistant/components/caldav/__init__.py @@ -0,0 +1 @@ +"""The caldav component.""" diff --git a/homeassistant/components/calendar/caldav.py b/homeassistant/components/caldav/calendar.py similarity index 100% rename from homeassistant/components/calendar/caldav.py rename to homeassistant/components/caldav/calendar.py diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 95d6dba50c3..e453cdfd1a1 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -20,7 +20,7 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \ - SERVICE_TURN_ON + SERVICE_TURN_ON, EVENT_HOMEASSISTANT_START, CONF_FILENAME from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import bind_hass from homeassistant.helpers.entity import Entity @@ -33,11 +33,14 @@ from homeassistant.components.media_player.const import ( SERVICE_PLAY_MEDIA, DOMAIN as DOMAIN_MP) from homeassistant.components.stream import request_stream from homeassistant.components.stream.const import ( - OUTPUT_FORMATS, FORMAT_CONTENT_TYPE) + OUTPUT_FORMATS, FORMAT_CONTENT_TYPE, CONF_STREAM_SOURCE, CONF_LOOKBACK, + CONF_DURATION, SERVICE_RECORD, DOMAIN as DOMAIN_STREAM) from homeassistant.components import websocket_api import homeassistant.helpers.config_validation as cv -DOMAIN = 'camera' +from .const import DOMAIN, DATA_CAMERA_PREFS +from .prefs import CameraPreferences + DEPENDENCIES = ['http'] _LOGGER = logging.getLogger(__name__) @@ -60,6 +63,7 @@ STATE_IDLE = 'idle' # Bitfield of features supported by the camera entity SUPPORT_ON_OFF = 1 +SUPPORT_STREAM = 2 DEFAULT_CONTENT_TYPE = 'image/jpeg' ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}' @@ -67,7 +71,6 @@ ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}' TOKEN_CHANGE_INTERVAL = timedelta(minutes=5) _RND = SystemRandom() -FALLBACK_STREAM_INTERVAL = 1 # seconds MIN_STREAM_INTERVAL = 0.5 # seconds CAMERA_SERVICE_SCHEMA = vol.Schema({ @@ -83,6 +86,12 @@ CAMERA_SERVICE_PLAY_STREAM = CAMERA_SERVICE_SCHEMA.extend({ vol.Optional(ATTR_FORMAT, default='hls'): vol.In(OUTPUT_FORMATS), }) +CAMERA_SERVICE_RECORD = CAMERA_SERVICE_SCHEMA.extend({ + vol.Required(CONF_FILENAME): cv.template, + vol.Optional(CONF_DURATION, default=30): int, + vol.Optional(CONF_LOOKBACK, default=0): int, +}) + WS_TYPE_CAMERA_THUMBNAIL = 'camera_thumbnail' SCHEMA_WS_CAMERA_THUMBNAIL = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): WS_TYPE_CAMERA_THUMBNAIL, @@ -98,6 +107,20 @@ class Image: content = attr.ib(type=bytes) +@bind_hass +async def async_request_stream(hass, entity_id, fmt): + """Request a stream for a camera entity.""" + camera = _get_camera_from_entity_id(hass, entity_id) + camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) + + if not camera.stream_source: + raise HomeAssistantError("{} does not support play stream service" + .format(camera.entity_id)) + + return request_stream(hass, camera.stream_source, fmt=fmt, + keepalive=camera_prefs.preload_stream) + + @bind_hass async def async_get_image(hass, entity_id, timeout=10): """Fetch an image from a camera entity.""" @@ -184,6 +207,10 @@ async def async_setup(hass, config): component = hass.data[DOMAIN] = \ EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) + prefs = CameraPreferences(hass) + await prefs.async_initialize() + hass.data[DATA_CAMERA_PREFS] = prefs + hass.http.register_view(CameraImageView(component)) hass.http.register_view(CameraMjpegStream(component)) hass.components.websocket_api.async_register_command( @@ -191,9 +218,21 @@ async def async_setup(hass, config): SCHEMA_WS_CAMERA_THUMBNAIL ) hass.components.websocket_api.async_register_command(ws_camera_stream) + hass.components.websocket_api.async_register_command(websocket_get_prefs) + hass.components.websocket_api.async_register_command( + websocket_update_prefs) await component.async_setup(config) + @callback + def preload_stream(event): + for camera in component.entities: + camera_prefs = prefs.get(camera.entity_id) + if camera.stream_source and camera_prefs.preload_stream: + request_stream(hass, camera.stream_source, keepalive=True) + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, preload_stream) + @callback def update_tokens(time): """Update tokens of the entities.""" @@ -228,6 +267,10 @@ async def async_setup(hass, config): SERVICE_PLAY_STREAM, CAMERA_SERVICE_PLAY_STREAM, async_handle_play_stream_service ) + component.async_register_entity_service( + SERVICE_RECORD, CAMERA_SERVICE_RECORD, + async_handle_record_service + ) return True @@ -509,14 +552,17 @@ async def ws_camera_stream(hass, connection, msg): Async friendly. """ try: - camera = _get_camera_from_entity_id(hass, msg['entity_id']) + entity_id = msg['entity_id'] + camera = _get_camera_from_entity_id(hass, entity_id) + camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) if not camera.stream_source: raise HomeAssistantError("{} does not support play stream service" .format(camera.entity_id)) fmt = msg['format'] - url = request_stream(hass, camera.stream_source, fmt=fmt) + url = request_stream(hass, camera.stream_source, fmt=fmt, + keepalive=camera_prefs.preload_stream) connection.send_result(msg['id'], {'url': url}) except HomeAssistantError as ex: _LOGGER.error(ex) @@ -524,6 +570,36 @@ async def ws_camera_stream(hass, connection, msg): msg['id'], 'start_stream_failed', str(ex)) +@websocket_api.async_response +@websocket_api.websocket_command({ + vol.Required('type'): 'camera/get_prefs', + vol.Required('entity_id'): cv.entity_id, +}) +async def websocket_get_prefs(hass, connection, msg): + """Handle request for account info.""" + prefs = hass.data[DATA_CAMERA_PREFS].get(msg['entity_id']) + connection.send_result(msg['id'], prefs.as_dict()) + + +@websocket_api.async_response +@websocket_api.websocket_command({ + vol.Required('type'): 'camera/update_prefs', + vol.Required('entity_id'): cv.entity_id, + vol.Optional('preload_stream'): bool, +}) +async def websocket_update_prefs(hass, connection, msg): + """Handle request for account info.""" + prefs = hass.data[DATA_CAMERA_PREFS] + + changes = dict(msg) + changes.pop('id') + changes.pop('type') + entity_id = changes.pop('entity_id') + await prefs.async_update(entity_id, **changes) + + connection.send_result(msg['id'], prefs.get(entity_id).as_dict()) + + async def async_handle_snapshot_service(camera, service): """Handle snapshot services calls.""" hass = camera.hass @@ -560,10 +636,12 @@ async def async_handle_play_stream_service(camera, service_call): .format(camera.entity_id)) hass = camera.hass + camera_prefs = hass.data[DATA_CAMERA_PREFS].get(camera.entity_id) fmt = service_call.data[ATTR_FORMAT] entity_ids = service_call.data[ATTR_MEDIA_PLAYER] - url = request_stream(hass, camera.stream_source, fmt=fmt) + url = request_stream(hass, camera.stream_source, fmt=fmt, + keepalive=camera_prefs.preload_stream) data = { ATTR_ENTITY_ID: entity_ids, ATTR_MEDIA_CONTENT_ID: "{}{}".format(hass.config.api.base_url, url), @@ -573,3 +651,27 @@ async def async_handle_play_stream_service(camera, service_call): await hass.services.async_call( DOMAIN_MP, SERVICE_PLAY_MEDIA, data, blocking=True, context=service_call.context) + + +async def async_handle_record_service(camera, call): + """Handle stream recording service calls.""" + if not camera.stream_source: + raise HomeAssistantError("{} does not support record service" + .format(camera.entity_id)) + + hass = camera.hass + filename = call.data[CONF_FILENAME] + filename.hass = hass + video_path = filename.async_render( + variables={ATTR_ENTITY_ID: camera}) + + data = { + CONF_STREAM_SOURCE: camera.stream_source, + CONF_FILENAME: video_path, + CONF_DURATION: call.data[CONF_DURATION], + CONF_LOOKBACK: call.data[CONF_LOOKBACK], + } + + await hass.services.async_call( + DOMAIN_STREAM, SERVICE_RECORD, data, + blocking=True, context=call.context) diff --git a/homeassistant/components/camera/const.py b/homeassistant/components/camera/const.py new file mode 100644 index 00000000000..f87ca47460e --- /dev/null +++ b/homeassistant/components/camera/const.py @@ -0,0 +1,6 @@ +"""Constants for Camera component.""" +DOMAIN = 'camera' + +DATA_CAMERA_PREFS = 'camera_prefs' + +PREF_PRELOAD_STREAM = 'preload_stream' diff --git a/homeassistant/components/camera/prefs.py b/homeassistant/components/camera/prefs.py new file mode 100644 index 00000000000..927929bdf6e --- /dev/null +++ b/homeassistant/components/camera/prefs.py @@ -0,0 +1,60 @@ +"""Preference management for camera component.""" +from .const import DOMAIN, PREF_PRELOAD_STREAM + +STORAGE_KEY = DOMAIN +STORAGE_VERSION = 1 +_UNDEF = object() + + +class CameraEntityPreferences: + """Handle preferences for camera entity.""" + + def __init__(self, prefs): + """Initialize prefs.""" + self._prefs = prefs + + def as_dict(self): + """Return dictionary version.""" + return self._prefs + + @property + def preload_stream(self): + """Return if stream is loaded on hass start.""" + return self._prefs.get(PREF_PRELOAD_STREAM, False) + + +class CameraPreferences: + """Handle camera preferences.""" + + def __init__(self, hass): + """Initialize camera prefs.""" + self._hass = hass + self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + self._prefs = None + + async def async_initialize(self): + """Finish initializing the preferences.""" + prefs = await self._store.async_load() + + if prefs is None: + prefs = {} + + self._prefs = prefs + + async def async_update(self, entity_id, *, preload_stream=_UNDEF, + stream_options=_UNDEF): + """Update camera preferences.""" + if not self._prefs.get(entity_id): + self._prefs[entity_id] = {} + + for key, value in ( + (PREF_PRELOAD_STREAM, preload_stream), + ): + if value is not _UNDEF: + self._prefs[entity_id][key] = value + + await self._store.async_save(self._prefs) + + def get(self, entity_id): + """Get preferences for an entity.""" + return CameraEntityPreferences(self._prefs.get(entity_id, {})) diff --git a/homeassistant/components/camera/services.yaml b/homeassistant/components/camera/services.yaml index 575f1fe76f7..a3e42300cbd 100644 --- a/homeassistant/components/camera/services.yaml +++ b/homeassistant/components/camera/services.yaml @@ -51,6 +51,23 @@ play_stream: description: (Optional) Stream format supported by media player. example: 'hls' +record: + description: Record live camera feed. + fields: + entity_id: + description: Name of entities to record. + example: 'camera.living_room_camera' + filename: + description: Template of a Filename. Variable is entity_id. Must be mp4. + example: '/tmp/snapshot_{{ entity_id }}.mp4' + duration: + description: (Optional) Target recording length (in seconds). + default: 30 + example: 30 + lookback: + description: (Optional) Target lookback period (in seconds) to include in addition to duration. Only available if there is currently an active HLS stream. + example: 4 + local_file_update_file_path: description: Update the file_path for a local_file camera. fields: diff --git a/homeassistant/components/alarm_control_panel/canary.py b/homeassistant/components/canary/alarm_control_panel.py similarity index 93% rename from homeassistant/components/alarm_control_panel/canary.py rename to homeassistant/components/canary/alarm_control_panel.py index b22a76fdb3b..61794224666 100644 --- a/homeassistant/components/alarm_control_panel/canary.py +++ b/homeassistant/components/canary/alarm_control_panel.py @@ -7,9 +7,11 @@ https://home-assistant.io/components/alarm_control_panel.canary/ import logging from homeassistant.components.alarm_control_panel import AlarmControlPanel -from homeassistant.components.canary import DATA_CANARY -from homeassistant.const import STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY, \ - STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMED_HOME +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED) + +from . import DATA_CANARY DEPENDENCIES = ['canary'] diff --git a/homeassistant/components/camera/canary.py b/homeassistant/components/canary/camera.py similarity index 88% rename from homeassistant/components/camera/canary.py rename to homeassistant/components/canary/camera.py index eb0c8f3fc6d..c3a3af32450 100644 --- a/homeassistant/components/camera/canary.py +++ b/homeassistant/components/canary/camera.py @@ -5,28 +5,30 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/camera.canary/ """ import asyncio -import logging from datetime import timedelta +import logging import voluptuous as vol -from homeassistant.components.camera import Camera, PLATFORM_SCHEMA -from homeassistant.components.canary import DATA_CANARY, DEFAULT_TIMEOUT +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream from homeassistant.util import Throttle -CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' +from . import DATA_CANARY, DEFAULT_TIMEOUT DEPENDENCIES = ['canary', 'ffmpeg'] _LOGGER = logging.getLogger(__name__) +CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' +DEFAULT_ARGUMENTS = '-pred 1' + MIN_TIME_BETWEEN_SESSION_RENEW = timedelta(seconds=90) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string, + vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, }) @@ -79,7 +81,7 @@ class CanaryCamera(Camera): """Return a still image response from the camera.""" self.renew_live_stream_session() - from haffmpeg import ImageFrame, IMAGE_JPEG + from haffmpeg.tools import ImageFrame, IMAGE_JPEG ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop) image = await asyncio.shield(ffmpeg.get_image( self._live_stream_session.live_stream_url, @@ -92,15 +94,16 @@ class CanaryCamera(Camera): if self._live_stream_session is None: return - from haffmpeg import CameraMjpeg + from haffmpeg.camera import CameraMjpeg stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) await stream.open_camera( self._live_stream_session.live_stream_url, extra_cmd=self._ffmpeg_arguments) try: + stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream, + self.hass, request, stream_reader, self._ffmpeg.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/sensor/canary.py b/homeassistant/components/canary/sensor.py similarity index 98% rename from homeassistant/components/sensor/canary.py rename to homeassistant/components/canary/sensor.py index 015c6b378e0..d24c00c9266 100644 --- a/homeassistant/components/sensor/canary.py +++ b/homeassistant/components/canary/sensor.py @@ -5,11 +5,12 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.canary/ """ -from homeassistant.components.canary import DATA_CANARY from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level +from . import DATA_CANARY + DEPENDENCIES = ['canary'] SENSOR_VALUE_PRECISION = 2 diff --git a/homeassistant/components/cast/.translations/th.json b/homeassistant/components/cast/.translations/th.json new file mode 100644 index 00000000000..f0b06a06dc9 --- /dev/null +++ b/homeassistant/components/cast/.translations/th.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\u0e04\u0e38\u0e13\u0e15\u0e49\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32 Google Cast \u0e2b\u0e23\u0e37\u0e2d\u0e44\u0e21\u0e48?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 28373cc6c14..77332883a91 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -7,14 +7,13 @@ from typing import Optional, Tuple import attr import voluptuous as vol -from homeassistant.components.cast import DOMAIN as CAST_DOMAIN from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) + PLATFORM_SCHEMA, MediaPlayerDevice) from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) + MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, + SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_HOST, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) @@ -26,6 +25,8 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.typing import ConfigType, HomeAssistantType import homeassistant.util.dt as dt_util +from . import DOMAIN as CAST_DOMAIN + DEPENDENCIES = ('cast',) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/cert_expiry/__init__.py b/homeassistant/components/cert_expiry/__init__.py new file mode 100644 index 00000000000..78ceb60dd40 --- /dev/null +++ b/homeassistant/components/cert_expiry/__init__.py @@ -0,0 +1 @@ +"""The cert_expiry component.""" diff --git a/homeassistant/components/sensor/cert_expiry.py b/homeassistant/components/cert_expiry/sensor.py similarity index 100% rename from homeassistant/components/sensor/cert_expiry.py rename to homeassistant/components/cert_expiry/sensor.py diff --git a/homeassistant/components/channels/__init__.py b/homeassistant/components/channels/__init__.py new file mode 100644 index 00000000000..70a8c0677de --- /dev/null +++ b/homeassistant/components/channels/__init__.py @@ -0,0 +1 @@ +"""The channels component.""" diff --git a/homeassistant/components/media_player/channels.py b/homeassistant/components/channels/media_player.py similarity index 100% rename from homeassistant/components/media_player/channels.py rename to homeassistant/components/channels/media_player.py diff --git a/homeassistant/components/cisco_ios/__init__.py b/homeassistant/components/cisco_ios/__init__.py new file mode 100644 index 00000000000..77e7c6621eb --- /dev/null +++ b/homeassistant/components/cisco_ios/__init__.py @@ -0,0 +1 @@ +"""The cisco_ios component.""" diff --git a/homeassistant/components/device_tracker/cisco_ios.py b/homeassistant/components/cisco_ios/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/cisco_ios.py rename to homeassistant/components/cisco_ios/device_tracker.py diff --git a/homeassistant/components/cisco_mobility_express/device_tracker.py b/homeassistant/components/cisco_mobility_express/device_tracker.py index 60f8761aeeb..a722a994350 100644 --- a/homeassistant/components/cisco_mobility_express/device_tracker.py +++ b/homeassistant/components/cisco_mobility_express/device_tracker.py @@ -10,7 +10,7 @@ from homeassistant.const import ( CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_SSL, CONF_VERIFY_SSL) -REQUIREMENTS = ['ciscomobilityexpress==0.1.2'] +REQUIREMENTS = ['ciscomobilityexpress==0.1.5'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ciscospark/__init__.py b/homeassistant/components/ciscospark/__init__.py new file mode 100644 index 00000000000..f872a0257f7 --- /dev/null +++ b/homeassistant/components/ciscospark/__init__.py @@ -0,0 +1 @@ +"""The ciscospark component.""" diff --git a/homeassistant/components/notify/ciscospark.py b/homeassistant/components/ciscospark/notify.py similarity index 89% rename from homeassistant/components/notify/ciscospark.py rename to homeassistant/components/ciscospark/notify.py index e83e0e9024f..1eeb9b51f28 100644 --- a/homeassistant/components/notify/ciscospark.py +++ b/homeassistant/components/ciscospark/notify.py @@ -8,11 +8,12 @@ import logging import voluptuous as vol -from homeassistant.components.notify import ( - PLATFORM_SCHEMA, BaseNotificationService, ATTR_TITLE) -from homeassistant.const import (CONF_TOKEN) +from homeassistant.const import CONF_TOKEN import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import (ATTR_TITLE, PLATFORM_SCHEMA, + BaseNotificationService) + REQUIREMENTS = ['ciscosparkapi==0.4.2'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/citybikes/__init__.py b/homeassistant/components/citybikes/__init__.py new file mode 100644 index 00000000000..03ae63eb034 --- /dev/null +++ b/homeassistant/components/citybikes/__init__.py @@ -0,0 +1 @@ +"""The citybikes component.""" diff --git a/homeassistant/components/sensor/citybikes.py b/homeassistant/components/citybikes/sensor.py similarity index 100% rename from homeassistant/components/sensor/citybikes.py rename to homeassistant/components/citybikes/sensor.py diff --git a/homeassistant/components/clementine/__init__.py b/homeassistant/components/clementine/__init__.py new file mode 100644 index 00000000000..668ba937345 --- /dev/null +++ b/homeassistant/components/clementine/__init__.py @@ -0,0 +1 @@ +"""The clementine component.""" diff --git a/homeassistant/components/media_player/clementine.py b/homeassistant/components/clementine/media_player.py similarity index 100% rename from homeassistant/components/media_player/clementine.py rename to homeassistant/components/clementine/media_player.py diff --git a/homeassistant/components/clickatell/__init__.py b/homeassistant/components/clickatell/__init__.py new file mode 100644 index 00000000000..6c39bc749ad --- /dev/null +++ b/homeassistant/components/clickatell/__init__.py @@ -0,0 +1 @@ +"""The clickatell component.""" diff --git a/homeassistant/components/notify/clickatell.py b/homeassistant/components/clickatell/notify.py similarity index 88% rename from homeassistant/components/notify/clickatell.py rename to homeassistant/components/clickatell/notify.py index 6af2b455129..e473e54a3b7 100644 --- a/homeassistant/components/notify/clickatell.py +++ b/homeassistant/components/clickatell/notify.py @@ -9,10 +9,11 @@ import logging import requests import voluptuous as vol +from homeassistant.const import CONF_API_KEY, CONF_RECIPIENT import homeassistant.helpers.config_validation as cv -from homeassistant.const import (CONF_API_KEY, CONF_RECIPIENT) -from homeassistant.components.notify import ( - PLATFORM_SCHEMA, BaseNotificationService) + +from homeassistant.components.notify import (PLATFORM_SCHEMA, + BaseNotificationService) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/clicksend/__init__.py b/homeassistant/components/clicksend/__init__.py new file mode 100644 index 00000000000..3037224b9a1 --- /dev/null +++ b/homeassistant/components/clicksend/__init__.py @@ -0,0 +1 @@ +"""The clicksend component.""" diff --git a/homeassistant/components/notify/clicksend.py b/homeassistant/components/clicksend/notify.py similarity index 95% rename from homeassistant/components/notify/clicksend.py rename to homeassistant/components/clicksend/notify.py index faf30ac7cc6..3b2cdb7496d 100644 --- a/homeassistant/components/notify/clicksend.py +++ b/homeassistant/components/clicksend/notify.py @@ -7,16 +7,17 @@ https://home-assistant.io/components/notify.clicksend/ import json import logging +from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol -from aiohttp.hdrs import CONTENT_TYPE -import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import ( CONF_API_KEY, CONF_RECIPIENT, CONF_SENDER, CONF_USERNAME, CONTENT_TYPE_JSON) +import homeassistant.helpers.config_validation as cv + +from homeassistant.components.notify import (PLATFORM_SCHEMA, + BaseNotificationService) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/clicksend_tts/__init__.py b/homeassistant/components/clicksend_tts/__init__.py new file mode 100644 index 00000000000..53b59309701 --- /dev/null +++ b/homeassistant/components/clicksend_tts/__init__.py @@ -0,0 +1 @@ +"""The clicksend_tts component.""" diff --git a/homeassistant/components/notify/clicksend_tts.py b/homeassistant/components/clicksend_tts/notify.py similarity index 88% rename from homeassistant/components/notify/clicksend_tts.py rename to homeassistant/components/clicksend_tts/notify.py index c60e02ece1a..93e5126bbab 100644 --- a/homeassistant/components/notify/clicksend_tts.py +++ b/homeassistant/components/clicksend_tts/notify.py @@ -9,15 +9,16 @@ https://home-assistant.io/components/notify.clicksend_tts/ import json import logging +from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol -from aiohttp.hdrs import CONTENT_TYPE -import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import ( - CONF_API_KEY, CONF_USERNAME, CONF_RECIPIENT, CONTENT_TYPE_JSON) + CONF_API_KEY, CONF_RECIPIENT, CONF_USERNAME, CONTENT_TYPE_JSON) +import homeassistant.helpers.config_validation as cv + +from homeassistant.components.notify import (PLATFORM_SCHEMA, + BaseNotificationService) _LOGGER = logging.getLogger(__name__) @@ -27,6 +28,7 @@ HEADERS = {CONTENT_TYPE: CONTENT_TYPE_JSON} CONF_LANGUAGE = 'language' CONF_VOICE = 'voice' +CONF_CALLER = 'caller' DEFAULT_LANGUAGE = 'en-us' DEFAULT_VOICE = 'female' @@ -38,6 +40,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_RECIPIENT): cv.string, vol.Optional(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): cv.string, vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): cv.string, + vol.Optional(CONF_CALLER): cv.string, }) @@ -60,10 +63,13 @@ class ClicksendNotificationService(BaseNotificationService): self.recipient = config.get(CONF_RECIPIENT) self.language = config.get(CONF_LANGUAGE) self.voice = config.get(CONF_VOICE) + self.caller = config.get(CONF_CALLER) + if self.caller is None: + self.caller = self.recipient def send_message(self, message="", **kwargs): """Send a voice call to a user.""" - data = ({'messages': [{'source': 'hass.notify', 'from': self.recipient, + data = ({'messages': [{'source': 'hass.notify', 'from': self.caller, 'to': self.recipient, 'body': message, 'lang': self.language, 'voice': self.voice}]}) api_url = "{}/voice/send".format(BASE_API_URL) diff --git a/homeassistant/components/cloud/services.yaml b/homeassistant/components/cloud/services.yaml index 9ef814e0087..20c25225ce2 100644 --- a/homeassistant/components/cloud/services.yaml +++ b/homeassistant/components/cloud/services.yaml @@ -1,4 +1,4 @@ -# Describes the format for available light services +# Describes the format for available cloud services remote_connect: description: Make instance UI available outside over NabuCasa cloud. diff --git a/homeassistant/components/cmus/__init__.py b/homeassistant/components/cmus/__init__.py new file mode 100644 index 00000000000..f46f661ece3 --- /dev/null +++ b/homeassistant/components/cmus/__init__.py @@ -0,0 +1 @@ +"""The cmus component.""" diff --git a/homeassistant/components/media_player/cmus.py b/homeassistant/components/cmus/media_player.py similarity index 100% rename from homeassistant/components/media_player/cmus.py rename to homeassistant/components/cmus/media_player.py diff --git a/homeassistant/components/co2signal/__init__.py b/homeassistant/components/co2signal/__init__.py new file mode 100644 index 00000000000..a9c6422b4c6 --- /dev/null +++ b/homeassistant/components/co2signal/__init__.py @@ -0,0 +1 @@ +"""The co2signal component.""" diff --git a/homeassistant/components/sensor/co2signal.py b/homeassistant/components/co2signal/sensor.py similarity index 100% rename from homeassistant/components/sensor/co2signal.py rename to homeassistant/components/co2signal/sensor.py diff --git a/homeassistant/components/sensor/coinbase.py b/homeassistant/components/coinbase/sensor.py similarity index 100% rename from homeassistant/components/sensor/coinbase.py rename to homeassistant/components/coinbase/sensor.py diff --git a/homeassistant/components/coinmarketcap/__init__.py b/homeassistant/components/coinmarketcap/__init__.py new file mode 100644 index 00000000000..0cdb5a16a4a --- /dev/null +++ b/homeassistant/components/coinmarketcap/__init__.py @@ -0,0 +1 @@ +"""The coinmarketcap component.""" diff --git a/homeassistant/components/sensor/coinmarketcap.py b/homeassistant/components/coinmarketcap/sensor.py similarity index 100% rename from homeassistant/components/sensor/coinmarketcap.py rename to homeassistant/components/coinmarketcap/sensor.py diff --git a/homeassistant/components/comed_hourly_pricing/__init__.py b/homeassistant/components/comed_hourly_pricing/__init__.py new file mode 100644 index 00000000000..db6c2100e48 --- /dev/null +++ b/homeassistant/components/comed_hourly_pricing/__init__.py @@ -0,0 +1 @@ +"""The comed_hourly_pricing component.""" diff --git a/homeassistant/components/sensor/comed_hourly_pricing.py b/homeassistant/components/comed_hourly_pricing/sensor.py similarity index 100% rename from homeassistant/components/sensor/comed_hourly_pricing.py rename to homeassistant/components/comed_hourly_pricing/sensor.py diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py index a396dd276a5..88dcffcfd21 100644 --- a/homeassistant/components/comfoconnect/fan.py +++ b/homeassistant/components/comfoconnect/fan.py @@ -1,12 +1,12 @@ """Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" import logging -from homeassistant.components.comfoconnect import ( - DOMAIN, ComfoConnectBridge, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED) from homeassistant.components.fan import ( - FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, - SUPPORT_SET_SPEED) -from homeassistant.helpers.dispatcher import (dispatcher_connect) + SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_SET_SPEED, + FanEntity) +from homeassistant.helpers.dispatcher import dispatcher_connect + +from . import DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py index ac5de866cfb..edb96b8d279 100644 --- a/homeassistant/components/comfoconnect/sensor.py +++ b/homeassistant/components/comfoconnect/sensor.py @@ -1,15 +1,15 @@ """Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" import logging -from homeassistant.components.comfoconnect import ( - DOMAIN, ComfoConnectBridge, ATTR_CURRENT_TEMPERATURE, - ATTR_CURRENT_HUMIDITY, ATTR_OUTSIDE_TEMPERATURE, - ATTR_OUTSIDE_HUMIDITY, ATTR_AIR_FLOW_SUPPLY, - ATTR_AIR_FLOW_EXHAUST, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED) from homeassistant.const import CONF_RESOURCES, TEMP_CELSIUS from homeassistant.helpers.dispatcher import dispatcher_connect from homeassistant.helpers.entity import Entity +from . import ( + ATTR_AIR_FLOW_EXHAUST, ATTR_AIR_FLOW_SUPPLY, ATTR_CURRENT_HUMIDITY, + ATTR_CURRENT_TEMPERATURE, ATTR_OUTSIDE_HUMIDITY, ATTR_OUTSIDE_TEMPERATURE, + DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['comfoconnect'] diff --git a/homeassistant/components/command_line/__init__.py b/homeassistant/components/command_line/__init__.py new file mode 100644 index 00000000000..fe0640d3efa --- /dev/null +++ b/homeassistant/components/command_line/__init__.py @@ -0,0 +1 @@ +"""The command_line component.""" diff --git a/homeassistant/components/binary_sensor/command_line.py b/homeassistant/components/command_line/binary_sensor.py similarity index 92% rename from homeassistant/components/binary_sensor/command_line.py rename to homeassistant/components/command_line/binary_sensor.py index a3f1595787d..21ee1312e7a 100644 --- a/homeassistant/components/binary_sensor/command_line.py +++ b/homeassistant/components/command_line/binary_sensor.py @@ -4,18 +4,19 @@ Support for custom shell commands to retrieve values. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.command_line/ """ -import logging from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA) -from homeassistant.components.sensor.command_line import CommandSensorData + DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.const import ( - CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE, - CONF_COMMAND, CONF_DEVICE_CLASS) + CONF_COMMAND, CONF_DEVICE_CLASS, CONF_NAME, CONF_PAYLOAD_OFF, + CONF_PAYLOAD_ON, CONF_VALUE_TEMPLATE) +import homeassistant.helpers.config_validation as cv + +from .sensor import CommandSensorData _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/cover/command_line.py b/homeassistant/components/command_line/cover.py similarity index 100% rename from homeassistant/components/cover/command_line.py rename to homeassistant/components/command_line/cover.py diff --git a/homeassistant/components/notify/command_line.py b/homeassistant/components/command_line/notify.py similarity index 88% rename from homeassistant/components/notify/command_line.py rename to homeassistant/components/command_line/notify.py index 4a7483d6e4d..7ea5a6d8880 100644 --- a/homeassistant/components/notify/command_line.py +++ b/homeassistant/components/command_line/notify.py @@ -9,11 +9,12 @@ import subprocess import voluptuous as vol -from homeassistant.const import (CONF_COMMAND, CONF_NAME) -from homeassistant.components.notify import ( - BaseNotificationService, PLATFORM_SCHEMA) +from homeassistant.const import CONF_COMMAND, CONF_NAME import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import (PLATFORM_SCHEMA, + BaseNotificationService) + _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/sensor/command_line.py b/homeassistant/components/command_line/sensor.py similarity index 100% rename from homeassistant/components/sensor/command_line.py rename to homeassistant/components/command_line/sensor.py diff --git a/homeassistant/components/switch/command_line.py b/homeassistant/components/command_line/switch.py similarity index 100% rename from homeassistant/components/switch/command_line.py rename to homeassistant/components/command_line/switch.py diff --git a/homeassistant/components/concord232/__init__.py b/homeassistant/components/concord232/__init__.py new file mode 100644 index 00000000000..aec6c38ed5c --- /dev/null +++ b/homeassistant/components/concord232/__init__.py @@ -0,0 +1 @@ +"""The concord232 component.""" diff --git a/homeassistant/components/alarm_control_panel/concord232.py b/homeassistant/components/concord232/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/concord232.py rename to homeassistant/components/concord232/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/concord232.py b/homeassistant/components/concord232/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/concord232.py rename to homeassistant/components/concord232/binary_sensor.py diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index efabd03b586..7807c527370 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -32,7 +32,7 @@ ON_DEMAND = ('zwave',) async def async_setup(hass, config): """Set up the config component.""" await hass.components.frontend.async_register_built_in_panel( - 'config', 'config', 'hass:settings') + 'config', 'config', 'hass:settings', require_admin=True) async def setup_panel(panel_name): """Set up a panel.""" @@ -46,7 +46,6 @@ async def async_setup(hass, config): if success: key = '{}.{}'.format(DOMAIN, panel_name) hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: key}) - hass.config.components.add(key) @callback def component_loaded(event): diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 47ac9d3a4b2..175d90ff59c 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -2,11 +2,12 @@ from collections import OrderedDict import uuid -from homeassistant.components.config import EditIdBasedConfigView -from homeassistant.const import CONF_ID, SERVICE_RELOAD from homeassistant.components.automation import DOMAIN, PLATFORM_SCHEMA +from homeassistant.const import CONF_ID, SERVICE_RELOAD import homeassistant.helpers.config_validation as cv +from . import EditIdBasedConfigView + CONFIG_PATH = 'automations.yaml' diff --git a/homeassistant/components/config/customize.py b/homeassistant/components/config/customize.py index ec9d9d0ff27..85e9c0e6886 100644 --- a/homeassistant/components/config/customize.py +++ b/homeassistant/components/config/customize.py @@ -1,11 +1,11 @@ """Provide configuration end points for Customize.""" -from homeassistant.components.config import EditKeyBasedConfigView -from homeassistant.components import SERVICE_RELOAD_CORE_CONFIG +from homeassistant.components.homeassistant import SERVICE_RELOAD_CORE_CONFIG from homeassistant.config import DATA_CUSTOMIZE from homeassistant.core import DOMAIN - import homeassistant.helpers.config_validation as cv +from . import EditKeyBasedConfigView + CONFIG_PATH = 'customize.yaml' diff --git a/homeassistant/components/config/group.py b/homeassistant/components/config/group.py index f9b9a2c4918..60421bcc125 100644 --- a/homeassistant/components/config/group.py +++ b/homeassistant/components/config/group.py @@ -1,9 +1,9 @@ """Provide configuration end points for Groups.""" -from homeassistant.const import SERVICE_RELOAD -from homeassistant.components.config import EditKeyBasedConfigView from homeassistant.components.group import DOMAIN, GROUP_SCHEMA +from homeassistant.const import SERVICE_RELOAD import homeassistant.helpers.config_validation as cv +from . import EditKeyBasedConfigView CONFIG_PATH = 'groups.yaml' diff --git a/homeassistant/components/config/script.py b/homeassistant/components/config/script.py index 640e267d066..c8a58e5d72a 100644 --- a/homeassistant/components/config/script.py +++ b/homeassistant/components/config/script.py @@ -1,9 +1,9 @@ """Provide configuration end points for scripts.""" -from homeassistant.components.config import EditKeyBasedConfigView from homeassistant.components.script import DOMAIN, SCRIPT_ENTRY_SCHEMA from homeassistant.const import SERVICE_RELOAD import homeassistant.helpers.config_validation as cv +from . import EditKeyBasedConfigView CONFIG_PATH = 'scripts.yaml' diff --git a/homeassistant/components/config/zwave.py b/homeassistant/components/config/zwave.py index f29dde4594c..e7e39968401 100644 --- a/homeassistant/components/config/zwave.py +++ b/homeassistant/components/config/zwave.py @@ -4,13 +4,14 @@ import logging from aiohttp.web import Response -from homeassistant.components.config import EditKeyBasedConfigView from homeassistant.components.http import HomeAssistantView from homeassistant.components.zwave import DEVICE_CONFIG_SCHEMA_ENTRY, const from homeassistant.const import HTTP_NOT_FOUND, HTTP_OK import homeassistant.core as ha import homeassistant.helpers.config_validation as cv +from . import EditKeyBasedConfigView + _LOGGER = logging.getLogger(__name__) CONFIG_PATH = 'zwave_device_config.yaml' OZW_LOG_FILENAME = 'OZW_Log.txt' diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index 7082cb36726..bb2d692f249 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -6,17 +6,16 @@ import voluptuous as vol from homeassistant import core from homeassistant.components import http -from homeassistant.components.conversation.util import create_matcher -from homeassistant.components.http.data_validator import ( - RequestDataValidator) -from homeassistant.components.cover import (INTENT_OPEN_COVER, - INTENT_CLOSE_COVER) +from homeassistant.components.cover import ( + INTENT_CLOSE_COVER, INTENT_OPEN_COVER) +from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.const import EVENT_COMPONENT_LOADED from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import intent +from homeassistant.helpers import config_validation as cv, intent from homeassistant.loader import bind_hass -from homeassistant.setup import (ATTR_COMPONENT) +from homeassistant.setup import ATTR_COMPONENT + +from .util import create_matcher _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/coolmaster/__init__.py b/homeassistant/components/coolmaster/__init__.py new file mode 100644 index 00000000000..b27ae5f25b4 --- /dev/null +++ b/homeassistant/components/coolmaster/__init__.py @@ -0,0 +1 @@ +"""The coolmaster component.""" diff --git a/homeassistant/components/climate/coolmaster.py b/homeassistant/components/coolmaster/climate.py similarity index 100% rename from homeassistant/components/climate/coolmaster.py rename to homeassistant/components/coolmaster/climate.py diff --git a/homeassistant/components/cpuspeed/__init__.py b/homeassistant/components/cpuspeed/__init__.py new file mode 100644 index 00000000000..c6121a68835 --- /dev/null +++ b/homeassistant/components/cpuspeed/__init__.py @@ -0,0 +1 @@ +"""The cpuspeed component.""" diff --git a/homeassistant/components/sensor/cpuspeed.py b/homeassistant/components/cpuspeed/sensor.py similarity index 91% rename from homeassistant/components/sensor/cpuspeed.py rename to homeassistant/components/cpuspeed/sensor.py index f69d0b285ba..98d22c20d15 100644 --- a/homeassistant/components/sensor/cpuspeed.py +++ b/homeassistant/components/cpuspeed/sensor.py @@ -1,9 +1,4 @@ -""" -Support for displaying the current CPU speed. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.cpuspeed/ -""" +"""Support for displaying the current CPU speed.""" import logging import voluptuous as vol @@ -13,7 +8,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['py-cpuinfo==4.0.0'] +REQUIREMENTS = ['py-cpuinfo==5.0.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/crimereports/__init__.py b/homeassistant/components/crimereports/__init__.py new file mode 100644 index 00000000000..57af9df4dbf --- /dev/null +++ b/homeassistant/components/crimereports/__init__.py @@ -0,0 +1 @@ +"""The crimereports component.""" diff --git a/homeassistant/components/sensor/crimereports.py b/homeassistant/components/crimereports/sensor.py similarity index 100% rename from homeassistant/components/sensor/crimereports.py rename to homeassistant/components/crimereports/sensor.py diff --git a/homeassistant/components/cups/__init__.py b/homeassistant/components/cups/__init__.py new file mode 100644 index 00000000000..7cd5ce4ca0a --- /dev/null +++ b/homeassistant/components/cups/__init__.py @@ -0,0 +1 @@ +"""The cups component.""" diff --git a/homeassistant/components/sensor/cups.py b/homeassistant/components/cups/sensor.py similarity index 97% rename from homeassistant/components/sensor/cups.py rename to homeassistant/components/cups/sensor.py index b002d39352a..99dadcfe596 100644 --- a/homeassistant/components/sensor/cups.py +++ b/homeassistant/components/cups/sensor.py @@ -4,6 +4,7 @@ Details about printers which are connected to CUPS. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.cups/ """ +import importlib import logging from datetime import timedelta @@ -140,7 +141,7 @@ class CupsData: def update(self): """Get the latest data from CUPS.""" - from cups import Connection + cups = importlib.import_module('cups') - conn = Connection(host=self._host, port=self._port) + conn = cups.Connection(host=self._host, port=self._port) self.printers = conn.getPrinters() diff --git a/homeassistant/components/currencylayer/__init__.py b/homeassistant/components/currencylayer/__init__.py new file mode 100644 index 00000000000..237392ec13d --- /dev/null +++ b/homeassistant/components/currencylayer/__init__.py @@ -0,0 +1 @@ +"""The currencylayer component.""" diff --git a/homeassistant/components/sensor/currencylayer.py b/homeassistant/components/currencylayer/sensor.py similarity index 100% rename from homeassistant/components/sensor/currencylayer.py rename to homeassistant/components/currencylayer/sensor.py diff --git a/homeassistant/components/daikin/.translations/bg.json b/homeassistant/components/daikin/.translations/bg.json new file mode 100644 index 00000000000..beb1bc0d6e6 --- /dev/null +++ b/homeassistant/components/daikin/.translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "\u0410\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/daikin/.translations/es-419.json b/homeassistant/components/daikin/.translations/es-419.json index dae3afdba6f..6fa2b664a30 100644 --- a/homeassistant/components/daikin/.translations/es-419.json +++ b/homeassistant/components/daikin/.translations/es-419.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "device_fail": "Error inesperado al crear el dispositivo.", "device_timeout": "Tiempo de espera de conexi\u00f3n al dispositivo." }, "step": { diff --git a/homeassistant/components/daikin/.translations/hu.json b/homeassistant/components/daikin/.translations/hu.json index cbca935f551..f433a6215b8 100644 --- a/homeassistant/components/daikin/.translations/hu.json +++ b/homeassistant/components/daikin/.translations/hu.json @@ -8,7 +8,7 @@ "step": { "user": { "data": { - "host": "Kiszolg\u00e1l\u00f3" + "host": "Hoszt" }, "description": "Add meg a Daikin l\u00e9gkond\u00edcion\u00e1l\u00f3 IP-c\u00edm\u00e9t.", "title": "A Daikin l\u00e9gkond\u00edcion\u00e1l\u00f3 konfigur\u00e1l\u00e1sa" diff --git a/homeassistant/components/daikin/.translations/ru.json b/homeassistant/components/daikin/.translations/ru.json index 0549aa3b160..ce1f1ab3caa 100644 --- a/homeassistant/components/daikin/.translations/ru.json +++ b/homeassistant/components/daikin/.translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", "device_fail": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", "device_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443." }, diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index ce4a58162c7..5ad21f5954f 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -8,25 +8,24 @@ import async_timeout import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_HOSTS +from homeassistant.const import CONF_HOSTS, CONF_HOST import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import Throttle from . import config_flow # noqa pylint_disable=unused-import -from .const import KEY_HOST -REQUIREMENTS = ['pydaikin==0.9'] +REQUIREMENTS = ['pydaikin==1.3.1'] _LOGGER = logging.getLogger(__name__) DOMAIN = 'daikin' - +PARALLEL_UPDATES = 0 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -COMPONENT_TYPES = ['climate', 'sensor'] +COMPONENT_TYPES = ['climate', 'sensor', 'switch'] CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -53,7 +52,7 @@ async def async_setup(hass, config): DOMAIN, context={'source': SOURCE_IMPORT}, data={ - KEY_HOST: host, + CONF_HOST: host, })) return True @@ -61,7 +60,7 @@ async def async_setup(hass, config): async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Establish connection with Daikin.""" conf = entry.data - daikin_api = await daikin_api_setup(hass, conf[KEY_HOST]) + daikin_api = await daikin_api_setup(hass, conf[CONF_HOST]) if not daikin_api: return False hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: daikin_api}) @@ -87,9 +86,11 @@ async def async_unload_entry(hass, config_entry): async def daikin_api_setup(hass, host): """Create a Daikin instance only once.""" from pydaikin.appliance import Appliance + session = hass.helpers.aiohttp_client.async_get_clientsession() try: with async_timeout.timeout(10): - device = await hass.async_add_executor_job(Appliance, host) + device = Appliance(host, session) + await device.init() except asyncio.TimeoutError: _LOGGER.error("Connection to Daikin could not be established") return None @@ -97,8 +98,7 @@ async def daikin_api_setup(hass, host): _LOGGER.error("Unexpected error creating device") return None - name = device.values['name'] - api = DaikinApi(device, name) + api = DaikinApi(device) return api @@ -106,21 +106,29 @@ async def daikin_api_setup(hass, host): class DaikinApi: """Keep the Daikin instance in one place and centralize the update.""" - def __init__(self, device, name): + def __init__(self, device): """Initialize the Daikin Handle.""" self.device = device - self.name = name + self.name = device.values['name'] self.ip_address = device.ip + self._available = True @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self, **kwargs): + async def async_update(self, **kwargs): """Pull the latest data from Daikin.""" try: - self.device.update_status() + await self.device.update_status() + self._available = True except timeout: _LOGGER.warning( "Connection failed for %s", self.ip_address ) + self._available = False + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return self._available @property def mac(self): diff --git a/homeassistant/components/daikin/climate.py b/homeassistant/components/daikin/climate.py index 775e4a216e5..c42f2785576 100644 --- a/homeassistant/components/daikin/climate.py +++ b/homeassistant/components/daikin/climate.py @@ -4,19 +4,20 @@ import re import voluptuous as vol -from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_OPERATION_MODE, - ATTR_SWING_MODE, STATE_AUTO, STATE_COOL, STATE_DRY, - STATE_FAN_ONLY, STATE_HEAT, SUPPORT_FAN_MODE, - SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE) -from homeassistant.components.daikin import DOMAIN as DAIKIN_DOMAIN -from homeassistant.components.daikin.const import ( - ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE) + ATTR_SWING_MODE, STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, + STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, + SUPPORT_TARGET_TEMPERATURE) from homeassistant.const import ( ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, STATE_OFF, TEMP_CELSIUS) import homeassistant.helpers.config_validation as cv +from . import DOMAIN as DAIKIN_DOMAIN +from .const import ( + ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE) + _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -146,7 +147,7 @@ class DaikinClimate(ClimateDevice): return value - def set(self, settings): + async def _set(self, settings): """Set device settings using API.""" values = {} @@ -173,7 +174,7 @@ class DaikinClimate(ClimateDevice): _LOGGER.error("Invalid temperature %s", value) if values: - self._api.device.set(values) + await self._api.device.set(values) @property def supported_features(self): @@ -210,9 +211,9 @@ class DaikinClimate(ClimateDevice): """Return the supported step of target temperature.""" return 1 - def set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs): """Set new target temperature.""" - self.set(kwargs) + await self._set(kwargs) @property def current_operation(self): @@ -224,18 +225,18 @@ class DaikinClimate(ClimateDevice): """Return the list of available operation modes.""" return self._list.get(ATTR_OPERATION_MODE) - def set_operation_mode(self, operation_mode): + async def async_set_operation_mode(self, operation_mode): """Set HVAC mode.""" - self.set({ATTR_OPERATION_MODE: operation_mode}) + await self._set({ATTR_OPERATION_MODE: operation_mode}) @property def current_fan_mode(self): """Return the fan setting.""" return self.get(ATTR_FAN_MODE) - def set_fan_mode(self, fan_mode): + async def async_set_fan_mode(self, fan_mode): """Set fan mode.""" - self.set({ATTR_FAN_MODE: fan_mode}) + await self._set({ATTR_FAN_MODE: fan_mode}) @property def fan_list(self): @@ -247,18 +248,18 @@ class DaikinClimate(ClimateDevice): """Return the fan setting.""" return self.get(ATTR_SWING_MODE) - def set_swing_mode(self, swing_mode): + async def async_set_swing_mode(self, swing_mode): """Set new target temperature.""" - self.set({ATTR_SWING_MODE: swing_mode}) + await self._set({ATTR_SWING_MODE: swing_mode}) @property def swing_list(self): """List of available swing modes.""" return self._list.get(ATTR_SWING_MODE) - def update(self): + async def async_update(self): """Retrieve latest state.""" - self._api.update() + await self._api.async_update() @property def device_info(self): diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index b46e800c3d8..590b5a02738 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -6,8 +6,9 @@ import async_timeout import voluptuous as vol from homeassistant import config_entries +from homeassistant.const import CONF_HOST -from .const import KEY_HOST, KEY_IP, KEY_MAC +from .const import KEY_IP, KEY_MAC _LOGGER = logging.getLogger(__name__) @@ -29,7 +30,7 @@ class FlowHandler(config_entries.ConfigFlow): return self.async_create_entry( title=host, data={ - KEY_HOST: host, + CONF_HOST: host, KEY_MAC: mac }) @@ -55,14 +56,14 @@ class FlowHandler(config_entries.ConfigFlow): return self.async_show_form( step_id='user', data_schema=vol.Schema({ - vol.Required(KEY_HOST): str + vol.Required(CONF_HOST): str }) ) - return await self._create_device(user_input[KEY_HOST]) + return await self._create_device(user_input[CONF_HOST]) async def async_step_import(self, user_input): """Import a config entry.""" - host = user_input.get(KEY_HOST) + host = user_input.get(CONF_HOST) if not host: return await self.async_step_user() return await self._create_device(host) diff --git a/homeassistant/components/daikin/const.py b/homeassistant/components/daikin/const.py index cd2742b5b5e..90967904579 100644 --- a/homeassistant/components/daikin/const.py +++ b/homeassistant/components/daikin/const.py @@ -20,6 +20,5 @@ SENSOR_TYPES = { } } -KEY_HOST = 'host' KEY_MAC = 'mac' KEY_IP = 'ip' diff --git a/homeassistant/components/daikin/sensor.py b/homeassistant/components/daikin/sensor.py index 6065a182274..5a005e29989 100644 --- a/homeassistant/components/daikin/sensor.py +++ b/homeassistant/components/daikin/sensor.py @@ -1,14 +1,15 @@ """Support for Daikin AC sensors.""" import logging -from homeassistant.components.daikin import DOMAIN as DAIKIN_DOMAIN -from homeassistant.components.daikin.const import ( - ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, SENSOR_TYPE_TEMPERATURE, - SENSOR_TYPES) from homeassistant.const import CONF_ICON, CONF_NAME, CONF_TYPE from homeassistant.helpers.entity import Entity from homeassistant.util.unit_system import UnitSystem +from . import DOMAIN as DAIKIN_DOMAIN +from .const import ( + ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, SENSOR_TYPE_TEMPERATURE, + SENSOR_TYPES) + _LOGGER = logging.getLogger(__name__) @@ -96,9 +97,9 @@ class DaikinClimateSensor(Entity): """Return the unit of measurement.""" return self._unit_of_measurement - def update(self): + async def async_update(self): """Retrieve latest state.""" - self._api.update() + await self._api.async_update() @property def device_info(self): diff --git a/homeassistant/components/daikin/switch.py b/homeassistant/components/daikin/switch.py new file mode 100644 index 00000000000..29159c60fe9 --- /dev/null +++ b/homeassistant/components/daikin/switch.py @@ -0,0 +1,77 @@ +"""Support for Daikin AirBase zones.""" +import logging + +from homeassistant.helpers.entity import ToggleEntity + +from . import DOMAIN as DAIKIN_DOMAIN + +_LOGGER = logging.getLogger(__name__) + +ZONE_ICON = 'mdi:home-circle' + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Old way of setting up the platform. + + Can only be called when a user accidentally mentions the platform in their + config. But even in that case it would have been ignored. + """ + pass + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Daikin climate based on config_entry.""" + daikin_api = hass.data[DAIKIN_DOMAIN][entry.entry_id] + zones = daikin_api.device.zones + if zones: + async_add_entities([ + DaikinZoneSwitch(daikin_api, zone_id) + for zone_id in range(len(zones)) + ]) + + +class DaikinZoneSwitch(ToggleEntity): + """Representation of a zone.""" + + def __init__(self, daikin_api, zone_id): + """Initialize the zone.""" + self._api = daikin_api + self._zone_id = zone_id + + @property + def unique_id(self): + """Return a unique ID.""" + return "{}-zone{}".format(self._api.mac, self._zone_id) + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return ZONE_ICON + + @property + def name(self): + """Return the name of the sensor.""" + return "{} {}".format(self._api.name, + self._api.device.zones[self._zone_id][0]) + + @property + def is_on(self): + """Return the state of the sensor.""" + return self._api.device.zones[self._zone_id][1] == '1' + + @property + def device_info(self): + """Return a device description for device registry.""" + return self._api.device_info + + async def async_update(self): + """Retrieve latest state.""" + await self._api.async_update() + + async def async_turn_on(self, **kwargs): + """Turn the zone on.""" + await self._api.device.set_zone(self._zone_id, '1') + + async def async_turn_off(self, **kwargs): + """Turn the zone off.""" + await self._api.device.set_zone(self._zone_id, '0') diff --git a/homeassistant/components/danfoss_air/binary_sensor.py b/homeassistant/components/danfoss_air/binary_sensor.py index 4052a100540..723b0d08801 100644 --- a/homeassistant/components/danfoss_air/binary_sensor.py +++ b/homeassistant/components/danfoss_air/binary_sensor.py @@ -1,7 +1,7 @@ """Support for the for Danfoss Air HRV binary sensors.""" from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.danfoss_air import DOMAIN \ - as DANFOSS_AIR_DOMAIN + +from . import DOMAIN as DANFOSS_AIR_DOMAIN def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/danfoss_air/sensor.py b/homeassistant/components/danfoss_air/sensor.py index 9902184e624..a5dc2a2eb09 100644 --- a/homeassistant/components/danfoss_air/sensor.py +++ b/homeassistant/components/danfoss_air/sensor.py @@ -1,13 +1,13 @@ """Support for the for Danfoss Air HRV sensors.""" import logging -from homeassistant.components.danfoss_air import DOMAIN \ - as DANFOSS_AIR_DOMAIN from homeassistant.const import ( - TEMP_CELSIUS, DEVICE_CLASS_BATTERY, - DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY) + DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS) from homeassistant.helpers.entity import Entity +from . import DOMAIN as DANFOSS_AIR_DOMAIN + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/danfoss_air/switch.py b/homeassistant/components/danfoss_air/switch.py index ec85757be59..f5a7fd47f69 100644 --- a/homeassistant/components/danfoss_air/switch.py +++ b/homeassistant/components/danfoss_air/switch.py @@ -1,10 +1,9 @@ """Support for the for Danfoss Air HRV sswitches.""" import logging -from homeassistant.components.switch import ( - SwitchDevice) -from homeassistant.components.danfoss_air import DOMAIN \ - as DANFOSS_AIR_DOMAIN +from homeassistant.components.switch import SwitchDevice + +from . import DOMAIN as DANFOSS_AIR_DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/darksky/__init__.py b/homeassistant/components/darksky/__init__.py new file mode 100644 index 00000000000..90a5d06dc0e --- /dev/null +++ b/homeassistant/components/darksky/__init__.py @@ -0,0 +1 @@ +"""The darksky component.""" diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/darksky/sensor.py similarity index 92% rename from homeassistant/components/sensor/darksky.py rename to homeassistant/components/darksky/sensor.py index c68bb2cd3a3..540568b5785 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/darksky/sensor.py @@ -27,6 +27,7 @@ _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Powered by Dark Sky" CONF_FORECAST = 'forecast' +CONF_HOURLY_FORECAST = 'hourly_forecast' CONF_LANGUAGE = 'language' CONF_UNITS = 'units' @@ -193,6 +194,8 @@ PLATFORM_SCHEMA = vol.All( vol.All(cv.time_period, cv.positive_timedelta), vol.Optional(CONF_FORECAST): vol.All(cv.ensure_list, [vol.Range(min=0, max=7)]), + vol.Optional(CONF_HOURLY_FORECAST): + vol.All(cv.ensure_list, [vol.Range(min=0, max=48)]), }), cv.deprecated( CONF_UPDATE_INTERVAL, @@ -230,6 +233,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): name = config.get(CONF_NAME) forecast = config.get(CONF_FORECAST) + forecast_hour = config.get(CONF_HOURLY_FORECAST) sensors = [] for variable in config[CONF_MONITORED_CONDITIONS]: if variable in DEPRECATED_SENSOR_TYPES: @@ -240,7 +244,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if forecast is not None and 'daily' in SENSOR_TYPES[variable][7]: for forecast_day in forecast: sensors.append(DarkSkySensor( - forecast_data, variable, name, forecast_day)) + forecast_data, variable, name, forecast_day=forecast_day)) + if forecast_hour is not None and 'hourly' in SENSOR_TYPES[variable][7]: + for forecast_h in forecast_hour: + sensors.append(DarkSkySensor( + forecast_data, variable, name, forecast_hour=forecast_h)) add_entities(sensors, True) @@ -248,13 +256,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class DarkSkySensor(Entity): """Implementation of a Dark Sky sensor.""" - def __init__(self, forecast_data, sensor_type, name, forecast_day=None): + def __init__(self, forecast_data, sensor_type, name, + forecast_day=None, forecast_hour=None): """Initialize the sensor.""" self.client_name = name self._name = SENSOR_TYPES[sensor_type][0] self.forecast_data = forecast_data self.type = sensor_type self.forecast_day = forecast_day + self.forecast_hour = forecast_hour self._state = None self._icon = None self._unit_of_measurement = None @@ -262,11 +272,14 @@ class DarkSkySensor(Entity): @property def name(self): """Return the name of the sensor.""" - if self.forecast_day is None: - return '{} {}'.format(self.client_name, self._name) - - return '{} {} {}'.format( - self.client_name, self._name, self.forecast_day) + if self.forecast_day is not None: + return '{} {} {}d'.format( + self.client_name, self._name, self.forecast_day) + if self.forecast_hour is not None: + return '{} {} {}h'.format( + self.client_name, self._name, self.forecast_hour) + return '{} {}'.format( + self.client_name, self._name) @property def state(self): @@ -339,6 +352,13 @@ class DarkSkySensor(Entity): hourly = self.forecast_data.data_hourly self._state = getattr(hourly, 'summary', '') self._icon = getattr(hourly, 'icon', '') + elif self.forecast_hour is not None: + self.forecast_data.update_hourly() + hourly = self.forecast_data.data_hourly + if hasattr(hourly, 'data'): + self._state = self.get_state(hourly.data[self.forecast_hour]) + else: + self._state = 0 elif self.type == 'daily_summary': self.forecast_data.update_daily() daily = self.forecast_data.data_daily diff --git a/homeassistant/components/weather/darksky.py b/homeassistant/components/darksky/weather.py similarity index 94% rename from homeassistant/components/weather/darksky.py rename to homeassistant/components/darksky/weather.py index 17e3cbbcf14..5b3db4312bf 100644 --- a/homeassistant/components/weather/darksky.py +++ b/homeassistant/components/darksky/weather.py @@ -12,10 +12,10 @@ from homeassistant.components.weather import ( ATTR_FORECAST_WIND_SPEED, PLATFORM_SCHEMA, WeatherEntity) from homeassistant.const import ( CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE, CONF_NAME, - TEMP_CELSIUS, TEMP_FAHRENHEIT) + PRESSURE_HPA, PRESSURE_INHG, TEMP_CELSIUS, TEMP_FAHRENHEIT) import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle - +from homeassistant.util.pressure import convert as convert_pressure REQUIREMENTS = ['python-forecastio==1.4.0'] _LOGGER = logging.getLogger(__name__) @@ -131,7 +131,11 @@ class DarkSkyWeather(WeatherEntity): @property def pressure(self): """Return the pressure.""" - return self._ds_currently.get('pressure') + pressure = self._ds_currently.get('pressure') + if 'us' in self._dark_sky.units: + return round( + convert_pressure(pressure, PRESSURE_HPA, PRESSURE_INHG), 2) + return pressure @property def visibility(self): @@ -232,4 +236,6 @@ class DarkSkyData: @property def units(self): """Get the unit system of returned data.""" + if self.data is None: + return None return self.data.json.get('flags').get('units') diff --git a/homeassistant/components/ddwrt/__init__.py b/homeassistant/components/ddwrt/__init__.py new file mode 100644 index 00000000000..a2c36811285 --- /dev/null +++ b/homeassistant/components/ddwrt/__init__.py @@ -0,0 +1 @@ +"""The ddwrt component.""" diff --git a/homeassistant/components/device_tracker/ddwrt.py b/homeassistant/components/ddwrt/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/ddwrt.py rename to homeassistant/components/ddwrt/device_tracker.py diff --git a/homeassistant/components/deconz/.translations/bg.json b/homeassistant/components/deconz/.translations/bg.json index 2ea65762063..c2cc8f97a89 100644 --- a/homeassistant/components/deconz/.translations/bg.json +++ b/homeassistant/components/deconz/.translations/bg.json @@ -11,7 +11,7 @@ "step": { "init": { "data": { - "host": "\u0425\u043e\u0441\u0442", + "host": "\u0410\u0434\u0440\u0435\u0441", "port": "\u041f\u043e\u0440\u0442 (\u0441\u0442\u043e\u0439\u043d\u043e\u0441\u0442 \u043f\u043e \u043f\u043e\u0434\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043d\u0435: '80')" }, "title": "\u0414\u0435\u0444\u0438\u043d\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 deCONZ \u0448\u043b\u044e\u0437" diff --git a/homeassistant/components/deconz/.translations/fr.json b/homeassistant/components/deconz/.translations/fr.json index 56399a3c6fd..d18df13701e 100644 --- a/homeassistant/components/deconz/.translations/fr.json +++ b/homeassistant/components/deconz/.translations/fr.json @@ -12,7 +12,7 @@ "init": { "data": { "host": "H\u00f4te", - "port": "Port (valeur par d\u00e9faut : 80)" + "port": "Port" }, "title": "Initialiser la passerelle deCONZ" }, diff --git a/homeassistant/components/deconz/.translations/hu.json b/homeassistant/components/deconz/.translations/hu.json index 06211f61bf2..5bf8db46841 100644 --- a/homeassistant/components/deconz/.translations/hu.json +++ b/homeassistant/components/deconz/.translations/hu.json @@ -11,7 +11,7 @@ "step": { "init": { "data": { - "host": "H\u00e1zigazda (Host)", + "host": "Hoszt", "port": "Port" }, "title": "deCONZ \u00e1tj\u00e1r\u00f3 megad\u00e1sa" diff --git a/homeassistant/components/deconz/.translations/ru.json b/homeassistant/components/deconz/.translations/ru.json index c92f1562157..5fd31ab9d8f 100644 --- a/homeassistant/components/deconz/.translations/ru.json +++ b/homeassistant/components/deconz/.translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", "no_bridges": "\u0428\u043b\u044e\u0437\u044b deCONZ \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b", "one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 deCONZ" }, diff --git a/homeassistant/components/deconz/.translations/sl.json b/homeassistant/components/deconz/.translations/sl.json index cea6f8ef4dd..686bb5b1e2e 100644 --- a/homeassistant/components/deconz/.translations/sl.json +++ b/homeassistant/components/deconz/.translations/sl.json @@ -17,7 +17,7 @@ "title": "Dolo\u010dite deCONZ prehod" }, "link": { - "description": "Odklenite va\u0161 deCONZ gateway za registracijo z Home Assistant-om. \n1. Pojdite v deCONT sistemske nastavitve\n2. Pritisnite tipko \"odkleni prehod\"", + "description": "Odklenite va\u0161 deCONZ gateway za registracijo s Home Assistant-om. \n1. Pojdite v deCONZ sistemske nastavitve\n2. Pritisnite tipko \"odkleni prehod\"", "title": "Povezava z deCONZ" }, "options": { diff --git a/homeassistant/components/deconz/.translations/th.json b/homeassistant/components/deconz/.translations/th.json new file mode 100644 index 00000000000..e40765e8220 --- /dev/null +++ b/homeassistant/components/deconz/.translations/th.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "init": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index d107cba8f7b..8bdd946e2ef 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -12,7 +12,7 @@ from .config_flow import configured_hosts from .const import DEFAULT_PORT, DOMAIN, _LOGGER from .gateway import DeconzGateway -REQUIREMENTS = ['pydeconz==52'] +REQUIREMENTS = ['pydeconz==54'] CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -124,8 +124,7 @@ async def async_setup_entry(hass, config_entry): scenes = set(gateway.api.scenes.keys()) sensors = set(gateway.api.sensors.keys()) - if not await gateway.api.async_load_parameters(): - return + await gateway.api.async_load_parameters() gateway.async_add_device_callback( 'group', [group diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index cb68b842f4a..2b0c2037248 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -11,6 +11,10 @@ from .deconz_device import DeconzDevice DEPENDENCIES = ['deconz'] +ATTR_ORIENTATION = 'orientation' +ATTR_TILTANGLE = 'tiltangle' +ATTR_VIBRATIONSTRENGTH = 'vibrationstrength' + async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): @@ -74,7 +78,7 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorDevice): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - from pydeconz.sensor import PRESENCE + from pydeconz.sensor import PRESENCE, VIBRATION attr = {} if self._device.battery: attr[ATTR_BATTERY_LEVEL] = self._device.battery @@ -82,4 +86,8 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorDevice): attr[ATTR_ON] = self._device.on if self._device.type in PRESENCE and self._device.dark is not None: attr[ATTR_DARK] = self._device.dark + elif self._device.type in VIBRATION: + attr[ATTR_ORIENTATION] = self._device.orientation + attr[ATTR_TILTANGLE] = self._device.tiltangle + attr[ATTR_VIBRATIONSTRENGTH] = self._device.vibrationstrength return attr diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 8f90f303fca..38849fb37b3 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -1,15 +1,18 @@ """Config flow to configure deCONZ component.""" +import asyncio + +import async_timeout import voluptuous as vol from homeassistant import config_entries -from homeassistant.core import callback from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT +from homeassistant.core import callback from homeassistant.helpers import aiohttp_client from .const import ( - CONF_ALLOW_DECONZ_GROUPS, CONF_ALLOW_CLIP_SENSOR, DEFAULT_PORT, DOMAIN) - -CONF_BRIDGEID = 'bridgeid' + CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID, + DEFAULT_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_DECONZ_GROUPS, DEFAULT_PORT, + DOMAIN) @callback @@ -26,21 +29,20 @@ class DeconzFlowHandler(config_entries.ConfigFlow): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + _hassio_discovery = None + def __init__(self): """Initialize the deCONZ config flow.""" self.bridges = [] self.deconz_config = {} async def async_step_user(self, user_input=None): - """Handle a flow initialized by the user.""" - return await self.async_step_init(user_input) - - async def async_step_init(self, user_input=None): """Handle a deCONZ config flow start. Only allows one instance to be set up. If only one bridge is found go to link step. If more than one bridge is found let user choose bridge to link. + If no bridge is found allow user to manually input configuration. """ from pydeconz.utils import async_discovery @@ -52,11 +54,18 @@ class DeconzFlowHandler(config_entries.ConfigFlow): if bridge[CONF_HOST] == user_input[CONF_HOST]: self.deconz_config = bridge return await self.async_step_link() + self.deconz_config = user_input return await self.async_step_link() session = aiohttp_client.async_get_clientsession(self.hass) - self.bridges = await async_discovery(session) + + try: + with async_timeout.timeout(10): + self.bridges = await async_discovery(session) + + except asyncio.TimeoutError: + self.bridges = [] if len(self.bridges) == 1: self.deconz_config = self.bridges[0] @@ -64,8 +73,10 @@ class DeconzFlowHandler(config_entries.ConfigFlow): if len(self.bridges) > 1: hosts = [] + for bridge in self.bridges: hosts.append(bridge[CONF_HOST]) + return self.async_show_form( step_id='init', data_schema=vol.Schema({ @@ -74,7 +85,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow): ) return self.async_show_form( - step_id='user', + step_id='init', data_schema=vol.Schema({ vol.Required(CONF_HOST): str, vol.Required(CONF_PORT, default=DEFAULT_PORT): int, @@ -83,18 +94,27 @@ class DeconzFlowHandler(config_entries.ConfigFlow): async def async_step_link(self, user_input=None): """Attempt to link with the deCONZ bridge.""" + from pydeconz.errors import ResponseError, RequestError from pydeconz.utils import async_get_api_key errors = {} if user_input is not None: if configured_hosts(self.hass): return self.async_abort(reason='one_instance_only') + session = aiohttp_client.async_get_clientsession(self.hass) - api_key = await async_get_api_key(session, **self.deconz_config) - if api_key: + + try: + with async_timeout.timeout(10): + api_key = await async_get_api_key( + session, **self.deconz_config) + + except (ResponseError, RequestError, asyncio.TimeoutError): + errors['base'] = 'no_key' + + else: self.deconz_config[CONF_API_KEY] = api_key return await self.async_step_options() - errors['base'] = 'no_key' return self.async_show_form( step_id='link', @@ -117,8 +137,14 @@ class DeconzFlowHandler(config_entries.ConfigFlow): if CONF_BRIDGEID not in self.deconz_config: session = aiohttp_client.async_get_clientsession(self.hass) - self.deconz_config[CONF_BRIDGEID] = await async_get_bridgeid( - session, **self.deconz_config) + try: + with async_timeout.timeout(10): + self.deconz_config[CONF_BRIDGEID] = \ + await async_get_bridgeid( + session, **self.deconz_config) + + except asyncio.TimeoutError: + return self.async_abort(reason='no_bridges') return self.async_create_entry( title='deCONZ-' + self.deconz_config[CONF_BRIDGEID], @@ -128,8 +154,10 @@ class DeconzFlowHandler(config_entries.ConfigFlow): return self.async_show_form( step_id='options', data_schema=vol.Schema({ - vol.Optional(CONF_ALLOW_CLIP_SENSOR): bool, - vol.Optional(CONF_ALLOW_DECONZ_GROUPS): bool, + vol.Optional(CONF_ALLOW_CLIP_SENSOR, + default=DEFAULT_ALLOW_CLIP_SENSOR): bool, + vol.Optional(CONF_ALLOW_DECONZ_GROUPS, + default=DEFAULT_ALLOW_DECONZ_GROUPS): bool, }), ) @@ -168,3 +196,45 @@ class DeconzFlowHandler(config_entries.ConfigFlow): user_input = {CONF_ALLOW_CLIP_SENSOR: True, CONF_ALLOW_DECONZ_GROUPS: True} return await self.async_step_options(user_input=user_input) + + async def async_step_hassio(self, user_input=None): + """Prepare configuration for a Hass.io deCONZ bridge. + + This flow is triggered by the discovery component. + """ + if configured_hosts(self.hass): + return self.async_abort(reason='one_instance_only') + + self._hassio_discovery = user_input + + return await self.async_step_hassio_confirm() + + async def async_step_hassio_confirm(self, user_input=None): + """Confirm a Hass.io discovery.""" + if user_input is not None: + data = self._hassio_discovery + + return self.async_create_entry( + title=data['addon'], data={ + CONF_HOST: data[CONF_HOST], + CONF_PORT: data[CONF_PORT], + CONF_BRIDGEID: data['serial'], + CONF_API_KEY: data[CONF_API_KEY], + CONF_ALLOW_CLIP_SENSOR: + user_input[CONF_ALLOW_CLIP_SENSOR], + CONF_ALLOW_DECONZ_GROUPS: + user_input[CONF_ALLOW_DECONZ_GROUPS], + }) + + return self.async_show_form( + step_id='hassio_confirm', + description_placeholders={ + 'addon': self._hassio_discovery['addon'] + }, + data_schema=vol.Schema({ + vol.Optional(CONF_ALLOW_CLIP_SENSOR, + default=DEFAULT_ALLOW_CLIP_SENSOR): bool, + vol.Optional(CONF_ALLOW_DECONZ_GROUPS, + default=DEFAULT_ALLOW_DECONZ_GROUPS): bool, + }) + ) diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index bf0799d1fa2..b26fddd9147 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -1,14 +1,17 @@ """Constants for the deCONZ component.""" import logging -_LOGGER = logging.getLogger('homeassistant.components.deconz') +_LOGGER = logging.getLogger('.') DOMAIN = 'deconz' DEFAULT_PORT = 80 +DEFAULT_ALLOW_CLIP_SENSOR = False +DEFAULT_ALLOW_DECONZ_GROUPS = False CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor' CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups' +CONF_BRIDGEID = 'bridgeid' SUPPORTED_PLATFORMS = ['binary_sensor', 'climate', 'cover', 'light', 'scene', 'sensor', 'switch'] diff --git a/homeassistant/components/deconz/errors.py b/homeassistant/components/deconz/errors.py new file mode 100644 index 00000000000..be13e579ce0 --- /dev/null +++ b/homeassistant/components/deconz/errors.py @@ -0,0 +1,18 @@ +"""Errors for the deCONZ component.""" +from homeassistant.exceptions import HomeAssistantError + + +class DeconzException(HomeAssistantError): + """Base class for deCONZ exceptions.""" + + +class AlreadyConfigured(DeconzException): + """Gateway is already configured.""" + + +class AuthenticationRequired(DeconzException): + """Unknown error occurred.""" + + +class CannotConnect(DeconzException): + """Unable to connect to the gateway.""" diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index 829485e1e92..11fb247a6f4 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -1,6 +1,9 @@ """Representation of a deCONZ gateway.""" +import asyncio +import async_timeout + from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.const import CONF_EVENT, CONF_ID +from homeassistant.const import CONF_EVENT, CONF_HOST, CONF_ID from homeassistant.core import EventOrigin, callback from homeassistant.helpers import aiohttp_client from homeassistant.helpers.dispatcher import ( @@ -10,6 +13,7 @@ from homeassistant.util import slugify from .const import ( _LOGGER, DECONZ_REACHABLE, CONF_ALLOW_CLIP_SENSOR, NEW_DEVICE, NEW_SENSOR, SUPPORTED_PLATFORMS) +from .errors import AuthenticationRequired, CannotConnect class DeconzGateway: @@ -26,18 +30,23 @@ class DeconzGateway: self.events = [] self.listeners = [] - async def async_setup(self, tries=0): + async def async_setup(self): """Set up a deCONZ gateway.""" hass = self.hass - self.api = await get_gateway( - hass, self.config_entry.data, self.async_add_device_callback, - self.async_connection_status_callback - ) + try: + self.api = await get_gateway( + hass, self.config_entry.data, self.async_add_device_callback, + self.async_connection_status_callback + ) - if not self.api: + except CannotConnect: raise ConfigEntryNotReady + except Exception: # pylint: disable=broad-except + _LOGGER.error('Error connecting with deCONZ gateway') + return False + for component in SUPPORTED_PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup( @@ -113,17 +122,26 @@ class DeconzGateway: async def get_gateway(hass, config, async_add_device_callback, async_connection_status_callback): """Create a gateway object and verify configuration.""" - from pydeconz import DeconzSession + from pydeconz import DeconzSession, errors session = aiohttp_client.async_get_clientsession(hass) + deconz = DeconzSession(hass.loop, session, **config, async_add_device=async_add_device_callback, connection_status=async_connection_status_callback) - result = await deconz.async_load_parameters() - - if result: + try: + with async_timeout.timeout(10): + await deconz.async_load_parameters() return deconz - return result + + except errors.Unauthorized: + _LOGGER.warning("Invalid key for deCONZ at %s", config[CONF_HOST]) + raise AuthenticationRequired + + except (asyncio.TimeoutError, errors.RequestError): + _LOGGER.error( + "Error connecting to deCONZ gateway at %s", config[CONF_HOST]) + raise CannotConnect class DeconzEvent: diff --git a/homeassistant/components/deconz/strings.json b/homeassistant/components/deconz/strings.json index 1bf7235713a..d0ae34e7c7a 100644 --- a/homeassistant/components/deconz/strings.json +++ b/homeassistant/components/deconz/strings.json @@ -19,6 +19,14 @@ "allow_clip_sensor": "Allow importing virtual sensors", "allow_deconz_groups": "Allow importing deCONZ groups" } + }, + "hassio_confirm": { + "title": "deCONZ Zigbee gateway via Hass.io add-on", + "description": "Do you want to configure Home Assistant to connect to the deCONZ gateway provided by the hass.io add-on {addon}?", + "data": { + "allow_clip_sensor": "Allow importing virtual sensors", + "allow_deconz_groups": "Allow importing deCONZ groups" + } } }, "error": { @@ -30,4 +38,4 @@ "one_instance_only": "Component only supports one deCONZ instance" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/decora/__init__.py b/homeassistant/components/decora/__init__.py new file mode 100644 index 00000000000..694ff77fdb3 --- /dev/null +++ b/homeassistant/components/decora/__init__.py @@ -0,0 +1 @@ +"""The decora component.""" diff --git a/homeassistant/components/light/decora.py b/homeassistant/components/decora/light.py similarity index 98% rename from homeassistant/components/light/decora.py rename to homeassistant/components/decora/light.py index 5de8e03aea5..7c3274cf83b 100644 --- a/homeassistant/components/light/decora.py +++ b/homeassistant/components/decora/light.py @@ -4,6 +4,7 @@ Support for Decora dimmers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.decora/ """ +import importlib import logging from functools import wraps import time @@ -76,7 +77,7 @@ class DecoraLight(Light): def __init__(self, device): """Initialize the light.""" # pylint: disable=no-member - import decora + decora = importlib.import_module('decora') self._name = device['name'] self._address = device['address'] diff --git a/homeassistant/components/decora_wifi/__init__.py b/homeassistant/components/decora_wifi/__init__.py new file mode 100644 index 00000000000..b4bea73456e --- /dev/null +++ b/homeassistant/components/decora_wifi/__init__.py @@ -0,0 +1 @@ +"""The decora_wifi component.""" diff --git a/homeassistant/components/light/decora_wifi.py b/homeassistant/components/decora_wifi/light.py similarity index 100% rename from homeassistant/components/light/decora_wifi.py rename to homeassistant/components/decora_wifi/light.py diff --git a/homeassistant/components/default_config/__init__.py b/homeassistant/components/default_config/__init__.py index 888a4d51c95..6743893888d 100644 --- a/homeassistant/components/default_config/__init__.py +++ b/homeassistant/components/default_config/__init__.py @@ -1,7 +1,11 @@ """Component providing default configuration for new users.""" +try: + import av +except ImportError: + av = None DOMAIN = 'default_config' -DEPENDENCIES = ( +DEPENDENCIES = [ 'automation', 'cloud', 'config', @@ -17,7 +21,10 @@ DEPENDENCIES = ( 'system_health', 'updater', 'zeroconf', -) +] +# Only automatically set up the stream component when dependency installed +if av is not None: + DEPENDENCIES.append('stream') async def async_setup(hass, config): diff --git a/homeassistant/components/deluge/__init__.py b/homeassistant/components/deluge/__init__.py new file mode 100644 index 00000000000..ad40b688fcf --- /dev/null +++ b/homeassistant/components/deluge/__init__.py @@ -0,0 +1 @@ +"""The deluge component.""" diff --git a/homeassistant/components/sensor/deluge.py b/homeassistant/components/deluge/sensor.py similarity index 100% rename from homeassistant/components/sensor/deluge.py rename to homeassistant/components/deluge/sensor.py diff --git a/homeassistant/components/switch/deluge.py b/homeassistant/components/deluge/switch.py similarity index 100% rename from homeassistant/components/switch/deluge.py rename to homeassistant/components/deluge/switch.py diff --git a/homeassistant/components/air_quality/demo.py b/homeassistant/components/demo/air_quality.py similarity index 100% rename from homeassistant/components/air_quality/demo.py rename to homeassistant/components/demo/air_quality.py diff --git a/homeassistant/components/alarm_control_panel/demo.py b/homeassistant/components/demo/alarm_control_panel.py similarity index 93% rename from homeassistant/components/alarm_control_panel/demo.py rename to homeassistant/components/demo/alarm_control_panel.py index fb4dccc1c86..4d317f52daa 100644 --- a/homeassistant/components/alarm_control_panel/demo.py +++ b/homeassistant/components/demo/alarm_control_panel.py @@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ import datetime -from homeassistant.components.alarm_control_panel import manual +from homeassistant.components.manual.alarm_control_panel import ManualAlarm from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, @@ -17,7 +17,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Demo alarm control panel platform.""" async_add_entities([ - manual.ManualAlarm(hass, 'Alarm', '1234', None, False, { + ManualAlarm(hass, 'Alarm', '1234', None, False, { STATE_ALARM_ARMED_AWAY: { CONF_DELAY_TIME: datetime.timedelta(seconds=0), CONF_PENDING_TIME: datetime.timedelta(seconds=5), diff --git a/homeassistant/components/binary_sensor/demo.py b/homeassistant/components/demo/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/demo.py rename to homeassistant/components/demo/binary_sensor.py diff --git a/homeassistant/components/calendar/demo.py b/homeassistant/components/demo/calendar.py similarity index 99% rename from homeassistant/components/calendar/demo.py rename to homeassistant/components/demo/calendar.py index bf9d4abeb58..720b4cc5180 100644 --- a/homeassistant/components/calendar/demo.py +++ b/homeassistant/components/demo/calendar.py @@ -6,9 +6,10 @@ https://home-assistant.io/components/demo/ """ import copy -import homeassistant.util.dt as dt_util -from homeassistant.components.calendar import CalendarEventDevice, get_date from homeassistant.components.google import CONF_DEVICE_ID, CONF_NAME +import homeassistant.util.dt as dt_util + +from homeassistant.components.calendar import CalendarEventDevice, get_date def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/camera/demo.py b/homeassistant/components/demo/camera.py similarity index 97% rename from homeassistant/components/camera/demo.py rename to homeassistant/components/demo/camera.py index f950edb5c6c..34a0894ac60 100644 --- a/homeassistant/components/camera/demo.py +++ b/homeassistant/components/demo/camera.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/demo/ import logging import os -from homeassistant.components.camera import Camera, SUPPORT_ON_OFF +from homeassistant.components.camera import SUPPORT_ON_OFF, Camera _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/climate/demo.py b/homeassistant/components/demo/climate.py similarity index 94% rename from homeassistant/components/climate/demo.py rename to homeassistant/components/demo/climate.py index 5b4775982a6..b1dd1b0ba45 100644 --- a/homeassistant/components/climate/demo.py +++ b/homeassistant/components/demo/climate.py @@ -4,16 +4,16 @@ Demo platform that offers a fake climate device. For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT + from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH, - SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_FAN_MODE, - SUPPORT_OPERATION_MODE, SUPPORT_AUX_HEAT, SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, - SUPPORT_ON_OFF) -from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE + ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, SUPPORT_AUX_HEAT, + SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE, SUPPORT_ON_OFF, + SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW, + SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH, + SUPPORT_TARGET_TEMPERATURE_LOW) SUPPORT_FLAGS = SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH diff --git a/homeassistant/components/cover/demo.py b/homeassistant/components/demo/cover.py similarity index 98% rename from homeassistant/components/cover/demo.py rename to homeassistant/components/demo/cover.py index 21add0a6c62..ddcf07fd5e5 100644 --- a/homeassistant/components/cover/demo.py +++ b/homeassistant/components/demo/cover.py @@ -4,11 +4,12 @@ Demo platform for the cover component. For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ -from homeassistant.components.cover import ( - CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION, - ATTR_TILT_POSITION) from homeassistant.helpers.event import track_utc_time_change +from homeassistant.components.cover import ( + ATTR_POSITION, ATTR_TILT_POSITION, SUPPORT_CLOSE, SUPPORT_OPEN, + CoverDevice) + def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo covers.""" diff --git a/homeassistant/components/camera/demo_0.jpg b/homeassistant/components/demo/demo_0.jpg similarity index 100% rename from homeassistant/components/camera/demo_0.jpg rename to homeassistant/components/demo/demo_0.jpg diff --git a/homeassistant/components/camera/demo_1.jpg b/homeassistant/components/demo/demo_1.jpg similarity index 100% rename from homeassistant/components/camera/demo_1.jpg rename to homeassistant/components/demo/demo_1.jpg diff --git a/homeassistant/components/camera/demo_2.jpg b/homeassistant/components/demo/demo_2.jpg similarity index 100% rename from homeassistant/components/camera/demo_2.jpg rename to homeassistant/components/demo/demo_2.jpg diff --git a/homeassistant/components/camera/demo_3.jpg b/homeassistant/components/demo/demo_3.jpg similarity index 100% rename from homeassistant/components/camera/demo_3.jpg rename to homeassistant/components/demo/demo_3.jpg diff --git a/homeassistant/components/device_tracker/demo.py b/homeassistant/components/demo/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/demo.py rename to homeassistant/components/demo/device_tracker.py diff --git a/homeassistant/components/fan/demo.py b/homeassistant/components/demo/fan.py similarity index 92% rename from homeassistant/components/fan/demo.py rename to homeassistant/components/demo/fan.py index 840196c8bf0..53729795f71 100644 --- a/homeassistant/components/fan/demo.py +++ b/homeassistant/components/demo/fan.py @@ -4,11 +4,12 @@ Demo fan platform that has a fake fan. For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ -from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, - FanEntity, SUPPORT_SET_SPEED, - SUPPORT_OSCILLATE, SUPPORT_DIRECTION) from homeassistant.const import STATE_OFF +from homeassistant.components.fan import ( + SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SUPPORT_DIRECTION, SUPPORT_OSCILLATE, + SUPPORT_SET_SPEED, FanEntity) + FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION LIMITED_SUPPORT = SUPPORT_SET_SPEED diff --git a/homeassistant/components/geo_location/demo.py b/homeassistant/components/demo/geo_location.py similarity index 99% rename from homeassistant/components/geo_location/demo.py rename to homeassistant/components/demo/geo_location.py index 523e125a737..6b91faac92f 100644 --- a/homeassistant/components/geo_location/demo.py +++ b/homeassistant/components/demo/geo_location.py @@ -5,9 +5,10 @@ from math import cos, pi, radians, sin import random from typing import Optional -from homeassistant.components.geo_location import GeolocationEvent from homeassistant.helpers.event import track_time_interval +from homeassistant.components.geo_location import GeolocationEvent + _LOGGER = logging.getLogger(__name__) AVG_KM_PER_DEGREE = 111.0 diff --git a/homeassistant/components/image_processing/demo.py b/homeassistant/components/demo/image_processing.py similarity index 97% rename from homeassistant/components/image_processing/demo.py rename to homeassistant/components/demo/image_processing.py index 42ba7b4c05f..71ec2dccbc6 100644 --- a/homeassistant/components/image_processing/demo.py +++ b/homeassistant/components/demo/image_processing.py @@ -8,7 +8,7 @@ from homeassistant.components.image_processing import ( ImageProcessingFaceEntity, ATTR_CONFIDENCE, ATTR_NAME, ATTR_AGE, ATTR_GENDER ) -from homeassistant.components.image_processing.openalpr_local import ( +from homeassistant.components.openalpr_local.image_processing import ( ImageProcessingAlprEntity) diff --git a/homeassistant/components/light/demo.py b/homeassistant/components/demo/light.py similarity index 97% rename from homeassistant/components/light/demo.py rename to homeassistant/components/demo/light.py index 980d8491744..a5b22108e81 100644 --- a/homeassistant/components/light/demo.py +++ b/homeassistant/components/demo/light.py @@ -8,8 +8,8 @@ import random from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR, - ATTR_WHITE_VALUE, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, - SUPPORT_COLOR, SUPPORT_WHITE_VALUE, Light) + ATTR_WHITE_VALUE, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, + SUPPORT_EFFECT, SUPPORT_WHITE_VALUE, Light) LIGHT_COLORS = [ (56, 86), diff --git a/homeassistant/components/lock/demo.py b/homeassistant/components/demo/lock.py similarity index 92% rename from homeassistant/components/lock/demo.py rename to homeassistant/components/demo/lock.py index a0cc45991c8..03935c4f603 100644 --- a/homeassistant/components/lock/demo.py +++ b/homeassistant/components/demo/lock.py @@ -4,8 +4,9 @@ Demo lock platform that has two fake locks. For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ -from homeassistant.components.lock import LockDevice, SUPPORT_OPEN -from homeassistant.const import (STATE_LOCKED, STATE_UNLOCKED) +from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED + +from homeassistant.components.lock import SUPPORT_OPEN, LockDevice def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/mailbox/demo.py b/homeassistant/components/demo/mailbox.py similarity index 97% rename from homeassistant/components/mailbox/demo.py rename to homeassistant/components/demo/mailbox.py index 885988adb6b..fcffc44eefb 100644 --- a/homeassistant/components/mailbox/demo.py +++ b/homeassistant/components/demo/mailbox.py @@ -63,7 +63,7 @@ class DemoMailbox(Mailbox): raise StreamError("Message not found") audio_path = os.path.join( - os.path.dirname(__file__), '..', 'tts', 'demo.mp3') + os.path.dirname(__file__), 'tts.mp3') with open(audio_path, 'rb') as file: return file.read() diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/demo/media_player.py similarity index 99% rename from homeassistant/components/media_player/demo.py rename to homeassistant/components/demo/media_player.py index de455879d3d..33d2b98d225 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/demo/media_player.py @@ -4,16 +4,16 @@ Demo implementation of the media player. For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ +from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING import homeassistant.util.dt as dt_util -from homeassistant.components.media_player import ( - MediaPlayerDevice) + +from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) -from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/notify/demo.py b/homeassistant/components/demo/notify.py similarity index 100% rename from homeassistant/components/notify/demo.py rename to homeassistant/components/demo/notify.py diff --git a/homeassistant/components/sensor/demo.py b/homeassistant/components/demo/sensor.py similarity index 100% rename from homeassistant/components/sensor/demo.py rename to homeassistant/components/demo/sensor.py diff --git a/homeassistant/components/switch/demo.py b/homeassistant/components/demo/switch.py similarity index 100% rename from homeassistant/components/switch/demo.py rename to homeassistant/components/demo/switch.py diff --git a/homeassistant/components/tts/demo.mp3 b/homeassistant/components/demo/tts.mp3 similarity index 100% rename from homeassistant/components/tts/demo.mp3 rename to homeassistant/components/demo/tts.mp3 diff --git a/homeassistant/components/tts/demo.py b/homeassistant/components/demo/tts.py similarity index 90% rename from homeassistant/components/tts/demo.py rename to homeassistant/components/demo/tts.py index ba854fc2f5e..1498472ef9f 100644 --- a/homeassistant/components/tts/demo.py +++ b/homeassistant/components/demo/tts.py @@ -8,7 +8,7 @@ import os import voluptuous as vol -from homeassistant.components.tts import Provider, PLATFORM_SCHEMA, CONF_LANG +from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider SUPPORT_LANGUAGES = [ 'en', 'de' @@ -51,7 +51,7 @@ class DemoProvider(Provider): def get_tts_audio(self, message, language, options=None): """Load TTS from demo.""" - filename = os.path.join(os.path.dirname(__file__), 'demo.mp3') + filename = os.path.join(os.path.dirname(__file__), 'tts.mp3') try: with open(filename, 'rb') as voice: data = voice.read() diff --git a/homeassistant/components/vacuum/demo.py b/homeassistant/components/demo/vacuum.py similarity index 96% rename from homeassistant/components/vacuum/demo.py rename to homeassistant/components/demo/vacuum.py index 12507683f51..5ec7030f56c 100644 --- a/homeassistant/components/vacuum/demo.py +++ b/homeassistant/components/demo/vacuum.py @@ -7,12 +7,11 @@ https://home-assistant.io/components/demo/ import logging from homeassistant.components.vacuum import ( - ATTR_CLEANED_AREA, SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, - SUPPORT_FAN_SPEED, SUPPORT_LOCATE, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, - SUPPORT_SEND_COMMAND, SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_STATE, SUPPORT_START, STATE_CLEANING, - STATE_DOCKED, STATE_IDLE, STATE_PAUSED, STATE_RETURNING, VacuumDevice, - StateVacuumDevice) + ATTR_CLEANED_AREA, STATE_CLEANING, STATE_DOCKED, STATE_IDLE, STATE_PAUSED, + STATE_RETURNING, SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED, + SUPPORT_LOCATE, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND, + SUPPORT_START, SUPPORT_STATE, SUPPORT_STATUS, SUPPORT_STOP, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, StateVacuumDevice, VacuumDevice) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/water_heater/demo.py b/homeassistant/components/demo/water_heater.py similarity index 94% rename from homeassistant/components/water_heater/demo.py rename to homeassistant/components/demo/water_heater.py index b551993aca5..6ee17bf0088 100644 --- a/homeassistant/components/water_heater/demo.py +++ b/homeassistant/components/demo/water_heater.py @@ -1,10 +1,9 @@ """Demo platform that offers a fake water heater device.""" +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT + from homeassistant.components.water_heater import ( - WaterHeaterDevice, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_AWAY_MODE, - SUPPORT_OPERATION_MODE) -from homeassistant.const import TEMP_FAHRENHEIT, ATTR_TEMPERATURE, TEMP_CELSIUS + SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, + WaterHeaterDevice) SUPPORT_FLAGS_HEATER = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE) diff --git a/homeassistant/components/weather/demo.py b/homeassistant/components/demo/weather.py similarity index 100% rename from homeassistant/components/weather/demo.py rename to homeassistant/components/demo/weather.py diff --git a/homeassistant/components/denon/__init__.py b/homeassistant/components/denon/__init__.py new file mode 100644 index 00000000000..ab8cd1b896e --- /dev/null +++ b/homeassistant/components/denon/__init__.py @@ -0,0 +1 @@ +"""The denon component.""" diff --git a/homeassistant/components/media_player/denon.py b/homeassistant/components/denon/media_player.py similarity index 100% rename from homeassistant/components/media_player/denon.py rename to homeassistant/components/denon/media_player.py diff --git a/homeassistant/components/denonavr/__init__.py b/homeassistant/components/denonavr/__init__.py new file mode 100644 index 00000000000..dee84449d13 --- /dev/null +++ b/homeassistant/components/denonavr/__init__.py @@ -0,0 +1 @@ +"""The denonavr component.""" diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/denonavr/media_player.py similarity index 100% rename from homeassistant/components/media_player/denonavr.py rename to homeassistant/components/denonavr/media_player.py diff --git a/homeassistant/components/deutsche_bahn/__init__.py b/homeassistant/components/deutsche_bahn/__init__.py new file mode 100644 index 00000000000..0b696174fd5 --- /dev/null +++ b/homeassistant/components/deutsche_bahn/__init__.py @@ -0,0 +1 @@ +"""The deutsche_bahn component.""" diff --git a/homeassistant/components/sensor/deutsche_bahn.py b/homeassistant/components/deutsche_bahn/sensor.py similarity index 100% rename from homeassistant/components/sensor/deutsche_bahn.py rename to homeassistant/components/deutsche_bahn/sensor.py diff --git a/homeassistant/components/dht/__init__.py b/homeassistant/components/dht/__init__.py new file mode 100644 index 00000000000..23aa2b9d9df --- /dev/null +++ b/homeassistant/components/dht/__init__.py @@ -0,0 +1 @@ +"""The dht component.""" diff --git a/homeassistant/components/sensor/dht.py b/homeassistant/components/dht/sensor.py similarity index 100% rename from homeassistant/components/sensor/dht.py rename to homeassistant/components/dht/sensor.py diff --git a/homeassistant/components/dialogflow/.translations/bg.json b/homeassistant/components/dialogflow/.translations/bg.json new file mode 100644 index 00000000000..6f06d5c00c6 --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/bg.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/pl.json b/homeassistant/components/dialogflow/.translations/pl.json index 9a8e1c1eb11..3395b31b4c7 100644 --- a/homeassistant/components/dialogflow/.translations/pl.json +++ b/homeassistant/components/dialogflow/.translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Dialogflow Webhook]({twilio_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Dialogflow Webhook]({twilio_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/digital_ocean/binary_sensor.py b/homeassistant/components/digital_ocean/binary_sensor.py index 88df56cc629..d496a09161b 100644 --- a/homeassistant/components/digital_ocean/binary_sensor.py +++ b/homeassistant/components/digital_ocean/binary_sensor.py @@ -3,14 +3,15 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.components.digital_ocean import ( - CONF_DROPLETS, ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME, - ATTR_FEATURES, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, - ATTR_REGION, ATTR_VCPUS, ATTRIBUTION, DATA_DIGITAL_OCEAN) + PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.const import ATTR_ATTRIBUTION +import homeassistant.helpers.config_validation as cv + +from . import ( + ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME, ATTR_FEATURES, + ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, ATTR_REGION, ATTR_VCPUS, + ATTRIBUTION, CONF_DROPLETS, DATA_DIGITAL_OCEAN) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/digital_ocean/switch.py b/homeassistant/components/digital_ocean/switch.py index 9b5ddda3408..bc4a6a29b42 100644 --- a/homeassistant/components/digital_ocean/switch.py +++ b/homeassistant/components/digital_ocean/switch.py @@ -3,13 +3,14 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) -from homeassistant.components.digital_ocean import ( - CONF_DROPLETS, ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME, - ATTR_FEATURES, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, - ATTR_REGION, ATTR_VCPUS, ATTRIBUTION, DATA_DIGITAL_OCEAN) +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import ATTR_ATTRIBUTION +import homeassistant.helpers.config_validation as cv + +from . import ( + ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME, ATTR_FEATURES, + ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, ATTR_REGION, ATTR_VCPUS, + ATTRIBUTION, CONF_DROPLETS, DATA_DIGITAL_OCEAN) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/digitalloggers/__init__.py b/homeassistant/components/digitalloggers/__init__.py new file mode 100644 index 00000000000..6db88ca93d2 --- /dev/null +++ b/homeassistant/components/digitalloggers/__init__.py @@ -0,0 +1 @@ +"""The digitalloggers component.""" diff --git a/homeassistant/components/switch/digitalloggers.py b/homeassistant/components/digitalloggers/switch.py similarity index 100% rename from homeassistant/components/switch/digitalloggers.py rename to homeassistant/components/digitalloggers/switch.py diff --git a/homeassistant/components/directv/__init__.py b/homeassistant/components/directv/__init__.py new file mode 100644 index 00000000000..5934e1b6c51 --- /dev/null +++ b/homeassistant/components/directv/__init__.py @@ -0,0 +1 @@ +"""The directv component.""" diff --git a/homeassistant/components/media_player/directv.py b/homeassistant/components/directv/media_player.py similarity index 100% rename from homeassistant/components/media_player/directv.py rename to homeassistant/components/directv/media_player.py diff --git a/homeassistant/components/discogs/__init__.py b/homeassistant/components/discogs/__init__.py new file mode 100644 index 00000000000..90a17763ea6 --- /dev/null +++ b/homeassistant/components/discogs/__init__.py @@ -0,0 +1 @@ +"""The discogs component.""" diff --git a/homeassistant/components/sensor/discogs.py b/homeassistant/components/discogs/sensor.py similarity index 100% rename from homeassistant/components/sensor/discogs.py rename to homeassistant/components/discogs/sensor.py diff --git a/homeassistant/components/discord/__init__.py b/homeassistant/components/discord/__init__.py new file mode 100644 index 00000000000..a3cd87bc895 --- /dev/null +++ b/homeassistant/components/discord/__init__.py @@ -0,0 +1 @@ +"""The discord component.""" diff --git a/homeassistant/components/notify/discord.py b/homeassistant/components/discord/notify.py similarity index 91% rename from homeassistant/components/notify/discord.py rename to homeassistant/components/discord/notify.py index e9f0d5cec28..d73382d8bcf 100644 --- a/homeassistant/components/notify/discord.py +++ b/homeassistant/components/discord/notify.py @@ -8,10 +8,12 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - PLATFORM_SCHEMA, BaseNotificationService, ATTR_TARGET, ATTR_DATA) from homeassistant.const import CONF_TOKEN +import homeassistant.helpers.config_validation as cv + +from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET, + PLATFORM_SCHEMA, + BaseNotificationService) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index d4816213f50..ecbbe7ea5e0 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -46,11 +46,13 @@ SERVICE_ROKU = 'roku' SERVICE_SABNZBD = 'sabnzbd' SERVICE_SAMSUNG_PRINTER = 'samsung_printer' SERVICE_TELLDUSLIVE = 'tellstick' +SERVICE_YEELIGHT = 'yeelight' SERVICE_WEMO = 'belkin_wemo' SERVICE_WINK = 'wink' SERVICE_XIAOMI_GW = 'xiaomi_gw' CONFIG_ENTRY_HANDLERS = { + SERVICE_AXIS: 'axis', SERVICE_DAIKIN: 'daikin', SERVICE_DECONZ: 'deconz', 'esphome': 'esphome', @@ -68,7 +70,6 @@ SERVICE_HANDLERS = { SERVICE_NETGEAR: ('device_tracker', None), SERVICE_WEMO: ('wemo', None), SERVICE_HASSIO: ('hassio', None), - SERVICE_AXIS: ('axis', None), SERVICE_APPLE_TV: ('apple_tv', None), SERVICE_ENIGMA2: ('media_player', 'enigma2'), SERVICE_ROKU: ('roku', None), @@ -79,6 +80,7 @@ SERVICE_HANDLERS = { SERVICE_KONNECTED: ('konnected', None), SERVICE_OCTOPRINT: ('octoprint', None), SERVICE_FREEBOX: ('freebox', None), + SERVICE_YEELIGHT: ('yeelight', None), 'panasonic_viera': ('media_player', 'panasonic_viera'), 'plex_mediaserver': ('media_player', 'plex'), 'yamaha': ('media_player', 'yamaha'), @@ -86,7 +88,6 @@ SERVICE_HANDLERS = { 'directv': ('media_player', 'directv'), 'denonavr': ('media_player', 'denonavr'), 'samsung_tv': ('media_player', 'samsungtv'), - 'yeelight': ('light', 'yeelight'), 'frontier_silicon': ('media_player', 'frontier_silicon'), 'openhome': ('media_player', 'openhome'), 'harmony': ('remote', 'harmony'), diff --git a/homeassistant/components/dlib_face_detect/__init__.py b/homeassistant/components/dlib_face_detect/__init__.py new file mode 100644 index 00000000000..a732132955f --- /dev/null +++ b/homeassistant/components/dlib_face_detect/__init__.py @@ -0,0 +1 @@ +"""The dlib_face_detect component.""" diff --git a/homeassistant/components/image_processing/dlib_face_detect.py b/homeassistant/components/dlib_face_detect/image_processing.py similarity index 98% rename from homeassistant/components/image_processing/dlib_face_detect.py rename to homeassistant/components/dlib_face_detect/image_processing.py index cb9ea5ff5f9..fea756395e4 100644 --- a/homeassistant/components/image_processing/dlib_face_detect.py +++ b/homeassistant/components/dlib_face_detect/image_processing.py @@ -13,7 +13,7 @@ from homeassistant.components.image_processing import PLATFORM_SCHEMA # noqa from homeassistant.components.image_processing import ( ImageProcessingFaceEntity, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME) -REQUIREMENTS = ['face_recognition==1.0.0'] +REQUIREMENTS = ['face_recognition==1.2.3'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/dlib_face_identify/__init__.py b/homeassistant/components/dlib_face_identify/__init__.py new file mode 100644 index 00000000000..79b9e4ec4bc --- /dev/null +++ b/homeassistant/components/dlib_face_identify/__init__.py @@ -0,0 +1 @@ +"""The dlib_face_identify component.""" diff --git a/homeassistant/components/image_processing/dlib_face_identify.py b/homeassistant/components/dlib_face_identify/image_processing.py similarity index 98% rename from homeassistant/components/image_processing/dlib_face_identify.py rename to homeassistant/components/dlib_face_identify/image_processing.py index d8c3f5f621f..6611fb0edfb 100644 --- a/homeassistant/components/image_processing/dlib_face_identify.py +++ b/homeassistant/components/dlib_face_identify/image_processing.py @@ -15,7 +15,7 @@ from homeassistant.components.image_processing import ( CONF_NAME) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['face_recognition==1.0.0'] +REQUIREMENTS = ['face_recognition==1.2.3'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/dlink/__init__.py b/homeassistant/components/dlink/__init__.py new file mode 100644 index 00000000000..644e7975a0e --- /dev/null +++ b/homeassistant/components/dlink/__init__.py @@ -0,0 +1 @@ +"""The dlink component.""" diff --git a/homeassistant/components/switch/dlink.py b/homeassistant/components/dlink/switch.py similarity index 100% rename from homeassistant/components/switch/dlink.py rename to homeassistant/components/dlink/switch.py diff --git a/homeassistant/components/dlna_dmr/__init__.py b/homeassistant/components/dlna_dmr/__init__.py new file mode 100644 index 00000000000..f38456ec6ee --- /dev/null +++ b/homeassistant/components/dlna_dmr/__init__.py @@ -0,0 +1 @@ +"""The dlna_dmr component.""" diff --git a/homeassistant/components/media_player/dlna_dmr.py b/homeassistant/components/dlna_dmr/media_player.py similarity index 91% rename from homeassistant/components/media_player/dlna_dmr.py rename to homeassistant/components/dlna_dmr/media_player.py index fbb0ee58c5a..71195d66c69 100644 --- a/homeassistant/components/media_player/dlna_dmr.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -17,6 +17,9 @@ import voluptuous as vol from homeassistant.components.media_player import ( MediaPlayerDevice, PLATFORM_SCHEMA) from homeassistant.components.media_player.const import ( + MEDIA_TYPE_CHANNEL, MEDIA_TYPE_EPISODE, MEDIA_TYPE_IMAGE, + MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, + MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) @@ -29,7 +32,7 @@ from homeassistant.helpers.typing import HomeAssistantType import homeassistant.helpers.config_validation as cv from homeassistant.util import get_local_ip -REQUIREMENTS = ['async-upnp-client==0.14.5'] +REQUIREMENTS = ['async-upnp-client==0.14.7'] _LOGGER = logging.getLogger(__name__) @@ -51,20 +54,25 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) HOME_ASSISTANT_UPNP_CLASS_MAPPING = { - 'music': 'object.item.audioItem', - 'tvshow': 'object.item.videoItem', - 'video': 'object.item.videoItem', - 'episode': 'object.item.videoItem', - 'channel': 'object.item.videoItem', - 'playlist': 'object.item.playlist', + MEDIA_TYPE_MUSIC: 'object.item.audioItem', + MEDIA_TYPE_TVSHOW: 'object.item.videoItem', + MEDIA_TYPE_MOVIE: 'object.item.videoItem', + MEDIA_TYPE_VIDEO: 'object.item.videoItem', + MEDIA_TYPE_EPISODE: 'object.item.videoItem', + MEDIA_TYPE_CHANNEL: 'object.item.videoItem', + MEDIA_TYPE_IMAGE: 'object.item.imageItem', + MEDIA_TYPE_PLAYLIST: 'object.item.playlist', } +UPNP_CLASS_DEFAULT = 'object.item' HOME_ASSISTANT_UPNP_MIME_TYPE_MAPPING = { - 'music': 'audio/*', - 'tvshow': 'video/*', - 'video': 'video/*', - 'episode': 'video/*', - 'channel': 'video/*', - 'playlist': 'playlist/*', + MEDIA_TYPE_MUSIC: 'audio/*', + MEDIA_TYPE_TVSHOW: 'video/*', + MEDIA_TYPE_MOVIE: 'video/*', + MEDIA_TYPE_VIDEO: 'video/*', + MEDIA_TYPE_EPISODE: 'video/*', + MEDIA_TYPE_CHANNEL: 'video/*', + MEDIA_TYPE_IMAGE: 'image/*', + MEDIA_TYPE_PLAYLIST: 'playlist/*', } @@ -319,8 +327,10 @@ class DlnaDmrDevice(MediaPlayerDevice): async def async_play_media(self, media_type, media_id, **kwargs): """Play a piece of media.""" title = "Home Assistant" - mime_type = HOME_ASSISTANT_UPNP_MIME_TYPE_MAPPING[media_type] - upnp_class = HOME_ASSISTANT_UPNP_CLASS_MAPPING[media_type] + mime_type = HOME_ASSISTANT_UPNP_MIME_TYPE_MAPPING.get(media_type, + media_type) + upnp_class = HOME_ASSISTANT_UPNP_CLASS_MAPPING.get(media_type, + UPNP_CLASS_DEFAULT) # Stop current playing media if self._device.can_stop: diff --git a/homeassistant/components/dnsip/__init__.py b/homeassistant/components/dnsip/__init__.py new file mode 100644 index 00000000000..603e8403e74 --- /dev/null +++ b/homeassistant/components/dnsip/__init__.py @@ -0,0 +1 @@ +"""The dnsip component.""" diff --git a/homeassistant/components/sensor/dnsip.py b/homeassistant/components/dnsip/sensor.py similarity index 100% rename from homeassistant/components/sensor/dnsip.py rename to homeassistant/components/dnsip/sensor.py diff --git a/homeassistant/components/doorbird/camera.py b/homeassistant/components/doorbird/camera.py index e201837aaf6..272a3cb932a 100644 --- a/homeassistant/components/doorbird/camera.py +++ b/homeassistant/components/doorbird/camera.py @@ -7,9 +7,10 @@ import aiohttp import async_timeout from homeassistant.components.camera import Camera -from homeassistant.components.doorbird import DOMAIN as DOORBIRD_DOMAIN from homeassistant.helpers.aiohttp_client import async_get_clientsession +from . import DOMAIN as DOORBIRD_DOMAIN + DEPENDENCIES = ['doorbird'] _CAMERA_LAST_VISITOR = "{} Last Ring" diff --git a/homeassistant/components/doorbird/switch.py b/homeassistant/components/doorbird/switch.py index 376713d4b27..ba6f96660d1 100644 --- a/homeassistant/components/doorbird/switch.py +++ b/homeassistant/components/doorbird/switch.py @@ -2,9 +2,10 @@ import datetime import logging -from homeassistant.components.doorbird import DOMAIN as DOORBIRD_DOMAIN from homeassistant.components.switch import SwitchDevice +from . import DOMAIN as DOORBIRD_DOMAIN + DEPENDENCIES = ['doorbird'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/dovado/notify.py b/homeassistant/components/dovado/notify.py index ea6ba2b455f..59827529ed3 100644 --- a/homeassistant/components/dovado/notify.py +++ b/homeassistant/components/dovado/notify.py @@ -1,9 +1,10 @@ """Support for SMS notifications from the Dovado router.""" import logging -from homeassistant.components.dovado import DOMAIN as DOVADO_DOMAIN -from homeassistant.components.notify import BaseNotificationService, \ - ATTR_TARGET +from homeassistant.components.notify import ( + ATTR_TARGET, BaseNotificationService) + +from . import DOMAIN as DOVADO_DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/dovado/sensor.py b/homeassistant/components/dovado/sensor.py index eb0016ed298..56c4ee03a3a 100644 --- a/homeassistant/components/dovado/sensor.py +++ b/homeassistant/components/dovado/sensor.py @@ -1,16 +1,17 @@ """Support for sensors from the Dovado router.""" +from datetime import timedelta import logging import re -from datetime import timedelta import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.dovado import DOMAIN as DOVADO_DOMAIN from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_SENSORS +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from . import DOMAIN as DOVADO_DOMAIN + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['dovado'] diff --git a/homeassistant/components/dsmr/__init__.py b/homeassistant/components/dsmr/__init__.py new file mode 100644 index 00000000000..107e221a465 --- /dev/null +++ b/homeassistant/components/dsmr/__init__.py @@ -0,0 +1 @@ +"""The dsmr component.""" diff --git a/homeassistant/components/sensor/dsmr.py b/homeassistant/components/dsmr/sensor.py similarity index 100% rename from homeassistant/components/sensor/dsmr.py rename to homeassistant/components/dsmr/sensor.py diff --git a/homeassistant/components/dte_energy_bridge/__init__.py b/homeassistant/components/dte_energy_bridge/__init__.py new file mode 100644 index 00000000000..2525d047bce --- /dev/null +++ b/homeassistant/components/dte_energy_bridge/__init__.py @@ -0,0 +1 @@ +"""The dte_energy_bridge component.""" diff --git a/homeassistant/components/sensor/dte_energy_bridge.py b/homeassistant/components/dte_energy_bridge/sensor.py similarity index 100% rename from homeassistant/components/sensor/dte_energy_bridge.py rename to homeassistant/components/dte_energy_bridge/sensor.py diff --git a/homeassistant/components/dublin_bus_transport/__init__.py b/homeassistant/components/dublin_bus_transport/__init__.py new file mode 100644 index 00000000000..138950af2b5 --- /dev/null +++ b/homeassistant/components/dublin_bus_transport/__init__.py @@ -0,0 +1 @@ +"""The dublin_bus_transport component.""" diff --git a/homeassistant/components/sensor/dublin_bus_transport.py b/homeassistant/components/dublin_bus_transport/sensor.py similarity index 100% rename from homeassistant/components/sensor/dublin_bus_transport.py rename to homeassistant/components/dublin_bus_transport/sensor.py diff --git a/homeassistant/components/duke_energy/__init__.py b/homeassistant/components/duke_energy/__init__.py new file mode 100644 index 00000000000..5a1f29add43 --- /dev/null +++ b/homeassistant/components/duke_energy/__init__.py @@ -0,0 +1 @@ +"""The duke_energy component.""" diff --git a/homeassistant/components/sensor/duke_energy.py b/homeassistant/components/duke_energy/sensor.py similarity index 100% rename from homeassistant/components/sensor/duke_energy.py rename to homeassistant/components/duke_energy/sensor.py diff --git a/homeassistant/components/dunehd/__init__.py b/homeassistant/components/dunehd/__init__.py new file mode 100644 index 00000000000..a8b8a8cf7af --- /dev/null +++ b/homeassistant/components/dunehd/__init__.py @@ -0,0 +1 @@ +"""The dunehd component.""" diff --git a/homeassistant/components/media_player/dunehd.py b/homeassistant/components/dunehd/media_player.py similarity index 100% rename from homeassistant/components/media_player/dunehd.py rename to homeassistant/components/dunehd/media_player.py diff --git a/homeassistant/components/dwd_weather_warnings/__init__.py b/homeassistant/components/dwd_weather_warnings/__init__.py new file mode 100644 index 00000000000..1841291f7a9 --- /dev/null +++ b/homeassistant/components/dwd_weather_warnings/__init__.py @@ -0,0 +1 @@ +"""The dwd_weather_warnings component.""" diff --git a/homeassistant/components/sensor/dwd_weather_warnings.py b/homeassistant/components/dwd_weather_warnings/sensor.py similarity index 99% rename from homeassistant/components/sensor/dwd_weather_warnings.py rename to homeassistant/components/dwd_weather_warnings/sensor.py index 4b7e07d4f3a..9e61c9e3196 100644 --- a/homeassistant/components/sensor/dwd_weather_warnings.py +++ b/homeassistant/components/dwd_weather_warnings/sensor.py @@ -25,7 +25,7 @@ from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_NAME, CONF_MONITORED_CONDITIONS) from homeassistant.util import Throttle import homeassistant.util.dt as dt_util -from homeassistant.components.sensor.rest import RestData +from homeassistant.components.rest.sensor import RestData _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/climate/dyson.py b/homeassistant/components/dyson/climate.py similarity index 96% rename from homeassistant/components/climate/dyson.py rename to homeassistant/components/dyson/climate.py index 09196a82bed..3e5c976b1f4 100644 --- a/homeassistant/components/climate/dyson.py +++ b/homeassistant/components/dyson/climate.py @@ -6,12 +6,13 @@ https://home-assistant.io/components/climate.dyson/ """ import logging -from homeassistant.components.dyson import DYSON_DEVICES from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - STATE_HEAT, STATE_COOL, STATE_IDLE, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE) -from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE + STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS + +from . import DYSON_DEVICES _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fan/dyson.py b/homeassistant/components/dyson/fan.py similarity index 99% rename from homeassistant/components/fan/dyson.py rename to homeassistant/components/dyson/fan.py index ef517021178..743d301df42 100644 --- a/homeassistant/components/fan/dyson.py +++ b/homeassistant/components/dyson/fan.py @@ -7,11 +7,12 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.dyson import DYSON_DEVICES from homeassistant.components.fan import ( DOMAIN, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity) from homeassistant.const import CONF_ENTITY_ID +import homeassistant.helpers.config_validation as cv + +from . import DYSON_DEVICES _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/dyson.py b/homeassistant/components/dyson/sensor.py similarity index 99% rename from homeassistant/components/sensor/dyson.py rename to homeassistant/components/dyson/sensor.py index 53913d47b72..ed8987f75c2 100644 --- a/homeassistant/components/sensor/dyson.py +++ b/homeassistant/components/dyson/sensor.py @@ -6,10 +6,11 @@ https://home-assistant.io/components/sensor.dyson/ """ import logging -from homeassistant.components.dyson import DYSON_DEVICES from homeassistant.const import STATE_OFF, TEMP_CELSIUS from homeassistant.helpers.entity import Entity +from . import DYSON_DEVICES + DEPENDENCIES = ['dyson'] SENSOR_UNITS = { diff --git a/homeassistant/components/vacuum/dyson.py b/homeassistant/components/dyson/vacuum.py similarity index 99% rename from homeassistant/components/vacuum/dyson.py rename to homeassistant/components/dyson/vacuum.py index 3d6e23c20c8..7902cfa1585 100644 --- a/homeassistant/components/vacuum/dyson.py +++ b/homeassistant/components/dyson/vacuum.py @@ -6,13 +6,14 @@ https://home-assistant.io/components/vacuum.dyson/ """ import logging -from homeassistant.components.dyson import DYSON_DEVICES from homeassistant.components.vacuum import ( SUPPORT_BATTERY, SUPPORT_FAN_SPEED, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, VacuumDevice) from homeassistant.helpers.icon import icon_for_battery_level +from . import DYSON_DEVICES + _LOGGER = logging.getLogger(__name__) ATTR_CLEAN_ID = 'clean_id' diff --git a/homeassistant/components/ebox/__init__.py b/homeassistant/components/ebox/__init__.py new file mode 100644 index 00000000000..3f807666a4b --- /dev/null +++ b/homeassistant/components/ebox/__init__.py @@ -0,0 +1 @@ +"""The ebox component.""" diff --git a/homeassistant/components/sensor/ebox.py b/homeassistant/components/ebox/sensor.py similarity index 100% rename from homeassistant/components/sensor/ebox.py rename to homeassistant/components/ebox/sensor.py diff --git a/homeassistant/components/ebusd/.translations/bg.json b/homeassistant/components/ebusd/.translations/bg.json new file mode 100644 index 00000000000..f188fe09a48 --- /dev/null +++ b/homeassistant/components/ebusd/.translations/bg.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "\u0414\u0435\u043d", + "night": "\u041d\u043e\u0449" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/fr.json b/homeassistant/components/ebusd/.translations/fr.json new file mode 100644 index 00000000000..66a79f926a3 --- /dev/null +++ b/homeassistant/components/ebusd/.translations/fr.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "journ\u00e9e", + "night": "Nuit" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/zh-Hans.json b/homeassistant/components/ebusd/.translations/zh-Hans.json new file mode 100644 index 00000000000..c43ca27b22a --- /dev/null +++ b/homeassistant/components/ebusd/.translations/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "\u65e5", + "night": "\u591c" + } +} \ No newline at end of file diff --git a/homeassistant/components/ecoal_boiler/sensor.py b/homeassistant/components/ecoal_boiler/sensor.py index 47ed2d6ebdf..ef8b39842d9 100644 --- a/homeassistant/components/ecoal_boiler/sensor.py +++ b/homeassistant/components/ecoal_boiler/sensor.py @@ -1,11 +1,11 @@ """Allows reading temperatures from ecoal/esterownik.pl controller.""" import logging -from homeassistant.components.ecoal_boiler import ( - DATA_ECOAL_BOILER, AVAILABLE_SENSORS, ) from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity +from . import AVAILABLE_SENSORS, DATA_ECOAL_BOILER + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['ecoal_boiler'] diff --git a/homeassistant/components/ecoal_boiler/switch.py b/homeassistant/components/ecoal_boiler/switch.py index f113125194a..db8759a032a 100644 --- a/homeassistant/components/ecoal_boiler/switch.py +++ b/homeassistant/components/ecoal_boiler/switch.py @@ -3,8 +3,8 @@ import logging from typing import Optional from homeassistant.components.switch import SwitchDevice -from homeassistant.components.ecoal_boiler import ( - DATA_ECOAL_BOILER, AVAILABLE_PUMPS, ) + +from . import AVAILABLE_PUMPS, DATA_ECOAL_BOILER _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index bfc67e7cfaf..44a3800afa9 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -279,6 +279,7 @@ class Thermostat(ClimateDevice): "fan": self.fan, "climate_mode": self.mode, "operation": operation, + "equipment_running": status, "climate_list": self.climate_list, "fan_min_on_time": self.fan_min_on_time } diff --git a/homeassistant/components/econet/__init__.py b/homeassistant/components/econet/__init__.py new file mode 100644 index 00000000000..48b7dad4c7c --- /dev/null +++ b/homeassistant/components/econet/__init__.py @@ -0,0 +1 @@ +"""The econet component.""" diff --git a/homeassistant/components/water_heater/econet.py b/homeassistant/components/econet/water_heater.py similarity index 100% rename from homeassistant/components/water_heater/econet.py rename to homeassistant/components/econet/water_heater.py diff --git a/homeassistant/components/ecovacs/vacuum.py b/homeassistant/components/ecovacs/vacuum.py index 9d2af730315..b9fe94f2bed 100644 --- a/homeassistant/components/ecovacs/vacuum.py +++ b/homeassistant/components/ecovacs/vacuum.py @@ -2,13 +2,13 @@ import logging from homeassistant.components.vacuum import ( - VacuumDevice, SUPPORT_BATTERY, SUPPORT_RETURN_HOME, SUPPORT_CLEAN_SPOT, - SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_LOCATE, SUPPORT_FAN_SPEED, SUPPORT_SEND_COMMAND, ) -from homeassistant.components.ecovacs import ( - ECOVACS_DEVICES) + SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED, SUPPORT_LOCATE, + SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND, SUPPORT_STATUS, SUPPORT_STOP, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, VacuumDevice) from homeassistant.helpers.icon import icon_for_battery_level +from . import ECOVACS_DEVICES + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['ecovacs'] diff --git a/homeassistant/components/eddystone_temperature/__init__.py b/homeassistant/components/eddystone_temperature/__init__.py new file mode 100644 index 00000000000..2d6f92498bd --- /dev/null +++ b/homeassistant/components/eddystone_temperature/__init__.py @@ -0,0 +1 @@ +"""The eddystone_temperature component.""" diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/eddystone_temperature/sensor.py similarity index 100% rename from homeassistant/components/sensor/eddystone_temperature.py rename to homeassistant/components/eddystone_temperature/sensor.py diff --git a/homeassistant/components/edimax/__init__.py b/homeassistant/components/edimax/__init__.py new file mode 100644 index 00000000000..33614bf4f95 --- /dev/null +++ b/homeassistant/components/edimax/__init__.py @@ -0,0 +1 @@ +"""The edimax component.""" diff --git a/homeassistant/components/switch/edimax.py b/homeassistant/components/edimax/switch.py similarity index 100% rename from homeassistant/components/switch/edimax.py rename to homeassistant/components/edimax/switch.py diff --git a/homeassistant/components/edp_redy/sensor.py b/homeassistant/components/edp_redy/sensor.py index 389ae77f35b..b8f9c031c29 100644 --- a/homeassistant/components/edp_redy/sensor.py +++ b/homeassistant/components/edp_redy/sensor.py @@ -1,10 +1,10 @@ """Support for EDP re:dy sensors.""" import logging -from homeassistant.helpers.entity import Entity from homeassistant.const import POWER_WATT +from homeassistant.helpers.entity import Entity -from homeassistant.components.edp_redy import EdpRedyDevice, EDP_REDY +from . import EDP_REDY, EdpRedyDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/edp_redy/switch.py b/homeassistant/components/edp_redy/switch.py index ad4ce8fe728..0c92f80ccf6 100644 --- a/homeassistant/components/edp_redy/switch.py +++ b/homeassistant/components/edp_redy/switch.py @@ -1,9 +1,10 @@ """Support for EDP re:dy plugs/switches.""" import logging -from homeassistant.components.edp_redy import EdpRedyDevice, EDP_REDY from homeassistant.components.switch import SwitchDevice +from . import EDP_REDY, EdpRedyDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['edp_redy'] diff --git a/homeassistant/components/ee_brightbox/__init__.py b/homeassistant/components/ee_brightbox/__init__.py new file mode 100644 index 00000000000..bec0886ae07 --- /dev/null +++ b/homeassistant/components/ee_brightbox/__init__.py @@ -0,0 +1 @@ +"""The ee_brightbox component.""" diff --git a/homeassistant/components/device_tracker/ee_brightbox.py b/homeassistant/components/ee_brightbox/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/ee_brightbox.py rename to homeassistant/components/ee_brightbox/device_tracker.py diff --git a/homeassistant/components/efergy/__init__.py b/homeassistant/components/efergy/__init__.py new file mode 100644 index 00000000000..8ceeb1585a4 --- /dev/null +++ b/homeassistant/components/efergy/__init__.py @@ -0,0 +1 @@ +"""The efergy component.""" diff --git a/homeassistant/components/sensor/efergy.py b/homeassistant/components/efergy/sensor.py similarity index 100% rename from homeassistant/components/sensor/efergy.py rename to homeassistant/components/efergy/sensor.py diff --git a/homeassistant/components/egardia/alarm_control_panel.py b/homeassistant/components/egardia/alarm_control_panel.py index e202a46f9f1..7fc60d5fb5d 100644 --- a/homeassistant/components/egardia/alarm_control_panel.py +++ b/homeassistant/components/egardia/alarm_control_panel.py @@ -4,14 +4,15 @@ import logging import requests import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.egardia import ( - CONF_REPORT_SERVER_CODES, CONF_REPORT_SERVER_ENABLED, - CONF_REPORT_SERVER_PORT, EGARDIA_DEVICE, EGARDIA_SERVER, - REPORT_SERVER_CODES_IGNORE) from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) +from . import ( + CONF_REPORT_SERVER_CODES, CONF_REPORT_SERVER_ENABLED, + CONF_REPORT_SERVER_PORT, EGARDIA_DEVICE, EGARDIA_SERVER, + REPORT_SERVER_CODES_IGNORE) + DEPENDENCIES = ['egardia'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/egardia/binary_sensor.py b/homeassistant/components/egardia/binary_sensor.py index 74a048a86c0..d11894ae675 100644 --- a/homeassistant/components/egardia/binary_sensor.py +++ b/homeassistant/components/egardia/binary_sensor.py @@ -2,10 +2,10 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.egardia import ( - ATTR_DISCOVER_DEVICES, EGARDIA_DEVICE) from homeassistant.const import STATE_OFF, STATE_ON +from . import ATTR_DISCOVER_DEVICES, EGARDIA_DEVICE + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['egardia'] diff --git a/homeassistant/components/eight_sleep/binary_sensor.py b/homeassistant/components/eight_sleep/binary_sensor.py index 2a9cb19a327..a3ca27b570d 100644 --- a/homeassistant/components/eight_sleep/binary_sensor.py +++ b/homeassistant/components/eight_sleep/binary_sensor.py @@ -2,8 +2,8 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.eight_sleep import ( - DATA_EIGHT, EightSleepHeatEntity, CONF_BINARY_SENSORS, NAME_MAP) + +from . import CONF_BINARY_SENSORS, DATA_EIGHT, NAME_MAP, EightSleepHeatEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index 2bb03c8d4f2..a1ad93ec54a 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -1,9 +1,9 @@ """Support for Eight Sleep sensors.""" import logging -from homeassistant.components.eight_sleep import ( - DATA_EIGHT, EightSleepHeatEntity, EightSleepUserEntity, - CONF_SENSORS, NAME_MAP) +from . import ( + CONF_SENSORS, DATA_EIGHT, NAME_MAP, EightSleepHeatEntity, + EightSleepUserEntity) DEPENDENCIES = ['eight_sleep'] diff --git a/homeassistant/components/eliqonline/__init__.py b/homeassistant/components/eliqonline/__init__.py new file mode 100644 index 00000000000..4cb38436ee4 --- /dev/null +++ b/homeassistant/components/eliqonline/__init__.py @@ -0,0 +1 @@ +"""The eliqonline component.""" diff --git a/homeassistant/components/sensor/eliqonline.py b/homeassistant/components/eliqonline/sensor.py similarity index 100% rename from homeassistant/components/sensor/eliqonline.py rename to homeassistant/components/eliqonline/sensor.py diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index 63b38c1d321..e9155dd17b5 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -1,16 +1,17 @@ """Each ElkM1 area will be created as a separate alarm_control_panel.""" import voluptuous as vol + import homeassistant.components.alarm_control_panel as alarm from homeassistant.const import ( ATTR_CODE, ATTR_ENTITY_ID, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMING, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) -from homeassistant.components.elkm1 import ( - DOMAIN as ELK_DOMAIN, create_elk_entities, ElkEntity) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send) +from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities + DEPENDENCIES = [ELK_DOMAIN] SIGNAL_ARM_ENTITY = 'elkm1_arm' diff --git a/homeassistant/components/elkm1/climate.py b/homeassistant/components/elkm1/climate.py index 72f93b5419c..93e4aa66b23 100644 --- a/homeassistant/components/elkm1/climate.py +++ b/homeassistant/components/elkm1/climate.py @@ -1,14 +1,13 @@ """Support for control of Elk-M1 connected thermostats.""" from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, STATE_AUTO, - STATE_COOL, STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT, - SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE_HIGH, + ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, STATE_AUTO, STATE_COOL, + STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW) -from homeassistant.components.elkm1 import ( - DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities) -from homeassistant.const import ( - STATE_ON, PRECISION_WHOLE) +from homeassistant.const import PRECISION_WHOLE, STATE_ON + +from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities DEPENDENCIES = [ELK_DOMAIN] diff --git a/homeassistant/components/elkm1/light.py b/homeassistant/components/elkm1/light.py index 3a282595d58..fe84ab3f251 100644 --- a/homeassistant/components/elkm1/light.py +++ b/homeassistant/components/elkm1/light.py @@ -1,8 +1,8 @@ """Support for control of ElkM1 lighting (X10, UPB, etc).""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) -from homeassistant.components.elkm1 import ( - DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities) + +from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities DEPENDENCIES = [ELK_DOMAIN] diff --git a/homeassistant/components/elkm1/scene.py b/homeassistant/components/elkm1/scene.py index c8583b1d8bf..1d08f4cf96d 100644 --- a/homeassistant/components/elkm1/scene.py +++ b/homeassistant/components/elkm1/scene.py @@ -1,8 +1,8 @@ """Support for control of ElkM1 tasks ("macros").""" -from homeassistant.components.elkm1 import ( - DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities) from homeassistant.components.scene import Scene +from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities + DEPENDENCIES = [ELK_DOMAIN] diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index 63da6ea5376..da27a3ac4b1 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -1,6 +1,5 @@ """Support for control of ElkM1 sensors.""" -from homeassistant.components.elkm1 import ( - DOMAIN as ELK_DOMAIN, create_elk_entities, ElkEntity) +from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities DEPENDENCIES = [ELK_DOMAIN] diff --git a/homeassistant/components/elkm1/switch.py b/homeassistant/components/elkm1/switch.py index 7badd6ee5dc..740a2965865 100644 --- a/homeassistant/components/elkm1/switch.py +++ b/homeassistant/components/elkm1/switch.py @@ -1,8 +1,8 @@ """Support for control of ElkM1 outputs (relays).""" -from homeassistant.components.elkm1 import ( - DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities) from homeassistant.components.switch import SwitchDevice +from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities + DEPENDENCIES = [ELK_DOMAIN] diff --git a/homeassistant/components/emby/__init__.py b/homeassistant/components/emby/__init__.py new file mode 100644 index 00000000000..053da956c64 --- /dev/null +++ b/homeassistant/components/emby/__init__.py @@ -0,0 +1 @@ +"""The emby component.""" diff --git a/homeassistant/components/media_player/emby.py b/homeassistant/components/emby/media_player.py similarity index 100% rename from homeassistant/components/media_player/emby.py rename to homeassistant/components/emby/media_player.py diff --git a/homeassistant/components/emoncms/__init__.py b/homeassistant/components/emoncms/__init__.py new file mode 100644 index 00000000000..5e7adbcd6e7 --- /dev/null +++ b/homeassistant/components/emoncms/__init__.py @@ -0,0 +1 @@ +"""The emoncms component.""" diff --git a/homeassistant/components/sensor/emoncms.py b/homeassistant/components/emoncms/sensor.py similarity index 100% rename from homeassistant/components/sensor/emoncms.py rename to homeassistant/components/emoncms/sensor.py diff --git a/homeassistant/components/emulated_roku/.translations/bg.json b/homeassistant/components/emulated_roku/.translations/bg.json new file mode 100644 index 00000000000..77a96095c25 --- /dev/null +++ b/homeassistant/components/emulated_roku/.translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host_ip": "\u0410\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/fr.json b/homeassistant/components/emulated_roku/.translations/fr.json index 5da2d437a35..629e006564b 100644 --- a/homeassistant/components/emulated_roku/.translations/fr.json +++ b/homeassistant/components/emulated_roku/.translations/fr.json @@ -1,7 +1,18 @@ { "config": { + "abort": { + "name_exists": "Ce nom est d\u00e9j\u00e0 utilis\u00e9" + }, "step": { "user": { + "data": { + "advertise_ip": "IP d'annonce", + "advertise_port": "Port d'annonce", + "host_ip": "IP h\u00f4te", + "listen_port": "Port d'\u00e9coute", + "name": "Nom", + "upnp_bind_multicast": "Lier la multidiffusion (True / False)" + }, "title": "D\u00e9finir la configuration du serveur" } }, diff --git a/homeassistant/components/emulated_roku/.translations/hu.json b/homeassistant/components/emulated_roku/.translations/hu.json index c38e6890d8a..9b6f7706253 100644 --- a/homeassistant/components/emulated_roku/.translations/hu.json +++ b/homeassistant/components/emulated_roku/.translations/hu.json @@ -6,7 +6,7 @@ "step": { "user": { "data": { - "host_ip": "H\u00e1zigazda IP", + "host_ip": "Hoszt IP", "listen_port": "Port figyel\u00e9se", "name": "N\u00e9v" }, diff --git a/homeassistant/components/emulated_roku/.translations/zh-Hans.json b/homeassistant/components/emulated_roku/.translations/zh-Hans.json index 5ff0466c9bc..88d8a822696 100644 --- a/homeassistant/components/emulated_roku/.translations/zh-Hans.json +++ b/homeassistant/components/emulated_roku/.translations/zh-Hans.json @@ -10,10 +10,12 @@ "advertise_port": "\u5e7f\u64ad\u7aef\u53e3", "host_ip": "\u4e3b\u673a IP", "listen_port": "\u76d1\u542c\u7aef\u53e3", - "name": "\u59d3\u540d" + "name": "\u59d3\u540d", + "upnp_bind_multicast": "\u7ed1\u5b9a\u591a\u64ad (True/False)" }, "title": "\u5b9a\u4e49\u670d\u52a1\u5668\u914d\u7f6e" } - } + }, + "title": "EmulatedRoku" } } \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/binding.py b/homeassistant/components/emulated_roku/binding.py index cd42560288d..9dfb58a5e34 100644 --- a/homeassistant/components/emulated_roku/binding.py +++ b/homeassistant/components/emulated_roku/binding.py @@ -5,7 +5,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from homeassistant.core import CoreState, EventOrigin -LOGGER = logging.getLogger('homeassistant.components.emulated_roku') +LOGGER = logging.getLogger('.') EVENT_ROKU_COMMAND = 'roku_command' diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py index 40101120f12..0b6f995be97 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -14,7 +14,7 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, STATE_PLAYING, CONF_PORT) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['openwebifpy==1.2.7'] +REQUIREMENTS = ['openwebifpy==3.1.0'] _LOGGER = logging.getLogger(__name__) @@ -24,6 +24,9 @@ ATTR_MEDIA_END_TIME = 'media_end_time' ATTR_MEDIA_START_TIME = 'media_start_time' CONF_USE_CHANNEL_ICON = "use_channel_icon" +CONF_DEEP_STANDBY = "deep_standby" +CONF_MAC_ADDRESS = "mac_address" +CONF_SOURCE_BOUQUET = "source_bouquet" DEFAULT_NAME = 'Enigma2 Media Player' DEFAULT_PORT = 80 @@ -31,6 +34,9 @@ DEFAULT_SSL = False DEFAULT_USE_CHANNEL_ICON = False DEFAULT_USERNAME = 'root' DEFAULT_PASSWORD = 'dreambox' +DEFAULT_DEEP_STANDBY = False +DEFAULT_MAC_ADDRESS = '' +DEFAULT_SOURCE_BOUQUET = '' SUPPORTED_ENIGMA2 = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_TURN_OFF | SUPPORT_NEXT_TRACK | SUPPORT_STOP | \ @@ -46,6 +52,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, vol.Optional(CONF_USE_CHANNEL_ICON, default=DEFAULT_USE_CHANNEL_ICON): cv.boolean, + vol.Optional(CONF_DEEP_STANDBY, default=DEFAULT_DEEP_STANDBY): cv.boolean, + vol.Optional(CONF_MAC_ADDRESS, default=DEFAULT_MAC_ADDRESS): cv.string, + vol.Optional(CONF_SOURCE_BOUQUET, + default=DEFAULT_SOURCE_BOUQUET): cv.string, }) @@ -62,6 +72,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): config[CONF_PASSWORD] = DEFAULT_PASSWORD config[CONF_SSL] = DEFAULT_SSL config[CONF_USE_CHANNEL_ICON] = DEFAULT_USE_CHANNEL_ICON + config[CONF_MAC_ADDRESS] = DEFAULT_MAC_ADDRESS + config[CONF_DEEP_STANDBY] = DEFAULT_DEEP_STANDBY + config[CONF_SOURCE_BOUQUET] = DEFAULT_SOURCE_BOUQUET from openwebif.api import CreateDevice device = \ @@ -70,7 +83,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): username=config.get(CONF_USERNAME), password=config.get(CONF_PASSWORD), is_https=config.get(CONF_SSL), - prefer_picon=config.get(CONF_USE_CHANNEL_ICON)) + prefer_picon=config.get(CONF_USE_CHANNEL_ICON), + mac_address=config.get(CONF_MAC_ADDRESS), + turn_off_to_deep=config.get(CONF_DEEP_STANDBY), + source_bouquet=config.get(CONF_SOURCE_BOUQUET)) add_devices([Enigma2Device(config[CONF_NAME], device)], True) diff --git a/homeassistant/components/enphase_envoy/__init__.py b/homeassistant/components/enphase_envoy/__init__.py new file mode 100644 index 00000000000..c4101fbcdf2 --- /dev/null +++ b/homeassistant/components/enphase_envoy/__init__.py @@ -0,0 +1 @@ +"""The enphase_envoy component.""" diff --git a/homeassistant/components/sensor/enphase_envoy.py b/homeassistant/components/enphase_envoy/sensor.py similarity index 100% rename from homeassistant/components/sensor/enphase_envoy.py rename to homeassistant/components/enphase_envoy/sensor.py diff --git a/homeassistant/components/entur_public_transport/__init__.py b/homeassistant/components/entur_public_transport/__init__.py new file mode 100644 index 00000000000..0bdce1909f4 --- /dev/null +++ b/homeassistant/components/entur_public_transport/__init__.py @@ -0,0 +1 @@ +"""Component for integrating entur public transport.""" diff --git a/homeassistant/components/entur_public_transport/sensor.py b/homeassistant/components/entur_public_transport/sensor.py new file mode 100644 index 00000000000..b2e22867690 --- /dev/null +++ b/homeassistant/components/entur_public_transport/sensor.py @@ -0,0 +1,240 @@ +"""Real-time information about public transport departures in Norway.""" +from datetime import datetime, timedelta +import logging + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, + CONF_SHOW_ON_MAP) +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle +import homeassistant.util.dt as dt_util + +REQUIREMENTS = ['enturclient==0.2.0'] + +_LOGGER = logging.getLogger(__name__) + +API_CLIENT_NAME = 'homeassistant-homeassistant' + +ATTRIBUTION = "Data provided by entur.org under NLOD" + +CONF_STOP_IDS = 'stop_ids' +CONF_EXPAND_PLATFORMS = 'expand_platforms' +CONF_WHITELIST_LINES = 'line_whitelist' +CONF_OMIT_NON_BOARDING = 'omit_non_boarding' +CONF_NUMBER_OF_DEPARTURES = 'number_of_departures' + +DEFAULT_NAME = 'Entur' +DEFAULT_ICON_KEY = 'bus' + +ICONS = { + 'air': 'mdi:airplane', + 'bus': 'mdi:bus', + 'metro': 'mdi:subway', + 'rail': 'mdi:train', + 'tram': 'mdi:tram', + 'water': 'mdi:ferry', +} + +SCAN_INTERVAL = timedelta(seconds=45) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_STOP_IDS): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_EXPAND_PLATFORMS, default=True): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean, + vol.Optional(CONF_WHITELIST_LINES, default=[]): cv.ensure_list, + vol.Optional(CONF_OMIT_NON_BOARDING, default=True): cv.boolean, + vol.Optional(CONF_NUMBER_OF_DEPARTURES, default=2): + vol.All(cv.positive_int, vol.Range(min=2, max=10)) +}) + + +ATTR_STOP_ID = 'stop_id' + +ATTR_ROUTE = 'route' +ATTR_ROUTE_ID = 'route_id' +ATTR_EXPECTED_AT = 'due_at' +ATTR_DELAY = 'delay' +ATTR_REALTIME = 'real_time' + +ATTR_NEXT_UP_IN = 'next_due_in' +ATTR_NEXT_UP_ROUTE = 'next_route' +ATTR_NEXT_UP_ROUTE_ID = 'next_route_id' +ATTR_NEXT_UP_AT = 'next_due_at' +ATTR_NEXT_UP_DELAY = 'next_delay' +ATTR_NEXT_UP_REALTIME = 'next_real_time' + +ATTR_TRANSPORT_MODE = 'transport_mode' + + +def due_in_minutes(timestamp: datetime) -> int: + """Get the time in minutes from a timestamp.""" + if timestamp is None: + return None + diff = timestamp - dt_util.now() + return int(diff.total_seconds() / 60) + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Set up the Entur public transport sensor.""" + from enturclient import EnturPublicTransportData + + expand = config.get(CONF_EXPAND_PLATFORMS) + line_whitelist = config.get(CONF_WHITELIST_LINES) + name = config.get(CONF_NAME) + show_on_map = config.get(CONF_SHOW_ON_MAP) + stop_ids = config.get(CONF_STOP_IDS) + omit_non_boarding = config.get(CONF_OMIT_NON_BOARDING) + number_of_departures = config.get(CONF_NUMBER_OF_DEPARTURES) + + stops = [s for s in stop_ids if "StopPlace" in s] + quays = [s for s in stop_ids if "Quay" in s] + + data = EnturPublicTransportData(API_CLIENT_NAME, + stops=stops, + quays=quays, + line_whitelist=line_whitelist, + omit_non_boarding=omit_non_boarding, + number_of_departures=number_of_departures, + web_session=async_get_clientsession(hass)) + + if expand: + await data.expand_all_quays() + await data.update() + + proxy = EnturProxy(data) + + entities = [] + for place in data.all_stop_places_quays(): + try: + given_name = "{} {}".format( + name, data.get_stop_info(place).name) + except KeyError: + given_name = "{} {}".format(name, place) + + entities.append( + EnturPublicTransportSensor(proxy, given_name, place, show_on_map)) + + async_add_entities(entities, True) + + +class EnturProxy: + """Proxy for the Entur client. + + Ensure throttle to not hit rate limiting on the API. + """ + + def __init__(self, api): + """Initialize the proxy.""" + self._api = api + + @Throttle(timedelta(seconds=15)) + async def async_update(self) -> None: + """Update data in client.""" + await self._api.update() + + def get_stop_info(self, stop_id: str) -> dict: + """Get info about specific stop place.""" + return self._api.get_stop_info(stop_id) + + +class EnturPublicTransportSensor(Entity): + """Implementation of a Entur public transport sensor.""" + + def __init__( + self, api: EnturProxy, name: str, stop: str, show_on_map: bool): + """Initialize the sensor.""" + self.api = api + self._stop = stop + self._show_on_map = show_on_map + self._name = name + self._state = None + self._icon = ICONS[DEFAULT_ICON_KEY] + self._attributes = {} + + @property + def name(self) -> str: + """Return the name of the sensor.""" + return self._name + + @property + def state(self) -> str: + """Return the state of the sensor.""" + return self._state + + @property + def device_state_attributes(self) -> dict: + """Return the state attributes.""" + self._attributes[ATTR_ATTRIBUTION] = ATTRIBUTION + self._attributes[ATTR_STOP_ID] = self._stop + return self._attributes + + @property + def unit_of_measurement(self) -> str: + """Return the unit this state is expressed in.""" + return 'min' + + @property + def icon(self) -> str: + """Icon to use in the frontend.""" + return self._icon + + async def async_update(self) -> None: + """Get the latest data and update the states.""" + await self.api.async_update() + + self._attributes = {} + + data = self.api.get_stop_info(self._stop) + if data is None: + self._state = None + return + + if self._show_on_map and data.latitude and data.longitude: + self._attributes[CONF_LATITUDE] = data.latitude + self._attributes[CONF_LONGITUDE] = data.longitude + + calls = data.estimated_calls + if not calls: + self._state = None + return + + self._state = due_in_minutes(calls[0].expected_departure_time) + self._icon = ICONS.get( + calls[0].transport_mode, ICONS[DEFAULT_ICON_KEY]) + + self._attributes[ATTR_ROUTE] = calls[0].front_display + self._attributes[ATTR_ROUTE_ID] = calls[0].line_id + self._attributes[ATTR_EXPECTED_AT] = calls[0]\ + .expected_departure_time.strftime("%H:%M") + self._attributes[ATTR_REALTIME] = calls[0].is_realtime + self._attributes[ATTR_DELAY] = calls[0].delay_in_min + + number_of_calls = len(calls) + if number_of_calls < 2: + return + + self._attributes[ATTR_NEXT_UP_ROUTE] = calls[1].front_display + self._attributes[ATTR_NEXT_UP_ROUTE_ID] = calls[1].line_id + self._attributes[ATTR_NEXT_UP_AT] = calls[1]\ + .expected_departure_time.strftime("%H:%M") + self._attributes[ATTR_NEXT_UP_IN] = "{} min"\ + .format(due_in_minutes(calls[1].expected_departure_time)) + self._attributes[ATTR_NEXT_UP_REALTIME] = calls[1].is_realtime + self._attributes[ATTR_NEXT_UP_DELAY] = calls[1].delay_in_min + + if number_of_calls < 3: + return + + for i, call in enumerate(calls[2:]): + key_name = "departure_#" + str(i + 3) + self._attributes[key_name] = "{}{} {}".format( + "" if bool(call.is_realtime) else "ca. ", + call.expected_departure_time.strftime("%H:%M"), + call.front_display) diff --git a/homeassistant/components/envirophat/__init__.py b/homeassistant/components/envirophat/__init__.py new file mode 100644 index 00000000000..68d3a99441c --- /dev/null +++ b/homeassistant/components/envirophat/__init__.py @@ -0,0 +1 @@ +"""The envirophat component.""" diff --git a/homeassistant/components/sensor/envirophat.py b/homeassistant/components/envirophat/sensor.py similarity index 98% rename from homeassistant/components/sensor/envirophat.py rename to homeassistant/components/envirophat/sensor.py index 1c90f5998e8..7683c06b69c 100644 --- a/homeassistant/components/sensor/envirophat.py +++ b/homeassistant/components/envirophat/sensor.py @@ -4,6 +4,7 @@ Support for Enviro pHAT sensors. For more details about this component, please refer to the documentation at https://home-assistant.io/components/sensor.envirophat """ +import importlib import logging from datetime import timedelta @@ -55,7 +56,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Sense HAT sensor platform.""" try: - import envirophat + envirophat = importlib.import_module('envirophat') except OSError: _LOGGER.error("No Enviro pHAT was found.") return False diff --git a/homeassistant/components/envisalink/__init__.py b/homeassistant/components/envisalink/__init__.py index b7590341f78..c46a26c6f85 100644 --- a/homeassistant/components/envisalink/__init__.py +++ b/homeassistant/components/envisalink/__init__.py @@ -6,7 +6,8 @@ import voluptuous as vol from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_TIMEOUT +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_TIMEOUT, \ + CONF_HOST from homeassistant.helpers.entity import Entity from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -20,7 +21,6 @@ DOMAIN = 'envisalink' DATA_EVL = 'envisalink' CONF_CODE = 'code' -CONF_EVL_HOST = 'host' CONF_EVL_KEEPALIVE = 'keepalive_interval' CONF_EVL_PORT = 'port' CONF_EVL_VERSION = 'evl_version' @@ -56,7 +56,7 @@ PARTITION_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_EVL_HOST): cv.string, + vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PANEL_TYPE): vol.All(cv.string, vol.In(['HONEYWELL', 'DSC'])), vol.Required(CONF_USERNAME): cv.string, @@ -95,7 +95,7 @@ async def async_setup(hass, config): conf = config.get(DOMAIN) - host = conf.get(CONF_EVL_HOST) + host = conf.get(CONF_HOST) port = conf.get(CONF_EVL_PORT) code = conf.get(CONF_CODE) panel_type = conf.get(CONF_PANEL_TYPE) diff --git a/homeassistant/components/envisalink/alarm_control_panel.py b/homeassistant/components/envisalink/alarm_control_panel.py index a4cc5864fc4..44874c6d5e8 100644 --- a/homeassistant/components/envisalink/alarm_control_panel.py +++ b/homeassistant/components/envisalink/alarm_control_panel.py @@ -3,16 +3,18 @@ import logging import voluptuous as vol -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.components.alarm_control_panel as alarm -import homeassistant.helpers.config_validation as cv -from homeassistant.components.envisalink import ( - DATA_EVL, EnvisalinkDevice, PARTITION_SCHEMA, CONF_CODE, CONF_PANIC, - CONF_PARTITIONNAME, SIGNAL_KEYPAD_UPDATE, SIGNAL_PARTITION_UPDATE) from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, - STATE_UNKNOWN, STATE_ALARM_TRIGGERED, STATE_ALARM_PENDING, ATTR_ENTITY_ID) + ATTR_ENTITY_ID, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, + STATE_UNKNOWN) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ( + CONF_CODE, CONF_PANIC, CONF_PARTITIONNAME, DATA_EVL, PARTITION_SCHEMA, + SIGNAL_KEYPAD_UPDATE, SIGNAL_PARTITION_UPDATE, EnvisalinkDevice) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/envisalink/binary_sensor.py b/homeassistant/components/envisalink/binary_sensor.py index 26b54e16cc8..267bba8cd28 100644 --- a/homeassistant/components/envisalink/binary_sensor.py +++ b/homeassistant/components/envisalink/binary_sensor.py @@ -1,16 +1,17 @@ """Support for Envisalink zone states- represented as binary sensors.""" -import logging import datetime +import logging +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.const import ATTR_LAST_TRIP_TIME from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.envisalink import ( - DATA_EVL, ZONE_SCHEMA, CONF_ZONENAME, CONF_ZONETYPE, EnvisalinkDevice, - SIGNAL_ZONE_UPDATE) -from homeassistant.const import ATTR_LAST_TRIP_TIME from homeassistant.util import dt as dt_util +from . import ( + CONF_ZONENAME, CONF_ZONETYPE, DATA_EVL, SIGNAL_ZONE_UPDATE, ZONE_SCHEMA, + EnvisalinkDevice) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['envisalink'] diff --git a/homeassistant/components/envisalink/sensor.py b/homeassistant/components/envisalink/sensor.py index cc6a8b87232..67a601b02a2 100644 --- a/homeassistant/components/envisalink/sensor.py +++ b/homeassistant/components/envisalink/sensor.py @@ -3,11 +3,12 @@ import logging from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.components.envisalink import ( - DATA_EVL, PARTITION_SCHEMA, CONF_PARTITIONNAME, EnvisalinkDevice, - SIGNAL_KEYPAD_UPDATE, SIGNAL_PARTITION_UPDATE) from homeassistant.helpers.entity import Entity +from . import ( + CONF_PARTITIONNAME, DATA_EVL, PARTITION_SCHEMA, SIGNAL_KEYPAD_UPDATE, + SIGNAL_PARTITION_UPDATE, EnvisalinkDevice) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['envisalink'] diff --git a/homeassistant/components/ephember/__init__.py b/homeassistant/components/ephember/__init__.py new file mode 100644 index 00000000000..97758383c11 --- /dev/null +++ b/homeassistant/components/ephember/__init__.py @@ -0,0 +1 @@ +"""The ephember component.""" diff --git a/homeassistant/components/climate/ephember.py b/homeassistant/components/ephember/climate.py similarity index 100% rename from homeassistant/components/climate/ephember.py rename to homeassistant/components/ephember/climate.py diff --git a/homeassistant/components/epson/__init__.py b/homeassistant/components/epson/__init__.py new file mode 100644 index 00000000000..eed342f77f9 --- /dev/null +++ b/homeassistant/components/epson/__init__.py @@ -0,0 +1 @@ +"""The epson component.""" diff --git a/homeassistant/components/media_player/epson.py b/homeassistant/components/epson/media_player.py similarity index 100% rename from homeassistant/components/media_player/epson.py rename to homeassistant/components/epson/media_player.py diff --git a/homeassistant/components/eq3btsmart/__init__.py b/homeassistant/components/eq3btsmart/__init__.py new file mode 100644 index 00000000000..f32eba6944f --- /dev/null +++ b/homeassistant/components/eq3btsmart/__init__.py @@ -0,0 +1 @@ +"""The eq3btsmart component.""" diff --git a/homeassistant/components/climate/eq3btsmart.py b/homeassistant/components/eq3btsmart/climate.py similarity index 100% rename from homeassistant/components/climate/eq3btsmart.py rename to homeassistant/components/eq3btsmart/climate.py diff --git a/homeassistant/components/esphome/.translations/bg.json b/homeassistant/components/esphome/.translations/bg.json new file mode 100644 index 00000000000..3574965cae6 --- /dev/null +++ b/homeassistant/components/esphome/.translations/bg.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "ESP \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d" + }, + "error": { + "connection_error": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 ESP. \u041c\u043e\u043b\u044f, \u0443\u0432\u0435\u0440\u0435\u0442\u0435 \u0441\u0435, \u0447\u0435 \u0432\u0430\u0448\u0438\u044f\u0442 YAML \u0444\u0430\u0439\u043b \u0441\u044a\u0434\u044a\u0440\u0436\u0430 \u0440\u0435\u0434 \"api:\".", + "invalid_password": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u0430 \u043f\u0430\u0440\u043e\u043b\u0430!", + "resolve_error": "\u041d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0435 \u043e\u0442\u043a\u0440\u0438\u0435 \u0430\u0434\u0440\u0435\u0441\u044a\u0442 \u043d\u0430 ESP. \u0410\u043a\u043e \u0442\u0430\u0437\u0438 \u0433\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u043e\u0434\u044a\u043b\u0436\u0430\u0432\u0430, \u0437\u0430\u0434\u0430\u0439\u0442\u0435 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u043d IP \u0430\u0434\u0440\u0435\u0441: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" + }, + "step": { + "authenticate": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + }, + "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430, \u043a\u043e\u044f\u0442\u043e \u0441\u0442\u0435 \u0437\u0430\u0434\u0430\u043b\u0438 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u0441\u0438 \u0437\u0430 {name} .", + "title": "\u041f\u0430\u0440\u043e\u043b\u0430" + }, + "discovery_confirm": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u0435 ESPHome \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e ` {name} ` \u043a\u044a\u043c Home Assistant?", + "title": "\u041e\u0442\u043a\u0440\u0438\u0442\u043e \u0435 ESPHome \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + }, + "user": { + "data": { + "host": "\u0410\u0434\u0440\u0435\u0441", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0437\u0430 \u0432\u0440\u044a\u0437\u043a\u0430 \u0441 [ESPHome](https://esphomelib.com/).", + "title": "ESPHome" + } + }, + "title": "ESPHome" + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/.translations/es-419.json b/homeassistant/components/esphome/.translations/es-419.json index 84000783435..58dbba34fa8 100644 --- a/homeassistant/components/esphome/.translations/es-419.json +++ b/homeassistant/components/esphome/.translations/es-419.json @@ -16,6 +16,9 @@ "description": "Por favor ingrese la contrase\u00f1a que estableci\u00f3 en su configuraci\u00f3n para {name} .", "title": "Escriba la contrase\u00f1a" }, + "discovery_confirm": { + "title": "Nodo ESPHome descubierto" + }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/esphome/.translations/fr.json b/homeassistant/components/esphome/.translations/fr.json index a52f6159797..b230a73c354 100644 --- a/homeassistant/components/esphome/.translations/fr.json +++ b/homeassistant/components/esphome/.translations/fr.json @@ -13,7 +13,7 @@ "data": { "password": "Mot de passe" }, - "description": "Veuillez saisir le mot de passe que vous avez d\u00e9fini dans votre configuration.", + "description": "Veuillez saisir le mot de passe que vous avez d\u00e9fini dans votre configuration pour {name}", "title": "Entrer votre mot de passe" }, "discovery_confirm": { diff --git a/homeassistant/components/esphome/.translations/hu.json b/homeassistant/components/esphome/.translations/hu.json index 1e72bd8030c..c665637ba05 100644 --- a/homeassistant/components/esphome/.translations/hu.json +++ b/homeassistant/components/esphome/.translations/hu.json @@ -4,22 +4,28 @@ "already_configured": "Az ESP-t m\u00e1r konfigur\u00e1ltad." }, "error": { - "connection_error": "Nem tud csatlakozni az ESP-hez. K\u00e9rlek gy\u0151z\u0151dj meg r\u00f3la, hogy a YAML f\u00e1jl tartalmaz egy \"api:\" sort.", - "invalid_password": "\u00c9rv\u00e9nytelen jelsz\u00f3!" + "connection_error": "Nem lehet csatlakozni az ESP-hez. K\u00e9rlek gy\u0151z\u0151dj meg r\u00f3la, hogy a YAML f\u00e1jl tartalmaz egy \"api:\" sort.", + "invalid_password": "\u00c9rv\u00e9nytelen jelsz\u00f3!", + "resolve_error": "Az ESP c\u00edme nem oldhat\u00f3 fel. Ha a hiba tov\u00e1bbra is fenn\u00e1ll, k\u00e9rlek, \u00e1ll\u00edts be egy statikus IP-c\u00edmet: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, "step": { "authenticate": { "data": { "password": "Jelsz\u00f3" }, - "description": "K\u00e9rj\u00fck, add meg a konfigur\u00e1ci\u00f3ban be\u00e1ll\u00edtott jelsz\u00f3t.", - "title": "Adja meg a jelsz\u00f3t" + "description": "K\u00e9rlek, add meg a konfigur\u00e1ci\u00f3ban {name} n\u00e9vhez be\u00e1ll\u00edtott jelsz\u00f3t.", + "title": "Add meg a jelsz\u00f3t" + }, + "discovery_confirm": { + "description": "Szeretn\u00e9d hozz\u00e1adni a(z) `{name}` ESPHome csom\u00f3pontot a Home Assistant-hoz?", + "title": "Felfedezett ESPHome csom\u00f3pont" }, "user": { "data": { - "host": "Kiszolg\u00e1l\u00f3", + "host": "Hoszt", "port": "Port" }, + "description": "K\u00e9rlek, add meg az [ESPHome](https://esphomelib.com/) csom\u00f3pontod kapcsol\u00f3d\u00e1si be\u00e1ll\u00edt\u00e1sait.", "title": "ESPHome" } }, diff --git a/homeassistant/components/esphome/.translations/pl.json b/homeassistant/components/esphome/.translations/pl.json index 697fbf0311e..d24fb929068 100644 --- a/homeassistant/components/esphome/.translations/pl.json +++ b/homeassistant/components/esphome/.translations/pl.json @@ -17,7 +17,7 @@ "title": "Wprowad\u017a has\u0142o" }, "discovery_confirm": { - "description": "Czy chcesz doda\u0107 w\u0119ze\u0142 ESPHome ` {name} ` do Home Assistant?", + "description": "Czy chcesz doda\u0107 w\u0119ze\u0142 ESPHome `{name}` do Home Assistant?", "title": "Znaleziono w\u0119ze\u0142 ESPHome " }, "user": { diff --git a/homeassistant/components/esphome/.translations/ru.json b/homeassistant/components/esphome/.translations/ru.json index 2b631ea219c..9777a920a94 100644 --- a/homeassistant/components/esphome/.translations/ru.json +++ b/homeassistant/components/esphome/.translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430" }, "error": { "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a ESP. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0412\u0430\u0448 YAML-\u0444\u0430\u0439\u043b \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 'api:'.", diff --git a/homeassistant/components/esphome/.translations/sl.json b/homeassistant/components/esphome/.translations/sl.json index 7745c455015..93ca607aabe 100644 --- a/homeassistant/components/esphome/.translations/sl.json +++ b/homeassistant/components/esphome/.translations/sl.json @@ -13,9 +13,13 @@ "data": { "password": "Geslo" }, - "description": "Vnesite geslo, ki ste ga nastavili v svoji konfiguraciji.", + "description": "Vnesite geslo, ki ste ga nastavili v konfiguraciji za {name}.", "title": "Vnesite geslo" }, + "discovery_confirm": { + "description": "\u017delite dodati ESPHome vozli\u0161\u010de ` {name} ` v Home Assistant?", + "title": "Odkrita ESPHome vozli\u0161\u010da" + }, "user": { "data": { "host": "Gostitelj", diff --git a/homeassistant/components/esphome/.translations/uk.json b/homeassistant/components/esphome/.translations/uk.json index 94dafeb3c2e..4f4c2f32c61 100644 --- a/homeassistant/components/esphome/.translations/uk.json +++ b/homeassistant/components/esphome/.translations/uk.json @@ -16,6 +16,10 @@ "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c, \u044f\u043a\u0438\u0439 \u0432\u0438 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 \u0443 \u0441\u0432\u043e\u0457\u0439 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457.", "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c" }, + "discovery_confirm": { + "description": "\u0414\u043e\u0434\u0430\u0442\u0438 ESPHome \u0432\u0443\u0437\u043e\u043b {name} \u0443 Home Assistant?", + "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u0432\u0443\u0437\u043e\u043b ESPHome " + }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442", diff --git a/homeassistant/components/esphome/.translations/zh-Hans.json b/homeassistant/components/esphome/.translations/zh-Hans.json index 0a8211be449..46790868aba 100644 --- a/homeassistant/components/esphome/.translations/zh-Hans.json +++ b/homeassistant/components/esphome/.translations/zh-Hans.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "ESP \u5df2\u914d\u7f6e\u5b8c\u6210" + }, "error": { "connection_error": "\u65e0\u6cd5\u8fde\u63a5\u5230 ESP\u3002\u8bf7\u786e\u8ba4\u60a8\u7684 YAML \u6587\u4ef6\u4e2d\u5305\u542b 'api:' \u884c\u3002", "invalid_password": "\u65e0\u6548\u7684\u5bc6\u7801\uff01", @@ -13,6 +16,10 @@ "description": "\u8bf7\u8f93\u5165\u60a8\u5728\u914d\u7f6e\u4e2d\u4e3a\u201c{name}\u201d\u8bbe\u7f6e\u7684\u5bc6\u7801\u3002", "title": "\u8f93\u5165\u5bc6\u7801" }, + "discovery_confirm": { + "description": "\u662f\u5426\u8981\u5c06 ESPHome \u8282\u70b9 `{name}` \u6dfb\u52a0\u5230 Home Assistant\uff1f", + "title": "\u53d1\u73b0\u4e86 ESPHome \u8282\u70b9" + }, "user": { "data": { "host": "\u4e3b\u673a", diff --git a/homeassistant/components/esphome/.translations/zh-Hant.json b/homeassistant/components/esphome/.translations/zh-Hant.json index 65817470860..9a5821f0b8f 100644 --- a/homeassistant/components/esphome/.translations/zh-Hant.json +++ b/homeassistant/components/esphome/.translations/zh-Hant.json @@ -17,15 +17,15 @@ "title": "\u8f38\u5165\u5bc6\u78bc" }, "discovery_confirm": { - "description": "\u662f\u5426\u8981\u5c07 ESPHome node\u300c{name}\u300d\u65b0\u589e\u81f3 Home Assistant\uff1f", - "title": "\u81ea\u52d5\u63a2\u7d22\u5230\u7684 ESPHome node" + "description": "\u662f\u5426\u8981\u5c07 ESPHome \u7bc0\u9ede\u300c{name}\u300d\u65b0\u589e\u81f3 Home Assistant\uff1f", + "title": "\u767c\u73fe\u5230 ESPHome \u7bc0\u9ede" }, "user": { "data": { "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0" }, - "description": "\u8acb\u8f38\u5165 [ESPHome](https://esphomelib.com/) node \u9023\u7dda\u8cc7\u8a0a\u3002", + "description": "\u8acb\u8f38\u5165 [ESPHome](https://esphomelib.com/) \u7bc0\u9ede\u9023\u7dda\u8cc7\u8a0a\u3002", "title": "ESPHome" } }, diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 51f565a0980..39422c530b3 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -32,12 +32,10 @@ if TYPE_CHECKING: ServiceCall, UserService DOMAIN = 'esphome' -REQUIREMENTS = ['aioesphomeapi==1.6.0'] +REQUIREMENTS = ['aioesphomeapi==1.7.0'] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'esphome' - DISPATCHER_UPDATE_ENTITY = 'esphome_{entry_id}_update_{component_key}_{key}' DISPATCHER_REMOVE_ENTITY = 'esphome_{entry_id}_remove_{component_key}_{key}' DISPATCHER_ON_LIST = 'esphome_{entry_id}_on_list' @@ -50,6 +48,7 @@ STORAGE_VERSION = 1 # The HA component types this integration supports HA_COMPONENTS = [ 'binary_sensor', + 'camera', 'cover', 'fan', 'light', @@ -543,7 +542,7 @@ class EsphomeEntity(Entity): self._remove_callbacks.append( async_dispatcher_connect(self.hass, DISPATCHER_UPDATE_ENTITY.format(**kwargs), - self.async_schedule_update_ha_state) + self._on_update) ) self._remove_callbacks.append( @@ -558,6 +557,10 @@ class EsphomeEntity(Entity): self.async_schedule_update_ha_state) ) + async def _on_update(self): + """Update the entity state when state or static info changed.""" + self.async_schedule_update_ha_state() + async def async_will_remove_from_hass(self): """Unregister callbacks.""" for remove_callback in self._remove_callbacks: diff --git a/homeassistant/components/esphome/binary_sensor.py b/homeassistant/components/esphome/binary_sensor.py index 4796df636ba..2db2f209fa5 100644 --- a/homeassistant/components/esphome/binary_sensor.py +++ b/homeassistant/components/esphome/binary_sensor.py @@ -1,11 +1,10 @@ """Support for ESPHome binary sensors.""" import logging - from typing import TYPE_CHECKING, Optional from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.esphome import EsphomeEntity, \ - platform_async_setup_entry + +from . import EsphomeEntity, platform_async_setup_entry if TYPE_CHECKING: # pylint: disable=unused-import diff --git a/homeassistant/components/esphome/camera.py b/homeassistant/components/esphome/camera.py new file mode 100644 index 00000000000..319a2c2a4d9 --- /dev/null +++ b/homeassistant/components/esphome/camera.py @@ -0,0 +1,83 @@ +"""Support for ESPHome cameras.""" +import asyncio +import logging +from typing import Optional, TYPE_CHECKING + +from homeassistant.components import camera +from homeassistant.components.camera import Camera +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.typing import HomeAssistantType +from . import EsphomeEntity, platform_async_setup_entry + +if TYPE_CHECKING: + # pylint: disable=unused-import + from aioesphomeapi import CameraInfo, CameraState # noqa + +DEPENDENCIES = ['esphome'] +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass: HomeAssistantType, + entry: ConfigEntry, async_add_entities) -> None: + """Set up esphome cameras based on a config entry.""" + # pylint: disable=redefined-outer-name + from aioesphomeapi import CameraInfo, CameraState # noqa + + await platform_async_setup_entry( + hass, entry, async_add_entities, + component_key='camera', + info_type=CameraInfo, entity_type=EsphomeCamera, + state_type=CameraState + ) + + +class EsphomeCamera(Camera, EsphomeEntity): + """A camera implementation for ESPHome.""" + + def __init__(self, entry_id: str, component_key: str, key: int): + """Initialize.""" + Camera.__init__(self) + EsphomeEntity.__init__(self, entry_id, component_key, key) + self._image_cond = asyncio.Condition() + + @property + def _static_info(self) -> 'CameraInfo': + return super()._static_info + + @property + def _state(self) -> Optional['CameraState']: + return super()._state + + async def _on_update(self): + """Notify listeners of new image when update arrives.""" + await super()._on_update() + async with self._image_cond: + self._image_cond.notify_all() + + async def async_camera_image(self) -> Optional[bytes]: + """Return single camera image bytes.""" + if not self.available: + return None + await self._client.request_single_image() + async with self._image_cond: + await self._image_cond.wait() + if not self.available: + return None + return self._state.image[:] + + async def _async_camera_stream_image(self) -> Optional[bytes]: + """Return a single camera image in a stream.""" + if not self.available: + return None + await self._client.request_image_stream() + async with self._image_cond: + await self._image_cond.wait() + if not self.available: + return None + return self._state.image[:] + + async def handle_async_mjpeg_stream(self, request): + """Serve an HTTP MJPEG stream from the camera.""" + return await camera.async_get_still_stream( + request, self._async_camera_stream_image, + camera.DEFAULT_CONTENT_TYPE, 0.0) diff --git a/homeassistant/components/esphome/cover.py b/homeassistant/components/esphome/cover.py index 14fce3fb4eb..d86c40e627e 100644 --- a/homeassistant/components/esphome/cover.py +++ b/homeassistant/components/esphome/cover.py @@ -1,15 +1,14 @@ """Support for ESPHome covers.""" import logging - from typing import TYPE_CHECKING, Optional -from homeassistant.components.cover import CoverDevice, SUPPORT_CLOSE, \ - SUPPORT_OPEN, SUPPORT_STOP -from homeassistant.components.esphome import EsphomeEntity, \ - platform_async_setup_entry +from homeassistant.components.cover import ( + SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP, CoverDevice) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType +from . import EsphomeEntity, platform_async_setup_entry + if TYPE_CHECKING: # pylint: disable=unused-import from aioesphomeapi import CoverInfo, CoverState # noqa diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index 49e2401545b..05f18cb014a 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -1,14 +1,15 @@ """Support for ESPHome fans.""" import logging -from typing import List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional -from homeassistant.components.esphome import EsphomeEntity, \ - platform_async_setup_entry -from homeassistant.components.fan import FanEntity, SPEED_HIGH, SPEED_LOW, \ - SPEED_MEDIUM, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, SPEED_OFF +from homeassistant.components.fan import ( + SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_OSCILLATE, + SUPPORT_SET_SPEED, FanEntity) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType +from . import EsphomeEntity, platform_async_setup_entry + if TYPE_CHECKING: # pylint: disable=unused-import from aioesphomeapi import FanInfo, FanState # noqa diff --git a/homeassistant/components/esphome/light.py b/homeassistant/components/esphome/light.py index 3b0889ede8e..c84c50010d9 100644 --- a/homeassistant/components/esphome/light.py +++ b/homeassistant/components/esphome/light.py @@ -1,18 +1,18 @@ """Support for ESPHome lights.""" import logging -from typing import Optional, List, Tuple, TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional, Tuple -from homeassistant.components.esphome import EsphomeEntity, \ - platform_async_setup_entry -from homeassistant.components.light import Light, SUPPORT_FLASH, \ - SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, SUPPORT_COLOR, \ - SUPPORT_WHITE_VALUE, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, ATTR_HS_COLOR, \ - ATTR_FLASH, ATTR_TRANSITION, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \ - ATTR_EFFECT, ATTR_WHITE_VALUE, FLASH_SHORT, FLASH_LONG +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR, + ATTR_TRANSITION, ATTR_WHITE_VALUE, FLASH_LONG, FLASH_SHORT, + SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, + SUPPORT_FLASH, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, Light) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.color as color_util +from . import EsphomeEntity, platform_async_setup_entry + if TYPE_CHECKING: # pylint: disable=unused-import from aioesphomeapi import LightInfo, LightState # noqa diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index f776ea4683b..e4fb7ef82ba 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -1,13 +1,13 @@ """Support for esphome sensors.""" import logging import math -from typing import Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, Optional -from homeassistant.components.esphome import EsphomeEntity, \ - platform_async_setup_entry from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType +from . import EsphomeEntity, platform_async_setup_entry + if TYPE_CHECKING: # pylint: disable=unused-import from aioesphomeapi import ( # noqa diff --git a/homeassistant/components/esphome/switch.py b/homeassistant/components/esphome/switch.py index 4bf4b416b8d..e5a9d0cf446 100644 --- a/homeassistant/components/esphome/switch.py +++ b/homeassistant/components/esphome/switch.py @@ -1,14 +1,13 @@ """Support for ESPHome switches.""" import logging - from typing import TYPE_CHECKING, Optional -from homeassistant.components.esphome import EsphomeEntity, \ - platform_async_setup_entry from homeassistant.components.switch import SwitchDevice from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType +from . import EsphomeEntity, platform_async_setup_entry + if TYPE_CHECKING: # pylint: disable=unused-import from aioesphomeapi import SwitchInfo, SwitchState # noqa diff --git a/homeassistant/components/etherscan/__init__.py b/homeassistant/components/etherscan/__init__.py new file mode 100644 index 00000000000..0e983bd6bea --- /dev/null +++ b/homeassistant/components/etherscan/__init__.py @@ -0,0 +1 @@ +"""The etherscan component.""" diff --git a/homeassistant/components/sensor/etherscan.py b/homeassistant/components/etherscan/sensor.py similarity index 100% rename from homeassistant/components/sensor/etherscan.py rename to homeassistant/components/etherscan/sensor.py diff --git a/homeassistant/components/everlights/__init__.py b/homeassistant/components/everlights/__init__.py new file mode 100644 index 00000000000..0b45bdd430b --- /dev/null +++ b/homeassistant/components/everlights/__init__.py @@ -0,0 +1 @@ +"""The everlights component.""" diff --git a/homeassistant/components/light/everlights.py b/homeassistant/components/everlights/light.py similarity index 100% rename from homeassistant/components/light/everlights.py rename to homeassistant/components/everlights/light.py diff --git a/homeassistant/components/evohome/climate.py b/homeassistant/components/evohome/climate.py index 955b82e37e3..eea34e07001 100644 --- a/homeassistant/components/evohome/climate.py +++ b/homeassistant/components/evohome/climate.py @@ -6,30 +6,18 @@ from requests.exceptions import HTTPError from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - STATE_AUTO, STATE_ECO, STATE_MANUAL, - SUPPORT_AWAY_MODE, - SUPPORT_ON_OFF, - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE, -) -from homeassistant.components.evohome import ( - DATA_EVOHOME, DISPATCHER_EVOHOME, - CONF_LOCATION_IDX, SCAN_INTERVAL_DEFAULT, - EVO_PARENT, EVO_CHILD, - GWS, TCS, -) + STATE_AUTO, STATE_ECO, STATE_MANUAL, SUPPORT_AWAY_MODE, SUPPORT_ON_OFF, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) from homeassistant.const import ( - CONF_SCAN_INTERVAL, - HTTP_TOO_MANY_REQUESTS, - PRECISION_HALVES, - STATE_OFF, - TEMP_CELSIUS -) + CONF_SCAN_INTERVAL, HTTP_TOO_MANY_REQUESTS, PRECISION_HALVES, STATE_OFF, + TEMP_CELSIUS) from homeassistant.core import callback from homeassistant.helpers.dispatcher import ( - dispatcher_send, - async_dispatcher_connect -) + async_dispatcher_connect, dispatcher_send) + +from . import ( + CONF_LOCATION_IDX, DATA_EVOHOME, DISPATCHER_EVOHOME, EVO_CHILD, EVO_PARENT, + GWS, SCAN_INTERVAL_DEFAULT, TCS) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/facebook/__init__.py b/homeassistant/components/facebook/__init__.py new file mode 100644 index 00000000000..1619b8a91f1 --- /dev/null +++ b/homeassistant/components/facebook/__init__.py @@ -0,0 +1 @@ +"""The facebook component.""" diff --git a/homeassistant/components/notify/facebook.py b/homeassistant/components/facebook/notify.py similarity index 95% rename from homeassistant/components/notify/facebook.py rename to homeassistant/components/facebook/notify.py index b73f845ea17..2c691661a29 100644 --- a/homeassistant/components/notify/facebook.py +++ b/homeassistant/components/facebook/notify.py @@ -11,11 +11,13 @@ from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol -from homeassistant.components.notify import ( - ATTR_DATA, ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONTENT_TYPE_JSON import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET, + PLATFORM_SCHEMA, + BaseNotificationService) + _LOGGER = logging.getLogger(__name__) CONF_PAGE_ACCESS_TOKEN = 'page_access_token' diff --git a/homeassistant/components/facebox/__init__.py b/homeassistant/components/facebox/__init__.py new file mode 100644 index 00000000000..9e5b6afb10b --- /dev/null +++ b/homeassistant/components/facebox/__init__.py @@ -0,0 +1 @@ +"""The facebox component.""" diff --git a/homeassistant/components/image_processing/facebox.py b/homeassistant/components/facebox/image_processing.py similarity index 100% rename from homeassistant/components/image_processing/facebox.py rename to homeassistant/components/facebox/image_processing.py diff --git a/homeassistant/components/fail2ban/__init__.py b/homeassistant/components/fail2ban/__init__.py new file mode 100644 index 00000000000..cb2716e581d --- /dev/null +++ b/homeassistant/components/fail2ban/__init__.py @@ -0,0 +1 @@ +"""The fail2ban component.""" diff --git a/homeassistant/components/sensor/fail2ban.py b/homeassistant/components/fail2ban/sensor.py similarity index 100% rename from homeassistant/components/sensor/fail2ban.py rename to homeassistant/components/fail2ban/sensor.py diff --git a/homeassistant/components/familyhub/__init__.py b/homeassistant/components/familyhub/__init__.py new file mode 100644 index 00000000000..1ac09b44a9d --- /dev/null +++ b/homeassistant/components/familyhub/__init__.py @@ -0,0 +1 @@ +"""The familyhub component.""" diff --git a/homeassistant/components/camera/familyhub.py b/homeassistant/components/familyhub/camera.py similarity index 93% rename from homeassistant/components/camera/familyhub.py rename to homeassistant/components/familyhub/camera.py index f3dd8b6d0c9..e14bd9f1098 100644 --- a/homeassistant/components/camera/familyhub.py +++ b/homeassistant/components/familyhub/camera.py @@ -8,8 +8,7 @@ import logging import voluptuous as vol -from homeassistant.components.camera import Camera -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.const import CONF_IP_ADDRESS, CONF_NAME from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/fastdotcom/sensor.py b/homeassistant/components/fastdotcom/sensor.py index 0f17179f918..37fc0815ddc 100644 --- a/homeassistant/components/fastdotcom/sensor.py +++ b/homeassistant/components/fastdotcom/sensor.py @@ -1,12 +1,12 @@ """Support for Fast.com internet speed testing sensor.""" import logging -from homeassistant.components.fastdotcom import DOMAIN as FASTDOTCOM_DOMAIN, \ - DATA_UPDATED from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity +from . import DATA_UPDATED, DOMAIN as FASTDOTCOM_DOMAIN + DEPENDENCIES = ['fastdotcom'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fedex/__init__.py b/homeassistant/components/fedex/__init__.py new file mode 100644 index 00000000000..d685ab50372 --- /dev/null +++ b/homeassistant/components/fedex/__init__.py @@ -0,0 +1 @@ +"""The fedex component.""" diff --git a/homeassistant/components/sensor/fedex.py b/homeassistant/components/fedex/sensor.py similarity index 100% rename from homeassistant/components/sensor/fedex.py rename to homeassistant/components/fedex/sensor.py diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py index 7b7e3a81294..05bc1d99167 100644 --- a/homeassistant/components/ffmpeg/__init__.py +++ b/homeassistant/components/ffmpeg/__init__.py @@ -12,7 +12,7 @@ from homeassistant.helpers.dispatcher import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['ha-ffmpeg==1.11'] +REQUIREMENTS = ['ha-ffmpeg==2.0'] DOMAIN = 'ffmpeg' diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/ffmpeg/camera.py similarity index 72% rename from homeassistant/components/camera/ffmpeg.py rename to homeassistant/components/ffmpeg/camera.py index db9e73f3e1b..07e56cfd51f 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/ffmpeg/camera.py @@ -9,22 +9,24 @@ import logging import voluptuous as vol +from homeassistant.components.camera import ( + PLATFORM_SCHEMA, Camera, SUPPORT_STREAM) from homeassistant.const import CONF_NAME -from homeassistant.components.camera import Camera, PLATFORM_SCHEMA -from homeassistant.components.ffmpeg import ( - DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS) +from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.aiohttp_client import ( - async_aiohttp_proxy_stream) + +from . import CONF_EXTRA_ARGUMENTS, CONF_INPUT, DATA_FFMPEG _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['ffmpeg'] + DEFAULT_NAME = 'FFmpeg' +DEFAULT_ARGUMENTS = "-pred 1" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_INPUT): cv.string, - vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, + vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) @@ -47,9 +49,19 @@ class FFmpegCamera(Camera): self._input = config.get(CONF_INPUT) self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS) + @property + def supported_features(self): + """Return supported features.""" + return SUPPORT_STREAM + + @property + def stream_source(self): + """Return the stream source.""" + return self._input.split(' ')[-1] + async def async_camera_image(self): """Return a still image response from the camera.""" - from haffmpeg import ImageFrame, IMAGE_JPEG + from haffmpeg.tools import ImageFrame, IMAGE_JPEG ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop) image = await asyncio.shield(ffmpeg.get_image( @@ -59,15 +71,16 @@ class FFmpegCamera(Camera): async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" - from haffmpeg import CameraMjpeg + from haffmpeg.camera import CameraMjpeg stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop) await stream.open_camera( self._input, extra_cmd=self._extra_arguments) try: + stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream, + self.hass, request, stream_reader, self._manager.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/ffmpeg_motion/__init__.py b/homeassistant/components/ffmpeg_motion/__init__.py new file mode 100644 index 00000000000..b13c0efeacf --- /dev/null +++ b/homeassistant/components/ffmpeg_motion/__init__.py @@ -0,0 +1 @@ +"""The ffmpeg_motion component.""" diff --git a/homeassistant/components/binary_sensor/ffmpeg_motion.py b/homeassistant/components/ffmpeg_motion/binary_sensor.py similarity index 98% rename from homeassistant/components/binary_sensor/ffmpeg_motion.py rename to homeassistant/components/ffmpeg_motion/binary_sensor.py index d0e597e13c0..8183b0e66a6 100644 --- a/homeassistant/components/binary_sensor/ffmpeg_motion.py +++ b/homeassistant/components/ffmpeg_motion/binary_sensor.py @@ -86,7 +86,7 @@ class FFmpegMotion(FFmpegBinarySensor): def __init__(self, hass, manager, config): """Initialize FFmpeg motion binary sensor.""" - from haffmpeg import SensorMotion + from haffmpeg.sensor import SensorMotion super().__init__(config) self.ffmpeg = SensorMotion( diff --git a/homeassistant/components/ffmpeg_noise/__init__.py b/homeassistant/components/ffmpeg_noise/__init__.py new file mode 100644 index 00000000000..ab233df98ce --- /dev/null +++ b/homeassistant/components/ffmpeg_noise/__init__.py @@ -0,0 +1 @@ +"""The ffmpeg_noise component.""" diff --git a/homeassistant/components/binary_sensor/ffmpeg_noise.py b/homeassistant/components/ffmpeg_noise/binary_sensor.py similarity index 96% rename from homeassistant/components/binary_sensor/ffmpeg_noise.py rename to homeassistant/components/ffmpeg_noise/binary_sensor.py index 3c2397d692b..56edf1ddfd6 100644 --- a/homeassistant/components/binary_sensor/ffmpeg_noise.py +++ b/homeassistant/components/ffmpeg_noise/binary_sensor.py @@ -10,7 +10,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import PLATFORM_SCHEMA -from homeassistant.components.binary_sensor.ffmpeg_motion import ( +from homeassistant.components.ffmpeg_motion.binary_sensor import ( FFmpegBinarySensor) from homeassistant.components.ffmpeg import ( DATA_FFMPEG, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS, @@ -55,7 +55,7 @@ class FFmpegNoise(FFmpegBinarySensor): def __init__(self, hass, manager, config): """Initialize FFmpeg noise binary sensor.""" - from haffmpeg import SensorNoise + from haffmpeg.sensor import SensorNoise super().__init__(config) self.ffmpeg = SensorNoise( diff --git a/homeassistant/components/fibaro/binary_sensor.py b/homeassistant/components/fibaro/binary_sensor.py index 2c2d9c30a79..f71a5f3662e 100644 --- a/homeassistant/components/fibaro/binary_sensor.py +++ b/homeassistant/components/fibaro/binary_sensor.py @@ -2,10 +2,10 @@ import logging from homeassistant.components.binary_sensor import ( - BinarySensorDevice, ENTITY_ID_FORMAT) -from homeassistant.components.fibaro import ( - FIBARO_DEVICES, FibaroDevice) -from homeassistant.const import (CONF_DEVICE_CLASS, CONF_ICON) + ENTITY_ID_FORMAT, BinarySensorDevice) +from homeassistant.const import CONF_DEVICE_CLASS, CONF_ICON + +from . import FIBARO_DEVICES, FibaroDevice DEPENDENCIES = ['fibaro'] diff --git a/homeassistant/components/fibaro/cover.py b/homeassistant/components/fibaro/cover.py index aa34fcc36a9..0f5cc32bc96 100644 --- a/homeassistant/components/fibaro/cover.py +++ b/homeassistant/components/fibaro/cover.py @@ -2,9 +2,9 @@ import logging from homeassistant.components.cover import ( - CoverDevice, ENTITY_ID_FORMAT, ATTR_POSITION, ATTR_TILT_POSITION) -from homeassistant.components.fibaro import ( - FIBARO_DEVICES, FibaroDevice) + ATTR_POSITION, ATTR_TILT_POSITION, ENTITY_ID_FORMAT, CoverDevice) + +from . import FIBARO_DEVICES, FibaroDevice DEPENDENCIES = ['fibaro'] diff --git a/homeassistant/components/fibaro/light.py b/homeassistant/components/fibaro/light.py index 5ee3e83b95f..600b566b36b 100644 --- a/homeassistant/components/fibaro/light.py +++ b/homeassistant/components/fibaro/light.py @@ -1,18 +1,17 @@ """Support for Fibaro lights.""" -import logging import asyncio from functools import partial +import logging -from homeassistant.const import ( - CONF_WHITE_VALUE) -from homeassistant.components.fibaro import ( - FIBARO_DEVICES, FibaroDevice, - CONF_DIMMING, CONF_COLOR, CONF_RESET_COLOR) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_WHITE_VALUE, ENTITY_ID_FORMAT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_WHITE_VALUE, Light) +from homeassistant.const import CONF_WHITE_VALUE import homeassistant.util.color as color_util +from . import ( + CONF_COLOR, CONF_DIMMING, CONF_RESET_COLOR, FIBARO_DEVICES, FibaroDevice) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['fibaro'] diff --git a/homeassistant/components/fibaro/scene.py b/homeassistant/components/fibaro/scene.py index 620f095b733..93f0cd5b63a 100644 --- a/homeassistant/components/fibaro/scene.py +++ b/homeassistant/components/fibaro/scene.py @@ -1,10 +1,9 @@ """Support for Fibaro scenes.""" import logging -from homeassistant.components.scene import ( - Scene) -from homeassistant.components.fibaro import ( - FIBARO_DEVICES, FibaroDevice) +from homeassistant.components.scene import Scene + +from . import FIBARO_DEVICES, FibaroDevice DEPENDENCIES = ['fibaro'] diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index 01452d8b394..20a37fd3c23 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -1,13 +1,13 @@ """Support for Fibaro sensors.""" import logging -from homeassistant.const import ( - DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_ILLUMINANCE, TEMP_CELSIUS, TEMP_FAHRENHEIT) -from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import ENTITY_ID_FORMAT -from homeassistant.components.fibaro import ( - FIBARO_DEVICES, FibaroDevice) +from homeassistant.const import ( + DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, TEMP_FAHRENHEIT) +from homeassistant.helpers.entity import Entity + +from . import FIBARO_DEVICES, FibaroDevice SENSOR_TYPES = { 'com.fibaro.temperatureSensor': diff --git a/homeassistant/components/fibaro/switch.py b/homeassistant/components/fibaro/switch.py index 04b8aba1cf4..024531f62c7 100644 --- a/homeassistant/components/fibaro/switch.py +++ b/homeassistant/components/fibaro/switch.py @@ -1,10 +1,10 @@ """Support for Fibaro switches.""" import logging -from homeassistant.util import convert from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice -from homeassistant.components.fibaro import ( - FIBARO_DEVICES, FibaroDevice) +from homeassistant.util import convert + +from . import FIBARO_DEVICES, FibaroDevice DEPENDENCIES = ['fibaro'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fido/__init__.py b/homeassistant/components/fido/__init__.py new file mode 100644 index 00000000000..d950d39ef70 --- /dev/null +++ b/homeassistant/components/fido/__init__.py @@ -0,0 +1 @@ +"""The fido component.""" diff --git a/homeassistant/components/sensor/fido.py b/homeassistant/components/fido/sensor.py similarity index 100% rename from homeassistant/components/sensor/fido.py rename to homeassistant/components/fido/sensor.py diff --git a/homeassistant/components/file/__init__.py b/homeassistant/components/file/__init__.py new file mode 100644 index 00000000000..ed31fa957dd --- /dev/null +++ b/homeassistant/components/file/__init__.py @@ -0,0 +1 @@ +"""The file component.""" diff --git a/homeassistant/components/notify/file.py b/homeassistant/components/file/notify.py similarity index 99% rename from homeassistant/components/notify/file.py rename to homeassistant/components/file/notify.py index 749a6d4b330..d449476469b 100644 --- a/homeassistant/components/notify/file.py +++ b/homeassistant/components/file/notify.py @@ -9,11 +9,12 @@ import os import voluptuous as vol -import homeassistant.util.dt as dt_util -from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_FILENAME import homeassistant.helpers.config_validation as cv +import homeassistant.util.dt as dt_util + +from homeassistant.components.notify import ( + ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) CONF_TIMESTAMP = 'timestamp' diff --git a/homeassistant/components/sensor/file.py b/homeassistant/components/file/sensor.py similarity index 100% rename from homeassistant/components/sensor/file.py rename to homeassistant/components/file/sensor.py diff --git a/homeassistant/components/filesize/__init__.py b/homeassistant/components/filesize/__init__.py new file mode 100644 index 00000000000..c532274997c --- /dev/null +++ b/homeassistant/components/filesize/__init__.py @@ -0,0 +1 @@ +"""The filesize component.""" diff --git a/homeassistant/components/sensor/filesize.py b/homeassistant/components/filesize/sensor.py similarity index 100% rename from homeassistant/components/sensor/filesize.py rename to homeassistant/components/filesize/sensor.py diff --git a/homeassistant/components/filter/__init__.py b/homeassistant/components/filter/__init__.py new file mode 100644 index 00000000000..ebdcec75abf --- /dev/null +++ b/homeassistant/components/filter/__init__.py @@ -0,0 +1 @@ +"""The filter component.""" diff --git a/homeassistant/components/sensor/filter.py b/homeassistant/components/filter/sensor.py similarity index 100% rename from homeassistant/components/sensor/filter.py rename to homeassistant/components/filter/sensor.py diff --git a/homeassistant/components/fints/__init__.py b/homeassistant/components/fints/__init__.py new file mode 100644 index 00000000000..0113fa75234 --- /dev/null +++ b/homeassistant/components/fints/__init__.py @@ -0,0 +1 @@ +"""The fints component.""" diff --git a/homeassistant/components/sensor/fints.py b/homeassistant/components/fints/sensor.py similarity index 100% rename from homeassistant/components/sensor/fints.py rename to homeassistant/components/fints/sensor.py diff --git a/homeassistant/components/fitbit/__init__.py b/homeassistant/components/fitbit/__init__.py new file mode 100644 index 00000000000..04946f6386f --- /dev/null +++ b/homeassistant/components/fitbit/__init__.py @@ -0,0 +1 @@ +"""The fitbit component.""" diff --git a/homeassistant/components/sensor/fitbit.py b/homeassistant/components/fitbit/sensor.py similarity index 100% rename from homeassistant/components/sensor/fitbit.py rename to homeassistant/components/fitbit/sensor.py diff --git a/homeassistant/components/fixer/__init__.py b/homeassistant/components/fixer/__init__.py new file mode 100644 index 00000000000..a5023b5db70 --- /dev/null +++ b/homeassistant/components/fixer/__init__.py @@ -0,0 +1 @@ +"""The fixer component.""" diff --git a/homeassistant/components/sensor/fixer.py b/homeassistant/components/fixer/sensor.py similarity index 100% rename from homeassistant/components/sensor/fixer.py rename to homeassistant/components/fixer/sensor.py diff --git a/homeassistant/components/flexit/__init__.py b/homeassistant/components/flexit/__init__.py new file mode 100644 index 00000000000..4ace1a38945 --- /dev/null +++ b/homeassistant/components/flexit/__init__.py @@ -0,0 +1 @@ +"""The flexit component.""" diff --git a/homeassistant/components/climate/flexit.py b/homeassistant/components/flexit/climate.py similarity index 100% rename from homeassistant/components/climate/flexit.py rename to homeassistant/components/flexit/climate.py diff --git a/homeassistant/components/flic/__init__.py b/homeassistant/components/flic/__init__.py new file mode 100644 index 00000000000..b15b06217c1 --- /dev/null +++ b/homeassistant/components/flic/__init__.py @@ -0,0 +1 @@ +"""The flic component.""" diff --git a/homeassistant/components/binary_sensor/flic.py b/homeassistant/components/flic/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/flic.py rename to homeassistant/components/flic/binary_sensor.py diff --git a/homeassistant/components/flock/__init__.py b/homeassistant/components/flock/__init__.py new file mode 100644 index 00000000000..1b58d21cff8 --- /dev/null +++ b/homeassistant/components/flock/__init__.py @@ -0,0 +1 @@ +"""The flock component.""" diff --git a/homeassistant/components/notify/flock.py b/homeassistant/components/flock/notify.py similarity index 93% rename from homeassistant/components/notify/flock.py rename to homeassistant/components/flock/notify.py index d26f629809f..b37483d2f13 100644 --- a/homeassistant/components/notify/flock.py +++ b/homeassistant/components/flock/notify.py @@ -10,12 +10,13 @@ import logging import async_timeout import voluptuous as vol -from homeassistant.components.notify import ( - PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import (PLATFORM_SCHEMA, + BaseNotificationService) + _LOGGER = logging.getLogger(__name__) _RESOURCE = 'https://api.flock.com/hooks/sendMessage/' diff --git a/homeassistant/components/flunearyou/__init__.py b/homeassistant/components/flunearyou/__init__.py new file mode 100644 index 00000000000..5657e646be5 --- /dev/null +++ b/homeassistant/components/flunearyou/__init__.py @@ -0,0 +1 @@ +"""The flunearyou component.""" diff --git a/homeassistant/components/sensor/flunearyou.py b/homeassistant/components/flunearyou/sensor.py similarity index 100% rename from homeassistant/components/sensor/flunearyou.py rename to homeassistant/components/flunearyou/sensor.py diff --git a/homeassistant/components/flux/__init__.py b/homeassistant/components/flux/__init__.py new file mode 100644 index 00000000000..c9eeda06fa8 --- /dev/null +++ b/homeassistant/components/flux/__init__.py @@ -0,0 +1 @@ +"""The flux component.""" diff --git a/homeassistant/components/switch/flux.py b/homeassistant/components/flux/switch.py similarity index 100% rename from homeassistant/components/switch/flux.py rename to homeassistant/components/flux/switch.py diff --git a/homeassistant/components/flux_led/__init__.py b/homeassistant/components/flux_led/__init__.py new file mode 100644 index 00000000000..572d6e3c983 --- /dev/null +++ b/homeassistant/components/flux_led/__init__.py @@ -0,0 +1 @@ +"""The flux_led component.""" diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/flux_led/light.py similarity index 100% rename from homeassistant/components/light/flux_led.py rename to homeassistant/components/flux_led/light.py diff --git a/homeassistant/components/folder/__init__.py b/homeassistant/components/folder/__init__.py new file mode 100644 index 00000000000..0b95217fd2a --- /dev/null +++ b/homeassistant/components/folder/__init__.py @@ -0,0 +1 @@ +"""The folder component.""" diff --git a/homeassistant/components/sensor/folder.py b/homeassistant/components/folder/sensor.py similarity index 100% rename from homeassistant/components/sensor/folder.py rename to homeassistant/components/folder/sensor.py diff --git a/homeassistant/components/foobot/__init__.py b/homeassistant/components/foobot/__init__.py new file mode 100644 index 00000000000..92edde9a5e1 --- /dev/null +++ b/homeassistant/components/foobot/__init__.py @@ -0,0 +1 @@ +"""The foobot component.""" diff --git a/homeassistant/components/sensor/foobot.py b/homeassistant/components/foobot/sensor.py similarity index 100% rename from homeassistant/components/sensor/foobot.py rename to homeassistant/components/foobot/sensor.py diff --git a/homeassistant/components/foscam/__init__.py b/homeassistant/components/foscam/__init__.py new file mode 100644 index 00000000000..5c63f7b2a15 --- /dev/null +++ b/homeassistant/components/foscam/__init__.py @@ -0,0 +1 @@ +"""The foscam component.""" diff --git a/homeassistant/components/camera/foscam.py b/homeassistant/components/foscam/camera.py similarity index 79% rename from homeassistant/components/camera/foscam.py rename to homeassistant/components/foscam/camera.py index ceec57f7755..b6f2162d57a 100644 --- a/homeassistant/components/camera/foscam.py +++ b/homeassistant/components/foscam/camera.py @@ -8,7 +8,8 @@ import logging import voluptuous as vol -from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) +from homeassistant.components.camera import ( + Camera, PLATFORM_SCHEMA, SUPPORT_STREAM) from homeassistant.const import ( CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT) from homeassistant.helpers import config_validation as cv @@ -57,6 +58,11 @@ class FoscamCam(Camera): self._foscam_session = FoscamCamera( ip_address, port, self._username, self._password, verbose=False) + self._media_port = None + result, response = self._foscam_session.get_port_info() + if result == 0: + self._media_port = response['mediaPort'] + def camera_image(self): """Return a still image response from the camera.""" # Send the request to snap a picture and return raw jpg data @@ -67,6 +73,24 @@ class FoscamCam(Camera): return response + @property + def supported_features(self): + """Return supported features.""" + if self._media_port: + return SUPPORT_STREAM + return 0 + + @property + def stream_source(self): + """Return the stream source.""" + if self._media_port: + return 'rtsp://{}:{}@{}:{}/videoMain'.format( + self._username, + self._password, + self._foscam_session.host, + self._media_port) + return None + @property def motion_detection_enabled(self): """Camera Motion Detection Status.""" diff --git a/homeassistant/components/free_mobile/__init__.py b/homeassistant/components/free_mobile/__init__.py new file mode 100644 index 00000000000..002a1475fde --- /dev/null +++ b/homeassistant/components/free_mobile/__init__.py @@ -0,0 +1 @@ +"""The free_mobile component.""" diff --git a/homeassistant/components/notify/free_mobile.py b/homeassistant/components/free_mobile/notify.py similarity index 92% rename from homeassistant/components/notify/free_mobile.py rename to homeassistant/components/free_mobile/notify.py index a27d0495193..1c6804f6d82 100644 --- a/homeassistant/components/notify/free_mobile.py +++ b/homeassistant/components/free_mobile/notify.py @@ -8,11 +8,12 @@ import logging import voluptuous as vol -from homeassistant.components.notify import ( - PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_ACCESS_TOKEN, CONF_USERNAME import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import (PLATFORM_SCHEMA, + BaseNotificationService) + REQUIREMENTS = ['freesms==0.1.2'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index 41e60d884ce..7accf7820f4 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -9,7 +9,7 @@ from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers.discovery import async_load_platform -REQUIREMENTS = ['aiofreepybox==0.0.6'] +REQUIREMENTS = ['aiofreepybox==0.0.8'] _LOGGER = logging.getLogger(__name__) @@ -60,7 +60,7 @@ async def async_setup_freebox(hass, config, host, port): } token_file = hass.config.path(FREEBOX_CONFIG_FILE) - api_version = 'v1' + api_version = 'v4' fbx = Freepybox( app_desc=app_desc, @@ -78,6 +78,8 @@ async def async_setup_freebox(hass, config, host, port): hass, 'sensor', DOMAIN, {}, config)) hass.async_create_task(async_load_platform( hass, 'device_tracker', DOMAIN, {}, config)) + hass.async_create_task(async_load_platform( + hass, 'switch', DOMAIN, {}, config)) async def close_fbx(event): """Close Freebox connection on HA Stop.""" diff --git a/homeassistant/components/freebox/device_tracker.py b/homeassistant/components/freebox/device_tracker.py index fb94f7f56f5..5418c1c61a7 100644 --- a/homeassistant/components/freebox/device_tracker.py +++ b/homeassistant/components/freebox/device_tracker.py @@ -3,7 +3,8 @@ from collections import namedtuple import logging from homeassistant.components.device_tracker import DeviceScanner -from homeassistant.components.freebox import DATA_FREEBOX + +from . import DATA_FREEBOX DEPENDENCIES = ['freebox'] diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index 49e68dc2c41..328665ab51c 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -1,9 +1,10 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" import logging -from homeassistant.components.freebox import DATA_FREEBOX from homeassistant.helpers.entity import Entity +from . import DATA_FREEBOX + DEPENDENCIES = ['freebox'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/freebox/switch.py b/homeassistant/components/freebox/switch.py new file mode 100644 index 00000000000..4de194fc902 --- /dev/null +++ b/homeassistant/components/freebox/switch.py @@ -0,0 +1,63 @@ +"""Support for Freebox Delta, Revolution and Mini 4K.""" +import logging + +from homeassistant.components.switch import SwitchDevice + +from . import DATA_FREEBOX + +DEPENDENCIES = ['freebox'] + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Set up the switch.""" + fbx = hass.data[DATA_FREEBOX] + async_add_entities([FbxWifiSwitch(fbx)], True) + + +class FbxWifiSwitch(SwitchDevice): + """Representation of a freebox wifi switch.""" + + def __init__(self, fbx): + """Initilize the Wifi switch.""" + self._name = 'Freebox WiFi' + self._state = None + self._fbx = fbx + + @property + def name(self): + """Return the name of the switch.""" + return self._name + + @property + def is_on(self): + """Return true if device is on.""" + return self._state + + async def _async_set_state(self, enabled): + """Turn the switch on or off.""" + from aiofreepybox.exceptions import InsufficientPermissionsError + + wifi_config = {"enabled": enabled} + try: + await self._fbx.wifi.set_global_config(wifi_config) + except InsufficientPermissionsError: + _LOGGER.warning('Home Assistant does not have permissions to' + ' modify the Freebox settings. Please refer' + ' to documentation.') + + async def async_turn_on(self, **kwargs): + """Turn the switch on.""" + await self._async_set_state(True) + + async def async_turn_off(self, **kwargs): + """Turn the switch off.""" + await self._async_set_state(False) + + async def async_update(self): + """Get the state and update it.""" + datas = await self._fbx.wifi.get_global_config() + active = datas['enabled'] + self._state = bool(active) diff --git a/homeassistant/components/fritz/__init__.py b/homeassistant/components/fritz/__init__.py new file mode 100644 index 00000000000..7069a29f163 --- /dev/null +++ b/homeassistant/components/fritz/__init__.py @@ -0,0 +1 @@ +"""The fritz component.""" diff --git a/homeassistant/components/device_tracker/fritz.py b/homeassistant/components/fritz/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/fritz.py rename to homeassistant/components/fritz/device_tracker.py diff --git a/homeassistant/components/fritzbox/binary_sensor.py b/homeassistant/components/fritzbox/binary_sensor.py index c68c79f1e77..65578c57180 100644 --- a/homeassistant/components/fritzbox/binary_sensor.py +++ b/homeassistant/components/fritzbox/binary_sensor.py @@ -4,7 +4,8 @@ import logging import requests from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.fritzbox import DOMAIN as FRITZBOX_DOMAIN + +from . import DOMAIN as FRITZBOX_DOMAIN DEPENDENCIES = ['fritzbox'] diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index e8c20061b4e..e2c9be833ac 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -3,19 +3,19 @@ import logging import requests -from homeassistant.components.fritzbox import DOMAIN as FRITZBOX_DOMAIN -from homeassistant.components.fritzbox import ( - ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_HOLIDAY_MODE, - ATTR_STATE_LOCKED, ATTR_STATE_SUMMER_MODE, - ATTR_STATE_WINDOW_OPEN) from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( ATTR_OPERATION_MODE, STATE_ECO, STATE_HEAT, STATE_MANUAL, - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE) + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) from homeassistant.const import ( - ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS, - STATE_OFF, STATE_ON) + ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES, STATE_OFF, + STATE_ON, TEMP_CELSIUS) + +from . import ( + ATTR_STATE_BATTERY_LOW, ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_HOLIDAY_MODE, + ATTR_STATE_LOCKED, ATTR_STATE_SUMMER_MODE, ATTR_STATE_WINDOW_OPEN, + DOMAIN as FRITZBOX_DOMAIN) + DEPENDENCIES = ['fritzbox'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index a1736fb9857..7309f8cc618 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -3,11 +3,11 @@ import logging import requests -from homeassistant.components.fritzbox import DOMAIN as FRITZBOX_DOMAIN -from homeassistant.components.fritzbox import ( - ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED) -from homeassistant.helpers.entity import Entity from homeassistant.const import TEMP_CELSIUS +from homeassistant.helpers.entity import Entity + +from . import ( + ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED, DOMAIN as FRITZBOX_DOMAIN) DEPENDENCIES = ['fritzbox'] diff --git a/homeassistant/components/fritzbox/switch.py b/homeassistant/components/fritzbox/switch.py index 617c4902068..e227cdaef8a 100644 --- a/homeassistant/components/fritzbox/switch.py +++ b/homeassistant/components/fritzbox/switch.py @@ -3,12 +3,12 @@ import logging import requests -from homeassistant.components.fritzbox import DOMAIN as FRITZBOX_DOMAIN -from homeassistant.components.fritzbox import ( - ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED) from homeassistant.components.switch import SwitchDevice -from homeassistant.const import (ATTR_TEMPERATURE, TEMP_CELSIUS, - ENERGY_KILO_WATT_HOUR) +from homeassistant.const import ( + ATTR_TEMPERATURE, ENERGY_KILO_WATT_HOUR, TEMP_CELSIUS) + +from . import ( + ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED, DOMAIN as FRITZBOX_DOMAIN) DEPENDENCIES = ['fritzbox'] diff --git a/homeassistant/components/fritzbox_callmonitor/__init__.py b/homeassistant/components/fritzbox_callmonitor/__init__.py new file mode 100644 index 00000000000..f9a52021606 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/__init__.py @@ -0,0 +1 @@ +"""The fritzbox_callmonitor component.""" diff --git a/homeassistant/components/sensor/fritzbox_callmonitor.py b/homeassistant/components/fritzbox_callmonitor/sensor.py similarity index 100% rename from homeassistant/components/sensor/fritzbox_callmonitor.py rename to homeassistant/components/fritzbox_callmonitor/sensor.py diff --git a/homeassistant/components/fritzbox_netmonitor/__init__.py b/homeassistant/components/fritzbox_netmonitor/__init__.py new file mode 100644 index 00000000000..8bea1da4a44 --- /dev/null +++ b/homeassistant/components/fritzbox_netmonitor/__init__.py @@ -0,0 +1 @@ +"""The fritzbox_netmonitor component.""" diff --git a/homeassistant/components/sensor/fritzbox_netmonitor.py b/homeassistant/components/fritzbox_netmonitor/sensor.py similarity index 100% rename from homeassistant/components/sensor/fritzbox_netmonitor.py rename to homeassistant/components/fritzbox_netmonitor/sensor.py diff --git a/homeassistant/components/fritzdect/__init__.py b/homeassistant/components/fritzdect/__init__.py new file mode 100644 index 00000000000..d64990bc3f0 --- /dev/null +++ b/homeassistant/components/fritzdect/__init__.py @@ -0,0 +1 @@ +"""The fritzdect component.""" diff --git a/homeassistant/components/switch/fritzdect.py b/homeassistant/components/fritzdect/switch.py similarity index 100% rename from homeassistant/components/switch/fritzdect.py rename to homeassistant/components/fritzdect/switch.py diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 30b9d350df6..f0358dbd6cc 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ from homeassistant.loader import bind_hass from .storage import async_setup_frontend_storage -REQUIREMENTS = ['home-assistant-frontend==20190321.0'] +REQUIREMENTS = ['home-assistant-frontend==20190331.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', @@ -123,14 +123,18 @@ class Panel: # Config to pass to the webcomponent config = None + # If the panel should only be visible to admins + require_admin = False + def __init__(self, component_name, sidebar_title, sidebar_icon, - frontend_url_path, config): + frontend_url_path, config, require_admin): """Initialize a built-in panel.""" self.component_name = component_name self.sidebar_title = sidebar_title self.sidebar_icon = sidebar_icon self.frontend_url_path = frontend_url_path or component_name self.config = config + self.require_admin = require_admin @callback def async_register_index_routes(self, router, index_view): @@ -150,16 +154,18 @@ class Panel: 'title': self.sidebar_title, 'config': self.config, 'url_path': self.frontend_url_path, + 'require_admin': self.require_admin, } @bind_hass async def async_register_built_in_panel(hass, component_name, sidebar_title=None, sidebar_icon=None, - frontend_url_path=None, config=None): + frontend_url_path=None, config=None, + require_admin=False): """Register a built-in panel.""" panel = Panel(component_name, sidebar_title, sidebar_icon, - frontend_url_path, config) + frontend_url_path, config, require_admin) panels = hass.data.get(DATA_PANELS) if panels is None: @@ -247,9 +253,11 @@ async def async_setup(hass, config): await asyncio.wait( [async_register_built_in_panel(hass, panel) for panel in ( - 'dev-event', 'dev-info', 'dev-service', 'dev-state', - 'dev-template', 'dev-mqtt', 'kiosk', 'states', 'profile')], - loop=hass.loop) + 'kiosk', 'states', 'profile')], loop=hass.loop) + await asyncio.wait( + [async_register_built_in_panel(hass, panel, require_admin=True) + for panel in ('dev-event', 'dev-info', 'dev-service', 'dev-state', + 'dev-template', 'dev-mqtt')], loop=hass.loop) hass.data[DATA_FINALIZE_PANEL] = async_finalize_panel @@ -478,9 +486,11 @@ def websocket_get_panels(hass, connection, msg): Async friendly. """ + user_is_admin = connection.user.is_admin panels = { - panel: connection.hass.data[DATA_PANELS][panel].to_response() - for panel in connection.hass.data[DATA_PANELS]} + panel_key: panel.to_response() + for panel_key, panel in connection.hass.data[DATA_PANELS].items() + if user_is_admin or not panel.require_admin} connection.send_message(websocket_api.result_message( msg['id'], panels)) diff --git a/homeassistant/components/frontier_silicon/__init__.py b/homeassistant/components/frontier_silicon/__init__.py new file mode 100644 index 00000000000..ddd74ca8efe --- /dev/null +++ b/homeassistant/components/frontier_silicon/__init__.py @@ -0,0 +1 @@ +"""The frontier_silicon component.""" diff --git a/homeassistant/components/media_player/frontier_silicon.py b/homeassistant/components/frontier_silicon/media_player.py similarity index 100% rename from homeassistant/components/media_player/frontier_silicon.py rename to homeassistant/components/frontier_silicon/media_player.py diff --git a/homeassistant/components/futurenow/__init__.py b/homeassistant/components/futurenow/__init__.py new file mode 100644 index 00000000000..530911fecf9 --- /dev/null +++ b/homeassistant/components/futurenow/__init__.py @@ -0,0 +1 @@ +"""The futurenow component.""" diff --git a/homeassistant/components/light/futurenow.py b/homeassistant/components/futurenow/light.py similarity index 100% rename from homeassistant/components/light/futurenow.py rename to homeassistant/components/futurenow/light.py diff --git a/homeassistant/components/garadget/__init__.py b/homeassistant/components/garadget/__init__.py new file mode 100644 index 00000000000..3d3977e9596 --- /dev/null +++ b/homeassistant/components/garadget/__init__.py @@ -0,0 +1 @@ +"""The garadget component.""" diff --git a/homeassistant/components/cover/garadget.py b/homeassistant/components/garadget/cover.py similarity index 100% rename from homeassistant/components/cover/garadget.py rename to homeassistant/components/garadget/cover.py diff --git a/homeassistant/components/gc100/binary_sensor.py b/homeassistant/components/gc100/binary_sensor.py index 27466f64cfb..ec69d1eec83 100644 --- a/homeassistant/components/gc100/binary_sensor.py +++ b/homeassistant/components/gc100/binary_sensor.py @@ -6,12 +6,13 @@ https://home-assistant.io/components/binary_sensor.gc100/ """ import voluptuous as vol -from homeassistant.components.gc100 import DATA_GC100, CONF_PORTS from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) + PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv +from . import CONF_PORTS, DATA_GC100 + DEPENDENCIES = ['gc100'] _SENSORS_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/gc100/switch.py b/homeassistant/components/gc100/switch.py index 2a8e7eaa847..94c824fa5d7 100644 --- a/homeassistant/components/gc100/switch.py +++ b/homeassistant/components/gc100/switch.py @@ -6,11 +6,12 @@ https://home-assistant.io/components/switch.gc100/ """ import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.gc100 import DATA_GC100, CONF_PORTS -from homeassistant.components.switch import (PLATFORM_SCHEMA) -from homeassistant.helpers.entity import ToggleEntity +from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.const import DEVICE_DEFAULT_NAME +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import ToggleEntity + +from . import CONF_PORTS, DATA_GC100 DEPENDENCIES = ['gc100'] diff --git a/homeassistant/components/gearbest/__init__.py b/homeassistant/components/gearbest/__init__.py new file mode 100644 index 00000000000..c97d9469296 --- /dev/null +++ b/homeassistant/components/gearbest/__init__.py @@ -0,0 +1 @@ +"""The gearbest component.""" diff --git a/homeassistant/components/sensor/gearbest.py b/homeassistant/components/gearbest/sensor.py similarity index 100% rename from homeassistant/components/sensor/gearbest.py rename to homeassistant/components/gearbest/sensor.py diff --git a/homeassistant/components/geizhals/__init__.py b/homeassistant/components/geizhals/__init__.py new file mode 100644 index 00000000000..28b1d623073 --- /dev/null +++ b/homeassistant/components/geizhals/__init__.py @@ -0,0 +1 @@ +"""The geizhals component.""" diff --git a/homeassistant/components/sensor/geizhals.py b/homeassistant/components/geizhals/sensor.py similarity index 100% rename from homeassistant/components/sensor/geizhals.py rename to homeassistant/components/geizhals/sensor.py diff --git a/homeassistant/components/generic/__init__.py b/homeassistant/components/generic/__init__.py new file mode 100644 index 00000000000..28f79fc91e6 --- /dev/null +++ b/homeassistant/components/generic/__init__.py @@ -0,0 +1 @@ +"""The generic component.""" diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/generic/camera.py similarity index 95% rename from homeassistant/components/camera/generic.py rename to homeassistant/components/generic/camera.py index c8d6721ac18..c9f8616f637 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/generic/camera.py @@ -18,7 +18,7 @@ from homeassistant.const import ( HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, CONF_VERIFY_SSL) from homeassistant.exceptions import TemplateError from homeassistant.components.camera import ( - PLATFORM_SCHEMA, DEFAULT_CONTENT_TYPE, Camera) + PLATFORM_SCHEMA, DEFAULT_CONTENT_TYPE, SUPPORT_STREAM, Camera) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import config_validation as cv from homeassistant.util.async_ import run_coroutine_threadsafe @@ -68,6 +68,7 @@ class GenericCamera(Camera): self._still_image_url.hass = hass self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE] self._frame_interval = 1 / device_info[CONF_FRAMERATE] + self._supported_features = SUPPORT_STREAM if self._stream_source else 0 self.content_type = device_info[CONF_CONTENT_TYPE] self.verify_ssl = device_info[CONF_VERIFY_SSL] @@ -85,6 +86,11 @@ class GenericCamera(Camera): self._last_url = None self._last_image = None + @property + def supported_features(self): + """Return supported features for this camera.""" + return self._supported_features + @property def frame_interval(self): """Return the interval between frames of the mjpeg stream.""" diff --git a/homeassistant/components/generic_thermostat/__init__.py b/homeassistant/components/generic_thermostat/__init__.py new file mode 100644 index 00000000000..d0bc392e4f4 --- /dev/null +++ b/homeassistant/components/generic_thermostat/__init__.py @@ -0,0 +1 @@ +"""The generic_thermostat component.""" diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/generic_thermostat/climate.py similarity index 96% rename from homeassistant/components/climate/generic_thermostat.py rename to homeassistant/components/generic_thermostat/climate.py index da4f79ec1e6..1eb0f8e79db 100644 --- a/homeassistant/components/climate/generic_thermostat.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -9,23 +9,23 @@ import logging import voluptuous as vol -from homeassistant.core import callback -from homeassistant.core import DOMAIN as HA_DOMAIN -from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA -from homeassistant.components.climate.const import ( - STATE_HEAT, STATE_COOL, STATE_IDLE, STATE_AUTO, - ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE, - SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE) from homeassistant.const import ( - STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, ATTR_ENTITY_ID, - SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_UNKNOWN, PRECISION_HALVES, - PRECISION_TENTHS, PRECISION_WHOLE) + ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES, + PRECISION_TENTHS, PRECISION_WHOLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, STATE_ON, STATE_UNKNOWN) +from homeassistant.core import DOMAIN as HA_DOMAIN, callback from homeassistant.helpers import condition +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import ( async_track_state_change, async_track_time_interval) -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate.const import ( + ATTR_AWAY_MODE, ATTR_OPERATION_MODE, STATE_AUTO, STATE_COOL, STATE_HEAT, + STATE_IDLE, SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['switch', 'sensor'] diff --git a/homeassistant/components/geo_json_events/__init__.py b/homeassistant/components/geo_json_events/__init__.py new file mode 100644 index 00000000000..0bc612b6e8b --- /dev/null +++ b/homeassistant/components/geo_json_events/__init__.py @@ -0,0 +1 @@ +"""The geo_json_events component.""" diff --git a/homeassistant/components/geo_location/geo_json_events.py b/homeassistant/components/geo_json_events/geo_location.py similarity index 100% rename from homeassistant/components/geo_location/geo_json_events.py rename to homeassistant/components/geo_json_events/geo_location.py diff --git a/homeassistant/components/geo_rss_events/__init__.py b/homeassistant/components/geo_rss_events/__init__.py new file mode 100644 index 00000000000..3a1a9072960 --- /dev/null +++ b/homeassistant/components/geo_rss_events/__init__.py @@ -0,0 +1 @@ +"""The geo_rss_events component.""" diff --git a/homeassistant/components/sensor/geo_rss_events.py b/homeassistant/components/geo_rss_events/sensor.py similarity index 100% rename from homeassistant/components/sensor/geo_rss_events.py rename to homeassistant/components/geo_rss_events/sensor.py diff --git a/homeassistant/components/geofency/.translations/bg.json b/homeassistant/components/geofency/.translations/bg.json new file mode 100644 index 00000000000..6f06d5c00c6 --- /dev/null +++ b/homeassistant/components/geofency/.translations/bg.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/fr.json b/homeassistant/components/geofency/.translations/fr.json index 142f40754b9..b390f2dab44 100644 --- a/homeassistant/components/geofency/.translations/fr.json +++ b/homeassistant/components/geofency/.translations/fr.json @@ -12,6 +12,7 @@ "description": "\u00cates-vous s\u00fbr de vouloir configurer le Webhook Geofency ?", "title": "Configurer le Webhook Geofency" } - } + }, + "title": "Geofency Webhook" } } \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/pl.json b/homeassistant/components/geofency/.translations/pl.json index 09d93e6911e..b2b8b606723 100644 --- a/homeassistant/components/geofency/.translations/pl.json +++ b/homeassistant/components/geofency/.translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 webhook w Geofency. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 webhook w Geofency. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/geofency/device_tracker.py b/homeassistant/components/geofency/device_tracker.py index 51201240c1c..5e7b8291840 100644 --- a/homeassistant/components/geofency/device_tracker.py +++ b/homeassistant/components/geofency/device_tracker.py @@ -8,10 +8,10 @@ import logging from homeassistant.components.device_tracker import ( DOMAIN as DEVICE_TRACKER_DOMAIN) -from homeassistant.components.geofency import ( - DOMAIN as GEOFENCY_DOMAIN, TRACKER_UPDATE) from homeassistant.helpers.dispatcher import async_dispatcher_connect +from . import DOMAIN as GEOFENCY_DOMAIN, TRACKER_UPDATE + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['geofency'] diff --git a/homeassistant/components/github/__init__.py b/homeassistant/components/github/__init__.py new file mode 100644 index 00000000000..6dd5d7f16ff --- /dev/null +++ b/homeassistant/components/github/__init__.py @@ -0,0 +1 @@ +"""The github component.""" diff --git a/homeassistant/components/sensor/github.py b/homeassistant/components/github/sensor.py similarity index 100% rename from homeassistant/components/sensor/github.py rename to homeassistant/components/github/sensor.py diff --git a/homeassistant/components/gitlab_ci/__init__.py b/homeassistant/components/gitlab_ci/__init__.py new file mode 100644 index 00000000000..93b2a08c714 --- /dev/null +++ b/homeassistant/components/gitlab_ci/__init__.py @@ -0,0 +1 @@ +"""The gitlab_ci component.""" diff --git a/homeassistant/components/sensor/gitlab_ci.py b/homeassistant/components/gitlab_ci/sensor.py similarity index 100% rename from homeassistant/components/sensor/gitlab_ci.py rename to homeassistant/components/gitlab_ci/sensor.py diff --git a/homeassistant/components/gitter/__init__.py b/homeassistant/components/gitter/__init__.py new file mode 100644 index 00000000000..25656f70f56 --- /dev/null +++ b/homeassistant/components/gitter/__init__.py @@ -0,0 +1 @@ +"""The gitter component.""" diff --git a/homeassistant/components/sensor/gitter.py b/homeassistant/components/gitter/sensor.py similarity index 100% rename from homeassistant/components/sensor/gitter.py rename to homeassistant/components/gitter/sensor.py diff --git a/homeassistant/components/glances/__init__.py b/homeassistant/components/glances/__init__.py new file mode 100644 index 00000000000..b458d8788fc --- /dev/null +++ b/homeassistant/components/glances/__init__.py @@ -0,0 +1 @@ +"""The glances component.""" diff --git a/homeassistant/components/sensor/glances.py b/homeassistant/components/glances/sensor.py similarity index 100% rename from homeassistant/components/sensor/glances.py rename to homeassistant/components/glances/sensor.py diff --git a/homeassistant/components/gntp/__init__.py b/homeassistant/components/gntp/__init__.py new file mode 100644 index 00000000000..c2814f86f06 --- /dev/null +++ b/homeassistant/components/gntp/__init__.py @@ -0,0 +1 @@ +"""The gntp component.""" diff --git a/homeassistant/components/notify/gntp.py b/homeassistant/components/gntp/notify.py similarity index 99% rename from homeassistant/components/notify/gntp.py rename to homeassistant/components/gntp/notify.py index 1a2b65f958f..915d33668b4 100644 --- a/homeassistant/components/notify/gntp.py +++ b/homeassistant/components/gntp/notify.py @@ -9,11 +9,12 @@ import os import voluptuous as vol -from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_PASSWORD, CONF_PORT import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import ( + ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) + REQUIREMENTS = ['gntp==1.0.3'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/gogogate2/__init__.py b/homeassistant/components/gogogate2/__init__.py new file mode 100644 index 00000000000..ef802a4aa59 --- /dev/null +++ b/homeassistant/components/gogogate2/__init__.py @@ -0,0 +1 @@ +"""The gogogate2 component.""" diff --git a/homeassistant/components/cover/gogogate2.py b/homeassistant/components/gogogate2/cover.py similarity index 100% rename from homeassistant/components/cover/gogogate2.py rename to homeassistant/components/gogogate2/cover.py diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index cc65c6d655d..9f71e7c4f20 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -1,14 +1,14 @@ """Support for Google Calendar Search binary sensors.""" -import logging from datetime import timedelta +import logging from homeassistant.components.calendar import CalendarEventDevice -from homeassistant.components.google import ( - CONF_CAL_ID, CONF_ENTITIES, CONF_TRACK, TOKEN_FILE, - CONF_IGNORE_AVAILABILITY, CONF_SEARCH, - GoogleCalendarService) from homeassistant.util import Throttle, dt +from . import ( + CONF_CAL_ID, CONF_ENTITIES, CONF_IGNORE_AVAILABILITY, CONF_SEARCH, + CONF_TRACK, TOKEN_FILE, GoogleCalendarService) + _LOGGER = logging.getLogger(__name__) DEFAULT_GOOGLE_SEARCH_PARAMS = { diff --git a/homeassistant/components/google/services.yaml b/homeassistant/components/google/services.yaml new file mode 100644 index 00000000000..34eecb33fd5 --- /dev/null +++ b/homeassistant/components/google/services.yaml @@ -0,0 +1,4 @@ +found_calendar: + description: Add calendar if it has not been already discovered. +scan_for_calendars: + description: Scan for new calendars. diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index 220ed6dd58c..852ea2469a2 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -21,6 +21,7 @@ DEFAULT_EXPOSED_DOMAINS = [ DEFAULT_ALLOW_UNLOCK = False PREFIX_TYPES = 'action.devices.types.' +TYPE_CAMERA = PREFIX_TYPES + 'CAMERA' TYPE_LIGHT = PREFIX_TYPES + 'LIGHT' TYPE_SWITCH = PREFIX_TYPES + 'SWITCH' TYPE_VACUUM = PREFIX_TYPES + 'VACUUM' @@ -28,6 +29,7 @@ TYPE_SCENE = PREFIX_TYPES + 'SCENE' TYPE_FAN = PREFIX_TYPES + 'FAN' TYPE_THERMOSTAT = PREFIX_TYPES + 'THERMOSTAT' TYPE_LOCK = PREFIX_TYPES + 'LOCK' +TYPE_BLINDS = PREFIX_TYPES + 'BLINDS' SERVICE_REQUEST_SYNC = 'request_sync' HOMEGRAPH_URL = 'https://homegraph.googleapis.com/' diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index fa272c25012..d84c8037c60 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -12,6 +12,7 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, ATTR_ENTITY_ID, ) from homeassistant.components import ( + camera, climate, cover, fan, @@ -30,7 +31,7 @@ from homeassistant.components import ( from . import trait from .const import ( TYPE_LIGHT, TYPE_LOCK, TYPE_SCENE, TYPE_SWITCH, TYPE_VACUUM, - TYPE_THERMOSTAT, TYPE_FAN, + TYPE_THERMOSTAT, TYPE_FAN, TYPE_CAMERA, TYPE_BLINDS, CONF_ALIASES, CONF_ROOM_HINT, ERR_FUNCTION_NOT_SUPPORTED, ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE, ERR_UNKNOWN_ERROR, @@ -42,8 +43,9 @@ HANDLERS = Registry() _LOGGER = logging.getLogger(__name__) DOMAIN_TO_GOOGLE_TYPES = { + camera.DOMAIN: TYPE_CAMERA, climate.DOMAIN: TYPE_THERMOSTAT, - cover.DOMAIN: TYPE_SWITCH, + cover.DOMAIN: TYPE_BLINDS, fan.DOMAIN: TYPE_FAN, group.DOMAIN: TYPE_SWITCH, input_boolean.DOMAIN: TYPE_SWITCH, @@ -74,6 +76,7 @@ class _GoogleEntity: self.hass = hass self.config = config self.state = state + self._traits = None @property def entity_id(self): @@ -83,13 +86,17 @@ class _GoogleEntity: @callback def traits(self): """Return traits for entity.""" + if self._traits is not None: + return self._traits + state = self.state domain = state.domain features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - return [Trait(self.hass, state, self.config) - for Trait in trait.TRAITS - if Trait.supported(domain, features)] + self._traits = [Trait(self.hass, state, self.config) + for Trait in trait.TRAITS + if Trait.supported(domain, features)] + return self._traits async def sync_serialize(self): """Serialize entity for a SYNC response. @@ -202,6 +209,12 @@ class _GoogleEntity: """Update the entity with latest info from Home Assistant.""" self.state = self.hass.states.get(self.entity_id) + if self._traits is None: + return + + for trt in self._traits: + trt.state = self.state + async def async_handle_message(hass, config, user_id, message): """Handle incoming API messages.""" diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index aff24f30512..de3a9530b50 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -2,6 +2,7 @@ import logging from homeassistant.components import ( + camera, cover, group, fan, @@ -21,10 +22,12 @@ from homeassistant.const import ( SERVICE_TURN_ON, STATE_LOCKED, STATE_OFF, + STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, + ATTR_ASSUMED_STATE, ) from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.util import color as color_util, temperature as temp_util @@ -34,6 +37,7 @@ from .helpers import SmartHomeError _LOGGER = logging.getLogger(__name__) PREFIX_TRAITS = 'action.devices.traits.' +TRAIT_CAMERA_STREAM = PREFIX_TRAITS + 'CameraStream' TRAIT_ONOFF = PREFIX_TRAITS + 'OnOff' TRAIT_DOCK = PREFIX_TRAITS + 'Dock' TRAIT_STARTSTOP = PREFIX_TRAITS + 'StartStop' @@ -45,9 +49,11 @@ TRAIT_TEMPERATURE_SETTING = PREFIX_TRAITS + 'TemperatureSetting' TRAIT_LOCKUNLOCK = PREFIX_TRAITS + 'LockUnlock' TRAIT_FANSPEED = PREFIX_TRAITS + 'FanSpeed' TRAIT_MODES = PREFIX_TRAITS + 'Modes' +TRAIT_OPENCLOSE = PREFIX_TRAITS + 'OpenClose' PREFIX_COMMANDS = 'action.devices.commands.' COMMAND_ONOFF = PREFIX_COMMANDS + 'OnOff' +COMMAND_GET_CAMERA_STREAM = PREFIX_COMMANDS + 'GetCameraStream' COMMAND_DOCK = PREFIX_COMMANDS + 'Dock' COMMAND_STARTSTOP = PREFIX_COMMANDS + 'StartStop' COMMAND_PAUSEUNPAUSE = PREFIX_COMMANDS + 'PauseUnpause' @@ -62,6 +68,7 @@ COMMAND_THERMOSTAT_SET_MODE = PREFIX_COMMANDS + 'ThermostatSetMode' COMMAND_LOCKUNLOCK = PREFIX_COMMANDS + 'LockUnlock' COMMAND_FANSPEED = PREFIX_COMMANDS + 'SetFanSpeed' COMMAND_MODES = PREFIX_COMMANDS + 'SetModes' +COMMAND_OPENCLOSE = PREFIX_COMMANDS + 'OpenClose' TRAITS = [] @@ -124,8 +131,6 @@ class BrightnessTrait(_Trait): """Test if state is supported.""" if domain == light.DOMAIN: return features & light.SUPPORT_BRIGHTNESS - if domain == cover.DOMAIN: - return features & cover.SUPPORT_SET_POSITION if domain == media_player.DOMAIN: return features & media_player.SUPPORT_VOLUME_SET @@ -145,11 +150,6 @@ class BrightnessTrait(_Trait): if brightness is not None: response['brightness'] = int(100 * (brightness / 255)) - elif domain == cover.DOMAIN: - position = self.state.attributes.get(cover.ATTR_CURRENT_POSITION) - if position is not None: - response['brightness'] = position - elif domain == media_player.DOMAIN: level = self.state.attributes.get( media_player.ATTR_MEDIA_VOLUME_LEVEL) @@ -169,12 +169,6 @@ class BrightnessTrait(_Trait): ATTR_ENTITY_ID: self.state.entity_id, light.ATTR_BRIGHTNESS_PCT: params['brightness'] }, blocking=True, context=data.context) - elif domain == cover.DOMAIN: - await self.hass.services.async_call( - cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION, { - ATTR_ENTITY_ID: self.state.entity_id, - cover.ATTR_POSITION: params['brightness'] - }, blocking=True, context=data.context) elif domain == media_player.DOMAIN: await self.hass.services.async_call( media_player.DOMAIN, media_player.SERVICE_VOLUME_SET, { @@ -184,6 +178,51 @@ class BrightnessTrait(_Trait): }, blocking=True, context=data.context) +@register_trait +class CameraStreamTrait(_Trait): + """Trait to stream from cameras. + + https://developers.google.com/actions/smarthome/traits/camerastream + """ + + name = TRAIT_CAMERA_STREAM + commands = [ + COMMAND_GET_CAMERA_STREAM + ] + + stream_info = None + + @staticmethod + def supported(domain, features): + """Test if state is supported.""" + if domain == camera.DOMAIN: + return features & camera.SUPPORT_STREAM + + return False + + def sync_attributes(self): + """Return stream attributes for a sync request.""" + return { + 'cameraStreamSupportedProtocols': [ + "hls", + ], + 'cameraStreamNeedAuthToken': False, + 'cameraStreamNeedDrmEncryption': False, + } + + def query_attributes(self): + """Return camera stream attributes.""" + return self.stream_info or {} + + async def execute(self, command, data, params): + """Execute a get camera stream command.""" + url = await self.hass.components.camera.async_request_stream( + self.state.entity_id, 'hls') + self.stream_info = { + 'cameraStreamAccessUrl': self.hass.config.api.base_url + url + } + + @register_trait class OnOffTrait(_Trait): """Trait to offer basic on and off functionality. @@ -199,15 +238,12 @@ class OnOffTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - if domain == climate.DOMAIN: - return features & climate.SUPPORT_ON_OFF != 0 return domain in ( group.DOMAIN, input_boolean.DOMAIN, switch.DOMAIN, fan.DOMAIN, light.DOMAIN, - cover.DOMAIN, media_player.DOMAIN, ) @@ -217,22 +253,13 @@ class OnOffTrait(_Trait): def query_attributes(self): """Return OnOff query attributes.""" - if self.state.domain == cover.DOMAIN: - return {'on': self.state.state != cover.STATE_CLOSED} return {'on': self.state.state != STATE_OFF} async def execute(self, command, data, params): """Execute an OnOff command.""" domain = self.state.domain - if domain == cover.DOMAIN: - service_domain = domain - if params['on']: - service = cover.SERVICE_OPEN_COVER - else: - service = cover.SERVICE_CLOSE_COVER - - elif domain == group.DOMAIN: + if domain == group.DOMAIN: service_domain = HA_DOMAIN service = SERVICE_TURN_ON if params['on'] else SERVICE_TURN_OFF @@ -537,10 +564,18 @@ class TemperatureSettingTrait(_Trait): def sync_attributes(self): """Return temperature point and modes attributes for a sync request.""" modes = [] - for mode in self.state.attributes.get(climate.ATTR_OPERATION_LIST, []): - google_mode = self.hass_to_google.get(mode) - if google_mode is not None: - modes.append(google_mode) + supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES) + + if supported & climate.SUPPORT_ON_OFF != 0: + modes.append(STATE_OFF) + modes.append(STATE_ON) + + if supported & climate.SUPPORT_OPERATION_MODE != 0: + for mode in self.state.attributes.get(climate.ATTR_OPERATION_LIST, + []): + google_mode = self.hass_to_google.get(mode) + if google_mode and google_mode not in modes: + modes.append(google_mode) return { 'availableThermostatModes': ','.join(modes), @@ -554,8 +589,16 @@ class TemperatureSettingTrait(_Trait): response = {} operation = attrs.get(climate.ATTR_OPERATION_MODE) - if operation is not None and operation in self.hass_to_google: + supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES) + + if (supported & climate.SUPPORT_ON_OFF + and self.state.state == STATE_OFF): + response['thermostatMode'] = 'off' + elif (supported & climate.SUPPORT_OPERATION_MODE and + operation in self.hass_to_google): response['thermostatMode'] = self.hass_to_google[operation] + elif supported & climate.SUPPORT_ON_OFF: + response['thermostatMode'] = 'on' unit = self.hass.config.units.temperature_unit @@ -568,15 +611,24 @@ class TemperatureSettingTrait(_Trait): if current_humidity is not None: response['thermostatHumidityAmbient'] = current_humidity - if (operation == climate.STATE_AUTO and - climate.ATTR_TARGET_TEMP_HIGH in attrs and - climate.ATTR_TARGET_TEMP_LOW in attrs): - response['thermostatTemperatureSetpointHigh'] = \ - round(temp_util.convert(attrs[climate.ATTR_TARGET_TEMP_HIGH], - unit, TEMP_CELSIUS), 1) - response['thermostatTemperatureSetpointLow'] = \ - round(temp_util.convert(attrs[climate.ATTR_TARGET_TEMP_LOW], - unit, TEMP_CELSIUS), 1) + if operation == climate.STATE_AUTO: + if (supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH and + supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW): + response['thermostatTemperatureSetpointHigh'] = \ + round(temp_util.convert( + attrs[climate.ATTR_TARGET_TEMP_HIGH], + unit, TEMP_CELSIUS), 1) + response['thermostatTemperatureSetpointLow'] = \ + round(temp_util.convert( + attrs[climate.ATTR_TARGET_TEMP_LOW], + unit, TEMP_CELSIUS), 1) + else: + target_temp = attrs.get(ATTR_TEMPERATURE) + if target_temp is not None: + target_temp = round( + temp_util.convert(target_temp, unit, TEMP_CELSIUS), 1) + response['thermostatTemperatureSetpointHigh'] = target_temp + response['thermostatTemperatureSetpointLow'] = target_temp else: target_temp = attrs.get(ATTR_TEMPERATURE) if target_temp is not None: @@ -636,20 +688,42 @@ class TemperatureSettingTrait(_Trait): "Lower bound for temperature range should be between " "{} and {}".format(min_temp, max_temp)) + supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES) + svc_data = { + ATTR_ENTITY_ID: self.state.entity_id, + } + + if(supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH and + supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW): + svc_data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high + svc_data[climate.ATTR_TARGET_TEMP_LOW] = temp_low + else: + svc_data[ATTR_TEMPERATURE] = (temp_high + temp_low) / 2 + await self.hass.services.async_call( - climate.DOMAIN, climate.SERVICE_SET_TEMPERATURE, { - ATTR_ENTITY_ID: self.state.entity_id, - climate.ATTR_TARGET_TEMP_HIGH: temp_high, - climate.ATTR_TARGET_TEMP_LOW: temp_low, - }, blocking=True, context=data.context) + climate.DOMAIN, climate.SERVICE_SET_TEMPERATURE, svc_data, + blocking=True, context=data.context) elif command == COMMAND_THERMOSTAT_SET_MODE: - await self.hass.services.async_call( - climate.DOMAIN, climate.SERVICE_SET_OPERATION_MODE, { - ATTR_ENTITY_ID: self.state.entity_id, - climate.ATTR_OPERATION_MODE: - self.google_to_hass[params['thermostatMode']], - }, blocking=True, context=data.context) + target_mode = params['thermostatMode'] + supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES) + + if (target_mode in [STATE_ON, STATE_OFF] and + supported & climate.SUPPORT_ON_OFF): + await self.hass.services.async_call( + climate.DOMAIN, + (SERVICE_TURN_ON + if target_mode == STATE_ON + else SERVICE_TURN_OFF), + {ATTR_ENTITY_ID: self.state.entity_id}, + blocking=True, context=data.context) + elif supported & climate.SUPPORT_OPERATION_MODE: + await self.hass.services.async_call( + climate.DOMAIN, climate.SERVICE_SET_OPERATION_MODE, { + ATTR_ENTITY_ID: self.state.entity_id, + climate.ATTR_OPERATION_MODE: + self.google_to_hass[target_mode], + }, blocking=True, context=data.context) @register_trait @@ -953,3 +1027,76 @@ class ModesTrait(_Trait): ATTR_ENTITY_ID: self.state.entity_id, media_player.ATTR_INPUT_SOURCE: source }, blocking=True, context=data.context) + + +@register_trait +class OpenCloseTrait(_Trait): + """Trait to open and close a cover. + + https://developers.google.com/actions/smarthome/traits/openclose + """ + + name = TRAIT_OPENCLOSE + commands = [ + COMMAND_OPENCLOSE + ] + + @staticmethod + def supported(domain, features): + """Test if state is supported.""" + return domain == cover.DOMAIN + + def sync_attributes(self): + """Return opening direction.""" + return {} + + def query_attributes(self): + """Return state query attributes.""" + domain = self.state.domain + response = {} + + if domain == cover.DOMAIN: + # When it's an assumed state, we will always report it as 50% + # Google will not issue an open command if the assumed state is + # open, even if that is currently incorrect. + if self.state.attributes.get(ATTR_ASSUMED_STATE): + response['openPercent'] = 50 + else: + position = self.state.attributes.get( + cover.ATTR_CURRENT_POSITION + ) + + if position is not None: + response['openPercent'] = position + elif self.state.state != cover.STATE_CLOSED: + response['openPercent'] = 100 + else: + response['openPercent'] = 0 + + return response + + async def execute(self, command, data, params): + """Execute an Open, close, Set position command.""" + domain = self.state.domain + + if domain == cover.DOMAIN: + position = self.state.attributes.get(cover.ATTR_CURRENT_POSITION) + if position is not None: + await self.hass.services.async_call( + cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION, { + ATTR_ENTITY_ID: self.state.entity_id, + cover.ATTR_POSITION: params['openPercent'] + }, blocking=True, context=data.context) + else: + if self.state.state != cover.STATE_CLOSED: + if params['openPercent'] < 100: + await self.hass.services.async_call( + cover.DOMAIN, cover.SERVICE_CLOSE_COVER, { + ATTR_ENTITY_ID: self.state.entity_id + }, blocking=True, context=data.context) + else: + if params['openPercent'] > 0: + await self.hass.services.async_call( + cover.DOMAIN, cover.SERVICE_OPEN_COVER, { + ATTR_ENTITY_ID: self.state.entity_id + }, blocking=True, context=data.context) diff --git a/homeassistant/components/google_maps/__init__.py b/homeassistant/components/google_maps/__init__.py new file mode 100644 index 00000000000..929df26fa0f --- /dev/null +++ b/homeassistant/components/google_maps/__init__.py @@ -0,0 +1 @@ +"""The google_maps component.""" diff --git a/homeassistant/components/device_tracker/google_maps.py b/homeassistant/components/google_maps/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/google_maps.py rename to homeassistant/components/google_maps/device_tracker.py diff --git a/homeassistant/components/google_travel_time/__init__.py b/homeassistant/components/google_travel_time/__init__.py new file mode 100644 index 00000000000..9d9a7cffe1d --- /dev/null +++ b/homeassistant/components/google_travel_time/__init__.py @@ -0,0 +1 @@ +"""The google_travel_time component.""" diff --git a/homeassistant/components/sensor/google_travel_time.py b/homeassistant/components/google_travel_time/sensor.py similarity index 100% rename from homeassistant/components/sensor/google_travel_time.py rename to homeassistant/components/google_travel_time/sensor.py diff --git a/homeassistant/components/google_wifi/__init__.py b/homeassistant/components/google_wifi/__init__.py new file mode 100644 index 00000000000..a12bd9d4b8c --- /dev/null +++ b/homeassistant/components/google_wifi/__init__.py @@ -0,0 +1 @@ +"""The google_wifi component.""" diff --git a/homeassistant/components/sensor/google_wifi.py b/homeassistant/components/google_wifi/sensor.py similarity index 100% rename from homeassistant/components/sensor/google_wifi.py rename to homeassistant/components/google_wifi/sensor.py diff --git a/homeassistant/components/googlehome/device_tracker.py b/homeassistant/components/googlehome/device_tracker.py index 462f5db3b9b..c024cde0c6c 100644 --- a/homeassistant/components/googlehome/device_tracker.py +++ b/homeassistant/components/googlehome/device_tracker.py @@ -1,13 +1,13 @@ """Support for Google Home Bluetooth tacker.""" -import logging from datetime import timedelta +import logging from homeassistant.components.device_tracker import DeviceScanner -from homeassistant.components.googlehome import ( - CLIENT, DOMAIN as GOOGLEHOME_DOMAIN, NAME) from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import slugify +from . import CLIENT, DOMAIN as GOOGLEHOME_DOMAIN, NAME + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['googlehome'] diff --git a/homeassistant/components/googlehome/sensor.py b/homeassistant/components/googlehome/sensor.py index 7577ee0b017..4f37740da85 100644 --- a/homeassistant/components/googlehome/sensor.py +++ b/homeassistant/components/googlehome/sensor.py @@ -1,13 +1,13 @@ """Support for Google Home alarm sensor.""" -import logging from datetime import timedelta +import logging -from homeassistant.components.googlehome import ( - CLIENT, DOMAIN as GOOGLEHOME_DOMAIN, NAME) from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util +from . import CLIENT, DOMAIN as GOOGLEHOME_DOMAIN, NAME + DEPENDENCIES = ['googlehome'] SCAN_INTERVAL = timedelta(seconds=10) diff --git a/homeassistant/components/gpmdp/__init__.py b/homeassistant/components/gpmdp/__init__.py new file mode 100644 index 00000000000..a8aa82c69c3 --- /dev/null +++ b/homeassistant/components/gpmdp/__init__.py @@ -0,0 +1 @@ +"""The gpmdp component.""" diff --git a/homeassistant/components/media_player/gpmdp.py b/homeassistant/components/gpmdp/media_player.py similarity index 100% rename from homeassistant/components/media_player/gpmdp.py rename to homeassistant/components/gpmdp/media_player.py diff --git a/homeassistant/components/gpsd/__init__.py b/homeassistant/components/gpsd/__init__.py new file mode 100644 index 00000000000..71656d4d13d --- /dev/null +++ b/homeassistant/components/gpsd/__init__.py @@ -0,0 +1 @@ +"""The gpsd component.""" diff --git a/homeassistant/components/sensor/gpsd.py b/homeassistant/components/gpsd/sensor.py similarity index 100% rename from homeassistant/components/sensor/gpsd.py rename to homeassistant/components/gpsd/sensor.py diff --git a/homeassistant/components/gpslogger/.translations/bg.json b/homeassistant/components/gpslogger/.translations/bg.json new file mode 100644 index 00000000000..6f06d5c00c6 --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/bg.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/pl.json b/homeassistant/components/gpslogger/.translations/pl.json index 3d82ac6fa5a..726ec2ad9b2 100644 --- a/homeassistant/components/gpslogger/.translations/pl.json +++ b/homeassistant/components/gpslogger/.translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistant'a, musisz skonfigurowa\u0107 webhook w aplikacji GPSLogger. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistant'a, musisz skonfigurowa\u0107 webhook w aplikacji GPSLogger. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/gpslogger/.translations/zh-Hans.json b/homeassistant/components/gpslogger/.translations/zh-Hans.json index 91d3ac74994..f99efa91c61 100644 --- a/homeassistant/components/gpslogger/.translations/zh-Hans.json +++ b/homeassistant/components/gpslogger/.translations/zh-Hans.json @@ -1,10 +1,18 @@ { "config": { "abort": { + "not_internet_accessible": "\u60a8\u7684 Home Assistant \u5b9e\u4f8b\u9700\u8981\u53ef\u4ece\u4e92\u8054\u7f51\u8bbf\u95ee\u4ee5\u63a5\u6536 GPSLogger \u6d88\u606f\u3002", "one_instance_allowed": "\u53ea\u6709\u4e00\u4e2a\u5b9e\u4f8b\u662f\u5fc5\u9700\u7684\u3002" }, "create_entry": { "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e GPSLogger \u7684 Webhook \u529f\u80fd\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002" - } + }, + "step": { + "user": { + "description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e GPSLogger Webhook \u5417\uff1f", + "title": "\u8bbe\u7f6e GPSLogger Webhook" + } + }, + "title": "GPSLogger Webhook" } } \ No newline at end of file diff --git a/homeassistant/components/gpslogger/__init__.py b/homeassistant/components/gpslogger/__init__.py index 2e956bd7fc5..12da63d8ebb 100644 --- a/homeassistant/components/gpslogger/__init__.py +++ b/homeassistant/components/gpslogger/__init__.py @@ -6,7 +6,6 @@ from aiohttp import web import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ATTR_BATTERY -from homeassistant.components.device_tracker.tile import ATTR_ALTITUDE from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, \ HTTP_OK, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_WEBHOOK_ID from homeassistant.helpers import config_entry_flow @@ -20,6 +19,7 @@ DEPENDENCIES = ['webhook'] TRACKER_UPDATE = '{}_tracker_update'.format(DOMAIN) +ATTR_ALTITUDE = 'altitude' ATTR_ACCURACY = 'accuracy' ATTR_ACTIVITY = 'activity' ATTR_DEVICE = 'device' diff --git a/homeassistant/components/gpslogger/device_tracker.py b/homeassistant/components/gpslogger/device_tracker.py index 90d2dc04f89..c9496975272 100644 --- a/homeassistant/components/gpslogger/device_tracker.py +++ b/homeassistant/components/gpslogger/device_tracker.py @@ -1,13 +1,13 @@ """Support for the GPSLogger device tracking.""" import logging -from homeassistant.components.device_tracker import DOMAIN as \ - DEVICE_TRACKER_DOMAIN -from homeassistant.components.gpslogger import DOMAIN as GPSLOGGER_DOMAIN, \ - TRACKER_UPDATE +from homeassistant.components.device_tracker import ( + DOMAIN as DEVICE_TRACKER_DOMAIN) from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType +from . import DOMAIN as GPSLOGGER_DOMAIN, TRACKER_UPDATE + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['gpslogger'] diff --git a/homeassistant/components/sensor/greeneye_monitor.py b/homeassistant/components/greeneye_monitor/sensor.py similarity index 100% rename from homeassistant/components/sensor/greeneye_monitor.py rename to homeassistant/components/greeneye_monitor/sensor.py diff --git a/homeassistant/components/greenwave/__init__.py b/homeassistant/components/greenwave/__init__.py new file mode 100644 index 00000000000..a7bd0cf437e --- /dev/null +++ b/homeassistant/components/greenwave/__init__.py @@ -0,0 +1 @@ +"""The greenwave component.""" diff --git a/homeassistant/components/light/greenwave.py b/homeassistant/components/greenwave/light.py similarity index 100% rename from homeassistant/components/light/greenwave.py rename to homeassistant/components/greenwave/light.py diff --git a/homeassistant/components/cover/group.py b/homeassistant/components/group/cover.py similarity index 94% rename from homeassistant/components/cover/group.py rename to homeassistant/components/group/cover.py index 0424c900747..c1825211433 100644 --- a/homeassistant/components/cover/group.py +++ b/homeassistant/components/group/cover.py @@ -8,22 +8,22 @@ import logging import voluptuous as vol -from homeassistant.core import callback -from homeassistant.components.cover import ( - DOMAIN, PLATFORM_SCHEMA, CoverDevice, ATTR_POSITION, - ATTR_CURRENT_POSITION, ATTR_TILT_POSITION, ATTR_CURRENT_TILT_POSITION, - SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, SUPPORT_SET_POSITION, - SUPPORT_OPEN_TILT, SUPPORT_CLOSE_TILT, - SUPPORT_STOP_TILT, SUPPORT_SET_TILT_POSITION, - SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION, - SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT, - SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION) from homeassistant.const import ( - ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, - CONF_ENTITIES, CONF_NAME, STATE_CLOSED) + ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_ENTITIES, + CONF_NAME, STATE_CLOSED) +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change +from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, ATTR_POSITION, + ATTR_TILT_POSITION, DOMAIN, PLATFORM_SCHEMA, SERVICE_CLOSE_COVER, + SERVICE_CLOSE_COVER_TILT, SERVICE_OPEN_COVER, SERVICE_OPEN_COVER_TILT, + SERVICE_SET_COVER_POSITION, SERVICE_SET_COVER_TILT_POSITION, + SERVICE_STOP_COVER, SERVICE_STOP_COVER_TILT, SUPPORT_CLOSE, + SUPPORT_CLOSE_TILT, SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, + SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, SUPPORT_STOP_TILT, CoverDevice) + _LOGGER = logging.getLogger(__name__) KEY_OPEN_CLOSE = 'open_close' diff --git a/homeassistant/components/light/group.py b/homeassistant/components/group/light.py similarity index 93% rename from homeassistant/components/light/group.py rename to homeassistant/components/group/light.py index bf54d3ecf29..c37c5cc4e8e 100644 --- a/homeassistant/components/light/group.py +++ b/homeassistant/components/group/light.py @@ -4,27 +4,28 @@ This platform allows several lights to be grouped into one light. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.group/ """ -import logging -import itertools -from typing import List, Tuple, Optional, Iterator, Any, Callable from collections import Counter +import itertools +import logging +from typing import Any, Callable, Iterator, List, Optional, Tuple import voluptuous as vol -from homeassistant.core import State, callback from homeassistant.components import light -from homeassistant.const import (STATE_ON, ATTR_ENTITY_ID, CONF_NAME, - CONF_ENTITIES, STATE_UNAVAILABLE, - ATTR_SUPPORTED_FEATURES) -from homeassistant.helpers.event import async_track_state_change -from homeassistant.helpers.typing import HomeAssistantType, ConfigType -from homeassistant.components.light import ( - SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION, - SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_WHITE_VALUE, PLATFORM_SCHEMA, - ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_WHITE_VALUE, ATTR_COLOR_TEMP, - ATTR_MIN_MIREDS, ATTR_MAX_MIREDS, ATTR_EFFECT_LIST, ATTR_EFFECT, - ATTR_FLASH, ATTR_TRANSITION) +from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_ENTITIES, CONF_NAME, + STATE_ON, STATE_UNAVAILABLE) +from homeassistant.core import State, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_state_change +from homeassistant.helpers.typing import ConfigType, HomeAssistantType + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_EFFECT_LIST, + ATTR_FLASH, ATTR_HS_COLOR, ATTR_MAX_MIREDS, ATTR_MIN_MIREDS, + ATTR_TRANSITION, ATTR_WHITE_VALUE, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH, + SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/group.py b/homeassistant/components/group/notify.py similarity index 97% rename from homeassistant/components/notify/group.py rename to homeassistant/components/group/notify.py index 5d25c2d815e..200464bb8cb 100644 --- a/homeassistant/components/notify/group.py +++ b/homeassistant/components/group/notify.py @@ -8,13 +8,15 @@ import asyncio from collections.abc import Mapping from copy import deepcopy import logging + import voluptuous as vol from homeassistant.const import ATTR_SERVICE -from homeassistant.components.notify import ( - DOMAIN, ATTR_MESSAGE, ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService) import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import ( + ATTR_DATA, ATTR_MESSAGE, DOMAIN, PLATFORM_SCHEMA, BaseNotificationService) + _LOGGER = logging.getLogger(__name__) CONF_SERVICES = 'services' diff --git a/homeassistant/components/gstreamer/__init__.py b/homeassistant/components/gstreamer/__init__.py new file mode 100644 index 00000000000..9fb97d25744 --- /dev/null +++ b/homeassistant/components/gstreamer/__init__.py @@ -0,0 +1 @@ +"""The gstreamer component.""" diff --git a/homeassistant/components/media_player/gstreamer.py b/homeassistant/components/gstreamer/media_player.py similarity index 100% rename from homeassistant/components/media_player/gstreamer.py rename to homeassistant/components/gstreamer/media_player.py diff --git a/homeassistant/components/gtfs/__init__.py b/homeassistant/components/gtfs/__init__.py new file mode 100644 index 00000000000..9c503c2bb96 --- /dev/null +++ b/homeassistant/components/gtfs/__init__.py @@ -0,0 +1 @@ +"""The gtfs component.""" diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py new file mode 100644 index 00000000000..5555459aa16 --- /dev/null +++ b/homeassistant/components/gtfs/sensor.py @@ -0,0 +1,684 @@ +""" +Support for GTFS (Google/General Transport Format Schema). + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.gtfs/ +""" +import datetime +import logging +import os +import threading +from typing import Any, Callable, Optional + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_NAME, CONF_OFFSET, DEVICE_CLASS_TIMESTAMP, + STATE_UNKNOWN) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.util import slugify +import homeassistant.util.dt as dt_util + +REQUIREMENTS = ['pygtfs==0.1.5'] + +_LOGGER = logging.getLogger(__name__) + +ATTR_ARRIVAL = 'arrival' +ATTR_BICYCLE = 'trip_bikes_allowed_state' +ATTR_DAY = 'day' +ATTR_FIRST = 'first' +ATTR_DROP_OFF_DESTINATION = 'destination_stop_drop_off_type_state' +ATTR_DROP_OFF_ORIGIN = 'origin_stop_drop_off_type_state' +ATTR_INFO = 'info' +ATTR_OFFSET = CONF_OFFSET +ATTR_LAST = 'last' +ATTR_LOCATION_DESTINATION = 'destination_station_location_type_name' +ATTR_LOCATION_ORIGIN = 'origin_station_location_type_name' +ATTR_PICKUP_DESTINATION = 'destination_stop_pickup_type_state' +ATTR_PICKUP_ORIGIN = 'origin_stop_pickup_type_state' +ATTR_ROUTE_TYPE = 'route_type_name' +ATTR_TIMEPOINT_DESTINATION = 'destination_stop_timepoint_exact' +ATTR_TIMEPOINT_ORIGIN = 'origin_stop_timepoint_exact' +ATTR_WHEELCHAIR = 'trip_wheelchair_access_available' +ATTR_WHEELCHAIR_DESTINATION = \ + 'destination_station_wheelchair_boarding_available' +ATTR_WHEELCHAIR_ORIGIN = 'origin_station_wheelchair_boarding_available' + +CONF_DATA = 'data' +CONF_DESTINATION = 'destination' +CONF_ORIGIN = 'origin' +CONF_TOMORROW = 'include_tomorrow' + +DEFAULT_NAME = 'GTFS Sensor' +DEFAULT_PATH = 'gtfs' + +BICYCLE_ALLOWED_DEFAULT = STATE_UNKNOWN +BICYCLE_ALLOWED_OPTIONS = { + 1: True, + 2: False, +} +DROP_OFF_TYPE_DEFAULT = STATE_UNKNOWN +DROP_OFF_TYPE_OPTIONS = { + 0: 'Regular', + 1: 'Not Available', + 2: 'Call Agency', + 3: 'Contact Driver', +} +ICON = 'mdi:train' +ICONS = { + 0: 'mdi:tram', + 1: 'mdi:subway', + 2: 'mdi:train', + 3: 'mdi:bus', + 4: 'mdi:ferry', + 5: 'mdi:train-variant', + 6: 'mdi:gondola', + 7: 'mdi:stairs', +} +LOCATION_TYPE_DEFAULT = 'Stop' +LOCATION_TYPE_OPTIONS = { + 0: 'Station', + 1: 'Stop', + 2: "Station Entrance/Exit", + 3: 'Other', +} +PICKUP_TYPE_DEFAULT = STATE_UNKNOWN +PICKUP_TYPE_OPTIONS = { + 0: 'Regular', + 1: "None Available", + 2: "Call Agency", + 3: "Contact Driver", +} +ROUTE_TYPE_OPTIONS = { + 0: 'Tram', + 1: 'Subway', + 2: 'Rail', + 3: 'Bus', + 4: 'Ferry', + 5: "Cable Tram", + 6: "Aerial Lift", + 7: 'Funicular', +} +TIMEPOINT_DEFAULT = True +TIMEPOINT_OPTIONS = { + 0: False, + 1: True, +} +WHEELCHAIR_ACCESS_DEFAULT = STATE_UNKNOWN +WHEELCHAIR_ACCESS_OPTIONS = { + 1: True, + 2: False, +} +WHEELCHAIR_BOARDING_DEFAULT = STATE_UNKNOWN +WHEELCHAIR_BOARDING_OPTIONS = { + 1: True, + 2: False, +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # type: ignore + vol.Required(CONF_ORIGIN): cv.string, + vol.Required(CONF_DESTINATION): cv.string, + vol.Required(CONF_DATA): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_OFFSET, default=0): cv.time_period, + vol.Optional(CONF_TOMORROW, default=False): cv.boolean, +}) + + +def get_next_departure(schedule: Any, start_station_id: Any, + end_station_id: Any, offset: cv.time_period, + include_tomorrow: cv.boolean = False) -> dict: + """Get the next departure for the given schedule.""" + now = datetime.datetime.now() + offset + now_date = now.strftime(dt_util.DATE_STR_FORMAT) + yesterday = now - datetime.timedelta(days=1) + yesterday_date = yesterday.strftime(dt_util.DATE_STR_FORMAT) + tomorrow = now + datetime.timedelta(days=1) + tomorrow_date = tomorrow.strftime(dt_util.DATE_STR_FORMAT) + + from sqlalchemy.sql import text + + # Fetch all departures for yesterday, today and optionally tomorrow, + # up to an overkill maximum in case of a departure every minute for those + # days. + limit = 24 * 60 * 60 * 2 + tomorrow_select = tomorrow_where = tomorrow_order = '' + if include_tomorrow: + limit = limit / 2 * 3 + tomorrow_name = tomorrow.strftime('%A').lower() + tomorrow_select = "calendar.{} AS tomorrow,".format(tomorrow_name) + tomorrow_where = "OR calendar.{} = 1".format(tomorrow_name) + tomorrow_order = "calendar.{} DESC,".format(tomorrow_name) + + sql_query = """ + SELECT trip.trip_id, trip.route_id, + time(origin_stop_time.arrival_time) AS origin_arrival_time, + time(origin_stop_time.departure_time) AS origin_depart_time, + date(origin_stop_time.departure_time) AS origin_depart_date, + origin_stop_time.drop_off_type AS origin_drop_off_type, + origin_stop_time.pickup_type AS origin_pickup_type, + origin_stop_time.shape_dist_traveled AS origin_dist_traveled, + origin_stop_time.stop_headsign AS origin_stop_headsign, + origin_stop_time.stop_sequence AS origin_stop_sequence, + origin_stop_time.timepoint AS origin_stop_timepoint, + time(destination_stop_time.arrival_time) AS dest_arrival_time, + time(destination_stop_time.departure_time) AS dest_depart_time, + destination_stop_time.drop_off_type AS dest_drop_off_type, + destination_stop_time.pickup_type AS dest_pickup_type, + destination_stop_time.shape_dist_traveled AS dest_dist_traveled, + destination_stop_time.stop_headsign AS dest_stop_headsign, + destination_stop_time.stop_sequence AS dest_stop_sequence, + destination_stop_time.timepoint AS dest_stop_timepoint, + calendar.{yesterday_name} AS yesterday, + calendar.{today_name} AS today, + {tomorrow_select} + calendar.start_date AS start_date, + calendar.end_date AS end_date + FROM trips trip + INNER JOIN calendar calendar + ON trip.service_id = calendar.service_id + INNER JOIN stop_times origin_stop_time + ON trip.trip_id = origin_stop_time.trip_id + INNER JOIN stops start_station + ON origin_stop_time.stop_id = start_station.stop_id + INNER JOIN stop_times destination_stop_time + ON trip.trip_id = destination_stop_time.trip_id + INNER JOIN stops end_station + ON destination_stop_time.stop_id = end_station.stop_id + WHERE (calendar.{yesterday_name} = 1 + OR calendar.{today_name} = 1 + {tomorrow_where} + ) + AND start_station.stop_id = :origin_station_id + AND end_station.stop_id = :end_station_id + AND origin_stop_sequence < dest_stop_sequence + AND calendar.start_date <= :today + AND calendar.end_date >= :today + ORDER BY calendar.{yesterday_name} DESC, + calendar.{today_name} DESC, + {tomorrow_order} + origin_stop_time.departure_time + LIMIT :limit + """.format(yesterday_name=yesterday.strftime('%A').lower(), + today_name=now.strftime('%A').lower(), + tomorrow_select=tomorrow_select, + tomorrow_where=tomorrow_where, + tomorrow_order=tomorrow_order) + result = schedule.engine.execute(text(sql_query), + origin_station_id=start_station_id, + end_station_id=end_station_id, + today=now_date, + limit=limit) + + # Create lookup timetable for today and possibly tomorrow, taking into + # account any departures from yesterday scheduled after midnight, + # as long as all departures are within the calendar date range. + timetable = {} + yesterday_start = today_start = tomorrow_start = None + yesterday_last = today_last = None + + for row in result: + if row['yesterday'] == 1 and yesterday_date >= row['start_date']: + extras = { + 'day': 'yesterday', + 'first': None, + 'last': False, + } + if yesterday_start is None: + yesterday_start = row['origin_depart_date'] + if yesterday_start != row['origin_depart_date']: + idx = '{} {}'.format(now_date, + row['origin_depart_time']) + timetable[idx] = {**row, **extras} + yesterday_last = idx + + if row['today'] == 1: + extras = { + 'day': 'today', + 'first': False, + 'last': False, + } + if today_start is None: + today_start = row['origin_depart_date'] + extras['first'] = True + if today_start == row['origin_depart_date']: + idx_prefix = now_date + else: + idx_prefix = tomorrow_date + idx = '{} {}'.format(idx_prefix, row['origin_depart_time']) + timetable[idx] = {**row, **extras} + today_last = idx + + if 'tomorrow' in row and row['tomorrow'] == 1 and tomorrow_date <= \ + row['end_date']: + extras = { + 'day': 'tomorrow', + 'first': False, + 'last': None, + } + if tomorrow_start is None: + tomorrow_start = row['origin_depart_date'] + extras['first'] = True + if tomorrow_start == row['origin_depart_date']: + idx = '{} {}'.format(tomorrow_date, + row['origin_depart_time']) + timetable[idx] = {**row, **extras} + + # Flag last departures. + for idx in [yesterday_last, today_last]: + if idx is not None: + timetable[idx]['last'] = True + + _LOGGER.debug("Timetable: %s", sorted(timetable.keys())) + + item = {} + for key in sorted(timetable.keys()): + if dt_util.parse_datetime(key) > now: + item = timetable[key] + _LOGGER.debug("Departure found for station %s @ %s -> %s", + start_station_id, key, item) + break + + if item == {}: + return {} + + # Format arrival and departure dates and times, accounting for the + # possibility of times crossing over midnight. + origin_arrival = now + if item['origin_arrival_time'] > item['origin_depart_time']: + origin_arrival -= datetime.timedelta(days=1) + origin_arrival_time = '{} {}'.format( + origin_arrival.strftime(dt_util.DATE_STR_FORMAT), + item['origin_arrival_time']) + + origin_depart_time = '{} {}'.format(now_date, item['origin_depart_time']) + + dest_arrival = now + if item['dest_arrival_time'] < item['origin_depart_time']: + dest_arrival += datetime.timedelta(days=1) + dest_arrival_time = '{} {}'.format( + dest_arrival.strftime(dt_util.DATE_STR_FORMAT), + item['dest_arrival_time']) + + dest_depart = dest_arrival + if item['dest_depart_time'] < item['dest_arrival_time']: + dest_depart += datetime.timedelta(days=1) + dest_depart_time = '{} {}'.format( + dest_depart.strftime(dt_util.DATE_STR_FORMAT), + item['dest_depart_time']) + + depart_time = dt_util.parse_datetime(origin_depart_time) + arrival_time = dt_util.parse_datetime(dest_arrival_time) + + origin_stop_time = { + 'Arrival Time': origin_arrival_time, + 'Departure Time': origin_depart_time, + 'Drop Off Type': item['origin_drop_off_type'], + 'Pickup Type': item['origin_pickup_type'], + 'Shape Dist Traveled': item['origin_dist_traveled'], + 'Headsign': item['origin_stop_headsign'], + 'Sequence': item['origin_stop_sequence'], + 'Timepoint': item['origin_stop_timepoint'], + } + + destination_stop_time = { + 'Arrival Time': dest_arrival_time, + 'Departure Time': dest_depart_time, + 'Drop Off Type': item['dest_drop_off_type'], + 'Pickup Type': item['dest_pickup_type'], + 'Shape Dist Traveled': item['dest_dist_traveled'], + 'Headsign': item['dest_stop_headsign'], + 'Sequence': item['dest_stop_sequence'], + 'Timepoint': item['dest_stop_timepoint'], + } + + return { + 'trip_id': item['trip_id'], + 'route_id': item['route_id'], + 'day': item['day'], + 'first': item['first'], + 'last': item['last'], + 'departure_time': depart_time, + 'arrival_time': arrival_time, + 'origin_stop_time': origin_stop_time, + 'destination_stop_time': destination_stop_time, + } + + +def setup_platform(hass: HomeAssistantType, config: ConfigType, + add_entities: Callable[[list], None], + discovery_info: Optional[dict] = None) -> bool: + """Set up the GTFS sensor.""" + gtfs_dir = hass.config.path(DEFAULT_PATH) + data = str(config.get(CONF_DATA)) + origin = config.get(CONF_ORIGIN) + destination = config.get(CONF_DESTINATION) + name = config.get(CONF_NAME) + offset = config.get(CONF_OFFSET) + include_tomorrow = config.get(CONF_TOMORROW) + + if not os.path.exists(gtfs_dir): + os.makedirs(gtfs_dir) + + if not os.path.exists(os.path.join(gtfs_dir, data)): + _LOGGER.error("The given GTFS data file/folder was not found") + return False + + import pygtfs + + (gtfs_root, _) = os.path.splitext(data) + + sqlite_file = "{}.sqlite?check_same_thread=False".format(gtfs_root) + joined_path = os.path.join(gtfs_dir, sqlite_file) + gtfs = pygtfs.Schedule(joined_path) + + # pylint: disable=no-member + if not gtfs.feeds: + pygtfs.append_feed(gtfs, os.path.join(gtfs_dir, data)) + + add_entities([ + GTFSDepartureSensor(gtfs, name, origin, destination, offset, + include_tomorrow)]) + return True + + +class GTFSDepartureSensor(Entity): + """Implementation of a GTFS departure sensor.""" + + def __init__(self, pygtfs: Any, name: Optional[Any], origin: Any, + destination: Any, offset: cv.time_period, + include_tomorrow: cv.boolean) -> None: + """Initialize the sensor.""" + self._pygtfs = pygtfs + self.origin = origin + self.destination = destination + self._include_tomorrow = include_tomorrow + self._offset = offset + self._custom_name = name + + self._available = False + self._icon = ICON + self._name = '' + self._state = None + self._attributes = {} # type: dict + + self._agency = None + self._departure = {} # type: dict + self._destination = None + self._origin = None + self._route = None + self._trip = None + + self.lock = threading.Lock() + self.update() + + @property + def name(self) -> str: + """Return the name of the sensor.""" + return self._name + + @property + def state(self) -> str: + """Return the state of the sensor.""" + if self._state is None: + return STATE_UNKNOWN + return self._state + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return self._available + + @property + def device_state_attributes(self) -> dict: + """Return the state attributes.""" + return self._attributes + + @property + def icon(self) -> str: + """Icon to use in the frontend, if any.""" + return self._icon + + @property + def device_class(self) -> str: + """Return the class of this device.""" + return DEVICE_CLASS_TIMESTAMP + + def update(self) -> None: + """Get the latest data from GTFS and update the states.""" + with self.lock: + # Fetch valid stop information once + if not self._origin: + stops = self._pygtfs.stops_by_id(self.origin) + if not stops: + self._available = False + _LOGGER.warning("Origin stop ID %s not found", self.origin) + return + self._origin = stops[0] + + if not self._destination: + stops = self._pygtfs.stops_by_id(self.destination) + if not stops: + self._available = False + _LOGGER.warning("Destination stop ID %s not found", + self.destination) + return + self._destination = stops[0] + + self._available = True + + # Fetch next departure + self._departure = get_next_departure( + self._pygtfs, self.origin, self.destination, self._offset, + self._include_tomorrow) + + # Define the state as a UTC timestamp with ISO 8601 format + if not self._departure: + self._state = None + else: + self._state = dt_util.as_utc( + self._departure['departure_time']).isoformat() + + # Fetch trip and route details once, unless updated + if not self._departure: + self._trip = None + else: + trip_id = self._departure['trip_id'] + if not self._trip or self._trip.trip_id != trip_id: + _LOGGER.info("Fetching trip details for %s", trip_id) + self._trip = self._pygtfs.trips_by_id(trip_id)[0] + + route_id = self._departure['route_id'] + if not self._route or self._route.route_id != route_id: + _LOGGER.info("Fetching route details for %s", route_id) + self._route = self._pygtfs.routes_by_id(route_id)[0] + + # Fetch agency details exactly once + if self._agency is None and self._route: + try: + _LOGGER.info("Fetching agency details for %s", + self._route.agency_id) + self._agency = self._pygtfs.agencies_by_id( + self._route.agency_id)[0] + except IndexError: + _LOGGER.warning( + "Agency ID '%s' not found in agency table. You may " + "want to update the agency database table to fix this " + "missing reference.", self._route.agency_id) + self._agency = False + + # Assign attributes, icon and name + self.update_attributes() + + if self._route: + self._icon = ICONS.get(self._route.route_type, ICON) + else: + self._icon = ICON + + name = '{agency} {origin} to {destination} next departure' + if not self._departure: + name = '{default}' + self._name = (self._custom_name or + name.format(agency=getattr(self._agency, + 'agency_name', + DEFAULT_NAME), + default=DEFAULT_NAME, + origin=self.origin, + destination=self.destination)) + + def update_attributes(self) -> None: + """Update state attributes.""" + # Add departure information + if self._departure: + self._attributes[ATTR_ARRIVAL] = dt_util.as_utc( + self._departure['arrival_time']).isoformat() + + self._attributes[ATTR_DAY] = self._departure['day'] + + if self._departure[ATTR_FIRST] is not None: + self._attributes[ATTR_FIRST] = self._departure['first'] + elif ATTR_FIRST in self._attributes.keys(): + del self._attributes[ATTR_FIRST] + + if self._departure[ATTR_LAST] is not None: + self._attributes[ATTR_LAST] = self._departure['last'] + elif ATTR_LAST in self._attributes.keys(): + del self._attributes[ATTR_LAST] + else: + if ATTR_ARRIVAL in self._attributes.keys(): + del self._attributes[ATTR_ARRIVAL] + if ATTR_DAY in self._attributes.keys(): + del self._attributes[ATTR_DAY] + if ATTR_FIRST in self._attributes.keys(): + del self._attributes[ATTR_FIRST] + if ATTR_LAST in self._attributes.keys(): + del self._attributes[ATTR_LAST] + + # Add contextual information + self._attributes[ATTR_OFFSET] = self._offset.seconds / 60 + + if self._state is None: + self._attributes[ATTR_INFO] = "No more departures" if \ + self._include_tomorrow else "No more departures today" + elif ATTR_INFO in self._attributes.keys(): + del self._attributes[ATTR_INFO] + + if self._agency: + self._attributes[ATTR_ATTRIBUTION] = self._agency.agency_name + elif ATTR_ATTRIBUTION in self._attributes.keys(): + del self._attributes[ATTR_ATTRIBUTION] + + # Add extra metadata + key = 'agency_id' + if self._agency and key not in self._attributes.keys(): + self.append_keys(self.dict_for_table(self._agency), 'Agency') + + key = 'origin_station_stop_id' + if self._origin and key not in self._attributes.keys(): + self.append_keys(self.dict_for_table(self._origin), + "Origin Station") + self._attributes[ATTR_LOCATION_ORIGIN] = \ + LOCATION_TYPE_OPTIONS.get( + self._origin.location_type, + LOCATION_TYPE_DEFAULT) + self._attributes[ATTR_WHEELCHAIR_ORIGIN] = \ + WHEELCHAIR_BOARDING_OPTIONS.get( + self._origin.wheelchair_boarding, + WHEELCHAIR_BOARDING_DEFAULT) + + key = 'destination_station_stop_id' + if self._destination and key not in self._attributes.keys(): + self.append_keys(self.dict_for_table(self._destination), + "Destination Station") + self._attributes[ATTR_LOCATION_DESTINATION] = \ + LOCATION_TYPE_OPTIONS.get( + self._destination.location_type, + LOCATION_TYPE_DEFAULT) + self._attributes[ATTR_WHEELCHAIR_DESTINATION] = \ + WHEELCHAIR_BOARDING_OPTIONS.get( + self._destination.wheelchair_boarding, + WHEELCHAIR_BOARDING_DEFAULT) + + # Manage Route metadata + key = 'route_id' + if not self._route and key in self._attributes.keys(): + self.remove_keys('Route') + elif self._route and (key not in self._attributes.keys() or + self._attributes[key] != self._route.route_id): + self.append_keys(self.dict_for_table(self._route), 'Route') + self._attributes[ATTR_ROUTE_TYPE] = \ + ROUTE_TYPE_OPTIONS[self._route.route_type] + + # Manage Trip metadata + key = 'trip_id' + if not self._trip and key in self._attributes.keys(): + self.remove_keys('Trip') + elif self._trip and (key not in self._attributes.keys() or + self._attributes[key] != self._trip.trip_id): + self.append_keys(self.dict_for_table(self._trip), 'Trip') + self._attributes[ATTR_BICYCLE] = BICYCLE_ALLOWED_OPTIONS.get( + self._trip.bikes_allowed, + BICYCLE_ALLOWED_DEFAULT) + self._attributes[ATTR_WHEELCHAIR] = WHEELCHAIR_ACCESS_OPTIONS.get( + self._trip.wheelchair_accessible, + WHEELCHAIR_ACCESS_DEFAULT) + + # Manage Stop Times metadata + prefix = 'origin_stop' + if self._departure: + self.append_keys(self._departure['origin_stop_time'], prefix) + self._attributes[ATTR_DROP_OFF_ORIGIN] = DROP_OFF_TYPE_OPTIONS.get( + self._departure['origin_stop_time']['Drop Off Type'], + DROP_OFF_TYPE_DEFAULT) + self._attributes[ATTR_PICKUP_ORIGIN] = PICKUP_TYPE_OPTIONS.get( + self._departure['origin_stop_time']['Pickup Type'], + PICKUP_TYPE_DEFAULT) + self._attributes[ATTR_TIMEPOINT_ORIGIN] = TIMEPOINT_OPTIONS.get( + self._departure['origin_stop_time']['Timepoint'], + TIMEPOINT_DEFAULT) + else: + self.remove_keys(prefix) + + prefix = 'destination_stop' + if self._departure: + self.append_keys(self._departure['destination_stop_time'], prefix) + self._attributes[ATTR_DROP_OFF_DESTINATION] = \ + DROP_OFF_TYPE_OPTIONS.get( + self._departure['destination_stop_time']['Drop Off Type'], + DROP_OFF_TYPE_DEFAULT) + self._attributes[ATTR_PICKUP_DESTINATION] = \ + PICKUP_TYPE_OPTIONS.get( + self._departure['destination_stop_time']['Pickup Type'], + PICKUP_TYPE_DEFAULT) + self._attributes[ATTR_TIMEPOINT_DESTINATION] = \ + TIMEPOINT_OPTIONS.get( + self._departure['destination_stop_time']['Timepoint'], + TIMEPOINT_DEFAULT) + else: + self.remove_keys(prefix) + + @staticmethod + def dict_for_table(resource: Any) -> dict: + """Return a dictionary for the SQLAlchemy resource given.""" + return dict((col, getattr(resource, col)) + for col in resource.__table__.columns.keys()) + + def append_keys(self, resource: dict, prefix: Optional[str] = None) -> \ + None: + """Properly format key val pairs to append to attributes.""" + for attr, val in resource.items(): + if val == '' or val is None or attr == 'feed_id': + continue + key = attr + if prefix and not key.startswith(prefix): + key = '{} {}'.format(prefix, key) + key = slugify(key) + self._attributes[key] = val + + def remove_keys(self, prefix: str) -> None: + """Remove attributes whose key starts with prefix.""" + self._attributes = {k: v for k, v in self._attributes.items() if + not k.startswith(prefix)} diff --git a/homeassistant/components/gtt/__init__.py b/homeassistant/components/gtt/__init__.py new file mode 100644 index 00000000000..cbb508154dd --- /dev/null +++ b/homeassistant/components/gtt/__init__.py @@ -0,0 +1 @@ +"""The gtt component.""" diff --git a/homeassistant/components/sensor/gtt.py b/homeassistant/components/gtt/sensor.py similarity index 100% rename from homeassistant/components/sensor/gtt.py rename to homeassistant/components/gtt/sensor.py diff --git a/homeassistant/components/hangouts/.translations/ru.json b/homeassistant/components/hangouts/.translations/ru.json index 6d93ec0d18f..143d6bc88ba 100644 --- a/homeassistant/components/hangouts/.translations/ru.json +++ b/homeassistant/components/hangouts/.translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430" }, "error": { diff --git a/homeassistant/components/hangouts/.translations/th.json b/homeassistant/components/hangouts/.translations/th.json index ae7fc861b77..7b6645edca2 100644 --- a/homeassistant/components/hangouts/.translations/th.json +++ b/homeassistant/components/hangouts/.translations/th.json @@ -1,6 +1,9 @@ { "config": { "step": { + "2fa": { + "title": "\u0e23\u0e2b\u0e31\u0e2a\u0e23\u0e31\u0e1a\u0e23\u0e2d\u0e07\u0e04\u0e27\u0e32\u0e21\u0e16\u0e39\u0e01\u0e15\u0e49\u0e2d\u0e07\u0e2a\u0e2d\u0e07\u0e1b\u0e31\u0e08\u0e08\u0e31\u0e22" + }, "user": { "data": { "password": "\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19" diff --git a/homeassistant/components/hangouts/__init__.py b/homeassistant/components/hangouts/__init__.py index 4796744c170..2d36de8b769 100644 --- a/homeassistant/components/hangouts/__init__.py +++ b/homeassistant/components/hangouts/__init__.py @@ -4,12 +4,12 @@ import logging import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.hangouts.intents import HelpIntent from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers import dispatcher, intent import homeassistant.helpers.config_validation as cv # We need an import from .config_flow, without it .config_flow is never loaded. +from .intents import HelpIntent from .config_flow import HangoutsFlowHandler # noqa: F401 from .const import ( CONF_BOT, CONF_DEFAULT_CONVERSATIONS, CONF_ERROR_SUPPRESSED_CONVERSATIONS, diff --git a/homeassistant/components/hangouts/const.py b/homeassistant/components/hangouts/const.py index ca0fdf986ee..38b238292b3 100644 --- a/homeassistant/components/hangouts/const.py +++ b/homeassistant/components/hangouts/const.py @@ -7,7 +7,7 @@ from homeassistant.components.notify import ( ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET) import homeassistant.helpers.config_validation as cv -_LOGGER = logging.getLogger('homeassistant.components.hangouts') +_LOGGER = logging.getLogger('.') DOMAIN = 'hangouts' diff --git a/homeassistant/components/hangouts/notify.py b/homeassistant/components/hangouts/notify.py index c3b5450be05..de9af2e0775 100644 --- a/homeassistant/components/hangouts/notify.py +++ b/homeassistant/components/hangouts/notify.py @@ -3,12 +3,13 @@ import logging import voluptuous as vol -from homeassistant.components.hangouts.const import ( - CONF_DEFAULT_CONVERSATIONS, DOMAIN, SERVICE_SEND_MESSAGE, TARGETS_SCHEMA) from homeassistant.components.notify import ( ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) +from .const import ( + CONF_DEFAULT_CONVERSATIONS, DOMAIN, SERVICE_SEND_MESSAGE, TARGETS_SCHEMA) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [DOMAIN] diff --git a/homeassistant/components/harman_kardon_avr/__init__.py b/homeassistant/components/harman_kardon_avr/__init__.py new file mode 100644 index 00000000000..c9e3afd6be3 --- /dev/null +++ b/homeassistant/components/harman_kardon_avr/__init__.py @@ -0,0 +1 @@ +"""The harman_kardon_avr component.""" diff --git a/homeassistant/components/media_player/harman_kardon_avr.py b/homeassistant/components/harman_kardon_avr/media_player.py similarity index 100% rename from homeassistant/components/media_player/harman_kardon_avr.py rename to homeassistant/components/harman_kardon_avr/media_player.py diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index f89a6539cd0..e8d04b1596d 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -6,7 +6,7 @@ import os import voluptuous as vol from homeassistant.auth.const import GROUP_ID_ADMIN -from homeassistant.components import SERVICE_CHECK_CONFIG +from homeassistant.components.homeassistant import SERVICE_CHECK_CONFIG import homeassistant.config as conf_util from homeassistant.const import ( ATTR_NAME, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP) @@ -20,6 +20,7 @@ from .auth import async_setup_auth from .discovery import async_setup_discovery from .handler import HassIO, HassioAPIError from .http import HassIOView +from .ingress import async_setup_ingress _LOGGER = logging.getLogger(__name__) @@ -188,6 +189,7 @@ async def async_setup(hass, config): sidebar_icon='hass:home-assistant', js_url='/api/hassio/app/entrypoint.js', embed_iframe=True, + require_admin=True, ) await hassio.update_hass_api(config.get('http', {}), refresh_token.token) @@ -269,4 +271,7 @@ async def async_setup(hass, config): # Init auth Hass.io feature async_setup_auth(hass) + # Init ingress Hass.io feature + async_setup_ingress(hass, host) + return True diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index 964f94bfb41..e4132562c31 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -9,6 +9,7 @@ ATTR_UUID = 'uuid' ATTR_USERNAME = 'username' ATTR_PASSWORD = 'password' -X_HASSIO = 'X-HASSIO-KEY' -X_HASS_USER_ID = 'X-HASS-USER-ID' -X_HASS_IS_ADMIN = 'X-HASS-IS-ADMIN' +X_HASSIO = 'X-Hassio-Key' +X_INGRESS_PATH = "X-Ingress-Path" +X_HASS_USER_ID = 'X-Hass-User-ID' +X_HASS_IS_ADMIN = 'X-Hass-Is-Admin' diff --git a/homeassistant/components/hassio/discovery.py b/homeassistant/components/hassio/discovery.py index 804247d2407..09a98edc148 100644 --- a/homeassistant/components/hassio/discovery.py +++ b/homeassistant/components/hassio/discovery.py @@ -81,7 +81,7 @@ class HassIODiscovery(HomeAssistantView): service = data[ATTR_SERVICE] config_data = data[ATTR_CONFIG] - # Read addinional Add-on info + # Read additional Add-on info try: addon_info = await self.hassio.get_addon_info(data[ATTR_ADDON]) except HassioAPIError as err: @@ -98,7 +98,7 @@ class HassIODiscovery(HomeAssistantView): service = data[ATTR_SERVICE] uuid = data[ATTR_UUID] - # Check if realy deletet / prevent injections + # Check if really deletet / prevent injections try: data = await self.hassio.get_discovery_message(uuid) except HassioAPIError: diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py index 01ded9ca11d..7284004d72f 100644 --- a/homeassistant/components/hassio/http.py +++ b/homeassistant/components/hassio/http.py @@ -3,10 +3,11 @@ import asyncio import logging import os import re +from typing import Dict, Union import aiohttp from aiohttp import web -from aiohttp.hdrs import CONTENT_TYPE +from aiohttp.hdrs import CONTENT_TYPE, CONTENT_LENGTH from aiohttp.web_exceptions import HTTPBadGateway import async_timeout @@ -20,7 +21,8 @@ _LOGGER = logging.getLogger(__name__) NO_TIMEOUT = re.compile( r'^(?:' r'|homeassistant/update' - r'|host/update' + r'|hassos/update' + r'|hassos/update/cli' r'|supervisor/update' r'|addons/[^/]+/(?:update|install|rebuild)' r'|snapshots/.+/full' @@ -44,25 +46,26 @@ class HassIOView(HomeAssistantView): url = "/api/hassio/{path:.+}" requires_auth = False - def __init__(self, host, websession): + def __init__(self, host: str, websession: aiohttp.ClientSession): """Initialize a Hass.io base view.""" self._host = host self._websession = websession - async def _handle(self, request, path): + async def _handle( + self, request: web.Request, path: str + ) -> Union[web.Response, web.StreamResponse]: """Route data to Hass.io.""" if _need_auth(path) and not request[KEY_AUTHENTICATED]: return web.Response(status=401) - client = await self._command_proxy(path, request) - - data = await client.read() - return _create_response(client, data) + return await self._command_proxy(path, request) get = _handle post = _handle - async def _command_proxy(self, path, request): + async def _command_proxy( + self, path: str, request: web.Request + ) -> Union[web.Response, web.StreamResponse]: """Return a client request with proxy origin for Hass.io supervisor. This method is a coroutine. @@ -71,29 +74,38 @@ class HassIOView(HomeAssistantView): hass = request.app['hass'] data = None - headers = { - X_HASSIO: os.environ.get('HASSIO_TOKEN', ""), - } - user = request.get('hass_user') - if user is not None: - headers[X_HASS_USER_ID] = request['hass_user'].id - headers[X_HASS_IS_ADMIN] = str(int(request['hass_user'].is_admin)) + headers = _init_header(request) try: with async_timeout.timeout(10, loop=hass.loop): data = await request.read() - if data: - headers[CONTENT_TYPE] = request.content_type - else: - data = None method = getattr(self._websession, request.method.lower()) client = await method( "http://{}/{}".format(self._host, path), data=data, headers=headers, timeout=read_timeout ) + print(client.headers) - return client + # Simple request + if int(client.headers.get(CONTENT_LENGTH, 0)) < 4194000: + # Return Response + body = await client.read() + return web.Response( + content_type=client.content_type, + status=client.status, + body=body, + ) + + # Stream response + response = web.StreamResponse(status=client.status) + response.content_type = client.content_type + + await response.prepare(request) + async for data in client.content.iter_chunked(4096): + await response.write(data) + + return response except aiohttp.ClientError as err: _LOGGER.error("Client error on api %s request %s", path, err) @@ -104,23 +116,30 @@ class HassIOView(HomeAssistantView): raise HTTPBadGateway() -def _create_response(client, data): - """Convert a response from client request.""" - return web.Response( - body=data, - status=client.status, - content_type=client.content_type, - ) +def _init_header(request: web.Request) -> Dict[str, str]: + """Create initial header.""" + headers = { + X_HASSIO: os.environ.get('HASSIO_TOKEN', ""), + CONTENT_TYPE: request.content_type, + } + + # Add user data + user = request.get('hass_user') + if user is not None: + headers[X_HASS_USER_ID] = request['hass_user'].id + headers[X_HASS_IS_ADMIN] = str(int(request['hass_user'].is_admin)) + + return headers -def _get_timeout(path): +def _get_timeout(path: str) -> int: """Return timeout for a URL path.""" if NO_TIMEOUT.match(path): return 0 return 300 -def _need_auth(path): +def _need_auth(path: str) -> bool: """Return if a path need authentication.""" if NO_AUTH.match(path): return False diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py new file mode 100644 index 00000000000..04241f53de9 --- /dev/null +++ b/homeassistant/components/hassio/ingress.py @@ -0,0 +1,215 @@ +"""Hass.io Add-on ingress service.""" +import asyncio +from ipaddress import ip_address +import os +from typing import Dict, Union + +import aiohttp +from aiohttp import web +from aiohttp import hdrs +from aiohttp.web_exceptions import HTTPBadGateway +from multidict import CIMultiDict + +from homeassistant.core import callback +from homeassistant.components.http import HomeAssistantView +from homeassistant.helpers.typing import HomeAssistantType + +from .const import X_HASSIO, X_INGRESS_PATH + + +@callback +def async_setup_ingress(hass: HomeAssistantType, host: str): + """Auth setup.""" + websession = hass.helpers.aiohttp_client.async_get_clientsession() + + hassio_ingress = HassIOIngress(host, websession) + hass.http.register_view(hassio_ingress) + + +class HassIOIngress(HomeAssistantView): + """Hass.io view to handle base part.""" + + name = "api:hassio:ingress" + url = "/api/hassio_ingress/{addon}/{path:.+}" + requires_auth = False + + def __init__(self, host: str, websession: aiohttp.ClientSession): + """Initialize a Hass.io ingress view.""" + self._host = host + self._websession = websession + + def _create_url(self, addon: str, path: str) -> str: + """Create URL to service.""" + return "http://{}/addons/{}/web/{}".format(self._host, addon, path) + + async def _handle( + self, request: web.Request, addon: str, path: str + ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]: + """Route data to Hass.io ingress service.""" + try: + # Websocket + if _is_websocket(request): + return await self._handle_websocket(request, addon, path) + + # Request + return await self._handle_request(request, addon, path) + + except aiohttp.ClientError: + pass + + raise HTTPBadGateway() from None + + get = _handle + post = _handle + put = _handle + delete = _handle + + async def _handle_websocket( + self, request: web.Request, addon: str, path: str + ) -> web.WebSocketResponse: + """Ingress route for websocket.""" + ws_server = web.WebSocketResponse() + await ws_server.prepare(request) + + # Preparing + url = self._create_url(addon, path) + source_header = _init_header(request, addon) + + # Support GET query + if request.query_string: + url = "{}?{}".format(url, request.query_string) + + # Start proxy + async with self._websession.ws_connect( + url, headers=source_header + ) as ws_client: + # Proxy requests + await asyncio.wait( + [ + _websocket_forward(ws_server, ws_client), + _websocket_forward(ws_client, ws_server), + ], + return_when=asyncio.FIRST_COMPLETED + ) + + return ws_server + + async def _handle_request( + self, request: web.Request, addon: str, path: str + ) -> Union[web.Response, web.StreamResponse]: + """Ingress route for request.""" + url = self._create_url(addon, path) + data = await request.read() + source_header = _init_header(request, addon) + + async with self._websession.request( + request.method, url, headers=source_header, + params=request.query, data=data, cookies=request.cookies + ) as result: + headers = _response_header(result) + + # Simple request + if hdrs.CONTENT_LENGTH in result.headers and \ + int(result.headers.get(hdrs.CONTENT_LENGTH, 0)) < 4194000: + # Return Response + body = await result.read() + return web.Response( + headers=headers, + status=result.status, + body=body + ) + + # Stream response + response = web.StreamResponse( + status=result.status, headers=headers) + response.content_type = result.content_type + + try: + await response.prepare(request) + async for data in result.content: + await response.write(data) + + except (aiohttp.ClientError, aiohttp.ClientPayloadError): + pass + + return response + + +def _init_header( + request: web.Request, addon: str +) -> Union[CIMultiDict, Dict[str, str]]: + """Create initial header.""" + headers = {} + + # filter flags + for name, value in request.headers.items(): + if name in (hdrs.CONTENT_LENGTH, hdrs.CONTENT_TYPE): + continue + headers[name] = value + + # Inject token / cleanup later on Supervisor + headers[X_HASSIO] = os.environ.get('HASSIO_TOKEN', "") + + # Ingress information + headers[X_INGRESS_PATH] = "/api/hassio_ingress/{}".format(addon) + + # Set X-Forwarded-For + forward_for = request.headers.get(hdrs.X_FORWARDED_FOR) + connected_ip = ip_address(request.transport.get_extra_info('peername')[0]) + if forward_for: + forward_for = "{}, {!s}".format(forward_for, connected_ip) + else: + forward_for = "{!s}".format(connected_ip) + headers[hdrs.X_FORWARDED_FOR] = forward_for + + # Set X-Forwarded-Host + forward_host = request.headers.get(hdrs.X_FORWARDED_HOST) + if not forward_host: + forward_host = request.host + headers[hdrs.X_FORWARDED_HOST] = forward_host + + # Set X-Forwarded-Proto + forward_proto = request.headers.get(hdrs.X_FORWARDED_PROTO) + if not forward_proto: + forward_proto = request.url.scheme + headers[hdrs.X_FORWARDED_PROTO] = forward_proto + + return headers + + +def _response_header(response: aiohttp.ClientResponse) -> Dict[str, str]: + """Create response header.""" + headers = {} + + for name, value in response.headers.items(): + if name in (hdrs.TRANSFER_ENCODING, hdrs.CONTENT_LENGTH, + hdrs.CONTENT_TYPE): + continue + headers[name] = value + + return headers + + +def _is_websocket(request: web.Request) -> bool: + """Return True if request is a websocket.""" + headers = request.headers + + if headers.get(hdrs.CONNECTION) == "Upgrade" and \ + headers.get(hdrs.UPGRADE) == "websocket": + return True + return False + + +async def _websocket_forward(ws_from, ws_to): + """Handle websocket message directly.""" + async for msg in ws_from: + if msg.type == aiohttp.WSMsgType.TEXT: + await ws_to.send_str(msg.data) + elif msg.type == aiohttp.WSMsgType.BINARY: + await ws_to.send_bytes(msg.data) + elif msg.type == aiohttp.WSMsgType.PING: + await ws_to.ping() + elif msg.type == aiohttp.WSMsgType.PONG: + await ws_to.pong() + elif ws_to.closed: + await ws_to.close(code=ws_to.close_code, message=msg.extra) diff --git a/homeassistant/components/haveibeenpwned/__init__.py b/homeassistant/components/haveibeenpwned/__init__.py new file mode 100644 index 00000000000..adead4ec46e --- /dev/null +++ b/homeassistant/components/haveibeenpwned/__init__.py @@ -0,0 +1 @@ +"""The haveibeenpwned component.""" diff --git a/homeassistant/components/sensor/haveibeenpwned.py b/homeassistant/components/haveibeenpwned/sensor.py similarity index 100% rename from homeassistant/components/sensor/haveibeenpwned.py rename to homeassistant/components/haveibeenpwned/sensor.py diff --git a/homeassistant/components/hddtemp/__init__.py b/homeassistant/components/hddtemp/__init__.py new file mode 100644 index 00000000000..121238df9fe --- /dev/null +++ b/homeassistant/components/hddtemp/__init__.py @@ -0,0 +1 @@ +"""The hddtemp component.""" diff --git a/homeassistant/components/sensor/hddtemp.py b/homeassistant/components/hddtemp/sensor.py similarity index 100% rename from homeassistant/components/sensor/hddtemp.py rename to homeassistant/components/hddtemp/sensor.py diff --git a/homeassistant/components/hdmi_cec/media_player.py b/homeassistant/components/hdmi_cec/media_player.py index 553983a1f03..b2d2910e145 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -1,7 +1,6 @@ """Support for HDMI CEC devices as media players.""" import logging -from homeassistant.components.hdmi_cec import ATTR_NEW, CecDevice from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( DOMAIN, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, @@ -10,6 +9,8 @@ from homeassistant.components.media_player.const import ( from homeassistant.const import ( STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING) +from . import ATTR_NEW, CecDevice + DEPENDENCIES = ['hdmi_cec'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hdmi_cec/switch.py b/homeassistant/components/hdmi_cec/switch.py index ff423890ba5..639f545707e 100644 --- a/homeassistant/components/hdmi_cec/switch.py +++ b/homeassistant/components/hdmi_cec/switch.py @@ -1,10 +1,11 @@ """Support for HDMI CEC devices as switches.""" import logging -from homeassistant.components.hdmi_cec import ATTR_NEW, CecDevice from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY +from . import ATTR_NEW, CecDevice + DEPENDENCIES = ['hdmi_cec'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/heatmiser/__init__.py b/homeassistant/components/heatmiser/__init__.py new file mode 100644 index 00000000000..bc6313f9e3d --- /dev/null +++ b/homeassistant/components/heatmiser/__init__.py @@ -0,0 +1 @@ +"""The heatmiser component.""" diff --git a/homeassistant/components/climate/heatmiser.py b/homeassistant/components/heatmiser/climate.py similarity index 100% rename from homeassistant/components/climate/heatmiser.py rename to homeassistant/components/heatmiser/climate.py diff --git a/homeassistant/components/hikvision/__init__.py b/homeassistant/components/hikvision/__init__.py new file mode 100644 index 00000000000..dbf7991b3c4 --- /dev/null +++ b/homeassistant/components/hikvision/__init__.py @@ -0,0 +1 @@ +"""The hikvision component.""" diff --git a/homeassistant/components/binary_sensor/hikvision.py b/homeassistant/components/hikvision/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/hikvision.py rename to homeassistant/components/hikvision/binary_sensor.py diff --git a/homeassistant/components/hikvisioncam/__init__.py b/homeassistant/components/hikvisioncam/__init__.py new file mode 100644 index 00000000000..32a2a86b28f --- /dev/null +++ b/homeassistant/components/hikvisioncam/__init__.py @@ -0,0 +1 @@ +"""The hikvisioncam component.""" diff --git a/homeassistant/components/switch/hikvisioncam.py b/homeassistant/components/hikvisioncam/switch.py similarity index 100% rename from homeassistant/components/switch/hikvisioncam.py rename to homeassistant/components/hikvisioncam/switch.py diff --git a/homeassistant/components/hipchat/__init__.py b/homeassistant/components/hipchat/__init__.py new file mode 100644 index 00000000000..8b79982fa43 --- /dev/null +++ b/homeassistant/components/hipchat/__init__.py @@ -0,0 +1 @@ +"""The hipchat component.""" diff --git a/homeassistant/components/notify/hipchat.py b/homeassistant/components/hipchat/notify.py similarity index 92% rename from homeassistant/components/notify/hipchat.py rename to homeassistant/components/hipchat/notify.py index 344827c00b4..9e415171f29 100644 --- a/homeassistant/components/notify/hipchat.py +++ b/homeassistant/components/hipchat/notify.py @@ -8,10 +8,12 @@ import logging import voluptuous as vol +from homeassistant.const import CONF_HOST, CONF_ROOM, CONF_TOKEN import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - ATTR_TARGET, ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService) -from homeassistant.const import CONF_TOKEN, CONF_HOST, CONF_ROOM + +from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET, + PLATFORM_SCHEMA, + BaseNotificationService) REQUIREMENTS = ['hipnotify==1.0.8'] diff --git a/homeassistant/components/history_stats/__init__.py b/homeassistant/components/history_stats/__init__.py new file mode 100644 index 00000000000..3c5385be6ad --- /dev/null +++ b/homeassistant/components/history_stats/__init__.py @@ -0,0 +1 @@ +"""The history_stats component.""" diff --git a/homeassistant/components/sensor/history_stats.py b/homeassistant/components/history_stats/sensor.py similarity index 100% rename from homeassistant/components/sensor/history_stats.py rename to homeassistant/components/history_stats/sensor.py diff --git a/homeassistant/components/hitron_coda/__init__.py b/homeassistant/components/hitron_coda/__init__.py new file mode 100644 index 00000000000..de65a34f3a4 --- /dev/null +++ b/homeassistant/components/hitron_coda/__init__.py @@ -0,0 +1 @@ +"""The hitron_coda component.""" diff --git a/homeassistant/components/device_tracker/hitron_coda.py b/homeassistant/components/hitron_coda/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/hitron_coda.py rename to homeassistant/components/hitron_coda/device_tracker.py diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index dee27c5c710..a0973f4d8e9 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -1,6 +1,7 @@ """Support for the Hive binary sensors.""" from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.hive import DATA_HIVE, DOMAIN + +from . import DATA_HIVE, DOMAIN DEPENDENCIES = ['hive'] diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index 45829cda087..dac7feb2927 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -1,12 +1,13 @@ """Support for the Hive climate devices.""" from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - STATE_AUTO, STATE_HEAT, - SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE) -from homeassistant.components.hive import DATA_HIVE, DOMAIN + STATE_AUTO, STATE_HEAT, SUPPORT_AUX_HEAT, SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE) from homeassistant.const import ( ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS) +from . import DATA_HIVE, DOMAIN + DEPENDENCIES = ['hive'] HIVE_TO_HASS_STATE = { diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index 2bec60f0ee4..3a2176c3eed 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -1,10 +1,11 @@ """Support for the Hive lights.""" -from homeassistant.components.hive import DATA_HIVE, DOMAIN from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, Light) import homeassistant.util.color as color_util +from . import DATA_HIVE, DOMAIN + DEPENDENCIES = ['hive'] diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index 142c8c7ee94..e7b7d6b4597 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -1,8 +1,9 @@ """Support for the Hive sensors.""" -from homeassistant.components.hive import DATA_HIVE, DOMAIN from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity +from . import DATA_HIVE, DOMAIN + DEPENDENCIES = ['hive'] FRIENDLY_NAMES = { diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py index c897e37f34b..fd4d3d69b50 100644 --- a/homeassistant/components/hive/switch.py +++ b/homeassistant/components/hive/switch.py @@ -1,7 +1,8 @@ """Support for the Hive switches.""" -from homeassistant.components.hive import DATA_HIVE, DOMAIN from homeassistant.components.switch import SwitchDevice +from . import DATA_HIVE, DOMAIN + DEPENDENCIES = ['hive'] diff --git a/homeassistant/components/hlk_sw16/__init__.py b/homeassistant/components/hlk_sw16/__init__.py index aab3f79b8b2..acb604bc010 100644 --- a/homeassistant/components/hlk_sw16/__init__.py +++ b/homeassistant/components/hlk_sw16/__init__.py @@ -13,7 +13,7 @@ from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import ( async_dispatcher_send, async_dispatcher_connect) -REQUIREMENTS = ['hlk-sw16==0.0.6'] +REQUIREMENTS = ['hlk-sw16==0.0.7'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hlk_sw16/switch.py b/homeassistant/components/hlk_sw16/switch.py index b1bfc5ce23d..164a504fa34 100644 --- a/homeassistant/components/hlk_sw16/switch.py +++ b/homeassistant/components/hlk_sw16/switch.py @@ -1,13 +1,11 @@ """Support for HLK-SW16 switches.""" import logging -from homeassistant.components.hlk_sw16 import ( - SW16Device, DOMAIN as HLK_SW16, - DATA_DEVICE_REGISTER) -from homeassistant.components.switch import ( - ToggleEntity) +from homeassistant.components.switch import ToggleEntity from homeassistant.const import CONF_NAME +from . import DATA_DEVICE_REGISTER, DOMAIN as HLK_SW16, SW16Device + DEPENDENCIES = [HLK_SW16] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py new file mode 100644 index 00000000000..ef01d133cff --- /dev/null +++ b/homeassistant/components/homeassistant/__init__.py @@ -0,0 +1,137 @@ +"""Integration providing core pieces of infrastructure.""" +import asyncio +import itertools as it +import logging +from typing import Awaitable + +import voluptuous as vol + +import homeassistant.core as ha +import homeassistant.config as conf_util +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.service import async_extract_entity_ids +from homeassistant.helpers import intent +from homeassistant.const import ( + ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, + SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, + RESTART_EXIT_CODE) +from homeassistant.helpers import config_validation as cv + +_LOGGER = logging.getLogger(__name__) +DOMAIN = ha.DOMAIN +SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' +SERVICE_CHECK_CONFIG = 'check_config' +SERVICE_UPDATE_ENTITY = 'update_entity' +SCHEMA_UPDATE_ENTITY = vol.Schema({ + ATTR_ENTITY_ID: cv.entity_ids +}) + + +async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: + """Set up general services related to Home Assistant.""" + async def async_handle_turn_service(service): + """Handle calls to homeassistant.turn_on/off.""" + entity_ids = await async_extract_entity_ids(hass, service) + + # Generic turn on/off method requires entity id + if not entity_ids: + _LOGGER.error( + "homeassistant/%s cannot be called without entity_id", + service.service) + return + + # Group entity_ids by domain. groupby requires sorted data. + by_domain = it.groupby(sorted(entity_ids), + lambda item: ha.split_entity_id(item)[0]) + + tasks = [] + + for domain, ent_ids in by_domain: + # We want to block for all calls and only return when all calls + # have been processed. If a service does not exist it causes a 10 + # second delay while we're blocking waiting for a response. + # But services can be registered on other HA instances that are + # listening to the bus too. So as an in between solution, we'll + # block only if the service is defined in the current HA instance. + blocking = hass.services.has_service(domain, service.service) + + # Create a new dict for this call + data = dict(service.data) + + # ent_ids is a generator, convert it to a list. + data[ATTR_ENTITY_ID] = list(ent_ids) + + tasks.append(hass.services.async_call( + domain, service.service, data, blocking)) + + await asyncio.wait(tasks, loop=hass.loop) + + hass.services.async_register( + ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service) + hass.services.async_register( + ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service) + hass.services.async_register( + ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service) + hass.helpers.intent.async_register(intent.ServiceIntentHandler( + intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned {} on")) + hass.helpers.intent.async_register(intent.ServiceIntentHandler( + intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF, + "Turned {} off")) + hass.helpers.intent.async_register(intent.ServiceIntentHandler( + intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}")) + + async def async_handle_core_service(call): + """Service handler for handling core services.""" + if call.service == SERVICE_HOMEASSISTANT_STOP: + hass.async_create_task(hass.async_stop()) + return + + try: + errors = await conf_util.async_check_ha_config_file(hass) + except HomeAssistantError: + return + + if errors: + _LOGGER.error(errors) + hass.components.persistent_notification.async_create( + "Config error. See dev-info panel for details.", + "Config validating", "{0}.check_config".format(ha.DOMAIN)) + return + + if call.service == SERVICE_HOMEASSISTANT_RESTART: + hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE)) + + async def async_handle_update_service(call): + """Service handler for updating an entity.""" + tasks = [hass.helpers.entity_component.async_update_entity(entity) + for entity in call.data[ATTR_ENTITY_ID]] + + if tasks: + await asyncio.wait(tasks) + + hass.services.async_register( + ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service) + hass.services.async_register( + ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service) + hass.services.async_register( + ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service) + hass.services.async_register( + ha.DOMAIN, SERVICE_UPDATE_ENTITY, async_handle_update_service, + schema=SCHEMA_UPDATE_ENTITY) + + async def async_handle_reload_config(call): + """Service handler for reloading core config.""" + try: + conf = await conf_util.async_hass_config_yaml(hass) + except HomeAssistantError as err: + _LOGGER.error(err) + return + + # auth only processed during startup + await conf_util.async_process_ha_core_config( + hass, conf.get(ha.DOMAIN) or {}) + + hass.services.async_register( + ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config) + + return True diff --git a/homeassistant/components/scene/homeassistant.py b/homeassistant/components/homeassistant/scene.py similarity index 95% rename from homeassistant/components/scene/homeassistant.py rename to homeassistant/components/homeassistant/scene.py index 96e24138b4a..617b5624110 100644 --- a/homeassistant/components/scene/homeassistant.py +++ b/homeassistant/components/homeassistant/scene.py @@ -3,13 +3,14 @@ from collections import namedtuple import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.scene import Scene, STATES from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_STATE, CONF_ENTITIES, CONF_NAME, CONF_PLATFORM, STATE_OFF, STATE_ON) from homeassistant.core import State -from homeassistant.helpers.state import async_reproduce_state, HASS_DOMAIN +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.state import HASS_DOMAIN, async_reproduce_state +from homeassistant.components.scene import STATES, Scene + PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_PLATFORM): HASS_DOMAIN, diff --git a/homeassistant/components/homekit_controller/.translations/bg.json b/homeassistant/components/homekit_controller/.translations/bg.json new file mode 100644 index 00000000000..be7d5d323ac --- /dev/null +++ b/homeassistant/components/homekit_controller/.translations/bg.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0435 \u0432\u0435\u0447\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e \u0441 \u0442\u043e\u0437\u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440.", + "already_paired": "\u0422\u043e\u0437\u0438 \u0430\u043a\u0441\u0435\u0441\u043e\u0430\u0440 \u0432\u0435\u0447\u0435 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 \u0434\u0440\u0443\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e. \u041c\u043e\u043b\u044f, \u0432\u044a\u0437\u0441\u0442\u0430\u043d\u043e\u0432\u0435\u0442\u0435 \u0437\u0430\u0432\u043e\u0434\u0441\u043a\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0438 \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e.", + "ignored_model": "\u041f\u043e\u0434\u0434\u0440\u044a\u0436\u043a\u0430\u0442\u0430 \u043d\u0430 HomeKit \u0437\u0430 \u0442\u043e\u0437\u0438 \u043c\u043e\u0434\u0435\u043b \u0435 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0430, \u0442\u044a\u0439 \u043a\u0430\u0442\u043e \u0435 \u043d\u0430\u043b\u0438\u0446\u0435 \u043f\u043e-\u043f\u044a\u043b\u043d\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u0442\u0430.", + "invalid_config_entry": "\u0422\u043e\u0432\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0441\u0435 \u043f\u043e\u043a\u0430\u0437\u0432\u0430 \u043a\u0430\u0442\u043e \u0433\u043e\u0442\u043e\u0432\u043e \u0437\u0430 \u0441\u0434\u0432\u043e\u044f\u0432\u0430\u043d\u0435, \u043d\u043e \u0432\u0435\u0447\u0435 \u0438\u043c\u0430 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430\u0449\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u0437\u0430 \u043d\u0435\u0433\u043e \u0432 Home Assistant, \u043a\u043e\u044f\u0442\u043e \u043f\u044a\u0440\u0432\u043e \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0431\u044a\u0434\u0435 \u043f\u0440\u0435\u043c\u0430\u0445\u043d\u0430\u0442\u0430.", + "no_devices": "\u041d\u0435 \u043c\u043e\u0433\u0430\u0442 \u0434\u0430 \u0431\u044a\u0434\u0430\u0442 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u043d\u0435\u0441\u0432\u044a\u0440\u0437\u0430\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" + }, + "error": { + "authentication_error": "\u0413\u0440\u0435\u0448\u0435\u043d HomeKit \u043a\u043e\u0434. \u041c\u043e\u043b\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0433\u043e \u0438 \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e.", + "unable_to_pair": "\u041d\u0435\u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442 \u0437\u0430 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435, \u043c\u043e\u043b\u044f \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e.", + "unknown_error": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0441\u044a\u043e\u0431\u0449\u0438 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430. \u0421\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u0431\u0435 \u043d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "step": { + "pair": { + "data": { + "pairing_code": "\u041a\u043e\u0434 \u0437\u0430 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" + }, + "description": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 HomeKit \u043a\u043e\u0434\u0430 \u0437\u0430 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0437\u0430 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0442\u043e\u0432\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e", + "title": "\u0421\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 HomeKit \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + }, + "user": { + "data": { + "device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + }, + "description": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e, \u0441 \u043a\u043e\u0435\u0442\u043e \u0438\u0441\u043a\u0430\u0442\u0435 \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0436\u0435\u0442\u0435", + "title": "\u0421\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 HomeKit \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + } + }, + "title": "HomeKit \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/.translations/da.json b/homeassistant/components/homekit_controller/.translations/da.json new file mode 100644 index 00000000000..3451053eb07 --- /dev/null +++ b/homeassistant/components/homekit_controller/.translations/da.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "device": "Enhed" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/.translations/de.json b/homeassistant/components/homekit_controller/.translations/de.json index 1f2dfe66dd2..126bb0362a8 100644 --- a/homeassistant/components/homekit_controller/.translations/de.json +++ b/homeassistant/components/homekit_controller/.translations/de.json @@ -18,7 +18,7 @@ "pairing_code": "Kopplungscode" }, "description": "Geben Sie Ihren HomeKit-Kopplungscode ein, um dieses Zubeh\u00f6r zu verwenden", - "title": "Kopplung mit {{ model }}" + "title": "Mit HomeKit Zubeh\u00f6r koppeln" }, "user": { "data": { diff --git a/homeassistant/components/homekit_controller/.translations/es-419.json b/homeassistant/components/homekit_controller/.translations/es-419.json new file mode 100644 index 00000000000..fb8d63080ce --- /dev/null +++ b/homeassistant/components/homekit_controller/.translations/es-419.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "El accesorio ya est\u00e1 configurado con este controlador.", + "already_paired": "Este accesorio ya est\u00e1 emparejado con otro dispositivo. Por favor, reinicie el accesorio y vuelva a intentarlo." + }, + "step": { + "user": { + "data": { + "device": "Dispositivo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/.translations/fr.json b/homeassistant/components/homekit_controller/.translations/fr.json new file mode 100644 index 00000000000..73cbbdf046a --- /dev/null +++ b/homeassistant/components/homekit_controller/.translations/fr.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "L'accessoire est d\u00e9j\u00e0 configur\u00e9 avec ce contr\u00f4leur.", + "already_paired": "Cet accessoire est d\u00e9j\u00e0 associ\u00e9 \u00e0 un autre appareil. R\u00e9initialisez l\u2019accessoire et r\u00e9essayez.", + "ignored_model": "La prise en charge de HomeKit pour ce mod\u00e8le est bloqu\u00e9e car une int\u00e9gration native plus compl\u00e8te est disponible.", + "invalid_config_entry": "Cet appareil est pr\u00eat \u00e0 \u00eatre coupl\u00e9, mais il existe d\u00e9j\u00e0 une entr\u00e9e de configuration en conflit dans Home Assistant \u00e0 supprimer.", + "no_devices": "Aucun appareil non appair\u00e9 n'a pu \u00eatre trouv\u00e9" + }, + "error": { + "authentication_error": "Code HomeKit incorrect. S'il vous pla\u00eet v\u00e9rifier et essayez \u00e0 nouveau.", + "unable_to_pair": "Impossible d'appairer, veuillez r\u00e9essayer.", + "unknown_error": "L'appareil a signal\u00e9 une erreur inconnue. L'appairage a \u00e9chou\u00e9." + }, + "step": { + "pair": { + "data": { + "pairing_code": "Code d\u2019appairage" + }, + "description": "Entrez votre code de jumelage HomeKit pour utiliser cet accessoire.", + "title": "Appairer avec l'accessoire HomeKit" + }, + "user": { + "data": { + "device": "Appareil" + }, + "description": "S\u00e9lectionnez l'appareil avec lequel vous voulez appairer", + "title": "Appairer avec l'accessoire HomeKit" + } + }, + "title": "Accessoire HomeKit" + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/.translations/sl.json b/homeassistant/components/homekit_controller/.translations/sl.json new file mode 100644 index 00000000000..afee189216d --- /dev/null +++ b/homeassistant/components/homekit_controller/.translations/sl.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "Dodatna oprema je \u017ee konfigurirana s tem krmilnikom.", + "already_paired": "Ta dodatna oprema je \u017ee povezana z drugo napravo. Ponastavite dodatno opremo in poskusite znova.", + "ignored_model": "Podpora za HomeKit za ta model je blokirana, saj je na voljo ve\u010d funkcij popolne nativne integracije.", + "invalid_config_entry": "Ta naprava se prikazuje kot pripravljena za povezavo, vendar je konflikt v nastavitvah Home Assistant, ki ga je treba najprej odstraniti.", + "no_devices": "Ni bilo mogo\u010de najti neuparjenih naprav" + }, + "error": { + "authentication_error": "Nepravilna koda HomeKit. Preverite in poskusite znova.", + "unable_to_pair": "Ni mogo\u010de seznaniti. Poskusite znova.", + "unknown_error": "Naprava je sporo\u010dila neznano napako. Seznanjanje ni uspelo." + }, + "step": { + "pair": { + "data": { + "pairing_code": "Koda za seznanjanje" + }, + "description": "Vnesi HomeKit kodo, \u010de \u017eeli\u0161 uporabiti to dodatno opremo", + "title": "Seznanite s HomeKit Opremo" + }, + "user": { + "data": { + "device": "Naprava" + }, + "description": "Izberite napravo, s katero se \u017eelite seznaniti", + "title": "Seznanite s HomeKit Opremo" + } + }, + "title": "HomeKit oprema" + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index ec38cf881d6..2a43d0ac9ce 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -1,24 +1,19 @@ """Support for Homekit device discovery.""" -import asyncio -import json import logging -import os from homeassistant.components.discovery import SERVICE_HOMEKIT from homeassistant.helpers import discovery from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import call_later +from .config_flow import load_old_pairings +from .connection import get_accessory_information, HKDevice from .const import ( - CONTROLLER, DOMAIN, HOMEKIT_ACCESSORY_DISPATCH, KNOWN_ACCESSORIES, - KNOWN_DEVICES + CONTROLLER, KNOWN_DEVICES ) - +from .const import DOMAIN # noqa: pylint: disable=unused-import REQUIREMENTS = ['homekit[IP]==0.13.0'] -HOMEKIT_DIR = '.homekit' - HOMEKIT_IGNORE = [ 'BSB002', 'Home Assistant Bridge', @@ -27,188 +22,21 @@ HOMEKIT_IGNORE = [ _LOGGER = logging.getLogger(__name__) -REQUEST_TIMEOUT = 5 # seconds -RETRY_INTERVAL = 60 # seconds - -PAIRING_FILE = "pairing.json" - - -def get_serial(accessory): - """Obtain the serial number of a HomeKit device.""" - # pylint: disable=import-error - from homekit.model.services import ServicesTypes - from homekit.model.characteristics import CharacteristicsTypes - - for service in accessory['services']: - if ServicesTypes.get_short(service['type']) != \ - 'accessory-information': - continue - for characteristic in service['characteristics']: - ctype = CharacteristicsTypes.get_short( - characteristic['type']) - if ctype != 'serial-number': - continue - return characteristic['value'] - return None - def escape_characteristic_name(char_name): """Escape any dash or dots in a characteristics name.""" return char_name.replace('-', '_').replace('.', '_') -class HKDevice(): - """HomeKit device.""" - - def __init__(self, hass, host, port, model, hkid, config_num, config): - """Initialise a generic HomeKit device.""" - _LOGGER.info("Setting up Homekit device %s", model) - self.hass = hass - self.controller = hass.data[CONTROLLER] - - self.host = host - self.port = port - self.model = model - self.hkid = hkid - self.config_num = config_num - self.config = config - self.configurator = hass.components.configurator - self._connection_warning_logged = False - - self.pairing_lock = asyncio.Lock(loop=hass.loop) - - self.pairing = self.controller.pairings.get(hkid) - - if self.pairing is not None: - self.accessory_setup() - else: - self.configure() - - def accessory_setup(self): - """Handle setup of a HomeKit accessory.""" - # pylint: disable=import-error - from homekit.model.services import ServicesTypes - from homekit.exceptions import AccessoryDisconnectedError - - self.pairing.pairing_data['AccessoryIP'] = self.host - self.pairing.pairing_data['AccessoryPort'] = self.port - - try: - data = self.pairing.list_accessories_and_characteristics() - except AccessoryDisconnectedError: - call_later( - self.hass, RETRY_INTERVAL, lambda _: self.accessory_setup()) - return - for accessory in data: - serial = get_serial(accessory) - if serial in self.hass.data[KNOWN_ACCESSORIES]: - continue - self.hass.data[KNOWN_ACCESSORIES][serial] = self - aid = accessory['aid'] - for service in accessory['services']: - devtype = ServicesTypes.get_short(service['type']) - _LOGGER.debug("Found %s", devtype) - service_info = {'serial': serial, - 'aid': aid, - 'iid': service['iid'], - 'model': self.model, - 'device-type': devtype} - component = HOMEKIT_ACCESSORY_DISPATCH.get(devtype, None) - if component is not None: - discovery.load_platform(self.hass, component, DOMAIN, - service_info, self.config) - - def device_config_callback(self, callback_data): - """Handle initial pairing.""" - import homekit # pylint: disable=import-error - code = callback_data.get('code').strip() - try: - self.controller.perform_pairing(self.hkid, self.hkid, code) - except homekit.UnavailableError: - error_msg = "This accessory is already paired to another device. \ - Please reset the accessory and try again." - _configurator = self.hass.data[DOMAIN+self.hkid] - self.configurator.notify_errors(_configurator, error_msg) - return - except homekit.AuthenticationError: - error_msg = "Incorrect HomeKit code for {}. Please check it and \ - try again.".format(self.model) - _configurator = self.hass.data[DOMAIN+self.hkid] - self.configurator.notify_errors(_configurator, error_msg) - return - except homekit.UnknownError: - error_msg = "Received an unknown error. Please file a bug." - _configurator = self.hass.data[DOMAIN+self.hkid] - self.configurator.notify_errors(_configurator, error_msg) - raise - - self.pairing = self.controller.pairings.get(self.hkid) - if self.pairing is not None: - pairing_file = os.path.join( - self.hass.config.path(), - HOMEKIT_DIR, - PAIRING_FILE, - ) - self.controller.save_data(pairing_file) - _configurator = self.hass.data[DOMAIN+self.hkid] - self.configurator.request_done(_configurator) - self.accessory_setup() - else: - error_msg = "Unable to pair, please try again" - _configurator = self.hass.data[DOMAIN+self.hkid] - self.configurator.notify_errors(_configurator, error_msg) - - def configure(self): - """Obtain the pairing code for a HomeKit device.""" - description = "Please enter the HomeKit code for your {}".format( - self.model) - self.hass.data[DOMAIN+self.hkid] = \ - self.configurator.request_config(self.model, - self.device_config_callback, - description=description, - submit_caption="submit", - fields=[{'id': 'code', - 'name': 'HomeKit code', - 'type': 'string'}]) - - async def get_characteristics(self, *args, **kwargs): - """Read latest state from homekit accessory.""" - async with self.pairing_lock: - chars = await self.hass.async_add_executor_job( - self.pairing.get_characteristics, - *args, - **kwargs, - ) - return chars - - async def put_characteristics(self, characteristics): - """Control a HomeKit device state from Home Assistant.""" - chars = [] - for row in characteristics: - chars.append(( - row['aid'], - row['iid'], - row['value'], - )) - - async with self.pairing_lock: - await self.hass.async_add_executor_job( - self.pairing.put_characteristics, - chars - ) - - class HomeKitEntity(Entity): """Representation of a Home Assistant HomeKit device.""" def __init__(self, accessory, devinfo): """Initialise a generic HomeKit device.""" self._available = True - self._name = accessory.model self._accessory = accessory self._aid = devinfo['aid'] self._iid = devinfo['iid'] - self._address = "homekit-{}-{}".format(devinfo['serial'], self._iid) self._features = 0 self._chars = {} self.setup() @@ -232,6 +60,7 @@ class HomeKitEntity(Entity): for accessory in pairing_data.get('accessories', []): if accessory['aid'] != self._aid: continue + self._accessory_info = get_accessory_information(accessory) for service in accessory['services']: if service['iid'] != self._iid: continue @@ -304,12 +133,13 @@ class HomeKitEntity(Entity): @property def unique_id(self): """Return the ID of this device.""" - return self._address + serial = self._accessory_info['serial-number'] + return "homekit-{}-{}".format(serial, self._iid) @property def name(self): """Return the name of the device if any.""" - return self._name + return self._accessory_info.get('name') @property def available(self) -> bool: @@ -329,25 +159,8 @@ def setup(hass, config): hass.data[CONTROLLER] = controller = homekit.Controller() - data_dir = os.path.join(hass.config.path(), HOMEKIT_DIR) - if not os.path.isdir(data_dir): - os.mkdir(data_dir) - - pairing_file = os.path.join(data_dir, PAIRING_FILE) - if os.path.exists(pairing_file): - controller.load_data(pairing_file) - - # Migrate any existing pairings to the new internal homekit_python format - for device in os.listdir(data_dir): - if not device.startswith('hk-'): - continue - alias = device[3:] - if alias in controller.pairings: - continue - with open(os.path.join(data_dir, device)) as pairing_data_fp: - pairing_data = json.load(pairing_data_fp) - controller.pairings[alias] = IpPairing(pairing_data) - controller.save_data(pairing_file) + for hkid, pairing_data in load_old_pairings(hass).items(): + controller.pairings[hkid] = IpPairing(pairing_data) def discovery_dispatch(service, discovery_info): """Dispatcher for Homekit discovery events.""" @@ -378,10 +191,8 @@ def setup(hass, config): return _LOGGER.debug('Discovered unique device %s', hkid) - device = HKDevice(hass, host, port, model, hkid, config_num, config) - hass.data[KNOWN_DEVICES][hkid] = device + HKDevice(hass, host, port, model, hkid, config_num, config) - hass.data[KNOWN_ACCESSORIES] = {} hass.data[KNOWN_DEVICES] = {} discovery.listen(hass, SERVICE_HOMEKIT, discovery_dispatch) return True diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index 61352c3bedc..f9bc25f4237 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -2,12 +2,12 @@ import logging from homeassistant.components.alarm_control_panel import AlarmControlPanel -from homeassistant.components.homekit_controller import ( - KNOWN_ACCESSORIES, HomeKitEntity) from homeassistant.const import ( ATTR_BATTERY_LEVEL, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) +from . import KNOWN_DEVICES, HomeKitEntity + DEPENDENCIES = ['homekit_controller'] ICON = 'mdi:security' @@ -34,7 +34,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Homekit Alarm Control Panel support.""" if discovery_info is None: return - accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']] + accessory = hass.data[KNOWN_DEVICES][discovery_info['serial']] add_entities([HomeKitAlarmControlPanel(accessory, discovery_info)], True) diff --git a/homeassistant/components/homekit_controller/binary_sensor.py b/homeassistant/components/homekit_controller/binary_sensor.py index 5d83ce6d984..2bd03b18932 100644 --- a/homeassistant/components/homekit_controller/binary_sensor.py +++ b/homeassistant/components/homekit_controller/binary_sensor.py @@ -2,8 +2,8 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.homekit_controller import ( - KNOWN_ACCESSORIES, HomeKitEntity) + +from . import KNOWN_DEVICES, HomeKitEntity DEPENDENCIES = ['homekit_controller'] @@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Homekit motion sensor support.""" if discovery_info is not None: - accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']] + accessory = hass.data[KNOWN_DEVICES][discovery_info['serial']] add_entities([HomeKitMotionSensor(accessory, discovery_info)], True) diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index 8696d2b1f97..67f1fb72bcf 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -3,11 +3,11 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - STATE_HEAT, STATE_COOL, STATE_IDLE, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE) -from homeassistant.components.homekit_controller import ( - HomeKitEntity, KNOWN_ACCESSORIES) -from homeassistant.const import TEMP_CELSIUS, STATE_OFF, ATTR_TEMPERATURE + STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE) +from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS + +from . import KNOWN_DEVICES, HomeKitEntity DEPENDENCIES = ['homekit_controller'] @@ -29,7 +29,7 @@ DEFAULT_VALID_MODES = list(MODE_HOMEKIT_TO_HASS) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Homekit climate.""" if discovery_info is not None: - accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']] + accessory = hass.data[KNOWN_DEVICES][discovery_info['serial']] add_entities([HomeKitClimateDevice(accessory, discovery_info)], True) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 5550846120b..2ca568b547f 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -1,4 +1,20 @@ """Helpers for managing a pairing with a HomeKit accessory or bridge.""" +import asyncio +import logging +import os + +from homeassistant.helpers import discovery +from homeassistant.helpers.event import call_later + +from .const import ( + CONTROLLER, DOMAIN, HOMEKIT_ACCESSORY_DISPATCH, KNOWN_DEVICES, + PAIRING_FILE, HOMEKIT_DIR +) + + +RETRY_INTERVAL = 60 # seconds + +_LOGGER = logging.getLogger(__name__) def get_accessory_information(accessory): @@ -33,3 +49,151 @@ def get_accessory_name(accessory_info): if field in accessory_info: return accessory_info[field] return None + + +class HKDevice(): + """HomeKit device.""" + + def __init__(self, hass, host, port, model, hkid, config_num, config): + """Initialise a generic HomeKit device.""" + _LOGGER.info("Setting up Homekit device %s", model) + self.hass = hass + self.controller = hass.data[CONTROLLER] + + self.host = host + self.port = port + self.model = model + self.hkid = hkid + self.config_num = config_num + self.config = config + self.configurator = hass.components.configurator + self._connection_warning_logged = False + + # This just tracks aid/iid pairs so we know if a HK service has been + # mapped to a HA entity. + self.entities = [] + + self.pairing_lock = asyncio.Lock(loop=hass.loop) + + self.pairing = self.controller.pairings.get(hkid) + + hass.data[KNOWN_DEVICES][hkid] = self + + if self.pairing is not None: + self.accessory_setup() + else: + self.configure() + + def accessory_setup(self): + """Handle setup of a HomeKit accessory.""" + # pylint: disable=import-error + from homekit.model.services import ServicesTypes + from homekit.exceptions import AccessoryDisconnectedError + + self.pairing.pairing_data['AccessoryIP'] = self.host + self.pairing.pairing_data['AccessoryPort'] = self.port + + try: + data = self.pairing.list_accessories_and_characteristics() + except AccessoryDisconnectedError: + call_later( + self.hass, RETRY_INTERVAL, lambda _: self.accessory_setup()) + return + for accessory in data: + aid = accessory['aid'] + for service in accessory['services']: + iid = service['iid'] + if (aid, iid) in self.entities: + # Don't add the same entity again + continue + + devtype = ServicesTypes.get_short(service['type']) + _LOGGER.debug("Found %s", devtype) + service_info = {'serial': self.hkid, + 'aid': aid, + 'iid': service['iid'], + 'model': self.model, + 'device-type': devtype} + component = HOMEKIT_ACCESSORY_DISPATCH.get(devtype, None) + if component is not None: + discovery.load_platform(self.hass, component, DOMAIN, + service_info, self.config) + + def device_config_callback(self, callback_data): + """Handle initial pairing.""" + import homekit # pylint: disable=import-error + code = callback_data.get('code').strip() + try: + self.controller.perform_pairing(self.hkid, self.hkid, code) + except homekit.UnavailableError: + error_msg = "This accessory is already paired to another device. \ + Please reset the accessory and try again." + _configurator = self.hass.data[DOMAIN+self.hkid] + self.configurator.notify_errors(_configurator, error_msg) + return + except homekit.AuthenticationError: + error_msg = "Incorrect HomeKit code for {}. Please check it and \ + try again.".format(self.model) + _configurator = self.hass.data[DOMAIN+self.hkid] + self.configurator.notify_errors(_configurator, error_msg) + return + except homekit.UnknownError: + error_msg = "Received an unknown error. Please file a bug." + _configurator = self.hass.data[DOMAIN+self.hkid] + self.configurator.notify_errors(_configurator, error_msg) + raise + + self.pairing = self.controller.pairings.get(self.hkid) + if self.pairing is not None: + pairing_file = os.path.join( + self.hass.config.path(), + HOMEKIT_DIR, + PAIRING_FILE, + ) + self.controller.save_data(pairing_file) + _configurator = self.hass.data[DOMAIN+self.hkid] + self.configurator.request_done(_configurator) + self.accessory_setup() + else: + error_msg = "Unable to pair, please try again" + _configurator = self.hass.data[DOMAIN+self.hkid] + self.configurator.notify_errors(_configurator, error_msg) + + def configure(self): + """Obtain the pairing code for a HomeKit device.""" + description = "Please enter the HomeKit code for your {}".format( + self.model) + self.hass.data[DOMAIN+self.hkid] = \ + self.configurator.request_config(self.model, + self.device_config_callback, + description=description, + submit_caption="submit", + fields=[{'id': 'code', + 'name': 'HomeKit code', + 'type': 'string'}]) + + async def get_characteristics(self, *args, **kwargs): + """Read latest state from homekit accessory.""" + async with self.pairing_lock: + chars = await self.hass.async_add_executor_job( + self.pairing.get_characteristics, + *args, + **kwargs, + ) + return chars + + async def put_characteristics(self, characteristics): + """Control a HomeKit device state from Home Assistant.""" + chars = [] + for row in characteristics: + chars.append(( + row['aid'], + row['iid'], + row['value'], + )) + + async with self.pairing_lock: + await self.hass.async_add_executor_job( + self.pairing.put_characteristics, + chars + ) diff --git a/homeassistant/components/homekit_controller/const.py b/homeassistant/components/homekit_controller/const.py index 873f6b343d2..de9663f1202 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -1,10 +1,12 @@ """Constants for the homekit_controller component.""" DOMAIN = 'homekit_controller' -KNOWN_ACCESSORIES = "{}-accessories".format(DOMAIN) KNOWN_DEVICES = "{}-devices".format(DOMAIN) CONTROLLER = "{}-controller".format(DOMAIN) +HOMEKIT_DIR = '.homekit' +PAIRING_FILE = 'pairing.json' + # Mapping from Homekit type to component. HOMEKIT_ACCESSORY_DISPATCH = { 'lightbulb': 'light', diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index ccb1939e141..26b7613ed2b 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -5,11 +5,11 @@ from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, SUPPORT_CLOSE, SUPPORT_CLOSE_TILT, SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION, CoverDevice) -from homeassistant.components.homekit_controller import ( - KNOWN_ACCESSORIES, HomeKitEntity) from homeassistant.const import ( STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING) +from . import KNOWN_DEVICES, HomeKitEntity + STATE_STOPPED = 'stopped' DEPENDENCIES = ['homekit_controller'] @@ -41,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up HomeKit Cover support.""" if discovery_info is None: return - accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']] + accessory = hass.data[KNOWN_DEVICES][discovery_info['serial']] if discovery_info['device-type'] == 'garage-door-opener': add_entities([HomeKitGarageDoorCover(accessory, discovery_info)], @@ -74,26 +74,14 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice): CharacteristicsTypes.DOOR_STATE_CURRENT, CharacteristicsTypes.DOOR_STATE_TARGET, CharacteristicsTypes.OBSTRUCTION_DETECTED, - CharacteristicsTypes.NAME, ] - def _setup_name(self, char): - self._name = char['value'] - def _update_door_state_current(self, value): self._state = CURRENT_GARAGE_STATE_MAP[value] def _update_obstruction_detected(self, value): self._obstruction_detected = value - def _update_name(self, value): - self._name = value - - @property - def available(self): - """Return True if entity is available.""" - return self._state is not None - @property def supported_features(self): """Flag supported features.""" @@ -153,11 +141,6 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): self._obstruction_detected = None self.lock_state = None - @property - def available(self): - """Return True if entity is available.""" - return self._state is not None - def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" # pylint: disable=import-error @@ -172,12 +155,8 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): CharacteristicsTypes.HORIZONTAL_TILT_CURRENT, CharacteristicsTypes.HORIZONTAL_TILT_TARGET, CharacteristicsTypes.OBSTRUCTION_DETECTED, - CharacteristicsTypes.NAME, ] - def _setup_name(self, char): - self._name = char['value'] - def _update_position_state(self, value): self._state = CURRENT_WINDOW_STATE_MAP[value] diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py index b5677c0e095..cb9259df4a9 100644 --- a/homeassistant/components/homekit_controller/light.py +++ b/homeassistant/components/homekit_controller/light.py @@ -1,12 +1,12 @@ """Support for Homekit lights.""" import logging -from homeassistant.components.homekit_controller import ( - KNOWN_ACCESSORIES, HomeKitEntity) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, Light) +from . import KNOWN_DEVICES, HomeKitEntity + DEPENDENCIES = ['homekit_controller'] _LOGGER = logging.getLogger(__name__) @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Homekit lighting.""" if discovery_info is not None: - accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']] + accessory = hass.data[KNOWN_DEVICES][discovery_info['serial']] add_entities([HomeKitLight(accessory, discovery_info)], True) diff --git a/homeassistant/components/homekit_controller/lock.py b/homeassistant/components/homekit_controller/lock.py index 6da5fa35655..0d0275fda16 100644 --- a/homeassistant/components/homekit_controller/lock.py +++ b/homeassistant/components/homekit_controller/lock.py @@ -1,12 +1,12 @@ """Support for HomeKit Controller locks.""" import logging -from homeassistant.components.homekit_controller import ( - KNOWN_ACCESSORIES, HomeKitEntity) from homeassistant.components.lock import LockDevice from homeassistant.const import ( ATTR_BATTERY_LEVEL, STATE_LOCKED, STATE_UNLOCKED) +from . import KNOWN_DEVICES, HomeKitEntity + DEPENDENCIES = ['homekit_controller'] _LOGGER = logging.getLogger(__name__) @@ -30,7 +30,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Homekit Lock support.""" if discovery_info is None: return - accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']] + accessory = hass.data[KNOWN_DEVICES][discovery_info['serial']] add_entities([HomeKitLock(accessory, discovery_info)], True) @@ -41,7 +41,6 @@ class HomeKitLock(HomeKitEntity, LockDevice): """Initialise the Lock.""" super().__init__(accessory, discovery_info) self._state = None - self._name = discovery_info['model'] self._battery_level = None def get_characteristic_types(self): @@ -60,21 +59,11 @@ class HomeKitLock(HomeKitEntity, LockDevice): def _update_battery_level(self, value): self._battery_level = value - @property - def name(self): - """Return the name of this device.""" - return self._name - @property def is_locked(self): """Return true if device is locked.""" return self._state == STATE_LOCKED - @property - def available(self): - """Return True if entity is available.""" - return self._state is not None - async def async_lock(self, **kwargs): """Lock the device.""" await self._set_lock_state(STATE_LOCKED) diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py index 5af0016eb16..8cbc8f248ba 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -1,8 +1,8 @@ """Support for Homekit sensors.""" -from homeassistant.components.homekit_controller import ( - KNOWN_ACCESSORIES, HomeKitEntity) from homeassistant.const import TEMP_CELSIUS +from . import KNOWN_DEVICES, HomeKitEntity + DEPENDENCIES = ['homekit_controller'] HUMIDITY_ICON = 'mdi-water-percent' @@ -16,7 +16,7 @@ UNIT_LUX = "lux" def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Homekit sensor support.""" if discovery_info is not None: - accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']] + accessory = hass.data[KNOWN_DEVICES][discovery_info['serial']] devtype = discovery_info['device-type'] if devtype == 'humidity': diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index 21f10e6243c..34e83c06526 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -1,10 +1,10 @@ """Support for Homekit switches.""" import logging -from homeassistant.components.homekit_controller import ( - KNOWN_ACCESSORIES, HomeKitEntity) from homeassistant.components.switch import SwitchDevice +from . import KNOWN_DEVICES, HomeKitEntity + DEPENDENCIES = ['homekit_controller'] OUTLET_IN_USE = "outlet_in_use" @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Homekit switch support.""" if discovery_info is not None: - accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']] + accessory = hass.data[KNOWN_DEVICES][discovery_info['serial']] add_entities([HomeKitSwitch(accessory, discovery_info)], True) diff --git a/homeassistant/components/homematic/binary_sensor.py b/homeassistant/components/homematic/binary_sensor.py index 1704411c9cc..7bf260a9bdc 100644 --- a/homeassistant/components/homematic/binary_sensor.py +++ b/homeassistant/components/homematic/binary_sensor.py @@ -2,9 +2,10 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice from homeassistant.const import STATE_UNKNOWN +from . import ATTR_DISCOVER_DEVICES, HMDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['homematic'] diff --git a/homeassistant/components/homematic/climate.py b/homeassistant/components/homematic/climate.py index e5eb292b4ff..146cad1bc4c 100644 --- a/homeassistant/components/homematic/climate.py +++ b/homeassistant/components/homematic/climate.py @@ -5,10 +5,10 @@ from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( STATE_AUTO, STATE_MANUAL, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) -from homeassistant.components.homematic import ( - ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT, HMDevice) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from . import ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT, HMDevice + DEPENDENCIES = ['homematic'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homematic/cover.py b/homeassistant/components/homematic/cover.py index 79a1afe9a0e..33b764dc31f 100644 --- a/homeassistant/components/homematic/cover.py +++ b/homeassistant/components/homematic/cover.py @@ -3,9 +3,10 @@ import logging from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, CoverDevice) -from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice from homeassistant.const import STATE_UNKNOWN +from . import ATTR_DISCOVER_DEVICES, HMDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['homematic'] diff --git a/homeassistant/components/homematic/light.py b/homeassistant/components/homematic/light.py index 21b875742c4..c3601461173 100644 --- a/homeassistant/components/homematic/light.py +++ b/homeassistant/components/homematic/light.py @@ -1,11 +1,12 @@ """Support for Homematic lights.""" import logging -from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_EFFECT, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_EFFECT, Light) +from . import ATTR_DISCOVER_DEVICES, HMDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['homematic'] diff --git a/homeassistant/components/homematic/lock.py b/homeassistant/components/homematic/lock.py index 5d857617fde..3c0ca040c5f 100644 --- a/homeassistant/components/homematic/lock.py +++ b/homeassistant/components/homematic/lock.py @@ -1,10 +1,11 @@ """Support for Homematic locks.""" import logging -from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice from homeassistant.components.lock import SUPPORT_OPEN, LockDevice from homeassistant.const import STATE_UNKNOWN +from . import ATTR_DISCOVER_DEVICES, HMDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['homematic'] diff --git a/homeassistant/components/homematic/notify.py b/homeassistant/components/homematic/notify.py index e6ef1a60e28..021560eee3c 100644 --- a/homeassistant/components/homematic/notify.py +++ b/homeassistant/components/homematic/notify.py @@ -8,14 +8,15 @@ import logging import voluptuous as vol -from homeassistant.components.homematic import ( - ATTR_ADDRESS, ATTR_CHANNEL, ATTR_INTERFACE, ATTR_PARAM, ATTR_VALUE, DOMAIN, - SERVICE_SET_DEVICE_VALUE) from homeassistant.components.notify import ( ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService) import homeassistant.helpers.config_validation as cv import homeassistant.helpers.template as template_helper +from . import ( + ATTR_ADDRESS, ATTR_CHANNEL, ATTR_INTERFACE, ATTR_PARAM, ATTR_VALUE, DOMAIN, + SERVICE_SET_DEVICE_VALUE) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["homematic"] diff --git a/homeassistant/components/homematic/sensor.py b/homeassistant/components/homematic/sensor.py index 8e3e55e1f7f..401d11f70c8 100644 --- a/homeassistant/components/homematic/sensor.py +++ b/homeassistant/components/homematic/sensor.py @@ -1,8 +1,9 @@ """Support for HomeMatic sensors.""" import logging -from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice -from homeassistant.const import STATE_UNKNOWN, POWER_WATT, ENERGY_WATT_HOUR +from homeassistant.const import ENERGY_WATT_HOUR, POWER_WATT, STATE_UNKNOWN + +from . import ATTR_DISCOVER_DEVICES, HMDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homematic/switch.py b/homeassistant/components/homematic/switch.py index cfcd26891e0..393ad09b310 100644 --- a/homeassistant/components/homematic/switch.py +++ b/homeassistant/components/homematic/switch.py @@ -1,10 +1,11 @@ """Support for HomeMatic switches.""" import logging -from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice from homeassistant.components.switch import SwitchDevice from homeassistant.const import STATE_UNKNOWN +from . import ATTR_DISCOVER_DEVICES, HMDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['homematic'] diff --git a/homeassistant/components/homematicip_cloud/.translations/ru.json b/homeassistant/components/homematicip_cloud/.translations/ru.json index e1aec6162f4..dde9345b2d2 100644 --- a/homeassistant/components/homematicip_cloud/.translations/ru.json +++ b/homeassistant/components/homematicip_cloud/.translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u0422\u043e\u0447\u043a\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430", + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", "connection_aborted": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 HMIP", "unknown": "\u0412\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, diff --git a/homeassistant/components/homematicip_cloud/.translations/th.json b/homeassistant/components/homematicip_cloud/.translations/th.json new file mode 100644 index 00000000000..cae3361a6ec --- /dev/null +++ b/homeassistant/components/homematicip_cloud/.translations/th.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "\u0e08\u0e38\u0e14\u0e40\u0e0a\u0e37\u0e48\u0e2d\u0e21\u0e15\u0e48\u0e2d (AP) \u0e44\u0e14\u0e49\u0e17\u0e33\u0e01\u0e32\u0e23\u0e01\u0e33\u0e2b\u0e19\u0e14\u0e04\u0e48\u0e32\u0e41\u0e25\u0e49\u0e27" + }, + "step": { + "init": { + "data": { + "hapid": "\u0e44\u0e2d\u0e14\u0e35\u0e08\u0e38\u0e14\u0e40\u0e02\u0e49\u0e32\u0e43\u0e0a\u0e49\u0e07\u0e32\u0e19 (SGTIN)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/alarm_control_panel.py b/homeassistant/components/homematicip_cloud/alarm_control_panel.py index efa1ea1f46e..eb5855bb980 100644 --- a/homeassistant/components/homematicip_cloud/alarm_control_panel.py +++ b/homeassistant/components/homematicip_cloud/alarm_control_panel.py @@ -2,12 +2,12 @@ import logging from homeassistant.components.alarm_control_panel import AlarmControlPanel -from homeassistant.components.homematicip_cloud import ( - DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) +from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['homematicip_cloud'] diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index 9445d6521cc..786a28a70a5 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -2,10 +2,9 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.homematicip_cloud import ( - DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) -from homeassistant.components.homematicip_cloud.device import ( - ATTR_GROUP_MEMBER_UNREACHABLE) + +from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice +from .device import ATTR_GROUP_MEMBER_UNREACHABLE DEPENDENCIES = ['homematicip_cloud'] diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index 08c88bbb796..955f3e5baa7 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -4,10 +4,10 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( STATE_AUTO, STATE_MANUAL, SUPPORT_TARGET_TEMPERATURE) -from homeassistant.components.homematicip_cloud import ( - DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice + _LOGGER = logging.getLogger(__name__) STATE_BOOST = 'Boost' diff --git a/homeassistant/components/homematicip_cloud/const.py b/homeassistant/components/homematicip_cloud/const.py index fbda56f2805..c9a5df601e4 100644 --- a/homeassistant/components/homematicip_cloud/const.py +++ b/homeassistant/components/homematicip_cloud/const.py @@ -1,7 +1,7 @@ """Constants for the HomematicIP Cloud component.""" import logging -_LOGGER = logging.getLogger('homeassistant.components.homematicip_cloud') +_LOGGER = logging.getLogger('.') DOMAIN = 'homematicip_cloud' diff --git a/homeassistant/components/homematicip_cloud/cover.py b/homeassistant/components/homematicip_cloud/cover.py index 86c11dab70d..735e8789670 100644 --- a/homeassistant/components/homematicip_cloud/cover.py +++ b/homeassistant/components/homematicip_cloud/cover.py @@ -2,8 +2,8 @@ import logging from homeassistant.components.cover import ATTR_POSITION, CoverDevice -from homeassistant.components.homematicip_cloud import ( - DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) + +from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice DEPENDENCIES = ['homematicip_cloud'] diff --git a/homeassistant/components/homematicip_cloud/device.py b/homeassistant/components/homematicip_cloud/device.py index 9940e6960db..0b815d0ec7e 100644 --- a/homeassistant/components/homematicip_cloud/device.py +++ b/homeassistant/components/homematicip_cloud/device.py @@ -6,21 +6,13 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_CONNECTED = 'connected' -ATTR_DEVICE_ID = 'device_id' -ATTR_DEVICE_LABEL = 'device_label' -ATTR_DEVICE_RSSI = 'device_rssi' -ATTR_DUTY_CYCLE = 'duty_cycle' -ATTR_FIRMWARE_STATE = 'firmware_state' -ATTR_GROUP_TYPE = 'group_type' -ATTR_HOME_ID = 'home_id' -ATTR_HOME_NAME = 'home_name' ATTR_LOW_BATTERY = 'low_battery' ATTR_MODEL_TYPE = 'model_type' -ATTR_OPERATION_LOCK = 'operation_lock' +# RSSI HAP -> Device +ATTR_RSSI_DEVICE = 'rssi_device' +# RSSI Device -> HAP +ATTR_RSSI_PEER = 'rssi_peer' ATTR_SABOTAGE = 'sabotage' -ATTR_STATUS_UPDATE = 'status_update' -ATTR_UNREACHABLE = 'unreachable' ATTR_GROUP_MEMBER_UNREACHABLE = 'group_member_unreachable' @@ -101,7 +93,13 @@ class HomematicipGenericDevice(Entity): """Return the state attributes of the generic device.""" attr = {ATTR_MODEL_TYPE: self._device.modelType} if hasattr(self._device, 'lowBat') and self._device.lowBat: - attr.update({ATTR_LOW_BATTERY: self._device.lowBat}) + attr[ATTR_LOW_BATTERY] = self._device.lowBat if hasattr(self._device, 'sabotage') and self._device.sabotage: - attr.update({ATTR_SABOTAGE: self._device.sabotage}) + attr[ATTR_SABOTAGE] = self._device.sabotage + if hasattr(self._device, 'rssiDeviceValue') and \ + self._device.rssiDeviceValue: + attr[ATTR_RSSI_DEVICE] = self._device.rssiDeviceValue + if hasattr(self._device, 'rssiPeerValue') and \ + self._device.rssiPeerValue: + attr[ATTR_RSSI_PEER] = self._device.rssiPeerValue return attr diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index 73c607683ba..f8b19b5bb1e 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -1,12 +1,12 @@ """Support for HomematicIP Cloud lights.""" import logging -from homeassistant.components.homematicip_cloud import ( - DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_NAME, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, Light) +from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice + DEPENDENCIES = ['homematicip_cloud'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index d6155998332..39758739400 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -1,12 +1,12 @@ """Support for HomematicIP Cloud sensors.""" import logging -from homeassistant.components.homematicip_cloud import ( - DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, POWER_WATT, TEMP_CELSIUS) +from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['homematicip_cloud'] diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index 74f50f87b25..62e72f0ade7 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -1,12 +1,11 @@ """Support for HomematicIP Cloud switches.""" import logging -from homeassistant.components.homematicip_cloud import ( - DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) -from homeassistant.components.homematicip_cloud.device import ( - ATTR_GROUP_MEMBER_UNREACHABLE) from homeassistant.components.switch import SwitchDevice +from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice +from .device import ATTR_GROUP_MEMBER_UNREACHABLE + DEPENDENCIES = ['homematicip_cloud'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homematicip_cloud/weather.py b/homeassistant/components/homematicip_cloud/weather.py index 5a6261195da..101adcdeaaa 100644 --- a/homeassistant/components/homematicip_cloud/weather.py +++ b/homeassistant/components/homematicip_cloud/weather.py @@ -2,10 +2,10 @@ """Support for HomematicIP Cloud weather devices.""" import logging -from homeassistant.components.homematicip_cloud import ( - DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) from homeassistant.components.weather import WeatherEntity +from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice + DEPENDENCIES = ['homematicip_cloud'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py index 7f5d7f6aab7..ca41dff9834 100644 --- a/homeassistant/components/homeworks/light.py +++ b/homeassistant/components/homeworks/light.py @@ -1,15 +1,16 @@ """Support for Lutron Homeworks lights.""" import logging -from homeassistant.components.homeworks import ( - CONF_ADDR, CONF_DIMMERS, CONF_RATE, ENTITY_SIGNAL, HOMEWORKS_CONTROLLER, - HomeworksDevice) from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) from homeassistant.const import CONF_NAME from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from . import ( + CONF_ADDR, CONF_DIMMERS, CONF_RATE, ENTITY_SIGNAL, HOMEWORKS_CONTROLLER, + HomeworksDevice) + DEPENDENCIES = ['homeworks'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/honeywell/__init__.py b/homeassistant/components/honeywell/__init__.py new file mode 100644 index 00000000000..59a90711e57 --- /dev/null +++ b/homeassistant/components/honeywell/__init__.py @@ -0,0 +1 @@ +"""Support for Honeywell Round Connected and Honeywell Evohome thermostats.""" diff --git a/homeassistant/components/climate/honeywell.py b/homeassistant/components/honeywell/climate.py similarity index 100% rename from homeassistant/components/climate/honeywell.py rename to homeassistant/components/honeywell/climate.py diff --git a/homeassistant/components/hook/__init__.py b/homeassistant/components/hook/__init__.py new file mode 100644 index 00000000000..bc85e27d742 --- /dev/null +++ b/homeassistant/components/hook/__init__.py @@ -0,0 +1 @@ +"""The hook component.""" diff --git a/homeassistant/components/switch/hook.py b/homeassistant/components/hook/switch.py similarity index 100% rename from homeassistant/components/switch/hook.py rename to homeassistant/components/hook/switch.py diff --git a/homeassistant/components/horizon/__init__.py b/homeassistant/components/horizon/__init__.py new file mode 100644 index 00000000000..77ac25098c3 --- /dev/null +++ b/homeassistant/components/horizon/__init__.py @@ -0,0 +1 @@ +"""The horizon component.""" diff --git a/homeassistant/components/media_player/horizon.py b/homeassistant/components/horizon/media_player.py similarity index 96% rename from homeassistant/components/media_player/horizon.py rename to homeassistant/components/horizon/media_player.py index e7cfcfe62b1..3b1ae36152d 100644 --- a/homeassistant/components/media_player/horizon.py +++ b/homeassistant/components/horizon/media_player.py @@ -21,7 +21,7 @@ from homeassistant.const import ( from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['einder==0.3.1'] +REQUIREMENTS = ['horimote==0.4.1'] _LOGGER = logging.getLogger(__name__) @@ -44,8 +44,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Horizon platform.""" - from einder import Client, keys - from einder.exceptions import AuthenticationError + from horimote import Client, keys + from horimote.exceptions import AuthenticationError host = config[CONF_HOST] name = config[CONF_NAME] @@ -162,7 +162,7 @@ class HorizonDevice(MediaPlayerDevice): def _send(self, key=None, channel=None): """Send a key to the Horizon device.""" - from einder.exceptions import AuthenticationError + from horimote.exceptions import AuthenticationError try: if key: diff --git a/homeassistant/components/hp_ilo/__init__.py b/homeassistant/components/hp_ilo/__init__.py new file mode 100644 index 00000000000..67135b947e4 --- /dev/null +++ b/homeassistant/components/hp_ilo/__init__.py @@ -0,0 +1 @@ +"""The HP Integrated Lights-Out (iLO) component.""" diff --git a/homeassistant/components/sensor/hp_ilo.py b/homeassistant/components/hp_ilo/sensor.py similarity index 87% rename from homeassistant/components/sensor/hp_ilo.py rename to homeassistant/components/hp_ilo/sensor.py index ac2d5ccb109..a017f0ee3e8 100644 --- a/homeassistant/components/sensor/hp_ilo.py +++ b/homeassistant/components/hp_ilo/sensor.py @@ -1,28 +1,23 @@ -""" -Support for information from HP ILO sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.sensor.hp_ilo/ -""" -import logging +"""Support for information from HP iLO sensors.""" from datetime import timedelta +import logging import voluptuous as vol -from homeassistant.const import ( - CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_NAME, - CONF_MONITORED_VARIABLES, CONF_VALUE_TEMPLATE, CONF_SENSOR_TYPE, - CONF_UNIT_OF_MEASUREMENT) from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_HOST, CONF_MONITORED_VARIABLES, CONF_NAME, CONF_PASSWORD, CONF_PORT, + CONF_SENSOR_TYPE, CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME, + CONF_VALUE_TEMPLATE) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['python-hpilo==3.9'] _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'HP ILO' +DEFAULT_NAME = "HP ILO" DEFAULT_PORT = 443 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) @@ -60,7 +55,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the HP ILO sensor.""" + """Set up the HP iLO sensors.""" hostname = config.get(CONF_HOST) port = config.get(CONF_PORT) login = config.get(CONF_USERNAME) @@ -73,7 +68,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hp_ilo_data = HpIloData(hostname, port, login, password) except ValueError as error: _LOGGER.error(error) - return False + return # Initialize and add all of the sensors. devices = [] @@ -93,11 +88,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class HpIloSensor(Entity): - """Representation of a HP ILO sensor.""" + """Representation of a HP iLO sensor.""" def __init__(self, hass, hp_ilo_data, sensor_type, sensor_name, sensor_value_template, unit_of_measurement): - """Initialize the sensor.""" + """Initialize the HP iLO sensor.""" self._hass = hass self._name = sensor_name self._unit_of_measurement = unit_of_measurement @@ -111,7 +106,7 @@ class HpIloSensor(Entity): self._state = None self._state_attributes = None - _LOGGER.debug("Created HP ILO sensor %r", self) + _LOGGER.debug("Created HP iLO sensor %r", self) @property def name(self): @@ -130,11 +125,11 @@ class HpIloSensor(Entity): @property def device_state_attributes(self): - """Return the state attributes.""" + """Return the device state attributes.""" return self._state_attributes def update(self): - """Get the latest data from HP ILO and updates the states.""" + """Get the latest data from HP iLO and updates the states.""" # Call the API for new data. Each sensor will re-trigger this # same exact call, but that's fine. Results should be cached for # a short period of time to prevent hitting API limits. @@ -148,7 +143,7 @@ class HpIloSensor(Entity): class HpIloData: - """Gets the latest data from HP ILO.""" + """Gets the latest data from HP iLO.""" def __init__(self, host, port, login, password): """Initialize the data object.""" @@ -163,7 +158,7 @@ class HpIloData: @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """Get the latest data from HP ILO.""" + """Get the latest data from HP iLO.""" import hpilo try: diff --git a/homeassistant/components/html5/__init__.py b/homeassistant/components/html5/__init__.py new file mode 100644 index 00000000000..88e437ef566 --- /dev/null +++ b/homeassistant/components/html5/__init__.py @@ -0,0 +1 @@ +"""The html5 component.""" diff --git a/homeassistant/components/notify/html5.py b/homeassistant/components/html5/notify.py similarity index 95% rename from homeassistant/components/notify/html5.py rename to homeassistant/components/html5/notify.py index 17f7e316357..8744d0afb28 100644 --- a/homeassistant/components/notify/html5.py +++ b/homeassistant/components/html5/notify.py @@ -5,9 +5,9 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.html5/ """ import datetime +from functools import partial import json import logging -from functools import partial import time import uuid @@ -15,18 +15,19 @@ from aiohttp.hdrs import AUTHORIZATION import voluptuous as vol from voluptuous.humanize import humanize_error -from homeassistant.util.json import load_json, save_json -from homeassistant.exceptions import HomeAssistantError from homeassistant.components import websocket_api from homeassistant.components.frontend import add_manifest_json_key from homeassistant.components.http import HomeAssistantView -from homeassistant.components.notify import ( - ATTR_DATA, ATTR_TITLE, ATTR_TARGET, PLATFORM_SCHEMA, ATTR_TITLE_DEFAULT, - BaseNotificationService, DOMAIN) from homeassistant.const import ( - URL_ROOT, HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED, HTTP_INTERNAL_SERVER_ERROR) + HTTP_BAD_REQUEST, HTTP_INTERNAL_SERVER_ERROR, HTTP_UNAUTHORIZED, URL_ROOT) +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.util import ensure_unique_string +from homeassistant.util.json import load_json, save_json + +from homeassistant.components.notify import ( + ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, DOMAIN, + PLATFORM_SCHEMA, BaseNotificationService) REQUIREMENTS = ['pywebpush==1.6.0'] @@ -44,8 +45,23 @@ ATTR_VAPID_PUB_KEY = 'vapid_pub_key' ATTR_VAPID_PRV_KEY = 'vapid_prv_key' ATTR_VAPID_EMAIL = 'vapid_email' + +def gcm_api_deprecated(value): + """Warn user that GCM API config is deprecated.""" + if not value: + return value + + _LOGGER.warning( + "Configuring html5_push_notifications via the GCM api" + " has been deprecated and will stop working after April 11," + " 2019. Use the VAPID configuration instead. For instructions," + " see https://www.home-assistant.io/components/notify.html5/") + return value + + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(ATTR_GCM_SENDER_ID): cv.string, + vol.Optional(ATTR_GCM_SENDER_ID): + vol.All(cv.string, gcm_api_deprecated), vol.Optional(ATTR_GCM_API_KEY): cv.string, vol.Optional(ATTR_VAPID_PUB_KEY): cv.string, vol.Optional(ATTR_VAPID_PRV_KEY): cv.string, diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py index 4736ef12391..0d8e327e086 100644 --- a/homeassistant/components/http/auth.py +++ b/homeassistant/components/http/auth.py @@ -190,6 +190,16 @@ def setup_auth(hass, app): elif (trusted_networks and await async_validate_trusted_networks(request)): + if request.path not in old_auth_warning: + # When removing this, don't forget to remove the print logic + # in http/view.py + request['deprecate_warning_message'] = \ + 'Access from trusted networks without auth token is ' \ + 'going to be removed in Home Assistant 0.96. Configure ' \ + 'the trusted networks auth provider or use long-lived ' \ + 'access tokens to access {} from {}'.format( + request.path, request[KEY_REAL_IP]) + old_auth_warning.add(request.path) authenticated = True elif (support_legacy and HTTP_HEADER_HA_AUTH in request.headers and diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index bb7f2c2fee2..8d5e0ee88b1 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -9,12 +9,12 @@ from aiohttp.web_exceptions import ( import voluptuous as vol from homeassistant import exceptions -from homeassistant.components.http.ban import process_success_login from homeassistant.const import CONTENT_TYPE_JSON from homeassistant.core import Context, is_callback from homeassistant.helpers.json import JSONEncoder -from .const import KEY_AUTHENTICATED, KEY_REAL_IP, KEY_HASS +from .ban import process_success_login +from .const import KEY_AUTHENTICATED, KEY_HASS, KEY_REAL_IP _LOGGER = logging.getLogger(__name__) @@ -98,12 +98,14 @@ def request_handler_factory(view, handler): if view.requires_auth: if authenticated: + if 'deprecate_warning_message' in request: + _LOGGER.warning(request['deprecate_warning_message']) await process_success_login(request) else: raise HTTPUnauthorized() - _LOGGER.info('Serving %s to %s (auth: %s)', - request.path, request.get(KEY_REAL_IP), authenticated) + _LOGGER.debug('Serving %s to %s (auth: %s)', + request.path, request.get(KEY_REAL_IP), authenticated) try: result = handler(request, **request.match_info) diff --git a/homeassistant/components/htu21d/__init__.py b/homeassistant/components/htu21d/__init__.py new file mode 100644 index 00000000000..c36c8bfcffb --- /dev/null +++ b/homeassistant/components/htu21d/__init__.py @@ -0,0 +1 @@ +"""The htu21d component.""" diff --git a/homeassistant/components/sensor/htu21d.py b/homeassistant/components/htu21d/sensor.py similarity index 100% rename from homeassistant/components/sensor/htu21d.py rename to homeassistant/components/htu21d/sensor.py diff --git a/homeassistant/components/huawei_router/__init__.py b/homeassistant/components/huawei_router/__init__.py new file mode 100644 index 00000000000..861809992c6 --- /dev/null +++ b/homeassistant/components/huawei_router/__init__.py @@ -0,0 +1 @@ +"""The huawei_router component.""" diff --git a/homeassistant/components/device_tracker/huawei_router.py b/homeassistant/components/huawei_router/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/huawei_router.py rename to homeassistant/components/huawei_router/device_tracker.py diff --git a/homeassistant/components/hue/.translations/bg.json b/homeassistant/components/hue/.translations/bg.json index 276f5053bf7..6a828282f52 100644 --- a/homeassistant/components/hue/.translations/bg.json +++ b/homeassistant/components/hue/.translations/bg.json @@ -15,7 +15,7 @@ "step": { "init": { "data": { - "host": "\u0425\u043e\u0441\u0442" + "host": "\u0410\u0434\u0440\u0435\u0441" }, "title": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f Philips Hue" }, diff --git a/homeassistant/components/hue/.translations/hu.json b/homeassistant/components/hue/.translations/hu.json index 4d534eace7d..e65286b5c64 100644 --- a/homeassistant/components/hue/.translations/hu.json +++ b/homeassistant/components/hue/.translations/hu.json @@ -15,7 +15,7 @@ "step": { "init": { "data": { - "host": "H\u00e1zigazda (Host)" + "host": "Hoszt" }, "title": "V\u00e1lassz Hue bridge-t" }, diff --git a/homeassistant/components/hue/.translations/ru.json b/homeassistant/components/hue/.translations/ru.json index b6e2ccce8ed..ce71fb670be 100644 --- a/homeassistant/components/hue/.translations/ru.json +++ b/homeassistant/components/hue/.translations/ru.json @@ -2,7 +2,7 @@ "config": { "abort": { "all_configured": "\u0412\u0441\u0435 Philips Hue \u0448\u043b\u044e\u0437\u044b \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b", - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0448\u043b\u044e\u0437\u0443", "discover_timeout": "\u0428\u043b\u044e\u0437 Philips Hue \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d", "no_bridges": "\u0428\u043b\u044e\u0437\u044b Philips Hue \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b", diff --git a/homeassistant/components/hue/.translations/th.json b/homeassistant/components/hue/.translations/th.json new file mode 100644 index 00000000000..0b91783f804 --- /dev/null +++ b/homeassistant/components/hue/.translations/th.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "Philips Hue" + } +} \ No newline at end of file diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index 2eb30d47804..d61a0aa7e89 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -1,6 +1,6 @@ """Constants for the Hue component.""" import logging -LOGGER = logging.getLogger('homeassistant.components.hue') +LOGGER = logging.getLogger('.') DOMAIN = "hue" API_NUPNP = 'https://www.meethue.com/api/nupnp' diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py new file mode 100644 index 00000000000..14ede545576 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -0,0 +1 @@ +"""The hunterdouglas_powerview component.""" diff --git a/homeassistant/components/scene/hunterdouglas_powerview.py b/homeassistant/components/hunterdouglas_powerview/scene.py similarity index 100% rename from homeassistant/components/scene/hunterdouglas_powerview.py rename to homeassistant/components/hunterdouglas_powerview/scene.py diff --git a/homeassistant/components/hydrawise/binary_sensor.py b/homeassistant/components/hydrawise/binary_sensor.py index bfe7cbd5531..85a51d3649e 100644 --- a/homeassistant/components/hydrawise/binary_sensor.py +++ b/homeassistant/components/hydrawise/binary_sensor.py @@ -3,13 +3,14 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.hydrawise import ( - BINARY_SENSORS, DATA_HYDRAWISE, HydrawiseEntity, DEVICE_MAP, - DEVICE_MAP_INDEX) from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) + PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.const import CONF_MONITORED_CONDITIONS +import homeassistant.helpers.config_validation as cv + +from . import ( + BINARY_SENSORS, DATA_HYDRAWISE, DEVICE_MAP, DEVICE_MAP_INDEX, + HydrawiseEntity) DEPENDENCIES = ['hydrawise'] diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py index 575686b92cd..fc15a54ed60 100644 --- a/homeassistant/components/hydrawise/sensor.py +++ b/homeassistant/components/hydrawise/sensor.py @@ -3,11 +3,12 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.hydrawise import ( - DATA_HYDRAWISE, HydrawiseEntity, DEVICE_MAP, DEVICE_MAP_INDEX, SENSORS) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_MONITORED_CONDITIONS +import homeassistant.helpers.config_validation as cv + +from . import ( + DATA_HYDRAWISE, DEVICE_MAP, DEVICE_MAP_INDEX, SENSORS, HydrawiseEntity) DEPENDENCIES = ['hydrawise'] diff --git a/homeassistant/components/hydrawise/switch.py b/homeassistant/components/hydrawise/switch.py index a6a8b9c54cf..dcbd5274a62 100644 --- a/homeassistant/components/hydrawise/switch.py +++ b/homeassistant/components/hydrawise/switch.py @@ -3,13 +3,14 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.hydrawise import ( - ALLOWED_WATERING_TIME, CONF_WATERING_TIME, - DATA_HYDRAWISE, DEFAULT_WATERING_TIME, HydrawiseEntity, SWITCHES, - DEVICE_MAP, DEVICE_MAP_INDEX) from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import CONF_MONITORED_CONDITIONS +import homeassistant.helpers.config_validation as cv + +from . import ( + ALLOWED_WATERING_TIME, CONF_WATERING_TIME, DATA_HYDRAWISE, + DEFAULT_WATERING_TIME, DEVICE_MAP, DEVICE_MAP_INDEX, SWITCHES, + HydrawiseEntity) DEPENDENCIES = ['hydrawise'] diff --git a/homeassistant/components/hydroquebec/__init__.py b/homeassistant/components/hydroquebec/__init__.py new file mode 100644 index 00000000000..08a12f7955e --- /dev/null +++ b/homeassistant/components/hydroquebec/__init__.py @@ -0,0 +1 @@ +"""The hydroquebec component.""" diff --git a/homeassistant/components/sensor/hydroquebec.py b/homeassistant/components/hydroquebec/sensor.py similarity index 100% rename from homeassistant/components/sensor/hydroquebec.py rename to homeassistant/components/hydroquebec/sensor.py diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py new file mode 100644 index 00000000000..60a0a2d3210 --- /dev/null +++ b/homeassistant/components/hyperion/__init__.py @@ -0,0 +1 @@ +"""The Hyperion component.""" diff --git a/homeassistant/components/light/hyperion.py b/homeassistant/components/hyperion/light.py similarity index 88% rename from homeassistant/components/light/hyperion.py rename to homeassistant/components/hyperion/light.py index 16be7d45825..1fc5f78d0e8 100644 --- a/homeassistant/components/light/hyperion.py +++ b/homeassistant/components/hyperion/light.py @@ -1,9 +1,4 @@ -""" -Support for Hyperion remotes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.hyperion/ -""" +"""Support for Hyperion remotes.""" import json import logging import socket @@ -11,9 +6,9 @@ import socket import voluptuous as vol from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_EFFECT, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, SUPPORT_EFFECT, Light, PLATFORM_SCHEMA) -from homeassistant.const import (CONF_HOST, CONF_PORT, CONF_NAME) + ATTR_BRIGHTNESS, ATTR_EFFECT, ATTR_HS_COLOR, PLATFORM_SCHEMA, + SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_EFFECT, Light) +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util @@ -47,34 +42,32 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR): - vol.All(list, vol.Length(min=3, max=3), - [vol.All(vol.Coerce(int), vol.Range(min=0, max=255))]), + vol.All(list, vol.Length(min=3, max=3), + [vol.All(vol.Coerce(int), vol.Range(min=0, max=255))]), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PRIORITY, default=DEFAULT_PRIORITY): cv.positive_int, - vol.Optional(CONF_HDMI_PRIORITY, - default=DEFAULT_HDMI_PRIORITY): cv.positive_int, - vol.Optional(CONF_EFFECT_LIST, - default=DEFAULT_EFFECT_LIST): vol.All(cv.ensure_list, - [cv.string]), + vol.Optional(CONF_HDMI_PRIORITY, default=DEFAULT_HDMI_PRIORITY): + cv.positive_int, + vol.Optional(CONF_EFFECT_LIST, default=DEFAULT_EFFECT_LIST): + vol.All(cv.ensure_list, [cv.string]), }) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up a Hyperion server remote.""" - host = config.get(CONF_HOST) - port = config.get(CONF_PORT) - priority = config.get(CONF_PRIORITY) - hdmi_priority = config.get(CONF_HDMI_PRIORITY) - default_color = config.get(CONF_DEFAULT_COLOR) - effect_list = config.get(CONF_EFFECT_LIST) + name = config[CONF_NAME] + host = config[CONF_HOST] + port = config[CONF_PORT] + priority = config[CONF_PRIORITY] + hdmi_priority = config[CONF_HDMI_PRIORITY] + default_color = config[CONF_DEFAULT_COLOR] + effect_list = config[CONF_EFFECT_LIST] - device = Hyperion(config.get(CONF_NAME), host, port, priority, - default_color, hdmi_priority, effect_list) + device = Hyperion( + name, host, port, priority, default_color, hdmi_priority, effect_list) if device.setup(): add_entities([device]) - return True - return False class Hyperion(Light): diff --git a/homeassistant/components/ialarm/__init__.py b/homeassistant/components/ialarm/__init__.py new file mode 100644 index 00000000000..d03609bc1d0 --- /dev/null +++ b/homeassistant/components/ialarm/__init__.py @@ -0,0 +1 @@ +"""The ialarm component.""" diff --git a/homeassistant/components/alarm_control_panel/ialarm.py b/homeassistant/components/ialarm/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/ialarm.py rename to homeassistant/components/ialarm/alarm_control_panel.py diff --git a/homeassistant/components/icloud/__init__.py b/homeassistant/components/icloud/__init__.py new file mode 100644 index 00000000000..1169104c99d --- /dev/null +++ b/homeassistant/components/icloud/__init__.py @@ -0,0 +1 @@ +"""The icloud component.""" diff --git a/homeassistant/components/device_tracker/icloud.py b/homeassistant/components/icloud/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/icloud.py rename to homeassistant/components/icloud/device_tracker.py diff --git a/homeassistant/components/ifttt/.translations/bg.json b/homeassistant/components/ifttt/.translations/bg.json new file mode 100644 index 00000000000..d0fb2a5a04e --- /dev/null +++ b/homeassistant/components/ifttt/.translations/bg.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Home Assistant \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u0435\u043d \u043e\u0442 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0437\u0430 \u0434\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0432\u0430 \u0441\u044a\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0442 IFTTT.", + "one_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + }, + "create_entry": { + "default": "\u0417\u0430 \u0434\u0430 \u0438\u0437\u043f\u0440\u0430\u0449\u0430\u0442\u0435 \u0441\u044a\u0431\u0438\u0442\u0438\u044f \u0432 Home Assistant, \u0449\u0435 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \"Make a web request\" \u043e\u0442 [IFTTT Webhook \u0430\u043f\u043b\u0435\u0442]({applet_url}). \n\n\u041f\u043e\u043f\u044a\u043b\u043d\u0435\u0442\u0435 \u0441\u043b\u0435\u0434\u043d\u0430\u0442\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json\n\n \u0412\u0438\u0436\u0442\u0435 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0442\u0430]({docs_url}) \u0437\u0430 \u0442\u043e\u0432\u0430 \u043a\u0430\u043a \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438\u0442\u0435 \u0437\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043d\u0430 \u0432\u0445\u043e\u0434\u044f\u0449\u0438 \u0434\u0430\u043d\u043d\u0438." + }, + "step": { + "user": { + "description": "\u0421\u0438\u0433\u0443\u0440\u043d\u0438 \u043b\u0438 \u0441\u0442\u0435, \u0447\u0435 \u0438\u0441\u043a\u0430\u0442\u0435 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 IFTTT?", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 IFTTT Webhook \u0430\u043f\u043b\u0435\u0442" + } + }, + "title": "IFTTT" + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/.translations/pl.json b/homeassistant/components/ifttt/.translations/pl.json index 7064364ebe6..270e74945a3 100644 --- a/homeassistant/components/ifttt/.translations/pl.json +++ b/homeassistant/components/ifttt/.translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wys\u0142a\u0107 zdarzenia do Home Assistant'a, b\u0119dziesz musia\u0142 u\u017cy\u0107 akcji \"Make a web request\" z [IFTTT Webhook apletu]({applet_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}`\n - Metoda: POST\n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." + "default": "Aby wys\u0142a\u0107 zdarzenia do Home Assistant'a, b\u0119dziesz musia\u0142 u\u017cy\u0107 akcji \"Make a web request\" z [IFTTT Webhook apletu]({applet_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}`\n - Metoda: POST\n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji, by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." }, "step": { "user": { diff --git a/homeassistant/components/ifttt/alarm_control_panel.py b/homeassistant/components/ifttt/alarm_control_panel.py index 98a176b1e82..3f806173196 100644 --- a/homeassistant/components/ifttt/alarm_control_panel.py +++ b/homeassistant/components/ifttt/alarm_control_panel.py @@ -7,14 +7,14 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import ( DOMAIN, PLATFORM_SCHEMA) -from homeassistant.components.ifttt import ( - ATTR_EVENT, DOMAIN as IFTTT_DOMAIN, SERVICE_TRIGGER) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_STATE, CONF_CODE, CONF_NAME, CONF_OPTIMISTIC, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED) import homeassistant.helpers.config_validation as cv +from . import ATTR_EVENT, DOMAIN as IFTTT_DOMAIN, SERVICE_TRIGGER + DEPENDENCIES = ['ifttt'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/iglo/__init__.py b/homeassistant/components/iglo/__init__.py new file mode 100644 index 00000000000..6e5ca1ad93b --- /dev/null +++ b/homeassistant/components/iglo/__init__.py @@ -0,0 +1 @@ +"""The iglo component.""" diff --git a/homeassistant/components/light/iglo.py b/homeassistant/components/iglo/light.py similarity index 100% rename from homeassistant/components/light/iglo.py rename to homeassistant/components/iglo/light.py diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py index daaf471e318..102acd82551 100644 --- a/homeassistant/components/ihc/__init__.py +++ b/homeassistant/components/ihc/__init__.py @@ -5,13 +5,6 @@ import os.path import voluptuous as vol from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA -from homeassistant.components.ihc.const import ( - ATTR_IHC_ID, ATTR_VALUE, CONF_AUTOSETUP, CONF_BINARY_SENSOR, CONF_DIMMABLE, - CONF_INFO, CONF_INVERTING, CONF_LIGHT, CONF_NODE, CONF_NOTE, CONF_OFF_ID, - CONF_ON_ID, CONF_POSITION, CONF_SENSOR, CONF_SWITCH, CONF_XPATH, - SERVICE_SET_RUNTIME_VALUE_BOOL, SERVICE_SET_RUNTIME_VALUE_FLOAT, - SERVICE_SET_RUNTIME_VALUE_INT, SERVICE_PULSE) -from homeassistant.components.ihc.util import async_pulse from homeassistant.config import load_yaml_config_file from homeassistant.const import ( CONF_ID, CONF_NAME, CONF_PASSWORD, CONF_TYPE, CONF_UNIT_OF_MEASUREMENT, @@ -20,6 +13,14 @@ from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import HomeAssistantType +from .const import ( + ATTR_IHC_ID, ATTR_VALUE, CONF_AUTOSETUP, CONF_BINARY_SENSOR, CONF_DIMMABLE, + CONF_INFO, CONF_INVERTING, CONF_LIGHT, CONF_NODE, CONF_NOTE, CONF_OFF_ID, + CONF_ON_ID, CONF_POSITION, CONF_SENSOR, CONF_SWITCH, CONF_XPATH, + SERVICE_PULSE, SERVICE_SET_RUNTIME_VALUE_BOOL, + SERVICE_SET_RUNTIME_VALUE_FLOAT, SERVICE_SET_RUNTIME_VALUE_INT) +from .util import async_pulse + REQUIREMENTS = ['ihcsdk==2.3.0', 'defusedxml==0.5.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ihc/binary_sensor.py b/homeassistant/components/ihc/binary_sensor.py index 7e3371a834c..69e3e1685af 100644 --- a/homeassistant/components/ihc/binary_sensor.py +++ b/homeassistant/components/ihc/binary_sensor.py @@ -1,10 +1,11 @@ """Support for IHC binary sensors.""" from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.ihc import IHC_CONTROLLER, IHC_DATA, IHC_INFO -from homeassistant.components.ihc.const import CONF_INVERTING -from homeassistant.components.ihc.ihcdevice import IHCDevice from homeassistant.const import CONF_TYPE +from . import IHC_CONTROLLER, IHC_DATA, IHC_INFO +from .const import CONF_INVERTING +from .ihcdevice import IHCDevice + DEPENDENCIES = ['ihc'] diff --git a/homeassistant/components/ihc/light.py b/homeassistant/components/ihc/light.py index 646be7597d0..ad6d0fb6511 100644 --- a/homeassistant/components/ihc/light.py +++ b/homeassistant/components/ihc/light.py @@ -1,15 +1,14 @@ """Support for IHC lights.""" import logging -from homeassistant.components.ihc import IHC_CONTROLLER, IHC_DATA, IHC_INFO -from homeassistant.components.ihc.const import ( - CONF_DIMMABLE, CONF_OFF_ID, CONF_ON_ID) -from homeassistant.components.ihc.util import ( - async_pulse, async_set_bool, async_set_int) -from homeassistant.components.ihc.ihcdevice import IHCDevice from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) +from . import IHC_CONTROLLER, IHC_DATA, IHC_INFO +from .const import CONF_DIMMABLE, CONF_OFF_ID, CONF_ON_ID +from .ihcdevice import IHCDevice +from .util import async_pulse, async_set_bool, async_set_int + DEPENDENCIES = ['ihc'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ihc/sensor.py b/homeassistant/components/ihc/sensor.py index 930ac221629..fd1f2cee53a 100644 --- a/homeassistant/components/ihc/sensor.py +++ b/homeassistant/components/ihc/sensor.py @@ -1,9 +1,10 @@ """Support for IHC sensors.""" -from homeassistant.components.ihc import IHC_CONTROLLER, IHC_DATA, IHC_INFO -from homeassistant.components.ihc.ihcdevice import IHCDevice from homeassistant.const import CONF_UNIT_OF_MEASUREMENT from homeassistant.helpers.entity import Entity +from . import IHC_CONTROLLER, IHC_DATA, IHC_INFO +from .ihcdevice import IHCDevice + DEPENDENCIES = ['ihc'] diff --git a/homeassistant/components/ihc/switch.py b/homeassistant/components/ihc/switch.py index d25b343446d..e2189492b8f 100644 --- a/homeassistant/components/ihc/switch.py +++ b/homeassistant/components/ihc/switch.py @@ -1,10 +1,11 @@ """Support for IHC switches.""" -from homeassistant.components.ihc import IHC_CONTROLLER, IHC_DATA, IHC_INFO -from homeassistant.components.ihc.const import CONF_OFF_ID, CONF_ON_ID -from homeassistant.components.ihc.util import async_pulse, async_set_bool -from homeassistant.components.ihc.ihcdevice import IHCDevice from homeassistant.components.switch import SwitchDevice +from . import IHC_CONTROLLER, IHC_DATA, IHC_INFO +from .const import CONF_OFF_ID, CONF_ON_ID +from .ihcdevice import IHCDevice +from .util import async_pulse, async_set_bool + DEPENDENCIES = ['ihc'] diff --git a/homeassistant/components/imap/__init__.py b/homeassistant/components/imap/__init__.py new file mode 100644 index 00000000000..d85f295a43e --- /dev/null +++ b/homeassistant/components/imap/__init__.py @@ -0,0 +1 @@ +"""The imap component.""" diff --git a/homeassistant/components/sensor/imap.py b/homeassistant/components/imap/sensor.py similarity index 100% rename from homeassistant/components/sensor/imap.py rename to homeassistant/components/imap/sensor.py diff --git a/homeassistant/components/imap_email_content/__init__.py b/homeassistant/components/imap_email_content/__init__.py new file mode 100644 index 00000000000..263f57a3a9d --- /dev/null +++ b/homeassistant/components/imap_email_content/__init__.py @@ -0,0 +1 @@ +"""The imap_email_content component.""" diff --git a/homeassistant/components/sensor/imap_email_content.py b/homeassistant/components/imap_email_content/sensor.py similarity index 100% rename from homeassistant/components/sensor/imap_email_content.py rename to homeassistant/components/imap_email_content/sensor.py diff --git a/homeassistant/components/sensor/influxdb.py b/homeassistant/components/influxdb/sensor.py similarity index 99% rename from homeassistant/components/sensor/influxdb.py rename to homeassistant/components/influxdb/sensor.py index 35229c2a805..9dbb4787df7 100644 --- a/homeassistant/components/sensor/influxdb.py +++ b/homeassistant/components/influxdb/sensor.py @@ -9,7 +9,6 @@ import logging import voluptuous as vol -from homeassistant.components.influxdb import CONF_DB_NAME from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SSL, @@ -20,6 +19,8 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle +from . import CONF_DB_NAME + _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['influxdb==5.2.0'] diff --git a/homeassistant/components/insteon/binary_sensor.py b/homeassistant/components/insteon/binary_sensor.py index 06eddb9a004..6f1e5675639 100644 --- a/homeassistant/components/insteon/binary_sensor.py +++ b/homeassistant/components/insteon/binary_sensor.py @@ -2,7 +2,8 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.insteon import InsteonEntity + +from . import InsteonEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/insteon/cover.py b/homeassistant/components/insteon/cover.py index 7de2e872489..1bb316152a9 100644 --- a/homeassistant/components/insteon/cover.py +++ b/homeassistant/components/insteon/cover.py @@ -5,7 +5,8 @@ import math from homeassistant.components.cover import ( ATTR_POSITION, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, CoverDevice) -from homeassistant.components.insteon import InsteonEntity + +from . import InsteonEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index 2b6097a4ba2..26a56d6df98 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -4,9 +4,10 @@ import logging from homeassistant.components.fan import ( SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_SET_SPEED, FanEntity) -from homeassistant.components.insteon import InsteonEntity from homeassistant.const import STATE_OFF +from . import InsteonEntity + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['insteon'] diff --git a/homeassistant/components/insteon/light.py b/homeassistant/components/insteon/light.py index e8ffc226716..676c053325c 100644 --- a/homeassistant/components/insteon/light.py +++ b/homeassistant/components/insteon/light.py @@ -1,10 +1,11 @@ """Support for Insteon lights via PowerLinc Modem.""" import logging -from homeassistant.components.insteon import InsteonEntity from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) +from . import InsteonEntity + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['insteon'] diff --git a/homeassistant/components/insteon/sensor.py b/homeassistant/components/insteon/sensor.py index d895d972027..edea87e1f73 100644 --- a/homeassistant/components/insteon/sensor.py +++ b/homeassistant/components/insteon/sensor.py @@ -1,9 +1,10 @@ """Support for INSTEON dimmers via PowerLinc Modem.""" import logging -from homeassistant.components.insteon import InsteonEntity from homeassistant.helpers.entity import Entity +from . import InsteonEntity + DEPENDENCIES = ['insteon'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/insteon/switch.py b/homeassistant/components/insteon/switch.py index 2a6b97a39d1..4fdcdb20bb2 100644 --- a/homeassistant/components/insteon/switch.py +++ b/homeassistant/components/insteon/switch.py @@ -1,9 +1,10 @@ """Support for INSTEON dimmers via PowerLinc Modem.""" import logging -from homeassistant.components.insteon import InsteonEntity from homeassistant.components.switch import SwitchDevice +from . import InsteonEntity + DEPENDENCIES = ['insteon'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/integration/__init__.py b/homeassistant/components/integration/__init__.py new file mode 100644 index 00000000000..4eea25fefe1 --- /dev/null +++ b/homeassistant/components/integration/__init__.py @@ -0,0 +1 @@ +"""The integration component.""" diff --git a/homeassistant/components/sensor/integration.py b/homeassistant/components/integration/sensor.py similarity index 100% rename from homeassistant/components/sensor/integration.py rename to homeassistant/components/integration/sensor.py diff --git a/homeassistant/components/ios/.translations/bg.json b/homeassistant/components/ios/.translations/bg.json new file mode 100644 index 00000000000..58028d1caae --- /dev/null +++ b/homeassistant/components/ios/.translations/bg.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Home Assistant iOS." + }, + "step": { + "confirm": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Home Assistant iOS \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430?", + "title": "Home Assistant iOS" + } + }, + "title": "Home Assistant iOS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ios/__init__.py b/homeassistant/components/ios/__init__.py index 737216af5c9..cc8bd62293a 100644 --- a/homeassistant/components/ios/__init__.py +++ b/homeassistant/components/ios/__init__.py @@ -76,7 +76,7 @@ PERMISSIONS = [ATTR_LOCATION_PERMISSION, ATTR_NOTIFICATIONS_PERMISSION] ATTR_BATTERY_STATE = 'state' ATTR_BATTERY_LEVEL = 'level' -ATTR_BATTERY_STATE_UNPLUGGED = 'Unplugged' +ATTR_BATTERY_STATE_UNPLUGGED = 'Not Charging' ATTR_BATTERY_STATE_CHARGING = 'Charging' ATTR_BATTERY_STATE_FULL = 'Full' ATTR_BATTERY_STATE_UNKNOWN = 'Unknown' diff --git a/homeassistant/components/iota/sensor.py b/homeassistant/components/iota/sensor.py index 5cd5db6169b..2955828aff5 100644 --- a/homeassistant/components/iota/sensor.py +++ b/homeassistant/components/iota/sensor.py @@ -1,10 +1,11 @@ """Support for IOTA wallet sensors.""" -import logging from datetime import timedelta +import logging -from homeassistant.components.iota import IotaDevice, CONF_WALLETS from homeassistant.const import CONF_NAME +from . import CONF_WALLETS, IotaDevice + _LOGGER = logging.getLogger(__name__) ATTR_TESTNET = 'testnet' diff --git a/homeassistant/components/iperf3/sensor.py b/homeassistant/components/iperf3/sensor.py index 59813ae0455..db9aafcdf4b 100644 --- a/homeassistant/components/iperf3/sensor.py +++ b/homeassistant/components/iperf3/sensor.py @@ -1,11 +1,11 @@ """Support for Iperf3 sensors.""" -from homeassistant.components.iperf3 import ( - DATA_UPDATED, DOMAIN as IPERF3_DOMAIN, SENSOR_TYPES, ATTR_VERSION) from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity +from . import ATTR_VERSION, DATA_UPDATED, DOMAIN as IPERF3_DOMAIN, SENSOR_TYPES + DEPENDENCIES = ['iperf3'] ATTRIBUTION = 'Data retrieved using Iperf3' diff --git a/homeassistant/components/ipma/.translations/bg.json b/homeassistant/components/ipma/.translations/bg.json new file mode 100644 index 00000000000..70d2c6ef6bc --- /dev/null +++ b/homeassistant/components/ipma/.translations/bg.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "\u0418\u043c\u0435\u0442\u043e \u0432\u0435\u0447\u0435 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430" + }, + "step": { + "user": { + "data": { + "latitude": "\u0428\u0438\u0440\u0438\u043d\u0430", + "longitude": "\u0414\u044a\u043b\u0436\u0438\u043d\u0430", + "name": "\u0418\u043c\u0435" + }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", + "title": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435" + } + }, + "title": "\u041f\u043e\u0440\u0442\u0443\u0433\u0430\u043b\u0441\u043a\u0430 \u043c\u0435\u0442\u0435\u043e\u0440\u043e\u043b\u043e\u0433\u0438\u0447\u043d\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/.translations/fr.json b/homeassistant/components/ipma/.translations/fr.json new file mode 100644 index 00000000000..058908db36b --- /dev/null +++ b/homeassistant/components/ipma/.translations/fr.json @@ -0,0 +1,13 @@ +{ + "config": { + "error": { + "name_exists": "Ce nom est d\u00e9j\u00e0 utilis\u00e9" + }, + "step": { + "user": { + "title": "Emplacement" + } + }, + "title": "Service m\u00e9t\u00e9orologique portugais (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/.translations/zh-Hans.json b/homeassistant/components/ipma/.translations/zh-Hans.json new file mode 100644 index 00000000000..6c5654b6388 --- /dev/null +++ b/homeassistant/components/ipma/.translations/zh-Hans.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "\u540d\u79f0\u5df2\u5b58\u5728" + }, + "step": { + "user": { + "data": { + "latitude": "\u7eac\u5ea6", + "longitude": "\u7ecf\u5ea6", + "name": "\u540d\u79f0" + }, + "description": "\u8461\u8404\u7259\u56fd\u5bb6\u5927\u6c14\u7814\u7a76\u6240", + "title": "\u4f4d\u7f6e" + } + }, + "title": "\u8461\u8404\u7259\u6c14\u8c61\u670d\u52a1\uff08IPMA\uff09" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/const.py b/homeassistant/components/ipma/const.py index bdd97c74e6a..1e778eff5bd 100644 --- a/homeassistant/components/ipma/const.py +++ b/homeassistant/components/ipma/const.py @@ -11,4 +11,4 @@ ENTITY_ID_SENSOR_FORMAT = WEATHER_DOMAIN + ".ipma_{}" ENTITY_ID_SENSOR_FORMAT_HOME = ENTITY_ID_SENSOR_FORMAT.format( HOME_LOCATION_NAME) -_LOGGER = logging.getLogger('homeassistant.components.ipma') +_LOGGER = logging.getLogger('.') diff --git a/homeassistant/components/irish_rail_transport/__init__.py b/homeassistant/components/irish_rail_transport/__init__.py new file mode 100644 index 00000000000..197b5fe7e99 --- /dev/null +++ b/homeassistant/components/irish_rail_transport/__init__.py @@ -0,0 +1 @@ +"""The irish_rail_transport component.""" diff --git a/homeassistant/components/sensor/irish_rail_transport.py b/homeassistant/components/irish_rail_transport/sensor.py similarity index 100% rename from homeassistant/components/sensor/irish_rail_transport.py rename to homeassistant/components/irish_rail_transport/sensor.py diff --git a/homeassistant/components/islamic_prayer_times/__init__.py b/homeassistant/components/islamic_prayer_times/__init__.py new file mode 100644 index 00000000000..642c31118bd --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/__init__.py @@ -0,0 +1 @@ +"""The islamic_prayer_times component.""" diff --git a/homeassistant/components/sensor/islamic_prayer_times.py b/homeassistant/components/islamic_prayer_times/sensor.py similarity index 100% rename from homeassistant/components/sensor/islamic_prayer_times.py rename to homeassistant/components/islamic_prayer_times/sensor.py diff --git a/homeassistant/components/iss/__init__.py b/homeassistant/components/iss/__init__.py new file mode 100644 index 00000000000..51487bdfaf2 --- /dev/null +++ b/homeassistant/components/iss/__init__.py @@ -0,0 +1 @@ +"""The iss component.""" diff --git a/homeassistant/components/binary_sensor/iss.py b/homeassistant/components/iss/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/iss.py rename to homeassistant/components/iss/binary_sensor.py diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index 013b99fbb15..ce95e71e8d4 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -4,14 +4,14 @@ import logging from typing import Callable from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice -from homeassistant.components.isy994 import ( - ISY994_NODES, ISY994_PROGRAMS, ISYDevice) from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import callback from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt as dt_util +from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice + _LOGGER = logging.getLogger(__name__) ISY_DEVICE_TYPES = { diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py index 22ea1629794..b40d6428f24 100644 --- a/homeassistant/components/isy994/cover.py +++ b/homeassistant/components/isy994/cover.py @@ -3,12 +3,12 @@ import logging from typing import Callable from homeassistant.components.cover import DOMAIN, CoverDevice -from homeassistant.components.isy994 import ( - ISY994_NODES, ISY994_PROGRAMS, ISYDevice) from homeassistant.const import ( STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING, STATE_UNKNOWN) from homeassistant.helpers.typing import ConfigType +from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice + _LOGGER = logging.getLogger(__name__) VALUE_TO_STATE = { diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py index 142eaedd66b..5a21a28fd8d 100644 --- a/homeassistant/components/isy994/fan.py +++ b/homeassistant/components/isy994/fan.py @@ -5,10 +5,10 @@ from typing import Callable from homeassistant.components.fan import ( DOMAIN, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_SET_SPEED, FanEntity) -from homeassistant.components.isy994 import ( - ISY994_NODES, ISY994_PROGRAMS, ISYDevice) from homeassistant.helpers.typing import ConfigType +from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice + _LOGGER = logging.getLogger(__name__) VALUE_TO_STATE = { diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index cc39a6d1a3b..0ac50a5f384 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -2,10 +2,11 @@ import logging from typing import Callable -from homeassistant.components.isy994 import ISY994_NODES, ISYDevice from homeassistant.components.light import DOMAIN, SUPPORT_BRIGHTNESS, Light from homeassistant.helpers.typing import ConfigType +from . import ISY994_NODES, ISYDevice + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/isy994/lock.py b/homeassistant/components/isy994/lock.py index a2e8b1a1e56..92cb317ed20 100644 --- a/homeassistant/components/isy994/lock.py +++ b/homeassistant/components/isy994/lock.py @@ -2,12 +2,12 @@ import logging from typing import Callable -from homeassistant.components.isy994 import ( - ISY994_NODES, ISY994_PROGRAMS, ISYDevice) from homeassistant.components.lock import DOMAIN, LockDevice from homeassistant.const import STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED from homeassistant.helpers.typing import ConfigType +from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice + _LOGGER = logging.getLogger(__name__) VALUE_TO_STATE = { diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index 2115c19f496..43c016ed4d1 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -2,13 +2,13 @@ import logging from typing import Callable -from homeassistant.components.isy994 import ( - ISY994_NODES, ISY994_WEATHER, ISYDevice) from homeassistant.components.sensor import DOMAIN from homeassistant.const import ( - TEMP_CELSIUS, TEMP_FAHRENHEIT, UNIT_UV_INDEX, POWER_WATT) + POWER_WATT, TEMP_CELSIUS, TEMP_FAHRENHEIT, UNIT_UV_INDEX) from homeassistant.helpers.typing import ConfigType +from . import ISY994_NODES, ISY994_WEATHER, ISYDevice + _LOGGER = logging.getLogger(__name__) UOM_FRIENDLY_NAME = { diff --git a/homeassistant/components/isy994/switch.py b/homeassistant/components/isy994/switch.py index 96f17c80bef..5f0acd1b1e2 100644 --- a/homeassistant/components/isy994/switch.py +++ b/homeassistant/components/isy994/switch.py @@ -2,11 +2,11 @@ import logging from typing import Callable -from homeassistant.components.isy994 import ( - ISY994_NODES, ISY994_PROGRAMS, ISYDevice) from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.helpers.typing import ConfigType +from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/itunes/__init__.py b/homeassistant/components/itunes/__init__.py new file mode 100644 index 00000000000..561d9d47b37 --- /dev/null +++ b/homeassistant/components/itunes/__init__.py @@ -0,0 +1 @@ +"""The itunes component.""" diff --git a/homeassistant/components/media_player/itunes.py b/homeassistant/components/itunes/media_player.py similarity index 100% rename from homeassistant/components/media_player/itunes.py rename to homeassistant/components/itunes/media_player.py diff --git a/homeassistant/components/jewish_calendar/__init__.py b/homeassistant/components/jewish_calendar/__init__.py new file mode 100644 index 00000000000..93a60e363e1 --- /dev/null +++ b/homeassistant/components/jewish_calendar/__init__.py @@ -0,0 +1 @@ +"""The jewish_calendar component.""" diff --git a/homeassistant/components/sensor/jewish_calendar.py b/homeassistant/components/jewish_calendar/sensor.py similarity index 100% rename from homeassistant/components/sensor/jewish_calendar.py rename to homeassistant/components/jewish_calendar/sensor.py diff --git a/homeassistant/components/joaoapps_join/__init__.py b/homeassistant/components/joaoapps_join/__init__.py index adc856bdd3a..f1371deed2b 100644 --- a/homeassistant/components/joaoapps_join/__init__.py +++ b/homeassistant/components/joaoapps_join/__init__.py @@ -6,7 +6,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_NAME, CONF_API_KEY -REQUIREMENTS = ['python-join-api==0.0.2'] +REQUIREMENTS = ['python-join-api==0.0.4'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/joaoapps_join/notify.py b/homeassistant/components/joaoapps_join/notify.py index c586147d632..0137520049d 100644 --- a/homeassistant/components/joaoapps_join/notify.py +++ b/homeassistant/components/joaoapps_join/notify.py @@ -7,7 +7,7 @@ from homeassistant.components.notify import ( from homeassistant.const import CONF_API_KEY import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-join-api==0.0.2'] +REQUIREMENTS = ['python-join-api==0.0.4'] _LOGGER = logging.getLogger(__name__) @@ -61,4 +61,7 @@ class JoinNotificationService(BaseNotificationService): device_id=self._device_id, device_ids=self._device_ids, device_names=self._device_names, text=message, title=title, icon=data.get('icon'), smallicon=data.get('smallicon'), + image=data.get('image'), sound=data.get('sound'), + notification_id=data.get('notification_id'), url=data.get('url'), + tts=data.get('tts'), tts_language=data.get('tts_language'), vibration=data.get('vibration'), api_key=self._api_key) diff --git a/homeassistant/components/juicenet/sensor.py b/homeassistant/components/juicenet/sensor.py index 00b183fca46..6b55e539547 100644 --- a/homeassistant/components/juicenet/sensor.py +++ b/homeassistant/components/juicenet/sensor.py @@ -1,9 +1,10 @@ """Support for monitoring juicenet/juicepoint/juicebox based EVSE sensors.""" import logging -from homeassistant.const import TEMP_CELSIUS, POWER_WATT, ENERGY_WATT_HOUR +from homeassistant.const import ENERGY_WATT_HOUR, POWER_WATT, TEMP_CELSIUS from homeassistant.helpers.entity import Entity -from homeassistant.components.juicenet import JuicenetDevice, DOMAIN + +from . import DOMAIN, JuicenetDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/kankun/__init__.py b/homeassistant/components/kankun/__init__.py new file mode 100644 index 00000000000..dca32748c1c --- /dev/null +++ b/homeassistant/components/kankun/__init__.py @@ -0,0 +1 @@ +"""The kankun component.""" diff --git a/homeassistant/components/switch/kankun.py b/homeassistant/components/kankun/switch.py similarity index 100% rename from homeassistant/components/switch/kankun.py rename to homeassistant/components/kankun/switch.py diff --git a/homeassistant/components/keenetic_ndms2/__init__.py b/homeassistant/components/keenetic_ndms2/__init__.py new file mode 100644 index 00000000000..cb0a718d716 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/__init__.py @@ -0,0 +1 @@ +"""The keenetic_ndms2 component.""" diff --git a/homeassistant/components/device_tracker/keenetic_ndms2.py b/homeassistant/components/keenetic_ndms2/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/keenetic_ndms2.py rename to homeassistant/components/keenetic_ndms2/device_tracker.py diff --git a/homeassistant/components/kiwi/__init__.py b/homeassistant/components/kiwi/__init__.py new file mode 100644 index 00000000000..00e903b0c0d --- /dev/null +++ b/homeassistant/components/kiwi/__init__.py @@ -0,0 +1 @@ +"""The kiwi component.""" diff --git a/homeassistant/components/lock/kiwi.py b/homeassistant/components/kiwi/lock.py similarity index 100% rename from homeassistant/components/lock/kiwi.py rename to homeassistant/components/kiwi/lock.py diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index c84e5820f04..8ee21e24c5e 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -3,12 +3,12 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( PLATFORM_SCHEMA, BinarySensorDevice) -from homeassistant.components.knx import ( - ATTR_DISCOVER_DEVICES, DATA_KNX, KNXAutomation) from homeassistant.const import CONF_ADDRESS, CONF_DEVICE_CLASS, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from . import ATTR_DISCOVER_DEVICES, DATA_KNX, KNXAutomation + CONF_SIGNIFICANT_BIT = 'significant_bit' CONF_DEFAULT_SIGNIFICANT_BIT = 1 CONF_AUTOMATION = 'automation' diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index 921b2936d97..e11e5449326 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -5,11 +5,12 @@ from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( STATE_DRY, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, STATE_MANUAL, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) -from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from . import ATTR_DISCOVER_DEVICES, DATA_KNX + CONF_SETPOINT_SHIFT_ADDRESS = 'setpoint_shift_address' CONF_SETPOINT_SHIFT_STATE_ADDRESS = 'setpoint_shift_state_address' CONF_SETPOINT_SHIFT_STEP = 'setpoint_shift_step' diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index 9423983f9f7..b2b287d1e87 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -5,12 +5,13 @@ from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, PLATFORM_SCHEMA, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, CoverDevice) -from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.const import CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_utc_time_change +from . import ATTR_DISCOVER_DEVICES, DATA_KNX + CONF_MOVE_LONG_ADDRESS = 'move_long_address' CONF_MOVE_SHORT_ADDRESS = 'move_short_address' CONF_POSITION_ADDRESS = 'position_address' diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index baba7edd21a..cf59f1fc135 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -3,7 +3,6 @@ from enum import Enum import voluptuous as vol -from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, Light) @@ -12,6 +11,8 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util +from . import ATTR_DISCOVER_DEVICES, DATA_KNX + CONF_STATE_ADDRESS = 'state_address' CONF_BRIGHTNESS_ADDRESS = 'brightness_address' CONF_BRIGHTNESS_STATE_ADDRESS = 'brightness_state_address' diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py index 1e1d7f185f0..742252d1874 100644 --- a/homeassistant/components/knx/notify.py +++ b/homeassistant/components/knx/notify.py @@ -1,13 +1,14 @@ """Support for KNX/IP notification services.""" import voluptuous as vol -from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_ADDRESS, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from . import ATTR_DISCOVER_DEVICES, DATA_KNX + DEFAULT_NAME = 'KNX Notify' DEPENDENCIES = ['knx'] diff --git a/homeassistant/components/knx/scene.py b/homeassistant/components/knx/scene.py index b1bb2bf3109..4bf186c28ff 100644 --- a/homeassistant/components/knx/scene.py +++ b/homeassistant/components/knx/scene.py @@ -1,12 +1,13 @@ """Support for KNX scenes.""" import voluptuous as vol -from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.components.scene import CONF_PLATFORM, Scene from homeassistant.const import CONF_ADDRESS, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from . import ATTR_DISCOVER_DEVICES, DATA_KNX + CONF_SCENE_NUMBER = 'scene_number' DEFAULT_NAME = 'KNX SCENE' diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index abbb61e150d..7ddafe53be4 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -1,13 +1,14 @@ """Support for KNX/IP sensors.""" import voluptuous as vol -from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_TYPE from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from . import ATTR_DISCOVER_DEVICES, DATA_KNX + DEFAULT_NAME = 'KNX Sensor' DEPENDENCIES = ['knx'] diff --git a/homeassistant/components/knx/switch.py b/homeassistant/components/knx/switch.py index cef14fb74dc..e3beff39677 100644 --- a/homeassistant/components/knx/switch.py +++ b/homeassistant/components/knx/switch.py @@ -1,12 +1,13 @@ """Support for KNX/IP switches.""" import voluptuous as vol -from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import CONF_ADDRESS, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from . import ATTR_DISCOVER_DEVICES, DATA_KNX + CONF_STATE_ADDRESS = 'state_address' DEFAULT_NAME = 'KNX Switch' diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py new file mode 100644 index 00000000000..cbe20384103 --- /dev/null +++ b/homeassistant/components/kodi/__init__.py @@ -0,0 +1 @@ +"""The kodi component.""" diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/kodi/media_player.py similarity index 100% rename from homeassistant/components/media_player/kodi.py rename to homeassistant/components/kodi/media_player.py diff --git a/homeassistant/components/notify/kodi.py b/homeassistant/components/kodi/notify.py similarity index 95% rename from homeassistant/components/notify/kodi.py rename to homeassistant/components/kodi/notify.py index 50d2246cd29..7c2a60f3498 100644 --- a/homeassistant/components/notify/kodi.py +++ b/homeassistant/components/kodi/notify.py @@ -10,14 +10,15 @@ import aiohttp import voluptuous as vol from homeassistant.const import ( - ATTR_ICON, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, - CONF_PROXY_SSL) -from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_DATA, PLATFORM_SCHEMA, - BaseNotificationService) + ATTR_ICON, CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_PROXY_SSL, + CONF_USERNAME) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import ( + ATTR_DATA, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, + BaseNotificationService) + REQUIREMENTS = ['jsonrpc-async==0.6'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index 3a2dc3b2417..276e395817c 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -429,7 +429,7 @@ class KonnectedView(HomeAssistantView): if not pin: return self.json_message( - 'Switch on pin ' + pin_num + ' not configured', + format('Switch on pin {} not configured', pin_num), status_code=HTTP_NOT_FOUND) return self.json( diff --git a/homeassistant/components/konnected/binary_sensor.py b/homeassistant/components/konnected/binary_sensor.py index a47f81b9556..1fbfbea1861 100644 --- a/homeassistant/components/konnected/binary_sensor.py +++ b/homeassistant/components/konnected/binary_sensor.py @@ -2,14 +2,14 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.konnected import ( - DOMAIN as KONNECTED_DOMAIN, PIN_TO_ZONE, SIGNAL_SENSOR_UPDATE) from homeassistant.const import ( - CONF_DEVICES, CONF_TYPE, CONF_NAME, CONF_BINARY_SENSORS, ATTR_ENTITY_ID, - ATTR_STATE) + ATTR_ENTITY_ID, ATTR_STATE, CONF_BINARY_SENSORS, CONF_DEVICES, CONF_NAME, + CONF_TYPE) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from . import DOMAIN as KONNECTED_DOMAIN, PIN_TO_ZONE, SIGNAL_SENSOR_UPDATE + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['konnected'] diff --git a/homeassistant/components/konnected/handlers.py b/homeassistant/components/konnected/handlers.py index 6e92e7f20c8..bd93ee80e21 100644 --- a/homeassistant/components/konnected/handlers.py +++ b/homeassistant/components/konnected/handlers.py @@ -19,7 +19,7 @@ async def async_handle_state_update(hass, context, msg): _LOGGER.debug("[state handler] context: %s msg: %s", context, msg) entity_id = context.get(ATTR_ENTITY_ID) state = bool(int(msg.get(ATTR_STATE))) - if msg.get(CONF_INVERSE): + if context.get(CONF_INVERSE): state = not state async_dispatcher_send( diff --git a/homeassistant/components/konnected/sensor.py b/homeassistant/components/konnected/sensor.py index eb3f5511346..a48d1a58619 100644 --- a/homeassistant/components/konnected/sensor.py +++ b/homeassistant/components/konnected/sensor.py @@ -1,15 +1,16 @@ """Support for DHT and DS18B20 sensors attached to a Konnected device.""" import logging -from homeassistant.components.konnected.const import ( - DOMAIN as KONNECTED_DOMAIN, SIGNAL_DS18B20_NEW, SIGNAL_SENSOR_UPDATE) from homeassistant.const import ( - CONF_DEVICES, CONF_PIN, CONF_TYPE, CONF_NAME, CONF_SENSORS, + CONF_DEVICES, CONF_NAME, CONF_PIN, CONF_SENSORS, CONF_TYPE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity +from .const import ( + DOMAIN as KONNECTED_DOMAIN, SIGNAL_DS18B20_NEW, SIGNAL_SENSOR_UPDATE) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['konnected'] diff --git a/homeassistant/components/konnected/switch.py b/homeassistant/components/konnected/switch.py index 1a4b495297e..7384d62900e 100644 --- a/homeassistant/components/konnected/switch.py +++ b/homeassistant/components/konnected/switch.py @@ -1,12 +1,13 @@ """Support for wired switches attached to a Konnected device.""" import logging -from homeassistant.components.konnected import ( - DOMAIN as KONNECTED_DOMAIN, PIN_TO_ZONE, CONF_ACTIVATION, CONF_MOMENTARY, - CONF_PAUSE, CONF_REPEAT, STATE_LOW, STATE_HIGH) -from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( ATTR_STATE, CONF_DEVICES, CONF_NAME, CONF_PIN, CONF_SWITCHES) +from homeassistant.helpers.entity import ToggleEntity + +from . import ( + CONF_ACTIVATION, CONF_MOMENTARY, CONF_PAUSE, CONF_REPEAT, + DOMAIN as KONNECTED_DOMAIN, STATE_HIGH, STATE_LOW) _LOGGER = logging.getLogger(__name__) @@ -40,7 +41,8 @@ class KonnectedSwitch(ToggleEntity): self._pause = self._data.get(CONF_PAUSE) self._repeat = self._data.get(CONF_REPEAT) self._state = self._boolean_state(self._data.get(ATTR_STATE)) - self._unique_id = '{}-{}'.format(device_id, PIN_TO_ZONE[pin_num]) + self._unique_id = '{}-{}'.format(device_id, hash(frozenset( + {self._pin_num, self._momentary, self._pause, self._repeat}))) self._name = self._data.get(CONF_NAME) @property diff --git a/homeassistant/components/kwb/__init__.py b/homeassistant/components/kwb/__init__.py new file mode 100644 index 00000000000..e48a7b79d40 --- /dev/null +++ b/homeassistant/components/kwb/__init__.py @@ -0,0 +1 @@ +"""The kwb component.""" diff --git a/homeassistant/components/sensor/kwb.py b/homeassistant/components/kwb/sensor.py similarity index 100% rename from homeassistant/components/sensor/kwb.py rename to homeassistant/components/kwb/sensor.py diff --git a/homeassistant/components/lacrosse/__init__.py b/homeassistant/components/lacrosse/__init__.py new file mode 100644 index 00000000000..5334c696aee --- /dev/null +++ b/homeassistant/components/lacrosse/__init__.py @@ -0,0 +1 @@ +"""The lacrosse component.""" diff --git a/homeassistant/components/sensor/lacrosse.py b/homeassistant/components/lacrosse/sensor.py similarity index 100% rename from homeassistant/components/sensor/lacrosse.py rename to homeassistant/components/lacrosse/sensor.py diff --git a/homeassistant/components/lametric/notify.py b/homeassistant/components/lametric/notify.py index 9903676d9f9..358bb056b00 100644 --- a/homeassistant/components/lametric/notify.py +++ b/homeassistant/components/lametric/notify.py @@ -5,11 +5,11 @@ from requests.exceptions import ConnectionError as RequestsConnectionError import voluptuous as vol from homeassistant.components.notify import ( - ATTR_TARGET, ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService) + ATTR_DATA, ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_ICON import homeassistant.helpers.config_validation as cv -from homeassistant.components.lametric import DOMAIN as LAMETRIC_DOMAIN +from . import DOMAIN as LAMETRIC_DOMAIN REQUIREMENTS = ['lmnotify==0.0.4'] diff --git a/homeassistant/components/lannouncer/__init__.py b/homeassistant/components/lannouncer/__init__.py new file mode 100644 index 00000000000..479e9893f84 --- /dev/null +++ b/homeassistant/components/lannouncer/__init__.py @@ -0,0 +1 @@ +"""The lannouncer component.""" diff --git a/homeassistant/components/notify/lannouncer.py b/homeassistant/components/lannouncer/notify.py similarity index 92% rename from homeassistant/components/notify/lannouncer.py rename to homeassistant/components/lannouncer/notify.py index 5677f38b06c..3b2e72b398c 100644 --- a/homeassistant/components/notify/lannouncer.py +++ b/homeassistant/components/lannouncer/notify.py @@ -5,16 +5,17 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.lannouncer/ """ import logging - -from urllib.parse import urlencode import socket +from urllib.parse import urlencode + import voluptuous as vol -from homeassistant.components.notify import ( - PLATFORM_SCHEMA, ATTR_DATA, BaseNotificationService) -from homeassistant.const import (CONF_HOST, CONF_PORT) +from homeassistant.const import CONF_HOST, CONF_PORT import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import (ATTR_DATA, PLATFORM_SCHEMA, + BaseNotificationService) + ATTR_METHOD = 'method' ATTR_METHOD_DEFAULT = 'speak' ATTR_METHOD_ALLOWED = ['speak', 'alarm'] diff --git a/homeassistant/components/lastfm/__init__.py b/homeassistant/components/lastfm/__init__.py new file mode 100644 index 00000000000..20128342931 --- /dev/null +++ b/homeassistant/components/lastfm/__init__.py @@ -0,0 +1 @@ +"""The lastfm component.""" diff --git a/homeassistant/components/sensor/lastfm.py b/homeassistant/components/lastfm/sensor.py similarity index 99% rename from homeassistant/components/sensor/lastfm.py rename to homeassistant/components/lastfm/sensor.py index bb5a09771c2..e4e28eff4f1 100644 --- a/homeassistant/components/sensor/lastfm.py +++ b/homeassistant/components/lastfm/sensor.py @@ -9,7 +9,7 @@ from homeassistant.const import CONF_API_KEY, ATTR_ATTRIBUTION import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['pylast==3.0.0'] +REQUIREMENTS = ['pylast==3.1.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/launch_library/__init__.py b/homeassistant/components/launch_library/__init__.py new file mode 100644 index 00000000000..ba4b78ab31f --- /dev/null +++ b/homeassistant/components/launch_library/__init__.py @@ -0,0 +1 @@ +"""The launch_library component.""" diff --git a/homeassistant/components/sensor/launch_library.py b/homeassistant/components/launch_library/sensor.py similarity index 100% rename from homeassistant/components/sensor/launch_library.py rename to homeassistant/components/launch_library/sensor.py diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index c7c180737f0..e380c2bb4a1 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -3,17 +3,21 @@ import logging import voluptuous as vol -from homeassistant.components.lcn.const import ( - CONF_CONNECTIONS, CONF_DIM_MODE, CONF_DIMMABLE, CONF_MOTOR, CONF_OUTPUT, - CONF_SK_NUM_TRIES, CONF_TRANSITION, DATA_LCN, DEFAULT_NAME, DIM_MODES, - DOMAIN, MOTOR_PORTS, OUTPUT_PORTS, PATTERN_ADDRESS, RELAY_PORTS) from homeassistant.const import ( CONF_ADDRESS, CONF_COVERS, CONF_HOST, CONF_LIGHTS, CONF_NAME, - CONF_PASSWORD, CONF_PORT, CONF_SWITCHES, CONF_USERNAME) + CONF_PASSWORD, CONF_PORT, CONF_SENSORS, CONF_SWITCHES, + CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.entity import Entity +from .const import ( + CONF_CONNECTIONS, CONF_DIM_MODE, CONF_DIMMABLE, CONF_MOTOR, CONF_OUTPUT, + CONF_SK_NUM_TRIES, CONF_SOURCE, CONF_TRANSITION, DATA_LCN, DEFAULT_NAME, + DIM_MODES, DOMAIN, LED_PORTS, LOGICOP_PORTS, MOTOR_PORTS, OUTPUT_PORTS, + PATTERN_ADDRESS, RELAY_PORTS, S0_INPUTS, SETPOINTS, THRESHOLDS, VAR_UNITS, + VARIABLES) + _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['pypck==0.5.9'] @@ -78,6 +82,17 @@ LIGHTS_SCHEMA = vol.Schema({ lambda value: value * 1000), }) +SENSORS_SCHEMA = vol.Schema({ + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_ADDRESS): is_address, + vol.Required(CONF_SOURCE): vol.All(vol.Upper, + vol.In(VARIABLES + SETPOINTS + + THRESHOLDS + S0_INPUTS + + LED_PORTS + LOGICOP_PORTS)), + vol.Optional(CONF_UNIT_OF_MEASUREMENT, default='native'): + vol.All(vol.Upper, vol.In(VAR_UNITS)) +}) + SWITCHES_SCHEMA = vol.Schema({ vol.Required(CONF_NAME): cv.string, vol.Required(CONF_ADDRESS): is_address, @@ -104,6 +119,8 @@ CONFIG_SCHEMA = vol.Schema({ cv.ensure_list, [COVERS_SCHEMA]), vol.Optional(CONF_LIGHTS): vol.All( cv.ensure_list, [LIGHTS_SCHEMA]), + vol.Optional(CONF_SENSORS): vol.All( + cv.ensure_list, [SENSORS_SCHEMA]), vol.Optional(CONF_SWITCHES): vol.All( cv.ensure_list, [SWITCHES_SCHEMA]) }) @@ -162,6 +179,7 @@ async def async_setup(hass, config): # load platforms for component, conf_key in (('cover', CONF_COVERS), ('light', CONF_LIGHTS), + ('sensor', CONF_SENSORS), ('switch', CONF_SWITCHES)): if conf_key in config[DOMAIN]: hass.async_create_task( diff --git a/homeassistant/components/lcn/const.py b/homeassistant/components/lcn/const.py index 02b35b06797..ee7a3a79cde 100644 --- a/homeassistant/components/lcn/const.py +++ b/homeassistant/components/lcn/const.py @@ -1,6 +1,9 @@ +# coding: utf-8 """Constants for the LCN component.""" import re +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT + DOMAIN = 'lcn' DATA_LCN = 'lcn' DEFAULT_NAME = 'pchk' @@ -16,11 +19,47 @@ CONF_DIM_MODE = 'dim_mode' CONF_DIMMABLE = 'dimmable' CONF_TRANSITION = 'transition' CONF_MOTOR = 'motor' +CONF_SOURCE = 'source' DIM_MODES = ['STEPS50', 'STEPS200'] + OUTPUT_PORTS = ['OUTPUT1', 'OUTPUT2', 'OUTPUT3', 'OUTPUT4'] + RELAY_PORTS = ['RELAY1', 'RELAY2', 'RELAY3', 'RELAY4', 'RELAY5', 'RELAY6', 'RELAY7', 'RELAY8', 'MOTORONOFF1', 'MOTORUPDOWN1', 'MOTORONOFF2', 'MOTORUPDOWN2', 'MOTORONOFF3', 'MOTORUPDOWN3', 'MOTORONOFF4', 'MOTORUPDOWN4'] + MOTOR_PORTS = ['MOTOR1', 'MOTOR2', 'MOTOR3', 'MOTOR4'] + +LED_PORTS = ['LED1', 'LED2', 'LED3', 'LED4', 'LED5', 'LED6', + 'LED7', 'LED8', 'LED9', 'LED10', 'LED11', 'LED12'] + +LOGICOP_PORTS = ['LOGICOP1', 'LOGICOP2', 'LOGICOP3', 'LOGICOP4'] + +VARIABLES = ['VAR1ORTVAR', 'VAR2ORR1VAR', 'VAR3ORR2VAR', + 'TVAR', 'R1VAR', 'R2VAR', + 'VAR1', 'VAR2', 'VAR3', 'VAR4', 'VAR5', 'VAR6', + 'VAR7', 'VAR8', 'VAR9', 'VAR10', 'VAR11', 'VAR12'] + +SETPOINTS = ['R1VARSETPOINT', 'R2VARSETPOINT'] + +THRESHOLDS = ['THRS1', 'THRS2', 'THRS3', 'THRS4', 'THRS5', + 'THRS2_1', 'THRS2_2', 'THRS2_3', 'THRS2_4', + 'THRS3_1', 'THRS3_2', 'THRS3_3', 'THRS3_4', + 'THRS4_1', 'THRS4_2', 'THRS4_3', 'THRS4_4'] + +S0_INPUTS = ['S0INPUT1', 'S0INPUT2', 'S0INPUT3', 'S0INPUT4'] + +VAR_UNITS = ['', 'LCN', 'NATIVE', + TEMP_CELSIUS, + '°K', + TEMP_FAHRENHEIT, + 'LUX_T', 'LX_T', + 'LUX_I', 'LUX', 'LX', + 'M/S', 'METERPERSECOND', + '%', 'PERCENT', + 'PPM', + 'VOLT', 'V', + 'AMPERE', 'AMP', 'A', + 'DEGREE', '°'] diff --git a/homeassistant/components/lcn/cover.py b/homeassistant/components/lcn/cover.py index 4b4542fd623..a32ff7c23f4 100755 --- a/homeassistant/components/lcn/cover.py +++ b/homeassistant/components/lcn/cover.py @@ -1,10 +1,10 @@ """Support for LCN covers.""" from homeassistant.components.cover import CoverDevice -from homeassistant.components.lcn import LcnDevice, get_connection -from homeassistant.components.lcn.const import ( - CONF_CONNECTIONS, CONF_MOTOR, DATA_LCN) from homeassistant.const import CONF_ADDRESS +from . import LcnDevice, get_connection +from .const import CONF_CONNECTIONS, CONF_MOTOR, DATA_LCN + DEPENDENCIES = ['lcn'] diff --git a/homeassistant/components/lcn/light.py b/homeassistant/components/lcn/light.py index 5f1008cbd57..00b78259354 100644 --- a/homeassistant/components/lcn/light.py +++ b/homeassistant/components/lcn/light.py @@ -1,13 +1,14 @@ """Support for LCN lights.""" -from homeassistant.components.lcn import LcnDevice, get_connection -from homeassistant.components.lcn.const import ( - CONF_CONNECTIONS, CONF_DIMMABLE, CONF_OUTPUT, CONF_TRANSITION, DATA_LCN, - OUTPUT_PORTS) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, Light) from homeassistant.const import CONF_ADDRESS +from . import LcnDevice, get_connection +from .const import ( + CONF_CONNECTIONS, CONF_DIMMABLE, CONF_OUTPUT, CONF_TRANSITION, DATA_LCN, + OUTPUT_PORTS) + DEPENDENCIES = ['lcn'] diff --git a/homeassistant/components/lcn/sensor.py b/homeassistant/components/lcn/sensor.py new file mode 100755 index 00000000000..5e50d092ada --- /dev/null +++ b/homeassistant/components/lcn/sensor.py @@ -0,0 +1,118 @@ +"""Support for LCN sensors.""" +from homeassistant.const import CONF_ADDRESS, CONF_UNIT_OF_MEASUREMENT + +from . import LcnDevice, get_connection +from .const import ( + CONF_CONNECTIONS, CONF_SOURCE, DATA_LCN, LED_PORTS, S0_INPUTS, SETPOINTS, + THRESHOLDS, VARIABLES) + +DEPENDENCIES = ['lcn'] + + +async def async_setup_platform(hass, hass_config, async_add_entities, + discovery_info=None): + """Set up the LCN sensor platform.""" + if discovery_info is None: + return + + import pypck + + devices = [] + for config in discovery_info: + address, connection_id = config[CONF_ADDRESS] + addr = pypck.lcn_addr.LcnAddr(*address) + connections = hass.data[DATA_LCN][CONF_CONNECTIONS] + connection = get_connection(connections, connection_id) + address_connection = connection.get_address_conn(addr) + + if config[CONF_SOURCE] in VARIABLES + SETPOINTS + THRESHOLDS + \ + S0_INPUTS: + device = LcnVariableSensor(config, address_connection) + else: # in LED_PORTS + LOGICOP_PORTS + device = LcnLedLogicSensor(config, address_connection) + + devices.append(device) + + async_add_entities(devices) + + +class LcnVariableSensor(LcnDevice): + """Representation of a LCN sensor for variables.""" + + def __init__(self, config, address_connection): + """Initialize the LCN sensor.""" + super().__init__(config, address_connection) + + self.variable = self.pypck.lcn_defs.Var[config[CONF_SOURCE]] + self.unit = self.pypck.lcn_defs.VarUnit.parse( + config[CONF_UNIT_OF_MEASUREMENT]) + + self._value = None + + async def async_added_to_hass(self): + """Run when entity about to be added to hass.""" + await super().async_added_to_hass() + self.hass.async_create_task( + self.address_connection.activate_status_request_handler( + self.variable)) + + @property + def state(self): + """Return the state of the entity.""" + return self._value + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self.unit.value + + def input_received(self, input_obj): + """Set sensor value when LCN input object (command) is received.""" + if not isinstance(input_obj, self.pypck.inputs.ModStatusVar) or \ + input_obj.get_var() != self.variable: + return + + self._value = (input_obj.get_value().to_var_unit(self.unit)) + self.async_schedule_update_ha_state() + + +class LcnLedLogicSensor(LcnDevice): + """Representation of a LCN sensor for leds and logicops.""" + + def __init__(self, config, address_connection): + """Initialize the LCN sensor.""" + super().__init__(config, address_connection) + + if config[CONF_SOURCE] in LED_PORTS: + self.source = self.pypck.lcn_defs.LedPort[config[CONF_SOURCE]] + else: + self.source = self.pypck.lcn_defs.LogicOpPort[config[CONF_SOURCE]] + + self._value = None + + async def async_added_to_hass(self): + """Run when entity about to be added to hass.""" + await super().async_added_to_hass() + self.hass.async_create_task( + self.address_connection.activate_status_request_handler( + self.source)) + + @property + def state(self): + """Return the state of the entity.""" + return self._value + + def input_received(self, input_obj): + """Set sensor value when LCN input object (command) is received.""" + if not isinstance(input_obj, + self.pypck.inputs.ModStatusLedsAndLogicOps): + return + + if self.source in self.pypck.lcn_defs.LedPort: + self._value = input_obj.get_led_state( + self.source.value).name.lower() + elif self.source in self.pypck.lcn_defs.LogicOpPort: + self._value = input_obj.get_logic_op_state( + self.source.value).name.lower() + + self.async_schedule_update_ha_state() diff --git a/homeassistant/components/lcn/switch.py b/homeassistant/components/lcn/switch.py index 09f35d26718..7c375f4a598 100755 --- a/homeassistant/components/lcn/switch.py +++ b/homeassistant/components/lcn/switch.py @@ -1,10 +1,10 @@ """Support for LCN switches.""" -from homeassistant.components.lcn import LcnDevice, get_connection -from homeassistant.components.lcn.const import ( - CONF_CONNECTIONS, CONF_OUTPUT, DATA_LCN, OUTPUT_PORTS) from homeassistant.components.switch import SwitchDevice from homeassistant.const import CONF_ADDRESS +from . import LcnDevice, get_connection +from .const import CONF_CONNECTIONS, CONF_OUTPUT, DATA_LCN, OUTPUT_PORTS + DEPENDENCIES = ['lcn'] diff --git a/homeassistant/components/lg_netcast/__init__.py b/homeassistant/components/lg_netcast/__init__.py new file mode 100644 index 00000000000..232d7bd10b8 --- /dev/null +++ b/homeassistant/components/lg_netcast/__init__.py @@ -0,0 +1 @@ +"""The lg_netcast component.""" diff --git a/homeassistant/components/media_player/lg_netcast.py b/homeassistant/components/lg_netcast/media_player.py similarity index 100% rename from homeassistant/components/media_player/lg_netcast.py rename to homeassistant/components/lg_netcast/media_player.py diff --git a/homeassistant/components/lg_soundbar/__init__.py b/homeassistant/components/lg_soundbar/__init__.py new file mode 100644 index 00000000000..175153556f9 --- /dev/null +++ b/homeassistant/components/lg_soundbar/__init__.py @@ -0,0 +1 @@ +"""The lg_soundbar component.""" diff --git a/homeassistant/components/media_player/lg_soundbar.py b/homeassistant/components/lg_soundbar/media_player.py similarity index 100% rename from homeassistant/components/media_player/lg_soundbar.py rename to homeassistant/components/lg_soundbar/media_player.py diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index 19a9f7583ec..014ca9ae6c8 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -16,9 +16,6 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_TRANSITION, VALID_BRIGHTNESS, VALID_BRIGHTNESS_PCT, Light, preprocess_turn_on_alternatives) -from homeassistant.components.lifx import ( - DOMAIN as LIFX_DOMAIN, DATA_LIFX_MANAGER, CONF_SERVER, CONF_PORT, - CONF_BROADCAST) from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -27,6 +24,10 @@ from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.service import async_extract_entity_ids import homeassistant.util.color as color_util +from . import ( + CONF_BROADCAST, CONF_PORT, CONF_SERVER, DATA_LIFX_MANAGER, + DOMAIN as LIFX_DOMAIN) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['lifx'] diff --git a/homeassistant/components/lifx_cloud/__init__.py b/homeassistant/components/lifx_cloud/__init__.py new file mode 100644 index 00000000000..c524b629671 --- /dev/null +++ b/homeassistant/components/lifx_cloud/__init__.py @@ -0,0 +1 @@ +"""The lifx_cloud component.""" diff --git a/homeassistant/components/scene/lifx_cloud.py b/homeassistant/components/lifx_cloud/scene.py similarity index 100% rename from homeassistant/components/scene/lifx_cloud.py rename to homeassistant/components/lifx_cloud/scene.py diff --git a/homeassistant/components/lifx_legacy/__init__.py b/homeassistant/components/lifx_legacy/__init__.py new file mode 100644 index 00000000000..83d5a0e5048 --- /dev/null +++ b/homeassistant/components/lifx_legacy/__init__.py @@ -0,0 +1 @@ +"""The lifx_legacy component.""" diff --git a/homeassistant/components/light/lifx_legacy.py b/homeassistant/components/lifx_legacy/light.py similarity index 100% rename from homeassistant/components/light/lifx_legacy.py rename to homeassistant/components/lifx_legacy/light.py diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml index a2863482477..cdf82e97429 100644 --- a/homeassistant/components/light/services.yaml +++ b/homeassistant/components/light/services.yaml @@ -178,29 +178,3 @@ xiaomi_miio_set_delayed_turn_off: time_period: description: Time period for the delayed turn off. example: "5, '0:05', {'minutes': 5}" - -yeelight_set_mode: - description: Set a operation mode. - fields: - entity_id: - description: Name of the light entity. - example: 'light.yeelight' - mode: - description: Operation mode. Valid values are 'last', 'normal', 'rgb', 'hsv', 'color_flow', 'moonlight'. - example: 'moonlight' - -yeelight_start_flow: - description: Start a custom flow, using transitions from https://yeelight.readthedocs.io/en/stable/yeelight.html#flow-objects - fields: - entity_id: - description: Name of the light entity. - example: 'light.yeelight' - count: - description: The number of times to run this flow (0 to run forever). - example: 0 - action: - description: The action to take after the flow stops. Can be 'recover', 'stay', 'off'. (default 'recover') - example: 'stay' - transitions: - description: Array of transitions, for desired effect. Examples https://yeelight.readthedocs.io/en/stable/flow.html - example: '[{ "TemperatureTransition": [1900, 1000, 80] }, { "TemperatureTransition": [1900, 1000, 10] }]' diff --git a/homeassistant/components/lightwave/light.py b/homeassistant/components/lightwave/light.py index 1dfbac37c88..f22533d2548 100644 --- a/homeassistant/components/lightwave/light.py +++ b/homeassistant/components/lightwave/light.py @@ -1,9 +1,10 @@ """Support for LightwaveRF lights.""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) -from homeassistant.components.lightwave import LIGHTWAVE_LINK from homeassistant.const import CONF_NAME +from . import LIGHTWAVE_LINK + DEPENDENCIES = ['lightwave'] MAX_BRIGHTNESS = 255 diff --git a/homeassistant/components/lightwave/switch.py b/homeassistant/components/lightwave/switch.py index d6c00b7fddb..dfa93b4b151 100644 --- a/homeassistant/components/lightwave/switch.py +++ b/homeassistant/components/lightwave/switch.py @@ -1,8 +1,9 @@ """Support for LightwaveRF switches.""" -from homeassistant.components.lightwave import LIGHTWAVE_LINK from homeassistant.components.switch import SwitchDevice from homeassistant.const import CONF_NAME +from . import LIGHTWAVE_LINK + DEPENDENCIES = ['lightwave'] diff --git a/homeassistant/components/limitlessled/__init__.py b/homeassistant/components/limitlessled/__init__.py new file mode 100644 index 00000000000..dd3c339456c --- /dev/null +++ b/homeassistant/components/limitlessled/__init__.py @@ -0,0 +1 @@ +"""The limitlessled component.""" diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/limitlessled/light.py similarity index 100% rename from homeassistant/components/light/limitlessled.py rename to homeassistant/components/limitlessled/light.py diff --git a/homeassistant/components/linksys_ap/__init__.py b/homeassistant/components/linksys_ap/__init__.py new file mode 100644 index 00000000000..5898aa36e98 --- /dev/null +++ b/homeassistant/components/linksys_ap/__init__.py @@ -0,0 +1 @@ +"""The linksys_ap component.""" diff --git a/homeassistant/components/device_tracker/linksys_ap.py b/homeassistant/components/linksys_ap/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/linksys_ap.py rename to homeassistant/components/linksys_ap/device_tracker.py diff --git a/homeassistant/components/linksys_smart/__init__.py b/homeassistant/components/linksys_smart/__init__.py new file mode 100644 index 00000000000..489596c7ec6 --- /dev/null +++ b/homeassistant/components/linksys_smart/__init__.py @@ -0,0 +1 @@ +"""The linksys_smart component.""" diff --git a/homeassistant/components/device_tracker/linksys_smart.py b/homeassistant/components/linksys_smart/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/linksys_smart.py rename to homeassistant/components/linksys_smart/device_tracker.py diff --git a/homeassistant/components/linky/__init__.py b/homeassistant/components/linky/__init__.py new file mode 100644 index 00000000000..345f13e8a57 --- /dev/null +++ b/homeassistant/components/linky/__init__.py @@ -0,0 +1 @@ +"""The linky component.""" diff --git a/homeassistant/components/sensor/linky.py b/homeassistant/components/linky/sensor.py similarity index 100% rename from homeassistant/components/sensor/linky.py rename to homeassistant/components/linky/sensor.py diff --git a/homeassistant/components/linode/binary_sensor.py b/homeassistant/components/linode/binary_sensor.py index a05681497de..19455917dbb 100644 --- a/homeassistant/components/linode/binary_sensor.py +++ b/homeassistant/components/linode/binary_sensor.py @@ -5,11 +5,12 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( PLATFORM_SCHEMA, BinarySensorDevice) -from homeassistant.components.linode import ( +import homeassistant.helpers.config_validation as cv + +from . import ( ATTR_CREATED, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, ATTR_NODE_ID, ATTR_NODE_NAME, ATTR_REGION, ATTR_VCPUS, CONF_NODES, DATA_LINODE) -import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/linode/switch.py b/homeassistant/components/linode/switch.py index 0cab2f4d0f2..e5f97ef756e 100644 --- a/homeassistant/components/linode/switch.py +++ b/homeassistant/components/linode/switch.py @@ -3,12 +3,13 @@ import logging import voluptuous as vol -from homeassistant.components.linode import ( +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +import homeassistant.helpers.config_validation as cv + +from . import ( ATTR_CREATED, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, ATTR_NODE_ID, ATTR_NODE_NAME, ATTR_REGION, ATTR_VCPUS, CONF_NODES, DATA_LINODE) -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice -import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/linux_battery/__init__.py b/homeassistant/components/linux_battery/__init__.py new file mode 100644 index 00000000000..a0882bd0f89 --- /dev/null +++ b/homeassistant/components/linux_battery/__init__.py @@ -0,0 +1 @@ +"""The linux_battery component.""" diff --git a/homeassistant/components/sensor/linux_battery.py b/homeassistant/components/linux_battery/sensor.py similarity index 100% rename from homeassistant/components/sensor/linux_battery.py rename to homeassistant/components/linux_battery/sensor.py diff --git a/homeassistant/components/light/litejet.py b/homeassistant/components/litejet/light.py similarity index 100% rename from homeassistant/components/light/litejet.py rename to homeassistant/components/litejet/light.py diff --git a/homeassistant/components/scene/litejet.py b/homeassistant/components/litejet/scene.py similarity index 100% rename from homeassistant/components/scene/litejet.py rename to homeassistant/components/litejet/scene.py diff --git a/homeassistant/components/switch/litejet.py b/homeassistant/components/litejet/switch.py similarity index 100% rename from homeassistant/components/switch/litejet.py rename to homeassistant/components/litejet/switch.py diff --git a/homeassistant/components/liveboxplaytv/__init__.py b/homeassistant/components/liveboxplaytv/__init__.py new file mode 100644 index 00000000000..384c0e4c34b --- /dev/null +++ b/homeassistant/components/liveboxplaytv/__init__.py @@ -0,0 +1 @@ +"""The liveboxplaytv component.""" diff --git a/homeassistant/components/media_player/liveboxplaytv.py b/homeassistant/components/liveboxplaytv/media_player.py similarity index 100% rename from homeassistant/components/media_player/liveboxplaytv.py rename to homeassistant/components/liveboxplaytv/media_player.py diff --git a/homeassistant/components/llamalab_automate/__init__.py b/homeassistant/components/llamalab_automate/__init__.py new file mode 100644 index 00000000000..f60abfb93c9 --- /dev/null +++ b/homeassistant/components/llamalab_automate/__init__.py @@ -0,0 +1 @@ +"""The llamalab_automate component.""" diff --git a/homeassistant/components/notify/llamalab_automate.py b/homeassistant/components/llamalab_automate/notify.py similarity index 92% rename from homeassistant/components/notify/llamalab_automate.py rename to homeassistant/components/llamalab_automate/notify.py index 0ddcb450bcf..6b59d11e0a3 100644 --- a/homeassistant/components/notify/llamalab_automate.py +++ b/homeassistant/components/llamalab_automate/notify.py @@ -5,14 +5,15 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.llamalab_automate/ """ import logging + import requests import voluptuous as vol -from homeassistant.components.notify import ( - BaseNotificationService, PLATFORM_SCHEMA) from homeassistant.const import CONF_API_KEY, CONF_DEVICE from homeassistant.helpers import config_validation as cv +from homeassistant.components.notify import (PLATFORM_SCHEMA, + BaseNotificationService) _LOGGER = logging.getLogger(__name__) _RESOURCE = 'https://llamalab.com/automate/cloud/message' diff --git a/homeassistant/components/local_file/__init__.py b/homeassistant/components/local_file/__init__.py new file mode 100644 index 00000000000..4ad752bbc54 --- /dev/null +++ b/homeassistant/components/local_file/__init__.py @@ -0,0 +1 @@ +"""The local_file component.""" diff --git a/homeassistant/components/camera/local_file.py b/homeassistant/components/local_file/camera.py similarity index 96% rename from homeassistant/components/camera/local_file.py rename to homeassistant/components/local_file/camera.py index d306509b762..56780d16f56 100644 --- a/homeassistant/components/camera/local_file.py +++ b/homeassistant/components/local_file/camera.py @@ -12,7 +12,8 @@ import voluptuous as vol from homeassistant.const import CONF_NAME from homeassistant.components.camera import ( - Camera, CAMERA_SERVICE_SCHEMA, DOMAIN, PLATFORM_SCHEMA) + Camera, CAMERA_SERVICE_SCHEMA, PLATFORM_SCHEMA) +from homeassistant.components.camera.const import DOMAIN from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/locative/.translations/bg.json b/homeassistant/components/locative/.translations/bg.json new file mode 100644 index 00000000000..1e80c86e862 --- /dev/null +++ b/homeassistant/components/locative/.translations/bg.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Home Assistant \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u0435\u043d \u043e\u0442 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0437\u0430 \u0434\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0432\u0430 \u0441\u044a\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0442 Geofency", + "one_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + }, + "create_entry": { + "default": "\u0417\u0430 \u0434\u0430 \u0438\u0437\u043f\u0440\u0430\u0449\u0430\u0442\u0435 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043a\u044a\u043c Home Assistant, \u0449\u0435 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u0442\u0430 webhook \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e Locative. \n\n \u041f\u043e\u043f\u044a\u043b\u043d\u0435\u0442\u0435 \u0441\u043b\u0435\u0434\u043d\u0430\u0442\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f: \n\n - URL: ` {webhook_url} ` \n - \u041c\u0435\u0442\u043e\u0434: POST \n\n \u0412\u0438\u0436\u0442\u0435 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0442\u0430]({docs_url}) \u0437\u0430 \u043f\u043e\u0432\u0435\u0447\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0438." + }, + "step": { + "user": { + "description": "\u0421\u0438\u0433\u0443\u0440\u043d\u0438 \u043b\u0438 \u0441\u0442\u0435, \u0447\u0435 \u0438\u0441\u043a\u0430\u0442\u0435 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Locative Webhook?", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u043d\u0435 \u043d\u0430 Locative Webhook" + } + }, + "title": "Locative Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/fr.json b/homeassistant/components/locative/.translations/fr.json index 81950c49b4c..a90f7ff989c 100644 --- a/homeassistant/components/locative/.translations/fr.json +++ b/homeassistant/components/locative/.translations/fr.json @@ -3,6 +3,16 @@ "abort": { "not_internet_accessible": "Votre instance Home Assistant doit \u00eatre accessible \u00e0 partir d'Internet pour recevoir les messages Geofency.", "one_instance_allowed": "Une seule instance est n\u00e9cessaire." - } + }, + "create_entry": { + "default": "Pour envoyer des localisations \u00e0 Home Assistant, vous devez configurer la fonctionnalit\u00e9 Webhook dans l'application Locative. \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n\n Voir [la documentation] ( {docs_url} ) pour plus de d\u00e9tails." + }, + "step": { + "user": { + "description": "\u00cates-vous s\u00fbr de vouloir configurer le Webhook Locative ?", + "title": "Configurer le Locative Webhook" + } + }, + "title": "Locative Webhook" } } \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/pl.json b/homeassistant/components/locative/.translations/pl.json index 89f6881593a..917744c32fd 100644 --- a/homeassistant/components/locative/.translations/pl.json +++ b/homeassistant/components/locative/.translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistant'a, musisz skonfigurowa\u0107 webhook w aplikacji Locative. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistant'a, musisz skonfigurowa\u0107 webhook w aplikacji Locative. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/locative/.translations/zh-Hans.json b/homeassistant/components/locative/.translations/zh-Hans.json index 967671de535..d6c831d5a0e 100644 --- a/homeassistant/components/locative/.translations/zh-Hans.json +++ b/homeassistant/components/locative/.translations/zh-Hans.json @@ -4,6 +4,9 @@ "not_internet_accessible": "\u60a8\u7684 Home Assistant \u5b9e\u4f8b\u9700\u8981\u53ef\u4ece\u4e92\u8054\u7f51\u8bbf\u95ee\u4ee5\u63a5\u6536 Geofency \u6d88\u606f\u3002", "one_instance_allowed": "\u53ea\u6709\u4e00\u4e2a\u5b9e\u4f8b\u662f\u5fc5\u9700\u7684\u3002" }, + "create_entry": { + "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e Locative app \u7684 Webhook \u529f\u80fd\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002" + }, "step": { "user": { "description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e\u5b9a\u4f4d Webhook\u5417\uff1f", diff --git a/homeassistant/components/locative/device_tracker.py b/homeassistant/components/locative/device_tracker.py index 78090914b2c..9dbf7e74fe3 100644 --- a/homeassistant/components/locative/device_tracker.py +++ b/homeassistant/components/locative/device_tracker.py @@ -6,13 +6,13 @@ https://home-assistant.io/components/device_tracker.locative/ """ import logging -from homeassistant.components.device_tracker import \ - DOMAIN as DEVICE_TRACKER_DOMAIN -from homeassistant.components.locative import DOMAIN as LOCATIVE_DOMAIN -from homeassistant.components.locative import TRACKER_UPDATE +from homeassistant.components.device_tracker import ( + DOMAIN as DEVICE_TRACKER_DOMAIN) from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util import slugify +from . import DOMAIN as LOCATIVE_DOMAIN, TRACKER_UPDATE + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['locative'] diff --git a/homeassistant/components/lockitron/__init__.py b/homeassistant/components/lockitron/__init__.py new file mode 100644 index 00000000000..d2f9f749533 --- /dev/null +++ b/homeassistant/components/lockitron/__init__.py @@ -0,0 +1 @@ +"""The lockitron component.""" diff --git a/homeassistant/components/lock/lockitron.py b/homeassistant/components/lockitron/lock.py similarity index 100% rename from homeassistant/components/lock/lockitron.py rename to homeassistant/components/lockitron/lock.py diff --git a/homeassistant/components/logi_circle/camera.py b/homeassistant/components/logi_circle/camera.py index 4f349dd986e..ff6f14431d5 100644 --- a/homeassistant/components/logi_circle/camera.py +++ b/homeassistant/components/logi_circle/camera.py @@ -1,19 +1,20 @@ """Support to the Logi Circle cameras.""" -import logging import asyncio from datetime import timedelta +import logging import voluptuous as vol -from homeassistant.helpers import config_validation as cv -from homeassistant.components.logi_circle import ( - DOMAIN as LOGI_CIRCLE_DOMAIN, ATTRIBUTION) from homeassistant.components.camera import ( - Camera, PLATFORM_SCHEMA, CAMERA_SERVICE_SCHEMA, SUPPORT_ON_OFF, - ATTR_ENTITY_ID, ATTR_FILENAME, DOMAIN) + ATTR_ENTITY_ID, ATTR_FILENAME, CAMERA_SERVICE_SCHEMA, + PLATFORM_SCHEMA, SUPPORT_ON_OFF, Camera) +from homeassistant.components.camera.const import DOMAIN from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, - CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF) + CONF_SCAN_INTERVAL, STATE_OFF, STATE_ON) +from homeassistant.helpers import config_validation as cv + +from . import ATTRIBUTION, DOMAIN as LOGI_CIRCLE_DOMAIN DEPENDENCIES = ['logi_circle'] diff --git a/homeassistant/components/logi_circle/sensor.py b/homeassistant/components/logi_circle/sensor.py index 4830219091c..06d1701a9eb 100644 --- a/homeassistant/components/logi_circle/sensor.py +++ b/homeassistant/components/logi_circle/sensor.py @@ -3,18 +3,18 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.logi_circle import ( - ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DOMAIN as LOGI_CIRCLE_DOMAIN) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, - CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS, - STATE_ON, STATE_OFF) + ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, CONF_ENTITY_NAMESPACE, + CONF_MONITORED_CONDITIONS, STATE_OFF, STATE_ON) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.util.dt import as_local +from . import ( + ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DOMAIN as LOGI_CIRCLE_DOMAIN) + DEPENDENCIES = ['logi_circle'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/london_air/__init__.py b/homeassistant/components/london_air/__init__.py new file mode 100644 index 00000000000..0f93dc83e27 --- /dev/null +++ b/homeassistant/components/london_air/__init__.py @@ -0,0 +1 @@ +"""The london_air component.""" diff --git a/homeassistant/components/sensor/london_air.py b/homeassistant/components/london_air/sensor.py similarity index 100% rename from homeassistant/components/sensor/london_air.py rename to homeassistant/components/london_air/sensor.py diff --git a/homeassistant/components/london_underground/__init__.py b/homeassistant/components/london_underground/__init__.py new file mode 100644 index 00000000000..b38aba6dbc3 --- /dev/null +++ b/homeassistant/components/london_underground/__init__.py @@ -0,0 +1 @@ +"""The london_underground component.""" diff --git a/homeassistant/components/sensor/london_underground.py b/homeassistant/components/london_underground/sensor.py similarity index 100% rename from homeassistant/components/sensor/london_underground.py rename to homeassistant/components/london_underground/sensor.py diff --git a/homeassistant/components/loopenergy/__init__.py b/homeassistant/components/loopenergy/__init__.py new file mode 100644 index 00000000000..4e963f2828a --- /dev/null +++ b/homeassistant/components/loopenergy/__init__.py @@ -0,0 +1 @@ +"""The loopenergy component.""" diff --git a/homeassistant/components/sensor/loopenergy.py b/homeassistant/components/loopenergy/sensor.py similarity index 100% rename from homeassistant/components/sensor/loopenergy.py rename to homeassistant/components/loopenergy/sensor.py diff --git a/homeassistant/components/luci/__init__.py b/homeassistant/components/luci/__init__.py new file mode 100644 index 00000000000..b0efa61ae77 --- /dev/null +++ b/homeassistant/components/luci/__init__.py @@ -0,0 +1 @@ +"""The luci component.""" diff --git a/homeassistant/components/device_tracker/luci.py b/homeassistant/components/luci/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/luci.py rename to homeassistant/components/luci/device_tracker.py diff --git a/homeassistant/components/luftdaten/.translations/bg.json b/homeassistant/components/luftdaten/.translations/bg.json new file mode 100644 index 00000000000..ecd7f17c84b --- /dev/null +++ b/homeassistant/components/luftdaten/.translations/bg.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "communication_error": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u0430 \u043a\u043e\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u044f \u0441 Luftdaten", + "invalid_sensor": "\u0421\u0435\u043d\u0437\u043e\u0440\u044a\u0442 \u043d\u0435 \u0435 \u043d\u0430\u043b\u0438\u0447\u0435\u043d \u0438\u043b\u0438 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d", + "sensor_exists": "\u0421\u0435\u043d\u0437\u043e\u0440\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0430\u043d" + }, + "step": { + "user": { + "data": { + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0432\u0430\u043d\u0435 \u043d\u0430 \u043a\u0430\u0440\u0442\u0430\u0442\u0430", + "station_id": "ID \u043d\u0430 \u0441\u0435\u043d\u0437\u043e\u0440\u0430 \u043d\u0430 Luftdaten" + }, + "title": "\u0414\u0435\u0444\u0438\u043d\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 Luftdaten" + } + }, + "title": "Luftdaten" + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/.translations/fr.json b/homeassistant/components/luftdaten/.translations/fr.json new file mode 100644 index 00000000000..2aeb29fcf0a --- /dev/null +++ b/homeassistant/components/luftdaten/.translations/fr.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "communication_error": "Impossible de communiquer avec l'API Luftdaten", + "invalid_sensor": "Capteur non disponible ou invalide", + "sensor_exists": "Capteur d\u00e9j\u00e0 enregistr\u00e9" + }, + "step": { + "user": { + "data": { + "show_on_map": "Montrer sur la carte" + }, + "title": "D\u00e9finir Luftdaten" + } + }, + "title": "Luftdaten" + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/sensor.py b/homeassistant/components/luftdaten/sensor.py index 398ec30a3f5..107673bac45 100644 --- a/homeassistant/components/luftdaten/sensor.py +++ b/homeassistant/components/luftdaten/sensor.py @@ -1,16 +1,17 @@ """Support for Luftdaten sensors.""" import logging -from homeassistant.components.luftdaten import ( - DATA_LUFTDATEN, DATA_LUFTDATEN_CLIENT, DEFAULT_ATTRIBUTION, DOMAIN, - SENSORS, TOPIC_UPDATE) -from homeassistant.components.luftdaten.const import ATTR_SENSOR_ID from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_SHOW_ON_MAP) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity +from . import ( + DATA_LUFTDATEN, DATA_LUFTDATEN_CLIENT, DEFAULT_ATTRIBUTION, DOMAIN, + SENSORS, TOPIC_UPDATE) +from .const import ATTR_SENSOR_ID + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['luftdaten'] diff --git a/homeassistant/components/lupusec/alarm_control_panel.py b/homeassistant/components/lupusec/alarm_control_panel.py index de62e5bfac2..0a88f3bd552 100644 --- a/homeassistant/components/lupusec/alarm_control_panel.py +++ b/homeassistant/components/lupusec/alarm_control_panel.py @@ -2,12 +2,11 @@ from datetime import timedelta from homeassistant.components.alarm_control_panel import AlarmControlPanel -from homeassistant.components.lupusec import DOMAIN as LUPUSEC_DOMAIN -from homeassistant.components.lupusec import LupusecDevice -from homeassistant.const import (STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_DISARMED, - STATE_ALARM_TRIGGERED) +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED) + +from . import DOMAIN as LUPUSEC_DOMAIN, LupusecDevice DEPENDENCIES = ['lupusec'] diff --git a/homeassistant/components/lupusec/binary_sensor.py b/homeassistant/components/lupusec/binary_sensor.py index 8a5e103db0d..2c3f5e0e0b8 100644 --- a/homeassistant/components/lupusec/binary_sensor.py +++ b/homeassistant/components/lupusec/binary_sensor.py @@ -1,11 +1,11 @@ """Support for Lupusec Security System binary sensors.""" -import logging from datetime import timedelta +import logging -from homeassistant.components.lupusec import (LupusecDevice, - DOMAIN as LUPUSEC_DOMAIN) -from homeassistant.components.binary_sensor import (BinarySensorDevice, - DEVICE_CLASSES) +from homeassistant.components.binary_sensor import ( + DEVICE_CLASSES, BinarySensorDevice) + +from . import DOMAIN as LUPUSEC_DOMAIN, LupusecDevice DEPENDENCIES = ['lupusec'] diff --git a/homeassistant/components/lupusec/switch.py b/homeassistant/components/lupusec/switch.py index 8a30d65fec3..0d86ea0a365 100644 --- a/homeassistant/components/lupusec/switch.py +++ b/homeassistant/components/lupusec/switch.py @@ -1,11 +1,11 @@ """Support for Lupusec Security System switches.""" -import logging from datetime import timedelta +import logging -from homeassistant.components.lupusec import (LupusecDevice, - DOMAIN as LUPUSEC_DOMAIN) from homeassistant.components.switch import SwitchDevice +from . import DOMAIN as LUPUSEC_DOMAIN, LupusecDevice + DEPENDENCIES = ['lupusec'] SCAN_INTERVAL = timedelta(seconds=2) diff --git a/homeassistant/components/lutron/cover.py b/homeassistant/components/lutron/cover.py index cc7a57a5522..da7f69095fc 100644 --- a/homeassistant/components/lutron/cover.py +++ b/homeassistant/components/lutron/cover.py @@ -2,10 +2,10 @@ import logging from homeassistant.components.cover import ( - CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION, - ATTR_POSITION) -from homeassistant.components.lutron import ( - LutronDevice, LUTRON_DEVICES, LUTRON_CONTROLLER) + ATTR_POSITION, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, + CoverDevice) + +from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py index c0b3b991147..5f3fd4787fd 100644 --- a/homeassistant/components/lutron/light.py +++ b/homeassistant/components/lutron/light.py @@ -3,8 +3,8 @@ import logging from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) -from homeassistant.components.lutron import ( - LutronDevice, LUTRON_DEVICES, LUTRON_CONTROLLER) + +from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/lutron/scene.py b/homeassistant/components/lutron/scene.py index f9002f2a839..a2d18c6d242 100644 --- a/homeassistant/components/lutron/scene.py +++ b/homeassistant/components/lutron/scene.py @@ -1,10 +1,10 @@ """Support for Lutron scenes.""" import logging -from homeassistant.components.lutron import ( - LutronDevice, LUTRON_DEVICES, LUTRON_CONTROLLER) from homeassistant.components.scene import Scene +from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['lutron'] diff --git a/homeassistant/components/lutron/switch.py b/homeassistant/components/lutron/switch.py index bfdb06be33c..b42c0d930bc 100644 --- a/homeassistant/components/lutron/switch.py +++ b/homeassistant/components/lutron/switch.py @@ -2,8 +2,8 @@ import logging from homeassistant.components.switch import SwitchDevice -from homeassistant.components.lutron import ( - LutronDevice, LUTRON_DEVICES, LUTRON_CONTROLLER) + +from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/lutron_caseta/cover.py b/homeassistant/components/lutron_caseta/cover.py index 5e09dcc3c85..d970f5282ff 100644 --- a/homeassistant/components/lutron_caseta/cover.py +++ b/homeassistant/components/lutron_caseta/cover.py @@ -2,10 +2,10 @@ import logging from homeassistant.components.cover import ( - CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION, - ATTR_POSITION, DOMAIN) -from homeassistant.components.lutron_caseta import ( - LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice) + ATTR_POSITION, DOMAIN, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, + CoverDevice) + +from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/lutron_caseta/light.py b/homeassistant/components/lutron_caseta/light.py index 3bab781f3b6..d883da73c91 100644 --- a/homeassistant/components/lutron_caseta/light.py +++ b/homeassistant/components/lutron_caseta/light.py @@ -2,11 +2,11 @@ import logging from homeassistant.components.light import ( - ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, DOMAIN) + ATTR_BRIGHTNESS, DOMAIN, SUPPORT_BRIGHTNESS, Light) from homeassistant.components.lutron.light import ( to_hass_level, to_lutron_level) -from homeassistant.components.lutron_caseta import ( - LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice) + +from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/lutron_caseta/scene.py b/homeassistant/components/lutron_caseta/scene.py index c6ca7bad3ac..2e7059a56fc 100644 --- a/homeassistant/components/lutron_caseta/scene.py +++ b/homeassistant/components/lutron_caseta/scene.py @@ -1,9 +1,10 @@ """Support for Lutron Caseta scenes.""" import logging -from homeassistant.components.lutron_caseta import LUTRON_CASETA_SMARTBRIDGE from homeassistant.components.scene import Scene +from . import LUTRON_CASETA_SMARTBRIDGE + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['lutron_caseta'] diff --git a/homeassistant/components/lutron_caseta/switch.py b/homeassistant/components/lutron_caseta/switch.py index 0ef0595187b..54c67091357 100644 --- a/homeassistant/components/lutron_caseta/switch.py +++ b/homeassistant/components/lutron_caseta/switch.py @@ -1,9 +1,9 @@ """Support for Lutron Caseta switches.""" import logging -from homeassistant.components.lutron_caseta import ( - LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice) -from homeassistant.components.switch import SwitchDevice, DOMAIN +from homeassistant.components.switch import DOMAIN, SwitchDevice + +from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/lw12wifi/__init__.py b/homeassistant/components/lw12wifi/__init__.py new file mode 100644 index 00000000000..d356a51547c --- /dev/null +++ b/homeassistant/components/lw12wifi/__init__.py @@ -0,0 +1 @@ +"""The lw12wifi component.""" diff --git a/homeassistant/components/light/lw12wifi.py b/homeassistant/components/lw12wifi/light.py similarity index 100% rename from homeassistant/components/light/lw12wifi.py rename to homeassistant/components/lw12wifi/light.py diff --git a/homeassistant/components/lyft/__init__.py b/homeassistant/components/lyft/__init__.py new file mode 100644 index 00000000000..a7ffe972cc9 --- /dev/null +++ b/homeassistant/components/lyft/__init__.py @@ -0,0 +1 @@ +"""The lyft component.""" diff --git a/homeassistant/components/sensor/lyft.py b/homeassistant/components/lyft/sensor.py similarity index 100% rename from homeassistant/components/sensor/lyft.py rename to homeassistant/components/lyft/sensor.py diff --git a/homeassistant/components/magicseaweed/__init__.py b/homeassistant/components/magicseaweed/__init__.py new file mode 100644 index 00000000000..848d02967fe --- /dev/null +++ b/homeassistant/components/magicseaweed/__init__.py @@ -0,0 +1 @@ +"""The magicseaweed component.""" diff --git a/homeassistant/components/sensor/magicseaweed.py b/homeassistant/components/magicseaweed/sensor.py similarity index 100% rename from homeassistant/components/sensor/magicseaweed.py rename to homeassistant/components/magicseaweed/sensor.py diff --git a/homeassistant/components/mailgun/.translations/bg.json b/homeassistant/components/mailgun/.translations/bg.json new file mode 100644 index 00000000000..6f06d5c00c6 --- /dev/null +++ b/homeassistant/components/mailgun/.translations/bg.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/pl.json b/homeassistant/components/mailgun/.translations/pl.json index ba89efab0c2..ccdc368afff 100644 --- a/homeassistant/components/mailgun/.translations/pl.json +++ b/homeassistant/components/mailgun/.translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Mailgun Webhook]({mailgun_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/x-www-form-urlencoded \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Mailgun Webhook]({mailgun_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/x-www-form-urlencoded \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji, by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." }, "step": { "user": { diff --git a/homeassistant/components/mailgun/notify.py b/homeassistant/components/mailgun/notify.py index 05137254fcc..b9f5bf0b100 100644 --- a/homeassistant/components/mailgun/notify.py +++ b/homeassistant/components/mailgun/notify.py @@ -3,14 +3,14 @@ import logging import voluptuous as vol -from homeassistant.components.mailgun import ( - CONF_SANDBOX, DOMAIN as MAILGUN_DOMAIN) from homeassistant.components.notify import ( - PLATFORM_SCHEMA, BaseNotificationService, ATTR_TITLE, ATTR_TITLE_DEFAULT, - ATTR_DATA) + ATTR_DATA, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, + BaseNotificationService) from homeassistant.const import ( CONF_API_KEY, CONF_DOMAIN, CONF_RECIPIENT, CONF_SENDER) +from . import CONF_SANDBOX, DOMAIN as MAILGUN_DOMAIN + REQUIREMENTS = ['pymailgunner==1.4'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/manual/__init__.py b/homeassistant/components/manual/__init__.py new file mode 100644 index 00000000000..64f6961e3ea --- /dev/null +++ b/homeassistant/components/manual/__init__.py @@ -0,0 +1 @@ +"""The manual component.""" diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/manual/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/manual.py rename to homeassistant/components/manual/alarm_control_panel.py diff --git a/homeassistant/components/manual_mqtt/__init__.py b/homeassistant/components/manual_mqtt/__init__.py new file mode 100644 index 00000000000..bdd0d5c09e4 --- /dev/null +++ b/homeassistant/components/manual_mqtt/__init__.py @@ -0,0 +1 @@ +"""The manual_mqtt component.""" diff --git a/homeassistant/components/alarm_control_panel/manual_mqtt.py b/homeassistant/components/manual_mqtt/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/manual_mqtt.py rename to homeassistant/components/manual_mqtt/alarm_control_panel.py diff --git a/homeassistant/components/marytts/__init__.py b/homeassistant/components/marytts/__init__.py new file mode 100644 index 00000000000..ec85cb6d4ab --- /dev/null +++ b/homeassistant/components/marytts/__init__.py @@ -0,0 +1 @@ +"""Support for MaryTTS integration.""" diff --git a/homeassistant/components/tts/marytts.py b/homeassistant/components/marytts/tts.py similarity index 97% rename from homeassistant/components/tts/marytts.py rename to homeassistant/components/marytts/tts.py index 61f01a9b292..f5d19c977a4 100644 --- a/homeassistant/components/tts/marytts.py +++ b/homeassistant/components/marytts/tts.py @@ -12,8 +12,8 @@ import aiohttp import async_timeout import voluptuous as vol +from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.components.tts import Provider, PLATFORM_SCHEMA, CONF_LANG from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/mastodon/__init__.py b/homeassistant/components/mastodon/__init__.py new file mode 100644 index 00000000000..123d23afb80 --- /dev/null +++ b/homeassistant/components/mastodon/__init__.py @@ -0,0 +1 @@ +"""The mastodon component.""" diff --git a/homeassistant/components/notify/mastodon.py b/homeassistant/components/mastodon/notify.py similarity index 93% rename from homeassistant/components/notify/mastodon.py rename to homeassistant/components/mastodon/notify.py index 095e3f98ff9..6192f6cdca5 100644 --- a/homeassistant/components/notify/mastodon.py +++ b/homeassistant/components/mastodon/notify.py @@ -8,11 +8,12 @@ import logging import voluptuous as vol -from homeassistant.components.notify import ( - PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_ACCESS_TOKEN import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import (PLATFORM_SCHEMA, + BaseNotificationService) + REQUIREMENTS = ['Mastodon.py==1.3.1'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/maxcube/binary_sensor.py b/homeassistant/components/maxcube/binary_sensor.py index 8d5ab84f6d3..6221b95d879 100644 --- a/homeassistant/components/maxcube/binary_sensor.py +++ b/homeassistant/components/maxcube/binary_sensor.py @@ -2,7 +2,8 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.maxcube import DATA_KEY + +from . import DATA_KEY _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index 170a3ba349c..c30ebc7d697 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -1,13 +1,13 @@ """Support for MAX! Thermostats via MAX! Cube.""" -import socket import logging +import socket from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - STATE_AUTO, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_OPERATION_MODE) -from homeassistant.components.maxcube import DATA_KEY -from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE + STATE_AUTO, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS + +from . import DATA_KEY _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/media_extractor/__init__.py b/homeassistant/components/media_extractor/__init__.py index ff6277242ca..67dd06efab8 100644 --- a/homeassistant/components/media_extractor/__init__.py +++ b/homeassistant/components/media_extractor/__init__.py @@ -12,7 +12,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID) from homeassistant.helpers import config_validation as cv -REQUIREMENTS = ['youtube_dl==2019.03.01'] +REQUIREMENTS = ['youtube_dl==2019.03.18'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py index bf7e6b4e0ce..1e53e607360 100644 --- a/homeassistant/components/media_player/const.py +++ b/homeassistant/components/media_player/const.py @@ -36,7 +36,9 @@ MEDIA_TYPE_VIDEO = 'video' MEDIA_TYPE_EPISODE = 'episode' MEDIA_TYPE_CHANNEL = 'channel' MEDIA_TYPE_PLAYLIST = 'playlist' +MEDIA_TYPE_IMAGE = 'image' MEDIA_TYPE_URL = 'url' +MEDIA_TYPE_GAME = 'game' SERVICE_CLEAR_PLAYLIST = 'clear_playlist' SERVICE_PLAY_MEDIA = 'play_media' diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index 3c91f19469b..c9da38d3657 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -131,7 +131,7 @@ play_media: description: The ID of the content to play. Platform dependent. example: 'https://home-assistant.io/images/cast/splash.png' media_content_type: - description: The type of the content to play. Must be one of music, tvshow, video, episode, channel or playlist + description: The type of the content to play. Must be one of image, music, tvshow, video, episode, channel or playlist example: 'music' select_source: diff --git a/homeassistant/components/mediaroom/__init__.py b/homeassistant/components/mediaroom/__init__.py new file mode 100644 index 00000000000..71ed614773a --- /dev/null +++ b/homeassistant/components/mediaroom/__init__.py @@ -0,0 +1 @@ +"""The mediaroom component.""" diff --git a/homeassistant/components/media_player/mediaroom.py b/homeassistant/components/mediaroom/media_player.py similarity index 100% rename from homeassistant/components/media_player/mediaroom.py rename to homeassistant/components/mediaroom/media_player.py diff --git a/homeassistant/components/climate/melissa.py b/homeassistant/components/melissa/climate.py similarity index 95% rename from homeassistant/components/climate/melissa.py rename to homeassistant/components/melissa/climate.py index b9eb28a61d7..0df294a148d 100644 --- a/homeassistant/components/climate/melissa.py +++ b/homeassistant/components/melissa/climate.py @@ -8,16 +8,15 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_ON_OFF, STATE_AUTO, STATE_HEAT, STATE_COOL, STATE_DRY, - STATE_FAN_ONLY, SUPPORT_FAN_MODE -) -from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH -from homeassistant.components.melissa import DATA_MELISSA + STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT, + SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE) +from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM from homeassistant.const import ( - TEMP_CELSIUS, STATE_ON, STATE_OFF, STATE_IDLE, ATTR_TEMPERATURE, - PRECISION_WHOLE -) + ATTR_TEMPERATURE, PRECISION_WHOLE, STATE_IDLE, STATE_OFF, STATE_ON, + TEMP_CELSIUS) + +from . import DATA_MELISSA DEPENDENCIES = ['melissa'] diff --git a/homeassistant/components/meraki/__init__.py b/homeassistant/components/meraki/__init__.py new file mode 100644 index 00000000000..ad9cf4abcaf --- /dev/null +++ b/homeassistant/components/meraki/__init__.py @@ -0,0 +1 @@ +"""The meraki component.""" diff --git a/homeassistant/components/device_tracker/meraki.py b/homeassistant/components/meraki/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/meraki.py rename to homeassistant/components/meraki/device_tracker.py diff --git a/homeassistant/components/message_bird/__init__.py b/homeassistant/components/message_bird/__init__.py new file mode 100644 index 00000000000..ed3828c5eda --- /dev/null +++ b/homeassistant/components/message_bird/__init__.py @@ -0,0 +1 @@ +"""The message_bird component.""" diff --git a/homeassistant/components/notify/message_bird.py b/homeassistant/components/message_bird/notify.py similarity index 93% rename from homeassistant/components/notify/message_bird.py rename to homeassistant/components/message_bird/notify.py index fa747ccba88..cfb22ff1d52 100644 --- a/homeassistant/components/notify/message_bird.py +++ b/homeassistant/components/message_bird/notify.py @@ -8,10 +8,11 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_API_KEY, CONF_SENDER +import homeassistant.helpers.config_validation as cv + +from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA, + BaseNotificationService) REQUIREMENTS = ['messagebird==1.2.0'] diff --git a/homeassistant/components/met/__init__.py b/homeassistant/components/met/__init__.py new file mode 100644 index 00000000000..67bd64f3e16 --- /dev/null +++ b/homeassistant/components/met/__init__.py @@ -0,0 +1 @@ +"""The met component.""" diff --git a/homeassistant/components/weather/met.py b/homeassistant/components/met/weather.py similarity index 100% rename from homeassistant/components/weather/met.py rename to homeassistant/components/met/weather.py diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index f0ef926793e..122b91cae44 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -1,11 +1,11 @@ """Support for Meteo-France raining forecast sensor.""" import logging -from homeassistant.components.meteo_france import ( - ATTRIBUTION, CONF_CITY, DATA_METEO_FRANCE, SENSOR_TYPES) from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS from homeassistant.helpers.entity import Entity +from . import ATTRIBUTION, CONF_CITY, DATA_METEO_FRANCE, SENSOR_TYPES + _LOGGER = logging.getLogger(__name__) STATE_ATTR_FORECAST = '1h rain forecast' diff --git a/homeassistant/components/meteo_france/weather.py b/homeassistant/components/meteo_france/weather.py index 849c9d9da10..b2b94c7622e 100644 --- a/homeassistant/components/meteo_france/weather.py +++ b/homeassistant/components/meteo_france/weather.py @@ -2,13 +2,13 @@ from datetime import datetime, timedelta import logging -from homeassistant.components.meteo_france import ( - ATTRIBUTION, CONDITION_CLASSES, CONF_CITY, DATA_METEO_FRANCE) from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, WeatherEntity) from homeassistant.const import TEMP_CELSIUS +from . import ATTRIBUTION, CONDITION_CLASSES, CONF_CITY, DATA_METEO_FRANCE + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/metoffice/__init__.py b/homeassistant/components/metoffice/__init__.py new file mode 100644 index 00000000000..94cc8b636d4 --- /dev/null +++ b/homeassistant/components/metoffice/__init__.py @@ -0,0 +1 @@ +"""The metoffice component.""" diff --git a/homeassistant/components/sensor/metoffice.py b/homeassistant/components/metoffice/sensor.py similarity index 100% rename from homeassistant/components/sensor/metoffice.py rename to homeassistant/components/metoffice/sensor.py diff --git a/homeassistant/components/weather/metoffice.py b/homeassistant/components/metoffice/weather.py similarity index 96% rename from homeassistant/components/weather/metoffice.py rename to homeassistant/components/metoffice/weather.py index 3b52eebcff6..a67dcdcdbd6 100644 --- a/homeassistant/components/weather/metoffice.py +++ b/homeassistant/components/metoffice/weather.py @@ -3,13 +3,13 @@ import logging import voluptuous as vol -from homeassistant.components.sensor.metoffice import ( - CONDITION_CLASSES, ATTRIBUTION, MetOfficeCurrentData) from homeassistant.components.weather import PLATFORM_SCHEMA, WeatherEntity from homeassistant.const import ( CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS) from homeassistant.helpers import config_validation as cv +from .sensor import ATTRIBUTION, CONDITION_CLASSES, MetOfficeCurrentData + REQUIREMENTS = ['datapoint==0.4.3'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mfi/__init__.py b/homeassistant/components/mfi/__init__.py new file mode 100644 index 00000000000..de354dfbc37 --- /dev/null +++ b/homeassistant/components/mfi/__init__.py @@ -0,0 +1 @@ +"""The mfi component.""" diff --git a/homeassistant/components/sensor/mfi.py b/homeassistant/components/mfi/sensor.py similarity index 100% rename from homeassistant/components/sensor/mfi.py rename to homeassistant/components/mfi/sensor.py diff --git a/homeassistant/components/switch/mfi.py b/homeassistant/components/mfi/switch.py similarity index 100% rename from homeassistant/components/switch/mfi.py rename to homeassistant/components/mfi/switch.py diff --git a/homeassistant/components/mhz19/__init__.py b/homeassistant/components/mhz19/__init__.py new file mode 100644 index 00000000000..5fa9bbb69e8 --- /dev/null +++ b/homeassistant/components/mhz19/__init__.py @@ -0,0 +1 @@ +"""The mhz19 component.""" diff --git a/homeassistant/components/sensor/mhz19.py b/homeassistant/components/mhz19/sensor.py similarity index 100% rename from homeassistant/components/sensor/mhz19.py rename to homeassistant/components/mhz19/sensor.py diff --git a/homeassistant/components/microsoft/__init__.py b/homeassistant/components/microsoft/__init__.py new file mode 100644 index 00000000000..2d281cd2bd8 --- /dev/null +++ b/homeassistant/components/microsoft/__init__.py @@ -0,0 +1 @@ +"""Support for Microsoft integration.""" diff --git a/homeassistant/components/tts/microsoft.py b/homeassistant/components/microsoft/tts.py similarity index 96% rename from homeassistant/components/tts/microsoft.py rename to homeassistant/components/microsoft/tts.py index 3cce7c1a78d..55cf7a4ae7a 100644 --- a/homeassistant/components/tts/microsoft.py +++ b/homeassistant/components/microsoft/tts.py @@ -4,13 +4,13 @@ Support for the Microsoft Cognitive Services text-to-speech service. For more details about this component, please refer to the documentation at https://home-assistant.io/components/tts.microsoft/ """ -import logging from http.client import HTTPException +import logging import voluptuous as vol -from homeassistant.components.tts import Provider, PLATFORM_SCHEMA, CONF_LANG -from homeassistant.const import CONF_TYPE, CONF_API_KEY +from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider +from homeassistant.const import CONF_API_KEY, CONF_TYPE import homeassistant.helpers.config_validation as cv CONF_GENDER = 'gender' diff --git a/homeassistant/components/microsoft_face_detect/__init__.py b/homeassistant/components/microsoft_face_detect/__init__.py new file mode 100644 index 00000000000..897a367a101 --- /dev/null +++ b/homeassistant/components/microsoft_face_detect/__init__.py @@ -0,0 +1 @@ +"""The microsoft_face_detect component.""" diff --git a/homeassistant/components/image_processing/microsoft_face_detect.py b/homeassistant/components/microsoft_face_detect/image_processing.py similarity index 100% rename from homeassistant/components/image_processing/microsoft_face_detect.py rename to homeassistant/components/microsoft_face_detect/image_processing.py diff --git a/homeassistant/components/microsoft_face_identify/__init__.py b/homeassistant/components/microsoft_face_identify/__init__.py new file mode 100644 index 00000000000..cd402d0fef1 --- /dev/null +++ b/homeassistant/components/microsoft_face_identify/__init__.py @@ -0,0 +1 @@ +"""The microsoft_face_identify component.""" diff --git a/homeassistant/components/image_processing/microsoft_face_identify.py b/homeassistant/components/microsoft_face_identify/image_processing.py similarity index 100% rename from homeassistant/components/image_processing/microsoft_face_identify.py rename to homeassistant/components/microsoft_face_identify/image_processing.py diff --git a/homeassistant/components/miflora/__init__.py b/homeassistant/components/miflora/__init__.py new file mode 100644 index 00000000000..ed1569e1af0 --- /dev/null +++ b/homeassistant/components/miflora/__init__.py @@ -0,0 +1 @@ +"""The miflora component.""" diff --git a/homeassistant/components/sensor/miflora.py b/homeassistant/components/miflora/sensor.py similarity index 100% rename from homeassistant/components/sensor/miflora.py rename to homeassistant/components/miflora/sensor.py diff --git a/homeassistant/components/mikrotik/__init__.py b/homeassistant/components/mikrotik/__init__.py new file mode 100644 index 00000000000..0fe5a1c70b1 --- /dev/null +++ b/homeassistant/components/mikrotik/__init__.py @@ -0,0 +1 @@ +"""The mikrotik component.""" diff --git a/homeassistant/components/device_tracker/mikrotik.py b/homeassistant/components/mikrotik/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/mikrotik.py rename to homeassistant/components/mikrotik/device_tracker.py diff --git a/homeassistant/components/mill/__init__.py b/homeassistant/components/mill/__init__.py new file mode 100644 index 00000000000..157ea345efd --- /dev/null +++ b/homeassistant/components/mill/__init__.py @@ -0,0 +1 @@ +"""The mill component.""" diff --git a/homeassistant/components/climate/mill.py b/homeassistant/components/mill/climate.py similarity index 100% rename from homeassistant/components/climate/mill.py rename to homeassistant/components/mill/climate.py diff --git a/homeassistant/components/min_max/__init__.py b/homeassistant/components/min_max/__init__.py new file mode 100644 index 00000000000..d13d963ff47 --- /dev/null +++ b/homeassistant/components/min_max/__init__.py @@ -0,0 +1 @@ +"""The min_max component.""" diff --git a/homeassistant/components/sensor/min_max.py b/homeassistant/components/min_max/sensor.py similarity index 100% rename from homeassistant/components/sensor/min_max.py rename to homeassistant/components/min_max/sensor.py diff --git a/homeassistant/components/mitemp_bt/__init__.py b/homeassistant/components/mitemp_bt/__init__.py new file mode 100644 index 00000000000..785956572af --- /dev/null +++ b/homeassistant/components/mitemp_bt/__init__.py @@ -0,0 +1 @@ +"""The mitemp_bt component.""" diff --git a/homeassistant/components/sensor/mitemp_bt.py b/homeassistant/components/mitemp_bt/sensor.py similarity index 100% rename from homeassistant/components/sensor/mitemp_bt.py rename to homeassistant/components/mitemp_bt/sensor.py diff --git a/homeassistant/components/mjpeg/__init__.py b/homeassistant/components/mjpeg/__init__.py new file mode 100644 index 00000000000..3e7469cff00 --- /dev/null +++ b/homeassistant/components/mjpeg/__init__.py @@ -0,0 +1 @@ +"""The mjpeg component.""" diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/mjpeg/camera.py similarity index 100% rename from homeassistant/components/camera/mjpeg.py rename to homeassistant/components/mjpeg/camera.py diff --git a/homeassistant/components/mobile_app/.translations/ca.json b/homeassistant/components/mobile_app/.translations/ca.json new file mode 100644 index 00000000000..25af1d5e18d --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/ca.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "install_app": "Obre l\u2019aplicaci\u00f3 m\u00f2bil per configurar la integraci\u00f3 amb Home Assistant. Mira [la documentaci\u00f3]({apps_url}) per veure la llista d\u2019aplicacions compatibles." + }, + "step": { + "confirm": { + "description": "Vols configurar el component d'aplicaci\u00f3 m\u00f2bil?", + "title": "Aplicaci\u00f3 m\u00f2bil" + } + }, + "title": "Aplicaci\u00f3 m\u00f2bil" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/de.json b/homeassistant/components/mobile_app/.translations/de.json new file mode 100644 index 00000000000..816d281752d --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/de.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "install_app": "\u00d6ffne die mobile App, um die Integration mit Home Assistant einzurichten. Eine Liste der kompatiblen Apps gibt es hier [the docs] ({apps_url})." + }, + "step": { + "confirm": { + "description": "M\u00f6chtest du die Mobile App-Komponente einrichten?", + "title": "Mobile App" + } + }, + "title": "Mobile App" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/en.json b/homeassistant/components/mobile_app/.translations/en.json index 646151a5229..79a5fe1fba8 100644 --- a/homeassistant/components/mobile_app/.translations/en.json +++ b/homeassistant/components/mobile_app/.translations/en.json @@ -1,14 +1,14 @@ { - "config": { - "title": "Mobile App", - "step": { - "confirm": { - "title": "Mobile App", - "description": "Do you want to set up the Mobile App component?" - } - }, - "abort": { - "install_app": "Open the mobile app to set up the integration with Home Assistant. See [the docs]({apps_url}) for a list of compatible apps." + "config": { + "abort": { + "install_app": "Open the mobile app to set up the integration with Home Assistant. See [the docs]({apps_url}) for a list of compatible apps." + }, + "step": { + "confirm": { + "description": "Do you want to set up the Mobile App component?", + "title": "Mobile App" + } + }, + "title": "Mobile App" } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/es-419.json b/homeassistant/components/mobile_app/.translations/es-419.json new file mode 100644 index 00000000000..417d0627616 --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/es-419.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "confirm": { + "title": "Aplicaci\u00f3n movil" + } + }, + "title": "Aplicaci\u00f3n movil" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/hu.json b/homeassistant/components/mobile_app/.translations/hu.json new file mode 100644 index 00000000000..e95f4743ae3 --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/hu.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "install_app": "Nyisd meg a mobil alkalmaz\u00e1st a Home Assistant-tal val\u00f3 integr\u00e1ci\u00f3hoz. A kompatibilis alkalmaz\u00e1sok list\u00e1j\u00e1nak megtekint\u00e9s\u00e9hez ellen\u0151rizd [a le\u00edr\u00e1st]({apps_url})." + }, + "step": { + "confirm": { + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a mobil alkalmaz\u00e1s komponenst?", + "title": "Mobil alkalmaz\u00e1s" + } + }, + "title": "Mobil alkalmaz\u00e1s" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/ko.json b/homeassistant/components/mobile_app/.translations/ko.json new file mode 100644 index 00000000000..faf30e5f985 --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/ko.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "install_app": "\ubaa8\ubc14\uc77c \uc571\uc744 \uc5f4\uc5b4 Home Assistant \uc640 \ud1b5\ud569\uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694. \ud638\ud658\ub418\ub294 \uc571 \ubaa9\ub85d\uc740 [\uc548\ub0b4]({apps_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + }, + "step": { + "confirm": { + "description": "\ubaa8\ubc14\uc77c \uc571 \ucef4\ud3ec\ub10c\ud2b8\uc758 \uc124\uc815\uc744 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\ubaa8\ubc14\uc77c \uc571" + } + }, + "title": "\ubaa8\ubc14\uc77c \uc571" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/lb.json b/homeassistant/components/mobile_app/.translations/lb.json new file mode 100644 index 00000000000..a66ae603291 --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/lb.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "install_app": "Maacht d'Mobil App op fir d'Integratioun mam Home Assistant anzeriichten. Kuckt an der [Dokumentatioun]({apps_url}) fir eng L\u00ebscht vun kompatiblen App's." + }, + "step": { + "confirm": { + "description": "Soll d'Mobil App konfigur\u00e9iert ginn?", + "title": "Mobil App" + } + }, + "title": "Mobil App" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/no.json b/homeassistant/components/mobile_app/.translations/no.json new file mode 100644 index 00000000000..7189bc53c16 --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/no.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "install_app": "\u00c5pne mobilappen for \u00e5 konfigurere integrasjonen med hjemmevirksomheten. Se [docs]({apps_url}) for en liste over kompatible apper." + }, + "step": { + "confirm": { + "description": "Vil du sette opp mobilapp-komponenten?", + "title": "Mobilapp" + } + }, + "title": "Mobilapp" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/pl.json b/homeassistant/components/mobile_app/.translations/pl.json new file mode 100644 index 00000000000..feb00c20779 --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/pl.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "install_app": "Otw\u00f3rz aplikacj\u0119 mobiln\u0105, aby skonfigurowa\u0107 integracj\u0119 z Home Assistant. Zapoznaj si\u0119 z [dokumentacj\u0105] ({apps_url}), by zobaczy\u0107 list\u0119 kompatybilnych aplikacji." + }, + "step": { + "confirm": { + "description": "Czy chcesz skonfigurowa\u0107 komponent aplikacji mobilnej?", + "title": "Aplikacja mobilna" + } + }, + "title": "Aplikacja mobilna" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/ru.json b/homeassistant/components/mobile_app/.translations/ru.json new file mode 100644 index 00000000000..202b7383253 --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/ru.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "install_app": "\u041e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0441 Home Assistant. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({apps_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043f\u0438\u0441\u043a\u0430 \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439." + }, + "step": { + "confirm": { + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435?", + "title": "\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435" + } + }, + "title": "\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/sl.json b/homeassistant/components/mobile_app/.translations/sl.json new file mode 100644 index 00000000000..6236421ffce --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/sl.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "install_app": "Odprite mobilno aplikacijo, da nastavite integracijo s storitvijo Home Assistant. Za seznam zdru\u017eljivih aplikacij si oglejte [docs] ({apps_url})." + }, + "step": { + "confirm": { + "description": "Ali \u017eelite nastaviti komponento aplikacije Mobile App?", + "title": "Mobilna Aplikacija" + } + }, + "title": "Mobilna Aplikacija" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/uk.json b/homeassistant/components/mobile_app/.translations/uk.json new file mode 100644 index 00000000000..654eb7675a8 --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/uk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u043e\u0433\u043e \u0434\u043e\u0434\u0430\u0442\u043a\u0430?", + "title": "\u041c\u043e\u0431\u0456\u043b\u044c\u043d\u0438\u0439 \u0434\u043e\u0434\u0430\u0442\u043e\u043a" + } + }, + "title": "\u041c\u043e\u0431\u0456\u043b\u044c\u043d\u0438\u0439 \u0434\u043e\u0434\u0430\u0442\u043e\u043a" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/.translations/zh-Hant.json b/homeassistant/components/mobile_app/.translations/zh-Hant.json new file mode 100644 index 00000000000..3b1ab72f7d3 --- /dev/null +++ b/homeassistant/components/mobile_app/.translations/zh-Hant.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "install_app": "\u958b\u555f\u624b\u6a5f App \u4ee5\u9032\u884c Home Assistant \u6574\u5408\u8a2d\u5b9a\u3002\u8acb\u53c3\u95b1 [\u6587\u4ef6]({apps_url}) \u7372\u5f97\u652f\u63f4\u7684\u624b\u6a5f App \u5217\u8868\u3002" + }, + "step": { + "confirm": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u624b\u6a5f App \u5143\u4ef6\uff1f", + "title": "\u624b\u6a5f App" + } + }, + "title": "\u624b\u6a5f App" + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index ecbe8d70847..a4ae78959cf 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -2,13 +2,13 @@ from homeassistant import config_entries from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.components.webhook import async_register as webhook_register -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, discovery from homeassistant.helpers.typing import ConfigType, HomeAssistantType -from .const import (ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_MANUFACTURER, - ATTR_MODEL, ATTR_OS_VERSION, DATA_BINARY_SENSOR, - DATA_CONFIG_ENTRIES, DATA_DELETED_IDS, DATA_DEVICES, - DATA_SENSOR, DATA_STORE, DOMAIN, STORAGE_KEY, +from .const import (ATTR_DEVICE_ID, ATTR_DEVICE_NAME, + ATTR_MANUFACTURER, ATTR_MODEL, ATTR_OS_VERSION, + DATA_BINARY_SENSOR, DATA_CONFIG_ENTRIES, DATA_DELETED_IDS, + DATA_DEVICES, DATA_SENSOR, DATA_STORE, DOMAIN, STORAGE_KEY, STORAGE_VERSION) from .http_api import RegistrationsView @@ -52,6 +52,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): except ValueError: pass + hass.async_create_task(discovery.async_load_platform( + hass, 'notify', DOMAIN, {}, config)) + return True diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index 3aa4626da29..61c50e97c6e 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -40,6 +40,8 @@ ATTR_MANUFACTURER = 'manufacturer' ATTR_MODEL = 'model' ATTR_OS_NAME = 'os_name' ATTR_OS_VERSION = 'os_version' +ATTR_PUSH_TOKEN = 'push_token' +ATTR_PUSH_URL = 'push_url' ATTR_SUPPORTS_ENCRYPTION = 'supports_encryption' ATTR_EVENT_DATA = 'event_data' diff --git a/homeassistant/components/mobile_app/notify.py b/homeassistant/components/mobile_app/notify.py new file mode 100644 index 00000000000..0120b1a6ffb --- /dev/null +++ b/homeassistant/components/mobile_app/notify.py @@ -0,0 +1,134 @@ +"""Support for mobile_app push notifications.""" +import asyncio +from datetime import datetime, timezone +import logging + +import async_timeout + +from homeassistant.components.notify import ( + ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, + BaseNotificationService) +from homeassistant.components.mobile_app.const import ( + ATTR_APP_DATA, ATTR_APP_ID, ATTR_APP_VERSION, ATTR_DEVICE_NAME, + ATTR_OS_VERSION, ATTR_PUSH_TOKEN, ATTR_PUSH_URL, DATA_CONFIG_ENTRIES, + DOMAIN) +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.util.dt as dt_util + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['mobile_app'] + + +def push_registrations(hass): + """Return a dictionary of push enabled registrations.""" + targets = {} + for webhook_id, entry in hass.data[DOMAIN][DATA_CONFIG_ENTRIES].items(): + data = entry.data + app_data = data[ATTR_APP_DATA] + if ATTR_PUSH_TOKEN in app_data and ATTR_PUSH_URL in app_data: + device_name = data[ATTR_DEVICE_NAME] + if device_name in targets: + _LOGGER.warning("Found duplicate device name %s", device_name) + continue + targets[device_name] = webhook_id + return targets + + +# pylint: disable=invalid-name +def log_rate_limits(hass, device_name, resp, level=logging.INFO): + """Output rate limit log line at given level.""" + rate_limits = resp['rateLimits'] + resetsAt = dt_util.parse_datetime(rate_limits['resetsAt']) + resetsAtTime = resetsAt - datetime.now(timezone.utc) + rate_limit_msg = ("mobile_app push notification rate limits for %s: " + "%d sent, %d allowed, %d errors, " + "resets in %s") + _LOGGER.log(level, rate_limit_msg, + device_name, + rate_limits['successful'], + rate_limits['maximum'], rate_limits['errors'], + str(resetsAtTime).split(".")[0]) + + +async def async_get_service(hass, config, discovery_info=None): + """Get the mobile_app notification service.""" + session = async_get_clientsession(hass) + return MobileAppNotificationService(session) + + +class MobileAppNotificationService(BaseNotificationService): + """Implement the notification service for mobile_app.""" + + def __init__(self, session): + """Initialize the service.""" + self._session = session + + @property + def targets(self): + """Return a dictionary of registered targets.""" + return push_registrations(self.hass) + + async def async_send_message(self, message="", **kwargs): + """Send a message to the Lambda APNS gateway.""" + data = {ATTR_MESSAGE: message} + + if kwargs.get(ATTR_TITLE) is not None: + # Remove default title from notifications. + if kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT: + data[ATTR_TITLE] = kwargs.get(ATTR_TITLE) + + targets = kwargs.get(ATTR_TARGET) + + if not targets: + targets = push_registrations(self.hass) + + if kwargs.get(ATTR_DATA) is not None: + data[ATTR_DATA] = kwargs.get(ATTR_DATA) + + for target in targets: + + entry = self.hass.data[DOMAIN][DATA_CONFIG_ENTRIES][target] + entry_data = entry.data + + app_data = entry_data[ATTR_APP_DATA] + push_token = app_data[ATTR_PUSH_TOKEN] + push_url = app_data[ATTR_PUSH_URL] + + data[ATTR_PUSH_TOKEN] = push_token + + reg_info = { + ATTR_APP_ID: entry_data[ATTR_APP_ID], + ATTR_APP_VERSION: entry_data[ATTR_APP_VERSION], + } + if ATTR_OS_VERSION in entry_data: + reg_info[ATTR_OS_VERSION] = entry_data[ATTR_OS_VERSION] + + data['registration_info'] = reg_info + + try: + with async_timeout.timeout(10, loop=self.hass.loop): + response = await self._session.post(push_url, json=data) + result = await response.json() + + if response.status == 201: + log_rate_limits(self.hass, + entry_data[ATTR_DEVICE_NAME], result) + return + + fallback_error = result.get("errorMessage", + "Unknown error") + fallback_message = ("Internal server error, " + "please try again later: " + "{}").format(fallback_error) + message = result.get("message", fallback_message) + if response.status == 429: + _LOGGER.warning(message) + log_rate_limits(self.hass, + entry_data[ATTR_DEVICE_NAME], + result, logging.WARNING) + else: + _LOGGER.error(message) + + except asyncio.TimeoutError: + _LOGGER.error("Timeout sending notification to %s", push_url) diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index 4e0ab74445d..0c10548452a 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -4,12 +4,12 @@ import logging import voluptuous as vol from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.modbus import ( - CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, CONF_SLAVE from homeassistant.helpers import config_validation as cv +from . import CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN + _LOGGER = logging.getLogger(__name__) CONF_COIL = 'coil' diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index 44daedac9c1..4d2b86903e7 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -6,11 +6,11 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE -from homeassistant.components.modbus import ( - CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN) from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, CONF_SLAVE import homeassistant.helpers.config_validation as cv +from . import CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN + _LOGGER = logging.getLogger(__name__) CONF_TARGET_TEMP = 'target_temp_register' diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index 3f8c68b25ff..10e11a9a656 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -4,8 +4,6 @@ import struct import voluptuous as vol -from homeassistant.components.modbus import ( - CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_NAME, CONF_OFFSET, CONF_SLAVE, CONF_STRUCTURE, @@ -13,6 +11,8 @@ from homeassistant.const import ( from homeassistant.helpers import config_validation as cv from homeassistant.helpers.restore_state import RestoreEntity +from . import CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN + _LOGGER = logging.getLogger(__name__) CONF_COUNT = 'count' diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index b7039a01da3..69c5e3e4838 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -3,8 +3,6 @@ import logging import voluptuous as vol -from homeassistant.components.modbus import ( - CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_NAME, CONF_SLAVE, STATE_ON) @@ -12,6 +10,8 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.restore_state import RestoreEntity +from . import CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN + _LOGGER = logging.getLogger(__name__) CONF_COIL = 'coil' diff --git a/homeassistant/components/modem_callerid/__init__.py b/homeassistant/components/modem_callerid/__init__.py new file mode 100644 index 00000000000..0ce41b0ea03 --- /dev/null +++ b/homeassistant/components/modem_callerid/__init__.py @@ -0,0 +1 @@ +"""The modem_callerid component.""" diff --git a/homeassistant/components/sensor/modem_callerid.py b/homeassistant/components/modem_callerid/sensor.py similarity index 100% rename from homeassistant/components/sensor/modem_callerid.py rename to homeassistant/components/modem_callerid/sensor.py diff --git a/homeassistant/components/mold_indicator/__init__.py b/homeassistant/components/mold_indicator/__init__.py new file mode 100644 index 00000000000..adadf41b2b0 --- /dev/null +++ b/homeassistant/components/mold_indicator/__init__.py @@ -0,0 +1 @@ +"""Calculates mold growth indication from temperature and humidity.""" diff --git a/homeassistant/components/sensor/mold_indicator.py b/homeassistant/components/mold_indicator/sensor.py similarity index 100% rename from homeassistant/components/sensor/mold_indicator.py rename to homeassistant/components/mold_indicator/sensor.py diff --git a/homeassistant/components/monoprice/__init__.py b/homeassistant/components/monoprice/__init__.py new file mode 100644 index 00000000000..d968e270390 --- /dev/null +++ b/homeassistant/components/monoprice/__init__.py @@ -0,0 +1 @@ +"""The monoprice component.""" diff --git a/homeassistant/components/media_player/monoprice.py b/homeassistant/components/monoprice/media_player.py similarity index 100% rename from homeassistant/components/media_player/monoprice.py rename to homeassistant/components/monoprice/media_player.py diff --git a/homeassistant/components/moon/.translations/sensor.ar.json b/homeassistant/components/moon/.translations/sensor.ar.json new file mode 100644 index 00000000000..94af741f5f4 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.ar.json @@ -0,0 +1,6 @@ +{ + "state": { + "first_quarter": "\u0627\u0644\u0631\u0628\u0639 \u0627\u0644\u0623\u0648\u0644", + "full_moon": "\u0627\u0644\u0642\u0645\u0631 \u0627\u0644\u0643\u0627\u0645\u0644" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.ca.json b/homeassistant/components/moon/.translations/sensor.ca.json new file mode 100644 index 00000000000..e294579da09 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.ca.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Quart creixent", + "full_moon": "Lluna plena", + "last_quarter": "Quart minvant", + "new_moon": "Lluna nova", + "waning_crescent": "Minvant (Lluna vella)", + "waning_gibbous": "Gibosa minvant", + "waxing_crescent": "Lluna nova visible", + "waxing_gibbous": "Gibosa creixent" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.cs.json b/homeassistant/components/moon/.translations/sensor.cs.json new file mode 100644 index 00000000000..ef1d5bf5f13 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.cs.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Prvn\u00ed \u010dtvr\u0165", + "full_moon": "\u00dapln\u011bk", + "last_quarter": "Posledn\u00ed \u010dtvr\u0165", + "new_moon": "Nov", + "waning_crescent": "Couvaj\u00edc\u00ed srpek", + "waning_gibbous": "Couvaj\u00edc\u00ed m\u011bs\u00edc", + "waxing_crescent": "Dor\u016fstaj\u00edc\u00ed srpek", + "waxing_gibbous": "Dor\u016fstaj\u00edc\u00ed m\u011bs\u00edc" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.da.json b/homeassistant/components/moon/.translations/sensor.da.json new file mode 100644 index 00000000000..c2406de68bb --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.da.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "F\u00f8rste kvartal", + "full_moon": "Fuldm\u00e5ne", + "last_quarter": "Sidste kvartal", + "new_moon": "Nym\u00e5ne", + "waning_crescent": "Aftagende halvm\u00e5ne", + "waning_gibbous": "Aftagende m\u00e5ne", + "waxing_crescent": "Tiltagende halvm\u00e5ne", + "waxing_gibbous": "Tiltagende m\u00e5ne" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.de.json b/homeassistant/components/moon/.translations/sensor.de.json new file mode 100644 index 00000000000..310ebf9c359 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.de.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Erstes Viertel", + "full_moon": "Vollmond", + "last_quarter": "Letztes Viertel", + "new_moon": "Neumond", + "waning_crescent": "Abnehmende Sichel", + "waning_gibbous": "Drittes Viertel", + "waxing_crescent": "Zunehmende Sichel", + "waxing_gibbous": "Zweites Viertel" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.en.json b/homeassistant/components/moon/.translations/sensor.en.json new file mode 100644 index 00000000000..587b9496114 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.en.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "First quarter", + "full_moon": "Full moon", + "last_quarter": "Last quarter", + "new_moon": "New moon", + "waning_crescent": "Waning crescent", + "waning_gibbous": "Waning gibbous", + "waxing_crescent": "Waxing crescent", + "waxing_gibbous": "Waxing gibbous" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.es-419.json b/homeassistant/components/moon/.translations/sensor.es-419.json new file mode 100644 index 00000000000..71cfab736cb --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.es-419.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Cuarto creciente", + "full_moon": "Luna llena", + "last_quarter": "Cuarto menguante", + "new_moon": "Luna nueva", + "waning_crescent": "Luna menguante", + "waning_gibbous": "Luna menguante gibosa", + "waxing_crescent": "Luna creciente", + "waxing_gibbous": "Luna creciente gibosa" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.es.json b/homeassistant/components/moon/.translations/sensor.es.json new file mode 100644 index 00000000000..3ce14cd4c77 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.es.json @@ -0,0 +1,10 @@ +{ + "state": { + "first_quarter": "Primer cuarto", + "full_moon": "Luna llena", + "last_quarter": "\u00daltimo cuarto", + "new_moon": "Luna nueva", + "waning_crescent": "Luna menguante", + "waxing_crescent": "Luna creciente" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.et.json b/homeassistant/components/moon/.translations/sensor.et.json new file mode 100644 index 00000000000..0d82e0d8f94 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.et.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Esimene veerand", + "full_moon": "T\u00e4iskuu", + "last_quarter": "Viimane veerand", + "new_moon": "Kuu loomine", + "waning_crescent": "Vanakuu", + "waning_gibbous": "Kahanev kuu", + "waxing_crescent": "Noorkuu", + "waxing_gibbous": "Kasvav kuu" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.fi.json b/homeassistant/components/moon/.translations/sensor.fi.json new file mode 100644 index 00000000000..10f8bb9b8a6 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.fi.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Ensimm\u00e4inen nelj\u00e4nnes", + "full_moon": "T\u00e4ysikuu", + "last_quarter": "Viimeinen nelj\u00e4nnes", + "new_moon": "Uusikuu", + "waning_crescent": "V\u00e4henev\u00e4 sirppi", + "waning_gibbous": "V\u00e4henev\u00e4 kuperakuu", + "waxing_crescent": "Kasvava sirppi", + "waxing_gibbous": "Kasvava kuperakuu" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.fr.json b/homeassistant/components/moon/.translations/sensor.fr.json new file mode 100644 index 00000000000..fac2b654a46 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.fr.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Premier quartier", + "full_moon": "Pleine lune", + "last_quarter": "Dernier quartier", + "new_moon": "Nouvelle lune", + "waning_crescent": "Dernier croissant", + "waning_gibbous": "Gibbeuse d\u00e9croissante", + "waxing_crescent": "Premier croissant", + "waxing_gibbous": "Gibbeuse croissante" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.he.json b/homeassistant/components/moon/.translations/sensor.he.json new file mode 100644 index 00000000000..6531d3c8265 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.he.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "\u05e8\u05d1\u05e2\u05d5\u05df \u05e8\u05d0\u05e9\u05d5\u05df", + "full_moon": "\u05d9\u05e8\u05d7 \u05de\u05dc\u05d0", + "last_quarter": "\u05e8\u05d1\u05e2\u05d5\u05df \u05d0\u05d7\u05e8\u05d5\u05df", + "new_moon": "\u05e8\u05d0\u05e9 \u05d7\u05d5\u05d3\u05e9", + "waning_crescent": "Waning crescent", + "waning_gibbous": "Waning gibbous", + "waxing_crescent": "Waxing crescent", + "waxing_gibbous": "Waxing gibbous" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.hu.json b/homeassistant/components/moon/.translations/sensor.hu.json new file mode 100644 index 00000000000..fff9f51f50d --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.hu.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Els\u0151 negyed", + "full_moon": "Telihold", + "last_quarter": "Utols\u00f3 negyed", + "new_moon": "\u00dajhold", + "waning_crescent": "Fogy\u00f3 holdsarl\u00f3", + "waning_gibbous": "Fogy\u00f3 hold", + "waxing_crescent": "N\u00f6v\u0151 holdsarl\u00f3", + "waxing_gibbous": "N\u00f6v\u0151 hold" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.id.json b/homeassistant/components/moon/.translations/sensor.id.json new file mode 100644 index 00000000000..3ce14204fb5 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.id.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Babak pertama", + "full_moon": "Bulan purnama", + "last_quarter": "Kuartal terakhir", + "new_moon": "Bulan baru", + "waning_crescent": "Waning crescent", + "waning_gibbous": "Waning gibbous", + "waxing_crescent": "Waxing crescent", + "waxing_gibbous": "Waxing gibbous" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.it.json b/homeassistant/components/moon/.translations/sensor.it.json new file mode 100644 index 00000000000..39c7f22f7af --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.it.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Primo quarto", + "full_moon": "Luna piena", + "last_quarter": "Ultimo quarto", + "new_moon": "Luna nuova", + "waning_crescent": "Luna calante", + "waning_gibbous": "Gibbosa calante", + "waxing_crescent": "Luna crescente", + "waxing_gibbous": "Gibbosa crescente" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.ko.json b/homeassistant/components/moon/.translations/sensor.ko.json new file mode 100644 index 00000000000..7e62250b892 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.ko.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "\ubc18\ub2ec(\ucc28\uc624\ub974\ub294)", + "full_moon": "\ubcf4\ub984\ub2ec", + "last_quarter": "\ubc18\ub2ec(\uc904\uc5b4\ub4dc\ub294)", + "new_moon": "\uc0ad\uc6d4", + "waning_crescent": "\uadf8\ubbd0\ub2ec", + "waning_gibbous": "\ud558\ud604\ub2ec", + "waxing_crescent": "\ucd08\uc2b9\ub2ec", + "waxing_gibbous": "\uc0c1\ud604\ub2ec" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.lb.json b/homeassistant/components/moon/.translations/sensor.lb.json new file mode 100644 index 00000000000..d2f95685634 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.lb.json @@ -0,0 +1,6 @@ +{ + "state": { + "full_moon": "Vollmound", + "new_moon": "Neimound" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.nl.json b/homeassistant/components/moon/.translations/sensor.nl.json new file mode 100644 index 00000000000..5e78d429b9f --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.nl.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Eerste kwartier", + "full_moon": "Volle maan", + "last_quarter": "Laatste kwartier", + "new_moon": "Nieuwe maan", + "waning_crescent": "Krimpende, sikkelvormige maan", + "waning_gibbous": "Krimpende, vooruitspringende maan", + "waxing_crescent": "Wassende, sikkelvormige maan", + "waxing_gibbous": "Wassende, vooruitspringende maan" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.nn.json b/homeassistant/components/moon/.translations/sensor.nn.json new file mode 100644 index 00000000000..7c516bcce50 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.nn.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Fyrste kvartal", + "full_moon": "Fullm\u00e5ne", + "last_quarter": "Siste kvartal", + "new_moon": "Nym\u00e5ne", + "waning_crescent": "Minkande halvm\u00e5ne", + "waning_gibbous": "Minkande m\u00e5ne", + "waxing_crescent": "Veksande halvm\u00e5ne", + "waxing_gibbous": "Veksande m\u00e5ne" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.no.json b/homeassistant/components/moon/.translations/sensor.no.json new file mode 100644 index 00000000000..a440fdde4f2 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.no.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "F\u00f8rste kvartal", + "full_moon": "Fullm\u00e5ne", + "last_quarter": "Siste kvarter", + "new_moon": "Nym\u00e5ne", + "waning_crescent": "Minkende m\u00e5nesigd", + "waning_gibbous": "Minkende m\u00e5ne", + "waxing_crescent": "Voksende m\u00e5nesigd", + "waxing_gibbous": "Voksende m\u00e5ne" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.pl.json b/homeassistant/components/moon/.translations/sensor.pl.json new file mode 100644 index 00000000000..85dfe79bae4 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.pl.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "pierwsza kwadra", + "full_moon": "pe\u0142nia", + "last_quarter": "ostatnia kwadra", + "new_moon": "n\u00f3w", + "waning_crescent": "sierp ubywaj\u0105cy", + "waning_gibbous": "ubywaj\u0105cy garbaty", + "waxing_crescent": "sierp przybywaj\u0105cy", + "waxing_gibbous": "przybywaj\u0105cy garbaty" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.pt-BR.json b/homeassistant/components/moon/.translations/sensor.pt-BR.json new file mode 100644 index 00000000000..93b17784a4e --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.pt-BR.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Quarto crescente", + "full_moon": "Lua cheia", + "last_quarter": "Quarto minguante", + "new_moon": "Lua Nova", + "waning_crescent": "Minguante", + "waning_gibbous": "Minguante gibosa", + "waxing_crescent": "Crescente", + "waxing_gibbous": "Crescente gibosa" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.pt.json b/homeassistant/components/moon/.translations/sensor.pt.json new file mode 100644 index 00000000000..14961ab98f0 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.pt.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Quarto crescente", + "full_moon": "Lua cheia", + "last_quarter": "Quarto minguante", + "new_moon": "Lua nova", + "waning_crescent": "Lua crescente", + "waning_gibbous": "Minguante convexa", + "waxing_crescent": "Lua minguante", + "waxing_gibbous": "Crescente convexa" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.ro.json b/homeassistant/components/moon/.translations/sensor.ro.json new file mode 100644 index 00000000000..6f64e497c74 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.ro.json @@ -0,0 +1,6 @@ +{ + "state": { + "full_moon": "Lun\u0103 plin\u0103", + "new_moon": "Lun\u0103 nou\u0103" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.ru.json b/homeassistant/components/moon/.translations/sensor.ru.json new file mode 100644 index 00000000000..6db932a1aed --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.ru.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "\u041f\u0435\u0440\u0432\u0430\u044f \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u044c", + "full_moon": "\u041f\u043e\u043b\u043d\u043e\u043b\u0443\u043d\u0438\u0435", + "last_quarter": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u044c", + "new_moon": "\u041d\u043e\u0432\u043e\u043b\u0443\u043d\u0438\u0435", + "waning_crescent": "\u0421\u0442\u0430\u0440\u0430\u044f \u043b\u0443\u043d\u0430", + "waning_gibbous": "\u0423\u0431\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u043b\u0443\u043d\u0430", + "waxing_crescent": "\u041c\u043e\u043b\u043e\u0434\u0430\u044f \u043b\u0443\u043d\u0430", + "waxing_gibbous": "\u041f\u0440\u0438\u0431\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u043b\u0443\u043d\u0430" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.sl.json b/homeassistant/components/moon/.translations/sensor.sl.json new file mode 100644 index 00000000000..1b69e10e6f9 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.sl.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "Prvi krajec", + "full_moon": "Polna luna", + "last_quarter": "Zadnji krajec", + "new_moon": "Mlaj", + "waning_crescent": "Zadnji izbo\u010dec", + "waning_gibbous": "Zadnji srpec", + "waxing_crescent": "Prvi izbo\u010dec", + "waxing_gibbous": "Prvi srpec" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.sv.json b/homeassistant/components/moon/.translations/sensor.sv.json new file mode 100644 index 00000000000..ae69c1c9654 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.sv.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "F\u00f6rsta kvartalet", + "full_moon": "Fullm\u00e5ne", + "last_quarter": "Sista kvartalet", + "new_moon": "Nym\u00e5ne", + "waning_crescent": "Avtagande halvm\u00e5ne", + "waning_gibbous": "Avtagande halvm\u00e5ne", + "waxing_crescent": "Tilltagande halvm\u00e5ne", + "waxing_gibbous": "Tilltagande halvm\u00e5ne" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.uk.json b/homeassistant/components/moon/.translations/sensor.uk.json new file mode 100644 index 00000000000..4e1a9f7acab --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.uk.json @@ -0,0 +1,8 @@ +{ + "state": { + "first_quarter": "\u041f\u0435\u0440\u0448\u0430 \u0447\u0432\u0435\u0440\u0442\u044c", + "full_moon": "\u041f\u043e\u0432\u043d\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c", + "last_quarter": "\u041e\u0441\u0442\u0430\u043d\u043d\u044f \u0447\u0432\u0435\u0440\u0442\u044c", + "new_moon": "\u041d\u043e\u0432\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.zh-Hans.json b/homeassistant/components/moon/.translations/sensor.zh-Hans.json new file mode 100644 index 00000000000..22ab0d49f62 --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.zh-Hans.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "\u4e0a\u5f26\u6708", + "full_moon": "\u6ee1\u6708", + "last_quarter": "\u4e0b\u5f26\u6708", + "new_moon": "\u65b0\u6708", + "waning_crescent": "\u6b8b\u6708", + "waning_gibbous": "\u4e8f\u51f8\u6708", + "waxing_crescent": "\u5ce8\u7709\u6708", + "waxing_gibbous": "\u76c8\u51f8\u6708" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/.translations/sensor.zh-Hant.json b/homeassistant/components/moon/.translations/sensor.zh-Hant.json new file mode 100644 index 00000000000..9cf4aad011e --- /dev/null +++ b/homeassistant/components/moon/.translations/sensor.zh-Hant.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "\u4e0a\u5f26\u6708", + "full_moon": "\u6eff\u6708", + "last_quarter": "\u4e0b\u5f26\u6708", + "new_moon": "\u65b0\u6708", + "waning_crescent": "\u6b98\u6708", + "waning_gibbous": "\u8667\u51f8\u6708", + "waxing_crescent": "\u86fe\u7709\u6708", + "waxing_gibbous": "\u76c8\u51f8\u6708" + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/__init__.py b/homeassistant/components/moon/__init__.py new file mode 100644 index 00000000000..f7049608dda --- /dev/null +++ b/homeassistant/components/moon/__init__.py @@ -0,0 +1 @@ +"""The moon component.""" diff --git a/homeassistant/components/sensor/moon.py b/homeassistant/components/moon/sensor.py similarity index 100% rename from homeassistant/components/sensor/moon.py rename to homeassistant/components/moon/sensor.py diff --git a/homeassistant/components/sensor/strings.moon.json b/homeassistant/components/moon/strings.sensor.json similarity index 100% rename from homeassistant/components/sensor/strings.moon.json rename to homeassistant/components/moon/strings.sensor.json diff --git a/homeassistant/components/mopar/__init__.py b/homeassistant/components/mopar/__init__.py new file mode 100644 index 00000000000..d845d585765 --- /dev/null +++ b/homeassistant/components/mopar/__init__.py @@ -0,0 +1,157 @@ +"""Support for Mopar vehicles.""" +import logging +from datetime import timedelta + +import voluptuous as vol + +from homeassistant.components.lock import DOMAIN as LOCK +from homeassistant.components.sensor import DOMAIN as SENSOR +from homeassistant.components.switch import DOMAIN as SWITCH +from homeassistant.const import ( + CONF_USERNAME, + CONF_PASSWORD, + CONF_PIN, + CONF_SCAN_INTERVAL +) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.discovery import load_platform +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.event import track_time_interval + +REQUIREMENTS = ['motorparts==1.1.0'] + +DOMAIN = 'mopar' +DATA_UPDATED = '{}_data_updated'.format(DOMAIN) + +_LOGGER = logging.getLogger(__name__) + +COOKIE_FILE = 'mopar_cookies.pickle' +SUCCESS_RESPONSE = 'completed' + +SUPPORTED_PLATFORMS = [LOCK, SENSOR, SWITCH] + +DEFAULT_INTERVAL = timedelta(days=7) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_PIN): cv.positive_int, + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + }) +}, extra=vol.ALLOW_EXTRA) + +SERVICE_HORN = 'sound_horn' +ATTR_VEHICLE_INDEX = 'vehicle_index' +SERVICE_HORN_SCHEMA = vol.Schema({ + vol.Required(ATTR_VEHICLE_INDEX): cv.positive_int +}) + + +def setup(hass, config): + """Set up the Mopar component.""" + import motorparts + + cookie = hass.config.path(COOKIE_FILE) + try: + session = motorparts.get_session( + config[CONF_USERNAME], + config[CONF_PASSWORD], + config[CONF_PIN], + cookie_path=cookie + ) + except motorparts.MoparError: + _LOGGER.error("Failed to login") + return False + + data = hass.data[DOMAIN] = MoparData(hass, session) + data.update(now=None) + + track_time_interval( + hass, data.update, config[CONF_SCAN_INTERVAL] + ) + + def handle_horn(call): + """Enable the horn on the Mopar vehicle.""" + data.actuate('horn', call.data[ATTR_VEHICLE_INDEX]) + + hass.services.register( + DOMAIN, + SERVICE_HORN, + handle_horn, + schema=SERVICE_HORN_SCHEMA + ) + + for platform in SUPPORTED_PLATFORMS: + load_platform(hass, platform, DOMAIN, {}, config) + + return True + + +class MoparData: + """ + Container for Mopar vehicle data. + + Prevents session expiry re-login race condition. + """ + + def __init__(self, hass, session): + """Initialize data.""" + self._hass = hass + self._session = session + self.vehicles = [] + self.vhrs = {} + self.tow_guides = {} + + def update(self, now, **kwargs): + """Update data.""" + import motorparts + + _LOGGER.debug("Updating vehicle data") + try: + self.vehicles = motorparts.get_summary(self._session)['vehicles'] + except motorparts.MoparError: + _LOGGER.exception("Failed to get summary") + return + + for index, _ in enumerate(self.vehicles): + try: + self.vhrs[index] = motorparts.get_report(self._session, index) + self.tow_guides[index] = motorparts.get_tow_guide( + self._session, index) + except motorparts.MoparError: + _LOGGER.warning("Failed to update for vehicle index %s", index) + return + + dispatcher_send(self._hass, DATA_UPDATED) + + @property + def attribution(self): + """Get the attribution string from Mopar.""" + import motorparts + + return motorparts.ATTRIBUTION + + def get_vehicle_name(self, index): + """Get the name corresponding with this vehicle.""" + vehicle = self.vehicles[index] + if not vehicle: + return None + return '{} {} {}'.format( + vehicle['year'], + vehicle['make'], + vehicle['model'] + ) + + def actuate(self, command, index): + """Run a command on the specified Mopar vehicle.""" + import motorparts + + try: + response = getattr(motorparts, command)(self._session, index) + except motorparts.MoparError as error: + _LOGGER.error(error) + return False + + return response == SUCCESS_RESPONSE diff --git a/homeassistant/components/mopar/lock.py b/homeassistant/components/mopar/lock.py new file mode 100644 index 00000000000..aa2e0161813 --- /dev/null +++ b/homeassistant/components/mopar/lock.py @@ -0,0 +1,55 @@ +"""Support for the Mopar vehicle lock.""" +import logging + +from homeassistant.components.lock import LockDevice +from homeassistant.components.mopar import ( + DOMAIN as MOPAR_DOMAIN +) +from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED + +DEPENDENCIES = ['mopar'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Mopar lock platform.""" + data = hass.data[MOPAR_DOMAIN] + add_entities([MoparLock(data, index) + for index, _ in enumerate(data.vehicles)], True) + + +class MoparLock(LockDevice): + """Representation of a Mopar vehicle lock.""" + + def __init__(self, data, index): + """Initialize the Mopar lock.""" + self._index = index + self._name = '{} Lock'.format(data.get_vehicle_name(self._index)) + self._actuate = data.actuate + self._state = None + + @property + def name(self): + """Return the name of the lock.""" + return self._name + + @property + def is_locked(self): + """Return true if vehicle is locked.""" + return self._state == STATE_LOCKED + + @property + def should_poll(self): + """Return the polling requirement for this lock.""" + return False + + def lock(self, **kwargs): + """Lock the vehicle.""" + if self._actuate('lock', self._index): + self._state = STATE_LOCKED + + def unlock(self, **kwargs): + """Unlock the vehicle.""" + if self._actuate('unlock', self._index): + self._state = STATE_UNLOCKED diff --git a/homeassistant/components/mopar/sensor.py b/homeassistant/components/mopar/sensor.py new file mode 100644 index 00000000000..0d6e5765fda --- /dev/null +++ b/homeassistant/components/mopar/sensor.py @@ -0,0 +1,93 @@ +"""Support for the Mopar vehicle sensor platform.""" +from homeassistant.components.mopar import ( + DOMAIN as MOPAR_DOMAIN, + DATA_UPDATED, + ATTR_VEHICLE_INDEX +) +from homeassistant.const import ( + ATTR_ATTRIBUTION, LENGTH_KILOMETERS) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import Entity + +DEPENDENCIES = ['mopar'] +ICON = 'mdi:car' + + +async def async_setup_platform(hass, config, add_entities, + discovery_info=None): + """Set up the Mopar platform.""" + data = hass.data[MOPAR_DOMAIN] + add_entities([MoparSensor(data, index) + for index, _ in enumerate(data.vehicles)], True) + + +class MoparSensor(Entity): + """Mopar vehicle sensor.""" + + def __init__(self, data, index): + """Initialize the sensor.""" + self._index = index + self._vehicle = {} + self._vhr = {} + self._tow_guide = {} + self._odometer = None + self._data = data + self._name = self._data.get_vehicle_name(self._index) + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._odometer + + @property + def device_state_attributes(self): + """Return the state attributes.""" + attributes = { + ATTR_VEHICLE_INDEX: self._index, + ATTR_ATTRIBUTION: self._data.attribution + } + attributes.update(self._vehicle) + attributes.update(self._vhr) + attributes.update(self._tow_guide) + return attributes + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self.hass.config.units.length_unit + + @property + def icon(self): + """Return the icon.""" + return ICON + + @property + def should_poll(self): + """Return the polling requirement for this sensor.""" + return False + + async def async_added_to_hass(self): + """Handle entity which will be added.""" + async_dispatcher_connect( + self.hass, DATA_UPDATED, self._schedule_immediate_update + ) + + def update(self): + """Update device state.""" + self._vehicle = self._data.vehicles[self._index] + self._vhr = self._data.vhrs.get(self._index, {}) + self._tow_guide = self._data.tow_guides.get(self._index, {}) + if 'odometer' in self._vhr: + odo = float(self._vhr['odometer']) + self._odometer = int(self.hass.config.units.length( + odo, LENGTH_KILOMETERS)) + + @callback + def _schedule_immediate_update(self): + self.async_schedule_update_ha_state(True) diff --git a/homeassistant/components/mopar/services.yaml b/homeassistant/components/mopar/services.yaml new file mode 100644 index 00000000000..7915aefcb0f --- /dev/null +++ b/homeassistant/components/mopar/services.yaml @@ -0,0 +1,6 @@ +sound_horn: + description: Trigger the vehicle's horn + fields: + vehicle_index: + description: The index of the vehicle to trigger. This is exposed in the sensor's device attributes. + example: 1 \ No newline at end of file diff --git a/homeassistant/components/mopar/switch.py b/homeassistant/components/mopar/switch.py new file mode 100644 index 00000000000..352cdafbd41 --- /dev/null +++ b/homeassistant/components/mopar/switch.py @@ -0,0 +1,53 @@ +"""Support for the Mopar vehicle switch.""" +import logging + +from homeassistant.components.mopar import DOMAIN as MOPAR_DOMAIN +from homeassistant.components.switch import SwitchDevice +from homeassistant.const import STATE_ON, STATE_OFF + +DEPENDENCIES = ['mopar'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Mopar Switch platform.""" + data = hass.data[MOPAR_DOMAIN] + add_entities([MoparSwitch(data, index) + for index, _ in enumerate(data.vehicles)], True) + + +class MoparSwitch(SwitchDevice): + """Representation of a Mopar switch.""" + + def __init__(self, data, index): + """Initialize the Switch.""" + self._index = index + self._name = '{} Switch'.format(data.get_vehicle_name(self._index)) + self._actuate = data.actuate + self._state = None + + @property + def name(self): + """Return the name of the switch.""" + return self._name + + @property + def is_on(self): + """Return True if the entity is on.""" + return self._state == STATE_ON + + @property + def should_poll(self): + """Return the polling requirement for this switch.""" + return False + + def turn_on(self, **kwargs): + """Turn on the Mopar Vehicle.""" + if self._actuate('engine_on', self._index): + self._state = STATE_ON + + def turn_off(self, **kwargs): + """Turn off the Mopar Vehicle.""" + if self._actuate('engine_off', self._index): + self._state = STATE_OFF diff --git a/homeassistant/components/mpchc/__init__.py b/homeassistant/components/mpchc/__init__.py new file mode 100644 index 00000000000..e8a0057a9b6 --- /dev/null +++ b/homeassistant/components/mpchc/__init__.py @@ -0,0 +1 @@ +"""The mpchc component.""" diff --git a/homeassistant/components/media_player/mpchc.py b/homeassistant/components/mpchc/media_player.py similarity index 100% rename from homeassistant/components/media_player/mpchc.py rename to homeassistant/components/mpchc/media_player.py diff --git a/homeassistant/components/mpd/__init__.py b/homeassistant/components/mpd/__init__.py new file mode 100644 index 00000000000..bf917ff19aa --- /dev/null +++ b/homeassistant/components/mpd/__init__.py @@ -0,0 +1 @@ +"""The mpd component.""" diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/mpd/media_player.py similarity index 100% rename from homeassistant/components/media_player/mpd.py rename to homeassistant/components/mpd/media_player.py diff --git a/homeassistant/components/mqtt/.translations/bg.json b/homeassistant/components/mqtt/.translations/bg.json new file mode 100644 index 00000000000..4312bdba6ec --- /dev/null +++ b/homeassistant/components/mqtt/.translations/bg.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430 MQTT." + }, + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 \u0431\u0440\u043e\u043a\u0435\u0440\u0430." + }, + "step": { + "broker": { + "data": { + "broker": "\u0411\u0440\u043e\u043a\u0435\u0440", + "discovery": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0442\u043e \u043e\u0442\u043a\u0440\u0438\u0432\u0430\u043d\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + }, + "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\u0442\u0430 \u0437\u0430 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 \u0412\u0430\u0448\u0438\u044f MQTT \u0431\u0440\u043e\u043a\u0435\u0440.", + "title": "MQTT" + }, + "hassio_confirm": { + "data": { + "discovery": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0442\u043e \u043e\u0442\u043a\u0440\u0438\u0432\u0430\u043d\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" + }, + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0436\u0435 \u0441 MQTT \u0431\u0440\u043e\u043a\u0435\u0440\u0430 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 hass.io \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430 {addon}?", + "title": "MQTT \u0431\u0440\u043e\u043a\u0435\u0440 \u0447\u0440\u0435\u0437 Hass.io \u0434\u043e\u0431\u0430\u0432\u043a\u0430" + } + }, + "title": "MQTT" + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index e4d468e2155..2c605fb4b0d 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -5,8 +5,8 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/mqtt/ """ import asyncio -import inspect from functools import partial, wraps +import inspect from itertools import groupby import json import logging @@ -22,6 +22,7 @@ import requests.certs import voluptuous as vol from homeassistant import config_entries +from homeassistant.components import websocket_api from homeassistant.const import ( CONF_DEVICE, CONF_NAME, CONF_PASSWORD, CONF_PAYLOAD, CONF_PORT, CONF_PROTOCOL, CONF_USERNAME, CONF_VALUE_TEMPLATE, @@ -37,7 +38,6 @@ from homeassistant.setup import async_prepare_setup_platform from homeassistant.util.async_ import ( run_callback_threadsafe, run_coroutine_threadsafe) from homeassistant.util.logging import catch_log_exception -from homeassistant.components import websocket_api # Loading the config flow file will register the flow from . import config_flow # noqa pylint: disable=unused-import @@ -1012,7 +1012,7 @@ class MqttDiscoveryUpdate(Entity): await super().async_added_to_hass() from homeassistant.helpers.dispatcher import async_dispatcher_connect - from homeassistant.components.mqtt.discovery import ( + from .discovery import ( MQTT_DISCOVERY_UPDATED, clear_discovery_hash) @callback diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index c350b32b4ff..9498b597590 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -11,12 +11,6 @@ import voluptuous as vol from homeassistant.components import mqtt import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, - CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, - MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) -from homeassistant.components.mqtt.discovery import ( - MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.const import ( CONF_CODE, CONF_DEVICE, CONF_NAME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, @@ -26,6 +20,12 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from . import ( + ATTR_DISCOVERY_HASH, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, + CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, + MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) +from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash + _LOGGER = logging.getLogger(__name__) CONF_CODE_ARM_REQUIRED = 'code_arm_required' diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index f2a93d06f8e..7532f305328 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -11,12 +11,6 @@ import voluptuous as vol from homeassistant.components import binary_sensor, mqtt from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, BinarySensorDevice) -from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_QOS, CONF_STATE_TOPIC, CONF_UNIQUE_ID, - MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, - MqttEntityDeviceInfo, subscription) -from homeassistant.components.mqtt.discovery import ( - MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.const import ( CONF_DEVICE, CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, CONF_NAME, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_VALUE_TEMPLATE) @@ -26,6 +20,12 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.helpers.event as evt from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from . import ( + ATTR_DISCOVERY_HASH, CONF_QOS, CONF_STATE_TOPIC, CONF_UNIQUE_ID, + MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, + MqttEntityDeviceInfo, subscription) +from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash + _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'MQTT Binary sensor' diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index ca41f3c4225..889e5533403 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -12,17 +12,17 @@ import voluptuous as vol from homeassistant.components import camera, mqtt from homeassistant.components.camera import PLATFORM_SCHEMA, Camera -from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttDiscoveryUpdate, - subscription) -from homeassistant.components.mqtt.discovery import ( - MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.const import CONF_NAME from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from . import ( + ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttDiscoveryUpdate, + subscription) +from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash + _LOGGER = logging.getLogger(__name__) CONF_TOPIC = 'topic' diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index ae847437932..a9c23d27e11 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -10,20 +10,13 @@ import voluptuous as vol from homeassistant.components import climate, mqtt from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA) + PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, ClimateDevice) from homeassistant.components.climate.const import ( - ATTR_OPERATION_MODE, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, - STATE_AUTO, STATE_COOL, - STATE_DRY, STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AUX_HEAT, SUPPORT_AWAY_MODE, - SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE, - SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE) + ATTR_OPERATION_MODE, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, STATE_AUTO, + STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AUX_HEAT, + SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE) from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM -from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, - CONF_UNIQUE_ID, MQTT_BASE_PLATFORM_SCHEMA, MqttAttributes, - MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) -from homeassistant.components.mqtt.discovery import ( - MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.const import ( ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON) @@ -32,6 +25,12 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from . import ( + ATTR_DISCOVERY_HASH, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + CONF_UNIQUE_ID, MQTT_BASE_PLATFORM_SCHEMA, MqttAttributes, + MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) +from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['mqtt'] diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 37222cbe868..8116421ac10 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -13,12 +13,6 @@ from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, DEVICE_CLASSES_SCHEMA, SUPPORT_CLOSE, SUPPORT_CLOSE_TILT, SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, SUPPORT_STOP_TILT, CoverDevice) -from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, - CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, - MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) -from homeassistant.components.mqtt.discovery import ( - MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.const import ( CONF_DEVICE, CONF_DEVICE_CLASS, CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN) @@ -28,6 +22,12 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from . import ( + ATTR_DISCOVERY_HASH, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, + CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, + MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) +from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['mqtt'] diff --git a/homeassistant/components/mqtt/device_tracker.py b/homeassistant/components/mqtt/device_tracker.py index bf55d955ce1..0f22ed150ca 100644 --- a/homeassistant/components/mqtt/device_tracker.py +++ b/homeassistant/components/mqtt/device_tracker.py @@ -9,12 +9,13 @@ import logging import voluptuous as vol from homeassistant.components import mqtt -from homeassistant.core import callback -from homeassistant.const import CONF_DEVICES -from homeassistant.components.mqtt import CONF_QOS from homeassistant.components.device_tracker import PLATFORM_SCHEMA +from homeassistant.const import CONF_DEVICES +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from . import CONF_QOS + DEPENDENCIES = ['mqtt'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 745e54d0ed7..cb87a208b4f 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -10,12 +10,13 @@ import logging import re from homeassistant.components import mqtt -from homeassistant.components.mqtt import ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC from homeassistant.const import CONF_DEVICE, CONF_PLATFORM from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import HomeAssistantType +from . import ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC + _LOGGER = logging.getLogger(__name__) TOPIC_MATCHER = re.compile( diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index 7c9f816eff7..b8bff6088d8 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -12,12 +12,6 @@ from homeassistant.components import fan, mqtt from homeassistant.components.fan import ( ATTR_SPEED, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity) -from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, - CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, - MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) -from homeassistant.components.mqtt.discovery import ( - MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.const import ( CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_STATE, STATE_OFF, STATE_ON) @@ -26,6 +20,12 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from . import ( + ATTR_DISCOVERY_HASH, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, + CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, + MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) +from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['mqtt'] diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index d9adc37d79a..ee459d2174f 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -10,12 +10,6 @@ import voluptuous as vol from homeassistant.components import lock, mqtt from homeassistant.components.lock import LockDevice -from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, - CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, - MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) -from homeassistant.components.mqtt.discovery import ( - MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.const import ( CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE) from homeassistant.core import callback @@ -23,6 +17,12 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from . import ( + ATTR_DISCOVERY_HASH, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, + CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, + MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) +from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash + _LOGGER = logging.getLogger(__name__) CONF_PAYLOAD_LOCK = 'payload_lock' diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index c6ef3344fcf..aa8d5e2a31e 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -12,12 +12,6 @@ from typing import Optional import voluptuous as vol from homeassistant.components import mqtt, sensor -from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_QOS, CONF_STATE_TOPIC, CONF_UNIQUE_ID, - MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, - MqttEntityDeviceInfo, subscription) -from homeassistant.components.mqtt.discovery import ( - MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA from homeassistant.const import ( CONF_DEVICE, CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, CONF_ICON, CONF_NAME, @@ -30,6 +24,12 @@ from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util import dt as dt_util +from . import ( + ATTR_DISCOVERY_HASH, CONF_QOS, CONF_STATE_TOPIC, CONF_UNIQUE_ID, + MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, + MqttEntityDeviceInfo, subscription) +from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash + _LOGGER = logging.getLogger(__name__) CONF_EXPIRE_AFTER = 'expire_after' diff --git a/homeassistant/components/mqtt/server.py b/homeassistant/components/mqtt/server.py index dda2214ce46..3373149a013 100644 --- a/homeassistant/components/mqtt/server.py +++ b/homeassistant/components/mqtt/server.py @@ -40,12 +40,12 @@ def async_start(hass, password, server_config): from hbmqtt.broker import Broker, BrokerException passwd = tempfile.NamedTemporaryFile() + + gen_server_config, client_config = generate_config(hass, passwd, password) + try: if server_config is None: - server_config, client_config = generate_config( - hass, passwd, password) - else: - client_config = None + server_config = gen_server_config broker = Broker(server_config, hass.loop) yield from broker.start() diff --git a/homeassistant/components/mqtt/subscription.py b/homeassistant/components/mqtt/subscription.py index e4563fc377e..e159132d560 100644 --- a/homeassistant/components/mqtt/subscription.py +++ b/homeassistant/components/mqtt/subscription.py @@ -10,10 +10,11 @@ from typing import Any, Callable, Dict, Optional import attr from homeassistant.components import mqtt -from homeassistant.components.mqtt import DEFAULT_QOS, MessageCallbackType from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass +from . import DEFAULT_QOS, MessageCallbackType + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index de7da6b7249..4847afd80c9 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -9,12 +9,6 @@ import logging import voluptuous as vol from homeassistant.components import mqtt, switch -from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, - CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, - MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) -from homeassistant.components.mqtt.discovery import ( - MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.components.switch import SwitchDevice from homeassistant.const import ( CONF_DEVICE, CONF_ICON, CONF_NAME, CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, @@ -25,6 +19,12 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from . import ( + ATTR_DISCOVERY_HASH, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, + CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, + MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) +from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['mqtt'] diff --git a/homeassistant/components/mqtt/vacuum.py b/homeassistant/components/mqtt/vacuum.py index eb7e78b6254..efa00821c1b 100644 --- a/homeassistant/components/mqtt/vacuum.py +++ b/homeassistant/components/mqtt/vacuum.py @@ -9,11 +9,6 @@ import logging import voluptuous as vol from homeassistant.components import mqtt -from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, - MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) -from homeassistant.components.mqtt.discovery import ( - MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.components.vacuum import ( DOMAIN, SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED, SUPPORT_LOCATE, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND, @@ -25,6 +20,11 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.icon import icon_for_battery_level +from . import ( + ATTR_DISCOVERY_HASH, CONF_UNIQUE_ID, MqttAttributes, MqttAvailability, + MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) +from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['mqtt'] diff --git a/homeassistant/components/mqtt_json/__init__.py b/homeassistant/components/mqtt_json/__init__.py new file mode 100644 index 00000000000..49014ea0f96 --- /dev/null +++ b/homeassistant/components/mqtt_json/__init__.py @@ -0,0 +1 @@ +"""The mqtt_json component.""" diff --git a/homeassistant/components/device_tracker/mqtt_json.py b/homeassistant/components/mqtt_json/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/mqtt_json.py rename to homeassistant/components/mqtt_json/device_tracker.py diff --git a/homeassistant/components/mqtt_room/__init__.py b/homeassistant/components/mqtt_room/__init__.py new file mode 100644 index 00000000000..77526ded34f --- /dev/null +++ b/homeassistant/components/mqtt_room/__init__.py @@ -0,0 +1 @@ +"""The mqtt_room component.""" diff --git a/homeassistant/components/sensor/mqtt_room.py b/homeassistant/components/mqtt_room/sensor.py similarity index 100% rename from homeassistant/components/sensor/mqtt_room.py rename to homeassistant/components/mqtt_room/sensor.py diff --git a/homeassistant/components/mvglive/__init__.py b/homeassistant/components/mvglive/__init__.py new file mode 100644 index 00000000000..b475746c440 --- /dev/null +++ b/homeassistant/components/mvglive/__init__.py @@ -0,0 +1 @@ +"""The mvglive component.""" diff --git a/homeassistant/components/sensor/mvglive.py b/homeassistant/components/mvglive/sensor.py similarity index 100% rename from homeassistant/components/sensor/mvglive.py rename to homeassistant/components/mvglive/sensor.py diff --git a/homeassistant/components/mychevy/binary_sensor.py b/homeassistant/components/mychevy/binary_sensor.py index 67f12a14359..a2435d596be 100644 --- a/homeassistant/components/mychevy/binary_sensor.py +++ b/homeassistant/components/mychevy/binary_sensor.py @@ -1,14 +1,13 @@ """Support for MyChevy binary sensors.""" import logging -from homeassistant.components.mychevy import ( - EVBinarySensorConfig, DOMAIN as MYCHEVY_DOMAIN, UPDATE_TOPIC -) from homeassistant.components.binary_sensor import ( ENTITY_ID_FORMAT, BinarySensorDevice) from homeassistant.core import callback from homeassistant.util import slugify +from . import DOMAIN as MYCHEVY_DOMAIN, UPDATE_TOPIC, EVBinarySensorConfig + _LOGGER = logging.getLogger(__name__) SENSORS = [ diff --git a/homeassistant/components/mychevy/sensor.py b/homeassistant/components/mychevy/sensor.py index c7d140e0c4c..42b27df6299 100644 --- a/homeassistant/components/mychevy/sensor.py +++ b/homeassistant/components/mychevy/sensor.py @@ -1,16 +1,16 @@ """Support for MyChevy sensors.""" import logging -from homeassistant.components.mychevy import ( - EVSensorConfig, DOMAIN as MYCHEVY_DOMAIN, MYCHEVY_ERROR, MYCHEVY_SUCCESS, - UPDATE_TOPIC, ERROR_TOPIC -) from homeassistant.components.sensor import ENTITY_ID_FORMAT from homeassistant.core import callback from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.util import slugify +from . import ( + DOMAIN as MYCHEVY_DOMAIN, ERROR_TOPIC, MYCHEVY_ERROR, MYCHEVY_SUCCESS, + UPDATE_TOPIC, EVSensorConfig) + _LOGGER = logging.getLogger(__name__) BATTERY_SENSOR = "batteryLevel" diff --git a/homeassistant/components/notify/mycroft.py b/homeassistant/components/mycroft/notify.py similarity index 99% rename from homeassistant/components/notify/mycroft.py rename to homeassistant/components/mycroft/notify.py index 1fd22c5c42b..a8a401a9c1f 100644 --- a/homeassistant/components/notify/mycroft.py +++ b/homeassistant/components/mycroft/notify.py @@ -6,7 +6,6 @@ https://home-assistant.io/components/notify.mycroft/ """ import logging - from homeassistant.components.notify import BaseNotificationService DEPENDENCIES = ['mycroft'] diff --git a/homeassistant/components/myq/__init__.py b/homeassistant/components/myq/__init__.py new file mode 100644 index 00000000000..e9fa7900d90 --- /dev/null +++ b/homeassistant/components/myq/__init__.py @@ -0,0 +1 @@ +"""The myq component.""" diff --git a/homeassistant/components/cover/myq.py b/homeassistant/components/myq/cover.py similarity index 100% rename from homeassistant/components/cover/myq.py rename to homeassistant/components/myq/cover.py diff --git a/homeassistant/components/mystrom/__init__.py b/homeassistant/components/mystrom/__init__.py new file mode 100644 index 00000000000..54a24b9b4af --- /dev/null +++ b/homeassistant/components/mystrom/__init__.py @@ -0,0 +1 @@ +"""The mystrom component.""" diff --git a/homeassistant/components/binary_sensor/mystrom.py b/homeassistant/components/mystrom/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/mystrom.py rename to homeassistant/components/mystrom/binary_sensor.py diff --git a/homeassistant/components/light/mystrom.py b/homeassistant/components/mystrom/light.py similarity index 100% rename from homeassistant/components/light/mystrom.py rename to homeassistant/components/mystrom/light.py diff --git a/homeassistant/components/switch/mystrom.py b/homeassistant/components/mystrom/switch.py similarity index 100% rename from homeassistant/components/switch/mystrom.py rename to homeassistant/components/mystrom/switch.py diff --git a/homeassistant/components/nad/__init__.py b/homeassistant/components/nad/__init__.py new file mode 100644 index 00000000000..4fd52c874a0 --- /dev/null +++ b/homeassistant/components/nad/__init__.py @@ -0,0 +1 @@ +"""The nad component.""" diff --git a/homeassistant/components/media_player/nad.py b/homeassistant/components/nad/media_player.py similarity index 98% rename from homeassistant/components/media_player/nad.py rename to homeassistant/components/nad/media_player.py index 127be02dac4..00738abe4d1 100644 --- a/homeassistant/components/media_player/nad.py +++ b/homeassistant/components/nad/media_player.py @@ -14,7 +14,7 @@ from homeassistant.components.media_player import ( from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) -from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON +from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON, CONF_HOST REQUIREMENTS = ['nad_receiver==0.0.11'] @@ -34,7 +34,6 @@ SUPPORT_NAD = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ CONF_TYPE = 'type' CONF_SERIAL_PORT = 'serial_port' # for NADReceiver -CONF_HOST = 'host' # for NADReceiverTelnet CONF_PORT = 'port' # for NADReceiverTelnet CONF_MIN_VOLUME = 'min_volume' CONF_MAX_VOLUME = 'max_volume' diff --git a/homeassistant/components/nanoleaf/__init__.py b/homeassistant/components/nanoleaf/__init__.py new file mode 100644 index 00000000000..776d6a61772 --- /dev/null +++ b/homeassistant/components/nanoleaf/__init__.py @@ -0,0 +1 @@ +"""The nanoleaf component.""" diff --git a/homeassistant/components/light/nanoleaf.py b/homeassistant/components/nanoleaf/light.py similarity index 98% rename from homeassistant/components/light/nanoleaf.py rename to homeassistant/components/nanoleaf/light.py index bd34c535b70..8cb899f1d28 100644 --- a/homeassistant/components/light/nanoleaf.py +++ b/homeassistant/components/nanoleaf/light.py @@ -195,6 +195,7 @@ class NanoleafLight(Light): def update(self): """Fetch new state data for this light.""" + from pynanoleaf import Unavailable try: self._available = self._light.available self._brightness = self._light.brightness @@ -203,7 +204,7 @@ class NanoleafLight(Light): self._effects_list = self._light.effects self._hs_color = self._light.hue, self._light.saturation self._state = self._light.on - except Exception as err: # pylint:disable=broad-except + except Unavailable as err: _LOGGER.error("Could not update status for %s (%s)", self.name, err) self._available = False diff --git a/homeassistant/components/neato/camera.py b/homeassistant/components/neato/camera.py index 530aa8fc6f1..f8106c3e645 100644 --- a/homeassistant/components/neato/camera.py +++ b/homeassistant/components/neato/camera.py @@ -1,10 +1,10 @@ """Support for loading picture from Neato.""" +from datetime import timedelta import logging -from datetime import timedelta from homeassistant.components.camera import Camera -from homeassistant.components.neato import ( - NEATO_MAP_DATA, NEATO_ROBOTS, NEATO_LOGIN) + +from . import NEATO_LOGIN, NEATO_MAP_DATA, NEATO_ROBOTS _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/neato/switch.py b/homeassistant/components/neato/switch.py index fcc72762b8d..ea60f9492e2 100644 --- a/homeassistant/components/neato/switch.py +++ b/homeassistant/components/neato/switch.py @@ -1,10 +1,13 @@ """Support for Neato Connected Vacuums switches.""" -import logging from datetime import timedelta +import logging + import requests + from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers.entity import ToggleEntity -from homeassistant.components.neato import NEATO_ROBOTS, NEATO_LOGIN + +from . import NEATO_LOGIN, NEATO_ROBOTS _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index 2f2f3904947..3575301ea97 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -1,22 +1,23 @@ """Support for Neato Connected Vacuums.""" -import logging from datetime import timedelta +import logging + import requests import voluptuous as vol -from homeassistant.const import (ATTR_ENTITY_ID) from homeassistant.components.vacuum import ( - StateVacuumDevice, SUPPORT_BATTERY, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, - SUPPORT_STATE, SUPPORT_STOP, SUPPORT_START, STATE_IDLE, - STATE_PAUSED, STATE_CLEANING, STATE_DOCKED, STATE_RETURNING, STATE_ERROR, - SUPPORT_MAP, ATTR_STATUS, ATTR_BATTERY_LEVEL, ATTR_BATTERY_ICON, - SUPPORT_LOCATE, SUPPORT_CLEAN_SPOT, DOMAIN) -from homeassistant.components.neato import ( - NEATO_ROBOTS, NEATO_LOGIN, NEATO_MAP_DATA, ACTION, ERRORS, MODE, ALERTS, - NEATO_PERSISTENT_MAPS) - -from homeassistant.helpers.service import extract_entity_ids + ATTR_BATTERY_ICON, ATTR_BATTERY_LEVEL, ATTR_STATUS, DOMAIN, STATE_CLEANING, + STATE_DOCKED, STATE_ERROR, STATE_IDLE, STATE_PAUSED, STATE_RETURNING, + SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_LOCATE, SUPPORT_MAP, + SUPPORT_PAUSE, SUPPORT_RETURN_HOME, SUPPORT_START, SUPPORT_STATE, + SUPPORT_STOP, StateVacuumDevice) +from homeassistant.const import ATTR_ENTITY_ID import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.service import extract_entity_ids + +from . import ( + ACTION, ALERTS, ERRORS, MODE, NEATO_LOGIN, NEATO_MAP_DATA, + NEATO_PERSISTENT_MAPS, NEATO_ROBOTS) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/nederlandse_spoorwegen/__init__.py b/homeassistant/components/nederlandse_spoorwegen/__init__.py new file mode 100644 index 00000000000..b052df36e34 --- /dev/null +++ b/homeassistant/components/nederlandse_spoorwegen/__init__.py @@ -0,0 +1 @@ +"""The nederlandse_spoorwegen component.""" diff --git a/homeassistant/components/sensor/nederlandse_spoorwegen.py b/homeassistant/components/nederlandse_spoorwegen/sensor.py similarity index 100% rename from homeassistant/components/sensor/nederlandse_spoorwegen.py rename to homeassistant/components/nederlandse_spoorwegen/sensor.py diff --git a/homeassistant/components/nello/__init__.py b/homeassistant/components/nello/__init__.py new file mode 100644 index 00000000000..dfe556f7f29 --- /dev/null +++ b/homeassistant/components/nello/__init__.py @@ -0,0 +1 @@ +"""The nello component.""" diff --git a/homeassistant/components/lock/nello.py b/homeassistant/components/nello/lock.py similarity index 100% rename from homeassistant/components/lock/nello.py rename to homeassistant/components/nello/lock.py diff --git a/homeassistant/components/ness_alarm/__init__.py b/homeassistant/components/ness_alarm/__init__.py index 653ade806ec..97896f9aa3f 100644 --- a/homeassistant/components/ness_alarm/__init__.py +++ b/homeassistant/components/ness_alarm/__init__.py @@ -8,20 +8,20 @@ import voluptuous as vol from homeassistant.components.binary_sensor import DEVICE_CLASSES from homeassistant.const import (ATTR_CODE, ATTR_STATE, EVENT_HOMEASSISTANT_STOP, - CONF_SCAN_INTERVAL) + CONF_SCAN_INTERVAL, CONF_HOST) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send -REQUIREMENTS = ['nessclient==0.9.14'] +REQUIREMENTS = ['nessclient==0.9.15'] _LOGGER = logging.getLogger(__name__) DOMAIN = 'ness_alarm' DATA_NESS = 'ness_alarm' -CONF_DEVICE_HOST = 'host' CONF_DEVICE_PORT = 'port' +CONF_INFER_ARMING_STATE = 'infer_arming_state' CONF_ZONES = 'zones' CONF_ZONE_NAME = 'name' CONF_ZONE_TYPE = 'type' @@ -29,6 +29,7 @@ CONF_ZONE_ID = 'id' ATTR_OUTPUT_ID = 'output_id' DEFAULT_ZONES = [] DEFAULT_SCAN_INTERVAL = datetime.timedelta(minutes=1) +DEFAULT_INFER_ARMING_STATE = False SIGNAL_ZONE_CHANGED = 'ness_alarm.zone_changed' SIGNAL_ARMING_STATE_CHANGED = 'ness_alarm.arming_state_changed' @@ -44,12 +45,15 @@ ZONE_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_DEVICE_HOST): cv.string, + vol.Required(CONF_HOST): cv.string, vol.Required(CONF_DEVICE_PORT): cv.port, vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): vol.All(cv.time_period, cv.positive_timedelta), vol.Optional(CONF_ZONES, default=DEFAULT_ZONES): vol.All(cv.ensure_list, [ZONE_SCHEMA]), + vol.Optional(CONF_INFER_ARMING_STATE, + default=DEFAULT_INFER_ARMING_STATE): + cv.boolean }), }, extra=vol.ALLOW_EXTRA) @@ -71,12 +75,14 @@ async def async_setup(hass, config): conf = config[DOMAIN] zones = conf[CONF_ZONES] - host = conf[CONF_DEVICE_HOST] + host = conf[CONF_HOST] port = conf[CONF_DEVICE_PORT] scan_interval = conf[CONF_SCAN_INTERVAL] + infer_arming_state = conf[CONF_INFER_ARMING_STATE] client = Client(host=host, port=port, loop=hass.loop, - update_interval=scan_interval.total_seconds()) + update_interval=scan_interval.total_seconds(), + infer_arming_state=infer_arming_state) hass.data[DATA_NESS] = client async def _close(event): diff --git a/homeassistant/components/alarm_control_panel/ness_alarm.py b/homeassistant/components/ness_alarm/alarm_control_panel.py similarity index 93% rename from homeassistant/components/alarm_control_panel/ness_alarm.py rename to homeassistant/components/ness_alarm/alarm_control_panel.py index ee3a0c213cb..f77b534980f 100644 --- a/homeassistant/components/alarm_control_panel/ness_alarm.py +++ b/homeassistant/components/ness_alarm/alarm_control_panel.py @@ -8,14 +8,14 @@ https://home-assistant.io/components/alarm_control_panel.ness_alarm/ import logging import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.ness_alarm import ( - DATA_NESS, SIGNAL_ARMING_STATE_CHANGED) from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMING, - STATE_ALARM_TRIGGERED, STATE_ALARM_PENDING, STATE_ALARM_DISARMED) + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMING, STATE_ALARM_DISARMED, + STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from . import DATA_NESS, SIGNAL_ARMING_STATE_CHANGED + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['ness_alarm'] diff --git a/homeassistant/components/binary_sensor/ness_alarm.py b/homeassistant/components/ness_alarm/binary_sensor.py similarity index 95% rename from homeassistant/components/binary_sensor/ness_alarm.py rename to homeassistant/components/ness_alarm/binary_sensor.py index 9f1479efd69..7b684f74aa1 100644 --- a/homeassistant/components/binary_sensor/ness_alarm.py +++ b/homeassistant/components/ness_alarm/binary_sensor.py @@ -7,12 +7,13 @@ https://home-assistant.io/components/binary_sensor.ness_alarm/ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.ness_alarm import ( - CONF_ZONES, CONF_ZONE_TYPE, CONF_ZONE_NAME, CONF_ZONE_ID, - SIGNAL_ZONE_CHANGED, ZoneChangedData) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from . import ( + CONF_ZONE_ID, CONF_ZONE_NAME, CONF_ZONE_TYPE, CONF_ZONES, + SIGNAL_ZONE_CHANGED, ZoneChangedData) + DEPENDENCIES = ['ness_alarm'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/nest/.translations/es-419.json b/homeassistant/components/nest/.translations/es-419.json index 117a4500d58..78239148a4e 100644 --- a/homeassistant/components/nest/.translations/es-419.json +++ b/homeassistant/components/nest/.translations/es-419.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_setup": "Solo puedes configurar una sola cuenta Nest.", + "authorize_url_fail": "Error desconocido al generar una URL de autorizaci\u00f3n.", "no_flows": "Debe configurar Nest antes de poder autenticarse con \u00e9l. [Lea las instrucciones] (https://www.home-assistant.io/components/nest/)." }, "error": { diff --git a/homeassistant/components/nest/binary_sensor.py b/homeassistant/components/nest/binary_sensor.py index 1077fdb073e..aa56bfbf29d 100644 --- a/homeassistant/components/nest/binary_sensor.py +++ b/homeassistant/components/nest/binary_sensor.py @@ -3,10 +3,11 @@ from itertools import chain import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.nest import ( - DATA_NEST, DATA_NEST_CONFIG, CONF_BINARY_SENSORS, NestSensorDevice) from homeassistant.const import CONF_MONITORED_CONDITIONS +from . import ( + CONF_BINARY_SENSORS, DATA_NEST, DATA_NEST_CONFIG, NestSensorDevice) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['nest'] diff --git a/homeassistant/components/nest/climate.py b/homeassistant/components/nest/climate.py index 88b6cbbbeb0..cd9a7cb71b6 100644 --- a/homeassistant/components/nest/climate.py +++ b/homeassistant/components/nest/climate.py @@ -3,20 +3,19 @@ import logging import voluptuous as vol -from homeassistant.components.nest import ( - DATA_NEST, SIGNAL_NEST_UPDATE, DOMAIN as NEST_DOMAIN) -from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( - STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ECO, - ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, - SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE) + ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, STATE_AUTO, STATE_COOL, + STATE_ECO, STATE_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW) from homeassistant.const import ( - ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT, - CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF) + ATTR_TEMPERATURE, CONF_SCAN_INTERVAL, STATE_OFF, STATE_ON, TEMP_CELSIUS, + TEMP_FAHRENHEIT) from homeassistant.helpers.dispatcher import async_dispatcher_connect +from . import DATA_NEST, DOMAIN as NEST_DOMAIN, SIGNAL_NEST_UPDATE + DEPENDENCIES = ['nest'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/nest/sensor.py b/homeassistant/components/nest/sensor.py index bde3f681c2b..ecae83e303c 100644 --- a/homeassistant/components/nest/sensor.py +++ b/homeassistant/components/nest/sensor.py @@ -1,13 +1,12 @@ """Support for Nest Thermostat sensors.""" import logging -from homeassistant.components.climate.const import ( - STATE_COOL, STATE_HEAT) -from homeassistant.components.nest import ( - DATA_NEST, DATA_NEST_CONFIG, CONF_SENSORS, NestSensorDevice) +from homeassistant.components.climate.const import STATE_COOL, STATE_HEAT from homeassistant.const import ( - TEMP_CELSIUS, TEMP_FAHRENHEIT, CONF_MONITORED_CONDITIONS, - DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, STATE_OFF) + CONF_MONITORED_CONDITIONS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT) + +from . import CONF_SENSORS, DATA_NEST, DATA_NEST_CONFIG, NestSensorDevice DEPENDENCIES = ['nest'] diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index 2e580627543..2036e55b3a8 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -12,7 +12,7 @@ from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle -REQUIREMENTS = ['pyatmo==1.8'] +REQUIREMENTS = ['pyatmo==1.9'] DEPENDENCIES = ['webhook'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/netatmo/binary_sensor.py b/homeassistant/components/netatmo/binary_sensor.py index 7986010ef64..a11ce6bddf7 100644 --- a/homeassistant/components/netatmo/binary_sensor.py +++ b/homeassistant/components/netatmo/binary_sensor.py @@ -4,11 +4,12 @@ import logging import voluptuous as vol from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.components.netatmo import CameraData + PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.const import CONF_TIMEOUT from homeassistant.helpers import config_validation as cv +from . import CameraData + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['netatmo'] diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 57d30d6cbc9..513852cde23 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -4,11 +4,12 @@ import logging import requests import voluptuous as vol +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.const import CONF_VERIFY_SSL -from homeassistant.components.netatmo import CameraData -from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) from homeassistant.helpers import config_validation as cv +from . import CameraData + DEPENDENCIES = ['netatmo'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 2d8b06dd466..d0537c5912b 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -315,6 +315,8 @@ class HomeData: self.home_id = self.homedata.gethomeId(self.home) except TypeError: _LOGGER.error("Error when getting home data.") + except AttributeError: + _LOGGER.error("No default_home in HomeData.") except pyatmo.NoDevice: _LOGGER.debug("No thermostat devices available.") diff --git a/homeassistant/components/netatmo_public/__init__.py b/homeassistant/components/netatmo_public/__init__.py new file mode 100644 index 00000000000..c332d208ddb --- /dev/null +++ b/homeassistant/components/netatmo_public/__init__.py @@ -0,0 +1 @@ +"""The netatmo_public component.""" diff --git a/homeassistant/components/sensor/netatmo_public.py b/homeassistant/components/netatmo_public/sensor.py similarity index 100% rename from homeassistant/components/sensor/netatmo_public.py rename to homeassistant/components/netatmo_public/sensor.py diff --git a/homeassistant/components/netdata/__init__.py b/homeassistant/components/netdata/__init__.py new file mode 100644 index 00000000000..34104716aa0 --- /dev/null +++ b/homeassistant/components/netdata/__init__.py @@ -0,0 +1 @@ +"""The netdata component.""" diff --git a/homeassistant/components/sensor/netdata.py b/homeassistant/components/netdata/sensor.py similarity index 100% rename from homeassistant/components/sensor/netdata.py rename to homeassistant/components/netdata/sensor.py diff --git a/homeassistant/components/netgear/__init__.py b/homeassistant/components/netgear/__init__.py new file mode 100644 index 00000000000..1b55d01b463 --- /dev/null +++ b/homeassistant/components/netgear/__init__.py @@ -0,0 +1 @@ +"""The netgear component.""" diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/netgear/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/netgear.py rename to homeassistant/components/netgear/device_tracker.py diff --git a/homeassistant/components/netgear_lte/__init__.py b/homeassistant/components/netgear_lte/__init__.py index 5f8c680b7f0..a259a361be4 100644 --- a/homeassistant/components/netgear_lte/__init__.py +++ b/homeassistant/components/netgear_lte/__init__.py @@ -8,50 +8,82 @@ import attr import voluptuous as vol from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP) + CONF_HOST, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_PASSWORD, + CONF_RECIPIENT, EVENT_HOMEASSISTANT_STOP) from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv +from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers.aiohttp_client import async_create_clientsession -from homeassistant.util import Throttle +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.event import async_track_time_interval + +from . import sensor_types REQUIREMENTS = ['eternalegypt==0.0.5'] _LOGGER = logging.getLogger(__name__) -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) +SCAN_INTERVAL = timedelta(seconds=10) +DISPATCHER_NETGEAR_LTE = 'netgear_lte_update' DOMAIN = 'netgear_lte' DATA_KEY = 'netgear_lte' +EVENT_SMS = 'netgear_lte_sms' + +SERVICE_DELETE_SMS = 'delete_sms' + +ATTR_HOST = 'host' +ATTR_SMS_ID = 'sms_id' +ATTR_FROM = 'from' +ATTR_MESSAGE = 'message' + + +NOTIFY_SCHEMA = vol.Schema({ + vol.Optional(CONF_NAME, default=DOMAIN): cv.string, + vol.Optional(CONF_RECIPIENT, default=[]): + vol.All(cv.ensure_list, [cv.string]), +}) + +SENSOR_SCHEMA = vol.Schema({ + vol.Optional(CONF_MONITORED_CONDITIONS, default=sensor_types.DEFAULT): + vol.All(cv.ensure_list, [vol.In(sensor_types.ALL)]), +}) + CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.All(cv.ensure_list, [vol.Schema({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(NOTIFY_DOMAIN, default={}): + vol.All(cv.ensure_list, [NOTIFY_SCHEMA]), + vol.Optional(SENSOR_DOMAIN, default={}): + SENSOR_SCHEMA, })]) }, extra=vol.ALLOW_EXTRA) +DELETE_SMS_SCHEMA = vol.Schema({ + vol.Required(ATTR_HOST): cv.string, + vol.Required(ATTR_SMS_ID): vol.All(cv.ensure_list, [cv.positive_int]), +}) + @attr.s class ModemData: """Class for modem state.""" + hass = attr.ib() host = attr.ib() modem = attr.ib() - serial_number = attr.ib(init=False, default=None) - unread_count = attr.ib(init=False, default=None) - usage = attr.ib(init=False, default=None) + data = attr.ib(init=False, default=None) connected = attr.ib(init=False, default=True) - @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self): """Call the API to update the data.""" import eternalegypt try: - information = await self.modem.information() - self.serial_number = information.serial_number - self.unread_count = sum(1 for x in information.sms if x.unread) - self.usage = information.usage + self.data = await self.modem.information() if not self.connected: _LOGGER.warning("Connected to %s", self.host) self.connected = True @@ -59,8 +91,9 @@ class ModemData: if self.connected: _LOGGER.warning("Lost connection to %s", self.host) self.connected = False - self.unread_count = None - self.usage = None + self.data = None + + async_dispatcher_send(self.hass, DISPATCHER_NETGEAR_LTE) @attr.s @@ -71,13 +104,8 @@ class LTEData: modem_data = attr.ib(init=False, factory=dict) def get_modem_data(self, config): - """Get the requested or the only modem_data value.""" - if CONF_HOST in config: - return self.modem_data.get(config[CONF_HOST]) - if len(self.modem_data) == 1: - return next(iter(self.modem_data.values())) - - return None + """Get modem_data for the host in config.""" + return self.modem_data.get(config[CONF_HOST]) async def async_setup(hass, config): @@ -87,9 +115,50 @@ async def async_setup(hass, config): hass, cookie_jar=aiohttp.CookieJar(unsafe=True)) hass.data[DATA_KEY] = LTEData(websession) - tasks = [_setup_lte(hass, conf) for conf in config.get(DOMAIN, [])] - if tasks: - await asyncio.wait(tasks) + async def delete_sms_handler(service): + """Apply a service.""" + host = service.data[ATTR_HOST] + conf = {CONF_HOST: host} + modem_data = hass.data[DATA_KEY].get_modem_data(conf) + + if not modem_data: + _LOGGER.error( + "%s: host %s unavailable", SERVICE_DELETE_SMS, host) + return + + for sms_id in service.data[ATTR_SMS_ID]: + await modem_data.modem.delete_sms(sms_id) + + hass.services.async_register( + DOMAIN, SERVICE_DELETE_SMS, delete_sms_handler, + schema=DELETE_SMS_SCHEMA) + + netgear_lte_config = config[DOMAIN] + + # Set up each modem + tasks = [_setup_lte(hass, lte_conf) for lte_conf in netgear_lte_config] + await asyncio.wait(tasks) + + # Load platforms for each modem + for lte_conf in netgear_lte_config: + # Notify + for notify_conf in lte_conf[NOTIFY_DOMAIN]: + discovery_info = { + CONF_HOST: lte_conf[CONF_HOST], + CONF_NAME: notify_conf.get(CONF_NAME), + NOTIFY_DOMAIN: notify_conf, + } + hass.async_create_task(discovery.async_load_platform( + hass, NOTIFY_DOMAIN, DOMAIN, discovery_info, config)) + + # Sensor + sensor_conf = lte_conf.get(SENSOR_DOMAIN) + discovery_info = { + CONF_HOST: lte_conf[CONF_HOST], + SENSOR_DOMAIN: sensor_conf, + } + hass.async_create_task(discovery.async_load_platform( + hass, SENSOR_DOMAIN, DOMAIN, discovery_info, config)) return True @@ -104,7 +173,7 @@ async def _setup_lte(hass, lte_config): websession = hass.data[DATA_KEY].websession modem = eternalegypt.Modem(hostname=host, websession=websession) - modem_data = ModemData(host, modem) + modem_data = ModemData(hass, host, modem) try: await _login(hass, modem_data, password) @@ -124,6 +193,19 @@ async def _setup_lte(hass, lte_config): async def _login(hass, modem_data, password): """Log in and complete setup.""" await modem_data.modem.login(password=password) + + def fire_sms_event(sms): + """Send an SMS event.""" + data = { + ATTR_HOST: modem_data.host, + ATTR_SMS_ID: sms.id, + ATTR_FROM: sms.sender, + ATTR_MESSAGE: sms.message, + } + hass.bus.async_fire(EVENT_SMS, data) + + await modem_data.modem.add_sms_listener(fire_sms_event) + await modem_data.async_update() hass.data[DATA_KEY].modem_data[modem_data.host] = modem_data @@ -133,6 +215,12 @@ async def _login(hass, modem_data, password): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cleanup) + async def _update(now): + """Periodic update.""" + await modem_data.async_update() + + async_track_time_interval(hass, _update, SCAN_INTERVAL) + async def _retry_login(hass, modem_data, password): """Sleep and retry setup.""" diff --git a/homeassistant/components/netgear_lte/notify.py b/homeassistant/components/netgear_lte/notify.py index 20a20b21291..fba1a335ace 100644 --- a/homeassistant/components/netgear_lte/notify.py +++ b/homeassistant/components/netgear_lte/notify.py @@ -2,28 +2,23 @@ import logging import attr -import voluptuous as vol from homeassistant.components.notify import ( - ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) -from homeassistant.const import CONF_HOST -import homeassistant.helpers.config_validation as cv + ATTR_TARGET, BaseNotificationService, DOMAIN) -from ..netgear_lte import DATA_KEY +from . import CONF_RECIPIENT, DATA_KEY DEPENDENCIES = ['netgear_lte'] _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST): cv.string, - vol.Required(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]), -}) - async def async_get_service(hass, config, discovery_info=None): """Get the notification service.""" - return NetgearNotifyService(hass, config) + if discovery_info is None: + return + + return NetgearNotifyService(hass, discovery_info) @attr.s @@ -35,17 +30,23 @@ class NetgearNotifyService(BaseNotificationService): async def async_send_message(self, message="", **kwargs): """Send a message to a user.""" + import eternalegypt + modem_data = self.hass.data[DATA_KEY].get_modem_data(self.config) if not modem_data: - _LOGGER.error("No modem available") + _LOGGER.error("Modem not ready") return - phone = self.config.get(ATTR_TARGET) - targets = kwargs.get(ATTR_TARGET, phone) - if targets and message: - for target in targets: - import eternalegypt - try: - await modem_data.modem.sms(target, message) - except eternalegypt.Error: - _LOGGER.error("Unable to send to %s", target) + targets = kwargs.get(ATTR_TARGET, self.config[DOMAIN][CONF_RECIPIENT]) + if not targets: + _LOGGER.warning("No recipients") + return + + if not message: + return + + for target in targets: + try: + await modem_data.modem.sms(target, message) + except eternalegypt.Error: + _LOGGER.error("Unable to send to %s", target) diff --git a/homeassistant/components/netgear_lte/sensor.py b/homeassistant/components/netgear_lte/sensor.py index 339fa678d61..1be960edfe3 100644 --- a/homeassistant/components/netgear_lte/sensor.py +++ b/homeassistant/components/netgear_lte/sensor.py @@ -1,43 +1,43 @@ """Support for Netgear LTE sensors.""" +import logging + import attr -import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_HOST, CONF_SENSORS +from homeassistant.components.sensor import DOMAIN from homeassistant.exceptions import PlatformNotReady -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from homeassistant.helpers.dispatcher import async_dispatcher_connect -from ..netgear_lte import DATA_KEY +from . import CONF_MONITORED_CONDITIONS, DATA_KEY, DISPATCHER_NETGEAR_LTE +from .sensor_types import SENSOR_SMS, SENSOR_USAGE DEPENDENCIES = ['netgear_lte'] -SENSOR_SMS = 'sms' -SENSOR_USAGE = 'usage' - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST): cv.string, - vol.Required(CONF_SENSORS): vol.All( - cv.ensure_list, [vol.In([SENSOR_SMS, SENSOR_USAGE])]), -}) +_LOGGER = logging.getLogger(__name__) async def async_setup_platform( hass, config, async_add_entities, discovery_info): """Set up Netgear LTE sensor devices.""" - modem_data = hass.data[DATA_KEY].get_modem_data(config) + if discovery_info is None: + return - if not modem_data: + modem_data = hass.data[DATA_KEY].get_modem_data(discovery_info) + + if not modem_data or not modem_data.data: raise PlatformNotReady + sensor_conf = discovery_info[DOMAIN] + monitored_conditions = sensor_conf[CONF_MONITORED_CONDITIONS] + sensors = [] - for sensor_type in config[CONF_SENSORS]: + for sensor_type in monitored_conditions: if sensor_type == SENSOR_SMS: sensors.append(SMSSensor(modem_data, sensor_type)) elif sensor_type == SENSOR_USAGE: sensors.append(UsageSensor(modem_data, sensor_type)) - async_add_entities(sensors, True) + async_add_entities(sensors) @attr.s @@ -47,14 +47,37 @@ class LTESensor(Entity): modem_data = attr.ib() sensor_type = attr.ib() + _unique_id = attr.ib(init=False) + + @_unique_id.default + def _init_unique_id(self): + """Register unique_id while we know data is valid.""" + return "{}_{}".format( + self.sensor_type, self.modem_data.data.serial_number) + + async def async_added_to_hass(self): + """Register callback.""" + async_dispatcher_connect( + self.hass, DISPATCHER_NETGEAR_LTE, self.async_write_ha_state) + async def async_update(self): - """Update state.""" + """Force update of state.""" await self.modem_data.async_update() + @property + def should_poll(self): + """Return that the sensor should not be polled.""" + return False + + @property + def available(self): + """Return the availability of the sensor.""" + return self.modem_data.data is not None + @property def unique_id(self): """Return a unique ID like 'usage_5TG365AB0078V'.""" - return "{}_{}".format(self.sensor_type, self.modem_data.serial_number) + return self._unique_id class SMSSensor(LTESensor): @@ -68,7 +91,7 @@ class SMSSensor(LTESensor): @property def state(self): """Return the state of the sensor.""" - return self.modem_data.unread_count + return sum(1 for x in self.modem_data.data.sms if x.unread) class UsageSensor(LTESensor): @@ -87,7 +110,4 @@ class UsageSensor(LTESensor): @property def state(self): """Return the state of the sensor.""" - if self.modem_data.usage is None: - return None - - return round(self.modem_data.usage / 1024**2, 1) + return round(self.modem_data.data.usage / 1024**2, 1) diff --git a/homeassistant/components/netgear_lte/sensor_types.py b/homeassistant/components/netgear_lte/sensor_types.py new file mode 100644 index 00000000000..b05ecf6074a --- /dev/null +++ b/homeassistant/components/netgear_lte/sensor_types.py @@ -0,0 +1,8 @@ +"""Define possible sensor types.""" + +SENSOR_SMS = 'sms' +SENSOR_USAGE = 'usage' + +ALL = [SENSOR_SMS, SENSOR_USAGE] + +DEFAULT = [SENSOR_USAGE] diff --git a/homeassistant/components/netgear_lte/services.yaml b/homeassistant/components/netgear_lte/services.yaml new file mode 100644 index 00000000000..8f61e7a44b5 --- /dev/null +++ b/homeassistant/components/netgear_lte/services.yaml @@ -0,0 +1,9 @@ +delete_sms: + description: Delete messages from the modem inbox. + fields: + host: + description: The modem that should have a message deleted. + example: 192.168.5.1 + sms_id: + description: Integer or list of integers with inbox IDs of messages to delete. + example: 7 diff --git a/homeassistant/components/netio/__init__.py b/homeassistant/components/netio/__init__.py new file mode 100644 index 00000000000..0b1b7d2c128 --- /dev/null +++ b/homeassistant/components/netio/__init__.py @@ -0,0 +1 @@ +"""The netio component.""" diff --git a/homeassistant/components/switch/netio.py b/homeassistant/components/netio/switch.py similarity index 100% rename from homeassistant/components/switch/netio.py rename to homeassistant/components/netio/switch.py diff --git a/homeassistant/components/neurio_energy/__init__.py b/homeassistant/components/neurio_energy/__init__.py new file mode 100644 index 00000000000..631556329e4 --- /dev/null +++ b/homeassistant/components/neurio_energy/__init__.py @@ -0,0 +1 @@ +"""The neurio_energy component.""" diff --git a/homeassistant/components/sensor/neurio_energy.py b/homeassistant/components/neurio_energy/sensor.py similarity index 100% rename from homeassistant/components/sensor/neurio_energy.py rename to homeassistant/components/neurio_energy/sensor.py diff --git a/homeassistant/components/nfandroidtv/__init__.py b/homeassistant/components/nfandroidtv/__init__.py new file mode 100644 index 00000000000..9965265e00d --- /dev/null +++ b/homeassistant/components/nfandroidtv/__init__.py @@ -0,0 +1 @@ +"""The nfandroidtv component.""" diff --git a/homeassistant/components/notify/nfandroidtv.py b/homeassistant/components/nfandroidtv/notify.py similarity index 96% rename from homeassistant/components/notify/nfandroidtv.py rename to homeassistant/components/nfandroidtv/notify.py index f99d97574b4..c4003a6312a 100644 --- a/homeassistant/components/notify/nfandroidtv.py +++ b/homeassistant/components/nfandroidtv/notify.py @@ -4,24 +4,23 @@ Notifications for Android TV notification service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.nfandroidtv/ """ -import logging -import io import base64 +import io +import logging import requests -from requests.auth import HTTPBasicAuth -from requests.auth import HTTPDigestAuth +from requests.auth import HTTPBasicAuth, HTTPDigestAuth import voluptuous as vol -from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_DATA, BaseNotificationService, - PLATFORM_SCHEMA) -from homeassistant.const import CONF_TIMEOUT +from homeassistant.const import CONF_TIMEOUT, CONF_HOST import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import ( + ATTR_DATA, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, + BaseNotificationService) + _LOGGER = logging.getLogger(__name__) -CONF_IP = 'host' CONF_DURATION = 'duration' CONF_FONTSIZE = 'fontsize' CONF_POSITION = 'position' @@ -95,7 +94,7 @@ COLORS = { } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_IP): cv.string, + vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_DURATION, default=DEFAULT_DURATION): vol.Coerce(int), vol.Optional(CONF_FONTSIZE, default=DEFAULT_FONTSIZE): vol.In(FONTSIZES.keys()), @@ -112,7 +111,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the Notifications for Android TV notification service.""" - remoteip = config.get(CONF_IP) + remoteip = config.get(CONF_HOST) duration = config.get(CONF_DURATION) fontsize = config.get(CONF_FONTSIZE) position = config.get(CONF_POSITION) diff --git a/homeassistant/components/niko_home_control/__init__.py b/homeassistant/components/niko_home_control/__init__.py new file mode 100644 index 00000000000..2cb5c70d1dd --- /dev/null +++ b/homeassistant/components/niko_home_control/__init__.py @@ -0,0 +1 @@ +"""The niko_home_control component.""" diff --git a/homeassistant/components/light/niko_home_control.py b/homeassistant/components/niko_home_control/light.py similarity index 100% rename from homeassistant/components/light/niko_home_control.py rename to homeassistant/components/niko_home_control/light.py diff --git a/homeassistant/components/nilu/__init__.py b/homeassistant/components/nilu/__init__.py new file mode 100644 index 00000000000..d45739ef2d6 --- /dev/null +++ b/homeassistant/components/nilu/__init__.py @@ -0,0 +1 @@ +"""The nilu component.""" diff --git a/homeassistant/components/air_quality/nilu.py b/homeassistant/components/nilu/air_quality.py similarity index 100% rename from homeassistant/components/air_quality/nilu.py rename to homeassistant/components/nilu/air_quality.py diff --git a/homeassistant/components/nissan_leaf/binary_sensor.py b/homeassistant/components/nissan_leaf/binary_sensor.py index 2397405ec20..5c71cf1fc51 100644 --- a/homeassistant/components/nissan_leaf/binary_sensor.py +++ b/homeassistant/components/nissan_leaf/binary_sensor.py @@ -1,10 +1,10 @@ """Plugged In Status Support for the Nissan Leaf.""" import logging -from homeassistant.components.nissan_leaf import ( - DATA_CHARGING, DATA_LEAF, DATA_PLUGGED_IN, LeafEntity) from homeassistant.components.binary_sensor import BinarySensorDevice +from . import DATA_CHARGING, DATA_LEAF, DATA_PLUGGED_IN, LeafEntity + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['nissan_leaf'] diff --git a/homeassistant/components/nissan_leaf/device_tracker.py b/homeassistant/components/nissan_leaf/device_tracker.py index 1ca7fceb911..95f6fcdcaf1 100644 --- a/homeassistant/components/nissan_leaf/device_tracker.py +++ b/homeassistant/components/nissan_leaf/device_tracker.py @@ -1,11 +1,11 @@ """Support for tracking a Nissan Leaf.""" import logging -from homeassistant.components.nissan_leaf import ( - DATA_LEAF, DATA_LOCATION, SIGNAL_UPDATE_LEAF) from homeassistant.helpers.dispatcher import dispatcher_connect from homeassistant.util import slugify +from . import DATA_LEAF, DATA_LOCATION, SIGNAL_UPDATE_LEAF + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['nissan_leaf'] diff --git a/homeassistant/components/nissan_leaf/sensor.py b/homeassistant/components/nissan_leaf/sensor.py index f6206f1f4ef..682f482b488 100644 --- a/homeassistant/components/nissan_leaf/sensor.py +++ b/homeassistant/components/nissan_leaf/sensor.py @@ -1,14 +1,15 @@ """Battery Charge and Range Support for the Nissan Leaf.""" import logging -from homeassistant.components.nissan_leaf import ( - DATA_BATTERY, DATA_CHARGING, DATA_LEAF, DATA_RANGE_AC, DATA_RANGE_AC_OFF, - LeafEntity) from homeassistant.const import DEVICE_CLASS_BATTERY from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.util.distance import LENGTH_KILOMETERS, LENGTH_MILES from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM +from . import ( + DATA_BATTERY, DATA_CHARGING, DATA_LEAF, DATA_RANGE_AC, DATA_RANGE_AC_OFF, + LeafEntity) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['nissan_leaf'] diff --git a/homeassistant/components/nissan_leaf/switch.py b/homeassistant/components/nissan_leaf/switch.py index 60b9a6630cd..e6d72103a6c 100644 --- a/homeassistant/components/nissan_leaf/switch.py +++ b/homeassistant/components/nissan_leaf/switch.py @@ -1,10 +1,10 @@ """Charge and Climate Control Support for the Nissan Leaf.""" import logging -from homeassistant.components.nissan_leaf import ( - DATA_CLIMATE, DATA_LEAF, LeafEntity) from homeassistant.helpers.entity import ToggleEntity +from . import DATA_CLIMATE, DATA_LEAF, LeafEntity + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['nissan_leaf'] diff --git a/homeassistant/components/nmap_tracker/__init__.py b/homeassistant/components/nmap_tracker/__init__.py new file mode 100644 index 00000000000..da699caaa73 --- /dev/null +++ b/homeassistant/components/nmap_tracker/__init__.py @@ -0,0 +1 @@ +"""The nmap_tracker component.""" diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/nmap_tracker/device_tracker.py similarity index 93% rename from homeassistant/components/device_tracker/nmap_tracker.py rename to homeassistant/components/nmap_tracker/device_tracker.py index 3c090e8cd3b..e553d323b72 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/nmap_tracker/device_tracker.py @@ -1,14 +1,9 @@ -""" -Support for scanning a network with nmap. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/device_tracker.nmap_tracker/ -""" -from datetime import timedelta +"""Support for scanning a network with nmap.""" import logging import re import subprocess from collections import namedtuple +from datetime import timedelta import voluptuous as vol @@ -74,7 +69,7 @@ class NmapDeviceScanner(DeviceScanner): self._options = config[CONF_OPTIONS] self.home_interval = timedelta(minutes=minutes) - _LOGGER.info("Scanner initialized") + _LOGGER.debug("Scanner initialized") def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" @@ -105,7 +100,7 @@ class NmapDeviceScanner(DeviceScanner): Returns boolean if scanning successful. """ - _LOGGER.info("Scanning...") + _LOGGER.debug("Scanning...") from nmap import PortScanner, PortScannerError scanner = PortScanner() @@ -146,5 +141,5 @@ class NmapDeviceScanner(DeviceScanner): self.last_results = last_results - _LOGGER.info("nmap scan successful") + _LOGGER.debug("nmap scan successful") return True diff --git a/homeassistant/components/nmbs/__init__.py b/homeassistant/components/nmbs/__init__.py new file mode 100644 index 00000000000..11013d471b5 --- /dev/null +++ b/homeassistant/components/nmbs/__init__.py @@ -0,0 +1 @@ +"""The nmbs component.""" diff --git a/homeassistant/components/sensor/nmbs.py b/homeassistant/components/nmbs/sensor.py similarity index 99% rename from homeassistant/components/sensor/nmbs.py rename to homeassistant/components/nmbs/sensor.py index 84e187fa5a4..15f29339087 100644 --- a/homeassistant/components/sensor/nmbs.py +++ b/homeassistant/components/nmbs/sensor.py @@ -124,7 +124,6 @@ class NMBSLiveBoard(Entity): attrs = { 'departure': "In {} minutes".format(departure), 'extra_train': int(self._attrs['isExtra']) > 0, - 'occupancy': self._attrs['occupancy']['name'], 'vehicle_id': self._attrs['vehicle'], 'monitored_station': self._station, ATTR_ATTRIBUTION: "https://api.irail.be/", diff --git a/homeassistant/components/noaa_tides/__init__.py b/homeassistant/components/noaa_tides/__init__.py new file mode 100644 index 00000000000..398b9e6963c --- /dev/null +++ b/homeassistant/components/noaa_tides/__init__.py @@ -0,0 +1 @@ +"""The noaa_tides component.""" diff --git a/homeassistant/components/sensor/noaa_tides.py b/homeassistant/components/noaa_tides/sensor.py similarity index 100% rename from homeassistant/components/sensor/noaa_tides.py rename to homeassistant/components/noaa_tides/sensor.py diff --git a/homeassistant/components/norway_air/__init__.py b/homeassistant/components/norway_air/__init__.py new file mode 100644 index 00000000000..95955c60c44 --- /dev/null +++ b/homeassistant/components/norway_air/__init__.py @@ -0,0 +1 @@ +"""The norway_air component.""" diff --git a/homeassistant/components/air_quality/norway_air.py b/homeassistant/components/norway_air/air_quality.py similarity index 100% rename from homeassistant/components/air_quality/norway_air.py rename to homeassistant/components/norway_air/air_quality.py diff --git a/homeassistant/components/nsw_fuel_station/__init__.py b/homeassistant/components/nsw_fuel_station/__init__.py new file mode 100644 index 00000000000..88ff1e779be --- /dev/null +++ b/homeassistant/components/nsw_fuel_station/__init__.py @@ -0,0 +1 @@ +"""The nsw_fuel_station component.""" diff --git a/homeassistant/components/sensor/nsw_fuel_station.py b/homeassistant/components/nsw_fuel_station/sensor.py similarity index 100% rename from homeassistant/components/sensor/nsw_fuel_station.py rename to homeassistant/components/nsw_fuel_station/sensor.py diff --git a/homeassistant/components/nsw_rural_fire_service_feed/__init__.py b/homeassistant/components/nsw_rural_fire_service_feed/__init__.py new file mode 100644 index 00000000000..b54959f80ce --- /dev/null +++ b/homeassistant/components/nsw_rural_fire_service_feed/__init__.py @@ -0,0 +1 @@ +"""The nsw_rural_fire_service_feed component.""" diff --git a/homeassistant/components/geo_location/nsw_rural_fire_service_feed.py b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py similarity index 100% rename from homeassistant/components/geo_location/nsw_rural_fire_service_feed.py rename to homeassistant/components/nsw_rural_fire_service_feed/geo_location.py diff --git a/homeassistant/components/climate/nuheat.py b/homeassistant/components/nuheat/climate.py similarity index 95% rename from homeassistant/components/climate/nuheat.py rename to homeassistant/components/nuheat/climate.py index f52d2c7b501..27e909f8f04 100644 --- a/homeassistant/components/climate/nuheat.py +++ b/homeassistant/components/nuheat/climate.py @@ -4,29 +4,22 @@ Support for NuHeat thermostats. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.nuheat/ """ -import logging from datetime import timedelta +import logging import voluptuous as vol from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - DOMAIN, - SUPPORT_HOLD_MODE, - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE, - STATE_AUTO, - STATE_HEAT, - STATE_IDLE) -from homeassistant.components.nuheat import DOMAIN as NUHEAT_DOMAIN + DOMAIN, STATE_AUTO, STATE_HEAT, STATE_IDLE, SUPPORT_HOLD_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) from homeassistant.const import ( - ATTR_ENTITY_ID, - ATTR_TEMPERATURE, - TEMP_CELSIUS, - TEMP_FAHRENHEIT) + ATTR_ENTITY_ID, ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT) import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle +from . import DOMAIN as NUHEAT_DOMAIN + DEPENDENCIES = ["nuheat"] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/nuki/__init__.py b/homeassistant/components/nuki/__init__.py new file mode 100644 index 00000000000..2e15ac8a68d --- /dev/null +++ b/homeassistant/components/nuki/__init__.py @@ -0,0 +1 @@ +"""The nuki component.""" diff --git a/homeassistant/components/lock/nuki.py b/homeassistant/components/nuki/lock.py similarity index 100% rename from homeassistant/components/lock/nuki.py rename to homeassistant/components/nuki/lock.py diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py new file mode 100644 index 00000000000..e51145c8eaa --- /dev/null +++ b/homeassistant/components/nut/__init__.py @@ -0,0 +1 @@ +"""The nut component.""" diff --git a/homeassistant/components/sensor/nut.py b/homeassistant/components/nut/sensor.py similarity index 100% rename from homeassistant/components/sensor/nut.py rename to homeassistant/components/nut/sensor.py diff --git a/homeassistant/components/nx584/__init__.py b/homeassistant/components/nx584/__init__.py new file mode 100644 index 00000000000..95bd0b4e9b4 --- /dev/null +++ b/homeassistant/components/nx584/__init__.py @@ -0,0 +1 @@ +"""Support for NX584 alarm control panels.""" diff --git a/homeassistant/components/alarm_control_panel/nx584.py b/homeassistant/components/nx584/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/nx584.py rename to homeassistant/components/nx584/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/nx584.py b/homeassistant/components/nx584/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/nx584.py rename to homeassistant/components/nx584/binary_sensor.py diff --git a/homeassistant/components/nzbget/__init__.py b/homeassistant/components/nzbget/__init__.py new file mode 100644 index 00000000000..2480daf2ead --- /dev/null +++ b/homeassistant/components/nzbget/__init__.py @@ -0,0 +1 @@ +"""The nzbget component.""" diff --git a/homeassistant/components/sensor/nzbget.py b/homeassistant/components/nzbget/sensor.py similarity index 100% rename from homeassistant/components/sensor/nzbget.py rename to homeassistant/components/nzbget/sensor.py diff --git a/homeassistant/components/octoprint/binary_sensor.py b/homeassistant/components/octoprint/binary_sensor.py index cb860177796..be3381f3bc8 100644 --- a/homeassistant/components/octoprint/binary_sensor.py +++ b/homeassistant/components/octoprint/binary_sensor.py @@ -3,10 +3,10 @@ import logging import requests -from homeassistant.components.octoprint import (BINARY_SENSOR_TYPES, - DOMAIN as COMPONENT_DOMAIN) from homeassistant.components.binary_sensor import BinarySensorDevice +from . import BINARY_SENSOR_TYPES, DOMAIN as COMPONENT_DOMAIN + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['octoprint'] diff --git a/homeassistant/components/octoprint/sensor.py b/homeassistant/components/octoprint/sensor.py index 2df307f02ef..f07d88d11da 100644 --- a/homeassistant/components/octoprint/sensor.py +++ b/homeassistant/components/octoprint/sensor.py @@ -3,11 +3,11 @@ import logging import requests -from homeassistant.components.octoprint import (SENSOR_TYPES, - DOMAIN as COMPONENT_DOMAIN) -from homeassistant.const import (TEMP_CELSIUS) +from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity +from . import DOMAIN as COMPONENT_DOMAIN, SENSOR_TYPES + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['octoprint'] diff --git a/homeassistant/components/oem/__init__.py b/homeassistant/components/oem/__init__.py new file mode 100644 index 00000000000..f78dfee9a5b --- /dev/null +++ b/homeassistant/components/oem/__init__.py @@ -0,0 +1 @@ +"""The oem component.""" diff --git a/homeassistant/components/climate/oem.py b/homeassistant/components/oem/climate.py similarity index 100% rename from homeassistant/components/climate/oem.py rename to homeassistant/components/oem/climate.py diff --git a/homeassistant/components/ohmconnect/__init__.py b/homeassistant/components/ohmconnect/__init__.py new file mode 100644 index 00000000000..1713f82a59b --- /dev/null +++ b/homeassistant/components/ohmconnect/__init__.py @@ -0,0 +1 @@ +"""The ohmconnect component.""" diff --git a/homeassistant/components/sensor/ohmconnect.py b/homeassistant/components/ohmconnect/sensor.py similarity index 100% rename from homeassistant/components/sensor/ohmconnect.py rename to homeassistant/components/ohmconnect/sensor.py diff --git a/homeassistant/components/onewire/__init__.py b/homeassistant/components/onewire/__init__.py new file mode 100644 index 00000000000..ac5d1393378 --- /dev/null +++ b/homeassistant/components/onewire/__init__.py @@ -0,0 +1 @@ +"""The onewire component.""" diff --git a/homeassistant/components/sensor/onewire.py b/homeassistant/components/onewire/sensor.py similarity index 100% rename from homeassistant/components/sensor/onewire.py rename to homeassistant/components/onewire/sensor.py diff --git a/homeassistant/components/onkyo/__init__.py b/homeassistant/components/onkyo/__init__.py new file mode 100644 index 00000000000..02c026d1973 --- /dev/null +++ b/homeassistant/components/onkyo/__init__.py @@ -0,0 +1 @@ +"""The onkyo component.""" diff --git a/homeassistant/components/media_player/onkyo.py b/homeassistant/components/onkyo/media_player.py similarity index 99% rename from homeassistant/components/media_player/onkyo.py rename to homeassistant/components/onkyo/media_player.py index df30c7e0782..2fb284bb24a 100644 --- a/homeassistant/components/media_player/onkyo.py +++ b/homeassistant/components/onkyo/media_player.py @@ -179,7 +179,7 @@ class OnkyoDevice(MediaPlayerDevice): except (ValueError, OSError, AttributeError, AssertionError): if self._receiver.command_socket: self._receiver.command_socket = None - _LOGGER.info("Resetting connection to %s", self._name) + _LOGGER.debug("Resetting connection to %s", self._name) else: _LOGGER.info("%s is disconnected. Attempting to reconnect", self._name) diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py new file mode 100644 index 00000000000..ea4c875ac20 --- /dev/null +++ b/homeassistant/components/onvif/__init__.py @@ -0,0 +1 @@ +"""The onvif component.""" diff --git a/homeassistant/components/camera/onvif.py b/homeassistant/components/onvif/camera.py similarity index 90% rename from homeassistant/components/camera/onvif.py rename to homeassistant/components/onvif/camera.py index da0bae7c50b..a196f87cd10 100644 --- a/homeassistant/components/camera/onvif.py +++ b/homeassistant/components/onvif/camera.py @@ -13,7 +13,9 @@ import voluptuous as vol from homeassistant.const import ( CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT, ATTR_ENTITY_ID) -from homeassistant.components.camera import Camera, PLATFORM_SCHEMA, DOMAIN +from homeassistant.components.camera import ( + Camera, PLATFORM_SCHEMA, SUPPORT_STREAM) +from homeassistant.components.camera.const import DOMAIN from homeassistant.components.ffmpeg import ( DATA_FFMPEG, CONF_EXTRA_ARGUMENTS) import homeassistant.helpers.config_validation as cv @@ -31,7 +33,7 @@ DEFAULT_NAME = 'ONVIF Camera' DEFAULT_PORT = 5000 DEFAULT_USERNAME = 'admin' DEFAULT_PASSWORD = '888888' -DEFAULT_ARGUMENTS = '-q:v 2' +DEFAULT_ARGUMENTS = '-pred 1' DEFAULT_PROFILE = 0 CONF_PROFILE = "profile" @@ -186,13 +188,14 @@ class ONVIFHassCamera(Camera): self.hass.data[ONVIF_DATA] = {} self.hass.data[ONVIF_DATA][ENTITIES] = [] self.hass.data[ONVIF_DATA][ENTITIES].append(self) + await self.hass.async_add_executor_job(self.obtain_input_uri) async def async_camera_image(self): """Return a still image response from the camera.""" - from haffmpeg import ImageFrame, IMAGE_JPEG + from haffmpeg.tools import ImageFrame, IMAGE_JPEG if not self._input: - await self.hass.async_add_job(self.obtain_input_uri) + await self.hass.async_add_executor_job(self.obtain_input_uri) if not self._input: return None @@ -206,10 +209,10 @@ class ONVIFHassCamera(Camera): async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" - from haffmpeg import CameraMjpeg + from haffmpeg.camera import CameraMjpeg if not self._input: - await self.hass.async_add_job(self.obtain_input_uri) + await self.hass.async_add_executor_job(self.obtain_input_uri) if not self._input: return None @@ -220,12 +223,25 @@ class ONVIFHassCamera(Camera): self._input, extra_cmd=self._ffmpeg_arguments) try: + stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream, + self.hass, request, stream_reader, ffmpeg_manager.ffmpeg_stream_content_type) finally: await stream.close() + @property + def supported_features(self): + """Return supported features.""" + if self._input: + return SUPPORT_STREAM + return 0 + + @property + def stream_source(self): + """Return the stream source.""" + return self._input + @property def name(self): """Return the name of this camera.""" diff --git a/homeassistant/components/openalpr_cloud/__init__.py b/homeassistant/components/openalpr_cloud/__init__.py new file mode 100644 index 00000000000..a8a104ed3be --- /dev/null +++ b/homeassistant/components/openalpr_cloud/__init__.py @@ -0,0 +1 @@ +"""The openalpr_cloud component.""" diff --git a/homeassistant/components/image_processing/openalpr_cloud.py b/homeassistant/components/openalpr_cloud/image_processing.py similarity index 98% rename from homeassistant/components/image_processing/openalpr_cloud.py rename to homeassistant/components/openalpr_cloud/image_processing.py index dea55b76025..c983a945548 100644 --- a/homeassistant/components/image_processing/openalpr_cloud.py +++ b/homeassistant/components/openalpr_cloud/image_processing.py @@ -17,7 +17,7 @@ from homeassistant.core import split_entity_id from homeassistant.const import CONF_API_KEY from homeassistant.components.image_processing import ( PLATFORM_SCHEMA, CONF_CONFIDENCE, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME) -from homeassistant.components.image_processing.openalpr_local import ( +from homeassistant.components.openalpr_local.image_processing import ( ImageProcessingAlprEntity) from homeassistant.helpers.aiohttp_client import async_get_clientsession diff --git a/homeassistant/components/openalpr_local/__init__.py b/homeassistant/components/openalpr_local/__init__.py new file mode 100644 index 00000000000..436f15baeeb --- /dev/null +++ b/homeassistant/components/openalpr_local/__init__.py @@ -0,0 +1 @@ +"""The openalpr_local component.""" diff --git a/homeassistant/components/image_processing/openalpr_local.py b/homeassistant/components/openalpr_local/image_processing.py similarity index 100% rename from homeassistant/components/image_processing/openalpr_local.py rename to homeassistant/components/openalpr_local/image_processing.py diff --git a/homeassistant/components/opencv/__init__.py b/homeassistant/components/opencv/__init__.py new file mode 100644 index 00000000000..0e4a755b2b9 --- /dev/null +++ b/homeassistant/components/opencv/__init__.py @@ -0,0 +1 @@ +"""The opencv component.""" diff --git a/homeassistant/components/image_processing/opencv.py b/homeassistant/components/opencv/image_processing.py similarity index 100% rename from homeassistant/components/image_processing/opencv.py rename to homeassistant/components/opencv/image_processing.py diff --git a/homeassistant/components/openevse/__init__.py b/homeassistant/components/openevse/__init__.py new file mode 100644 index 00000000000..48d45591854 --- /dev/null +++ b/homeassistant/components/openevse/__init__.py @@ -0,0 +1 @@ +"""The openevse component.""" diff --git a/homeassistant/components/sensor/openevse.py b/homeassistant/components/openevse/sensor.py similarity index 100% rename from homeassistant/components/sensor/openevse.py rename to homeassistant/components/openevse/sensor.py diff --git a/homeassistant/components/openexchangerates/__init__.py b/homeassistant/components/openexchangerates/__init__.py new file mode 100644 index 00000000000..93d53614bdb --- /dev/null +++ b/homeassistant/components/openexchangerates/__init__.py @@ -0,0 +1 @@ +"""The openexchangerates component.""" diff --git a/homeassistant/components/sensor/openexchangerates.py b/homeassistant/components/openexchangerates/sensor.py similarity index 100% rename from homeassistant/components/sensor/openexchangerates.py rename to homeassistant/components/openexchangerates/sensor.py diff --git a/homeassistant/components/opengarage/__init__.py b/homeassistant/components/opengarage/__init__.py new file mode 100644 index 00000000000..2f4d2e09cfb --- /dev/null +++ b/homeassistant/components/opengarage/__init__.py @@ -0,0 +1 @@ +"""The opengarage component.""" diff --git a/homeassistant/components/cover/opengarage.py b/homeassistant/components/opengarage/cover.py similarity index 100% rename from homeassistant/components/cover/opengarage.py rename to homeassistant/components/opengarage/cover.py diff --git a/homeassistant/components/openhardwaremonitor/__init__.py b/homeassistant/components/openhardwaremonitor/__init__.py new file mode 100644 index 00000000000..498bb3f11cd --- /dev/null +++ b/homeassistant/components/openhardwaremonitor/__init__.py @@ -0,0 +1 @@ +"""The openhardwaremonitor component.""" diff --git a/homeassistant/components/sensor/openhardwaremonitor.py b/homeassistant/components/openhardwaremonitor/sensor.py similarity index 100% rename from homeassistant/components/sensor/openhardwaremonitor.py rename to homeassistant/components/openhardwaremonitor/sensor.py diff --git a/homeassistant/components/openhome/__init__.py b/homeassistant/components/openhome/__init__.py new file mode 100644 index 00000000000..78294ceb6f4 --- /dev/null +++ b/homeassistant/components/openhome/__init__.py @@ -0,0 +1 @@ +"""The openhome component.""" diff --git a/homeassistant/components/media_player/openhome.py b/homeassistant/components/openhome/media_player.py similarity index 100% rename from homeassistant/components/media_player/openhome.py rename to homeassistant/components/openhome/media_player.py diff --git a/homeassistant/components/opensensemap/__init__.py b/homeassistant/components/opensensemap/__init__.py new file mode 100644 index 00000000000..e03f4133d88 --- /dev/null +++ b/homeassistant/components/opensensemap/__init__.py @@ -0,0 +1 @@ +"""The opensensemap component.""" diff --git a/homeassistant/components/air_quality/opensensemap.py b/homeassistant/components/opensensemap/air_quality.py similarity index 100% rename from homeassistant/components/air_quality/opensensemap.py rename to homeassistant/components/opensensemap/air_quality.py diff --git a/homeassistant/components/opensky/__init__.py b/homeassistant/components/opensky/__init__.py new file mode 100644 index 00000000000..da805999d53 --- /dev/null +++ b/homeassistant/components/opensky/__init__.py @@ -0,0 +1 @@ +"""The opensky component.""" diff --git a/homeassistant/components/sensor/opensky.py b/homeassistant/components/opensky/sensor.py similarity index 100% rename from homeassistant/components/sensor/opensky.py rename to homeassistant/components/opensky/sensor.py diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index acb277c0ef5..1476363c6bd 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -15,7 +15,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyotgw==0.4b2'] +REQUIREMENTS = ['pyotgw==0.4b3'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/opentherm_gw/binary_sensor.py b/homeassistant/components/opentherm_gw/binary_sensor.py index b35998c807b..d0b60a25770 100644 --- a/homeassistant/components/opentherm_gw/binary_sensor.py +++ b/homeassistant/components/opentherm_gw/binary_sensor.py @@ -2,12 +2,12 @@ import logging from homeassistant.components.binary_sensor import ( - BinarySensorDevice, ENTITY_ID_FORMAT) -from homeassistant.components.opentherm_gw import ( - DATA_GW_VARS, DATA_OPENTHERM_GW, SIGNAL_OPENTHERM_GW_UPDATE) + ENTITY_ID_FORMAT, BinarySensorDevice) from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import async_generate_entity_id +from . import DATA_GW_VARS, DATA_OPENTHERM_GW, SIGNAL_OPENTHERM_GW_UPDATE + _LOGGER = logging.getLogger(__name__) DEVICE_CLASS_COLD = 'cold' diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py index 1a7c031638f..60f1901d43e 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -3,14 +3,15 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - STATE_IDLE, STATE_HEAT, STATE_COOL, SUPPORT_TARGET_TEMPERATURE) -from homeassistant.components.opentherm_gw import ( + STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_TARGET_TEMPERATURE) +from homeassistant.const import ( + ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES, PRECISION_TENTHS, + PRECISION_WHOLE, TEMP_CELSIUS) +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ( CONF_FLOOR_TEMP, CONF_PRECISION, DATA_DEVICE, DATA_GW_VARS, DATA_OPENTHERM_GW, SIGNAL_OPENTHERM_GW_UPDATE) -from homeassistant.const import (ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES, - PRECISION_TENTHS, PRECISION_WHOLE, - TEMP_CELSIUS) -from homeassistant.helpers.dispatcher import async_dispatcher_connect _LOGGER = logging.getLogger(__name__) @@ -38,6 +39,7 @@ class OpenThermGateway(ClimateDevice): self.temp_precision = config.get(CONF_PRECISION) self._current_operation = STATE_IDLE self._current_temperature = None + self._new_target_temperature = None self._target_temperature = None self._away_mode_a = None self._away_mode_b = None @@ -62,11 +64,10 @@ class OpenThermGateway(ClimateDevice): else: self._current_operation = STATE_IDLE self._current_temperature = status.get(self._gw_vars.DATA_ROOM_TEMP) - - temp = status.get(self._gw_vars.DATA_ROOM_SETPOINT_OVRD) - if temp is None: - temp = status.get(self._gw_vars.DATA_ROOM_SETPOINT) - self._target_temperature = temp + temp_upd = status.get(self._gw_vars.DATA_ROOM_SETPOINT) + if self._target_temperature != temp_upd: + self._new_target_temperature = None + self._target_temperature = temp_upd # GPIO mode 5: 0 == Away # GPIO mode 6: 1 == Away @@ -137,7 +138,7 @@ class OpenThermGateway(ClimateDevice): @property def target_temperature(self): """Return the temperature we try to reach.""" - return self._target_temperature + return self._new_target_temperature or self._target_temperature @property def target_temperature_step(self): @@ -153,7 +154,9 @@ class OpenThermGateway(ClimateDevice): """Set new target temperature.""" if ATTR_TEMPERATURE in kwargs: temp = float(kwargs[ATTR_TEMPERATURE]) - self._target_temperature = await self._gateway.set_target_temp( + if temp == self.target_temperature: + return + self._new_target_temperature = await self._gateway.set_target_temp( temp) self.async_schedule_update_ha_state() diff --git a/homeassistant/components/opentherm_gw/sensor.py b/homeassistant/components/opentherm_gw/sensor.py index 070f847e5e5..5c64b8ab719 100644 --- a/homeassistant/components/opentherm_gw/sensor.py +++ b/homeassistant/components/opentherm_gw/sensor.py @@ -1,13 +1,13 @@ """Support for OpenTherm Gateway sensors.""" import logging -from homeassistant.components.opentherm_gw import ( - DATA_GW_VARS, DATA_OPENTHERM_GW, SIGNAL_OPENTHERM_GW_UPDATE) from homeassistant.components.sensor import ENTITY_ID_FORMAT from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity, async_generate_entity_id +from . import DATA_GW_VARS, DATA_OPENTHERM_GW, SIGNAL_OPENTHERM_GW_UPDATE + _LOGGER = logging.getLogger(__name__) UNIT_BAR = 'bar' diff --git a/homeassistant/components/openuv/binary_sensor.py b/homeassistant/components/openuv/binary_sensor.py index b790427b228..cfc82a75729 100644 --- a/homeassistant/components/openuv/binary_sensor.py +++ b/homeassistant/components/openuv/binary_sensor.py @@ -2,13 +2,14 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.openuv import ( - BINARY_SENSORS, DATA_OPENUV_CLIENT, DATA_PROTECTION_WINDOW, DOMAIN, - TOPIC_UPDATE, TYPE_PROTECTION_WINDOW, OpenUvEntity) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.dt import as_local, parse_datetime, utcnow +from . import ( + BINARY_SENSORS, DATA_OPENUV_CLIENT, DATA_PROTECTION_WINDOW, DOMAIN, + TOPIC_UPDATE, TYPE_PROTECTION_WINDOW, OpenUvEntity) + _LOGGER = logging.getLogger(__name__) ATTR_PROTECTION_WINDOW_ENDING_TIME = 'end_time' ATTR_PROTECTION_WINDOW_ENDING_UV = 'end_uv' diff --git a/homeassistant/components/openuv/sensor.py b/homeassistant/components/openuv/sensor.py index 489a100a5e5..42780d57b3c 100644 --- a/homeassistant/components/openuv/sensor.py +++ b/homeassistant/components/openuv/sensor.py @@ -1,15 +1,16 @@ """Support for OpenUV sensors.""" import logging -from homeassistant.components.openuv import ( +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.dt import as_local, parse_datetime + +from . import ( DATA_OPENUV_CLIENT, DATA_UV, DOMAIN, SENSORS, TOPIC_UPDATE, TYPE_CURRENT_OZONE_LEVEL, TYPE_CURRENT_UV_INDEX, TYPE_CURRENT_UV_LEVEL, TYPE_MAX_UV_INDEX, TYPE_SAFE_EXPOSURE_TIME_1, TYPE_SAFE_EXPOSURE_TIME_2, TYPE_SAFE_EXPOSURE_TIME_3, TYPE_SAFE_EXPOSURE_TIME_4, TYPE_SAFE_EXPOSURE_TIME_5, TYPE_SAFE_EXPOSURE_TIME_6, OpenUvEntity) -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.util.dt import as_local, parse_datetime _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/openweathermap/__init__.py b/homeassistant/components/openweathermap/__init__.py new file mode 100644 index 00000000000..43cad1520ca --- /dev/null +++ b/homeassistant/components/openweathermap/__init__.py @@ -0,0 +1 @@ +"""The openweathermap component.""" diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/openweathermap/sensor.py similarity index 100% rename from homeassistant/components/sensor/openweathermap.py rename to homeassistant/components/openweathermap/sensor.py diff --git a/homeassistant/components/weather/openweathermap.py b/homeassistant/components/openweathermap/weather.py similarity index 95% rename from homeassistant/components/weather/openweathermap.py rename to homeassistant/components/openweathermap/weather.py index 58016dd3e2c..8a37bc97575 100644 --- a/homeassistant/components/weather/openweathermap.py +++ b/homeassistant/components/openweathermap/weather.py @@ -10,10 +10,10 @@ from homeassistant.components.weather import ( ATTR_FORECAST_WIND_SPEED, PLATFORM_SCHEMA, WeatherEntity) from homeassistant.const import ( CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE, CONF_NAME, - STATE_UNKNOWN, TEMP_CELSIUS) + PRESSURE_HPA, PRESSURE_INHG, STATE_UNKNOWN, TEMP_CELSIUS) import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle - +from homeassistant.util.pressure import convert as convert_pressure REQUIREMENTS = ['pyowm==2.10.0'] _LOGGER = logging.getLogger(__name__) @@ -114,7 +114,11 @@ class OpenWeatherMapWeather(WeatherEntity): @property def pressure(self): """Return the pressure.""" - return self.data.get_pressure().get('press') + pressure = self.data.get_pressure().get('press') + if self.hass.config.units.name == 'imperial': + return round( + convert_pressure(pressure, PRESSURE_HPA, PRESSURE_INHG), 2) + return pressure @property def humidity(self): diff --git a/homeassistant/components/opple/__init__.py b/homeassistant/components/opple/__init__.py new file mode 100644 index 00000000000..41ef2b0fdd8 --- /dev/null +++ b/homeassistant/components/opple/__init__.py @@ -0,0 +1 @@ +"""The opple component.""" diff --git a/homeassistant/components/light/opple.py b/homeassistant/components/opple/light.py similarity index 100% rename from homeassistant/components/light/opple.py rename to homeassistant/components/opple/light.py diff --git a/homeassistant/components/orvibo/__init__.py b/homeassistant/components/orvibo/__init__.py new file mode 100644 index 00000000000..81cddecb672 --- /dev/null +++ b/homeassistant/components/orvibo/__init__.py @@ -0,0 +1 @@ +"""The orvibo component.""" diff --git a/homeassistant/components/switch/orvibo.py b/homeassistant/components/orvibo/switch.py similarity index 100% rename from homeassistant/components/switch/orvibo.py rename to homeassistant/components/orvibo/switch.py diff --git a/homeassistant/components/osramlightify/__init__.py b/homeassistant/components/osramlightify/__init__.py new file mode 100644 index 00000000000..582d3a5e3fb --- /dev/null +++ b/homeassistant/components/osramlightify/__init__.py @@ -0,0 +1 @@ +"""The osramlightify component.""" diff --git a/homeassistant/components/light/osramlightify.py b/homeassistant/components/osramlightify/light.py similarity index 100% rename from homeassistant/components/light/osramlightify.py rename to homeassistant/components/osramlightify/light.py diff --git a/homeassistant/components/otp/__init__.py b/homeassistant/components/otp/__init__.py new file mode 100644 index 00000000000..bf80d41a92d --- /dev/null +++ b/homeassistant/components/otp/__init__.py @@ -0,0 +1 @@ +"""The otp component.""" diff --git a/homeassistant/components/sensor/otp.py b/homeassistant/components/otp/sensor.py similarity index 100% rename from homeassistant/components/sensor/otp.py rename to homeassistant/components/otp/sensor.py diff --git a/homeassistant/components/owlet/binary_sensor.py b/homeassistant/components/owlet/binary_sensor.py index cb66278150a..bcdd0fec11f 100644 --- a/homeassistant/components/owlet/binary_sensor.py +++ b/homeassistant/components/owlet/binary_sensor.py @@ -2,9 +2,9 @@ from datetime import timedelta from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.owlet import DOMAIN as OWLET_DOMAIN from homeassistant.util import dt as dt_util +from . import DOMAIN as OWLET_DOMAIN from .const import SENSOR_BASE_STATION, SENSOR_MOVEMENT SCAN_INTERVAL = timedelta(seconds=120) diff --git a/homeassistant/components/owlet/sensor.py b/homeassistant/components/owlet/sensor.py index b91cc387718..849e0858ade 100644 --- a/homeassistant/components/owlet/sensor.py +++ b/homeassistant/components/owlet/sensor.py @@ -1,10 +1,10 @@ """Support for Owlet sensors.""" from datetime import timedelta -from homeassistant.components.owlet import DOMAIN as OWLET_DOMAIN from homeassistant.helpers.entity import Entity from homeassistant.util import dt as dt_util +from . import DOMAIN as OWLET_DOMAIN from .const import SENSOR_HEART_RATE, SENSOR_OXYGEN_LEVEL SCAN_INTERVAL = timedelta(seconds=120) diff --git a/homeassistant/components/owntracks/.translations/bg.json b/homeassistant/components/owntracks/.translations/bg.json new file mode 100644 index 00000000000..1989e1a0703 --- /dev/null +++ b/homeassistant/components/owntracks/.translations/bg.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + }, + "create_entry": { + "default": "\n\n \u0412 Android \u043e\u0442\u0432\u043e\u0440\u0435\u0442\u0435 [\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e OwnTracks]({android_url}), \u043f\u0440\u0435\u043c\u0438\u043d\u0435\u0442\u0435 \u043a\u044a\u043c Preferences - > Connection. \u041f\u0440\u043e\u043c\u0435\u043d\u0435\u0442\u0435 \u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438: \n - Mode: Private HTTP\n - Host: {webhook_url} \n - Identification: \n - Username: ` ` \n - Device ID: ` ` \n\n \u0412 iOS \u043e\u0442\u0432\u043e\u0440\u0435\u0442\u0435 [\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e OwnTracks]({ios_url}), \u0434\u043e\u043a\u043e\u0441\u043d\u0435\u0442\u0435 (i) \u0438\u043a\u043e\u043d\u0430\u0442\u0430 \u0432 \u0433\u043e\u0440\u043d\u0438\u044f \u043b\u044f\u0432 \u044a\u0433\u044a\u043b - > Settings. \u041f\u0440\u043e\u043c\u0435\u043d\u0435\u0442\u0435 \u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438: \n - Mode: HTTP \n - URL: {webhook_url} \n - Turn on authentication \n - UserID: ` ` \n\n {secret} \n \n \u0412\u0438\u0436\u0442\u0435 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0442\u0430]({docs_url}) \u0437\u0430 \u043f\u043e\u0432\u0435\u0447\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f." + }, + "step": { + "user": { + "description": "\u0421\u0438\u0433\u0443\u0440\u043d\u0438 \u043b\u0438 \u0441\u0442\u0435, \u0447\u0435 \u0438\u0441\u043a\u0430\u0442\u0435 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 OwnTracks?", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043d\u0430 OwnTracks" + } + }, + "title": "OwnTracks" + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/.translations/fr.json b/homeassistant/components/owntracks/.translations/fr.json new file mode 100644 index 00000000000..46a0f2f2921 --- /dev/null +++ b/homeassistant/components/owntracks/.translations/fr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + }, + "step": { + "user": { + "description": "\u00cates-vous s\u00fbr de vouloir configurer OwnTracks?", + "title": "Configurer OwnTracks" + } + }, + "title": "OwnTracks" + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/.translations/pl.json b/homeassistant/components/owntracks/.translations/pl.json index 85a39095f87..fd6cba18237 100644 --- a/homeassistant/components/owntracks/.translations/pl.json +++ b/homeassistant/components/owntracks/.translations/pl.json @@ -4,7 +4,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "\n\nNa Androidzie, otw\u00f3rz [aplikacj\u0119 OwnTracks]({android_url}), id\u017a do: ustawienia -> po\u0142aczenia. Zmie\u0144 nast\u0119puj\u0105ce ustawienia:\n - Tryb: Private HTTP\n - Host: {webhook_url}\n - Identyfikacja:\n - Nazwa u\u017cytkownika: ``\n - ID urz\u0105dzenia: ``\n\nNa iOS, otw\u00f3rz [aplikacj\u0119 OwnTracks]({ios_url}), naci\u015bnij ikon\u0119 (i) w lewym g\u00f3rnym rogu -> ustawienia. Zmie\u0144 nast\u0119puj\u0105ce ustawienia:\n - Tryb: HTTP\n - URL: {webhook_url}\n - W\u0142\u0105cz uwierzytelnianie\n - ID u\u017cytkownika: ``\n\n{secret}\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y." + "default": "\n\nNa Androidzie, otw\u00f3rz [aplikacj\u0119 OwnTracks]({android_url}), id\u017a do: ustawienia -> po\u0142aczenia. Zmie\u0144 nast\u0119puj\u0105ce ustawienia:\n - Tryb: Private HTTP\n - Host: {webhook_url}\n - Identyfikacja:\n - Nazwa u\u017cytkownika: ``\n - ID urz\u0105dzenia: ``\n\nNa iOS, otw\u00f3rz [aplikacj\u0119 OwnTracks]({ios_url}), naci\u015bnij ikon\u0119 (i) w lewym g\u00f3rnym rogu -> ustawienia. Zmie\u0144 nast\u0119puj\u0105ce ustawienia:\n - Tryb: HTTP\n - URL: {webhook_url}\n - W\u0142\u0105cz uwierzytelnianie\n - ID u\u017cytkownika: ``\n\n{secret}\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/owntracks/device_tracker.py b/homeassistant/components/owntracks/device_tracker.py index be8698a47b1..f1214b62b0e 100644 --- a/homeassistant/components/owntracks/device_tracker.py +++ b/homeassistant/components/owntracks/device_tracker.py @@ -9,12 +9,11 @@ import logging from homeassistant.components import zone as zone_comp from homeassistant.components.device_tracker import ( - ATTR_SOURCE_TYPE, SOURCE_TYPE_BLUETOOTH_LE, SOURCE_TYPE_GPS -) -from homeassistant.components.owntracks import DOMAIN as OT_DOMAIN + ATTR_SOURCE_TYPE, SOURCE_TYPE_BLUETOOTH_LE, SOURCE_TYPE_GPS) from homeassistant.const import STATE_HOME -from homeassistant.util import slugify, decorator +from homeassistant.util import decorator, slugify +from . import DOMAIN as OT_DOMAIN DEPENDENCIES = ['owntracks'] diff --git a/homeassistant/components/panasonic_bluray/__init__.py b/homeassistant/components/panasonic_bluray/__init__.py new file mode 100644 index 00000000000..a39b070b3c5 --- /dev/null +++ b/homeassistant/components/panasonic_bluray/__init__.py @@ -0,0 +1 @@ +"""The panasonic_bluray component.""" diff --git a/homeassistant/components/media_player/panasonic_bluray.py b/homeassistant/components/panasonic_bluray/media_player.py similarity index 100% rename from homeassistant/components/media_player/panasonic_bluray.py rename to homeassistant/components/panasonic_bluray/media_player.py diff --git a/homeassistant/components/panasonic_viera/__init__.py b/homeassistant/components/panasonic_viera/__init__.py new file mode 100644 index 00000000000..bb63c98079e --- /dev/null +++ b/homeassistant/components/panasonic_viera/__init__.py @@ -0,0 +1 @@ +"""The panasonic_viera component.""" diff --git a/homeassistant/components/media_player/panasonic_viera.py b/homeassistant/components/panasonic_viera/media_player.py similarity index 91% rename from homeassistant/components/media_player/panasonic_viera.py rename to homeassistant/components/panasonic_viera/media_player.py index e6546f7c1e2..f1ac0cd90d4 100644 --- a/homeassistant/components/media_player/panasonic_viera.py +++ b/homeassistant/components/panasonic_viera/media_player.py @@ -19,12 +19,15 @@ from homeassistant.const import ( CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['panasonic_viera==0.3.1', 'wakeonlan==1.1.6'] +REQUIREMENTS = ['panasonic_viera==0.3.2', 'wakeonlan==1.1.6'] _LOGGER = logging.getLogger(__name__) +CONF_APP_POWER = 'app_power' + DEFAULT_NAME = 'Panasonic Viera TV' DEFAULT_PORT = 55000 +DEFAULT_APP_POWER = False SUPPORT_VIERATV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ @@ -37,6 +40,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_MAC): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_APP_POWER, default=DEFAULT_APP_POWER): cv.boolean, }) @@ -47,6 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): mac = config.get(CONF_MAC) name = config.get(CONF_NAME) port = config.get(CONF_PORT) + app_power = config.get(CONF_APP_POWER) if discovery_info: _LOGGER.debug('%s', discovery_info) @@ -59,20 +64,21 @@ def setup_platform(hass, config, add_entities, discovery_info=None): else: uuid = None remote = RemoteControl(host, port) - add_entities([PanasonicVieraTVDevice(mac, name, remote, host, uuid)]) + add_entities([PanasonicVieraTVDevice( + mac, name, remote, host, app_power, uuid)]) return True host = config.get(CONF_HOST) remote = RemoteControl(host, port) - add_entities([PanasonicVieraTVDevice(mac, name, remote, host)]) + add_entities([PanasonicVieraTVDevice(mac, name, remote, host, app_power)]) return True class PanasonicVieraTVDevice(MediaPlayerDevice): """Representation of a Panasonic Viera TV.""" - def __init__(self, mac, name, remote, host, uuid=None): + def __init__(self, mac, name, remote, host, app_power, uuid=None): """Initialize the Panasonic device.""" import wakeonlan # Save a reference to the imported class @@ -86,6 +92,7 @@ class PanasonicVieraTVDevice(MediaPlayerDevice): self._remote = remote self._host = host self._volume = 0 + self._app_power = app_power @property def unique_id(self) -> str: @@ -134,7 +141,7 @@ class PanasonicVieraTVDevice(MediaPlayerDevice): @property def supported_features(self): """Flag media player features that are supported.""" - if self._mac: + if self._mac or self._app_power: return SUPPORT_VIERATV | SUPPORT_TURN_ON return SUPPORT_VIERATV @@ -143,6 +150,9 @@ class PanasonicVieraTVDevice(MediaPlayerDevice): if self._mac: self._wol.send_magic_packet(self._mac, ip_address=self._host) self._state = STATE_ON + elif self._app_power: + self._remote.turn_on() + self._state = STATE_ON def turn_off(self): """Turn off media player.""" diff --git a/homeassistant/components/pandora/__init__.py b/homeassistant/components/pandora/__init__.py new file mode 100644 index 00000000000..9664730bdab --- /dev/null +++ b/homeassistant/components/pandora/__init__.py @@ -0,0 +1 @@ +"""The pandora component.""" diff --git a/homeassistant/components/media_player/pandora.py b/homeassistant/components/pandora/media_player.py similarity index 100% rename from homeassistant/components/media_player/pandora.py rename to homeassistant/components/pandora/media_player.py diff --git a/homeassistant/components/panel_custom/__init__.py b/homeassistant/components/panel_custom/__init__.py index 2fce5d9857c..7fe2191f4c4 100644 --- a/homeassistant/components/panel_custom/__init__.py +++ b/homeassistant/components/panel_custom/__init__.py @@ -23,6 +23,7 @@ CONF_MODULE_URL = 'module_url' CONF_EMBED_IFRAME = 'embed_iframe' CONF_TRUST_EXTERNAL_SCRIPT = 'trust_external_script' CONF_URL_EXCLUSIVE_GROUP = 'url_exclusive_group' +CONF_REQUIRE_ADMIN = 'require_admin' MSG_URL_CONFLICT = \ 'Pass in only one of webcomponent_path, module_url or js_url' @@ -52,6 +53,7 @@ CONFIG_SCHEMA = vol.Schema({ default=DEFAULT_EMBED_IFRAME): cv.boolean, vol.Optional(CONF_TRUST_EXTERNAL_SCRIPT, default=DEFAULT_TRUST_EXTERNAL): cv.boolean, + vol.Optional(CONF_REQUIRE_ADMIN, default=False): cv.boolean, })]) }, extra=vol.ALLOW_EXTRA) @@ -77,7 +79,9 @@ async def async_register_panel( # Should user be asked for confirmation when loading external source trust_external=DEFAULT_TRUST_EXTERNAL, # Configuration to be passed to the panel - config=None): + config=None, + # If your panel should only be shown to admin users + require_admin=False): """Register a new custom panel.""" if js_url is None and html_url is None and module_url is None: raise ValueError('Either js_url, module_url or html_url is required.') @@ -115,7 +119,8 @@ async def async_register_panel( sidebar_title=sidebar_title, sidebar_icon=sidebar_icon, frontend_url_path=frontend_url_path, - config=config + config=config, + require_admin=require_admin, ) @@ -134,6 +139,7 @@ async def async_setup(hass, config): 'config': panel.get(CONF_CONFIG), 'trust_external': panel[CONF_TRUST_EXTERNAL_SCRIPT], 'embed_iframe': panel[CONF_EMBED_IFRAME], + 'require_admin': panel[CONF_REQUIRE_ADMIN], } panel_path = panel.get(CONF_WEBCOMPONENT_PATH) diff --git a/homeassistant/components/panel_iframe/__init__.py b/homeassistant/components/panel_iframe/__init__.py index b82f9fa9789..9319dfcc6ad 100644 --- a/homeassistant/components/panel_iframe/__init__.py +++ b/homeassistant/components/panel_iframe/__init__.py @@ -12,6 +12,7 @@ CONF_TITLE = 'title' CONF_RELATIVE_URL_ERROR_MSG = "Invalid relative URL. Absolute path required." CONF_RELATIVE_URL_REGEX = r'\A/' +CONF_REQUIRE_ADMIN = 'require_admin' CONFIG_SCHEMA = vol.Schema({ DOMAIN: cv.schema_with_slug_keys( @@ -19,6 +20,7 @@ CONFIG_SCHEMA = vol.Schema({ # pylint: disable=no-value-for-parameter vol.Optional(CONF_TITLE): cv.string, vol.Optional(CONF_ICON): cv.icon, + vol.Optional(CONF_REQUIRE_ADMIN, default=False): cv.boolean, vol.Required(CONF_URL): vol.Any( vol.Match( CONF_RELATIVE_URL_REGEX, @@ -34,6 +36,7 @@ async def async_setup(hass, config): for url_path, info in config[DOMAIN].items(): await hass.components.frontend.async_register_built_in_panel( 'iframe', info.get(CONF_TITLE), info.get(CONF_ICON), - url_path, {'url': info[CONF_URL]}) + url_path, {'url': info[CONF_URL]}, + require_admin=info[CONF_REQUIRE_ADMIN]) return True diff --git a/homeassistant/components/pencom/__init__.py b/homeassistant/components/pencom/__init__.py new file mode 100644 index 00000000000..5e53d8f59ab --- /dev/null +++ b/homeassistant/components/pencom/__init__.py @@ -0,0 +1 @@ +"""The pencom component.""" diff --git a/homeassistant/components/switch/pencom.py b/homeassistant/components/pencom/switch.py similarity index 100% rename from homeassistant/components/switch/pencom.py rename to homeassistant/components/pencom/switch.py diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py new file mode 100644 index 00000000000..4b011c9f207 --- /dev/null +++ b/homeassistant/components/philips_js/__init__.py @@ -0,0 +1 @@ +"""The philips_js component.""" diff --git a/homeassistant/components/media_player/philips_js.py b/homeassistant/components/philips_js/media_player.py similarity index 100% rename from homeassistant/components/media_player/philips_js.py rename to homeassistant/components/philips_js/media_player.py diff --git a/homeassistant/components/pi_hole/__init__.py b/homeassistant/components/pi_hole/__init__.py new file mode 100644 index 00000000000..432e0f3fa11 --- /dev/null +++ b/homeassistant/components/pi_hole/__init__.py @@ -0,0 +1 @@ +"""The pi_hole component.""" diff --git a/homeassistant/components/sensor/pi_hole.py b/homeassistant/components/pi_hole/sensor.py similarity index 100% rename from homeassistant/components/sensor/pi_hole.py rename to homeassistant/components/pi_hole/sensor.py diff --git a/homeassistant/components/picotts/__init__.py b/homeassistant/components/picotts/__init__.py new file mode 100644 index 00000000000..7ffc80db2f9 --- /dev/null +++ b/homeassistant/components/picotts/__init__.py @@ -0,0 +1 @@ +"""Support for pico integration.""" diff --git a/homeassistant/components/tts/picotts.py b/homeassistant/components/picotts/tts.py similarity index 95% rename from homeassistant/components/tts/picotts.py rename to homeassistant/components/picotts/tts.py index 59c698303ad..c164e7fb85d 100644 --- a/homeassistant/components/tts/picotts.py +++ b/homeassistant/components/picotts/tts.py @@ -4,14 +4,15 @@ Support for the Pico TTS speech service. For more details about this component, please refer to the documentation at https://home-assistant.io/components/tts.picotts/ """ +import logging import os -import tempfile import shutil import subprocess -import logging +import tempfile + import voluptuous as vol -from homeassistant.components.tts import Provider, PLATFORM_SCHEMA, CONF_LANG +from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/piglow/__init__.py b/homeassistant/components/piglow/__init__.py new file mode 100644 index 00000000000..e6d4bbd3ec2 --- /dev/null +++ b/homeassistant/components/piglow/__init__.py @@ -0,0 +1 @@ +"""The piglow component.""" diff --git a/homeassistant/components/light/piglow.py b/homeassistant/components/piglow/light.py similarity index 100% rename from homeassistant/components/light/piglow.py rename to homeassistant/components/piglow/light.py diff --git a/homeassistant/components/ping/__init__.py b/homeassistant/components/ping/__init__.py new file mode 100644 index 00000000000..e55f13dc717 --- /dev/null +++ b/homeassistant/components/ping/__init__.py @@ -0,0 +1 @@ +"""The ping component.""" diff --git a/homeassistant/components/binary_sensor/ping.py b/homeassistant/components/ping/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/ping.py rename to homeassistant/components/ping/binary_sensor.py diff --git a/homeassistant/components/device_tracker/ping.py b/homeassistant/components/ping/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/ping.py rename to homeassistant/components/ping/device_tracker.py diff --git a/homeassistant/components/pioneer/__init__.py b/homeassistant/components/pioneer/__init__.py new file mode 100644 index 00000000000..331b578dccf --- /dev/null +++ b/homeassistant/components/pioneer/__init__.py @@ -0,0 +1 @@ +"""The pioneer component.""" diff --git a/homeassistant/components/media_player/pioneer.py b/homeassistant/components/pioneer/media_player.py similarity index 100% rename from homeassistant/components/media_player/pioneer.py rename to homeassistant/components/pioneer/media_player.py diff --git a/homeassistant/components/pjlink/__init__.py b/homeassistant/components/pjlink/__init__.py new file mode 100644 index 00000000000..ab4d7fd377d --- /dev/null +++ b/homeassistant/components/pjlink/__init__.py @@ -0,0 +1 @@ +"""The pjlink component.""" diff --git a/homeassistant/components/media_player/pjlink.py b/homeassistant/components/pjlink/media_player.py similarity index 100% rename from homeassistant/components/media_player/pjlink.py rename to homeassistant/components/pjlink/media_player.py diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py new file mode 100644 index 00000000000..6e4e02026ab --- /dev/null +++ b/homeassistant/components/plex/__init__.py @@ -0,0 +1 @@ +"""The plex component.""" diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/plex/media_player.py similarity index 94% rename from homeassistant/components/media_player/plex.py rename to homeassistant/components/plex/media_player.py index 35000fa35c3..a68a2faade8 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/plex/media_player.py @@ -170,21 +170,31 @@ def setup_plexserver( config, device, None, plex_sessions, update_devices, update_sessions) plex_clients[device.machineIdentifier] = new_client + _LOGGER.debug("New device: %s", device.machineIdentifier) new_plex_clients.append(new_client) else: + _LOGGER.debug("Refreshing device: %s", + device.machineIdentifier) plex_clients[device.machineIdentifier].refresh(device, None) # add devices with a session and no client (ex. PlexConnect Apple TV's) if config.get(CONF_INCLUDE_NON_CLIENTS): for machine_identifier, (session, player) in plex_sessions.items(): + if machine_identifier in available_client_ids: + # Avoid using session if already added as a device. + _LOGGER.debug("Skipping session, device exists: %s", + machine_identifier) + continue if (machine_identifier not in plex_clients and machine_identifier is not None): new_client = PlexClient( config, player, session, plex_sessions, update_devices, update_sessions) plex_clients[machine_identifier] = new_client + _LOGGER.debug("New session: %s", machine_identifier) new_plex_clients.append(new_client) else: + _LOGGER.debug("Refreshing session: %s", machine_identifier) plex_clients[machine_identifier].refresh(None, session) clients_to_remove = [] @@ -312,6 +322,7 @@ class PlexClient(MediaPlayerDevice): self._media_image_url = None self._media_title = None self._media_position = None + self._media_position_updated_at = None # Music self._media_album_artist = None self._media_album_name = None @@ -351,7 +362,6 @@ class PlexClient(MediaPlayerDevice): self._media_duration = None self._media_image_url = None self._media_title = None - self._media_position = None # Music self._media_album_artist = None self._media_album_name = None @@ -407,7 +417,21 @@ class PlexClient(MediaPlayerDevice): self._make = self._player.device else: self._is_player_available = False - self._media_position = self._session.viewOffset + + # Calculate throttled position for proper progress display. + position = int(self._session.viewOffset / 1000) + now = dt_util.utcnow() + if self._media_position is not None: + pos_diff = (position - self._media_position) + time_diff = now - self._media_position_updated_at + if (pos_diff != 0 and + abs(time_diff.total_seconds() - pos_diff) > 5): + self._media_position_updated_at = now + self._media_position = position + else: + self._media_position_updated_at = now + self._media_position = position + self._media_content_id = self._session.ratingKey self._media_content_rating = getattr( self._session, 'contentRating', None) @@ -416,7 +440,7 @@ class PlexClient(MediaPlayerDevice): if self._is_player_active and self._session is not None: self._session_type = self._session.type - self._media_duration = self._session.duration + self._media_duration = int(self._session.duration / 1000) # title (movie name, tv episode name, music song name) self._media_title = self._session.title # media type @@ -611,6 +635,16 @@ class PlexClient(MediaPlayerDevice): """Return the duration of current playing media in seconds.""" return self._media_duration + @property + def media_position(self): + """Return the duration of current playing media in seconds.""" + return self._media_position + + @property + def media_position_updated_at(self): + """When was the position of the current playing media valid.""" + return self._media_position_updated_at + @property def media_image_url(self): """Return the image URL of current playing media.""" diff --git a/homeassistant/components/sensor/plex.py b/homeassistant/components/plex/sensor.py similarity index 89% rename from homeassistant/components/sensor/plex.py rename to homeassistant/components/plex/sensor.py index 46766d75010..eaf73ceb566 100644 --- a/homeassistant/components/sensor/plex.py +++ b/homeassistant/components/plex/sensor.py @@ -11,7 +11,7 @@ import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.const import ( CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_HOST, CONF_PORT, CONF_TOKEN, - CONF_SSL) + CONF_SSL, CONF_VERIFY_SSL) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -26,6 +26,7 @@ DEFAULT_HOST = 'localhost' DEFAULT_NAME = 'Plex' DEFAULT_PORT = 32400 DEFAULT_SSL = False +DEFAULT_VERIFY_SSL = True MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) @@ -38,6 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_SERVER): cv.string, vol.Optional(CONF_USERNAME): cv.string, vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, }) @@ -59,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: add_entities([PlexSensor( name, plex_url, plex_user, plex_password, plex_server, - plex_token)], True) + plex_token, config.get(CONF_VERIFY_SSL))], True) except (plexapi.exceptions.BadRequest, plexapi.exceptions.Unauthorized, plexapi.exceptions.NotFound) as error: _LOGGER.error(error) @@ -70,23 +72,30 @@ class PlexSensor(Entity): """Representation of a Plex now playing sensor.""" def __init__(self, name, plex_url, plex_user, plex_password, - plex_server, plex_token): + plex_server, plex_token, verify_ssl): """Initialize the sensor.""" from plexapi.myplex import MyPlexAccount from plexapi.server import PlexServer + from requests import Session self._name = name self._state = 0 self._now_playing = [] + cert_session = None + if not verify_ssl: + _LOGGER.info("Ignoring SSL verification") + cert_session = Session() + cert_session.verify = False + if plex_token: - self._server = PlexServer(plex_url, plex_token) + self._server = PlexServer(plex_url, plex_token, cert_session) elif plex_user and plex_password: user = MyPlexAccount(plex_user, plex_password) server = plex_server if plex_server else user.resources()[0].name self._server = user.resource(server).connect() else: - self._server = PlexServer(plex_url) + self._server = PlexServer(plex_url, None, cert_session) @property def name(self): diff --git a/homeassistant/components/plum_lightpad/light.py b/homeassistant/components/plum_lightpad/light.py index 43cceaa671f..233539560f4 100644 --- a/homeassistant/components/plum_lightpad/light.py +++ b/homeassistant/components/plum_lightpad/light.py @@ -1,9 +1,10 @@ """Support for Plum Lightpad lights.""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, Light) -from homeassistant.components.plum_lightpad import PLUM_DATA import homeassistant.util.color as color_util +from . import PLUM_DATA + DEPENDENCIES = ['plum_lightpad'] diff --git a/homeassistant/components/pocketcasts/__init__.py b/homeassistant/components/pocketcasts/__init__.py new file mode 100644 index 00000000000..0bca6c6f893 --- /dev/null +++ b/homeassistant/components/pocketcasts/__init__.py @@ -0,0 +1 @@ +"""The pocketcasts component.""" diff --git a/homeassistant/components/sensor/pocketcasts.py b/homeassistant/components/pocketcasts/sensor.py similarity index 100% rename from homeassistant/components/sensor/pocketcasts.py rename to homeassistant/components/pocketcasts/sensor.py diff --git a/homeassistant/components/point/.translations/fr.json b/homeassistant/components/point/.translations/fr.json new file mode 100644 index 00000000000..ba1b1e27668 --- /dev/null +++ b/homeassistant/components/point/.translations/fr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_setup": "Vous ne pouvez configurer qu'un compte Point.", + "authorize_url_fail": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation.", + "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9." + }, + "step": { + "user": { + "data": { + "flow_impl": "Fournisseur" + }, + "description": "Choisissez via quel fournisseur d'authentification vous souhaitez vous authentifier avec Point.", + "title": "Fournisseur d'authentification" + } + }, + "title": "Minut Point" + } +} \ No newline at end of file diff --git a/homeassistant/components/point/.translations/ru.json b/homeassistant/components/point/.translations/ru.json index 60c1d62ab91..d2f3f90cb77 100644 --- a/homeassistant/components/point/.translations/ru.json +++ b/homeassistant/components/point/.translations/ru.json @@ -4,7 +4,7 @@ "already_setup": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", "authorize_url_fail": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", - "external_setup": "\u0422\u043e\u0447\u043a\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u0438\u0437 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430.", + "external_setup": "Point \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0438\u0437 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430.", "no_flows": "\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Point \u043f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c, \u043a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e. [\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/point/)." }, "create_entry": { diff --git a/homeassistant/components/point/.translations/zh-Hans.json b/homeassistant/components/point/.translations/zh-Hans.json index 16d1bddbaf7..e171aedf1ce 100644 --- a/homeassistant/components/point/.translations/zh-Hans.json +++ b/homeassistant/components/point/.translations/zh-Hans.json @@ -1,8 +1,14 @@ { "config": { "abort": { + "already_setup": "\u60a8\u53ea\u80fd\u914d\u7f6e\u4e00\u4e2a Point \u5e10\u6237\u3002", "authorize_url_fail": "\u751f\u6210\u6388\u6743\u7f51\u5740\u65f6\u53d1\u751f\u672a\u77e5\u9519\u8bef\u3002", - "authorize_url_timeout": "\u751f\u6210\u6388\u6743\u7f51\u5740\u8d85\u65f6\u3002" + "authorize_url_timeout": "\u751f\u6210\u6388\u6743\u7f51\u5740\u8d85\u65f6\u3002", + "external_setup": "Point\u914d\u7f6e\u6210\u529f\u3002", + "no_flows": "\u60a8\u9700\u8981\u5148\u914d\u7f6e Point\uff0c\u7136\u540e\u624d\u80fd\u5bf9\u5176\u8fdb\u884c\u6388\u6743\u3002 [\u8bf7\u9605\u8bfb\u8bf4\u660e](https://www.home-assistant.io/components/point/)\u3002" + }, + "create_entry": { + "default": "\u4f7f\u7528 Minut \u4e3a\u60a8\u7684 Point \u8bbe\u5907\u8fdb\u884c\u8eab\u4efd\u9a8c\u8bc1\u6210\u529f" }, "error": { "follow_link": "\u8bf7\u5728\u70b9\u51fb\u63d0\u4ea4\u524d\u6309\u7167\u94fe\u63a5\u8fdb\u884c\u8eab\u4efd\u9a8c\u8bc1", diff --git a/homeassistant/components/point/alarm_control_panel.py b/homeassistant/components/point/alarm_control_panel.py index a50dffe42b9..4fd5ffea641 100644 --- a/homeassistant/components/point/alarm_control_panel.py +++ b/homeassistant/components/point/alarm_control_panel.py @@ -3,13 +3,13 @@ import logging from homeassistant.components.alarm_control_panel import ( DOMAIN, AlarmControlPanel) -from homeassistant.components.point.const import ( - DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW, SIGNAL_WEBHOOK) from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from .const import DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW, SIGNAL_WEBHOOK + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/point/binary_sensor.py b/homeassistant/components/point/binary_sensor.py index c29ce421682..2276d4e2fb5 100644 --- a/homeassistant/components/point/binary_sensor.py +++ b/homeassistant/components/point/binary_sensor.py @@ -2,12 +2,12 @@ import logging from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice -from homeassistant.components.point import MinutPointEntity -from homeassistant.components.point.const import ( - DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW, SIGNAL_WEBHOOK) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from . import MinutPointEntity +from .const import DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW, SIGNAL_WEBHOOK + _LOGGER = logging.getLogger(__name__) EVENTS = { diff --git a/homeassistant/components/point/sensor.py b/homeassistant/components/point/sensor.py index 90b83ba42e3..46399c82af4 100644 --- a/homeassistant/components/point/sensor.py +++ b/homeassistant/components/point/sensor.py @@ -1,9 +1,6 @@ """Support for Minut Point sensors.""" import logging -from homeassistant.components.point import MinutPointEntity -from homeassistant.components.point.const import ( - DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW) from homeassistant.components.sensor import DOMAIN from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, @@ -11,6 +8,9 @@ from homeassistant.const import ( from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.dt import parse_datetime +from . import MinutPointEntity +from .const import DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW + _LOGGER = logging.getLogger(__name__) DEVICE_CLASS_SOUND = 'sound_level' diff --git a/homeassistant/components/pollen/__init__.py b/homeassistant/components/pollen/__init__.py new file mode 100644 index 00000000000..566297ecb14 --- /dev/null +++ b/homeassistant/components/pollen/__init__.py @@ -0,0 +1 @@ +"""The pollen component.""" diff --git a/homeassistant/components/sensor/pollen.py b/homeassistant/components/pollen/sensor.py similarity index 99% rename from homeassistant/components/sensor/pollen.py rename to homeassistant/components/pollen/sensor.py index 08fe45a22a6..3fc4d1fce3d 100644 --- a/homeassistant/components/sensor/pollen.py +++ b/homeassistant/components/pollen/sensor.py @@ -239,8 +239,8 @@ class ForecastSensor(BaseSensor): if self._kind == TYPE_ALLERGY_FORECAST: outlook = self.pollen.data[TYPE_ALLERGY_OUTLOOK] - self._attrs[ATTR_OUTLOOK] = outlook['Outlook'] - self._attrs[ATTR_SEASON] = outlook['Season'] + self._attrs[ATTR_OUTLOOK] = outlook.get('Outlook') + self._attrs[ATTR_SEASON] = outlook.get('Season') self._state = average diff --git a/homeassistant/components/postnl/__init__.py b/homeassistant/components/postnl/__init__.py new file mode 100644 index 00000000000..96c3212c7a1 --- /dev/null +++ b/homeassistant/components/postnl/__init__.py @@ -0,0 +1 @@ +"""The postnl component.""" diff --git a/homeassistant/components/sensor/postnl.py b/homeassistant/components/postnl/sensor.py similarity index 100% rename from homeassistant/components/sensor/postnl.py rename to homeassistant/components/postnl/sensor.py diff --git a/homeassistant/components/prezzibenzina/__init__.py b/homeassistant/components/prezzibenzina/__init__.py new file mode 100644 index 00000000000..af68e845bbc --- /dev/null +++ b/homeassistant/components/prezzibenzina/__init__.py @@ -0,0 +1 @@ +"""The prezzibenzina component.""" diff --git a/homeassistant/components/sensor/prezzibenzina.py b/homeassistant/components/prezzibenzina/sensor.py similarity index 100% rename from homeassistant/components/sensor/prezzibenzina.py rename to homeassistant/components/prezzibenzina/sensor.py diff --git a/homeassistant/components/proliphix/__init__.py b/homeassistant/components/proliphix/__init__.py new file mode 100644 index 00000000000..0611e88211b --- /dev/null +++ b/homeassistant/components/proliphix/__init__.py @@ -0,0 +1 @@ +"""The proliphix component.""" diff --git a/homeassistant/components/climate/proliphix.py b/homeassistant/components/proliphix/climate.py similarity index 100% rename from homeassistant/components/climate/proliphix.py rename to homeassistant/components/proliphix/climate.py diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index 9053a872134..de0de8ae162 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -103,6 +103,16 @@ class PrometheusMetrics: full_metric_name, documentation, labels) return self._metrics[metric] + @staticmethod + def state_as_number(state): + """Return a state casted to a float.""" + try: + value = state_helper.state_as_number(state) + except ValueError: + _LOGGER.warning("Could not convert %s to float", state) + value = 0 + return value + @staticmethod def _labels(state): return { @@ -130,7 +140,7 @@ class PrometheusMetrics: self.prometheus_client.Gauge, 'State of the binary sensor (0/1)', ) - value = state_helper.state_as_number(state) + value = self.state_as_number(state) metric.labels(**self._labels(state)).set(value) def _handle_input_boolean(self, state): @@ -139,7 +149,7 @@ class PrometheusMetrics: self.prometheus_client.Gauge, 'State of the input boolean (0/1)', ) - value = state_helper.state_as_number(state) + value = self.state_as_number(state) metric.labels(**self._labels(state)).set(value) def _handle_device_tracker(self, state): @@ -148,7 +158,7 @@ class PrometheusMetrics: self.prometheus_client.Gauge, 'State of the device tracker (0/1)', ) - value = state_helper.state_as_number(state) + value = self.state_as_number(state) metric.labels(**self._labels(state)).set(value) def _handle_person(self, state): @@ -157,7 +167,7 @@ class PrometheusMetrics: self.prometheus_client.Gauge, 'State of the person (0/1)', ) - value = state_helper.state_as_number(state) + value = self.state_as_number(state) metric.labels(**self._labels(state)).set(value) def _handle_light(self, state): @@ -171,7 +181,7 @@ class PrometheusMetrics: if 'brightness' in state.attributes: value = state.attributes['brightness'] / 255.0 else: - value = state_helper.state_as_number(state) + value = self.state_as_number(state) value = value * 100 metric.labels(**self._labels(state)).set(value) except ValueError: @@ -183,7 +193,7 @@ class PrometheusMetrics: self.prometheus_client.Gauge, 'State of the lock (0/1)', ) - value = state_helper.state_as_number(state) + value = self.state_as_number(state) metric.labels(**self._labels(state)).set(value) def _handle_climate(self, state): @@ -209,7 +219,7 @@ class PrometheusMetrics: 'climate_state', self.prometheus_client.Gauge, 'State of the thermostat (0/1)') try: - value = state_helper.state_as_number(state) + value = self.state_as_number(state) metric.labels(**self._labels(state)).set(value) except ValueError: pass @@ -232,7 +242,7 @@ class PrometheusMetrics: state.entity_id) try: - value = state_helper.state_as_number(state) + value = self.state_as_number(state) if unit == TEMP_FAHRENHEIT: value = fahrenheit_to_celsius(value) _metric.labels(**self._labels(state)).set(value) @@ -249,7 +259,7 @@ class PrometheusMetrics: ) try: - value = state_helper.state_as_number(state) + value = self.state_as_number(state) metric.labels(**self._labels(state)).set(value) except ValueError: pass diff --git a/homeassistant/components/prowl/__init__.py b/homeassistant/components/prowl/__init__.py new file mode 100644 index 00000000000..1cf58a25120 --- /dev/null +++ b/homeassistant/components/prowl/__init__.py @@ -0,0 +1 @@ +"""The prowl component.""" diff --git a/homeassistant/components/notify/prowl.py b/homeassistant/components/prowl/notify.py similarity index 97% rename from homeassistant/components/notify/prowl.py rename to homeassistant/components/prowl/notify.py index f0741766a70..6d911789121 100644 --- a/homeassistant/components/notify/prowl.py +++ b/homeassistant/components/prowl/notify.py @@ -4,18 +4,19 @@ Prowl notification service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.prowl/ """ -import logging import asyncio +import logging import async_timeout import voluptuous as vol -from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_DATA, PLATFORM_SCHEMA, - BaseNotificationService) from homeassistant.const import CONF_API_KEY -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv + +from homeassistant.components.notify import ( + ATTR_DATA, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, + BaseNotificationService) _LOGGER = logging.getLogger(__name__) _RESOURCE = 'https://api.prowlapp.com/publicapi/' diff --git a/homeassistant/components/proxy/__init__.py b/homeassistant/components/proxy/__init__.py new file mode 100644 index 00000000000..311c0732726 --- /dev/null +++ b/homeassistant/components/proxy/__init__.py @@ -0,0 +1 @@ +"""The proxy component.""" diff --git a/homeassistant/components/camera/proxy.py b/homeassistant/components/proxy/camera.py similarity index 100% rename from homeassistant/components/camera/proxy.py rename to homeassistant/components/proxy/camera.py diff --git a/homeassistant/components/ps4/.translations/bg.json b/homeassistant/components/ps4/.translations/bg.json new file mode 100644 index 00000000000..0a3c4393c7c --- /dev/null +++ b/homeassistant/components/ps4/.translations/bg.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "credential_error": "\u0413\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438 \u0438\u0437\u0432\u043b\u0438\u0447\u0430\u043d\u0435 \u043d\u0430 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u0434\u0430\u043d\u043d\u0438.", + "devices_configured": "\u0412\u0441\u0438\u0447\u043a\u0438 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441\u0430 \u0432\u0435\u0447\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0438.", + "no_devices_found": "\u041d\u0435 \u0441\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 PlayStation 4 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430.", + "port_987_bind_error": "\u041d\u0435\u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442 \u0437\u0430 \u0440\u0435\u0437\u0435\u0440\u0438\u0432\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u0440\u0442 987.", + "port_997_bind_error": "\u041d\u0435\u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442 \u0437\u0430 \u0440\u0435\u0437\u0435\u0440\u0438\u0432\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u0440\u0442 997." + }, + "error": { + "login_failed": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 PlayStation 4. \u041f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0434\u0430\u043b\u0438 \u0432\u044a\u0432\u0435\u0434\u0435\u043d\u0438\u044f PIN \u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u0435\u043d.", + "not_ready": "PlayStation 4 \u043a\u043e\u043d\u0437\u043e\u043b\u0430\u0442\u0430 \u043d\u0435 \u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u0438\u043b\u0438 \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u0430 \u043a\u044a\u043c \u043c\u0440\u0435\u0436\u0430\u0442\u0430." + }, + "step": { + "creds": { + "description": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0438 \u0441\u0430 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u0434\u0430\u043d\u043d\u0438. \u041d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \"\u0417\u0430\u043f\u0430\u0437\u0432\u0430\u043d\u0435\" \u0438 \u0441\u043b\u0435\u0434 \u0442\u043e\u0432\u0430 \u0432 PS4 2nd Screen App, \u043d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \"Refresh devices\" \u0438 \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \"Home-Assistant\" \u0437\u0430 \u0434\u0430 \u043f\u0440\u043e\u0434\u044a\u043b\u0436\u0438\u0442\u0435.", + "title": "PlayStation 4" + }, + "link": { + "data": { + "code": "PIN", + "ip_address": "IP \u0430\u0434\u0440\u0435\u0441", + "name": "\u0418\u043c\u0435", + "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + }, + "description": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0432\u0430\u0448\u0430\u0442\u0430 PlayStation 4 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f. \u0417\u0430 \u201ePIN\u201c \u043e\u0442\u0438\u0434\u0435\u0442\u0435 \u0432 \u201eSettings\u201c \u043d\u0430 \u0432\u0430\u0448\u0430\u0442\u0430 PlayStation 4 \u043a\u043e\u043d\u0437\u043e\u043b\u0430. \u0421\u043b\u0435\u0434 \u0442\u043e\u0432\u0430 \u043f\u0440\u0435\u043c\u0438\u043d\u0435\u0442\u0435 \u043a\u044a\u043c \u201eMobile App Connection Settings\u201c \u0438 \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u201eAdd Device\u201c. \u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f PIN \u043a\u043e\u0434.", + "title": "PlayStation 4" + } + }, + "title": "PlayStation 4" + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/.translations/ca.json b/homeassistant/components/ps4/.translations/ca.json index 350b65ca815..5e4b572ab45 100644 --- a/homeassistant/components/ps4/.translations/ca.json +++ b/homeassistant/components/ps4/.translations/ca.json @@ -9,6 +9,7 @@ }, "error": { "login_failed": "No s'ha pogut sincronitzar amb la PlayStation 4. Verifica el codi PIN.", + "no_ipaddress": "Introdueix l'adre\u00e7a IP de la PlayStation 4 que vulguis configurar.", "not_ready": "La PlayStation 4 no est\u00e0 engegada o no s'ha connectada a la xarxa." }, "step": { @@ -25,6 +26,14 @@ }, "description": "Introdueix la informaci\u00f3 de la teva PlayStation 4. Pel 'PIN', ves a 'Configuraci\u00f3' de la PlayStation 4, despr\u00e9s navega fins a 'Configuraci\u00f3 de la connexi\u00f3 de l'aplicaci\u00f3 m\u00f2bil' i selecciona 'Afegir dispositiu'. Introdueix el PIN que es mostra.", "title": "PlayStation 4" + }, + "mode": { + "data": { + "ip_address": "Adre\u00e7a IP (deixa-ho en blanc si fas servir la detecci\u00f3 autom\u00e0tica).", + "mode": "Mode de configuraci\u00f3" + }, + "description": "Selecciona el mode de configuraci\u00f3. El camp de l'adre\u00e7a IP es pot deixar en blanc si selecciones descobriment autom\u00e0tic (els dispositius es descobriran autom\u00e0ticament).", + "title": "PlayStation 4" } }, "title": "PlayStation 4" diff --git a/homeassistant/components/ps4/.translations/de.json b/homeassistant/components/ps4/.translations/de.json index 8f4e8838673..e2eadb1fe30 100644 --- a/homeassistant/components/ps4/.translations/de.json +++ b/homeassistant/components/ps4/.translations/de.json @@ -9,6 +9,7 @@ }, "error": { "login_failed": "Fehler beim Koppeln mit PlayStation 4. \u00dcberpr\u00fcfe, ob die PIN korrekt ist.", + "no_ipaddress": "Gib die IP-Adresse der PlayStation 4 ein, die konfiguriert werden soll.", "not_ready": "PlayStation 4 ist nicht eingeschaltet oder mit dem Netzwerk verbunden." }, "step": { @@ -25,6 +26,14 @@ }, "description": "Geben Sie Ihre PlayStation 4-Informationen ein. Navigiere f\u00fcr \"PIN\" auf der PlayStation 4-Konsole zu \"Einstellungen\". Navigiere dann zu \"Mobile App-Verbindungseinstellungen\" und w\u00e4hle \"Ger\u00e4t hinzuf\u00fcgen\" aus. Gib die angezeigte PIN ein.", "title": "PlayStation 4" + }, + "mode": { + "data": { + "ip_address": "IP-Adresse (Leer lassen, wenn automatische Erkennung verwendet wird).", + "mode": "Konfigurationsmodus" + }, + "description": "W\u00e4hlen Sie den Modus f\u00fcr die Konfiguration aus. Das Feld IP-Adresse kann leer bleiben, wenn die automatische Erkennung ausgew\u00e4hlt wird, da Ger\u00e4te automatisch erkannt werden.", + "title": "PlayStation 4" } }, "title": "PlayStation 4" diff --git a/homeassistant/components/ps4/.translations/en.json b/homeassistant/components/ps4/.translations/en.json index c0b476ff4e2..8949e77b4cc 100644 --- a/homeassistant/components/ps4/.translations/en.json +++ b/homeassistant/components/ps4/.translations/en.json @@ -4,11 +4,12 @@ "credential_error": "Error fetching credentials.", "devices_configured": "All devices found are already configured.", "no_devices_found": "No PlayStation 4 devices found on the network.", - "port_987_bind_error": "Could not bind to port 987.", - "port_997_bind_error": "Could not bind to port 997." + "port_987_bind_error": "Could not bind to port 987. Refer to the [documentation](https://www.home-assistant.io/components/ps4/) for additional info.", + "port_997_bind_error": "Could not bind to port 997. Refer to the [documentation](https://www.home-assistant.io/components/ps4/) for additional info." }, "error": { "login_failed": "Failed to pair to PlayStation 4. Verify PIN is correct.", + "no_ipaddress": "Enter the IP Address of the PlayStation 4 you would like to configure.", "not_ready": "PlayStation 4 is not on or connected to network." }, "step": { @@ -23,7 +24,15 @@ "name": "Name", "region": "Region" }, - "description": "Enter your PlayStation 4 information. For 'PIN', navigate to 'Settings' on your PlayStation 4 console. Then navigate to 'Mobile App Connection Settings' and select 'Add Device'. Enter the PIN that is displayed.", + "description": "Enter your PlayStation 4 information. For 'PIN', navigate to 'Settings' on your PlayStation 4 console. Then navigate to 'Mobile App Connection Settings' and select 'Add Device'. Enter the PIN that is displayed. Refer to the [documentation](https://www.home-assistant.io/components/ps4/) for additional info.", + "title": "PlayStation 4" + }, + "mode": { + "data": { + "ip_address": "IP Address (Leave empty if using Auto Discovery).", + "mode": "Config Mode" + }, + "description": "Select mode for configuration. The IP Address field can be left blank if selecting Auto Discovery, as devices will be automatically discovered.", "title": "PlayStation 4" } }, diff --git a/homeassistant/components/ps4/.translations/fr.json b/homeassistant/components/ps4/.translations/fr.json index d7983448417..bb654eed228 100644 --- a/homeassistant/components/ps4/.translations/fr.json +++ b/homeassistant/components/ps4/.translations/fr.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "credential_error": "Erreur lors de l'extraction des informations d'identification.", + "devices_configured": "Tous les p\u00e9riph\u00e9riques trouv\u00e9s sont d\u00e9j\u00e0 configur\u00e9s.", + "no_devices_found": "Aucun appareil PlayStation 4 trouv\u00e9 sur le r\u00e9seau.", + "port_987_bind_error": "Impossible de se connecter au port 997.", + "port_997_bind_error": "Impossible de se connecter au port 997." + }, + "error": { + "login_failed": "\u00c9chec de l'association \u00e0 la PlayStation 4. V\u00e9rifiez que le code PIN est correct.", + "not_ready": "PlayStation 4 n'est pas allum\u00e9e ou connect\u00e9e au r\u00e9seau." + }, "step": { "creds": { + "description": "Informations d\u2019identification n\u00e9cessaires. Appuyez sur \u00ab\u00a0Envoyer\u00a0\u00bb puis dans la PS4 2\u00e8me \u00e9cran App, actualisez les p\u00e9riph\u00e9riques et s\u00e9lectionnez le dispositif \u00ab\u00a0Home Assistant\u00a0\u00bb pour continuer.", "title": "PlayStation 4" }, "link": { diff --git a/homeassistant/components/ps4/.translations/ko.json b/homeassistant/components/ps4/.translations/ko.json index ca77537e4e1..ba864f07320 100644 --- a/homeassistant/components/ps4/.translations/ko.json +++ b/homeassistant/components/ps4/.translations/ko.json @@ -4,11 +4,12 @@ "credential_error": "\uc790\uaca9 \uc99d\uba85\uc744 \uac00\uc838\uc624\ub294 \uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "devices_configured": "\ubc1c\uacac \ub41c \ubaa8\ub4e0 \uae30\uae30\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "no_devices_found": "PlayStation 4 \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "port_987_bind_error": "\ud3ec\ud2b8 987 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", - "port_997_bind_error": "\ud3ec\ud2b8 997 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + "port_987_bind_error": "\ud3ec\ud2b8 987 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "port_997_bind_error": "\ud3ec\ud2b8 997 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "error": { "login_failed": "PlayStation 4 \uc640 \ud398\uc5b4\ub9c1\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. PIN \uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "no_ipaddress": "\uad6c\uc131\ud558\uace0\uc790 \ud558\ub294 PlayStation 4 \uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", "not_ready": "PlayStation 4 \uac00 \ucf1c\uc838 \uc788\uc9c0 \uc54a\uac70\ub098 \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "step": { @@ -23,7 +24,15 @@ "name": "\uc774\ub984", "region": "\uc9c0\uc5ed" }, - "description": "PlayStation 4 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. 'PIN' \uc744 \ud655\uc778\ud558\ub824\uba74, PlayStation 4 \ucf58\uc194\uc5d0\uc11c '\uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud55c \ub4a4 '\ubaa8\ubc14\uc77c \uc571 \uc811\uc18d \uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud558\uc5ec '\uae30\uae30 \ub4f1\ub85d\ud558\uae30' \ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ud654\uba74\uc5d0 \ud45c\uc2dc\ub41c 8\uc790\ub9ac \uc22b\uc790\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "description": "PlayStation 4 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. 'PIN' \uc744 \ud655\uc778\ud558\ub824\uba74, PlayStation 4 \ucf58\uc194\uc5d0\uc11c '\uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud55c \ub4a4 '\ubaa8\ubc14\uc77c \uc571 \uc811\uc18d \uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud558\uc5ec '\uae30\uae30 \ub4f1\ub85d\ud558\uae30' \ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ud654\uba74\uc5d0 \ud45c\uc2dc\ub41c 8\uc790\ub9ac \uc22b\uc790\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "title": "PlayStation 4" + }, + "mode": { + "data": { + "ip_address": "IP \uc8fc\uc18c (\uc790\ub3d9 \uac80\uc0c9\uc744 \uc0ac\uc6a9\ud558\ub294 \uacbd\uc6b0 \ube44\uc6cc\ub450\uc138\uc694)", + "mode": "\uad6c\uc131 \ubaa8\ub4dc" + }, + "description": "\uad6c\uc131 \ubaa8\ub4dc\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc790\ub3d9 \uac80\uc0c9\uc744 \uc120\ud0dd\ud558\uba74 \uae30\uae30\uac00 \uc790\ub3d9\uc73c\ub85c \uac80\uc0c9\ub418\ubbc0\ub85c IP \uc8fc\uc18c \ud544\ub4dc\ub294 \ube44\uc6cc\ub458 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "PlayStation 4" } }, diff --git a/homeassistant/components/ps4/.translations/lb.json b/homeassistant/components/ps4/.translations/lb.json index 15b90cb6b6b..5c5847f28c0 100644 --- a/homeassistant/components/ps4/.translations/lb.json +++ b/homeassistant/components/ps4/.translations/lb.json @@ -9,6 +9,7 @@ }, "error": { "login_failed": "Feeler beim verbanne mat der Playstation 4. Iwwerpr\u00e9ift op de PIN korrekt ass.", + "no_ipaddress": "Gitt d'IP Adresse vun der Playstation 4 an:", "not_ready": "PlayStation 4 ass net un oder mam Netzwierk verbonnen." }, "step": { @@ -25,6 +26,14 @@ }, "description": "Gitt \u00e4r Playstation 4 Informatiounen an. Fir 'PIN', gitt an d'Astellunge vun der Playstation 4 Konsole. Dann op 'Mobile App Verbindungs Astellungen' a wielt \"Apparat dob\u00e4isetzen' aus. Gitt de PIN an deen ugewise g\u00ebtt.", "title": "PlayStation 4" + }, + "mode": { + "data": { + "ip_address": "IP Address (Eidel loossen falls Auto Discovery benotzt g\u00ebtt)", + "mode": "Konfiguratioun's Modus" + }, + "description": "Konfiguratioun's Modus auswielen. D'Feld IP Adress kann eidel bl\u00e9iwen wann Auto Discovery benotzt g\u00ebtt, well d'Apparaten automatesch entdeckt ginn.", + "title": "PlayStation 4" } }, "title": "PlayStation 4" diff --git a/homeassistant/components/ps4/.translations/ru.json b/homeassistant/components/ps4/.translations/ru.json index 41232ddc2d4..424d0964729 100644 --- a/homeassistant/components/ps4/.translations/ru.json +++ b/homeassistant/components/ps4/.translations/ru.json @@ -4,11 +4,12 @@ "credential_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u0443\u0447\u0435\u0442\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445.", "devices_configured": "\u0412\u0441\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b.", "no_devices_found": "\u0412 \u0441\u0435\u0442\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 PlayStation 4.", - "port_987_bind_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u043f\u043e\u0440\u0442\u0443 987.", - "port_997_bind_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u043f\u043e\u0440\u0442\u0443 997." + "port_987_bind_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u043f\u043e\u0440\u0442\u0443 987. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/ps4/).", + "port_997_bind_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u043f\u043e\u0440\u0442\u0443 997. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/ps4/)." }, "error": { "login_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 PlayStation 4. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e PIN-\u043a\u043e\u0434 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439.", + "no_ipaddress": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441 PlayStation 4.", "not_ready": "PlayStation 4 \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u0438\u043b\u0438 \u043d\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u043a \u0441\u0435\u0442\u0438." }, "step": { @@ -23,7 +24,15 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "region": "\u0420\u0435\u0433\u0438\u043e\u043d" }, - "description": "\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f PIN-\u043a\u043e\u0434\u0430 \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043f\u0443\u043d\u043a\u0442\u0443 **\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438** \u043d\u0430 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 PlayStation 4. \u0417\u0430\u0442\u0435\u043c \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 **\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f** \u0438 \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 **\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e**.", + "description": "\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f PIN-\u043a\u043e\u0434\u0430 \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043f\u0443\u043d\u043a\u0442\u0443 **\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438** \u043d\u0430 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 PlayStation 4. \u0417\u0430\u0442\u0435\u043c \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 **\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f** \u0438 \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 **\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e**. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439](https://www.home-assistant.io/components/ps4/) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438.", + "title": "PlayStation 4" + }, + "mode": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441 (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0440\u0435\u0436\u0438\u043c\u0430 \u0430\u0432\u0442\u043e\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f)", + "mode": "\u0420\u0435\u0436\u0438\u043c" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u041f\u043e\u043b\u0435 'IP-\u0430\u0434\u0440\u0435\u0441' \u043c\u043e\u0436\u043d\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c, \u0435\u0441\u043b\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'Auto Discovery', \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.", "title": "PlayStation 4" } }, diff --git a/homeassistant/components/ps4/.translations/sl.json b/homeassistant/components/ps4/.translations/sl.json new file mode 100644 index 00000000000..429a409fb7e --- /dev/null +++ b/homeassistant/components/ps4/.translations/sl.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "credential_error": "Napaka pri pridobivanju poverilnic.", + "devices_configured": "Vse najdene naprave so \u017ee konfigurirane.", + "no_devices_found": "V omre\u017eju ni najdenih naprav PS4.", + "port_987_bind_error": "Ne morem se povezati z vrati 987. Dodatne informacije najdete v [dokumentaciji] (https://www.home-assistant.io/components/ps4/).", + "port_997_bind_error": "Ne morem se povezati z vrati 997. Dodatne informacije najdete v [dokumentaciji] (https://www.home-assistant.io/components/ps4/)." + }, + "error": { + "login_failed": "Neuspelo seznanjanje s PlayStation 4. Preverite, ali je koda PIN pravilna.", + "no_ipaddress": "Vnesite IP naslov PlayStation-a 4, ki ga \u017eelite konfigurirati.", + "not_ready": "PlayStation 4 ni vklopljen ali povezan z omre\u017ejem." + }, + "step": { + "creds": { + "description": "Potrebne so poverilnice. Pritisnite 'Po\u0161lji' in nato v aplikaciji PS4 2nd Screen App, osve\u017eite naprave in izberite napravo 'Home-Assistant' za nadaljevanje.", + "title": "PlayStation 4" + }, + "link": { + "data": { + "code": "PIN", + "ip_address": "IP naslov", + "name": "Ime", + "region": "Regija" + }, + "description": "Vnesite va\u0161e PlayStation 4 podatke. Za 'PIN' pojdite na 'Nastavitve' na konzoli PlayStation 4. Nato se pomaknite do mo\u017enosti \u00bbNastavitve povezave z mobilno aplikacijo\u00ab in izberite \u00bbDodaj napravo\u00ab. Vnesite prikazano kodo PIN. Dodatne informacije najdete v [dokumentaciji] (https://www.home-assistant.io/components/ps4/).", + "title": "PlayStation 4" + }, + "mode": { + "data": { + "ip_address": "Naslov IP (Pustite prazno, \u010de uporabljate samodejno odkrivanje).", + "mode": "Na\u010din konfiguracije" + }, + "description": "Izberite na\u010din za konfiguracijo. IP-Naslov, polje lahko pustite prazno, \u010de izberete samodejno odkrivanje, saj bodo naprave samodejno odkrite.", + "title": "PlayStation 4" + } + }, + "title": "PlayStation 4" + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/.translations/zh-Hans.json b/homeassistant/components/ps4/.translations/zh-Hans.json index 8c975e8170c..118226354af 100644 --- a/homeassistant/components/ps4/.translations/zh-Hans.json +++ b/homeassistant/components/ps4/.translations/zh-Hans.json @@ -23,6 +23,7 @@ "name": "\u540d\u79f0", "region": "\u5730\u533a" }, + "description": "\u8f93\u5165\u60a8\u7684 PlayStation 4 \u4fe1\u606f\u3002\u5bf9\u4e8e \"PIN\", \u8bf7\u5bfc\u822a\u5230 PlayStation 4 \u63a7\u5236\u53f0\u4e0a\u7684 \"\u8bbe\u7f6e\"\u3002\u7136\u540e\u5bfc\u822a\u5230 \"\u79fb\u52a8\u5e94\u7528\u8fde\u63a5\u8bbe\u7f6e\", \u7136\u540e\u9009\u62e9 \"\u6dfb\u52a0\u8bbe\u5907\"\u3002\u8f93\u5165\u663e\u793a\u7684 PIN\u3002", "title": "PlayStation 4" } }, diff --git a/homeassistant/components/ps4/__init__.py b/homeassistant/components/ps4/__init__.py index 087d3f89f80..9183bbe1989 100644 --- a/homeassistant/components/ps4/__init__.py +++ b/homeassistant/components/ps4/__init__.py @@ -6,12 +6,15 @@ https://home-assistant.io/components/ps4/ """ import logging -from homeassistant.components.ps4.config_flow import PlayStation4FlowHandler # noqa: pylint: disable=unused-import -from homeassistant.components.ps4.const import DOMAIN # noqa: pylint: disable=unused-import +from homeassistant.const import CONF_REGION +from homeassistant.util import location + +from .config_flow import PlayStation4FlowHandler # noqa: pylint: disable=unused-import +from .const import DOMAIN # noqa: pylint: disable=unused-import _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pyps4-homeassistant==0.4.8'] +REQUIREMENTS = ['pyps4-homeassistant==0.5.2'] async def async_setup(hass, config): @@ -31,3 +34,40 @@ async def async_unload_entry(hass, entry): await hass.config_entries.async_forward_entry_unload( entry, 'media_player') return True + + +async def async_migrate_entry(hass, entry): + """Migrate old entry.""" + from pyps4_homeassistant.media_art import COUNTRIES + + config_entries = hass.config_entries + data = entry.data + version = entry.version + + reason = {1: "Region codes have changed"} # From 0.89 + + # Migrate Version 1 -> Version 2 + if version == 1: + loc = await hass.async_add_executor_job(location.detect_location_info) + if loc: + country = loc.country_name + if country in COUNTRIES: + for device in data['devices']: + device[CONF_REGION] = country + entry.version = 2 + config_entries.async_update_entry(entry, data=data) + _LOGGER.info( + "PlayStation 4 Config Updated: \ + Region changed to: %s", country) + return True + + msg = """{} for the PlayStation 4 Integration. + Please remove the PS4 Integration and re-configure + [here](/config/integrations).""".format(reason[version]) + + hass.components.persistent_notification.async_create( + title="PlayStation 4 Integration Configuration Requires Update", + message=msg, + notification_id='config_entry_migration' + ) + return False diff --git a/homeassistant/components/ps4/config_flow.py b/homeassistant/components/ps4/config_flow.py index e0b41dfadd5..1b184a3774f 100644 --- a/homeassistant/components/ps4/config_flow.py +++ b/homeassistant/components/ps4/config_flow.py @@ -5,13 +5,17 @@ import logging import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.ps4.const import ( - DEFAULT_NAME, DEFAULT_REGION, DOMAIN, REGIONS) from homeassistant.const import ( CONF_CODE, CONF_HOST, CONF_IP_ADDRESS, CONF_NAME, CONF_REGION, CONF_TOKEN) +from .const import DEFAULT_NAME, DOMAIN + _LOGGER = logging.getLogger(__name__) +CONF_MODE = 'Config Mode' +CONF_AUTO = "Auto Discover" +CONF_MANUAL = "Manual Entry" + UDP_PORT = 987 TCP_PORT = 997 PORT_MSG = {UDP_PORT: 'port_987_bind_error', TCP_PORT: 'port_997_bind_error'} @@ -21,7 +25,7 @@ PORT_MSG = {UDP_PORT: 'port_987_bind_error', TCP_PORT: 'port_997_bind_error'} class PlayStation4FlowHandler(config_entries.ConfigFlow): """Handle a PlayStation 4 config flow.""" - VERSION = 1 + VERSION = 2 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL def __init__(self): @@ -34,6 +38,8 @@ class PlayStation4FlowHandler(config_entries.ConfigFlow): self.host = None self.region = None self.pin = None + self.m_device = None + self.device_list = [] async def async_step_user(self, user_input=None): """Handle a user config flow.""" @@ -46,7 +52,7 @@ class PlayStation4FlowHandler(config_entries.ConfigFlow): return self.async_abort(reason=reason) # Skip Creds Step if a device is configured. if self.hass.config_entries.async_entries(DOMAIN): - return await self.async_step_link() + return await self.async_step_mode() return await self.async_step_creds() async def async_step_creds(self, user_input=None): @@ -56,53 +62,82 @@ class PlayStation4FlowHandler(config_entries.ConfigFlow): self.helper.get_creds) if self.creds is not None: - return await self.async_step_link() + return await self.async_step_mode() return self.async_abort(reason='credential_error') return self.async_show_form( step_id='creds') + async def async_step_mode(self, user_input=None): + """Prompt for mode.""" + errors = {} + mode = [CONF_AUTO, CONF_MANUAL] + + if user_input is not None: + if user_input[CONF_MODE] == CONF_MANUAL: + try: + device = user_input[CONF_IP_ADDRESS] + if device: + self.m_device = device + except KeyError: + errors[CONF_IP_ADDRESS] = 'no_ipaddress' + if not errors: + return await self.async_step_link() + + mode_schema = OrderedDict() + mode_schema[vol.Required( + CONF_MODE, default=CONF_AUTO)] = vol.In(list(mode)) + mode_schema[vol.Optional(CONF_IP_ADDRESS)] = str + + return self.async_show_form( + step_id='mode', + data_schema=vol.Schema(mode_schema), + errors=errors, + ) + async def async_step_link(self, user_input=None): """Prompt user input. Create or edit entry.""" + from pyps4_homeassistant.media_art import COUNTRIES + regions = sorted(COUNTRIES.keys()) errors = {} - # Search for device. - devices = await self.hass.async_add_executor_job( - self.helper.has_devices) + if user_input is None: + # Search for device. + devices = await self.hass.async_add_executor_job( + self.helper.has_devices, self.m_device) - # Abort if can't find device. - if not devices: - return self.async_abort(reason='no_devices_found') + # Abort if can't find device. + if not devices: + return self.async_abort(reason='no_devices_found') - device_list = [ - device['host-ip'] for device in devices] + self.device_list = [device['host-ip'] for device in devices] - # If entry exists check that devices found aren't configured. - if self.hass.config_entries.async_entries(DOMAIN): - creds = {} - for entry in self.hass.config_entries.async_entries(DOMAIN): - # Retrieve creds from entry - creds['data'] = entry.data[CONF_TOKEN] - # Retrieve device data from entry - conf_devices = entry.data['devices'] - for c_device in conf_devices: - if c_device['host'] in device_list: - # Remove configured device from search list. - device_list.remove(c_device['host']) - # If list is empty then all devices are configured. - if not device_list: - return self.async_abort(reason='devices_configured') - # Add existing creds for linking. Should be only 1. - if not creds: - # Abort if creds is missing. - return self.async_abort(reason='credential_error') - self.creds = creds['data'] + # If entry exists check that devices found aren't configured. + if self.hass.config_entries.async_entries(DOMAIN): + creds = {} + for entry in self.hass.config_entries.async_entries(DOMAIN): + # Retrieve creds from entry + creds['data'] = entry.data[CONF_TOKEN] + # Retrieve device data from entry + conf_devices = entry.data['devices'] + for c_device in conf_devices: + if c_device['host'] in self.device_list: + # Remove configured device from search list. + self.device_list.remove(c_device['host']) + # If list is empty then all devices are configured. + if not self.device_list: + return self.async_abort(reason='devices_configured') + # Add existing creds for linking. Should be only 1. + if not creds: + # Abort if creds is missing. + return self.async_abort(reason='credential_error') + self.creds = creds['data'] # Login to PS4 with user data. if user_input is not None: self.region = user_input[CONF_REGION] self.name = user_input[CONF_NAME] - self.pin = user_input[CONF_CODE] + self.pin = str(user_input[CONF_CODE]) self.host = user_input[CONF_IP_ADDRESS] is_ready, is_login = await self.hass.async_add_executor_job( @@ -130,10 +165,11 @@ class PlayStation4FlowHandler(config_entries.ConfigFlow): # Show User Input form. link_schema = OrderedDict() - link_schema[vol.Required(CONF_IP_ADDRESS)] = vol.In(list(device_list)) - link_schema[vol.Required( - CONF_REGION, default=DEFAULT_REGION)] = vol.In(list(REGIONS)) - link_schema[vol.Required(CONF_CODE)] = str + link_schema[vol.Required(CONF_IP_ADDRESS)] = vol.In( + list(self.device_list)) + link_schema[vol.Required(CONF_REGION)] = vol.In(list(regions)) + link_schema[vol.Required(CONF_CODE)] = vol.All( + vol.Strip, vol.Length(min=8, max=8), vol.Coerce(int)) link_schema[vol.Required(CONF_NAME, default=DEFAULT_NAME)] = str return self.async_show_form( diff --git a/homeassistant/components/ps4/const.py b/homeassistant/components/ps4/const.py index 0618ca9675f..bbf654530b0 100644 --- a/homeassistant/components/ps4/const.py +++ b/homeassistant/components/ps4/const.py @@ -1,5 +1,7 @@ """Constants for PlayStation 4.""" DEFAULT_NAME = "PlayStation 4" -DEFAULT_REGION = "R1" +DEFAULT_REGION = "United States" DOMAIN = 'ps4' -REGIONS = ('R1', 'R2', 'R3', 'R4', 'R5') + +# Deprecated used for logger/backwards compatibility from 0.89 +REGIONS = ['R1', 'R2', 'R3', 'R4', 'R5'] diff --git a/homeassistant/components/ps4/media_player.py b/homeassistant/components/ps4/media_player.py index 74dce515d9d..80c1fda52de 100644 --- a/homeassistant/components/ps4/media_player.py +++ b/homeassistant/components/ps4/media_player.py @@ -2,29 +2,25 @@ Support for PlayStation 4 consoles. For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/media_player.ps4/ +https://home-assistant.io/components/ps4/ """ -from datetime import timedelta import logging import socket import voluptuous as vol -import homeassistant.helpers.config_validation as cv -import homeassistant.util as util from homeassistant.components.media_player import ( - MediaPlayerDevice, ENTITY_IMAGE_URL) + ENTITY_IMAGE_URL, MediaPlayerDevice) from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, -) -from homeassistant.components.ps4.const import DOMAIN as PS4_DOMAIN + MEDIA_TYPE_GAME, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, + SUPPORT_TURN_ON) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_COMMAND, CONF_HOST, CONF_NAME, CONF_REGION, - CONF_TOKEN, STATE_IDLE, STATE_OFF, STATE_PLAYING, -) + ATTR_COMMAND, ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_REGION, + CONF_TOKEN, STATE_IDLE, STATE_OFF, STATE_PLAYING) +import homeassistant.helpers.config_validation as cv from homeassistant.util.json import load_json, save_json +from .const import DOMAIN as PS4_DOMAIN, REGIONS as deprecated_regions DEPENDENCIES = ['ps4'] @@ -38,9 +34,6 @@ ICON = 'mdi:playstation' GAMES_FILE = '.ps4-games.json' MEDIA_IMAGE_DEFAULT = None -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) -MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=10) - COMMANDS = ( 'up', 'down', @@ -139,7 +132,6 @@ class PS4Device(MediaPlayerDevice): """Subscribe PS4 events.""" self.hass.data[PS4_DATA].devices.append(self) - @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) def update(self): """Retrieve the latest data.""" try: @@ -150,6 +142,12 @@ class PS4Device(MediaPlayerDevice): self._games = self.load_games() if self._games is not None: self._source_list = list(sorted(self._games.values())) + # Non-Breaking although data returned may be inaccurate. + if self._region in deprecated_regions: + _LOGGER.info("""Region: %s has been deprecated. + Please remove PS4 integration + and Re-configure again to utilize + current regions""", self._region) except socket.timeout: status = None if status is not None: @@ -283,6 +281,8 @@ class PS4Device(MediaPlayerDevice): async def async_will_remove_from_hass(self): """Remove Entity from Hass.""" + # Close TCP Socket + await self.hass.async_add_executor_job(self._ps4.close) self.hass.data[PS4_DATA].devices.remove(self) @property @@ -328,7 +328,7 @@ class PS4Device(MediaPlayerDevice): @property def media_content_type(self): """Content type of current playing media.""" - return MEDIA_TYPE_MUSIC + return MEDIA_TYPE_GAME @property def media_image_url(self): diff --git a/homeassistant/components/ps4/strings.json b/homeassistant/components/ps4/strings.json index 5f4e2a7c8b4..ea69d8c7a8c 100644 --- a/homeassistant/components/ps4/strings.json +++ b/homeassistant/components/ps4/strings.json @@ -6,9 +6,17 @@ "title": "PlayStation 4", "description": "Credentials needed. Press 'Submit' and then in the PS4 2nd Screen App, refresh devices and select the 'Home-Assistant' device to continue." }, + "mode": { + "title": "PlayStation 4", + "description": "Select mode for configuration. The IP Address field can be left blank if selecting Auto Discovery, as devices will be automatically discovered.", + "data": { + "mode": "Config Mode", + "ip_address": "IP Address (Leave empty if using Auto Discovery)." + } + }, "link": { "title": "PlayStation 4", - "description": "Enter your PlayStation 4 information. For 'PIN', navigate to 'Settings' on your PlayStation 4 console. Then navigate to 'Mobile App Connection Settings' and select 'Add Device'. Enter the PIN that is displayed.", + "description": "Enter your PlayStation 4 information. For 'PIN', navigate to 'Settings' on your PlayStation 4 console. Then navigate to 'Mobile App Connection Settings' and select 'Add Device'. Enter the PIN that is displayed. Refer to the [documentation](https://www.home-assistant.io/components/ps4/) for additional info.", "data": { "region": "Region", "name": "Name", @@ -19,14 +27,15 @@ }, "error": { "not_ready": "PlayStation 4 is not on or connected to network.", - "login_failed": "Failed to pair to PlayStation 4. Verify PIN is correct." + "login_failed": "Failed to pair to PlayStation 4. Verify PIN is correct.", + "no_ipaddress": "Enter the IP Address of the PlayStation 4 you would like to configure." }, "abort": { "credential_error": "Error fetching credentials.", "no_devices_found": "No PlayStation 4 devices found on the network.", - "devices_configured": "All devices found are already configured.", - "port_987_bind_error": "Could not bind to port 987.", - "port_997_bind_error": "Could not bind to port 997." + "devices_configured": "All devices found are already configured.", + "port_987_bind_error": "Could not bind to port 987. Refer to the [documentation](https://www.home-assistant.io/components/ps4/) for additional info.", + "port_997_bind_error": "Could not bind to port 997. Refer to the [documentation](https://www.home-assistant.io/components/ps4/) for additional info." } } } diff --git a/homeassistant/components/pulseaudio_loopback/__init__.py b/homeassistant/components/pulseaudio_loopback/__init__.py new file mode 100644 index 00000000000..14f05080f5f --- /dev/null +++ b/homeassistant/components/pulseaudio_loopback/__init__.py @@ -0,0 +1 @@ +"""The pulseaudio_loopback component.""" diff --git a/homeassistant/components/switch/pulseaudio_loopback.py b/homeassistant/components/pulseaudio_loopback/switch.py similarity index 100% rename from homeassistant/components/switch/pulseaudio_loopback.py rename to homeassistant/components/pulseaudio_loopback/switch.py diff --git a/homeassistant/components/push/__init__.py b/homeassistant/components/push/__init__.py new file mode 100644 index 00000000000..81ace3bf398 --- /dev/null +++ b/homeassistant/components/push/__init__.py @@ -0,0 +1 @@ +"""The push component.""" diff --git a/homeassistant/components/camera/push.py b/homeassistant/components/push/camera.py similarity index 98% rename from homeassistant/components/camera/push.py rename to homeassistant/components/push/camera.py index 36c4a3109ba..5490cd1508c 100644 --- a/homeassistant/components/camera/push.py +++ b/homeassistant/components/push/camera.py @@ -14,7 +14,8 @@ import aiohttp import async_timeout from homeassistant.components.camera import Camera, PLATFORM_SCHEMA,\ - STATE_IDLE, STATE_RECORDING, DOMAIN + STATE_IDLE, STATE_RECORDING +from homeassistant.components.camera.const import DOMAIN from homeassistant.core import callback from homeassistant.const import CONF_NAME, CONF_TIMEOUT, CONF_WEBHOOK_ID from homeassistant.helpers import config_validation as cv diff --git a/homeassistant/components/pushbullet/__init__.py b/homeassistant/components/pushbullet/__init__.py new file mode 100644 index 00000000000..153fa389fcc --- /dev/null +++ b/homeassistant/components/pushbullet/__init__.py @@ -0,0 +1 @@ +"""The pushbullet component.""" diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/pushbullet/notify.py similarity index 99% rename from homeassistant/components/notify/pushbullet.py rename to homeassistant/components/pushbullet/notify.py index a94cf4f1055..f0b4ec24da8 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/pushbullet/notify.py @@ -9,11 +9,12 @@ import mimetypes import voluptuous as vol +from homeassistant.const import CONF_API_KEY +import homeassistant.helpers.config_validation as cv + from homeassistant.components.notify import ( ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) -from homeassistant.const import CONF_API_KEY -import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pushbullet.py==0.11.0'] diff --git a/homeassistant/components/sensor/pushbullet.py b/homeassistant/components/pushbullet/sensor.py similarity index 100% rename from homeassistant/components/sensor/pushbullet.py rename to homeassistant/components/pushbullet/sensor.py diff --git a/homeassistant/components/pushetta/__init__.py b/homeassistant/components/pushetta/__init__.py new file mode 100644 index 00000000000..f992fecddb7 --- /dev/null +++ b/homeassistant/components/pushetta/__init__.py @@ -0,0 +1 @@ +"""The pushetta component.""" diff --git a/homeassistant/components/notify/pushetta.py b/homeassistant/components/pushetta/notify.py similarity index 99% rename from homeassistant/components/notify/pushetta.py rename to homeassistant/components/pushetta/notify.py index a29cd587105..106c0641a69 100644 --- a/homeassistant/components/notify/pushetta.py +++ b/homeassistant/components/pushetta/notify.py @@ -8,11 +8,12 @@ import logging import voluptuous as vol -from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_API_KEY import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import ( + ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) + _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['pushetta==1.0.15'] diff --git a/homeassistant/components/pushover/__init__.py b/homeassistant/components/pushover/__init__.py new file mode 100644 index 00000000000..921d37ed332 --- /dev/null +++ b/homeassistant/components/pushover/__init__.py @@ -0,0 +1 @@ +"""The pushover component.""" diff --git a/homeassistant/components/notify/pushover.py b/homeassistant/components/pushover/notify.py similarity index 95% rename from homeassistant/components/notify/pushover.py rename to homeassistant/components/pushover/notify.py index b249ca804b3..78e9ed11c95 100644 --- a/homeassistant/components/notify/pushover.py +++ b/homeassistant/components/pushover/notify.py @@ -8,12 +8,13 @@ import logging import voluptuous as vol -from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_TARGET, ATTR_DATA, - BaseNotificationService, PLATFORM_SCHEMA) from homeassistant.const import CONF_API_KEY import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import ( + ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, + BaseNotificationService) + REQUIREMENTS = ['python-pushover==0.3'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/pushsafer/__init__.py b/homeassistant/components/pushsafer/__init__.py new file mode 100644 index 00000000000..81dfc7e15fd --- /dev/null +++ b/homeassistant/components/pushsafer/__init__.py @@ -0,0 +1 @@ +"""The pushsafer component.""" diff --git a/homeassistant/components/notify/pushsafer.py b/homeassistant/components/pushsafer/notify.py similarity index 98% rename from homeassistant/components/notify/pushsafer.py rename to homeassistant/components/pushsafer/notify.py index 94dc08a8113..a1fa2b7409c 100644 --- a/homeassistant/components/notify/pushsafer.py +++ b/homeassistant/components/pushsafer/notify.py @@ -4,19 +4,20 @@ Pushsafer platform for notify component. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.pushsafer/ """ -import logging import base64 +import logging import mimetypes import requests from requests.auth import HTTPBasicAuth import voluptuous as vol -from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_TARGET, ATTR_DATA, - PLATFORM_SCHEMA, BaseNotificationService) import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import ( + ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, + BaseNotificationService) + _LOGGER = logging.getLogger(__name__) _RESOURCE = 'https://www.pushsafer.com/api' _ALLOWED_IMAGES = ['image/gif', 'image/jpeg', 'image/png'] diff --git a/homeassistant/components/pvoutput/__init__.py b/homeassistant/components/pvoutput/__init__.py new file mode 100644 index 00000000000..0ea3aabe9eb --- /dev/null +++ b/homeassistant/components/pvoutput/__init__.py @@ -0,0 +1 @@ +"""The pvoutput component.""" diff --git a/homeassistant/components/sensor/pvoutput.py b/homeassistant/components/pvoutput/sensor.py similarity index 98% rename from homeassistant/components/sensor/pvoutput.py rename to homeassistant/components/pvoutput/sensor.py index a12093099c4..dbcd38af3cc 100644 --- a/homeassistant/components/sensor/pvoutput.py +++ b/homeassistant/components/pvoutput/sensor.py @@ -13,7 +13,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.components.sensor.rest import RestData +from homeassistant.components.rest.sensor import RestData from homeassistant.const import ( ATTR_TEMPERATURE, CONF_API_KEY, CONF_NAME, ATTR_DATE, ATTR_TIME, ATTR_VOLTAGE) diff --git a/homeassistant/components/pyload/__init__.py b/homeassistant/components/pyload/__init__.py new file mode 100644 index 00000000000..19103572e0b --- /dev/null +++ b/homeassistant/components/pyload/__init__.py @@ -0,0 +1 @@ +"""The pyload component.""" diff --git a/homeassistant/components/sensor/pyload.py b/homeassistant/components/pyload/sensor.py similarity index 100% rename from homeassistant/components/sensor/pyload.py rename to homeassistant/components/pyload/sensor.py diff --git a/homeassistant/components/qbittorrent/__init__.py b/homeassistant/components/qbittorrent/__init__.py new file mode 100644 index 00000000000..a5274f7a5a9 --- /dev/null +++ b/homeassistant/components/qbittorrent/__init__.py @@ -0,0 +1 @@ +"""The qbittorrent component.""" diff --git a/homeassistant/components/sensor/qbittorrent.py b/homeassistant/components/qbittorrent/sensor.py similarity index 100% rename from homeassistant/components/sensor/qbittorrent.py rename to homeassistant/components/qbittorrent/sensor.py diff --git a/homeassistant/components/qnap/__init__.py b/homeassistant/components/qnap/__init__.py new file mode 100644 index 00000000000..534096628df --- /dev/null +++ b/homeassistant/components/qnap/__init__.py @@ -0,0 +1 @@ +"""The qnap component.""" diff --git a/homeassistant/components/sensor/qnap.py b/homeassistant/components/qnap/sensor.py similarity index 100% rename from homeassistant/components/sensor/qnap.py rename to homeassistant/components/qnap/sensor.py diff --git a/homeassistant/components/qrcode/__init__.py b/homeassistant/components/qrcode/__init__.py new file mode 100644 index 00000000000..bcc1985a2dc --- /dev/null +++ b/homeassistant/components/qrcode/__init__.py @@ -0,0 +1 @@ +"""The qrcode component.""" diff --git a/homeassistant/components/image_processing/qrcode.py b/homeassistant/components/qrcode/image_processing.py similarity index 100% rename from homeassistant/components/image_processing/qrcode.py rename to homeassistant/components/qrcode/image_processing.py diff --git a/homeassistant/components/quantum_gateway/__init__.py b/homeassistant/components/quantum_gateway/__init__.py new file mode 100644 index 00000000000..d502c2b216c --- /dev/null +++ b/homeassistant/components/quantum_gateway/__init__.py @@ -0,0 +1 @@ +"""The quantum_gateway component.""" diff --git a/homeassistant/components/device_tracker/quantum_gateway.py b/homeassistant/components/quantum_gateway/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/quantum_gateway.py rename to homeassistant/components/quantum_gateway/device_tracker.py diff --git a/homeassistant/components/qwikswitch/binary_sensor.py b/homeassistant/components/qwikswitch/binary_sensor.py index 2fe14773d3a..17021f7a9e9 100644 --- a/homeassistant/components/qwikswitch/binary_sensor.py +++ b/homeassistant/components/qwikswitch/binary_sensor.py @@ -7,9 +7,10 @@ https://home-assistant.io/components/binary_sensor.qwikswitch/ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.qwikswitch import QSEntity, DOMAIN as QWIKSWITCH from homeassistant.core import callback +from . import DOMAIN as QWIKSWITCH, QSEntity + DEPENDENCIES = [QWIKSWITCH] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/qwikswitch/light.py b/homeassistant/components/qwikswitch/light.py index 413358d9cee..46a0a88483b 100644 --- a/homeassistant/components/qwikswitch/light.py +++ b/homeassistant/components/qwikswitch/light.py @@ -4,10 +4,10 @@ Support for Qwikswitch Relays and Dimmers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.qwikswitch/ """ -from homeassistant.components.qwikswitch import ( - QSToggleEntity, DOMAIN as QWIKSWITCH) from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light +from . import DOMAIN as QWIKSWITCH, QSToggleEntity + DEPENDENCIES = [QWIKSWITCH] diff --git a/homeassistant/components/qwikswitch/sensor.py b/homeassistant/components/qwikswitch/sensor.py index c8d33de4baf..07d0247e4f6 100644 --- a/homeassistant/components/qwikswitch/sensor.py +++ b/homeassistant/components/qwikswitch/sensor.py @@ -6,9 +6,10 @@ https://home-assistant.io/components/sensor.qwikswitch/ """ import logging -from homeassistant.components.qwikswitch import DOMAIN as QWIKSWITCH, QSEntity from homeassistant.core import callback +from . import DOMAIN as QWIKSWITCH, QSEntity + DEPENDENCIES = [QWIKSWITCH] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/qwikswitch/switch.py b/homeassistant/components/qwikswitch/switch.py index 3b8e6d74df2..ec544df8c75 100644 --- a/homeassistant/components/qwikswitch/switch.py +++ b/homeassistant/components/qwikswitch/switch.py @@ -4,10 +4,10 @@ Support for Qwikswitch relays. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.qwikswitch/ """ -from homeassistant.components.qwikswitch import ( - QSToggleEntity, DOMAIN as QWIKSWITCH) from homeassistant.components.switch import SwitchDevice +from . import DOMAIN as QWIKSWITCH, QSToggleEntity + DEPENDENCIES = [QWIKSWITCH] diff --git a/homeassistant/components/rachio/binary_sensor.py b/homeassistant/components/rachio/binary_sensor.py index 36a32c79c5c..9cf57ea3230 100644 --- a/homeassistant/components/rachio/binary_sensor.py +++ b/homeassistant/components/rachio/binary_sensor.py @@ -8,17 +8,13 @@ from abc import abstractmethod import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.rachio import (DOMAIN as DOMAIN_RACHIO, - KEY_DEVICE_ID, - KEY_STATUS, - KEY_SUBTYPE, - SIGNAL_RACHIO_CONTROLLER_UPDATE, - STATUS_OFFLINE, - STATUS_ONLINE, - SUBTYPE_OFFLINE, - SUBTYPE_ONLINE,) from homeassistant.helpers.dispatcher import dispatcher_connect +from . import ( + DOMAIN as DOMAIN_RACHIO, KEY_DEVICE_ID, KEY_STATUS, KEY_SUBTYPE, + SIGNAL_RACHIO_CONTROLLER_UPDATE, STATUS_OFFLINE, STATUS_ONLINE, + SUBTYPE_OFFLINE, SUBTYPE_ONLINE) + DEPENDENCIES = ['rachio'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 4797aae9a8c..fe584441afd 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -9,26 +9,15 @@ from datetime import timedelta import logging from homeassistant.components.switch import SwitchDevice -from homeassistant.components.rachio import (CONF_MANUAL_RUN_MINS, - DOMAIN as DOMAIN_RACHIO, - KEY_DEVICE_ID, - KEY_ENABLED, - KEY_ID, - KEY_NAME, - KEY_ON, - KEY_SUBTYPE, - KEY_SUMMARY, - KEY_ZONE_ID, - KEY_ZONE_NUMBER, - SIGNAL_RACHIO_CONTROLLER_UPDATE, - SIGNAL_RACHIO_ZONE_UPDATE, - SUBTYPE_ZONE_STARTED, - SUBTYPE_ZONE_STOPPED, - SUBTYPE_ZONE_COMPLETED, - SUBTYPE_SLEEP_MODE_ON, - SUBTYPE_SLEEP_MODE_OFF) from homeassistant.helpers.dispatcher import dispatcher_connect +from . import ( + CONF_MANUAL_RUN_MINS, DOMAIN as DOMAIN_RACHIO, KEY_DEVICE_ID, KEY_ENABLED, + KEY_ID, KEY_NAME, KEY_ON, KEY_SUBTYPE, KEY_SUMMARY, KEY_ZONE_ID, + KEY_ZONE_NUMBER, SIGNAL_RACHIO_CONTROLLER_UPDATE, + SIGNAL_RACHIO_ZONE_UPDATE, SUBTYPE_SLEEP_MODE_OFF, SUBTYPE_SLEEP_MODE_ON, + SUBTYPE_ZONE_COMPLETED, SUBTYPE_ZONE_STARTED, SUBTYPE_ZONE_STOPPED) + DEPENDENCIES = ['rachio'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/radarr/__init__.py b/homeassistant/components/radarr/__init__.py new file mode 100644 index 00000000000..24377725bfc --- /dev/null +++ b/homeassistant/components/radarr/__init__.py @@ -0,0 +1 @@ +"""The radarr component.""" diff --git a/homeassistant/components/sensor/radarr.py b/homeassistant/components/radarr/sensor.py similarity index 100% rename from homeassistant/components/sensor/radarr.py rename to homeassistant/components/radarr/sensor.py diff --git a/homeassistant/components/radiotherm/__init__.py b/homeassistant/components/radiotherm/__init__.py new file mode 100644 index 00000000000..adc8cdbd6ee --- /dev/null +++ b/homeassistant/components/radiotherm/__init__.py @@ -0,0 +1 @@ +"""The radiotherm component.""" diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/radiotherm/climate.py similarity index 98% rename from homeassistant/components/climate/radiotherm.py rename to homeassistant/components/radiotherm/climate.py index bad20884536..4132d3c27c7 100644 --- a/homeassistant/components/climate/radiotherm.py +++ b/homeassistant/components/radiotherm/climate.py @@ -246,8 +246,8 @@ class RadioThermostat(ClimateDevice): try: data = self.device.tstat['raw'] except radiotherm.validate.RadiothermTstatError: - _LOGGER.error('%s (%s) was busy (invalid value returned)', - self._name, self.device.host) + _LOGGER.warning('%s (%s) was busy (invalid value returned)', + self._name, self.device.host) return current_temp = data['temp'] diff --git a/homeassistant/components/sensor/rainbird.py b/homeassistant/components/rainbird/sensor.py similarity index 97% rename from homeassistant/components/sensor/rainbird.py rename to homeassistant/components/rainbird/sensor.py index 1af2b771014..3d0de04e53e 100644 --- a/homeassistant/components/sensor/rainbird.py +++ b/homeassistant/components/rainbird/sensor.py @@ -8,12 +8,13 @@ import logging import voluptuous as vol -from homeassistant.components.rainbird import DATA_RAINBIRD -import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_MONITORED_CONDITIONS from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_MONITORED_CONDITIONS +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from . import DATA_RAINBIRD + DEPENDENCIES = ['rainbird'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/rainbird.py b/homeassistant/components/rainbird/switch.py similarity index 88% rename from homeassistant/components/switch/rainbird.py rename to homeassistant/components/rainbird/switch.py index 86f25102f70..2031769b343 100644 --- a/homeassistant/components/switch/rainbird.py +++ b/homeassistant/components/rainbird/switch.py @@ -9,13 +9,14 @@ import logging import voluptuous as vol -from homeassistant.components.rainbird import DATA_RAINBIRD -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) -from homeassistant.const import (CONF_SWITCHES, CONF_ZONE, - CONF_FRIENDLY_NAME, CONF_TRIGGER_TIME, - CONF_SCAN_INTERVAL) +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.const import ( + CONF_FRIENDLY_NAME, CONF_SCAN_INTERVAL, CONF_SWITCHES, CONF_TRIGGER_TIME, + CONF_ZONE) from homeassistant.helpers import config_validation as cv +from . import DATA_RAINBIRD + DEPENDENCIES = ['rainbird'] DOMAIN = 'rainbird' diff --git a/homeassistant/components/raincloud/binary_sensor.py b/homeassistant/components/raincloud/binary_sensor.py index 810c7d201cb..cb66fc3c6af 100644 --- a/homeassistant/components/raincloud/binary_sensor.py +++ b/homeassistant/components/raincloud/binary_sensor.py @@ -8,12 +8,12 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.raincloud import ( - BINARY_SENSORS, DATA_RAINCLOUD, ICON_MAP, RainCloudEntity) from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) + PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.const import CONF_MONITORED_CONDITIONS +import homeassistant.helpers.config_validation as cv + +from . import BINARY_SENSORS, DATA_RAINCLOUD, ICON_MAP, RainCloudEntity DEPENDENCIES = ['raincloud'] diff --git a/homeassistant/components/raincloud/sensor.py b/homeassistant/components/raincloud/sensor.py index 15a346880f3..8bcccf06171 100644 --- a/homeassistant/components/raincloud/sensor.py +++ b/homeassistant/components/raincloud/sensor.py @@ -8,13 +8,13 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.raincloud import ( - DATA_RAINCLOUD, ICON_MAP, RainCloudEntity, SENSORS) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_MONITORED_CONDITIONS +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.icon import icon_for_battery_level +from . import DATA_RAINCLOUD, ICON_MAP, SENSORS, RainCloudEntity + DEPENDENCIES = ['raincloud'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/raincloud/switch.py b/homeassistant/components/raincloud/switch.py index 1b76e8974b0..3901e1e0bd8 100644 --- a/homeassistant/components/raincloud/switch.py +++ b/homeassistant/components/raincloud/switch.py @@ -3,13 +3,13 @@ import logging import voluptuous as vol +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv -from homeassistant.components.raincloud import ( - ALLOWED_WATERING_TIME, ATTRIBUTION, CONF_WATERING_TIME, - DATA_RAINCLOUD, DEFAULT_WATERING_TIME, RainCloudEntity, SWITCHES) -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION) + +from . import ( + ALLOWED_WATERING_TIME, ATTRIBUTION, CONF_WATERING_TIME, DATA_RAINCLOUD, + DEFAULT_WATERING_TIME, SWITCHES, RainCloudEntity) DEPENDENCIES = ['raincloud'] diff --git a/homeassistant/components/rainmachine/.translations/bg.json b/homeassistant/components/rainmachine/.translations/bg.json new file mode 100644 index 00000000000..80fb5f07f13 --- /dev/null +++ b/homeassistant/components/rainmachine/.translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "ip_address": "\u0410\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/.translations/fr.json b/homeassistant/components/rainmachine/.translations/fr.json new file mode 100644 index 00000000000..64d8f582ad3 --- /dev/null +++ b/homeassistant/components/rainmachine/.translations/fr.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Compte d\u00e9j\u00e0 enregistr\u00e9", + "invalid_credentials": "Informations d'identification invalides" + }, + "step": { + "user": { + "data": { + "ip_address": "Nom d'h\u00f4te ou adresse IP", + "password": "Mot de passe", + "port": "Port" + }, + "title": "Veuillez saisir vos informations" + } + }, + "title": "RainMachine" + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/.translations/hu.json b/homeassistant/components/rainmachine/.translations/hu.json index 0f5b6b71126..d95ec9eaa1b 100644 --- a/homeassistant/components/rainmachine/.translations/hu.json +++ b/homeassistant/components/rainmachine/.translations/hu.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "ip_address": "Kiszolg\u00e1l\u00f3 neve vagy IP c\u00edme", + "ip_address": "Hosztn\u00e9v vagy IP c\u00edm", "password": "Jelsz\u00f3", "port": "Port" }, diff --git a/homeassistant/components/rainmachine/.translations/zh-Hans.json b/homeassistant/components/rainmachine/.translations/zh-Hans.json index e7171ca2867..f3d8308fabf 100644 --- a/homeassistant/components/rainmachine/.translations/zh-Hans.json +++ b/homeassistant/components/rainmachine/.translations/zh-Hans.json @@ -13,6 +13,7 @@ }, "title": "\u586b\u5199\u60a8\u7684\u4fe1\u606f" } - } + }, + "title": "RainMachine" } } \ No newline at end of file diff --git a/homeassistant/components/rainmachine/binary_sensor.py b/homeassistant/components/rainmachine/binary_sensor.py index efae9330365..929dbcf314c 100644 --- a/homeassistant/components/rainmachine/binary_sensor.py +++ b/homeassistant/components/rainmachine/binary_sensor.py @@ -7,13 +7,14 @@ https://home-assistant.io/components/binary_sensor.rainmachine/ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.rainmachine import ( +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ( BINARY_SENSORS, DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, SENSOR_UPDATE_TOPIC, TYPE_FREEZE, TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH, TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity) -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect DEPENDENCIES = ['rainmachine'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rainmachine/const.py b/homeassistant/components/rainmachine/const.py index e0e79e8c160..4d08a871f61 100644 --- a/homeassistant/components/rainmachine/const.py +++ b/homeassistant/components/rainmachine/const.py @@ -1,8 +1,8 @@ """Define constants for the SimpliSafe component.""" -import logging from datetime import timedelta +import logging -LOGGER = logging.getLogger('homeassistant.components.rainmachine') +LOGGER = logging.getLogger('.') DOMAIN = 'rainmachine' diff --git a/homeassistant/components/rainmachine/sensor.py b/homeassistant/components/rainmachine/sensor.py index 86a97bc291c..908daa2c83d 100644 --- a/homeassistant/components/rainmachine/sensor.py +++ b/homeassistant/components/rainmachine/sensor.py @@ -6,12 +6,13 @@ https://home-assistant.io/components/sensor.rainmachine/ """ import logging -from homeassistant.components.rainmachine import ( - DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, SENSOR_UPDATE_TOPIC, SENSORS, - RainMachineEntity) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from . import ( + DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, SENSOR_UPDATE_TOPIC, SENSORS, + RainMachineEntity) + DEPENDENCIES = ['rainmachine'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rainmachine/switch.py b/homeassistant/components/rainmachine/switch.py index e3a1ddab912..6b658c0fcbf 100644 --- a/homeassistant/components/rainmachine/switch.py +++ b/homeassistant/components/rainmachine/switch.py @@ -4,18 +4,19 @@ This component provides support for RainMachine programs and zones. For more details about this component, please refer to the documentation at https://home-assistant.io/components/switch.rainmachine/ """ -import logging from datetime import datetime +import logging -from homeassistant.components.rainmachine import ( - DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, PROGRAM_UPDATE_TOPIC, - ZONE_UPDATE_TOPIC, RainMachineEntity) -from homeassistant.const import ATTR_ID from homeassistant.components.switch import SwitchDevice +from homeassistant.const import ATTR_ID from homeassistant.core import callback from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send) +from . import ( + DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, PROGRAM_UPDATE_TOPIC, + ZONE_UPDATE_TOPIC, RainMachineEntity) + DEPENDENCIES = ['rainmachine'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/random/__init__.py b/homeassistant/components/random/__init__.py new file mode 100644 index 00000000000..01bde80b0c3 --- /dev/null +++ b/homeassistant/components/random/__init__.py @@ -0,0 +1 @@ +"""The random component.""" diff --git a/homeassistant/components/binary_sensor/random.py b/homeassistant/components/random/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/random.py rename to homeassistant/components/random/binary_sensor.py diff --git a/homeassistant/components/sensor/random.py b/homeassistant/components/random/sensor.py similarity index 100% rename from homeassistant/components/sensor/random.py rename to homeassistant/components/random/sensor.py diff --git a/homeassistant/components/raspihats/binary_sensor.py b/homeassistant/components/raspihats/binary_sensor.py index b0ebc2e3579..29fa474f781 100644 --- a/homeassistant/components/raspihats/binary_sensor.py +++ b/homeassistant/components/raspihats/binary_sensor.py @@ -5,13 +5,14 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( PLATFORM_SCHEMA, BinarySensorDevice) -from homeassistant.components.raspihats import ( - CONF_BOARD, CONF_CHANNELS, CONF_I2C_HATS, CONF_INDEX, CONF_INVERT_LOGIC, - I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException) from homeassistant.const import ( CONF_ADDRESS, CONF_DEVICE_CLASS, CONF_NAME, DEVICE_DEFAULT_NAME) import homeassistant.helpers.config_validation as cv +from . import ( + CONF_BOARD, CONF_CHANNELS, CONF_I2C_HATS, CONF_INDEX, CONF_INVERT_LOGIC, + I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['raspihats'] diff --git a/homeassistant/components/raspihats/switch.py b/homeassistant/components/raspihats/switch.py index 26fcda3c8d7..93538682ad8 100644 --- a/homeassistant/components/raspihats/switch.py +++ b/homeassistant/components/raspihats/switch.py @@ -3,14 +3,15 @@ import logging import voluptuous as vol -from homeassistant.components.raspihats import ( - CONF_BOARD, CONF_CHANNELS, CONF_I2C_HATS, CONF_INDEX, CONF_INITIAL_STATE, - CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException) from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.const import CONF_ADDRESS, CONF_NAME, DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity +from . import ( + CONF_BOARD, CONF_CHANNELS, CONF_I2C_HATS, CONF_INDEX, CONF_INITIAL_STATE, + CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException) + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['raspihats'] diff --git a/homeassistant/components/raspyrfm/__init__.py b/homeassistant/components/raspyrfm/__init__.py new file mode 100644 index 00000000000..67522764824 --- /dev/null +++ b/homeassistant/components/raspyrfm/__init__.py @@ -0,0 +1 @@ +"""The raspyrfm component.""" diff --git a/homeassistant/components/switch/raspyrfm.py b/homeassistant/components/raspyrfm/switch.py similarity index 100% rename from homeassistant/components/switch/raspyrfm.py rename to homeassistant/components/raspyrfm/switch.py diff --git a/homeassistant/components/recollect_waste/__init__.py b/homeassistant/components/recollect_waste/__init__.py new file mode 100644 index 00000000000..8ba2fc676f4 --- /dev/null +++ b/homeassistant/components/recollect_waste/__init__.py @@ -0,0 +1 @@ +"""The recollect_waste component.""" diff --git a/homeassistant/components/sensor/recollect_waste.py b/homeassistant/components/recollect_waste/sensor.py similarity index 100% rename from homeassistant/components/sensor/recollect_waste.py rename to homeassistant/components/recollect_waste/sensor.py diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 6c338457b34..0df1fa42ad4 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -25,7 +25,7 @@ from . import migration, purge from .const import DATA_INSTANCE from .util import session_scope -REQUIREMENTS = ['sqlalchemy==1.2.18'] +REQUIREMENTS = ['sqlalchemy==1.3.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 972862e7a9c..f81dd9e736f 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -19,6 +19,11 @@ def migrate_schema(instance): SchemaChanges.change_id.desc()).first() current_version = getattr(res, 'schema_version', None) + if current_version is None: + current_version = _inspect_schema_version(instance.engine, session) + _LOGGER.debug("No schema version found. Inspected version: %s", + current_version) + if current_version == SCHEMA_VERSION: # Clean up if old migration left file if os.path.isfile(progress_path): @@ -32,11 +37,6 @@ def migrate_schema(instance): _LOGGER.warning("Database is about to upgrade. Schema version: %s", current_version) - if current_version is None: - current_version = _inspect_schema_version(instance.engine, session) - _LOGGER.debug("No schema version found. Inspected version: %s", - current_version) - try: for version in range(current_version, SCHEMA_VERSION): new_version = version + 1 diff --git a/homeassistant/components/recswitch/__init__.py b/homeassistant/components/recswitch/__init__.py new file mode 100644 index 00000000000..38006ad3aeb --- /dev/null +++ b/homeassistant/components/recswitch/__init__.py @@ -0,0 +1 @@ +"""The recswitch component.""" diff --git a/homeassistant/components/switch/recswitch.py b/homeassistant/components/recswitch/switch.py similarity index 100% rename from homeassistant/components/switch/recswitch.py rename to homeassistant/components/recswitch/switch.py diff --git a/homeassistant/components/reddit/sensor.py b/homeassistant/components/reddit/sensor.py index 1b6a960669c..3ba43196551 100644 --- a/homeassistant/components/reddit/sensor.py +++ b/homeassistant/components/reddit/sensor.py @@ -15,6 +15,7 @@ _LOGGER = logging.getLogger(__name__) CONF_CLIENT_ID = 'client_id' CONF_CLIENT_SECRET = 'client_secret' +CONF_SORT_BY = 'sort_by' CONF_SUBREDDITS = 'subreddits' ATTR_ID = 'id' @@ -29,6 +30,10 @@ ATTR_URL = 'url' DEFAULT_NAME = 'Reddit' +DOMAIN = 'reddit' + +LIST_TYPES = ['top', 'controversial', 'hot', 'new'] + SCAN_INTERVAL = timedelta(seconds=300) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -37,6 +42,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_SUBREDDITS): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_SORT_BY, default='hot'): + vol.All(cv.string, vol.In(LIST_TYPES)), vol.Optional(CONF_MAXIMUM, default=10): cv.positive_int }) @@ -48,6 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): subreddits = config[CONF_SUBREDDITS] user_agent = '{}_home_assistant_sensor'.format(config[CONF_USERNAME]) limit = config[CONF_MAXIMUM] + sort_by = config[CONF_SORT_BY] try: reddit = praw.Reddit( @@ -63,18 +71,20 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("Reddit error %s", err) return - sensors = [RedditSensor(reddit, sub, limit) for sub in subreddits] + sensors = [RedditSensor(reddit, subreddit, limit, sort_by) + for subreddit in subreddits] add_entities(sensors, True) class RedditSensor(Entity): """Representation of a Reddit sensor.""" - def __init__(self, reddit, subreddit: str, limit: int): + def __init__(self, reddit, subreddit: str, limit: int, sort_by: str): """Initialize the Reddit sensor.""" self._reddit = reddit - self._limit = limit self._subreddit = subreddit + self._limit = limit + self._sort_by = sort_by self._subreddit_data = [] @@ -93,7 +103,8 @@ class RedditSensor(Entity): """Return the state attributes.""" return { ATTR_SUBREDDIT: self._subreddit, - ATTR_POSTS: self._subreddit_data + ATTR_POSTS: self._subreddit_data, + CONF_SORT_BY: self._sort_by } @property @@ -109,17 +120,19 @@ class RedditSensor(Entity): try: subreddit = self._reddit.subreddit(self._subreddit) + if hasattr(subreddit, self._sort_by): + method_to_call = getattr(subreddit, self._sort_by) - for submission in subreddit.top(limit=self._limit): - self._subreddit_data.append({ - ATTR_ID: submission.id, - ATTR_URL: submission.url, - ATTR_TITLE: submission.title, - ATTR_SCORE: submission.score, - ATTR_COMMENTS_NUMBER: submission.num_comments, - ATTR_CREATED: submission.created, - ATTR_BODY: submission.selftext - }) + for submission in method_to_call(limit=self._limit): + self._subreddit_data.append({ + ATTR_ID: submission.id, + ATTR_URL: submission.url, + ATTR_TITLE: submission.title, + ATTR_SCORE: submission.score, + ATTR_COMMENTS_NUMBER: submission.num_comments, + ATTR_CREATED: submission.created, + ATTR_BODY: submission.selftext + }) except praw.exceptions.PRAWException as err: _LOGGER.error("Reddit error %s", err) diff --git a/homeassistant/components/rejseplanen/__init__.py b/homeassistant/components/rejseplanen/__init__.py new file mode 100644 index 00000000000..c67ab71dbb9 --- /dev/null +++ b/homeassistant/components/rejseplanen/__init__.py @@ -0,0 +1 @@ +"""The rejseplanen component.""" diff --git a/homeassistant/components/sensor/rejseplanen.py b/homeassistant/components/rejseplanen/sensor.py similarity index 100% rename from homeassistant/components/sensor/rejseplanen.py rename to homeassistant/components/rejseplanen/sensor.py diff --git a/homeassistant/components/rest/__init__.py b/homeassistant/components/rest/__init__.py new file mode 100644 index 00000000000..fcdf39e8398 --- /dev/null +++ b/homeassistant/components/rest/__init__.py @@ -0,0 +1 @@ +"""The rest component.""" diff --git a/homeassistant/components/binary_sensor/rest.py b/homeassistant/components/rest/binary_sensor.py similarity index 91% rename from homeassistant/components/binary_sensor/rest.py rename to homeassistant/components/rest/binary_sensor.py index 304ed701148..1a94159290f 100644 --- a/homeassistant/components/binary_sensor/rest.py +++ b/homeassistant/components/rest/binary_sensor.py @@ -6,19 +6,20 @@ https://home-assistant.io/components/binary_sensor.rest/ """ import logging -import voluptuous as vol from requests.auth import HTTPBasicAuth, HTTPDigestAuth +import voluptuous as vol from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA) -from homeassistant.components.sensor.rest import RestData + DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.const import ( - CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE, - CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT, - CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION, - HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS) -import homeassistant.helpers.config_validation as cv + CONF_AUTHENTICATION, CONF_DEVICE_CLASS, CONF_HEADERS, CONF_METHOD, + CONF_NAME, CONF_PASSWORD, CONF_PAYLOAD, CONF_RESOURCE, CONF_TIMEOUT, + CONF_USERNAME, CONF_VALUE_TEMPLATE, CONF_VERIFY_SSL, + HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION) from homeassistant.exceptions import PlatformNotReady +import homeassistant.helpers.config_validation as cv + +from .sensor import RestData _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/notify/rest.py b/homeassistant/components/rest/notify.py similarity index 65% rename from homeassistant/components/notify/rest.py rename to homeassistant/components/rest/notify.py index df25045c6ec..de75db83848 100644 --- a/homeassistant/components/notify/rest.py +++ b/homeassistant/components/rest/notify.py @@ -9,13 +9,16 @@ import logging import requests import voluptuous as vol -from homeassistant.components.notify import ( - ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, BaseNotificationService, - PLATFORM_SCHEMA) -from homeassistant.const import (CONF_RESOURCE, CONF_METHOD, CONF_NAME, - CONF_HEADERS) +from homeassistant.const import ( + CONF_AUTHENTICATION, CONF_HEADERS, CONF_METHOD, CONF_NAME, CONF_PASSWORD, + CONF_RESOURCE, CONF_USERNAME, CONF_VERIFY_SSL, HTTP_BASIC_AUTHENTICATION, + HTTP_DIGEST_AUTHENTICATION) import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import ( + ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, + BaseNotificationService) + CONF_DATA = 'data' CONF_DATA_TEMPLATE = 'data_template' CONF_MESSAGE_PARAMETER_NAME = 'message_param_name' @@ -23,6 +26,7 @@ CONF_TARGET_PARAMETER_NAME = 'target_param_name' CONF_TITLE_PARAMETER_NAME = 'title_param_name' DEFAULT_MESSAGE_PARAM_NAME = 'message' DEFAULT_METHOD = 'GET' +DEFAULT_VERIFY_SSL = True PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_RESOURCE): cv.url, @@ -35,7 +39,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_TARGET_PARAMETER_NAME): cv.string, vol.Optional(CONF_TITLE_PARAMETER_NAME): cv.string, vol.Optional(CONF_DATA): dict, - vol.Optional(CONF_DATA_TEMPLATE): {cv.match_all: cv.template_complex} + vol.Optional(CONF_DATA_TEMPLATE): {cv.match_all: cv.template_complex}, + vol.Optional(CONF_AUTHENTICATION): + vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]), + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, }) _LOGGER = logging.getLogger(__name__) @@ -51,17 +60,30 @@ def get_service(hass, config, discovery_info=None): target_param_name = config.get(CONF_TARGET_PARAMETER_NAME) data = config.get(CONF_DATA) data_template = config.get(CONF_DATA_TEMPLATE) + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + verify_ssl = config.get(CONF_VERIFY_SSL) + + if username and password: + if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION: + auth = requests.auth.HTTPDigestAuth(username, password) + else: + auth = requests.auth.HTTPBasicAuth(username, password) + else: + auth = None return RestNotificationService( hass, resource, method, headers, message_param_name, - title_param_name, target_param_name, data, data_template) + title_param_name, target_param_name, data, data_template, auth, + verify_ssl) class RestNotificationService(BaseNotificationService): """Implementation of a notification service for REST.""" def __init__(self, hass, resource, method, headers, message_param_name, - title_param_name, target_param_name, data, data_template): + title_param_name, target_param_name, data, data_template, + auth, verify_ssl): """Initialize the service.""" self._resource = resource self._hass = hass @@ -72,6 +94,8 @@ class RestNotificationService(BaseNotificationService): self._target_param_name = target_param_name self._data = data self._data_template = data_template + self._auth = auth + self._verify_ssl = verify_ssl def send_message(self, message="", **kwargs): """Send a message to a user.""" @@ -104,16 +128,30 @@ class RestNotificationService(BaseNotificationService): if self._method == 'POST': response = requests.post(self._resource, headers=self._headers, - data=data, timeout=10) + data=data, timeout=10, auth=self._auth, + verify=self._verify_ssl) elif self._method == 'POST_JSON': response = requests.post(self._resource, headers=self._headers, - json=data, timeout=10) + json=data, timeout=10, auth=self._auth, + verify=self._verify_ssl) else: # default GET response = requests.get(self._resource, headers=self._headers, - params=data, timeout=10) + params=data, timeout=10, auth=self._auth, + verify=self._verify_ssl) - success_codes = (200, 201, 202, 203, 204, 205, 206, 207, 208, 226) - if response.status_code not in success_codes: + if response.status_code >= 500 and response.status_code < 600: _LOGGER.exception( - "Error sending message. Response %d: %s:", + "Server error. Response %d: %s:", + response.status_code, response.reason) + elif response.status_code >= 400 and response.status_code < 500: + _LOGGER.exception( + "Client error. Response %d: %s:", + response.status_code, response.reason) + elif response.status_code >= 200 and response.status_code < 300: + _LOGGER.debug( + "Success. Response %d: %s:", + response.status_code, response.reason) + else: + _LOGGER.debug( + "Response %d: %s:", response.status_code, response.reason) diff --git a/homeassistant/components/sensor/rest.py b/homeassistant/components/rest/sensor.py similarity index 100% rename from homeassistant/components/sensor/rest.py rename to homeassistant/components/rest/sensor.py diff --git a/homeassistant/components/switch/rest.py b/homeassistant/components/rest/switch.py similarity index 100% rename from homeassistant/components/switch/rest.py rename to homeassistant/components/rest/switch.py diff --git a/homeassistant/components/binary_sensor/rflink.py b/homeassistant/components/rflink/binary_sensor.py similarity index 94% rename from homeassistant/components/binary_sensor/rflink.py rename to homeassistant/components/rflink/binary_sensor.py index 73b912d62da..5318642a5b1 100644 --- a/homeassistant/components/binary_sensor/rflink.py +++ b/homeassistant/components/rflink/binary_sensor.py @@ -10,13 +10,12 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) -from homeassistant.components.rflink import ( - CONF_ALIASES, CONF_DEVICES, RflinkDevice) -from homeassistant.const import ( - CONF_FORCE_UPDATE, CONF_NAME, CONF_DEVICE_CLASS) +from homeassistant.const import CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, CONF_NAME import homeassistant.helpers.config_validation as cv import homeassistant.helpers.event as evt +from . import CONF_ALIASES, CONF_DEVICES, RflinkDevice + CONF_OFF_DELAY = 'off_delay' DEFAULT_FORCE_UPDATE = False diff --git a/homeassistant/components/cover/rflink.py b/homeassistant/components/rflink/cover.py similarity index 96% rename from homeassistant/components/cover/rflink.py rename to homeassistant/components/rflink/cover.py index cdc7cac3adb..f91ef1cc682 100644 --- a/homeassistant/components/cover/rflink.py +++ b/homeassistant/components/rflink/cover.py @@ -8,15 +8,15 @@ import logging import voluptuous as vol -from homeassistant.components.rflink import ( +from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice +from homeassistant.const import CONF_NAME, STATE_OPEN +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.restore_state import RestoreEntity + +from . import ( CONF_ALIASES, CONF_DEVICE_DEFAULTS, CONF_DEVICES, CONF_FIRE_EVENT, CONF_GROUP, CONF_GROUP_ALIASES, CONF_NOGROUP_ALIASES, CONF_SIGNAL_REPETITIONS, DEVICE_DEFAULTS_SCHEMA, RflinkCommand) -from homeassistant.components.cover import ( - CoverDevice, PLATFORM_SCHEMA) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.const import CONF_NAME, STATE_OPEN DEPENDENCIES = ['rflink'] diff --git a/homeassistant/components/light/rflink.py b/homeassistant/components/rflink/light.py similarity index 98% rename from homeassistant/components/light/rflink.py rename to homeassistant/components/rflink/light.py index 726433b4f70..cdb34328b51 100644 --- a/homeassistant/components/light/rflink.py +++ b/homeassistant/components/rflink/light.py @@ -10,15 +10,15 @@ import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light) -from homeassistant.components.rflink import ( +from homeassistant.const import CONF_NAME, CONF_TYPE +import homeassistant.helpers.config_validation as cv + +from . import ( CONF_ALIASES, CONF_ALIASSES, CONF_AUTOMATIC_ADD, CONF_DEVICE_DEFAULTS, CONF_DEVICES, CONF_FIRE_EVENT, CONF_GROUP, CONF_GROUP_ALIASES, CONF_GROUP_ALIASSES, CONF_NOGROUP_ALIASES, CONF_NOGROUP_ALIASSES, CONF_SIGNAL_REPETITIONS, DATA_DEVICE_REGISTER, DEVICE_DEFAULTS_SCHEMA, - EVENT_KEY_COMMAND, EVENT_KEY_ID, SwitchableRflinkDevice, - remove_deprecated) -import homeassistant.helpers.config_validation as cv -from homeassistant.const import (CONF_NAME, CONF_TYPE) + EVENT_KEY_COMMAND, EVENT_KEY_ID, SwitchableRflinkDevice, remove_deprecated) DEPENDENCIES = ['rflink'] diff --git a/homeassistant/components/sensor/rflink.py b/homeassistant/components/rflink/sensor.py similarity index 92% rename from homeassistant/components/sensor/rflink.py rename to homeassistant/components/rflink/sensor.py index f3ec776fda8..e46cc09d0ba 100644 --- a/homeassistant/components/sensor/rflink.py +++ b/homeassistant/components/rflink/sensor.py @@ -8,17 +8,17 @@ import logging import voluptuous as vol -from homeassistant.components.rflink import ( - CONF_ALIASES, CONF_ALIASSES, CONF_AUTOMATIC_ADD, CONF_DEVICES, - DATA_DEVICE_REGISTER, DATA_ENTITY_LOOKUP, EVENT_KEY_ID, - EVENT_KEY_SENSOR, EVENT_KEY_UNIT, RflinkDevice, remove_deprecated, - SIGNAL_AVAILABILITY, SIGNAL_HANDLE_EVENT, TMP_ENTITY) -from homeassistant.components.sensor import ( - PLATFORM_SCHEMA) -import homeassistant.helpers.config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, CONF_UNIT_OF_MEASUREMENT) -from homeassistant.helpers.dispatcher import (async_dispatcher_connect) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ( + CONF_ALIASES, CONF_ALIASSES, CONF_AUTOMATIC_ADD, CONF_DEVICES, + DATA_DEVICE_REGISTER, DATA_ENTITY_LOOKUP, EVENT_KEY_ID, EVENT_KEY_SENSOR, + EVENT_KEY_UNIT, SIGNAL_AVAILABILITY, SIGNAL_HANDLE_EVENT, TMP_ENTITY, + RflinkDevice, remove_deprecated) DEPENDENCIES = ['rflink'] diff --git a/homeassistant/components/switch/rflink.py b/homeassistant/components/rflink/switch.py similarity index 95% rename from homeassistant/components/switch/rflink.py rename to homeassistant/components/rflink/switch.py index 25e4e367fdf..a1470bf115f 100644 --- a/homeassistant/components/switch/rflink.py +++ b/homeassistant/components/rflink/switch.py @@ -8,15 +8,15 @@ import logging import voluptuous as vol -from homeassistant.components.rflink import ( +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv + +from . import ( CONF_ALIASES, CONF_ALIASSES, CONF_DEVICE_DEFAULTS, CONF_DEVICES, CONF_FIRE_EVENT, CONF_GROUP, CONF_GROUP_ALIASES, CONF_GROUP_ALIASSES, CONF_NOGROUP_ALIASES, CONF_NOGROUP_ALIASSES, CONF_SIGNAL_REPETITIONS, DEVICE_DEFAULTS_SCHEMA, SwitchableRflinkDevice, remove_deprecated) -from homeassistant.components.switch import ( - PLATFORM_SCHEMA, SwitchDevice) -import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_NAME DEPENDENCIES = ['rflink'] diff --git a/homeassistant/components/rfxtrx/binary_sensor.py b/homeassistant/components/rfxtrx/binary_sensor.py index 9a49bd02b97..d548897fb80 100644 --- a/homeassistant/components/rfxtrx/binary_sensor.py +++ b/homeassistant/components/rfxtrx/binary_sensor.py @@ -6,15 +6,14 @@ import voluptuous as vol from homeassistant.components import rfxtrx from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) -from homeassistant.components.rfxtrx import ( - ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_DEVICES, - CONF_FIRE_EVENT, CONF_OFF_DELAY) from homeassistant.const import ( CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_DEVICE_CLASS, CONF_NAME) -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import event as evt -from homeassistant.util import dt as dt_util -from homeassistant.util import slugify +from homeassistant.helpers import config_validation as cv, event as evt +from homeassistant.util import dt as dt_util, slugify + +from . import ( + ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_DEVICES, + CONF_FIRE_EVENT, CONF_OFF_DELAY) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rfxtrx/cover.py b/homeassistant/components/rfxtrx/cover.py index 5a657923683..7ac0e2aa43f 100644 --- a/homeassistant/components/rfxtrx/cover.py +++ b/homeassistant/components/rfxtrx/cover.py @@ -2,13 +2,14 @@ import voluptuous as vol from homeassistant.components import rfxtrx -from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA +from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice from homeassistant.const import CONF_NAME -from homeassistant.components.rfxtrx import ( - CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, DEFAULT_SIGNAL_REPETITIONS, - CONF_SIGNAL_REPETITIONS, CONF_DEVICES) from homeassistant.helpers import config_validation as cv +from . import ( + CONF_AUTOMATIC_ADD, CONF_DEVICES, CONF_FIRE_EVENT, CONF_SIGNAL_REPETITIONS, + DEFAULT_SIGNAL_REPETITIONS) + DEPENDENCIES = ['rfxtrx'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/rfxtrx/light.py b/homeassistant/components/rfxtrx/light.py index d0b75c2f962..3320a67214e 100644 --- a/homeassistant/components/rfxtrx/light.py +++ b/homeassistant/components/rfxtrx/light.py @@ -5,13 +5,14 @@ import voluptuous as vol from homeassistant.components import rfxtrx from homeassistant.components.light import ( - ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, PLATFORM_SCHEMA) + ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light) from homeassistant.const import CONF_NAME -from homeassistant.components.rfxtrx import ( - CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, DEFAULT_SIGNAL_REPETITIONS, - CONF_SIGNAL_REPETITIONS, CONF_DEVICES) from homeassistant.helpers import config_validation as cv +from . import ( + CONF_AUTOMATIC_ADD, CONF_DEVICES, CONF_FIRE_EVENT, CONF_SIGNAL_REPETITIONS, + DEFAULT_SIGNAL_REPETITIONS) + DEPENDENCIES = ['rfxtrx'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rfxtrx/sensor.py b/homeassistant/components/rfxtrx/sensor.py index 74c64635563..cc54320cb67 100644 --- a/homeassistant/components/rfxtrx/sensor.py +++ b/homeassistant/components/rfxtrx/sensor.py @@ -4,15 +4,16 @@ import logging import voluptuous as vol from homeassistant.components import rfxtrx -from homeassistant.components.rfxtrx import ( - ATTR_DATA_TYPE, ATTR_FIRE_EVENT, CONF_AUTOMATIC_ADD, CONF_DATA_TYPE, - CONF_DEVICES, CONF_FIRE_EVENT, DATA_TYPES) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import slugify +from . import ( + ATTR_DATA_TYPE, ATTR_FIRE_EVENT, CONF_AUTOMATIC_ADD, CONF_DATA_TYPE, + CONF_DEVICES, CONF_FIRE_EVENT, DATA_TYPES) + DEPENDENCIES = ['rfxtrx'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py index 141cf2c2c1a..908c07ea745 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -4,12 +4,13 @@ import logging import voluptuous as vol from homeassistant.components import rfxtrx -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA -from homeassistant.components.rfxtrx import ( - CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, DEFAULT_SIGNAL_REPETITIONS, - CONF_SIGNAL_REPETITIONS, CONF_DEVICES) -from homeassistant.helpers import config_validation as cv +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import CONF_NAME +from homeassistant.helpers import config_validation as cv + +from . import ( + CONF_AUTOMATIC_ADD, CONF_DEVICES, CONF_FIRE_EVENT, CONF_SIGNAL_REPETITIONS, + DEFAULT_SIGNAL_REPETITIONS) DEPENDENCIES = ['rfxtrx'] diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index 94f3be305fa..74da7a9d542 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.const import CONF_PASSWORD, CONF_USERNAME import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['ring_doorbell==0.2.2'] +REQUIREMENTS = ['ring_doorbell==0.2.3'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/ring.py b/homeassistant/components/ring/binary_sensor.py similarity index 96% rename from homeassistant/components/binary_sensor/ring.py rename to homeassistant/components/ring/binary_sensor.py index 79fc61a62d4..bcc365a2e83 100644 --- a/homeassistant/components/binary_sensor/ring.py +++ b/homeassistant/components/ring/binary_sensor.py @@ -4,20 +4,18 @@ This component provides HA sensor support for Ring Door Bell/Chimes. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.ring/ """ -import logging from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv - -from homeassistant.components.ring import ( - ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DATA_RING) - -from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) + PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) +import homeassistant.helpers.config_validation as cv + +from . import ATTRIBUTION, DATA_RING, DEFAULT_ENTITY_NAMESPACE DEPENDENCIES = ['ring'] diff --git a/homeassistant/components/camera/ring.py b/homeassistant/components/ring/camera.py similarity index 94% rename from homeassistant/components/camera/ring.py rename to homeassistant/components/ring/camera.py index ce9ceb7b76f..8970e61b1a1 100644 --- a/homeassistant/components/camera/ring.py +++ b/homeassistant/components/ring/camera.py @@ -5,21 +5,20 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/camera.ring/ """ import asyncio -import logging - from datetime import timedelta +import logging import voluptuous as vol -from homeassistant.helpers import config_validation as cv -from homeassistant.components.ring import ( - DATA_RING, ATTRIBUTION, NOTIFICATION_ID) -from homeassistant.components.camera import Camera, PLATFORM_SCHEMA +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.const import ATTR_ATTRIBUTION, CONF_SCAN_INTERVAL +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream from homeassistant.util import dt as dt_util +from . import ATTRIBUTION, DATA_RING, NOTIFICATION_ID + CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' DEPENDENCIES = ['ring', 'ffmpeg'] @@ -116,7 +115,7 @@ class RingCam(Camera): async def async_camera_image(self): """Return a still image response from the camera.""" - from haffmpeg import ImageFrame, IMAGE_JPEG + from haffmpeg.tools import ImageFrame, IMAGE_JPEG ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop) if self._video_url is None: @@ -129,7 +128,7 @@ class RingCam(Camera): async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" - from haffmpeg import CameraMjpeg + from haffmpeg.camera import CameraMjpeg if self._video_url is None: return @@ -139,8 +138,9 @@ class RingCam(Camera): self._video_url, extra_cmd=self._ffmpeg_arguments) try: + stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream, + self.hass, request, stream_reader, self._ffmpeg.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/sensor/ring.py b/homeassistant/components/ring/sensor.py similarity index 97% rename from homeassistant/components/sensor/ring.py rename to homeassistant/components/ring/sensor.py index d58e0cf8b3f..5e323d89ad8 100644 --- a/homeassistant/components/sensor/ring.py +++ b/homeassistant/components/ring/sensor.py @@ -9,16 +9,15 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.ring import ( - ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DATA_RING) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS, - ATTR_ATTRIBUTION) + ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level +from . import ATTRIBUTION, DATA_RING, DEFAULT_ENTITY_NAMESPACE + DEPENDENCIES = ['ring'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ripple/__init__.py b/homeassistant/components/ripple/__init__.py new file mode 100644 index 00000000000..d55a7d09487 --- /dev/null +++ b/homeassistant/components/ripple/__init__.py @@ -0,0 +1 @@ +"""The ripple component.""" diff --git a/homeassistant/components/sensor/ripple.py b/homeassistant/components/ripple/sensor.py similarity index 100% rename from homeassistant/components/sensor/ripple.py rename to homeassistant/components/ripple/sensor.py diff --git a/homeassistant/components/ritassist/__init__.py b/homeassistant/components/ritassist/__init__.py new file mode 100644 index 00000000000..653e18874d3 --- /dev/null +++ b/homeassistant/components/ritassist/__init__.py @@ -0,0 +1 @@ +"""The ritassist component.""" diff --git a/homeassistant/components/device_tracker/ritassist.py b/homeassistant/components/ritassist/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/ritassist.py rename to homeassistant/components/ritassist/device_tracker.py diff --git a/homeassistant/components/rmvtransport/__init__.py b/homeassistant/components/rmvtransport/__init__.py new file mode 100644 index 00000000000..abaee33e926 --- /dev/null +++ b/homeassistant/components/rmvtransport/__init__.py @@ -0,0 +1 @@ +"""The rmvtransport component.""" diff --git a/homeassistant/components/sensor/rmvtransport.py b/homeassistant/components/rmvtransport/sensor.py similarity index 100% rename from homeassistant/components/sensor/rmvtransport.py rename to homeassistant/components/rmvtransport/sensor.py diff --git a/homeassistant/components/rocketchat/__init__.py b/homeassistant/components/rocketchat/__init__.py new file mode 100644 index 00000000000..1d2c281f661 --- /dev/null +++ b/homeassistant/components/rocketchat/__init__.py @@ -0,0 +1 @@ +"""The rocketchat component.""" diff --git a/homeassistant/components/notify/rocketchat.py b/homeassistant/components/rocketchat/notify.py similarity index 92% rename from homeassistant/components/notify/rocketchat.py rename to homeassistant/components/rocketchat/notify.py index e9b481b1cf3..8bf1e172264 100644 --- a/homeassistant/components/notify/rocketchat.py +++ b/homeassistant/components/rocketchat/notify.py @@ -8,11 +8,12 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_URL, CONF_USERNAME, CONF_PASSWORD, CONF_ROOM) -from homeassistant.components.notify import ( - ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService) + CONF_PASSWORD, CONF_ROOM, CONF_URL, CONF_USERNAME) +import homeassistant.helpers.config_validation as cv + +from homeassistant.components.notify import (ATTR_DATA, PLATFORM_SCHEMA, + BaseNotificationService) REQUIREMENTS = ['rocketchat-API==0.6.1'] diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py new file mode 100644 index 00000000000..c0e5f68483e --- /dev/null +++ b/homeassistant/components/roomba/__init__.py @@ -0,0 +1 @@ +"""The roomba component.""" diff --git a/homeassistant/components/vacuum/roomba.py b/homeassistant/components/roomba/vacuum.py similarity index 100% rename from homeassistant/components/vacuum/roomba.py rename to homeassistant/components/roomba/vacuum.py diff --git a/homeassistant/components/rova/__init__.py b/homeassistant/components/rova/__init__.py new file mode 100644 index 00000000000..411ea6c7239 --- /dev/null +++ b/homeassistant/components/rova/__init__.py @@ -0,0 +1 @@ +"""The rova component.""" diff --git a/homeassistant/components/sensor/rova.py b/homeassistant/components/rova/sensor.py similarity index 100% rename from homeassistant/components/sensor/rova.py rename to homeassistant/components/rova/sensor.py diff --git a/homeassistant/components/rpi_camera/__init__.py b/homeassistant/components/rpi_camera/__init__.py new file mode 100644 index 00000000000..04638e463a1 --- /dev/null +++ b/homeassistant/components/rpi_camera/__init__.py @@ -0,0 +1 @@ +"""The rpi_camera component.""" diff --git a/homeassistant/components/camera/rpi_camera.py b/homeassistant/components/rpi_camera/camera.py similarity index 100% rename from homeassistant/components/camera/rpi_camera.py rename to homeassistant/components/rpi_camera/camera.py diff --git a/homeassistant/components/rpi_gpio_pwm/__init__.py b/homeassistant/components/rpi_gpio_pwm/__init__.py new file mode 100644 index 00000000000..46aa24c12e6 --- /dev/null +++ b/homeassistant/components/rpi_gpio_pwm/__init__.py @@ -0,0 +1 @@ +"""The rpi_gpio_pwm component.""" diff --git a/homeassistant/components/light/rpi_gpio_pwm.py b/homeassistant/components/rpi_gpio_pwm/light.py similarity index 100% rename from homeassistant/components/light/rpi_gpio_pwm.py rename to homeassistant/components/rpi_gpio_pwm/light.py diff --git a/homeassistant/components/rpi_rf/__init__.py b/homeassistant/components/rpi_rf/__init__.py new file mode 100644 index 00000000000..6e4d58099d9 --- /dev/null +++ b/homeassistant/components/rpi_rf/__init__.py @@ -0,0 +1 @@ +"""The rpi_rf component.""" diff --git a/homeassistant/components/switch/rpi_rf.py b/homeassistant/components/rpi_rf/switch.py similarity index 98% rename from homeassistant/components/switch/rpi_rf.py rename to homeassistant/components/rpi_rf/switch.py index e7f14685eb2..6844cb0f383 100644 --- a/homeassistant/components/switch/rpi_rf.py +++ b/homeassistant/components/rpi_rf/switch.py @@ -4,6 +4,7 @@ Allows to configure a switch using a 433MHz module via GPIO on a Raspberry Pi. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.rpi_rf/ """ +import importlib import logging import voluptuous as vol @@ -47,7 +48,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=no-member def setup_platform(hass, config, add_entities, discovery_info=None): """Find and return switches controlled by a generic RF device via GPIO.""" - import rpi_rf + rpi_rf = importlib.import_module('rpi_rf') from threading import RLock gpio = config.get(CONF_GPIO) diff --git a/homeassistant/components/rtorrent/__init__.py b/homeassistant/components/rtorrent/__init__.py new file mode 100644 index 00000000000..adc4d329539 --- /dev/null +++ b/homeassistant/components/rtorrent/__init__.py @@ -0,0 +1 @@ +"""The rtorrent component.""" diff --git a/homeassistant/components/sensor/rtorrent.py b/homeassistant/components/rtorrent/sensor.py similarity index 100% rename from homeassistant/components/sensor/rtorrent.py rename to homeassistant/components/rtorrent/sensor.py diff --git a/homeassistant/components/russound_rio/__init__.py b/homeassistant/components/russound_rio/__init__.py new file mode 100644 index 00000000000..6d7fe3b1215 --- /dev/null +++ b/homeassistant/components/russound_rio/__init__.py @@ -0,0 +1 @@ +"""The russound_rio component.""" diff --git a/homeassistant/components/media_player/russound_rio.py b/homeassistant/components/russound_rio/media_player.py similarity index 100% rename from homeassistant/components/media_player/russound_rio.py rename to homeassistant/components/russound_rio/media_player.py diff --git a/homeassistant/components/russound_rnet/__init__.py b/homeassistant/components/russound_rnet/__init__.py new file mode 100644 index 00000000000..04bb6c50f7f --- /dev/null +++ b/homeassistant/components/russound_rnet/__init__.py @@ -0,0 +1 @@ +"""The russound_rnet component.""" diff --git a/homeassistant/components/media_player/russound_rnet.py b/homeassistant/components/russound_rnet/media_player.py similarity index 100% rename from homeassistant/components/media_player/russound_rnet.py rename to homeassistant/components/russound_rnet/media_player.py diff --git a/homeassistant/components/ruter/__init__.py b/homeassistant/components/ruter/__init__.py new file mode 100644 index 00000000000..84e25904d9e --- /dev/null +++ b/homeassistant/components/ruter/__init__.py @@ -0,0 +1 @@ +"""The ruter component.""" diff --git a/homeassistant/components/sensor/ruter.py b/homeassistant/components/ruter/sensor.py similarity index 100% rename from homeassistant/components/sensor/ruter.py rename to homeassistant/components/ruter/sensor.py diff --git a/homeassistant/components/sabnzbd/sensor.py b/homeassistant/components/sabnzbd/sensor.py index ca8fc64eea4..4968725a4be 100644 --- a/homeassistant/components/sabnzbd/sensor.py +++ b/homeassistant/components/sabnzbd/sensor.py @@ -1,11 +1,11 @@ """Support for monitoring an SABnzbd NZB client.""" import logging -from homeassistant.components.sabnzbd import DATA_SABNZBD, \ - SIGNAL_SABNZBD_UPDATED, SENSOR_TYPES from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity +from . import DATA_SABNZBD, SENSOR_TYPES, SIGNAL_SABNZBD_UPDATED + DEPENDENCIES = ['sabnzbd'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/samsungtv/__init__.py b/homeassistant/components/samsungtv/__init__.py new file mode 100644 index 00000000000..e43ea1ba984 --- /dev/null +++ b/homeassistant/components/samsungtv/__init__.py @@ -0,0 +1 @@ +"""The samsungtv component.""" diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/samsungtv/media_player.py similarity index 100% rename from homeassistant/components/media_player/samsungtv.py rename to homeassistant/components/samsungtv/media_player.py diff --git a/homeassistant/components/satel_integra/__init__.py b/homeassistant/components/satel_integra/__init__.py index 93f157cd5ec..5f5c43d961f 100644 --- a/homeassistant/components/satel_integra/__init__.py +++ b/homeassistant/components/satel_integra/__init__.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_HOST from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import async_load_platform @@ -23,7 +23,6 @@ DOMAIN = 'satel_integra' DATA_SATEL = 'satel_integra' -CONF_DEVICE_HOST = 'host' CONF_DEVICE_PORT = 'port' CONF_DEVICE_PARTITION = 'partition' CONF_ARM_HOME_MODE = 'arm_home_mode' @@ -48,7 +47,7 @@ ZONE_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_DEVICE_HOST): cv.string, + vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_DEVICE_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_DEVICE_PARTITION, default=DEFAULT_DEVICE_PARTITION): cv.positive_int, @@ -68,7 +67,7 @@ async def async_setup(hass, config): zones = conf.get(CONF_ZONES) outputs = conf.get(CONF_OUTPUTS) - host = conf.get(CONF_DEVICE_HOST) + host = conf.get(CONF_HOST) port = conf.get(CONF_DEVICE_PORT) partition = conf.get(CONF_DEVICE_PARTITION) diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 35eedabd58a..86392c0902d 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -5,8 +5,7 @@ import logging import voluptuous as vol -from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_PLATFORM, SERVICE_TURN_ON) +from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM, SERVICE_TURN_ON import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent @@ -28,9 +27,8 @@ def _hass_domain_validator(config): def _platform_validator(config): """Validate it is a valid platform.""" try: - platform = importlib.import_module( - 'homeassistant.components.scene.{}'.format( - config[CONF_PLATFORM])) + platform = importlib.import_module('.{}'.format(config[CONF_PLATFORM]), + __name__) except ImportError: try: platform = importlib.import_module( diff --git a/homeassistant/components/scrape/__init__.py b/homeassistant/components/scrape/__init__.py new file mode 100644 index 00000000000..f9222c126b5 --- /dev/null +++ b/homeassistant/components/scrape/__init__.py @@ -0,0 +1 @@ +"""The scrape component.""" diff --git a/homeassistant/components/sensor/scrape.py b/homeassistant/components/scrape/sensor.py similarity index 98% rename from homeassistant/components/sensor/scrape.py rename to homeassistant/components/scrape/sensor.py index 70dfae392be..a6d16852df3 100644 --- a/homeassistant/components/sensor/scrape.py +++ b/homeassistant/components/scrape/sensor.py @@ -10,7 +10,7 @@ import voluptuous as vol from requests.auth import HTTPBasicAuth, HTTPDigestAuth from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.components.sensor.rest import RestData +from homeassistant.components.rest.sensor import RestData from homeassistant.const import ( CONF_NAME, CONF_RESOURCE, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, CONF_VERIFY_SSL, CONF_USERNAME, CONF_HEADERS, diff --git a/homeassistant/components/season/.translations/sensor.af.json b/homeassistant/components/season/.translations/sensor.af.json new file mode 100644 index 00000000000..0dbe4a131ee --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.af.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Herfs", + "spring": "Lente", + "summer": "Somer", + "winter": "Winter" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.bg.json b/homeassistant/components/season/.translations/sensor.bg.json new file mode 100644 index 00000000000..e3865ca42e5 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.bg.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "\u0415\u0441\u0435\u043d", + "spring": "\u041f\u0440\u043e\u043b\u0435\u0442", + "summer": "\u041b\u044f\u0442\u043e", + "winter": "\u0417\u0438\u043c\u0430" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.ca.json b/homeassistant/components/season/.translations/sensor.ca.json new file mode 100644 index 00000000000..9bce187ec65 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.ca.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Tardor", + "spring": "Primavera", + "summer": "Estiu", + "winter": "Hivern" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.cs.json b/homeassistant/components/season/.translations/sensor.cs.json new file mode 100644 index 00000000000..e2d7e7919be --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.cs.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Podzim", + "spring": "Jaro", + "summer": "L\u00e9to", + "winter": "Zima" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.cy.json b/homeassistant/components/season/.translations/sensor.cy.json new file mode 100644 index 00000000000..0d1553ac3ea --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.cy.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Hydref", + "spring": "Gwanwyn", + "summer": "Haf", + "winter": "Gaeaf" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.da.json b/homeassistant/components/season/.translations/sensor.da.json new file mode 100644 index 00000000000..9cded2f9c0f --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.da.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Efter\u00e5r", + "spring": "For\u00e5r", + "summer": "Sommer", + "winter": "Vinter" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.de.json b/homeassistant/components/season/.translations/sensor.de.json new file mode 100644 index 00000000000..50d702340b9 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.de.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Herbst", + "spring": "Fr\u00fchling", + "summer": "Sommer", + "winter": "Winter" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.en.json b/homeassistant/components/season/.translations/sensor.en.json new file mode 100644 index 00000000000..b42100215ca --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.en.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Autumn", + "spring": "Spring", + "summer": "Summer", + "winter": "Winter" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.es-419.json b/homeassistant/components/season/.translations/sensor.es-419.json new file mode 100644 index 00000000000..65df6a58b10 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.es-419.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Oto\u00f1o", + "spring": "Primavera", + "summer": "Verano", + "winter": "Invierno" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.es.json b/homeassistant/components/season/.translations/sensor.es.json new file mode 100644 index 00000000000..65df6a58b10 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.es.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Oto\u00f1o", + "spring": "Primavera", + "summer": "Verano", + "winter": "Invierno" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.et.json b/homeassistant/components/season/.translations/sensor.et.json new file mode 100644 index 00000000000..1415a3b907b --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.et.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "S\u00fcgis", + "spring": "Kevad", + "summer": "Suvi", + "winter": "Talv" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.eu.json b/homeassistant/components/season/.translations/sensor.eu.json new file mode 100644 index 00000000000..f226d920043 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.eu.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Udazkeneko", + "spring": "Spring", + "summer": "Uda", + "winter": "Winter" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.fi.json b/homeassistant/components/season/.translations/sensor.fi.json new file mode 100644 index 00000000000..f01f6451549 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.fi.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Syksy", + "spring": "Kev\u00e4t", + "summer": "Kes\u00e4", + "winter": "Talvi" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.fr.json b/homeassistant/components/season/.translations/sensor.fr.json new file mode 100644 index 00000000000..ec9f9657428 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.fr.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Automne", + "spring": "Printemps", + "summer": "\u00c9t\u00e9", + "winter": "Hiver" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.he.json b/homeassistant/components/season/.translations/sensor.he.json new file mode 100644 index 00000000000..282c24f3ad9 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.he.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "\u05e1\u05ea\u05d9\u05d5", + "spring": "\u05d0\u05d1\u05d9\u05d1", + "summer": "\u05e7\u05d9\u05e5", + "winter": "\u05d7\u05d5\u05e8\u05e3" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.hu.json b/homeassistant/components/season/.translations/sensor.hu.json new file mode 100644 index 00000000000..63596b09784 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.hu.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "\u0150sz", + "spring": "Tavasz", + "summer": "Ny\u00e1r", + "winter": "T\u00e9l" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.id.json b/homeassistant/components/season/.translations/sensor.id.json new file mode 100644 index 00000000000..ed0666aee36 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.id.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Musim gugur", + "spring": "Musim semi", + "summer": "Musim panas", + "winter": "Musim dingin" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.is.json b/homeassistant/components/season/.translations/sensor.is.json new file mode 100644 index 00000000000..2d48745436b --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.is.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Haust", + "spring": "Vor", + "summer": "Sumar", + "winter": "Vetur" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.it.json b/homeassistant/components/season/.translations/sensor.it.json new file mode 100644 index 00000000000..d9138f6b16e --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.it.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Autunno", + "spring": "Primavera", + "summer": "Estate", + "winter": "Inverno" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.ja.json b/homeassistant/components/season/.translations/sensor.ja.json new file mode 100644 index 00000000000..e441b1aa8ac --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.ja.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "\u79cb", + "spring": "\u6625", + "summer": "\u590f", + "winter": "\u51ac" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.ko.json b/homeassistant/components/season/.translations/sensor.ko.json new file mode 100644 index 00000000000..f2bf0a7bae5 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.ko.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "\uac00\uc744", + "spring": "\ubd04", + "summer": "\uc5ec\ub984", + "winter": "\uaca8\uc6b8" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.lb.json b/homeassistant/components/season/.translations/sensor.lb.json new file mode 100644 index 00000000000..f33afde7a07 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.lb.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Hierscht", + "spring": "Fr\u00e9ijoer", + "summer": "Summer", + "winter": "Wanter" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.lv.json b/homeassistant/components/season/.translations/sensor.lv.json new file mode 100644 index 00000000000..a96e1112f71 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.lv.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Rudens", + "spring": "Pavasaris", + "summer": "Vasara", + "winter": "Ziema" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.nl.json b/homeassistant/components/season/.translations/sensor.nl.json new file mode 100644 index 00000000000..6054a8e2be5 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.nl.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Herfst", + "spring": "Lente", + "summer": "Zomer", + "winter": "Winter" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.nn.json b/homeassistant/components/season/.translations/sensor.nn.json new file mode 100644 index 00000000000..dbcff7ef819 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.nn.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Haust", + "spring": "V\u00e5r", + "summer": "Sommar", + "winter": "Vinter" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.no.json b/homeassistant/components/season/.translations/sensor.no.json new file mode 100644 index 00000000000..9d520dae6a5 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.no.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "H\u00f8st", + "spring": "V\u00e5r", + "summer": "Sommer", + "winter": "Vinter" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.pl.json b/homeassistant/components/season/.translations/sensor.pl.json new file mode 100644 index 00000000000..9b313e511c9 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.pl.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "jesie\u0144", + "spring": "wiosna", + "summer": "lato", + "winter": "zima" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.pt-BR.json b/homeassistant/components/season/.translations/sensor.pt-BR.json new file mode 100644 index 00000000000..fde45ad6c8e --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.pt-BR.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Outono", + "spring": "Primavera", + "summer": "Ver\u00e3o", + "winter": "Inverno" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.pt.json b/homeassistant/components/season/.translations/sensor.pt.json new file mode 100644 index 00000000000..fde45ad6c8e --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.pt.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Outono", + "spring": "Primavera", + "summer": "Ver\u00e3o", + "winter": "Inverno" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.ro.json b/homeassistant/components/season/.translations/sensor.ro.json new file mode 100644 index 00000000000..04f90318290 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.ro.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Toamn\u0103", + "spring": "Prim\u0103var\u0103", + "summer": "Var\u0103", + "winter": "Iarn\u0103" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.ru.json b/homeassistant/components/season/.translations/sensor.ru.json new file mode 100644 index 00000000000..2b04886b72d --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.ru.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "\u041e\u0441\u0435\u043d\u044c", + "spring": "\u0412\u0435\u0441\u043d\u0430", + "summer": "\u041b\u0435\u0442\u043e", + "winter": "\u0417\u0438\u043c\u0430" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.sl.json b/homeassistant/components/season/.translations/sensor.sl.json new file mode 100644 index 00000000000..f715a3ec13a --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.sl.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "Jesen", + "spring": "Pomlad", + "summer": "Poletje", + "winter": "Zima" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.sv.json b/homeassistant/components/season/.translations/sensor.sv.json new file mode 100644 index 00000000000..02332d76906 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.sv.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "H\u00f6st", + "spring": "V\u00e5r", + "summer": "Sommar", + "winter": "Vinter" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.th.json b/homeassistant/components/season/.translations/sensor.th.json new file mode 100644 index 00000000000..09799730389 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.th.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "\u0e24\u0e14\u0e39\u0e43\u0e1a\u0e44\u0e21\u0e49\u0e23\u0e48\u0e27\u0e07", + "spring": "\u0e24\u0e14\u0e39\u0e43\u0e1a\u0e44\u0e21\u0e49\u0e1c\u0e25\u0e34", + "summer": "\u0e24\u0e14\u0e39\u0e23\u0e49\u0e2d\u0e19", + "winter": "\u0e24\u0e14\u0e39\u0e2b\u0e19\u0e32\u0e27" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.uk.json b/homeassistant/components/season/.translations/sensor.uk.json new file mode 100644 index 00000000000..766e59a43da --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.uk.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "\u041e\u0441\u0456\u043d\u044c", + "spring": "\u0412\u0435\u0441\u043d\u0430", + "summer": "\u041b\u0456\u0442\u043e", + "winter": "\u0417\u0438\u043c\u0430" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.vi.json b/homeassistant/components/season/.translations/sensor.vi.json new file mode 100644 index 00000000000..a3bb21dee27 --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.vi.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "M\u00f9a thu", + "spring": "M\u00f9a xu\u00e2n", + "summer": "M\u00f9a h\u00e8", + "winter": "M\u00f9a \u0111\u00f4ng" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.zh-Hans.json b/homeassistant/components/season/.translations/sensor.zh-Hans.json new file mode 100644 index 00000000000..78801f4b1df --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.zh-Hans.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "\u79cb\u5b63", + "spring": "\u6625\u5b63", + "summer": "\u590f\u5b63", + "winter": "\u51ac\u5b63" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/.translations/sensor.zh-Hant.json b/homeassistant/components/season/.translations/sensor.zh-Hant.json new file mode 100644 index 00000000000..78801f4b1df --- /dev/null +++ b/homeassistant/components/season/.translations/sensor.zh-Hant.json @@ -0,0 +1,8 @@ +{ + "state": { + "autumn": "\u79cb\u5b63", + "spring": "\u6625\u5b63", + "summer": "\u590f\u5b63", + "winter": "\u51ac\u5b63" + } +} \ No newline at end of file diff --git a/homeassistant/components/season/__init__.py b/homeassistant/components/season/__init__.py new file mode 100644 index 00000000000..095a4704b12 --- /dev/null +++ b/homeassistant/components/season/__init__.py @@ -0,0 +1 @@ +"""The season component.""" diff --git a/homeassistant/components/sensor/season.py b/homeassistant/components/season/sensor.py similarity index 98% rename from homeassistant/components/sensor/season.py rename to homeassistant/components/season/sensor.py index c9b222f2b26..84a2b426e9e 100644 --- a/homeassistant/components/sensor/season.py +++ b/homeassistant/components/season/sensor.py @@ -130,5 +130,5 @@ class Season(Entity): def update(self): """Update season.""" - self.datetime = datetime.now() + self.datetime = datetime.utcnow() self.season = get_season(self.datetime, self.hemisphere, self.type) diff --git a/homeassistant/components/sensor/strings.season.json b/homeassistant/components/season/strings.sensor.json similarity index 100% rename from homeassistant/components/sensor/strings.season.json rename to homeassistant/components/season/strings.sensor.json diff --git a/homeassistant/components/sendgrid/__init__.py b/homeassistant/components/sendgrid/__init__.py new file mode 100644 index 00000000000..91fff97c150 --- /dev/null +++ b/homeassistant/components/sendgrid/__init__.py @@ -0,0 +1 @@ +"""The sendgrid component.""" diff --git a/homeassistant/components/notify/sendgrid.py b/homeassistant/components/sendgrid/notify.py similarity index 97% rename from homeassistant/components/notify/sendgrid.py rename to homeassistant/components/sendgrid/notify.py index e72dcbbed36..211e288725e 100644 --- a/homeassistant/components/notify/sendgrid.py +++ b/homeassistant/components/sendgrid/notify.py @@ -8,11 +8,12 @@ import logging import voluptuous as vol +from homeassistant.const import ( + CONF_API_KEY, CONF_RECIPIENT, CONF_SENDER, CONTENT_TYPE_TEXT_PLAIN) +import homeassistant.helpers.config_validation as cv + from homeassistant.components.notify import ( ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) -from homeassistant.const import ( - CONF_API_KEY, CONF_SENDER, CONF_RECIPIENT, CONTENT_TYPE_TEXT_PLAIN) -import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['sendgrid==5.6.0'] diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index 97771200bcd..be3ab75b555 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -44,7 +44,7 @@ async def async_setup(hass, config): _LOGGER.error("Could not authenticate with sense server") return False hass.async_create_task( - async_load_platform(hass, 'sensor', DOMAIN, None, config)) + async_load_platform(hass, 'sensor', DOMAIN, {}, config)) hass.async_create_task( - async_load_platform(hass, 'binary_sensor', DOMAIN, None, config)) + async_load_platform(hass, 'binary_sensor', DOMAIN, {}, config)) return True diff --git a/homeassistant/components/sense/binary_sensor.py b/homeassistant/components/sense/binary_sensor.py index 545aaa8ae7b..da9bae3cc84 100644 --- a/homeassistant/components/sense/binary_sensor.py +++ b/homeassistant/components/sense/binary_sensor.py @@ -2,7 +2,8 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.sense import SENSE_DATA + +from . import SENSE_DATA DEPENDENCIES = ['sense'] @@ -52,6 +53,8 @@ MDI_ICONS = { async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Sense binary sensor.""" + if discovery_info is None: + return data = hass.data[SENSE_DATA] sense_devices = await data.get_discovered_device_data() diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py index 4810ebf1958..0224884e18a 100644 --- a/homeassistant/components/sense/sensor.py +++ b/homeassistant/components/sense/sensor.py @@ -2,11 +2,12 @@ from datetime import timedelta import logging -from homeassistant.components.sense import SENSE_DATA -from homeassistant.const import POWER_WATT, ENERGY_KILO_WATT_HOUR +from homeassistant.const import ENERGY_KILO_WATT_HOUR, POWER_WATT from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle +from . import SENSE_DATA + _LOGGER = logging.getLogger(__name__) ACTIVE_NAME = 'Energy' @@ -48,6 +49,8 @@ SENSOR_VARIANTS = [PRODUCTION_NAME.lower(), CONSUMPTION_NAME.lower()] async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Sense sensor.""" + if discovery_info is None: + return data = hass.data[SENSE_DATA] @Throttle(MIN_TIME_BETWEEN_DAILY_UPDATES) diff --git a/homeassistant/components/sensehat/__init__.py b/homeassistant/components/sensehat/__init__.py new file mode 100644 index 00000000000..baef85d7f53 --- /dev/null +++ b/homeassistant/components/sensehat/__init__.py @@ -0,0 +1 @@ +"""The sensehat component.""" diff --git a/homeassistant/components/light/sensehat.py b/homeassistant/components/sensehat/light.py similarity index 100% rename from homeassistant/components/light/sensehat.py rename to homeassistant/components/sensehat/light.py diff --git a/homeassistant/components/sensor/sensehat.py b/homeassistant/components/sensehat/sensor.py similarity index 100% rename from homeassistant/components/sensor/sensehat.py rename to homeassistant/components/sensehat/sensor.py diff --git a/homeassistant/components/sensibo/__init__.py b/homeassistant/components/sensibo/__init__.py new file mode 100644 index 00000000000..41959bdc9b2 --- /dev/null +++ b/homeassistant/components/sensibo/__init__.py @@ -0,0 +1 @@ +"""The sensibo component.""" diff --git a/homeassistant/components/climate/sensibo.py b/homeassistant/components/sensibo/climate.py similarity index 96% rename from homeassistant/components/climate/sensibo.py rename to homeassistant/components/sensibo/climate.py index 7850b08fd6b..3affaba3e1f 100644 --- a/homeassistant/components/climate/sensibo.py +++ b/homeassistant/components/sensibo/climate.py @@ -12,16 +12,15 @@ import aiohttp import async_timeout import voluptuous as vol -from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_STATE, ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID, - STATE_ON, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT) from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA from homeassistant.components.climate.const import ( - ATTR_CURRENT_HUMIDITY, DOMAIN, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, + DOMAIN, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, SUPPORT_ON_OFF, STATE_HEAT, STATE_COOL, STATE_FAN_ONLY, STATE_DRY, STATE_AUTO) +from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_STATE, ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID, + STATE_ON, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT) from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -86,7 +85,7 @@ async def async_setup_platform(hass, config, async_add_entities, devices.append(SensiboClimate( client, dev, hass.config.units.temperature_unit)) except (aiohttp.client_exceptions.ClientConnectorError, - asyncio.TimeoutError): + asyncio.TimeoutError, pysensibo.SensiboError): _LOGGER.exception('Failed to connect to Sensibo servers.') raise PlatformNotReady @@ -128,6 +127,7 @@ class SensiboClimate(ClimateDevice): self._id = data['id'] self._external_state = None self._units = units + self._available = False self._do_update(data) @property @@ -139,7 +139,7 @@ class SensiboClimate(ClimateDevice): self._name = data['room']['name'] self._measurements = data['measurements'] self._ac_states = data['acState'] - self._status = data['connectionStatus']['isAlive'] + self._available = data['connectionStatus']['isAlive'] capabilities = data['remoteCapabilities'] self._operations = [SENSIBO_TO_HA[mode] for mode in capabilities['modes']] @@ -168,8 +168,7 @@ class SensiboClimate(ClimateDevice): @property def device_state_attributes(self): """Return the state attributes.""" - return {ATTR_CURRENT_HUMIDITY: self.current_humidity, - 'battery': self.current_battery} + return {'battery': self.current_battery} @property def temperature_unit(self): @@ -179,7 +178,7 @@ class SensiboClimate(ClimateDevice): @property def available(self): """Return True if entity is available.""" - return self._status + return self._available @property def target_temperature(self): @@ -348,10 +347,13 @@ class SensiboClimate(ClimateDevice): async def async_update(self): """Retrieve latest state.""" + import pysensibo try: with async_timeout.timeout(TIMEOUT): data = await self._client.async_get_device( self._id, _FETCH_FIELDS) self._do_update(data) - except aiohttp.client_exceptions.ClientError: + except (aiohttp.client_exceptions.ClientError, + pysensibo.SensiboError): _LOGGER.warning('Failed to connect to Sensibo servers.') + self._available = False diff --git a/homeassistant/components/sensor/.translations/moon.bg.json b/homeassistant/components/sensor/.translations/moon.bg.json new file mode 100644 index 00000000000..c764ccbc3e0 --- /dev/null +++ b/homeassistant/components/sensor/.translations/moon.bg.json @@ -0,0 +1,12 @@ +{ + "state": { + "first_quarter": "\u041f\u044a\u0440\u0432\u0430 \u0447\u0435\u0442\u0432\u044a\u0440\u0442\u0438\u043d\u0430", + "full_moon": "\u041f\u044a\u043b\u043d\u043e\u043b\u0443\u043d\u0438\u0435", + "last_quarter": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0430 \u0447\u0435\u0442\u0432\u044a\u0440\u0442\u0438\u043d\u0430", + "new_moon": "\u041d\u043e\u0432\u043e\u043b\u0443\u043d\u0438\u0435", + "waning_crescent": "\u041d\u0430\u043c\u0430\u043b\u044f\u0432\u0430\u0449 \u043f\u043e\u043b\u0443\u043c\u0435\u0441\u0435\u0446", + "waning_gibbous": "\u041d\u0430\u043c\u0430\u043b\u044f\u0432\u0430\u0449 \u043f\u043e\u043b\u0443\u043c\u0435\u0441\u0435\u0446", + "waxing_crescent": "\u041d\u0430\u0440\u0430\u0441\u0442\u0432\u0430\u0449 \u043f\u043e\u043b\u0443\u043c\u0435\u0441\u0435\u0446", + "waxing_gibbous": "\u041d\u0430\u0440\u0430\u0441\u0442\u0432\u0430\u0449 \u043f\u043e\u043b\u0443\u043c\u0435\u0441\u0435\u0446" + } +} \ No newline at end of file diff --git a/homeassistant/components/sensor/.translations/moon.ca.json b/homeassistant/components/sensor/.translations/moon.ca.json index 56eaf8d3b4c..e294579da09 100644 --- a/homeassistant/components/sensor/.translations/moon.ca.json +++ b/homeassistant/components/sensor/.translations/moon.ca.json @@ -4,7 +4,7 @@ "full_moon": "Lluna plena", "last_quarter": "Quart minvant", "new_moon": "Lluna nova", - "waning_crescent": "Lluna vella minvant", + "waning_crescent": "Minvant (Lluna vella)", "waning_gibbous": "Gibosa minvant", "waxing_crescent": "Lluna nova visible", "waxing_gibbous": "Gibosa creixent" diff --git a/homeassistant/components/sensor/api_streams.py b/homeassistant/components/sensor/api_streams.py deleted file mode 100644 index 1fbef2c5896..00000000000 --- a/homeassistant/components/sensor/api_streams.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -Entity to track connections to stream API. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.api_streams/ -""" -import logging - -from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.core import callback -from homeassistant.helpers.entity import Entity - -NAME_WS = 'homeassistant.components.websocket_api' -NAME_STREAM = 'homeassistant.components.api' - - -class StreamHandler(logging.Handler): - """Check log messages for stream connect/disconnect.""" - - def __init__(self, entity): - """Initialize handler.""" - super().__init__() - self.entity = entity - self.count = 0 - - def handle(self, record): - """Handle a log message.""" - if record.name == NAME_STREAM: - if not record.msg.startswith('STREAM'): - return - - if record.msg.endswith('ATTACHED'): - self.entity.count += 1 - elif record.msg.endswith('RESPONSE CLOSED'): - self.entity.count -= 1 - - else: - if not record.msg.startswith('WS'): - return - if len(record.args) < 2: - return - if record.args[1] == 'Connected': - self.entity.count += 1 - elif record.args[1] == 'Closed connection': - self.entity.count -= 1 - - self.entity.schedule_update_ha_state() - - -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): - """Set up the API streams platform.""" - entity = APICount() - handler = StreamHandler(entity) - - logging.getLogger(NAME_STREAM).addHandler(handler) - logging.getLogger(NAME_WS).addHandler(handler) - - @callback - def remove_logger(event): - """Remove our handlers.""" - logging.getLogger(NAME_STREAM).removeHandler(handler) - logging.getLogger(NAME_WS).removeHandler(handler) - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, remove_logger) - - async_add_entities([entity]) - - -class APICount(Entity): - """Entity to represent how many people are connected to the stream API.""" - - def __init__(self): - """Initialize the API count.""" - self.count = 0 - - @property - def name(self): - """Return name of entity.""" - return "Connected clients" - - @property - def state(self): - """Return current API count.""" - return self.count - - @property - def unit_of_measurement(self): - """Return the unit of measurement.""" - return "clients" diff --git a/homeassistant/components/sensor/entur_public_transport.py b/homeassistant/components/sensor/entur_public_transport.py deleted file mode 100644 index 330f5f8cc56..00000000000 --- a/homeassistant/components/sensor/entur_public_transport.py +++ /dev/null @@ -1,202 +0,0 @@ -""" -Real-time information about public transport departures in Norway. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.entur_public_transport/ -""" -from datetime import datetime, timedelta -import logging - -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, - CONF_SHOW_ON_MAP) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle -import homeassistant.util.dt as dt_util - -REQUIREMENTS = ['enturclient==0.1.3'] - -_LOGGER = logging.getLogger(__name__) - -ATTR_NEXT_UP_IN = 'next_due_in' - -API_CLIENT_NAME = 'homeassistant-homeassistant' - -ATTRIBUTION = "Data provided by entur.org under NLOD" - -CONF_STOP_IDS = 'stop_ids' -CONF_EXPAND_PLATFORMS = 'expand_platforms' -CONF_WHITELIST_LINES = 'line_whitelist' - -DEFAULT_NAME = 'Entur' -DEFAULT_ICON_KEY = 'bus' - -ICONS = { - 'air': 'mdi:airplane', - 'bus': 'mdi:bus', - 'metro': 'mdi:subway', - 'rail': 'mdi:train', - 'tram': 'mdi:tram', - 'water': 'mdi:ferry', -} - -SCAN_INTERVAL = timedelta(minutes=1) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_STOP_IDS): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_EXPAND_PLATFORMS, default=True): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean, - vol.Optional(CONF_WHITELIST_LINES, default=[]): cv.ensure_list, -}) - - -def due_in_minutes(timestamp: str) -> str: - """Get the time in minutes from a timestamp. - - The timestamp should be in the format - year-month-yearThour:minute:second+timezone - """ - if timestamp is None: - return None - diff = datetime.strptime( - timestamp, "%Y-%m-%dT%H:%M:%S%z") - dt_util.now() - - return str(int(diff.total_seconds() / 60)) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Entur public transport sensor.""" - from enturclient import EnturPublicTransportData - from enturclient.consts import CONF_NAME as API_NAME - - expand = config.get(CONF_EXPAND_PLATFORMS) - line_whitelist = config.get(CONF_WHITELIST_LINES) - name = config.get(CONF_NAME) - show_on_map = config.get(CONF_SHOW_ON_MAP) - stop_ids = config.get(CONF_STOP_IDS) - - stops = [s for s in stop_ids if "StopPlace" in s] - quays = [s for s in stop_ids if "Quay" in s] - - data = EnturPublicTransportData(API_CLIENT_NAME, - stops, - quays, - expand, - line_whitelist) - data.update() - - proxy = EnturProxy(data) - - entities = [] - for item in data.all_stop_places_quays(): - try: - given_name = "{} {}".format( - name, data.get_stop_info(item)[API_NAME]) - except KeyError: - given_name = "{} {}".format(name, item) - - entities.append( - EnturPublicTransportSensor(proxy, given_name, item, show_on_map)) - - add_entities(entities, True) - - -class EnturProxy: - """Proxy for the Entur client. - - Ensure throttle to not hit rate limiting on the API. - """ - - def __init__(self, api): - """Initialize the proxy.""" - self._api = api - - @Throttle(SCAN_INTERVAL) - def update(self) -> None: - """Update data in client.""" - self._api.update() - - def get_stop_info(self, stop_id: str) -> dict: - """Get info about specific stop place.""" - return self._api.get_stop_info(stop_id) - - -class EnturPublicTransportSensor(Entity): - """Implementation of a Entur public transport sensor.""" - - def __init__( - self, api: EnturProxy, name: str, stop: str, show_on_map: bool): - """Initialize the sensor.""" - from enturclient.consts import ATTR_STOP_ID - - self.api = api - self._stop = stop - self._show_on_map = show_on_map - self._name = name - self._state = None - self._icon = ICONS[DEFAULT_ICON_KEY] - self._attributes = { - ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_STOP_ID: self._stop, - } - - @property - def name(self) -> str: - """Return the name of the sensor.""" - return self._name - - @property - def state(self) -> str: - """Return the state of the sensor.""" - return self._state - - @property - def device_state_attributes(self) -> dict: - """Return the state attributes.""" - return self._attributes - - @property - def unit_of_measurement(self) -> str: - """Return the unit this state is expressed in.""" - return 'min' - - @property - def icon(self) -> str: - """Icon to use in the frontend.""" - return self._icon - - def update(self) -> None: - """Get the latest data and update the states.""" - from enturclient.consts import ( - ATTR, ATTR_EXPECTED_AT, ATTR_NEXT_UP_AT, CONF_LOCATION, - CONF_LATITUDE as LAT, CONF_LONGITUDE as LONG, CONF_TRANSPORT_MODE) - - self.api.update() - - data = self.api.get_stop_info(self._stop) - if data is not None and ATTR in data: - attrs = data[ATTR] - self._attributes.update(attrs) - - if ATTR_NEXT_UP_AT in attrs: - self._attributes[ATTR_NEXT_UP_IN] = \ - due_in_minutes(attrs[ATTR_NEXT_UP_AT]) - - if CONF_LOCATION in data and self._show_on_map: - self._attributes[CONF_LATITUDE] = data[CONF_LOCATION][LAT] - self._attributes[CONF_LONGITUDE] = data[CONF_LOCATION][LONG] - - if ATTR_EXPECTED_AT in attrs: - self._state = due_in_minutes(attrs[ATTR_EXPECTED_AT]) - else: - self._state = None - - self._icon = ICONS.get( - data[CONF_TRANSPORT_MODE], ICONS[DEFAULT_ICON_KEY]) - else: - self._state = None diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py deleted file mode 100644 index eec08be093f..00000000000 --- a/homeassistant/components/sensor/gtfs.py +++ /dev/null @@ -1,316 +0,0 @@ -""" -Support for GTFS (Google/General Transport Format Schema). - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.gtfs/ -""" -import os -import logging -import datetime -import threading - -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME -from homeassistant.helpers.entity import Entity -import homeassistant.helpers.config_validation as cv - -REQUIREMENTS = ['pygtfs==0.1.5'] - -_LOGGER = logging.getLogger(__name__) - -CONF_DATA = 'data' -CONF_DESTINATION = 'destination' -CONF_ORIGIN = 'origin' -CONF_OFFSET = 'offset' - -DEFAULT_NAME = 'GTFS Sensor' -DEFAULT_PATH = 'gtfs' - -ICON = 'mdi:train' -ICONS = { - 0: 'mdi:tram', - 1: 'mdi:subway', - 2: 'mdi:train', - 3: 'mdi:bus', - 4: 'mdi:ferry', - 5: 'mdi:train-variant', - 6: 'mdi:gondola', - 7: 'mdi:stairs', -} - -DATE_FORMAT = '%Y-%m-%d' -TIME_FORMAT = '%Y-%m-%d %H:%M:%S' - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ORIGIN): cv.string, - vol.Required(CONF_DESTINATION): cv.string, - vol.Required(CONF_DATA): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_OFFSET, default=0): cv.time_period, -}) - - -def get_next_departure(sched, start_station_id, end_station_id, offset): - """Get the next departure for the given schedule.""" - origin_station = sched.stops_by_id(start_station_id)[0] - destination_station = sched.stops_by_id(end_station_id)[0] - - now = datetime.datetime.now() + offset - day_name = now.strftime('%A').lower() - now_str = now.strftime('%H:%M:%S') - today = now.strftime(DATE_FORMAT) - - from sqlalchemy.sql import text - - sql_query = text(""" - SELECT trip.trip_id, trip.route_id, - time(origin_stop_time.arrival_time) AS origin_arrival_time, - time(origin_stop_time.departure_time) AS origin_depart_time, - origin_stop_time.drop_off_type AS origin_drop_off_type, - origin_stop_time.pickup_type AS origin_pickup_type, - origin_stop_time.shape_dist_traveled AS origin_dist_traveled, - origin_stop_time.stop_headsign AS origin_stop_headsign, - origin_stop_time.stop_sequence AS origin_stop_sequence, - time(destination_stop_time.arrival_time) AS dest_arrival_time, - time(destination_stop_time.departure_time) AS dest_depart_time, - destination_stop_time.drop_off_type AS dest_drop_off_type, - destination_stop_time.pickup_type AS dest_pickup_type, - destination_stop_time.shape_dist_traveled AS dest_dist_traveled, - destination_stop_time.stop_headsign AS dest_stop_headsign, - destination_stop_time.stop_sequence AS dest_stop_sequence - FROM trips trip - INNER JOIN calendar calendar - ON trip.service_id = calendar.service_id - INNER JOIN stop_times origin_stop_time - ON trip.trip_id = origin_stop_time.trip_id - INNER JOIN stops start_station - ON origin_stop_time.stop_id = start_station.stop_id - INNER JOIN stop_times destination_stop_time - ON trip.trip_id = destination_stop_time.trip_id - INNER JOIN stops end_station - ON destination_stop_time.stop_id = end_station.stop_id - WHERE calendar.{day_name} = 1 - AND origin_depart_time > time(:now_str) - AND start_station.stop_id = :origin_station_id - AND end_station.stop_id = :end_station_id - AND origin_stop_sequence < dest_stop_sequence - AND calendar.start_date <= :today - AND calendar.end_date >= :today - ORDER BY origin_stop_time.departure_time - LIMIT 1 - """.format(day_name=day_name)) - result = sched.engine.execute(sql_query, now_str=now_str, - origin_station_id=origin_station.id, - end_station_id=destination_station.id, - today=today) - item = {} - for row in result: - item = row - - if item == {}: - return None - - # Format arrival and departure dates and times, accounting for the - # possibility of times crossing over midnight. - origin_arrival = now - if item['origin_arrival_time'] > item['origin_depart_time']: - origin_arrival -= datetime.timedelta(days=1) - origin_arrival_time = '{} {}'.format(origin_arrival.strftime(DATE_FORMAT), - item['origin_arrival_time']) - - origin_depart_time = '{} {}'.format(today, item['origin_depart_time']) - - dest_arrival = now - if item['dest_arrival_time'] < item['origin_depart_time']: - dest_arrival += datetime.timedelta(days=1) - dest_arrival_time = '{} {}'.format(dest_arrival.strftime(DATE_FORMAT), - item['dest_arrival_time']) - - dest_depart = dest_arrival - if item['dest_depart_time'] < item['dest_arrival_time']: - dest_depart += datetime.timedelta(days=1) - dest_depart_time = '{} {}'.format(dest_depart.strftime(DATE_FORMAT), - item['dest_depart_time']) - - depart_time = datetime.datetime.strptime(origin_depart_time, TIME_FORMAT) - arrival_time = datetime.datetime.strptime(dest_arrival_time, TIME_FORMAT) - - seconds_until = (depart_time - datetime.datetime.now()).total_seconds() - minutes_until = int(seconds_until / 60) - - route = sched.routes_by_id(item['route_id'])[0] - - origin_stop_time_dict = { - 'Arrival Time': origin_arrival_time, - 'Departure Time': origin_depart_time, - 'Drop Off Type': item['origin_drop_off_type'], - 'Pickup Type': item['origin_pickup_type'], - 'Shape Dist Traveled': item['origin_dist_traveled'], - 'Headsign': item['origin_stop_headsign'], - 'Sequence': item['origin_stop_sequence'] - } - - destination_stop_time_dict = { - 'Arrival Time': dest_arrival_time, - 'Departure Time': dest_depart_time, - 'Drop Off Type': item['dest_drop_off_type'], - 'Pickup Type': item['dest_pickup_type'], - 'Shape Dist Traveled': item['dest_dist_traveled'], - 'Headsign': item['dest_stop_headsign'], - 'Sequence': item['dest_stop_sequence'] - } - - return { - 'trip_id': item['trip_id'], - 'trip': sched.trips_by_id(item['trip_id'])[0], - 'route': route, - 'agency': sched.agencies_by_id(route.agency_id)[0], - 'origin_station': origin_station, - 'departure_time': depart_time, - 'destination_station': destination_station, - 'arrival_time': arrival_time, - 'seconds_until_departure': seconds_until, - 'minutes_until_departure': minutes_until, - 'origin_stop_time': origin_stop_time_dict, - 'destination_stop_time': destination_stop_time_dict - } - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the GTFS sensor.""" - gtfs_dir = hass.config.path(DEFAULT_PATH) - data = config.get(CONF_DATA) - origin = config.get(CONF_ORIGIN) - destination = config.get(CONF_DESTINATION) - name = config.get(CONF_NAME) - offset = config.get(CONF_OFFSET) - - if not os.path.exists(gtfs_dir): - os.makedirs(gtfs_dir) - - if not os.path.exists(os.path.join(gtfs_dir, data)): - _LOGGER.error("The given GTFS data file/folder was not found") - return False - - import pygtfs - - (gtfs_root, _) = os.path.splitext(data) - - sqlite_file = "{}.sqlite?check_same_thread=False".format(gtfs_root) - joined_path = os.path.join(gtfs_dir, sqlite_file) - gtfs = pygtfs.Schedule(joined_path) - - # pylint: disable=no-member - if not gtfs.feeds: - pygtfs.append_feed(gtfs, os.path.join(gtfs_dir, data)) - - add_entities([ - GTFSDepartureSensor(gtfs, name, origin, destination, offset)]) - - -class GTFSDepartureSensor(Entity): - """Implementation of an GTFS departures sensor.""" - - def __init__(self, pygtfs, name, origin, destination, offset): - """Initialize the sensor.""" - self._pygtfs = pygtfs - self.origin = origin - self.destination = destination - self._offset = offset - self._custom_name = name - self._icon = ICON - self._name = '' - self._unit_of_measurement = 'min' - self._state = None - self._attributes = {} - self.lock = threading.Lock() - self.update() - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return self._unit_of_measurement - - @property - def device_state_attributes(self): - """Return the state attributes.""" - return self._attributes - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return self._icon - - def update(self): - """Get the latest data from GTFS and update the states.""" - with self.lock: - self._departure = get_next_departure( - self._pygtfs, self.origin, self.destination, self._offset) - if not self._departure: - self._state = None - self._attributes = {'Info': 'No more departures today'} - if self._name == '': - self._name = (self._custom_name or DEFAULT_NAME) - return - - self._state = self._departure['minutes_until_departure'] - - origin_station = self._departure['origin_station'] - destination_station = self._departure['destination_station'] - origin_stop_time = self._departure['origin_stop_time'] - destination_stop_time = self._departure['destination_stop_time'] - agency = self._departure['agency'] - route = self._departure['route'] - trip = self._departure['trip'] - - name = '{} {} to {} next departure' - self._name = (self._custom_name or - name.format(agency.agency_name, - origin_station.stop_id, - destination_station.stop_id)) - - # Build attributes - self._attributes = {} - self._attributes['offset'] = self._offset.seconds / 60 - - self._icon = ICONS.get(route.route_type, ICON) - - def dict_for_table(resource): - """Return a dict for the SQLAlchemy resource given.""" - return dict((col, getattr(resource, col)) - for col in resource.__table__.columns.keys()) - - def append_keys(resource, prefix=None): - """Properly format key val pairs to append to attributes.""" - for key, val in resource.items(): - if val == "" or val is None or key == 'feed_id': - continue - pretty_key = key.replace('_', ' ') - pretty_key = pretty_key.title() - pretty_key = pretty_key.replace('Id', 'ID') - pretty_key = pretty_key.replace('Url', 'URL') - if prefix is not None and \ - pretty_key.startswith(prefix) is False: - pretty_key = '{} {}'.format(prefix, pretty_key) - self._attributes[pretty_key] = val - - append_keys(dict_for_table(agency), 'Agency') - append_keys(dict_for_table(route), 'Route') - append_keys(dict_for_table(trip), 'Trip') - append_keys(dict_for_table(origin_station), 'Origin Station') - append_keys(dict_for_table(destination_station), - 'Destination Station') - append_keys(origin_stop_time, 'Origin Stop') - append_keys(destination_stop_time, 'Destination Stop') diff --git a/homeassistant/components/sensor/iliad_italy.py b/homeassistant/components/sensor/iliad_italy.py deleted file mode 100644 index 1e1e5077e80..00000000000 --- a/homeassistant/components/sensor/iliad_italy.py +++ /dev/null @@ -1,119 +0,0 @@ -""" -Sensor to get Iliad Italy personal data. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.iliad_italy/ -""" -from datetime import timedelta -import logging - -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME -from homeassistant.helpers.aiohttp_client import async_get_clientsession -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -import homeassistant.util.dt as dt_util - -REQUIREMENTS = ['aioiliad==0.1.1'] - -_LOGGER = logging.getLogger(__name__) - -ATTRIBUTION = "Data provided by Iliad Italy" - -ICON = 'mdi:phone' - -SCAN_INTERVAL = timedelta(minutes=10) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, -}) - - -async def async_setup_platform( - hass, conf, async_add_entities, discovery_info=None): - """Set up the Iliad Italy sensor platform.""" - from aioiliad import Iliad - iliad = Iliad(conf[CONF_USERNAME], conf[CONF_PASSWORD], - async_get_clientsession(hass), hass.loop) - await iliad.login() - - if not iliad.is_logged_in(): - _LOGGER.error("Check username and password") - return - - async_add_entities([IliadSensor(iliad)], True) - - -class IliadSensor(Entity): - """Representation of a Iliad Italy Sensor.""" - - def __init__(self, iliad): - """Initialize the Iliad Italy sensor.""" - from aioiliad.IliadData import IliadData - self._iliad = iliad - self._iliaddata = IliadData(self._iliad) - self._data = None - self._state = None - - @property - def name(self): - """Return the name of the sensor.""" - return "Iliad {}".format(self._data['info']['utente']) - - @property - def icon(self): - """Return the icon of the sensor.""" - return ICON - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - @property - def unit_of_measurement(self): - """Return the unit of measurement of the sensor.""" - return '€' - - @property - def device_state_attributes(self): - """Return the state attributes of the sensor.""" - attr = { - ATTR_ATTRIBUTION: ATTRIBUTION, - 'next_renewal': - dt_util.utc_from_timestamp( - self._data['info']['rinnovo']).isoformat(), - 'italy_sent_sms': self._data['italy']['sms'], - 'italy_over_plan_sms': self._data['italy']['sms_extra'], - 'italy_sent_mms': self._data['italy']['mms'], - 'italy_over_plan_mms': self._data['italy']['mms_extra'], - 'italy_calls_seconds': self._data['italy']['chiamate'], - 'italy_over_plan_calls': self._data['italy']['chiamate_extra'], - 'italy_data': self._data['italy']['internet'], - 'italy_data_max': self._data['italy']['internet_max'], - 'italy_data_over_plan': self._data['italy']['internet_over'], - - 'abroad_sent_sms': self._data['estero']['sms'], - 'abroad_over_plan_sms': self._data['estero']['sms_extra'], - 'abroad_sent_mms': self._data['estero']['mms'], - 'abroad_over_plan_mms': self._data['estero']['mms_extra'], - 'abroad_calls_seconds': self._data['estero']['chiamate'], - 'abroad_over_plan_calls': self._data['estero']['chiamate_extra'], - 'abroad_data': self._data['estero']['internet'], - 'abroad_data_max': self._data['estero']['internet_max'], - 'abroad_data_over_plan': self._data['estero']['internet_over'], - } - return attr - - async def async_update(self): - """Update device state.""" - await self._iliaddata.update() - self._data = { - 'italy': self._iliaddata.get_italy(), - 'estero': self._iliaddata.get_estero(), - 'info': self._iliaddata.get_general_info() - } - self._state = self._data['info']['credito'].replace('€', '') diff --git a/homeassistant/components/sensor/mopar.py b/homeassistant/components/sensor/mopar.py deleted file mode 100644 index e2dda136244..00000000000 --- a/homeassistant/components/sensor/mopar.py +++ /dev/null @@ -1,162 +0,0 @@ -""" -Sensor for Mopar vehicles. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.mopar/ -""" -from datetime import timedelta -import logging - -import voluptuous as vol - -from homeassistant.components.sensor import DOMAIN, PLATFORM_SCHEMA -from homeassistant.const import ( - ATTR_ATTRIBUTION, ATTR_COMMAND, CONF_PASSWORD, CONF_PIN, CONF_USERNAME, - LENGTH_KILOMETERS) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle - -REQUIREMENTS = ['motorparts==1.1.0'] - -_LOGGER = logging.getLogger(__name__) - -ATTR_VEHICLE_INDEX = 'vehicle_index' - -COOKIE_FILE = 'mopar_cookies.pickle' - -MIN_TIME_BETWEEN_UPDATES = timedelta(days=7) - -SERVICE_REMOTE_COMMAND = 'mopar_remote_command' - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_PIN): cv.positive_int, -}) - -REMOTE_COMMAND_SCHEMA = vol.Schema({ - vol.Required(ATTR_COMMAND): cv.string, - vol.Required(ATTR_VEHICLE_INDEX): cv.positive_int -}) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Mopar platform.""" - import motorparts - cookie = hass.config.path(COOKIE_FILE) - try: - session = motorparts.get_session( - config.get(CONF_USERNAME), config.get(CONF_PASSWORD), - config.get(CONF_PIN), cookie_path=cookie) - except motorparts.MoparError: - _LOGGER.error("Failed to login") - return - - def _handle_service(service): - """Handle service call.""" - index = service.data.get(ATTR_VEHICLE_INDEX) - command = service.data.get(ATTR_COMMAND) - try: - motorparts.remote_command(session, command, index) - except motorparts.MoparError as error: - _LOGGER.error(str(error)) - - hass.services.register(DOMAIN, SERVICE_REMOTE_COMMAND, _handle_service, - schema=REMOTE_COMMAND_SCHEMA) - - data = MoparData(session) - add_entities([MoparSensor(data, index) - for index, _ in enumerate(data.vehicles)], True) - - -class MoparData: - """Container for Mopar vehicle data. - - Prevents session expiry re-login race condition. - """ - - def __init__(self, session): - """Initialize data.""" - self._session = session - self.vehicles = [] - self.vhrs = {} - self.tow_guides = {} - self.update() - - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self, **kwargs): - """Update data.""" - import motorparts - _LOGGER.info("Updating vehicle data") - try: - self.vehicles = motorparts.get_summary(self._session)['vehicles'] - except motorparts.MoparError: - _LOGGER.exception("Failed to get summary") - return - for index, _ in enumerate(self.vehicles): - try: - self.vhrs[index] = motorparts.get_report(self._session, index) - self.tow_guides[index] = motorparts.get_tow_guide( - self._session, index) - except motorparts.MoparError: - _LOGGER.warning("Failed to update for vehicle index %s", index) - - -class MoparSensor(Entity): - """Mopar vehicle sensor.""" - - def __init__(self, data, index): - """Initialize the sensor.""" - self._index = index - self._vehicle = {} - self._vhr = {} - self._tow_guide = {} - self._odometer = None - self._data = data - - def update(self): - """Update device state.""" - self._data.update() - self._vehicle = self._data.vehicles[self._index] - self._vhr = self._data.vhrs.get(self._index, {}) - self._tow_guide = self._data.tow_guides.get(self._index, {}) - if 'odometer' in self._vhr: - odo = float(self._vhr['odometer']) - self._odometer = int(self.hass.config.units.length( - odo, LENGTH_KILOMETERS)) - - @property - def name(self): - """Return the name of the sensor.""" - return '{} {} {}'.format( - self._vehicle['year'], self._vehicle['make'], - self._vehicle['model']) - - @property - def state(self): - """Return the state of the sensor.""" - return self._odometer - - @property - def device_state_attributes(self): - """Return the state attributes.""" - import motorparts - attributes = { - ATTR_VEHICLE_INDEX: self._index, - ATTR_ATTRIBUTION: motorparts.ATTRIBUTION - } - attributes.update(self._vehicle) - attributes.update(self._vhr) - attributes.update(self._tow_guide) - return attributes - - @property - def unit_of_measurement(self): - """Return the unit of measurement.""" - return self.hass.config.units.length_unit - - @property - def icon(self): - """Return the icon.""" - return 'mdi:car' diff --git a/homeassistant/components/sensor/solaredge.py b/homeassistant/components/sensor/solaredge.py deleted file mode 100644 index a0d76c564c1..00000000000 --- a/homeassistant/components/sensor/solaredge.py +++ /dev/null @@ -1,167 +0,0 @@ -""" -Support for SolarEdge Monitoring API. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.solaredge/ -""" - -from datetime import timedelta -import logging - -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_API_KEY, CONF_MONITORED_CONDITIONS, CONF_NAME, POWER_WATT, - ENERGY_WATT_HOUR) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle - -REQUIREMENTS = ['solaredge==0.0.2'] - -# Config for solaredge monitoring api requests. -CONF_SITE_ID = "site_id" - -UPDATE_DELAY = timedelta(minutes=10) -SCAN_INTERVAL = timedelta(minutes=10) - -# Supported sensor types: -# Key: ['json_key', 'name', unit, icon] -SENSOR_TYPES = { - 'lifetime_energy': ['lifeTimeData', "Lifetime energy", - ENERGY_WATT_HOUR, 'mdi:solar-power'], - 'energy_this_year': ['lastYearData', "Energy this year", - ENERGY_WATT_HOUR, 'mdi:solar-power'], - 'energy_this_month': ['lastMonthData', "Energy this month", - ENERGY_WATT_HOUR, 'mdi:solar-power'], - 'energy_today': ['lastDayData', "Energy today", - ENERGY_WATT_HOUR, 'mdi:solar-power'], - 'current_power': ['currentPower', "Current Power", - POWER_WATT, 'mdi:solar-power'] -} - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_SITE_ID): cv.string, - vol.Optional(CONF_NAME, default='SolarEdge'): cv.string, - vol.Optional(CONF_MONITORED_CONDITIONS, default=['current_power']): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]) -}) - -_LOGGER = logging.getLogger(__name__) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Create the SolarEdge Monitoring API sensor.""" - import solaredge - from requests.exceptions import HTTPError, ConnectTimeout - - api_key = config[CONF_API_KEY] - site_id = config[CONF_SITE_ID] - platform_name = config[CONF_NAME] - - # Create new SolarEdge object to retrieve data - api = solaredge.Solaredge(api_key) - - # Check if api can be reached and site is active - try: - response = api.get_details(site_id) - - if response['details']['status'].lower() != 'active': - _LOGGER.error("SolarEdge site is not active") - return - _LOGGER.debug("Credentials correct and site is active") - except KeyError: - _LOGGER.error("Missing details data in solaredge response") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve details from SolarEdge API") - return - - # Create solaredge data service which will retrieve and update the data. - data = SolarEdgeData(hass, api, site_id) - - # Create a new sensor for each sensor type. - entities = [] - for sensor_key in config[CONF_MONITORED_CONDITIONS]: - sensor = SolarEdgeSensor(platform_name, sensor_key, data) - entities.append(sensor) - - add_entities(entities, True) - - -class SolarEdgeSensor(Entity): - """Representation of an SolarEdge Monitoring API sensor.""" - - def __init__(self, platform_name, sensor_key, data): - """Initialize the sensor.""" - self.platform_name = platform_name - self.sensor_key = sensor_key - self.data = data - self._state = None - - self._json_key = SENSOR_TYPES[self.sensor_key][0] - self._unit_of_measurement = SENSOR_TYPES[self.sensor_key][2] - - @property - def name(self): - """Return the name.""" - return "{} ({})".format(self.platform_name, - SENSOR_TYPES[self.sensor_key][1]) - - @property - def unit_of_measurement(self): - """Return the unit of measurement.""" - return self._unit_of_measurement - - @property - def icon(self): - """Return the sensor icon.""" - return SENSOR_TYPES[self.sensor_key][3] - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - def update(self): - """Get the latest data from the sensor and update the state.""" - self.data.update() - self._state = self.data.data[self._json_key] - - -class SolarEdgeData: - """Get and update the latest data.""" - - def __init__(self, hass, api, site_id): - """Initialize the data object.""" - self.hass = hass - self.api = api - self.data = {} - self.site_id = site_id - - @Throttle(UPDATE_DELAY) - def update(self): - """Update the data from the SolarEdge Monitoring API.""" - from requests.exceptions import HTTPError, ConnectTimeout - - try: - data = self.api.get_overview(self.site_id) - overview = data['overview'] - except KeyError: - _LOGGER.error("Missing overview data, skipping update") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve data, skipping update") - return - - self.data = {} - - for key, value in overview.items(): - if 'energy' in value: - self.data[key] = value['energy'] - elif 'power' in value: - self.data[key] = value['power'] - - _LOGGER.debug("Updated SolarEdge overview data: %s", self.data) diff --git a/homeassistant/components/serial/__init__.py b/homeassistant/components/serial/__init__.py new file mode 100644 index 00000000000..50bf5c4573b --- /dev/null +++ b/homeassistant/components/serial/__init__.py @@ -0,0 +1 @@ +"""The serial component.""" diff --git a/homeassistant/components/sensor/serial.py b/homeassistant/components/serial/sensor.py similarity index 100% rename from homeassistant/components/sensor/serial.py rename to homeassistant/components/serial/sensor.py diff --git a/homeassistant/components/serial_pm/__init__.py b/homeassistant/components/serial_pm/__init__.py new file mode 100644 index 00000000000..713d0bad66c --- /dev/null +++ b/homeassistant/components/serial_pm/__init__.py @@ -0,0 +1 @@ +"""The serial_pm component.""" diff --git a/homeassistant/components/sensor/serial_pm.py b/homeassistant/components/serial_pm/sensor.py similarity index 100% rename from homeassistant/components/sensor/serial_pm.py rename to homeassistant/components/serial_pm/sensor.py diff --git a/homeassistant/components/sesame/__init__.py b/homeassistant/components/sesame/__init__.py new file mode 100644 index 00000000000..f3c57e5cd07 --- /dev/null +++ b/homeassistant/components/sesame/__init__.py @@ -0,0 +1 @@ +"""The sesame component.""" diff --git a/homeassistant/components/lock/sesame.py b/homeassistant/components/sesame/lock.py similarity index 100% rename from homeassistant/components/lock/sesame.py rename to homeassistant/components/sesame/lock.py diff --git a/homeassistant/components/seven_segments/__init__.py b/homeassistant/components/seven_segments/__init__.py new file mode 100644 index 00000000000..6826c4396bb --- /dev/null +++ b/homeassistant/components/seven_segments/__init__.py @@ -0,0 +1 @@ +"""The seven_segments component.""" diff --git a/homeassistant/components/image_processing/seven_segments.py b/homeassistant/components/seven_segments/image_processing.py similarity index 100% rename from homeassistant/components/image_processing/seven_segments.py rename to homeassistant/components/seven_segments/image_processing.py diff --git a/homeassistant/components/seventeentrack/__init__.py b/homeassistant/components/seventeentrack/__init__.py new file mode 100644 index 00000000000..43eebf346f0 --- /dev/null +++ b/homeassistant/components/seventeentrack/__init__.py @@ -0,0 +1 @@ +"""The seventeentrack component.""" diff --git a/homeassistant/components/sensor/seventeentrack.py b/homeassistant/components/seventeentrack/sensor.py similarity index 100% rename from homeassistant/components/sensor/seventeentrack.py rename to homeassistant/components/seventeentrack/sensor.py diff --git a/homeassistant/components/shodan/__init__.py b/homeassistant/components/shodan/__init__.py new file mode 100644 index 00000000000..76439c48080 --- /dev/null +++ b/homeassistant/components/shodan/__init__.py @@ -0,0 +1 @@ +"""The shodan component.""" diff --git a/homeassistant/components/sensor/shodan.py b/homeassistant/components/shodan/sensor.py similarity index 100% rename from homeassistant/components/sensor/shodan.py rename to homeassistant/components/shodan/sensor.py diff --git a/homeassistant/components/sht31/__init__.py b/homeassistant/components/sht31/__init__.py new file mode 100644 index 00000000000..16bfe384d94 --- /dev/null +++ b/homeassistant/components/sht31/__init__.py @@ -0,0 +1 @@ +"""The sht31 component.""" diff --git a/homeassistant/components/sensor/sht31.py b/homeassistant/components/sht31/sensor.py similarity index 100% rename from homeassistant/components/sensor/sht31.py rename to homeassistant/components/sht31/sensor.py diff --git a/homeassistant/components/sigfox/__init__.py b/homeassistant/components/sigfox/__init__.py new file mode 100644 index 00000000000..ead4ea997f9 --- /dev/null +++ b/homeassistant/components/sigfox/__init__.py @@ -0,0 +1 @@ +"""The sigfox component.""" diff --git a/homeassistant/components/sensor/sigfox.py b/homeassistant/components/sigfox/sensor.py similarity index 100% rename from homeassistant/components/sensor/sigfox.py rename to homeassistant/components/sigfox/sensor.py diff --git a/homeassistant/components/simplepush/__init__.py b/homeassistant/components/simplepush/__init__.py new file mode 100644 index 00000000000..8253cfad8b4 --- /dev/null +++ b/homeassistant/components/simplepush/__init__.py @@ -0,0 +1 @@ +"""The simplepush component.""" diff --git a/homeassistant/components/notify/simplepush.py b/homeassistant/components/simplepush/notify.py similarity index 99% rename from homeassistant/components/notify/simplepush.py rename to homeassistant/components/simplepush/notify.py index 9d5c58fc5b1..63222d4adc1 100644 --- a/homeassistant/components/notify/simplepush.py +++ b/homeassistant/components/simplepush/notify.py @@ -8,10 +8,11 @@ import logging import voluptuous as vol +from homeassistant.const import CONF_PASSWORD import homeassistant.helpers.config_validation as cv + from homeassistant.components.notify import ( ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) -from homeassistant.const import CONF_PASSWORD REQUIREMENTS = ['simplepush==1.1.4'] diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index f494ccf390e..e6b9aba643d 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -109,7 +109,14 @@ async def async_setup_entry(hass, config_entry): """Refresh data from the SimpliSafe account.""" for system in systems: _LOGGER.debug('Updating system data: %s', system.system_id) - await system.update() + + try: + await system.update() + except SimplipyError as err: + _LOGGER.error( + 'There was error updating "%s": %s', system.address, err) + continue + async_dispatcher_send(hass, TOPIC_UPDATE.format(system.system_id)) if system.api.refresh_token_dirty: diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 9fdeea73da8..69311778698 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -3,14 +3,14 @@ import logging import re import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.simplisafe.const import ( - DATA_CLIENT, DOMAIN, TOPIC_UPDATE) from homeassistant.const import ( CONF_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from .const import DATA_CLIENT, DOMAIN, TOPIC_UPDATE + _LOGGER = logging.getLogger(__name__) ATTR_ALARM_ACTIVE = 'alarm_active' diff --git a/homeassistant/components/simulated/__init__.py b/homeassistant/components/simulated/__init__.py new file mode 100644 index 00000000000..35c6d106d03 --- /dev/null +++ b/homeassistant/components/simulated/__init__.py @@ -0,0 +1 @@ +"""The simulated component.""" diff --git a/homeassistant/components/sensor/simulated.py b/homeassistant/components/simulated/sensor.py similarity index 100% rename from homeassistant/components/sensor/simulated.py rename to homeassistant/components/simulated/sensor.py diff --git a/homeassistant/components/sisyphus/light.py b/homeassistant/components/sisyphus/light.py index c9d20959696..182e5e78198 100644 --- a/homeassistant/components/sisyphus/light.py +++ b/homeassistant/components/sisyphus/light.py @@ -1,9 +1,10 @@ """Support for the light on the Sisyphus Kinetic Art Table.""" import logging -from homeassistant.const import CONF_NAME from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light -from homeassistant.components.sisyphus import DATA_SISYPHUS +from homeassistant.const import CONF_NAME + +from . import DATA_SISYPHUS _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sisyphus/media_player.py b/homeassistant/components/sisyphus/media_player.py index 463ac2b6cd1..11546c3fd43 100644 --- a/homeassistant/components/sisyphus/media_player.py +++ b/homeassistant/components/sisyphus/media_player.py @@ -6,10 +6,11 @@ from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) -from homeassistant.components.sisyphus import DATA_SISYPHUS from homeassistant.const import ( CONF_HOST, CONF_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) +from . import DATA_SISYPHUS + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['sisyphus'] diff --git a/homeassistant/components/sky_hub/__init__.py b/homeassistant/components/sky_hub/__init__.py new file mode 100644 index 00000000000..a5b8969018f --- /dev/null +++ b/homeassistant/components/sky_hub/__init__.py @@ -0,0 +1 @@ +"""The sky_hub component.""" diff --git a/homeassistant/components/device_tracker/sky_hub.py b/homeassistant/components/sky_hub/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/sky_hub.py rename to homeassistant/components/sky_hub/device_tracker.py diff --git a/homeassistant/components/skybeacon/__init__.py b/homeassistant/components/skybeacon/__init__.py new file mode 100644 index 00000000000..5e0a7b7a4a3 --- /dev/null +++ b/homeassistant/components/skybeacon/__init__.py @@ -0,0 +1 @@ +"""The skybeacon component.""" diff --git a/homeassistant/components/sensor/skybeacon.py b/homeassistant/components/skybeacon/sensor.py similarity index 100% rename from homeassistant/components/sensor/skybeacon.py rename to homeassistant/components/skybeacon/sensor.py diff --git a/homeassistant/components/skybell/binary_sensor.py b/homeassistant/components/skybell/binary_sensor.py index 169e1b51a4e..8c2b8355258 100644 --- a/homeassistant/components/skybell/binary_sensor.py +++ b/homeassistant/components/skybell/binary_sensor.py @@ -5,13 +5,13 @@ import logging import voluptuous as vol from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.components.skybell import ( - DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice) + PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.const import ( CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) import homeassistant.helpers.config_validation as cv +from . import DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice + DEPENDENCIES = ['skybell'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/skybell/camera.py b/homeassistant/components/skybell/camera.py index c22489aa654..04b03f84bf7 100644 --- a/homeassistant/components/skybell/camera.py +++ b/homeassistant/components/skybell/camera.py @@ -5,13 +5,11 @@ import logging import requests import voluptuous as vol -from homeassistant.components.camera import PLATFORM_SCHEMA +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv -from homeassistant.components.camera import Camera -from homeassistant.components.skybell import ( - DOMAIN as SKYBELL_DOMAIN, SkybellDevice) +from . import DOMAIN as SKYBELL_DOMAIN, SkybellDevice DEPENDENCIES = ['skybell'] diff --git a/homeassistant/components/skybell/light.py b/homeassistant/components/skybell/light.py index 02be279f609..d413f9df412 100644 --- a/homeassistant/components/skybell/light.py +++ b/homeassistant/components/skybell/light.py @@ -1,14 +1,12 @@ """Light/LED support for the Skybell HD Doorbell.""" import logging - from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR, Light) -from homeassistant.components.skybell import ( - DOMAIN as SKYBELL_DOMAIN, SkybellDevice) + ATTR_BRIGHTNESS, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, Light) import homeassistant.util.color as color_util +from . import DOMAIN as SKYBELL_DOMAIN, SkybellDevice + DEPENDENCIES = ['skybell'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/skybell/sensor.py b/homeassistant/components/skybell/sensor.py index 89841ae74ef..067e850dfcf 100644 --- a/homeassistant/components/skybell/sensor.py +++ b/homeassistant/components/skybell/sensor.py @@ -5,12 +5,12 @@ import logging import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.components.skybell import ( - DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice) from homeassistant.const import ( CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) import homeassistant.helpers.config_validation as cv +from . import DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice + DEPENDENCIES = ['skybell'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/skybell/switch.py b/homeassistant/components/skybell/switch.py index 32f1d7f9392..674bbf22a08 100644 --- a/homeassistant/components/skybell/switch.py +++ b/homeassistant/components/skybell/switch.py @@ -3,14 +3,13 @@ import logging import voluptuous as vol - -from homeassistant.components.skybell import ( - DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice) from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import ( CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) import homeassistant.helpers.config_validation as cv +from . import DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice + DEPENDENCIES = ['skybell'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/slack/__init__.py b/homeassistant/components/slack/__init__.py new file mode 100644 index 00000000000..a999c2375ff --- /dev/null +++ b/homeassistant/components/slack/__init__.py @@ -0,0 +1 @@ +"""The slack component.""" diff --git a/homeassistant/components/notify/slack.py b/homeassistant/components/slack/notify.py similarity index 96% rename from homeassistant/components/notify/slack.py rename to homeassistant/components/slack/notify.py index 961f671203f..eabddf01299 100644 --- a/homeassistant/components/notify/slack.py +++ b/homeassistant/components/slack/notify.py @@ -7,15 +7,15 @@ https://home-assistant.io/components/notify.slack/ import logging import requests -from requests.auth import HTTPBasicAuth -from requests.auth import HTTPDigestAuth +from requests.auth import HTTPBasicAuth, HTTPDigestAuth import voluptuous as vol +from homeassistant.const import CONF_API_KEY, CONF_ICON, CONF_USERNAME import homeassistant.helpers.config_validation as cv + from homeassistant.components.notify import ( - ATTR_TARGET, ATTR_TITLE, ATTR_DATA, PLATFORM_SCHEMA, + ATTR_DATA, ATTR_TARGET, ATTR_TITLE, PLATFORM_SCHEMA, BaseNotificationService) -from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_ICON) REQUIREMENTS = ['slacker==0.12.0'] diff --git a/homeassistant/components/binary_sensor/sleepiq.py b/homeassistant/components/sleepiq/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/sleepiq.py rename to homeassistant/components/sleepiq/binary_sensor.py diff --git a/homeassistant/components/sensor/sleepiq.py b/homeassistant/components/sleepiq/sensor.py similarity index 100% rename from homeassistant/components/sensor/sleepiq.py rename to homeassistant/components/sleepiq/sensor.py diff --git a/homeassistant/components/sma/__init__.py b/homeassistant/components/sma/__init__.py new file mode 100644 index 00000000000..97d7147596c --- /dev/null +++ b/homeassistant/components/sma/__init__.py @@ -0,0 +1 @@ +"""The sma component.""" diff --git a/homeassistant/components/sensor/sma.py b/homeassistant/components/sma/sensor.py similarity index 100% rename from homeassistant/components/sensor/sma.py rename to homeassistant/components/sma/sensor.py diff --git a/homeassistant/components/smappee/sensor.py b/homeassistant/components/smappee/sensor.py index 09584851c6a..98527c769d9 100644 --- a/homeassistant/components/smappee/sensor.py +++ b/homeassistant/components/smappee/sensor.py @@ -1,10 +1,11 @@ """Support for monitoring a Smappee energy sensor.""" -import logging from datetime import timedelta +import logging -from homeassistant.components.smappee import DATA_SMAPPEE +from homeassistant.const import ENERGY_KILO_WATT_HOUR, POWER_WATT from homeassistant.helpers.entity import Entity -from homeassistant.const import POWER_WATT, ENERGY_KILO_WATT_HOUR + +from . import DATA_SMAPPEE DEPENDENCIES = ['smappee'] diff --git a/homeassistant/components/smappee/switch.py b/homeassistant/components/smappee/switch.py index 3b9bee081f7..963caf457fe 100644 --- a/homeassistant/components/smappee/switch.py +++ b/homeassistant/components/smappee/switch.py @@ -1,8 +1,9 @@ """Support for interacting with Smappee Comport Plugs.""" import logging -from homeassistant.components.smappee import DATA_SMAPPEE -from homeassistant.components.switch import (SwitchDevice) +from homeassistant.components.switch import SwitchDevice + +from . import DATA_SMAPPEE DEPENDENCIES = ['smappee'] diff --git a/homeassistant/components/smartthings/.translations/bg.json b/homeassistant/components/smartthings/.translations/bg.json new file mode 100644 index 00000000000..8a13a76a2a9 --- /dev/null +++ b/homeassistant/components/smartthings/.translations/bg.json @@ -0,0 +1,28 @@ +{ + "config": { + "error": { + "app_not_installed": "\u041c\u043e\u043b\u044f, \u0443\u0432\u0435\u0440\u0435\u0442\u0435 \u0441\u0435, \u0447\u0435 \u0441\u0442\u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043b\u0438 \u0438 \u043e\u0442\u043e\u0440\u0438\u0437\u0438\u0440\u0430\u043b\u0438 HomeAmistant SmartApp \u0438 \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e.", + "app_setup_error": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u043d\u0435 \u043d\u0430 SmartApp. \u041c\u043e\u043b\u044f \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e.", + "base_url_not_https": "`base_url` \u0437\u0430 `http` \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d \u0438 \u0434\u0430 \u0437\u0430\u043f\u043e\u0447\u0432\u0430 \u0441 `https://`", + "token_already_setup": "\u041a\u043e\u0434\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d.", + "token_forbidden": "\u041a\u043e\u0434\u044a\u0442 \u043d\u044f\u043c\u0430 \u0438\u0437\u0438\u0441\u043a\u0443\u0435\u043c\u0438\u0442\u0435 OAuth \u043f\u0440\u0430\u0432\u0430.", + "token_invalid_format": "\u041a\u043e\u0434\u044a\u0442 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0435 \u0432\u044a\u0432 \u0444\u043e\u0440\u043c\u0430\u0442 UID/GUID", + "token_unauthorized": "\u041a\u043e\u0434\u044a\u0442 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d \u0438\u043b\u0438 \u0432\u0435\u0447\u0435 \u043d\u0435 \u0435 \u043e\u0442\u043e\u0440\u0438\u0437\u0438\u0440\u0430\u043d.", + "webhook_error": "SmartThings \u043d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0430 \u0430\u0434\u0440\u0435\u0441\u0430, \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d \u0432 `base_url`. \u041c\u043e\u043b\u044f, \u043f\u0440\u0435\u0433\u043b\u0435\u0434\u0430\u0439\u0442\u0435 \u0438\u0437\u0438\u0441\u043a\u0432\u0430\u043d\u0438\u044f\u0442\u0430 \u0437\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430." + }, + "step": { + "user": { + "data": { + "access_token": "\u041a\u043e\u0434 \u0437\u0430 \u0434\u043e\u0441\u0442\u044a\u043f" + }, + "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 SmartThings [Personal Access Token] ( {token_url} ), \u043a\u043e\u0439\u0442\u043e \u0435 \u0441\u044a\u0437\u0434\u0430\u0434\u0435\u043d \u0441\u043f\u043e\u0440\u0435\u0434 [\u0443\u043a\u0430\u0437\u0430\u043d\u0438\u044f\u0442\u0430] ( {component_url} ).", + "title": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043a\u043e\u0434 \u0437\u0430 \u0434\u043e\u0441\u0442\u044a\u043f (Personal Access Token)" + }, + "wait_install": { + "description": "\u041c\u043e\u043b\u044f, \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u0439\u0442\u0435 Home Assistant SmartApp \u043f\u043e\u043d\u0435 \u043d\u0430 \u0435\u0434\u043d\u043e \u043c\u044f\u0441\u0442\u043e \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u0432\u044a\u0440\u0445\u0443 \u0417\u0430\u043f\u0430\u0437\u0432\u0430\u043d\u0435.", + "title": "\u0418\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u0439\u0442\u0435 SmartApp" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/fr.json b/homeassistant/components/smartthings/.translations/fr.json index 6503634b92c..56c30acbf1b 100644 --- a/homeassistant/components/smartthings/.translations/fr.json +++ b/homeassistant/components/smartthings/.translations/fr.json @@ -5,17 +5,21 @@ "app_setup_error": "Impossible de configurer la SmartApp. Veuillez r\u00e9essayer.", "base_url_not_https": "Le param\u00e8tre `base_url` du composant` http` doit \u00eatre configur\u00e9 et commencer par `https: //`.", "token_already_setup": "Le jeton a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9.", + "token_forbidden": "Le jeton n'a pas les port\u00e9es OAuth requises.", "token_invalid_format": "Le jeton doit \u00eatre au format UID / GUID", - "token_unauthorized": "Le jeton est invalide ou n'est plus autoris\u00e9." + "token_unauthorized": "Le jeton est invalide ou n'est plus autoris\u00e9.", + "webhook_error": "SmartThings n'a pas pu valider le point de terminaison configur\u00e9 en \u00ab\u00a0base_url\u00a0\u00bb. Veuillez consulter les exigences du composant." }, "step": { "user": { "data": { "access_token": "Jeton d'acc\u00e8s" }, + "description": "Veuillez entrer un [jeton d'acc\u00e8s personnel SmartThings] ( {token_url} ) cr\u00e9\u00e9 selon les [instructions] ( {component_url} ).", "title": "Entrer un jeton d'acc\u00e8s personnel" }, "wait_install": { + "description": "Veuillez installer la SmartApp de Home Assistant dans au moins un emplacement et cliquez sur Soumettre.", "title": "Installer SmartApp" } }, diff --git a/homeassistant/components/smartthings/.translations/sl.json b/homeassistant/components/smartthings/.translations/sl.json index e274d8c9394..506eb98cc95 100644 --- a/homeassistant/components/smartthings/.translations/sl.json +++ b/homeassistant/components/smartthings/.translations/sl.json @@ -7,7 +7,8 @@ "token_already_setup": "\u017deton je \u017ee nastavljen.", "token_forbidden": "\u017deton nima zahtevanih OAuth obsegov.", "token_invalid_format": "\u017deton mora biti v formatu UID / GUID", - "token_unauthorized": "\u017deton ni veljaven ali ni ve\u010d poobla\u0161\u010den." + "token_unauthorized": "\u017deton ni veljaven ali ni ve\u010d poobla\u0161\u010den.", + "webhook_error": "SmartThings ni mogel potrditi kon\u010dne to\u010dke nastavljene v 'base_url`. Prosimo, preglejte zahteve komponente." }, "step": { "user": { diff --git a/homeassistant/components/smartthings/.translations/zh-Hans.json b/homeassistant/components/smartthings/.translations/zh-Hans.json new file mode 100644 index 00000000000..2326c394cc0 --- /dev/null +++ b/homeassistant/components/smartthings/.translations/zh-Hans.json @@ -0,0 +1,28 @@ +{ + "config": { + "error": { + "app_not_installed": "\u8bf7\u786e\u4fdd\u60a8\u5df2\u5b89\u88c5\u5e76\u6388\u6743 Home Assistant SmartApp\uff0c\u7136\u540e\u518d\u8bd5\u4e00\u6b21\u3002", + "app_setup_error": "\u65e0\u6cd5\u8bbe\u7f6e SmartApp\u3002\u8bf7\u518d\u8bd5\u4e00\u6b21\u3002", + "base_url_not_https": "\u5fc5\u987b\u914d\u7f6e `http` \u7ec4\u4ef6\u7684 `base_url` \u5e76\u4ee5 `https://` \u5f00\u5934\u3002", + "token_already_setup": "\u4ee4\u724c\u5df2\u7ecf\u8bbe\u7f6e\u3002", + "token_forbidden": "\u4ee4\u724c\u6ca1\u6709\u6240\u9700\u7684 OAuth \u4f5c\u7528\u57df\u3002", + "token_invalid_format": "\u4ee4\u724c\u5fc5\u987b\u7b26\u5408 UID/GUID \u683c\u5f0f", + "token_unauthorized": "\u4ee4\u724c\u65e0\u6548\u6216\u5df2\u5931\u6548\u3002", + "webhook_error": "SmartThings \u65e0\u6cd5\u9a8c\u8bc1 `base_url` \u4e2d\u914d\u7f6e\u7684\u7aef\u70b9\u3002\u8bf7\u67e5\u770b\u7ec4\u4ef6\u9700\u6c42\u3002" + }, + "step": { + "user": { + "data": { + "access_token": "\u8bbf\u95ee\u4ee4\u724c" + }, + "description": "\u8bf7\u8f93\u5165\u6309\u7167[\u8bf4\u660e]({component_url})\u521b\u5efa\u7684 SmartThings [\u4e2a\u4eba\u8bbf\u95ee\u4ee4\u724c]({token_url})\u3002", + "title": "\u8f93\u5165\u4e2a\u4eba\u8bbf\u95ee\u4ee4\u724c" + }, + "wait_install": { + "description": "\u8bf7\u81f3\u5c11\u5728\u4e00\u4e2a\u4f4d\u7f6e\u5b89\u88c5 Home Assistant SmartApp\uff0c\u7136\u540e\u70b9\u51fb\u201c\u63d0\u4ea4\u201d\u3002", + "title": "\u5b89\u88c5 SmartApp" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py index 131da75f4fe..53602c3643c 100644 --- a/homeassistant/components/smartthings/cover.py +++ b/homeassistant/components/smartthings/cover.py @@ -135,6 +135,8 @@ class SmartThingsCover(SmartThingsEntity, CoverDevice): @property def current_cover_position(self): """Return current position of cover.""" + if not self._supported_features & SUPPORT_SET_POSITION: + return None return self._device.status.level @property diff --git a/homeassistant/components/smhi/const.py b/homeassistant/components/smhi/const.py index 9689857e546..4cd1d6f7b4b 100644 --- a/homeassistant/components/smhi/const.py +++ b/homeassistant/components/smhi/const.py @@ -13,4 +13,4 @@ ENTITY_ID_SENSOR_FORMAT = WEATHER_DOMAIN + ".smhi_{}" ENTITY_ID_SENSOR_FORMAT_HOME = ENTITY_ID_SENSOR_FORMAT.format( HOME_LOCATION_NAME) -LOGGER = logging.getLogger('homeassistant.components.smhi') +LOGGER = logging.getLogger('.') diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index 6136d093a33..fc3399f755c 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -7,8 +7,6 @@ from typing import Dict, List import aiohttp import async_timeout -from homeassistant.components.smhi.const import ( - ATTR_SMHI_CLOUDINESS, ENTITY_ID_SENSOR_FORMAT) from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, WeatherEntity) @@ -19,6 +17,8 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import aiohttp_client from homeassistant.util import Throttle, slugify +from .const import ATTR_SMHI_CLOUDINESS, ENTITY_ID_SENSOR_FORMAT + DEPENDENCIES = ['smhi'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/smtp/__init__.py b/homeassistant/components/smtp/__init__.py new file mode 100644 index 00000000000..5e7fb41c212 --- /dev/null +++ b/homeassistant/components/smtp/__init__.py @@ -0,0 +1 @@ +"""The smtp component.""" diff --git a/homeassistant/components/notify/smtp.py b/homeassistant/components/smtp/notify.py similarity index 97% rename from homeassistant/components/notify/smtp.py rename to homeassistant/components/smtp/notify.py index fc38647a065..4104013bcf7 100644 --- a/homeassistant/components/notify/smtp.py +++ b/homeassistant/components/smtp/notify.py @@ -4,25 +4,26 @@ Mail (SMTP) notification service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.smtp/ """ -import logging -import smtplib +from email.mime.application import MIMEApplication +from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from email.mime.image import MIMEImage -from email.mime.application import MIMEApplication import email.utils +import logging import os +import smtplib import voluptuous as vol +from homeassistant.const import ( + CONF_PASSWORD, CONF_PORT, CONF_RECIPIENT, CONF_SENDER, CONF_TIMEOUT, + CONF_USERNAME) import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util + from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_DATA, PLATFORM_SCHEMA, + ATTR_DATA, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) -from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, CONF_PORT, CONF_TIMEOUT, - CONF_SENDER, CONF_RECIPIENT) _LOGGER = logging.getLogger(__name__) @@ -151,10 +152,10 @@ class MailNotificationService(BaseNotificationService): if data: if ATTR_HTML in data: msg = _build_html_msg( - message, data[ATTR_HTML], images=data.get(ATTR_IMAGES)) + message, data[ATTR_HTML], images=data.get(ATTR_IMAGES, [])) else: msg = _build_multipart_msg( - message, images=data.get(ATTR_IMAGES)) + message, images=data.get(ATTR_IMAGES, [])) else: msg = _build_text_msg(message) diff --git a/homeassistant/components/snapcast/__init__.py b/homeassistant/components/snapcast/__init__.py new file mode 100644 index 00000000000..b5279fa3ce0 --- /dev/null +++ b/homeassistant/components/snapcast/__init__.py @@ -0,0 +1 @@ +"""The snapcast component.""" diff --git a/homeassistant/components/media_player/snapcast.py b/homeassistant/components/snapcast/media_player.py similarity index 100% rename from homeassistant/components/media_player/snapcast.py rename to homeassistant/components/snapcast/media_player.py diff --git a/homeassistant/components/snmp/__init__.py b/homeassistant/components/snmp/__init__.py new file mode 100644 index 00000000000..a4c922877f3 --- /dev/null +++ b/homeassistant/components/snmp/__init__.py @@ -0,0 +1 @@ +"""The snmp component.""" diff --git a/homeassistant/components/device_tracker/snmp.py b/homeassistant/components/snmp/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/snmp.py rename to homeassistant/components/snmp/device_tracker.py diff --git a/homeassistant/components/sensor/snmp.py b/homeassistant/components/snmp/sensor.py similarity index 100% rename from homeassistant/components/sensor/snmp.py rename to homeassistant/components/snmp/sensor.py diff --git a/homeassistant/components/switch/snmp.py b/homeassistant/components/snmp/switch.py similarity index 100% rename from homeassistant/components/switch/snmp.py rename to homeassistant/components/snmp/switch.py diff --git a/homeassistant/components/sochain/__init__.py b/homeassistant/components/sochain/__init__.py new file mode 100644 index 00000000000..31a000d3947 --- /dev/null +++ b/homeassistant/components/sochain/__init__.py @@ -0,0 +1 @@ +"""The sochain component.""" diff --git a/homeassistant/components/sensor/sochain.py b/homeassistant/components/sochain/sensor.py similarity index 100% rename from homeassistant/components/sensor/sochain.py rename to homeassistant/components/sochain/sensor.py diff --git a/homeassistant/components/socialblade/__init__.py b/homeassistant/components/socialblade/__init__.py new file mode 100644 index 00000000000..c497d99d32c --- /dev/null +++ b/homeassistant/components/socialblade/__init__.py @@ -0,0 +1 @@ +"""The socialblade component.""" diff --git a/homeassistant/components/sensor/socialblade.py b/homeassistant/components/socialblade/sensor.py similarity index 100% rename from homeassistant/components/sensor/socialblade.py rename to homeassistant/components/socialblade/sensor.py diff --git a/homeassistant/components/solaredge/__init__.py b/homeassistant/components/solaredge/__init__.py new file mode 100644 index 00000000000..b675126c5fd --- /dev/null +++ b/homeassistant/components/solaredge/__init__.py @@ -0,0 +1 @@ +"""The solaredge component.""" diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py new file mode 100644 index 00000000000..d56ccc53b68 --- /dev/null +++ b/homeassistant/components/solaredge/sensor.py @@ -0,0 +1,430 @@ +""" +Support for SolarEdge Monitoring API. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.solaredge/ +""" + +from datetime import timedelta +import logging + +import voluptuous as vol + +from requests.exceptions import HTTPError, ConnectTimeout +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_API_KEY, CONF_MONITORED_CONDITIONS, CONF_NAME, POWER_WATT, + ENERGY_WATT_HOUR) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle + +REQUIREMENTS = ['solaredge==0.0.2', 'stringcase==1.2.0'] + +# Config for solaredge monitoring api requests. +CONF_SITE_ID = "site_id" + +OVERVIEW_UPDATE_DELAY = timedelta(minutes=10) +DETAILS_UPDATE_DELAY = timedelta(hours=12) +INVENTORY_UPDATE_DELAY = timedelta(hours=12) +POWER_FLOW_UPDATE_DELAY = timedelta(minutes=10) + +SCAN_INTERVAL = timedelta(minutes=10) + +# Supported overview sensor types: +# Key: ['json_key', 'name', unit, icon] +SENSOR_TYPES = { + 'lifetime_energy': ['lifeTimeData', "Lifetime energy", + ENERGY_WATT_HOUR, 'mdi:solar-power'], + 'energy_this_year': ['lastYearData', "Energy this year", + ENERGY_WATT_HOUR, 'mdi:solar-power'], + 'energy_this_month': ['lastMonthData', "Energy this month", + ENERGY_WATT_HOUR, 'mdi:solar-power'], + 'energy_today': ['lastDayData', "Energy today", + ENERGY_WATT_HOUR, 'mdi:solar-power'], + 'current_power': ['currentPower', "Current Power", POWER_WATT, + 'mdi:solar-power'], + 'site_details': [None, 'Site details', None, None], + 'meters': ['meters', 'Meters', None, None], + 'sensors': ['sensors', 'Sensors', None, None], + 'gateways': ['gateways', 'Gateways', None, None], + 'batteries': ['batteries', 'Batteries', None, None], + 'inverters': ['inverters', 'Inverters', None, None], + 'power_consumption': ['LOAD', 'Power Consumption', None, 'mdi:flash'], + 'solar_power': ['PV', 'Solar Power', None, 'mdi:solar-power'], + 'grid_power': ['GRID', 'Grid Power', None, 'mdi:power-plug'], + 'storage_power': ['STORAGE', 'Storage Power', None, 'mdi:car-battery'] +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_SITE_ID): cv.string, + vol.Optional(CONF_NAME, default='SolarEdge'): cv.string, + vol.Optional(CONF_MONITORED_CONDITIONS, default=['current_power']): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]) +}) + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Create the SolarEdge Monitoring API sensor.""" + import solaredge + + api_key = config[CONF_API_KEY] + site_id = config[CONF_SITE_ID] + platform_name = config[CONF_NAME] + + # Create new SolarEdge object to retrieve data + api = solaredge.Solaredge(api_key) + + # Check if api can be reached and site is active + try: + response = api.get_details(site_id) + + if response['details']['status'].lower() != 'active': + _LOGGER.error("SolarEdge site is not active") + return + _LOGGER.debug("Credentials correct and site is active") + except KeyError: + _LOGGER.error("Missing details data in solaredge response") + return + except (ConnectTimeout, HTTPError): + _LOGGER.error("Could not retrieve details from SolarEdge API") + return + + # Create sensor factory that will create sensors based on sensor_key. + sensor_factory = SolarEdgeSensorFactory(platform_name, site_id, api) + + # Create a new sensor for each sensor type. + entities = [] + for sensor_key in config[CONF_MONITORED_CONDITIONS]: + sensor = sensor_factory.create_sensor(sensor_key) + if sensor is not None: + entities.append(sensor) + + add_entities(entities, True) + + +class SolarEdgeSensorFactory: + """Factory which creates sensors based on the sensor_key.""" + + def __init__(self, platform_name, site_id, api): + """Initialize the factory.""" + self.platform_name = platform_name + + details = SolarEdgeDetailsDataService(api, site_id) + overview = SolarEdgeOverviewDataService(api, site_id) + inventory = SolarEdgeInventoryDataService(api, site_id) + flow = SolarEdgePowerFlowDataService(api, site_id) + + self.services = { + 'site_details': (SolarEdgeDetailsSensor, details) + } + + for key in ['lifetime_energy', 'energy_this_year', 'energy_this_month', + 'energy_today', 'current_power']: + self.services[key] = (SolarEdgeOverviewSensor, overview) + + for key in ['meters', 'sensors', 'gateways', 'batteries', 'inverters']: + self.services[key] = (SolarEdgeInventorySensor, inventory) + + for key in ['power_consumption', 'solar_power', 'grid_power', + 'storage_power']: + self.services[key] = (SolarEdgePowerFlowSensor, flow) + + def create_sensor(self, sensor_key): + """Create and return a sensor based on the sensor_key.""" + sensor_class, service = self.services[sensor_key] + + return sensor_class(self.platform_name, sensor_key, service) + + +class SolarEdgeSensor(Entity): + """Abstract class for a solaredge sensor.""" + + def __init__(self, platform_name, sensor_key, data_service): + """Initialize the sensor.""" + self.platform_name = platform_name + self.sensor_key = sensor_key + self.data_service = data_service + + self._state = None + + self._unit_of_measurement = SENSOR_TYPES[self.sensor_key][2] + self._icon = SENSOR_TYPES[self.sensor_key][3] + + @property + def name(self): + """Return the name.""" + return "{} ({})".format(self.platform_name, + SENSOR_TYPES[self.sensor_key][1]) + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self._unit_of_measurement + + @property + def icon(self): + """Return the sensor icon.""" + return self._icon + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + +class SolarEdgeOverviewSensor(SolarEdgeSensor): + """Representation of an SolarEdge Monitoring API overview sensor.""" + + def __init__(self, platform_name, sensor_key, data_service): + """Initialize the overview sensor.""" + super().__init__(platform_name, sensor_key, data_service) + + self._json_key = SENSOR_TYPES[self.sensor_key][0] + + def update(self): + """Get the latest data from the sensor and update the state.""" + self.data_service.update() + self._state = self.data_service.data[self._json_key] + + +class SolarEdgeDetailsSensor(SolarEdgeSensor): + """Representation of an SolarEdge Monitoring API details sensor.""" + + def __init__(self, platform_name, sensor_key, data_service): + """Initialize the details sensor.""" + super().__init__(platform_name, sensor_key, data_service) + + self._attributes = {} + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attributes + + def update(self): + """Get the latest details and update state and attributes.""" + self.data_service.update() + self._state = self.data_service.data + self._attributes = self.data_service.attributes + + +class SolarEdgeInventorySensor(SolarEdgeSensor): + """Representation of an SolarEdge Monitoring API inventory sensor.""" + + def __init__(self, platform_name, sensor_key, data_service): + """Initialize the inventory sensor.""" + super().__init__(platform_name, sensor_key, data_service) + + self._json_key = SENSOR_TYPES[self.sensor_key][0] + + self._attributes = {} + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attributes + + def update(self): + """Get the latest inventory data and update state and attributes.""" + self.data_service.update() + self._state = self.data_service.data[self._json_key] + self._attributes = self.data_service.attributes[self._json_key] + + +class SolarEdgePowerFlowSensor(SolarEdgeSensor): + """Representation of an SolarEdge Monitoring API power flow sensor.""" + + def __init__(self, platform_name, sensor_key, data_service): + """Initialize the power flow sensor.""" + super().__init__(platform_name, sensor_key, data_service) + + self._json_key = SENSOR_TYPES[self.sensor_key][0] + + self._attributes = {} + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attributes + + def update(self): + """Get the latest inventory data and update state and attributes.""" + self.data_service.update() + self._state = self.data_service.data.get(self._json_key) + self._attributes = self.data_service.attributes.get(self._json_key) + self._unit_of_measurement = self.data_service.unit + + +class SolarEdgeDataService: + """Get and update the latest data.""" + + def __init__(self, api, site_id): + """Initialize the data object.""" + self.api = api + self.site_id = site_id + + self.data = {} + self.attributes = {} + + +class SolarEdgeOverviewDataService(SolarEdgeDataService): + """Get and update the latest overview data.""" + + @Throttle(OVERVIEW_UPDATE_DELAY) + def update(self): + """Update the data from the SolarEdge Monitoring API.""" + try: + data = self.api.get_overview(self.site_id) + overview = data['overview'] + except KeyError: + _LOGGER.error("Missing overview data, skipping update") + return + except (ConnectTimeout, HTTPError): + _LOGGER.error("Could not retrieve data, skipping update") + return + + self.data = {} + + for key, value in overview.items(): + if key in ['lifeTimeData', 'lastYearData', + 'lastMonthData', 'lastDayData']: + data = value['energy'] + elif key in ['currentPower']: + data = value['power'] + else: + data = value + self.data[key] = data + + _LOGGER.debug("Updated SolarEdge overview: %s", self.data) + + +class SolarEdgeDetailsDataService(SolarEdgeDataService): + """Get and update the latest details data.""" + + def __init__(self, api, site_id): + """Initialize the details data service.""" + super().__init__(api, site_id) + + self.data = None + + @Throttle(DETAILS_UPDATE_DELAY) + def update(self): + """Update the data from the SolarEdge Monitoring API.""" + from stringcase import snakecase + + try: + data = self.api.get_details(self.site_id) + details = data['details'] + except KeyError: + _LOGGER.error("Missing details data, skipping update") + return + except (ConnectTimeout, HTTPError): + _LOGGER.error("Could not retrieve data, skipping update") + return + + self.data = None + self.attributes = {} + + for key, value in details.items(): + key = snakecase(key) + + if key in ['primary_module']: + for module_key, module_value in value.items(): + self.attributes[snakecase(module_key)] = module_value + elif key in ['peak_power', 'type', 'name', 'last_update_time', + 'installation_date']: + self.attributes[key] = value + elif key == 'status': + self.data = value + + _LOGGER.debug("Updated SolarEdge details: %s, %s", + self.data, self.attributes) + + +class SolarEdgeInventoryDataService(SolarEdgeDataService): + """Get and update the latest inventory data.""" + + @Throttle(INVENTORY_UPDATE_DELAY) + def update(self): + """Update the data from the SolarEdge Monitoring API.""" + try: + data = self.api.get_inventory(self.site_id) + inventory = data['Inventory'] + except KeyError: + _LOGGER.error("Missing inventory data, skipping update") + return + except (ConnectTimeout, HTTPError): + _LOGGER.error("Could not retrieve data, skipping update") + return + + self.data = {} + self.attributes = {} + + for key, value in inventory.items(): + self.data[key] = len(value) + self.attributes[key] = {key: value} + + _LOGGER.debug("Updated SolarEdge inventory: %s, %s", + self.data, self.attributes) + + +class SolarEdgePowerFlowDataService(SolarEdgeDataService): + """Get and update the latest power flow data.""" + + def __init__(self, api, site_id): + """Initialize the power flow data service.""" + super().__init__(api, site_id) + + self.unit = None + + @Throttle(POWER_FLOW_UPDATE_DELAY) + def update(self): + """Update the data from the SolarEdge Monitoring API.""" + try: + data = self.api.get_current_power_flow(self.site_id) + power_flow = data['siteCurrentPowerFlow'] + except KeyError: + _LOGGER.error("Missing power flow data, skipping update") + return + except (ConnectTimeout, HTTPError): + _LOGGER.error("Could not retrieve data, skipping update") + return + + power_from = [] + power_to = [] + + if 'connections' not in power_flow: + _LOGGER.error("Missing connections in power flow data") + return + + for connection in power_flow['connections']: + power_from.append(connection['from'].lower()) + power_to.append(connection['to'].lower()) + + self.data = {} + self.attributes = {} + self.unit = power_flow['unit'] + + for key, value in power_flow.items(): + if key in ['LOAD', 'PV', 'GRID', 'STORAGE']: + self.data[key] = value['currentPower'] + self.attributes[key] = {'status': value['status']} + + if key in ['GRID']: + export = key.lower() in power_to + self.data[key] *= -1 if export else 1 + self.attributes[key]['flow'] = ('export' if export + else 'import') + + if key in ['STORAGE']: + charge = key.lower() in power_to + self.data[key] *= -1 if charge else 1 + self.attributes[key]['flow'] = ('charge' if charge + else 'discharge') + + _LOGGER.debug("Updated SolarEdge power flow: %s, %s", + self.data, self.attributes) diff --git a/homeassistant/components/sonarr/__init__.py b/homeassistant/components/sonarr/__init__.py new file mode 100644 index 00000000000..63c194cc969 --- /dev/null +++ b/homeassistant/components/sonarr/__init__.py @@ -0,0 +1 @@ +"""The sonarr component.""" diff --git a/homeassistant/components/sensor/sonarr.py b/homeassistant/components/sonarr/sensor.py similarity index 100% rename from homeassistant/components/sensor/sonarr.py rename to homeassistant/components/sonarr/sensor.py diff --git a/homeassistant/components/songpal/__init__.py b/homeassistant/components/songpal/__init__.py new file mode 100644 index 00000000000..7b181d375a5 --- /dev/null +++ b/homeassistant/components/songpal/__init__.py @@ -0,0 +1 @@ +"""The songpal component.""" diff --git a/homeassistant/components/media_player/songpal.py b/homeassistant/components/songpal/media_player.py similarity index 100% rename from homeassistant/components/media_player/songpal.py rename to homeassistant/components/songpal/media_player.py diff --git a/homeassistant/components/sonos/.translations/th.json b/homeassistant/components/sonos/.translations/th.json new file mode 100644 index 00000000000..4efff6bffb4 --- /dev/null +++ b/homeassistant/components/sonos/.translations/th.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\u0e04\u0e38\u0e13\u0e15\u0e49\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32 Sonos \u0e2b\u0e23\u0e37\u0e2d\u0e44\u0e21\u0e48?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 684e25ba599..ba7854e4f0d 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -1,9 +1,9 @@ """Support to interface with Sonos players.""" +import asyncio import datetime import functools as ft import logging import socket -import asyncio import urllib import async_timeout @@ -11,20 +11,20 @@ import requests import voluptuous as vol from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) + PLATFORM_SCHEMA, MediaPlayerDevice) from homeassistant.components.media_player.const import ( - ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, - SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, - SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) -from homeassistant.components.sonos import DOMAIN as SONOS_DOMAIN + ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST, + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, + SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TIME, CONF_HOSTS, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) import homeassistant.helpers.config_validation as cv from homeassistant.util.dt import utcnow +from . import DOMAIN as SONOS_DOMAIN + DEPENDENCIES = ('sonos',) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sony_projector/__init__.py b/homeassistant/components/sony_projector/__init__.py new file mode 100644 index 00000000000..dfe52c7fa75 --- /dev/null +++ b/homeassistant/components/sony_projector/__init__.py @@ -0,0 +1 @@ +"""The sony_projector component.""" diff --git a/homeassistant/components/switch/sony_projector.py b/homeassistant/components/sony_projector/switch.py similarity index 100% rename from homeassistant/components/switch/sony_projector.py rename to homeassistant/components/sony_projector/switch.py diff --git a/homeassistant/components/soundtouch/__init__.py b/homeassistant/components/soundtouch/__init__.py new file mode 100644 index 00000000000..6cd3c88fefc --- /dev/null +++ b/homeassistant/components/soundtouch/__init__.py @@ -0,0 +1 @@ +"""The soundtouch component.""" diff --git a/homeassistant/components/media_player/soundtouch.py b/homeassistant/components/soundtouch/media_player.py similarity index 100% rename from homeassistant/components/media_player/soundtouch.py rename to homeassistant/components/soundtouch/media_player.py diff --git a/homeassistant/components/alarm_control_panel/spc.py b/homeassistant/components/spc/alarm_control_panel.py similarity index 97% rename from homeassistant/components/alarm_control_panel/spc.py rename to homeassistant/components/spc/alarm_control_panel.py index 7adbb616774..623a4b0dbd1 100644 --- a/homeassistant/components/alarm_control_panel/spc.py +++ b/homeassistant/components/spc/alarm_control_panel.py @@ -7,12 +7,13 @@ https://home-assistant.io/components/alarm_control_panel.spc/ import logging import homeassistant.components.alarm_control_panel as alarm -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.core import callback -from homeassistant.components.spc import (DATA_API, SIGNAL_UPDATE_ALARM) from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import DATA_API, SIGNAL_UPDATE_ALARM _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/spc.py b/homeassistant/components/spc/binary_sensor.py similarity index 96% rename from homeassistant/components/binary_sensor/spc.py rename to homeassistant/components/spc/binary_sensor.py index baa25266804..6a0712d62bb 100644 --- a/homeassistant/components/binary_sensor/spc.py +++ b/homeassistant/components/spc/binary_sensor.py @@ -7,9 +7,10 @@ https://home-assistant.io/components/binary_sensor.spc/ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.core import callback -from homeassistant.components.spc import (DATA_API, SIGNAL_UPDATE_SENSOR) +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import DATA_API, SIGNAL_UPDATE_SENSOR _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index 4eae738b0d3..f140f881ef4 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -1,21 +1,21 @@ """Support for testing internet speed via Speedtest.net.""" -import logging from datetime import timedelta +import logging import voluptuous as vol +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.const import ( + CONF_MONITORED_CONDITIONS, CONF_SCAN_INTERVAL, CONF_UPDATE_INTERVAL, + CONF_UPDATE_INTERVAL_INVALIDATION_VERSION) import homeassistant.helpers.config_validation as cv -from homeassistant.components.speedtestdotnet.const import DOMAIN, \ - DATA_UPDATED, SENSOR_TYPES -from homeassistant.const import CONF_MONITORED_CONDITIONS, \ - CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL, \ - CONF_UPDATE_INTERVAL_INVALIDATION_VERSION from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.event import async_track_time_interval -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -REQUIREMENTS = ['speedtest-cli==2.0.2'] +from .const import DATA_UPDATED, DOMAIN, SENSOR_TYPES + +REQUIREMENTS = ['speedtest-cli==2.1.1'] _LOGGER = logging.getLogger(__name__) @@ -34,8 +34,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.All(cv.time_period, cv.positive_timedelta), vol.Optional(CONF_MANUAL, default=False): cv.boolean, vol.Optional( - CONF_MONITORED_CONDITIONS, - default=list(SENSOR_TYPES) + CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES) ): vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES))]) }), cv.deprecated( @@ -55,8 +54,7 @@ async def async_setup(hass, config): if not conf[CONF_MANUAL]: async_track_time_interval( - hass, data.update, conf[CONF_SCAN_INTERVAL] - ) + hass, data.update, conf[CONF_SCAN_INTERVAL]) def update(call=None): """Service call to manually update the data.""" @@ -66,13 +64,8 @@ async def async_setup(hass, config): hass.async_create_task( async_load_platform( - hass, - SENSOR_DOMAIN, - DOMAIN, - conf[CONF_MONITORED_CONDITIONS], - config - ) - ) + hass, SENSOR_DOMAIN, DOMAIN, conf[CONF_MONITORED_CONDITIONS], + config)) return True @@ -89,7 +82,7 @@ class SpeedtestData: def update(self, now=None): """Get the latest data from speedtest.net.""" import speedtest - _LOGGER.debug("Executing speedtest.net speedtest") + _LOGGER.debug("Executing speedtest.net speed test") speed = speedtest.Speedtest() speed.get_servers(self._servers) speed.get_best_server() diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py index 4deb6550444..fb92bb76ac8 100644 --- a/homeassistant/components/speedtestdotnet/sensor.py +++ b/homeassistant/components/speedtestdotnet/sensor.py @@ -1,13 +1,14 @@ """Support for Speedtest.net internet speed testing sensor.""" import logging -from homeassistant.components.speedtestdotnet.const import \ - DOMAIN as SPEEDTESTDOTNET_DOMAIN, DATA_UPDATED, SENSOR_TYPES from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity +from .const import ( + DATA_UPDATED, DOMAIN as SPEEDTESTDOTNET_DOMAIN, SENSOR_TYPES) + DEPENDENCIES = ['speedtestdotnet'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/spider/climate.py b/homeassistant/components/spider/climate.py index b3380ec8fb4..3b612441a88 100644 --- a/homeassistant/components/spider/climate.py +++ b/homeassistant/components/spider/climate.py @@ -4,12 +4,12 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - STATE_COOL, STATE_HEAT, STATE_IDLE, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_FAN_MODE) -from homeassistant.components.spider import DOMAIN as SPIDER_DOMAIN + STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from . import DOMAIN as SPIDER_DOMAIN + DEPENDENCIES = ['spider'] FAN_LIST = [ diff --git a/homeassistant/components/spider/switch.py b/homeassistant/components/spider/switch.py index 227e4748515..e43762be460 100644 --- a/homeassistant/components/spider/switch.py +++ b/homeassistant/components/spider/switch.py @@ -1,9 +1,10 @@ """Support for Spider switches.""" import logging -from homeassistant.components.spider import DOMAIN as SPIDER_DOMAIN from homeassistant.components.switch import SwitchDevice +from . import DOMAIN as SPIDER_DOMAIN + DEPENDENCIES = ['spider'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/spotcrime/__init__.py b/homeassistant/components/spotcrime/__init__.py new file mode 100644 index 00000000000..26bb50b8b02 --- /dev/null +++ b/homeassistant/components/spotcrime/__init__.py @@ -0,0 +1 @@ +"""The spotcrime component.""" diff --git a/homeassistant/components/sensor/spotcrime.py b/homeassistant/components/spotcrime/sensor.py similarity index 100% rename from homeassistant/components/sensor/spotcrime.py rename to homeassistant/components/spotcrime/sensor.py diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py new file mode 100644 index 00000000000..fdfce7e498b --- /dev/null +++ b/homeassistant/components/spotify/__init__.py @@ -0,0 +1 @@ +"""The spotify component.""" diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/spotify/media_player.py similarity index 100% rename from homeassistant/components/media_player/spotify.py rename to homeassistant/components/spotify/media_player.py diff --git a/homeassistant/components/sql/__init__.py b/homeassistant/components/sql/__init__.py new file mode 100644 index 00000000000..ae354f85adb --- /dev/null +++ b/homeassistant/components/sql/__init__.py @@ -0,0 +1 @@ +"""The sql component.""" diff --git a/homeassistant/components/sensor/sql.py b/homeassistant/components/sql/sensor.py similarity index 99% rename from homeassistant/components/sensor/sql.py rename to homeassistant/components/sql/sensor.py index bd246c0d01c..bc40d5efb42 100644 --- a/homeassistant/components/sensor/sql.py +++ b/homeassistant/components/sql/sensor.py @@ -15,7 +15,7 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['sqlalchemy==1.2.18'] +REQUIREMENTS = ['sqlalchemy==1.3.0'] CONF_COLUMN_NAME = 'column' CONF_QUERIES = 'queries' diff --git a/homeassistant/components/squeezebox/__init__.py b/homeassistant/components/squeezebox/__init__.py new file mode 100644 index 00000000000..5250a6dc267 --- /dev/null +++ b/homeassistant/components/squeezebox/__init__.py @@ -0,0 +1 @@ +"""The squeezebox component.""" diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/squeezebox/media_player.py similarity index 100% rename from homeassistant/components/media_player/squeezebox.py rename to homeassistant/components/squeezebox/media_player.py diff --git a/homeassistant/components/srp_energy/__init__.py b/homeassistant/components/srp_energy/__init__.py new file mode 100644 index 00000000000..71e04d7b8c9 --- /dev/null +++ b/homeassistant/components/srp_energy/__init__.py @@ -0,0 +1 @@ +"""The srp_energy component.""" diff --git a/homeassistant/components/sensor/srp_energy.py b/homeassistant/components/srp_energy/sensor.py similarity index 99% rename from homeassistant/components/sensor/srp_energy.py rename to homeassistant/components/srp_energy/sensor.py index a8466bd8721..4d2cd863b12 100644 --- a/homeassistant/components/sensor/srp_energy.py +++ b/homeassistant/components/srp_energy/sensor.py @@ -19,7 +19,7 @@ from homeassistant.util import Throttle from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['srpenergy==1.0.5'] +REQUIREMENTS = ['srpenergy==1.0.6'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/starlingbank/__init__.py b/homeassistant/components/starlingbank/__init__.py new file mode 100644 index 00000000000..3d2e657fcd9 --- /dev/null +++ b/homeassistant/components/starlingbank/__init__.py @@ -0,0 +1 @@ +"""The starlingbank component.""" diff --git a/homeassistant/components/sensor/starlingbank.py b/homeassistant/components/starlingbank/sensor.py similarity index 100% rename from homeassistant/components/sensor/starlingbank.py rename to homeassistant/components/starlingbank/sensor.py diff --git a/homeassistant/components/startca/__init__.py b/homeassistant/components/startca/__init__.py new file mode 100644 index 00000000000..aca4a424a36 --- /dev/null +++ b/homeassistant/components/startca/__init__.py @@ -0,0 +1 @@ +"""The startca component.""" diff --git a/homeassistant/components/sensor/startca.py b/homeassistant/components/startca/sensor.py similarity index 100% rename from homeassistant/components/sensor/startca.py rename to homeassistant/components/startca/sensor.py diff --git a/homeassistant/components/statistics/__init__.py b/homeassistant/components/statistics/__init__.py new file mode 100644 index 00000000000..3f0f03b4909 --- /dev/null +++ b/homeassistant/components/statistics/__init__.py @@ -0,0 +1 @@ +"""The statistics component.""" diff --git a/homeassistant/components/sensor/statistics.py b/homeassistant/components/statistics/sensor.py similarity index 100% rename from homeassistant/components/sensor/statistics.py rename to homeassistant/components/statistics/sensor.py diff --git a/homeassistant/components/steam_online/__init__.py b/homeassistant/components/steam_online/__init__.py new file mode 100644 index 00000000000..99f384322df --- /dev/null +++ b/homeassistant/components/steam_online/__init__.py @@ -0,0 +1 @@ +"""The steam_online component.""" diff --git a/homeassistant/components/sensor/steam_online.py b/homeassistant/components/steam_online/sensor.py similarity index 100% rename from homeassistant/components/sensor/steam_online.py rename to homeassistant/components/steam_online/sensor.py diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index c881ec1276a..1e8ae5d60e3 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -10,15 +10,19 @@ import threading import voluptuous as vol from homeassistant.auth.util import generate_secret -from homeassistant.const import EVENT_HOMEASSISTANT_STOP +import homeassistant.helpers.config_validation as cv +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_FILENAME from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import bind_hass -from .const import DOMAIN, ATTR_STREAMS, ATTR_ENDPOINTS +from .const import ( + DOMAIN, ATTR_STREAMS, ATTR_ENDPOINTS, CONF_STREAM_SOURCE, + CONF_DURATION, CONF_LOOKBACK, SERVICE_RECORD) from .core import PROVIDERS from .worker import stream_worker from .hls import async_setup_hls +from .recorder import async_setup_recorder REQUIREMENTS = ['av==6.1.2'] @@ -30,6 +34,16 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({}), }, extra=vol.ALLOW_EXTRA) +STREAM_SERVICE_SCHEMA = vol.Schema({ + vol.Required(CONF_STREAM_SOURCE): cv.string, +}) + +SERVICE_RECORD_SCHEMA = STREAM_SERVICE_SCHEMA.extend({ + vol.Required(CONF_FILENAME): cv.string, + vol.Optional(CONF_DURATION, default=30): int, + vol.Optional(CONF_LOOKBACK, default=0): int, +}) + # Set log level to error for libav logging.getLogger('libav').setLevel(logging.ERROR) @@ -56,6 +70,9 @@ def request_stream(hass, stream_source, *, fmt='hls', stream = Stream(hass, stream_source, options=options, keepalive=keepalive) streams[stream_source] = stream + else: + # Update keepalive option on existing stream + stream.keepalive = keepalive # Add provider stream.add_provider(fmt) @@ -79,6 +96,9 @@ async def async_setup(hass, config): hls_endpoint = async_setup_hls(hass) hass.data[DOMAIN][ATTR_ENDPOINTS]['hls'] = hls_endpoint + # Setup Recorder + async_setup_recorder(hass) + @callback def shutdown(event): """Stop all stream workers.""" @@ -89,6 +109,13 @@ async def async_setup(hass, config): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown) + async def async_record(call): + """Call record stream service handler.""" + await async_handle_record_service(hass, call) + + hass.services.async_register(DOMAIN, SERVICE_RECORD, + async_record, schema=SERVICE_RECORD_SCHEMA) + return True @@ -116,15 +143,15 @@ class Stream: def add_provider(self, fmt): """Add provider output stream.""" - provider = PROVIDERS[fmt](self) - if not self._outputs.get(provider.format): - self._outputs[provider.format] = provider - return self._outputs[provider.format] + if not self._outputs.get(fmt): + provider = PROVIDERS[fmt](self) + self._outputs[fmt] = provider + return self._outputs[fmt] def remove_provider(self, provider): """Remove provider output stream.""" - if provider.format in self._outputs: - del self._outputs[provider.format] + if provider.name in self._outputs: + del self._outputs[provider.name] self.check_idle() if not self._outputs: @@ -162,3 +189,44 @@ class Stream: self._thread.join() self._thread = None _LOGGER.info("Stopped stream: %s", self.source) + + +async def async_handle_record_service(hass, call): + """Handle save video service calls.""" + stream_source = call.data[CONF_STREAM_SOURCE] + video_path = call.data[CONF_FILENAME] + duration = call.data[CONF_DURATION] + lookback = call.data[CONF_LOOKBACK] + + # Check for file access + if not hass.config.is_allowed_path(video_path): + raise HomeAssistantError("Can't write {}, no access to path!" + .format(video_path)) + + # Check for active stream + streams = hass.data[DOMAIN][ATTR_STREAMS] + stream = streams.get(stream_source) + if not stream: + stream = Stream(hass, stream_source) + streams[stream_source] = stream + + # Add recorder + recorder = stream.outputs.get('recorder') + if recorder: + raise HomeAssistantError("Stream already recording to {}!" + .format(recorder.video_path)) + + recorder = stream.add_provider('recorder') + recorder.video_path = video_path + recorder.timeout = duration + + stream.start() + + # Take advantage of lookback + hls = stream.outputs.get('hls') + if lookback > 0 and hls: + num_segments = min(int(lookback // hls.target_duration), + hls.num_segments) + # Wait for latest segment, then add the lookback + await hls.recv() + recorder.prepend(list(hls.get_segment())[-num_segments:]) diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index a87daaa9d40..9421faaff9a 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -1,10 +1,16 @@ """Constants for Stream component.""" DOMAIN = 'stream' +CONF_STREAM_SOURCE = 'stream_source' +CONF_LOOKBACK = 'lookback' +CONF_DURATION = 'duration' + ATTR_ENDPOINTS = 'endpoints' ATTR_STREAMS = 'streams' ATTR_KEEPALIVE = 'keepalive' +SERVICE_RECORD = 'record' + OUTPUT_FORMATS = ['hls'] FORMAT_CONTENT_TYPE = { diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 59c0a6b650f..745c334fce0 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -41,15 +41,21 @@ class StreamOutput: num_segments = 3 - def __init__(self, stream) -> None: + def __init__(self, stream, timeout: int = 300) -> None: """Initialize a stream output.""" self.idle = False + self.timeout = timeout self._stream = stream self._cursor = None self._event = asyncio.Event() self._segments = deque(maxlen=self.num_segments) self._unsub = None + @property + def name(self) -> str: + """Return provider name.""" + return None + @property def format(self) -> str: """Return container format.""" @@ -82,7 +88,8 @@ class StreamOutput: # Reset idle timeout if self._unsub is not None: self._unsub() - self._unsub = async_call_later(self._stream.hass, 300, self._timeout) + self._unsub = async_call_later( + self._stream.hass, self.timeout, self._timeout) if not sequence: return self._segments @@ -111,14 +118,14 @@ class StreamOutput: # Start idle timeout when we start recieving data if self._unsub is None: self._unsub = async_call_later( - self._stream.hass, 300, self._timeout) + self._stream.hass, self.timeout, self._timeout) if segment is None: self._event.set() # Cleanup provider if self._unsub is not None: self._unsub() - self._cleanup() + self.cleanup() return self._segments.append(segment) @@ -133,11 +140,11 @@ class StreamOutput: self.idle = True self._stream.check_idle() else: - self._cleanup() + self.cleanup() - def _cleanup(self): - """Remove provider.""" - self._segments = [] + def cleanup(self): + """Handle cleanup.""" + self._segments = deque(maxlen=self.num_segments) self._stream.remove_provider(self) diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index 8f5dd6c1884..aa5ce105764 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -110,6 +110,11 @@ class M3U8Renderer: class HlsStreamOutput(StreamOutput): """Represents HLS Output formats.""" + @property + def name(self) -> str: + """Return provider name.""" + return 'hls' + @property def format(self) -> str: """Return container format.""" diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py new file mode 100644 index 00000000000..15e2108c82a --- /dev/null +++ b/homeassistant/components/stream/recorder.py @@ -0,0 +1,92 @@ +"""Provide functionality to record stream.""" +import threading +from typing import List + +from homeassistant.core import callback + +from .core import Segment, StreamOutput, PROVIDERS + + +@callback +def async_setup_recorder(hass): + """Only here so Provider Registry works.""" + + +def recorder_save_worker(file_out: str, segments: List[Segment]): + """Handle saving stream.""" + import av + + output = av.open(file_out, 'w', options={'movflags': 'frag_keyframe'}) + output_v = None + + for segment in segments: + # Seek to beginning and open segment + segment.segment.seek(0) + source = av.open(segment.segment, 'r', format='mpegts') + source_v = source.streams.video[0] + + # Add output streams + if not output_v: + output_v = output.add_stream(template=source_v) + + # Remux video + for packet in source.demux(source_v): + if packet is not None and packet.dts is not None: + packet.stream = output_v + output.mux(packet) + + output.close() + + +@PROVIDERS.register('recorder') +class RecorderOutput(StreamOutput): + """Represents HLS Output formats.""" + + def __init__(self, stream, timeout: int = 30) -> None: + """Initialize recorder output.""" + super().__init__(stream, timeout) + self.video_path = None + self._segments = [] + + @property + def name(self) -> str: + """Return provider name.""" + return 'recorder' + + @property + def format(self) -> str: + """Return container format.""" + return 'mpegts' + + @property + def audio_codec(self) -> str: + """Return desired audio codec.""" + return 'aac' + + @property + def video_codec(self) -> str: + """Return desired video codec.""" + return 'h264' + + def prepend(self, segments: List[Segment]) -> None: + """Prepend segments to existing list.""" + own_segments = self.segments + segments = [s for s in segments if s.sequence not in own_segments] + self._segments = segments + self._segments + + @callback + def _timeout(self, _now=None): + """Handle recorder timeout.""" + self._unsub = None + self.cleanup() + + def cleanup(self): + """Write recording and clean up.""" + thread = threading.Thread( + name='recorder_save_worker', + target=recorder_save_worker, + args=(self.video_path, self._segments)) + thread.start() + + self._segments = [] + self._stream.remove_provider(self) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index d0196761968..3ca8ac079e3 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -112,7 +112,7 @@ def stream_worker(hass, stream, quit_event): a_packet, buffer = create_stream_buffer( stream_output, video_stream, audio_frame) audio_packets[buffer.astream] = a_packet - outputs[stream_output.format] = buffer + outputs[stream_output.name] = buffer # First video packet tends to have a weird dts/pts if first_packet: diff --git a/homeassistant/components/stride/__init__.py b/homeassistant/components/stride/__init__.py new file mode 100644 index 00000000000..461a3ee744f --- /dev/null +++ b/homeassistant/components/stride/__init__.py @@ -0,0 +1 @@ +"""The stride component.""" diff --git a/homeassistant/components/notify/stride.py b/homeassistant/components/stride/notify.py similarity index 91% rename from homeassistant/components/notify/stride.py rename to homeassistant/components/stride/notify.py index f31e50a5886..9d05bd17f34 100644 --- a/homeassistant/components/notify/stride.py +++ b/homeassistant/components/stride/notify.py @@ -8,10 +8,12 @@ import logging import voluptuous as vol +from homeassistant.const import CONF_ROOM, CONF_TOKEN import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - ATTR_TARGET, ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService) -from homeassistant.const import CONF_TOKEN, CONF_ROOM + +from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET, + PLATFORM_SCHEMA, + BaseNotificationService) REQUIREMENTS = ['pystride==0.1.7'] diff --git a/homeassistant/components/supervisord/__init__.py b/homeassistant/components/supervisord/__init__.py new file mode 100644 index 00000000000..5819bb6c965 --- /dev/null +++ b/homeassistant/components/supervisord/__init__.py @@ -0,0 +1 @@ +"""The supervisord component.""" diff --git a/homeassistant/components/sensor/supervisord.py b/homeassistant/components/supervisord/sensor.py similarity index 100% rename from homeassistant/components/sensor/supervisord.py rename to homeassistant/components/supervisord/sensor.py diff --git a/homeassistant/components/swiss_hydrological_data/__init__.py b/homeassistant/components/swiss_hydrological_data/__init__.py new file mode 100644 index 00000000000..2ee369c05eb --- /dev/null +++ b/homeassistant/components/swiss_hydrological_data/__init__.py @@ -0,0 +1 @@ +"""The swiss_hydrological_data component.""" diff --git a/homeassistant/components/sensor/swiss_hydrological_data.py b/homeassistant/components/swiss_hydrological_data/sensor.py similarity index 100% rename from homeassistant/components/sensor/swiss_hydrological_data.py rename to homeassistant/components/swiss_hydrological_data/sensor.py diff --git a/homeassistant/components/swiss_public_transport/__init__.py b/homeassistant/components/swiss_public_transport/__init__.py new file mode 100644 index 00000000000..c53cb1f6934 --- /dev/null +++ b/homeassistant/components/swiss_public_transport/__init__.py @@ -0,0 +1 @@ +"""The swiss_public_transport component.""" diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/swiss_public_transport/sensor.py similarity index 100% rename from homeassistant/components/sensor/swiss_public_transport.py rename to homeassistant/components/swiss_public_transport/sensor.py diff --git a/homeassistant/components/swisscom/__init__.py b/homeassistant/components/swisscom/__init__.py new file mode 100644 index 00000000000..5e0c11af090 --- /dev/null +++ b/homeassistant/components/swisscom/__init__.py @@ -0,0 +1 @@ +"""The swisscom component.""" diff --git a/homeassistant/components/device_tracker/swisscom.py b/homeassistant/components/swisscom/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/swisscom.py rename to homeassistant/components/swisscom/device_tracker.py diff --git a/homeassistant/components/light/switch.py b/homeassistant/components/switch/light.py similarity index 94% rename from homeassistant/components/light/switch.py rename to homeassistant/components/switch/light.py index de6247a2772..64f8779e4ab 100644 --- a/homeassistant/components/light/switch.py +++ b/homeassistant/components/switch/light.py @@ -5,22 +5,18 @@ For more information about this platform, please refer to the documentation at https://home-assistant.io/components/light.switch/ """ import logging + import voluptuous as vol -from homeassistant.core import State, callback -from homeassistant.components.light import ( - Light, PLATFORM_SCHEMA) from homeassistant.components import switch from homeassistant.const import ( - STATE_ON, - ATTR_ENTITY_ID, - CONF_NAME, - CONF_ENTITY_ID, - STATE_UNAVAILABLE -) -from homeassistant.helpers.typing import HomeAssistantType, ConfigType -from homeassistant.helpers.event import async_track_state_change + ATTR_ENTITY_ID, CONF_ENTITY_ID, CONF_NAME, STATE_ON, STATE_UNAVAILABLE) +from homeassistant.core import State, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_state_change +from homeassistant.helpers.typing import ConfigType, HomeAssistantType + +from homeassistant.components.light import PLATFORM_SCHEMA, Light _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switchbot/__init__.py b/homeassistant/components/switchbot/__init__.py new file mode 100644 index 00000000000..a8768a9cd44 --- /dev/null +++ b/homeassistant/components/switchbot/__init__.py @@ -0,0 +1 @@ +"""The switchbot component.""" diff --git a/homeassistant/components/switch/switchbot.py b/homeassistant/components/switchbot/switch.py similarity index 73% rename from homeassistant/components/switch/switchbot.py rename to homeassistant/components/switchbot/switch.py index a85357b525a..3db9b5fd226 100644 --- a/homeassistant/components/switch/switchbot.py +++ b/homeassistant/components/switchbot/switch.py @@ -1,9 +1,4 @@ -""" -Support for Switchbot. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.switchbot -""" +"""Support for Switchbot.""" import logging import voluptuous as vol @@ -11,6 +6,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, CONF_MAC +from homeassistant.helpers.restore_state import RestoreEntity REQUIREMENTS = ['PySwitchbot==0.5'] @@ -18,10 +14,12 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'Switchbot' -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MAC): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_MAC): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -31,18 +29,27 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([SwitchBot(mac_addr, name)]) -class SwitchBot(SwitchDevice): +class SwitchBot(SwitchDevice, RestoreEntity): """Representation of a Switchbot.""" def __init__(self, mac, name) -> None: """Initialize the Switchbot.""" # pylint: disable=import-error, no-member import switchbot - self._state = False + + self._state = None self._name = name self._mac = mac self._device = switchbot.Switchbot(mac=mac) + async def async_added_to_hass(self): + """Run when entity about to be added.""" + await super().async_added_to_hass() + state = await self.async_get_last_state() + if not state: + return + self._state = state.state + def turn_on(self, **kwargs) -> None: """Turn device on.""" if self._device.turn_on(): diff --git a/homeassistant/components/switchmate/__init__.py b/homeassistant/components/switchmate/__init__.py new file mode 100644 index 00000000000..8c965cdb086 --- /dev/null +++ b/homeassistant/components/switchmate/__init__.py @@ -0,0 +1 @@ +"""The switchmate component.""" diff --git a/homeassistant/components/switch/switchmate.py b/homeassistant/components/switchmate/switch.py similarity index 100% rename from homeassistant/components/switch/switchmate.py rename to homeassistant/components/switchmate/switch.py diff --git a/homeassistant/components/syncthru/__init__.py b/homeassistant/components/syncthru/__init__.py new file mode 100644 index 00000000000..e523e3fd722 --- /dev/null +++ b/homeassistant/components/syncthru/__init__.py @@ -0,0 +1 @@ +"""The syncthru component.""" diff --git a/homeassistant/components/sensor/syncthru.py b/homeassistant/components/syncthru/sensor.py similarity index 100% rename from homeassistant/components/sensor/syncthru.py rename to homeassistant/components/syncthru/sensor.py diff --git a/homeassistant/components/synology/__init__.py b/homeassistant/components/synology/__init__.py new file mode 100644 index 00000000000..0ab4b45e298 --- /dev/null +++ b/homeassistant/components/synology/__init__.py @@ -0,0 +1 @@ +"""The synology component.""" diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/synology/camera.py similarity index 100% rename from homeassistant/components/camera/synology.py rename to homeassistant/components/synology/camera.py diff --git a/homeassistant/components/synology_chat/__init__.py b/homeassistant/components/synology_chat/__init__.py new file mode 100644 index 00000000000..836eff6ee48 --- /dev/null +++ b/homeassistant/components/synology_chat/__init__.py @@ -0,0 +1 @@ +"""The synology_chat component.""" diff --git a/homeassistant/components/notify/synology_chat.py b/homeassistant/components/synology_chat/notify.py similarity index 92% rename from homeassistant/components/notify/synology_chat.py rename to homeassistant/components/synology_chat/notify.py index 922631b4045..32277dc1971 100644 --- a/homeassistant/components/notify/synology_chat.py +++ b/homeassistant/components/synology_chat/notify.py @@ -4,17 +4,18 @@ SynologyChat platform for notify component. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.synology_chat/ """ -import logging import json +import logging import requests import voluptuous as vol -from homeassistant.components.notify import ( - BaseNotificationService, PLATFORM_SCHEMA, ATTR_DATA) from homeassistant.const import CONF_RESOURCE, CONF_VERIFY_SSL import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import (ATTR_DATA, PLATFORM_SCHEMA, + BaseNotificationService) + ATTR_FILE_URL = 'file_url' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/synology_srm/__init__.py b/homeassistant/components/synology_srm/__init__.py new file mode 100644 index 00000000000..cd77bce1014 --- /dev/null +++ b/homeassistant/components/synology_srm/__init__.py @@ -0,0 +1 @@ +"""The synology_srm component.""" diff --git a/homeassistant/components/device_tracker/synology_srm.py b/homeassistant/components/synology_srm/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/synology_srm.py rename to homeassistant/components/synology_srm/device_tracker.py diff --git a/homeassistant/components/synologydsm/__init__.py b/homeassistant/components/synologydsm/__init__.py new file mode 100644 index 00000000000..137a3975b99 --- /dev/null +++ b/homeassistant/components/synologydsm/__init__.py @@ -0,0 +1 @@ +"""The synologydsm component.""" diff --git a/homeassistant/components/sensor/synologydsm.py b/homeassistant/components/synologydsm/sensor.py similarity index 100% rename from homeassistant/components/sensor/synologydsm.py rename to homeassistant/components/synologydsm/sensor.py diff --git a/homeassistant/components/syslog/__init__.py b/homeassistant/components/syslog/__init__.py new file mode 100644 index 00000000000..c46e56e76ff --- /dev/null +++ b/homeassistant/components/syslog/__init__.py @@ -0,0 +1 @@ +"""The syslog component.""" diff --git a/homeassistant/components/notify/syslog.py b/homeassistant/components/syslog/notify.py similarity index 100% rename from homeassistant/components/notify/syslog.py rename to homeassistant/components/syslog/notify.py diff --git a/homeassistant/components/systemmonitor/__init__.py b/homeassistant/components/systemmonitor/__init__.py new file mode 100644 index 00000000000..27dc9d367c2 --- /dev/null +++ b/homeassistant/components/systemmonitor/__init__.py @@ -0,0 +1 @@ +"""The systemmonitor component.""" diff --git a/homeassistant/components/sensor/systemmonitor.py b/homeassistant/components/systemmonitor/sensor.py similarity index 99% rename from homeassistant/components/sensor/systemmonitor.py rename to homeassistant/components/systemmonitor/sensor.py index 8eccdc7b3b7..cf65daa4395 100644 --- a/homeassistant/components/sensor/systemmonitor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -12,7 +12,7 @@ from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util -REQUIREMENTS = ['psutil==5.5.1'] +REQUIREMENTS = ['psutil==5.6.1'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sytadin/__init__.py b/homeassistant/components/sytadin/__init__.py new file mode 100644 index 00000000000..5243fe379a7 --- /dev/null +++ b/homeassistant/components/sytadin/__init__.py @@ -0,0 +1 @@ +"""The sytadin component.""" diff --git a/homeassistant/components/sensor/sytadin.py b/homeassistant/components/sytadin/sensor.py similarity index 100% rename from homeassistant/components/sensor/sytadin.py rename to homeassistant/components/sytadin/sensor.py diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index f698906caa7..56c670184b5 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -7,7 +7,8 @@ from homeassistant.components.climate.const import ( from homeassistant.const import ( ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS) from homeassistant.util.temperature import convert as convert_temperature -from homeassistant.components.tado import DATA_TADO + +from . import DATA_TADO _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index a1eb918ac5d..8fa858977a1 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -1,10 +1,11 @@ """Support for Tado sensors for each zone.""" import logging -from homeassistant.components.tado import DATA_TADO from homeassistant.const import ATTR_ID, ATTR_NAME, TEMP_CELSIUS from homeassistant.helpers.entity import Entity +from . import DATA_TADO + _LOGGER = logging.getLogger(__name__) ATTR_DATA_ID = 'data_id' diff --git a/homeassistant/components/tahoma/binary_sensor.py b/homeassistant/components/tahoma/binary_sensor.py index 69855f7cb57..948c6f90a58 100644 --- a/homeassistant/components/tahoma/binary_sensor.py +++ b/homeassistant/components/tahoma/binary_sensor.py @@ -1,12 +1,11 @@ """Support for Tahoma binary sensors.""" -import logging from datetime import timedelta +import logging -from homeassistant.components.binary_sensor import ( - BinarySensorDevice) -from homeassistant.components.tahoma import ( - DOMAIN as TAHOMA_DOMAIN, TahomaDevice) -from homeassistant.const import (STATE_OFF, STATE_ON, ATTR_BATTERY_LEVEL) +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice DEPENDENCIES = ['tahoma'] diff --git a/homeassistant/components/tahoma/cover.py b/homeassistant/components/tahoma/cover.py index 6dbf9a39807..85e785f9ca3 100644 --- a/homeassistant/components/tahoma/cover.py +++ b/homeassistant/components/tahoma/cover.py @@ -2,10 +2,10 @@ from datetime import timedelta import logging +from homeassistant.components.cover import ATTR_POSITION, CoverDevice from homeassistant.util.dt import utcnow -from homeassistant.components.cover import CoverDevice, ATTR_POSITION -from homeassistant.components.tahoma import ( - DOMAIN as TAHOMA_DOMAIN, TahomaDevice) + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice DEPENDENCIES = ['tahoma'] diff --git a/homeassistant/components/tahoma/scene.py b/homeassistant/components/tahoma/scene.py index 643cc65aa19..eedb95d1a77 100644 --- a/homeassistant/components/tahoma/scene.py +++ b/homeassistant/components/tahoma/scene.py @@ -2,8 +2,8 @@ import logging from homeassistant.components.scene import Scene -from homeassistant.components.tahoma import ( - DOMAIN as TAHOMA_DOMAIN) + +from . import DOMAIN as TAHOMA_DOMAIN DEPENDENCIES = ['tahoma'] diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py index 8a2ea976ba7..3c03911804a 100644 --- a/homeassistant/components/tahoma/sensor.py +++ b/homeassistant/components/tahoma/sensor.py @@ -1,11 +1,11 @@ """Support for Tahoma sensors.""" -import logging from datetime import timedelta +import logging -from homeassistant.helpers.entity import Entity -from homeassistant.components.tahoma import ( - DOMAIN as TAHOMA_DOMAIN, TahomaDevice) from homeassistant.const import ATTR_BATTERY_LEVEL +from homeassistant.helpers.entity import Entity + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice DEPENDENCIES = ['tahoma'] diff --git a/homeassistant/components/tahoma/switch.py b/homeassistant/components/tahoma/switch.py index 779bff9ce0d..71f00ed8937 100644 --- a/homeassistant/components/tahoma/switch.py +++ b/homeassistant/components/tahoma/switch.py @@ -2,9 +2,9 @@ import logging from homeassistant.components.switch import SwitchDevice -from homeassistant.components.tahoma import ( - DOMAIN as TAHOMA_DOMAIN, TahomaDevice) -from homeassistant.const import (STATE_OFF, STATE_ON) +from homeassistant.const import STATE_OFF, STATE_ON + +from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice DEPENDENCIES = ['tahoma'] diff --git a/homeassistant/components/tank_utility/__init__.py b/homeassistant/components/tank_utility/__init__.py new file mode 100644 index 00000000000..1590c90738f --- /dev/null +++ b/homeassistant/components/tank_utility/__init__.py @@ -0,0 +1 @@ +"""The tank_utility component.""" diff --git a/homeassistant/components/sensor/tank_utility.py b/homeassistant/components/tank_utility/sensor.py similarity index 100% rename from homeassistant/components/sensor/tank_utility.py rename to homeassistant/components/tank_utility/sensor.py diff --git a/homeassistant/components/tapsaff/__init__.py b/homeassistant/components/tapsaff/__init__.py new file mode 100644 index 00000000000..95f6c663dd6 --- /dev/null +++ b/homeassistant/components/tapsaff/__init__.py @@ -0,0 +1 @@ +"""The tapsaff component.""" diff --git a/homeassistant/components/binary_sensor/tapsaff.py b/homeassistant/components/tapsaff/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/tapsaff.py rename to homeassistant/components/tapsaff/binary_sensor.py diff --git a/homeassistant/components/tautulli/__init__.py b/homeassistant/components/tautulli/__init__.py new file mode 100644 index 00000000000..f8ce5ca08b5 --- /dev/null +++ b/homeassistant/components/tautulli/__init__.py @@ -0,0 +1 @@ +"""The tautulli component.""" diff --git a/homeassistant/components/sensor/tautulli.py b/homeassistant/components/tautulli/sensor.py similarity index 100% rename from homeassistant/components/sensor/tautulli.py rename to homeassistant/components/tautulli/sensor.py diff --git a/homeassistant/components/tcp/__init__.py b/homeassistant/components/tcp/__init__.py new file mode 100644 index 00000000000..614f637a71a --- /dev/null +++ b/homeassistant/components/tcp/__init__.py @@ -0,0 +1 @@ +"""The tcp component.""" diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/tcp/binary_sensor.py similarity index 89% rename from homeassistant/components/binary_sensor/tcp.py rename to homeassistant/components/tcp/binary_sensor.py index 764b6829c91..80d77cd52a1 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/tcp/binary_sensor.py @@ -7,8 +7,8 @@ https://home-assistant.io/components/binary_sensor.tcp/ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.sensor.tcp import ( - TcpSensor, CONF_VALUE_ON, PLATFORM_SCHEMA) + +from .sensor import CONF_VALUE_ON, PLATFORM_SCHEMA, TcpSensor _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/tcp/sensor.py similarity index 100% rename from homeassistant/components/sensor/tcp.py rename to homeassistant/components/tcp/sensor.py diff --git a/homeassistant/components/ted5000/__init__.py b/homeassistant/components/ted5000/__init__.py new file mode 100644 index 00000000000..a10d5778b38 --- /dev/null +++ b/homeassistant/components/ted5000/__init__.py @@ -0,0 +1 @@ +"""The ted5000 component.""" diff --git a/homeassistant/components/sensor/ted5000.py b/homeassistant/components/ted5000/sensor.py similarity index 100% rename from homeassistant/components/sensor/ted5000.py rename to homeassistant/components/ted5000/sensor.py diff --git a/homeassistant/components/teksavvy/__init__.py b/homeassistant/components/teksavvy/__init__.py new file mode 100644 index 00000000000..ee0dcd1c810 --- /dev/null +++ b/homeassistant/components/teksavvy/__init__.py @@ -0,0 +1 @@ +"""The teksavvy component.""" diff --git a/homeassistant/components/sensor/teksavvy.py b/homeassistant/components/teksavvy/sensor.py similarity index 100% rename from homeassistant/components/sensor/teksavvy.py rename to homeassistant/components/teksavvy/sensor.py diff --git a/homeassistant/components/telegram/__init__.py b/homeassistant/components/telegram/__init__.py new file mode 100644 index 00000000000..1aca4e510c6 --- /dev/null +++ b/homeassistant/components/telegram/__init__.py @@ -0,0 +1 @@ +"""The telegram component.""" diff --git a/homeassistant/components/notify/telegram.py b/homeassistant/components/telegram/notify.py similarity index 97% rename from homeassistant/components/notify/telegram.py rename to homeassistant/components/telegram/notify.py index 1dff82fa2cd..428c7e093d2 100644 --- a/homeassistant/components/notify/telegram.py +++ b/homeassistant/components/telegram/notify.py @@ -8,11 +8,12 @@ import logging import voluptuous as vol -from homeassistant.components.notify import ( - ATTR_MESSAGE, ATTR_TITLE, ATTR_DATA, ATTR_TARGET, - PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import ATTR_LOCATION +from homeassistant.components.notify import ( + ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET, ATTR_TITLE, PLATFORM_SCHEMA, + BaseNotificationService) + _LOGGER = logging.getLogger(__name__) DOMAIN = 'telegram_bot' diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 78d45535c48..e8b19ec2b2c 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -48,6 +48,7 @@ ATTR_TEXT = 'text' ATTR_URL = 'url' ATTR_USER_ID = 'user_id' ATTR_USERNAME = 'username' +ATTR_VERIFY_SSL = 'verify_ssl' CONF_ALLOWED_CHAT_IDS = 'allowed_chat_ids' CONF_PROXY_URL = 'proxy_url' @@ -108,6 +109,7 @@ SERVICE_SCHEMA_SEND_FILE = BASE_SERVICE_SCHEMA.extend({ vol.Optional(ATTR_USERNAME): cv.string, vol.Optional(ATTR_PASSWORD): cv.string, vol.Optional(ATTR_AUTHENTICATION): cv.string, + vol.Optional(ATTR_VERIFY_SSL): cv.boolean, }) SERVICE_SCHEMA_SEND_LOCATION = BASE_SERVICE_SCHEMA.extend({ @@ -164,7 +166,7 @@ SERVICE_MAP = { def load_data(hass, url=None, filepath=None, username=None, password=None, - authentication=None, num_retries=5): + authentication=None, num_retries=5, verify_ssl=None): """Load data into ByteIO/File container from a source.""" try: if url is not None: @@ -175,6 +177,8 @@ def load_data(hass, url=None, filepath=None, username=None, password=None, params["auth"] = HTTPDigestAuth(username, password) else: params["auth"] = HTTPBasicAuth(username, password) + if verify_ssl: + params["verify"] = verify_ssl retry_num = 0 while retry_num < num_retries: req = requests.get(url, **params) @@ -536,6 +540,7 @@ class TelegramNotificationService: username=kwargs.get(ATTR_USERNAME), password=kwargs.get(ATTR_PASSWORD), authentication=kwargs.get(ATTR_AUTHENTICATION), + verify_ssl=kwargs.get(ATTR_VERIFY_SSL), ) if file_content: for chat_id in self._get_target_chat_ids(target): diff --git a/homeassistant/components/telegram_bot/broadcast.py b/homeassistant/components/telegram_bot/broadcast.py index eb52ab496d7..a129ebf6604 100644 --- a/homeassistant/components/telegram_bot/broadcast.py +++ b/homeassistant/components/telegram_bot/broadcast.py @@ -1,9 +1,7 @@ """Support for Telegram bot to send messages only.""" import logging -from homeassistant.components.telegram_bot import ( - initialize_bot, - PLATFORM_SCHEMA as TELEGRAM_PLATFORM_SCHEMA) +from . import PLATFORM_SCHEMA as TELEGRAM_PLATFORM_SCHEMA, initialize_bot _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/telegram_bot/polling.py b/homeassistant/components/telegram_bot/polling.py index 9936b690985..7d0039319e3 100644 --- a/homeassistant/components/telegram_bot/polling.py +++ b/homeassistant/components/telegram_bot/polling.py @@ -1,14 +1,14 @@ """Support for Telegram bot using polling.""" import logging -from homeassistant.components.telegram_bot import ( - initialize_bot, - CONF_ALLOWED_CHAT_IDS, BaseTelegramBotEntity, - PLATFORM_SCHEMA as TELEGRAM_PLATFORM_SCHEMA) from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from homeassistant.core import callback +from . import ( + CONF_ALLOWED_CHAT_IDS, PLATFORM_SCHEMA as TELEGRAM_PLATFORM_SCHEMA, + BaseTelegramBotEntity, initialize_bot) + _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = TELEGRAM_PLATFORM_SCHEMA diff --git a/homeassistant/components/telegram_bot/services.yaml b/homeassistant/components/telegram_bot/services.yaml index d8039c0b384..206898bfda2 100644 --- a/homeassistant/components/telegram_bot/services.yaml +++ b/homeassistant/components/telegram_bot/services.yaml @@ -52,6 +52,9 @@ send_photo: disable_notification: description: Sends the message silently. iOS users and Web users will not receive a notification, Android users will receive a notification with no sound. example: true + verify_ssl: + description: Enable or disable SSL certificate verification. Set to false if you're downloading the file from a URL and you don't want to validate the SSL certificate of the server. + example: false keyboard: description: List of rows of commands, comma-separated, to make a custom keyboard. example: '["/command1, /command2", "/command3"]' @@ -80,6 +83,9 @@ send_sticker: disable_notification: description: Sends the message silently. iOS users and Web users will not receive a notification, Android users will receive a notification with no sound. example: true + verify_ssl: + description: Enable or disable SSL certificate verification. Set to false if you're downloading the file from a URL and you don't want to validate the SSL certificate of the server. + example: false keyboard: description: List of rows of commands, comma-separated, to make a custom keyboard. example: '["/command1, /command2", "/command3"]' @@ -111,6 +117,9 @@ send_video: disable_notification: description: Sends the message silently. iOS users and Web users will not receive a notification, Android users will receive a notification with no sound. example: true + verify_ssl: + description: Enable or disable SSL certificate verification. Set to false if you're downloading the file from a URL and you don't want to validate the SSL certificate of the server. + example: false keyboard: description: List of rows of commands, comma-separated, to make a custom keyboard. example: '["/command1, /command2", "/command3"]' @@ -142,6 +151,9 @@ send_document: disable_notification: description: Sends the message silently. iOS users and Web users will not receive a notification, Android users will receive a notification with no sound. example: true + verify_ssl: + description: Enable or disable SSL certificate verification. Set to false if you're downloading the file from a URL and you don't want to validate the SSL certificate of the server. + example: false keyboard: description: List of rows of commands, comma-separated, to make a custom keyboard. example: '["/command1, /command2", "/command3"]' diff --git a/homeassistant/components/telegram_bot/webhooks.py b/homeassistant/components/telegram_bot/webhooks.py index 41a206944e7..424ece81549 100644 --- a/homeassistant/components/telegram_bot/webhooks.py +++ b/homeassistant/components/telegram_bot/webhooks.py @@ -7,14 +7,14 @@ import voluptuous as vol from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.const import KEY_REAL_IP -from homeassistant.components.telegram_bot import ( - CONF_ALLOWED_CHAT_IDS, BaseTelegramBotEntity, PLATFORM_SCHEMA, - initialize_bot) from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST, - HTTP_UNAUTHORIZED, CONF_URL) + CONF_URL, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED) import homeassistant.helpers.config_validation as cv +from . import ( + CONF_ALLOWED_CHAT_IDS, PLATFORM_SCHEMA, BaseTelegramBotEntity, + initialize_bot) + DEPENDENCIES = ['http'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tellduslive/.translations/bg.json b/homeassistant/components/tellduslive/.translations/bg.json new file mode 100644 index 00000000000..beb1bc0d6e6 --- /dev/null +++ b/homeassistant/components/tellduslive/.translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "\u0410\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/.translations/fr.json b/homeassistant/components/tellduslive/.translations/fr.json index a20e6bff2b5..a7ddd4c6fa6 100644 --- a/homeassistant/components/tellduslive/.translations/fr.json +++ b/homeassistant/components/tellduslive/.translations/fr.json @@ -1,14 +1,26 @@ { "config": { "abort": { - "already_setup": "TelldusLive est d\u00e9j\u00e0 configur\u00e9" + "all_configured": "TelldusLive est d\u00e9j\u00e0 configur\u00e9", + "already_setup": "TelldusLive est d\u00e9j\u00e0 configur\u00e9", + "authorize_url_fail": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation.", + "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", + "unknown": "Une erreur inconnue s'est produite" }, "error": { "auth_error": "Erreur d'authentification, veuillez r\u00e9essayer." }, "step": { + "auth": { + "description": "Pour lier votre compte TelldusLive: \n 1. Cliquez sur le lien ci-dessous \n 2. Connectez-vous \u00e0 Telldus Live \n 3. Autorisez ** {app_name} ** (cliquez sur ** Oui **). \n 4. Revenez ici et cliquez sur ** ENVOYER **. \n\n [Lien compte TelldusLive] ( {auth_url} )", + "title": "S\u2019authentifier sur TelldusLive" + }, "user": { - "description": "Vide" + "data": { + "host": "H\u00f4te" + }, + "description": "Vide", + "title": "Choisissez le point de terminaison." } }, "title": "Telldus Live" diff --git a/homeassistant/components/tellduslive/.translations/hu.json b/homeassistant/components/tellduslive/.translations/hu.json index 6057d7b3212..cd219be04e1 100644 --- a/homeassistant/components/tellduslive/.translations/hu.json +++ b/homeassistant/components/tellduslive/.translations/hu.json @@ -13,7 +13,7 @@ "step": { "user": { "data": { - "host": "Kiszolg\u00e1l\u00f3" + "host": "Hoszt" }, "description": "\u00dcres", "title": "V\u00e1lassz v\u00e9gpontot." diff --git a/homeassistant/components/tellduslive/.translations/ru.json b/homeassistant/components/tellduslive/.translations/ru.json index 80dff6dc88a..3b34e048b11 100644 --- a/homeassistant/components/tellduslive/.translations/ru.json +++ b/homeassistant/components/tellduslive/.translations/ru.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "all_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_setup": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "all_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", + "already_setup": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", "authorize_url_fail": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430" diff --git a/homeassistant/components/tellduslive/.translations/zh-Hans.json b/homeassistant/components/tellduslive/.translations/zh-Hans.json index 4b1afd548e8..bcf36cafda0 100644 --- a/homeassistant/components/tellduslive/.translations/zh-Hans.json +++ b/homeassistant/components/tellduslive/.translations/zh-Hans.json @@ -2,6 +2,7 @@ "config": { "abort": { "all_configured": "Tellduslive \u5df2\u914d\u7f6e\u5b8c\u6210", + "already_setup": "TelldusLive \u5df2\u914d\u7f6e\u5b8c\u6210", "authorize_url_fail": "\u751f\u6210\u6388\u6743\u7f51\u5740\u65f6\u53d1\u751f\u672a\u77e5\u9519\u8bef\u3002", "authorize_url_timeout": "\u751f\u6210\u6388\u6743\u7f51\u5740\u8d85\u65f6\u3002", "unknown": "\u53d1\u751f\u672a\u77e5\u7684\u9519\u8bef" @@ -11,13 +12,16 @@ }, "step": { "auth": { - "description": "\u8981\u94fe\u63a5\u60a8\u7684TelldusLive\u8d26\u6237\uff1a \n 1. \u70b9\u51fb\u4e0b\u9762\u7684\u94fe\u63a5\n 2. \u767b\u5f55 Telldus Live \n 3. \u6388\u6743 **{app_name}** (\u70b9\u51fb **\u662f**)\u3002 \n 4. \u8fd4\u56de\u6b64\u9875\uff0c\u7136\u540e\u70b9\u51fb**\u63d0\u4ea4**\u3002 \n\n [\u94fe\u63a5 TelldusLive \u8d26\u6237]({auth_url})" + "description": "\u8981\u94fe\u63a5\u60a8\u7684TelldusLive\u8d26\u6237\uff1a \n 1. \u70b9\u51fb\u4e0b\u9762\u7684\u94fe\u63a5\n 2. \u767b\u5f55 Telldus Live \n 3. \u6388\u6743 **{app_name}** (\u70b9\u51fb **\u662f**)\u3002 \n 4. \u8fd4\u56de\u6b64\u9875\uff0c\u7136\u540e\u70b9\u51fb**\u63d0\u4ea4**\u3002 \n\n [\u94fe\u63a5 TelldusLive \u8d26\u6237]({auth_url})", + "title": "\u4f7f\u7528 TelldusLive \u8fdb\u884c\u8eab\u4efd\u9a8c\u8bc1" }, "user": { "data": { "host": "\u4e3b\u673a" - } + }, + "title": "\u9009\u62e9 endpoint\u3002" } - } + }, + "title": "Telldus Live" } } \ No newline at end of file diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py index 397e21922d9..6a6b18557b0 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -14,7 +14,7 @@ from homeassistant.helpers.event import async_call_later from . import config_flow # noqa pylint_disable=unused-import from .const import ( - CONF_HOST, DOMAIN, KEY_HOST, KEY_SCAN_INTERVAL, KEY_SESSION, + CONF_HOST, DOMAIN, KEY_SCAN_INTERVAL, KEY_SESSION, MIN_UPDATE_INTERVAL, NOT_SO_PRIVATE_KEY, PUBLIC_KEY, SCAN_INTERVAL, SIGNAL_UPDATE_ENTITY, TELLDUS_DISCOVERY_NEW) @@ -54,7 +54,7 @@ async def async_setup_entry(hass, entry): from tellduslive import Session conf = entry.data[KEY_SESSION] - if KEY_HOST in conf: + if CONF_HOST in conf: # Session(**conf) does blocking IO when # communicating with local devices. session = await hass.async_add_executor_job(partial(Session, **conf)) @@ -108,7 +108,7 @@ async def async_setup(hass, config): DOMAIN, context={'source': config_entries.SOURCE_IMPORT}, data={ - KEY_HOST: config[DOMAIN].get(CONF_HOST), + CONF_HOST: config[DOMAIN].get(CONF_HOST), KEY_SCAN_INTERVAL: config[DOMAIN][CONF_SCAN_INTERVAL], })) return True diff --git a/homeassistant/components/tellduslive/binary_sensor.py b/homeassistant/components/tellduslive/binary_sensor.py index 85faeca96d4..fc13f75838a 100644 --- a/homeassistant/components/tellduslive/binary_sensor.py +++ b/homeassistant/components/tellduslive/binary_sensor.py @@ -3,9 +3,10 @@ import logging from homeassistant.components import binary_sensor, tellduslive from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.tellduslive.entry import TelldusLiveEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect +from .entry import TelldusLiveEntity + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tellduslive/config_flow.py b/homeassistant/components/tellduslive/config_flow.py index 62463bc0a9e..ff02419d624 100644 --- a/homeassistant/components/tellduslive/config_flow.py +++ b/homeassistant/components/tellduslive/config_flow.py @@ -7,10 +7,11 @@ import async_timeout import voluptuous as vol from homeassistant import config_entries +from homeassistant.const import CONF_HOST from homeassistant.util.json import load_json from .const import ( - APPLICATION_NAME, CLOUD_NAME, DOMAIN, KEY_HOST, KEY_SCAN_INTERVAL, + APPLICATION_NAME, CLOUD_NAME, DOMAIN, KEY_SCAN_INTERVAL, KEY_SESSION, NOT_SO_PRIVATE_KEY, PUBLIC_KEY, SCAN_INTERVAL, TELLDUS_CONFIG_FILE) @@ -50,14 +51,14 @@ class FlowHandler(config_entries.ConfigFlow): return self.async_abort(reason='already_setup') if user_input is not None or len(self._hosts) == 1: - if user_input is not None and user_input[KEY_HOST] != CLOUD_NAME: - self._host = user_input[KEY_HOST] + if user_input is not None and user_input[CONF_HOST] != CLOUD_NAME: + self._host = user_input[CONF_HOST] return await self.async_step_auth() return self.async_show_form( step_id='user', data_schema=vol.Schema({ - vol.Required(KEY_HOST): + vol.Required(CONF_HOST): vol.In(list(self._hosts)) })) @@ -70,7 +71,7 @@ class FlowHandler(config_entries.ConfigFlow): host = self._host or CLOUD_NAME if self._host: session = { - KEY_HOST: host, + CONF_HOST: host, KEY_TOKEN: self._session.access_token } else: @@ -80,7 +81,7 @@ class FlowHandler(config_entries.ConfigFlow): } return self.async_create_entry( title=host, data={ - KEY_HOST: host, + CONF_HOST: host, KEY_SCAN_INTERVAL: self._scan_interval.seconds, KEY_SESSION: session, }) @@ -124,8 +125,8 @@ class FlowHandler(config_entries.ConfigFlow): return self.async_abort(reason='already_setup') self._scan_interval = user_input[KEY_SCAN_INTERVAL] - if user_input[KEY_HOST] != DOMAIN: - self._hosts.append(user_input[KEY_HOST]) + if user_input[CONF_HOST] != DOMAIN: + self._hosts.append(user_input[CONF_HOST]) if not await self.hass.async_add_executor_job( os.path.isfile, self.hass.config.path(TELLDUS_CONFIG_FILE)): @@ -135,14 +136,14 @@ class FlowHandler(config_entries.ConfigFlow): load_json, self.hass.config.path(TELLDUS_CONFIG_FILE)) host = next(iter(conf)) - if user_input[KEY_HOST] != host: + if user_input[CONF_HOST] != host: return await self.async_step_user() host = CLOUD_NAME if host == 'tellduslive' else host return self.async_create_entry( title=host, data={ - KEY_HOST: host, + CONF_HOST: host, KEY_SCAN_INTERVAL: self._scan_interval.seconds, KEY_SESSION: next(iter(conf.values())), }) diff --git a/homeassistant/components/tellduslive/const.py b/homeassistant/components/tellduslive/const.py index 80b0513b763..7898cd8b1f6 100644 --- a/homeassistant/components/tellduslive/const.py +++ b/homeassistant/components/tellduslive/const.py @@ -13,7 +13,6 @@ KEY_CONFIG = 'tellduslive_config' SIGNAL_UPDATE_ENTITY = 'tellduslive_update' -KEY_HOST = 'host' KEY_SESSION = 'session' KEY_SCAN_INTERVAL = 'scan_interval' diff --git a/homeassistant/components/tellduslive/cover.py b/homeassistant/components/tellduslive/cover.py index 1bd3158d100..6dac00ed7a2 100644 --- a/homeassistant/components/tellduslive/cover.py +++ b/homeassistant/components/tellduslive/cover.py @@ -3,9 +3,10 @@ import logging from homeassistant.components import cover, tellduslive from homeassistant.components.cover import CoverDevice -from homeassistant.components.tellduslive.entry import TelldusLiveEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect +from .entry import TelldusLiveEntity + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tellduslive/light.py b/homeassistant/components/tellduslive/light.py index 12baf8384f6..3847c66b6cb 100644 --- a/homeassistant/components/tellduslive/light.py +++ b/homeassistant/components/tellduslive/light.py @@ -4,9 +4,10 @@ import logging from homeassistant.components import light, tellduslive from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) -from homeassistant.components.tellduslive.entry import TelldusLiveEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect +from .entry import TelldusLiveEntity + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tellduslive/sensor.py b/homeassistant/components/tellduslive/sensor.py index 42c93aa52f9..156c11c95a7 100644 --- a/homeassistant/components/tellduslive/sensor.py +++ b/homeassistant/components/tellduslive/sensor.py @@ -2,12 +2,13 @@ import logging from homeassistant.components import sensor, tellduslive -from homeassistant.components.tellduslive.entry import TelldusLiveEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, - TEMP_CELSIUS, POWER_WATT) + POWER_WATT, TEMP_CELSIUS) from homeassistant.helpers.dispatcher import async_dispatcher_connect +from .entry import TelldusLiveEntity + _LOGGER = logging.getLogger(__name__) SENSOR_TYPE_TEMPERATURE = 'temp' diff --git a/homeassistant/components/tellduslive/switch.py b/homeassistant/components/tellduslive/switch.py index bb0164b10bb..55275b5b754 100644 --- a/homeassistant/components/tellduslive/switch.py +++ b/homeassistant/components/tellduslive/switch.py @@ -2,10 +2,11 @@ import logging from homeassistant.components import switch, tellduslive -from homeassistant.components.tellduslive.entry import TelldusLiveEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import ToggleEntity +from .entry import TelldusLiveEntity + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tellstick/cover.py b/homeassistant/components/tellstick/cover.py index d0c9c031435..b90e34229fd 100644 --- a/homeassistant/components/tellstick/cover.py +++ b/homeassistant/components/tellstick/cover.py @@ -1,8 +1,9 @@ """Support for Tellstick covers.""" from homeassistant.components.cover import CoverDevice -from homeassistant.components.tellstick import ( - DEFAULT_SIGNAL_REPETITIONS, ATTR_DISCOVER_DEVICES, ATTR_DISCOVER_CONFIG, - DATA_TELLSTICK, TellstickDevice) + +from . import ( + ATTR_DISCOVER_CONFIG, ATTR_DISCOVER_DEVICES, DATA_TELLSTICK, + DEFAULT_SIGNAL_REPETITIONS, TellstickDevice) def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/tellstick/light.py b/homeassistant/components/tellstick/light.py index 5deee5e08a6..15c8b0c5eb9 100644 --- a/homeassistant/components/tellstick/light.py +++ b/homeassistant/components/tellstick/light.py @@ -1,10 +1,10 @@ """Support for Tellstick lights.""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) -from homeassistant.components.tellstick import ( - DEFAULT_SIGNAL_REPETITIONS, ATTR_DISCOVER_DEVICES, ATTR_DISCOVER_CONFIG, - DATA_TELLSTICK, TellstickDevice) +from . import ( + ATTR_DISCOVER_CONFIG, ATTR_DISCOVER_DEVICES, DATA_TELLSTICK, + DEFAULT_SIGNAL_REPETITIONS, TellstickDevice) SUPPORT_TELLSTICK = SUPPORT_BRIGHTNESS diff --git a/homeassistant/components/tellstick/switch.py b/homeassistant/components/tellstick/switch.py index 56d563e494c..75c18bee8c5 100644 --- a/homeassistant/components/tellstick/switch.py +++ b/homeassistant/components/tellstick/switch.py @@ -1,9 +1,10 @@ """Support for Tellstick switches.""" -from homeassistant.components.tellstick import ( - DEFAULT_SIGNAL_REPETITIONS, ATTR_DISCOVER_DEVICES, - ATTR_DISCOVER_CONFIG, DATA_TELLSTICK, TellstickDevice) from homeassistant.helpers.entity import ToggleEntity +from . import ( + ATTR_DISCOVER_CONFIG, ATTR_DISCOVER_DEVICES, DATA_TELLSTICK, + DEFAULT_SIGNAL_REPETITIONS, TellstickDevice) + def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Tellstick switches.""" diff --git a/homeassistant/components/telnet/__init__.py b/homeassistant/components/telnet/__init__.py new file mode 100644 index 00000000000..12444ab1691 --- /dev/null +++ b/homeassistant/components/telnet/__init__.py @@ -0,0 +1 @@ +"""The telnet component.""" diff --git a/homeassistant/components/switch/telnet.py b/homeassistant/components/telnet/switch.py similarity index 100% rename from homeassistant/components/switch/telnet.py rename to homeassistant/components/telnet/switch.py diff --git a/homeassistant/components/temper/__init__.py b/homeassistant/components/temper/__init__.py new file mode 100644 index 00000000000..587da1c6309 --- /dev/null +++ b/homeassistant/components/temper/__init__.py @@ -0,0 +1 @@ +"""The temper component.""" diff --git a/homeassistant/components/sensor/temper.py b/homeassistant/components/temper/sensor.py similarity index 100% rename from homeassistant/components/sensor/temper.py rename to homeassistant/components/temper/sensor.py diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py new file mode 100644 index 00000000000..0c205a0196c --- /dev/null +++ b/homeassistant/components/template/__init__.py @@ -0,0 +1 @@ +"""The template component.""" diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/template/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/template.py rename to homeassistant/components/template/binary_sensor.py diff --git a/homeassistant/components/cover/template.py b/homeassistant/components/template/cover.py similarity index 100% rename from homeassistant/components/cover/template.py rename to homeassistant/components/template/cover.py diff --git a/homeassistant/components/fan/template.py b/homeassistant/components/template/fan.py similarity index 100% rename from homeassistant/components/fan/template.py rename to homeassistant/components/template/fan.py diff --git a/homeassistant/components/light/template.py b/homeassistant/components/template/light.py similarity index 100% rename from homeassistant/components/light/template.py rename to homeassistant/components/template/light.py diff --git a/homeassistant/components/lock/template.py b/homeassistant/components/template/lock.py similarity index 100% rename from homeassistant/components/lock/template.py rename to homeassistant/components/template/lock.py diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/template/sensor.py similarity index 100% rename from homeassistant/components/sensor/template.py rename to homeassistant/components/template/sensor.py diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/template/switch.py similarity index 100% rename from homeassistant/components/switch/template.py rename to homeassistant/components/template/switch.py diff --git a/homeassistant/components/tensorflow/__init__.py b/homeassistant/components/tensorflow/__init__.py new file mode 100644 index 00000000000..00a695d6aa8 --- /dev/null +++ b/homeassistant/components/tensorflow/__init__.py @@ -0,0 +1 @@ +"""The tensorflow component.""" diff --git a/homeassistant/components/image_processing/tensorflow.py b/homeassistant/components/tensorflow/image_processing.py similarity index 100% rename from homeassistant/components/image_processing/tensorflow.py rename to homeassistant/components/tensorflow/image_processing.py diff --git a/homeassistant/components/tesla/binary_sensor.py b/homeassistant/components/tesla/binary_sensor.py index 2c037140f0a..a87239d2430 100644 --- a/homeassistant/components/tesla/binary_sensor.py +++ b/homeassistant/components/tesla/binary_sensor.py @@ -2,8 +2,9 @@ import logging from homeassistant.components.binary_sensor import ( - BinarySensorDevice, ENTITY_ID_FORMAT) -from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN, TeslaDevice + ENTITY_ID_FORMAT, BinarySensorDevice) + +from . import DOMAIN as TESLA_DOMAIN, TeslaDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tesla/climate.py b/homeassistant/components/tesla/climate.py index 118e7204bca..603ce1a4d61 100644 --- a/homeassistant/components/tesla/climate.py +++ b/homeassistant/components/tesla/climate.py @@ -1,14 +1,14 @@ """Support for Tesla HVAC system.""" import logging -from homeassistant.components.climate import ClimateDevice, ENTITY_ID_FORMAT +from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice from homeassistant.components.climate.const import ( SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) -from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN -from homeassistant.components.tesla import TeslaDevice from homeassistant.const import ( ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT) +from . import DOMAIN as TESLA_DOMAIN, TeslaDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['tesla'] diff --git a/homeassistant/components/tesla/device_tracker.py b/homeassistant/components/tesla/device_tracker.py index 0aeab5b1c7d..5a7693d8370 100644 --- a/homeassistant/components/tesla/device_tracker.py +++ b/homeassistant/components/tesla/device_tracker.py @@ -1,10 +1,11 @@ """Support for tracking Tesla cars.""" import logging -from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN from homeassistant.helpers.event import track_utc_time_change from homeassistant.util import slugify +from . import DOMAIN as TESLA_DOMAIN + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['tesla'] diff --git a/homeassistant/components/tesla/lock.py b/homeassistant/components/tesla/lock.py index 34d660ac83c..ade394496d6 100644 --- a/homeassistant/components/tesla/lock.py +++ b/homeassistant/components/tesla/lock.py @@ -2,10 +2,10 @@ import logging from homeassistant.components.lock import ENTITY_ID_FORMAT, LockDevice -from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN -from homeassistant.components.tesla import TeslaDevice from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED +from . import DOMAIN as TESLA_DOMAIN, TeslaDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['tesla'] diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py index 1d4505ed9a4..99705d3f793 100644 --- a/homeassistant/components/tesla/sensor.py +++ b/homeassistant/components/tesla/sensor.py @@ -3,12 +3,12 @@ from datetime import timedelta import logging from homeassistant.components.sensor import ENTITY_ID_FORMAT -from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN -from homeassistant.components.tesla import TeslaDevice from homeassistant.const import ( - TEMP_CELSIUS, TEMP_FAHRENHEIT, LENGTH_KILOMETERS, LENGTH_MILES) + LENGTH_KILOMETERS, LENGTH_MILES, TEMP_CELSIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import Entity +from . import DOMAIN as TESLA_DOMAIN, TeslaDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['tesla'] diff --git a/homeassistant/components/tesla/switch.py b/homeassistant/components/tesla/switch.py index a1787e9993e..e00164ff1a7 100644 --- a/homeassistant/components/tesla/switch.py +++ b/homeassistant/components/tesla/switch.py @@ -2,10 +2,10 @@ import logging from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice -from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN -from homeassistant.components.tesla import TeslaDevice from homeassistant.const import STATE_OFF, STATE_ON +from . import DOMAIN as TESLA_DOMAIN, TeslaDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['tesla'] diff --git a/homeassistant/components/tfiac/__init__.py b/homeassistant/components/tfiac/__init__.py new file mode 100644 index 00000000000..bb097a7edd0 --- /dev/null +++ b/homeassistant/components/tfiac/__init__.py @@ -0,0 +1 @@ +"""The tfiac component.""" diff --git a/homeassistant/components/tfiac/climate.py b/homeassistant/components/tfiac/climate.py new file mode 100644 index 00000000000..44fa1909823 --- /dev/null +++ b/homeassistant/components/tfiac/climate.py @@ -0,0 +1,185 @@ +"""Climate platform that offers a climate device for the TFIAC protocol.""" +from concurrent import futures +from datetime import timedelta +import logging + +import voluptuous as vol + +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate.const import ( + STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT, + SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, + SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE) +from homeassistant.const import ATTR_TEMPERATURE, CONF_HOST, TEMP_FAHRENHEIT +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['pytfiac==0.3'] + +SCAN_INTERVAL = timedelta(seconds=60) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, +}) + +_LOGGER = logging.getLogger(__name__) + +MIN_TEMP = 61 +MAX_TEMP = 88 +OPERATION_MAP = { + STATE_HEAT: 'heat', + STATE_AUTO: 'selfFeel', + STATE_DRY: 'dehumi', + STATE_FAN_ONLY: 'fan', + STATE_COOL: 'cool', +} +OPERATION_MAP_REV = { + v: k for k, v in OPERATION_MAP.items()} +FAN_LIST = ['Auto', 'Low', 'Middle', 'High'] +SWING_LIST = [ + 'Off', + 'Vertical', + 'Horizontal', + 'Both', +] + +CURR_TEMP = 'current_temp' +TARGET_TEMP = 'target_temp' +OPERATION_MODE = 'operation' +FAN_MODE = 'fan_mode' +SWING_MODE = 'swing_mode' +ON_MODE = 'is_on' + + +async def async_setup_platform(hass, config, async_add_devices, + discovery_info=None): + """Set up the TFIAC climate device.""" + from pytfiac import Tfiac + + tfiac_client = Tfiac(config[CONF_HOST]) + try: + await tfiac_client.update() + except futures.TimeoutError: + _LOGGER.error("Unable to connect to %s", config[CONF_HOST]) + return + async_add_devices([TfiacClimate(hass, tfiac_client)]) + + +class TfiacClimate(ClimateDevice): + """TFIAC class.""" + + def __init__(self, hass, client): + """Init class.""" + self._client = client + self._available = True + + @property + def available(self): + """Return if the device is available.""" + return self._available + + async def async_update(self): + """Update status via socket polling.""" + try: + await self._client.update() + self._available = True + except futures.TimeoutError: + self._available = False + + @property + def supported_features(self): + """Return the list of supported features.""" + return (SUPPORT_FAN_MODE | SUPPORT_ON_OFF | SUPPORT_OPERATION_MODE + | SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE) + + @property + def min_temp(self): + """Return the minimum temperature.""" + return MIN_TEMP + + @property + def max_temp(self): + """Return the maximum temperature.""" + return MAX_TEMP + + @property + def name(self): + """Return the name of the climate device.""" + return self._client.name + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + return self._client.status['target_temp'] + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return TEMP_FAHRENHEIT + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._client.status['current_temp'] + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + operation = self._client.status['operation'] + return OPERATION_MAP_REV.get(operation, operation) + + @property + def is_on(self): + """Return true if on.""" + return self._client.status[ON_MODE] == 'on' + + @property + def operation_list(self): + """Return the list of available operation modes.""" + return sorted(OPERATION_MAP) + + @property + def current_fan_mode(self): + """Return the fan setting.""" + return self._client.status['fan_mode'] + + @property + def fan_list(self): + """Return the list of available fan modes.""" + return FAN_LIST + + @property + def current_swing_mode(self): + """Return the swing setting.""" + return self._client.status['swing_mode'] + + @property + def swing_list(self): + """List of available swing modes.""" + return SWING_LIST + + async def async_set_temperature(self, **kwargs): + """Set new target temperature.""" + if kwargs.get(ATTR_TEMPERATURE) is not None: + await self._client.set_state(TARGET_TEMP, + kwargs.get(ATTR_TEMPERATURE)) + + async def async_set_operation_mode(self, operation_mode): + """Set new operation mode.""" + await self._client.set_state(OPERATION_MODE, + OPERATION_MAP[operation_mode]) + + async def async_set_fan_mode(self, fan_mode): + """Set new fan mode.""" + await self._client.set_state(FAN_MODE, fan_mode) + + async def async_set_swing_mode(self, swing_mode): + """Set new swing mode.""" + await self._client.set_swing(swing_mode) + + async def async_turn_on(self): + """Turn device on.""" + await self._client.set_state(ON_MODE, 'on') + + async def async_turn_off(self): + """Turn device off.""" + await self._client.set_state(ON_MODE, 'off') diff --git a/homeassistant/components/thermoworks_smoke/__init__.py b/homeassistant/components/thermoworks_smoke/__init__.py new file mode 100644 index 00000000000..4fea3085a3c --- /dev/null +++ b/homeassistant/components/thermoworks_smoke/__init__.py @@ -0,0 +1 @@ +"""The thermoworks_smoke component.""" diff --git a/homeassistant/components/sensor/thermoworks_smoke.py b/homeassistant/components/thermoworks_smoke/sensor.py similarity index 100% rename from homeassistant/components/sensor/thermoworks_smoke.py rename to homeassistant/components/thermoworks_smoke/sensor.py diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index 05da90bf7ac..d59b429721b 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -8,13 +8,13 @@ import async_timeout import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.components.thethingsnetwork import ( - DATA_TTN, TTN_APP_ID, TTN_ACCESS_KEY, TTN_DATA_STORAGE_URL) from homeassistant.const import CONTENT_TYPE_JSON from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from . import DATA_TTN, TTN_ACCESS_KEY, TTN_APP_ID, TTN_DATA_STORAGE_URL + _LOGGER = logging.getLogger(__name__) ATTR_DEVICE_ID = 'device_id' diff --git a/homeassistant/components/thomson/__init__.py b/homeassistant/components/thomson/__init__.py new file mode 100644 index 00000000000..3c1ce045f39 --- /dev/null +++ b/homeassistant/components/thomson/__init__.py @@ -0,0 +1 @@ +"""The thomson component.""" diff --git a/homeassistant/components/device_tracker/thomson.py b/homeassistant/components/thomson/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/thomson.py rename to homeassistant/components/thomson/device_tracker.py diff --git a/homeassistant/components/threshold/__init__.py b/homeassistant/components/threshold/__init__.py new file mode 100644 index 00000000000..98ebdcd8418 --- /dev/null +++ b/homeassistant/components/threshold/__init__.py @@ -0,0 +1 @@ +"""The threshold component.""" diff --git a/homeassistant/components/binary_sensor/threshold.py b/homeassistant/components/threshold/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/threshold.py rename to homeassistant/components/threshold/binary_sensor.py diff --git a/homeassistant/components/tibber/__init__.py b/homeassistant/components/tibber/__init__.py index f254774eea4..19cf6fe6525 100644 --- a/homeassistant/components/tibber/__init__.py +++ b/homeassistant/components/tibber/__init__.py @@ -11,7 +11,7 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_ACCESS_TOKEN, from homeassistant.helpers import discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession -REQUIREMENTS = ['pyTibber==0.9.6'] +REQUIREMENTS = ['pyTibber==0.10.1'] DOMAIN = 'tibber' diff --git a/homeassistant/components/tibber/notify.py b/homeassistant/components/tibber/notify.py index 6ae22c34209..604fadb870c 100644 --- a/homeassistant/components/tibber/notify.py +++ b/homeassistant/components/tibber/notify.py @@ -4,7 +4,8 @@ import logging from homeassistant.components.notify import ( ATTR_TITLE, ATTR_TITLE_DEFAULT, BaseNotificationService) -from homeassistant.components.tibber import DOMAIN as TIBBER_DOMAIN + +from . import DOMAIN as TIBBER_DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index f3e0c39a1e6..9f3e9cdcc62 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -1,16 +1,15 @@ """Support for Tibber sensors.""" import asyncio - +from datetime import timedelta import logging -from datetime import timedelta import aiohttp -from homeassistant.components.tibber import DOMAIN as TIBBER_DOMAIN from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.entity import Entity -from homeassistant.util import dt as dt_util -from homeassistant.util import Throttle +from homeassistant.util import Throttle, dt as dt_util + +from . import DOMAIN as TIBBER_DOMAIN _LOGGER = logging.getLogger(__name__) @@ -69,7 +68,7 @@ class TibberSensorElPrice(Entity): return if (not self._last_data_timestamp or - (self._last_data_timestamp - now).total_seconds()/3600 < 12 + (self._last_data_timestamp - now).total_seconds() / 3600 < 12 or not self._is_available): _LOGGER.debug("Asking for new data.") await self._fetch_data() @@ -136,12 +135,13 @@ class TibberSensorElPrice(Entity): for key, price_total in self._tibber_home.price_total.items(): price_time = dt_util.as_local(dt_util.parse_datetime(key)) price_total = round(price_total, 3) - time_diff = (now - price_time).total_seconds()/60 + time_diff = (now - price_time).total_seconds() / 60 if (not self._last_data_timestamp or price_time > self._last_data_timestamp): self._last_data_timestamp = price_time if 0 <= time_diff < 60: state = price_total + level = self._tibber_home.price_level[key] self._last_updated = price_time if now.date() == price_time.date(): max_price = max(max_price, price_total) @@ -152,6 +152,7 @@ class TibberSensorElPrice(Entity): self._device_state_attributes['max_price'] = max_price self._device_state_attributes['avg_price'] = round(sum_price / num, 3) self._device_state_attributes['min_price'] = min_price + self._device_state_attributes['price_level'] = level return state is not None @@ -232,4 +233,4 @@ class TibberSensorRT(Entity): """Return a unique ID.""" home = self._tibber_home.info['viewer']['home'] _id = home['meteringPointData']['consumptionEan'] - return'{}_rt_consumption'.format(_id) + return '{}_rt_consumption'.format(_id) diff --git a/homeassistant/components/tikteck/__init__.py b/homeassistant/components/tikteck/__init__.py new file mode 100644 index 00000000000..59511d5bea9 --- /dev/null +++ b/homeassistant/components/tikteck/__init__.py @@ -0,0 +1 @@ +"""The tikteck component.""" diff --git a/homeassistant/components/light/tikteck.py b/homeassistant/components/tikteck/light.py similarity index 100% rename from homeassistant/components/light/tikteck.py rename to homeassistant/components/tikteck/light.py diff --git a/homeassistant/components/tile/__init__.py b/homeassistant/components/tile/__init__.py new file mode 100644 index 00000000000..f0192d0ed32 --- /dev/null +++ b/homeassistant/components/tile/__init__.py @@ -0,0 +1 @@ +"""The tile component.""" diff --git a/homeassistant/components/device_tracker/tile.py b/homeassistant/components/tile/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/tile.py rename to homeassistant/components/tile/device_tracker.py diff --git a/homeassistant/components/time_date/__init__.py b/homeassistant/components/time_date/__init__.py new file mode 100644 index 00000000000..25e6fa14f39 --- /dev/null +++ b/homeassistant/components/time_date/__init__.py @@ -0,0 +1 @@ +"""The time_date component.""" diff --git a/homeassistant/components/sensor/time_date.py b/homeassistant/components/time_date/sensor.py similarity index 95% rename from homeassistant/components/sensor/time_date.py rename to homeassistant/components/time_date/sensor.py index 1b346d409c4..7825867df64 100644 --- a/homeassistant/components/sensor/time_date.py +++ b/homeassistant/components/time_date/sensor.py @@ -25,6 +25,7 @@ OPTION_TYPES = { 'time': 'Time', 'date': 'Date', 'date_time': 'Date & Time', + 'date_time_iso': 'Date & Time ISO', 'time_date': 'Time & Date', 'beat': 'Internet Time', 'time_utc': 'Time (UTC)', @@ -123,6 +124,9 @@ class TimeDateSensor(Entity): self._state = time_utc elif self.type == 'beat': self._state = '@{0:03d}'.format(beat) + elif self.type == 'date_time_iso': + self._state = dt_util.parse_datetime( + '{} {}'.format(date, time)).isoformat() @callback def point_in_time_listener(self, time_date): diff --git a/homeassistant/components/tod/__init__.py b/homeassistant/components/tod/__init__.py new file mode 100644 index 00000000000..fa15326becb --- /dev/null +++ b/homeassistant/components/tod/__init__.py @@ -0,0 +1 @@ +"""The tod component.""" diff --git a/homeassistant/components/binary_sensor/tod.py b/homeassistant/components/tod/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/tod.py rename to homeassistant/components/tod/binary_sensor.py diff --git a/homeassistant/components/todoist/__init__.py b/homeassistant/components/todoist/__init__.py new file mode 100644 index 00000000000..78a9cb89624 --- /dev/null +++ b/homeassistant/components/todoist/__init__.py @@ -0,0 +1 @@ +"""The todoist component.""" diff --git a/homeassistant/components/calendar/todoist.py b/homeassistant/components/todoist/calendar.py similarity index 100% rename from homeassistant/components/calendar/todoist.py rename to homeassistant/components/todoist/calendar.py diff --git a/homeassistant/components/tomato/__init__.py b/homeassistant/components/tomato/__init__.py new file mode 100644 index 00000000000..e8a67f7e3bc --- /dev/null +++ b/homeassistant/components/tomato/__init__.py @@ -0,0 +1 @@ +"""The tomato component.""" diff --git a/homeassistant/components/device_tracker/tomato.py b/homeassistant/components/tomato/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/tomato.py rename to homeassistant/components/tomato/device_tracker.py diff --git a/homeassistant/components/toon/.translations/bg.json b/homeassistant/components/toon/.translations/bg.json new file mode 100644 index 00000000000..e4aa0d8c088 --- /dev/null +++ b/homeassistant/components/toon/.translations/bg.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "client_id": "\u041a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u044f\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043e\u0442 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d.", + "client_secret": "\u041a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0430\u0442\u0430 \u043f\u0430\u0440\u043e\u043b\u0430 \u043e\u0442 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u0430.", + "no_agreements": "\u0422\u043e\u0437\u0438 \u043f\u0440\u043e\u0444\u0438\u043b \u043d\u044f\u043c\u0430 Toon \u0434\u0438\u0441\u043f\u043b\u0435\u0438.", + "no_app": "\u0422\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Toon, \u043f\u0440\u0435\u0434\u0438 \u0434\u0430 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0430 \u0441\u0435 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u0430\u0442\u0435. [\u041c\u043e\u043b\u044f, \u043f\u0440\u043e\u0447\u0435\u0442\u0435\u0442\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438\u0442\u0435] (https://www.home-assistant.io/components/toon/).", + "unknown_auth_fail": "\u0412\u044a\u0437\u043d\u0438\u043a\u043d\u0430 \u043d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + }, + "error": { + "credentials": "\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u0438 \u0441\u0430 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u0438.", + "display_exists": "\u0418\u0437\u0431\u0440\u0430\u043d\u0438\u044f\u0442 \u0434\u0438\u0441\u043f\u043b\u0435\u0439 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d." + }, + "step": { + "authenticate": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + }, + "description": "\u0423\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0441 \u0412\u0430\u0448\u0438\u044f Eneco Toon \u043f\u0440\u043e\u0444\u0438\u043b (\u043d\u0435 \u043f\u0440\u043e\u0444\u0438\u043b\u0430 \u0437\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u0446\u0438).", + "title": "\u0421\u0432\u044a\u0440\u0436\u0435\u0442\u0435 \u0412\u0430\u0448\u0438\u044f \u0430\u043a\u0430\u0443\u043d\u0442 \u0432 \u0422\u043e\u043e\u043d" + }, + "display": { + "data": { + "display": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0434\u0438\u0441\u043f\u043b\u0435\u0439" + }, + "description": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0434\u0438\u0441\u043f\u043b\u0435\u044f \u043d\u0430 Toon, \u0441 \u043a\u043e\u0439\u0442\u043e \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0436\u0435\u0442\u0435.", + "title": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0434\u0438\u0441\u043f\u043b\u0435\u0439" + } + }, + "title": "Toon" + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/.translations/es-419.json b/homeassistant/components/toon/.translations/es-419.json index db064def53b..a0ce81495a8 100644 --- a/homeassistant/components/toon/.translations/es-419.json +++ b/homeassistant/components/toon/.translations/es-419.json @@ -13,6 +13,7 @@ "username": "Nombre de usuario" } } - } + }, + "title": "Toon" } } \ No newline at end of file diff --git a/homeassistant/components/toon/.translations/fr.json b/homeassistant/components/toon/.translations/fr.json index 5bf0c60199e..7c41cdc0d24 100644 --- a/homeassistant/components/toon/.translations/fr.json +++ b/homeassistant/components/toon/.translations/fr.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "client_id": "L'ID client de la configuration n'est pas valide.", + "client_secret": "Le client secret de la configuration n'est pas valide.", "no_agreements": "Ce compte n'a pas d'affichages Toon.", "no_app": "Vous devez configurer Toon avant de pouvoir vous authentifier avec celui-ci. [Veuillez lire les instructions] (https://www.home-assistant.io/components/toon/).", "unknown_auth_fail": "Une erreur inattendue s'est produite lors de l'authentification." diff --git a/homeassistant/components/toon/.translations/sl.json b/homeassistant/components/toon/.translations/sl.json new file mode 100644 index 00000000000..18c1a739e5a --- /dev/null +++ b/homeassistant/components/toon/.translations/sl.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "client_id": "ID odjemalca iz konfiguracije je neveljaven.", + "client_secret": "Skrivnost iz konfiguracije odjemalca ni veljaven.", + "no_agreements": "Ta ra\u010dun nima prikazov Toon.", + "no_app": "Toon morate konfigurirati, preden ga boste lahko uporabili za overitev. [Preberite navodila] (https://www.home-assistant.io/components/toon/).", + "unknown_auth_fail": "Pri preverjanju pristnosti je pri\u0161lo do nepri\u010dakovane napake." + }, + "error": { + "credentials": "Navedene poverilnice niso veljavne.", + "display_exists": "Izbrani zaslon je \u017ee konfiguriran." + }, + "step": { + "authenticate": { + "data": { + "password": "Geslo", + "tenant": "Najemnik", + "username": "Uporabni\u0161ko ime" + }, + "description": "Prijavite se s svojim Eneco toon ra\u010dunom (ne razvijalskim).", + "title": "Pove\u017eite svoj Toon ra\u010dun" + }, + "display": { + "data": { + "display": "Izberite zaslon" + }, + "description": "Izberite zaslon Toon, s katerim se \u017eelite povezati.", + "title": "Izberite zaslon" + } + }, + "title": "Toon" + } +} \ No newline at end of file diff --git a/homeassistant/components/torque/__init__.py b/homeassistant/components/torque/__init__.py new file mode 100644 index 00000000000..2f680bcca13 --- /dev/null +++ b/homeassistant/components/torque/__init__.py @@ -0,0 +1 @@ +"""The torque component.""" diff --git a/homeassistant/components/sensor/torque.py b/homeassistant/components/torque/sensor.py similarity index 100% rename from homeassistant/components/sensor/torque.py rename to homeassistant/components/torque/sensor.py diff --git a/homeassistant/components/totalconnect/__init__.py b/homeassistant/components/totalconnect/__init__.py new file mode 100644 index 00000000000..084846a8b85 --- /dev/null +++ b/homeassistant/components/totalconnect/__init__.py @@ -0,0 +1 @@ +"""The totalconnect component.""" diff --git a/homeassistant/components/alarm_control_panel/totalconnect.py b/homeassistant/components/totalconnect/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/totalconnect.py rename to homeassistant/components/totalconnect/alarm_control_panel.py diff --git a/homeassistant/components/touchline/__init__.py b/homeassistant/components/touchline/__init__.py new file mode 100644 index 00000000000..284870313d8 --- /dev/null +++ b/homeassistant/components/touchline/__init__.py @@ -0,0 +1 @@ +"""The touchline component.""" diff --git a/homeassistant/components/climate/touchline.py b/homeassistant/components/touchline/climate.py similarity index 100% rename from homeassistant/components/climate/touchline.py rename to homeassistant/components/touchline/climate.py diff --git a/homeassistant/components/tplink/.translations/bg.json b/homeassistant/components/tplink/.translations/bg.json new file mode 100644 index 00000000000..25ffb753076 --- /dev/null +++ b/homeassistant/components/tplink/.translations/bg.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u041d\u0435 \u0441\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 TP-Link \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430.", + "single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + }, + "step": { + "confirm": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 TP-Link \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430?", + "title": "TP-Link Smart Home" + } + }, + "title": "TP-Link Smart Home" + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/.translations/sl.json b/homeassistant/components/tplink/.translations/sl.json new file mode 100644 index 00000000000..e686ee4bc04 --- /dev/null +++ b/homeassistant/components/tplink/.translations/sl.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "no_devices_found": "TP-Link naprav ni mogo\u010de najti v omre\u017eju.", + "single_instance_allowed": "Potrebna je samo ena konfiguracija." + }, + "step": { + "confirm": { + "description": "\u017delite namestiti pametne naprave TP-Link?", + "title": "TP-Link Pametni Dom" + } + }, + "title": "TP-Link Pametni Dom" + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/.translations/zh-Hans.json b/homeassistant/components/tplink/.translations/zh-Hans.json new file mode 100644 index 00000000000..ca3ac913375 --- /dev/null +++ b/homeassistant/components/tplink/.translations/zh-Hans.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230 TP-Link \u8bbe\u5907\u3002", + "single_instance_allowed": "\u53ea\u80fd\u914d\u7f6e\u4e00\u6b21\u3002" + }, + "step": { + "confirm": { + "description": "\u60a8\u60f3\u8981\u914d\u7f6e TP-Link \u667a\u80fd\u8bbe\u5907\u5417\uff1f", + "title": "TP-Link Smart Home" + } + }, + "title": "TP-Link Smart Home" + } +} \ No newline at end of file diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/tplink/device_tracker.py similarity index 98% rename from homeassistant/components/device_tracker/tplink.py rename to homeassistant/components/tplink/device_tracker.py index 78f16a82d56..33a5d5f32f8 100644 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/tplink/device_tracker.py @@ -77,8 +77,8 @@ class TplinkDeviceScanner(DeviceScanner): self.last_results = {} self.success_init = self._update_info() - except requests.exceptions.ConnectionError: - _LOGGER.debug("ConnectionError in TplinkDeviceScanner") + except requests.exceptions.RequestException: + _LOGGER.debug("RequestException in %s", __class__.__name__) def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" @@ -123,8 +123,8 @@ class Tplink1DeviceScanner(DeviceScanner): self.success_init = False try: self.success_init = self._update_info() - except requests.exceptions.ConnectionError: - _LOGGER.debug("ConnectionError in Tplink1DeviceScanner") + except requests.exceptions.RequestException: + _LOGGER.debug("RequestException in %s", __class__.__name__) def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index de1a943c33a..0ba1dfaa33a 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -8,15 +8,14 @@ import logging import time from homeassistant.components.light import ( - Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR_TEMP, SUPPORT_COLOR) -from homeassistant.util.color import \ - color_temperature_mired_to_kelvin as mired_to_kelvin -from homeassistant.util.color import ( - color_temperature_kelvin_to_mired as kelvin_to_mired) + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, SUPPORT_COLOR_TEMP, Light) import homeassistant.helpers.device_registry as dr -from homeassistant.components.tplink import (DOMAIN as TPLINK_DOMAIN, - CONF_LIGHT) +from homeassistant.util.color import ( + color_temperature_kelvin_to_mired as kelvin_to_mired, + color_temperature_mired_to_kelvin as mired_to_kelvin) + +from . import CONF_LIGHT, DOMAIN as TPLINK_DOMAIN DEPENDENCIES = ['tplink'] diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 65b884169c7..a75945e9956 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -8,12 +8,12 @@ import logging import time from homeassistant.components.switch import ( - SwitchDevice, ATTR_CURRENT_POWER_W, ATTR_TODAY_ENERGY_KWH) -from homeassistant.components.tplink import (DOMAIN as TPLINK_DOMAIN, - CONF_SWITCH) + ATTR_CURRENT_POWER_W, ATTR_TODAY_ENERGY_KWH, SwitchDevice) from homeassistant.const import ATTR_VOLTAGE import homeassistant.helpers.device_registry as dr +from . import CONF_SWITCH, DOMAIN as TPLINK_DOMAIN + DEPENDENCIES = ['tplink'] PARALLEL_UPDATES = 0 diff --git a/homeassistant/components/traccar/__init__.py b/homeassistant/components/traccar/__init__.py new file mode 100644 index 00000000000..03805760c53 --- /dev/null +++ b/homeassistant/components/traccar/__init__.py @@ -0,0 +1 @@ +"""The traccar component.""" diff --git a/homeassistant/components/device_tracker/traccar.py b/homeassistant/components/traccar/device_tracker.py similarity index 51% rename from homeassistant/components/device_tracker/traccar.py rename to homeassistant/components/traccar/device_tracker.py index 1447f7c896c..e3ac1427941 100644 --- a/homeassistant/components/device_tracker/traccar.py +++ b/homeassistant/components/traccar/device_tracker.py @@ -4,7 +4,7 @@ Support for Traccar device tracking. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.traccar/ """ -from datetime import timedelta +from datetime import datetime, timedelta import logging import voluptuous as vol @@ -13,14 +13,15 @@ from homeassistant.components.device_tracker import PLATFORM_SCHEMA from homeassistant.const import ( CONF_HOST, CONF_PORT, CONF_SSL, CONF_VERIFY_SSL, CONF_PASSWORD, CONF_USERNAME, ATTR_BATTERY_LEVEL, - CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS) + CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS, + CONF_EVENT) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import slugify -REQUIREMENTS = ['pytraccar==0.3.0'] +REQUIREMENTS = ['pytraccar==0.5.0', 'stringcase==1.2.0'] _LOGGER = logging.getLogger(__name__) @@ -30,6 +31,25 @@ ATTR_GEOFENCE = 'geofence' ATTR_MOTION = 'motion' ATTR_SPEED = 'speed' ATTR_TRACKER = 'tracker' +ATTR_TRACCAR_ID = 'traccar_id' + +EVENT_DEVICE_MOVING = 'device_moving' +EVENT_COMMAND_RESULT = 'command_result' +EVENT_DEVICE_FUEL_DROP = 'device_fuel_drop' +EVENT_GEOFENCE_ENTER = 'geofence_enter' +EVENT_DEVICE_OFFLINE = 'device_offline' +EVENT_DRIVER_CHANGED = 'driver_changed' +EVENT_GEOFENCE_EXIT = 'geofence_exit' +EVENT_DEVICE_OVERSPEED = 'device_overspeed' +EVENT_DEVICE_ONLINE = 'device_online' +EVENT_DEVICE_STOPPED = 'device_stopped' +EVENT_MAINTENANCE = 'maintenance' +EVENT_ALARM = 'alarm' +EVENT_TEXT_MESSAGE = 'text_message' +EVENT_DEVICE_UNKNOWN = 'device_unknown' +EVENT_IGNITION_OFF = 'ignition_off' +EVENT_IGNITION_ON = 'ignition_on' +EVENT_ALL_EVENTS = 'all_events' DEFAULT_SCAN_INTERVAL = timedelta(seconds=30) SCAN_INTERVAL = DEFAULT_SCAN_INTERVAL @@ -43,6 +63,25 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_EVENT, + default=[]): vol.All(cv.ensure_list, + [vol.Any(EVENT_DEVICE_MOVING, + EVENT_COMMAND_RESULT, + EVENT_DEVICE_FUEL_DROP, + EVENT_GEOFENCE_ENTER, + EVENT_DEVICE_OFFLINE, + EVENT_DRIVER_CHANGED, + EVENT_GEOFENCE_EXIT, + EVENT_DEVICE_OVERSPEED, + EVENT_DEVICE_ONLINE, + EVENT_DEVICE_STOPPED, + EVENT_MAINTENANCE, + EVENT_ALARM, + EVENT_TEXT_MESSAGE, + EVENT_DEVICE_UNKNOWN, + EVENT_IGNITION_OFF, + EVENT_IGNITION_ON, + EVENT_ALL_EVENTS)]), }) @@ -58,7 +97,7 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None): scanner = TraccarScanner( api, hass, async_see, config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL), - config[CONF_MONITORED_CONDITIONS]) + config[CONF_MONITORED_CONDITIONS], config[CONF_EVENT]) return await scanner.async_init() @@ -66,8 +105,12 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None): class TraccarScanner: """Define an object to retrieve Traccar data.""" - def __init__(self, api, hass, async_see, scan_interval, custom_attributes): + def __init__(self, api, hass, async_see, scan_interval, + custom_attributes, + event_types): """Initialize.""" + from stringcase import camelcase + self._event_types = {camelcase(evt): evt for evt in event_types} self._custom_attributes = custom_attributes self._scan_interval = scan_interval self._async_see = async_see @@ -89,6 +132,12 @@ class TraccarScanner: """Update info from Traccar.""" _LOGGER.debug('Updating device data.') await self._api.get_device_info(self._custom_attributes) + self._hass.async_create_task(self.import_device_data()) + if self._event_types: + self._hass.async_create_task(self.import_events()) + + async def import_device_data(self): + """Import device data from Traccar.""" for devicename in self._api.device_info: device = self._api.device_info[devicename] attr = {} @@ -105,6 +154,8 @@ class TraccarScanner: attr[ATTR_BATTERY_LEVEL] = device['battery'] if device.get('motion') is not None: attr[ATTR_MOTION] = device['motion'] + if device.get('traccar_id') is not None: + attr[ATTR_TRACCAR_ID] = device['traccar_id'] for custom_attr in self._custom_attributes: if device.get(custom_attr) is not None: attr[custom_attr] = device[custom_attr] @@ -112,3 +163,27 @@ class TraccarScanner: dev_id=slugify(device['device_id']), gps=(device.get('latitude'), device.get('longitude')), attributes=attr) + + async def import_events(self): + """Import events from Traccar.""" + device_ids = [device['id'] for device in self._api.devices] + end_interval = datetime.utcnow() + start_interval = end_interval - self._scan_interval + events = await self._api.get_events( + device_ids=device_ids, + from_time=start_interval, + to_time=end_interval, + event_types=self._event_types.keys()) + if events is not None: + for event in events: + device_name = next(( + dev.get('name') for dev in self._api.devices() + if dev.get('id') == event['deviceId']), None) + self._hass.bus.async_fire( + 'traccar_' + self._event_types.get(event["type"]), { + 'device_traccar_id': event['deviceId'], + 'device_name': device_name, + 'type': event['type'], + 'serverTime': event['serverTime'], + 'attributes': event['attributes'] + }) diff --git a/homeassistant/components/trackr/__init__.py b/homeassistant/components/trackr/__init__.py new file mode 100644 index 00000000000..b78eb8078a2 --- /dev/null +++ b/homeassistant/components/trackr/__init__.py @@ -0,0 +1 @@ +"""The trackr component.""" diff --git a/homeassistant/components/device_tracker/trackr.py b/homeassistant/components/trackr/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/trackr.py rename to homeassistant/components/trackr/device_tracker.py diff --git a/homeassistant/components/tradfri/.translations/bg.json b/homeassistant/components/tradfri/.translations/bg.json new file mode 100644 index 00000000000..15d052c758f --- /dev/null +++ b/homeassistant/components/tradfri/.translations/bg.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u0428\u043b\u044e\u0437\u0430 \u0435 \u0432\u0435\u0447\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d" + }, + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 \u0448\u043b\u044e\u0437\u0430.", + "invalid_key": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0430\u043d\u0435 \u0441 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d\u0438\u044f \u043a\u043b\u044e\u0447. \u0410\u043a\u043e \u0442\u043e\u0432\u0430 \u043f\u0440\u043e\u0434\u044a\u043b\u0436\u0430\u0432\u0430 \u0434\u0430 \u0441\u0435 \u0441\u043b\u0443\u0447\u0432\u0430, \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u0434\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u0442\u0435 \u0448\u043b\u044e\u0437\u0430.", + "timeout": "\u0412\u0440\u0435\u043c\u0435\u0442\u043e \u0437\u0430 \u043f\u043e\u0442\u0432\u044a\u0440\u0436\u0434\u0430\u0432\u0430\u043d\u0435 \u043d\u0430 \u043a\u043e\u0434\u0430 \u0438\u0437\u0442\u0435\u0447\u0435." + }, + "step": { + "auth": { + "data": { + "host": "\u0410\u0434\u0440\u0435\u0441", + "security_code": "\u041a\u043e\u0434 \u0437\u0430 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442" + }, + "description": "\u041c\u043e\u0436\u0435\u0442\u0435 \u0434\u0430 \u043d\u0430\u043c\u0435\u0440\u0438\u0442\u0435 \u043a\u043e\u0434\u0430 \u0437\u0430 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442 \u043d\u0430 \u0433\u044a\u0440\u0431\u0430 \u043d\u0430 \u0448\u043b\u044e\u0437\u0430.", + "title": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043a\u043e\u0434 \u0437\u0430 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442" + } + }, + "title": "IKEA TR\u00c5DFRI" + } +} \ No newline at end of file diff --git a/homeassistant/components/tradfri/.translations/ru.json b/homeassistant/components/tradfri/.translations/ru.json index c42ca6b7b2b..352579f810c 100644 --- a/homeassistant/components/tradfri/.translations/ru.json +++ b/homeassistant/components/tradfri/.translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u0428\u043b\u044e\u0437 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d" + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430" }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0448\u043b\u044e\u0437\u0443", diff --git a/homeassistant/components/tradfri/config_flow.py b/homeassistant/components/tradfri/config_flow.py index 2e24fde8294..0ad269b8780 100644 --- a/homeassistant/components/tradfri/config_flow.py +++ b/homeassistant/components/tradfri/config_flow.py @@ -11,7 +11,6 @@ from homeassistant import config_entries from .const import ( CONF_IMPORT_GROUPS, CONF_IDENTITY, CONF_HOST, CONF_KEY, CONF_GATEWAY_ID) -KEY_HOST = 'host' KEY_SECURITY_CODE = 'security_code' KEY_IMPORT_GROUPS = 'import_groups' @@ -45,7 +44,7 @@ class FlowHandler(config_entries.ConfigFlow): errors = {} if user_input is not None: - host = user_input.get(KEY_HOST, self._host) + host = user_input.get(CONF_HOST, self._host) try: auth = await authenticate( self.hass, host, @@ -67,7 +66,7 @@ class FlowHandler(config_entries.ConfigFlow): fields = OrderedDict() if self._host is None: - fields[vol.Required(KEY_HOST)] = str + fields[vol.Required(CONF_HOST)] = str fields[vol.Required(KEY_SECURITY_CODE)] = str diff --git a/homeassistant/components/tradfri/light.py b/homeassistant/components/tradfri/light.py index e5e27ecbed1..38ce428b51b 100644 --- a/homeassistant/components/tradfri/light.py +++ b/homeassistant/components/tradfri/light.py @@ -1,19 +1,16 @@ """Support for IKEA Tradfri lights.""" import logging -from homeassistant.core import callback from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, SUPPORT_COLOR_TEMP, - SUPPORT_COLOR, Light) -from homeassistant.components.light import \ - PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA -from homeassistant.components.tradfri import ( - KEY_GATEWAY, KEY_API, DOMAIN as TRADFRI_DOMAIN) -from homeassistant.components.tradfri.const import ( - CONF_IMPORT_GROUPS, CONF_GATEWAY_ID) + PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION, Light) +from homeassistant.core import callback import homeassistant.util.color as color_util +from . import DOMAIN as TRADFRI_DOMAIN, KEY_API, KEY_GATEWAY +from .const import CONF_GATEWAY_ID, CONF_IMPORT_GROUPS + _LOGGER = logging.getLogger(__name__) ATTR_DIMMER = 'dimmer' diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index 97c7dc9627d..acc84a93590 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -1,12 +1,12 @@ """Support for IKEA Tradfri sensors.""" +from datetime import timedelta import logging -from datetime import timedelta - from homeassistant.core import callback -from homeassistant.components.tradfri import KEY_GATEWAY, KEY_API from homeassistant.helpers.entity import Entity +from . import KEY_API, KEY_GATEWAY + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['tradfri'] diff --git a/homeassistant/components/tradfri/switch.py b/homeassistant/components/tradfri/switch.py index 23e6cb20c8f..ef9a9537cff 100644 --- a/homeassistant/components/tradfri/switch.py +++ b/homeassistant/components/tradfri/switch.py @@ -1,12 +1,11 @@ """Support for IKEA Tradfri switches.""" import logging -from homeassistant.core import callback from homeassistant.components.switch import SwitchDevice -from homeassistant.components.tradfri import ( - KEY_GATEWAY, KEY_API, DOMAIN as TRADFRI_DOMAIN) -from homeassistant.components.tradfri.const import ( - CONF_GATEWAY_ID) +from homeassistant.core import callback + +from . import DOMAIN as TRADFRI_DOMAIN, KEY_API, KEY_GATEWAY +from .const import CONF_GATEWAY_ID _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/trafikverket_weatherstation/__init__.py b/homeassistant/components/trafikverket_weatherstation/__init__.py new file mode 100644 index 00000000000..7feac4aad27 --- /dev/null +++ b/homeassistant/components/trafikverket_weatherstation/__init__.py @@ -0,0 +1 @@ +"""The trafikverket_weatherstation component.""" diff --git a/homeassistant/components/sensor/trafikverket_weatherstation.py b/homeassistant/components/trafikverket_weatherstation/sensor.py similarity index 61% rename from homeassistant/components/sensor/trafikverket_weatherstation.py rename to homeassistant/components/trafikverket_weatherstation/sensor.py index 38cc23dabbe..2dffd580b7e 100644 --- a/homeassistant/components/sensor/trafikverket_weatherstation.py +++ b/homeassistant/components/trafikverket_weatherstation/sensor.py @@ -14,17 +14,20 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_API_KEY, CONF_MONITORED_CONDITIONS, CONF_NAME) + ATTR_ATTRIBUTION, CONF_API_KEY, CONF_MONITORED_CONDITIONS, + CONF_NAME, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ['pytrafikverket==0.1.5.8'] +REQUIREMENTS = ['pytrafikverket==0.1.5.9'] _LOGGER = logging.getLogger(__name__) -ATTRIBUTION = "Data provided by Trafikverket API" +ATTRIBUTION = "Data provided by Trafikverket" +ATTR_MEASURE_TIME = 'measure_time' +ATTR_ACTIVE = 'active' CONF_STATION = 'station' @@ -33,13 +36,33 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) SCAN_INTERVAL = timedelta(seconds=300) SENSOR_TYPES = { - 'air_temp': ['Air temperature', '°C', 'air_temp'], - 'road_temp': ['Road temperature', '°C', 'road_temp'], - 'precipitation': ['Precipitation type', None, 'precipitationtype'], - 'wind_direction': ['Wind direction', '°', 'winddirection'], - 'wind_direction_text': ['Wind direction text', None, 'winddirectiontext'], - 'wind_speed': ['Wind speed', 'm/s', 'windforce'], - 'humidity': ['Humidity', '%', 'humidity'], + 'air_temp': [ + 'Air temperature', TEMP_CELSIUS, + 'air_temp', 'mdi:thermometer', DEVICE_CLASS_TEMPERATURE], + 'road_temp': [ + 'Road temperature', TEMP_CELSIUS, + 'road_temp', 'mdi:thermometer', DEVICE_CLASS_TEMPERATURE], + 'precipitation': [ + 'Precipitation type', None, + 'precipitationtype', 'mdi:weather-snowy-rainy', None], + 'wind_direction': [ + 'Wind direction', '°', + 'winddirection', 'mdi:flag-triangle', None], + 'wind_direction_text': [ + 'Wind direction text', None, + 'winddirectiontext', 'mdi:flag-triangle', None], + 'wind_speed': [ + 'Wind speed', 'm/s', + 'windforce', 'mdi:weather-windy', None], + 'humidity': [ + 'Humidity', '%', + 'humidity', 'mdi:water-percent', DEVICE_CLASS_HUMIDITY], + 'precipitation_amount': [ + 'Precipitation amount', 'mm', + 'precipitation_amount', 'mdi:cup-water', None], + 'precipitation_amountname': [ + 'Precipitation name', None, + 'precipitation_amountname', 'mdi:weather-pouring', None], } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -85,9 +108,8 @@ class TrafikverketWeatherStation(Entity): self._unit = SENSOR_TYPES[sensor_type][1] self._station = sensor_station self._weather_api = weather_api - self._attributes = { - ATTR_ATTRIBUTION: ATTRIBUTION, - } + self._icon = SENSOR_TYPES[sensor_type][3] + self._device_class = SENSOR_TYPES[sensor_type][4] self._weather = None @property @@ -95,6 +117,25 @@ class TrafikverketWeatherStation(Entity): """Return the name of the sensor.""" return '{} {}'.format(self._client, self._name) + @property + def icon(self): + """Icon to use in the frontend.""" + return self._icon + + @property + def device_state_attributes(self): + """Return the state attributes of Trafikverket Weatherstation.""" + return { + ATTR_ATTRIBUTION: ATTRIBUTION, + ATTR_ACTIVE: self._weather.active, + ATTR_MEASURE_TIME: self._weather.measure_time, + } + + @property + def device_class(self): + """Return the device class of the sensor.""" + return self._device_class + @property def state(self): """Return the state of the device.""" @@ -116,4 +157,4 @@ class TrafikverketWeatherStation(Entity): SENSOR_TYPES[self._type][2]) except (asyncio.TimeoutError, aiohttp.ClientError, ValueError) as error: - _LOGGER.error("Couldn't fetch weather data: %s", error) + _LOGGER.error("Could not fetch weather data: %s", error) diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index 061ed2c0c64..dfd4c195097 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -1,15 +1,14 @@ """Support for monitoring the Transmission BitTorrent client API.""" from datetime import timedelta - import logging -from homeassistant.components.transmission import ( - DATA_TRANSMISSION, SENSOR_TYPES, DATA_UPDATED) from homeassistant.const import STATE_IDLE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity +from . import DATA_TRANSMISSION, DATA_UPDATED, SENSOR_TYPES + DEPENDENCIES = ['transmission'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py index 373397eddd6..854a2e727b0 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -1,14 +1,13 @@ """Support for setting the Transmission BitTorrent client Turtle Mode.""" import logging -from homeassistant.components.transmission import ( - DATA_TRANSMISSION, DATA_UPDATED) -from homeassistant.const import ( - STATE_OFF, STATE_ON) +from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import ToggleEntity +from . import DATA_TRANSMISSION, DATA_UPDATED + DEPENDENCIES = ['transmission'] _LOGGING = logging.getLogger(__name__) diff --git a/homeassistant/components/transport_nsw/__init__.py b/homeassistant/components/transport_nsw/__init__.py new file mode 100644 index 00000000000..679c2f3944c --- /dev/null +++ b/homeassistant/components/transport_nsw/__init__.py @@ -0,0 +1 @@ +"""The transport_nsw component.""" diff --git a/homeassistant/components/sensor/transport_nsw.py b/homeassistant/components/transport_nsw/sensor.py similarity index 100% rename from homeassistant/components/sensor/transport_nsw.py rename to homeassistant/components/transport_nsw/sensor.py diff --git a/homeassistant/components/travisci/__init__.py b/homeassistant/components/travisci/__init__.py new file mode 100644 index 00000000000..9337f87592f --- /dev/null +++ b/homeassistant/components/travisci/__init__.py @@ -0,0 +1 @@ +"""The travisci component.""" diff --git a/homeassistant/components/sensor/travisci.py b/homeassistant/components/travisci/sensor.py similarity index 100% rename from homeassistant/components/sensor/travisci.py rename to homeassistant/components/travisci/sensor.py diff --git a/homeassistant/components/trend/__init__.py b/homeassistant/components/trend/__init__.py new file mode 100644 index 00000000000..77159060263 --- /dev/null +++ b/homeassistant/components/trend/__init__.py @@ -0,0 +1 @@ +"""A sensor that monitors trends in other components.""" diff --git a/homeassistant/components/binary_sensor/trend.py b/homeassistant/components/trend/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/trend.py rename to homeassistant/components/trend/binary_sensor.py diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index 06714760a02..b7a10dad862 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -1,15 +1,15 @@ """Support for the Tuya climate devices.""" -from homeassistant.components.climate import ClimateDevice, ENTITY_ID_FORMAT +from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice from homeassistant.components.climate.const import ( - STATE_AUTO, STATE_COOL, STATE_ECO, - STATE_FAN_ONLY, STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) -from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH -from homeassistant.components.tuya import DATA_TUYA, TuyaDevice - + STATE_AUTO, STATE_COOL, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT, + SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE) +from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM from homeassistant.const import ( ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS, TEMP_FAHRENHEIT) +from . import DATA_TUYA, TuyaDevice + DEPENDENCIES = ['tuya'] DEVICE_TYPE = 'climate' diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index ac2309cbf9e..274f4d93869 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -1,7 +1,8 @@ """Support for Tuya covers.""" from homeassistant.components.cover import ( - CoverDevice, ENTITY_ID_FORMAT, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP) -from homeassistant.components.tuya import DATA_TUYA, TuyaDevice + ENTITY_ID_FORMAT, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP, CoverDevice) + +from . import DATA_TUYA, TuyaDevice DEPENDENCIES = ['tuya'] diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index b6e2cb6950c..259417869dc 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -1,9 +1,10 @@ """Support for Tuya fans.""" from homeassistant.components.fan import ( - ENTITY_ID_FORMAT, FanEntity, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED) -from homeassistant.components.tuya import DATA_TUYA, TuyaDevice + ENTITY_ID_FORMAT, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity) from homeassistant.const import STATE_OFF +from . import DATA_TUYA, TuyaDevice + DEPENDENCIES = ['tuya'] diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 1cf2f811872..17f9b43dcbe 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -1,11 +1,11 @@ """Support for the Tuya lights.""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ENTITY_ID_FORMAT, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_COLOR, Light) - -from homeassistant.components.tuya import DATA_TUYA, TuyaDevice + SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, Light) from homeassistant.util import color as colorutil +from . import DATA_TUYA, TuyaDevice + DEPENDENCIES = ['tuya'] diff --git a/homeassistant/components/tuya/scene.py b/homeassistant/components/tuya/scene.py index 33d207d8545..24383dca6e4 100644 --- a/homeassistant/components/tuya/scene.py +++ b/homeassistant/components/tuya/scene.py @@ -1,6 +1,7 @@ """Support for the Tuya scenes.""" -from homeassistant.components.scene import Scene, DOMAIN -from homeassistant.components.tuya import DATA_TUYA, TuyaDevice +from homeassistant.components.scene import DOMAIN, Scene + +from . import DATA_TUYA, TuyaDevice DEPENDENCIES = ['tuya'] diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 1e8fab2cc1b..c2e32eedc59 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -1,6 +1,7 @@ """Support for Tuya switches.""" from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice -from homeassistant.components.tuya import DATA_TUYA, TuyaDevice + +from . import DATA_TUYA, TuyaDevice DEPENDENCIES = ['tuya'] diff --git a/homeassistant/components/twilio/.translations/bg.json b/homeassistant/components/twilio/.translations/bg.json new file mode 100644 index 00000000000..6f06d5c00c6 --- /dev/null +++ b/homeassistant/components/twilio/.translations/bg.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/pl.json b/homeassistant/components/twilio/.translations/pl.json index 19c835c4b8c..2b963ff1be5 100644 --- a/homeassistant/components/twilio/.translations/pl.json +++ b/homeassistant/components/twilio/.translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Twilio Webhook]({twilio_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/x-www-form-urlencoded \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Twilio Webhook]({twilio_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/x-www-form-urlencoded \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji, by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." }, "step": { "user": { diff --git a/homeassistant/components/twilio_call/__init__.py b/homeassistant/components/twilio_call/__init__.py new file mode 100644 index 00000000000..87b225b713a --- /dev/null +++ b/homeassistant/components/twilio_call/__init__.py @@ -0,0 +1 @@ +"""The twilio_call component.""" diff --git a/homeassistant/components/notify/twilio_call.py b/homeassistant/components/twilio_call/notify.py similarity index 92% rename from homeassistant/components/notify/twilio_call.py rename to homeassistant/components/twilio_call/notify.py index 538a4fd9512..a1a28a03b18 100644 --- a/homeassistant/components/notify/twilio_call.py +++ b/homeassistant/components/twilio_call/notify.py @@ -11,8 +11,9 @@ import voluptuous as vol from homeassistant.components.twilio import DATA_TWILIO import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) + +from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA, + BaseNotificationService) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/twilio_sms/__init__.py b/homeassistant/components/twilio_sms/__init__.py new file mode 100644 index 00000000000..3bf3898ac3f --- /dev/null +++ b/homeassistant/components/twilio_sms/__init__.py @@ -0,0 +1 @@ +"""The twilio_sms component.""" diff --git a/homeassistant/components/notify/twilio_sms.py b/homeassistant/components/twilio_sms/notify.py similarity index 91% rename from homeassistant/components/notify/twilio_sms.py rename to homeassistant/components/twilio_sms/notify.py index e106c5ae6aa..b3b35ea1789 100644 --- a/homeassistant/components/notify/twilio_sms.py +++ b/homeassistant/components/twilio_sms/notify.py @@ -10,8 +10,9 @@ import voluptuous as vol from homeassistant.components.twilio import DATA_TWILIO import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) + +from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA, + BaseNotificationService) _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["twilio"] diff --git a/homeassistant/components/twitch/__init__.py b/homeassistant/components/twitch/__init__.py new file mode 100644 index 00000000000..0cdeb813945 --- /dev/null +++ b/homeassistant/components/twitch/__init__.py @@ -0,0 +1 @@ +"""The twitch component.""" diff --git a/homeassistant/components/sensor/twitch.py b/homeassistant/components/twitch/sensor.py similarity index 100% rename from homeassistant/components/sensor/twitch.py rename to homeassistant/components/twitch/sensor.py diff --git a/homeassistant/components/twitter/__init__.py b/homeassistant/components/twitter/__init__.py new file mode 100644 index 00000000000..1ecba66a44e --- /dev/null +++ b/homeassistant/components/twitter/__init__.py @@ -0,0 +1 @@ +"""The twitter component.""" diff --git a/homeassistant/components/notify/twitter.py b/homeassistant/components/twitter/notify.py similarity index 97% rename from homeassistant/components/notify/twitter.py rename to homeassistant/components/twitter/notify.py index d494952716e..9172da36785 100644 --- a/homeassistant/components/notify/twitter.py +++ b/homeassistant/components/twitter/notify.py @@ -4,21 +4,22 @@ Twitter platform for notify component. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.twitter/ """ +from datetime import datetime, timedelta +from functools import partial import json import logging import mimetypes import os -from datetime import timedelta, datetime -from functools import partial import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import ( - ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_ACCESS_TOKEN, CONF_USERNAME +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_point_in_time +from homeassistant.components.notify import (ATTR_DATA, PLATFORM_SCHEMA, + BaseNotificationService) + REQUIREMENTS = ['TwitterAPI==2.5.9'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ubee/__init__.py b/homeassistant/components/ubee/__init__.py new file mode 100644 index 00000000000..cc7b131a2bd --- /dev/null +++ b/homeassistant/components/ubee/__init__.py @@ -0,0 +1 @@ +"""The ubee component.""" diff --git a/homeassistant/components/device_tracker/ubee.py b/homeassistant/components/ubee/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/ubee.py rename to homeassistant/components/ubee/device_tracker.py diff --git a/homeassistant/components/uber/__init__.py b/homeassistant/components/uber/__init__.py new file mode 100644 index 00000000000..b555f83fed9 --- /dev/null +++ b/homeassistant/components/uber/__init__.py @@ -0,0 +1 @@ +"""The uber component.""" diff --git a/homeassistant/components/sensor/uber.py b/homeassistant/components/uber/sensor.py similarity index 100% rename from homeassistant/components/sensor/uber.py rename to homeassistant/components/uber/sensor.py diff --git a/homeassistant/components/ubus/__init__.py b/homeassistant/components/ubus/__init__.py new file mode 100644 index 00000000000..227825ac7c0 --- /dev/null +++ b/homeassistant/components/ubus/__init__.py @@ -0,0 +1 @@ +"""The ubus component.""" diff --git a/homeassistant/components/device_tracker/ubus.py b/homeassistant/components/ubus/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/ubus.py rename to homeassistant/components/ubus/device_tracker.py diff --git a/homeassistant/components/ue_smart_radio/__init__.py b/homeassistant/components/ue_smart_radio/__init__.py new file mode 100644 index 00000000000..2d686b7c5ea --- /dev/null +++ b/homeassistant/components/ue_smart_radio/__init__.py @@ -0,0 +1 @@ +"""The ue_smart_radio component.""" diff --git a/homeassistant/components/media_player/ue_smart_radio.py b/homeassistant/components/ue_smart_radio/media_player.py similarity index 100% rename from homeassistant/components/media_player/ue_smart_radio.py rename to homeassistant/components/ue_smart_radio/media_player.py diff --git a/homeassistant/components/uk_transport/__init__.py b/homeassistant/components/uk_transport/__init__.py new file mode 100644 index 00000000000..b02a6bf3f64 --- /dev/null +++ b/homeassistant/components/uk_transport/__init__.py @@ -0,0 +1 @@ +"""The uk_transport component.""" diff --git a/homeassistant/components/sensor/uk_transport.py b/homeassistant/components/uk_transport/sensor.py similarity index 100% rename from homeassistant/components/sensor/uk_transport.py rename to homeassistant/components/uk_transport/sensor.py diff --git a/homeassistant/components/unifi/.translations/bg.json b/homeassistant/components/unifi/.translations/bg.json new file mode 100644 index 00000000000..beb1bc0d6e6 --- /dev/null +++ b/homeassistant/components/unifi/.translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "\u0410\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/.translations/hu.json b/homeassistant/components/unifi/.translations/hu.json index 6f78beaffd6..b927e652ba7 100644 --- a/homeassistant/components/unifi/.translations/hu.json +++ b/homeassistant/components/unifi/.translations/hu.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "host": "Host", + "host": "Hoszt", "password": "Jelsz\u00f3", "port": "Port", "site": "Site azonos\u00edt\u00f3", diff --git a/homeassistant/components/unifi/.translations/ru.json b/homeassistant/components/unifi/.translations/ru.json index 381f4831c53..c061ab36e7b 100644 --- a/homeassistant/components/unifi/.translations/ru.json +++ b/homeassistant/components/unifi/.translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", "user_privilege": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c" }, "error": { diff --git a/homeassistant/components/unifi/.translations/zh-Hans.json b/homeassistant/components/unifi/.translations/zh-Hans.json index c8796536e2f..80ed9eb2fa5 100644 --- a/homeassistant/components/unifi/.translations/zh-Hans.json +++ b/homeassistant/components/unifi/.translations/zh-Hans.json @@ -5,6 +5,7 @@ "user_privilege": "\u7528\u6237\u987b\u4e3a\u7ba1\u7406\u5458" }, "error": { + "faulty_credentials": "\u9519\u8bef\u7684\u7528\u6237\u51ed\u636e", "service_unavailable": "\u6ca1\u6709\u53ef\u7528\u7684\u670d\u52a1" }, "step": { diff --git a/homeassistant/components/unifi/const.py b/homeassistant/components/unifi/const.py index 8fe90823c49..fdeb15ee4ad 100644 --- a/homeassistant/components/unifi/const.py +++ b/homeassistant/components/unifi/const.py @@ -1,7 +1,7 @@ """Constants for the UniFi component.""" import logging -LOGGER = logging.getLogger('homeassistant.components.unifi') +LOGGER = logging.getLogger('.') DOMAIN = 'unifi' CONTROLLER_ID = '{host}-{site}' diff --git a/homeassistant/components/device_tracker/unifi.py b/homeassistant/components/unifi/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/unifi.py rename to homeassistant/components/unifi/device_tracker.py diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 425b9878f6d..e90da2dbcd8 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -1,19 +1,18 @@ """Support for devices connected to UniFi POE.""" import asyncio -import logging - from datetime import timedelta +import logging import async_timeout from homeassistant.components import unifi from homeassistant.components.switch import SwitchDevice -from homeassistant.components.unifi.const import ( - CONF_CONTROLLER, CONF_SITE_ID, CONTROLLER_ID, DOMAIN) from homeassistant.const import CONF_HOST from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from .const import CONF_CONTROLLER, CONF_SITE_ID, CONTROLLER_ID, DOMAIN + DEPENDENCIES = [DOMAIN] SCAN_INTERVAL = timedelta(seconds=15) diff --git a/homeassistant/components/unifi_direct/__init__.py b/homeassistant/components/unifi_direct/__init__.py new file mode 100644 index 00000000000..a73c6be43d4 --- /dev/null +++ b/homeassistant/components/unifi_direct/__init__.py @@ -0,0 +1 @@ +"""The unifi_direct component.""" diff --git a/homeassistant/components/device_tracker/unifi_direct.py b/homeassistant/components/unifi_direct/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/unifi_direct.py rename to homeassistant/components/unifi_direct/device_tracker.py diff --git a/homeassistant/components/universal/__init__.py b/homeassistant/components/universal/__init__.py new file mode 100644 index 00000000000..b21cd96ad94 --- /dev/null +++ b/homeassistant/components/universal/__init__.py @@ -0,0 +1 @@ +"""The universal component.""" diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/universal/media_player.py similarity index 100% rename from homeassistant/components/media_player/universal.py rename to homeassistant/components/universal/media_player.py diff --git a/homeassistant/components/upc_connect/__init__.py b/homeassistant/components/upc_connect/__init__.py new file mode 100644 index 00000000000..1793d6a3856 --- /dev/null +++ b/homeassistant/components/upc_connect/__init__.py @@ -0,0 +1 @@ +"""The upc_connect component.""" diff --git a/homeassistant/components/device_tracker/upc_connect.py b/homeassistant/components/upc_connect/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/upc_connect.py rename to homeassistant/components/upc_connect/device_tracker.py diff --git a/homeassistant/components/upcloud/binary_sensor.py b/homeassistant/components/upcloud/binary_sensor.py index 3fd54b349a2..a0c3c9f34c6 100644 --- a/homeassistant/components/upcloud/binary_sensor.py +++ b/homeassistant/components/upcloud/binary_sensor.py @@ -3,11 +3,11 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.components.upcloud import ( - UpCloudServerEntity, CONF_SERVERS, DATA_UPCLOUD) + PLATFORM_SCHEMA, BinarySensorDevice) +import homeassistant.helpers.config_validation as cv + +from . import CONF_SERVERS, DATA_UPCLOUD, UpCloudServerEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/upcloud/switch.py b/homeassistant/components/upcloud/switch.py index 0b44d787f6f..7e84adccf55 100644 --- a/homeassistant/components/upcloud/switch.py +++ b/homeassistant/components/upcloud/switch.py @@ -3,11 +3,11 @@ import logging import voluptuous as vol +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import STATE_OFF import homeassistant.helpers.config_validation as cv -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) -from homeassistant.components.upcloud import ( - UpCloudServerEntity, CONF_SERVERS, DATA_UPCLOUD) + +from . import CONF_SERVERS, DATA_UPCLOUD, UpCloudServerEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/upnp/.translations/bg.json b/homeassistant/components/upnp/.translations/bg.json new file mode 100644 index 00000000000..a19d2d44159 --- /dev/null +++ b/homeassistant/components/upnp/.translations/bg.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "UPnP/IGD \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d", + "no_devices_discovered": "\u041d\u044f\u043c\u0430 \u043e\u0442\u043a\u0440\u0438\u0442\u0438 UPnP/IGD", + "no_sensors_or_port_mapping": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u0439\u0442\u0435 \u0441\u0435\u043d\u0437\u043e\u0440\u0438\u0442\u0435 \u0438\u043b\u0438 \u043f\u0440\u0435\u043d\u0430\u0441\u043e\u0447\u0432\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u0440\u0442\u0430", + "single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430 UPnP/IGD." + }, + "step": { + "init": { + "title": "UPnP/IGD" + }, + "user": { + "data": { + "enable_port_mapping": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043f\u0440\u0435\u043d\u0430\u0441\u043e\u0447\u0432\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u0440\u0442\u0430 \u0437\u0430 Home Assistant", + "enable_sensors": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0442\u0440\u0430\u0444\u0438\u0447\u043d\u0438 \u0441\u0435\u043d\u0437\u043e\u0440\u0438", + "igd": "UPnP/IGD" + }, + "title": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u043e\u043f\u0446\u0438\u0438 \u0437\u0430 UPnP/IGD" + } + }, + "title": "UPnP/IGD" + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/.translations/hu.json b/homeassistant/components/upnp/.translations/hu.json index 7d3827e76da..29dab5e09da 100644 --- a/homeassistant/components/upnp/.translations/hu.json +++ b/homeassistant/components/upnp/.translations/hu.json @@ -13,7 +13,7 @@ }, "step": { "confirm": { - "description": "Be akarja \u00e1ll\u00edtani a UPnP/IGD-t?", + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a UPnP/IGD-t?", "title": "UPnP/IGD" }, "init": { diff --git a/homeassistant/components/upnp/.translations/ru.json b/homeassistant/components/upnp/.translations/ru.json index 6a7c43f9e46..668b9a377fc 100644 --- a/homeassistant/components/upnp/.translations/ru.json +++ b/homeassistant/components/upnp/.translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", "incomplete_device": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0435\u043f\u043e\u043b\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP", "no_devices_discovered": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e UPnP / IGD", "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP / IGD \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.", diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index a9fb84f733e..5f4abcb24c7 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -23,7 +23,7 @@ from .const import DOMAIN from .const import LOGGER as _LOGGER from .device import Device -REQUIREMENTS = ['async-upnp-client==0.14.5'] +REQUIREMENTS = ['async-upnp-client==0.14.7'] NOTIFICATION_ID = 'upnp_notification' NOTIFICATION_TITLE = 'UPnP/IGD Setup' diff --git a/homeassistant/components/upnp/const.py b/homeassistant/components/upnp/const.py index 04932488acd..9d2957660dc 100644 --- a/homeassistant/components/upnp/const.py +++ b/homeassistant/components/upnp/const.py @@ -1,12 +1,11 @@ """Constants for the IGD component.""" import logging - CONF_ENABLE_PORT_MAPPING = 'port_mapping' CONF_ENABLE_SENSORS = 'sensors' CONF_HASS = 'hass' CONF_LOCAL_IP = 'local_ip' CONF_PORTS = 'ports' DOMAIN = 'upnp' -LOGGER = logging.getLogger('homeassistant.components.upnp') +LOGGER = logging.getLogger('.') SIGNAL_REMOVE_SENSOR = 'upnp_remove_sensor' diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index 6bbf0a3dd53..5ebe2a78d0d 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -23,7 +23,10 @@ class Device: async def async_discover(cls, hass: HomeAssistantType): """Discovery UPNP/IGD devices.""" _LOGGER.debug('Discovering UPnP/IGD devices') - local_ip = hass.data[DOMAIN]['config'].get(CONF_LOCAL_IP) + local_ip = None + if DOMAIN in hass.data and \ + 'config' in hass.data[DOMAIN]: + local_ip = hass.data[DOMAIN]['config'].get(CONF_LOCAL_IP) if local_ip: local_ip = IPv4Address(local_ip) diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index 8eccf8834a1..708ef314ab4 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -8,11 +8,12 @@ from datetime import datetime import logging from homeassistant.core import callback +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from homeassistant.components.upnp.const import DOMAIN as DOMAIN_UPNP -from homeassistant.components.upnp.const import SIGNAL_REMOVE_SENSOR +from homeassistant.helpers.typing import HomeAssistantType +from .const import DOMAIN as DOMAIN_UPNP, SIGNAL_REMOVE_SENSOR _LOGGER = logging.getLogger(__name__) @@ -47,7 +48,9 @@ OUT = 'sent' KBYTE = 1024 -async def async_setup_platform(hass, config, async_add_entities, +async def async_setup_platform(hass: HomeAssistantType, + config, + async_add_entities, discovery_info=None): """Old way of setting up UPnP/IGD sensors.""" _LOGGER.debug('async_setup_platform: config: %s, discovery: %s', @@ -112,8 +115,11 @@ class UpnpSensor(Entity): 'identifiers': { (DOMAIN_UPNP, self.unique_id) }, + 'connections': { + (dr.CONNECTION_UPNP, self._device.udn) + }, 'name': self.name, - 'via_hub': (DOMAIN_UPNP, self._device.udn), + 'manufacturer': self._device.manufacturer, } diff --git a/homeassistant/components/ups/__init__.py b/homeassistant/components/ups/__init__.py new file mode 100644 index 00000000000..690d3102f9c --- /dev/null +++ b/homeassistant/components/ups/__init__.py @@ -0,0 +1 @@ +"""The ups component.""" diff --git a/homeassistant/components/sensor/ups.py b/homeassistant/components/ups/sensor.py similarity index 100% rename from homeassistant/components/sensor/ups.py rename to homeassistant/components/ups/sensor.py diff --git a/homeassistant/components/uptime/__init__.py b/homeassistant/components/uptime/__init__.py new file mode 100644 index 00000000000..99abc91cdf1 --- /dev/null +++ b/homeassistant/components/uptime/__init__.py @@ -0,0 +1 @@ +"""The uptime component.""" diff --git a/homeassistant/components/sensor/uptime.py b/homeassistant/components/uptime/sensor.py similarity index 100% rename from homeassistant/components/sensor/uptime.py rename to homeassistant/components/uptime/sensor.py diff --git a/homeassistant/components/uptimerobot/__init__.py b/homeassistant/components/uptimerobot/__init__.py new file mode 100644 index 00000000000..3dad1b00fff --- /dev/null +++ b/homeassistant/components/uptimerobot/__init__.py @@ -0,0 +1 @@ +"""The uptimerobot component.""" diff --git a/homeassistant/components/binary_sensor/uptimerobot.py b/homeassistant/components/uptimerobot/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/uptimerobot.py rename to homeassistant/components/uptimerobot/binary_sensor.py diff --git a/homeassistant/components/uscis/__init__.py b/homeassistant/components/uscis/__init__.py new file mode 100644 index 00000000000..f45e0ab9353 --- /dev/null +++ b/homeassistant/components/uscis/__init__.py @@ -0,0 +1 @@ +"""The uscis component.""" diff --git a/homeassistant/components/sensor/uscis.py b/homeassistant/components/uscis/sensor.py similarity index 100% rename from homeassistant/components/sensor/uscis.py rename to homeassistant/components/uscis/sensor.py diff --git a/homeassistant/components/usgs_earthquakes_feed/__init__.py b/homeassistant/components/usgs_earthquakes_feed/__init__.py new file mode 100644 index 00000000000..a05751e10b7 --- /dev/null +++ b/homeassistant/components/usgs_earthquakes_feed/__init__.py @@ -0,0 +1 @@ +"""The usgs_earthquakes_feed component.""" diff --git a/homeassistant/components/geo_location/usgs_earthquakes_feed.py b/homeassistant/components/usgs_earthquakes_feed/geo_location.py similarity index 100% rename from homeassistant/components/geo_location/usgs_earthquakes_feed.py rename to homeassistant/components/usgs_earthquakes_feed/geo_location.py diff --git a/homeassistant/components/usps/camera.py b/homeassistant/components/usps/camera.py index d4769102d14..5b5eaca4ce2 100644 --- a/homeassistant/components/usps/camera.py +++ b/homeassistant/components/usps/camera.py @@ -3,7 +3,8 @@ from datetime import timedelta import logging from homeassistant.components.camera import Camera -from homeassistant.components.usps import DATA_USPS + +from . import DATA_USPS _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/usps/sensor.py b/homeassistant/components/usps/sensor.py index 1603715861d..3e5fea5c4ee 100644 --- a/homeassistant/components/usps/sensor.py +++ b/homeassistant/components/usps/sensor.py @@ -2,12 +2,13 @@ from collections import defaultdict import logging -from homeassistant.components.usps import DATA_USPS from homeassistant.const import ATTR_ATTRIBUTION, ATTR_DATE from homeassistant.helpers.entity import Entity from homeassistant.util import slugify from homeassistant.util.dt import now +from . import DATA_USPS + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['usps'] diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index dd1514f5e43..2c151634a95 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -118,7 +118,8 @@ class UtilityMeterSensor(RestoreEntity): self._collecting = async_track_state_change( self.hass, self._sensor_source_id, self.async_reading) else: - self._collecting() + if self._collecting: + self._collecting() self._collecting = None _LOGGER.debug("%s - %s - source <%s>", self._name, diff --git a/homeassistant/components/uvc/__init__.py b/homeassistant/components/uvc/__init__.py new file mode 100644 index 00000000000..0d2f64eb0ae --- /dev/null +++ b/homeassistant/components/uvc/__init__.py @@ -0,0 +1 @@ +"""The uvc component.""" diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/uvc/camera.py similarity index 100% rename from homeassistant/components/camera/uvc.py rename to homeassistant/components/uvc/camera.py diff --git a/homeassistant/components/vasttrafik/__init__.py b/homeassistant/components/vasttrafik/__init__.py new file mode 100644 index 00000000000..25846435c7a --- /dev/null +++ b/homeassistant/components/vasttrafik/__init__.py @@ -0,0 +1 @@ +"""The vasttrafik component.""" diff --git a/homeassistant/components/sensor/vasttrafik.py b/homeassistant/components/vasttrafik/sensor.py similarity index 100% rename from homeassistant/components/sensor/vasttrafik.py rename to homeassistant/components/vasttrafik/sensor.py diff --git a/homeassistant/components/velbus/binary_sensor.py b/homeassistant/components/velbus/binary_sensor.py index 43ffa232b40..cbe1350bd4f 100644 --- a/homeassistant/components/velbus/binary_sensor.py +++ b/homeassistant/components/velbus/binary_sensor.py @@ -2,8 +2,8 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.velbus import ( - DOMAIN as VELBUS_DOMAIN, VelbusEntity) + +from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py index 1f45408a666..470524bb6f3 100644 --- a/homeassistant/components/velbus/climate.py +++ b/homeassistant/components/velbus/climate.py @@ -4,10 +4,9 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( STATE_HEAT, SUPPORT_TARGET_TEMPERATURE) -from homeassistant.components.velbus import ( - DOMAIN as VELBUS_DOMAIN, VelbusEntity) -from homeassistant.const import ( - TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT + +from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index 72a5a7af79b..b176ab76c4b 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -5,12 +5,12 @@ import time import voluptuous as vol from homeassistant.components.cover import ( - CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE, - SUPPORT_STOP) -from homeassistant.components.velbus import DOMAIN -from homeassistant.const import (CONF_COVERS, CONF_NAME) + PLATFORM_SCHEMA, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP, CoverDevice) +from homeassistant.const import CONF_COVERS, CONF_NAME import homeassistant.helpers.config_validation as cv +from . import DOMAIN + _LOGGER = logging.getLogger(__name__) COVER_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 10ad89ab847..ad78a795a30 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -1,8 +1,7 @@ """Support for Velbus sensors.""" import logging -from homeassistant.components.velbus import ( - DOMAIN as VELBUS_DOMAIN, VelbusEntity) +from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py index 7104bb0750d..b5ef89ca480 100644 --- a/homeassistant/components/velbus/switch.py +++ b/homeassistant/components/velbus/switch.py @@ -2,8 +2,8 @@ import logging from homeassistant.components.switch import SwitchDevice -from homeassistant.components.velbus import ( - DOMAIN as VELBUS_DOMAIN, VelbusEntity) + +from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index 6abaa42bb9d..1893909b706 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -2,9 +2,10 @@ from homeassistant.components.cover import ( ATTR_POSITION, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_STOP, CoverDevice) -from homeassistant.components.velux import DATA_VELUX from homeassistant.core import callback +from . import DATA_VELUX + DEPENDENCIES = ['velux'] diff --git a/homeassistant/components/velux/scene.py b/homeassistant/components/velux/scene.py index b0716dc2cb8..614d3f349a2 100644 --- a/homeassistant/components/velux/scene.py +++ b/homeassistant/components/velux/scene.py @@ -1,6 +1,7 @@ """Support for VELUX scenes.""" from homeassistant.components.scene import Scene -from homeassistant.components.velux import _LOGGER, DATA_VELUX + +from . import _LOGGER, DATA_VELUX DEPENDENCIES = ['velux'] diff --git a/homeassistant/components/venstar/__init__.py b/homeassistant/components/venstar/__init__.py new file mode 100644 index 00000000000..abc35a0d6bd --- /dev/null +++ b/homeassistant/components/venstar/__init__.py @@ -0,0 +1 @@ +"""The venstar component.""" diff --git a/homeassistant/components/climate/venstar.py b/homeassistant/components/venstar/climate.py similarity index 100% rename from homeassistant/components/climate/venstar.py rename to homeassistant/components/venstar/climate.py diff --git a/homeassistant/components/vera/binary_sensor.py b/homeassistant/components/vera/binary_sensor.py index 837422dbc7c..c81fa31938f 100644 --- a/homeassistant/components/vera/binary_sensor.py +++ b/homeassistant/components/vera/binary_sensor.py @@ -2,9 +2,9 @@ import logging from homeassistant.components.binary_sensor import ( - BinarySensorDevice, ENTITY_ID_FORMAT) -from homeassistant.components.vera import ( - VERA_CONTROLLER, VERA_DEVICES, VeraDevice) + ENTITY_ID_FORMAT, BinarySensorDevice) + +from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice DEPENDENCIES = ['vera'] diff --git a/homeassistant/components/vera/climate.py b/homeassistant/components/vera/climate.py index 9c812da9208..f8ff9c21b89 100644 --- a/homeassistant/components/vera/climate.py +++ b/homeassistant/components/vera/climate.py @@ -1,21 +1,15 @@ """Support for Vera thermostats.""" import logging -from homeassistant.util import convert -from homeassistant.components.climate import ClimateDevice, ENTITY_ID_FORMAT +from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice from homeassistant.components.climate.const import ( - STATE_AUTO, STATE_COOL, - STATE_HEAT, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE) + STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) from homeassistant.const import ( - STATE_ON, - STATE_OFF, - TEMP_FAHRENHEIT, - TEMP_CELSIUS, - ATTR_TEMPERATURE) + ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT) +from homeassistant.util import convert -from homeassistant.components.vera import ( - VERA_CONTROLLER, VERA_DEVICES, VeraDevice) +from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice DEPENDENCIES = ['vera'] diff --git a/homeassistant/components/vera/cover.py b/homeassistant/components/vera/cover.py index 1168cca8425..4cf2aac3bb4 100644 --- a/homeassistant/components/vera/cover.py +++ b/homeassistant/components/vera/cover.py @@ -1,10 +1,10 @@ """Support for Vera cover - curtains, rollershutters etc.""" import logging -from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT, \ - ATTR_POSITION -from homeassistant.components.vera import ( - VERA_CONTROLLER, VERA_DEVICES, VeraDevice) +from homeassistant.components.cover import ( + ATTR_POSITION, ENTITY_ID_FORMAT, CoverDevice) + +from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice DEPENDENCIES = ['vera'] diff --git a/homeassistant/components/vera/light.py b/homeassistant/components/vera/light.py index 93e54b915c7..e4e315bb52e 100644 --- a/homeassistant/components/vera/light.py +++ b/homeassistant/components/vera/light.py @@ -2,12 +2,12 @@ import logging from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_HS_COLOR, ENTITY_ID_FORMAT, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR, Light) -from homeassistant.components.vera import ( - VERA_CONTROLLER, VERA_DEVICES, VeraDevice) + ATTR_BRIGHTNESS, ATTR_HS_COLOR, ENTITY_ID_FORMAT, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, Light) import homeassistant.util.color as color_util +from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['vera'] diff --git a/homeassistant/components/vera/lock.py b/homeassistant/components/vera/lock.py index 61d5f0baf28..5ace07b87d7 100644 --- a/homeassistant/components/vera/lock.py +++ b/homeassistant/components/vera/lock.py @@ -2,9 +2,9 @@ import logging from homeassistant.components.lock import ENTITY_ID_FORMAT, LockDevice -from homeassistant.const import (STATE_LOCKED, STATE_UNLOCKED) -from homeassistant.components.vera import ( - VERA_CONTROLLER, VERA_DEVICES, VeraDevice) +from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED + +from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vera/scene.py b/homeassistant/components/vera/scene.py index 0960512f6d1..5000f9bc50f 100644 --- a/homeassistant/components/vera/scene.py +++ b/homeassistant/components/vera/scene.py @@ -1,10 +1,10 @@ """Support for Vera scenes.""" import logging -from homeassistant.util import slugify from homeassistant.components.scene import Scene -from homeassistant.components.vera import ( - VERA_CONTROLLER, VERA_SCENES, VERA_ID_FORMAT) +from homeassistant.util import slugify + +from . import VERA_CONTROLLER, VERA_ID_FORMAT, VERA_SCENES _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py index 8b68cc9190f..3c026046b3e 100644 --- a/homeassistant/components/vera/sensor.py +++ b/homeassistant/components/vera/sensor.py @@ -1,14 +1,13 @@ """Support for Vera sensors.""" -import logging from datetime import timedelta +import logging -from homeassistant.const import ( - TEMP_CELSIUS, TEMP_FAHRENHEIT) -from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import ENTITY_ID_FORMAT +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.helpers.entity import Entity from homeassistant.util import convert -from homeassistant.components.vera import ( - VERA_CONTROLLER, VERA_DEVICES, VeraDevice) + +from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vera/switch.py b/homeassistant/components/vera/switch.py index 2f4d18e34e1..f422e49bf42 100644 --- a/homeassistant/components/vera/switch.py +++ b/homeassistant/components/vera/switch.py @@ -1,10 +1,10 @@ """Support for Vera switches.""" import logging -from homeassistant.util import convert from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice -from homeassistant.components.vera import ( - VERA_CONTROLLER, VERA_DEVICES, VeraDevice) +from homeassistant.util import convert + +from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index adcdcd668cb..dc73be056db 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -3,11 +3,11 @@ import logging from time import sleep import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.verisure import CONF_ALARM, CONF_CODE_DIGITS -from homeassistant.components.verisure import HUB as hub from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED) +from . import CONF_ALARM, CONF_CODE_DIGITS, HUB as hub + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index 4c9e79724fe..1c1e0ee15c3 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -2,8 +2,8 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.verisure import CONF_DOOR_WINDOW -from homeassistant.components.verisure import HUB as hub + +from . import CONF_DOOR_WINDOW, HUB as hub _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index 7112e535a95..fad3d2bef04 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -5,8 +5,8 @@ import os from homeassistant.components.camera import Camera from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.components.verisure import HUB as hub -from homeassistant.components.verisure import CONF_SMARTCAM + +from . import CONF_SMARTCAM, HUB as hub _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index cdd230ea7f7..2010504c990 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -1,13 +1,11 @@ """Support for Verisure locks.""" import logging -from time import sleep -from time import time -from homeassistant.components.verisure import HUB as hub -from homeassistant.components.verisure import ( - CONF_LOCKS, CONF_DEFAULT_LOCK_CODE, CONF_CODE_DIGITS) +from time import sleep, time + from homeassistant.components.lock import LockDevice -from homeassistant.const import ( - ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED) +from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED + +from . import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, HUB as hub _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 13706d8408f..cf5205a6116 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -1,12 +1,11 @@ """Support for Verisure sensors.""" import logging -from homeassistant.components.verisure import HUB as hub -from homeassistant.components.verisure import ( - CONF_THERMOMETERS, CONF_HYDROMETERS, CONF_MOUSE) from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity +from . import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS, HUB as hub + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index a418eec6bc5..eb69d4c02a1 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -2,10 +2,10 @@ import logging from time import time -from homeassistant.components.verisure import HUB as hub -from homeassistant.components.verisure import CONF_SMARTPLUGS from homeassistant.components.switch import SwitchDevice +from . import CONF_SMARTPLUGS, HUB as hub + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/version/__init__.py b/homeassistant/components/version/__init__.py new file mode 100644 index 00000000000..eb257007f7c --- /dev/null +++ b/homeassistant/components/version/__init__.py @@ -0,0 +1 @@ +"""The version component.""" diff --git a/homeassistant/components/sensor/version.py b/homeassistant/components/version/sensor.py similarity index 100% rename from homeassistant/components/sensor/version.py rename to homeassistant/components/version/sensor.py diff --git a/homeassistant/components/vesync/__init__.py b/homeassistant/components/vesync/__init__.py new file mode 100644 index 00000000000..73b28a3d008 --- /dev/null +++ b/homeassistant/components/vesync/__init__.py @@ -0,0 +1 @@ +"""The vesync component.""" diff --git a/homeassistant/components/switch/vesync.py b/homeassistant/components/vesync/switch.py similarity index 100% rename from homeassistant/components/switch/vesync.py rename to homeassistant/components/vesync/switch.py diff --git a/homeassistant/components/viaggiatreno/__init__.py b/homeassistant/components/viaggiatreno/__init__.py new file mode 100644 index 00000000000..2eb6ed6cddc --- /dev/null +++ b/homeassistant/components/viaggiatreno/__init__.py @@ -0,0 +1 @@ +"""The viaggiatreno component.""" diff --git a/homeassistant/components/sensor/viaggiatreno.py b/homeassistant/components/viaggiatreno/sensor.py similarity index 100% rename from homeassistant/components/sensor/viaggiatreno.py rename to homeassistant/components/viaggiatreno/sensor.py diff --git a/homeassistant/components/vizio/__init__.py b/homeassistant/components/vizio/__init__.py new file mode 100644 index 00000000000..3575f2cf648 --- /dev/null +++ b/homeassistant/components/vizio/__init__.py @@ -0,0 +1 @@ +"""The vizio component.""" diff --git a/homeassistant/components/media_player/vizio.py b/homeassistant/components/vizio/media_player.py similarity index 100% rename from homeassistant/components/media_player/vizio.py rename to homeassistant/components/vizio/media_player.py diff --git a/homeassistant/components/vlc/__init__.py b/homeassistant/components/vlc/__init__.py new file mode 100644 index 00000000000..91a3eb35444 --- /dev/null +++ b/homeassistant/components/vlc/__init__.py @@ -0,0 +1 @@ +"""The vlc component.""" diff --git a/homeassistant/components/media_player/vlc.py b/homeassistant/components/vlc/media_player.py similarity index 100% rename from homeassistant/components/media_player/vlc.py rename to homeassistant/components/vlc/media_player.py diff --git a/homeassistant/components/voicerss/__init__.py b/homeassistant/components/voicerss/__init__.py new file mode 100644 index 00000000000..4894ca30bbd --- /dev/null +++ b/homeassistant/components/voicerss/__init__.py @@ -0,0 +1 @@ +"""Support for VoiceRSS integration.""" diff --git a/homeassistant/components/tts/voicerss.py b/homeassistant/components/voicerss/tts.py similarity index 98% rename from homeassistant/components/tts/voicerss.py rename to homeassistant/components/voicerss/tts.py index 22eba69e510..436f070e503 100644 --- a/homeassistant/components/tts/voicerss.py +++ b/homeassistant/components/voicerss/tts.py @@ -11,8 +11,8 @@ import aiohttp import async_timeout import voluptuous as vol +from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider from homeassistant.const import CONF_API_KEY -from homeassistant.components.tts import Provider, PLATFORM_SCHEMA, CONF_LANG from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/volkszaehler/__init__.py b/homeassistant/components/volkszaehler/__init__.py new file mode 100644 index 00000000000..a1a6533e4f9 --- /dev/null +++ b/homeassistant/components/volkszaehler/__init__.py @@ -0,0 +1 @@ +"""The volkszaehler component.""" diff --git a/homeassistant/components/sensor/volkszaehler.py b/homeassistant/components/volkszaehler/sensor.py similarity index 100% rename from homeassistant/components/sensor/volkszaehler.py rename to homeassistant/components/volkszaehler/sensor.py diff --git a/homeassistant/components/volumio/__init__.py b/homeassistant/components/volumio/__init__.py new file mode 100644 index 00000000000..823533336ba --- /dev/null +++ b/homeassistant/components/volumio/__init__.py @@ -0,0 +1 @@ +"""The volumio component.""" diff --git a/homeassistant/components/media_player/volumio.py b/homeassistant/components/volumio/media_player.py similarity index 100% rename from homeassistant/components/media_player/volumio.py rename to homeassistant/components/volumio/media_player.py diff --git a/homeassistant/components/volvooncall/binary_sensor.py b/homeassistant/components/volvooncall/binary_sensor.py index 7158e4df69b..4b4f4ff77cc 100644 --- a/homeassistant/components/volvooncall/binary_sensor.py +++ b/homeassistant/components/volvooncall/binary_sensor.py @@ -1,9 +1,10 @@ """Support for VOC.""" import logging -from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASSES) + DEVICE_CLASSES, BinarySensorDevice) + +from . import DATA_KEY, VolvoEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/volvooncall/device_tracker.py b/homeassistant/components/volvooncall/device_tracker.py index d4838c01505..6c7e0914f6e 100644 --- a/homeassistant/components/volvooncall/device_tracker.py +++ b/homeassistant/components/volvooncall/device_tracker.py @@ -1,10 +1,11 @@ """Support for tracking a Volvo.""" import logging -from homeassistant.util import slugify -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.components.device_tracker import SOURCE_TYPE_GPS -from homeassistant.components.volvooncall import DATA_KEY, SIGNAL_STATE_UPDATED +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util import slugify + +from . import DATA_KEY, SIGNAL_STATE_UPDATED _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/volvooncall/lock.py b/homeassistant/components/volvooncall/lock.py index f281ea64461..4a81f9017ff 100644 --- a/homeassistant/components/volvooncall/lock.py +++ b/homeassistant/components/volvooncall/lock.py @@ -2,7 +2,8 @@ import logging from homeassistant.components.lock import LockDevice -from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY + +from . import DATA_KEY, VolvoEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/volvooncall/sensor.py b/homeassistant/components/volvooncall/sensor.py index 07f16e580bd..8921bf057c1 100644 --- a/homeassistant/components/volvooncall/sensor.py +++ b/homeassistant/components/volvooncall/sensor.py @@ -1,7 +1,7 @@ """Support for Volvo On Call sensors.""" import logging -from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY +from . import DATA_KEY, VolvoEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/volvooncall/switch.py b/homeassistant/components/volvooncall/switch.py index d3985557cff..372909d1e0a 100644 --- a/homeassistant/components/volvooncall/switch.py +++ b/homeassistant/components/volvooncall/switch.py @@ -1,9 +1,10 @@ """Support for Volvo heater.""" import logging -from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY from homeassistant.helpers.entity import ToggleEntity +from . import DATA_KEY, VolvoEntity + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/vultr.py b/homeassistant/components/vultr/binary_sensor.py similarity index 89% rename from homeassistant/components/binary_sensor/vultr.py rename to homeassistant/components/vultr/binary_sensor.py index 149a6c28290..dccb648c9c2 100644 --- a/homeassistant/components/binary_sensor/vultr.py +++ b/homeassistant/components/vultr/binary_sensor.py @@ -8,15 +8,16 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_NAME from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.components.vultr import ( - CONF_SUBSCRIPTION, ATTR_AUTO_BACKUPS, ATTR_ALLOWED_BANDWIDTH, - ATTR_CREATED_AT, ATTR_SUBSCRIPTION_ID, ATTR_SUBSCRIPTION_NAME, - ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, ATTR_DISK, - ATTR_COST_PER_MONTH, ATTR_OS, ATTR_REGION, ATTR_VCPUS, DATA_VULTR) + PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv + +from . import ( + ATTR_ALLOWED_BANDWIDTH, ATTR_AUTO_BACKUPS, ATTR_COST_PER_MONTH, + ATTR_CREATED_AT, ATTR_DISK, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, + ATTR_MEMORY, ATTR_OS, ATTR_REGION, ATTR_SUBSCRIPTION_ID, + ATTR_SUBSCRIPTION_NAME, ATTR_VCPUS, CONF_SUBSCRIPTION, DATA_VULTR) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/vultr.py b/homeassistant/components/vultr/sensor.py similarity index 98% rename from homeassistant/components/sensor/vultr.py rename to homeassistant/components/vultr/sensor.py index a727e5bd2ec..7ca731cabac 100644 --- a/homeassistant/components/sensor/vultr.py +++ b/homeassistant/components/vultr/sensor.py @@ -9,13 +9,14 @@ import logging import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.components.vultr import ( - ATTR_CURRENT_BANDWIDTH_USED, ATTR_PENDING_CHARGES, CONF_SUBSCRIPTION, - DATA_VULTR) from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from . import ( + ATTR_CURRENT_BANDWIDTH_USED, ATTR_PENDING_CHARGES, CONF_SUBSCRIPTION, + DATA_VULTR) + _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'Vultr {} {}' diff --git a/homeassistant/components/switch/vultr.py b/homeassistant/components/vultr/switch.py similarity index 89% rename from homeassistant/components/switch/vultr.py rename to homeassistant/components/vultr/switch.py index 874d1979d7d..1f7d9ceaa28 100644 --- a/homeassistant/components/switch/vultr.py +++ b/homeassistant/components/vultr/switch.py @@ -8,14 +8,15 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import CONF_NAME -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) -from homeassistant.components.vultr import ( - CONF_SUBSCRIPTION, ATTR_AUTO_BACKUPS, ATTR_ALLOWED_BANDWIDTH, - ATTR_CREATED_AT, ATTR_SUBSCRIPTION_ID, ATTR_SUBSCRIPTION_NAME, - ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, ATTR_DISK, - ATTR_COST_PER_MONTH, ATTR_OS, ATTR_REGION, ATTR_VCPUS, DATA_VULTR) +import homeassistant.helpers.config_validation as cv + +from . import ( + ATTR_ALLOWED_BANDWIDTH, ATTR_AUTO_BACKUPS, ATTR_COST_PER_MONTH, + ATTR_CREATED_AT, ATTR_DISK, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, + ATTR_MEMORY, ATTR_OS, ATTR_REGION, ATTR_SUBSCRIPTION_ID, + ATTR_SUBSCRIPTION_NAME, ATTR_VCPUS, CONF_SUBSCRIPTION, DATA_VULTR) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/w800rf32/binary_sensor.py b/homeassistant/components/w800rf32/binary_sensor.py index 855a5f3ed0a..c9424834953 100644 --- a/homeassistant/components/w800rf32/binary_sensor.py +++ b/homeassistant/components/w800rf32/binary_sensor.py @@ -5,13 +5,13 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) -from homeassistant.components.w800rf32 import (W800RF32_DEVICE) -from homeassistant.const import (CONF_DEVICE_CLASS, CONF_NAME, CONF_DEVICES) +from homeassistant.const import CONF_DEVICE_CLASS, CONF_DEVICES, CONF_NAME from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import event as evt +from homeassistant.helpers import config_validation as cv, event as evt +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util import dt as dt_util -from homeassistant.helpers.dispatcher import (async_dispatcher_connect) + +from . import W800RF32_DEVICE _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/wake_on_lan.py b/homeassistant/components/wake_on_lan/switch.py similarity index 100% rename from homeassistant/components/switch/wake_on_lan.py rename to homeassistant/components/wake_on_lan/switch.py diff --git a/homeassistant/components/waqi/__init__.py b/homeassistant/components/waqi/__init__.py new file mode 100644 index 00000000000..5cacd9e5e1b --- /dev/null +++ b/homeassistant/components/waqi/__init__.py @@ -0,0 +1 @@ +"""The waqi component.""" diff --git a/homeassistant/components/sensor/waqi.py b/homeassistant/components/waqi/sensor.py similarity index 100% rename from homeassistant/components/sensor/waqi.py rename to homeassistant/components/waqi/sensor.py diff --git a/homeassistant/components/waterfurnace/sensor.py b/homeassistant/components/waterfurnace/sensor.py index 65632f51494..8a43a7dac77 100644 --- a/homeassistant/components/waterfurnace/sensor.py +++ b/homeassistant/components/waterfurnace/sensor.py @@ -6,14 +6,13 @@ https://home-assistant.io/components/sensor.waterfurnace/ """ from homeassistant.components.sensor import ENTITY_ID_FORMAT -from homeassistant.components.waterfurnace import ( - DOMAIN as WF_DOMAIN, UPDATE_TOPIC -) from homeassistant.const import TEMP_FAHRENHEIT from homeassistant.core import callback from homeassistant.helpers.entity import Entity from homeassistant.util import slugify +from . import DOMAIN as WF_DOMAIN, UPDATE_TOPIC + class WFSensorConfig: """Water Furnace Sensor configuration.""" diff --git a/homeassistant/components/waze_travel_time/__init__.py b/homeassistant/components/waze_travel_time/__init__.py new file mode 100644 index 00000000000..9674bd9850e --- /dev/null +++ b/homeassistant/components/waze_travel_time/__init__.py @@ -0,0 +1 @@ +"""The waze_travel_time component.""" diff --git a/homeassistant/components/sensor/waze_travel_time.py b/homeassistant/components/waze_travel_time/sensor.py similarity index 100% rename from homeassistant/components/sensor/waze_travel_time.py rename to homeassistant/components/waze_travel_time/sensor.py diff --git a/homeassistant/components/websocket_api/connection.py b/homeassistant/components/websocket_api/connection.py index d65ba4c54d8..c09e8c4c6e2 100644 --- a/homeassistant/components/websocket_api/connection.py +++ b/homeassistant/components/websocket_api/connection.py @@ -93,11 +93,11 @@ class ActiveConnection: err_message = 'Unauthorized' elif isinstance(err, vol.Invalid): code = const.ERR_INVALID_FORMAT - err_message = 'Invalid format' + err_message = vol.humanize.humanize_error(msg, err) else: - self.logger.exception('Error handling message: %s', msg) code = const.ERR_UNKNOWN_ERROR err_message = 'Unknown error' + self.logger.exception('Error handling message: %s', err_message) self.send_message( messages.error_message(msg['id'], code, err_message)) diff --git a/homeassistant/components/websocket_api/const.py b/homeassistant/components/websocket_api/const.py index 01145275b31..4c3e0d564dc 100644 --- a/homeassistant/components/websocket_api/const.py +++ b/homeassistant/components/websocket_api/const.py @@ -20,3 +20,7 @@ TYPE_RESULT = 'result' # Originally, this was just asyncio.CancelledError, but issue #9546 showed # that futures.CancelledErrors can also occur in some situations. CANCELLATION_ERRORS = (asyncio.CancelledError, futures.CancelledError) + +# Event types +SIGNAL_WEBSOCKET_CONNECTED = 'websocket_connected' +SIGNAL_WEBSOCKET_DISCONNECTED = 'websocket_disconnected' diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index 1ab2b09d7fa..85cb256df90 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -13,7 +13,9 @@ from homeassistant.core import callback from homeassistant.components.http import HomeAssistantView from homeassistant.helpers.json import JSONEncoder -from .const import MAX_PENDING_MSG, CANCELLATION_ERRORS, URL, ERR_UNKNOWN_ERROR +from .const import ( + MAX_PENDING_MSG, CANCELLATION_ERRORS, URL, ERR_UNKNOWN_ERROR, + SIGNAL_WEBSOCKET_CONNECTED, SIGNAL_WEBSOCKET_DISCONNECTED) from .auth import AuthPhase, auth_required_message from .error import Disconnect from .messages import error_message @@ -142,6 +144,8 @@ class WebSocketHandler: self._logger.debug("Received %s", msg) connection = await auth.async_handle(msg) + self.hass.helpers.dispatcher.async_dispatcher_send( + SIGNAL_WEBSOCKET_CONNECTED) # Command phase while not wsock.closed: @@ -192,4 +196,7 @@ class WebSocketHandler: else: self._logger.warning("Disconnected: %s", disconnect_warn) + self.hass.helpers.dispatcher.async_dispatcher_send( + SIGNAL_WEBSOCKET_DISCONNECTED) + return wsock diff --git a/homeassistant/components/websocket_api/sensor.py b/homeassistant/components/websocket_api/sensor.py new file mode 100644 index 00000000000..fd9108c0513 --- /dev/null +++ b/homeassistant/components/websocket_api/sensor.py @@ -0,0 +1,53 @@ +"""Entity to track connections to websocket API.""" + +from homeassistant.core import callback +from homeassistant.helpers.entity import Entity + +from .const import SIGNAL_WEBSOCKET_CONNECTED, SIGNAL_WEBSOCKET_DISCONNECTED + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Set up the API streams platform.""" + entity = APICount() + + # pylint: disable=protected-access + hass.helpers.dispatcher.async_dispatcher_connect( + SIGNAL_WEBSOCKET_CONNECTED, entity._increment) + hass.helpers.dispatcher.async_dispatcher_connect( + SIGNAL_WEBSOCKET_DISCONNECTED, entity._decrement) + + async_add_entities([entity]) + + +class APICount(Entity): + """Entity to represent how many people are connected to the stream API.""" + + def __init__(self): + """Initialize the API count.""" + self.count = 0 + + @property + def name(self): + """Return name of entity.""" + return "Connected clients" + + @property + def state(self): + """Return current API count.""" + return self.count + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return "clients" + + @callback + def _increment(self): + self.count += 1 + self.async_schedule_update_ha_state() + + @callback + def _decrement(self): + self.count -= 1 + self.async_schedule_update_ha_state() diff --git a/homeassistant/components/whois/__init__.py b/homeassistant/components/whois/__init__.py new file mode 100644 index 00000000000..3f3ffefde48 --- /dev/null +++ b/homeassistant/components/whois/__init__.py @@ -0,0 +1 @@ +"""The whois component.""" diff --git a/homeassistant/components/sensor/whois.py b/homeassistant/components/whois/sensor.py similarity index 100% rename from homeassistant/components/sensor/whois.py rename to homeassistant/components/whois/sensor.py diff --git a/homeassistant/components/wink/alarm_control_panel.py b/homeassistant/components/wink/alarm_control_panel.py index 594198ddd12..73ca9a3cac4 100644 --- a/homeassistant/components/wink/alarm_control_panel.py +++ b/homeassistant/components/wink/alarm_control_panel.py @@ -2,10 +2,11 @@ import logging import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED) +from . import DOMAIN, WinkDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['wink'] diff --git a/homeassistant/components/wink/binary_sensor.py b/homeassistant/components/wink/binary_sensor.py index 12e1b557a73..f3757d7bf39 100644 --- a/homeassistant/components/wink/binary_sensor.py +++ b/homeassistant/components/wink/binary_sensor.py @@ -2,7 +2,8 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.wink import DOMAIN, WinkDevice + +from . import DOMAIN, WinkDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wink/climate.py b/homeassistant/components/wink/climate.py index efd8eecf5af..f5e75c1fb8d 100644 --- a/homeassistant/components/wink/climate.py +++ b/homeassistant/components/wink/climate.py @@ -4,17 +4,17 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( ATTR_CURRENT_HUMIDITY, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - STATE_AUTO, STATE_COOL, STATE_ECO, - STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AUX_HEAT, - SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, + STATE_AUTO, STATE_COOL, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT, + SUPPORT_AUX_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW) -from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.const import ( ATTR_TEMPERATURE, PRECISION_TENTHS, STATE_OFF, STATE_ON, STATE_UNKNOWN, TEMP_CELSIUS) from homeassistant.helpers.temperature import display_temp as show_temp +from . import DOMAIN, WinkDevice + _LOGGER = logging.getLogger(__name__) ATTR_ECO_TARGET = 'eco_target' diff --git a/homeassistant/components/wink/cover.py b/homeassistant/components/wink/cover.py index 19ff792592f..f4c4841c2a2 100644 --- a/homeassistant/components/wink/cover.py +++ b/homeassistant/components/wink/cover.py @@ -1,6 +1,7 @@ """Support for Wink covers.""" -from homeassistant.components.cover import CoverDevice, ATTR_POSITION -from homeassistant.components.wink import WinkDevice, DOMAIN +from homeassistant.components.cover import ATTR_POSITION, CoverDevice + +from . import DOMAIN, WinkDevice DEPENDENCIES = ['wink'] diff --git a/homeassistant/components/wink/fan.py b/homeassistant/components/wink/fan.py index 5bc6e03c49b..52a27eb3c3d 100644 --- a/homeassistant/components/wink/fan.py +++ b/homeassistant/components/wink/fan.py @@ -2,9 +2,10 @@ import logging from homeassistant.components.fan import ( - SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SUPPORT_DIRECTION, - SUPPORT_SET_SPEED, FanEntity) -from homeassistant.components.wink import DOMAIN, WinkDevice + SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SUPPORT_DIRECTION, SUPPORT_SET_SPEED, + FanEntity) + +from . import DOMAIN, WinkDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wink/light.py b/homeassistant/components/wink/light.py index 14a983154f8..95747bcc1b2 100644 --- a/homeassistant/components/wink/light.py +++ b/homeassistant/components/wink/light.py @@ -1,11 +1,12 @@ """Support for Wink lights.""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR_TEMP, SUPPORT_COLOR, Light) -from homeassistant.components.wink import DOMAIN, WinkDevice + SUPPORT_COLOR, SUPPORT_COLOR_TEMP, Light) from homeassistant.util import color as color_util -from homeassistant.util.color import \ - color_temperature_mired_to_kelvin as mired_to_kelvin +from homeassistant.util.color import ( + color_temperature_mired_to_kelvin as mired_to_kelvin) + +from . import DOMAIN, WinkDevice DEPENDENCIES = ['wink'] diff --git a/homeassistant/components/wink/lock.py b/homeassistant/components/wink/lock.py index 0ef4f30dc5a..8e6fb9b2805 100644 --- a/homeassistant/components/wink/lock.py +++ b/homeassistant/components/wink/lock.py @@ -4,11 +4,12 @@ import logging import voluptuous as vol from homeassistant.components.lock import LockDevice -from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.const import ( ATTR_CODE, ATTR_ENTITY_ID, ATTR_NAME, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv +from . import DOMAIN, WinkDevice + DEPENDENCIES = ['wink'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wink/scene.py b/homeassistant/components/wink/scene.py index d05fe58a427..e77402c4d45 100644 --- a/homeassistant/components/wink/scene.py +++ b/homeassistant/components/wink/scene.py @@ -2,7 +2,8 @@ import logging from homeassistant.components.scene import Scene -from homeassistant.components.wink import DOMAIN, WinkDevice + +from . import DOMAIN, WinkDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index ab61769c94d..3dfd704d564 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -1,9 +1,10 @@ """Support for Wink sensors.""" import logging -from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.const import TEMP_CELSIUS +from . import DOMAIN, WinkDevice + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['wink'] diff --git a/homeassistant/components/wink/switch.py b/homeassistant/components/wink/switch.py index cd55026879a..6ee777dd1fc 100644 --- a/homeassistant/components/wink/switch.py +++ b/homeassistant/components/wink/switch.py @@ -1,9 +1,10 @@ """Support for Wink switches.""" import logging -from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.helpers.entity import ToggleEntity +from . import DOMAIN, WinkDevice + DEPENDENCIES = ['wink'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wink/water_heater.py b/homeassistant/components/wink/water_heater.py index 34cd86a50f4..343f4a76601 100644 --- a/homeassistant/components/wink/water_heater.py +++ b/homeassistant/components/wink/water_heater.py @@ -2,14 +2,12 @@ import logging from homeassistant.components.water_heater import ( - ATTR_TEMPERATURE, STATE_ECO, STATE_ELECTRIC, - STATE_PERFORMANCE, SUPPORT_AWAY_MODE, STATE_HEAT_PUMP, - STATE_GAS, STATE_HIGH_DEMAND, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - WaterHeaterDevice) -from homeassistant.components.wink import DOMAIN, WinkDevice -from homeassistant.const import ( - STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS) + ATTR_TEMPERATURE, STATE_ECO, STATE_ELECTRIC, STATE_GAS, STATE_HEAT_PUMP, + STATE_HIGH_DEMAND, STATE_PERFORMANCE, SUPPORT_AWAY_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, WaterHeaterDevice) +from homeassistant.const import STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS + +from . import DOMAIN, WinkDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wirelesstag/binary_sensor.py b/homeassistant/components/wirelesstag/binary_sensor.py index 6f2c24a7788..aefa5ed34a9 100644 --- a/homeassistant/components/wirelesstag/binary_sensor.py +++ b/homeassistant/components/wirelesstag/binary_sensor.py @@ -3,17 +3,16 @@ import logging import voluptuous as vol -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.components.wirelesstag import ( - DOMAIN as WIRELESSTAG_DOMAIN, - SIGNAL_BINARY_EVENT_UPDATE, - WirelessTagBaseSensor) -from homeassistant.const import ( - CONF_MONITORED_CONDITIONS, STATE_ON, STATE_OFF) + PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.const import CONF_MONITORED_CONDITIONS, STATE_OFF, STATE_ON +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ( + DOMAIN as WIRELESSTAG_DOMAIN, SIGNAL_BINARY_EVENT_UPDATE, + WirelessTagBaseSensor) DEPENDENCIES = ['wirelesstag'] diff --git a/homeassistant/components/wirelesstag/sensor.py b/homeassistant/components/wirelesstag/sensor.py index 3703e214d83..ca26e07b985 100644 --- a/homeassistant/components/wirelesstag/sensor.py +++ b/homeassistant/components/wirelesstag/sensor.py @@ -1,17 +1,16 @@ """Sensor support for Wireless Sensor Tags platform.""" import logging + import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_MONITORED_CONDITIONS from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.const import ( - CONF_MONITORED_CONDITIONS) -from homeassistant.components.wirelesstag import ( - DOMAIN as WIRELESSTAG_DOMAIN, - SIGNAL_TAG_UPDATE, - WirelessTagBaseSensor) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ( + DOMAIN as WIRELESSTAG_DOMAIN, SIGNAL_TAG_UPDATE, WirelessTagBaseSensor) DEPENDENCIES = ['wirelesstag'] diff --git a/homeassistant/components/wirelesstag/switch.py b/homeassistant/components/wirelesstag/switch.py index 913438e9d8c..4a2b64acda1 100644 --- a/homeassistant/components/wirelesstag/switch.py +++ b/homeassistant/components/wirelesstag/switch.py @@ -3,15 +3,12 @@ import logging import voluptuous as vol - -from homeassistant.components.wirelesstag import ( - DOMAIN as WIRELESSTAG_DOMAIN, - WirelessTagBaseSensor) from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice -from homeassistant.const import ( - CONF_MONITORED_CONDITIONS) +from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv +from . import DOMAIN as WIRELESSTAG_DOMAIN, WirelessTagBaseSensor + DEPENDENCIES = ['wirelesstag'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/workday/__init__.py b/homeassistant/components/workday/__init__.py new file mode 100644 index 00000000000..8daef2b3518 --- /dev/null +++ b/homeassistant/components/workday/__init__.py @@ -0,0 +1 @@ +"""Sensor to indicate whether the current day is a workday.""" diff --git a/homeassistant/components/binary_sensor/workday.py b/homeassistant/components/workday/binary_sensor.py similarity index 88% rename from homeassistant/components/binary_sensor/workday.py rename to homeassistant/components/workday/binary_sensor.py index 6b547927af4..b505e075018 100644 --- a/homeassistant/components/binary_sensor/workday.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Sensor to indicate whether the current day is a workday. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.workday/ -""" +"""Sensor to indicate whether the current day is a workday.""" import logging from datetime import datetime, timedelta @@ -14,7 +9,7 @@ from homeassistant.const import CONF_NAME, WEEKDAYS from homeassistant.components.binary_sensor import BinarySensorDevice import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['holidays==0.9.9'] +REQUIREMENTS = ['holidays==0.9.10'] _LOGGER = logging.getLogger(__name__) @@ -22,15 +17,22 @@ _LOGGER = logging.getLogger(__name__) # There seems to be no way to get the list out at runtime ALL_COUNTRIES = [ 'Argentina', 'AR', 'Australia', 'AU', 'Austria', 'AT', - 'Brazil', 'BR', 'Belarus', 'BY', 'Belgium', 'BE', + 'Brazil', 'BR', 'Belarus', 'BY', 'Belgium', 'BE', 'Bulgaria', 'BG', 'Canada', 'CA', 'Colombia', 'CO', 'Croatia', 'HR', 'Czech', 'CZ', - 'Denmark', 'DK', 'England', 'EuropeanCentralBank', 'ECB', 'TAR', - 'Finland', 'FI', 'France', 'FRA', 'Germany', 'DE', 'Hungary', 'HU', - 'Honduras', 'HUD', - 'India', 'IND', 'Ireland', 'Isle of Man', 'Italy', 'IT', 'Japan', 'JP', - 'Mexico', 'MX', 'Netherlands', 'NL', 'NewZealand', 'NZ', - 'Northern Ireland', 'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT', - 'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI', 'Slovakia', 'SK', + 'Denmark', 'DK', + 'England', 'EuropeanCentralBank', 'ECB', 'TAR', + 'Finland', 'FI', 'France', 'FRA', + 'Germany', 'DE', + 'Hungary', 'HU', 'Honduras', 'HUD', + 'India', 'IND', 'Ireland', 'IE', 'Isle of Man', 'Italy', 'IT', + 'Japan', 'JP', + 'Lithuania', 'LT', 'Luxembourg', 'LU', + 'Mexico', 'MX', + 'Netherlands', 'NL', 'NewZealand', 'NZ', 'Northern Ireland', + 'Norway', 'NO', + 'Polish', 'PL', 'Portugal', 'PT', 'PortugalExt', 'PTE', + 'Russia', 'RU', + 'Scotland', 'Slovenia', 'SI', 'Slovakia', 'SK', 'South Africa', 'ZA', 'Spain', 'ES', 'Sweden', 'SE', 'Switzerland', 'CH', 'Ukraine', 'UA', 'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales', ] diff --git a/homeassistant/components/worldclock/__init__.py b/homeassistant/components/worldclock/__init__.py new file mode 100644 index 00000000000..978eaac8968 --- /dev/null +++ b/homeassistant/components/worldclock/__init__.py @@ -0,0 +1 @@ +"""The worldclock component.""" diff --git a/homeassistant/components/sensor/worldclock.py b/homeassistant/components/worldclock/sensor.py similarity index 100% rename from homeassistant/components/sensor/worldclock.py rename to homeassistant/components/worldclock/sensor.py diff --git a/homeassistant/components/worldtidesinfo/__init__.py b/homeassistant/components/worldtidesinfo/__init__.py new file mode 100644 index 00000000000..313beb529e4 --- /dev/null +++ b/homeassistant/components/worldtidesinfo/__init__.py @@ -0,0 +1 @@ +"""The worldtidesinfo component.""" diff --git a/homeassistant/components/sensor/worldtidesinfo.py b/homeassistant/components/worldtidesinfo/sensor.py similarity index 100% rename from homeassistant/components/sensor/worldtidesinfo.py rename to homeassistant/components/worldtidesinfo/sensor.py diff --git a/homeassistant/components/worxlandroid/__init__.py b/homeassistant/components/worxlandroid/__init__.py new file mode 100644 index 00000000000..dae50f24dc9 --- /dev/null +++ b/homeassistant/components/worxlandroid/__init__.py @@ -0,0 +1 @@ +"""The worxlandroid component.""" diff --git a/homeassistant/components/sensor/worxlandroid.py b/homeassistant/components/worxlandroid/sensor.py similarity index 100% rename from homeassistant/components/sensor/worxlandroid.py rename to homeassistant/components/worxlandroid/sensor.py diff --git a/homeassistant/components/wsdot/__init__.py b/homeassistant/components/wsdot/__init__.py new file mode 100644 index 00000000000..9135f042c62 --- /dev/null +++ b/homeassistant/components/wsdot/__init__.py @@ -0,0 +1 @@ +"""The wsdot component.""" diff --git a/homeassistant/components/sensor/wsdot.py b/homeassistant/components/wsdot/sensor.py similarity index 100% rename from homeassistant/components/sensor/wsdot.py rename to homeassistant/components/wsdot/sensor.py diff --git a/homeassistant/components/wunderground/__init__.py b/homeassistant/components/wunderground/__init__.py new file mode 100644 index 00000000000..faed41fdbea --- /dev/null +++ b/homeassistant/components/wunderground/__init__.py @@ -0,0 +1 @@ +"""The wunderground component.""" diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/wunderground/sensor.py similarity index 100% rename from homeassistant/components/sensor/wunderground.py rename to homeassistant/components/wunderground/sensor.py diff --git a/homeassistant/components/x10/__init__.py b/homeassistant/components/x10/__init__.py new file mode 100644 index 00000000000..4c3b9bc5ce4 --- /dev/null +++ b/homeassistant/components/x10/__init__.py @@ -0,0 +1 @@ +"""The x10 component.""" diff --git a/homeassistant/components/light/x10.py b/homeassistant/components/x10/light.py similarity index 100% rename from homeassistant/components/light/x10.py rename to homeassistant/components/x10/light.py diff --git a/homeassistant/components/xbox_live/__init__.py b/homeassistant/components/xbox_live/__init__.py new file mode 100644 index 00000000000..cc9e8ac3518 --- /dev/null +++ b/homeassistant/components/xbox_live/__init__.py @@ -0,0 +1 @@ +"""The xbox_live component.""" diff --git a/homeassistant/components/sensor/xbox_live.py b/homeassistant/components/xbox_live/sensor.py similarity index 100% rename from homeassistant/components/sensor/xbox_live.py rename to homeassistant/components/xbox_live/sensor.py diff --git a/homeassistant/components/xeoma/__init__.py b/homeassistant/components/xeoma/__init__.py new file mode 100644 index 00000000000..e68d3a24035 --- /dev/null +++ b/homeassistant/components/xeoma/__init__.py @@ -0,0 +1 @@ +"""The xeoma component.""" diff --git a/homeassistant/components/camera/xeoma.py b/homeassistant/components/xeoma/camera.py similarity index 100% rename from homeassistant/components/camera/xeoma.py rename to homeassistant/components/xeoma/camera.py diff --git a/homeassistant/components/xfinity/__init__.py b/homeassistant/components/xfinity/__init__.py new file mode 100644 index 00000000000..22e37eccde9 --- /dev/null +++ b/homeassistant/components/xfinity/__init__.py @@ -0,0 +1 @@ +"""The xfinity component.""" diff --git a/homeassistant/components/device_tracker/xfinity.py b/homeassistant/components/xfinity/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/xfinity.py rename to homeassistant/components/xfinity/device_tracker.py diff --git a/homeassistant/components/xiaomi/__init__.py b/homeassistant/components/xiaomi/__init__.py new file mode 100644 index 00000000000..6fc7294864d --- /dev/null +++ b/homeassistant/components/xiaomi/__init__.py @@ -0,0 +1 @@ +"""The xiaomi component.""" diff --git a/homeassistant/components/camera/xiaomi.py b/homeassistant/components/xiaomi/camera.py similarity index 94% rename from homeassistant/components/camera/xiaomi.py rename to homeassistant/components/xiaomi/camera.py index 93e9dd4a07c..b0acf50ec8c 100644 --- a/homeassistant/components/camera/xiaomi.py +++ b/homeassistant/components/xiaomi/camera.py @@ -23,6 +23,7 @@ DEFAULT_BRAND = 'Xiaomi Home Camera' DEFAULT_PATH = '/media/mmcblk0p1/record' DEFAULT_PORT = 21 DEFAULT_USERNAME = 'root' +DEFAULT_ARGUMENTS = '-pred 1' CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' CONF_MODEL = 'model' @@ -39,7 +40,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string, vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string + vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string }) @@ -138,7 +139,7 @@ class XiaomiCamera(Camera): async def async_camera_image(self): """Return a still image response from the camera.""" - from haffmpeg import ImageFrame, IMAGE_JPEG + from haffmpeg.tools import ImageFrame, IMAGE_JPEG url = await self.hass.async_add_job(self.get_latest_video_url) if url != self._last_url: @@ -152,15 +153,16 @@ class XiaomiCamera(Camera): async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" - from haffmpeg import CameraMjpeg + from haffmpeg.camera import CameraMjpeg stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop) await stream.open_camera( self._last_url, extra_cmd=self._extra_arguments) try: + stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream, + self.hass, request, stream_reader, self._manager.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/device_tracker/xiaomi.py b/homeassistant/components/xiaomi/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/xiaomi.py rename to homeassistant/components/xiaomi/device_tracker.py diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index 66fc1fa13dd..9b113170f8a 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -62,7 +62,6 @@ SERVICE_SCHEMA_REMOVE_DEVICE = vol.Schema({ GATEWAY_CONFIG = vol.Schema({ - vol.Optional(CONF_MAC, default=None): vol.Any(GW_MAC, None), vol.Optional(CONF_KEY): vol.All(cv.string, vol.Length(min=16, max=16)), vol.Optional(CONF_HOST): cv.string, @@ -70,6 +69,14 @@ GATEWAY_CONFIG = vol.Schema({ vol.Optional(CONF_DISABLE, default=False): cv.boolean, }) +GATEWAY_CONFIG_MAC_OPTIONAL = GATEWAY_CONFIG.extend({ + vol.Optional(CONF_MAC): GW_MAC, +}) + +GATEWAY_CONFIG_MAC_REQUIRED = GATEWAY_CONFIG.extend({ + vol.Required(CONF_MAC): GW_MAC, +}) + def _fix_conf_defaults(config): """Update some configuration defaults.""" @@ -89,7 +96,10 @@ def _fix_conf_defaults(config): CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Optional(CONF_GATEWAYS, default={}): - vol.All(cv.ensure_list, [GATEWAY_CONFIG], [_fix_conf_defaults]), + vol.All(cv.ensure_list, vol.Any( + vol.All([GATEWAY_CONFIG_MAC_OPTIONAL], vol.Length(max=1)), + vol.All([GATEWAY_CONFIG_MAC_REQUIRED], vol.Length(min=2)) + ), [_fix_conf_defaults]), vol.Optional(CONF_INTERFACE, default='any'): cv.string, vol.Optional(CONF_DISCOVERY_RETRY, default=3): cv.positive_int }) diff --git a/homeassistant/components/xiaomi_aqara/binary_sensor.py b/homeassistant/components/xiaomi_aqara/binary_sensor.py index afffb71bae5..56818c51b81 100644 --- a/homeassistant/components/xiaomi_aqara/binary_sensor.py +++ b/homeassistant/components/xiaomi_aqara/binary_sensor.py @@ -2,11 +2,11 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY, - XiaomiDevice) from homeassistant.core import callback from homeassistant.helpers.event import async_call_later +from . import PY_XIAOMI_GATEWAY, XiaomiDevice + _LOGGER = logging.getLogger(__name__) NO_CLOSE = 'no_close' @@ -476,18 +476,24 @@ class XiaomiCube(XiaomiBinarySensor): self._last_action = data[self._data_key] if 'rotate' in data: + action_value = float(data['rotate'] + if isinstance(data['rotate'], int) + else data['rotate'].replace(",", ".")) self._hass.bus.fire('xiaomi_aqara.cube_action', { 'entity_id': self.entity_id, 'action_type': 'rotate', - 'action_value': float(data['rotate'].replace(",", ".")) + 'action_value': action_value }) self._last_action = 'rotate' if 'rotate_degree' in data: + action_value = float(data['rotate_degree'] + if isinstance(data['rotate_degree'], int) + else data['rotate_degree'].replace(",", ".")) self._hass.bus.fire('xiaomi_aqara.cube_action', { 'entity_id': self.entity_id, 'action_type': 'rotate', - 'action_value': float(data['rotate_degree'].replace(",", ".")) + 'action_value': action_value }) self._last_action = 'rotate' diff --git a/homeassistant/components/xiaomi_aqara/cover.py b/homeassistant/components/xiaomi_aqara/cover.py index ead2c0e9219..cd9190dca35 100644 --- a/homeassistant/components/xiaomi_aqara/cover.py +++ b/homeassistant/components/xiaomi_aqara/cover.py @@ -1,9 +1,9 @@ """Support for Xiaomi curtain.""" import logging -from homeassistant.components.cover import CoverDevice, ATTR_POSITION -from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY, - XiaomiDevice) +from homeassistant.components.cover import ATTR_POSITION, CoverDevice + +from . import PY_XIAOMI_GATEWAY, XiaomiDevice _LOGGER = logging.getLogger(__name__) @@ -17,8 +17,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for device in gateway.devices['cover']: model = device['model'] if model == 'curtain': + if 'proto' not in device or int(device['proto'][0:1]) == 1: + data_key = 'status' + else: + data_key = 'curtain_status' devices.append(XiaomiGenericCover(device, "Curtain", - 'status', gateway)) + data_key, gateway)) add_entities(devices) diff --git a/homeassistant/components/xiaomi_aqara/light.py b/homeassistant/components/xiaomi_aqara/light.py index 30433ccea3d..d0fbdea8fad 100644 --- a/homeassistant/components/xiaomi_aqara/light.py +++ b/homeassistant/components/xiaomi_aqara/light.py @@ -1,14 +1,14 @@ """Support for Xiaomi Gateway Light.""" +import binascii import logging import struct -import binascii -from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY, - XiaomiDevice) -from homeassistant.components.light import (ATTR_BRIGHTNESS, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, Light) + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, Light) import homeassistant.util.color as color_util +from . import PY_XIAOMI_GATEWAY, XiaomiDevice + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_aqara/lock.py b/homeassistant/components/xiaomi_aqara/lock.py index f19492664b1..56d68f38be9 100644 --- a/homeassistant/components/xiaomi_aqara/lock.py +++ b/homeassistant/components/xiaomi_aqara/lock.py @@ -1,11 +1,12 @@ """Support for Xiaomi Aqara locks.""" import logging -from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY, - XiaomiDevice) + from homeassistant.components.lock import LockDevice -from homeassistant.const import (STATE_LOCKED, STATE_UNLOCKED) -from homeassistant.helpers.event import async_call_later +from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import callback +from homeassistant.helpers.event import async_call_later + +from . import PY_XIAOMI_GATEWAY, XiaomiDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_aqara/sensor.py b/homeassistant/components/xiaomi_aqara/sensor.py index 133814e216e..c5cc00f14ba 100644 --- a/homeassistant/components/xiaomi_aqara/sensor.py +++ b/homeassistant/components/xiaomi_aqara/sensor.py @@ -1,11 +1,11 @@ """Support for Xiaomi Aqara sensors.""" import logging -from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY, - XiaomiDevice) from homeassistant.const import ( - DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, - TEMP_CELSIUS, DEVICE_CLASS_PRESSURE) + DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS) + +from . import PY_XIAOMI_GATEWAY, XiaomiDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_aqara/switch.py b/homeassistant/components/xiaomi_aqara/switch.py index c3cde8ede6d..211f9d127c4 100644 --- a/homeassistant/components/xiaomi_aqara/switch.py +++ b/homeassistant/components/xiaomi_aqara/switch.py @@ -2,8 +2,8 @@ import logging from homeassistant.components.switch import SwitchDevice -from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY, - XiaomiDevice) + +from . import PY_XIAOMI_GATEWAY, XiaomiDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_miio/device_tracker.py b/homeassistant/components/xiaomi_miio/device_tracker.py index 1aec3647d61..e7ea9fbbb40 100644 --- a/homeassistant/components/xiaomi_miio/device_tracker.py +++ b/homeassistant/components/xiaomi_miio/device_tracker.py @@ -8,7 +8,7 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_TOKEN import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-miio==0.4.4', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index c4cfa6bbe6b..51d4780160d 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -13,7 +13,7 @@ from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_TOKEN, from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-miio==0.4.4', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) @@ -227,7 +227,7 @@ AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER = { AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_CA = { **AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_COMMON, - ATTR_MOTOR_SPEED: 'speed', + ATTR_MOTOR_SPEED: 'motor_speed', ATTR_DEPTH: 'depth', ATTR_DRY: 'dry', } @@ -305,6 +305,7 @@ FEATURE_FLAGS_AIRPURIFIER_V3 = (FEATURE_SET_BUZZER | FEATURE_FLAGS_AIRHUMIDIFIER = (FEATURE_SET_BUZZER | FEATURE_SET_CHILD_LOCK | + FEATURE_SET_LED | FEATURE_SET_LED_BRIGHTNESS | FEATURE_SET_TARGET_HUMIDITY) @@ -348,7 +349,7 @@ SERVICE_SCHEMA_LED_BRIGHTNESS = AIRPURIFIER_SERVICE_SCHEMA.extend({ SERVICE_SCHEMA_FAVORITE_LEVEL = AIRPURIFIER_SERVICE_SCHEMA.extend({ vol.Required(ATTR_LEVEL): - vol.All(vol.Coerce(int), vol.Clamp(min=0, max=16)) + vol.All(vol.Coerce(int), vol.Clamp(min=0, max=17)) }) SERVICE_SCHEMA_VOLUME = AIRPURIFIER_SERVICE_SCHEMA.extend({ diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index 3ae6bfe6cd7..ec07a557342 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -9,14 +9,15 @@ from math import ceil import voluptuous as vol from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_ENTITY_ID, DOMAIN, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, Light) + ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_COLOR_TEMP, ATTR_ENTITY_ID, DOMAIN, + PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, + Light) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.util import color, dt -REQUIREMENTS = ['python-miio==0.4.4', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) @@ -81,7 +82,7 @@ XIAOMI_MIIO_SERVICE_SCHEMA = vol.Schema({ SERVICE_SCHEMA_SET_SCENE = XIAOMI_MIIO_SERVICE_SCHEMA.extend({ vol.Required(ATTR_SCENE): - vol.All(vol.Coerce(int), vol.Clamp(min=1, max=4)) + vol.All(vol.Coerce(int), vol.Clamp(min=1, max=6)) }) SERVICE_SCHEMA_SET_DELAYED_TURN_OFF = XIAOMI_MIIO_SERVICE_SCHEMA.extend({ @@ -774,7 +775,99 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): @property def supported_features(self): """Return the supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP + return SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP + + async def async_turn_on(self, **kwargs): + """Turn the light on.""" + if ATTR_COLOR_TEMP in kwargs: + color_temp = kwargs[ATTR_COLOR_TEMP] + percent_color_temp = self.translate( + color_temp, self.max_mireds, + self.min_mireds, CCT_MIN, CCT_MAX) + + if ATTR_BRIGHTNESS in kwargs: + brightness = kwargs[ATTR_BRIGHTNESS] + percent_brightness = ceil(100 * brightness / 255.0) + + if ATTR_HS_COLOR in kwargs: + hs_color = kwargs[ATTR_HS_COLOR] + rgb = color.color_hs_to_RGB(*hs_color) + + if ATTR_BRIGHTNESS in kwargs and ATTR_HS_COLOR in kwargs: + _LOGGER.debug( + "Setting brightness and color: " + "%s %s%%, %s", + brightness, percent_brightness, rgb) + + result = await self._try_command( + "Setting brightness and color failed: " + "%s bri, %s color", + self._light.set_brightness_and_rgb, + percent_brightness, rgb) + + if result: + self._hs_color = hs_color + self._brightness = brightness + + elif ATTR_BRIGHTNESS in kwargs and ATTR_COLOR_TEMP in kwargs: + _LOGGER.debug( + "Setting brightness and color temperature: " + "%s %s%%, %s mireds, %s%% cct", + brightness, percent_brightness, + color_temp, percent_color_temp) + + result = await self._try_command( + "Setting brightness and color temperature failed: " + "%s bri, %s cct", + self._light.set_brightness_and_color_temperature, + percent_brightness, percent_color_temp) + + if result: + self._color_temp = color_temp + self._brightness = brightness + + elif ATTR_HS_COLOR in kwargs: + _LOGGER.debug( + "Setting color: %s", rgb) + + result = await self._try_command( + "Setting color failed: %s", + self._light.set_rgb, rgb) + + if result: + self._hs_color = hs_color + + elif ATTR_COLOR_TEMP in kwargs: + _LOGGER.debug( + "Setting color temperature: " + "%s mireds, %s%% cct", + color_temp, percent_color_temp) + + result = await self._try_command( + "Setting color temperature failed: %s cct", + self._light.set_color_temperature, percent_color_temp) + + if result: + self._color_temp = color_temp + + elif ATTR_BRIGHTNESS in kwargs: + brightness = kwargs[ATTR_BRIGHTNESS] + percent_brightness = ceil(100 * brightness / 255.0) + + _LOGGER.debug( + "Setting brightness: %s %s%%", + brightness, percent_brightness) + + result = await self._try_command( + "Setting brightness failed: %s", + self._light.set_brightness, percent_brightness) + + if result: + self._brightness = brightness + + else: + await self._try_command( + "Turning the light on failed.", self._light.on) async def async_update(self): """Fetch state from the device.""" diff --git a/homeassistant/components/xiaomi_miio/remote.py b/homeassistant/components/xiaomi_miio/remote.py index 1c4ae85a0d8..450279c1825 100644 --- a/homeassistant/components/xiaomi_miio/remote.py +++ b/homeassistant/components/xiaomi_miio/remote.py @@ -17,7 +17,7 @@ from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.util.dt import utcnow -REQUIREMENTS = ['python-miio==0.4.4', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index bd5f8642e54..41d3ce65b13 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -9,7 +9,7 @@ from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['python-miio==0.4.4', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index eb7f09f95e6..d1acce02e47 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -12,7 +12,7 @@ from homeassistant.const import ( from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-miio==0.4.4', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) @@ -36,6 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 'chuangmi.plug.v2', 'chuangmi.plug.v3', 'chuangmi.plug.hmi205', + 'lumi.acpartner.v3', ]), }) @@ -150,6 +151,13 @@ async def async_setup_platform(hass, config, async_add_entities, device = XiaomiPlugGenericSwitch(name, plug, model, unique_id) devices.append(device) hass.data[DATA_KEY][host] = device + elif model in ['lumi.acpartner.v3']: + from miio import AirConditioningCompanionV3 + plug = AirConditioningCompanionV3(host, token) + device = XiaomiAirConditioningCompanionSwitch(name, plug, model, + unique_id) + devices.append(device) + hass.data[DATA_KEY][host] = device else: _LOGGER.error( 'Unsupported device found! Please create an issue at ' @@ -294,9 +302,7 @@ class XiaomiPlugGenericSwitch(SwitchDevice): self._available = True self._state = state.is_on - self._state_attrs.update({ - ATTR_TEMPERATURE: state.temperature - }) + self._state_attrs[ATTR_TEMPERATURE] = state.temperature except DeviceException as ex: self._available = False @@ -342,9 +348,7 @@ class XiaomiPowerStripSwitch(XiaomiPlugGenericSwitch): else: self._device_features = FEATURE_FLAGS_POWER_STRIP_V1 - self._state_attrs.update({ - ATTR_LOAD_POWER: None, - }) + self._state_attrs[ATTR_LOAD_POWER] = None if self._device_features & FEATURE_SET_POWER_MODE == 1: self._state_attrs[ATTR_POWER_MODE] = None @@ -418,13 +422,9 @@ class ChuangMiPlugSwitch(XiaomiPlugGenericSwitch): if self._model == MODEL_PLUG_V3: self._device_features = FEATURE_FLAGS_PLUG_V3 - self._state_attrs.update({ - ATTR_WIFI_LED: None, - }) + self._state_attrs[ATTR_WIFI_LED] = None if self._channel_usb is False: - self._state_attrs.update({ - ATTR_LOAD_POWER: None, - }) + self._state_attrs[ATTR_LOAD_POWER] = None async def async_turn_on(self, **kwargs): """Turn a channel on.""" @@ -471,9 +471,7 @@ class ChuangMiPlugSwitch(XiaomiPlugGenericSwitch): else: self._state = state.is_on - self._state_attrs.update({ - ATTR_TEMPERATURE: state.temperature - }) + self._state_attrs[ATTR_TEMPERATURE] = state.temperature if state.wifi_led: self._state_attrs[ATTR_WIFI_LED] = state.wifi_led @@ -484,3 +482,55 @@ class ChuangMiPlugSwitch(XiaomiPlugGenericSwitch): except DeviceException as ex: self._available = False _LOGGER.error("Got exception while fetching the state: %s", ex) + + +class XiaomiAirConditioningCompanionSwitch(XiaomiPlugGenericSwitch): + """Representation of a Xiaomi AirConditioning Companion.""" + + def __init__(self, name, plug, model, unique_id): + """Initialize the acpartner switch.""" + super().__init__(name, plug, model, unique_id) + + self._state_attrs.update({ + ATTR_TEMPERATURE: None, + ATTR_LOAD_POWER: None, + }) + + async def async_turn_on(self, **kwargs): + """Turn the socket on.""" + result = await self._try_command( + "Turning the socket on failed.", self._plug.socket_on) + + if result: + self._state = True + self._skip_update = True + + async def async_turn_off(self, **kwargs): + """Turn the socket off.""" + result = await self._try_command( + "Turning the socket off failed.", self._plug.socket_off) + + if result: + self._state = False + self._skip_update = True + + async def async_update(self): + """Fetch state from the device.""" + from miio import DeviceException + + # On state change the device doesn't provide the new state immediately. + if self._skip_update: + self._skip_update = False + return + + try: + state = await self.hass.async_add_executor_job(self._plug.status) + _LOGGER.debug("Got new state: %s", state) + + self._available = True + self._state = state.power_socket == 'on' + self._state_attrs[ATTR_LOAD_POWER] = state.load_power + + except DeviceException as ex: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index 36196158600..c7f69a40330 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -16,7 +16,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-miio==0.4.4', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_tv/__init__.py b/homeassistant/components/xiaomi_tv/__init__.py new file mode 100644 index 00000000000..4dd89753b06 --- /dev/null +++ b/homeassistant/components/xiaomi_tv/__init__.py @@ -0,0 +1 @@ +"""The xiaomi_tv component.""" diff --git a/homeassistant/components/media_player/xiaomi_tv.py b/homeassistant/components/xiaomi_tv/media_player.py similarity index 100% rename from homeassistant/components/media_player/xiaomi_tv.py rename to homeassistant/components/xiaomi_tv/media_player.py diff --git a/homeassistant/components/xmpp/__init__.py b/homeassistant/components/xmpp/__init__.py new file mode 100644 index 00000000000..40736a4fd36 --- /dev/null +++ b/homeassistant/components/xmpp/__init__.py @@ -0,0 +1 @@ +"""The xmpp component.""" diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/xmpp/notify.py similarity index 99% rename from homeassistant/components/notify/xmpp.py rename to homeassistant/components/xmpp/notify.py index 462dd007d53..5a14046bd41 100644 --- a/homeassistant/components/notify/xmpp.py +++ b/homeassistant/components/xmpp/notify.py @@ -14,13 +14,14 @@ import string import requests import voluptuous as vol -from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import ( CONF_PASSWORD, CONF_RECIPIENT, CONF_RESOURCE, CONF_ROOM, CONF_SENDER) import homeassistant.helpers.config_validation as cv import homeassistant.helpers.template as template_helper +from homeassistant.components.notify import ( + ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) + REQUIREMENTS = ['slixmpp==1.4.2'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xs1/climate.py b/homeassistant/components/xs1/climate.py index e579761474b..080b87c1346 100644 --- a/homeassistant/components/xs1/climate.py +++ b/homeassistant/components/xs1/climate.py @@ -3,12 +3,11 @@ from functools import partial import logging from homeassistant.components.climate import ClimateDevice -from homeassistant.components.climate.const import ( - SUPPORT_TARGET_TEMPERATURE) -from homeassistant.components.xs1 import ( - ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity) +from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE from homeassistant.const import ATTR_TEMPERATURE +from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity + DEPENDENCIES = ['xs1'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xs1/sensor.py b/homeassistant/components/xs1/sensor.py index 9de91a6b012..f5fdcf1fb34 100644 --- a/homeassistant/components/xs1/sensor.py +++ b/homeassistant/components/xs1/sensor.py @@ -1,10 +1,10 @@ """Support for XS1 sensors.""" import logging -from homeassistant.components.xs1 import ( - ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity) from homeassistant.helpers.entity import Entity +from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity + DEPENDENCIES = ['xs1'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xs1/switch.py b/homeassistant/components/xs1/switch.py index 35568587b19..d8b344fc716 100644 --- a/homeassistant/components/xs1/switch.py +++ b/homeassistant/components/xs1/switch.py @@ -1,10 +1,10 @@ """Support for XS1 switches.""" import logging -from homeassistant.components.xs1 import ( - ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity) from homeassistant.helpers.entity import ToggleEntity +from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['xs1'] diff --git a/homeassistant/components/yale_smart_alarm/__init__.py b/homeassistant/components/yale_smart_alarm/__init__.py new file mode 100644 index 00000000000..2ce2fb13495 --- /dev/null +++ b/homeassistant/components/yale_smart_alarm/__init__.py @@ -0,0 +1 @@ +"""The yale_smart_alarm component.""" diff --git a/homeassistant/components/alarm_control_panel/yale_smart_alarm.py b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/yale_smart_alarm.py rename to homeassistant/components/yale_smart_alarm/alarm_control_panel.py diff --git a/homeassistant/components/yamaha/__init__.py b/homeassistant/components/yamaha/__init__.py new file mode 100644 index 00000000000..92a34517ec6 --- /dev/null +++ b/homeassistant/components/yamaha/__init__.py @@ -0,0 +1 @@ +"""The yamaha component.""" diff --git a/homeassistant/components/media_player/yamaha.py b/homeassistant/components/yamaha/media_player.py similarity index 100% rename from homeassistant/components/media_player/yamaha.py rename to homeassistant/components/yamaha/media_player.py diff --git a/homeassistant/components/yamaha_musiccast/__init__.py b/homeassistant/components/yamaha_musiccast/__init__.py new file mode 100644 index 00000000000..bf270b508d9 --- /dev/null +++ b/homeassistant/components/yamaha_musiccast/__init__.py @@ -0,0 +1 @@ +"""The yamaha_musiccast component.""" diff --git a/homeassistant/components/media_player/yamaha_musiccast.py b/homeassistant/components/yamaha_musiccast/media_player.py similarity index 100% rename from homeassistant/components/media_player/yamaha_musiccast.py rename to homeassistant/components/yamaha_musiccast/media_player.py diff --git a/homeassistant/components/yandextts/__init__.py b/homeassistant/components/yandextts/__init__.py new file mode 100644 index 00000000000..86ac9b58f73 --- /dev/null +++ b/homeassistant/components/yandextts/__init__.py @@ -0,0 +1 @@ +"""Support for the yandex speechkit tts integration.""" diff --git a/homeassistant/components/tts/yandextts.py b/homeassistant/components/yandextts/tts.py similarity index 98% rename from homeassistant/components/tts/yandextts.py rename to homeassistant/components/yandextts/tts.py index 112e78413ed..e60b890e84f 100644 --- a/homeassistant/components/tts/yandextts.py +++ b/homeassistant/components/yandextts/tts.py @@ -11,12 +11,11 @@ import aiohttp import async_timeout import voluptuous as vol +from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider from homeassistant.const import CONF_API_KEY -from homeassistant.components.tts import Provider, PLATFORM_SCHEMA, CONF_LANG from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv - _LOGGER = logging.getLogger(__name__) YANDEX_API_URL = "https://tts.voicetech.yandex.net/generate?" diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py new file mode 100644 index 00000000000..fb218a67698 --- /dev/null +++ b/homeassistant/components/yeelight/__init__.py @@ -0,0 +1,315 @@ +"""Support for Xiaomi Yeelight WiFi color bulb.""" + +import logging +from datetime import timedelta + +import voluptuous as vol +from homeassistant.components.discovery import SERVICE_YEELIGHT +from homeassistant.const import CONF_DEVICES, CONF_NAME, CONF_SCAN_INTERVAL, \ + CONF_HOST, ATTR_ENTITY_ID +from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.components.binary_sensor import DOMAIN as \ + BINARY_SENSOR_DOMAIN +from homeassistant.helpers import discovery +from homeassistant.helpers.discovery import load_platform +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.event import track_time_interval + +REQUIREMENTS = ['yeelight==0.4.4'] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "yeelight" +DATA_YEELIGHT = DOMAIN +DATA_UPDATED = '{}_data_updated'.format(DOMAIN) + +DEFAULT_NAME = 'Yeelight' +DEFAULT_TRANSITION = 350 + +CONF_MODEL = 'model' +CONF_TRANSITION = 'transition' +CONF_SAVE_ON_CHANGE = 'save_on_change' +CONF_MODE_MUSIC = 'use_music_mode' +CONF_FLOW_PARAMS = 'flow_params' +CONF_CUSTOM_EFFECTS = 'custom_effects' + +ATTR_COUNT = 'count' +ATTR_ACTION = 'action' +ATTR_TRANSITIONS = 'transitions' + +ACTION_RECOVER = 'recover' +ACTION_STAY = 'stay' +ACTION_OFF = 'off' + +SCAN_INTERVAL = timedelta(seconds=30) + +YEELIGHT_RGB_TRANSITION = 'RGBTransition' +YEELIGHT_HSV_TRANSACTION = 'HSVTransition' +YEELIGHT_TEMPERATURE_TRANSACTION = 'TemperatureTransition' +YEELIGHT_SLEEP_TRANSACTION = 'SleepTransition' + +YEELIGHT_FLOW_TRANSITION_SCHEMA = { + vol.Optional(ATTR_COUNT, default=0): cv.positive_int, + vol.Optional(ATTR_ACTION, default=ACTION_RECOVER): + vol.Any(ACTION_RECOVER, ACTION_OFF, ACTION_STAY), + vol.Required(ATTR_TRANSITIONS): [{ + vol.Exclusive(YEELIGHT_RGB_TRANSITION, CONF_TRANSITION): + vol.All(cv.ensure_list, [cv.positive_int]), + vol.Exclusive(YEELIGHT_HSV_TRANSACTION, CONF_TRANSITION): + vol.All(cv.ensure_list, [cv.positive_int]), + vol.Exclusive(YEELIGHT_TEMPERATURE_TRANSACTION, CONF_TRANSITION): + vol.All(cv.ensure_list, [cv.positive_int]), + vol.Exclusive(YEELIGHT_SLEEP_TRANSACTION, CONF_TRANSITION): + vol.All(cv.ensure_list, [cv.positive_int]), + }] +} + +DEVICE_SCHEMA = vol.Schema({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_TRANSITION, default=DEFAULT_TRANSITION): cv.positive_int, + vol.Optional(CONF_MODE_MUSIC, default=False): cv.boolean, + vol.Optional(CONF_SAVE_ON_CHANGE, default=False): cv.boolean, + vol.Optional(CONF_MODEL): cv.string, +}) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA}, + vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): + cv.time_period, + vol.Optional(CONF_CUSTOM_EFFECTS): [{ + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_FLOW_PARAMS): YEELIGHT_FLOW_TRANSITION_SCHEMA + }] + }), +}, extra=vol.ALLOW_EXTRA) + +YEELIGHT_SERVICE_SCHEMA = vol.Schema({ + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, +}) + +UPDATE_REQUEST_PROPERTIES = [ + "power", + "main_power", + "bright", + "ct", + "rgb", + "hue", + "sat", + "color_mode", + "bg_power", + "bg_lmode", + "bg_flowing", + "bg_ct", + "bg_bright", + "bg_hue", + "bg_sat", + "bg_rgb", + "nl_br", + "active_mode", +] + + +def _transitions_config_parser(transitions): + """Parse transitions config into initialized objects.""" + import yeelight + + transition_objects = [] + for transition_config in transitions: + transition, params = list(transition_config.items())[0] + transition_objects.append(getattr(yeelight, transition)(*params)) + + return transition_objects + + +def _parse_custom_effects(effects_config): + import yeelight + + effects = {} + for config in effects_config: + params = config[CONF_FLOW_PARAMS] + action = yeelight.Flow.actions[params[ATTR_ACTION]] + transitions = _transitions_config_parser( + params[ATTR_TRANSITIONS]) + + effects[config[CONF_NAME]] = { + ATTR_COUNT: params[ATTR_COUNT], + ATTR_ACTION: action, + ATTR_TRANSITIONS: transitions + } + + return effects + + +def setup(hass, config): + """Set up the Yeelight bulbs.""" + conf = config.get(DOMAIN, {}) + yeelight_data = hass.data[DATA_YEELIGHT] = {} + + def device_discovered(service, info): + _LOGGER.debug("Adding autodetected %s", info['hostname']) + + device_type = info['device_type'] + + name = "yeelight_%s_%s" % (device_type, + info['properties']['mac']) + ipaddr = info[CONF_HOST] + device_config = DEVICE_SCHEMA({ + CONF_NAME: name, + CONF_MODEL: device_type + }) + + _setup_device(hass, config, ipaddr, device_config) + + discovery.listen(hass, SERVICE_YEELIGHT, device_discovered) + + def update(event): + for device in yeelight_data.values(): + device.update() + + track_time_interval( + hass, update, conf.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL) + ) + + if DOMAIN in config: + for ipaddr, device_config in conf[CONF_DEVICES].items(): + _LOGGER.debug("Adding configured %s", device_config[CONF_NAME]) + _setup_device(hass, config, ipaddr, device_config) + + return True + + +def _setup_device(hass, hass_config, ipaddr, device_config): + devices = hass.data[DATA_YEELIGHT] + + if ipaddr in devices: + return + + device = YeelightDevice(hass, ipaddr, device_config) + + devices[ipaddr] = device + + platform_config = device_config.copy() + platform_config[CONF_HOST] = ipaddr + platform_config[CONF_CUSTOM_EFFECTS] = _parse_custom_effects( + hass_config.get(DOMAIN, {}).get(CONF_CUSTOM_EFFECTS, {}) + ) + + load_platform(hass, LIGHT_DOMAIN, DOMAIN, platform_config, hass_config) + load_platform(hass, BINARY_SENSOR_DOMAIN, DOMAIN, platform_config, + hass_config) + + +class YeelightDevice: + """Represents single Yeelight device.""" + + def __init__(self, hass, ipaddr, config): + """Initialize device.""" + self._hass = hass + self._config = config + self._ipaddr = ipaddr + self._name = config.get(CONF_NAME) + self._model = config.get(CONF_MODEL) + self._bulb_device = None + self._available = False + + @property + def bulb(self): + """Return bulb device.""" + import yeelight + if self._bulb_device is None: + try: + self._bulb_device = yeelight.Bulb(self._ipaddr, + model=self._model) + # force init for type + self.update() + + self._available = True + except yeelight.BulbException as ex: + self._available = False + _LOGGER.error("Failed to connect to bulb %s, %s: %s", + self._ipaddr, self._name, ex) + + return self._bulb_device + + @property + def name(self): + """Return the name of the device if any.""" + return self._name + + @property + def config(self): + """Return device config.""" + return self._config + + @property + def ipaddr(self): + """Return ip address.""" + return self._ipaddr + + @property + def available(self): + """Return true is device is available.""" + return self._available + + @property + def is_nightlight_enabled(self) -> bool: + """Return true / false if nightlight is currently enabled.""" + if self.bulb is None: + return False + + return self.bulb.last_properties.get('active_mode') == '1' + + @property + def is_nightlight_supported(self) -> bool: + """Return true / false if nightlight is supported.""" + return self.bulb.get_model_specs().get('night_light', False) + + @property + def is_ambilight_supported(self) -> bool: + """Return true / false if ambilight is supported.""" + return self.bulb.get_model_specs().get('background_light', False) + + def turn_on(self, duration=DEFAULT_TRANSITION, light_type=None): + """Turn on device.""" + import yeelight + + if not light_type: + light_type = yeelight.enums.LightType.Main + + try: + self.bulb.turn_on(duration=duration, light_type=light_type) + except yeelight.BulbException as ex: + _LOGGER.error("Unable to turn the bulb on: %s", ex) + return + + def turn_off(self, duration=DEFAULT_TRANSITION, light_type=None): + """Turn off device.""" + import yeelight + + if not light_type: + light_type = yeelight.enums.LightType.Main + + try: + self.bulb.turn_off(duration=duration, light_type=light_type) + except yeelight.BulbException as ex: + _LOGGER.error("Unable to turn the bulb off: %s", ex) + return + + def update(self): + """Read new properties from the device.""" + import yeelight + + if not self.bulb: + return + + try: + self.bulb.get_properties(UPDATE_REQUEST_PROPERTIES) + self._available = True + except yeelight.BulbException as ex: + if self._available: # just inform once + _LOGGER.error("Unable to update bulb status: %s", ex) + self._available = False + + dispatcher_send(self._hass, DATA_UPDATED, self._ipaddr) diff --git a/homeassistant/components/yeelight/binary_sensor.py b/homeassistant/components/yeelight/binary_sensor.py new file mode 100644 index 00000000000..cf7bbc5244e --- /dev/null +++ b/homeassistant/components/yeelight/binary_sensor.py @@ -0,0 +1,57 @@ +"""Sensor platform support for yeelight.""" +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.components.yeelight import DATA_YEELIGHT, DATA_UPDATED + +DEPENDENCIES = ['yeelight'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Yeelight sensors.""" + if not discovery_info: + return + + device = hass.data[DATA_YEELIGHT][discovery_info['host']] + + if device.is_nightlight_supported: + _LOGGER.debug("Adding nightlight mode sensor for %s", device.name) + add_entities([YeelightNightlightModeSensor(device)]) + + +class YeelightNightlightModeSensor(BinarySensorDevice): + """Representation of a Yeelight nightlight mode sensor.""" + + def __init__(self, device): + """Initialize nightlight mode sensor.""" + self._device = device + + @callback + def _schedule_immediate_update(self, ipaddr): + if ipaddr == self._device.ipaddr: + self.async_schedule_update_ha_state() + + async def async_added_to_hass(self): + """Handle entity which will be added.""" + async_dispatcher_connect( + self.hass, DATA_UPDATED, self._schedule_immediate_update + ) + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def name(self): + """Return the name of the sensor.""" + return "{} nightlight".format(self._device.name) + + @property + def is_on(self): + """Return true if nightlight mode is on.""" + return self._device.is_nightlight_enabled diff --git a/homeassistant/components/light/yeelight.py b/homeassistant/components/yeelight/light.py similarity index 62% rename from homeassistant/components/light/yeelight.py rename to homeassistant/components/yeelight/light.py index b4b540f729b..92b668c6987 100644 --- a/homeassistant/components/light/yeelight.py +++ b/homeassistant/components/yeelight/light.py @@ -1,99 +1,31 @@ -""" -Support for Xiaomi Yeelight Wifi color bulb. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.yeelight/ -""" +"""Light platform support for yeelight.""" import logging import voluptuous as vol - +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.service import extract_entity_ids from homeassistant.util.color import ( color_temperature_mired_to_kelvin as mired_to_kelvin, color_temperature_kelvin_to_mired as kelvin_to_mired) -from homeassistant.const import CONF_DEVICES, CONF_NAME +from homeassistant.const import CONF_HOST, ATTR_ENTITY_ID +from homeassistant.core import callback from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_COLOR_TEMP, ATTR_FLASH, FLASH_SHORT, FLASH_LONG, ATTR_EFFECT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_TRANSITION, SUPPORT_COLOR_TEMP, SUPPORT_FLASH, - SUPPORT_EFFECT, Light, PLATFORM_SCHEMA, ATTR_ENTITY_ID, DOMAIN) -import homeassistant.helpers.config_validation as cv + SUPPORT_EFFECT, Light) import homeassistant.util.color as color_util +from homeassistant.components.yeelight import ( + CONF_TRANSITION, DATA_YEELIGHT, CONF_MODE_MUSIC, + CONF_SAVE_ON_CHANGE, CONF_CUSTOM_EFFECTS, DATA_UPDATED, + YEELIGHT_SERVICE_SCHEMA, DOMAIN, ATTR_TRANSITIONS, + YEELIGHT_FLOW_TRANSITION_SCHEMA, _transitions_config_parser, + ACTION_RECOVER) -REQUIREMENTS = ['yeelight==0.4.3'] +DEPENDENCIES = ['yeelight'] _LOGGER = logging.getLogger(__name__) -LEGACY_DEVICE_TYPE_MAP = { - 'color1': 'rgb', - 'mono1': 'white', - 'strip1': 'strip', - 'bslamp1': 'bedside', - 'ceiling1': 'ceiling', -} - -DEFAULT_NAME = 'Yeelight' -DEFAULT_TRANSITION = 350 - -CONF_MODEL = 'model' -CONF_TRANSITION = 'transition' -CONF_SAVE_ON_CHANGE = 'save_on_change' -CONF_MODE_MUSIC = 'use_music_mode' -CONF_CUSTOM_EFFECTS = 'custom_effects' -CONF_FLOW_PARAMS = 'flow_params' - -DATA_KEY = 'light.yeelight' - -ATTR_MODE = 'mode' -ATTR_COUNT = 'count' -ATTR_ACTION = 'action' -ATTR_TRANSITIONS = 'transitions' - -ACTION_RECOVER = 'recover' -ACTION_STAY = 'stay' -ACTION_OFF = 'off' - -YEELIGHT_RGB_TRANSITION = 'RGBTransition' -YEELIGHT_HSV_TRANSACTION = 'HSVTransition' -YEELIGHT_TEMPERATURE_TRANSACTION = 'TemperatureTransition' -YEELIGHT_SLEEP_TRANSACTION = 'SleepTransition' - -YEELIGHT_SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids, -}) - -YEELIGHT_FLOW_TRANSITION_SCHEMA = { - vol.Optional(ATTR_COUNT, default=0): cv.positive_int, - vol.Optional(ATTR_ACTION, default=ACTION_RECOVER): - vol.Any(ACTION_RECOVER, ACTION_OFF, ACTION_STAY), - vol.Required(ATTR_TRANSITIONS): [{ - vol.Exclusive(YEELIGHT_RGB_TRANSITION, CONF_TRANSITION): - vol.All(cv.ensure_list, [cv.positive_int]), - vol.Exclusive(YEELIGHT_HSV_TRANSACTION, CONF_TRANSITION): - vol.All(cv.ensure_list, [cv.positive_int]), - vol.Exclusive(YEELIGHT_TEMPERATURE_TRANSACTION, CONF_TRANSITION): - vol.All(cv.ensure_list, [cv.positive_int]), - vol.Exclusive(YEELIGHT_SLEEP_TRANSACTION, CONF_TRANSITION): - vol.All(cv.ensure_list, [cv.positive_int]), - }] -} - -DEVICE_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_TRANSITION, default=DEFAULT_TRANSITION): cv.positive_int, - vol.Optional(CONF_MODE_MUSIC, default=False): cv.boolean, - vol.Optional(CONF_SAVE_ON_CHANGE, default=False): cv.boolean, - vol.Optional(CONF_MODEL): cv.string, -}) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA}, - vol.Optional(CONF_CUSTOM_EFFECTS): [{ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_FLOW_PARAMS): YEELIGHT_FLOW_TRANSITION_SCHEMA - }] -}) - SUPPORT_YEELIGHT = (SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_FLASH) @@ -106,6 +38,11 @@ SUPPORT_YEELIGHT_RGB = (SUPPORT_YEELIGHT | SUPPORT_EFFECT | SUPPORT_COLOR_TEMP) +ATTR_MODE = 'mode' + +SERVICE_SET_MODE = 'set_mode' +SERVICE_START_FLOW = 'start_flow' + EFFECT_DISCO = "Disco" EFFECT_TEMP = "Slow Temp" EFFECT_STROBE = "Strobe epilepsy!" @@ -143,9 +80,6 @@ YEELIGHT_EFFECT_LIST = [ EFFECT_TWITTER, EFFECT_STOP] -SERVICE_SET_MODE = 'yeelight_set_mode' -SERVICE_START_FLOW = 'yeelight_start_flow' - def _cmd(func): """Define a wrapper to catch exceptions from the bulb.""" @@ -160,74 +94,47 @@ def _cmd(func): return _wrap -def _parse_custom_effects(effects_config): - effects = {} - for config in effects_config: - params = config[CONF_FLOW_PARAMS] - transitions = YeelightLight.transitions_config_parser( - params[ATTR_TRANSITIONS]) - - effects[config[CONF_NAME]] = \ - {ATTR_COUNT: params[ATTR_COUNT], ATTR_TRANSITIONS: transitions} - - return effects - - def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Yeelight bulbs.""" from yeelight.enums import PowerMode - if DATA_KEY not in hass.data: - hass.data[DATA_KEY] = {} + data_key = '{}_lights'.format(DATA_YEELIGHT) - lights = [] - if discovery_info is not None: - _LOGGER.debug("Adding autodetected %s", discovery_info['hostname']) + if not discovery_info: + return - device_type = discovery_info['device_type'] - legacy_device_type = LEGACY_DEVICE_TYPE_MAP.get(device_type, - device_type) + if data_key not in hass.data: + hass.data[data_key] = [] - # Not using hostname, as it seems to vary. - name = "yeelight_%s_%s" % (legacy_device_type, - discovery_info['properties']['mac']) - device = {'name': name, 'ipaddr': discovery_info['host']} + device = hass.data[DATA_YEELIGHT][discovery_info[CONF_HOST]] + _LOGGER.debug("Adding %s", device.name) - light = YeelightLight(device, DEVICE_SCHEMA({CONF_MODEL: device_type})) - lights.append(light) - hass.data[DATA_KEY][name] = light - else: - for ipaddr, device_config in config[CONF_DEVICES].items(): - name = device_config[CONF_NAME] - _LOGGER.debug("Adding configured %s", name) + custom_effects = discovery_info[CONF_CUSTOM_EFFECTS] - device = {'name': name, 'ipaddr': ipaddr} + lights = [YeelightLight(device, custom_effects=custom_effects)] - if CONF_CUSTOM_EFFECTS in config: - custom_effects = \ - _parse_custom_effects(config[CONF_CUSTOM_EFFECTS]) - else: - custom_effects = None - - light = YeelightLight(device, device_config, - custom_effects=custom_effects) - lights.append(light) - hass.data[DATA_KEY][name] = light + if device.is_ambilight_supported: + lights.append( + YeelightAmbientLight(device, custom_effects=custom_effects)) + hass.data[data_key] += lights add_entities(lights, True) def service_handler(service): """Dispatch service calls to target entities.""" params = {key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID} - entity_ids = service.data.get(ATTR_ENTITY_ID) - target_devices = [dev for dev in hass.data[DATA_KEY].values() - if dev.entity_id in entity_ids] + + entity_ids = extract_entity_ids(hass, service) + target_devices = [light for light in hass.data[data_key] + if light.entity_id in entity_ids] for target_device in target_devices: if service.service == SERVICE_SET_MODE: target_device.set_mode(**params) elif service.service == SERVICE_START_FLOW: + params[ATTR_TRANSITIONS] = \ + _transitions_config_parser(params[ATTR_TRANSITIONS]) target_device.start_flow(**params) service_schema_set_mode = YEELIGHT_SERVICE_SCHEMA.extend({ @@ -249,22 +156,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class YeelightLight(Light): """Representation of a Yeelight light.""" - def __init__(self, device, config, custom_effects=None): + def __init__(self, device, custom_effects=None): """Initialize the Yeelight light.""" - self.config = config - self._name = device['name'] - self._ipaddr = device['ipaddr'] + self.config = device.config + self._device = device self._supported_features = SUPPORT_YEELIGHT - self._available = False - self._bulb_device = None self._brightness = None self._color_temp = None self._is_on = None self._hs = None - self._model = config.get('model') self._min_mireds = None self._max_mireds = None @@ -273,10 +176,26 @@ class YeelightLight(Light): else: self._custom_effects = {} + @callback + def _schedule_immediate_update(self, ipaddr): + if ipaddr == self.device.ipaddr: + self.async_schedule_update_ha_state(True) + + async def async_added_to_hass(self): + """Handle entity which will be added.""" + async_dispatcher_connect( + self.hass, DATA_UPDATED, self._schedule_immediate_update + ) + + @property + def should_poll(self): + """No polling needed.""" + return False + @property def available(self) -> bool: """Return if bulb is available.""" - return self._available + return self.device.available @property def supported_features(self) -> int: @@ -296,7 +215,7 @@ class YeelightLight(Light): @property def name(self) -> str: """Return the name of the device if any.""" - return self._name + return self.device.name @property def is_on(self) -> bool: @@ -328,9 +247,16 @@ class YeelightLight(Light): """Return list with custom effects names.""" return list(self.custom_effects.keys()) + @property + def light_type(self): + """Return light type.""" + import yeelight + return yeelight.enums.LightType.Main + def _get_hs_from_properties(self): - rgb = self._properties.get('rgb', None) - color_mode = self._properties.get('color_mode', None) + rgb = self._get_property('rgb') + color_mode = self._get_property('color_mode') + if not rgb or not color_mode: return None @@ -339,8 +265,9 @@ class YeelightLight(Light): temp_in_k = mired_to_kelvin(self._color_temp) return color_util.color_temperature_to_hs(temp_in_k) if color_mode == 3: # hsv - hue = int(self._properties.get('hue')) - sat = int(self._properties.get('sat')) + hue = int(self._get_property('hue')) + sat = int(self._get_property('sat')) + return (hue / 360 * 65536, sat / 100 * 255) rgb = int(rgb) @@ -357,27 +284,26 @@ class YeelightLight(Light): @property def _properties(self) -> dict: - if self._bulb_device is None: + if self._bulb is None: return {} - return self._bulb_device.last_properties + return self._bulb.last_properties + + def _get_property(self, prop, default=None): + return self._properties.get(prop, default) + + @property + def device(self): + """Return yeelight device.""" + return self._device + + @property + def _is_nightlight_enabled(self): + return self.device.is_nightlight_enabled # F821: https://github.com/PyCQA/pyflakes/issues/373 @property def _bulb(self) -> 'yeelight.Bulb': # noqa: F821 - import yeelight - if self._bulb_device is None: - try: - self._bulb_device = yeelight.Bulb(self._ipaddr, - model=self._model) - self._bulb_device.get_properties() # force init for type - - self._available = True - except yeelight.BulbException as ex: - self._available = False - _LOGGER.error("Failed to connect to bulb %s, %s: %s", - self._ipaddr, self._name, ex) - - return self._bulb_device + return self.device.bulb def set_music_mode(self, mode) -> None: """Set the music mode on or off.""" @@ -389,38 +315,45 @@ class YeelightLight(Light): def update(self) -> None: """Update properties from the bulb.""" import yeelight - try: - self._bulb.get_properties() + bulb_type = self._bulb.bulb_type - if self._bulb_device.bulb_type == yeelight.BulbType.Color: - self._supported_features = SUPPORT_YEELIGHT_RGB - elif self._bulb_device.bulb_type == yeelight.BulbType.WhiteTemp: + if bulb_type == yeelight.BulbType.Color: + self._supported_features = SUPPORT_YEELIGHT_RGB + elif self.light_type == yeelight.enums.LightType.Ambient: + self._supported_features = SUPPORT_YEELIGHT_RGB + elif bulb_type in (yeelight.BulbType.WhiteTemp, + yeelight.BulbType.WhiteTempMood): + if self._is_nightlight_enabled: + self._supported_features = SUPPORT_YEELIGHT + else: self._supported_features = SUPPORT_YEELIGHT_WHITE_TEMP - if self._min_mireds is None: - model_specs = self._bulb.get_model_specs() - self._min_mireds = \ - kelvin_to_mired(model_specs['color_temp']['max']) - self._max_mireds = \ - kelvin_to_mired(model_specs['color_temp']['min']) + if self.min_mireds is None: + model_specs = self._bulb.get_model_specs() + self._min_mireds = \ + kelvin_to_mired(model_specs['color_temp']['max']) + self._max_mireds = \ + kelvin_to_mired(model_specs['color_temp']['min']) - self._is_on = self._properties.get('power') == 'on' + if bulb_type == yeelight.BulbType.WhiteTempMood: + self._is_on = self._get_property('main_power') == 'on' + else: + self._is_on = self._get_property('power') == 'on' - bright = self._properties.get('bright', None) - if bright: - self._brightness = round(255 * (int(bright) / 100)) + if self._is_nightlight_enabled: + bright = self._get_property('nl_br') + else: + bright = self._get_property('bright') - temp_in_k = self._properties.get('ct', None) - if temp_in_k: - self._color_temp = kelvin_to_mired(int(temp_in_k)) + if bright: + self._brightness = round(255 * (int(bright) / 100)) - self._hs = self._get_hs_from_properties() + temp_in_k = self._get_property('ct') - self._available = True - except yeelight.BulbException as ex: - if self._available: # just inform once - _LOGGER.error("Unable to update bulb status: %s", ex) - self._available = False + if temp_in_k: + self._color_temp = kelvin_to_mired(int(temp_in_k)) + + self._hs = self._get_hs_from_properties() @_cmd def set_brightness(self, brightness, duration) -> None: @@ -428,14 +361,16 @@ class YeelightLight(Light): if brightness: _LOGGER.debug("Setting brightness: %s", brightness) self._bulb.set_brightness(brightness / 255 * 100, - duration=duration) + duration=duration, + light_type=self.light_type) @_cmd def set_rgb(self, rgb, duration) -> None: """Set bulb's color.""" if rgb and self.supported_features & SUPPORT_COLOR: _LOGGER.debug("Setting RGB: %s", rgb) - self._bulb.set_rgb(rgb[0], rgb[1], rgb[2], duration=duration) + self._bulb.set_rgb(rgb[0], rgb[1], rgb[2], duration=duration, + light_type=self.light_type) @_cmd def set_colortemp(self, colortemp, duration) -> None: @@ -444,7 +379,8 @@ class YeelightLight(Light): temp_in_k = mired_to_kelvin(colortemp) _LOGGER.debug("Setting color temp: %s K", temp_in_k) - self._bulb.set_color_temp(temp_in_k, duration=duration) + self._bulb.set_color_temp(temp_in_k, duration=duration, + light_type=self.light_type) @_cmd def set_default(self) -> None: @@ -482,7 +418,7 @@ class YeelightLight(Light): flow = Flow(count=count, transitions=transitions) try: - self._bulb.start_flow(flow) + self._bulb.start_flow(flow, light_type=self.light_type) except BulbException as ex: _LOGGER.error("Unable to set flash: %s", ex) @@ -496,7 +432,7 @@ class YeelightLight(Light): police2, christmas, rgb, randomloop, lsd, slowdown) if effect == EFFECT_STOP: - self._bulb.stop_flow() + self._bulb.stop_flow(light_type=self.light_type) return effects_map = { @@ -528,7 +464,7 @@ class YeelightLight(Light): flow = Flow(count=2, transitions=pulse(0, 172, 237)) try: - self._bulb.start_flow(flow) + self._bulb.start_flow(flow, light_type=self.light_type) except BulbException as ex: _LOGGER.error("Unable to set effect: %s", ex) @@ -546,11 +482,7 @@ class YeelightLight(Light): if ATTR_TRANSITION in kwargs: # passed kwarg overrides config duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s - try: - self._bulb.turn_on(duration=duration) - except yeelight.BulbException as ex: - _LOGGER.error("Unable to turn the bulb on: %s", ex) - return + self.device.turn_on(duration=duration, light_type=self.light_type) if self.config[CONF_MODE_MUSIC] and not self._bulb.music_mode: try: @@ -579,39 +511,27 @@ class YeelightLight(Light): except yeelight.BulbException as ex: _LOGGER.error("Unable to set the defaults: %s", ex) return + self.device.update() def turn_off(self, **kwargs) -> None: """Turn off.""" - import yeelight duration = int(self.config[CONF_TRANSITION]) # in ms if ATTR_TRANSITION in kwargs: # passed kwarg overrides config duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s - try: - self._bulb.turn_off(duration=duration) - except yeelight.BulbException as ex: - _LOGGER.error("Unable to turn the bulb off: %s", ex) + + self.device.turn_off(duration=duration, light_type=self.light_type) + self.device.update() def set_mode(self, mode: str): """Set a power mode.""" import yeelight + try: self._bulb.set_power_mode(yeelight.enums.PowerMode[mode.upper()]) - self.async_schedule_update_ha_state(True) + self.device.update() except yeelight.BulbException as ex: _LOGGER.error("Unable to set the power mode: %s", ex) - @staticmethod - def transitions_config_parser(transitions): - """Parse transitions config into initialized objects.""" - import yeelight - - transition_objects = [] - for transition_config in transitions: - transition, params = list(transition_config.items())[0] - transition_objects.append(getattr(yeelight, transition)(*params)) - - return transition_objects - def start_flow(self, transitions, count=0, action=ACTION_RECOVER): """Start flow.""" import yeelight @@ -620,8 +540,47 @@ class YeelightLight(Light): flow = yeelight.Flow( count=count, action=yeelight.Flow.actions[action], - transitions=self.transitions_config_parser(transitions)) + transitions=transitions) - self._bulb.start_flow(flow) + self._bulb.start_flow(flow, light_type=self.light_type) + self.device.update() except yeelight.BulbException as ex: _LOGGER.error("Unable to set effect: %s", ex) + + +class YeelightAmbientLight(YeelightLight): + """Representation of a Yeelight ambient light.""" + + PROPERTIES_MAPPING = { + "color_mode": "bg_lmode", + "main_power": "bg_power", + } + + def __init__(self, *args, **kwargs): + """Initialize the Yeelight Ambient light.""" + super().__init__(*args, **kwargs) + self._min_mireds = kelvin_to_mired(6500) + self._max_mireds = kelvin_to_mired(1700) + + @property + def name(self) -> str: + """Return the name of the device if any.""" + return "{} ambilight".format(self.device.name) + + @property + def light_type(self): + """Return light type.""" + import yeelight + return yeelight.enums.LightType.Ambient + + @property + def _is_nightlight_enabled(self): + return False + + def _get_property(self, prop, default=None): + bg_prop = self.PROPERTIES_MAPPING.get(prop) + + if not bg_prop: + bg_prop = "bg_" + prop + + return self._properties.get(bg_prop, default) diff --git a/homeassistant/components/yeelight/services.yaml b/homeassistant/components/yeelight/services.yaml new file mode 100644 index 00000000000..14dcfb27a4d --- /dev/null +++ b/homeassistant/components/yeelight/services.yaml @@ -0,0 +1,25 @@ +set_mode: + description: Set a operation mode. + fields: + entity_id: + description: Name of the light entity. + example: 'light.yeelight' + mode: + description: Operation mode. Valid values are 'last', 'normal', 'rgb', 'hsv', 'color_flow', 'moonlight'. + example: 'moonlight' + +start_flow: + description: Start a custom flow, using transitions from https://yeelight.readthedocs.io/en/stable/yeelight.html#flow-objects + fields: + entity_id: + description: Name of the light entity. + example: 'light.yeelight' + count: + description: The number of times to run this flow (0 to run forever). + example: 0 + action: + description: The action to take after the flow stops. Can be 'recover', 'stay', 'off'. (default 'recover') + example: 'stay' + transitions: + description: Array of transitions, for desired effect. Examples https://yeelight.readthedocs.io/en/stable/flow.html + example: '[{ "TemperatureTransition": [1900, 1000, 80] }, { "TemperatureTransition": [1900, 1000, 10] }]' diff --git a/homeassistant/components/yeelightsunflower/__init__.py b/homeassistant/components/yeelightsunflower/__init__.py new file mode 100644 index 00000000000..4f0421eeb3c --- /dev/null +++ b/homeassistant/components/yeelightsunflower/__init__.py @@ -0,0 +1 @@ +"""The yeelightsunflower component.""" diff --git a/homeassistant/components/light/yeelightsunflower.py b/homeassistant/components/yeelightsunflower/light.py similarity index 100% rename from homeassistant/components/light/yeelightsunflower.py rename to homeassistant/components/yeelightsunflower/light.py diff --git a/homeassistant/components/yessssms/__init__.py b/homeassistant/components/yessssms/__init__.py new file mode 100644 index 00000000000..bc5f422ba75 --- /dev/null +++ b/homeassistant/components/yessssms/__init__.py @@ -0,0 +1 @@ +"""The yessssms component.""" diff --git a/homeassistant/components/notify/yessssms.py b/homeassistant/components/yessssms/notify.py similarity index 92% rename from homeassistant/components/notify/yessssms.py rename to homeassistant/components/yessssms/notify.py index e16e384ca25..529aa4e7b6e 100644 --- a/homeassistant/components/notify/yessssms.py +++ b/homeassistant/components/yessssms/notify.py @@ -8,11 +8,11 @@ import logging import voluptuous as vol -from homeassistant.components.notify import ( - PLATFORM_SCHEMA, BaseNotificationService) -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_RECIPIENT +from homeassistant.const import CONF_PASSWORD, CONF_RECIPIENT, CONF_USERNAME import homeassistant.helpers.config_validation as cv +from homeassistant.components.notify import (PLATFORM_SCHEMA, + BaseNotificationService) REQUIREMENTS = ['YesssSMS==0.2.3'] diff --git a/homeassistant/components/yi/__init__.py b/homeassistant/components/yi/__init__.py new file mode 100644 index 00000000000..a37399d930d --- /dev/null +++ b/homeassistant/components/yi/__init__.py @@ -0,0 +1 @@ +"""The yi component.""" diff --git a/homeassistant/components/camera/yi.py b/homeassistant/components/yi/camera.py similarity index 93% rename from homeassistant/components/camera/yi.py rename to homeassistant/components/yi/camera.py index 7d731d2a433..f82c8c38129 100644 --- a/homeassistant/components/camera/yi.py +++ b/homeassistant/components/yi/camera.py @@ -26,6 +26,7 @@ DEFAULT_PASSWORD = '' DEFAULT_PATH = '/tmp/sd/record' DEFAULT_PORT = 21 DEFAULT_USERNAME = 'root' +DEFAULT_ARGUMENTS = '-pred 1' CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' @@ -36,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string, vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string + vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string }) @@ -118,7 +119,7 @@ class YiCamera(Camera): async def async_camera_image(self): """Return a still image response from the camera.""" - from haffmpeg import ImageFrame, IMAGE_JPEG + from haffmpeg.tools import ImageFrame, IMAGE_JPEG url = await self._get_latest_video_url() if url and url != self._last_url: @@ -135,7 +136,7 @@ class YiCamera(Camera): async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" - from haffmpeg import CameraMjpeg + from haffmpeg.camera import CameraMjpeg if not self._is_on: return @@ -145,8 +146,9 @@ class YiCamera(Camera): self._last_url, extra_cmd=self._extra_arguments) try: + stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream, + self.hass, request, stream_reader, self._manager.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/yr/__init__.py b/homeassistant/components/yr/__init__.py new file mode 100644 index 00000000000..8d33bd56d43 --- /dev/null +++ b/homeassistant/components/yr/__init__.py @@ -0,0 +1 @@ +"""The yr component.""" diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/yr/sensor.py similarity index 100% rename from homeassistant/components/sensor/yr.py rename to homeassistant/components/yr/sensor.py diff --git a/homeassistant/components/yweather/__init__.py b/homeassistant/components/yweather/__init__.py new file mode 100644 index 00000000000..0d5012f4c5d --- /dev/null +++ b/homeassistant/components/yweather/__init__.py @@ -0,0 +1 @@ +"""The yweather component.""" diff --git a/homeassistant/components/sensor/yweather.py b/homeassistant/components/yweather/sensor.py similarity index 100% rename from homeassistant/components/sensor/yweather.py rename to homeassistant/components/yweather/sensor.py diff --git a/homeassistant/components/weather/yweather.py b/homeassistant/components/yweather/weather.py similarity index 100% rename from homeassistant/components/weather/yweather.py rename to homeassistant/components/yweather/weather.py diff --git a/homeassistant/components/zamg/__init__.py b/homeassistant/components/zamg/__init__.py new file mode 100644 index 00000000000..a0f80956d98 --- /dev/null +++ b/homeassistant/components/zamg/__init__.py @@ -0,0 +1 @@ +"""The zamg component.""" diff --git a/homeassistant/components/sensor/zamg.py b/homeassistant/components/zamg/sensor.py similarity index 100% rename from homeassistant/components/sensor/zamg.py rename to homeassistant/components/zamg/sensor.py diff --git a/homeassistant/components/weather/zamg.py b/homeassistant/components/zamg/weather.py similarity index 98% rename from homeassistant/components/weather/zamg.py rename to homeassistant/components/zamg/weather.py index 60707fa5e30..bc90a56cc10 100644 --- a/homeassistant/components/weather/zamg.py +++ b/homeassistant/components/zamg/weather.py @@ -3,9 +3,6 @@ import logging import voluptuous as vol -# Reuse data and API logic from the sensor implementation -from homeassistant.components.sensor.zamg import ( - ATTRIBUTION, CONF_STATION_ID, ZamgData, closest_station, zamg_stations) from homeassistant.components.weather import ( ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, ATTR_WEATHER_TEMPERATURE, ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_SPEED, PLATFORM_SCHEMA, @@ -14,6 +11,10 @@ from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS) from homeassistant.helpers import config_validation as cv +# Reuse data and API logic from the sensor implementation +from .sensor import ( + ATTRIBUTION, CONF_STATION_ID, ZamgData, closest_station, zamg_stations) + _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/zengge/__init__.py b/homeassistant/components/zengge/__init__.py new file mode 100644 index 00000000000..ab4fe5e35c3 --- /dev/null +++ b/homeassistant/components/zengge/__init__.py @@ -0,0 +1 @@ +"""The zengge component.""" diff --git a/homeassistant/components/light/zengge.py b/homeassistant/components/zengge/light.py similarity index 100% rename from homeassistant/components/light/zengge.py rename to homeassistant/components/zengge/light.py diff --git a/homeassistant/components/zestimate/__init__.py b/homeassistant/components/zestimate/__init__.py new file mode 100644 index 00000000000..5742ae56f35 --- /dev/null +++ b/homeassistant/components/zestimate/__init__.py @@ -0,0 +1 @@ +"""The zestimate component.""" diff --git a/homeassistant/components/sensor/zestimate.py b/homeassistant/components/zestimate/sensor.py similarity index 100% rename from homeassistant/components/sensor/zestimate.py rename to homeassistant/components/zestimate/sensor.py diff --git a/homeassistant/components/zha/.translations/fr.json b/homeassistant/components/zha/.translations/fr.json new file mode 100644 index 00000000000..de1a2274dd3 --- /dev/null +++ b/homeassistant/components/zha/.translations/fr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Une seule configuration de ZHA est autoris\u00e9e." + }, + "error": { + "cannot_connect": "Impossible de se connecter au p\u00e9riph\u00e9rique ZHA." + }, + "step": { + "user": { + "data": { + "usb_path": "Chemin du p\u00e9riph\u00e9rique USB" + }, + "title": "ZHA" + } + }, + "title": "ZHA" + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/.translations/zh-Hans.json b/homeassistant/components/zha/.translations/zh-Hans.json index ce458fa32f1..2c81c603186 100644 --- a/homeassistant/components/zha/.translations/zh-Hans.json +++ b/homeassistant/components/zha/.translations/zh-Hans.json @@ -9,6 +9,7 @@ "step": { "user": { "data": { + "radio_type": "\u65e0\u7ebf\u7535\u7c7b\u578b", "usb_path": "USB \u8bbe\u5907\u8def\u5f84" }, "description": "\u7a7a\u767d", diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 82efd564742..292b4fde61f 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -4,10 +4,7 @@ Support for Zigbee Home Automation devices. For more details about this component, please refer to the documentation at https://home-assistant.io/components/zha/ """ -import asyncio import logging -import os -import types import voluptuous as vol @@ -21,20 +18,20 @@ from . import api from .core import ZHAGateway from .core.const import ( COMPONENTS, CONF_BAUDRATE, CONF_DATABASE, CONF_DEVICE_CONFIG, - CONF_RADIO_TYPE, CONF_USB_PATH, DATA_ZHA, DATA_ZHA_BRIDGE_ID, + CONF_RADIO_TYPE, CONF_USB_PATH, DATA_ZHA, DATA_ZHA_CONFIG, DATA_ZHA_CORE_COMPONENT, DATA_ZHA_DISPATCHERS, - DATA_ZHA_RADIO, DEFAULT_BAUDRATE, DEFAULT_DATABASE_NAME, DATA_ZHA_GATEWAY, + DATA_ZHA_RADIO, DEFAULT_BAUDRATE, DATA_ZHA_GATEWAY, DEFAULT_RADIO_TYPE, DOMAIN, RadioType, DATA_ZHA_CORE_EVENTS, ENABLE_QUIRKS) -from .core.gateway import establish_device_mappings +from .core.registries import establish_device_mappings from .core.channels.registry import populate_channel_registry -from .core.store import async_get_registry +from .core.patches import apply_cluster_listener_patch REQUIREMENTS = [ - 'bellows-homeassistant==0.7.1', - 'zigpy-homeassistant==0.3.0', - 'zigpy-xbee-homeassistant==0.1.2', + 'bellows-homeassistant==0.7.2', + 'zigpy-homeassistant==0.3.1', + 'zigpy-xbee-homeassistant==0.1.3', 'zha-quirks==0.0.7', - 'zigpy-deconz==0.1.2' + 'zigpy-deconz==0.1.3' ] DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({ @@ -108,82 +105,32 @@ async def async_setup_entry(hass, config_entry): # pylint: disable=W0611, W0612 import zhaquirks # noqa - usb_path = config_entry.data.get(CONF_USB_PATH) - baudrate = config.get(CONF_BAUDRATE, DEFAULT_BAUDRATE) - radio_type = config_entry.data.get(CONF_RADIO_TYPE) - if radio_type == RadioType.ezsp.name: - import bellows.ezsp - from bellows.zigbee.application import ControllerApplication - radio = bellows.ezsp.EZSP() - radio_description = "EZSP" - elif radio_type == RadioType.xbee.name: - import zigpy_xbee.api - from zigpy_xbee.zigbee.application import ControllerApplication - radio = zigpy_xbee.api.XBee() - radio_description = "XBee" - elif radio_type == RadioType.deconz.name: - import zigpy_deconz.api - from zigpy_deconz.zigbee.application import ControllerApplication - radio = zigpy_deconz.api.Deconz() - radio_description = "Deconz" - - await radio.connect(usb_path, baudrate) - hass.data[DATA_ZHA][DATA_ZHA_RADIO] = radio - - if CONF_DATABASE in config: - database = config[CONF_DATABASE] - else: - database = os.path.join(hass.config.config_dir, DEFAULT_DATABASE_NAME) - # patch zigpy listener to prevent flooding logs with warnings due to # how zigpy implemented its listeners - from zigpy.appdb import ClusterPersistingListener + apply_cluster_listener_patch() - def zha_send_event(self, cluster, command, args): - pass - - ClusterPersistingListener.zha_send_event = types.MethodType( - zha_send_event, - ClusterPersistingListener - ) - - zha_storage = await async_get_registry(hass) - zha_gateway = ZHAGateway(hass, config, zha_storage) - - # Patch handle_message until zigpy can provide an event here - def handle_message(sender, is_reply, profile, cluster, - src_ep, dst_ep, tsn, command_id, args): - """Handle message from a device.""" - if not sender.initializing and sender.ieee in zha_gateway.devices and \ - not zha_gateway.devices[sender.ieee].available: - zha_gateway.async_device_became_available( - sender, is_reply, profile, cluster, src_ep, dst_ep, tsn, - command_id, args - ) - return sender.handle_message( - is_reply, profile, cluster, src_ep, dst_ep, tsn, command_id, args) - - application_controller = ControllerApplication(radio, database) - application_controller.handle_message = handle_message - application_controller.add_listener(zha_gateway) - await application_controller.startup(auto_form=True) - - hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(application_controller.ieee) - - init_tasks = [] - for device in application_controller.devices.values(): - init_tasks.append(zha_gateway.async_device_initialized(device, False)) - await asyncio.gather(*init_tasks) + zha_gateway = ZHAGateway(hass, config) + await zha_gateway.async_initialize(config_entry) device_registry = await \ hass.helpers.device_registry.async_get_registry() device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={(CONNECTION_ZIGBEE, str(application_controller.ieee))}, - identifiers={(DOMAIN, str(application_controller.ieee))}, + connections={ + ( + CONNECTION_ZIGBEE, + str(zha_gateway.application_controller.ieee) + ) + }, + identifiers={ + ( + DOMAIN, + str(zha_gateway.application_controller.ieee) + ) + }, name="Zigbee Coordinator", manufacturer="ZHA", - model=radio_description, + model=zha_gateway.radio_description, ) for component in COMPONENTS: @@ -192,7 +139,7 @@ async def async_setup_entry(hass, config_entry): config_entry, component) ) - api.async_load_api(hass, application_controller, zha_gateway) + api.async_load_api(hass) async def async_zha_shutdown(event): """Handle shutdown tasks.""" diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 6d79f3b3320..2f88ad3a78b 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -7,17 +7,22 @@ https://home-assistant.io/components/zha/ import asyncio import logging + import voluptuous as vol from homeassistant.components import websocket_api +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import async_get_registry +from homeassistant.helpers.dispatcher import async_dispatcher_connect + from .core.const import ( - DOMAIN, ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, ATTR_ATTRIBUTE, ATTR_VALUE, - ATTR_MANUFACTURER, ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_ARGS, IN, OUT, - CLIENT_COMMANDS, SERVER_COMMANDS, SERVER, NAME, ATTR_ENDPOINT_ID, - DATA_ZHA_GATEWAY, DATA_ZHA) -from .core.helpers import get_matched_clusters, async_is_bindable_target + ATTR_ARGS, ATTR_ATTRIBUTE, ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, + ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_ENDPOINT_ID, ATTR_MANUFACTURER, + ATTR_VALUE, CLIENT_COMMANDS, DATA_ZHA, DATA_ZHA_GATEWAY, DOMAIN, IN, + MFG_CLUSTER_ID_START, NAME, OUT, SERVER, SERVER_COMMANDS) +from .core.helpers import ( + async_is_bindable_target, convert_ieee, get_matched_clusters) _LOGGER = logging.getLogger(__name__) @@ -46,14 +51,15 @@ IEEE_SERVICE = 'ieee_based_service' SERVICE_SCHEMAS = { SERVICE_PERMIT: vol.Schema({ + vol.Optional(ATTR_IEEE_ADDRESS, default=None): convert_ieee, vol.Optional(ATTR_DURATION, default=60): - vol.All(vol.Coerce(int), vol.Range(1, 254)), + vol.All(vol.Coerce(int), vol.Range(0, 254)), }), IEEE_SERVICE: vol.Schema({ - vol.Required(ATTR_IEEE_ADDRESS): cv.string, + vol.Required(ATTR_IEEE_ADDRESS): convert_ieee, }), SERVICE_SET_ZIGBEE_CLUSTER_ATTRIBUTE: vol.Schema({ - vol.Required(ATTR_IEEE): cv.string, + vol.Required(ATTR_IEEE): convert_ieee, vol.Required(ATTR_ENDPOINT_ID): cv.positive_int, vol.Required(ATTR_CLUSTER_ID): cv.positive_int, vol.Optional(ATTR_CLUSTER_TYPE, default=IN): cv.string, @@ -62,7 +68,7 @@ SERVICE_SCHEMAS = { vol.Optional(ATTR_MANUFACTURER): cv.positive_int, }), SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND: vol.Schema({ - vol.Required(ATTR_IEEE): cv.string, + vol.Required(ATTR_IEEE): convert_ieee, vol.Required(ATTR_ENDPOINT_ID): cv.positive_int, vol.Required(ATTR_CLUSTER_ID): cv.positive_int, vol.Optional(ATTR_CLUSTER_TYPE, default=IN): cv.string, @@ -74,6 +80,44 @@ SERVICE_SCHEMAS = { } +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command({ + vol.Required('type'): 'zha/devices/permit', + vol.Optional(ATTR_IEEE, default=None): convert_ieee, + vol.Optional(ATTR_DURATION, default=60): vol.All(vol.Coerce(int), + vol.Range(0, 254)) +}) +async def websocket_permit_devices(hass, connection, msg): + """Permit ZHA zigbee devices.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + duration = msg.get(ATTR_DURATION) + ieee = msg.get(ATTR_IEEE) + + async def forward_messages(data): + """Forward events to websocket.""" + connection.send_message(websocket_api.event_message(msg['id'], data)) + + remove_dispatcher_function = async_dispatcher_connect( + hass, + "zha_gateway_message", + forward_messages + ) + + @callback + def async_cleanup() -> None: + """Remove signal listener and turn off debug mode.""" + zha_gateway.async_disable_debug_mode() + remove_dispatcher_function() + + connection.subscriptions[msg['id']] = async_cleanup + zha_gateway.async_enable_debug_mode() + await zha_gateway.application_controller.permit(time_s=duration, + node=ieee) + connection.send_result(msg['id']) + + +@websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command({ vol.Required(TYPE): 'zha/devices' @@ -85,28 +129,40 @@ async def websocket_get_devices(hass, connection, msg): devices = [] for device in zha_gateway.devices.values(): - ret_device = {} - ret_device.update(device.device_info) - ret_device['entities'] = [{ - 'entity_id': entity_ref.reference_id, - NAME: entity_ref.device_info[NAME] - } for entity_ref in zha_gateway.device_registry[device.ieee]] + devices.append( + async_get_device_info( + hass, device, ha_device_registry=ha_device_registry + ) + ) + connection.send_result(msg[ID], devices) + +@callback +def async_get_device_info(hass, device, ha_device_registry=None): + """Get ZHA device.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + ret_device = {} + ret_device.update(device.device_info) + ret_device['entities'] = [{ + 'entity_id': entity_ref.reference_id, + NAME: entity_ref.device_info[NAME] + } for entity_ref in zha_gateway.device_registry[device.ieee]] + + if ha_device_registry is not None: reg_device = ha_device_registry.async_get_device( {(DOMAIN, str(device.ieee))}, set()) if reg_device is not None: ret_device['user_given_name'] = reg_device.name_by_user ret_device['device_reg_id'] = reg_device.id - - devices.append(ret_device) - - connection.send_result(msg[ID], devices) + ret_device['area_id'] = reg_device.area_id + return ret_device +@websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command({ vol.Required(TYPE): 'zha/devices/reconfigure', - vol.Required(ATTR_IEEE): str + vol.Required(ATTR_IEEE): convert_ieee, }) async def websocket_reconfigure_node(hass, connection, msg): """Reconfigure a ZHA nodes entities by its ieee address.""" @@ -117,10 +173,11 @@ async def websocket_reconfigure_node(hass, connection, msg): hass.async_create_task(device.async_configure()) +@websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command({ vol.Required(TYPE): 'zha/devices/clusters', - vol.Required(ATTR_IEEE): str + vol.Required(ATTR_IEEE): convert_ieee, }) async def websocket_device_clusters(hass, connection, msg): """Return a list of device clusters.""" @@ -149,10 +206,11 @@ async def websocket_device_clusters(hass, connection, msg): connection.send_result(msg[ID], response_clusters) +@websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command({ vol.Required(TYPE): 'zha/devices/clusters/attributes', - vol.Required(ATTR_IEEE): str, + vol.Required(ATTR_IEEE): convert_ieee, vol.Required(ATTR_ENDPOINT_ID): int, vol.Required(ATTR_CLUSTER_ID): int, vol.Required(ATTR_CLUSTER_TYPE): str @@ -190,10 +248,11 @@ async def websocket_device_cluster_attributes(hass, connection, msg): connection.send_result(msg[ID], cluster_attributes) +@websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command({ vol.Required(TYPE): 'zha/devices/clusters/commands', - vol.Required(ATTR_IEEE): str, + vol.Required(ATTR_IEEE): convert_ieee, vol.Required(ATTR_ENDPOINT_ID): int, vol.Required(ATTR_CLUSTER_ID): int, vol.Required(ATTR_CLUSTER_TYPE): str @@ -241,10 +300,11 @@ async def websocket_device_cluster_commands(hass, connection, msg): connection.send_result(msg[ID], cluster_commands) +@websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command({ vol.Required(TYPE): 'zha/devices/clusters/attributes/value', - vol.Required(ATTR_IEEE): str, + vol.Required(ATTR_IEEE): convert_ieee, vol.Required(ATTR_ENDPOINT_ID): int, vol.Required(ATTR_CLUSTER_ID): int, vol.Required(ATTR_CLUSTER_TYPE): str, @@ -261,6 +321,8 @@ async def websocket_read_zigbee_cluster_attributes(hass, connection, msg): attribute = msg[ATTR_ATTRIBUTE] manufacturer = msg.get(ATTR_MANUFACTURER) or None zha_device = zha_gateway.get_device(ieee) + if cluster_id >= MFG_CLUSTER_ID_START and manufacturer is None: + manufacturer = zha_device.manufacturer_code success = failure = None if zha_device is not None: cluster = zha_device.async_get_cluster( @@ -283,10 +345,11 @@ async def websocket_read_zigbee_cluster_attributes(hass, connection, msg): connection.send_result(msg[ID], str(success.get(attribute))) +@websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command({ vol.Required(TYPE): 'zha/devices/bindable', - vol.Required(ATTR_IEEE): str, + vol.Required(ATTR_IEEE): convert_ieee, }) async def websocket_get_bindable_devices(hass, connection, msg): """Directly bind devices.""" @@ -311,11 +374,12 @@ async def websocket_get_bindable_devices(hass, connection, msg): )) +@websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command({ vol.Required(TYPE): 'zha/devices/bind', - vol.Required(ATTR_SOURCE_IEEE): str, - vol.Required(ATTR_TARGET_IEEE): str + vol.Required(ATTR_SOURCE_IEEE): convert_ieee, + vol.Required(ATTR_TARGET_IEEE): convert_ieee, }) async def websocket_bind_devices(hass, connection, msg): """Directly bind devices.""" @@ -330,11 +394,12 @@ async def websocket_bind_devices(hass, connection, msg): ) +@websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command({ vol.Required(TYPE): 'zha/devices/unbind', - vol.Required(ATTR_SOURCE_IEEE): str, - vol.Required(ATTR_TARGET_IEEE): str + vol.Required(ATTR_SOURCE_IEEE): convert_ieee, + vol.Required(ATTR_TARGET_IEEE): convert_ieee, }) async def websocket_unbind_devices(hass, connection, msg): """Remove a direct binding between devices.""" @@ -386,27 +451,33 @@ async def async_binding_operation(zha_gateway, source_ieee, target_ieee, await asyncio.gather(*bind_tasks) -def async_load_api(hass, application_controller, zha_gateway): +def async_load_api(hass): """Set up the web socket API.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + application_controller = zha_gateway.application_controller + async def permit(service): """Allow devices to join this network.""" duration = service.data.get(ATTR_DURATION) - _LOGGER.info("Permitting joins for %ss", duration) - await application_controller.permit(duration) + ieee = service.data.get(ATTR_IEEE_ADDRESS) + if ieee: + _LOGGER.info("Permitting joins for %ss on %s device", + duration, ieee) + else: + _LOGGER.info("Permitting joins for %ss", duration) + await application_controller.permit(time_s=duration, node=ieee) - hass.services.async_register(DOMAIN, SERVICE_PERMIT, permit, - schema=SERVICE_SCHEMAS[SERVICE_PERMIT]) + hass.helpers.service.async_register_admin_service( + DOMAIN, SERVICE_PERMIT, permit, schema=SERVICE_SCHEMAS[SERVICE_PERMIT]) async def remove(service): """Remove a node from the network.""" - from bellows.types import EmberEUI64, uint8_t ieee = service.data.get(ATTR_IEEE_ADDRESS) - ieee = EmberEUI64([uint8_t(p, base=16) for p in ieee.split(':')]) _LOGGER.info("Removing node %s", ieee) await application_controller.remove(ieee) - hass.services.async_register(DOMAIN, SERVICE_REMOVE, remove, - schema=SERVICE_SCHEMAS[IEEE_SERVICE]) + hass.helpers.service.async_register_admin_service( + DOMAIN, SERVICE_REMOVE, remove, schema=SERVICE_SCHEMAS[IEEE_SERVICE]) async def set_zigbee_cluster_attributes(service): """Set zigbee attribute for cluster on zha entity.""" @@ -418,6 +489,8 @@ def async_load_api(hass, application_controller, zha_gateway): value = service.data.get(ATTR_VALUE) manufacturer = service.data.get(ATTR_MANUFACTURER) or None zha_device = zha_gateway.get_device(ieee) + if cluster_id >= MFG_CLUSTER_ID_START and manufacturer is None: + manufacturer = zha_device.manufacturer_code response = None if zha_device is not None: response = await zha_device.write_zigbee_attribute( @@ -438,11 +511,12 @@ def async_load_api(hass, application_controller, zha_gateway): "{}: [{}]".format(RESPONSE, response) ) - hass.services.async_register(DOMAIN, SERVICE_SET_ZIGBEE_CLUSTER_ATTRIBUTE, - set_zigbee_cluster_attributes, - schema=SERVICE_SCHEMAS[ - SERVICE_SET_ZIGBEE_CLUSTER_ATTRIBUTE - ]) + hass.helpers.service.async_register_admin_service( + DOMAIN, SERVICE_SET_ZIGBEE_CLUSTER_ATTRIBUTE, + set_zigbee_cluster_attributes, + schema=SERVICE_SCHEMAS[ + SERVICE_SET_ZIGBEE_CLUSTER_ATTRIBUTE + ]) async def issue_zigbee_cluster_command(service): """Issue command on zigbee cluster on zha entity.""" @@ -455,6 +529,8 @@ def async_load_api(hass, application_controller, zha_gateway): args = service.data.get(ATTR_ARGS) manufacturer = service.data.get(ATTR_MANUFACTURER) or None zha_device = zha_gateway.get_device(ieee) + if cluster_id >= MFG_CLUSTER_ID_START and manufacturer is None: + manufacturer = zha_device.manufacturer_code response = None if zha_device is not None: response = await zha_device.issue_cluster_command( @@ -477,12 +553,14 @@ def async_load_api(hass, application_controller, zha_gateway): "{}: [{}]".format(RESPONSE, response) ) - hass.services.async_register(DOMAIN, SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND, - issue_zigbee_cluster_command, - schema=SERVICE_SCHEMAS[ - SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND - ]) + hass.helpers.service.async_register_admin_service( + DOMAIN, SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND, + issue_zigbee_cluster_command, + schema=SERVICE_SCHEMAS[ + SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND + ]) + websocket_api.async_register_command(hass, websocket_permit_devices) websocket_api.async_register_command(hass, websocket_get_devices) websocket_api.async_register_command(hass, websocket_reconfigure_node) websocket_api.async_register_command(hass, websocket_device_clusters) diff --git a/homeassistant/components/zha/const.py b/homeassistant/components/zha/const.py index abcd17a0461..e7cf424990b 100644 --- a/homeassistant/components/zha/const.py +++ b/homeassistant/components/zha/const.py @@ -6,3 +6,4 @@ at https://home-assistant.io/components/fan.zha/ """ # pylint: disable=W0614,W0401 from .core.const import * # noqa: F401,F403 +from .core.registries import * # noqa: F401,F403 diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index 92518bd33ff..10370c42c66 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -15,11 +15,12 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send from ..helpers import ( bind_configure_reporting, construct_unique_id, - safe_read, get_attr_id_by_name) + safe_read, get_attr_id_by_name, bind_cluster) from ..const import ( - CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_DEFAULT, SIGNAL_ATTR_UPDATED, - ATTRIBUTE_CHANNEL, EVENT_RELAY_CHANNEL, ZDO_CHANNEL + REPORT_CONFIG_DEFAULT, SIGNAL_ATTR_UPDATED, ATTRIBUTE_CHANNEL, + EVENT_RELAY_CHANNEL, ZDO_CHANNEL ) +from ..registries import CLUSTER_REPORT_CONFIGS NODE_DESCRIPTOR_REQUEST = 0x0002 MAINS_POWERED = 1 @@ -82,9 +83,14 @@ class ChannelStatus(Enum): class ZigbeeChannel: """Base channel for a Zigbee cluster.""" + CHANNEL_NAME = None + def __init__(self, cluster, device): """Initialize ZigbeeChannel.""" - self.name = 'channel_{}'.format(cluster.cluster_id) + self._channel_name = cluster.ep_attribute + if self.CHANNEL_NAME: + self._channel_name = self.CHANNEL_NAME + self._generic_id = 'channel_0x{:04x}'.format(cluster.cluster_id) self._cluster = cluster self._zha_device = device self._unique_id = construct_unique_id(cluster) @@ -95,6 +101,11 @@ class ZigbeeChannel: self._status = ChannelStatus.CREATED self._cluster.add_listener(self) + @property + def generic_id(self): + """Return the generic id for this channel.""" + return self._generic_id + @property def unique_id(self): """Return the unique id for this channel.""" @@ -110,6 +121,11 @@ class ZigbeeChannel: """Return the device this channel is linked to.""" return self._zha_device + @property + def name(self) -> str: + """Return friendly name.""" + return self._channel_name + @property def status(self): """Return the status of the channel.""" @@ -125,22 +141,24 @@ class ZigbeeChannel: manufacturer_code = self._zha_device.manufacturer_code if self.cluster.cluster_id >= 0xfc00 and manufacturer_code: manufacturer = manufacturer_code - - skip_bind = False # bind cluster only for the 1st configured attr - for report_config in self._report_config: - attr = report_config.get('attr') - min_report_interval, max_report_interval, change = \ - report_config.get('config') - await bind_configure_reporting( - self._unique_id, self.cluster, attr, - min_report=min_report_interval, - max_report=max_report_interval, - reportable_change=change, - skip_bind=skip_bind, - manufacturer=manufacturer - ) - skip_bind = True - await asyncio.sleep(uniform(0.1, 0.5)) + if self.cluster.bind_only: + await bind_cluster(self._unique_id, self.cluster) + else: + skip_bind = False # bind cluster only for the 1st configured attr + for report_config in self._report_config: + attr = report_config.get('attr') + min_report_interval, max_report_interval, change = \ + report_config.get('config') + await bind_configure_reporting( + self._unique_id, self.cluster, attr, + min_report=min_report_interval, + max_report=max_report_interval, + reportable_change=change, + skip_bind=skip_bind, + manufacturer=manufacturer + ) + skip_bind = True + await asyncio.sleep(uniform(0.1, 0.5)) _LOGGER.debug( "%s: finished channel configuration", self._unique_id @@ -214,10 +232,11 @@ class ZigbeeChannel: class AttributeListeningChannel(ZigbeeChannel): """Channel for attribute reports from the cluster.""" + CHANNEL_NAME = ATTRIBUTE_CHANNEL + def __init__(self, cluster, device): """Initialize AttributeListeningChannel.""" super().__init__(cluster, device) - self.name = ATTRIBUTE_CHANNEL attr = self._report_config[0].get('attr') if isinstance(attr, str): self.value_attribute = get_attr_id_by_name(self.cluster, attr) @@ -339,10 +358,7 @@ class ZDOChannel: class EventRelayChannel(ZigbeeChannel): """Event relay that can be attached to zigbee clusters.""" - def __init__(self, cluster, device): - """Initialize EventRelayChannel.""" - super().__init__(cluster, device) - self.name = EVENT_RELAY_CHANNEL + CHANNEL_NAME = EVENT_RELAY_CHANNEL @callback def attribute_updated(self, attrid, value): diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py index cd16fe5d22e..061541d4dae 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -7,12 +7,11 @@ https://home-assistant.io/components/zha/ import logging from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send -from . import ZigbeeChannel, parse_and_log_command +from . import ZigbeeChannel, parse_and_log_command, MAINS_POWERED from ..helpers import get_attr_id_by_name from ..const import ( - SIGNAL_ATTR_UPDATED, - SIGNAL_MOVE_LEVEL, SIGNAL_SET_LEVEL, SIGNAL_STATE_ATTR, BASIC_CHANNEL, - ON_OFF_CHANNEL, LEVEL_CHANNEL, POWER_CONFIGURATION_CHANNEL + SIGNAL_ATTR_UPDATED, SIGNAL_MOVE_LEVEL, SIGNAL_SET_LEVEL, + SIGNAL_STATE_ATTR ) _LOGGER = logging.getLogger(__name__) @@ -26,7 +25,6 @@ class OnOffChannel(ZigbeeChannel): def __init__(self, cluster, device): """Initialize OnOffChannel.""" super().__init__(cluster, device) - self.name = ON_OFF_CHANNEL self._state = None @callback @@ -66,9 +64,14 @@ class OnOffChannel(ZigbeeChannel): async def async_update(self): """Initialize channel.""" - _LOGGER.debug("Attempting to update onoff state") + from_cache = not self.device.power_source == MAINS_POWERED + _LOGGER.debug( + "%s is attempting to update onoff state - from cache: %s", + self._unique_id, + from_cache + ) self._state = bool( - await self.get_attribute_value(self.ON_OFF, from_cache=False)) + await self.get_attribute_value(self.ON_OFF, from_cache=from_cache)) await super().async_update() @@ -77,11 +80,6 @@ class LevelControlChannel(ZigbeeChannel): CURRENT_LEVEL = 0 - def __init__(self, cluster, device): - """Initialize LevelControlChannel.""" - super().__init__(cluster, device) - self.name = LEVEL_CHANNEL - @callback def cluster_command(self, tsn, command_id, args): """Handle commands received to this cluster.""" @@ -149,7 +147,6 @@ class BasicChannel(ZigbeeChannel): def __init__(self, cluster, device): """Initialize BasicChannel.""" super().__init__(cluster, device) - self.name = BASIC_CHANNEL self._power_source = None async def async_configure(self): @@ -171,11 +168,6 @@ class BasicChannel(ZigbeeChannel): class PowerConfigurationChannel(ZigbeeChannel): """Channel for the zigbee power configuration cluster.""" - def __init__(self, cluster, device): - """Initialize PowerConfigurationChannel.""" - super().__init__(cluster, device) - self.name = POWER_CONFIGURATION_CHANNEL - @callback def attribute_updated(self, attrid, value): """Handle attribute updates on this cluster.""" diff --git a/homeassistant/components/zha/core/channels/homeautomation.py b/homeassistant/components/zha/core/channels/homeautomation.py index 2518889fcb1..e4b67dd0db7 100644 --- a/homeassistant/components/zha/core/channels/homeautomation.py +++ b/homeassistant/components/zha/core/channels/homeautomation.py @@ -15,18 +15,15 @@ _LOGGER = logging.getLogger(__name__) class ElectricalMeasurementChannel(AttributeListeningChannel): """Channel that polls active power level.""" - def __init__(self, cluster, device): - """Initialize ElectricalMeasurementChannel.""" - super().__init__(cluster, device) - self.name = ELECTRICAL_MEASUREMENT_CHANNEL + CHANNEL_NAME = ELECTRICAL_MEASUREMENT_CHANNEL async def async_update(self): """Retrieve latest state.""" _LOGGER.debug("%s async_update", self.unique_id) # This is a polling channel. Don't allow cache. - result = await self.get_attribute_value( - ELECTRICAL_MEASUREMENT_CHANNEL, from_cache=False) + result = await self.get_attribute_value('active_power', + from_cache=False) async_dispatcher_send( self._zha_device.hass, "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), @@ -35,6 +32,5 @@ class ElectricalMeasurementChannel(AttributeListeningChannel): async def async_initialize(self, from_cache): """Initialize channel.""" - await self.get_attribute_value( - ELECTRICAL_MEASUREMENT_CHANNEL, from_cache=from_cache) + await self.get_attribute_value('active_power', from_cache=from_cache) await super().async_initialize(from_cache) diff --git a/homeassistant/components/zha/core/channels/hvac.py b/homeassistant/components/zha/core/channels/hvac.py index c62ec66588e..3da881e75d8 100644 --- a/homeassistant/components/zha/core/channels/hvac.py +++ b/homeassistant/components/zha/core/channels/hvac.py @@ -8,7 +8,7 @@ import logging from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send from . import ZigbeeChannel -from ..const import FAN_CHANNEL, SIGNAL_ATTR_UPDATED +from ..const import SIGNAL_ATTR_UPDATED _LOGGER = logging.getLogger(__name__) @@ -18,11 +18,6 @@ class FanChannel(ZigbeeChannel): _value_attribute = 0 - def __init__(self, cluster, device): - """Initialize FanChannel.""" - super().__init__(cluster, device) - self.name = FAN_CHANNEL - async def async_set_speed(self, value) -> None: """Set the speed of the fan.""" from zigpy.exceptions import DeliveryError diff --git a/homeassistant/components/zha/core/channels/lighting.py b/homeassistant/components/zha/core/channels/lighting.py index 9c904a7a001..696a15b483b 100644 --- a/homeassistant/components/zha/core/channels/lighting.py +++ b/homeassistant/components/zha/core/channels/lighting.py @@ -6,7 +6,6 @@ https://home-assistant.io/components/zha/ """ import logging from . import ZigbeeChannel -from ..const import COLOR_CHANNEL _LOGGER = logging.getLogger(__name__) @@ -21,7 +20,6 @@ class ColorChannel(ZigbeeChannel): def __init__(self, cluster, device): """Initialize ColorChannel.""" super().__init__(cluster, device) - self.name = COLOR_CHANNEL self._color_capabilities = None def get_color_capabilities(self): diff --git a/homeassistant/components/zha/core/channels/security.py b/homeassistant/components/zha/core/channels/security.py index e8c0e71a263..03b50b7c7ba 100644 --- a/homeassistant/components/zha/core/channels/security.py +++ b/homeassistant/components/zha/core/channels/security.py @@ -9,7 +9,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send from . import ZigbeeChannel from ..helpers import bind_cluster -from ..const import SIGNAL_ATTR_UPDATED, ZONE_CHANNEL +from ..const import SIGNAL_ATTR_UPDATED _LOGGER = logging.getLogger(__name__) @@ -17,11 +17,6 @@ _LOGGER = logging.getLogger(__name__) class IASZoneChannel(ZigbeeChannel): """Channel for the IASZone Zigbee cluster.""" - def __init__(self, cluster, device): - """Initialize IASZoneChannel.""" - super().__init__(cluster, device) - self.name = ZONE_CHANNEL - @callback def cluster_command(self, tsn, command_id, args): """Handle commands received to this cluster.""" diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 33376b056c6..b7f418253d8 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -1,5 +1,12 @@ """All constants related to the ZHA component.""" import enum +import logging + +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR +from homeassistant.components.fan import DOMAIN as FAN +from homeassistant.components.light import DOMAIN as LIGHT +from homeassistant.components.sensor import DOMAIN as SENSOR +from homeassistant.components.switch import DOMAIN as SWITCH DOMAIN = 'zha' @@ -17,13 +24,13 @@ DATA_ZHA_CORE_EVENTS = 'zha_core_events' DATA_ZHA_GATEWAY = 'zha_gateway' ZHA_DISCOVERY_NEW = 'zha_discovery_new_{}' -COMPONENTS = [ - 'binary_sensor', - 'fan', - 'light', - 'sensor', - 'switch', -] +COMPONENTS = ( + BINARY_SENSOR, + FAN, + LIGHT, + SENSOR, + SWITCH, +) CONF_BAUDRATE = 'baudrate' CONF_DATABASE = 'database_path' @@ -33,6 +40,10 @@ CONF_USB_PATH = 'usb_path' DATA_DEVICE_CONFIG = 'zha_device_config' ENABLE_QUIRKS = 'enable_quirks' +RADIO = 'radio' +RADIO_DESCRIPTION = 'radio_description' +CONTROLLER = 'controller' + DEFAULT_RADIO_TYPE = 'ezsp' DEFAULT_BAUDRATE = 57600 DEFAULT_DATABASE_NAME = 'zigbee.db' @@ -76,12 +87,12 @@ ZDO_CHANNEL = 'zdo' ON_OFF_CHANNEL = 'on_off' ATTRIBUTE_CHANNEL = 'attribute' BASIC_CHANNEL = 'basic' -COLOR_CHANNEL = 'color' +COLOR_CHANNEL = 'light_color' FAN_CHANNEL = 'fan' LEVEL_CHANNEL = ATTR_LEVEL -ZONE_CHANNEL = 'zone' -ELECTRICAL_MEASUREMENT_CHANNEL = 'active_power' -POWER_CONFIGURATION_CHANNEL = 'battery' +ZONE_CHANNEL = 'ias_zone' +ELECTRICAL_MEASUREMENT_CHANNEL = 'electrical_measurement' +POWER_CONFIGURATION_CHANNEL = 'power' EVENT_RELAY_CHANNEL = 'event_relay' SIGNAL_ATTR_UPDATED = 'attribute_updated' @@ -96,6 +107,34 @@ QUIRK_CLASS = 'quirk_class' MANUFACTURER_CODE = 'manufacturer_code' POWER_SOURCE = 'power_source' +BELLOWS = 'bellows' +ZHA = 'homeassistant.components.zha' +ZIGPY = 'zigpy' +ZIGPY_XBEE = 'zigpy_xbee' +ZIGPY_DECONZ = 'zigpy_deconz' +ORIGINAL = 'original' +CURRENT = 'current' +DEBUG_LEVELS = { + BELLOWS: logging.DEBUG, + ZHA: logging.DEBUG, + ZIGPY: logging.DEBUG, + ZIGPY_XBEE: logging.DEBUG, + ZIGPY_DECONZ: logging.DEBUG, +} +ADD_DEVICE_RELAY_LOGGERS = [ZHA, ZIGPY] +TYPE = 'type' +NWK = 'nwk' +SIGNATURE = 'signature' +RAW_INIT = 'raw_device_initialized' +ZHA_GW_MSG = 'zha_gateway_message' +DEVICE_REMOVED = 'device_removed' +DEVICE_INFO = 'device_info' +DEVICE_FULL_INIT = 'device_fully_initialized' +DEVICE_JOINED = 'device_joined' +LOG_OUTPUT = 'log_output' +LOG_ENTRY = 'log_entry' +MFG_CLUSTER_ID_START = 0xfc00 + class RadioType(enum.Enum): """Possible options for radio type.""" @@ -111,15 +150,6 @@ class RadioType(enum.Enum): DISCOVERY_KEY = 'zha_discovery_info' -DEVICE_CLASS = {} -SINGLE_INPUT_CLUSTER_DEVICE_CLASS = {} -SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS = {} -CLUSTER_REPORT_CONFIGS = {} -CUSTOM_CLUSTER_MAPPINGS = {} -COMPONENT_CLUSTERS = {} -EVENT_RELAY_CLUSTERS = [] -NO_SENSOR_CLUSTERS = [] -BINDABLE_CLUSTERS = [] REPORT_CONFIG_MAX_INT = 900 REPORT_CONFIG_MAX_INT_BATTERY_SAVE = 10800 @@ -134,7 +164,7 @@ REPORT_CONFIG_DEFAULT = (REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, REPORT_CONFIG_ASAP = (REPORT_CONFIG_MIN_INT_ASAP, REPORT_CONFIG_MAX_INT, REPORT_CONFIG_RPT_CHANGE) REPORT_CONFIG_BATTERY_SAVE = (REPORT_CONFIG_MIN_INT_BATTERY_SAVE, - REPORT_CONFIG_MAX_INT, + REPORT_CONFIG_MAX_INT_BATTERY_SAVE, REPORT_CONFIG_RPT_CHANGE) REPORT_CONFIG_IMMEDIATE = (REPORT_CONFIG_MIN_INT_IMMEDIATE, REPORT_CONFIG_MAX_INT, diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 0ddb67484c6..435ab25acc6 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -49,7 +49,7 @@ class ZHADevice: self._zha_gateway = zha_gateway self.cluster_channels = {} self._relay_channels = {} - self._all_channels = {} + self._all_channels = [] self._name = "{} {}".format( self.manufacturer, self.model @@ -135,7 +135,7 @@ class ZHADevice: @property def all_channels(self): """Return cluster channels and relay channels for device.""" - return self._all_channels.values() + return self._all_channels @property def available_signal(self): @@ -195,10 +195,10 @@ class ZHADevice: if isinstance(cluster_channel, EventRelayChannel): self._relay_channels[cluster_channel.unique_id] = cluster_channel - self._all_channels[cluster_channel.unique_id] = cluster_channel + self._all_channels.append(cluster_channel) else: self.cluster_channels[cluster_channel.name] = cluster_channel - self._all_channels[cluster_channel.name] = cluster_channel + self._all_channels.append(cluster_channel) async def async_configure(self): """Configure the device.""" diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py new file mode 100644 index 00000000000..f5bd6ee99f2 --- /dev/null +++ b/homeassistant/components/zha/core/discovery.py @@ -0,0 +1,266 @@ +""" +Device discovery functions for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" + +import logging + +from homeassistant import const as ha_const +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_send +from .channels import ( + AttributeListeningChannel, EventRelayChannel, ZDOChannel +) +from .channels.registry import ZIGBEE_CHANNEL_REGISTRY +from .const import ( + CONF_DEVICE_CONFIG, COMPONENTS, ZHA_DISCOVERY_NEW, DATA_ZHA, + SENSOR_TYPE, UNKNOWN, GENERIC, POWER_CONFIGURATION_CHANNEL +) +from .registries import ( + BINARY_SENSOR_TYPES, NO_SENSOR_CLUSTERS, EVENT_RELAY_CLUSTERS, + SENSOR_TYPES, DEVICE_CLASS, COMPONENT_CLUSTERS, + SINGLE_INPUT_CLUSTER_DEVICE_CLASS, SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS +) +from ..device_entity import ZhaDeviceEntity + +_LOGGER = logging.getLogger(__name__) + + +@callback +def async_process_endpoint( + hass, config, endpoint_id, endpoint, discovery_infos, device, + zha_device, is_new_join): + """Process an endpoint on a zigpy device.""" + import zigpy.profiles + + if endpoint_id == 0: # ZDO + _async_create_cluster_channel( + endpoint, + zha_device, + is_new_join, + channel_class=ZDOChannel + ) + return + + component = None + profile_clusters = ([], []) + device_key = "{}-{}".format(device.ieee, endpoint_id) + node_config = {} + if CONF_DEVICE_CONFIG in config: + node_config = config[CONF_DEVICE_CONFIG].get( + device_key, {} + ) + + if endpoint.profile_id in zigpy.profiles.PROFILES: + profile = zigpy.profiles.PROFILES[endpoint.profile_id] + if DEVICE_CLASS.get(endpoint.profile_id, {}).get( + endpoint.device_type, None): + profile_clusters = profile.CLUSTERS[endpoint.device_type] + profile_info = DEVICE_CLASS[endpoint.profile_id] + component = profile_info[endpoint.device_type] + + if ha_const.CONF_TYPE in node_config: + component = node_config[ha_const.CONF_TYPE] + profile_clusters = COMPONENT_CLUSTERS[component] + + if component and component in COMPONENTS: + profile_match = _async_handle_profile_match( + hass, endpoint, profile_clusters, zha_device, + component, device_key, is_new_join) + discovery_infos.append(profile_match) + + discovery_infos.extend(_async_handle_single_cluster_matches( + hass, + endpoint, + zha_device, + profile_clusters, + device_key, + is_new_join + )) + + +@callback +def _async_create_cluster_channel(cluster, zha_device, is_new_join, + channels=None, channel_class=None): + """Create a cluster channel and attach it to a device.""" + if channel_class is None: + channel_class = ZIGBEE_CHANNEL_REGISTRY.get(cluster.cluster_id, + AttributeListeningChannel) + channel = channel_class(cluster, zha_device) + zha_device.add_cluster_channel(channel) + if channels is not None: + channels.append(channel) + + +@callback +def async_dispatch_discovery_info(hass, is_new_join, discovery_info): + """Dispatch or store discovery information.""" + if not discovery_info['channels']: + _LOGGER.warning( + "there are no channels in the discovery info: %s", discovery_info) + return + component = discovery_info['component'] + if is_new_join: + async_dispatcher_send( + hass, + ZHA_DISCOVERY_NEW.format(component), + discovery_info + ) + else: + hass.data[DATA_ZHA][component][discovery_info['unique_id']] = \ + discovery_info + + +@callback +def _async_handle_profile_match(hass, endpoint, profile_clusters, zha_device, + component, device_key, is_new_join): + """Dispatch a profile match to the appropriate HA component.""" + in_clusters = [endpoint.in_clusters[c] + for c in profile_clusters[0] + if c in endpoint.in_clusters] + out_clusters = [endpoint.out_clusters[c] + for c in profile_clusters[1] + if c in endpoint.out_clusters] + + channels = [] + + for cluster in in_clusters: + _async_create_cluster_channel( + cluster, zha_device, is_new_join, channels=channels) + + for cluster in out_clusters: + _async_create_cluster_channel( + cluster, zha_device, is_new_join, channels=channels) + + discovery_info = { + 'unique_id': device_key, + 'zha_device': zha_device, + 'channels': channels, + 'component': component + } + + if component == 'binary_sensor': + discovery_info.update({SENSOR_TYPE: UNKNOWN}) + cluster_ids = [] + cluster_ids.extend(profile_clusters[0]) + cluster_ids.extend(profile_clusters[1]) + for cluster_id in cluster_ids: + if cluster_id in BINARY_SENSOR_TYPES: + discovery_info.update({ + SENSOR_TYPE: BINARY_SENSOR_TYPES.get( + cluster_id, UNKNOWN) + }) + break + + return discovery_info + + +@callback +def _async_handle_single_cluster_matches(hass, endpoint, zha_device, + profile_clusters, device_key, + is_new_join): + """Dispatch single cluster matches to HA components.""" + cluster_matches = [] + cluster_match_results = [] + for cluster in endpoint.in_clusters.values(): + # don't let profiles prevent these channels from being created + if cluster.cluster_id in NO_SENSOR_CLUSTERS: + cluster_match_results.append( + _async_handle_channel_only_cluster_match( + zha_device, + cluster, + is_new_join, + )) + + if cluster.cluster_id not in profile_clusters[0]: + cluster_match_results.append(_async_handle_single_cluster_match( + hass, + zha_device, + cluster, + device_key, + SINGLE_INPUT_CLUSTER_DEVICE_CLASS, + is_new_join, + )) + + for cluster in endpoint.out_clusters.values(): + if cluster.cluster_id not in profile_clusters[1]: + cluster_match_results.append(_async_handle_single_cluster_match( + hass, + zha_device, + cluster, + device_key, + SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, + is_new_join, + )) + + if cluster.cluster_id in EVENT_RELAY_CLUSTERS: + _async_create_cluster_channel( + cluster, + zha_device, + is_new_join, + channel_class=EventRelayChannel + ) + + for cluster_match in cluster_match_results: + if cluster_match is not None: + cluster_matches.append(cluster_match) + return cluster_matches + + +@callback +def _async_handle_channel_only_cluster_match( + zha_device, cluster, is_new_join): + """Handle a channel only cluster match.""" + _async_create_cluster_channel(cluster, zha_device, is_new_join) + + +@callback +def _async_handle_single_cluster_match(hass, zha_device, cluster, device_key, + device_classes, is_new_join): + """Dispatch a single cluster match to a HA component.""" + component = None # sub_component = None + for cluster_type, candidate_component in device_classes.items(): + if isinstance(cluster_type, int): + if cluster.cluster_id == cluster_type: + component = candidate_component + elif isinstance(cluster, cluster_type): + component = candidate_component + break + + if component is None or component not in COMPONENTS: + return + channels = [] + _async_create_cluster_channel(cluster, zha_device, is_new_join, + channels=channels) + + cluster_key = "{}-{}".format(device_key, cluster.cluster_id) + discovery_info = { + 'unique_id': cluster_key, + 'zha_device': zha_device, + 'channels': channels, + 'entity_suffix': '_{}'.format(cluster.cluster_id), + 'component': component + } + + if component == 'sensor': + discovery_info.update({ + SENSOR_TYPE: SENSOR_TYPES.get(cluster.cluster_id, GENERIC) + }) + if component == 'binary_sensor': + discovery_info.update({ + SENSOR_TYPE: BINARY_SENSOR_TYPES.get(cluster.cluster_id, UNKNOWN) + }) + + return discovery_info + + +@callback +def async_create_device_entity(zha_device): + """Create ZHADeviceEntity.""" + device_entity_channels = [] + if POWER_CONFIGURATION_CHANNEL in zha_device.cluster_channels: + channel = zha_device.cluster_channels.get(POWER_CONFIGURATION_CHANNEL) + device_entity_channels.append(channel) + return ZhaDeviceEntity(zha_device, device_entity_channels) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 8a925ddfda4..71e41c2509b 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -5,39 +5,39 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/zha/ """ +import asyncio import collections import itertools import logging -from homeassistant import const as ha_const +import os +import traceback + +from homeassistant.components.system_log import LogEntry, _figure_out_source from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity_component import EntityComponent -from . import const as zha_const + +from ..api import async_get_device_info +from .channels import MAINS_POWERED, ZDOChannel from .const import ( - COMPONENTS, CONF_DEVICE_CONFIG, DATA_ZHA, DATA_ZHA_CORE_COMPONENT, DOMAIN, - ZHA_DISCOVERY_NEW, DEVICE_CLASS, SINGLE_INPUT_CLUSTER_DEVICE_CLASS, - SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, COMPONENT_CLUSTERS, HUMIDITY, - TEMPERATURE, ILLUMINANCE, PRESSURE, METERING, ELECTRICAL_MEASUREMENT, - GENERIC, SENSOR_TYPE, EVENT_RELAY_CLUSTERS, UNKNOWN, OPENING, ZONE, - OCCUPANCY, CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_IMMEDIATE, - REPORT_CONFIG_ASAP, REPORT_CONFIG_DEFAULT, REPORT_CONFIG_MIN_INT, - REPORT_CONFIG_MAX_INT, REPORT_CONFIG_OP, SIGNAL_REMOVE, - NO_SENSOR_CLUSTERS, POWER_CONFIGURATION_CHANNEL, BINDABLE_CLUSTERS, - DATA_ZHA_GATEWAY, ACCELERATION) -from .device import ZHADevice, DeviceStatus -from ..device_entity import ZhaDeviceEntity -from .channels import ( - AttributeListeningChannel, EventRelayChannel, ZDOChannel, MAINS_POWERED -) -from .channels.registry import ZIGBEE_CHANNEL_REGISTRY -from .helpers import convert_ieee + ADD_DEVICE_RELAY_LOGGERS, ATTR_MANUFACTURER, BELLOWS, CONF_BAUDRATE, + CONF_DATABASE, CONF_RADIO_TYPE, CONF_USB_PATH, CONTROLLER, CURRENT, + DATA_ZHA, DATA_ZHA_BRIDGE_ID, DATA_ZHA_CORE_COMPONENT, DATA_ZHA_GATEWAY, + DATA_ZHA_RADIO, DEBUG_LEVELS, DEFAULT_BAUDRATE, DEFAULT_DATABASE_NAME, + DEVICE_FULL_INIT, DEVICE_INFO, DEVICE_JOINED, DEVICE_REMOVED, DOMAIN, IEEE, + LOG_ENTRY, LOG_OUTPUT, MODEL, NWK, ORIGINAL, RADIO, RADIO_DESCRIPTION, + RAW_INIT, SIGNAL_REMOVE, SIGNATURE, TYPE, ZHA, ZHA_GW_MSG, ZIGPY, + ZIGPY_DECONZ, ZIGPY_XBEE) +from .device import DeviceStatus, ZHADevice +from .discovery import ( + async_create_device_entity, async_dispatch_discovery_info, + async_process_endpoint) +from .patches import apply_application_controller_patch +from .registries import RADIO_TYPES +from .store import async_get_registry _LOGGER = logging.getLogger(__name__) -SENSOR_TYPES = {} -BINARY_SENSOR_TYPES = {} -SMARTTHINGS_HUMIDITY_CLUSTER = 64581 -SMARTTHINGS_ACCELERATION_CLUSTER = 64514 EntityReference = collections.namedtuple( 'EntityReference', 'reference_id zha_device cluster_channels device_info') @@ -45,16 +45,57 @@ EntityReference = collections.namedtuple( class ZHAGateway: """Gateway that handles events that happen on the ZHA Zigbee network.""" - def __init__(self, hass, config, zha_storage): + def __init__(self, hass, config): """Initialize the gateway.""" self._hass = hass self._config = config self._component = EntityComponent(_LOGGER, DOMAIN, hass) self._devices = {} self._device_registry = collections.defaultdict(list) - self.zha_storage = zha_storage + self.zha_storage = None + self.application_controller = None + self.radio_description = None hass.data[DATA_ZHA][DATA_ZHA_CORE_COMPONENT] = self._component hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self + self._log_levels = { + ORIGINAL: async_capture_log_levels(), + CURRENT: async_capture_log_levels() + } + self.debug_enabled = False + self._log_relay_handler = LogRelayHandler(hass, self) + + async def async_initialize(self, config_entry): + """Initialize controller and connect radio.""" + self.zha_storage = await async_get_registry(self._hass) + + usb_path = config_entry.data.get(CONF_USB_PATH) + baudrate = self._config.get(CONF_BAUDRATE, DEFAULT_BAUDRATE) + radio_type = config_entry.data.get(CONF_RADIO_TYPE) + + radio_details = RADIO_TYPES[radio_type][RADIO]() + radio = radio_details[RADIO] + self.radio_description = RADIO_TYPES[radio_type][RADIO_DESCRIPTION] + await radio.connect(usb_path, baudrate) + self._hass.data[DATA_ZHA][DATA_ZHA_RADIO] = radio + + if CONF_DATABASE in self._config: + database = self._config[CONF_DATABASE] + else: + database = os.path.join( + self._hass.config.config_dir, DEFAULT_DATABASE_NAME) + + self.application_controller = radio_details[CONTROLLER]( + radio, database) + apply_application_controller_patch(self) + self.application_controller.add_listener(self) + await self.application_controller.startup(auto_form=True) + self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str( + self.application_controller.ieee) + + init_tasks = [] + for device in self.application_controller.devices.values(): + init_tasks.append(self.async_device_initialized(device, False)) + await asyncio.gather(*init_tasks) def device_joined(self, device): """Handle device joined. @@ -62,13 +103,37 @@ class ZHAGateway: At this point, no information about the device is known other than its address """ - # Wait for device_initialized, instead - pass + async_dispatcher_send( + self._hass, + ZHA_GW_MSG, + { + TYPE: DEVICE_JOINED, + NWK: device.nwk, + IEEE: str(device.ieee) + } + ) def raw_device_initialized(self, device): """Handle a device initialization without quirks loaded.""" - # Wait for device_initialized, instead - pass + endpoint_ids = device.endpoints.keys() + ept_id = next((ept_id for ept_id in endpoint_ids if ept_id != 0), None) + manufacturer = 'Unknown' + model = 'Unknown' + if ept_id is not None: + manufacturer = device.endpoints[ept_id].manufacturer + model = device.endpoints[ept_id].model + async_dispatcher_send( + self._hass, + ZHA_GW_MSG, + { + TYPE: RAW_INIT, + NWK: device.nwk, + IEEE: str(device.ieee), + MODEL: model, + ATTR_MANUFACTURER: manufacturer, + SIGNATURE: device.get_signature() + } + ) def device_initialized(self, device): """Handle device joined and basic information discovered.""" @@ -84,15 +149,24 @@ class ZHAGateway: device = self._devices.pop(device.ieee, None) self._device_registry.pop(device.ieee, None) if device is not None: + device_info = async_get_device_info(self._hass, device) self._hass.async_create_task(device.async_unsub_dispatcher()) async_dispatcher_send( self._hass, "{}_{}".format(SIGNAL_REMOVE, str(device.ieee)) ) + if device_info is not None: + async_dispatcher_send( + self._hass, + ZHA_GW_MSG, + { + TYPE: DEVICE_REMOVED, + DEVICE_INFO: device_info + } + ) - def get_device(self, ieee_str): + def get_device(self, ieee): """Return ZHADevice for given ieee.""" - ieee = convert_ieee(ieee_str) return self._devices.get(ieee) def get_entity_reference(self, entity_id): @@ -125,6 +199,28 @@ class ZHAGateway: ) ) + @callback + def async_enable_debug_mode(self): + """Enable debug mode for ZHA.""" + self._log_levels[ORIGINAL] = async_capture_log_levels() + async_set_logger_levels(DEBUG_LEVELS) + self._log_levels[CURRENT] = async_capture_log_levels() + + for logger_name in ADD_DEVICE_RELAY_LOGGERS: + logging.getLogger(logger_name).addHandler(self._log_relay_handler) + + self.debug_enabled = True + + @callback + def async_disable_debug_mode(self): + """Disable debug mode for ZHA.""" + async_set_logger_levels(self._log_levels[ORIGINAL]) + self._log_levels[CURRENT] = async_capture_log_levels() + for logger_name in ADD_DEVICE_RELAY_LOGGERS: + logging.getLogger(logger_name).removeHandler( + self._log_relay_handler) + self.debug_enabled = False + @callback def _async_get_or_create_device(self, zigpy_device, is_new_join): """Get or create a ZHA device.""" @@ -166,10 +262,15 @@ class ZHAGateway: discovery_infos = [] for endpoint_id, endpoint in device.endpoints.items(): - self._async_process_endpoint( - endpoint_id, endpoint, discovery_infos, device, zha_device, - is_new_join + async_process_endpoint( + self._hass, self._config, endpoint_id, endpoint, + discovery_infos, device, zha_device, is_new_join ) + if endpoint_id != 0: + for cluster in endpoint.in_clusters.values(): + cluster.bind_only = False + for cluster in endpoint.out_clusters.values(): + cluster.bind_only = True if is_new_join: # configure the device @@ -191,459 +292,72 @@ class ZHAGateway: await zha_device.async_initialize(from_cache=True) for discovery_info in discovery_infos: - _async_dispatch_discovery_info( + async_dispatch_discovery_info( self._hass, is_new_join, discovery_info ) - device_entity = _async_create_device_entity(zha_device) + device_entity = async_create_device_entity(zha_device) await self._component.async_add_entities([device_entity]) - @callback - def _async_process_endpoint( - self, endpoint_id, endpoint, discovery_infos, device, zha_device, - is_new_join): - """Process an endpoint on a zigpy device.""" - import zigpy.profiles - - if endpoint_id == 0: # ZDO - _async_create_cluster_channel( - endpoint, - zha_device, - is_new_join, - channel_class=ZDOChannel + if is_new_join: + device_info = async_get_device_info(self._hass, zha_device) + async_dispatcher_send( + self._hass, + ZHA_GW_MSG, + { + TYPE: DEVICE_FULL_INIT, + DEVICE_INFO: device_info + } ) - return - - component = None - profile_clusters = ([], []) - device_key = "{}-{}".format(device.ieee, endpoint_id) - node_config = {} - if CONF_DEVICE_CONFIG in self._config: - node_config = self._config[CONF_DEVICE_CONFIG].get( - device_key, {} - ) - - if endpoint.profile_id in zigpy.profiles.PROFILES: - profile = zigpy.profiles.PROFILES[endpoint.profile_id] - if zha_const.DEVICE_CLASS.get(endpoint.profile_id, - {}).get(endpoint.device_type, - None): - profile_clusters = profile.CLUSTERS[endpoint.device_type] - profile_info = zha_const.DEVICE_CLASS[endpoint.profile_id] - component = profile_info[endpoint.device_type] - - if ha_const.CONF_TYPE in node_config: - component = node_config[ha_const.CONF_TYPE] - profile_clusters = zha_const.COMPONENT_CLUSTERS[component] - - if component and component in COMPONENTS: - profile_match = _async_handle_profile_match( - self._hass, endpoint, profile_clusters, zha_device, - component, device_key, is_new_join) - discovery_infos.append(profile_match) - - discovery_infos.extend(_async_handle_single_cluster_matches( - self._hass, - endpoint, - zha_device, - profile_clusters, - device_key, - is_new_join - )) @callback -def _async_create_cluster_channel(cluster, zha_device, is_new_join, - channels=None, channel_class=None): - """Create a cluster channel and attach it to a device.""" - if channel_class is None: - channel_class = ZIGBEE_CHANNEL_REGISTRY.get(cluster.cluster_id, - AttributeListeningChannel) - channel = channel_class(cluster, zha_device) - zha_device.add_cluster_channel(channel) - if channels is not None: - channels.append(channel) +def async_capture_log_levels(): + """Capture current logger levels for ZHA.""" + return { + BELLOWS: logging.getLogger(BELLOWS).getEffectiveLevel(), + ZHA: logging.getLogger(ZHA).getEffectiveLevel(), + ZIGPY: logging.getLogger(ZIGPY).getEffectiveLevel(), + ZIGPY_XBEE: logging.getLogger(ZIGPY_XBEE).getEffectiveLevel(), + ZIGPY_DECONZ: logging.getLogger(ZIGPY_DECONZ).getEffectiveLevel(), + } @callback -def _async_dispatch_discovery_info(hass, is_new_join, discovery_info): - """Dispatch or store discovery information.""" - if not discovery_info['channels']: - _LOGGER.warning( - "there are no channels in the discovery info: %s", discovery_info) - return - component = discovery_info['component'] - if is_new_join: +def async_set_logger_levels(levels): + """Set logger levels for ZHA.""" + logging.getLogger(BELLOWS).setLevel(levels[BELLOWS]) + logging.getLogger(ZHA).setLevel(levels[ZHA]) + logging.getLogger(ZIGPY).setLevel(levels[ZIGPY]) + logging.getLogger(ZIGPY_XBEE).setLevel(levels[ZIGPY_XBEE]) + logging.getLogger(ZIGPY_DECONZ).setLevel(levels[ZIGPY_DECONZ]) + + +class LogRelayHandler(logging.Handler): + """Log handler for error messages.""" + + def __init__(self, hass, gateway): + """Initialize a new LogErrorHandler.""" + super().__init__() + self.hass = hass + self.gateway = gateway + + def emit(self, record): + """Relay log message via dispatcher.""" + stack = [] + if record.levelno >= logging.WARN: + if not record.exc_info: + stack = [f for f, _, _, _ in traceback.extract_stack()] + + entry = LogEntry(record, stack, + _figure_out_source(record, stack, self.hass)) async_dispatcher_send( - hass, - ZHA_DISCOVERY_NEW.format(component), - discovery_info + self.hass, + ZHA_GW_MSG, + { + TYPE: LOG_OUTPUT, + LOG_ENTRY: entry.to_dict() + } ) - else: - hass.data[DATA_ZHA][component][discovery_info['unique_id']] = \ - discovery_info - - -@callback -def _async_handle_profile_match(hass, endpoint, profile_clusters, zha_device, - component, device_key, is_new_join): - """Dispatch a profile match to the appropriate HA component.""" - in_clusters = [endpoint.in_clusters[c] - for c in profile_clusters[0] - if c in endpoint.in_clusters] - out_clusters = [endpoint.out_clusters[c] - for c in profile_clusters[1] - if c in endpoint.out_clusters] - - channels = [] - - for cluster in in_clusters: - _async_create_cluster_channel( - cluster, zha_device, is_new_join, channels=channels) - - for cluster in out_clusters: - _async_create_cluster_channel( - cluster, zha_device, is_new_join, channels=channels) - - discovery_info = { - 'unique_id': device_key, - 'zha_device': zha_device, - 'channels': channels, - 'component': component - } - - if component == 'binary_sensor': - discovery_info.update({SENSOR_TYPE: UNKNOWN}) - cluster_ids = [] - cluster_ids.extend(profile_clusters[0]) - cluster_ids.extend(profile_clusters[1]) - for cluster_id in cluster_ids: - if cluster_id in BINARY_SENSOR_TYPES: - discovery_info.update({ - SENSOR_TYPE: BINARY_SENSOR_TYPES.get( - cluster_id, UNKNOWN) - }) - break - - return discovery_info - - -@callback -def _async_handle_single_cluster_matches(hass, endpoint, zha_device, - profile_clusters, device_key, - is_new_join): - """Dispatch single cluster matches to HA components.""" - cluster_matches = [] - cluster_match_results = [] - for cluster in endpoint.in_clusters.values(): - # don't let profiles prevent these channels from being created - if cluster.cluster_id in NO_SENSOR_CLUSTERS: - cluster_match_results.append( - _async_handle_channel_only_cluster_match( - zha_device, - cluster, - is_new_join, - )) - - if cluster.cluster_id not in profile_clusters[0]: - cluster_match_results.append(_async_handle_single_cluster_match( - hass, - zha_device, - cluster, - device_key, - zha_const.SINGLE_INPUT_CLUSTER_DEVICE_CLASS, - is_new_join, - )) - - for cluster in endpoint.out_clusters.values(): - if cluster.cluster_id not in profile_clusters[1]: - cluster_match_results.append(_async_handle_single_cluster_match( - hass, - zha_device, - cluster, - device_key, - zha_const.SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, - is_new_join, - )) - - if cluster.cluster_id in EVENT_RELAY_CLUSTERS: - _async_create_cluster_channel( - cluster, - zha_device, - is_new_join, - channel_class=EventRelayChannel - ) - - for cluster_match in cluster_match_results: - if cluster_match is not None: - cluster_matches.append(cluster_match) - return cluster_matches - - -@callback -def _async_handle_channel_only_cluster_match( - zha_device, cluster, is_new_join): - """Handle a channel only cluster match.""" - _async_create_cluster_channel(cluster, zha_device, is_new_join) - - -@callback -def _async_handle_single_cluster_match(hass, zha_device, cluster, device_key, - device_classes, is_new_join): - """Dispatch a single cluster match to a HA component.""" - component = None # sub_component = None - for cluster_type, candidate_component in device_classes.items(): - if isinstance(cluster_type, int): - if cluster.cluster_id == cluster_type: - component = candidate_component - elif isinstance(cluster, cluster_type): - component = candidate_component - break - - if component is None or component not in COMPONENTS: - return - channels = [] - _async_create_cluster_channel(cluster, zha_device, is_new_join, - channels=channels) - - cluster_key = "{}-{}".format(device_key, cluster.cluster_id) - discovery_info = { - 'unique_id': cluster_key, - 'zha_device': zha_device, - 'channels': channels, - 'entity_suffix': '_{}'.format(cluster.cluster_id), - 'component': component - } - - if component == 'sensor': - discovery_info.update({ - SENSOR_TYPE: SENSOR_TYPES.get(cluster.cluster_id, GENERIC) - }) - if component == 'binary_sensor': - discovery_info.update({ - SENSOR_TYPE: BINARY_SENSOR_TYPES.get(cluster.cluster_id, UNKNOWN) - }) - - return discovery_info - - -@callback -def _async_create_device_entity(zha_device): - """Create ZHADeviceEntity.""" - device_entity_channels = [] - if POWER_CONFIGURATION_CHANNEL in zha_device.cluster_channels: - channel = zha_device.cluster_channels.get(POWER_CONFIGURATION_CHANNEL) - device_entity_channels.append(channel) - return ZhaDeviceEntity(zha_device, device_entity_channels) - - -def establish_device_mappings(): - """Establish mappings between ZCL objects and HA ZHA objects. - - These cannot be module level, as importing bellows must be done in a - in a function. - """ - from zigpy import zcl - from zigpy.profiles import PROFILES, zha, zll - - if zha.PROFILE_ID not in DEVICE_CLASS: - DEVICE_CLASS[zha.PROFILE_ID] = {} - if zll.PROFILE_ID not in DEVICE_CLASS: - DEVICE_CLASS[zll.PROFILE_ID] = {} - - EVENT_RELAY_CLUSTERS.append(zcl.clusters.general.LevelControl.cluster_id) - EVENT_RELAY_CLUSTERS.append(zcl.clusters.general.OnOff.cluster_id) - - NO_SENSOR_CLUSTERS.append(zcl.clusters.general.Basic.cluster_id) - NO_SENSOR_CLUSTERS.append( - zcl.clusters.general.PowerConfiguration.cluster_id) - NO_SENSOR_CLUSTERS.append(zcl.clusters.lightlink.LightLink.cluster_id) - - BINDABLE_CLUSTERS.append(zcl.clusters.general.LevelControl.cluster_id) - BINDABLE_CLUSTERS.append(zcl.clusters.general.OnOff.cluster_id) - BINDABLE_CLUSTERS.append(zcl.clusters.lighting.Color.cluster_id) - - DEVICE_CLASS[zha.PROFILE_ID].update({ - zha.DeviceType.ON_OFF_SWITCH: 'binary_sensor', - zha.DeviceType.LEVEL_CONTROL_SWITCH: 'binary_sensor', - zha.DeviceType.REMOTE_CONTROL: 'binary_sensor', - zha.DeviceType.SMART_PLUG: 'switch', - zha.DeviceType.LEVEL_CONTROLLABLE_OUTPUT: 'light', - zha.DeviceType.ON_OFF_LIGHT: 'light', - zha.DeviceType.DIMMABLE_LIGHT: 'light', - zha.DeviceType.COLOR_DIMMABLE_LIGHT: 'light', - zha.DeviceType.ON_OFF_LIGHT_SWITCH: 'binary_sensor', - zha.DeviceType.DIMMER_SWITCH: 'binary_sensor', - zha.DeviceType.COLOR_DIMMER_SWITCH: 'binary_sensor', - }) - - DEVICE_CLASS[zll.PROFILE_ID].update({ - zll.DeviceType.ON_OFF_LIGHT: 'light', - zll.DeviceType.ON_OFF_PLUGIN_UNIT: 'switch', - zll.DeviceType.DIMMABLE_LIGHT: 'light', - zll.DeviceType.DIMMABLE_PLUGIN_UNIT: 'light', - zll.DeviceType.COLOR_LIGHT: 'light', - zll.DeviceType.EXTENDED_COLOR_LIGHT: 'light', - zll.DeviceType.COLOR_TEMPERATURE_LIGHT: 'light', - zll.DeviceType.COLOR_CONTROLLER: 'binary_sensor', - zll.DeviceType.COLOR_SCENE_CONTROLLER: 'binary_sensor', - zll.DeviceType.CONTROLLER: 'binary_sensor', - zll.DeviceType.SCENE_CONTROLLER: 'binary_sensor', - zll.DeviceType.ON_OFF_SENSOR: 'binary_sensor', - }) - - SINGLE_INPUT_CLUSTER_DEVICE_CLASS.update({ - zcl.clusters.general.OnOff: 'switch', - zcl.clusters.measurement.RelativeHumidity: 'sensor', - # this works for now but if we hit conflicts we can break it out to - # a different dict that is keyed by manufacturer - SMARTTHINGS_HUMIDITY_CLUSTER: 'sensor', - zcl.clusters.measurement.TemperatureMeasurement: 'sensor', - zcl.clusters.measurement.PressureMeasurement: 'sensor', - zcl.clusters.measurement.IlluminanceMeasurement: 'sensor', - zcl.clusters.smartenergy.Metering: 'sensor', - zcl.clusters.homeautomation.ElectricalMeasurement: 'sensor', - zcl.clusters.security.IasZone: 'binary_sensor', - zcl.clusters.measurement.OccupancySensing: 'binary_sensor', - zcl.clusters.hvac.Fan: 'fan', - SMARTTHINGS_ACCELERATION_CLUSTER: 'binary_sensor', - }) - - SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS.update({ - zcl.clusters.general.OnOff: 'binary_sensor', - }) - - SENSOR_TYPES.update({ - zcl.clusters.measurement.RelativeHumidity.cluster_id: HUMIDITY, - SMARTTHINGS_HUMIDITY_CLUSTER: HUMIDITY, - zcl.clusters.measurement.TemperatureMeasurement.cluster_id: - TEMPERATURE, - zcl.clusters.measurement.PressureMeasurement.cluster_id: PRESSURE, - zcl.clusters.measurement.IlluminanceMeasurement.cluster_id: - ILLUMINANCE, - zcl.clusters.smartenergy.Metering.cluster_id: METERING, - zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: - ELECTRICAL_MEASUREMENT, - }) - - BINARY_SENSOR_TYPES.update({ - zcl.clusters.measurement.OccupancySensing.cluster_id: OCCUPANCY, - zcl.clusters.security.IasZone.cluster_id: ZONE, - zcl.clusters.general.OnOff.cluster_id: OPENING, - SMARTTHINGS_ACCELERATION_CLUSTER: ACCELERATION, - }) - - CLUSTER_REPORT_CONFIGS.update({ - zcl.clusters.general.Alarms.cluster_id: [], - zcl.clusters.general.Basic.cluster_id: [], - zcl.clusters.general.Commissioning.cluster_id: [], - zcl.clusters.general.Identify.cluster_id: [], - zcl.clusters.general.Groups.cluster_id: [], - zcl.clusters.general.Scenes.cluster_id: [], - zcl.clusters.general.Partition.cluster_id: [], - zcl.clusters.general.Ota.cluster_id: [], - zcl.clusters.general.PowerProfile.cluster_id: [], - zcl.clusters.general.ApplianceControl.cluster_id: [], - zcl.clusters.general.PollControl.cluster_id: [], - zcl.clusters.general.GreenPowerProxy.cluster_id: [], - zcl.clusters.general.OnOffConfiguration.cluster_id: [], - zcl.clusters.lightlink.LightLink.cluster_id: [], - zcl.clusters.general.OnOff.cluster_id: [{ - 'attr': 'on_off', - 'config': REPORT_CONFIG_IMMEDIATE - }], - zcl.clusters.general.LevelControl.cluster_id: [{ - 'attr': 'current_level', - 'config': REPORT_CONFIG_ASAP - }], - zcl.clusters.lighting.Color.cluster_id: [{ - 'attr': 'current_x', - 'config': REPORT_CONFIG_DEFAULT - }, { - 'attr': 'current_y', - 'config': REPORT_CONFIG_DEFAULT - }, { - 'attr': 'color_temperature', - 'config': REPORT_CONFIG_DEFAULT - }], - zcl.clusters.measurement.RelativeHumidity.cluster_id: [{ - 'attr': 'measured_value', - 'config': ( - REPORT_CONFIG_MIN_INT, - REPORT_CONFIG_MAX_INT, - 50 - ) - }], - zcl.clusters.measurement.TemperatureMeasurement.cluster_id: [{ - 'attr': 'measured_value', - 'config': ( - REPORT_CONFIG_MIN_INT, - REPORT_CONFIG_MAX_INT, - 50 - ) - }], - SMARTTHINGS_ACCELERATION_CLUSTER: [{ - 'attr': 'acceleration', - 'config': REPORT_CONFIG_ASAP - }, { - 'attr': 'x_axis', - 'config': REPORT_CONFIG_ASAP - }, { - 'attr': 'y_axis', - 'config': REPORT_CONFIG_ASAP - }, { - 'attr': 'z_axis', - 'config': REPORT_CONFIG_ASAP - }], - SMARTTHINGS_HUMIDITY_CLUSTER: [{ - 'attr': 'measured_value', - 'config': ( - REPORT_CONFIG_MIN_INT, - REPORT_CONFIG_MAX_INT, - 50 - ) - }], - zcl.clusters.measurement.PressureMeasurement.cluster_id: [{ - 'attr': 'measured_value', - 'config': REPORT_CONFIG_DEFAULT - }], - zcl.clusters.measurement.IlluminanceMeasurement.cluster_id: [{ - 'attr': 'measured_value', - 'config': REPORT_CONFIG_DEFAULT - }], - zcl.clusters.smartenergy.Metering.cluster_id: [{ - 'attr': 'instantaneous_demand', - 'config': REPORT_CONFIG_DEFAULT - }], - zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: [{ - 'attr': 'active_power', - 'config': REPORT_CONFIG_DEFAULT - }], - zcl.clusters.general.PowerConfiguration.cluster_id: [{ - 'attr': 'battery_voltage', - 'config': REPORT_CONFIG_DEFAULT - }, { - 'attr': 'battery_percentage_remaining', - 'config': REPORT_CONFIG_DEFAULT - }], - zcl.clusters.measurement.OccupancySensing.cluster_id: [{ - 'attr': 'occupancy', - 'config': REPORT_CONFIG_IMMEDIATE - }], - zcl.clusters.hvac.Fan.cluster_id: [{ - 'attr': 'fan_mode', - 'config': REPORT_CONFIG_OP - }], - }) - - # A map of hass components to all Zigbee clusters it could use - for profile_id, classes in DEVICE_CLASS.items(): - profile = PROFILES[profile_id] - for device_type, component in classes.items(): - if component not in COMPONENT_CLUSTERS: - COMPONENT_CLUSTERS[component] = (set(), set()) - clusters = profile.CLUSTERS[device_type] - COMPONENT_CLUSTERS[component][0].update(clusters[0]) - COMPONENT_CLUSTERS[component][1].update(clusters[1]) diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index d6e9cc32338..695f2be5960 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -11,7 +11,9 @@ from concurrent.futures import TimeoutError as Timeout from homeassistant.core import callback from .const import ( DEFAULT_BAUDRATE, REPORT_CONFIG_MAX_INT, REPORT_CONFIG_MIN_INT, - REPORT_CONFIG_RPT_CHANGE, RadioType, IN, OUT, BINDABLE_CLUSTERS) + REPORT_CONFIG_RPT_CHANGE, RadioType, IN, OUT +) +from .registries import BINDABLE_CLUSTERS _LOGGER = logging.getLogger(__name__) @@ -146,6 +148,8 @@ async def check_zigpy_connection(usb_path, radio_type, database_path): def convert_ieee(ieee_str): """Convert given ieee string to EUI64.""" from zigpy.types import EUI64, uint8_t + if ieee_str is None: + return None return EUI64([uint8_t(p, base=16) for p in ieee_str.split(':')]) diff --git a/homeassistant/components/zha/core/patches.py b/homeassistant/components/zha/core/patches.py new file mode 100644 index 00000000000..8f708a568d1 --- /dev/null +++ b/homeassistant/components/zha/core/patches.py @@ -0,0 +1,41 @@ +""" +Patch functions for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" + +import types + + +def apply_cluster_listener_patch(): + """Apply patches to ZHA objects.""" + # patch zigpy listener to prevent flooding logs with warnings due to + # how zigpy implemented its listeners + from zigpy.appdb import ClusterPersistingListener + + def zha_send_event(self, cluster, command, args): + pass + + ClusterPersistingListener.zha_send_event = types.MethodType( + zha_send_event, + ClusterPersistingListener + ) + + +def apply_application_controller_patch(zha_gateway): + """Apply patches to ZHA objects.""" + # Patch handle_message until zigpy can provide an event here + def handle_message(sender, is_reply, profile, cluster, + src_ep, dst_ep, tsn, command_id, args): + """Handle message from a device.""" + if not sender.initializing and sender.ieee in zha_gateway.devices and \ + not zha_gateway.devices[sender.ieee].available: + zha_gateway.async_device_became_available( + sender, is_reply, profile, cluster, src_ep, dst_ep, tsn, + command_id, args + ) + return sender.handle_message( + is_reply, profile, cluster, src_ep, dst_ep, tsn, command_id, args) + + zha_gateway.application_controller.handle_message = handle_message diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py new file mode 100644 index 00000000000..3cbd31aa304 --- /dev/null +++ b/homeassistant/components/zha/core/registries.py @@ -0,0 +1,280 @@ +""" +Mapping registries for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" + +from .const import ( + HUMIDITY, + TEMPERATURE, ILLUMINANCE, PRESSURE, METERING, ELECTRICAL_MEASUREMENT, + OCCUPANCY, REPORT_CONFIG_IMMEDIATE, OPENING, ZONE, RADIO_DESCRIPTION, + REPORT_CONFIG_ASAP, REPORT_CONFIG_DEFAULT, REPORT_CONFIG_MIN_INT, + REPORT_CONFIG_MAX_INT, REPORT_CONFIG_OP, ACCELERATION, RadioType, RADIO, + CONTROLLER +) + +SMARTTHINGS_HUMIDITY_CLUSTER = 64581 +SMARTTHINGS_ACCELERATION_CLUSTER = 64514 + +DEVICE_CLASS = {} +SINGLE_INPUT_CLUSTER_DEVICE_CLASS = {} +SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS = {} +SENSOR_TYPES = {} +RADIO_TYPES = {} +BINARY_SENSOR_TYPES = {} +CLUSTER_REPORT_CONFIGS = {} +CUSTOM_CLUSTER_MAPPINGS = {} +COMPONENT_CLUSTERS = {} +EVENT_RELAY_CLUSTERS = [] +NO_SENSOR_CLUSTERS = [] +BINDABLE_CLUSTERS = [] + + +def establish_device_mappings(): + """Establish mappings between ZCL objects and HA ZHA objects. + + These cannot be module level, as importing bellows must be done in a + in a function. + """ + from zigpy import zcl + from zigpy.profiles import PROFILES, zha, zll + + if zha.PROFILE_ID not in DEVICE_CLASS: + DEVICE_CLASS[zha.PROFILE_ID] = {} + if zll.PROFILE_ID not in DEVICE_CLASS: + DEVICE_CLASS[zll.PROFILE_ID] = {} + + def get_ezsp_radio(): + import bellows.ezsp + from bellows.zigbee.application import ControllerApplication + return { + RADIO: bellows.ezsp.EZSP(), + CONTROLLER: ControllerApplication + } + + RADIO_TYPES[RadioType.ezsp.name] = { + RADIO: get_ezsp_radio, + RADIO_DESCRIPTION: 'EZSP' + } + + def get_xbee_radio(): + import zigpy_xbee.api + from zigpy_xbee.zigbee.application import ControllerApplication + return { + RADIO: zigpy_xbee.api.XBee(), + CONTROLLER: ControllerApplication + } + + RADIO_TYPES[RadioType.xbee.name] = { + RADIO: get_xbee_radio, + RADIO_DESCRIPTION: 'XBee' + } + + def get_deconz_radio(): + import zigpy_deconz.api + from zigpy_deconz.zigbee.application import ControllerApplication + return { + RADIO: zigpy_deconz.api.Deconz(), + CONTROLLER: ControllerApplication + } + + RADIO_TYPES[RadioType.deconz.name] = { + RADIO: get_deconz_radio, + RADIO_DESCRIPTION: 'Deconz' + } + + EVENT_RELAY_CLUSTERS.append(zcl.clusters.general.LevelControl.cluster_id) + EVENT_RELAY_CLUSTERS.append(zcl.clusters.general.OnOff.cluster_id) + + NO_SENSOR_CLUSTERS.append(zcl.clusters.general.Basic.cluster_id) + NO_SENSOR_CLUSTERS.append( + zcl.clusters.general.PowerConfiguration.cluster_id) + NO_SENSOR_CLUSTERS.append(zcl.clusters.lightlink.LightLink.cluster_id) + + BINDABLE_CLUSTERS.append(zcl.clusters.general.LevelControl.cluster_id) + BINDABLE_CLUSTERS.append(zcl.clusters.general.OnOff.cluster_id) + BINDABLE_CLUSTERS.append(zcl.clusters.lighting.Color.cluster_id) + + DEVICE_CLASS[zha.PROFILE_ID].update({ + zha.DeviceType.ON_OFF_SWITCH: 'binary_sensor', + zha.DeviceType.LEVEL_CONTROL_SWITCH: 'binary_sensor', + zha.DeviceType.REMOTE_CONTROL: 'binary_sensor', + zha.DeviceType.SMART_PLUG: 'switch', + zha.DeviceType.LEVEL_CONTROLLABLE_OUTPUT: 'light', + zha.DeviceType.ON_OFF_LIGHT: 'light', + zha.DeviceType.DIMMABLE_LIGHT: 'light', + zha.DeviceType.COLOR_DIMMABLE_LIGHT: 'light', + zha.DeviceType.ON_OFF_LIGHT_SWITCH: 'binary_sensor', + zha.DeviceType.DIMMER_SWITCH: 'binary_sensor', + zha.DeviceType.COLOR_DIMMER_SWITCH: 'binary_sensor', + }) + + DEVICE_CLASS[zll.PROFILE_ID].update({ + zll.DeviceType.ON_OFF_LIGHT: 'light', + zll.DeviceType.ON_OFF_PLUGIN_UNIT: 'switch', + zll.DeviceType.DIMMABLE_LIGHT: 'light', + zll.DeviceType.DIMMABLE_PLUGIN_UNIT: 'light', + zll.DeviceType.COLOR_LIGHT: 'light', + zll.DeviceType.EXTENDED_COLOR_LIGHT: 'light', + zll.DeviceType.COLOR_TEMPERATURE_LIGHT: 'light', + zll.DeviceType.COLOR_CONTROLLER: 'binary_sensor', + zll.DeviceType.COLOR_SCENE_CONTROLLER: 'binary_sensor', + zll.DeviceType.CONTROLLER: 'binary_sensor', + zll.DeviceType.SCENE_CONTROLLER: 'binary_sensor', + zll.DeviceType.ON_OFF_SENSOR: 'binary_sensor', + }) + + SINGLE_INPUT_CLUSTER_DEVICE_CLASS.update({ + zcl.clusters.general.OnOff: 'switch', + zcl.clusters.measurement.RelativeHumidity: 'sensor', + # this works for now but if we hit conflicts we can break it out to + # a different dict that is keyed by manufacturer + SMARTTHINGS_HUMIDITY_CLUSTER: 'sensor', + zcl.clusters.measurement.TemperatureMeasurement: 'sensor', + zcl.clusters.measurement.PressureMeasurement: 'sensor', + zcl.clusters.measurement.IlluminanceMeasurement: 'sensor', + zcl.clusters.smartenergy.Metering: 'sensor', + zcl.clusters.homeautomation.ElectricalMeasurement: 'sensor', + zcl.clusters.security.IasZone: 'binary_sensor', + zcl.clusters.measurement.OccupancySensing: 'binary_sensor', + zcl.clusters.hvac.Fan: 'fan', + SMARTTHINGS_ACCELERATION_CLUSTER: 'binary_sensor', + }) + + SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS.update({ + zcl.clusters.general.OnOff: 'binary_sensor', + }) + + SENSOR_TYPES.update({ + zcl.clusters.measurement.RelativeHumidity.cluster_id: HUMIDITY, + SMARTTHINGS_HUMIDITY_CLUSTER: HUMIDITY, + zcl.clusters.measurement.TemperatureMeasurement.cluster_id: + TEMPERATURE, + zcl.clusters.measurement.PressureMeasurement.cluster_id: PRESSURE, + zcl.clusters.measurement.IlluminanceMeasurement.cluster_id: + ILLUMINANCE, + zcl.clusters.smartenergy.Metering.cluster_id: METERING, + zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: + ELECTRICAL_MEASUREMENT, + }) + + BINARY_SENSOR_TYPES.update({ + zcl.clusters.measurement.OccupancySensing.cluster_id: OCCUPANCY, + zcl.clusters.security.IasZone.cluster_id: ZONE, + zcl.clusters.general.OnOff.cluster_id: OPENING, + SMARTTHINGS_ACCELERATION_CLUSTER: ACCELERATION, + }) + + CLUSTER_REPORT_CONFIGS.update({ + zcl.clusters.general.Alarms.cluster_id: [], + zcl.clusters.general.Basic.cluster_id: [], + zcl.clusters.general.Commissioning.cluster_id: [], + zcl.clusters.general.Identify.cluster_id: [], + zcl.clusters.general.Groups.cluster_id: [], + zcl.clusters.general.Scenes.cluster_id: [], + zcl.clusters.general.Partition.cluster_id: [], + zcl.clusters.general.Ota.cluster_id: [], + zcl.clusters.general.PowerProfile.cluster_id: [], + zcl.clusters.general.ApplianceControl.cluster_id: [], + zcl.clusters.general.PollControl.cluster_id: [], + zcl.clusters.general.GreenPowerProxy.cluster_id: [], + zcl.clusters.general.OnOffConfiguration.cluster_id: [], + zcl.clusters.lightlink.LightLink.cluster_id: [], + zcl.clusters.general.OnOff.cluster_id: [{ + 'attr': 'on_off', + 'config': REPORT_CONFIG_IMMEDIATE + }], + zcl.clusters.general.LevelControl.cluster_id: [{ + 'attr': 'current_level', + 'config': REPORT_CONFIG_ASAP + }], + zcl.clusters.lighting.Color.cluster_id: [{ + 'attr': 'current_x', + 'config': REPORT_CONFIG_DEFAULT + }, { + 'attr': 'current_y', + 'config': REPORT_CONFIG_DEFAULT + }, { + 'attr': 'color_temperature', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.measurement.RelativeHumidity.cluster_id: [{ + 'attr': 'measured_value', + 'config': ( + REPORT_CONFIG_MIN_INT, + REPORT_CONFIG_MAX_INT, + 50 + ) + }], + zcl.clusters.measurement.TemperatureMeasurement.cluster_id: [{ + 'attr': 'measured_value', + 'config': ( + REPORT_CONFIG_MIN_INT, + REPORT_CONFIG_MAX_INT, + 50 + ) + }], + SMARTTHINGS_ACCELERATION_CLUSTER: [{ + 'attr': 'acceleration', + 'config': REPORT_CONFIG_ASAP + }, { + 'attr': 'x_axis', + 'config': REPORT_CONFIG_ASAP + }, { + 'attr': 'y_axis', + 'config': REPORT_CONFIG_ASAP + }, { + 'attr': 'z_axis', + 'config': REPORT_CONFIG_ASAP + }], + SMARTTHINGS_HUMIDITY_CLUSTER: [{ + 'attr': 'measured_value', + 'config': ( + REPORT_CONFIG_MIN_INT, + REPORT_CONFIG_MAX_INT, + 50 + ) + }], + zcl.clusters.measurement.PressureMeasurement.cluster_id: [{ + 'attr': 'measured_value', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.measurement.IlluminanceMeasurement.cluster_id: [{ + 'attr': 'measured_value', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.smartenergy.Metering.cluster_id: [{ + 'attr': 'instantaneous_demand', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: [{ + 'attr': 'active_power', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.general.PowerConfiguration.cluster_id: [{ + 'attr': 'battery_voltage', + 'config': REPORT_CONFIG_DEFAULT + }, { + 'attr': 'battery_percentage_remaining', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.measurement.OccupancySensing.cluster_id: [{ + 'attr': 'occupancy', + 'config': REPORT_CONFIG_IMMEDIATE + }], + zcl.clusters.hvac.Fan.cluster_id: [{ + 'attr': 'fan_mode', + 'config': REPORT_CONFIG_OP + }], + }) + + # A map of hass components to all Zigbee clusters it could use + for profile_id, classes in DEVICE_CLASS.items(): + profile = PROFILES[profile_id] + for device_type, component in classes.items(): + if component not in COMPONENT_CLUSTERS: + COMPONENT_CLUSTERS[component] = (set(), set()) + clusters = profile.CLUSTERS[device_type] + COMPONENT_CLUSTERS[component][0].update(clusters[0]) + COMPONENT_CLUSTERS[component][1].update(clusters[1]) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 8b2cd349b9d..6ba4efa9b0f 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -11,6 +11,7 @@ from homeassistant.components import light from homeassistant.const import STATE_ON from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.event import async_track_time_interval import homeassistant.util.color as color_util from .const import ( DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, COLOR_CHANNEL, @@ -96,11 +97,6 @@ class Light(ZhaEntity, light.Light): self._supported_features |= light.SUPPORT_COLOR self._hs_color = (0, 0) - @property - def should_poll(self) -> bool: - """Poll state from device.""" - return True - @property def is_on(self) -> bool: """Return true if entity is on.""" @@ -157,6 +153,7 @@ class Light(ZhaEntity, light.Light): if self._level_channel: await self.async_accept_signal( self._level_channel, SIGNAL_SET_LEVEL, self.set_level) + async_track_time_interval(self.hass, self.refresh, SCAN_INTERVAL) @callback def async_restore_last_state(self, last_state): @@ -247,3 +244,7 @@ class Light(ZhaEntity, light.Light): if self._level_channel: self._brightness = await self._level_channel.get_attribute_value( 'current_level') + + async def refresh(self, time): + """Call async_update at an interval.""" + await self.async_update() diff --git a/homeassistant/components/zha/services.yaml b/homeassistant/components/zha/services.yaml index 0d7fe06fe25..048054077f8 100644 --- a/homeassistant/components/zha/services.yaml +++ b/homeassistant/components/zha/services.yaml @@ -6,6 +6,9 @@ permit: duration: description: Time to permit joins, in seconds example: 60 + ieee_address: + description: IEEE address of the node permitting new joins + example: "00:0d:6f:00:05:7d:2d:34" remove: description: Remove a node from the ZigBee network. diff --git a/homeassistant/components/zhong_hong/__init__.py b/homeassistant/components/zhong_hong/__init__.py new file mode 100644 index 00000000000..f14ec68593b --- /dev/null +++ b/homeassistant/components/zhong_hong/__init__.py @@ -0,0 +1 @@ +"""The zhong_hong component.""" diff --git a/homeassistant/components/climate/zhong_hong.py b/homeassistant/components/zhong_hong/climate.py similarity index 100% rename from homeassistant/components/climate/zhong_hong.py rename to homeassistant/components/zhong_hong/climate.py diff --git a/homeassistant/components/zigbee/binary_sensor.py b/homeassistant/components/zigbee/binary_sensor.py index eec1832f07d..ccf4e70df34 100644 --- a/homeassistant/components/zigbee/binary_sensor.py +++ b/homeassistant/components/zigbee/binary_sensor.py @@ -2,8 +2,8 @@ import voluptuous as vol from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.zigbee import ( - ZigBeeDigitalIn, ZigBeeDigitalInConfig, PLATFORM_SCHEMA) + +from . import PLATFORM_SCHEMA, ZigBeeDigitalIn, ZigBeeDigitalInConfig CONF_ON_STATE = 'on_state' diff --git a/homeassistant/components/zigbee/light.py b/homeassistant/components/zigbee/light.py index e5016900be7..b9be0d89323 100644 --- a/homeassistant/components/zigbee/light.py +++ b/homeassistant/components/zigbee/light.py @@ -2,8 +2,8 @@ import voluptuous as vol from homeassistant.components.light import Light -from homeassistant.components.zigbee import ( - ZigBeeDigitalOut, ZigBeeDigitalOutConfig, PLATFORM_SCHEMA) + +from . import PLATFORM_SCHEMA, ZigBeeDigitalOut, ZigBeeDigitalOutConfig CONF_ON_STATE = 'on_state' diff --git a/homeassistant/components/zigbee/sensor.py b/homeassistant/components/zigbee/sensor.py index 48503e396a4..48301ac9728 100644 --- a/homeassistant/components/zigbee/sensor.py +++ b/homeassistant/components/zigbee/sensor.py @@ -1,14 +1,15 @@ """Support for Zigbee sensors.""" -import logging from binascii import hexlify +import logging import voluptuous as vol from homeassistant.components import zigbee -from homeassistant.components.zigbee import PLATFORM_SCHEMA from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity +from . import PLATFORM_SCHEMA + _LOGGER = logging.getLogger(__name__) CONF_TYPE = 'type' diff --git a/homeassistant/components/zigbee/switch.py b/homeassistant/components/zigbee/switch.py index ef36e17d74b..ddfd47a047e 100644 --- a/homeassistant/components/zigbee/switch.py +++ b/homeassistant/components/zigbee/switch.py @@ -2,8 +2,8 @@ import voluptuous as vol from homeassistant.components.switch import SwitchDevice -from homeassistant.components.zigbee import ( - ZigBeeDigitalOut, ZigBeeDigitalOutConfig, PLATFORM_SCHEMA) + +from . import PLATFORM_SCHEMA, ZigBeeDigitalOut, ZigBeeDigitalOutConfig DEPENDENCIES = ['zigbee'] diff --git a/homeassistant/components/ziggo_mediabox_xl/__init__.py b/homeassistant/components/ziggo_mediabox_xl/__init__.py new file mode 100644 index 00000000000..4627f7cef7a --- /dev/null +++ b/homeassistant/components/ziggo_mediabox_xl/__init__.py @@ -0,0 +1 @@ +"""The ziggo_mediabox_xl component.""" diff --git a/homeassistant/components/media_player/ziggo_mediabox_xl.py b/homeassistant/components/ziggo_mediabox_xl/media_player.py similarity index 100% rename from homeassistant/components/media_player/ziggo_mediabox_xl.py rename to homeassistant/components/ziggo_mediabox_xl/media_player.py diff --git a/homeassistant/components/zoneminder/binary_sensor.py b/homeassistant/components/zoneminder/binary_sensor.py index f20f09e70a6..ce59d4573be 100644 --- a/homeassistant/components/zoneminder/binary_sensor.py +++ b/homeassistant/components/zoneminder/binary_sensor.py @@ -1,7 +1,7 @@ """Support for ZoneMinder binary sensors.""" from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN +from . import DOMAIN as ZONEMINDER_DOMAIN DEPENDENCIES = ['zoneminder'] diff --git a/homeassistant/components/zoneminder/camera.py b/homeassistant/components/zoneminder/camera.py index 7f74712335c..fe3333fa3ed 100644 --- a/homeassistant/components/zoneminder/camera.py +++ b/homeassistant/components/zoneminder/camera.py @@ -1,10 +1,11 @@ """Support for ZoneMinder camera streaming.""" import logging -from homeassistant.const import CONF_NAME, CONF_VERIFY_SSL -from homeassistant.components.camera.mjpeg import ( +from homeassistant.components.mjpeg.camera import ( CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera, filter_urllib3_logging) -from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN +from homeassistant.const import CONF_NAME, CONF_VERIFY_SSL + +from . import DOMAIN as ZONEMINDER_DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zoneminder/sensor.py b/homeassistant/components/zoneminder/sensor.py index 9eb6beb491c..e205d921422 100644 --- a/homeassistant/components/zoneminder/sensor.py +++ b/homeassistant/components/zoneminder/sensor.py @@ -3,12 +3,13 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN from homeassistant.const import CONF_MONITORED_CONDITIONS +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from . import DOMAIN as ZONEMINDER_DOMAIN + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['zoneminder'] diff --git a/homeassistant/components/zoneminder/switch.py b/homeassistant/components/zoneminder/switch.py index b411a148d43..78e72c5fd4a 100644 --- a/homeassistant/components/zoneminder/switch.py +++ b/homeassistant/components/zoneminder/switch.py @@ -3,11 +3,12 @@ import logging import voluptuous as vol -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) -from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN -from homeassistant.const import (CONF_COMMAND_ON, CONF_COMMAND_OFF) +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.const import CONF_COMMAND_OFF, CONF_COMMAND_ON import homeassistant.helpers.config_validation as cv +from . import DOMAIN as ZONEMINDER_DOMAIN + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['zoneminder'] diff --git a/homeassistant/components/zwave/.translations/bg.json b/homeassistant/components/zwave/.translations/bg.json new file mode 100644 index 00000000000..7140e3956df --- /dev/null +++ b/homeassistant/components/zwave/.translations/bg.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Z-Wave \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d", + "one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430 \u0441\u0430\u043c\u043e \u0435\u0434\u0438\u043d Z-Wave \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440." + }, + "error": { + "option_error": "\u0412\u0430\u043b\u0438\u0434\u0438\u0440\u0430\u043d\u0435\u0442\u043e \u043d\u0430 Z-Wave \u043d\u0435 \u0431\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e. \u041f\u0440\u0430\u0432\u0438\u043b\u0435\u043d \u043b\u0438 \u0435 \u043f\u044a\u0442\u044f\u0442 \u043a\u044a\u043c USB \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e?" + }, + "step": { + "user": { + "data": { + "network_key": "\u041c\u0440\u0435\u0436\u043e\u0432 \u043a\u043b\u044e\u0447 (\u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e \u0437\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0430\u043d\u0435)", + "usb_path": "USB \u043f\u044a\u0442" + }, + "description": "\u0412\u0438\u0436\u0442\u0435 https://www.home-assistant.io/docs/z-wave/installation/ \u0437\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e\u0442\u043d\u043e\u0441\u043d\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0438\u0442\u0435 \u043f\u0440\u043e\u043c\u0435\u043d\u043b\u0438\u0432\u0438", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u043d\u0435 \u043d\u0430 Z-Wave" + } + }, + "title": "Z-Wave" + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/.translations/pl.json b/homeassistant/components/zwave/.translations/pl.json index a96010a74a8..c392f0093a0 100644 --- a/homeassistant/components/zwave/.translations/pl.json +++ b/homeassistant/components/zwave/.translations/pl.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "network_key": "Klucz sieciowy (pozostaw pusty by generowa\u0107 automatycznie)", + "network_key": "Klucz sieciowy (pozostaw pusty, by generowa\u0107 automatycznie)", "usb_path": "\u015acie\u017cka do kontrolera Z-Wave USB" }, "description": "Zobacz https://www.home-assistant.io/docs/z-wave/installation/, aby uzyska\u0107 informacje na temat zmiennych konfiguracyjnych", diff --git a/homeassistant/components/zwave/.translations/ru.json b/homeassistant/components/zwave/.translations/ru.json index b6856e4590a..a64b4db185d 100644 --- a/homeassistant/components/zwave/.translations/ru.json +++ b/homeassistant/components/zwave/.translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430", "one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0441 \u043e\u0434\u043d\u0438\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u043c Z-Wave" }, "error": { diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index be76eca4efd..4abaaa31210 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -37,7 +37,7 @@ from .discovery_schemas import DISCOVERY_SCHEMAS from .util import (check_node_schema, check_value_schema, node_name, check_has_unique_id, is_node_parsed) -REQUIREMENTS = ['pydispatcher==2.0.5', 'homeassistant-pyozw==0.1.2'] +REQUIREMENTS = ['pydispatcher==2.0.5', 'homeassistant-pyozw==0.1.3'] _LOGGER = logging.getLogger(__name__) @@ -169,8 +169,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_DEBUG, default=DEFAULT_DEBUG): cv.boolean, vol.Optional(CONF_POLLING_INTERVAL, default=DEFAULT_POLLING_INTERVAL): cv.positive_int, - vol.Optional(CONF_USB_STICK_PATH, default=DEFAULT_CONF_USB_STICK_PATH): - cv.string, + vol.Optional(CONF_USB_STICK_PATH): cv.string, }), }, extra=vol.ALLOW_EXTRA) @@ -239,7 +238,8 @@ async def async_setup(hass, config): hass.async_create_task(hass.config_entries.flow.async_init( DOMAIN, context={'source': config_entries.SOURCE_IMPORT}, data={ - CONF_USB_STICK_PATH: conf[CONF_USB_STICK_PATH], + CONF_USB_STICK_PATH: conf.get( + CONF_USB_STICK_PATH, DEFAULT_CONF_USB_STICK_PATH), CONF_NETWORK_KEY: conf.get(CONF_NETWORK_KEY), } )) @@ -271,9 +271,14 @@ async def async_setup_entry(hass, config_entry): config.get(CONF_DEVICE_CONFIG_DOMAIN), config.get(CONF_DEVICE_CONFIG_GLOB)) + usb_path = config.get( + CONF_USB_STICK_PATH, config_entry.data[CONF_USB_STICK_PATH]) + + _LOGGER.info('Z-Wave USB path is %s', usb_path) + # Setup options options = ZWaveOption( - config_entry.data[CONF_USB_STICK_PATH], + usb_path, user_path=hass.config.config_dir, config_path=config.get(CONF_CONFIG_PATH)) @@ -374,7 +379,7 @@ async def async_setup_entry(hass, config_entry): def network_ready(): """Handle the query of all awake nodes.""" - _LOGGER.info("Zwave network is ready for use. All awake nodes " + _LOGGER.info("Z-Wave network is ready for use. All awake nodes " "have been queried. Sleeping nodes will be " "queried when they awake.") hass.bus.fire(const.EVENT_NETWORK_READY) diff --git a/homeassistant/components/zwave/const.py b/homeassistant/components/zwave/const.py index fece48655df..67b5341a4e6 100644 --- a/homeassistant/components/zwave/const.py +++ b/homeassistant/components/zwave/const.py @@ -29,7 +29,7 @@ CONF_USB_STICK_PATH = 'usb_path' CONF_CONFIG_PATH = 'config_path' CONF_NETWORK_KEY = 'network_key' -DEFAULT_CONF_AUTOHEAL = True +DEFAULT_CONF_AUTOHEAL = False DEFAULT_CONF_USB_STICK_PATH = '/zwaveusbstick' DEFAULT_POLLING_INTERVAL = 60000 DEFAULT_DEBUG = False diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index e00d7204a79..df635807abe 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -143,6 +143,7 @@ HANDLERS = Registry() # Components that have config flows. In future we will auto-generate this list. FLOWS = [ 'ambient_station', + 'axis', 'cast', 'daikin', 'deconz', diff --git a/homeassistant/const.py b/homeassistant/const.py index d8bd84a079a..36c61a51916 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,8 +1,8 @@ # coding: utf-8 """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 -MINOR_VERSION = 90 -PATCH_VERSION = '2' +MINOR_VERSION = 91 +PATCH_VERSION = '0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) @@ -343,6 +343,13 @@ LENGTH_FEET = 'ft' # type: str LENGTH_YARD = 'yd' # type: str LENGTH_MILES = 'mi' # type: str +# Pressure units +PRESSURE_PA = 'Pa' # type: str +PRESSURE_HPA = 'hPa' # type: str +PRESSURE_MBAR = 'mbar' # type: str +PRESSURE_INHG = 'inHg' # type: str +PRESSURE_PSI = 'psi' # type: str + # Volume units VOLUME_LITERS = 'L' # type: str VOLUME_MILLILITERS = 'mL' # type: str @@ -455,6 +462,7 @@ UNIT_NOT_RECOGNIZED_TEMPLATE = '{} is not a recognized {} unit.' # type: str LENGTH = 'length' # type: str MASS = 'mass' # type: str +PRESSURE = 'pressure' # type: str VOLUME = 'volume' # type: str TEMPERATURE = 'temperature' # type: str SPEED_MS = 'speed_ms' # type: str diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 644d14cf869..adf5410516d 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -1,6 +1,7 @@ """Provide a way to connect devices to one physical location.""" import logging import uuid +from asyncio import Event from collections import OrderedDict from typing import MutableMapping # noqa: F401 from typing import Iterable, Optional, cast @@ -9,6 +10,7 @@ import attr from homeassistant.core import callback from homeassistant.loader import bind_hass + from .typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) @@ -133,14 +135,21 @@ class AreaRegistry: @bind_hass async def async_get_registry(hass: HomeAssistantType) -> AreaRegistry: """Return area registry instance.""" - task = hass.data.get(DATA_REGISTRY) + reg_or_evt = hass.data.get(DATA_REGISTRY) - if task is None: - async def _load_reg() -> AreaRegistry: - registry = AreaRegistry(hass) - await registry.async_load() - return registry + if not reg_or_evt: + evt = hass.data[DATA_REGISTRY] = Event() - task = hass.data[DATA_REGISTRY] = hass.async_create_task(_load_reg()) + reg = AreaRegistry(hass) + await reg.async_load() - return cast(AreaRegistry, await task) + hass.data[DATA_REGISTRY] = reg + evt.set() + return reg + + if isinstance(reg_or_evt, Event): + evt = reg_or_evt + await evt.wait() + return cast(AreaRegistry, hass.data.get(DATA_REGISTRY)) + + return cast(AreaRegistry, reg_or_evt) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 4bba80aa154..6513f9368b0 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -8,6 +8,7 @@ from datetime import (timedelta, datetime as datetime_sys, from socket import _GLOBAL_DEFAULT_TIMEOUT from typing import Any, Union, TypeVar, Callable, Sequence, Dict, Optional from urllib.parse import urlparse +from uuid import UUID import voluptuous as vol from pkg_resources import parse_version @@ -532,6 +533,20 @@ def x10_address(value): return str(value).lower() +def uuid4_hex(value): + """Validate a v4 UUID in hex format.""" + try: + result = UUID(value, version=4) + except (ValueError, AttributeError, TypeError) as error: + raise vol.Invalid('Invalid Version4 UUID', error_message=str(error)) + + if result.hex != value.lower(): + # UUID() will create a uuid4 if input is invalid + raise vol.Invalid('Invalid Version4 UUID') + + return result.hex + + def ensure_list_csv(value: Any) -> Sequence: """Ensure that input is a list or make one from comma-separated string.""" if isinstance(value, str): diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 1ea6c400208..25c9933fd11 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1,15 +1,17 @@ """Provide a way to connect entities belonging to one device.""" import logging import uuid -from typing import List, Optional - +from asyncio import Event from collections import OrderedDict +from typing import List, Optional, cast import attr from homeassistant.core import callback from homeassistant.loader import bind_hass +from .typing import HomeAssistantType + _LOGGER = logging.getLogger(__name__) _UNDEF = object() @@ -273,19 +275,26 @@ class DeviceRegistry: @bind_hass -async def async_get_registry(hass) -> DeviceRegistry: +async def async_get_registry(hass: HomeAssistantType) -> DeviceRegistry: """Return device registry instance.""" - task = hass.data.get(DATA_REGISTRY) + reg_or_evt = hass.data.get(DATA_REGISTRY) - if task is None: - async def _load_reg(): - registry = DeviceRegistry(hass) - await registry.async_load() - return registry + if not reg_or_evt: + evt = hass.data[DATA_REGISTRY] = Event() - task = hass.data[DATA_REGISTRY] = hass.async_create_task(_load_reg()) + reg = DeviceRegistry(hass) + await reg.async_load() - return await task + hass.data[DATA_REGISTRY] = reg + evt.set() + return reg + + if isinstance(reg_or_evt, Event): + evt = reg_or_evt + await evt.wait() + return cast(DeviceRegistry, hass.data.get(DATA_REGISTRY)) + + return cast(DeviceRegistry, reg_or_evt) @callback diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 87cc4d4fd90..a092c89405e 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -27,7 +27,6 @@ class EntityPlatform: domain: str platform_name: str scan_interval: timedelta - parallel_updates: int entity_namespace: str async_entities_added_callback: @callback method """ @@ -52,22 +51,21 @@ class EntityPlatform: # which powers entity_component.add_entities if platform is None: self.parallel_updates = None + self.parallel_updates_semaphore = None return - # Async platforms do all updates in parallel by default - if hasattr(platform, 'async_setup_platform'): - default_parallel_updates = 0 - else: - default_parallel_updates = 1 + self.parallel_updates = getattr(platform, 'PARALLEL_UPDATES', None) + # semaphore will be created on demand + self.parallel_updates_semaphore = None - parallel_updates = getattr(platform, 'PARALLEL_UPDATES', - default_parallel_updates) - - if parallel_updates: - self.parallel_updates = asyncio.Semaphore( - parallel_updates, loop=hass.loop) - else: - self.parallel_updates = None + def _get_parallel_updates_semaphore(self): + """Get or create a semaphore for parallel updates.""" + if self.parallel_updates_semaphore is None: + self.parallel_updates_semaphore = asyncio.Semaphore( + self.parallel_updates if self.parallel_updates else 1, + loop=self.hass.loop + ) + return self.parallel_updates_semaphore async def async_setup(self, platform_config, discovery_info=None): """Set up the platform from a config file.""" @@ -240,7 +238,22 @@ class EntityPlatform: entity.hass = self.hass entity.platform = self - entity.parallel_updates = self.parallel_updates + + # Async entity + # PARALLEL_UPDATE == None: entity.parallel_updates = None + # PARALLEL_UPDATE == 0: entity.parallel_updates = None + # PARALLEL_UPDATE > 0: entity.parallel_updates = Semaphore(p) + # Sync entity + # PARALLEL_UPDATE == None: entity.parallel_updates = Semaphore(1) + # PARALLEL_UPDATE == 0: entity.parallel_updates = None + # PARALLEL_UPDATE > 0: entity.parallel_updates = Semaphore(p) + if hasattr(entity, 'async_update') and not self.parallel_updates: + entity.parallel_updates = None + elif (not hasattr(entity, 'async_update') + and self.parallel_updates == 0): + entity.parallel_updates = None + else: + entity.parallel_updates = self._get_parallel_updates_semaphore() # Update properties before we generate the entity_id if update_before_add: diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index c0a0dfaa7d9..be50d11d17d 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -7,10 +7,11 @@ The Entity Registry will persist itself 10 seconds after a new entity is registered. Registering a new entity while a timer is in progress resets the timer. """ +from asyncio import Event from collections import OrderedDict from itertools import chain import logging -from typing import Optional, List +from typing import List, Optional, cast import weakref import attr @@ -20,6 +21,8 @@ from homeassistant.loader import bind_hass from homeassistant.util import ensure_unique_string, slugify from homeassistant.util.yaml import load_yaml +from .typing import HomeAssistantType + PATH_REGISTRY = 'entity_registry.yaml' DATA_REGISTRY = 'entity_registry' SAVE_DELAY = 10 @@ -277,19 +280,26 @@ class EntityRegistry: @bind_hass -async def async_get_registry(hass) -> EntityRegistry: +async def async_get_registry(hass: HomeAssistantType) -> EntityRegistry: """Return entity registry instance.""" - task = hass.data.get(DATA_REGISTRY) + reg_or_evt = hass.data.get(DATA_REGISTRY) - if task is None: - async def _load_reg(): - registry = EntityRegistry(hass) - await registry.async_load() - return registry + if not reg_or_evt: + evt = hass.data[DATA_REGISTRY] = Event() - task = hass.data[DATA_REGISTRY] = hass.async_create_task(_load_reg()) + reg = EntityRegistry(hass) + await reg.async_load() - return await task + hass.data[DATA_REGISTRY] = reg + evt.set() + return reg + + if isinstance(reg_or_evt, Event): + evt = reg_or_evt + await evt.wait() + return cast(EntityRegistry, hass.data.get(DATA_REGISTRY)) + + return cast(EntityRegistry, reg_or_evt) @callback diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index d79c68ffd5e..24275c87061 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -442,10 +442,18 @@ class TemplateMethods: return None -def forgiving_round(value, precision=0): +def forgiving_round(value, precision=0, method="common"): """Round accepted strings.""" try: - value = round(float(value), precision) + # support rounding methods like jinja + multiplier = float(10 ** precision) + if method == "ceil": + value = math.ceil(float(value) * multiplier) / multiplier + elif method == "floor": + value = math.floor(float(value) * multiplier) / multiplier + else: + # if method is common or something else, use common rounding + value = round(float(value), precision) return int(value) if precision == 0 else value except (ValueError, TypeError): # If value can't be converted to float diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index e1c5895b89a..63a6421d5f6 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -62,7 +62,7 @@ def component_translation_file(hass: HomeAssistantType, component: str, # It's a platform parts = component.split('.', 1) module = get_platform(hass, *parts) - assert module is not None + assert module is not None, component # Either within HA or custom_components # Either light/hue.py or hue/light.py diff --git a/homeassistant/loader.py b/homeassistant/loader.py index e36ad5451c1..4ca19935206 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -37,6 +37,7 @@ DATA_KEY = 'components' PACKAGE_CUSTOM_COMPONENTS = 'custom_components' PACKAGE_BUILTIN = 'homeassistant.components' LOOKUP_PATHS = [PACKAGE_CUSTOM_COMPONENTS, PACKAGE_BUILTIN] +COMPONENTS_WITH_BAD_PLATFORMS = ['automation', 'mqtt', 'telegram_bot'] class LoaderError(Exception): @@ -83,7 +84,11 @@ def get_platform(hass, # type: HomeAssistant """ # If the platform has a component, we will limit the platform loading path # to be the same source (custom/built-in). - component = _load_file(hass, platform_name, LOOKUP_PATHS) + if domain not in COMPONENTS_WITH_BAD_PLATFORMS: + component = _load_file(hass, platform_name, LOOKUP_PATHS) + else: + # avoid load component for legacy platform + component = None # Until we have moved all platforms under their component/own folder, it # can be that the component is None. @@ -99,10 +104,22 @@ def get_platform(hass, # type: HomeAssistant if platform is not None: return platform - # Legacy platform check: light/hue.py - platform = _load_file( - hass, PLATFORM_FORMAT.format(domain=platform_name, platform=domain), - base_paths) + # Legacy platform check for automation: components/automation/event.py + if component is None and domain in COMPONENTS_WITH_BAD_PLATFORMS: + platform = _load_file( + hass, + PLATFORM_FORMAT.format(domain=platform_name, platform=domain), + base_paths + ) + + # Legacy platform check for custom: custom_components/light/hue.py + # Only check if the component was also in custom components. + if component is None or base_paths[0] == PACKAGE_CUSTOM_COMPONENTS: + platform = _load_file( + hass, + PLATFORM_FORMAT.format(domain=platform_name, platform=domain), + [PACKAGE_CUSTOM_COMPONENTS] + ) if platform is None: if component is None: @@ -113,8 +130,8 @@ def get_platform(hass, # type: HomeAssistant _LOGGER.error("Unable to find platform %s.%s", platform_name, extra) return None - if platform.__name__.startswith(PACKAGE_CUSTOM_COMPONENTS): - _LOGGER.warning( + if domain not in COMPONENTS_WITH_BAD_PLATFORMS: + _LOGGER.error( "Integrations need to be in their own folder. Change %s/%s.py to " "%s/%s.py. This will stop working soon.", domain, platform_name, platform_name, domain) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index fa1fe5a959d..04704a00484 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ python-slugify==1.2.6 pytz>=2018.07 pyyaml>=3.13,<4 requests==2.21.0 -ruamel.yaml==0.15.88 +ruamel.yaml==0.15.89 voluptuous==0.11.5 voluptuous-serialize==2.1.0 diff --git a/homeassistant/scripts/credstash.py b/homeassistant/scripts/credstash.py index 302910c5b08..e2950f8d7a0 100644 --- a/homeassistant/scripts/credstash.py +++ b/homeassistant/scripts/credstash.py @@ -4,7 +4,7 @@ import getpass from homeassistant.util.yaml import _SECRET_NAMESPACE -REQUIREMENTS = ['credstash==1.15.0', 'botocore==1.7.34'] +REQUIREMENTS = ['credstash==1.15.0'] def run(args): @@ -26,14 +26,13 @@ def run(args): # pylint: disable=import-error, no-member import credstash - import botocore args = parser.parse_args(args) table = _SECRET_NAMESPACE try: credstash.listSecrets(table=table) - except botocore.errorfactory.ClientError: + except Exception: # pylint: disable=broad-except credstash.createDdbTable(table=table) if args.action == 'list': diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index f77d1752d6a..d369bd89098 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -9,7 +9,7 @@ from typing import Any, Optional, Tuple, Dict import requests -ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json' +ELEVATION_URL = 'https://api.open-elevation.com/api/v1/lookup' IP_API = 'http://ip-api.com/json' IPAPI = 'https://ipapi.co/json/' @@ -70,7 +70,6 @@ def elevation(latitude: float, longitude: float) -> int: ELEVATION_URL, params={ 'locations': '{},{}'.format(latitude, longitude), - 'sensor': 'false', }, timeout=10) except requests.RequestException: diff --git a/homeassistant/util/pressure.py b/homeassistant/util/pressure.py new file mode 100644 index 00000000000..ecfa6344d29 --- /dev/null +++ b/homeassistant/util/pressure.py @@ -0,0 +1,51 @@ +"""Pressure util functions.""" + +import logging +from numbers import Number + +from homeassistant.const import ( + PRESSURE_PA, + PRESSURE_HPA, + PRESSURE_MBAR, + PRESSURE_INHG, + PRESSURE_PSI, + UNIT_NOT_RECOGNIZED_TEMPLATE, + PRESSURE, +) + +_LOGGER = logging.getLogger(__name__) + +VALID_UNITS = [ + PRESSURE_PA, + PRESSURE_HPA, + PRESSURE_MBAR, + PRESSURE_INHG, + PRESSURE_PSI, +] + +UNIT_CONVERSION = { + PRESSURE_PA: 1, + PRESSURE_HPA: 1 / 100, + PRESSURE_MBAR: 1 / 100, + PRESSURE_INHG: 1 / 3386.389, + PRESSURE_PSI: 1 / 6894.757, +} + + +def convert(value: float, unit_1: str, unit_2: str) -> float: + """Convert one unit of measurement to another.""" + if unit_1 not in VALID_UNITS: + raise ValueError( + UNIT_NOT_RECOGNIZED_TEMPLATE.format(unit_1, PRESSURE)) + if unit_2 not in VALID_UNITS: + raise ValueError( + UNIT_NOT_RECOGNIZED_TEMPLATE.format(unit_2, PRESSURE)) + + if not isinstance(value, Number): + raise TypeError('{} is not of numeric type'.format(value)) + + if unit_1 == unit_2 or unit_1 not in VALID_UNITS: + return value + + pascals = value / UNIT_CONVERSION[unit_1] + return pascals * UNIT_CONVERSION[unit_2] diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index 5f6d202b5e9..8e506dfca2e 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -5,27 +5,19 @@ from typing import Optional from numbers import Number from homeassistant.const import ( - TEMP_CELSIUS, TEMP_FAHRENHEIT, LENGTH_CENTIMETERS, LENGTH_METERS, - LENGTH_KILOMETERS, LENGTH_INCHES, LENGTH_FEET, LENGTH_YARD, LENGTH_MILES, - VOLUME_LITERS, VOLUME_MILLILITERS, VOLUME_GALLONS, VOLUME_FLUID_OUNCE, + TEMP_CELSIUS, TEMP_FAHRENHEIT, LENGTH_MILES, LENGTH_KILOMETERS, + PRESSURE_PA, PRESSURE_PSI, VOLUME_LITERS, VOLUME_GALLONS, MASS_GRAMS, MASS_KILOGRAMS, MASS_OUNCES, MASS_POUNDS, - CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL, LENGTH, MASS, VOLUME, - TEMPERATURE, UNIT_NOT_RECOGNIZED_TEMPLATE) + CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL, LENGTH, MASS, PRESSURE, + VOLUME, TEMPERATURE, UNIT_NOT_RECOGNIZED_TEMPLATE) from homeassistant.util import temperature as temperature_util from homeassistant.util import distance as distance_util +from homeassistant.util import pressure as pressure_util from homeassistant.util import volume as volume_util _LOGGER = logging.getLogger(__name__) -LENGTH_UNITS = [ - LENGTH_MILES, - LENGTH_YARD, - LENGTH_FEET, - LENGTH_INCHES, - LENGTH_KILOMETERS, - LENGTH_METERS, - LENGTH_CENTIMETERS, -] +LENGTH_UNITS = distance_util.VALID_UNITS MASS_UNITS = [ MASS_POUNDS, @@ -34,12 +26,9 @@ MASS_UNITS = [ MASS_GRAMS, ] -VOLUME_UNITS = [ - VOLUME_GALLONS, - VOLUME_FLUID_OUNCE, - VOLUME_LITERS, - VOLUME_MILLILITERS, -] +PRESSURE_UNITS = pressure_util.VALID_UNITS + +VOLUME_UNITS = volume_util.VALID_UNITS TEMPERATURE_UNITS = [ TEMP_FAHRENHEIT, @@ -57,6 +46,8 @@ def is_valid_unit(unit: str, unit_type: str) -> bool: units = MASS_UNITS elif unit_type == VOLUME: units = VOLUME_UNITS + elif unit_type == PRESSURE: + units = PRESSURE_UNITS else: return False @@ -67,7 +58,7 @@ class UnitSystem: """A container for units of measure.""" def __init__(self, name: str, temperature: str, length: str, - volume: str, mass: str) -> None: + volume: str, mass: str, pressure: str) -> None: """Initialize the unit system object.""" errors = \ ', '.join(UNIT_NOT_RECOGNIZED_TEMPLATE.format(unit, unit_type) @@ -75,7 +66,8 @@ class UnitSystem: (temperature, TEMPERATURE), (length, LENGTH), (volume, VOLUME), - (mass, MASS), ] + (mass, MASS), + (pressure, PRESSURE), ] if not is_valid_unit(unit, unit_type)) # type: str if errors: @@ -85,6 +77,7 @@ class UnitSystem: self.temperature_unit = temperature self.length_unit = length self.mass_unit = mass + self.pressure_unit = pressure self.volume_unit = volume @property @@ -109,6 +102,14 @@ class UnitSystem: return distance_util.convert(length, from_unit, self.length_unit) + def pressure(self, pressure: Optional[float], from_unit: str) -> float: + """Convert the given pressure to this unit system.""" + if not isinstance(pressure, Number): + raise TypeError('{} is not a numeric value.'.format(str(pressure))) + + return pressure_util.convert(pressure, from_unit, + self.pressure_unit) + def volume(self, volume: Optional[float], from_unit: str) -> float: """Convert the given volume to this unit system.""" if not isinstance(volume, Number): @@ -121,13 +122,16 @@ class UnitSystem: return { LENGTH: self.length_unit, MASS: self.mass_unit, + PRESSURE: self.pressure_unit, TEMPERATURE: self.temperature_unit, VOLUME: self.volume_unit } METRIC_SYSTEM = UnitSystem(CONF_UNIT_SYSTEM_METRIC, TEMP_CELSIUS, - LENGTH_KILOMETERS, VOLUME_LITERS, MASS_GRAMS) + LENGTH_KILOMETERS, VOLUME_LITERS, MASS_GRAMS, + PRESSURE_PA) IMPERIAL_SYSTEM = UnitSystem(CONF_UNIT_SYSTEM_IMPERIAL, TEMP_FAHRENHEIT, - LENGTH_MILES, VOLUME_GALLONS, MASS_POUNDS) + LENGTH_MILES, VOLUME_GALLONS, MASS_POUNDS, + PRESSURE_PSI) diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py index c988cb811b2..15bf73f459d 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml.py @@ -144,7 +144,7 @@ def _find_files(directory: str, pattern: str) -> Iterator[str]: """Recursively load files in a directory.""" for root, dirs, files in os.walk(directory, topdown=True): dirs[:] = [d for d in dirs if _is_file_valid(d)] - for basename in files: + for basename in sorted(files): if _is_file_valid(basename) and fnmatch.fnmatch(basename, pattern): filename = os.path.join(root, basename) yield filename diff --git a/mypyrc b/mypyrc new file mode 100644 index 00000000000..7c73d12e381 --- /dev/null +++ b/mypyrc @@ -0,0 +1,21 @@ +homeassistant/*.py +homeassistant/auth/ +homeassistant/util/ +homeassistant/helpers/__init__.py +homeassistant/helpers/aiohttp_client.py +homeassistant/helpers/area_registry.py +homeassistant/helpers/condition.py +homeassistant/helpers/deprecation.py +homeassistant/helpers/dispatcher.py +homeassistant/helpers/entity_values.py +homeassistant/helpers/entityfilter.py +homeassistant/helpers/icon.py +homeassistant/helpers/intent.py +homeassistant/helpers/json.py +homeassistant/helpers/location.py +homeassistant/helpers/signal.py +homeassistant/helpers/state.py +homeassistant/helpers/sun.py +homeassistant/helpers/temperature.py +homeassistant/helpers/translation.py +homeassistant/helpers/typing.py diff --git a/pylintrc b/pylintrc index a88aabe1936..7d349033f70 100644 --- a/pylintrc +++ b/pylintrc @@ -42,7 +42,6 @@ reports=no [TYPECHECK] # For attrs ignored-classes=_CountingAttr -generated-members=botocore.errorfactory [FORMAT] expected-line-ending-format=LF diff --git a/requirements_all.txt b/requirements_all.txt index 814f80c1597..a773c9c8c31 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -13,20 +13,20 @@ python-slugify==1.2.6 pytz>=2018.07 pyyaml>=3.13,<4 requests==2.21.0 -ruamel.yaml==0.15.88 +ruamel.yaml==0.15.89 voluptuous==0.11.5 voluptuous-serialize==2.1.0 # homeassistant.components.nuimo_controller --only-binary=all nuimo==0.1.0 -# homeassistant.components.sensor.dht +# homeassistant.components.dht.sensor # Adafruit-DHT==1.4.0 -# homeassistant.components.sensor.sht31 +# homeassistant.components.sht31.sensor Adafruit-GPIO==1.0.3 -# homeassistant.components.sensor.sht31 +# homeassistant.components.sht31.sensor Adafruit-SHT31==1.0.2 # homeassistant.components.bbb_gpio @@ -35,16 +35,16 @@ Adafruit-SHT31==1.0.2 # homeassistant.components.homekit HAP-python==2.4.2 -# homeassistant.components.notify.mastodon +# homeassistant.components.mastodon.notify Mastodon.py==1.3.1 -# homeassistant.components.sensor.github +# homeassistant.components.github.sensor PyGithub==1.43.5 # homeassistant.components.isy994 PyISY==1.1.1 -# homeassistant.components.sensor.mvglive +# homeassistant.components.mvglive.sensor PyMVGLive==1.1.4 # homeassistant.components.arduino @@ -57,13 +57,13 @@ PyNaCl==1.3.0 # homeassistant.auth.mfa_modules.totp PyQRCode==1.2.1 -# homeassistant.components.sensor.rmvtransport +# homeassistant.components.rmvtransport.sensor PyRMVtransport==0.1.3 -# homeassistant.components.switch.switchbot +# homeassistant.components.switchbot.switch # PySwitchbot==0.5 -# homeassistant.components.sensor.transport_nsw +# homeassistant.components.transport_nsw.sensor PyTransportNSW==0.1.1 # homeassistant.components.xiaomi_aqara @@ -75,25 +75,25 @@ PyXiaomiGateway==0.12.2 # homeassistant.components.remember_the_milk RtmAPI==0.7.0 -# homeassistant.components.sensor.travisci +# homeassistant.components.travisci.sensor TravisPy==0.3.5 -# homeassistant.components.notify.twitter +# homeassistant.components.twitter.notify TwitterAPI==2.5.9 # homeassistant.components.tof.sensor # VL53L1X2==0.1.5 -# homeassistant.components.sensor.waze_travel_time +# homeassistant.components.waze_travel_time.sensor WazeRouteCalculator==0.9 -# homeassistant.components.notify.yessssms +# homeassistant.components.yessssms.notify YesssSMS==0.2.3 # homeassistant.components.abode abodepy==0.15.0 -# homeassistant.components.media_player.frontier_silicon +# homeassistant.components.frontier_silicon.media_player afsapi==0.0.4 # homeassistant.components.ambient_station @@ -102,19 +102,22 @@ aioambient==0.1.3 # homeassistant.components.asuswrt aioasuswrt==1.1.21 -# homeassistant.components.device_tracker.automatic +# homeassistant.components.automatic.device_tracker aioautomatic==0.6.5 -# homeassistant.components.sensor.dnsip +# homeassistant.components.aws +aiobotocore==0.10.2 + +# homeassistant.components.dnsip.sensor aiodns==1.1.1 # homeassistant.components.esphome -aioesphomeapi==1.6.0 +aioesphomeapi==1.7.0 # homeassistant.components.freebox -aiofreepybox==0.0.6 +aiofreepybox==0.0.8 -# homeassistant.components.camera.yi +# homeassistant.components.yi.camera aioftp==0.12.0 # homeassistant.components.harmony.remote @@ -127,10 +130,7 @@ aiohttp_cors==0.7.0 # homeassistant.components.hue aiohue==1.9.1 -# homeassistant.components.sensor.iliad_italy -aioiliad==0.1.1 - -# homeassistant.components.sensor.imap +# homeassistant.components.imap.sensor aioimaplib==0.7.15 # homeassistant.components.lifx @@ -139,37 +139,37 @@ aiolifx==0.6.7 # homeassistant.components.lifx.light aiolifx_effects==0.2.1 -# homeassistant.components.scene.hunterdouglas_powerview +# homeassistant.components.hunterdouglas_powerview.scene aiopvapi==1.6.14 # homeassistant.components.unifi aiounifi==4 -# homeassistant.components.cover.aladdin_connect +# homeassistant.components.aladdin_connect.cover aladdin_connect==0.3 # homeassistant.components.alarmdecoder alarmdecoder==1.13.2 -# homeassistant.components.sensor.alpha_vantage +# homeassistant.components.alpha_vantage.sensor alpha_vantage==2.1.0 # homeassistant.components.amcrest -amcrest==1.2.5 +amcrest==1.2.7 # homeassistant.components.androidtv.media_player -androidtv==0.0.12 +androidtv==0.0.14 -# homeassistant.components.switch.anel_pwrctrl +# homeassistant.components.anel_pwrctrl.switch anel_pwrctrl-homeassistant==0.0.1.dev2 -# homeassistant.components.media_player.anthemav +# homeassistant.components.anthemav.media_player anthemav==1.1.10 # homeassistant.components.apcupsd apcaccess==0.0.13 -# homeassistant.components.notify.apns +# homeassistant.components.apns.notify apns2==0.3.0 # homeassistant.components.aqualogic @@ -179,37 +179,37 @@ aqualogic==1.0 asterisk_mbox==0.5.0 # homeassistant.components.upnp -# homeassistant.components.media_player.dlna_dmr -async-upnp-client==0.14.5 +# homeassistant.components.dlna_dmr.media_player +async-upnp-client==0.14.7 # homeassistant.components.stream av==6.1.2 -# homeassistant.components.light.avion +# homeassistant.components.avion.light # avion==0.10 # homeassistant.components.axis -axis==16 +axis==17 -# homeassistant.components.tts.baidu +# homeassistant.components.baidu.tts baidu-aip==1.6.6 -# homeassistant.components.sensor.modem_callerid +# homeassistant.components.modem_callerid.sensor basicmodem==0.7 -# homeassistant.components.sensor.linux_battery +# homeassistant.components.linux_battery.sensor batinfo==0.4.2 -# homeassistant.components.sensor.eddystone_temperature +# homeassistant.components.eddystone_temperature.sensor # beacontools[scan]==1.2.3 -# homeassistant.components.device_tracker.linksys_ap -# homeassistant.components.sensor.scrape -# homeassistant.components.sensor.sytadin +# homeassistant.components.linksys_ap.device_tracker +# homeassistant.components.scrape.sensor +# homeassistant.components.sytadin.sensor beautifulsoup4==4.7.1 # homeassistant.components.zha -bellows-homeassistant==0.7.1 +bellows-homeassistant==0.7.2 # homeassistant.components.bmw_connected_drive bimmer_connected==0.5.3 @@ -217,87 +217,84 @@ bimmer_connected==0.5.3 # homeassistant.components.blink blinkpy==0.13.1 -# homeassistant.components.light.blinksticklight +# homeassistant.components.blinksticklight.light blinkstick==1.1.8 -# homeassistant.components.light.blinkt +# homeassistant.components.blinkt.light # blinkt==0.1.0 -# homeassistant.components.sensor.bitcoin +# homeassistant.components.bitcoin.sensor blockchain==1.4.4 -# homeassistant.components.light.decora +# homeassistant.components.decora.light # bluepy==1.1.4 -# homeassistant.components.sensor.bme680 +# homeassistant.components.bme680.sensor # bme680==1.0.5 # homeassistant.components.route53 -# homeassistant.components.notify.aws_lambda -# homeassistant.components.notify.aws_sns -# homeassistant.components.notify.aws_sqs -# homeassistant.components.tts.amazon_polly +# homeassistant.components.amazon_polly.tts +# homeassistant.components.aws_lambda.notify +# homeassistant.components.aws_sns.notify +# homeassistant.components.aws_sqs.notify boto3==1.9.16 -# homeassistant.scripts.credstash -botocore==1.7.34 - -# homeassistant.components.media_player.braviatv +# homeassistant.components.braviatv.media_player braviarc-homeassistant==0.3.7.dev0 -# homeassistant.components.sensor.broadlink -# homeassistant.components.switch.broadlink +# homeassistant.components.broadlink.sensor +# homeassistant.components.broadlink.switch broadlink==0.9.0 -# homeassistant.components.sensor.brottsplatskartan +# homeassistant.components.brottsplatskartan.sensor brottsplatskartan==0.0.1 -# homeassistant.components.cover.brunt +# homeassistant.components.brunt.cover brunt==0.1.3 -# homeassistant.components.device_tracker.bluetooth_tracker +# homeassistant.components.bluetooth_tracker.device_tracker bt_proximity==0.1.2 -# homeassistant.components.device_tracker.bt_home_hub_5 +# homeassistant.components.bt_home_hub_5.device_tracker bthomehub5-devicelist==0.1.1 -# homeassistant.components.device_tracker.bt_smarthub +# homeassistant.components.bt_smarthub.device_tracker btsmarthub_devicelist==0.1.3 -# homeassistant.components.sensor.buienradar -# homeassistant.components.weather.buienradar +# homeassistant.components.buienradar.sensor +# homeassistant.components.buienradar.weather buienradar==0.91 -# homeassistant.components.calendar.caldav +# homeassistant.components.caldav.calendar caldav==0.5.0 # homeassistant.components.cisco_mobility_express.device_tracker -ciscomobilityexpress==0.1.2 +ciscomobilityexpress==0.1.5 -# homeassistant.components.notify.ciscospark +# homeassistant.components.ciscospark.notify ciscosparkapi==0.4.2 # homeassistant.components.cppm_tracker.device_tracker clearpasspy==1.0.2 -# homeassistant.components.sensor.co2signal +# homeassistant.components.co2signal.sensor co2signal==0.4.2 # homeassistant.components.coinbase coinbase==2.1.0 -# homeassistant.components.sensor.coinmarketcap +# homeassistant.components.coinmarketcap.sensor coinmarketcap==5.0.3 # homeassistant.scripts.check_config colorlog==4.0.2 -# homeassistant.components.alarm_control_panel.concord232 -# homeassistant.components.binary_sensor.concord232 +# homeassistant.components.concord232.alarm_control_panel +# homeassistant.components.concord232.binary_sensor concord232==0.15 -# homeassistant.components.climate.eq3btsmart -# homeassistant.components.sensor.eddystone_temperature +# homeassistant.components.eddystone_temperature.sensor +# homeassistant.components.eq3btsmart.climate # homeassistant.components.xiaomi_miio.device_tracker # homeassistant.components.xiaomi_miio.fan # homeassistant.components.xiaomi_miio.light @@ -310,48 +307,48 @@ construct==2.9.45 # homeassistant.scripts.credstash # credstash==1.15.0 -# homeassistant.components.sensor.crimereports +# homeassistant.components.crimereports.sensor crimereports==1.0.1 # homeassistant.components.datadog datadog==0.15.0 -# homeassistant.components.sensor.metoffice -# homeassistant.components.weather.metoffice +# homeassistant.components.metoffice.sensor +# homeassistant.components.metoffice.weather datapoint==0.4.3 -# homeassistant.components.light.decora +# homeassistant.components.decora.light # decora==0.6 -# homeassistant.components.light.decora_wifi +# homeassistant.components.decora_wifi.light # decora_wifi==1.3 # homeassistant.components.ihc # homeassistant.components.namecheapdns -# homeassistant.components.device_tracker.upc_connect -# homeassistant.components.sensor.ohmconnect +# homeassistant.components.ohmconnect.sensor +# homeassistant.components.upc_connect.device_tracker defusedxml==0.5.0 -# homeassistant.components.sensor.deluge -# homeassistant.components.switch.deluge +# homeassistant.components.deluge.sensor +# homeassistant.components.deluge.switch deluge-client==1.4.0 -# homeassistant.components.media_player.denonavr +# homeassistant.components.denonavr.media_player denonavr==0.7.8 -# homeassistant.components.media_player.directv +# homeassistant.components.directv.media_player directpy==0.5 -# homeassistant.components.sensor.discogs +# homeassistant.components.discogs.sensor discogs_client==2.2.1 -# homeassistant.components.notify.discord +# homeassistant.components.discord.notify discord.py==0.16.12 # homeassistant.components.updater distro==1.4.0 -# homeassistant.components.switch.digitalloggers +# homeassistant.components.digitalloggers.switch dlipower==0.7.165 # homeassistant.components.doorbird @@ -360,7 +357,7 @@ doorbirdpy==2.0.6 # homeassistant.components.dovado dovado==0.4.1 -# homeassistant.components.sensor.dsmr +# homeassistant.components.dsmr.sensor dsmr_parser==0.12 # homeassistant.components.dweet @@ -376,13 +373,10 @@ ecoaliface==0.4.0 # homeassistant.components.edp_redy edp_redy==0.0.3 -# homeassistant.components.device_tracker.ee_brightbox +# homeassistant.components.ee_brightbox.device_tracker eebrightbox==0.0.4 -# homeassistant.components.media_player.horizon -einder==0.3.1 - -# homeassistant.components.sensor.eliqonline +# homeassistant.components.eliqonline.sensor eliqonline==1.2.2 # homeassistant.components.elkm1 @@ -394,19 +388,19 @@ emulated_roku==0.1.8 # homeassistant.components.enocean enocean==0.40 -# homeassistant.components.sensor.entur_public_transport -enturclient==0.1.3 +# homeassistant.components.entur_public_transport.sensor +enturclient==0.2.0 -# homeassistant.components.sensor.envirophat +# homeassistant.components.envirophat.sensor # envirophat==0.0.6 -# homeassistant.components.sensor.enphase_envoy +# homeassistant.components.enphase_envoy.sensor envoy_reader==0.3 -# homeassistant.components.sensor.season +# homeassistant.components.season.sensor ephem==3.7.6.0 -# homeassistant.components.media_player.epson +# homeassistant.components.epson.media_player epson-projector==0.1.3 # homeassistant.components.netgear_lte @@ -416,17 +410,17 @@ eternalegypt==0.0.5 # evdev==0.6.1 # homeassistant.components.evohome -# homeassistant.components.climate.honeywell +# homeassistant.components.honeywell.climate evohomeclient==0.2.8 -# homeassistant.components.image_processing.dlib_face_detect -# homeassistant.components.image_processing.dlib_face_identify -# face_recognition==1.0.0 +# homeassistant.components.dlib_face_detect.image_processing +# homeassistant.components.dlib_face_identify.image_processing +# face_recognition==1.2.3 # homeassistant.components.fastdotcom fastdotcom==0.0.3 -# homeassistant.components.sensor.fedex +# homeassistant.components.fedex.sensor fedexdeliverymanager==1.0.6 # homeassistant.components.feedreader @@ -435,56 +429,56 @@ feedparser-homeassistant==5.2.2.dev1 # homeassistant.components.fibaro fiblary3==0.1.7 -# homeassistant.components.sensor.fints +# homeassistant.components.fints.sensor fints==1.0.1 -# homeassistant.components.sensor.fitbit +# homeassistant.components.fitbit.sensor fitbit==0.3.0 -# homeassistant.components.sensor.fixer +# homeassistant.components.fixer.sensor fixerio==1.0.0a0 -# homeassistant.components.light.flux_led +# homeassistant.components.flux_led.light flux_led==0.22 -# homeassistant.components.sensor.foobot +# homeassistant.components.foobot.sensor foobot_async==0.3.1 -# homeassistant.components.notify.free_mobile +# homeassistant.components.free_mobile.notify freesms==0.1.2 -# homeassistant.components.device_tracker.fritz -# homeassistant.components.sensor.fritzbox_callmonitor -# homeassistant.components.sensor.fritzbox_netmonitor +# homeassistant.components.fritz.device_tracker +# homeassistant.components.fritzbox_callmonitor.sensor +# homeassistant.components.fritzbox_netmonitor.sensor # fritzconnection==0.6.5 -# homeassistant.components.switch.fritzdect +# homeassistant.components.fritzdect.switch fritzhome==1.0.4 # homeassistant.components.google.tts gTTS-token==1.1.3 -# homeassistant.components.sensor.gearbest +# homeassistant.components.gearbest.sensor gearbest_parser==1.0.7 -# homeassistant.components.sensor.geizhals +# homeassistant.components.geizhals.sensor geizhals==0.0.9 -# homeassistant.components.geo_location.geo_json_events -# homeassistant.components.geo_location.nsw_rural_fire_service_feed -# homeassistant.components.geo_location.usgs_earthquakes_feed +# homeassistant.components.geo_json_events.geo_location +# homeassistant.components.nsw_rural_fire_service_feed.geo_location +# homeassistant.components.usgs_earthquakes_feed.geo_location geojson_client==0.3 -# homeassistant.components.sensor.geo_rss_events +# homeassistant.components.geo_rss_events.sensor georss_client==0.5 -# homeassistant.components.sensor.gitter +# homeassistant.components.gitter.sensor gitterpy==0.1.7 -# homeassistant.components.sensor.glances +# homeassistant.components.glances.sensor glances_api==0.2.0 -# homeassistant.components.notify.gntp +# homeassistant.components.gntp.notify gntp==1.0.3 # homeassistant.components.google @@ -496,25 +490,25 @@ google-cloud-pubsub==0.39.1 # homeassistant.components.googlehome googledevices==1.0.2 -# homeassistant.components.sensor.google_travel_time +# homeassistant.components.google_travel_time.sensor googlemaps==2.5.1 -# homeassistant.components.sensor.gpsd +# homeassistant.components.gpsd.sensor gps3==0.33.3 # homeassistant.components.greeneye_monitor greeneye_monitor==1.0 -# homeassistant.components.light.greenwave +# homeassistant.components.greenwave.light greenwavereality==0.5.1 -# homeassistant.components.media_player.gstreamer +# homeassistant.components.gstreamer.media_player gstreamer-player==1.1.2 # homeassistant.components.ffmpeg -ha-ffmpeg==1.11 +ha-ffmpeg==2.0 -# homeassistant.components.media_player.philips_js +# homeassistant.components.philips_js.media_player ha-philipsjs==0.0.5 # homeassistant.components.habitica @@ -529,35 +523,35 @@ hass-nabucasa==0.11 # homeassistant.components.mqtt.server hbmqtt==0.9.4 -# homeassistant.components.sensor.jewish_calendar +# homeassistant.components.jewish_calendar.sensor hdate==0.8.7 -# homeassistant.components.climate.heatmiser +# homeassistant.components.heatmiser.climate heatmiserV3==0.9.1 -# homeassistant.components.switch.hikvisioncam +# homeassistant.components.hikvisioncam.switch hikvision==0.4 -# homeassistant.components.notify.hipchat +# homeassistant.components.hipchat.notify hipnotify==1.0.8 -# homeassistant.components.media_player.harman_kardon_avr +# homeassistant.components.harman_kardon_avr.media_player hkavr==0.0.5 # homeassistant.components.hlk_sw16 -hlk-sw16==0.0.6 +hlk-sw16==0.0.7 -# homeassistant.components.sensor.pi_hole +# homeassistant.components.pi_hole.sensor hole==0.3.0 -# homeassistant.components.binary_sensor.workday -holidays==0.9.9 +# homeassistant.components.workday.binary_sensor +holidays==0.9.10 # homeassistant.components.frontend -home-assistant-frontend==20190321.0 +home-assistant-frontend==20190331.0 # homeassistant.components.zwave -homeassistant-pyozw==0.1.2 +homeassistant-pyozw==0.1.3 # homeassistant.components.homekit_controller homekit[IP]==0.13.0 @@ -565,6 +559,9 @@ homekit[IP]==0.13.0 # homeassistant.components.homematicip_cloud homematicip==0.10.6 +# homeassistant.components.horizon.media_player +horimote==0.4.1 + # homeassistant.components.google # homeassistant.components.remember_the_milk httplib2==0.10.3 @@ -575,22 +572,22 @@ huawei-lte-api==1.1.5 # homeassistant.components.hydrawise hydrawiser==0.1.1 -# homeassistant.components.sensor.bh1750 -# homeassistant.components.sensor.bme280 -# homeassistant.components.sensor.htu21d +# homeassistant.components.bh1750.sensor +# homeassistant.components.bme280.sensor +# homeassistant.components.htu21d.sensor # i2csense==0.0.4 # homeassistant.components.watson_iot ibmiotf==0.3.4 -# homeassistant.components.light.iglo +# homeassistant.components.iglo.light iglo==1.2.7 # homeassistant.components.ihc ihcsdk==2.3.0 # homeassistant.components.influxdb -# homeassistant.components.sensor.influxdb +# homeassistant.components.influxdb.sensor influxdb==5.2.0 # homeassistant.components.insteon @@ -605,11 +602,11 @@ ipify==1.0.0 # homeassistant.components.verisure jsonpath==0.75 -# homeassistant.components.media_player.kodi -# homeassistant.components.notify.kodi +# homeassistant.components.kodi.media_player +# homeassistant.components.kodi.notify jsonrpc-async==0.6 -# homeassistant.components.media_player.kodi +# homeassistant.components.kodi.media_player jsonrpc-websocket==0.6 # homeassistant.scripts.keyring @@ -618,7 +615,7 @@ keyring==17.1.1 # homeassistant.scripts.keyring keyrings.alt==3.1.1 -# homeassistant.components.lock.kiwi +# homeassistant.components.kiwi.lock kiwiki-client==0.1.1 # homeassistant.components.konnected @@ -630,44 +627,44 @@ lakeside==0.12 # homeassistant.components.dyson libpurecoollink==0.4.2 -# homeassistant.components.camera.foscam +# homeassistant.components.foscam.camera libpyfoscam==1.0 -# homeassistant.components.device_tracker.mikrotik +# homeassistant.components.mikrotik.device_tracker librouteros==2.2.0 -# homeassistant.components.media_player.soundtouch +# homeassistant.components.soundtouch.media_player libsoundtouch==0.7.2 -# homeassistant.components.light.lifx_legacy +# homeassistant.components.lifx_legacy.light liffylights==0.9.4 -# homeassistant.components.light.osramlightify +# homeassistant.components.osramlightify.light lightify==1.0.6.1 # homeassistant.components.lightwave lightwave==0.15 -# homeassistant.components.light.limitlessled +# homeassistant.components.limitlessled.light limitlessled==1.1.3 # homeassistant.components.linode linode-api==4.1.9b1 -# homeassistant.components.media_player.liveboxplaytv +# homeassistant.components.liveboxplaytv.media_player liveboxplaytv==2.0.2 # homeassistant.components.lametric # homeassistant.components.lametric.notify lmnotify==0.0.4 -# homeassistant.components.device_tracker.google_maps +# homeassistant.components.google_maps.device_tracker locationsharinglib==3.0.11 # homeassistant.components.logi_circle logi_circle==0.1.7 -# homeassistant.components.sensor.london_underground +# homeassistant.components.london_underground.sensor london-tube-status==0.2 # homeassistant.components.luftdaten @@ -676,13 +673,13 @@ luftdaten==0.3.4 # homeassistant.components.lupusec lupupy==0.0.17 -# homeassistant.components.light.lw12wifi +# homeassistant.components.lw12wifi.light lw12==0.9.2 -# homeassistant.components.sensor.lyft +# homeassistant.components.lyft.sensor lyft_rides==0.2 -# homeassistant.components.sensor.magicseaweed +# homeassistant.components.magicseaweed.sensor magicseaweed==1.0.3 # homeassistant.components.matrix @@ -694,26 +691,26 @@ maxcube-api==0.1.0 # homeassistant.components.mythicbeastsdns mbddns==0.1.2 -# homeassistant.components.notify.message_bird +# homeassistant.components.message_bird.notify messagebird==1.2.0 # homeassistant.components.meteo_france meteofrance==0.3.4 -# homeassistant.components.sensor.mfi -# homeassistant.components.switch.mfi +# homeassistant.components.mfi.sensor +# homeassistant.components.mfi.switch mficlient==0.3.0 -# homeassistant.components.sensor.miflora +# homeassistant.components.miflora.sensor miflora==0.4.0 -# homeassistant.components.climate.mill +# homeassistant.components.mill.climate millheater==0.3.4 -# homeassistant.components.sensor.mitemp_bt +# homeassistant.components.mitemp_bt.sensor mitemp_bt==0.0.1 -# homeassistant.components.sensor.mopar +# homeassistant.components.mopar motorparts==1.1.0 # homeassistant.components.tts @@ -728,95 +725,95 @@ mycroftapi==2.0 # homeassistant.components.usps myusps==1.3.2 -# homeassistant.components.media_player.nad +# homeassistant.components.nad.media_player nad_receiver==0.0.11 -# homeassistant.components.device_tracker.keenetic_ndms2 +# homeassistant.components.keenetic_ndms2.device_tracker ndms2_client==0.0.6 # homeassistant.components.ness_alarm -nessclient==0.9.14 +nessclient==0.9.15 -# homeassistant.components.sensor.netdata +# homeassistant.components.netdata.sensor netdata==0.1.2 # homeassistant.components.discovery netdisco==2.5.0 -# homeassistant.components.sensor.neurio_energy +# homeassistant.components.neurio_energy.sensor neurio==0.3.1 -# homeassistant.components.light.niko_home_control +# homeassistant.components.niko_home_control.light niko-home-control==0.1.8 -# homeassistant.components.air_quality.nilu +# homeassistant.components.nilu.air_quality niluclient==0.1.2 -# homeassistant.components.sensor.nederlandse_spoorwegen +# homeassistant.components.nederlandse_spoorwegen.sensor nsapi==2.7.4 -# homeassistant.components.sensor.nsw_fuel_station +# homeassistant.components.nsw_fuel_station.sensor nsw-fuel-api-client==1.0.10 # homeassistant.components.nuheat nuheat==0.3.0 -# homeassistant.components.binary_sensor.trend -# homeassistant.components.image_processing.opencv -# homeassistant.components.image_processing.tensorflow -# homeassistant.components.sensor.pollen +# homeassistant.components.opencv.image_processing +# homeassistant.components.pollen.sensor +# homeassistant.components.tensorflow.image_processing +# homeassistant.components.trend.binary_sensor numpy==1.16.2 # homeassistant.components.google oauth2client==4.0.0 -# homeassistant.components.climate.oem +# homeassistant.components.oem.climate oemthermostat==1.1 -# homeassistant.components.media_player.onkyo +# homeassistant.components.onkyo.media_player onkyo-eiscp==1.2.4 -# homeassistant.components.camera.onvif +# homeassistant.components.onvif.camera onvif-py3==0.1.3 -# homeassistant.components.sensor.openevse +# homeassistant.components.openevse.sensor openevsewifi==0.4 -# homeassistant.components.media_player.openhome +# homeassistant.components.openhome.media_player openhomedevice==0.4.2 -# homeassistant.components.air_quality.opensensemap +# homeassistant.components.opensensemap.air_quality opensensemap-api==0.1.5 # homeassistant.components.enigma2.media_player -openwebifpy==1.2.7 +openwebifpy==3.1.0 -# homeassistant.components.device_tracker.luci +# homeassistant.components.luci.device_tracker openwrt-luci-rpc==1.0.5 -# homeassistant.components.switch.orvibo +# homeassistant.components.orvibo.switch orvibo==1.1.1 # homeassistant.components.mqtt # homeassistant.components.shiftr paho-mqtt==1.4.0 -# homeassistant.components.media_player.panasonic_bluray +# homeassistant.components.panasonic_bluray.media_player panacotta==0.1 -# homeassistant.components.media_player.panasonic_viera -panasonic_viera==0.3.1 +# homeassistant.components.panasonic_viera.media_player +panasonic_viera==0.3.2 -# homeassistant.components.media_player.dunehd +# homeassistant.components.dunehd.media_player pdunehd==1.3 -# homeassistant.components.switch.pencom +# homeassistant.components.pencom.switch pencompy==0.0.3 -# homeassistant.components.device_tracker.aruba -# homeassistant.components.device_tracker.cisco_ios -# homeassistant.components.device_tracker.unifi_direct -# homeassistant.components.media_player.pandora +# homeassistant.components.aruba.device_tracker +# homeassistant.components.cisco_ios.device_tracker +# homeassistant.components.pandora.media_player +# homeassistant.components.unifi_direct.device_tracker pexpect==4.6.0 # homeassistant.components.rpi_pfio @@ -825,69 +822,69 @@ pifacecommon==4.2.2 # homeassistant.components.rpi_pfio pifacedigitalio==3.0.5 -# homeassistant.components.light.piglow +# homeassistant.components.piglow.light piglow==1.2.4 # homeassistant.components.pilight pilight==0.1.1 -# homeassistant.components.camera.proxy -# homeassistant.components.image_processing.qrcode -# homeassistant.components.image_processing.tensorflow +# homeassistant.components.proxy.camera +# homeassistant.components.qrcode.image_processing +# homeassistant.components.tensorflow.image_processing pillow==5.4.1 # homeassistant.components.dominos pizzapi==0.0.3 -# homeassistant.components.media_player.plex -# homeassistant.components.sensor.plex +# homeassistant.components.plex.media_player +# homeassistant.components.plex.sensor plexapi==3.0.6 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 -# homeassistant.components.sensor.mhz19 -# homeassistant.components.sensor.serial_pm +# homeassistant.components.mhz19.sensor +# homeassistant.components.serial_pm.sensor pmsensor==0.4 -# homeassistant.components.sensor.pocketcasts +# homeassistant.components.pocketcasts.sensor pocketcasts==0.1 -# homeassistant.components.sensor.postnl +# homeassistant.components.postnl.sensor postnl_api==1.0.2 # homeassistant.components.reddit.sensor praw==6.1.1 -# homeassistant.components.sensor.islamic_prayer_times +# homeassistant.components.islamic_prayer_times.sensor prayer_times_calculator==0.0.3 -# homeassistant.components.sensor.prezzibenzina +# homeassistant.components.prezzibenzina.sensor prezzibenzina-py==1.1.4 -# homeassistant.components.climate.proliphix +# homeassistant.components.proliphix.climate proliphix==0.4.1 # homeassistant.components.prometheus prometheus_client==0.2.0 -# homeassistant.components.image_processing.tensorflow +# homeassistant.components.tensorflow.image_processing protobuf==3.6.1 -# homeassistant.components.sensor.systemmonitor -psutil==5.5.1 +# homeassistant.components.systemmonitor.sensor +psutil==5.6.1 # homeassistant.components.wink pubnubsub-handler==1.0.3 -# homeassistant.components.notify.pushbullet -# homeassistant.components.sensor.pushbullet +# homeassistant.components.pushbullet.notify +# homeassistant.components.pushbullet.sensor pushbullet.py==0.11.0 -# homeassistant.components.notify.pushetta +# homeassistant.components.pushetta.notify pushetta==1.0.15 -# homeassistant.components.light.rpi_gpio_pwm +# homeassistant.components.rpi_gpio_pwm.light pwmled==1.4.1 # homeassistant.components.august @@ -896,16 +893,16 @@ py-august==0.7.0 # homeassistant.components.canary py-canary==0.5.0 -# homeassistant.components.sensor.cpuspeed -py-cpuinfo==4.0.0 +# homeassistant.components.cpuspeed.sensor +py-cpuinfo==5.0.0 # homeassistant.components.melissa py-melissa-climate==2.0.0 -# homeassistant.components.camera.synology +# homeassistant.components.synology.camera py-synology==0.2.0 -# homeassistant.components.sensor.seventeentrack +# homeassistant.components.seventeentrack.sensor py17track==2.2.2 # homeassistant.components.hdmi_cec @@ -914,57 +911,57 @@ pyCEC==0.4.13 # homeassistant.components.tplink pyHS100==0.3.4 -# homeassistant.components.air_quality.norway_air -# homeassistant.components.weather.met +# homeassistant.components.met.weather +# homeassistant.components.norway_air.air_quality pyMetno==0.4.6 # homeassistant.components.rfxtrx pyRFXtrx==0.23 -# homeassistant.components.switch.switchmate +# homeassistant.components.switchmate.switch # pySwitchmate==0.4.5 # homeassistant.components.tibber -pyTibber==0.9.6 +pyTibber==0.10.1 -# homeassistant.components.switch.dlink +# homeassistant.components.dlink.switch pyW215==0.6.0 # homeassistant.components.w800rf32 pyW800rf32==0.1 -# homeassistant.components.sensor.noaa_tides +# homeassistant.components.noaa_tides.sensor # py_noaa==0.3.0 # homeassistant.components.ads pyads==3.0.7 -# homeassistant.components.sensor.aftership +# homeassistant.components.aftership.sensor pyaftership==0.1.2 -# homeassistant.components.sensor.airvisual +# homeassistant.components.airvisual.sensor pyairvisual==3.0.1 -# homeassistant.components.alarm_control_panel.alarmdotcom +# homeassistant.components.alarmdotcom.alarm_control_panel pyalarmdotcom==0.3.2 # homeassistant.components.arlo pyarlo==0.2.3 # homeassistant.components.netatmo -pyatmo==1.8 +pyatmo==1.9 # homeassistant.components.apple_tv pyatv==0.3.12 -# homeassistant.components.device_tracker.bbox -# homeassistant.components.sensor.bbox +# homeassistant.components.bbox.device_tracker +# homeassistant.components.bbox.sensor pybbox==0.0.5-alpha -# homeassistant.components.media_player.blackbird +# homeassistant.components.blackbird.media_player pyblackbird==0.5 -# homeassistant.components.device_tracker.bluetooth_tracker +# homeassistant.components.bluetooth_tracker.device_tracker # pybluez==0.22 # homeassistant.components.neato @@ -976,35 +973,35 @@ pycarwings2==2.8 # homeassistant.components.cloudflare pycfdns==0.0.1 -# homeassistant.components.media_player.channels +# homeassistant.components.channels.media_player pychannels==1.0.0 # homeassistant.components.cast pychromecast==3.0.0 -# homeassistant.components.media_player.cmus +# homeassistant.components.cmus.media_player pycmus==0.1.1 # homeassistant.components.comfoconnect pycomfoconnect==0.3 -# homeassistant.components.climate.coolmaster +# homeassistant.components.coolmaster.climate pycoolmasternet==0.0.4 -# homeassistant.components.tts.microsoft +# homeassistant.components.microsoft.tts pycsspeechtts==1.0.2 -# homeassistant.components.sensor.cups +# homeassistant.components.cups.sensor # pycups==1.9.73 # homeassistant.components.daikin -pydaikin==0.9 +pydaikin==1.3.1 # homeassistant.components.danfoss_air pydanfossair==0.0.7 # homeassistant.components.deconz -pydeconz==52 +pydeconz==54 # homeassistant.components.zwave pydispatcher==2.0.5 @@ -1012,46 +1009,46 @@ pydispatcher==2.0.5 # homeassistant.components.android_ip_webcam pydroid-ipcam==0.8 -# homeassistant.components.sensor.duke_energy +# homeassistant.components.duke_energy.sensor pydukeenergy==0.0.6 -# homeassistant.components.sensor.ebox +# homeassistant.components.ebox.sensor pyebox==1.1.4 -# homeassistant.components.water_heater.econet +# homeassistant.components.econet.water_heater pyeconet==0.0.10 -# homeassistant.components.switch.edimax +# homeassistant.components.edimax.switch pyedimax==0.1 # homeassistant.components.eight_sleep pyeight==0.1.1 -# homeassistant.components.media_player.emby +# homeassistant.components.emby.media_player pyemby==1.6 # homeassistant.components.envisalink pyenvisalink==3.8 -# homeassistant.components.climate.ephember +# homeassistant.components.ephember.climate pyephember==0.2.0 -# homeassistant.components.light.everlights +# homeassistant.components.everlights.light pyeverlights==0.1.0 -# homeassistant.components.sensor.fido +# homeassistant.components.fido.sensor pyfido==2.1.1 -# homeassistant.components.climate.flexit +# homeassistant.components.flexit.climate pyflexit==0.3 -# homeassistant.components.binary_sensor.flic +# homeassistant.components.flic.binary_sensor pyflic-homeassistant==0.4.dev0 -# homeassistant.components.sensor.flunearyou +# homeassistant.components.flunearyou.sensor pyflunearyou==1.0.3 -# homeassistant.components.light.futurenow +# homeassistant.components.futurenow.light pyfnip==0.2 # homeassistant.components.fritzbox @@ -1060,23 +1057,23 @@ pyfritzhome==0.4.0 # homeassistant.components.ifttt pyfttt==0.3 -# homeassistant.components.device_tracker.bluetooth_le_tracker -# homeassistant.components.sensor.skybeacon +# homeassistant.components.bluetooth_le_tracker.device_tracker +# homeassistant.components.skybeacon.sensor pygatt[GATTTOOL]==3.2.0 -# homeassistant.components.cover.gogogate2 +# homeassistant.components.gogogate2.cover pygogogate2==0.1.1 -# homeassistant.components.sensor.gtfs +# homeassistant.components.gtfs.sensor pygtfs==0.1.5 -# homeassistant.components.sensor.gtt +# homeassistant.components.gtt.sensor pygtt==1.1.2 -# homeassistant.components.sensor.version +# homeassistant.components.version.sensor pyhaversion==2.0.3 -# homeassistant.components.binary_sensor.hikvision +# homeassistant.components.hikvision.binary_sensor pyhik==0.2.2 # homeassistant.components.hive @@ -1088,22 +1085,22 @@ pyhomematic==0.1.58 # homeassistant.components.homeworks pyhomeworks==0.0.6 -# homeassistant.components.sensor.hydroquebec +# homeassistant.components.hydroquebec.sensor pyhydroquebec==2.2.2 -# homeassistant.components.alarm_control_panel.ialarm +# homeassistant.components.ialarm.alarm_control_panel pyialarm==0.3 -# homeassistant.components.device_tracker.icloud +# homeassistant.components.icloud.device_tracker pyicloud==0.9.1 # homeassistant.components.ipma.weather pyipma==1.2.1 -# homeassistant.components.sensor.irish_rail_transport +# homeassistant.components.irish_rail_transport.sensor pyirishrail==0.0.2 -# homeassistant.components.binary_sensor.iss +# homeassistant.components.iss.binary_sensor pyiss==1.0.1 # homeassistant.components.itach.remote @@ -1112,32 +1109,32 @@ pyitachip2ir==0.0.7 # homeassistant.components.kira pykira==0.1.1 -# homeassistant.components.sensor.kwb +# homeassistant.components.kwb.sensor pykwb==0.0.8 -# homeassistant.components.sensor.lacrosse +# homeassistant.components.lacrosse.sensor pylacrosse==0.3.1 -# homeassistant.components.sensor.lastfm -pylast==3.0.0 +# homeassistant.components.lastfm.sensor +pylast==3.1.0 -# homeassistant.components.sensor.launch_library +# homeassistant.components.launch_library.sensor pylaunches==0.2.0 -# homeassistant.components.media_player.lg_netcast +# homeassistant.components.lg_netcast.media_player pylgnetcast-homeassistant==0.2.0.dev0 # homeassistant.components.webostv.media_player # homeassistant.components.webostv.notify pylgtv==0.1.9 -# homeassistant.components.sensor.linky +# homeassistant.components.linky.sensor pylinky==0.3.0 # homeassistant.components.litejet pylitejet==0.1 -# homeassistant.components.sensor.loopenergy +# homeassistant.components.loopenergy.sensor pyloopenergy==0.1.0 # homeassistant.components.lutron_caseta @@ -1149,10 +1146,10 @@ pylutron==0.2.0 # homeassistant.components.mailgun.notify pymailgunner==1.4 -# homeassistant.components.media_player.mediaroom +# homeassistant.components.mediaroom.media_player pymediaroom==0.6.4 -# homeassistant.components.media_player.xiaomi_tv +# homeassistant.components.xiaomi_tv.media_player pymitv==1.4.3 # homeassistant.components.mochad @@ -1161,116 +1158,116 @@ pymochad==0.2.0 # homeassistant.components.modbus pymodbus==1.5.2 -# homeassistant.components.media_player.monoprice +# homeassistant.components.monoprice.media_player pymonoprice==0.3 -# homeassistant.components.media_player.yamaha_musiccast +# homeassistant.components.yamaha_musiccast.media_player pymusiccast==0.1.6 -# homeassistant.components.cover.myq +# homeassistant.components.myq.cover pymyq==1.1.0 # homeassistant.components.mysensors pymysensors==0.18.0 -# homeassistant.components.light.nanoleaf +# homeassistant.components.nanoleaf.light pynanoleaf==0.0.5 -# homeassistant.components.lock.nello +# homeassistant.components.nello.lock pynello==2.0.2 -# homeassistant.components.device_tracker.netgear +# homeassistant.components.netgear.device_tracker pynetgear==0.5.2 -# homeassistant.components.switch.netio +# homeassistant.components.netio.switch pynetio==0.1.9.1 -# homeassistant.components.lock.nuki +# homeassistant.components.nuki.lock pynuki==1.3.2 -# homeassistant.components.sensor.nut +# homeassistant.components.nut.sensor pynut2==2.1.2 -# homeassistant.components.alarm_control_panel.nx584 -# homeassistant.components.binary_sensor.nx584 +# homeassistant.components.nx584.alarm_control_panel +# homeassistant.components.nx584.binary_sensor pynx584==0.4 # homeassistant.components.openuv pyopenuv==1.0.9 -# homeassistant.components.light.opple +# homeassistant.components.opple.light pyoppleio==1.0.5 # homeassistant.components.iota pyota==2.0.5 # homeassistant.components.opentherm_gw -pyotgw==0.4b2 +pyotgw==0.4b3 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp -# homeassistant.components.sensor.otp +# homeassistant.components.otp.sensor pyotp==2.2.6 # homeassistant.components.owlet pyowlet==1.0.2 -# homeassistant.components.sensor.openweathermap -# homeassistant.components.weather.openweathermap +# homeassistant.components.openweathermap.sensor +# homeassistant.components.openweathermap.weather pyowm==2.10.0 # homeassistant.components.lcn pypck==0.5.9 -# homeassistant.components.media_player.pjlink +# homeassistant.components.pjlink.media_player pypjlink2==1.2.0 # homeassistant.components.point pypoint==1.1.1 -# homeassistant.components.sensor.pollen +# homeassistant.components.pollen.sensor pypollencom==2.2.3 # homeassistant.components.ps4 -pyps4-homeassistant==0.4.8 +pyps4-homeassistant==0.5.2 # homeassistant.components.qwikswitch pyqwikswitch==0.8 -# homeassistant.components.sensor.nmbs +# homeassistant.components.nmbs.sensor pyrail==0.0.3 # homeassistant.components.rainbird pyrainbird==0.1.6 -# homeassistant.components.switch.recswitch +# homeassistant.components.recswitch.switch pyrecswitch==1.0.2 -# homeassistant.components.sensor.ruter +# homeassistant.components.ruter.sensor pyruter==1.1.0 # homeassistant.components.sabnzbd pysabnzbd==1.1.0 -# homeassistant.components.switch.sony_projector +# homeassistant.components.sony_projector.switch pysdcp==1 -# homeassistant.components.climate.sensibo +# homeassistant.components.sensibo.climate pysensibo==1.0.3 -# homeassistant.components.sensor.serial +# homeassistant.components.serial.sensor pyserial-asyncio==0.4 -# homeassistant.components.switch.acer_projector +# homeassistant.components.acer_projector.switch pyserial==3.1.1 -# homeassistant.components.lock.sesame +# homeassistant.components.sesame.lock pysesame==0.1.0 # homeassistant.components.goalfeed pysher==1.0.1 -# homeassistant.components.sensor.sma +# homeassistant.components.sma.sensor pysma==0.3.1 # homeassistant.components.smartthings @@ -1279,9 +1276,9 @@ pysmartapp==0.3.2 # homeassistant.components.smartthings pysmartthings==0.6.7 -# homeassistant.components.device_tracker.snmp -# homeassistant.components.sensor.snmp -# homeassistant.components.switch.snmp +# homeassistant.components.snmp.device_tracker +# homeassistant.components.snmp.sensor +# homeassistant.components.snmp.switch pysnmp==4.4.8 # homeassistant.components.sonos @@ -1290,26 +1287,29 @@ pysonos==0.0.8 # homeassistant.components.spc pyspcwebgw==0.4.0 -# homeassistant.components.notify.stride +# homeassistant.components.stride.notify pystride==0.1.7 -# homeassistant.components.sensor.syncthru +# homeassistant.components.syncthru.sensor pysyncthru==0.3.1 -# homeassistant.components.sensor.tautulli +# homeassistant.components.tautulli.sensor pytautulli==0.5.0 -# homeassistant.components.media_player.liveboxplaytv +# homeassistant.components.liveboxplaytv.media_player pyteleloisirs==3.4 +# homeassistant.components.tfiac.climate +pytfiac==0.3 + # homeassistant.components.thinkingcleaner.sensor # homeassistant.components.thinkingcleaner.switch pythinkingcleaner==0.0.3 -# homeassistant.components.sensor.blockchain +# homeassistant.components.blockchain.sensor python-blockchain-api==0.0.2 -# homeassistant.components.media_player.clementine +# homeassistant.components.clementine.media_player python-clementine-remote==1.0.1 # homeassistant.components.digital_ocean @@ -1318,31 +1318,31 @@ python-digitalocean==1.13.2 # homeassistant.components.ecobee python-ecobee-api==0.0.18 -# homeassistant.components.climate.eq3btsmart +# homeassistant.components.eq3btsmart.climate # python-eq3bt==0.1.9 -# homeassistant.components.sensor.etherscan +# homeassistant.components.etherscan.sensor python-etherscan-api==0.0.3 -# homeassistant.components.camera.familyhub +# homeassistant.components.familyhub.camera python-family-hub-local==0.0.2 -# homeassistant.components.sensor.darksky -# homeassistant.components.weather.darksky +# homeassistant.components.darksky.sensor +# homeassistant.components.darksky.weather python-forecastio==1.4.0 # homeassistant.components.gc100 python-gc100==1.0.3a -# homeassistant.components.sensor.gitlab_ci +# homeassistant.components.gitlab_ci.sensor python-gitlab==1.6.0 -# homeassistant.components.sensor.hp_ilo +# homeassistant.components.hp_ilo.sensor python-hpilo==3.9 # homeassistant.components.joaoapps_join # homeassistant.components.joaoapps_join.notify -python-join-api==0.0.2 +python-join-api==0.0.4 # homeassistant.components.juicenet python-juicenet==0.0.5 @@ -1357,40 +1357,40 @@ python-juicenet==0.0.5 # homeassistant.components.xiaomi_miio.sensor # homeassistant.components.xiaomi_miio.switch # homeassistant.components.xiaomi_miio.vacuum -python-miio==0.4.4 +python-miio==0.4.5 -# homeassistant.components.media_player.mpd +# homeassistant.components.mpd.media_player python-mpd2==1.0.0 -# homeassistant.components.light.mystrom -# homeassistant.components.switch.mystrom +# homeassistant.components.mystrom.light +# homeassistant.components.mystrom.switch python-mystrom==0.5.0 # homeassistant.components.nest python-nest==4.1.0 -# homeassistant.components.device_tracker.nmap_tracker +# homeassistant.components.nmap_tracker.device_tracker python-nmap==0.6.1 -# homeassistant.components.notify.pushover +# homeassistant.components.pushover.notify python-pushover==0.3 -# homeassistant.components.sensor.qbittorrent +# homeassistant.components.qbittorrent.sensor python-qbittorrent==0.3.1 -# homeassistant.components.sensor.ripple +# homeassistant.components.ripple.sensor python-ripple-api==0.0.3 # homeassistant.components.roku python-roku==3.1.5 -# homeassistant.components.sensor.sochain +# homeassistant.components.sochain.sensor python-sochain-api==0.0.2 -# homeassistant.components.media_player.songpal +# homeassistant.components.songpal.media_player python-songpal==0.0.9.1 -# homeassistant.components.sensor.synologydsm +# homeassistant.components.synologydsm.sensor python-synology==0.2.0 # homeassistant.components.tado @@ -1399,55 +1399,55 @@ python-tado==0.2.9 # homeassistant.components.telegram_bot python-telegram-bot==11.1.0 -# homeassistant.components.sensor.twitch +# homeassistant.components.twitch.sensor python-twitch-client==0.6.0 # homeassistant.components.velbus python-velbus==2.0.22 -# homeassistant.components.media_player.vlc +# homeassistant.components.vlc.media_player python-vlc==1.1.2 -# homeassistant.components.sensor.whois +# homeassistant.components.whois.sensor python-whois==0.7.1 # homeassistant.components.wink python-wink==1.10.3 -# homeassistant.components.sensor.awair +# homeassistant.components.awair.sensor python_awair==0.0.3 -# homeassistant.components.sensor.swiss_public_transport +# homeassistant.components.swiss_public_transport.sensor python_opendata_transport==0.1.4 # homeassistant.components.egardia pythonegardia==1.0.39 -# homeassistant.components.device_tracker.tile +# homeassistant.components.tile.device_tracker pytile==2.0.6 -# homeassistant.components.climate.touchline +# homeassistant.components.touchline.climate pytouchline==0.7 -# homeassistant.components.device_tracker.traccar -pytraccar==0.3.0 +# homeassistant.components.traccar.device_tracker +pytraccar==0.5.0 -# homeassistant.components.device_tracker.trackr +# homeassistant.components.trackr.device_tracker pytrackr==0.0.5 # homeassistant.components.tradfri pytradfri[async]==6.0.1 -# homeassistant.components.sensor.trafikverket_weatherstation -pytrafikverket==0.1.5.8 +# homeassistant.components.trafikverket_weatherstation.sensor +pytrafikverket==0.1.5.9 -# homeassistant.components.device_tracker.ubee +# homeassistant.components.ubee.device_tracker pyubee==0.2 -# homeassistant.components.device_tracker.unifi +# homeassistant.components.unifi.device_tracker pyunifi==2.16 -# homeassistant.components.binary_sensor.uptimerobot +# homeassistant.components.uptimerobot.binary_sensor pyuptimerobot==0.0.5 # homeassistant.components.keyboard @@ -1456,40 +1456,40 @@ pyuptimerobot==0.0.5 # homeassistant.components.vera pyvera==0.2.45 -# homeassistant.components.switch.vesync +# homeassistant.components.vesync.switch pyvesync_v2==0.9.6 -# homeassistant.components.media_player.vizio +# homeassistant.components.vizio.media_player pyvizio==0.0.4 # homeassistant.components.velux pyvlx==0.2.10 -# homeassistant.components.notify.html5 +# homeassistant.components.html5.notify pywebpush==1.6.0 # homeassistant.components.wemo pywemo==0.4.34 -# homeassistant.components.camera.xeoma +# homeassistant.components.xeoma.camera pyxeoma==1.4.1 # homeassistant.components.zabbix pyzabbix==0.7.4 -# homeassistant.components.image_processing.qrcode +# homeassistant.components.qrcode.image_processing pyzbar==0.1.7 -# homeassistant.components.sensor.qnap +# homeassistant.components.qnap.sensor qnapstats==0.2.7 -# homeassistant.components.device_tracker.quantum_gateway +# homeassistant.components.quantum_gateway.device_tracker quantum-gateway==0.0.5 # homeassistant.components.rachio rachiopy==0.1.3 -# homeassistant.components.climate.radiotherm +# homeassistant.components.radiotherm.climate radiotherm==2.0.0 # homeassistant.components.raincloud @@ -1498,10 +1498,10 @@ raincloudy==0.0.5 # homeassistant.components.raspihats # raspihats==2.2.3 -# homeassistant.components.switch.raspyrfm +# homeassistant.components.raspyrfm.switch raspyrfm-client==1.2.8 -# homeassistant.components.sensor.recollect_waste +# homeassistant.components.recollect_waste.sensor recollect-waste==1.0.1 # homeassistant.components.rainmachine @@ -1517,64 +1517,64 @@ rfk101py==0.0.1 rflink==0.0.37 # homeassistant.components.ring -ring_doorbell==0.2.2 +ring_doorbell==0.2.3 -# homeassistant.components.device_tracker.ritassist +# homeassistant.components.ritassist.device_tracker ritassist==0.9.2 -# homeassistant.components.sensor.rejseplanen +# homeassistant.components.rejseplanen.sensor rjpl==0.3.5 -# homeassistant.components.notify.rocketchat +# homeassistant.components.rocketchat.notify rocketchat-API==0.6.1 -# homeassistant.components.vacuum.roomba +# homeassistant.components.roomba.vacuum roombapy==1.3.1 -# homeassistant.components.sensor.rova +# homeassistant.components.rova.sensor rova==0.1.0 -# homeassistant.components.switch.rpi_rf +# homeassistant.components.rpi_rf.switch # rpi-rf==0.9.7 -# homeassistant.components.media_player.russound_rnet +# homeassistant.components.russound_rnet.media_player russound==0.1.9 -# homeassistant.components.media_player.russound_rio +# homeassistant.components.russound_rio.media_player russound_rio==0.1.4 -# homeassistant.components.media_player.yamaha +# homeassistant.components.yamaha.media_player rxv==0.6.0 -# homeassistant.components.media_player.samsungtv +# homeassistant.components.samsungtv.media_player samsungctl[websocket]==0.7.1 # homeassistant.components.satel_integra satel_integra==0.3.2 -# homeassistant.components.sensor.deutsche_bahn +# homeassistant.components.deutsche_bahn.sensor schiene==0.23 # homeassistant.components.scsgate scsgate==0.1.0 -# homeassistant.components.notify.sendgrid +# homeassistant.components.sendgrid.notify sendgrid==5.6.0 -# homeassistant.components.light.sensehat -# homeassistant.components.sensor.sensehat +# homeassistant.components.sensehat.light +# homeassistant.components.sensehat.sensor sense-hat==2.2.0 # homeassistant.components.sense sense_energy==0.7.0 -# homeassistant.components.media_player.aquostv +# homeassistant.components.aquostv.media_player sharp_aquos_rc==0.3.2 -# homeassistant.components.sensor.shodan +# homeassistant.components.shodan.sensor shodan==1.11.1 -# homeassistant.components.notify.simplepush +# homeassistant.components.simplepush.notify simplepush==1.1.4 # homeassistant.components.simplisafe @@ -1586,94 +1586,96 @@ sisyphus-control==2.1 # homeassistant.components.skybell skybellpy==0.3.0 -# homeassistant.components.notify.slack +# homeassistant.components.slack.notify slacker==0.12.0 # homeassistant.components.sleepiq sleepyq==0.6 -# homeassistant.components.notify.xmpp +# homeassistant.components.xmpp.notify slixmpp==1.4.2 # homeassistant.components.smappee smappy==0.2.16 # homeassistant.components.raspihats -# homeassistant.components.sensor.bh1750 -# homeassistant.components.sensor.bme280 -# homeassistant.components.sensor.bme680 -# homeassistant.components.sensor.envirophat -# homeassistant.components.sensor.htu21d +# homeassistant.components.bh1750.sensor +# homeassistant.components.bme280.sensor +# homeassistant.components.bme680.sensor +# homeassistant.components.envirophat.sensor +# homeassistant.components.htu21d.sensor # smbus-cffi==0.5.1 # homeassistant.components.smhi smhi-pkg==1.0.10 -# homeassistant.components.media_player.snapcast +# homeassistant.components.snapcast.media_player snapcast==2.0.9 -# homeassistant.components.sensor.socialblade +# homeassistant.components.socialblade.sensor socialbladeclient==0.2 -# homeassistant.components.sensor.solaredge +# homeassistant.components.solaredge.sensor solaredge==0.0.2 -# homeassistant.components.climate.honeywell +# homeassistant.components.honeywell.climate somecomfort==0.5.2 # homeassistant.components.speedtestdotnet -speedtest-cli==2.0.2 +speedtest-cli==2.1.1 # homeassistant.components.spider spiderpy==1.3.1 -# homeassistant.components.sensor.spotcrime +# homeassistant.components.spotcrime.sensor spotcrime==1.0.3 -# homeassistant.components.media_player.spotify +# homeassistant.components.spotify.media_player spotipy-homeassistant==2.4.4.dev1 # homeassistant.components.recorder -# homeassistant.components.sensor.sql -sqlalchemy==1.2.18 +# homeassistant.components.sql.sensor +sqlalchemy==1.3.0 -# homeassistant.components.sensor.srp_energy -srpenergy==1.0.5 +# homeassistant.components.srp_energy.sensor +srpenergy==1.0.6 -# homeassistant.components.sensor.starlingbank +# homeassistant.components.starlingbank.sensor starlingbank==3.1 # homeassistant.components.statsd statsd==3.2.1 -# homeassistant.components.sensor.steam_online +# homeassistant.components.steam_online.sensor steamodd==4.21 -# homeassistant.components.sensor.thermoworks_smoke +# homeassistant.components.solaredge.sensor +# homeassistant.components.thermoworks_smoke.sensor +# homeassistant.components.traccar.device_tracker stringcase==1.2.0 # homeassistant.components.ecovacs sucks==0.9.3 -# homeassistant.components.camera.onvif +# homeassistant.components.onvif.camera suds-passworddigest-homeassistant==0.1.2a0.dev0 -# homeassistant.components.camera.onvif +# homeassistant.components.onvif.camera suds-py3==1.3.3.0 -# homeassistant.components.sensor.swiss_hydrological_data +# homeassistant.components.swiss_hydrological_data.sensor swisshydrodata==0.0.3 -# homeassistant.components.device_tracker.synology_srm +# homeassistant.components.synology_srm.device_tracker synology-srm==0.0.6 # homeassistant.components.tahoma tahoma-api==0.0.14 -# homeassistant.components.sensor.tank_utility +# homeassistant.components.tank_utility.sensor tank_utility==1.4.0 -# homeassistant.components.binary_sensor.tapsaff +# homeassistant.components.tapsaff.binary_sensor tapsaff==0.2.0 # homeassistant.components.tellstick @@ -1685,37 +1687,37 @@ tellcore-py==1.1.2 # homeassistant.components.tellduslive tellduslive==0.10.10 -# homeassistant.components.media_player.lg_soundbar +# homeassistant.components.lg_soundbar.media_player temescal==0.1 -# homeassistant.components.sensor.temper +# homeassistant.components.temper.sensor temperusb==1.5.3 # homeassistant.components.tesla teslajsonpy==0.0.25 -# homeassistant.components.sensor.thermoworks_smoke +# homeassistant.components.thermoworks_smoke.sensor thermoworks_smoke==0.1.8 # homeassistant.components.thingspeak thingspeak==0.4.1 -# homeassistant.components.light.tikteck +# homeassistant.components.tikteck.light tikteck==0.4 -# homeassistant.components.calendar.todoist +# homeassistant.components.todoist.calendar todoist-python==7.0.17 # homeassistant.components.toon toonapilib==3.2.2 -# homeassistant.components.alarm_control_panel.totalconnect +# homeassistant.components.totalconnect.alarm_control_panel total_connect_client==0.25 # homeassistant.components.tplink_lte tp-connected==0.0.4 -# homeassistant.components.device_tracker.tplink +# homeassistant.components.tplink.device_tracker tplink==0.2.1 # homeassistant.components.transmission @@ -1727,25 +1729,25 @@ tuyapy==0.1.3 # homeassistant.components.twilio twilio==6.19.1 -# homeassistant.components.sensor.uber +# homeassistant.components.uber.sensor uber_rides==0.6.0 # homeassistant.components.upcloud upcloud-api==0.4.3 -# homeassistant.components.sensor.ups +# homeassistant.components.ups.sensor upsmychoice==1.0.6 -# homeassistant.components.sensor.uscis +# homeassistant.components.uscis.sensor uscisstatus==0.1.1 -# homeassistant.components.camera.uvc +# homeassistant.components.uvc.camera uvcclient==0.11.0 -# homeassistant.components.climate.venstar +# homeassistant.components.venstar.climate venstarcolortouch==0.6 -# homeassistant.components.sensor.volkszaehler +# homeassistant.components.volkszaehler.sensor volkszaehler==0.1.2 # homeassistant.components.volvooncall @@ -1754,19 +1756,19 @@ volvooncall==0.8.7 # homeassistant.components.verisure vsure==1.5.2 -# homeassistant.components.sensor.vasttrafik +# homeassistant.components.vasttrafik.sensor vtjp==0.1.14 # homeassistant.components.vultr vultr==0.1.2 # homeassistant.components.wake_on_lan -# homeassistant.components.media_player.panasonic_viera -# homeassistant.components.media_player.samsungtv -# homeassistant.components.switch.wake_on_lan +# homeassistant.components.panasonic_viera.media_player +# homeassistant.components.samsungtv.media_player +# homeassistant.components.wake_on_lan.switch wakeonlan==1.1.6 -# homeassistant.components.sensor.waqi +# homeassistant.components.waqi.sensor waqiasync==1.0.0 # homeassistant.components.folder_watcher @@ -1775,7 +1777,7 @@ watchdog==0.8.3 # homeassistant.components.waterfurnace waterfurnace==1.1.0 -# homeassistant.components.media_player.gpmdp +# homeassistant.components.gpmdp.media_player websocket-client==0.54.0 # homeassistant.components.webostv.media_player @@ -1790,42 +1792,42 @@ wunderpy2==0.1.6 # homeassistant.components.zigbee xbee-helper==0.0.7 -# homeassistant.components.sensor.xbox_live +# homeassistant.components.xbox_live.sensor xboxapi==0.1.1 -# homeassistant.components.device_tracker.xfinity +# homeassistant.components.xfinity.device_tracker xfinity-gateway==0.0.4 # homeassistant.components.knx xknx==0.10.0 -# homeassistant.components.media_player.bluesound -# homeassistant.components.sensor.startca -# homeassistant.components.sensor.ted5000 -# homeassistant.components.sensor.yr -# homeassistant.components.sensor.zestimate +# homeassistant.components.bluesound.media_player +# homeassistant.components.startca.sensor +# homeassistant.components.ted5000.sensor +# homeassistant.components.yr.sensor +# homeassistant.components.zestimate.sensor xmltodict==0.11.0 # homeassistant.components.xs1 xs1-api-client==2.3.5 -# homeassistant.components.sensor.yweather -# homeassistant.components.weather.yweather +# homeassistant.components.yweather.sensor +# homeassistant.components.yweather.weather yahooweather==0.10 -# homeassistant.components.alarm_control_panel.yale_smart_alarm +# homeassistant.components.yale_smart_alarm.alarm_control_panel yalesmartalarmclient==0.1.6 -# homeassistant.components.light.yeelight -yeelight==0.4.3 +# homeassistant.components.yeelight +yeelight==0.4.4 -# homeassistant.components.light.yeelightsunflower +# homeassistant.components.yeelightsunflower.light yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2019.03.01 +youtube_dl==2019.03.18 -# homeassistant.components.light.zengge +# homeassistant.components.zengge.light zengge==0.2 # homeassistant.components.zeroconf @@ -1834,20 +1836,20 @@ zeroconf==0.21.3 # homeassistant.components.zha zha-quirks==0.0.7 -# homeassistant.components.climate.zhong_hong +# homeassistant.components.zhong_hong.climate zhong_hong_hvac==1.0.9 -# homeassistant.components.media_player.ziggo_mediabox_xl +# homeassistant.components.ziggo_mediabox_xl.media_player ziggo-mediabox-xl==1.1.0 # homeassistant.components.zha -zigpy-deconz==0.1.2 +zigpy-deconz==0.1.3 # homeassistant.components.zha -zigpy-homeassistant==0.3.0 +zigpy-homeassistant==0.3.1 # homeassistant.components.zha -zigpy-xbee-homeassistant==0.1.2 +zigpy-xbee-homeassistant==0.1.3 # homeassistant.components.zoneminder zm-py==0.3.3 diff --git a/requirements_docs.txt b/requirements_docs.txt index 1efe929d666..eca1abdb365 100644 --- a/requirements_docs.txt +++ b/requirements_docs.txt @@ -1,3 +1,3 @@ -Sphinx==1.8.4 +Sphinx==1.8.5 sphinx-autodoc-typehints==1.6.0 sphinx-autodoc-annotation==1.0.post1 \ No newline at end of file diff --git a/requirements_test.txt b/requirements_test.txt index 9aa5d7d5c91..bf96353144c 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -13,5 +13,5 @@ pytest-aiohttp==0.3.0 pytest-cov==2.6.1 pytest-sugar==0.9.2 pytest-timeout==1.3.3 -pytest==4.3.0 +pytest==4.3.1 requests_mock==1.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fcaf93701fe..27d96c5e606 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -14,7 +14,7 @@ pytest-aiohttp==0.3.0 pytest-cov==2.6.1 pytest-sugar==0.9.2 pytest-timeout==1.3.3 -pytest==4.3.0 +pytest==4.3.1 requests_mock==1.5.2 @@ -25,21 +25,24 @@ HAP-python==2.4.2 # homeassistant.components.owntracks PyNaCl==1.3.0 -# homeassistant.components.sensor.rmvtransport +# homeassistant.components.rmvtransport.sensor PyRMVtransport==0.1.3 -# homeassistant.components.sensor.transport_nsw +# homeassistant.components.transport_nsw.sensor PyTransportNSW==0.1.1 -# homeassistant.components.notify.yessssms +# homeassistant.components.yessssms.notify YesssSMS==0.2.3 # homeassistant.components.ambient_station aioambient==0.1.3 -# homeassistant.components.device_tracker.automatic +# homeassistant.components.automatic.device_tracker aioautomatic==0.6.5 +# homeassistant.components.aws +aiobotocore==0.10.2 + # homeassistant.components.emulated_hue # homeassistant.components.http aiohttp_cors==0.7.0 @@ -50,65 +53,65 @@ aiohue==1.9.1 # homeassistant.components.unifi aiounifi==4 -# homeassistant.components.notify.apns +# homeassistant.components.apns.notify apns2==0.3.0 # homeassistant.components.stream av==6.1.2 -# homeassistant.components.zha -bellows-homeassistant==0.7.1 +# homeassistant.components.axis +axis==17 -# homeassistant.components.calendar.caldav +# homeassistant.components.zha +bellows-homeassistant==0.7.2 + +# homeassistant.components.caldav.calendar caldav==0.5.0 -# homeassistant.components.sensor.coinmarketcap +# homeassistant.components.coinmarketcap.sensor coinmarketcap==5.0.3 # homeassistant.components.ihc # homeassistant.components.namecheapdns -# homeassistant.components.device_tracker.upc_connect -# homeassistant.components.sensor.ohmconnect +# homeassistant.components.ohmconnect.sensor +# homeassistant.components.upc_connect.device_tracker defusedxml==0.5.0 -# homeassistant.components.sensor.dsmr +# homeassistant.components.dsmr.sensor dsmr_parser==0.12 -# homeassistant.components.device_tracker.ee_brightbox +# homeassistant.components.ee_brightbox.device_tracker eebrightbox==0.0.4 # homeassistant.components.emulated_roku emulated_roku==0.1.8 -# homeassistant.components.sensor.entur_public_transport -enturclient==0.1.3 - -# homeassistant.components.sensor.season +# homeassistant.components.season.sensor ephem==3.7.6.0 # homeassistant.components.evohome -# homeassistant.components.climate.honeywell +# homeassistant.components.honeywell.climate evohomeclient==0.2.8 # homeassistant.components.feedreader feedparser-homeassistant==5.2.2.dev1 -# homeassistant.components.sensor.foobot +# homeassistant.components.foobot.sensor foobot_async==0.3.1 # homeassistant.components.google.tts gTTS-token==1.1.3 -# homeassistant.components.geo_location.geo_json_events -# homeassistant.components.geo_location.nsw_rural_fire_service_feed -# homeassistant.components.geo_location.usgs_earthquakes_feed +# homeassistant.components.geo_json_events.geo_location +# homeassistant.components.nsw_rural_fire_service_feed.geo_location +# homeassistant.components.usgs_earthquakes_feed.geo_location geojson_client==0.3 -# homeassistant.components.sensor.geo_rss_events +# homeassistant.components.geo_rss_events.sensor georss_client==0.5 # homeassistant.components.ffmpeg -ha-ffmpeg==1.11 +ha-ffmpeg==2.0 # homeassistant.components.hangouts hangups==0.4.6 @@ -119,14 +122,14 @@ hass-nabucasa==0.11 # homeassistant.components.mqtt.server hbmqtt==0.9.4 -# homeassistant.components.sensor.jewish_calendar +# homeassistant.components.jewish_calendar.sensor hdate==0.8.7 -# homeassistant.components.binary_sensor.workday -holidays==0.9.9 +# homeassistant.components.workday.binary_sensor +holidays==0.9.10 # homeassistant.components.frontend -home-assistant-frontend==20190321.0 +home-assistant-frontend==20190331.0 # homeassistant.components.homekit_controller homekit[IP]==0.13.0 @@ -135,7 +138,7 @@ homekit[IP]==0.13.0 homematicip==0.10.6 # homeassistant.components.influxdb -# homeassistant.components.sensor.influxdb +# homeassistant.components.influxdb.sensor influxdb==5.2.0 # homeassistant.components.verisure @@ -144,7 +147,7 @@ jsonpath==0.75 # homeassistant.components.dyson libpurecoollink==0.4.2 -# homeassistant.components.media_player.soundtouch +# homeassistant.components.soundtouch.media_player libsoundtouch==0.7.2 # homeassistant.components.luftdaten @@ -153,38 +156,38 @@ luftdaten==0.3.4 # homeassistant.components.mythicbeastsdns mbddns==0.1.2 -# homeassistant.components.sensor.mfi -# homeassistant.components.switch.mfi +# homeassistant.components.mfi.sensor +# homeassistant.components.mfi.switch mficlient==0.3.0 -# homeassistant.components.binary_sensor.trend -# homeassistant.components.image_processing.opencv -# homeassistant.components.image_processing.tensorflow -# homeassistant.components.sensor.pollen +# homeassistant.components.opencv.image_processing +# homeassistant.components.pollen.sensor +# homeassistant.components.tensorflow.image_processing +# homeassistant.components.trend.binary_sensor numpy==1.16.2 # homeassistant.components.mqtt # homeassistant.components.shiftr paho-mqtt==1.4.0 -# homeassistant.components.device_tracker.aruba -# homeassistant.components.device_tracker.cisco_ios -# homeassistant.components.device_tracker.unifi_direct -# homeassistant.components.media_player.pandora +# homeassistant.components.aruba.device_tracker +# homeassistant.components.cisco_ios.device_tracker +# homeassistant.components.pandora.media_player +# homeassistant.components.unifi_direct.device_tracker pexpect==4.6.0 # homeassistant.components.pilight pilight==0.1.1 -# homeassistant.components.sensor.mhz19 -# homeassistant.components.sensor.serial_pm +# homeassistant.components.mhz19.sensor +# homeassistant.components.serial_pm.sensor pmsensor==0.4 # homeassistant.components.prometheus prometheus_client==0.2.0 -# homeassistant.components.notify.pushbullet -# homeassistant.components.sensor.pushbullet +# homeassistant.components.pushbullet.notify +# homeassistant.components.pushbullet.sensor pushbullet.py==0.11.0 # homeassistant.components.canary @@ -193,11 +196,11 @@ py-canary==0.5.0 # homeassistant.components.tplink pyHS100==0.3.4 -# homeassistant.components.media_player.blackbird +# homeassistant.components.blackbird.media_player pyblackbird==0.5 # homeassistant.components.deconz -pydeconz==52 +pydeconz==54 # homeassistant.components.zwave pydispatcher==2.0.5 @@ -208,11 +211,11 @@ pyhomematic==0.1.58 # homeassistant.components.litejet pylitejet==0.1 -# homeassistant.components.media_player.monoprice +# homeassistant.components.monoprice.media_player pymonoprice==0.3 -# homeassistant.components.alarm_control_panel.nx584 -# homeassistant.components.binary_sensor.nx584 +# homeassistant.components.nx584.alarm_control_panel +# homeassistant.components.nx584.binary_sensor pynx584==0.4 # homeassistant.components.openuv @@ -220,11 +223,11 @@ pyopenuv==1.0.9 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp -# homeassistant.components.sensor.otp +# homeassistant.components.otp.sensor pyotp==2.2.6 # homeassistant.components.ps4 -pyps4-homeassistant==0.4.8 +pyps4-homeassistant==0.5.2 # homeassistant.components.qwikswitch pyqwikswitch==0.8 @@ -241,23 +244,23 @@ pysonos==0.0.8 # homeassistant.components.spc pyspcwebgw==0.4.0 -# homeassistant.components.sensor.darksky -# homeassistant.components.weather.darksky +# homeassistant.components.darksky.sensor +# homeassistant.components.darksky.weather python-forecastio==1.4.0 # homeassistant.components.nest python-nest==4.1.0 -# homeassistant.components.sensor.awair +# homeassistant.components.awair.sensor python_awair==0.0.3 # homeassistant.components.tradfri pytradfri[async]==6.0.1 -# homeassistant.components.device_tracker.unifi +# homeassistant.components.unifi.device_tracker pyunifi==2.16 -# homeassistant.components.notify.html5 +# homeassistant.components.html5.notify pywebpush==1.6.0 # homeassistant.components.rainmachine @@ -270,9 +273,9 @@ restrictedpython==4.0b8 rflink==0.0.37 # homeassistant.components.ring -ring_doorbell==0.2.2 +ring_doorbell==0.2.3 -# homeassistant.components.media_player.yamaha +# homeassistant.components.yamaha.media_player rxv==0.6.0 # homeassistant.components.simplisafe @@ -284,15 +287,15 @@ sleepyq==0.6 # homeassistant.components.smhi smhi-pkg==1.0.10 -# homeassistant.components.climate.honeywell +# homeassistant.components.honeywell.climate somecomfort==0.5.2 # homeassistant.components.recorder -# homeassistant.components.sensor.sql -sqlalchemy==1.2.18 +# homeassistant.components.sql.sensor +sqlalchemy==1.3.0 -# homeassistant.components.sensor.srp_energy -srpenergy==1.0.5 +# homeassistant.components.srp_energy.sensor +srpenergy==1.0.6 # homeassistant.components.statsd statsd==3.2.1 @@ -300,7 +303,7 @@ statsd==3.2.1 # homeassistant.components.toon toonapilib==3.2.2 -# homeassistant.components.camera.uvc +# homeassistant.components.uvc.camera uvcclient==0.11.0 # homeassistant.components.verisure @@ -310,10 +313,10 @@ vsure==1.5.2 vultr==0.1.2 # homeassistant.components.wake_on_lan -# homeassistant.components.media_player.panasonic_viera -# homeassistant.components.media_player.samsungtv -# homeassistant.components.switch.wake_on_lan +# homeassistant.components.panasonic_viera.media_player +# homeassistant.components.samsungtv.media_player +# homeassistant.components.wake_on_lan.switch wakeonlan==1.1.6 # homeassistant.components.zha -zigpy-homeassistant==0.3.0 +zigpy-homeassistant==0.3.1 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index ada84d2bbcd..d2d6588672f 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -41,18 +41,19 @@ COMMENT_REQUIREMENTS = ( TEST_REQUIREMENTS = ( 'aioambient', 'aioautomatic', + 'aiobotocore', 'aiohttp_cors', 'aiohue', 'aiounifi', 'apns2', 'av', + 'axis', 'caldav', 'coinmarketcap', 'defusedxml', 'dsmr_parser', 'eebrightbox', 'emulated_roku', - 'enturclient', 'ephem', 'evohomeclient', 'feedparser-homeassistant', @@ -217,11 +218,12 @@ def gather_modules(): explore_module('homeassistant.auth', True)): try: module = importlib.import_module(package) - except ImportError: + except ImportError as err: for pattern in IGNORE_PACKAGES: if fnmatch.fnmatch(package, pattern): break else: + print("{}: {}".format(package.replace('.', '/') + '.py', err)) errors.append(package) continue diff --git a/script/translations_upload b/script/translations_upload index 5bf9fe1e121..52045e41d60 100755 --- a/script/translations_upload +++ b/script/translations_upload @@ -26,7 +26,8 @@ LANG_ISO=en CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) -if [ "${CURRENT_BRANCH-}" != "dev" ] && [ "${TRAVIS_BRANCH-}" != "dev" ] ; then +# Check Travis and CircleCI environment as well +if [ "${CURRENT_BRANCH-}" != "dev" ] && [ "${TRAVIS_BRANCH-}" != "dev" ] && [ "${CIRCLE_BRANCH-}" != "dev" ]; then echo "Please only run the translations upload script from a clean checkout of dev." exit 1 fi diff --git a/setup.py b/setup.py index c4c9d0e53ed..8f7e84dd8d8 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ REQUIRES = [ 'pytz>=2018.07', 'pyyaml>=3.13,<4', 'requests==2.21.0', - 'ruamel.yaml==0.15.88', + 'ruamel.yaml==0.15.89', 'voluptuous==0.11.5', 'voluptuous-serialize==2.1.0', ] diff --git a/tests/auth/providers/test_trusted_networks.py b/tests/auth/providers/test_trusted_networks.py index 57e74e750d5..9468799095c 100644 --- a/tests/auth/providers/test_trusted_networks.py +++ b/tests/auth/providers/test_trusted_networks.py @@ -1,5 +1,5 @@ """Test the Trusted Networks auth provider.""" -from ipaddress import ip_address +from ipaddress import ip_address, ip_network import pytest import voluptuous as vol @@ -25,8 +25,47 @@ def provider(hass, store): '192.168.0.1', '192.168.128.0/24', '::1', - 'fd00::/8' - ] + 'fd00::/8', + ], + }) + ) + + +@pytest.fixture +def provider_with_user(hass, store): + """Mock provider with trusted users config.""" + return tn_auth.TrustedNetworksAuthProvider( + hass, store, tn_auth.CONFIG_SCHEMA({ + 'type': 'trusted_networks', + 'trusted_networks': [ + '192.168.0.1', + '192.168.128.0/24', + '::1', + 'fd00::/8', + ], + # user_id will be injected in test + 'trusted_users': { + '192.168.0.1': [], + '192.168.128.0/24': [], + 'fd00::/8': [], + }, + }) + ) + + +@pytest.fixture +def provider_bypass_login(hass, store): + """Mock provider with allow_bypass_login config.""" + return tn_auth.TrustedNetworksAuthProvider( + hass, store, tn_auth.CONFIG_SCHEMA({ + 'type': 'trusted_networks', + 'trusted_networks': [ + '192.168.0.1', + '192.168.128.0/24', + '::1', + 'fd00::/8', + ], + 'allow_bypass_login': True, }) ) @@ -39,6 +78,23 @@ def manager(hass, store, provider): }, {}) +@pytest.fixture +def manager_with_user(hass, store, provider_with_user): + """Mock manager with trusted user.""" + return auth.AuthManager(hass, store, { + (provider_with_user.type, provider_with_user.id): provider_with_user + }, {}) + + +@pytest.fixture +def manager_bypass_login(hass, store, provider_bypass_login): + """Mock manager with allow bypass login.""" + return auth.AuthManager(hass, store, { + (provider_bypass_login.type, provider_bypass_login.id): + provider_bypass_login + }, {}) + + async def test_trusted_networks_credentials(manager, provider): """Test trusted_networks credentials related functions.""" owner = await manager.async_create_user("test-owner") @@ -104,3 +160,157 @@ async def test_login_flow(manager, provider): step = await flow.async_step_init({'user': user.id}) assert step['type'] == 'create_entry' assert step['data']['user'] == user.id + + +async def test_trusted_users_login(manager_with_user, provider_with_user): + """Test available user list changed per different IP.""" + owner = await manager_with_user.async_create_user("test-owner") + sys_user = await manager_with_user.async_create_system_user( + "test-sys-user") # system user will not be available to select + user = await manager_with_user.async_create_user("test-user") + + # change the trusted users config + config = provider_with_user.config['trusted_users'] + assert ip_network('192.168.0.1') in config + config[ip_network('192.168.0.1')] = [owner.id] + assert ip_network('192.168.128.0/24') in config + config[ip_network('192.168.128.0/24')] = [sys_user.id, user.id] + + # not from trusted network + flow = await provider_with_user.async_login_flow( + {'ip_address': ip_address('127.0.0.1')}) + step = await flow.async_step_init() + assert step['type'] == 'abort' + assert step['reason'] == 'not_whitelisted' + + # from trusted network, list users intersect trusted_users + flow = await provider_with_user.async_login_flow( + {'ip_address': ip_address('192.168.0.1')}) + step = await flow.async_step_init() + assert step['step_id'] == 'init' + + schema = step['data_schema'] + # only owner listed + assert schema({'user': owner.id}) + with pytest.raises(vol.Invalid): + assert schema({'user': user.id}) + + # from trusted network, list users intersect trusted_users + flow = await provider_with_user.async_login_flow( + {'ip_address': ip_address('192.168.128.1')}) + step = await flow.async_step_init() + assert step['step_id'] == 'init' + + schema = step['data_schema'] + # only user listed + assert schema({'user': user.id}) + with pytest.raises(vol.Invalid): + assert schema({'user': owner.id}) + with pytest.raises(vol.Invalid): + assert schema({'user': sys_user.id}) + + # from trusted network, list users intersect trusted_users + flow = await provider_with_user.async_login_flow( + {'ip_address': ip_address('::1')}) + step = await flow.async_step_init() + assert step['step_id'] == 'init' + + schema = step['data_schema'] + # both owner and user listed + assert schema({'user': owner.id}) + assert schema({'user': user.id}) + with pytest.raises(vol.Invalid): + assert schema({'user': sys_user.id}) + + # from trusted network, list users intersect trusted_users + flow = await provider_with_user.async_login_flow( + {'ip_address': ip_address('fd00::1')}) + step = await flow.async_step_init() + assert step['step_id'] == 'init' + + schema = step['data_schema'] + # no user listed + with pytest.raises(vol.Invalid): + assert schema({'user': owner.id}) + with pytest.raises(vol.Invalid): + assert schema({'user': user.id}) + with pytest.raises(vol.Invalid): + assert schema({'user': sys_user.id}) + + +async def test_trusted_group_login(manager_with_user, provider_with_user): + """Test config trusted_user with group_id.""" + owner = await manager_with_user.async_create_user("test-owner") + # create a user in user group + user = await manager_with_user.async_create_user("test-user") + await manager_with_user.async_update_user( + user, group_ids=[auth.const.GROUP_ID_USER]) + + # change the trusted users config + config = provider_with_user.config['trusted_users'] + assert ip_network('192.168.0.1') in config + config[ip_network('192.168.0.1')] = [{'group': [auth.const.GROUP_ID_USER]}] + assert ip_network('192.168.128.0/24') in config + config[ip_network('192.168.128.0/24')] = [ + owner.id, {'group': [auth.const.GROUP_ID_USER]}] + + # not from trusted network + flow = await provider_with_user.async_login_flow( + {'ip_address': ip_address('127.0.0.1')}) + step = await flow.async_step_init() + assert step['type'] == 'abort' + assert step['reason'] == 'not_whitelisted' + + # from trusted network, list users intersect trusted_users + flow = await provider_with_user.async_login_flow( + {'ip_address': ip_address('192.168.0.1')}) + step = await flow.async_step_init() + assert step['step_id'] == 'init' + + schema = step['data_schema'] + # only user listed + print(user.id) + assert schema({'user': user.id}) + with pytest.raises(vol.Invalid): + assert schema({'user': owner.id}) + + # from trusted network, list users intersect trusted_users + flow = await provider_with_user.async_login_flow( + {'ip_address': ip_address('192.168.128.1')}) + step = await flow.async_step_init() + assert step['step_id'] == 'init' + + schema = step['data_schema'] + # both owner and user listed + assert schema({'user': owner.id}) + assert schema({'user': user.id}) + + +async def test_bypass_login_flow(manager_bypass_login, provider_bypass_login): + """Test login flow can be bypass if only one user available.""" + owner = await manager_bypass_login.async_create_user("test-owner") + + # not from trusted network + flow = await provider_bypass_login.async_login_flow( + {'ip_address': ip_address('127.0.0.1')}) + step = await flow.async_step_init() + assert step['type'] == 'abort' + assert step['reason'] == 'not_whitelisted' + + # from trusted network, only one available user, bypass the login flow + flow = await provider_bypass_login.async_login_flow( + {'ip_address': ip_address('192.168.0.1')}) + step = await flow.async_step_init() + assert step['type'] == 'create_entry' + assert step['data']['user'] == owner.id + + user = await manager_bypass_login.async_create_user("test-user") + + # from trusted network, two available user, show up login form + flow = await provider_bypass_login.async_login_flow( + {'ip_address': ip_address('192.168.0.1')}) + step = await flow.async_step_init() + schema = step['data_schema'] + # both owner and user listed + assert schema({'user': owner.id}) + assert schema({'user': user.id}) diff --git a/tests/common.py b/tests/common.py index 8681db1b4f3..9fe5375ad7c 100644 --- a/tests/common.py +++ b/tests/common.py @@ -327,11 +327,7 @@ def mock_registry(hass, mock_entries=None): registry = entity_registry.EntityRegistry(hass) registry.entities = mock_entries or OrderedDict() - async def _get_reg(): - return registry - - hass.data[entity_registry.DATA_REGISTRY] = \ - hass.loop.create_task(_get_reg()) + hass.data[entity_registry.DATA_REGISTRY] = registry return registry @@ -340,11 +336,7 @@ def mock_area_registry(hass, mock_entries=None): registry = area_registry.AreaRegistry(hass) registry.areas = mock_entries or OrderedDict() - async def _get_reg(): - return registry - - hass.data[area_registry.DATA_REGISTRY] = \ - hass.loop.create_task(_get_reg()) + hass.data[area_registry.DATA_REGISTRY] = registry return registry @@ -353,11 +345,7 @@ def mock_device_registry(hass, mock_entries=None): registry = device_registry.DeviceRegistry(hass) registry.devices = mock_entries or OrderedDict() - async def _get_reg(): - return registry - - hass.data[device_registry.DATA_REGISTRY] = \ - hass.loop.create_task(_get_reg()) + hass.data[device_registry.DATA_REGISTRY] = registry return registry diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 845e59295ac..924a568dea2 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -1490,6 +1490,63 @@ async def test_report_colored_temp_light_state(hass): 'colorTemperatureInKelvin', 0) +async def test_report_fan_speed_state(hass): + """Test PercentageController reports fan speed correctly.""" + hass.states.async_set( + 'fan.off', 'off', {'friendly_name': "Off fan", + 'speed': "off", + 'supported_features': 1}) + hass.states.async_set( + 'fan.low_speed', 'on', {'friendly_name': "Low speed fan", + 'speed': "low", + 'supported_features': 1}) + hass.states.async_set( + 'fan.medium_speed', 'on', {'friendly_name': "Medium speed fan", + 'speed': "medium", + 'supported_features': 1}) + hass.states.async_set( + 'fan.high_speed', 'on', {'friendly_name': "High speed fan", + 'speed': "high", + 'supported_features': 1}) + + properties = await reported_properties(hass, 'fan.off') + properties.assert_equal('Alexa.PercentageController', 'percentage', 0) + + properties = await reported_properties(hass, 'fan.low_speed') + properties.assert_equal('Alexa.PercentageController', 'percentage', 33) + + properties = await reported_properties(hass, 'fan.medium_speed') + properties.assert_equal('Alexa.PercentageController', 'percentage', 66) + + properties = await reported_properties(hass, 'fan.high_speed') + properties.assert_equal('Alexa.PercentageController', 'percentage', 100) + + +async def test_report_cover_percentage_state(hass): + """Test PercentageController reports cover percentage correctly.""" + hass.states.async_set( + 'cover.fully_open', 'open', {'friendly_name': "Fully open cover", + 'current_position': 100, + 'supported_features': 15}) + hass.states.async_set( + 'cover.half_open', 'open', {'friendly_name': "Half open cover", + 'current_position': 50, + 'supported_features': 15}) + hass.states.async_set( + 'cover.closed', 'closed', {'friendly_name': "Closed cover", + 'current_position': 0, + 'supported_features': 15}) + + properties = await reported_properties(hass, 'cover.fully_open') + properties.assert_equal('Alexa.PercentageController', 'percentage', 100) + + properties = await reported_properties(hass, 'cover.half_open') + properties.assert_equal('Alexa.PercentageController', 'percentage', 50) + + properties = await reported_properties(hass, 'cover.closed') + properties.assert_equal('Alexa.PercentageController', 'percentage', 0) + + async def reported_properties(hass, endpoint): """Use ReportState to get properties and return them. diff --git a/tests/components/api_streams/__init__.py b/tests/components/api_streams/__init__.py new file mode 100644 index 00000000000..b1d0cf13569 --- /dev/null +++ b/tests/components/api_streams/__init__.py @@ -0,0 +1 @@ +"""Tests for the api_streams component.""" diff --git a/tests/components/apns/__init__.py b/tests/components/apns/__init__.py new file mode 100644 index 00000000000..42c980a62a7 --- /dev/null +++ b/tests/components/apns/__init__.py @@ -0,0 +1 @@ +"""Tests for the apns component.""" diff --git a/tests/components/notify/test_apns.py b/tests/components/apns/test_notify.py similarity index 92% rename from tests/components/notify/test_apns.py rename to tests/components/apns/test_notify.py index 9964a58cd24..7303f4872e3 100644 --- a/tests/components/notify/test_apns.py +++ b/tests/components/apns/test_notify.py @@ -8,7 +8,7 @@ import yaml import homeassistant.components.notify as notify from homeassistant.setup import setup_component -from homeassistant.components.notify import apns +import homeassistant.components.apns.notify as apns from homeassistant.core import State from tests.common import assert_setup_component, get_test_home_assistant @@ -23,7 +23,7 @@ CONFIG = { } -@patch('homeassistant.components.notify.apns.open', mock_open(), create=True) +@patch('homeassistant.components.apns.notify.open', mock_open(), create=True) class TestApns(unittest.TestCase): """Test the APNS component.""" @@ -102,7 +102,7 @@ class TestApns(unittest.TestCase): assert setup_component(self.hass, notify.DOMAIN, config) assert not handle_config[notify.DOMAIN] - @patch('homeassistant.components.notify.apns._write_device') + @patch('homeassistant.components.apns.notify._write_device') def test_register_new_device(self, mock_write): """Test registering a new device with a name.""" yaml_file = {5678: {'name': 'test device 2'}} @@ -116,7 +116,7 @@ class TestApns(unittest.TestCase): mock_write.side_effect = fake_write with patch( - 'homeassistant.components.notify.apns.load_yaml_config_file', + 'homeassistant.components.apns.notify.load_yaml_config_file', Mock(return_value=yaml_file)): self._setup_notify() @@ -128,7 +128,7 @@ class TestApns(unittest.TestCase): assert len(written_devices) == 1 assert written_devices[0].name == 'test device' - @patch('homeassistant.components.notify.apns._write_device') + @patch('homeassistant.components.apns.notify._write_device') def test_register_device_without_name(self, mock_write): """Test registering a without a name.""" yaml_file = { @@ -151,7 +151,7 @@ class TestApns(unittest.TestCase): mock_write.side_effect = fake_write with patch( - 'homeassistant.components.notify.apns.load_yaml_config_file', + 'homeassistant.components.apns.notify.load_yaml_config_file', Mock(return_value=yaml_file)): self._setup_notify() @@ -166,7 +166,7 @@ class TestApns(unittest.TestCase): assert test_device is not None assert test_device.name is None - @patch('homeassistant.components.notify.apns._write_device') + @patch('homeassistant.components.apns.notify._write_device') def test_update_existing_device(self, mock_write): """Test updating an existing device.""" yaml_file = { @@ -187,7 +187,7 @@ class TestApns(unittest.TestCase): mock_write.side_effect = fake_write with patch( - 'homeassistant.components.notify.apns.load_yaml_config_file', + 'homeassistant.components.apns.notify.load_yaml_config_file', Mock(return_value=yaml_file)): self._setup_notify() @@ -206,7 +206,7 @@ class TestApns(unittest.TestCase): assert 'updated device 1' == test_device_1.name - @patch('homeassistant.components.notify.apns._write_device') + @patch('homeassistant.components.apns.notify._write_device') def test_update_existing_device_with_tracking_id(self, mock_write): """Test updating an existing device that has a tracking id.""" yaml_file = { @@ -229,7 +229,7 @@ class TestApns(unittest.TestCase): mock_write.side_effect = fake_write with patch( - 'homeassistant.components.notify.apns.load_yaml_config_file', + 'homeassistant.components.apns.notify.load_yaml_config_file', Mock(return_value=yaml_file)): self._setup_notify() @@ -259,7 +259,7 @@ class TestApns(unittest.TestCase): yaml_file = {1234: {'name': 'test device 1'}} with patch( - 'homeassistant.components.notify.apns.load_yaml_config_file', + 'homeassistant.components.apns.notify.load_yaml_config_file', Mock(return_value=yaml_file)): self._setup_notify() @@ -294,7 +294,7 @@ class TestApns(unittest.TestCase): }} with patch( - 'homeassistant.components.notify.apns.load_yaml_config_file', + 'homeassistant.components.apns.notify.load_yaml_config_file', Mock(return_value=yaml_file)): self._setup_notify() @@ -325,7 +325,7 @@ class TestApns(unittest.TestCase): } with patch( - 'homeassistant.components.notify.apns.load_yaml_config_file', + 'homeassistant.components.apns.notify.load_yaml_config_file', Mock(return_value=yaml_file)), \ patch('os.path.isfile', Mock(return_value=True)): notify_service = apns.ApnsNotificationService( @@ -353,7 +353,7 @@ class TestApns(unittest.TestCase): assert 'Hello' == payload.alert @patch('apns2.client.APNsClient') - @patch('homeassistant.components.notify.apns._write_device') + @patch('homeassistant.components.apns.notify._write_device') def test_disable_when_unregistered(self, mock_write, mock_client): """Test disabling a device when it is unregistered.""" send = mock_client.return_value.send_notification @@ -379,7 +379,7 @@ class TestApns(unittest.TestCase): mock_write.side_effect = fake_write with patch( - 'homeassistant.components.notify.apns.load_yaml_config_file', + 'homeassistant.components.apns.notify.load_yaml_config_file', Mock(return_value=yaml_file)): self._setup_notify() diff --git a/tests/components/asuswrt/__init__.py b/tests/components/asuswrt/__init__.py new file mode 100644 index 00000000000..4635400b481 --- /dev/null +++ b/tests/components/asuswrt/__init__.py @@ -0,0 +1 @@ +"""Tests for the asuswrt component.""" diff --git a/tests/components/device_tracker/test_asuswrt.py b/tests/components/asuswrt/test_device_tracker.py similarity index 100% rename from tests/components/device_tracker/test_asuswrt.py rename to tests/components/asuswrt/test_device_tracker.py diff --git a/tests/components/aurora/__init__.py b/tests/components/aurora/__init__.py new file mode 100644 index 00000000000..4ce9649eff9 --- /dev/null +++ b/tests/components/aurora/__init__.py @@ -0,0 +1 @@ +"""The tests for the Aurora sensor platform.""" diff --git a/tests/components/binary_sensor/test_aurora.py b/tests/components/aurora/test_binary_sensor.py similarity index 97% rename from tests/components/binary_sensor/test_aurora.py rename to tests/components/aurora/test_binary_sensor.py index 109da8e8a43..48f02f3c9f2 100644 --- a/tests/components/binary_sensor/test_aurora.py +++ b/tests/components/aurora/test_binary_sensor.py @@ -4,7 +4,7 @@ import unittest import requests_mock -from homeassistant.components.binary_sensor import aurora +from homeassistant.components.aurora import binary_sensor as aurora from tests.common import load_fixture, get_test_home_assistant diff --git a/tests/components/automatic/__init__.py b/tests/components/automatic/__init__.py new file mode 100644 index 00000000000..4f7f83b97b5 --- /dev/null +++ b/tests/components/automatic/__init__.py @@ -0,0 +1 @@ +"""Tests for the automatic component.""" diff --git a/tests/components/device_tracker/test_automatic.py b/tests/components/automatic/test_device_tracker.py similarity index 95% rename from tests/components/device_tracker/test_automatic.py rename to tests/components/automatic/test_device_tracker.py index 84ea84cdcad..03b631b4689 100644 --- a/tests/components/device_tracker/test_automatic.py +++ b/tests/components/automatic/test_device_tracker.py @@ -6,7 +6,7 @@ from unittest.mock import patch, MagicMock import aioautomatic from homeassistant.setup import async_setup_component -from homeassistant.components.device_tracker.automatic import ( +from homeassistant.components.automatic.device_tracker import ( async_setup_scanner) _LOGGER = logging.getLogger(__name__) @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) @patch('json.dump') @patch('os.makedirs') @patch('os.path.isfile', return_value=True) -@patch('homeassistant.components.device_tracker.automatic.open', create=True) +@patch('homeassistant.components.automatic.device_tracker.open', create=True) def test_invalid_credentials( mock_open, mock_isfile, mock_makedirs, mock_json_dump, mock_json_load, mock_create_session, hass): @@ -52,7 +52,7 @@ def test_invalid_credentials( @patch('json.dump') @patch('os.makedirs') @patch('os.path.isfile', return_value=True) -@patch('homeassistant.components.device_tracker.automatic.open', create=True) +@patch('homeassistant.components.automatic.device_tracker.open', create=True) def test_valid_credentials( mock_open, mock_isfile, mock_makedirs, mock_json_dump, mock_json_load, mock_ws_connect, mock_create_session, hass): diff --git a/tests/components/awair/__init__.py b/tests/components/awair/__init__.py new file mode 100644 index 00000000000..5331ae5492a --- /dev/null +++ b/tests/components/awair/__init__.py @@ -0,0 +1 @@ +"""Tests for the awair component.""" diff --git a/tests/components/sensor/test_awair.py b/tests/components/awair/test_sensor.py similarity index 98% rename from tests/components/sensor/test_awair.py rename to tests/components/awair/test_sensor.py index e00cc816518..d251e8fdce8 100644 --- a/tests/components/sensor/test_awair.py +++ b/tests/components/awair/test_sensor.py @@ -7,7 +7,7 @@ import logging from unittest.mock import patch from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.components.sensor.awair import ( +from homeassistant.components.awair.sensor import ( ATTR_LAST_API_UPDATE, ATTR_TIMESTAMP, DEVICE_CLASS_CARBON_DIOXIDE, @@ -54,7 +54,7 @@ def alter_time(retval): patch_one = patch("homeassistant.util.dt.utcnow", return_value=retval) patch_two = patch("homeassistant.util.utcnow", return_value=retval) patch_three = patch( - "homeassistant.components.sensor.awair.dt.utcnow", return_value=retval + "homeassistant.components.awair.sensor.dt.utcnow", return_value=retval ) with patch_one, patch_two, patch_three: diff --git a/tests/components/aws/__init__.py b/tests/components/aws/__init__.py new file mode 100644 index 00000000000..270922b1e1e --- /dev/null +++ b/tests/components/aws/__init__.py @@ -0,0 +1 @@ +"""Tests for the aws component.""" diff --git a/tests/components/aws/test_init.py b/tests/components/aws/test_init.py new file mode 100644 index 00000000000..89dd9deaa0a --- /dev/null +++ b/tests/components/aws/test_init.py @@ -0,0 +1,199 @@ +"""Tests for the aws component config and setup.""" +from asynctest import patch as async_patch, MagicMock, CoroutineMock + +from homeassistant.components import aws +from homeassistant.setup import async_setup_component + + +class MockAioSession: + """Mock AioSession.""" + + def __init__(self, *args, **kwargs): + """Init a mock session.""" + + def create_client(self, *args, **kwargs): # pylint: disable=no-self-use + """Create a mocked client.""" + return MagicMock( + __aenter__=CoroutineMock(return_value=CoroutineMock( + get_user=CoroutineMock(), # iam + invoke=CoroutineMock(), # lambda + publish=CoroutineMock(), # sns + send_message=CoroutineMock(), # sqs + )), + __aexit__=CoroutineMock() + ) + + +async def test_empty_config(hass): + """Test a default config will be create for empty config.""" + with async_patch('aiobotocore.AioSession', new=MockAioSession): + await async_setup_component(hass, 'aws', { + 'aws': {} + }) + await hass.async_block_till_done() + + sessions = hass.data[aws.DATA_SESSIONS] + assert sessions is not None + assert len(sessions) == 1 + assert isinstance(sessions.get('default'), MockAioSession) + + +async def test_empty_credential(hass): + """Test a default config will be create for empty credential section.""" + with async_patch('aiobotocore.AioSession', new=MockAioSession): + await async_setup_component(hass, 'aws', { + 'aws': { + 'notify': [{ + 'service': 'lambda', + 'name': 'New Lambda Test', + 'region_name': 'us-east-1', + }] + } + }) + await hass.async_block_till_done() + + sessions = hass.data[aws.DATA_SESSIONS] + assert sessions is not None + assert len(sessions) == 1 + assert isinstance(sessions.get('default'), MockAioSession) + + assert hass.services.has_service('notify', 'new_lambda_test') is True + await hass.services.async_call( + 'notify', + 'new_lambda_test', + {'message': 'test', 'target': 'ARN'}, + blocking=True + ) + + +async def test_profile_credential(hass): + """Test credentials with profile name.""" + with async_patch('aiobotocore.AioSession', new=MockAioSession): + await async_setup_component(hass, 'aws', { + 'aws': { + 'credentials': { + 'name': 'test', + 'profile_name': 'test-profile', + }, + 'notify': [{ + 'service': 'sns', + 'credential_name': 'test', + 'name': 'SNS Test', + 'region_name': 'us-east-1', + }] + } + }) + await hass.async_block_till_done() + + sessions = hass.data[aws.DATA_SESSIONS] + assert sessions is not None + assert len(sessions) == 1 + assert isinstance(sessions.get('test'), MockAioSession) + + assert hass.services.has_service('notify', 'sns_test') is True + await hass.services.async_call( + 'notify', + 'sns_test', + {'title': 'test', 'message': 'test', 'target': 'ARN'}, + blocking=True + ) + + +async def test_access_key_credential(hass): + """Test credentials with access key.""" + with async_patch('aiobotocore.AioSession', new=MockAioSession): + await async_setup_component(hass, 'aws', { + 'aws': { + 'credentials': [ + { + 'name': 'test', + 'profile_name': 'test-profile', + }, + { + 'name': 'key', + 'aws_access_key_id': 'test-key', + 'aws_secret_access_key': 'test-secret', + }, + ], + 'notify': [{ + 'service': 'sns', + 'credential_name': 'key', + 'name': 'SNS Test', + 'region_name': 'us-east-1', + }] + } + }) + await hass.async_block_till_done() + + sessions = hass.data[aws.DATA_SESSIONS] + assert sessions is not None + assert len(sessions) == 2 + assert isinstance(sessions.get('key'), MockAioSession) + + assert hass.services.has_service('notify', 'sns_test') is True + await hass.services.async_call( + 'notify', + 'sns_test', + {'title': 'test', 'message': 'test', 'target': 'ARN'}, + blocking=True + ) + + +async def test_notify_credential(hass): + """Test notify service can use access key directly.""" + with async_patch('aiobotocore.AioSession', new=MockAioSession): + await async_setup_component(hass, 'aws', { + 'aws': { + 'notify': [{ + 'service': 'sqs', + 'credential_name': 'test', + 'name': 'SQS Test', + 'region_name': 'us-east-1', + 'aws_access_key_id': 'some-key', + 'aws_secret_access_key': 'some-secret', + }] + } + }) + await hass.async_block_till_done() + + sessions = hass.data[aws.DATA_SESSIONS] + assert sessions is not None + assert len(sessions) == 1 + assert isinstance(sessions.get('default'), MockAioSession) + + assert hass.services.has_service('notify', 'sqs_test') is True + await hass.services.async_call( + 'notify', + 'sqs_test', + {'message': 'test', 'target': 'ARN'}, + blocking=True + ) + + +async def test_notify_credential_profile(hass): + """Test notify service can use profile directly.""" + with async_patch('aiobotocore.AioSession', new=MockAioSession): + await async_setup_component(hass, 'aws', { + 'aws': { + 'notify': [{ + 'service': 'sqs', + 'name': 'SQS Test', + 'region_name': 'us-east-1', + 'profile_name': 'test', + }] + } + }) + await hass.async_block_till_done() + + sessions = hass.data[aws.DATA_SESSIONS] + assert sessions is not None + assert len(sessions) == 1 + assert isinstance(sessions.get('default'), MockAioSession) + + assert hass.services.has_service('notify', 'sqs_test') is True + await hass.services.async_call( + 'notify', + 'sqs_test', + {'message': 'test', 'target': 'ARN'}, + blocking=True + ) diff --git a/tests/components/axis/__init__.py b/tests/components/axis/__init__.py new file mode 100644 index 00000000000..c7e0f05a814 --- /dev/null +++ b/tests/components/axis/__init__.py @@ -0,0 +1 @@ +"""Tests for the Axis component.""" diff --git a/tests/components/axis/test_binary_sensor.py b/tests/components/axis/test_binary_sensor.py new file mode 100644 index 00000000000..9ca8b81793b --- /dev/null +++ b/tests/components/axis/test_binary_sensor.py @@ -0,0 +1,102 @@ +"""Axis binary sensor platform tests.""" + +from unittest.mock import Mock + +from homeassistant import config_entries +from homeassistant.components import axis +from homeassistant.setup import async_setup_component + +import homeassistant.components.binary_sensor as binary_sensor + +EVENTS = [ + { + 'operation': 'Initialized', + 'topic': 'tns1:Device/tnsaxis:Sensor/PIR', + 'source': 'sensor', + 'source_idx': '0', + 'type': 'state', + 'value': '0' + }, + { + 'operation': 'Initialized', + 'topic': 'tnsaxis:CameraApplicationPlatform/VMD/Camera1Profile1', + 'type': 'active', + 'value': '1' + } +] + +ENTRY_CONFIG = { + axis.CONF_DEVICE: { + axis.config_flow.CONF_HOST: '1.2.3.4', + axis.config_flow.CONF_USERNAME: 'user', + axis.config_flow.CONF_PASSWORD: 'pass', + axis.config_flow.CONF_PORT: 80 + }, + axis.config_flow.CONF_MAC: '1234ABCD', + axis.config_flow.CONF_MODEL: 'model', + axis.config_flow.CONF_NAME: 'model 0' +} + +ENTRY_OPTIONS = { + axis.CONF_CAMERA: False, + axis.CONF_EVENTS: True, + axis.CONF_TRIGGER_TIME: 0 +} + + +async def setup_device(hass): + """Load the Axis binary sensor platform.""" + from axis import AxisDevice + loop = Mock() + + config_entry = config_entries.ConfigEntry( + 1, axis.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test', + config_entries.CONN_CLASS_LOCAL_PUSH, options=ENTRY_OPTIONS) + device = axis.AxisNetworkDevice(hass, config_entry) + device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE], + signal=device.async_signal_callback) + hass.data[axis.DOMAIN] = {device.serial: device} + + await hass.config_entries.async_forward_entry_setup( + config_entry, 'binary_sensor') + # To flush out the service call to update the group + await hass.async_block_till_done() + + return device + + +async def test_platform_manually_configured(hass): + """Test that nothing happens when platform is manually configured.""" + assert await async_setup_component(hass, binary_sensor.DOMAIN, { + 'binary_sensor': { + 'platform': axis.DOMAIN + } + }) is True + + assert axis.DOMAIN not in hass.data + + +async def test_no_binary_sensors(hass): + """Test that no sensors in Axis results in no sensor entities.""" + await setup_device(hass) + + assert len(hass.states.async_all()) == 0 + + +async def test_binary_sensors(hass): + """Test that sensors are loaded properly.""" + device = await setup_device(hass) + + for event in EVENTS: + device.api.stream.event.manage_event(event) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 2 + + pir = hass.states.get('binary_sensor.model_0_pir_0') + assert pir.state == 'off' + assert pir.name == 'model 0 PIR 0' + + vmd4 = hass.states.get('binary_sensor.model_0_vmd4_camera1profile1') + assert vmd4.state == 'on' + assert vmd4.name == 'model 0 VMD4 Camera1Profile1' diff --git a/tests/components/axis/test_camera.py b/tests/components/axis/test_camera.py new file mode 100644 index 00000000000..c585ada6319 --- /dev/null +++ b/tests/components/axis/test_camera.py @@ -0,0 +1,73 @@ +"""Axis camera platform tests.""" + +from unittest.mock import Mock + +from homeassistant import config_entries +from homeassistant.components import axis +from homeassistant.setup import async_setup_component + +import homeassistant.components.camera as camera + + +ENTRY_CONFIG = { + axis.CONF_DEVICE: { + axis.config_flow.CONF_HOST: '1.2.3.4', + axis.config_flow.CONF_USERNAME: 'user', + axis.config_flow.CONF_PASSWORD: 'pass', + axis.config_flow.CONF_PORT: 80 + }, + axis.config_flow.CONF_MAC: '1234ABCD', + axis.config_flow.CONF_MODEL: 'model', + axis.config_flow.CONF_NAME: 'model 0' +} + +ENTRY_OPTIONS = { + axis.CONF_CAMERA: False, + axis.CONF_EVENTS: True, + axis.CONF_TRIGGER_TIME: 0 +} + + +async def setup_device(hass): + """Load the Axis binary sensor platform.""" + from axis import AxisDevice + loop = Mock() + + config_entry = config_entries.ConfigEntry( + 1, axis.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test', + config_entries.CONN_CLASS_LOCAL_PUSH, options=ENTRY_OPTIONS) + device = axis.AxisNetworkDevice(hass, config_entry) + device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE], + signal=device.async_signal_callback) + hass.data[axis.DOMAIN] = {device.serial: device} + + await hass.config_entries.async_forward_entry_setup( + config_entry, 'camera') + # To flush out the service call to update the group + await hass.async_block_till_done() + + return device + + +async def test_platform_manually_configured(hass): + """Test that nothing happens when platform is manually configured.""" + assert await async_setup_component(hass, camera.DOMAIN, { + 'camera': { + 'platform': axis.DOMAIN + } + }) is True + + assert axis.DOMAIN not in hass.data + + +async def test_camera(hass): + """Test that Axis camera platform is loaded properly.""" + await setup_device(hass) + + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + + cam = hass.states.get('camera.model_0') + assert cam.state == 'idle' + assert cam.name == 'model 0' diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py new file mode 100644 index 00000000000..7e18b36c6a6 --- /dev/null +++ b/tests/components/axis/test_config_flow.py @@ -0,0 +1,319 @@ +"""Test Axis config flow.""" +from unittest.mock import Mock, patch + +from homeassistant.components import axis +from homeassistant.components.axis import config_flow + +from tests.common import mock_coro, MockConfigEntry + +import axis as axis_lib + + +async def test_configured_devices(hass): + """Test that configured devices works as expected.""" + result = config_flow.configured_devices(hass) + + assert not result + + entry = MockConfigEntry(domain=axis.DOMAIN, + data={axis.CONF_DEVICE: {axis.CONF_HOST: ''}}) + entry.add_to_hass(hass) + + result = config_flow.configured_devices(hass) + + assert len(result) == 1 + + +async def test_flow_works(hass): + """Test that config flow works.""" + flow = config_flow.AxisFlowHandler() + flow.hass = hass + + with patch('axis.AxisDevice') as mock_device: + def mock_constructor( + loop, host, username, password, port, web_proto, event_types, + signal): + """Fake the controller constructor.""" + mock_device.loop = loop + mock_device.host = host + mock_device.username = username + mock_device.password = password + mock_device.port = port + return mock_device + + def mock_get_param(param): + """Fake get param method.""" + return param + + mock_device.side_effect = mock_constructor + mock_device.vapix.load_params.return_value = Mock() + mock_device.vapix.get_param.side_effect = mock_get_param + + result = await flow.async_step_user(user_input={ + config_flow.CONF_HOST: '1.2.3.4', + config_flow.CONF_USERNAME: 'user', + config_flow.CONF_PASSWORD: 'pass', + config_flow.CONF_PORT: 81 + }) + + assert result['type'] == 'create_entry' + assert result['title'] == '{} - {}'.format( + axis_lib.vapix.VAPIX_MODEL_ID, axis_lib.vapix.VAPIX_SERIAL_NUMBER) + assert result['data'] == { + axis.CONF_DEVICE: { + config_flow.CONF_HOST: '1.2.3.4', + config_flow.CONF_USERNAME: 'user', + config_flow.CONF_PASSWORD: 'pass', + config_flow.CONF_PORT: 81 + }, + config_flow.CONF_MAC: axis_lib.vapix.VAPIX_SERIAL_NUMBER, + config_flow.CONF_MODEL: axis_lib.vapix.VAPIX_MODEL_ID, + config_flow.CONF_NAME: 'Brand.ProdNbr 0' + } + + +async def test_flow_fails_already_configured(hass): + """Test that config flow fails on already configured device.""" + flow = config_flow.AxisFlowHandler() + flow.hass = hass + + entry = MockConfigEntry(domain=axis.DOMAIN, data={axis.CONF_DEVICE: { + axis.CONF_HOST: '1.2.3.4' + }}) + entry.add_to_hass(hass) + + result = await flow.async_step_user(user_input={ + config_flow.CONF_HOST: '1.2.3.4', + config_flow.CONF_USERNAME: 'user', + config_flow.CONF_PASSWORD: 'pass', + config_flow.CONF_PORT: 81 + }) + + assert result['errors'] == {'base': 'already_configured'} + + +async def test_flow_fails_faulty_credentials(hass): + """Test that config flow fails on faulty credentials.""" + flow = config_flow.AxisFlowHandler() + flow.hass = hass + + with patch('homeassistant.components.axis.config_flow.get_device', + side_effect=config_flow.AuthenticationRequired): + result = await flow.async_step_user(user_input={ + config_flow.CONF_HOST: '1.2.3.4', + config_flow.CONF_USERNAME: 'user', + config_flow.CONF_PASSWORD: 'pass', + config_flow.CONF_PORT: 81 + }) + + assert result['errors'] == {'base': 'faulty_credentials'} + + +async def test_flow_fails_device_unavailable(hass): + """Test that config flow fails on device unavailable.""" + flow = config_flow.AxisFlowHandler() + flow.hass = hass + + with patch('homeassistant.components.axis.config_flow.get_device', + side_effect=config_flow.CannotConnect): + result = await flow.async_step_user(user_input={ + config_flow.CONF_HOST: '1.2.3.4', + config_flow.CONF_USERNAME: 'user', + config_flow.CONF_PASSWORD: 'pass', + config_flow.CONF_PORT: 81 + }) + + assert result['errors'] == {'base': 'device_unavailable'} + + +async def test_flow_create_entry(hass): + """Test that create entry can generate a name without other entries.""" + flow = config_flow.AxisFlowHandler() + flow.hass = hass + flow.model = 'model' + + result = await flow._create_entry() + + assert result['data'][config_flow.CONF_NAME] == 'model 0' + + +async def test_flow_create_entry_more_entries(hass): + """Test that create entry can generate a name with other entries.""" + entry = MockConfigEntry( + domain=axis.DOMAIN, data={config_flow.CONF_NAME: 'model 0', + config_flow.CONF_MODEL: 'model'}) + entry.add_to_hass(hass) + entry2 = MockConfigEntry( + domain=axis.DOMAIN, data={config_flow.CONF_NAME: 'model 1', + config_flow.CONF_MODEL: 'model'}) + entry2.add_to_hass(hass) + + flow = config_flow.AxisFlowHandler() + flow.hass = hass + flow.model = 'model' + + result = await flow._create_entry() + + assert result['data'][config_flow.CONF_NAME] == 'model 2' + + +async def test_discovery_flow(hass): + """Test that discovery for new devices work.""" + flow = config_flow.AxisFlowHandler() + flow.hass = hass + + with patch.object(axis, 'get_device', return_value=mock_coro(Mock())): + result = await flow.async_step_discovery(discovery_info={ + config_flow.CONF_HOST: '1.2.3.4', + config_flow.CONF_PORT: 80, + 'properties': {'macaddress': '1234'} + }) + + assert result['type'] == 'form' + assert result['step_id'] == 'user' + + +async def test_discovery_flow_known_device(hass): + """Test that discovery for known devices work. + + This is legacy support from devices registered with configurator. + """ + flow = config_flow.AxisFlowHandler() + flow.hass = hass + + with patch('homeassistant.components.axis.config_flow.load_json', + return_value={'1234ABCD': { + config_flow.CONF_HOST: '2.3.4.5', + config_flow.CONF_USERNAME: 'user', + config_flow.CONF_PASSWORD: 'pass', + config_flow.CONF_PORT: 80}}), \ + patch('axis.AxisDevice') as mock_device: + def mock_constructor( + loop, host, username, password, port, web_proto, event_types, + signal): + """Fake the controller constructor.""" + mock_device.loop = loop + mock_device.host = host + mock_device.username = username + mock_device.password = password + mock_device.port = port + return mock_device + + def mock_get_param(param): + """Fake get param method.""" + return param + + mock_device.side_effect = mock_constructor + mock_device.vapix.load_params.return_value = Mock() + mock_device.vapix.get_param.side_effect = mock_get_param + + result = await flow.async_step_discovery(discovery_info={ + config_flow.CONF_HOST: '1.2.3.4', + config_flow.CONF_PORT: 80, + 'hostname': 'name', + 'properties': {'macaddress': '1234ABCD'} + }) + + assert result['type'] == 'create_entry' + + +async def test_discovery_flow_already_configured(hass): + """Test that discovery doesn't setup already configured devices.""" + flow = config_flow.AxisFlowHandler() + flow.hass = hass + + entry = MockConfigEntry(domain=axis.DOMAIN, data={axis.CONF_DEVICE: { + axis.CONF_HOST: '1.2.3.4' + }}) + entry.add_to_hass(hass) + + result = await flow.async_step_discovery(discovery_info={ + config_flow.CONF_HOST: '1.2.3.4', + config_flow.CONF_USERNAME: 'user', + config_flow.CONF_PASSWORD: 'pass', + config_flow.CONF_PORT: 81 + }) + print(result) + assert result['type'] == 'abort' + + +async def test_discovery_flow_link_local_address(hass): + """Test that discovery doesn't setup devices with link local addresses.""" + flow = config_flow.AxisFlowHandler() + flow.hass = hass + + result = await flow.async_step_discovery(discovery_info={ + config_flow.CONF_HOST: '169.254.3.4' + }) + + assert result['type'] == 'abort' + + +async def test_discovery_flow_bad_config_file(hass): + """Test that discovery with bad config files abort.""" + flow = config_flow.AxisFlowHandler() + flow.hass = hass + + with patch('homeassistant.components.axis.config_flow.load_json', + return_value={'1234ABCD': { + config_flow.CONF_HOST: '2.3.4.5', + config_flow.CONF_USERNAME: 'user', + config_flow.CONF_PASSWORD: 'pass', + config_flow.CONF_PORT: 80}}), \ + patch('homeassistant.components.axis.config_flow.DEVICE_SCHEMA', + side_effect=config_flow.vol.Invalid('')): + result = await flow.async_step_discovery(discovery_info={ + config_flow.CONF_HOST: '1.2.3.4', + 'properties': {'macaddress': '1234ABCD'} + }) + + assert result['type'] == 'abort' + + +async def test_import_flow_works(hass): + """Test that import flow works.""" + flow = config_flow.AxisFlowHandler() + flow.hass = hass + + with patch('axis.AxisDevice') as mock_device: + def mock_constructor( + loop, host, username, password, port, web_proto, event_types, + signal): + """Fake the controller constructor.""" + mock_device.loop = loop + mock_device.host = host + mock_device.username = username + mock_device.password = password + mock_device.port = port + return mock_device + + def mock_get_param(param): + """Fake get param method.""" + return param + + mock_device.side_effect = mock_constructor + mock_device.vapix.load_params.return_value = Mock() + mock_device.vapix.get_param.side_effect = mock_get_param + + result = await flow.async_step_import(import_config={ + config_flow.CONF_HOST: '1.2.3.4', + config_flow.CONF_USERNAME: 'user', + config_flow.CONF_PASSWORD: 'pass', + config_flow.CONF_PORT: 81, + config_flow.CONF_NAME: 'name' + }) + + assert result['type'] == 'create_entry' + assert result['title'] == '{} - {}'.format( + axis_lib.vapix.VAPIX_MODEL_ID, axis_lib.vapix.VAPIX_SERIAL_NUMBER) + assert result['data'] == { + axis.CONF_DEVICE: { + config_flow.CONF_HOST: '1.2.3.4', + config_flow.CONF_USERNAME: 'user', + config_flow.CONF_PASSWORD: 'pass', + config_flow.CONF_PORT: 81 + }, + config_flow.CONF_MAC: axis_lib.vapix.VAPIX_SERIAL_NUMBER, + config_flow.CONF_MODEL: axis_lib.vapix.VAPIX_MODEL_ID, + config_flow.CONF_NAME: 'name' + } diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py new file mode 100644 index 00000000000..2a0a7d6391c --- /dev/null +++ b/tests/components/axis/test_device.py @@ -0,0 +1,152 @@ +"""Test Axis device.""" +from unittest.mock import Mock, patch + +import pytest + +from tests.common import mock_coro + +from homeassistant.components.axis import device, errors + +DEVICE_DATA = { + device.CONF_HOST: '1.2.3.4', + device.CONF_USERNAME: 'username', + device.CONF_PASSWORD: 'password', + device.CONF_PORT: 1234 +} + +ENTRY_OPTIONS = { + device.CONF_CAMERA: True, + device.CONF_EVENTS: ['pir'], +} + +ENTRY_CONFIG = { + device.CONF_DEVICE: DEVICE_DATA, + device.CONF_MAC: 'mac', + device.CONF_MODEL: 'model', + device.CONF_NAME: 'name' +} + + +async def test_device_setup(): + """Successful setup.""" + hass = Mock() + entry = Mock() + entry.data = ENTRY_CONFIG + entry.options = ENTRY_OPTIONS + api = Mock() + + axis_device = device.AxisNetworkDevice(hass, entry) + + assert axis_device.host == DEVICE_DATA[device.CONF_HOST] + assert axis_device.model == ENTRY_CONFIG[device.CONF_MODEL] + assert axis_device.name == ENTRY_CONFIG[device.CONF_NAME] + assert axis_device.serial == ENTRY_CONFIG[device.CONF_MAC] + + with patch.object(device, 'get_device', return_value=mock_coro(api)): + assert await axis_device.async_setup() is True + + assert axis_device.api is api + assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 2 + assert hass.config_entries.async_forward_entry_setup.mock_calls[0][1] == \ + (entry, 'camera') + assert hass.config_entries.async_forward_entry_setup.mock_calls[1][1] == \ + (entry, 'binary_sensor') + + +async def test_device_not_accessible(): + """Failed setup schedules a retry of setup.""" + hass = Mock() + hass.data = dict() + entry = Mock() + entry.data = ENTRY_CONFIG + entry.options = ENTRY_OPTIONS + + axis_device = device.AxisNetworkDevice(hass, entry) + + with patch.object(device, 'get_device', + side_effect=errors.CannotConnect), \ + pytest.raises(device.ConfigEntryNotReady): + await axis_device.async_setup() + + assert not hass.helpers.event.async_call_later.mock_calls + + +async def test_device_unknown_error(): + """Unknown errors are handled.""" + hass = Mock() + entry = Mock() + entry.data = ENTRY_CONFIG + entry.options = ENTRY_OPTIONS + + axis_device = device.AxisNetworkDevice(hass, entry) + + with patch.object(device, 'get_device', side_effect=Exception): + assert await axis_device.async_setup() is False + + assert not hass.helpers.event.async_call_later.mock_calls + + +async def test_new_event_sends_signal(hass): + """Make sure that new event send signal.""" + entry = Mock() + entry.data = ENTRY_CONFIG + + axis_device = device.AxisNetworkDevice(hass, entry) + + with patch.object(device, 'async_dispatcher_send') as mock_dispatch_send: + axis_device.async_signal_callback(action='add', event='event') + await hass.async_block_till_done() + + assert len(mock_dispatch_send.mock_calls) == 1 + assert len(mock_dispatch_send.mock_calls[0]) == 3 + + +async def test_shutdown(): + """Successful shutdown.""" + hass = Mock() + entry = Mock() + entry.data = ENTRY_CONFIG + + axis_device = device.AxisNetworkDevice(hass, entry) + axis_device.api = Mock() + + axis_device.shutdown(None) + + assert len(axis_device.api.stop.mock_calls) == 1 + + +async def test_get_device(hass): + """Successful call.""" + with patch('axis.vapix.Vapix.load_params', + return_value=mock_coro()): + assert await device.get_device(hass, DEVICE_DATA) + + +async def test_get_device_fails(hass): + """Device unauthorized yields authentication required error.""" + import axis + + with patch('axis.vapix.Vapix.load_params', + side_effect=axis.Unauthorized), \ + pytest.raises(errors.AuthenticationRequired): + await device.get_device(hass, DEVICE_DATA) + + +async def test_get_device_device_unavailable(hass): + """Device unavailable yields cannot connect error.""" + import axis + + with patch('axis.vapix.Vapix.load_params', + side_effect=axis.RequestError), \ + pytest.raises(errors.CannotConnect): + await device.get_device(hass, DEVICE_DATA) + + +async def test_get_device_unknown_error(hass): + """Device yield unknown error.""" + import axis + + with patch('axis.vapix.Vapix.load_params', + side_effect=axis.AxisException), \ + pytest.raises(errors.AuthenticationRequired): + await device.get_device(hass, DEVICE_DATA) diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py new file mode 100644 index 00000000000..c1c4c06f6ac --- /dev/null +++ b/tests/components/axis/test_init.py @@ -0,0 +1,98 @@ +"""Test Axis component setup process.""" +from unittest.mock import Mock, patch + +from homeassistant.setup import async_setup_component +from homeassistant.components import axis + +from tests.common import mock_coro, MockConfigEntry + + +async def test_setup(hass): + """Test configured options for a device are loaded via config entry.""" + with patch.object(hass, 'config_entries') as mock_config_entries, \ + patch.object(axis, 'configured_devices', return_value={}): + + assert await async_setup_component(hass, axis.DOMAIN, { + axis.DOMAIN: { + 'device_name': { + axis.CONF_HOST: '1.2.3.4', + axis.config_flow.CONF_PORT: 80, + } + } + }) + + assert len(mock_config_entries.flow.mock_calls) == 1 + + +async def test_setup_device_already_configured(hass): + """Test already configured device does not configure a second.""" + with patch.object(hass, 'config_entries') as mock_config_entries, \ + patch.object(axis, 'configured_devices', return_value={'1.2.3.4'}): + + assert await async_setup_component(hass, axis.DOMAIN, { + axis.DOMAIN: { + 'device_name': { + axis.CONF_HOST: '1.2.3.4' + } + } + }) + + assert not mock_config_entries.flow.mock_calls + + +async def test_setup_no_config(hass): + """Test setup without configuration.""" + assert await async_setup_component(hass, axis.DOMAIN, {}) + assert axis.DOMAIN not in hass.data + + +async def test_setup_entry(hass): + """Test successful setup of entry.""" + entry = MockConfigEntry( + domain=axis.DOMAIN, data={axis.device.CONF_MAC: '0123'}) + + mock_device = Mock() + mock_device.async_setup.return_value = mock_coro(True) + mock_device.async_update_device_registry.return_value = mock_coro(True) + mock_device.serial.return_value = '1' + + with patch.object(axis, 'AxisNetworkDevice') as mock_device_class, \ + patch.object( + axis, 'async_populate_options', return_value=mock_coro(True)): + mock_device_class.return_value = mock_device + + assert await axis.async_setup_entry(hass, entry) + + assert len(hass.data[axis.DOMAIN]) == 1 + + +async def test_setup_entry_fails(hass): + """Test successful setup of entry.""" + entry = MockConfigEntry( + domain=axis.DOMAIN, data={axis.device.CONF_MAC: '0123'}, options=True) + + mock_device = Mock() + mock_device.async_setup.return_value = mock_coro(False) + + with patch.object(axis, 'AxisNetworkDevice') as mock_device_class: + mock_device_class.return_value = mock_device + + assert not await axis.async_setup_entry(hass, entry) + + assert not hass.data[axis.DOMAIN] + + +async def test_populate_options(hass): + """Test successful populate options.""" + entry = MockConfigEntry(domain=axis.DOMAIN, data={'device': {}}) + entry.add_to_hass(hass) + + with patch.object(axis, 'get_device', return_value=mock_coro(Mock())): + + await axis.async_populate_options(hass, entry) + + assert entry.options == { + axis.CONF_CAMERA: True, + axis.CONF_EVENTS: True, + axis.CONF_TRIGGER_TIME: axis.DEFAULT_TRIGGER_TIME + } diff --git a/tests/components/bayesian/__init__.py b/tests/components/bayesian/__init__.py new file mode 100644 index 00000000000..d3850a2e520 --- /dev/null +++ b/tests/components/bayesian/__init__.py @@ -0,0 +1 @@ +"""Tests for bayesian component.""" diff --git a/tests/components/binary_sensor/test_bayesian.py b/tests/components/bayesian/test_binary_sensor.py similarity index 99% rename from tests/components/binary_sensor/test_bayesian.py rename to tests/components/bayesian/test_binary_sensor.py index b52459ec47d..4b10470fc89 100644 --- a/tests/components/binary_sensor/test_bayesian.py +++ b/tests/components/bayesian/test_binary_sensor.py @@ -2,7 +2,7 @@ import unittest from homeassistant.setup import setup_component -from homeassistant.components.binary_sensor import bayesian +from homeassistant.components.bayesian import binary_sensor as bayesian from tests.common import get_test_home_assistant diff --git a/tests/components/blackbird/__init__.py b/tests/components/blackbird/__init__.py new file mode 100644 index 00000000000..5f6031695f4 --- /dev/null +++ b/tests/components/blackbird/__init__.py @@ -0,0 +1 @@ +"""Tests for the blackbird component.""" diff --git a/tests/components/media_player/test_blackbird.py b/tests/components/blackbird/test_media_player.py similarity index 99% rename from tests/components/media_player/test_blackbird.py rename to tests/components/blackbird/test_media_player.py index 6ab3b69b558..3430a232808 100644 --- a/tests/components/media_player/test_blackbird.py +++ b/tests/components/blackbird/test_media_player.py @@ -9,7 +9,7 @@ from homeassistant.components.media_player.const import ( from homeassistant.const import STATE_ON, STATE_OFF import tests.common -from homeassistant.components.media_player.blackbird import ( +from homeassistant.components.blackbird.media_player import ( DATA_BLACKBIRD, PLATFORM_SCHEMA, SERVICE_SETALLZONES, setup_platform) import pytest diff --git a/tests/components/bom/__init__.py b/tests/components/bom/__init__.py new file mode 100644 index 00000000000..a129618ef2c --- /dev/null +++ b/tests/components/bom/__init__.py @@ -0,0 +1 @@ +"""Tests for the bom component.""" diff --git a/tests/components/sensor/test_bom.py b/tests/components/bom/test_sensor.py similarity index 98% rename from tests/components/sensor/test_bom.py rename to tests/components/bom/test_sensor.py index fc2722f9742..d5c197d9eea 100644 --- a/tests/components/sensor/test_bom.py +++ b/tests/components/bom/test_sensor.py @@ -8,7 +8,7 @@ from urllib.parse import urlparse import requests from homeassistant.components import sensor -from homeassistant.components.sensor.bom import BOMCurrentData +from homeassistant.components.bom.sensor import BOMCurrentData from homeassistant.setup import setup_component from tests.common import ( assert_setup_component, get_test_home_assistant, load_fixture) diff --git a/tests/components/caldav/__init__.py b/tests/components/caldav/__init__.py new file mode 100644 index 00000000000..385d11b2f4e --- /dev/null +++ b/tests/components/caldav/__init__.py @@ -0,0 +1 @@ +"""Tests for the caldav component.""" diff --git a/tests/components/calendar/test_caldav.py b/tests/components/caldav/test_calendar.py similarity index 99% rename from tests/components/calendar/test_caldav.py rename to tests/components/caldav/test_calendar.py index 45de0052ce6..ceca68c1f8a 100644 --- a/tests/components/calendar/test_caldav.py +++ b/tests/components/caldav/test_calendar.py @@ -6,7 +6,7 @@ import unittest from unittest.mock import (patch, Mock, MagicMock) import homeassistant.components.calendar as calendar_base -import homeassistant.components.calendar.caldav as caldav +import homeassistant.components.caldav.calendar as caldav from caldav.objects import Event from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.util import dt diff --git a/tests/components/camera/common.py b/tests/components/camera/common.py index 21f7244bd29..bebb991a7af 100644 --- a/tests/components/camera/common.py +++ b/tests/components/camera/common.py @@ -4,7 +4,9 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ from homeassistant.components.camera import ( - ATTR_FILENAME, DOMAIN, SERVICE_ENABLE_MOTION, SERVICE_SNAPSHOT) + ATTR_FILENAME, SERVICE_ENABLE_MOTION, SERVICE_SNAPSHOT) +from homeassistant.components.camera.const import ( + DOMAIN, DATA_CAMERA_PREFS, PREF_PRELOAD_STREAM) from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \ SERVICE_TURN_ON from homeassistant.core import callback @@ -45,3 +47,13 @@ def async_snapshot(hass, filename, entity_id=None): hass.async_add_job(hass.services.async_call( DOMAIN, SERVICE_SNAPSHOT, data)) + + +def mock_camera_prefs(hass, entity_id, prefs={}): + """Fixture for cloud component.""" + prefs_to_set = { + PREF_PRELOAD_STREAM: True, + } + prefs_to_set.update(prefs) + hass.data[DATA_CAMERA_PREFS]._prefs[entity_id] = prefs_to_set + return prefs_to_set diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index 840e30161f3..e730f39656e 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -1,13 +1,17 @@ """The tests for the camera component.""" import asyncio import base64 +import io from unittest.mock import patch, mock_open, PropertyMock import pytest from homeassistant.setup import setup_component, async_setup_component -from homeassistant.const import (ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE) +from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, EVENT_HOMEASSISTANT_START) from homeassistant.components import camera, http +from homeassistant.components.camera.const import DOMAIN, PREF_PRELOAD_STREAM +from homeassistant.components.camera.prefs import CameraEntityPreferences from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.exceptions import HomeAssistantError from homeassistant.util.async_ import run_coroutine_threadsafe @@ -16,7 +20,6 @@ from tests.common import ( get_test_home_assistant, get_test_instance_port, assert_setup_component, mock_coro) from tests.components.camera import common -from tests.components.stream.common import generate_h264_video @pytest.fixture @@ -28,7 +31,7 @@ def mock_camera(hass): } })) - with patch('homeassistant.components.camera.demo.DemoCamera.camera_image', + with patch('homeassistant.components.demo.camera.DemoCamera.camera_image', return_value=b'Test'): yield @@ -41,6 +44,12 @@ def mock_stream(hass): })) +@pytest.fixture +def setup_camera_prefs(hass): + """Initialize HTTP API.""" + return common.mock_camera_prefs(hass, 'camera.demo_camera') + + class TestSetupCamera: """Test class for setup camera.""" @@ -92,7 +101,7 @@ class TestGetImage: """Stop everything that was started.""" self.hass.stop() - @patch('homeassistant.components.camera.demo.DemoCamera.camera_image', + @patch('homeassistant.components.demo.camera.DemoCamera.camera_image', autospec=True, return_value=b'Test') def test_get_image_from_camera(self, mock_camera): """Grab an image from camera entity.""" @@ -146,7 +155,7 @@ def test_snapshot_service(hass, mock_camera): assert mock_write.mock_calls[0][1][0] == b'Test' -async def test_webocket_camera_thumbnail(hass, hass_ws_client, mock_camera): +async def test_websocket_camera_thumbnail(hass, hass_ws_client, mock_camera): """Test camera_thumbnail websocket command.""" await async_setup_component(hass, 'camera') @@ -167,8 +176,8 @@ async def test_webocket_camera_thumbnail(hass, hass_ws_client, mock_camera): base64.b64encode(b'Test').decode('utf-8') -async def test_webocket_stream_no_source(hass, hass_ws_client, - mock_camera, mock_stream): +async def test_websocket_stream_no_source(hass, hass_ws_client, + mock_camera, mock_stream): """Test camera/stream websocket command.""" await async_setup_component(hass, 'camera') @@ -191,17 +200,17 @@ async def test_webocket_stream_no_source(hass, hass_ws_client, assert not msg['success'] -async def test_webocket_camera_stream(hass, hass_ws_client, hass_client, - mock_camera, mock_stream): +async def test_websocket_camera_stream(hass, hass_ws_client, + mock_camera, mock_stream): """Test camera/stream websocket command.""" await async_setup_component(hass, 'camera') with patch('homeassistant.components.camera.request_stream', return_value='http://home.assistant/playlist.m3u8' ) as mock_request_stream, \ - patch('homeassistant.components.camera.demo.DemoCamera.stream_source', + patch('homeassistant.components.demo.camera.DemoCamera.stream_source', new_callable=PropertyMock) as mock_stream_source: - mock_stream_source.return_value = generate_h264_video() + mock_stream_source.return_value = io.BytesIO() # Request playlist through WebSocket client = await hass_ws_client(hass) await client.send_json({ @@ -219,6 +228,44 @@ async def test_webocket_camera_stream(hass, hass_ws_client, hass_client, assert msg['result']['url'][-13:] == 'playlist.m3u8' +async def test_websocket_get_prefs(hass, hass_ws_client, + mock_camera): + """Test get camera preferences websocket command.""" + await async_setup_component(hass, 'camera') + + # Request preferences through websocket + client = await hass_ws_client(hass) + await client.send_json({ + 'id': 7, + 'type': 'camera/get_prefs', + 'entity_id': 'camera.demo_camera', + }) + msg = await client.receive_json() + + # Assert WebSocket response + assert msg['success'] + + +async def test_websocket_update_prefs(hass, hass_ws_client, + mock_camera, setup_camera_prefs): + """Test updating preference.""" + await async_setup_component(hass, 'camera') + assert setup_camera_prefs[PREF_PRELOAD_STREAM] + client = await hass_ws_client(hass) + await client.send_json({ + 'id': 8, + 'type': 'camera/update_prefs', + 'entity_id': 'camera.demo_camera', + 'preload_stream': False, + }) + response = await client.receive_json() + + assert response['success'] + assert not setup_camera_prefs[PREF_PRELOAD_STREAM] + assert response['result'][PREF_PRELOAD_STREAM] == \ + setup_camera_prefs[PREF_PRELOAD_STREAM] + + async def test_play_stream_service_no_source(hass, mock_camera, mock_stream): """Test camera play_stream service.""" data = { @@ -241,12 +288,91 @@ async def test_handle_play_stream_service(hass, mock_camera, mock_stream): } with patch('homeassistant.components.camera.request_stream' ) as mock_request_stream, \ - patch('homeassistant.components.camera.demo.DemoCamera.stream_source', + patch('homeassistant.components.demo.camera.DemoCamera.stream_source', new_callable=PropertyMock) as mock_stream_source: - mock_stream_source.return_value = generate_h264_video() + mock_stream_source.return_value = io.BytesIO() # Call service await hass.services.async_call( camera.DOMAIN, camera.SERVICE_PLAY_STREAM, data, blocking=True) # So long as we request the stream, the rest should be covered # by the play_media service tests. assert mock_request_stream.called + + +async def test_no_preload_stream(hass, mock_stream): + """Test camera preload preference.""" + demo_prefs = CameraEntityPreferences({ + PREF_PRELOAD_STREAM: False, + }) + with patch('homeassistant.components.camera.request_stream' + ) as mock_request_stream, \ + patch('homeassistant.components.camera.prefs.CameraPreferences.get', + return_value=demo_prefs), \ + patch('homeassistant.components.demo.camera.DemoCamera.stream_source', + new_callable=PropertyMock) as mock_stream_source: + mock_stream_source.return_value = io.BytesIO() + await async_setup_component(hass, 'camera', { + DOMAIN: { + 'platform': 'demo' + } + }) + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + assert not mock_request_stream.called + + +async def test_preload_stream(hass, mock_stream): + """Test camera preload preference.""" + demo_prefs = CameraEntityPreferences({ + PREF_PRELOAD_STREAM: True, + }) + with patch('homeassistant.components.camera.request_stream' + ) as mock_request_stream, \ + patch('homeassistant.components.camera.prefs.CameraPreferences.get', + return_value=demo_prefs), \ + patch('homeassistant.components.demo.camera.DemoCamera.stream_source', + new_callable=PropertyMock) as mock_stream_source: + mock_stream_source.return_value = io.BytesIO() + await async_setup_component(hass, 'camera', { + DOMAIN: { + 'platform': 'demo' + } + }) + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + assert mock_request_stream.called + + +async def test_record_service_invalid_path(hass, mock_camera): + """Test record service with invalid path.""" + data = { + ATTR_ENTITY_ID: 'camera.demo_camera', + camera.CONF_FILENAME: '/my/invalid/path' + } + with patch.object(hass.config, 'is_allowed_path', return_value=False), \ + pytest.raises(HomeAssistantError): + # Call service + await hass.services.async_call( + camera.DOMAIN, camera.SERVICE_RECORD, data, blocking=True) + + +async def test_record_service(hass, mock_camera, mock_stream): + """Test record service.""" + data = { + ATTR_ENTITY_ID: 'camera.demo_camera', + camera.CONF_FILENAME: '/my/path' + } + + with patch('homeassistant.components.demo.camera.DemoCamera.stream_source', + new_callable=PropertyMock) as mock_stream_source, \ + patch( + 'homeassistant.components.stream.async_handle_record_service', + return_value=mock_coro()) as mock_record_service, \ + patch.object(hass.config, 'is_allowed_path', return_value=True): + mock_stream_source.return_value = io.BytesIO() + # Call service + await hass.services.async_call( + camera.DOMAIN, camera.SERVICE_RECORD, data, blocking=True) + # So long as we call stream.record, the rest should be covered + # by those tests. + assert mock_record_service.called diff --git a/tests/components/sensor/test_canary.py b/tests/components/canary/test_sensor.py similarity index 98% rename from tests/components/sensor/test_canary.py rename to tests/components/canary/test_sensor.py index dde8f4e0f94..c785a98e0d8 100644 --- a/tests/components/sensor/test_canary.py +++ b/tests/components/canary/test_sensor.py @@ -4,8 +4,8 @@ import unittest from unittest.mock import Mock from homeassistant.components.canary import DATA_CANARY -from homeassistant.components.sensor import canary -from homeassistant.components.sensor.canary import CanarySensor, \ +from homeassistant.components.canary import sensor as canary +from homeassistant.components.canary.sensor import CanarySensor, \ SENSOR_TYPES, ATTR_AIR_QUALITY, STATE_AIR_QUALITY_NORMAL, \ STATE_AIR_QUALITY_ABNORMAL, STATE_AIR_QUALITY_VERY_ABNORMAL from tests.common import (get_test_home_assistant) diff --git a/tests/components/coinmarketcap/__init__.py b/tests/components/coinmarketcap/__init__.py new file mode 100644 index 00000000000..9e9b871bbe2 --- /dev/null +++ b/tests/components/coinmarketcap/__init__.py @@ -0,0 +1 @@ +"""Tests for the coinmarketcap component.""" diff --git a/tests/components/sensor/test_coinmarketcap.py b/tests/components/coinmarketcap/test_sensor.py similarity index 100% rename from tests/components/sensor/test_coinmarketcap.py rename to tests/components/coinmarketcap/test_sensor.py diff --git a/tests/components/command_line/__init__.py b/tests/components/command_line/__init__.py new file mode 100644 index 00000000000..d79b3e27db3 --- /dev/null +++ b/tests/components/command_line/__init__.py @@ -0,0 +1 @@ +"""Tests for command_line component.""" diff --git a/tests/components/binary_sensor/test_command_line.py b/tests/components/command_line/test_binary_sensor.py similarity index 96% rename from tests/components/binary_sensor/test_command_line.py rename to tests/components/command_line/test_binary_sensor.py index 2a6b35ea5e0..be93d6561ec 100644 --- a/tests/components/binary_sensor/test_command_line.py +++ b/tests/components/command_line/test_binary_sensor.py @@ -2,7 +2,7 @@ import unittest from homeassistant.const import (STATE_ON, STATE_OFF) -from homeassistant.components.binary_sensor import command_line +from homeassistant.components.command_line import binary_sensor as command_line from homeassistant.helpers import template from tests.common import get_test_home_assistant diff --git a/tests/components/cover/test_command_line.py b/tests/components/command_line/test_cover.py similarity index 97% rename from tests/components/cover/test_command_line.py rename to tests/components/command_line/test_cover.py index 0e03539d58c..b583e1a83a6 100644 --- a/tests/components/cover/test_command_line.py +++ b/tests/components/command_line/test_cover.py @@ -6,7 +6,7 @@ from unittest import mock import pytest from homeassistant.components.cover import DOMAIN -import homeassistant.components.cover.command_line as cmd_rs +import homeassistant.components.command_line.cover as cmd_rs from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER, SERVICE_STOP_COVER) diff --git a/tests/components/notify/test_command_line.py b/tests/components/command_line/test_notify.py similarity index 97% rename from tests/components/notify/test_command_line.py rename to tests/components/command_line/test_notify.py index 66aa451d389..522519f2cce 100644 --- a/tests/components/notify/test_command_line.py +++ b/tests/components/command_line/test_notify.py @@ -65,7 +65,7 @@ class TestCommandLine(unittest.TestCase): # the echo command adds a line break assert fil.read() == "{}\n".format(message) - @patch('homeassistant.components.notify.command_line._LOGGER.error') + @patch('homeassistant.components.command_line.notify._LOGGER.error') def test_error_for_none_zero_exit_code(self, mock_error): """Test if an error is logged for non zero exit codes.""" with assert_setup_component(1) as handle_config: diff --git a/tests/components/sensor/test_command_line.py b/tests/components/command_line/test_sensor.py similarity index 96% rename from tests/components/sensor/test_command_line.py rename to tests/components/command_line/test_sensor.py index cacd7a4156d..40bb44a68cc 100644 --- a/tests/components/sensor/test_command_line.py +++ b/tests/components/command_line/test_sensor.py @@ -3,7 +3,7 @@ import unittest from unittest.mock import patch from homeassistant.helpers.template import Template -from homeassistant.components.sensor import command_line +from homeassistant.components.command_line import sensor as command_line from tests.common import get_test_home_assistant @@ -95,7 +95,7 @@ class TestCommandSensorSensor(unittest.TestCase): assert 'value_three' == \ self.sensor.device_state_attributes['key_three'] - @patch('homeassistant.components.sensor.command_line._LOGGER') + @patch('homeassistant.components.command_line.sensor._LOGGER') def test_update_with_json_attrs_no_data(self, mock_logger): """Test attributes when no JSON result fetched.""" data = command_line.CommandSensorData( @@ -108,7 +108,7 @@ class TestCommandSensorSensor(unittest.TestCase): assert {} == self.sensor.device_state_attributes assert mock_logger.warning.called - @patch('homeassistant.components.sensor.command_line._LOGGER') + @patch('homeassistant.components.command_line.sensor._LOGGER') def test_update_with_json_attrs_not_dict(self, mock_logger): """Test attributes get extracted from a JSON result.""" data = command_line.CommandSensorData( @@ -121,7 +121,7 @@ class TestCommandSensorSensor(unittest.TestCase): assert {} == self.sensor.device_state_attributes assert mock_logger.warning.called - @patch('homeassistant.components.sensor.command_line._LOGGER') + @patch('homeassistant.components.command_line.sensor._LOGGER') def test_update_with_json_attrs_bad_JSON(self, mock_logger): """Test attributes get extracted from a JSON result.""" data = command_line.CommandSensorData( diff --git a/tests/components/switch/test_command_line.py b/tests/components/command_line/test_switch.py similarity index 99% rename from tests/components/switch/test_command_line.py rename to tests/components/command_line/test_switch.py index 06618e248ce..0cc9f27d07c 100644 --- a/tests/components/switch/test_command_line.py +++ b/tests/components/command_line/test_switch.py @@ -7,7 +7,7 @@ import unittest from homeassistant.setup import setup_component from homeassistant.const import STATE_ON, STATE_OFF import homeassistant.components.switch as switch -import homeassistant.components.switch.command_line as command_line +import homeassistant.components.command_line.switch as command_line from tests.common import get_test_home_assistant from tests.components.switch import common diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 852a5adf6a2..6d2304433ab 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -31,10 +31,30 @@ def client(hass, hass_client): yield hass.loop.run_until_complete(hass_client()) +@HANDLERS.register('comp1') +class Comp1ConfigFlow: + """Config flow with options flow.""" + + @staticmethod + @callback + def async_get_options_flow(config, options): + """Get options flow.""" + pass + + +@HANDLERS.register('comp2') +class Comp2ConfigFlow: + """Config flow without options flow.""" + + def __init__(self): + """Init.""" + pass + + async def test_get_entries(hass, client): """Test get entries.""" MockConfigEntry( - domain='comp', + domain='comp1', title='Test 1', source='bla', connection_class=core_ce.CONN_CLASS_LOCAL_POLL, @@ -47,18 +67,6 @@ async def test_get_entries(hass, client): connection_class=core_ce.CONN_CLASS_ASSUMED, ).add_to_hass(hass) - class CompConfigFlow: - @staticmethod - @callback - def async_get_options_flow(config, options): - pass - HANDLERS['comp'] = CompConfigFlow() - - class Comp2ConfigFlow: - def __init__(self): - pass - HANDLERS['comp2'] = Comp2ConfigFlow() - resp = await client.get('/api/config/config_entries/entry') assert resp.status == 200 data = await resp.json() @@ -66,7 +74,7 @@ async def test_get_entries(hass, client): entry.pop('entry_id') assert data == [ { - 'domain': 'comp', + 'domain': 'comp1', 'title': 'Test 1', 'source': 'bla', 'state': 'not_loaded', diff --git a/tests/components/config/test_init.py b/tests/components/config/test_init.py index 57ea7e7a492..41a0fb089b5 100644 --- a/tests/components/config/test_init.py +++ b/tests/components/config/test_init.py @@ -29,7 +29,6 @@ def test_load_on_demand_already_loaded(hass, aiohttp_client): yield from async_setup_component(hass, 'config', {}) yield from hass.async_block_till_done() - assert 'config.zwave' in hass.config.components assert stp.called @@ -47,5 +46,4 @@ def test_load_on_demand_on_load(hass, aiohttp_client): hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: 'zwave'}) yield from hass.async_block_till_done() - assert 'config.zwave' in hass.config.components assert stp.called diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index 2aa1f499a76..812456a3594 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -5,7 +5,6 @@ import pytest from homeassistant.core import DOMAIN as HASS_DOMAIN from homeassistant.setup import async_setup_component from homeassistant.components import conversation -import homeassistant.components as component from homeassistant.components.cover import (SERVICE_OPEN_COVER) from homeassistant.helpers import intent @@ -16,7 +15,7 @@ async def test_calling_intent(hass): """Test calling an intent from a conversation.""" intents = async_mock_intent(hass, 'OrderBeer') - result = await component.async_setup(hass, {}) + result = await async_setup_component(hass, 'homeassistant', {}) assert result result = await async_setup_component(hass, 'conversation', { @@ -146,7 +145,7 @@ async def test_http_processing_intent(hass, hass_client): @pytest.mark.parametrize('sentence', ('turn on kitchen', 'turn kitchen on')) async def test_turn_on_intent(hass, sentence): """Test calling the turn on intent.""" - result = await component.async_setup(hass, {}) + result = await async_setup_component(hass, 'homeassistant', {}) assert result result = await async_setup_component(hass, 'conversation', {}) @@ -197,7 +196,7 @@ async def test_cover_intents_loading(hass): @pytest.mark.parametrize('sentence', ('turn off kitchen', 'turn kitchen off')) async def test_turn_off_intent(hass, sentence): """Test calling the turn on intent.""" - result = await component.async_setup(hass, {}) + result = await async_setup_component(hass, 'homeassistant', {}) assert result result = await async_setup_component(hass, 'conversation', {}) @@ -222,7 +221,7 @@ async def test_turn_off_intent(hass, sentence): @pytest.mark.parametrize('sentence', ('toggle kitchen', 'kitchen toggle')) async def test_toggle_intent(hass, sentence): """Test calling the turn on intent.""" - result = await component.async_setup(hass, {}) + result = await async_setup_component(hass, 'homeassistant', {}) assert result result = await async_setup_component(hass, 'conversation', {}) @@ -246,7 +245,7 @@ async def test_toggle_intent(hass, sentence): async def test_http_api(hass, hass_client): """Test the HTTP conversation API.""" - result = await component.async_setup(hass, {}) + result = await async_setup_component(hass, 'homeassistant', {}) assert result result = await async_setup_component(hass, 'conversation', {}) @@ -270,7 +269,7 @@ async def test_http_api(hass, hass_client): async def test_http_api_wrong_data(hass, hass_client): """Test the HTTP conversation API.""" - result = await component.async_setup(hass, {}) + result = await async_setup_component(hass, 'homeassistant', {}) assert result result = await async_setup_component(hass, 'conversation', {}) diff --git a/tests/components/cover/test_init.py b/tests/components/cover/test_init.py index 5df492d3d47..09cb5752b55 100644 --- a/tests/components/cover/test_init.py +++ b/tests/components/cover/test_init.py @@ -2,7 +2,7 @@ from homeassistant.components.cover import (SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER) -from homeassistant.components import intent +from homeassistant.helpers import intent import homeassistant.components as comps from tests.common import async_mock_service diff --git a/tests/components/daikin/test_config_flow.py b/tests/components/daikin/test_config_flow.py index f4ad676b9aa..f6b2f0b1d41 100644 --- a/tests/components/daikin/test_config_flow.py +++ b/tests/components/daikin/test_config_flow.py @@ -6,7 +6,8 @@ import pytest from homeassistant import data_entry_flow from homeassistant.components.daikin import config_flow -from homeassistant.components.daikin.const import KEY_HOST, KEY_IP, KEY_MAC +from homeassistant.components.daikin.const import KEY_IP, KEY_MAC +from homeassistant.const import CONF_HOST from tests.common import MockConfigEntry, MockDependency @@ -37,10 +38,10 @@ async def test_user(hass, mock_daikin): assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['step_id'] == 'user' - result = await flow.async_step_user({KEY_HOST: HOST}) + result = await flow.async_step_user({CONF_HOST: HOST}) assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result['title'] == HOST - assert result['data'][KEY_HOST] == HOST + assert result['data'][CONF_HOST] == HOST assert result['data'][KEY_MAC] == MAC @@ -49,7 +50,7 @@ async def test_abort_if_already_setup(hass, mock_daikin): flow = init_config_flow(hass) MockConfigEntry(domain='daikin', data={KEY_MAC: MAC}).add_to_hass(hass) - result = await flow.async_step_user({KEY_HOST: HOST}) + result = await flow.async_step_user({CONF_HOST: HOST}) assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT assert result['reason'] == 'already_configured' @@ -62,10 +63,10 @@ async def test_import(hass, mock_daikin): assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['step_id'] == 'user' - result = await flow.async_step_import({KEY_HOST: HOST}) + result = await flow.async_step_import({CONF_HOST: HOST}) assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result['title'] == HOST - assert result['data'][KEY_HOST] == HOST + assert result['data'][CONF_HOST] == HOST assert result['data'][KEY_MAC] == MAC @@ -76,7 +77,7 @@ async def test_discovery(hass, mock_daikin): result = await flow.async_step_discovery({KEY_IP: HOST, KEY_MAC: MAC}) assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result['title'] == HOST - assert result['data'][KEY_HOST] == HOST + assert result['data'][CONF_HOST] == HOST assert result['data'][KEY_MAC] == MAC @@ -88,6 +89,6 @@ async def test_device_abort(hass, mock_daikin, s_effect, reason): flow = init_config_flow(hass) mock_daikin.Appliance.side_effect = s_effect - result = await flow.async_step_user({KEY_HOST: HOST}) + result = await flow.async_step_user({CONF_HOST: HOST}) assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT assert result['reason'] == reason diff --git a/tests/components/darksky/__init__.py b/tests/components/darksky/__init__.py new file mode 100644 index 00000000000..b58d250e975 --- /dev/null +++ b/tests/components/darksky/__init__.py @@ -0,0 +1 @@ +"""Tests for the darksky component.""" diff --git a/tests/components/sensor/test_darksky.py b/tests/components/darksky/test_sensor.py similarity index 95% rename from tests/components/sensor/test_darksky.py rename to tests/components/darksky/test_sensor.py index 58ce932020a..ffb90d8474b 100644 --- a/tests/components/sensor/test_darksky.py +++ b/tests/components/darksky/test_sensor.py @@ -9,7 +9,7 @@ import requests_mock import forecastio -from homeassistant.components.sensor import darksky +from homeassistant.components.darksky import sensor as darksky from homeassistant.setup import setup_component from tests.common import (load_fixture, get_test_home_assistant, @@ -20,6 +20,7 @@ VALID_CONFIG_MINIMAL = { 'platform': 'darksky', 'api_key': 'foo', 'forecast': [1, 2], + 'hourly_forecast': [1, 2], 'monitored_conditions': ['summary', 'icon', 'temperature_high'], 'scan_interval': timedelta(seconds=120), } @@ -30,6 +31,7 @@ INVALID_CONFIG_MINIMAL = { 'platform': 'darksky', 'api_key': 'foo', 'forecast': [1, 2], + 'hourly_forecast': [1, 2], 'monitored_conditions': ['sumary', 'iocn', 'temperature_high'], 'scan_interval': timedelta(seconds=120), } @@ -40,6 +42,7 @@ VALID_CONFIG_LANG_DE = { 'platform': 'darksky', 'api_key': 'foo', 'forecast': [1, 2], + 'hourly_forecast': [1, 2], 'units': 'us', 'language': 'de', 'monitored_conditions': ['summary', 'icon', 'temperature_high', @@ -54,6 +57,7 @@ INVALID_CONFIG_LANG = { 'platform': 'darksky', 'api_key': 'foo', 'forecast': [1, 2], + 'hourly_forecast': [1, 2], 'language': 'yz', 'monitored_conditions': ['summary', 'icon', 'temperature_high'], 'scan_interval': timedelta(seconds=120), @@ -157,7 +161,7 @@ class TestDarkSkySetup(unittest.TestCase): assert mock_get_forecast.called assert mock_get_forecast.call_count == 1 - assert len(self.hass.states.entity_ids()) == 8 + assert len(self.hass.states.entity_ids()) == 12 state = self.hass.states.get('sensor.dark_sky_summary') assert state is not None diff --git a/tests/components/weather/test_darksky.py b/tests/components/darksky/test_weather.py similarity index 100% rename from tests/components/weather/test_darksky.py rename to tests/components/darksky/test_weather.py diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index fa274f1d676..953bb776419 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -46,11 +46,14 @@ async def setup_gateway(hass, data, allow_clip_sensor=True): """Load the deCONZ sensor platform.""" from pydeconz import DeconzSession - session = Mock(put=asynctest.CoroutineMock( - return_value=Mock(status=200, - json=asynctest.CoroutineMock(), - text=asynctest.CoroutineMock(), - ) + response = Mock( + status=200, json=asynctest.CoroutineMock(), + text=asynctest.CoroutineMock()) + response.content_type = 'application/json' + + session = Mock( + put=asynctest.CoroutineMock( + return_value=response ) ) diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index 9e1d6a2fca1..863e4e93fc5 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -1,7 +1,8 @@ """Tests for deCONZ config flow.""" -import pytest +from unittest.mock import patch + +import asyncio -import voluptuous as vol from homeassistant.components.deconz import config_flow from tests.common import MockConfigEntry @@ -12,15 +13,17 @@ async def test_flow_works(hass, aioclient_mock): """Test that config flow works.""" aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[ {'id': 'id', 'internalipaddress': '1.2.3.4', 'internalport': 80} - ]) + ], headers={'content-type': 'application/json'}) aioclient_mock.post('http://1.2.3.4:80/api', json=[ {"success": {"username": "1234567890ABCDEF"}} - ]) + ], headers={'content-type': 'application/json'}) flow = config_flow.DeconzFlowHandler() flow.hass = hass + await flow.async_step_user() await flow.async_step_link(user_input={}) + result = await flow.async_step_options( user_input={'allow_clip_sensor': True, 'allow_deconz_groups': True}) @@ -41,35 +44,53 @@ async def test_flow_already_registered_bridge(hass): MockConfigEntry(domain='deconz', data={ 'host': '1.2.3.4' }).add_to_hass(hass) + flow = config_flow.DeconzFlowHandler() flow.hass = hass - result = await flow.async_step_init() + result = await flow.async_step_user() assert result['type'] == 'abort' +async def test_flow_bridge_discovery_fails(hass, aioclient_mock): + """Test config flow works when discovery fails.""" + flow = config_flow.DeconzFlowHandler() + flow.hass = hass + + with patch('pydeconz.utils.async_discovery', + side_effect=asyncio.TimeoutError): + result = await flow.async_step_user() + + assert result['type'] == 'form' + assert result['step_id'] == 'init' + + async def test_flow_no_discovered_bridges(hass, aioclient_mock): """Test config flow discovers no bridges.""" - aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[]) + aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[], + headers={'content-type': 'application/json'}) + flow = config_flow.DeconzFlowHandler() flow.hass = hass - result = await flow.async_step_init() + result = await flow.async_step_user() assert result['type'] == 'form' - assert result['step_id'] == 'user' + assert result['step_id'] == 'init' async def test_flow_one_bridge_discovered(hass, aioclient_mock): """Test config flow discovers one bridge.""" aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[ {'id': 'id', 'internalipaddress': '1.2.3.4', 'internalport': 80} - ]) + ], headers={'content-type': 'application/json'}) + flow = config_flow.DeconzFlowHandler() flow.hass = hass - result = await flow.async_step_init() + result = await flow.async_step_user() assert result['type'] == 'form' assert result['step_id'] == 'link' + assert flow.deconz_config['host'] == '1.2.3.4' async def test_flow_two_bridges_discovered(hass, aioclient_mock): @@ -77,19 +98,14 @@ async def test_flow_two_bridges_discovered(hass, aioclient_mock): aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[ {'id': 'id1', 'internalipaddress': '1.2.3.4', 'internalport': 80}, {'id': 'id2', 'internalipaddress': '5.6.7.8', 'internalport': 80} - ]) + ], headers={'content-type': 'application/json'}) + flow = config_flow.DeconzFlowHandler() flow.hass = hass - result = await flow.async_step_init() - assert result['type'] == 'form' - assert result['step_id'] == 'init' - - with pytest.raises(vol.Invalid): - assert result['data_schema']({'host': '0.0.0.0'}) - - result['data_schema']({'host': '1.2.3.4'}) - result['data_schema']({'host': '5.6.7.8'}) + result = await flow.async_step_user() + assert result['data_schema']({'host': '1.2.3.4'}) + assert result['data_schema']({'host': '5.6.7.8'}) async def test_flow_two_bridges_selection(hass, aioclient_mock): @@ -101,7 +117,7 @@ async def test_flow_two_bridges_selection(hass, aioclient_mock): {'bridgeid': 'id2', 'host': '5.6.7.8', 'port': 80} ] - result = await flow.async_step_init(user_input={'host': '1.2.3.4'}) + result = await flow.async_step_user(user_input={'host': '1.2.3.4'}) assert result['type'] == 'form' assert result['step_id'] == 'link' assert flow.deconz_config['host'] == '1.2.3.4' @@ -110,25 +126,28 @@ async def test_flow_two_bridges_selection(hass, aioclient_mock): async def test_flow_manual_configuration(hass, aioclient_mock): """Test config flow with manual input.""" aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[]) + flow = config_flow.DeconzFlowHandler() flow.hass = hass user_input = {'host': '1.2.3.4', 'port': 80} - result = await flow.async_step_init(user_input) + result = await flow.async_step_user(user_input) assert result['type'] == 'form' assert result['step_id'] == 'link' assert flow.deconz_config == user_input -async def test_link_no_api_key(hass, aioclient_mock): +async def test_link_no_api_key(hass): """Test config flow should abort if no API key was possible to retrieve.""" - aioclient_mock.post('http://1.2.3.4:80/api', json=[]) flow = config_flow.DeconzFlowHandler() flow.hass = hass flow.deconz_config = {'host': '1.2.3.4', 'port': 80} - result = await flow.async_step_link(user_input={}) + with patch('pydeconz.utils.async_get_api_key', + side_effect=pydeconz.errors.ResponseError): + result = await flow.async_step_link(user_input={}) + assert result['type'] == 'form' assert result['step_id'] == 'link' assert result['errors'] == {'base': 'no_key'} @@ -143,6 +162,7 @@ async def test_link_already_registered_bridge(hass): MockConfigEntry(domain='deconz', data={ 'host': '1.2.3.4' }).add_to_hass(hass) + flow = config_flow.DeconzFlowHandler() flow.hass = hass flow.deconz_config = {'host': '1.2.3.4', 'port': 80} @@ -155,6 +175,7 @@ async def test_bridge_discovery(hass): """Test a bridge being discovered.""" flow = config_flow.DeconzFlowHandler() flow.hass = hass + result = await flow.async_step_discovery({ 'host': '1.2.3.4', 'port': 80, @@ -222,14 +243,18 @@ async def test_import_with_api_key(hass): async def test_options(hass, aioclient_mock): """Test that options work and that bridgeid can be requested.""" aioclient_mock.get('http://1.2.3.4:80/api/1234567890ABCDEF/config', - json={"bridgeid": "id"}) + json={"bridgeid": "id"}, + headers={'content-type': 'application/json'}) + flow = config_flow.DeconzFlowHandler() flow.hass = hass flow.deconz_config = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} + result = await flow.async_step_options( user_input={'allow_clip_sensor': False, 'allow_deconz_groups': False}) + assert result['type'] == 'create_entry' assert result['title'] == 'deCONZ-id' assert result['data'] == { @@ -240,3 +265,52 @@ async def test_options(hass, aioclient_mock): 'allow_clip_sensor': False, 'allow_deconz_groups': False } + + +async def test_hassio_single_instance(hass): + """Test we only allow a single config flow.""" + MockConfigEntry(domain='deconz', data={ + 'host': '1.2.3.4' + }).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + 'deconz', context={'source': 'hassio'}) + assert result['type'] == 'abort' + assert result['reason'] == 'one_instance_only' + + +async def test_hassio_confirm(hass): + """Test we can finish a config flow.""" + result = await hass.config_entries.flow.async_init( + 'deconz', + data={ + 'addon': 'Mock Addon', + 'host': 'mock-deconz', + 'port': 8080, + 'serial': 'aa:bb', + 'api_key': '1234567890ABCDEF', + }, + context={'source': 'hassio'} + ) + assert result['type'] == 'form' + assert result['step_id'] == 'hassio_confirm' + assert result['description_placeholders'] == { + 'addon': 'Mock Addon', + } + + result = await hass.config_entries.flow.async_configure( + result['flow_id'], { + 'allow_clip_sensor': True, + 'allow_deconz_groups': True, + } + ) + + assert result['type'] == 'create_entry' + assert result['result'].data == { + 'host': 'mock-deconz', + 'port': 8080, + 'bridgeid': 'aa:bb', + 'api_key': '1234567890ABCDEF', + 'allow_clip_sensor': True, + 'allow_deconz_groups': True, + } diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index d73f225b2ac..6006ff66898 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -4,10 +4,13 @@ from unittest.mock import Mock, patch import pytest from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.components.deconz import gateway +from homeassistant.components.deconz import errors, gateway from tests.common import mock_coro +import pydeconz + + ENTRY_CONFIG = { "host": "1.2.3.4", "port": 80, @@ -62,11 +65,25 @@ async def test_gateway_retry(): deconz_gateway = gateway.DeconzGateway(hass, entry) with patch.object( - gateway, 'get_gateway', return_value=mock_coro(False) - ), pytest.raises(ConfigEntryNotReady): + gateway, 'get_gateway', side_effect=errors.CannotConnect), \ + pytest.raises(ConfigEntryNotReady): await deconz_gateway.async_setup() +async def test_gateway_setup_fails(): + """Retry setup.""" + hass = Mock() + entry = Mock() + entry.data = ENTRY_CONFIG + + deconz_gateway = gateway.DeconzGateway(hass, entry) + + with patch.object(gateway, 'get_gateway', side_effect=Exception): + result = await deconz_gateway.async_setup() + + assert not result + + async def test_connection_status(hass): """Make sure that connection status triggers a dispatcher send.""" entry = Mock() @@ -170,10 +187,20 @@ async def test_get_gateway(hass): assert await gateway.get_gateway(hass, ENTRY_CONFIG, Mock(), Mock()) -async def test_get_gateway_fails(hass): +async def test_get_gateway_fails_unauthorized(hass): """Failed call.""" with patch('pydeconz.DeconzSession.async_load_parameters', - return_value=mock_coro(False)): + side_effect=pydeconz.errors.Unauthorized), \ + pytest.raises(errors.AuthenticationRequired): + assert await gateway.get_gateway( + hass, ENTRY_CONFIG, Mock(), Mock()) is False + + +async def test_get_gateway_fails_cannot_connect(hass): + """Failed call.""" + with patch('pydeconz.DeconzSession.async_load_parameters', + side_effect=pydeconz.errors.RequestError), \ + pytest.raises(errors.CannotConnect): assert await gateway.get_gateway( hass, ENTRY_CONFIG, Mock(), Mock()) is False diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index cbba47eb431..e0afadccc81 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -1,6 +1,7 @@ """Test deCONZ component setup process.""" from unittest.mock import Mock, patch +import asyncio import pytest import voluptuous as vol @@ -76,13 +77,22 @@ async def test_setup_entry_already_registered_bridge(hass): assert await deconz.async_setup_entry(hass, {}) is False +async def test_setup_entry_fails(hass): + """Test setup entry fails if deCONZ is not available.""" + entry = Mock() + entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} + with patch('pydeconz.DeconzSession.async_load_parameters', + side_effect=Exception): + await deconz.async_setup_entry(hass, entry) + + async def test_setup_entry_no_available_bridge(hass): """Test setup entry fails if deCONZ is not available.""" entry = Mock() entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} with patch( 'pydeconz.DeconzSession.async_load_parameters', - return_value=mock_coro(False) + side_effect=asyncio.TimeoutError ), pytest.raises(ConfigEntryNotReady): await deconz.async_setup_entry(hass, entry) @@ -185,6 +195,7 @@ async def test_service_refresh_devices(hass): }) entry.add_to_hass(hass) mock_registry = Mock() + with patch.object(deconz, 'DeconzGateway') as mock_gateway, \ patch('homeassistant.helpers.device_registry.async_get_registry', return_value=mock_coro(mock_registry)): @@ -196,6 +207,7 @@ async def test_service_refresh_devices(hass): await hass.services.async_call( 'deconz', 'device_refresh', service_data={}) await hass.async_block_till_done() + with patch.object(hass.data[deconz.DOMAIN].api, 'async_load_parameters', return_value=mock_coro(False)): await hass.services.async_call( diff --git a/tests/components/calendar/test_demo.py b/tests/components/demo/test_calendar.py similarity index 100% rename from tests/components/calendar/test_demo.py rename to tests/components/demo/test_calendar.py diff --git a/tests/components/camera/test_demo.py b/tests/components/demo/test_camera.py similarity index 97% rename from tests/components/camera/test_demo.py rename to tests/components/demo/test_camera.py index f6e2513380c..6329a36543f 100644 --- a/tests/components/camera/test_demo.py +++ b/tests/components/demo/test_camera.py @@ -27,7 +27,7 @@ async def test_init_state_is_streaming(hass, demo_camera): assert demo_camera.state == STATE_STREAMING mock_on_img = mock_open(read_data=b'ON') - with patch('homeassistant.components.camera.demo.open', mock_on_img, + with patch('homeassistant.components.demo.camera.open', mock_on_img, create=True): image = await camera.async_get_image(hass, demo_camera.entity_id) assert mock_on_img.called diff --git a/tests/components/climate/test_demo.py b/tests/components/demo/test_climate.py similarity index 100% rename from tests/components/climate/test_demo.py rename to tests/components/demo/test_climate.py diff --git a/tests/components/cover/test_demo.py b/tests/components/demo/test_cover.py similarity index 100% rename from tests/components/cover/test_demo.py rename to tests/components/demo/test_cover.py diff --git a/tests/components/fan/test_demo.py b/tests/components/demo/test_fan.py similarity index 100% rename from tests/components/fan/test_demo.py rename to tests/components/demo/test_fan.py diff --git a/tests/components/geo_location/test_demo.py b/tests/components/demo/test_geo_location.py similarity index 97% rename from tests/components/geo_location/test_demo.py rename to tests/components/demo/test_geo_location.py index d1c11fe55bf..5a46ca99839 100644 --- a/tests/components/geo_location/test_demo.py +++ b/tests/components/demo/test_geo_location.py @@ -3,7 +3,7 @@ import unittest from unittest.mock import patch from homeassistant.components import geo_location -from homeassistant.components.geo_location.demo import \ +from homeassistant.components.demo.geo_location import \ NUMBER_OF_DEMO_DEVICES, DEFAULT_UNIT_OF_MEASUREMENT, \ DEFAULT_UPDATE_INTERVAL from homeassistant.setup import setup_component diff --git a/tests/components/light/test_demo.py b/tests/components/demo/test_light.py similarity index 100% rename from tests/components/light/test_demo.py rename to tests/components/demo/test_light.py diff --git a/tests/components/lock/test_demo.py b/tests/components/demo/test_lock.py similarity index 100% rename from tests/components/lock/test_demo.py rename to tests/components/demo/test_lock.py diff --git a/tests/components/media_player/test_demo.py b/tests/components/demo/test_media_player.py similarity index 99% rename from tests/components/media_player/test_demo.py rename to tests/components/demo/test_media_player.py index 8cbe0a594d2..83acf8be601 100644 --- a/tests/components/media_player/test_demo.py +++ b/tests/components/demo/test_media_player.py @@ -184,7 +184,7 @@ class TestDemoMediaPlayer(unittest.TestCase): state = self.hass.states.get(ent_id) assert 1 == state.attributes.get('media_episode') - @patch('homeassistant.components.media_player.demo.DemoYoutubePlayer.' + @patch('homeassistant.components.demo.media_player.DemoYoutubePlayer.' 'media_seek', autospec=True) def test_play_media(self, mock_seek): """Test play_media .""" diff --git a/tests/components/notify/test_demo.py b/tests/components/demo/test_notify.py similarity index 97% rename from tests/components/notify/test_demo.py rename to tests/components/demo/test_notify.py index 4c3f3bf3f73..35cf8abe6bd 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/demo/test_notify.py @@ -7,7 +7,7 @@ import voluptuous as vol import homeassistant.components.notify as notify from homeassistant.setup import setup_component -from homeassistant.components.notify import demo +import homeassistant.components.demo.notify as demo from homeassistant.core import callback from homeassistant.helpers import discovery, script @@ -50,7 +50,7 @@ class TestNotifyDemo(unittest.TestCase): """Test setup.""" self._setup_notify() - @patch('homeassistant.components.notify.demo.get_service', autospec=True) + @patch('homeassistant.components.demo.notify.get_service', autospec=True) def test_no_notify_service(self, mock_demo_get_service): """Test missing platform notify service instance.""" mock_demo_get_service.return_value = None @@ -63,7 +63,7 @@ class TestNotifyDemo(unittest.TestCase): ['ERROR:homeassistant.components.notify:' 'Failed to initialize notification service demo'] - @patch('homeassistant.components.notify.demo.get_service', autospec=True) + @patch('homeassistant.components.demo.notify.get_service', autospec=True) def test_discover_notify(self, mock_demo_get_service): """Test discovery of notify demo platform.""" assert notify.DOMAIN not in self.hass.config.components diff --git a/tests/components/vacuum/test_demo.py b/tests/components/demo/test_vacuum.py similarity index 99% rename from tests/components/vacuum/test_demo.py rename to tests/components/demo/test_vacuum.py index e0b560bbb48..523fe17f824 100644 --- a/tests/components/vacuum/test_demo.py +++ b/tests/components/demo/test_vacuum.py @@ -9,7 +9,7 @@ from homeassistant.components.vacuum import ( SERVICE_SEND_COMMAND, SERVICE_SET_FAN_SPEED, STATE_DOCKED, STATE_CLEANING, STATE_PAUSED, STATE_IDLE, STATE_RETURNING) -from homeassistant.components.vacuum.demo import ( +from homeassistant.components.demo.vacuum import ( DEMO_VACUUM_BASIC, DEMO_VACUUM_COMPLETE, DEMO_VACUUM_MINIMAL, DEMO_VACUUM_MOST, DEMO_VACUUM_NONE, DEMO_VACUUM_STATE, FAN_SPEEDS) from homeassistant.const import ( diff --git a/tests/components/water_heater/test_demo.py b/tests/components/demo/test_water_heater.py similarity index 100% rename from tests/components/water_heater/test_demo.py rename to tests/components/demo/test_water_heater.py diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index e5fbced7293..63f1c60327a 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -175,7 +175,7 @@ async def test_gravatar_and_picture(hass): @patch( 'homeassistant.components.device_tracker.DeviceTracker.see') @patch( - 'homeassistant.components.device_tracker.demo.setup_scanner', + 'homeassistant.components.demo.device_tracker.setup_scanner', autospec=True) async def test_discover_platform(mock_demo_setup_scanner, mock_see, hass): """Test discovery of device_tracker demo platform.""" @@ -626,7 +626,7 @@ def test_see_schema_allowing_ios_calls(): device_tracker.SERVICE_SEE_PAYLOAD_SCHEMA({ 'dev_id': 'Test', "battery": 35, - "battery_status": 'Unplugged', + "battery_status": 'Not Charging', "gps": [10.0, 10.0], "gps_accuracy": 300, "hostname": 'beer', diff --git a/tests/components/directv/__init__.py b/tests/components/directv/__init__.py new file mode 100644 index 00000000000..9a32215e53d --- /dev/null +++ b/tests/components/directv/__init__.py @@ -0,0 +1 @@ +"""Tests for the directv component.""" diff --git a/tests/components/media_player/test_directv.py b/tests/components/directv/test_media_player.py similarity index 99% rename from tests/components/media_player/test_directv.py rename to tests/components/directv/test_media_player.py index 5918946fe6c..6a7bd0ce06f 100644 --- a/tests/components/media_player/test_directv.py +++ b/tests/components/directv/test_media_player.py @@ -13,7 +13,7 @@ from homeassistant.components.media_player.const import ( SERVICE_PLAY_MEDIA, SUPPORT_PAUSE, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA, SUPPORT_STOP, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, SUPPORT_PLAY) -from homeassistant.components.media_player.directv import ( +from homeassistant.components.directv.media_player import ( ATTR_MEDIA_CURRENTLY_RECORDING, ATTR_MEDIA_RATING, ATTR_MEDIA_RECORDED, ATTR_MEDIA_START_TIME, DEFAULT_DEVICE, DEFAULT_PORT) from homeassistant.const import ( diff --git a/tests/components/dsmr/__init__.py b/tests/components/dsmr/__init__.py new file mode 100644 index 00000000000..cab1d0fe2e7 --- /dev/null +++ b/tests/components/dsmr/__init__.py @@ -0,0 +1 @@ +"""Tests for the dsmr component.""" diff --git a/tests/components/sensor/test_dsmr.py b/tests/components/dsmr/test_sensor.py similarity index 99% rename from tests/components/sensor/test_dsmr.py rename to tests/components/dsmr/test_sensor.py index c2ea61e5bb4..366d2163818 100644 --- a/tests/components/sensor/test_dsmr.py +++ b/tests/components/dsmr/test_sensor.py @@ -12,7 +12,7 @@ from unittest.mock import Mock import asynctest from homeassistant.bootstrap import async_setup_component -from homeassistant.components.sensor.dsmr import DerivativeDSMREntity +from homeassistant.components.dsmr.sensor import DerivativeDSMREntity import pytest from tests.common import assert_setup_component diff --git a/tests/components/dte_energy_bridge/__init__.py b/tests/components/dte_energy_bridge/__init__.py new file mode 100644 index 00000000000..615944bda88 --- /dev/null +++ b/tests/components/dte_energy_bridge/__init__.py @@ -0,0 +1 @@ +"""Tests for the dte_energy_bridge component.""" diff --git a/tests/components/sensor/test_dte_energy_bridge.py b/tests/components/dte_energy_bridge/test_sensor.py similarity index 100% rename from tests/components/sensor/test_dte_energy_bridge.py rename to tests/components/dte_energy_bridge/test_sensor.py diff --git a/tests/components/climate/test_dyson.py b/tests/components/dyson/test_climate.py similarity index 99% rename from tests/components/climate/test_dyson.py rename to tests/components/dyson/test_climate.py index e2cfffe6458..43ce6344ec4 100644 --- a/tests/components/climate/test_dyson.py +++ b/tests/components/dyson/test_climate.py @@ -6,7 +6,7 @@ from libpurecoollink.const import (FocusMode, HeatMode, HeatState, HeatTarget, TiltState) from libpurecoollink.dyson_pure_state import DysonPureHotCoolState from libpurecoollink.dyson_pure_hotcool_link import DysonPureHotCoolLink -from homeassistant.components.climate import dyson +from homeassistant.components.dyson import climate as dyson from homeassistant.components import dyson as dyson_parent from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE from homeassistant.setup import setup_component diff --git a/tests/components/fan/test_dyson.py b/tests/components/dyson/test_fan.py similarity index 98% rename from tests/components/fan/test_dyson.py rename to tests/components/dyson/test_fan.py index aacab700f05..a04116f10f2 100644 --- a/tests/components/fan/test_dyson.py +++ b/tests/components/dyson/test_fan.py @@ -4,8 +4,8 @@ from unittest import mock from homeassistant.setup import setup_component from homeassistant.components import dyson as dyson_parent -from homeassistant.components.dyson import DYSON_DEVICES -from homeassistant.components.fan import (dyson, ATTR_SPEED, ATTR_SPEED_LIST, +from homeassistant.components.dyson import DYSON_DEVICES, fan as dyson +from homeassistant.components.fan import (ATTR_SPEED, ATTR_SPEED_LIST, ATTR_OSCILLATING) from tests.common import get_test_home_assistant from libpurecoollink.const import FanSpeed, FanMode, NightMode, Oscillation diff --git a/tests/components/sensor/test_dyson.py b/tests/components/dyson/test_sensor.py similarity index 99% rename from tests/components/sensor/test_dyson.py rename to tests/components/dyson/test_sensor.py index d4609c2b2c5..3218038c7e3 100644 --- a/tests/components/sensor/test_dyson.py +++ b/tests/components/dyson/test_sensor.py @@ -4,7 +4,7 @@ from unittest import mock from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, \ STATE_OFF -from homeassistant.components.sensor import dyson +from homeassistant.components.dyson import sensor as dyson from tests.common import get_test_home_assistant from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink diff --git a/tests/components/vacuum/test_dyson.py b/tests/components/dyson/test_vacuum.py similarity index 98% rename from tests/components/vacuum/test_dyson.py rename to tests/components/dyson/test_vacuum.py index 0bdaa619c7b..05ad8cf0db7 100644 --- a/tests/components/vacuum/test_dyson.py +++ b/tests/components/dyson/test_vacuum.py @@ -5,8 +5,8 @@ from unittest import mock from libpurecoollink.dyson_360_eye import Dyson360Eye from libpurecoollink.const import Dyson360EyeMode, PowerMode -from homeassistant.components.vacuum import dyson -from homeassistant.components.vacuum.dyson import Dyson360EyeDevice +from homeassistant.components.dyson import vacuum as dyson +from homeassistant.components.dyson.vacuum import Dyson360EyeDevice from tests.common import get_test_home_assistant diff --git a/tests/components/ecobee/test_climate.py b/tests/components/ecobee/test_climate.py index f4412b5d564..3215a9d5b4c 100644 --- a/tests/components/ecobee/test_climate.py +++ b/tests/components/ecobee/test_climate.py @@ -183,7 +183,8 @@ class TestEcobee(unittest.TestCase): 'fan': 'off', 'fan_min_on_time': 10, 'climate_mode': 'Climate1', - 'operation': 'heat'} == \ + 'operation': 'heat', + 'equipment_running': 'heatPump2'} == \ self.thermostat.device_state_attributes self.ecobee['equipmentStatus'] = 'auxHeat2' @@ -192,7 +193,8 @@ class TestEcobee(unittest.TestCase): 'fan': 'off', 'fan_min_on_time': 10, 'climate_mode': 'Climate1', - 'operation': 'heat'} == \ + 'operation': 'heat', + 'equipment_running': 'auxHeat2'} == \ self.thermostat.device_state_attributes self.ecobee['equipmentStatus'] = 'compCool1' assert {'actual_humidity': 15, @@ -200,7 +202,8 @@ class TestEcobee(unittest.TestCase): 'fan': 'off', 'fan_min_on_time': 10, 'climate_mode': 'Climate1', - 'operation': 'cool'} == \ + 'operation': 'cool', + 'equipment_running': 'compCool1'} == \ self.thermostat.device_state_attributes self.ecobee['equipmentStatus'] = '' assert {'actual_humidity': 15, @@ -208,7 +211,8 @@ class TestEcobee(unittest.TestCase): 'fan': 'off', 'fan_min_on_time': 10, 'climate_mode': 'Climate1', - 'operation': 'idle'} == \ + 'operation': 'idle', + 'equipment_running': ''} == \ self.thermostat.device_state_attributes self.ecobee['equipmentStatus'] = 'Unknown' @@ -217,7 +221,8 @@ class TestEcobee(unittest.TestCase): 'fan': 'off', 'fan_min_on_time': 10, 'climate_mode': 'Climate1', - 'operation': 'Unknown'} == \ + 'operation': 'Unknown', + 'equipment_running': 'Unknown'} == \ self.thermostat.device_state_attributes def test_is_away_mode_on(self): diff --git a/tests/components/ee_brightbox/__init__.py b/tests/components/ee_brightbox/__init__.py new file mode 100644 index 00000000000..03abf6af02a --- /dev/null +++ b/tests/components/ee_brightbox/__init__.py @@ -0,0 +1 @@ +"""Tests for the ee_brightbox component.""" diff --git a/tests/components/device_tracker/test_ee_brightbox.py b/tests/components/ee_brightbox/test_device_tracker.py similarity index 100% rename from tests/components/device_tracker/test_ee_brightbox.py rename to tests/components/ee_brightbox/test_device_tracker.py diff --git a/tests/components/efergy/__init__.py b/tests/components/efergy/__init__.py new file mode 100644 index 00000000000..242d36fb932 --- /dev/null +++ b/tests/components/efergy/__init__.py @@ -0,0 +1 @@ +"""Tests for the efergy component.""" diff --git a/tests/components/sensor/test_efergy.py b/tests/components/efergy/test_sensor.py similarity index 100% rename from tests/components/sensor/test_efergy.py rename to tests/components/efergy/test_sensor.py diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 8be99a02148..08001b0ebab 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -8,8 +8,7 @@ from aiohttp.hdrs import CONTENT_TYPE import pytest from tests.common import get_test_instance_port -from homeassistant import core, const, setup -import homeassistant.components as core_components +from homeassistant import const, setup from homeassistant.components import ( fan, http, light, script, emulated_hue, media_player, cover, climate) from homeassistant.components.emulated_hue import Config @@ -33,8 +32,8 @@ JSON_HEADERS = {CONTENT_TYPE: const.CONTENT_TYPE_JSON} def hass_hue(loop, hass): """Set up a Home Assistant instance for these tests.""" # We need to do this to get access to homeassistant/turn_(on,off) - loop.run_until_complete( - core_components.async_setup(hass, {core.DOMAIN: {}})) + loop.run_until_complete(setup.async_setup_component( + hass, 'homeassistant', {})) loop.run_until_complete(setup.async_setup_component( hass, http.DOMAIN, diff --git a/tests/components/everlights/__init__.py b/tests/components/everlights/__init__.py new file mode 100644 index 00000000000..3d1e3fbb91e --- /dev/null +++ b/tests/components/everlights/__init__.py @@ -0,0 +1 @@ +"""Tests for the everlights component.""" diff --git a/tests/components/light/test_everlights.py b/tests/components/everlights/test_light.py similarity index 89% rename from tests/components/light/test_everlights.py rename to tests/components/everlights/test_light.py index 026e7927c8d..26480f779a3 100644 --- a/tests/components/light/test_everlights.py +++ b/tests/components/everlights/test_light.py @@ -1,5 +1,5 @@ """The tests for the everlights component.""" -from homeassistant.components.light import everlights +from homeassistant.components.everlights import light as everlights def test_color_rgb_to_int(): diff --git a/tests/components/facebook/__init__.py b/tests/components/facebook/__init__.py new file mode 100644 index 00000000000..5f52b1f7614 --- /dev/null +++ b/tests/components/facebook/__init__.py @@ -0,0 +1 @@ +"""Tests for the facebook component.""" diff --git a/tests/components/notify/test_facebook.py b/tests/components/facebook/test_notify.py similarity index 97% rename from tests/components/notify/test_facebook.py rename to tests/components/facebook/test_notify.py index a74395d5a5e..ae4698c562f 100644 --- a/tests/components/notify/test_facebook.py +++ b/tests/components/facebook/test_notify.py @@ -2,7 +2,8 @@ import unittest import requests_mock -import homeassistant.components.notify.facebook as facebook +# import homeassistant.components.facebook as facebook +import homeassistant.components.facebook.notify as facebook class TestFacebook(unittest.TestCase): diff --git a/tests/components/facebox/__init__.py b/tests/components/facebox/__init__.py new file mode 100644 index 00000000000..fbbb6640e40 --- /dev/null +++ b/tests/components/facebox/__init__.py @@ -0,0 +1 @@ +"""Tests for the facebox component.""" diff --git a/tests/components/image_processing/test_facebox.py b/tests/components/facebox/test_image_processing.py similarity index 96% rename from tests/components/image_processing/test_facebox.py rename to tests/components/facebox/test_image_processing.py index 62e47a07c08..971b6830ae1 100644 --- a/tests/components/image_processing/test_facebox.py +++ b/tests/components/facebox/test_image_processing.py @@ -12,7 +12,7 @@ from homeassistant.const import ( HTTP_BAD_REQUEST, HTTP_OK, HTTP_UNAUTHORIZED, STATE_UNKNOWN) from homeassistant.setup import async_setup_component import homeassistant.components.image_processing as ip -import homeassistant.components.image_processing.facebox as fb +import homeassistant.components.facebox.image_processing as fb MOCK_IP = '192.168.0.1' MOCK_PORT = '8080' @@ -72,8 +72,8 @@ VALID_CONFIG = { @pytest.fixture def mock_healthybox(): """Mock fb.check_box_health.""" - check_box_health = 'homeassistant.components.image_processing.' \ - 'facebox.check_box_health' + check_box_health = 'homeassistant.components.facebox.image_processing.' \ + 'check_box_health' with patch(check_box_health, return_value=MOCK_BOX_ID) as _mock_healthybox: yield _mock_healthybox @@ -81,7 +81,7 @@ def mock_healthybox(): @pytest.fixture def mock_isfile(): """Mock os.path.isfile.""" - with patch('homeassistant.components.image_processing.facebox.cv.isfile', + with patch('homeassistant.components.facebox.image_processing.cv.isfile', return_value=True) as _mock_isfile: yield _mock_isfile @@ -89,7 +89,7 @@ def mock_isfile(): @pytest.fixture def mock_image(): """Return a mock camera image.""" - with patch('homeassistant.components.camera.demo.DemoCamera.camera_image', + with patch('homeassistant.components.demo.camera.DemoCamera.camera_image', return_value=b'Test') as image: yield image @@ -98,7 +98,7 @@ def mock_image(): def mock_open_file(): """Mock open.""" mopen = mock_open() - with patch('homeassistant.components.image_processing.facebox.open', + with patch('homeassistant.components.facebox.image_processing.open', mopen, create=True) as _mock_open: yield _mock_open diff --git a/tests/components/fail2ban/__init__.py b/tests/components/fail2ban/__init__.py new file mode 100644 index 00000000000..ed1aef1e833 --- /dev/null +++ b/tests/components/fail2ban/__init__.py @@ -0,0 +1 @@ +"""Tests for the fail2ban component.""" diff --git a/tests/components/sensor/test_fail2ban.py b/tests/components/fail2ban/test_sensor.py similarity index 92% rename from tests/components/sensor/test_fail2ban.py rename to tests/components/fail2ban/test_sensor.py index f9be2cbf985..1958dd683e2 100644 --- a/tests/components/sensor/test_fail2ban.py +++ b/tests/components/fail2ban/test_sensor.py @@ -5,7 +5,7 @@ from unittest.mock import Mock, patch from mock_open import MockOpen from homeassistant.setup import setup_component -from homeassistant.components.sensor.fail2ban import ( +from homeassistant.components.fail2ban.sensor import ( BanSensor, BanLogParser, STATE_CURRENT_BANS, STATE_ALL_BANS ) @@ -78,7 +78,7 @@ class TestBanSensor(unittest.TestCase): } } mock_fh = MockOpen() - with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, + with patch('homeassistant.components.fail2ban.sensor.open', mock_fh, create=True): assert setup_component(self.hass, 'sensor', config) self.hass.block_till_done() @@ -94,7 +94,7 @@ class TestBanSensor(unittest.TestCase): } } mock_fh = MockOpen() - with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, + with patch('homeassistant.components.fail2ban.sensor.open', mock_fh, create=True): assert setup_component(self.hass, 'sensor', config) self.hass.block_till_done() @@ -106,7 +106,7 @@ class TestBanSensor(unittest.TestCase): sensor = BanSensor('fail2ban', 'jail_one', log_parser) assert sensor.name == 'fail2ban jail_one' mock_fh = MockOpen(read_data=fake_log('single_ban')) - with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, + with patch('homeassistant.components.fail2ban.sensor.open', mock_fh, create=True): sensor.update() @@ -122,7 +122,7 @@ class TestBanSensor(unittest.TestCase): sensor = BanSensor('fail2ban', 'jail_one', log_parser) assert sensor.name == 'fail2ban jail_one' mock_fh = MockOpen(read_data=fake_log('ipv6_ban')) - with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, + with patch('homeassistant.components.fail2ban.sensor.open', mock_fh, create=True): sensor.update() @@ -139,7 +139,7 @@ class TestBanSensor(unittest.TestCase): sensor = BanSensor('fail2ban', 'jail_one', log_parser) assert sensor.name == 'fail2ban jail_one' mock_fh = MockOpen(read_data=fake_log('multi_ban')) - with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, + with patch('homeassistant.components.fail2ban.sensor.open', mock_fh, create=True): sensor.update() @@ -155,7 +155,7 @@ class TestBanSensor(unittest.TestCase): sensor = BanSensor('fail2ban', 'jail_one', log_parser) assert sensor.name == 'fail2ban jail_one' mock_fh = MockOpen(read_data=fake_log('unban_all')) - with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, + with patch('homeassistant.components.fail2ban.sensor.open', mock_fh, create=True): sensor.update() @@ -170,7 +170,7 @@ class TestBanSensor(unittest.TestCase): sensor = BanSensor('fail2ban', 'jail_one', log_parser) assert sensor.name == 'fail2ban jail_one' mock_fh = MockOpen(read_data=fake_log('unban_one')) - with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, + with patch('homeassistant.components.fail2ban.sensor.open', mock_fh, create=True): sensor.update() @@ -188,7 +188,7 @@ class TestBanSensor(unittest.TestCase): assert sensor1.name == 'fail2ban jail_one' assert sensor2.name == 'fail2ban jail_two' mock_fh = MockOpen(read_data=fake_log('multi_jail')) - with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, + with patch('homeassistant.components.fail2ban.sensor.open', mock_fh, create=True): sensor1.update() sensor2.update() @@ -208,7 +208,7 @@ class TestBanSensor(unittest.TestCase): sensor = BanSensor('fail2ban', 'jail_one', log_parser) assert sensor.name == 'fail2ban jail_one' mock_fh = MockOpen(read_data=fake_log('single_ban')) - with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, + with patch('homeassistant.components.fail2ban.sensor.open', mock_fh, create=True): sensor.update() assert sensor.state == '111.111.111.111' diff --git a/tests/components/binary_sensor/test_ffmpeg.py b/tests/components/ffmpeg/test_sensor.py similarity index 94% rename from tests/components/binary_sensor/test_ffmpeg.py rename to tests/components/ffmpeg/test_sensor.py index 2c17207af32..d1fd6124b4c 100644 --- a/tests/components/binary_sensor/test_ffmpeg.py +++ b/tests/components/ffmpeg/test_sensor.py @@ -33,7 +33,8 @@ class TestFFmpegNoiseSetup: assert self.hass.data['ffmpeg'].binary == 'ffmpeg' assert self.hass.states.get('binary_sensor.ffmpeg_noise') is not None - @patch('haffmpeg.SensorNoise.open_sensor', return_value=mock_coro()) + @patch('haffmpeg.sensor.SensorNoise.open_sensor', + return_value=mock_coro()) def test_setup_component_start(self, mock_start): """Set up ffmpeg component.""" with assert_setup_component(1, 'binary_sensor'): @@ -48,7 +49,7 @@ class TestFFmpegNoiseSetup: entity = self.hass.states.get('binary_sensor.ffmpeg_noise') assert entity.state == 'unavailable' - @patch('haffmpeg.SensorNoise') + @patch('haffmpeg.sensor.SensorNoise') def test_setup_component_start_callback(self, mock_ffmpeg): """Set up ffmpeg component.""" with assert_setup_component(1, 'binary_sensor'): @@ -95,7 +96,8 @@ class TestFFmpegMotionSetup: assert self.hass.data['ffmpeg'].binary == 'ffmpeg' assert self.hass.states.get('binary_sensor.ffmpeg_motion') is not None - @patch('haffmpeg.SensorMotion.open_sensor', return_value=mock_coro()) + @patch('haffmpeg.sensor.SensorMotion.open_sensor', + return_value=mock_coro()) def test_setup_component_start(self, mock_start): """Set up ffmpeg component.""" with assert_setup_component(1, 'binary_sensor'): @@ -110,7 +112,7 @@ class TestFFmpegMotionSetup: entity = self.hass.states.get('binary_sensor.ffmpeg_motion') assert entity.state == 'unavailable' - @patch('haffmpeg.SensorMotion') + @patch('haffmpeg.sensor.SensorMotion') def test_setup_component_start_callback(self, mock_ffmpeg): """Set up ffmpeg component.""" with assert_setup_component(1, 'binary_sensor'): diff --git a/tests/components/fido/__init__.py b/tests/components/fido/__init__.py new file mode 100644 index 00000000000..e54f0f39fb2 --- /dev/null +++ b/tests/components/fido/__init__.py @@ -0,0 +1 @@ +"""Tests for the fido component.""" diff --git a/tests/components/sensor/test_fido.py b/tests/components/fido/test_sensor.py similarity index 98% rename from tests/components/sensor/test_fido.py rename to tests/components/fido/test_sensor.py index c0bbadc043b..98a5fba4b46 100644 --- a/tests/components/sensor/test_fido.py +++ b/tests/components/fido/test_sensor.py @@ -5,7 +5,7 @@ import sys from unittest.mock import MagicMock from homeassistant.bootstrap import async_setup_component -from homeassistant.components.sensor import fido +from homeassistant.components.fido import sensor as fido from tests.common import assert_setup_component diff --git a/tests/components/file/__init__.py b/tests/components/file/__init__.py new file mode 100644 index 00000000000..60027c0bfa7 --- /dev/null +++ b/tests/components/file/__init__.py @@ -0,0 +1 @@ +"""Tests for the file component.""" diff --git a/tests/components/notify/test_file.py b/tests/components/file/test_notify.py similarity index 96% rename from tests/components/notify/test_file.py rename to tests/components/file/test_notify.py index e67ed532604..b6ed6e26aa1 100644 --- a/tests/components/notify/test_file.py +++ b/tests/components/file/test_notify.py @@ -52,9 +52,9 @@ class TestNotifyFile(unittest.TestCase): m_open = mock_open() with patch( - 'homeassistant.components.notify.file.open', + 'homeassistant.components.file.notify.open', m_open, create=True - ), patch('homeassistant.components.notify.file.os.stat') as mock_st, \ + ), patch('homeassistant.components.file.notify.os.stat') as mock_st, \ patch('homeassistant.util.dt.utcnow', return_value=dt_util.utcnow()): diff --git a/tests/components/sensor/test_file.py b/tests/components/file/test_sensor.py similarity index 94% rename from tests/components/sensor/test_file.py rename to tests/components/file/test_sensor.py index 3ac37b989c7..65def0b5443 100644 --- a/tests/components/sensor/test_file.py +++ b/tests/components/file/test_sensor.py @@ -39,7 +39,7 @@ class TestFileSensor(unittest.TestCase): } m_open = MockOpen(read_data='43\n45\n21') - with patch('homeassistant.components.sensor.file.open', m_open, + with patch('homeassistant.components.file.sensor.open', m_open, create=True): assert setup_component(self.hass, 'sensor', config) self.hass.block_till_done() @@ -64,7 +64,7 @@ class TestFileSensor(unittest.TestCase): '{"temperature": 26, "humidity": 36}' m_open = MockOpen(read_data=data) - with patch('homeassistant.components.sensor.file.open', m_open, + with patch('homeassistant.components.file.sensor.open', m_open, create=True): assert setup_component(self.hass, 'sensor', config) self.hass.block_till_done() @@ -85,7 +85,7 @@ class TestFileSensor(unittest.TestCase): } m_open = MockOpen(read_data='') - with patch('homeassistant.components.sensor.file.open', m_open, + with patch('homeassistant.components.file.sensor.open', m_open, create=True): assert setup_component(self.hass, 'sensor', config) self.hass.block_till_done() diff --git a/tests/components/filesize/__init__.py b/tests/components/filesize/__init__.py new file mode 100644 index 00000000000..02876267482 --- /dev/null +++ b/tests/components/filesize/__init__.py @@ -0,0 +1 @@ +"""Tests for the filesize component.""" diff --git a/tests/components/sensor/test_filesize.py b/tests/components/filesize/test_sensor.py similarity index 96% rename from tests/components/sensor/test_filesize.py rename to tests/components/filesize/test_sensor.py index b8b5c67da7d..dbc78bbd29b 100644 --- a/tests/components/sensor/test_filesize.py +++ b/tests/components/filesize/test_sensor.py @@ -2,7 +2,7 @@ import unittest import os -from homeassistant.components.sensor.filesize import CONF_FILE_PATHS +from homeassistant.components.filesize.sensor import CONF_FILE_PATHS from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/filter/__init__.py b/tests/components/filter/__init__.py new file mode 100644 index 00000000000..f69eb7fac1f --- /dev/null +++ b/tests/components/filter/__init__.py @@ -0,0 +1 @@ +"""Tests for the filter component.""" diff --git a/tests/components/sensor/test_filter.py b/tests/components/filter/test_sensor.py similarity index 99% rename from tests/components/sensor/test_filter.py rename to tests/components/filter/test_sensor.py index 29308f2a83d..d14eba405a8 100644 --- a/tests/components/sensor/test_filter.py +++ b/tests/components/filter/test_sensor.py @@ -3,7 +3,7 @@ from datetime import timedelta import unittest from unittest.mock import patch -from homeassistant.components.sensor.filter import ( +from homeassistant.components.filter.sensor import ( LowPassFilter, OutlierFilter, ThrottleFilter, TimeSMAFilter, RangeFilter, TimeThrottleFilter) import homeassistant.util.dt as dt_util diff --git a/tests/components/flux/__init__.py b/tests/components/flux/__init__.py new file mode 100644 index 00000000000..69feef62c52 --- /dev/null +++ b/tests/components/flux/__init__.py @@ -0,0 +1 @@ +"""Tests for the flux component.""" diff --git a/tests/components/switch/test_flux.py b/tests/components/flux/test_switch.py similarity index 97% rename from tests/components/switch/test_flux.py rename to tests/components/flux/test_switch.py index 84db2fd0df0..c43f1071e33 100644 --- a/tests/components/switch/test_flux.py +++ b/tests/components/flux/test_switch.py @@ -181,7 +181,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -227,7 +227,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -319,7 +319,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -369,7 +369,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -420,7 +420,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -519,7 +519,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -569,7 +569,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -616,7 +616,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -665,7 +665,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -727,7 +727,7 @@ class TestSwitchFlux(unittest.TestCase): print('sunset {}'.format(sunset_time)) return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -779,7 +779,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -823,7 +823,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_utcnow', + with patch('homeassistant.components.flux.switch.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): diff --git a/tests/components/folder/__init__.py b/tests/components/folder/__init__.py new file mode 100644 index 00000000000..9d5477c09ac --- /dev/null +++ b/tests/components/folder/__init__.py @@ -0,0 +1 @@ +"""Tests for the folder component.""" diff --git a/tests/components/sensor/test_folder.py b/tests/components/folder/test_sensor.py similarity index 96% rename from tests/components/sensor/test_folder.py rename to tests/components/folder/test_sensor.py index e4f97fbaa46..8e445dd4f53 100644 --- a/tests/components/sensor/test_folder.py +++ b/tests/components/folder/test_sensor.py @@ -2,7 +2,7 @@ import unittest import os -from homeassistant.components.sensor.folder import CONF_FOLDER_PATHS +from homeassistant.components.folder.sensor import CONF_FOLDER_PATHS from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/foobot/__init__.py b/tests/components/foobot/__init__.py new file mode 100644 index 00000000000..88d916d997f --- /dev/null +++ b/tests/components/foobot/__init__.py @@ -0,0 +1 @@ +"""Tests for the foobot component.""" diff --git a/tests/components/sensor/test_foobot.py b/tests/components/foobot/test_sensor.py similarity index 98% rename from tests/components/sensor/test_foobot.py rename to tests/components/foobot/test_sensor.py index 71f2d0481bf..7187fd1fea9 100644 --- a/tests/components/sensor/test_foobot.py +++ b/tests/components/foobot/test_sensor.py @@ -7,7 +7,7 @@ import pytest import homeassistant.components.sensor as sensor -from homeassistant.components.sensor import foobot +from homeassistant.components.foobot import sensor as foobot from homeassistant.const import (TEMP_CELSIUS) from homeassistant.exceptions import PlatformNotReady from homeassistant.setup import async_setup_component diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index b1b9a70d594..e4ed2c15ecb 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -249,7 +249,7 @@ async def test_get_panels(hass, hass_ws_client): """Test get_panels command.""" await async_setup_component(hass, 'frontend') await hass.components.frontend.async_register_built_in_panel( - 'map', 'Map', 'mdi:tooltip-account') + 'map', 'Map', 'mdi:tooltip-account', require_admin=True) client = await hass_ws_client(hass) await client.send_json({ @@ -266,6 +266,31 @@ async def test_get_panels(hass, hass_ws_client): assert msg['result']['map']['url_path'] == 'map' assert msg['result']['map']['icon'] == 'mdi:tooltip-account' assert msg['result']['map']['title'] == 'Map' + assert msg['result']['map']['require_admin'] is True + + +async def test_get_panels_non_admin(hass, hass_ws_client, hass_admin_user): + """Test get_panels command.""" + hass_admin_user.groups = [] + await async_setup_component(hass, 'frontend') + await hass.components.frontend.async_register_built_in_panel( + 'map', 'Map', 'mdi:tooltip-account', require_admin=True) + await hass.components.frontend.async_register_built_in_panel( + 'history', 'History', 'mdi:history') + + client = await hass_ws_client(hass) + await client.send_json({ + 'id': 5, + 'type': 'get_panels', + }) + + msg = await client.receive_json() + + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] + assert 'history' in msg['result'] + assert 'map' not in msg['result'] async def test_get_translations(hass, hass_ws_client): diff --git a/tests/components/generic/__init__.py b/tests/components/generic/__init__.py new file mode 100644 index 00000000000..1477d9d9a2f --- /dev/null +++ b/tests/components/generic/__init__.py @@ -0,0 +1 @@ +"""Tests for the generic component.""" diff --git a/tests/components/camera/test_generic.py b/tests/components/generic/test_camera.py similarity index 100% rename from tests/components/camera/test_generic.py rename to tests/components/generic/test_camera.py diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/generic_thermostat/test_climate.py similarity index 99% rename from tests/components/climate/test_generic_thermostat.py rename to tests/components/generic_thermostat/test_climate.py index 1d532f4757c..49d49fdd3d4 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -23,7 +23,6 @@ from homeassistant.util.unit_system import METRIC_SYSTEM from homeassistant.components import input_boolean, switch from homeassistant.components.climate.const import ( ATTR_OPERATION_MODE, STATE_HEAT, STATE_COOL, DOMAIN) -import homeassistant.components as comps from tests.common import assert_setup_component, mock_restore_cache from tests.components.climate import common @@ -68,7 +67,7 @@ def setup_comp_1(hass): """Initialize components.""" hass.config.units = METRIC_SYSTEM assert hass.loop.run_until_complete( - comps.async_setup(hass, {}) + async_setup_component(hass, 'homeassistant', {}) ) diff --git a/tests/components/geo_json_events/__init__.py b/tests/components/geo_json_events/__init__.py new file mode 100644 index 00000000000..09a76738530 --- /dev/null +++ b/tests/components/geo_json_events/__init__.py @@ -0,0 +1 @@ +"""Tests for the geo_json_events component.""" diff --git a/tests/components/geo_location/test_geo_json_events.py b/tests/components/geo_json_events/test_geo_location.py similarity index 99% rename from tests/components/geo_location/test_geo_json_events.py rename to tests/components/geo_json_events/test_geo_location.py index 46d1ed630c4..7004b78c148 100644 --- a/tests/components/geo_location/test_geo_json_events.py +++ b/tests/components/geo_json_events/test_geo_location.py @@ -3,7 +3,7 @@ from asynctest.mock import patch, MagicMock, call from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE -from homeassistant.components.geo_location.geo_json_events import \ +from homeassistant.components.geo_json_events.geo_location import \ SCAN_INTERVAL, ATTR_EXTERNAL_ID, SIGNAL_DELETE_ENTITY, SIGNAL_UPDATE_ENTITY from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_START, \ CONF_RADIUS, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_FRIENDLY_NAME, \ diff --git a/tests/components/geo_rss_events/__init__.py b/tests/components/geo_rss_events/__init__.py new file mode 100644 index 00000000000..dd10a455699 --- /dev/null +++ b/tests/components/geo_rss_events/__init__.py @@ -0,0 +1 @@ +"""Tests for the geo_rss_events component.""" diff --git a/tests/components/sensor/test_geo_rss_events.py b/tests/components/geo_rss_events/test_sensor.py similarity index 99% rename from tests/components/sensor/test_geo_rss_events.py rename to tests/components/geo_rss_events/test_sensor.py index fae5016cf91..f6101b13ea6 100644 --- a/tests/components/sensor/test_geo_rss_events.py +++ b/tests/components/geo_rss_events/test_sensor.py @@ -9,7 +9,7 @@ from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, ATTR_FRIENDLY_NAME, \ from homeassistant.setup import setup_component from tests.common import get_test_home_assistant, \ assert_setup_component, fire_time_changed -import homeassistant.components.sensor.geo_rss_events as geo_rss_events +import homeassistant.components.geo_rss_events.sensor as geo_rss_events import homeassistant.util.dt as dt_util URL = 'http://geo.rss.local/geo_rss_events.xml' diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 03cc327a5c5..331c6d2d9f5 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -1,3 +1,5 @@ + + """Tests for the Google Assistant integration.""" DEMO_DEVICES = [{ @@ -93,9 +95,9 @@ DEMO_DEVICES = [{ 'name': 'Living Room Window' }, 'traits': - ['action.devices.traits.OnOff', 'action.devices.traits.Brightness'], + ['action.devices.traits.OpenClose'], 'type': - 'action.devices.types.SWITCH', + 'action.devices.types.BLINDS', 'willReportState': False }, { @@ -105,9 +107,9 @@ DEMO_DEVICES = [{ 'name': 'Hall Window' }, 'traits': - ['action.devices.traits.OnOff', 'action.devices.traits.Brightness'], + ['action.devices.traits.OpenClose'], 'type': - 'action.devices.types.SWITCH', + 'action.devices.types.BLINDS', 'willReportState': False }, { @@ -115,16 +117,18 @@ DEMO_DEVICES = [{ 'name': { 'name': 'Garage Door' }, - 'traits': ['action.devices.traits.OnOff'], - 'type': 'action.devices.types.SWITCH', + 'traits': ['action.devices.traits.OpenClose'], + 'type': + 'action.devices.types.BLINDS', 'willReportState': False }, { 'id': 'cover.kitchen_window', 'name': { 'name': 'Kitchen Window' }, - 'traits': ['action.devices.traits.OnOff'], - 'type': 'action.devices.types.SWITCH', + 'traits': ['action.devices.traits.OpenClose'], + 'type': + 'action.devices.types.BLINDS', 'willReportState': False }, { 'id': 'group.all_covers', @@ -231,10 +235,7 @@ DEMO_DEVICES = [{ 'name': { 'name': 'HeatPump' }, - 'traits': [ - 'action.devices.traits.OnOff', - 'action.devices.traits.TemperatureSetting' - ], + 'traits': ['action.devices.traits.TemperatureSetting'], 'type': 'action.devices.types.THERMOSTAT', 'willReportState': False }, { diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py index 18f99c82685..60df4a8e79c 100644 --- a/tests/components/google_assistant/test_google_assistant.py +++ b/tests/components/google_assistant/test_google_assistant.py @@ -8,7 +8,7 @@ import pytest from homeassistant import core, const, setup from homeassistant.components import ( - fan, cover, light, switch, lock, async_setup, media_player) + fan, cover, light, switch, lock, media_player) from homeassistant.components.climate import const as climate from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES from homeassistant.components import google_assistant as ga @@ -56,7 +56,7 @@ def assistant_client(loop, hass, aiohttp_client): def hass_fixture(loop, hass): """Set up a Home Assistant instance for these tests.""" # We need to do this to get access to homeassistant/turn_(on,off) - loop.run_until_complete(async_setup(hass, {core.DOMAIN: {}})) + loop.run_until_complete(setup.async_setup_component(hass, core.DOMAIN, {})) loop.run_until_complete( setup.async_setup_component(hass, light.DOMAIN, { @@ -107,6 +107,8 @@ def hass_fixture(loop, hass): return hass +# pylint: disable=redefined-outer-name + @asyncio.coroutine def test_sync_request(hass_fixture, assistant_client, auth_header): @@ -205,7 +207,6 @@ def test_query_climate_request(hass_fixture, assistant_client, auth_header): devices = body['payload']['devices'] assert len(devices) == 3 assert devices['climate.heatpump'] == { - 'on': True, 'online': True, 'thermostatTemperatureSetpoint': 20.0, 'thermostatTemperatureAmbient': 25.0, @@ -262,7 +263,6 @@ def test_query_climate_request_f(hass_fixture, assistant_client, auth_header): devices = body['payload']['devices'] assert len(devices) == 3 assert devices['climate.heatpump'] == { - 'on': True, 'online': True, 'thermostatTemperatureSetpoint': -6.7, 'thermostatTemperatureAmbient': -3.9, diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index 302e8d8674f..bc59cc8ff2d 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -1,21 +1,23 @@ """Test Google Smart Home.""" +from unittest.mock import patch, Mock import pytest from homeassistant.core import State, EVENT_CALL_SERVICE from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS) from homeassistant.setup import async_setup_component +from homeassistant.components import camera from homeassistant.components.climate.const import ( ATTR_MIN_TEMP, ATTR_MAX_TEMP, STATE_HEAT, SUPPORT_OPERATION_MODE ) from homeassistant.components.google_assistant import ( const, trait, helpers, smart_home as sh, EVENT_COMMAND_RECEIVED, EVENT_QUERY_RECEIVED, EVENT_SYNC_RECEIVED) -from homeassistant.components.light.demo import DemoLight +from homeassistant.components.demo.light import DemoLight from homeassistant.helpers import device_registry from tests.common import (mock_device_registry, mock_registry, - mock_area_registry) + mock_area_registry, mock_coro) BASIC_CONFIG = helpers.Config( should_expose=lambda state: True, @@ -557,3 +559,57 @@ async def test_query_disconnect(hass): }) assert result is None + + +async def test_trait_execute_adding_query_data(hass): + """Test a trait execute influencing query data.""" + hass.config.api = Mock(base_url='http://1.1.1.1:8123') + hass.states.async_set('camera.office', 'idle', { + 'supported_features': camera.SUPPORT_STREAM + }) + + with patch('homeassistant.components.camera.async_request_stream', + return_value=mock_coro('/api/streams/bla')): + result = await sh.async_handle_message( + hass, BASIC_CONFIG, None, + { + "requestId": REQ_ID, + "inputs": [{ + "intent": "action.devices.EXECUTE", + "payload": { + "commands": [{ + "devices": [ + {"id": "camera.office"}, + ], + "execution": [{ + "command": + "action.devices.commands.GetCameraStream", + "params": { + "StreamToChromecast": True, + "SupportedStreamProtocols": [ + "progressive_mp4", + "hls", + "dash", + "smooth_stream" + ] + } + }] + }] + } + }] + }) + + assert result == { + "requestId": REQ_ID, + "payload": { + "commands": [{ + "ids": ['camera.office'], + "status": "SUCCESS", + "states": { + "online": True, + 'cameraStreamAccessUrl': + 'http://1.1.1.1:8123/api/streams/bla', + } + }] + } + } diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 301de9c8c25..81a7fbe1bf7 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1,7 +1,10 @@ """Tests for the Google Assistant traits.""" +from unittest.mock import patch, Mock + import pytest from homeassistant.components import ( + camera, cover, fan, input_boolean, @@ -18,10 +21,11 @@ from homeassistant.components.climate import const as climate from homeassistant.components.google_assistant import trait, helpers, const from homeassistant.const import ( STATE_ON, STATE_OFF, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, - TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE) + TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, + ATTR_ASSUMED_STATE) from homeassistant.core import State, DOMAIN as HA_DOMAIN, EVENT_CALL_SERVICE from homeassistant.util import color -from tests.common import async_mock_service +from tests.common import async_mock_service, mock_coro BASIC_CONFIG = helpers.Config( should_expose=lambda state: True, @@ -80,33 +84,6 @@ async def test_brightness_light(hass): } -async def test_brightness_cover(hass): - """Test brightness trait support for cover domain.""" - assert trait.BrightnessTrait.supported(cover.DOMAIN, - cover.SUPPORT_SET_POSITION) - - trt = trait.BrightnessTrait(hass, State('cover.bla', cover.STATE_OPEN, { - cover.ATTR_CURRENT_POSITION: 75 - }), BASIC_CONFIG) - - assert trt.sync_attributes() == {} - - assert trt.query_attributes() == { - 'brightness': 75 - } - - calls = async_mock_service( - hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION) - await trt.execute( - trait.COMMAND_BRIGHTNESS_ABSOLUTE, BASIC_DATA, - {'brightness': 50}) - assert len(calls) == 1 - assert calls[0].data == { - ATTR_ENTITY_ID: 'cover.bla', - cover.ATTR_POSITION: 50 - } - - async def test_brightness_media_player(hass): """Test brightness trait support for media player domain.""" assert trait.BrightnessTrait.supported(media_player.DOMAIN, @@ -135,6 +112,35 @@ async def test_brightness_media_player(hass): } +async def test_camera_stream(hass): + """Test camera stream trait support for camera domain.""" + hass.config.api = Mock(base_url='http://1.1.1.1:8123') + assert trait.CameraStreamTrait.supported(camera.DOMAIN, + camera.SUPPORT_STREAM) + + trt = trait.CameraStreamTrait( + hass, State('camera.bla', camera.STATE_IDLE, {}), BASIC_CONFIG + ) + + assert trt.sync_attributes() == { + 'cameraStreamSupportedProtocols': [ + "hls", + ], + 'cameraStreamNeedAuthToken': False, + 'cameraStreamNeedDrmEncryption': False, + } + + assert trt.query_attributes() == {} + + with patch('homeassistant.components.camera.async_request_stream', + return_value=mock_coro('/api/streams/bla')): + await trt.execute(trait.COMMAND_GET_CAMERA_STREAM, BASIC_DATA, {}) + + assert trt.query_attributes() == { + 'cameraStreamAccessUrl': 'http://1.1.1.1:8123/api/streams/bla' + } + + async def test_onoff_group(hass): """Test OnOff trait support for group domain.""" assert trait.OnOffTrait.supported(group.DOMAIN, 0) @@ -326,46 +332,6 @@ async def test_onoff_light(hass): } -async def test_onoff_cover(hass): - """Test OnOff trait support for cover domain.""" - assert trait.OnOffTrait.supported(cover.DOMAIN, 0) - - trt_on = trait.OnOffTrait(hass, State('cover.bla', cover.STATE_OPEN), - BASIC_CONFIG) - - assert trt_on.sync_attributes() == {} - - assert trt_on.query_attributes() == { - 'on': True - } - - trt_off = trait.OnOffTrait(hass, State('cover.bla', cover.STATE_CLOSED), - BASIC_CONFIG) - - assert trt_off.query_attributes() == { - 'on': False - } - - on_calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER) - await trt_on.execute( - trait.COMMAND_ONOFF, BASIC_DATA, - {'on': True}) - assert len(on_calls) == 1 - assert on_calls[0].data == { - ATTR_ENTITY_ID: 'cover.bla', - } - - off_calls = async_mock_service(hass, cover.DOMAIN, - cover.SERVICE_CLOSE_COVER) - await trt_on.execute( - trait.COMMAND_ONOFF, BASIC_DATA, - {'on': False}) - assert len(off_calls) == 1 - assert off_calls[0].data == { - ATTR_ENTITY_ID: 'cover.bla', - } - - async def test_onoff_media_player(hass): """Test OnOff trait support for media_player domain.""" assert trait.OnOffTrait.supported(media_player.DOMAIN, 0) @@ -408,44 +374,9 @@ async def test_onoff_media_player(hass): async def test_onoff_climate(hass): - """Test OnOff trait support for climate domain.""" - assert trait.OnOffTrait.supported(climate.DOMAIN, climate.SUPPORT_ON_OFF) - - trt_on = trait.OnOffTrait(hass, State('climate.bla', STATE_ON), - BASIC_CONFIG) - - assert trt_on.sync_attributes() == {} - - assert trt_on.query_attributes() == { - 'on': True - } - - trt_off = trait.OnOffTrait(hass, State('climate.bla', STATE_OFF), - BASIC_CONFIG) - - assert trt_off.query_attributes() == { - 'on': False - } - - on_calls = async_mock_service(hass, climate.DOMAIN, SERVICE_TURN_ON) - await trt_on.execute( - trait.COMMAND_ONOFF, BASIC_DATA, - {'on': True}) - assert len(on_calls) == 1 - assert on_calls[0].data == { - ATTR_ENTITY_ID: 'climate.bla', - } - - off_calls = async_mock_service(hass, climate.DOMAIN, - SERVICE_TURN_OFF) - - await trt_on.execute( - trait.COMMAND_ONOFF, BASIC_DATA, - {'on': False}) - assert len(off_calls) == 1 - assert off_calls[0].data == { - ATTR_ENTITY_ID: 'climate.bla', - } + """Test OnOff trait not supported for climate domain.""" + assert not trait.OnOffTrait.supported( + climate.DOMAIN, climate.SUPPORT_ON_OFF) async def test_dock_vacuum(hass): @@ -673,6 +604,50 @@ async def test_scene_script(hass): } +async def test_temperature_setting_climate_onoff(hass): + """Test TemperatureSetting trait support for climate domain - range.""" + assert not trait.TemperatureSettingTrait.supported(climate.DOMAIN, 0) + assert trait.TemperatureSettingTrait.supported( + climate.DOMAIN, climate.SUPPORT_OPERATION_MODE) + + hass.config.units.temperature_unit = TEMP_FAHRENHEIT + + trt = trait.TemperatureSettingTrait(hass, State( + 'climate.bla', climate.STATE_AUTO, { + ATTR_SUPPORTED_FEATURES: ( + climate.SUPPORT_OPERATION_MODE | climate.SUPPORT_ON_OFF | + climate.SUPPORT_TARGET_TEMPERATURE_HIGH | + climate.SUPPORT_TARGET_TEMPERATURE_LOW), + climate.ATTR_OPERATION_MODE: climate.STATE_COOL, + climate.ATTR_OPERATION_LIST: [ + climate.STATE_COOL, + climate.STATE_HEAT, + climate.STATE_AUTO, + ], + climate.ATTR_MIN_TEMP: None, + climate.ATTR_MAX_TEMP: None, + }), BASIC_CONFIG) + assert trt.sync_attributes() == { + 'availableThermostatModes': 'off,on,cool,heat,heatcool', + 'thermostatTemperatureUnit': 'F', + } + assert trt.can_execute(trait.COMMAND_THERMOSTAT_SET_MODE, {}) + + calls = async_mock_service( + hass, climate.DOMAIN, SERVICE_TURN_ON) + await trt.execute(trait.COMMAND_THERMOSTAT_SET_MODE, BASIC_DATA, { + 'thermostatMode': 'on', + }) + assert len(calls) == 1 + + calls = async_mock_service( + hass, climate.DOMAIN, SERVICE_TURN_OFF) + await trt.execute(trait.COMMAND_THERMOSTAT_SET_MODE, BASIC_DATA, { + 'thermostatMode': 'off', + }) + assert len(calls) == 1 + + async def test_temperature_setting_climate_range(hass): """Test TemperatureSetting trait support for climate domain - range.""" assert not trait.TemperatureSettingTrait.supported(climate.DOMAIN, 0) @@ -685,6 +660,10 @@ async def test_temperature_setting_climate_range(hass): 'climate.bla', climate.STATE_AUTO, { climate.ATTR_CURRENT_TEMPERATURE: 70, climate.ATTR_CURRENT_HUMIDITY: 25, + ATTR_SUPPORTED_FEATURES: + climate.SUPPORT_OPERATION_MODE | + climate.SUPPORT_TARGET_TEMPERATURE_HIGH | + climate.SUPPORT_TARGET_TEMPERATURE_LOW, climate.ATTR_OPERATION_MODE: climate.STATE_AUTO, climate.ATTR_OPERATION_LIST: [ STATE_OFF, @@ -708,7 +687,6 @@ async def test_temperature_setting_climate_range(hass): 'thermostatTemperatureSetpointLow': 18.3, 'thermostatTemperatureSetpointHigh': 23.9, } - assert trt.can_execute(trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, {}) assert trt.can_execute(trait.COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE, {}) assert trt.can_execute(trait.COMMAND_THERMOSTAT_SET_MODE, {}) @@ -755,6 +733,8 @@ async def test_temperature_setting_climate_setpoint(hass): trt = trait.TemperatureSettingTrait(hass, State( 'climate.bla', climate.STATE_AUTO, { + ATTR_SUPPORTED_FEATURES: ( + climate.SUPPORT_OPERATION_MODE | climate.SUPPORT_ON_OFF), climate.ATTR_OPERATION_MODE: climate.STATE_COOL, climate.ATTR_OPERATION_LIST: [ STATE_OFF, @@ -766,7 +746,7 @@ async def test_temperature_setting_climate_setpoint(hass): climate.ATTR_CURRENT_TEMPERATURE: 20 }), BASIC_CONFIG) assert trt.sync_attributes() == { - 'availableThermostatModes': 'off,cool', + 'availableThermostatModes': 'off,on,cool', 'thermostatTemperatureUnit': 'C', } assert trt.query_attributes() == { @@ -775,7 +755,6 @@ async def test_temperature_setting_climate_setpoint(hass): 'thermostatTemperatureSetpoint': 18, } assert trt.can_execute(trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, {}) - assert trt.can_execute(trait.COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE, {}) assert trt.can_execute(trait.COMMAND_THERMOSTAT_SET_MODE, {}) calls = async_mock_service( @@ -796,6 +775,54 @@ async def test_temperature_setting_climate_setpoint(hass): } +async def test_temperature_setting_climate_setpoint_auto(hass): + """ + Test TemperatureSetting trait support for climate domain. + + Setpoint in auto mode. + """ + hass.config.units.temperature_unit = TEMP_CELSIUS + + trt = trait.TemperatureSettingTrait(hass, State( + 'climate.bla', climate.STATE_AUTO, { + ATTR_SUPPORTED_FEATURES: ( + climate.SUPPORT_OPERATION_MODE | climate.SUPPORT_ON_OFF), + climate.ATTR_OPERATION_MODE: climate.STATE_AUTO, + climate.ATTR_OPERATION_LIST: [ + STATE_OFF, + climate.STATE_AUTO, + ], + climate.ATTR_MIN_TEMP: 10, + climate.ATTR_MAX_TEMP: 30, + ATTR_TEMPERATURE: 18, + climate.ATTR_CURRENT_TEMPERATURE: 20 + }), BASIC_CONFIG) + assert trt.sync_attributes() == { + 'availableThermostatModes': 'off,on,heatcool', + 'thermostatTemperatureUnit': 'C', + } + assert trt.query_attributes() == { + 'thermostatMode': 'heatcool', + 'thermostatTemperatureAmbient': 20, + 'thermostatTemperatureSetpointHigh': 18, + 'thermostatTemperatureSetpointLow': 18, + } + assert trt.can_execute(trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, {}) + assert trt.can_execute(trait.COMMAND_THERMOSTAT_SET_MODE, {}) + + calls = async_mock_service( + hass, climate.DOMAIN, climate.SERVICE_SET_TEMPERATURE) + + await trt.execute( + trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, BASIC_DATA, + {'thermostatTemperatureSetpoint': 19}) + assert len(calls) == 1 + assert calls[0].data == { + ATTR_ENTITY_ID: 'climate.bla', + ATTR_TEMPERATURE: 19 + } + + async def test_lock_unlock_lock(hass): """Test LockUnlock trait locking support for lock domain.""" assert trait.LockUnlockTrait.supported(lock.DOMAIN, lock.SUPPORT_OPEN) @@ -1026,3 +1053,48 @@ async def test_modes(hass): 'entity_id': 'media_player.living_room', 'source': 'media' } + + +async def test_openclose_cover(hass): + """Test cover trait.""" + assert trait.OpenCloseTrait.supported(cover.DOMAIN, + cover.SUPPORT_SET_POSITION) + + # No position + trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, { + }), BASIC_CONFIG) + + assert trt.sync_attributes() == {} + assert trt.query_attributes() == { + 'openPercent': 100 + } + + # Assumed state + trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, { + ATTR_ASSUMED_STATE: True, + }), BASIC_CONFIG) + + assert trt.sync_attributes() == {} + assert trt.query_attributes() == { + 'openPercent': 50 + } + + trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, { + cover.ATTR_CURRENT_POSITION: 75 + }), BASIC_CONFIG) + + assert trt.sync_attributes() == {} + assert trt.query_attributes() == { + 'openPercent': 75 + } + + calls = async_mock_service( + hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION) + await trt.execute( + trait.COMMAND_OPENCLOSE, BASIC_DATA, + {'openPercent': 50}) + assert len(calls) == 1 + assert calls[0].data == { + ATTR_ENTITY_ID: 'cover.bla', + cover.ATTR_POSITION: 50 + } diff --git a/tests/components/google_pubsub/__init__.py b/tests/components/google_pubsub/__init__.py new file mode 100644 index 00000000000..2387617705b --- /dev/null +++ b/tests/components/google_pubsub/__init__.py @@ -0,0 +1 @@ +"""Tests for google_pubsub component.""" diff --git a/tests/components/google_wifi/__init__.py b/tests/components/google_wifi/__init__.py new file mode 100644 index 00000000000..8b95a49ecef --- /dev/null +++ b/tests/components/google_wifi/__init__.py @@ -0,0 +1 @@ +"""Tests for the google_wifi component.""" diff --git a/tests/components/sensor/test_google_wifi.py b/tests/components/google_wifi/test_sensor.py similarity index 99% rename from tests/components/sensor/test_google_wifi.py rename to tests/components/google_wifi/test_sensor.py index 989cd13c5d6..ee0cf3b0658 100644 --- a/tests/components/sensor/test_google_wifi.py +++ b/tests/components/google_wifi/test_sensor.py @@ -7,7 +7,7 @@ import requests_mock from homeassistant import core as ha from homeassistant.setup import setup_component -import homeassistant.components.sensor.google_wifi as google_wifi +import homeassistant.components.google_wifi.sensor as google_wifi from homeassistant.const import STATE_UNKNOWN from homeassistant.util import dt as dt_util diff --git a/tests/components/cover/test_group.py b/tests/components/group/test_cover.py similarity index 99% rename from tests/components/cover/test_group.py rename to tests/components/group/test_cover.py index 2211c8c77bc..04e8f9c964d 100644 --- a/tests/components/cover/test_group.py +++ b/tests/components/group/test_cover.py @@ -6,7 +6,7 @@ import pytest from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, ATTR_POSITION, ATTR_TILT_POSITION, DOMAIN) -from homeassistant.components.cover.group import DEFAULT_NAME +from homeassistant.components.group.cover import DEFAULT_NAME from homeassistant.const import ( ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_SUPPORTED_FEATURES, CONF_ENTITIES, diff --git a/tests/components/light/test_group.py b/tests/components/group/test_light.py similarity index 99% rename from tests/components/light/test_group.py rename to tests/components/group/test_light.py index 472bdf42385..51580e503bd 100644 --- a/tests/components/light/test_group.py +++ b/tests/components/group/test_light.py @@ -3,7 +3,7 @@ from unittest.mock import MagicMock import asynctest -from homeassistant.components.light import group +import homeassistant.components.group.light as group from homeassistant.setup import async_setup_component from tests.components.light import common diff --git a/tests/components/notify/test_group.py b/tests/components/group/test_notify.py similarity index 96% rename from tests/components/notify/test_group.py rename to tests/components/group/test_notify.py index bbd7c11ffeb..9412e9f95a4 100644 --- a/tests/components/notify/test_group.py +++ b/tests/components/group/test_notify.py @@ -4,7 +4,8 @@ from unittest.mock import MagicMock, patch from homeassistant.setup import setup_component import homeassistant.components.notify as notify -from homeassistant.components.notify import group, demo +import homeassistant.components.group.notify as group +import homeassistant.components.demo.notify as demo from homeassistant.util.async_ import run_coroutine_threadsafe from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/hassio/test_http.py b/tests/components/hassio/test_http.py index 3f58c6e697e..3a58048735b 100644 --- a/tests/components/hassio/test_http.py +++ b/tests/components/hassio/test_http.py @@ -1,29 +1,22 @@ """The tests for the hassio component.""" import asyncio -from unittest.mock import patch, Mock, MagicMock +from unittest.mock import patch import pytest from homeassistant.const import HTTP_HEADER_HA_AUTH -from tests.common import mock_coro from . import API_PASSWORD @asyncio.coroutine -def test_forward_request(hassio_client): +def test_forward_request(hassio_client, aioclient_mock): """Test fetching normal path.""" - response = MagicMock() - response.read.return_value = mock_coro('data') + aioclient_mock.post("http://127.0.0.1/beer", text="response") - with patch('homeassistant.components.hassio.HassIOView._command_proxy', - Mock(return_value=mock_coro(response))), \ - patch('homeassistant.components.hassio.http' - '._create_response') as mresp: - mresp.return_value = 'response' - resp = yield from hassio_client.post('/api/hassio/beer', headers={ - HTTP_HEADER_HA_AUTH: API_PASSWORD - }) + resp = yield from hassio_client.post('/api/hassio/beer', headers={ + HTTP_HEADER_HA_AUTH: API_PASSWORD + }) # Check we got right response assert resp.status == 200 @@ -31,8 +24,7 @@ def test_forward_request(hassio_client): assert body == 'response' # Check we forwarded command - assert len(mresp.mock_calls) == 1 - assert mresp.mock_calls[0][1] == (response, 'data') + assert len(aioclient_mock.mock_calls) == 1 @asyncio.coroutine @@ -55,18 +47,13 @@ def test_auth_required_forward_request(hassio_noauth_client, build_type): 'app/index.html', 'app/hassio-app.html', 'app/index.html', 'app/hassio-app.html', 'app/some-chunk.js', 'app/app.js', ]) -def test_forward_request_no_auth_for_panel(hassio_client, build_type): +def test_forward_request_no_auth_for_panel( + hassio_client, build_type, aioclient_mock): """Test no auth needed for .""" - response = MagicMock() - response.read.return_value = mock_coro('data') + aioclient_mock.get( + "http://127.0.0.1/{}".format(build_type), text="response") - with patch('homeassistant.components.hassio.HassIOView._command_proxy', - Mock(return_value=mock_coro(response))), \ - patch('homeassistant.components.hassio.http.' - '_create_response') as mresp: - mresp.return_value = 'response' - resp = yield from hassio_client.get( - '/api/hassio/{}'.format(build_type)) + resp = yield from hassio_client.get('/api/hassio/{}'.format(build_type)) # Check we got right response assert resp.status == 200 @@ -74,22 +61,16 @@ def test_forward_request_no_auth_for_panel(hassio_client, build_type): assert body == 'response' # Check we forwarded command - assert len(mresp.mock_calls) == 1 - assert mresp.mock_calls[0][1] == (response, 'data') + assert len(aioclient_mock.mock_calls) == 1 @asyncio.coroutine -def test_forward_request_no_auth_for_logo(hassio_client): +def test_forward_request_no_auth_for_logo(hassio_client, aioclient_mock): """Test no auth needed for .""" - response = MagicMock() - response.read.return_value = mock_coro('data') + aioclient_mock.get( + "http://127.0.0.1/addons/bl_b392/logo", text="response") - with patch('homeassistant.components.hassio.HassIOView._command_proxy', - Mock(return_value=mock_coro(response))), \ - patch('homeassistant.components.hassio.http.' - '_create_response') as mresp: - mresp.return_value = 'response' - resp = yield from hassio_client.get('/api/hassio/addons/bl_b392/logo') + resp = yield from hassio_client.get('/api/hassio/addons/bl_b392/logo') # Check we got right response assert resp.status == 200 @@ -97,24 +78,18 @@ def test_forward_request_no_auth_for_logo(hassio_client): assert body == 'response' # Check we forwarded command - assert len(mresp.mock_calls) == 1 - assert mresp.mock_calls[0][1] == (response, 'data') + assert len(aioclient_mock.mock_calls) == 1 @asyncio.coroutine -def test_forward_log_request(hassio_client): +def test_forward_log_request(hassio_client, aioclient_mock): """Test fetching normal log path doesn't remove ANSI color escape codes.""" - response = MagicMock() - response.read.return_value = mock_coro('data') + aioclient_mock.get( + "http://127.0.0.1/beer/logs", text="\033[32mresponse\033[0m") - with patch('homeassistant.components.hassio.HassIOView._command_proxy', - Mock(return_value=mock_coro(response))), \ - patch('homeassistant.components.hassio.http.' - '_create_response') as mresp: - mresp.return_value = '\033[32mresponse\033[0m' - resp = yield from hassio_client.get('/api/hassio/beer/logs', headers={ - HTTP_HEADER_HA_AUTH: API_PASSWORD - }) + resp = yield from hassio_client.get('/api/hassio/beer/logs', headers={ + HTTP_HEADER_HA_AUTH: API_PASSWORD + }) # Check we got right response assert resp.status == 200 @@ -122,8 +97,7 @@ def test_forward_log_request(hassio_client): assert body == '\033[32mresponse\033[0m' # Check we forwarded command - assert len(mresp.mock_calls) == 1 - assert mresp.mock_calls[0][1] == (response, 'data') + assert len(aioclient_mock.mock_calls) == 1 @asyncio.coroutine @@ -151,5 +125,5 @@ async def test_forwarding_user_info(hassio_client, hass_admin_user, assert len(aioclient_mock.mock_calls) == 1 req_headers = aioclient_mock.mock_calls[0][-1] - req_headers['X-HASS-USER-ID'] == hass_admin_user.id - req_headers['X-HASS-IS-ADMIN'] == '1' + req_headers['X-Hass-User-ID'] == hass_admin_user.id + req_headers['X-Hass-Is-Admin'] == '1' diff --git a/tests/components/hassio/test_ingress.py b/tests/components/hassio/test_ingress.py new file mode 100644 index 00000000000..4b72eda4c25 --- /dev/null +++ b/tests/components/hassio/test_ingress.py @@ -0,0 +1,163 @@ +"""The tests for the hassio component.""" + +from aiohttp.hdrs import X_FORWARDED_FOR, X_FORWARDED_HOST, X_FORWARDED_PROTO +from aiohttp.client_exceptions import WSServerHandshakeError +import pytest + + +@pytest.mark.parametrize( + 'build_type', [ + ("a3_vl", "test/beer/ping?index=1"), ("core", "index.html"), + ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5") + ]) +async def test_ingress_request_get( + hassio_client, build_type, aioclient_mock): + """Test no auth needed for .""" + aioclient_mock.get("http://127.0.0.1/addons/{}/web/{}".format( + build_type[0], build_type[1]), text="test") + + resp = await hassio_client.get( + '/api/hassio_ingress/{}/{}'.format(build_type[0], build_type[1]), + headers={"X-Test-Header": "beer"} + ) + + # Check we got right response + assert resp.status == 200 + body = await resp.text() + assert body == "test" + + # Check we forwarded command + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[-1][3]["X-Hassio-Key"] == "123456" + assert aioclient_mock.mock_calls[-1][3]["X-Ingress-Path"] == \ + "/api/hassio_ingress/{}".format(build_type[0]) + assert aioclient_mock.mock_calls[-1][3]["X-Test-Header"] == "beer" + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_FOR] + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_HOST] + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_PROTO] + + +@pytest.mark.parametrize( + 'build_type', [ + ("a3_vl", "test/beer/ping?index=1"), ("core", "index.html"), + ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5") + ]) +async def test_ingress_request_post( + hassio_client, build_type, aioclient_mock): + """Test no auth needed for .""" + aioclient_mock.post("http://127.0.0.1/addons/{}/web/{}".format( + build_type[0], build_type[1]), text="test") + + resp = await hassio_client.post( + '/api/hassio_ingress/{}/{}'.format(build_type[0], build_type[1]), + headers={"X-Test-Header": "beer"} + ) + + # Check we got right response + assert resp.status == 200 + body = await resp.text() + assert body == "test" + + # Check we forwarded command + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[-1][3]["X-Hassio-Key"] == "123456" + assert aioclient_mock.mock_calls[-1][3]["X-Ingress-Path"] == \ + "/api/hassio_ingress/{}".format(build_type[0]) + assert aioclient_mock.mock_calls[-1][3]["X-Test-Header"] == "beer" + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_FOR] + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_HOST] + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_PROTO] + + +@pytest.mark.parametrize( + 'build_type', [ + ("a3_vl", "test/beer/ping?index=1"), ("core", "index.html"), + ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5") + ]) +async def test_ingress_request_put( + hassio_client, build_type, aioclient_mock): + """Test no auth needed for .""" + aioclient_mock.put("http://127.0.0.1/addons/{}/web/{}".format( + build_type[0], build_type[1]), text="test") + + resp = await hassio_client.put( + '/api/hassio_ingress/{}/{}'.format(build_type[0], build_type[1]), + headers={"X-Test-Header": "beer"} + ) + + # Check we got right response + assert resp.status == 200 + body = await resp.text() + assert body == "test" + + # Check we forwarded command + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[-1][3]["X-Hassio-Key"] == "123456" + assert aioclient_mock.mock_calls[-1][3]["X-Ingress-Path"] == \ + "/api/hassio_ingress/{}".format(build_type[0]) + assert aioclient_mock.mock_calls[-1][3]["X-Test-Header"] == "beer" + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_FOR] + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_HOST] + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_PROTO] + + +@pytest.mark.parametrize( + 'build_type', [ + ("a3_vl", "test/beer/ping?index=1"), ("core", "index.html"), + ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5") + ]) +async def test_ingress_request_delete( + hassio_client, build_type, aioclient_mock): + """Test no auth needed for .""" + aioclient_mock.delete("http://127.0.0.1/addons/{}/web/{}".format( + build_type[0], build_type[1]), text="test") + + resp = await hassio_client.delete( + '/api/hassio_ingress/{}/{}'.format(build_type[0], build_type[1]), + headers={"X-Test-Header": "beer"} + ) + + # Check we got right response + assert resp.status == 200 + body = await resp.text() + assert body == "test" + + # Check we forwarded command + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[-1][3]["X-Hassio-Key"] == "123456" + assert aioclient_mock.mock_calls[-1][3]["X-Ingress-Path"] == \ + "/api/hassio_ingress/{}".format(build_type[0]) + assert aioclient_mock.mock_calls[-1][3]["X-Test-Header"] == "beer" + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_FOR] + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_HOST] + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_PROTO] + + +@pytest.mark.parametrize( + 'build_type', [ + ("a3_vl", "test/beer/ws"), ("core", "ws.php"), + ("local", "panel/config/stream"), ("jk_921", "hulk"), + ("demo", "ws/connection?id=9&token=SJAKWS283") + ]) +async def test_ingress_websocket( + hassio_client, build_type, aioclient_mock): + """Test no auth needed for .""" + aioclient_mock.get("http://127.0.0.1/addons/{}/web/{}".format( + build_type[0], build_type[1])) + + # Ignore error because we can setup a full IO infrastructure + with pytest.raises(WSServerHandshakeError): + await hassio_client.ws_connect( + '/api/hassio_ingress/{}/{}'.format(build_type[0], build_type[1]), + headers={"X-Test-Header": "beer"} + ) + + # Check we forwarded command + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[-1][3]["X-Hassio-Key"] == "123456" + assert aioclient_mock.mock_calls[-1][3]["X-Ingress-Path"] == \ + "/api/hassio_ingress/{}".format(build_type[0]) + assert aioclient_mock.mock_calls[-1][3]["X-Test-Header"] == "beer" + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_FOR] + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_HOST] + assert aioclient_mock.mock_calls[-1][3][X_FORWARDED_PROTO] diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index dafb8f1a028..f1f148f8495 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -8,6 +8,7 @@ import pytest from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.setup import async_setup_component from homeassistant.components.hassio import STORAGE_KEY +from homeassistant.components import frontend from tests.common import mock_coro @@ -44,6 +45,28 @@ def test_setup_api_ping(hass, aioclient_mock): assert hass.components.hassio.is_hassio() +async def test_setup_api_panel(hass, aioclient_mock): + """Test setup with API ping.""" + assert await async_setup_component(hass, 'frontend', {}) + with patch.dict(os.environ, MOCK_ENVIRON): + result = await async_setup_component(hass, 'hassio', {}) + assert result + + panels = hass.data[frontend.DATA_PANELS] + + assert panels.get('hassio').to_response() == { + 'component_name': 'custom', + 'icon': 'hass:home-assistant', + 'title': 'Hass.io', + 'url_path': 'hassio', + 'require_admin': True, + 'config': {'_panel_custom': {'embed_iframe': True, + 'js_url': '/api/hassio/app/entrypoint.js', + 'name': 'hassio-main', + 'trust_external': False}}, + } + + @asyncio.coroutine def test_setup_api_push_api_data(hass, aioclient_mock): """Test setup with API push.""" @@ -184,7 +207,7 @@ def test_setup_hassio_no_additional_data(hass, aioclient_mock): assert result assert aioclient_mock.call_count == 3 - assert aioclient_mock.mock_calls[-1][3]['X-HASSIO-KEY'] == "123456" + assert aioclient_mock.mock_calls[-1][3]['X-Hassio-Key'] == "123456" @asyncio.coroutine @@ -196,8 +219,8 @@ def test_fail_setup_without_environ_var(hass): @asyncio.coroutine -def test_fail_setup_cannot_connect(hass, caplog): - """Fail setup if cannot connect.""" +def test_warn_when_cannot_connect(hass, caplog): + """Fail warn when we cannot connect.""" with patch.dict(os.environ, MOCK_ENVIRON), \ patch('homeassistant.components.hassio.HassIO.is_connected', Mock(return_value=mock_coro(None))): diff --git a/tests/components/hddtemp/__init__.py b/tests/components/hddtemp/__init__.py new file mode 100644 index 00000000000..11d53678a39 --- /dev/null +++ b/tests/components/hddtemp/__init__.py @@ -0,0 +1 @@ +"""Tests for the hddtemp component.""" diff --git a/tests/components/sensor/test_hddtemp.py b/tests/components/hddtemp/test_sensor.py similarity index 100% rename from tests/components/sensor/test_hddtemp.py rename to tests/components/hddtemp/test_sensor.py diff --git a/tests/components/history_stats/__init__.py b/tests/components/history_stats/__init__.py new file mode 100644 index 00000000000..34018f171bb --- /dev/null +++ b/tests/components/history_stats/__init__.py @@ -0,0 +1 @@ +"""Tests for the history_stats component.""" diff --git a/tests/components/sensor/test_history_stats.py b/tests/components/history_stats/test_sensor.py similarity index 99% rename from tests/components/sensor/test_history_stats.py rename to tests/components/history_stats/test_sensor.py index eda0034c922..05a2d585d16 100644 --- a/tests/components/sensor/test_history_stats.py +++ b/tests/components/history_stats/test_sensor.py @@ -9,7 +9,7 @@ from homeassistant.helpers import template from homeassistant.const import STATE_UNKNOWN from homeassistant.setup import setup_component -from homeassistant.components.sensor.history_stats import HistoryStatsSensor +from homeassistant.components.history_stats.sensor import HistoryStatsSensor import homeassistant.core as ha from homeassistant.helpers.template import Template import homeassistant.util.dt as dt_util diff --git a/tests/components/homeassistant/__init__.py b/tests/components/homeassistant/__init__.py new file mode 100644 index 00000000000..334751e6de5 --- /dev/null +++ b/tests/components/homeassistant/__init__.py @@ -0,0 +1 @@ +"""Tests for the Home Assistant integration to provide core functionality.""" diff --git a/tests/components/init/test_init.py b/tests/components/homeassistant/test_init.py similarity index 94% rename from tests/components/init/test_init.py rename to tests/components/homeassistant/test_init.py index da06927db3a..b72589e60e3 100644 --- a/tests/components/init/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -12,7 +12,8 @@ from homeassistant.const import ( SERVICE_HOMEASSISTANT_STOP, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE) import homeassistant.components as comps -from homeassistant.components import ( +from homeassistant.setup import async_setup_component +from homeassistant.components.homeassistant import ( SERVICE_CHECK_CONFIG, SERVICE_RELOAD_CORE_CONFIG) import homeassistant.helpers.intent as intent from homeassistant.exceptions import HomeAssistantError @@ -97,7 +98,8 @@ class TestComponentsCore(unittest.TestCase): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() assert run_coroutine_threadsafe( - comps.async_setup(self.hass, {}), self.hass.loop + async_setup_component(self.hass, 'homeassistant', {}), + self.hass.loop ).result() self.hass.states.set('light.Bowl', STATE_ON) @@ -186,7 +188,7 @@ class TestComponentsCore(unittest.TestCase): assert state.attributes.get('hello') == 'world' @patch('homeassistant.config.os.path.isfile', Mock(return_value=True)) - @patch('homeassistant.components._LOGGER.error') + @patch('homeassistant.components.homeassistant._LOGGER.error') @patch('homeassistant.config.async_process_ha_core_config') def test_reload_core_with_wrong_conf(self, mock_process, mock_error): """Test reload core conf service.""" @@ -244,7 +246,7 @@ class TestComponentsCore(unittest.TestCase): async def test_turn_on_intent(hass): """Test HassTurnOn intent.""" - result = await comps.async_setup(hass, {}) + result = await async_setup_component(hass, 'homeassistant', {}) assert result hass.states.async_set('light.test_light', 'off') @@ -265,7 +267,7 @@ async def test_turn_on_intent(hass): async def test_turn_off_intent(hass): """Test HassTurnOff intent.""" - result = await comps.async_setup(hass, {}) + result = await async_setup_component(hass, 'homeassistant', {}) assert result hass.states.async_set('light.test_light', 'on') @@ -286,7 +288,7 @@ async def test_turn_off_intent(hass): async def test_toggle_intent(hass): """Test HassToggle intent.""" - result = await comps.async_setup(hass, {}) + result = await async_setup_component(hass, 'homeassistant', {}) assert result hass.states.async_set('light.test_light', 'off') @@ -310,7 +312,7 @@ async def test_turn_on_multiple_intent(hass): This tests that matching finds the proper entity among similar names. """ - result = await comps.async_setup(hass, {}) + result = await async_setup_component(hass, 'homeassistant', {}) assert result hass.states.async_set('light.test_light', 'off') @@ -333,7 +335,7 @@ async def test_turn_on_multiple_intent(hass): async def test_turn_on_to_not_block_for_domains_without_service(hass): """Test if turn_on is blocking domain with no service.""" - await comps.async_setup(hass, {}) + await async_setup_component(hass, 'homeassistant', {}) async_mock_service(hass, 'light', SERVICE_TURN_ON) hass.states.async_set('light.Bowl', STATE_ON) hass.states.async_set('light.Ceiling', STATE_OFF) @@ -359,7 +361,7 @@ async def test_turn_on_to_not_block_for_domains_without_service(hass): async def test_entity_update(hass): """Test being able to call entity update.""" - await comps.async_setup(hass, {}) + await async_setup_component(hass, 'homeassistant', {}) with patch('homeassistant.helpers.entity_component.async_update_entity', return_value=mock_coro()) as mock_update: diff --git a/tests/components/homekit_controller/__init__.py b/tests/components/homekit_controller/__init__.py new file mode 100644 index 00000000000..364f8d7beeb --- /dev/null +++ b/tests/components/homekit_controller/__init__.py @@ -0,0 +1 @@ +"""Tests for homekit_controller component.""" diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index 0447de97929..2d659d42dfb 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -1,4 +1,5 @@ """Code to support homekit_controller tests.""" +import json from datetime import timedelta from unittest import mock @@ -7,8 +8,9 @@ from homekit.model.characteristics import ( AbstractCharacteristic, CharacteristicPermissions, CharacteristicsTypes) from homekit.model import Accessory, get_id from homekit.exceptions import AccessoryNotFoundError -from homeassistant.components.homekit_controller import ( - DOMAIN, HOMEKIT_ACCESSORY_DISPATCH, SERVICE_HOMEKIT) +from homeassistant.components.homekit_controller import SERVICE_HOMEKIT +from homeassistant.components.homekit_controller.const import ( + DOMAIN, HOMEKIT_ACCESSORY_DISPATCH) from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed, fire_service_discovered @@ -147,6 +149,41 @@ class FakeService(AbstractService): return char +def setup_accessories_from_file(path): + """Load an collection of accessory defs from JSON data.""" + with open(path, 'r') as accessories_data: + accessories_json = json.load(accessories_data) + + accessories = [] + + for accessory_data in accessories_json: + accessory = Accessory('Name', 'Mfr', 'Model', '0001', '0.1') + accessory.services = [] + accessory.aid = accessory_data['aid'] + for service_data in accessory_data['services']: + service = FakeService('public.hap.service.accessory-information') + service.type = service_data['type'] + service.iid = service_data['iid'] + + for char_data in service_data['characteristics']: + char = FakeCharacteristic(1, '23', None) + char.type = char_data['type'] + char.iid = char_data['iid'] + char.perms = char_data['perms'] + char.format = char_data['format'] + if 'description' in char_data: + char.description = char_data['description'] + if 'value' in char_data: + char.value = char_data['value'] + service.characteristics.append(char) + + accessory.services.append(service) + + accessories.append(accessory) + + return accessories + + async def setup_platform(hass): """Load the platform but with a fake Controller API.""" config = { @@ -161,6 +198,30 @@ async def setup_platform(hass): return fake_controller +async def setup_test_accessories(hass, accessories, capitalize=False): + """Load a fake homekit accessory based on a homekit accessory model. + + If capitalize is True, property names will be in upper case. + """ + fake_controller = await setup_platform(hass) + pairing = fake_controller.add(accessories) + + discovery_info = { + 'host': '127.0.0.1', + 'port': 8080, + 'properties': { + ('MD' if capitalize else 'md'): 'TestDevice', + ('ID' if capitalize else 'id'): '00:00:00:00:00:00', + ('C#' if capitalize else 'c#'): 1, + } + } + + fire_service_discovered(hass, SERVICE_HOMEKIT, discovery_info) + await hass.async_block_till_done() + + return pairing + + async def setup_test_component(hass, services, capitalize=False, suffix=None): """Load a fake homekit accessory based on a homekit accessory model. @@ -177,24 +238,10 @@ async def setup_test_component(hass, services, capitalize=False, suffix=None): assert domain, 'Cannot map test homekit services to homeassistant domain' - fake_controller = await setup_platform(hass) - accessory = Accessory('TestDevice', 'example.com', 'Test', '0001', '0.1') accessory.services.extend(services) - pairing = fake_controller.add([accessory]) - discovery_info = { - 'host': '127.0.0.1', - 'port': 8080, - 'properties': { - ('MD' if capitalize else 'md'): 'TestDevice', - ('ID' if capitalize else 'id'): '00:00:00:00:00:00', - ('C#' if capitalize else 'c#'): 1, - } - } - - fire_service_discovered(hass, SERVICE_HOMEKIT, discovery_info) - await hass.async_block_till_done() + pairing = await setup_test_accessories(hass, [accessory], capitalize) entity = 'testdevice' if suffix is None else 'testdevice_{}'.format(suffix) return Helper(hass, '.'.join((domain, entity)), pairing, accessory) diff --git a/tests/components/homekit_controller/specific_devices/koogeek_ls1.json b/tests/components/homekit_controller/specific_devices/koogeek_ls1.json new file mode 100644 index 00000000000..9b05ce76639 --- /dev/null +++ b/tests/components/homekit_controller/specific_devices/koogeek_ls1.json @@ -0,0 +1,244 @@ +[ + { + "aid": 1, + "services": [ + { + "characteristics": [ + { + "format": "string", + "iid": 2, + "maxLen": 64, + "perms": [ + "pr" + ], + "type": "23", + "value": "Koogeek-LS1-20833F" + }, + { + "format": "string", + "iid": 3, + "maxLen": 64, + "perms": [ + "pr" + ], + "type": "20", + "value": "Koogeek" + }, + { + "format": "string", + "iid": 4, + "maxLen": 64, + "perms": [ + "pr" + ], + "type": "21", + "value": "LS1" + }, + { + "format": "string", + "iid": 5, + "maxLen": 64, + "perms": [ + "pr" + ], + "type": "30", + "value": "AAAA011111111111" + }, + { + "format": "bool", + "iid": 6, + "perms": [ + "pw" + ], + "type": "14" + }, + { + "format": "string", + "iid": 23, + "perms": [ + "pr" + ], + "type": "52", + "value": "2.2.15" + } + ], + "iid": 1, + "type": "3E" + }, + { + "characteristics": [ + { + "ev": false, + "format": "bool", + "iid": 8, + "perms": [ + "pr", + "pw", + "ev" + ], + "type": "25", + "value": false + }, + { + "ev": false, + "format": "float", + "iid": 9, + "maxValue": 359, + "minStep": 1, + "minValue": 0, + "perms": [ + "pr", + "pw", + "ev" + ], + "type": "13", + "unit": "arcdegrees", + "value": 44 + }, + { + "ev": false, + "format": "float", + "iid": 10, + "maxValue": 100, + "minStep": 1, + "minValue": 0, + "perms": [ + "pr", + "pw", + "ev" + ], + "type": "2F", + "unit": "percentage", + "value": 0 + }, + { + "ev": false, + "format": "int", + "iid": 11, + "maxValue": 100, + "minStep": 1, + "minValue": 0, + "perms": [ + "pr", + "pw", + "ev" + ], + "type": "8", + "unit": "percentage", + "value": 100 + }, + { + "format": "string", + "iid": 12, + "maxLen": 64, + "perms": [ + "pr" + ], + "type": "23", + "value": "Light Strip" + } + ], + "iid": 7, + "primary": true, + "type": "43" + }, + { + "characteristics": [ + { + "description": "TIMER_SETTINGS", + "format": "tlv8", + "iid": 14, + "perms": [ + "pr", + "pw" + ], + "type": "4aaaf942-0dec-11e5-b939-0800200c9a66", + "value": "AHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + } + ], + "iid": 13, + "type": "4aaaf940-0dec-11e5-b939-0800200c9a66" + }, + { + "characteristics": [ + { + "description": "FW Upgrade supported types", + "format": "string", + "iid": 16, + "perms": [ + "pr", + "hd" + ], + "type": "151909D2-3802-11E4-916C-0800200C9A66", + "value": "url,data" + }, + { + "description": "FW Upgrade URL", + "format": "string", + "iid": 17, + "maxLen": 256, + "perms": [ + "pw", + "hd" + ], + "type": "151909D1-3802-11E4-916C-0800200C9A66" + }, + { + "description": "FW Upgrade Status", + "ev": false, + "format": "int", + "iid": 18, + "perms": [ + "pr", + "ev", + "hd" + ], + "type": "151909D6-3802-11E4-916C-0800200C9A66", + "value": 0 + }, + { + "description": "FW Upgrade Data", + "format": "data", + "iid": 19, + "perms": [ + "pw", + "hd" + ], + "type": "151909D7-3802-11E4-916C-0800200C9A66" + } + ], + "hidden": true, + "iid": 15, + "type": "151909D0-3802-11E4-916C-0800200C9A66" + }, + { + "characteristics": [ + { + "description": "Timezone", + "format": "int", + "iid": 21, + "perms": [ + "pr", + "pw" + ], + "type": "151909D5-3802-11E4-916C-0800200C9A66", + "value": 0 + }, + { + "description": "Time value since Epoch", + "format": "int", + "iid": 22, + "perms": [ + "pr", + "pw" + ], + "type": "151909D4-3802-11E4-916C-0800200C9A66", + "value": 1550348623 + } + ], + "iid": 20, + "type": "151909D3-3802-11E4-916C-0800200C9A66" + } + ] + } +] \ No newline at end of file diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py new file mode 100644 index 00000000000..7b7981cf6de --- /dev/null +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py @@ -0,0 +1,85 @@ +"""Make sure that existing Koogeek LS1 support isn't broken.""" + +import os +from datetime import timedelta +from unittest import mock + +import pytest + +from homekit.exceptions import AccessoryDisconnectedError, EncryptionError +import homeassistant.util.dt as dt_util +from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR +from tests.common import async_fire_time_changed +from tests.components.homekit_controller.common import ( + setup_accessories_from_file, setup_test_accessories, FakePairing, Helper +) + +LIGHT_ON = ('lightbulb', 'on') + + +async def test_koogeek_ls1_setup(hass): + """Test that a Koogeek LS1 can be correctly setup in HA.""" + profile_path = os.path.join(os.path.dirname(__file__), 'koogeek_ls1.json') + accessories = setup_accessories_from_file(profile_path) + pairing = await setup_test_accessories(hass, accessories) + + entity_registry = await hass.helpers.entity_registry.async_get_registry() + + # Assert that the entity is correctly added to the entity registry + entry = entity_registry.async_get('light.koogeek_ls1_20833f') + assert entry.unique_id == 'homekit-AAAA011111111111-7' + + helper = Helper(hass, 'light.koogeek_ls1_20833f', pairing, accessories[0]) + state = await helper.poll_and_get_state() + + # Assert that the friendly name is detected correctly + assert state.attributes['friendly_name'] == 'Koogeek-LS1-20833F' + + # Assert that all optional features the LS1 supports are detected + assert state.attributes['supported_features'] == ( + SUPPORT_BRIGHTNESS | SUPPORT_COLOR + ) + + +@pytest.mark.parametrize('failure_cls', [ + AccessoryDisconnectedError, EncryptionError +]) +async def test_recover_from_failure(hass, utcnow, failure_cls): + """ + Test that entity actually recovers from a network connection drop. + + See https://github.com/home-assistant/home-assistant/issues/18949 + """ + profile_path = os.path.join(os.path.dirname(__file__), 'koogeek_ls1.json') + accessories = setup_accessories_from_file(profile_path) + pairing = await setup_test_accessories(hass, accessories) + + helper = Helper(hass, 'light.koogeek_ls1_20833f', pairing, accessories[0]) + + # Set light state on fake device to off + helper.characteristics[LIGHT_ON].set_value(False) + + # Test that entity starts off in a known state + state = await helper.poll_and_get_state() + assert state.state == 'off' + + # Set light state on fake device to on + helper.characteristics[LIGHT_ON].set_value(True) + + # Test that entity remains in the same state if there is a network error + next_update = dt_util.utcnow() + timedelta(seconds=60) + with mock.patch.object(FakePairing, 'get_characteristics') as get_char: + get_char.side_effect = failure_cls('Disconnected') + + state = await helper.poll_and_get_state() + assert state.state == 'off' + + get_char.assert_called_with([(1, 8), (1, 9), (1, 10), (1, 11)]) + + # Test that entity changes state when network error goes away + next_update += timedelta(seconds=60) + async_fire_time_changed(hass, next_update) + await hass.async_block_till_done() + + state = await helper.poll_and_get_state() + assert state.state == 'on' diff --git a/tests/components/homematic/__init__.py b/tests/components/homematic/__init__.py new file mode 100644 index 00000000000..9a021f82a7f --- /dev/null +++ b/tests/components/homematic/__init__.py @@ -0,0 +1 @@ +"""Tests for the homematic component.""" diff --git a/tests/components/notify/test_homematic.py b/tests/components/homematic/test_notify.py similarity index 100% rename from tests/components/notify/test_homematic.py rename to tests/components/homematic/test_notify.py diff --git a/tests/components/honeywell/__init__.py b/tests/components/honeywell/__init__.py new file mode 100644 index 00000000000..7c6b4ca78c6 --- /dev/null +++ b/tests/components/honeywell/__init__.py @@ -0,0 +1 @@ +"""Tests for honeywell component.""" diff --git a/tests/components/climate/test_honeywell.py b/tests/components/honeywell/test_climate.py similarity index 96% rename from tests/components/climate/test_honeywell.py rename to tests/components/honeywell/test_climate.py index b01b5b35c35..e8e0c0a2929 100644 --- a/tests/components/climate/test_honeywell.py +++ b/tests/components/honeywell/test_climate.py @@ -11,7 +11,7 @@ from homeassistant.const import ( from homeassistant.components.climate.const import ( ATTR_FAN_MODE, ATTR_OPERATION_MODE, ATTR_FAN_LIST, ATTR_OPERATION_LIST) -import homeassistant.components.climate.honeywell as honeywell +import homeassistant.components.honeywell.climate as honeywell import pytest @@ -19,8 +19,8 @@ class TestHoneywell(unittest.TestCase): """A test class for Honeywell themostats.""" @mock.patch('somecomfort.SomeComfort') - @mock.patch('homeassistant.components.climate.' - 'honeywell.HoneywellUSThermostat') + @mock.patch('homeassistant.components.honeywell.' + 'climate.HoneywellUSThermostat') def test_setup_us(self, mock_ht, mock_sc): """Test for the US setup.""" config = { @@ -105,8 +105,8 @@ class TestHoneywell(unittest.TestCase): assert not add_entities.called @mock.patch('somecomfort.SomeComfort') - @mock.patch('homeassistant.components.climate.' - 'honeywell.HoneywellUSThermostat') + @mock.patch('homeassistant.components.honeywell.' + 'climate.HoneywellUSThermostat') def _test_us_filtered_devices(self, mock_ht, mock_sc, loc=None, dev=None): """Test for US filtered thermostats.""" config = { @@ -171,7 +171,7 @@ class TestHoneywell(unittest.TestCase): assert [mock.sentinel.loc2dev1] == devices @mock.patch('evohomeclient.EvohomeClient') - @mock.patch('homeassistant.components.climate.honeywell.' + @mock.patch('homeassistant.components.honeywell.climate.' 'RoundThermostat') def test_eu_setup_full_config(self, mock_round, mock_evo): """Test the EU setup with complete configuration.""" @@ -198,7 +198,7 @@ class TestHoneywell(unittest.TestCase): assert 2 == add_entities.call_count @mock.patch('evohomeclient.EvohomeClient') - @mock.patch('homeassistant.components.climate.honeywell.' + @mock.patch('homeassistant.components.honeywell.climate.' 'RoundThermostat') def test_eu_setup_partial_config(self, mock_round, mock_evo): """Test the EU setup with partial configuration.""" @@ -222,7 +222,7 @@ class TestHoneywell(unittest.TestCase): ]) @mock.patch('evohomeclient.EvohomeClient') - @mock.patch('homeassistant.components.climate.honeywell.' + @mock.patch('homeassistant.components.honeywell.climate.' 'RoundThermostat') def test_eu_setup_bad_temp(self, mock_round, mock_evo): """Test the EU setup with invalid temperature.""" @@ -237,7 +237,7 @@ class TestHoneywell(unittest.TestCase): honeywell.PLATFORM_SCHEMA(config) @mock.patch('evohomeclient.EvohomeClient') - @mock.patch('homeassistant.components.climate.honeywell.' + @mock.patch('homeassistant.components.honeywell.climate.' 'RoundThermostat') def test_eu_setup_error(self, mock_round, mock_evo): """Test the EU setup with errors.""" diff --git a/tests/components/html5/__init__.py b/tests/components/html5/__init__.py new file mode 100644 index 00000000000..48106286f2e --- /dev/null +++ b/tests/components/html5/__init__.py @@ -0,0 +1 @@ +"""Tests for the html5 component.""" diff --git a/tests/components/notify/test_html5.py b/tests/components/html5/test_notify.py similarity index 94% rename from tests/components/notify/test_html5.py rename to tests/components/html5/test_notify.py index e33c297b166..140544bf9ea 100644 --- a/tests/components/notify/test_html5.py +++ b/tests/components/html5/test_notify.py @@ -5,7 +5,7 @@ from aiohttp.hdrs import AUTHORIZATION from homeassistant.setup import async_setup_component from homeassistant.exceptions import HomeAssistantError -from homeassistant.components.notify import html5 +import homeassistant.components.html5.notify as html5 CONFIG_FILE = 'file.conf' @@ -54,7 +54,7 @@ async def mock_client(hass, hass_client, registrations=None): if registrations is None: registrations = {} - with patch('homeassistant.components.notify.html5._load_config', + with patch('homeassistant.components.html5.notify._load_config', return_value=registrations): await async_setup_component(hass, 'notify', { 'notify': { @@ -189,7 +189,7 @@ async def test_registering_new_device_view(hass, hass_client): """Test that the HTML view works.""" client = await mock_client(hass, hass_client) - with patch('homeassistant.components.notify.html5.save_json') as mock_save: + with patch('homeassistant.components.html5.notify.save_json') as mock_save: resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_1)) assert resp.status == 200 @@ -206,7 +206,7 @@ async def test_registering_new_device_view_with_name(hass, hass_client): SUB_WITH_NAME = SUBSCRIPTION_1.copy() SUB_WITH_NAME['name'] = 'test device' - with patch('homeassistant.components.notify.html5.save_json') as mock_save: + with patch('homeassistant.components.html5.notify.save_json') as mock_save: resp = await client.post(REGISTER_URL, data=json.dumps(SUB_WITH_NAME)) assert resp.status == 200 @@ -220,7 +220,7 @@ async def test_registering_new_device_expiration_view(hass, hass_client): """Test that the HTML view works.""" client = await mock_client(hass, hass_client) - with patch('homeassistant.components.notify.html5.save_json') as mock_save: + with patch('homeassistant.components.html5.notify.save_json') as mock_save: resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_4)) assert resp.status == 200 @@ -234,7 +234,7 @@ async def test_registering_new_device_fails_view(hass, hass_client): registrations = {} client = await mock_client(hass, hass_client, registrations) - with patch('homeassistant.components.notify.html5.save_json', + with patch('homeassistant.components.html5.notify.save_json', side_effect=HomeAssistantError()): resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_4)) @@ -247,7 +247,7 @@ async def test_registering_existing_device_view(hass, hass_client): registrations = {} client = await mock_client(hass, hass_client, registrations) - with patch('homeassistant.components.notify.html5.save_json') as mock_save: + with patch('homeassistant.components.html5.notify.save_json') as mock_save: await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_1)) resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_4)) @@ -268,7 +268,7 @@ async def test_registering_existing_device_view_with_name(hass, hass_client): SUB_WITH_NAME = SUBSCRIPTION_1.copy() SUB_WITH_NAME['name'] = 'test device' - with patch('homeassistant.components.notify.html5.save_json') as mock_save: + with patch('homeassistant.components.html5.notify.save_json') as mock_save: await client.post(REGISTER_URL, data=json.dumps(SUB_WITH_NAME)) resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_4)) @@ -286,7 +286,7 @@ async def test_registering_existing_device_fails_view(hass, hass_client): registrations = {} client = await mock_client(hass, hass_client, registrations) - with patch('homeassistant.components.notify.html5.save_json') as mock_save: + with patch('homeassistant.components.html5.notify.save_json') as mock_save: await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_1)) mock_save.side_effect = HomeAssistantError resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_4)) @@ -312,7 +312,7 @@ async def test_registering_new_device_validation(hass, hass_client): })) assert resp.status == 400 - with patch('homeassistant.components.notify.html5.save_json', + with patch('homeassistant.components.html5.notify.save_json', return_value=False): resp = await client.post(REGISTER_URL, data=json.dumps({ 'browser': 'chrome', @@ -329,7 +329,7 @@ async def test_unregistering_device_view(hass, hass_client): } client = await mock_client(hass, hass_client, registrations) - with patch('homeassistant.components.notify.html5.save_json') as mock_save: + with patch('homeassistant.components.html5.notify.save_json') as mock_save: resp = await client.delete(REGISTER_URL, data=json.dumps({ 'subscription': SUBSCRIPTION_1['subscription'], })) @@ -347,7 +347,7 @@ async def test_unregister_device_view_handle_unknown_subscription( registrations = {} client = await mock_client(hass, hass_client, registrations) - with patch('homeassistant.components.notify.html5.save_json') as mock_save: + with patch('homeassistant.components.html5.notify.save_json') as mock_save: resp = await client.delete(REGISTER_URL, data=json.dumps({ 'subscription': SUBSCRIPTION_3['subscription'] })) @@ -366,7 +366,7 @@ async def test_unregistering_device_view_handles_save_error( } client = await mock_client(hass, hass_client, registrations) - with patch('homeassistant.components.notify.html5.save_json', + with patch('homeassistant.components.html5.notify.save_json', side_effect=HomeAssistantError()): resp = await client.delete(REGISTER_URL, data=json.dumps({ 'subscription': SUBSCRIPTION_1['subscription'], diff --git a/tests/components/hydroquebec/__init__.py b/tests/components/hydroquebec/__init__.py new file mode 100644 index 00000000000..1342395d265 --- /dev/null +++ b/tests/components/hydroquebec/__init__.py @@ -0,0 +1 @@ +"""Tests for the hydroquebec component.""" diff --git a/tests/components/sensor/test_hydroquebec.py b/tests/components/hydroquebec/test_sensor.py similarity index 97% rename from tests/components/sensor/test_hydroquebec.py rename to tests/components/hydroquebec/test_sensor.py index eed5ab4a6a6..e7883bb9853 100644 --- a/tests/components/sensor/test_hydroquebec.py +++ b/tests/components/hydroquebec/test_sensor.py @@ -5,7 +5,7 @@ import sys from unittest.mock import MagicMock from homeassistant.bootstrap import async_setup_component -from homeassistant.components.sensor import hydroquebec +from homeassistant.components.hydroquebec import sensor as hydroquebec from tests.common import assert_setup_component diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py index 7a31b2ffadf..86f5f820be3 100644 --- a/tests/components/image_processing/test_init.py +++ b/tests/components/image_processing/test_init.py @@ -80,7 +80,7 @@ class TestImageProcessing: """Stop everything that was started.""" self.hass.stop() - @patch('homeassistant.components.camera.demo.DemoCamera.camera_image', + @patch('homeassistant.components.demo.camera.DemoCamera.camera_image', autospec=True, return_value=b'Test') def test_get_image_from_camera(self, mock_camera): """Grab an image from camera entity.""" @@ -126,7 +126,7 @@ class TestImageProcessingAlpr: }, } - with patch('homeassistant.components.image_processing.demo.' + with patch('homeassistant.components.demo.image_processing.' 'DemoImageProcessingAlpr.should_poll', new_callable=PropertyMock(return_value=False)): setup_component(self.hass, ip.DOMAIN, config) @@ -188,7 +188,7 @@ class TestImageProcessingAlpr: assert event_data[0]['confidence'] == 98.3 assert event_data[0]['entity_id'] == 'image_processing.demo_alpr' - @patch('homeassistant.components.image_processing.demo.' + @patch('homeassistant.components.demo.image_processing.' 'DemoImageProcessingAlpr.confidence', new_callable=PropertyMock(return_value=95)) def test_alpr_event_single_call_confidence(self, confidence_mock, @@ -228,7 +228,7 @@ class TestImageProcessingFace: }, } - with patch('homeassistant.components.image_processing.demo.' + with patch('homeassistant.components.demo.image_processing.' 'DemoImageProcessingFace.should_poll', new_callable=PropertyMock(return_value=False)): setup_component(self.hass, ip.DOMAIN, config) @@ -273,7 +273,7 @@ class TestImageProcessingFace: assert event_data[0]['entity_id'] == \ 'image_processing.demo_face' - @patch('homeassistant.components.image_processing.demo.' + @patch('homeassistant.components.demo.image_processing.' 'DemoImageProcessingFace.confidence', new_callable=PropertyMock(return_value=None)) def test_face_event_call_no_confidence(self, mock_config, aioclient_mock): diff --git a/tests/components/imap_email_content/__init__.py b/tests/components/imap_email_content/__init__.py new file mode 100644 index 00000000000..2c7e5692366 --- /dev/null +++ b/tests/components/imap_email_content/__init__.py @@ -0,0 +1 @@ +"""Tests for the imap_email_content component.""" diff --git a/tests/components/sensor/test_imap_email_content.py b/tests/components/imap_email_content/test_sensor.py similarity index 98% rename from tests/components/sensor/test_imap_email_content.py rename to tests/components/imap_email_content/test_sensor.py index a0cfb783d0b..2afb3c39341 100644 --- a/tests/components/sensor/test_imap_email_content.py +++ b/tests/components/imap_email_content/test_sensor.py @@ -8,7 +8,8 @@ import unittest from homeassistant.helpers.template import Template from homeassistant.helpers.event import track_state_change -from homeassistant.components.sensor import imap_email_content +from homeassistant.components.imap_email_content \ + import sensor as imap_email_content from tests.common import get_test_home_assistant diff --git a/tests/components/init/__init__.py b/tests/components/init/__init__.py deleted file mode 100644 index b935cf060c8..00000000000 --- a/tests/components/init/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the init component.""" diff --git a/tests/components/integration/__init__.py b/tests/components/integration/__init__.py new file mode 100644 index 00000000000..5dd7e83b062 --- /dev/null +++ b/tests/components/integration/__init__.py @@ -0,0 +1 @@ +"""Tests for the integration component.""" diff --git a/tests/components/sensor/test_integration.py b/tests/components/integration/test_sensor.py similarity index 100% rename from tests/components/sensor/test_integration.py rename to tests/components/integration/test_sensor.py diff --git a/tests/components/islamic_prayer_times/__init__.py b/tests/components/islamic_prayer_times/__init__.py new file mode 100644 index 00000000000..4a2f0002516 --- /dev/null +++ b/tests/components/islamic_prayer_times/__init__.py @@ -0,0 +1 @@ +"""Tests for the islamic_prayer_times component.""" diff --git a/tests/components/sensor/test_islamic_prayer_times.py b/tests/components/islamic_prayer_times/test_sensor.py similarity index 97% rename from tests/components/sensor/test_islamic_prayer_times.py rename to tests/components/islamic_prayer_times/test_sensor.py index 2c132ac5a8f..5310fb6befb 100644 --- a/tests/components/sensor/test_islamic_prayer_times.py +++ b/tests/components/islamic_prayer_times/test_sensor.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta from unittest.mock import patch from homeassistant.setup import async_setup_component -from homeassistant.components.sensor.islamic_prayer_times import \ +from homeassistant.components.islamic_prayer_times.sensor import \ IslamicPrayerTimesData from tests.common import MockDependency import homeassistant.util.dt as dt_util @@ -154,7 +154,7 @@ async def test_islamic_prayer_times_sensor_update(hass): future = midnight_dt + timedelta(days=1, minutes=1) with patch( - 'homeassistant.components.sensor.islamic_prayer_times' + 'homeassistant.components.islamic_prayer_times.sensor' '.dt_util.utcnow', return_value=future): async_fire_time_changed(hass, future) diff --git a/tests/components/jewish_calendar/__init__.py b/tests/components/jewish_calendar/__init__.py new file mode 100644 index 00000000000..d6928c189e8 --- /dev/null +++ b/tests/components/jewish_calendar/__init__.py @@ -0,0 +1 @@ +"""Tests for the jewish_calendar component.""" diff --git a/tests/components/sensor/test_jewish_calendar.py b/tests/components/jewish_calendar/test_sensor.py similarity index 99% rename from tests/components/sensor/test_jewish_calendar.py rename to tests/components/jewish_calendar/test_sensor.py index 7243874a41d..6a7f9249fe1 100644 --- a/tests/components/sensor/test_jewish_calendar.py +++ b/tests/components/jewish_calendar/test_sensor.py @@ -9,7 +9,7 @@ import pytest from homeassistant.util.async_ import run_coroutine_threadsafe from homeassistant.util.dt import get_time_zone, set_default_time_zone from homeassistant.setup import setup_component -from homeassistant.components.sensor.jewish_calendar import ( +from homeassistant.components.jewish_calendar.sensor import ( JewishCalSensor, CANDLE_LIGHT_DEFAULT) from tests.common import get_test_home_assistant diff --git a/tests/components/light/test_litejet.py b/tests/components/litejet/test_light.py similarity index 100% rename from tests/components/light/test_litejet.py rename to tests/components/litejet/test_light.py diff --git a/tests/components/scene/test_litejet.py b/tests/components/litejet/test_scene.py similarity index 100% rename from tests/components/scene/test_litejet.py rename to tests/components/litejet/test_scene.py diff --git a/tests/components/switch/test_litejet.py b/tests/components/litejet/test_switch.py similarity index 100% rename from tests/components/switch/test_litejet.py rename to tests/components/litejet/test_switch.py diff --git a/tests/components/local_file/__init__.py b/tests/components/local_file/__init__.py new file mode 100644 index 00000000000..1a76dca2377 --- /dev/null +++ b/tests/components/local_file/__init__.py @@ -0,0 +1 @@ +"""Tests for the local_file component.""" diff --git a/tests/components/camera/test_local_file.py b/tests/components/local_file/test_camera.py similarity index 95% rename from tests/components/camera/test_local_file.py rename to tests/components/local_file/test_camera.py index f2dbb294136..a96f9768be4 100644 --- a/tests/components/camera/test_local_file.py +++ b/tests/components/local_file/test_camera.py @@ -2,8 +2,8 @@ import asyncio from unittest import mock -from homeassistant.components.camera import DOMAIN -from homeassistant.components.camera.local_file import ( +from homeassistant.components.camera.const import DOMAIN +from homeassistant.components.local_file.camera import ( SERVICE_UPDATE_FILE_PATH) from homeassistant.setup import async_setup_component @@ -28,7 +28,7 @@ def test_loading_file(hass, hass_client): m_open = mock.mock_open(read_data=b'hello') with mock.patch( - 'homeassistant.components.camera.local_file.open', + 'homeassistant.components.local_file.camera.open', m_open, create=True ): resp = yield from client.get('/api/camera_proxy/camera.config_test') @@ -87,7 +87,7 @@ def test_camera_content_type(hass, hass_client): image = 'hello' m_open = mock.mock_open(read_data=image.encode()) - with mock.patch('homeassistant.components.camera.local_file.open', + with mock.patch('homeassistant.components.local_file.camera.open', m_open, create=True): resp_1 = yield from client.get('/api/camera_proxy/camera.test_jpg') resp_2 = yield from client.get('/api/camera_proxy/camera.test_png') diff --git a/tests/components/london_air/__init__.py b/tests/components/london_air/__init__.py new file mode 100644 index 00000000000..a7d3e8bb1db --- /dev/null +++ b/tests/components/london_air/__init__.py @@ -0,0 +1 @@ +"""Tests for the london_air component.""" diff --git a/tests/components/sensor/test_london_air.py b/tests/components/london_air/test_sensor.py similarity index 95% rename from tests/components/sensor/test_london_air.py rename to tests/components/london_air/test_sensor.py index e6a11b6724c..d30cc39d808 100644 --- a/tests/components/sensor/test_london_air.py +++ b/tests/components/london_air/test_sensor.py @@ -2,7 +2,7 @@ import unittest import requests_mock -from homeassistant.components.sensor.london_air import ( +from homeassistant.components.london_air.sensor import ( CONF_LOCATIONS, URL) from homeassistant.setup import setup_component from tests.common import load_fixture, get_test_home_assistant diff --git a/tests/components/manual/__init__.py b/tests/components/manual/__init__.py new file mode 100644 index 00000000000..ac0fc57ac6e --- /dev/null +++ b/tests/components/manual/__init__.py @@ -0,0 +1 @@ +"""Tests for manual component.""" diff --git a/tests/components/alarm_control_panel/test_manual.py b/tests/components/manual/test_alarm_control_panel.py similarity index 94% rename from tests/components/alarm_control_panel/test_manual.py rename to tests/components/manual/test_alarm_control_panel.py index 36bae21dc32..a6e59af64d5 100644 --- a/tests/components/alarm_control_panel/test_manual.py +++ b/tests/components/manual/test_alarm_control_panel.py @@ -1,7 +1,7 @@ """The tests for the manual Alarm Control Panel component.""" from datetime import timedelta from unittest.mock import patch, MagicMock -from homeassistant.components.alarm_control_panel import demo +from homeassistant.components.demo import alarm_control_panel as demo from homeassistant.setup import async_setup_component from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, @@ -76,7 +76,7 @@ async def test_arm_home_with_pending(hass): assert state.attributes['post_pending_state'] == STATE_ALARM_ARMED_HOME future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -184,7 +184,7 @@ async def test_arm_away_with_pending(hass): assert state.attributes['post_pending_state'] == STATE_ALARM_ARMED_AWAY future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -269,7 +269,7 @@ async def test_arm_night_with_pending(hass): STATE_ALARM_ARMED_NIGHT future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -332,7 +332,7 @@ async def test_trigger_no_pending(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=60) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -374,7 +374,7 @@ async def test_trigger_with_delay(hass): state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -458,7 +458,7 @@ async def test_trigger_with_pending(hass): assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=2) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -467,7 +467,7 @@ async def test_trigger_with_pending(hass): assert state.state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -512,7 +512,7 @@ async def test_trigger_with_unused_specific_delay(hass): state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -557,7 +557,7 @@ async def test_trigger_with_specific_delay(hass): state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -601,7 +601,7 @@ async def test_trigger_with_pending_and_delay(hass): assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -611,7 +611,7 @@ async def test_trigger_with_pending_and_delay(hass): assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED future += timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -658,7 +658,7 @@ async def test_trigger_with_pending_and_specific_delay(hass): assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -668,7 +668,7 @@ async def test_trigger_with_pending_and_specific_delay(hass): assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED future += timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -699,7 +699,7 @@ async def test_armed_home_with_specific_pending(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -730,7 +730,7 @@ async def test_armed_away_with_specific_pending(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -761,7 +761,7 @@ async def test_armed_night_with_specific_pending(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -794,7 +794,7 @@ async def test_trigger_with_specific_pending(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -803,7 +803,7 @@ async def test_trigger_with_specific_pending(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -836,7 +836,7 @@ async def test_trigger_with_disarm_after_trigger(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -899,7 +899,7 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -934,7 +934,7 @@ async def test_trigger_with_specific_trigger_time(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -973,7 +973,7 @@ async def test_trigger_with_no_disarm_after_trigger(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -1012,7 +1012,7 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -1027,7 +1027,7 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -1065,7 +1065,7 @@ async def test_disarm_while_pending_trigger(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -1104,7 +1104,7 @@ async def test_disarm_during_trigger_with_invalid_code(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -1202,7 +1202,7 @@ async def test_arm_custom_bypass_with_pending(hass): STATE_ALARM_ARMED_CUSTOM_BYPASS future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -1257,7 +1257,7 @@ async def test_armed_custom_bypass_with_specific_pending(hass): hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -1311,7 +1311,7 @@ async def test_arm_away_after_disabled_disarmed(hass): state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -1330,7 +1330,7 @@ async def test_arm_away_after_disabled_disarmed(hass): state.attributes['post_pending_state'] future += timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual.' + with patch(('homeassistant.components.manual.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() diff --git a/tests/components/manual_mqtt/__init__.py b/tests/components/manual_mqtt/__init__.py new file mode 100644 index 00000000000..e1b3f3c4e38 --- /dev/null +++ b/tests/components/manual_mqtt/__init__.py @@ -0,0 +1 @@ +"""Tests for manual_mqtt component.""" diff --git a/tests/components/alarm_control_panel/test_manual_mqtt.py b/tests/components/manual_mqtt/test_alarm_control_panel.py similarity index 95% rename from tests/components/alarm_control_panel/test_manual_mqtt.py rename to tests/components/manual_mqtt/test_alarm_control_panel.py index 3d063f8a34a..f5558331bce 100644 --- a/tests/components/alarm_control_panel/test_manual_mqtt.py +++ b/tests/components/manual_mqtt/test_alarm_control_panel.py @@ -106,7 +106,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): assert state.attributes['post_pending_state'] == STATE_ALARM_ARMED_HOME future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -221,7 +221,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): assert state.attributes['post_pending_state'] == STATE_ALARM_ARMED_AWAY future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -309,7 +309,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): STATE_ALARM_ARMED_NIGHT future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -374,7 +374,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=60) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -417,7 +417,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -504,7 +504,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=2) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -513,7 +513,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -547,7 +547,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -612,7 +612,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -648,7 +648,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -688,7 +688,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -703,7 +703,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -742,7 +742,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -782,7 +782,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -828,7 +828,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -874,7 +874,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -919,7 +919,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -929,7 +929,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED future += timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -977,7 +977,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -987,7 +987,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED future += timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1019,7 +1019,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1051,7 +1051,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1083,7 +1083,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1117,7 +1117,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1126,7 +1126,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1181,7 +1181,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1200,7 +1200,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): state.attributes['post_pending_state'] future += timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1275,7 +1275,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1309,7 +1309,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1343,7 +1343,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1409,7 +1409,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.mock_publish.async_publish.reset_mock() # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1425,7 +1425,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.mock_publish.async_publish.reset_mock() # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() @@ -1441,7 +1441,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.mock_publish.async_publish.reset_mock() # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) - with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' + with patch(('homeassistant.components.manual_mqtt.alarm_control_panel.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) self.hass.block_till_done() diff --git a/tests/components/marytts/__init__.py b/tests/components/marytts/__init__.py new file mode 100644 index 00000000000..061776a1398 --- /dev/null +++ b/tests/components/marytts/__init__.py @@ -0,0 +1 @@ +"""The tests for marytts tts platforms.""" diff --git a/tests/components/tts/test_marytts.py b/tests/components/marytts/test_tts.py similarity index 98% rename from tests/components/tts/test_marytts.py rename to tests/components/marytts/test_tts.py index 7520ba2fbaa..24915dd85c8 100644 --- a/tests/components/tts/test_marytts.py +++ b/tests/components/marytts/test_tts.py @@ -11,7 +11,7 @@ from homeassistant.components.media_player.const import ( from tests.common import ( get_test_home_assistant, assert_setup_component, mock_service) -from .test_init import mutagen_mock # noqa +from tests.components.tts.test_init import mutagen_mock # noqa class TestTTSMaryTTSPlatform: diff --git a/tests/components/climate/test_melissa.py b/tests/components/melissa/test_climate.py similarity index 95% rename from tests/components/climate/test_melissa.py rename to tests/components/melissa/test_climate.py index 6b77981a914..b6dc1a8de4f 100644 --- a/tests/components/climate/test_melissa.py +++ b/tests/components/melissa/test_climate.py @@ -2,9 +2,9 @@ from unittest.mock import Mock, patch import json -from homeassistant.components.climate.melissa import MelissaClimate +from homeassistant.components.melissa.climate import MelissaClimate -from homeassistant.components.climate import melissa +from homeassistant.components.melissa import climate as melissa from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_ON_OFF, SUPPORT_FAN_MODE, STATE_HEAT, STATE_FAN_ONLY, STATE_DRY, @@ -56,7 +56,7 @@ def melissa_mock(): async def test_setup_platform(hass): """Test setup_platform.""" - with patch("homeassistant.components.climate.melissa.MelissaClimate" + with patch("homeassistant.components.melissa.climate.MelissaClimate" ) as mocked_thermostat: api = melissa_mock() device = (await api.async_fetch_devices())[_SERIAL] @@ -319,9 +319,9 @@ async def test_send(hass): async def test_update(hass): """Test update.""" - with patch('homeassistant.components.melissa'): - with patch('homeassistant.components.climate.melissa._LOGGER.warning' - ) as mocked_warning: + with patch('homeassistant.components.melissa.climate._LOGGER.warning' + ) as mocked_warning: + with patch('homeassistant.components.melissa'): api = melissa_mock() device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) @@ -374,9 +374,9 @@ async def test_melissa_fan_to_hass(hass): async def test_hass_mode_to_melissa(hass): """Test for hass operations to melssa.""" - with patch('homeassistant.components.melissa'): - with patch('homeassistant.components.climate.melissa._LOGGER.warning' - ) as mocked_warning: + with patch('homeassistant.components.melissa.climate._LOGGER.warning' + ) as mocked_warning: + with patch('homeassistant.components.melissa'): api = melissa_mock() device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) @@ -391,9 +391,10 @@ async def test_hass_mode_to_melissa(hass): async def test_hass_fan_to_melissa(hass): """Test for translate melissa states to hass.""" - with patch('homeassistant.components.melissa'): - with patch('homeassistant.components.climate.melissa._LOGGER.warning' - ) as mocked_warning: + with patch( + 'homeassistant.components.melissa.climate._LOGGER.warning' + ) as mocked_warning: + with patch('homeassistant.components.melissa'): api = melissa_mock() device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) diff --git a/tests/components/meraki/__init__.py b/tests/components/meraki/__init__.py new file mode 100644 index 00000000000..e0ab288f466 --- /dev/null +++ b/tests/components/meraki/__init__.py @@ -0,0 +1 @@ +"""Tests for the meraki component.""" diff --git a/tests/components/device_tracker/test_meraki.py b/tests/components/meraki/test_device_tracker.py similarity index 97% rename from tests/components/device_tracker/test_meraki.py rename to tests/components/meraki/test_device_tracker.py index 02b24c4c476..f4ae96f3ed2 100644 --- a/tests/components/device_tracker/test_meraki.py +++ b/tests/components/meraki/test_device_tracker.py @@ -4,12 +4,12 @@ import json import pytest -from homeassistant.components.device_tracker.meraki import ( +from homeassistant.components.meraki.device_tracker import ( CONF_VALIDATOR, CONF_SECRET) from homeassistant.setup import async_setup_component import homeassistant.components.device_tracker as device_tracker from homeassistant.const import CONF_PLATFORM -from homeassistant.components.device_tracker.meraki import URL +from homeassistant.components.meraki.device_tracker import URL @pytest.fixture diff --git a/tests/components/mfi/__init__.py b/tests/components/mfi/__init__.py new file mode 100644 index 00000000000..e0c3ebbec33 --- /dev/null +++ b/tests/components/mfi/__init__.py @@ -0,0 +1 @@ +"""Tests for the mfi component.""" diff --git a/tests/components/sensor/test_mfi.py b/tests/components/mfi/test_sensor.py similarity index 98% rename from tests/components/sensor/test_mfi.py rename to tests/components/mfi/test_sensor.py index d30618a330d..58566f7bcb2 100644 --- a/tests/components/sensor/test_mfi.py +++ b/tests/components/mfi/test_sensor.py @@ -6,7 +6,7 @@ import requests from homeassistant.setup import setup_component import homeassistant.components.sensor as sensor -import homeassistant.components.sensor.mfi as mfi +import homeassistant.components.mfi.sensor as mfi from homeassistant.const import TEMP_CELSIUS from tests.common import get_test_home_assistant @@ -104,7 +104,7 @@ class TestMfiSensorSetup(unittest.TestCase): ) @mock.patch('mficlient.client.MFiClient') - @mock.patch('homeassistant.components.sensor.mfi.MfiSensor') + @mock.patch('homeassistant.components.mfi.sensor.MfiSensor') def test_setup_adds_proper_devices(self, mock_sensor, mock_client): """Test if setup adds devices.""" ports = {i: mock.MagicMock(model=model) diff --git a/tests/components/switch/test_mfi.py b/tests/components/mfi/test_switch.py similarity index 95% rename from tests/components/switch/test_mfi.py rename to tests/components/mfi/test_switch.py index 222efee0e46..07e8c5c6912 100644 --- a/tests/components/switch/test_mfi.py +++ b/tests/components/mfi/test_switch.py @@ -4,8 +4,8 @@ import unittest.mock as mock from homeassistant.setup import setup_component import homeassistant.components.switch as switch -import homeassistant.components.switch.mfi as mfi -from tests.components.sensor import test_mfi as test_mfi_sensor +import homeassistant.components.mfi.switch as mfi +from tests.components.mfi import test_sensor as test_mfi_sensor from tests.common import get_test_home_assistant @@ -29,7 +29,7 @@ class TestMfiSwitchSetup(test_mfi_sensor.TestMfiSensorSetup): } @mock.patch('mficlient.client.MFiClient') - @mock.patch('homeassistant.components.switch.mfi.MfiSwitch') + @mock.patch('homeassistant.components.mfi.switch.MfiSwitch') def test_setup_adds_proper_devices(self, mock_switch, mock_client): """Test if setup adds devices.""" ports = {i: mock.MagicMock(model=model) diff --git a/tests/components/mhz19/__init__.py b/tests/components/mhz19/__init__.py new file mode 100644 index 00000000000..a35660a3726 --- /dev/null +++ b/tests/components/mhz19/__init__.py @@ -0,0 +1 @@ +"""Tests for the mhz19 component.""" diff --git a/tests/components/sensor/test_mhz19.py b/tests/components/mhz19/test_sensor.py similarity index 98% rename from tests/components/sensor/test_mhz19.py rename to tests/components/mhz19/test_sensor.py index 003cc199954..a5ca5ec10d0 100644 --- a/tests/components/sensor/test_mhz19.py +++ b/tests/components/mhz19/test_sensor.py @@ -4,7 +4,7 @@ from unittest.mock import patch, DEFAULT, Mock from homeassistant.setup import setup_component from homeassistant.components.sensor import DOMAIN -import homeassistant.components.sensor.mhz19 as mhz19 +import homeassistant.components.mhz19.sensor as mhz19 from homeassistant.const import TEMP_FAHRENHEIT from tests.common import get_test_home_assistant, assert_setup_component diff --git a/tests/components/microsoft_face_detect/__init__.py b/tests/components/microsoft_face_detect/__init__.py new file mode 100644 index 00000000000..df079d019eb --- /dev/null +++ b/tests/components/microsoft_face_detect/__init__.py @@ -0,0 +1 @@ +"""Tests for the microsoft_face_detect component.""" diff --git a/tests/components/image_processing/test_microsoft_face_detect.py b/tests/components/microsoft_face_detect/test_image_processing.py similarity index 98% rename from tests/components/image_processing/test_microsoft_face_detect.py rename to tests/components/microsoft_face_detect/test_image_processing.py index 9e65386a3c6..a6599fb4032 100644 --- a/tests/components/image_processing/test_microsoft_face_detect.py +++ b/tests/components/microsoft_face_detect/test_image_processing.py @@ -105,7 +105,7 @@ class TestMicrosoftFaceDetect: """Stop everything that was started.""" self.hass.stop() - @patch('homeassistant.components.image_processing.microsoft_face_detect.' + @patch('homeassistant.components.microsoft_face_detect.image_processing.' 'MicrosoftFaceDetectEntity.should_poll', new_callable=PropertyMock(return_value=False)) def test_ms_detect_process_image(self, poll_mock, aioclient_mock): diff --git a/tests/components/microsoft_face_identify/__init__.py b/tests/components/microsoft_face_identify/__init__.py new file mode 100644 index 00000000000..0b31c83b942 --- /dev/null +++ b/tests/components/microsoft_face_identify/__init__.py @@ -0,0 +1 @@ +"""Tests for the microsoft_face_identify component.""" diff --git a/tests/components/image_processing/test_microsoft_face_identify.py b/tests/components/microsoft_face_identify/test_image_processing.py similarity index 98% rename from tests/components/image_processing/test_microsoft_face_identify.py rename to tests/components/microsoft_face_identify/test_image_processing.py index c24e758da97..ccee60eed48 100644 --- a/tests/components/image_processing/test_microsoft_face_identify.py +++ b/tests/components/microsoft_face_identify/test_image_processing.py @@ -106,7 +106,7 @@ class TestMicrosoftFaceIdentify: """Stop everything that was started.""" self.hass.stop() - @patch('homeassistant.components.image_processing.microsoft_face_identify.' + @patch('homeassistant.components.microsoft_face_identify.image_processing.' 'MicrosoftFaceIdentifyEntity.should_poll', new_callable=PropertyMock(return_value=False)) def test_ms_identify_process_image(self, poll_mock, aioclient_mock): diff --git a/tests/components/min_max/__init__.py b/tests/components/min_max/__init__.py new file mode 100644 index 00000000000..3767049b537 --- /dev/null +++ b/tests/components/min_max/__init__.py @@ -0,0 +1 @@ +"""Tests for the min_max component.""" diff --git a/tests/components/sensor/test_min_max.py b/tests/components/min_max/test_sensor.py similarity index 100% rename from tests/components/sensor/test_min_max.py rename to tests/components/min_max/test_sensor.py diff --git a/tests/components/mobile_app/test_notify.py b/tests/components/mobile_app/test_notify.py new file mode 100644 index 00000000000..395dee6c117 --- /dev/null +++ b/tests/components/mobile_app/test_notify.py @@ -0,0 +1,81 @@ +"""Notify platform tests for mobile_app.""" +# pylint: disable=redefined-outer-name +import pytest + +from homeassistant.setup import async_setup_component + +from homeassistant.components.mobile_app.const import DOMAIN + +from tests.common import MockConfigEntry + + +@pytest.fixture +async def setup_push_receiver(hass, aioclient_mock): + """Fixture that sets up a mocked push receiver.""" + push_url = 'https://mobile-push.home-assistant.dev/push' + + from datetime import datetime, timedelta + now = (datetime.now() + timedelta(hours=24)) + iso_time = now.strftime("%Y-%m-%dT%H:%M:%SZ") + + aioclient_mock.post(push_url, json={ + 'rateLimits': { + 'attempts': 1, + 'successful': 1, + 'errors': 0, + 'total': 1, + 'maximum': 150, + 'remaining': 149, + 'resetsAt': iso_time + } + }) + + entry = MockConfigEntry( + connection_class="cloud_push", + data={ + "app_data": { + "push_token": "PUSH_TOKEN", + "push_url": push_url + }, + "app_id": "io.homeassistant.mobile_app", + "app_name": "mobile_app tests", + "app_version": "1.0", + "device_id": "4d5e6f", + "device_name": "Test", + "manufacturer": "Home Assistant", + "model": "mobile_app", + "os_name": "Linux", + "os_version": "5.0.6", + "secret": "123abc", + "supports_encryption": False, + "user_id": "1a2b3c", + "webhook_id": "webhook_id" + }, + domain=DOMAIN, + source="registration", + title="mobile_app test entry", + version=1 + ) + entry.add_to_hass(hass) + + await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + + +async def test_notify_works(hass, aioclient_mock, setup_push_receiver): + """Test notify works.""" + assert hass.services.has_service('notify', 'mobile_app_test') is True + assert await hass.services.async_call('notify', 'mobile_app_test', + {'message': 'Hello world'}, + blocking=True) + + assert len(aioclient_mock.mock_calls) == 1 + call = aioclient_mock.mock_calls + + call_json = call[0][2] + + assert call_json["push_token"] == "PUSH_TOKEN" + assert call_json["message"] == "Hello world" + assert call_json["registration_info"]["app_id"] == \ + "io.homeassistant.mobile_app" + assert call_json["registration_info"]["app_version"] == "1.0" diff --git a/tests/components/mold_indicator/__init__.py b/tests/components/mold_indicator/__init__.py new file mode 100644 index 00000000000..aefb419b74c --- /dev/null +++ b/tests/components/mold_indicator/__init__.py @@ -0,0 +1 @@ +"""The tests for the MoldIndicator sensor.""" diff --git a/tests/components/sensor/test_moldindicator.py b/tests/components/mold_indicator/test_sensor.py similarity index 99% rename from tests/components/sensor/test_moldindicator.py rename to tests/components/mold_indicator/test_sensor.py index 8b39804cacd..fb6654ce7d9 100644 --- a/tests/components/sensor/test_moldindicator.py +++ b/tests/components/mold_indicator/test_sensor.py @@ -3,7 +3,7 @@ import unittest from homeassistant.setup import setup_component import homeassistant.components.sensor as sensor -from homeassistant.components.sensor.mold_indicator import (ATTR_DEWPOINT, +from homeassistant.components.mold_indicator.sensor import (ATTR_DEWPOINT, ATTR_CRITICAL_TEMP) from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, TEMP_CELSIUS) diff --git a/tests/components/monoprice/__init__.py b/tests/components/monoprice/__init__.py new file mode 100644 index 00000000000..3b2ac0426b9 --- /dev/null +++ b/tests/components/monoprice/__init__.py @@ -0,0 +1 @@ +"""Tests for the monoprice component.""" diff --git a/tests/components/media_player/test_monoprice.py b/tests/components/monoprice/test_media_player.py similarity index 99% rename from tests/components/media_player/test_monoprice.py rename to tests/components/monoprice/test_media_player.py index d6374cf9dd7..eeccd6fbf78 100644 --- a/tests/components/media_player/test_monoprice.py +++ b/tests/components/monoprice/test_media_player.py @@ -10,7 +10,7 @@ from homeassistant.components.media_player.const import ( from homeassistant.const import STATE_ON, STATE_OFF import tests.common -from homeassistant.components.media_player.monoprice import ( +from homeassistant.components.monoprice.media_player import ( DATA_MONOPRICE, PLATFORM_SCHEMA, SERVICE_SNAPSHOT, SERVICE_RESTORE, setup_platform) import pytest diff --git a/tests/components/moon/__init__.py b/tests/components/moon/__init__.py new file mode 100644 index 00000000000..13afa4696d2 --- /dev/null +++ b/tests/components/moon/__init__.py @@ -0,0 +1 @@ +"""Tests for the moon component.""" diff --git a/tests/components/sensor/test_moon.py b/tests/components/moon/test_sensor.py similarity index 92% rename from tests/components/sensor/test_moon.py rename to tests/components/moon/test_sensor.py index 14c93678a0f..6dd177fe1d0 100644 --- a/tests/components/sensor/test_moon.py +++ b/tests/components/moon/test_sensor.py @@ -23,7 +23,7 @@ class TestMoonSensor(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() - @patch('homeassistant.components.sensor.moon.dt_util.utcnow', + @patch('homeassistant.components.moon.sensor.dt_util.utcnow', return_value=DAY1) def test_moon_day1(self, mock_request): """Test the Moon sensor.""" @@ -39,7 +39,7 @@ class TestMoonSensor(unittest.TestCase): state = self.hass.states.get('sensor.moon_day1') assert state.state == 'waxing_crescent' - @patch('homeassistant.components.sensor.moon.dt_util.utcnow', + @patch('homeassistant.components.moon.sensor.dt_util.utcnow', return_value=DAY2) def test_moon_day2(self, mock_request): """Test the Moon sensor.""" diff --git a/tests/components/mqtt_json/__init__.py b/tests/components/mqtt_json/__init__.py new file mode 100644 index 00000000000..75eea75e604 --- /dev/null +++ b/tests/components/mqtt_json/__init__.py @@ -0,0 +1 @@ +"""Tests for the mqtt_json component.""" diff --git a/tests/components/device_tracker/test_mqtt_json.py b/tests/components/mqtt_json/test_device_tracker.py similarity index 98% rename from tests/components/device_tracker/test_mqtt_json.py rename to tests/components/mqtt_json/test_device_tracker.py index 252d40338fc..ea87be42bd6 100644 --- a/tests/components/device_tracker/test_mqtt_json.py +++ b/tests/components/mqtt_json/test_device_tracker.py @@ -39,7 +39,7 @@ async def test_ensure_device_tracker_platform_validation(hass): """Check that Qos was added by validation.""" assert 'qos' in config - with patch('homeassistant.components.device_tracker.mqtt_json.' + with patch('homeassistant.components.mqtt_json.device_tracker.' 'async_setup_scanner', autospec=True, side_effect=mock_setup_scanner) as mock_sp: diff --git a/tests/components/mqtt_room/__init__.py b/tests/components/mqtt_room/__init__.py new file mode 100644 index 00000000000..2fcfed6c813 --- /dev/null +++ b/tests/components/mqtt_room/__init__.py @@ -0,0 +1 @@ +"""Tests for the mqtt_room component.""" diff --git a/tests/components/sensor/test_mqtt_room.py b/tests/components/mqtt_room/test_sensor.py similarity index 100% rename from tests/components/sensor/test_mqtt_room.py rename to tests/components/mqtt_room/test_sensor.py diff --git a/tests/components/ness_alarm/test_init.py b/tests/components/ness_alarm/test_init.py index 7d2104c9306..534f055dbad 100644 --- a/tests/components/ness_alarm/test_init.py +++ b/tests/components/ness_alarm/test_init.py @@ -6,20 +6,20 @@ from asynctest import patch, MagicMock from homeassistant.components import alarm_control_panel from homeassistant.components.ness_alarm import ( - DOMAIN, CONF_DEVICE_PORT, CONF_DEVICE_HOST, CONF_ZONE_NAME, CONF_ZONES, + DOMAIN, CONF_DEVICE_PORT, CONF_ZONE_NAME, CONF_ZONES, CONF_ZONE_ID, SERVICE_AUX, SERVICE_PANIC, ATTR_CODE, ATTR_OUTPUT_ID) from homeassistant.const import ( STATE_ALARM_ARMING, SERVICE_ALARM_DISARM, ATTR_ENTITY_ID, SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_TRIGGER, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING, - STATE_ALARM_TRIGGERED, STATE_UNKNOWN) + STATE_ALARM_TRIGGERED, STATE_UNKNOWN, CONF_HOST) from homeassistant.setup import async_setup_component from tests.common import MockDependency VALID_CONFIG = { DOMAIN: { - CONF_DEVICE_HOST: 'alarm.local', + CONF_HOST: 'alarm.local', CONF_DEVICE_PORT: 1234, CONF_ZONES: [ { diff --git a/tests/components/nsw_fuel_station/__init__.py b/tests/components/nsw_fuel_station/__init__.py new file mode 100644 index 00000000000..965a21529ec --- /dev/null +++ b/tests/components/nsw_fuel_station/__init__.py @@ -0,0 +1 @@ +"""Tests for the nsw_fuel_station component.""" diff --git a/tests/components/sensor/test_nsw_fuel_station.py b/tests/components/nsw_fuel_station/test_sensor.py similarity index 100% rename from tests/components/sensor/test_nsw_fuel_station.py rename to tests/components/nsw_fuel_station/test_sensor.py diff --git a/tests/components/nsw_rural_fire_service_feed/__init__.py b/tests/components/nsw_rural_fire_service_feed/__init__.py new file mode 100644 index 00000000000..691fd2f01ac --- /dev/null +++ b/tests/components/nsw_rural_fire_service_feed/__init__.py @@ -0,0 +1 @@ +"""Tests for the nsw_rural_fire_service_feed component.""" diff --git a/tests/components/geo_location/test_nsw_rural_fire_service_feed.py b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py similarity index 99% rename from tests/components/geo_location/test_nsw_rural_fire_service_feed.py rename to tests/components/nsw_rural_fire_service_feed/test_geo_location.py index 3254fd570ce..facc0604058 100644 --- a/tests/components/geo_location/test_nsw_rural_fire_service_feed.py +++ b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py @@ -4,7 +4,7 @@ from asynctest.mock import patch, MagicMock, call from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE -from homeassistant.components.geo_location.nsw_rural_fire_service_feed import \ +from homeassistant.components.nsw_rural_fire_service_feed.geo_location import \ ATTR_EXTERNAL_ID, SCAN_INTERVAL, ATTR_CATEGORY, ATTR_FIRE, ATTR_LOCATION, \ ATTR_COUNCIL_AREA, ATTR_STATUS, ATTR_TYPE, ATTR_SIZE, \ ATTR_RESPONSIBLE_AGENCY, ATTR_PUBLICATION_DATE diff --git a/tests/components/climate/test_nuheat.py b/tests/components/nuheat/test_climate.py similarity index 97% rename from tests/components/climate/test_nuheat.py rename to tests/components/nuheat/test_climate.py index 19919d25954..6a697e5cb0e 100644 --- a/tests/components/climate/test_nuheat.py +++ b/tests/components/nuheat/test_climate.py @@ -9,7 +9,7 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, STATE_HEAT, STATE_IDLE) -import homeassistant.components.climate.nuheat as nuheat +import homeassistant.components.nuheat.climate as nuheat from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT SCHEDULE_HOLD = 3 @@ -57,7 +57,7 @@ class TestNuHeat(unittest.TestCase): """Stop hass.""" self.hass.stop() - @patch("homeassistant.components.climate.nuheat.NuHeatThermostat") + @patch("homeassistant.components.nuheat.climate.NuHeatThermostat") def test_setup_platform(self, mocked_thermostat): """Test setup_platform.""" mocked_thermostat.return_value = self.thermostat @@ -73,7 +73,7 @@ class TestNuHeat(unittest.TestCase): nuheat.setup_platform(self.hass, config, add_entities, discovery_info) add_entities.assert_called_once_with(thermostats, True) - @patch("homeassistant.components.climate.nuheat.NuHeatThermostat") + @patch("homeassistant.components.nuheat.climate.NuHeatThermostat") def test_resume_program_service(self, mocked_thermostat): """Test resume program service.""" mocked_thermostat.return_value = self.thermostat diff --git a/tests/components/nx584/__init__.py b/tests/components/nx584/__init__.py new file mode 100644 index 00000000000..7c2ce89e7b7 --- /dev/null +++ b/tests/components/nx584/__init__.py @@ -0,0 +1 @@ +"""Tests for nx584 component.""" diff --git a/tests/components/binary_sensor/test_nx584.py b/tests/components/nx584/test_binary_sensor.py similarity index 95% rename from tests/components/binary_sensor/test_nx584.py rename to tests/components/nx584/test_binary_sensor.py index 3239ddbc436..53516885f30 100644 --- a/tests/components/binary_sensor/test_nx584.py +++ b/tests/components/nx584/test_binary_sensor.py @@ -5,7 +5,7 @@ from unittest import mock from nx584 import client as nx584_client -from homeassistant.components.binary_sensor import nx584 +from homeassistant.components.nx584 import binary_sensor as nx584 from homeassistant.setup import setup_component from tests.common import get_test_home_assistant @@ -42,8 +42,8 @@ class TestNX584SensorSetup(unittest.TestCase): self.hass.stop() self._mock_client.stop() - @mock.patch('homeassistant.components.binary_sensor.nx584.NX584Watcher') - @mock.patch('homeassistant.components.binary_sensor.nx584.NX584ZoneSensor') + @mock.patch('homeassistant.components.nx584.binary_sensor.NX584Watcher') + @mock.patch('homeassistant.components.nx584.binary_sensor.NX584ZoneSensor') def test_setup_defaults(self, mock_nx, mock_watcher): """Test the setup with no configuration.""" add_entities = mock.MagicMock() @@ -61,8 +61,8 @@ class TestNX584SensorSetup(unittest.TestCase): assert nx584_client.Client.call_args == \ mock.call('http://localhost:5007') - @mock.patch('homeassistant.components.binary_sensor.nx584.NX584Watcher') - @mock.patch('homeassistant.components.binary_sensor.nx584.NX584ZoneSensor') + @mock.patch('homeassistant.components.nx584.binary_sensor.NX584Watcher') + @mock.patch('homeassistant.components.nx584.binary_sensor.NX584ZoneSensor') def test_setup_full_config(self, mock_nx, mock_watcher): """Test the setup with full configuration.""" config = { diff --git a/tests/components/openalpr_cloud/__init__.py b/tests/components/openalpr_cloud/__init__.py new file mode 100644 index 00000000000..21e2605966b --- /dev/null +++ b/tests/components/openalpr_cloud/__init__.py @@ -0,0 +1 @@ +"""Tests for the openalpr_cloud component.""" diff --git a/tests/components/image_processing/test_openalpr_cloud.py b/tests/components/openalpr_cloud/test_image_processing.py similarity index 98% rename from tests/components/image_processing/test_openalpr_cloud.py rename to tests/components/openalpr_cloud/test_image_processing.py index 8a71db7fb7b..d7be9effc8a 100644 --- a/tests/components/image_processing/test_openalpr_cloud.py +++ b/tests/components/openalpr_cloud/test_image_processing.py @@ -5,7 +5,7 @@ from unittest.mock import patch, PropertyMock from homeassistant.core import callback from homeassistant.setup import setup_component from homeassistant.components import camera, image_processing as ip -from homeassistant.components.image_processing.openalpr_cloud import ( +from homeassistant.components.openalpr_cloud.image_processing import ( OPENALPR_API_URL) from tests.common import ( @@ -126,7 +126,7 @@ class TestOpenAlprCloud: }, } - with patch('homeassistant.components.image_processing.openalpr_cloud.' + with patch('homeassistant.components.openalpr_cloud.image_processing.' 'OpenAlprCloudEntity.should_poll', new_callable=PropertyMock(return_value=False)): setup_component(self.hass, ip.DOMAIN, config) diff --git a/tests/components/openalpr_local/__init__.py b/tests/components/openalpr_local/__init__.py new file mode 100644 index 00000000000..36b7703491f --- /dev/null +++ b/tests/components/openalpr_local/__init__.py @@ -0,0 +1 @@ +"""Tests for the openalpr_local component.""" diff --git a/tests/components/image_processing/test_openalpr_local.py b/tests/components/openalpr_local/test_image_processing.py similarity index 98% rename from tests/components/image_processing/test_openalpr_local.py rename to tests/components/openalpr_local/test_image_processing.py index 6d860da3313..5a5a2604c6c 100644 --- a/tests/components/image_processing/test_openalpr_local.py +++ b/tests/components/openalpr_local/test_image_processing.py @@ -118,7 +118,7 @@ class TestOpenAlprLocal: }, } - with patch('homeassistant.components.image_processing.openalpr_local.' + with patch('homeassistant.components.openalpr_local.image_processing.' 'OpenAlprLocalEntity.should_poll', new_callable=PropertyMock(return_value=False)): setup_component(self.hass, ip.DOMAIN, config) diff --git a/tests/components/openhardwaremonitor/__init__.py b/tests/components/openhardwaremonitor/__init__.py new file mode 100644 index 00000000000..9a27cc3254f --- /dev/null +++ b/tests/components/openhardwaremonitor/__init__.py @@ -0,0 +1 @@ +"""Tests for the openhardwaremonitor component.""" diff --git a/tests/components/sensor/test_openhardwaremonitor.py b/tests/components/openhardwaremonitor/test_sensor.py similarity index 100% rename from tests/components/sensor/test_openhardwaremonitor.py rename to tests/components/openhardwaremonitor/test_sensor.py diff --git a/tests/components/panel_custom/test_init.py b/tests/components/panel_custom/test_init.py index c265324179d..8c95f96085a 100644 --- a/tests/components/panel_custom/test_init.py +++ b/tests/components/panel_custom/test_init.py @@ -130,6 +130,7 @@ async def test_module_webcomponent(hass): }, 'embed_iframe': True, 'trust_external_script': True, + 'require_admin': True, } } @@ -145,6 +146,7 @@ async def test_module_webcomponent(hass): panel = panels['nice_url'] + assert panel.require_admin assert panel.config == { 'hello': 'world', '_panel_custom': { diff --git a/tests/components/panel_iframe/test_init.py b/tests/components/panel_iframe/test_init.py index cb868f64b58..da7878399d4 100644 --- a/tests/components/panel_iframe/test_init.py +++ b/tests/components/panel_iframe/test_init.py @@ -41,11 +41,13 @@ class TestPanelIframe(unittest.TestCase): 'icon': 'mdi:network-wireless', 'title': 'Router', 'url': 'http://192.168.1.1', + 'require_admin': True, }, 'weather': { 'icon': 'mdi:weather', 'title': 'Weather', 'url': 'https://www.wunderground.com/us/ca/san-diego', + 'require_admin': True, }, 'api': { 'icon': 'mdi:weather', @@ -67,7 +69,8 @@ class TestPanelIframe(unittest.TestCase): 'config': {'url': 'http://192.168.1.1'}, 'icon': 'mdi:network-wireless', 'title': 'Router', - 'url_path': 'router' + 'url_path': 'router', + 'require_admin': True, } assert panels.get('weather').to_response() == { @@ -76,6 +79,7 @@ class TestPanelIframe(unittest.TestCase): 'icon': 'mdi:weather', 'title': 'Weather', 'url_path': 'weather', + 'require_admin': True, } assert panels.get('api').to_response() == { @@ -84,6 +88,7 @@ class TestPanelIframe(unittest.TestCase): 'icon': 'mdi:weather', 'title': 'Api', 'url_path': 'api', + 'require_admin': False, } assert panels.get('ftp').to_response() == { @@ -92,4 +97,5 @@ class TestPanelIframe(unittest.TestCase): 'icon': 'mdi:weather', 'title': 'FTP', 'url_path': 'ftp', + 'require_admin': False, } diff --git a/tests/components/ps4/test_config_flow.py b/tests/components/ps4/test_config_flow.py index 271db46d856..06fe1ef65da 100644 --- a/tests/components/ps4/test_config_flow.py +++ b/tests/components/ps4/test_config_flow.py @@ -44,6 +44,9 @@ MOCK_DATA = { MOCK_UDP_PORT = int(987) MOCK_TCP_PORT = int(997) +MOCK_AUTO = {"Config Mode": 'Auto Discover'} +MOCK_MANUAL = {"Config Mode": 'Manual Entry', CONF_IP_ADDRESS: MOCK_HOST} + async def test_full_flow_implementation(hass): """Test registering an implementation and flow works.""" @@ -58,13 +61,18 @@ async def test_full_flow_implementation(hass): assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['step_id'] == 'creds' - # Step Creds results with form in Step Link. + # Step Creds results with form in Step Mode. with patch('pyps4_homeassistant.Helper.get_creds', - return_value=MOCK_CREDS), \ - patch('pyps4_homeassistant.Helper.has_devices', - return_value=[{'host-ip': MOCK_HOST}]): + return_value=MOCK_CREDS): result = await flow.async_step_creds({}) assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + assert result['step_id'] == 'mode' + + # Step Mode with User Input which is not manual, results in Step Link. + with patch('pyps4_homeassistant.Helper.has_devices', + return_value=[{'host-ip': MOCK_HOST}]): + result = await flow.async_step_mode(MOCK_AUTO) + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['step_id'] == 'link' # User Input results in created entry. @@ -78,6 +86,8 @@ async def test_full_flow_implementation(hass): assert result['data']['devices'] == [MOCK_DEVICE] assert result['title'] == MOCK_TITLE + await hass.async_block_till_done() + # Add entry using result data. mock_data = { CONF_TOKEN: result['data'][CONF_TOKEN], @@ -104,14 +114,19 @@ async def test_multiple_flow_implementation(hass): assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['step_id'] == 'creds' - # Step Creds results with form in Step Link. + # Step Creds results with form in Step Mode. with patch('pyps4_homeassistant.Helper.get_creds', - return_value=MOCK_CREDS), \ - patch('pyps4_homeassistant.Helper.has_devices', - return_value=[{'host-ip': MOCK_HOST}, - {'host-ip': MOCK_HOST_ADDITIONAL}]): + return_value=MOCK_CREDS): result = await flow.async_step_creds({}) assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + assert result['step_id'] == 'mode' + + # Step Mode with User Input which is not manual, results in Step Link. + with patch('pyps4_homeassistant.Helper.has_devices', + return_value=[{'host-ip': MOCK_HOST}, + {'host-ip': MOCK_HOST_ADDITIONAL}]): + result = await flow.async_step_mode(MOCK_AUTO) + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['step_id'] == 'link' # User Input results in created entry. @@ -142,7 +157,7 @@ async def test_multiple_flow_implementation(hass): # Test additional flow. - # User Step Started, results in Step Link: + # User Step Started, results in Step Mode: with patch('pyps4_homeassistant.Helper.port_bind', return_value=None), \ patch('pyps4_homeassistant.Helper.has_devices', @@ -150,6 +165,14 @@ async def test_multiple_flow_implementation(hass): {'host-ip': MOCK_HOST_ADDITIONAL}]): result = await flow.async_step_user() assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + assert result['step_id'] == 'mode' + + # Step Mode with User Input which is not manual, results in Step Link. + with patch('pyps4_homeassistant.Helper.has_devices', + return_value=[{'host-ip': MOCK_HOST}, + {'host-ip': MOCK_HOST_ADDITIONAL}]): + result = await flow.async_step_mode(MOCK_AUTO) + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['step_id'] == 'link' # Step Link @@ -158,12 +181,14 @@ async def test_multiple_flow_implementation(hass): {'host-ip': MOCK_HOST_ADDITIONAL}]), \ patch('pyps4_homeassistant.Helper.link', return_value=(True, True)): - result = await flow.async_step_link(user_input=MOCK_CONFIG_ADDITIONAL) + result = await flow.async_step_link(MOCK_CONFIG_ADDITIONAL) assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result['data'][CONF_TOKEN] == MOCK_CREDS assert len(result['data']['devices']) == 1 assert result['title'] == MOCK_TITLE + await hass.async_block_till_done() + mock_data = { CONF_TOKEN: result['data'][CONF_TOKEN], 'devices': result['data']['devices']} @@ -230,7 +255,7 @@ async def test_additional_device(hass): {'host-ip': MOCK_HOST_ADDITIONAL}]), \ patch('pyps4_homeassistant.Helper.link', return_value=(True, True)): - result = await flow.async_step_link(user_input=MOCK_CONFIG_ADDITIONAL) + result = await flow.async_step_link(MOCK_CONFIG_ADDITIONAL) assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result['data'][CONF_TOKEN] == MOCK_CREDS assert len(result['data']['devices']) == 1 @@ -249,12 +274,26 @@ async def test_no_devices_found_abort(hass): flow = ps4.PlayStation4FlowHandler() flow.hass = hass - with patch('pyps4_homeassistant.Helper.has_devices', return_value=None): - result = await flow.async_step_link(MOCK_CONFIG) + with patch('pyps4_homeassistant.Helper.has_devices', return_value=[]): + result = await flow.async_step_link() assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT assert result['reason'] == 'no_devices_found' +async def test_manual_mode(hass): + """Test host specified in manual mode is passed to Step Link.""" + flow = ps4.PlayStation4FlowHandler() + flow.hass = hass + + # Step Mode with User Input: manual, results in Step Link. + with patch('pyps4_homeassistant.Helper.has_devices', + return_value=[{'host-ip': flow.m_device}]): + result = await flow.async_step_mode(MOCK_MANUAL) + assert flow.m_device == MOCK_HOST + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + assert result['step_id'] == 'link' + + async def test_credential_abort(hass): """Test that failure to get credentials aborts flow.""" flow = ps4.PlayStation4FlowHandler() @@ -266,8 +305,8 @@ async def test_credential_abort(hass): assert result['reason'] == 'credential_error' -async def test_invalid_pin_error(hass): - """Test that invalid pin throws an error.""" +async def test_wrong_pin_error(hass): + """Test that incorrect pin throws an error.""" flow = ps4.PlayStation4FlowHandler() flow.hass = hass @@ -294,3 +333,16 @@ async def test_device_connection_error(hass): assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['step_id'] == 'link' assert result['errors'] == {'base': 'not_ready'} + + +async def test_manual_mode_no_ip_error(hass): + """Test no IP specified in manual mode throws an error.""" + flow = ps4.PlayStation4FlowHandler() + flow.hass = hass + + mock_input = {"Config Mode": 'Manual Entry'} + + result = await flow.async_step_mode(mock_input) + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + assert result['step_id'] == 'mode' + assert result['errors'] == {CONF_IP_ADDRESS: 'no_ipaddress'} diff --git a/tests/components/push/__init__.py b/tests/components/push/__init__.py new file mode 100644 index 00000000000..1ef6ee48b29 --- /dev/null +++ b/tests/components/push/__init__.py @@ -0,0 +1 @@ +"""Tests for the push component.""" diff --git a/tests/components/camera/test_push.py b/tests/components/push/test_camera.py similarity index 100% rename from tests/components/camera/test_push.py rename to tests/components/push/test_camera.py diff --git a/tests/components/pushbullet/__init__.py b/tests/components/pushbullet/__init__.py new file mode 100644 index 00000000000..c7f7911950c --- /dev/null +++ b/tests/components/pushbullet/__init__.py @@ -0,0 +1 @@ +"""Tests for the pushbullet component.""" diff --git a/tests/components/notify/test_pushbullet.py b/tests/components/pushbullet/test_notify.py similarity index 100% rename from tests/components/notify/test_pushbullet.py rename to tests/components/pushbullet/test_notify.py diff --git a/tests/components/radarr/__init__.py b/tests/components/radarr/__init__.py new file mode 100644 index 00000000000..13cc76db384 --- /dev/null +++ b/tests/components/radarr/__init__.py @@ -0,0 +1 @@ +"""Tests for the radarr component.""" diff --git a/tests/components/sensor/test_radarr.py b/tests/components/radarr/test_sensor.py similarity index 99% rename from tests/components/sensor/test_radarr.py rename to tests/components/radarr/test_sensor.py index 5ac23f76251..3263cb07a0d 100644 --- a/tests/components/sensor/test_radarr.py +++ b/tests/components/radarr/test_sensor.py @@ -3,7 +3,7 @@ import unittest import pytest -from homeassistant.components.sensor import radarr +import homeassistant.components.radarr.sensor as radarr from tests.common import get_test_home_assistant diff --git a/tests/components/random/__init__.py b/tests/components/random/__init__.py new file mode 100644 index 00000000000..a9137dde998 --- /dev/null +++ b/tests/components/random/__init__.py @@ -0,0 +1 @@ +"""Tests for random component.""" diff --git a/tests/components/binary_sensor/test_random.py b/tests/components/random/test_binary_sensor.py similarity index 100% rename from tests/components/binary_sensor/test_random.py rename to tests/components/random/test_binary_sensor.py diff --git a/tests/components/sensor/test_random.py b/tests/components/random/test_sensor.py similarity index 100% rename from tests/components/sensor/test_random.py rename to tests/components/random/test_sensor.py diff --git a/tests/components/reddit/__init__.py b/tests/components/reddit/__init__.py new file mode 100644 index 00000000000..67e0db82f42 --- /dev/null +++ b/tests/components/reddit/__init__.py @@ -0,0 +1 @@ +"""Tests for the the Reddit component.""" diff --git a/tests/components/reddit/test_sensor.py b/tests/components/reddit/test_sensor.py new file mode 100644 index 00000000000..2bb22a0024b --- /dev/null +++ b/tests/components/reddit/test_sensor.py @@ -0,0 +1,175 @@ +"""The tests for the Reddit platform.""" +import copy +import unittest +from unittest.mock import patch + +from homeassistant.components.reddit.sensor import ( + DOMAIN, ATTR_SUBREDDIT, ATTR_POSTS, CONF_SORT_BY, + ATTR_ID, ATTR_URL, ATTR_TITLE, ATTR_SCORE, ATTR_COMMENTS_NUMBER, + ATTR_CREATED, ATTR_BODY) +from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, CONF_MAXIMUM) +from homeassistant.setup import setup_component + +from tests.common import (get_test_home_assistant, + MockDependency) + + +VALID_CONFIG = { + 'sensor': { + 'platform': DOMAIN, + 'client_id': 'test_client_id', + 'client_secret': 'test_client_secret', + CONF_USERNAME: 'test_username', + CONF_PASSWORD: 'test_password', + 'subreddits': ['worldnews', 'news'], + + } +} + +VALID_LIMITED_CONFIG = { + 'sensor': { + 'platform': DOMAIN, + 'client_id': 'test_client_id', + 'client_secret': 'test_client_secret', + CONF_USERNAME: 'test_username', + CONF_PASSWORD: 'test_password', + 'subreddits': ['worldnews', 'news'], + CONF_MAXIMUM: 1 + } +} + + +INVALID_SORT_BY_CONFIG = { + 'sensor': { + 'platform': DOMAIN, + 'client_id': 'test_client_id', + 'client_secret': 'test_client_secret', + CONF_USERNAME: 'test_username', + CONF_PASSWORD: 'test_password', + 'subreddits': ['worldnews', 'news'], + 'sort_by': 'invalid_sort_by' + } +} + + +class ObjectView(): + """Use dict properties as attributes.""" + + def __init__(self, d): + """Set dict as internal dict.""" + self.__dict__ = d + + +MOCK_RESULTS = { + 'results': [ + ObjectView({ + 'id': 0, + 'url': 'http://example.com/1', + 'title': 'example1', + 'score': '1', + 'num_comments': '1', + 'created': '', + 'selftext': 'example1 selftext' + }), + ObjectView({ + 'id': 1, + 'url': 'http://example.com/2', + 'title': 'example2', + 'score': '2', + 'num_comments': '2', + 'created': '', + 'selftext': 'example2 selftext' + }) + ] +} + +MOCK_RESULTS_LENGTH = len(MOCK_RESULTS['results']) + + +class MockPraw(): + """Mock class for tmdbsimple library.""" + + def __init__(self, client_id: str, client_secret: + str, username: str, password: str, + user_agent: str): + """Add mock data for API return.""" + self._data = MOCK_RESULTS + + def subreddit(self, subreddit: str): + """Return an instance of a sunbreddit.""" + return MockSubreddit(subreddit, self._data) + + +class MockSubreddit(): + """Mock class for a subreddit instance.""" + + def __init__(self, subreddit: str, data): + """Add mock data for API return.""" + self._subreddit = subreddit + self._data = data + + def top(self, limit): + """Return top posts for a subreddit.""" + return self._return_data(limit) + + def controversial(self, limit): + """Return controversial posts for a subreddit.""" + return self._return_data(limit) + + def hot(self, limit): + """Return hot posts for a subreddit.""" + return self._return_data(limit) + + def new(self, limit): + """Return new posts for a subreddit.""" + return self._return_data(limit) + + def _return_data(self, limit): + """Test method to return modified data.""" + data = copy.deepcopy(self._data) + return data['results'][:limit] + + +class TestRedditSetup(unittest.TestCase): + """Test the Reddit platform.""" + + def setUp(self): + """Initialize values for this testcase class.""" + self.hass = get_test_home_assistant() + + def tearDown(self): # pylint: disable=invalid-name + """Stop everything that was started.""" + self.hass.stop() + + @MockDependency('praw') + @patch('praw.Reddit', new=MockPraw) + def test_setup_with_valid_config(self, mock_praw): + """Test the platform setup with movie configuration.""" + setup_component(self.hass, 'sensor', VALID_CONFIG) + + state = self.hass.states.get('sensor.reddit_worldnews') + assert int(state.state) == MOCK_RESULTS_LENGTH + + state = self.hass.states.get('sensor.reddit_news') + assert int(state.state) == MOCK_RESULTS_LENGTH + + assert state.attributes[ATTR_SUBREDDIT] == 'news' + + assert state.attributes[ATTR_POSTS][0] == { + ATTR_ID: 0, + ATTR_URL: 'http://example.com/1', + ATTR_TITLE: 'example1', + ATTR_SCORE: '1', + ATTR_COMMENTS_NUMBER: '1', + ATTR_CREATED: '', + ATTR_BODY: 'example1 selftext' + } + + assert state.attributes[CONF_SORT_BY] == 'hot' + + @MockDependency('praw') + @patch('praw.Reddit', new=MockPraw) + def test_setup_with_invalid_config(self, mock_praw): + """Test the platform setup with invalid movie configuration.""" + setup_component(self.hass, 'sensor', INVALID_SORT_BY_CONFIG) + assert not self.hass.states.get('sensor.reddit_worldnews') diff --git a/tests/components/rest/__init__.py b/tests/components/rest/__init__.py new file mode 100644 index 00000000000..b2dc9f1839f --- /dev/null +++ b/tests/components/rest/__init__.py @@ -0,0 +1 @@ +"""Tests for rest component.""" diff --git a/tests/components/binary_sensor/test_rest.py b/tests/components/rest/test_binary_sensor.py similarity index 99% rename from tests/components/binary_sensor/test_rest.py rename to tests/components/rest/test_binary_sensor.py index befeca07251..3a91edcbcb6 100644 --- a/tests/components/binary_sensor/test_rest.py +++ b/tests/components/rest/test_binary_sensor.py @@ -10,7 +10,7 @@ import requests_mock from homeassistant.exceptions import PlatformNotReady from homeassistant.setup import setup_component import homeassistant.components.binary_sensor as binary_sensor -import homeassistant.components.binary_sensor.rest as rest +import homeassistant.components.rest.binary_sensor as rest from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.helpers import template diff --git a/tests/components/sensor/test_rest.py b/tests/components/rest/test_sensor.py similarity index 98% rename from tests/components/sensor/test_rest.py rename to tests/components/rest/test_sensor.py index 343cc696763..bb957b5b68d 100644 --- a/tests/components/sensor/test_rest.py +++ b/tests/components/rest/test_sensor.py @@ -10,7 +10,7 @@ import requests_mock from homeassistant.exceptions import PlatformNotReady from homeassistant.setup import setup_component import homeassistant.components.sensor as sensor -import homeassistant.components.sensor.rest as rest +import homeassistant.components.rest.sensor as rest from homeassistant.helpers.config_validation import template from tests.common import get_test_home_assistant, assert_setup_component @@ -214,7 +214,7 @@ class TestRestSensor(unittest.TestCase): assert 'some_json_value' == \ self.sensor.device_state_attributes['key'] - @patch('homeassistant.components.sensor.rest._LOGGER') + @patch('homeassistant.components.rest.sensor._LOGGER') def test_update_with_json_attrs_no_data(self, mock_logger): """Test attributes when no JSON result fetched.""" self.rest.update = Mock('rest.RestData.update', @@ -227,7 +227,7 @@ class TestRestSensor(unittest.TestCase): assert {} == self.sensor.device_state_attributes assert mock_logger.warning.called - @patch('homeassistant.components.sensor.rest._LOGGER') + @patch('homeassistant.components.rest.sensor._LOGGER') def test_update_with_json_attrs_not_dict(self, mock_logger): """Test attributes get extracted from a JSON result.""" self.rest.update = Mock('rest.RestData.update', @@ -241,7 +241,7 @@ class TestRestSensor(unittest.TestCase): assert {} == self.sensor.device_state_attributes assert mock_logger.warning.called - @patch('homeassistant.components.sensor.rest._LOGGER') + @patch('homeassistant.components.rest.sensor._LOGGER') def test_update_with_json_attrs_bad_JSON(self, mock_logger): """Test attributes get extracted from a JSON result.""" self.rest.update = Mock('rest.RestData.update', diff --git a/tests/components/switch/test_rest.py b/tests/components/rest/test_switch.py similarity index 99% rename from tests/components/switch/test_rest.py rename to tests/components/rest/test_switch.py index 56f3f0eebc5..11cf2334ae8 100644 --- a/tests/components/switch/test_rest.py +++ b/tests/components/rest/test_switch.py @@ -3,7 +3,7 @@ import asyncio import aiohttp -import homeassistant.components.switch.rest as rest +import homeassistant.components.rest.switch as rest from homeassistant.setup import setup_component from homeassistant.util.async_ import run_coroutine_threadsafe from homeassistant.helpers.template import Template diff --git a/tests/components/binary_sensor/test_rflink.py b/tests/components/rflink/test_binary_sensor.py similarity index 100% rename from tests/components/binary_sensor/test_rflink.py rename to tests/components/rflink/test_binary_sensor.py diff --git a/tests/components/cover/test_rflink.py b/tests/components/rflink/test_cover.py similarity index 100% rename from tests/components/cover/test_rflink.py rename to tests/components/rflink/test_cover.py diff --git a/tests/components/light/test_rflink.py b/tests/components/rflink/test_light.py similarity index 100% rename from tests/components/light/test_rflink.py rename to tests/components/rflink/test_light.py diff --git a/tests/components/sensor/test_rflink.py b/tests/components/rflink/test_sensor.py similarity index 100% rename from tests/components/sensor/test_rflink.py rename to tests/components/rflink/test_sensor.py diff --git a/tests/components/switch/test_rflink.py b/tests/components/rflink/test_switch.py similarity index 100% rename from tests/components/switch/test_rflink.py rename to tests/components/rflink/test_switch.py diff --git a/tests/components/binary_sensor/test_ring.py b/tests/components/ring/test_binary_sensor.py similarity index 97% rename from tests/components/binary_sensor/test_ring.py rename to tests/components/ring/test_binary_sensor.py index 924ed01f9e8..84ff9672850 100644 --- a/tests/components/binary_sensor/test_ring.py +++ b/tests/components/ring/test_binary_sensor.py @@ -3,7 +3,7 @@ import os import unittest import requests_mock -from homeassistant.components.binary_sensor import ring +from homeassistant.components.ring import binary_sensor as ring from homeassistant.components import ring as base_ring from tests.components.ring.test_init import ATTRIBUTION, VALID_CONFIG diff --git a/tests/components/sensor/test_ring.py b/tests/components/ring/test_sensor.py similarity index 98% rename from tests/components/sensor/test_ring.py rename to tests/components/ring/test_sensor.py index c54f22af8dc..872e647aced 100644 --- a/tests/components/sensor/test_ring.py +++ b/tests/components/ring/test_sensor.py @@ -3,7 +3,7 @@ import os import unittest import requests_mock -from homeassistant.components.sensor import ring +import homeassistant.components.ring.sensor as ring from homeassistant.components import ring as base_ring from tests.components.ring.test_init import ATTRIBUTION, VALID_CONFIG diff --git a/tests/components/rmvtransport/__init__.py b/tests/components/rmvtransport/__init__.py new file mode 100644 index 00000000000..db55bd32248 --- /dev/null +++ b/tests/components/rmvtransport/__init__.py @@ -0,0 +1 @@ +"""Tests for the rmvtransport component.""" diff --git a/tests/components/sensor/test_rmvtransport.py b/tests/components/rmvtransport/test_sensor.py similarity index 100% rename from tests/components/sensor/test_rmvtransport.py rename to tests/components/rmvtransport/test_sensor.py diff --git a/tests/components/samsungtv/__init__.py b/tests/components/samsungtv/__init__.py new file mode 100644 index 00000000000..4ad1622c6ca --- /dev/null +++ b/tests/components/samsungtv/__init__.py @@ -0,0 +1 @@ +"""Tests for the samsungtv component.""" diff --git a/tests/components/media_player/test_samsungtv.py b/tests/components/samsungtv/test_media_player.py similarity index 96% rename from tests/components/media_player/test_samsungtv.py rename to tests/components/samsungtv/test_media_player.py index a74f476981a..b175a722036 100644 --- a/tests/components/media_player/test_samsungtv.py +++ b/tests/components/samsungtv/test_media_player.py @@ -10,7 +10,7 @@ import pytest import tests.common from homeassistant.components.media_player.const import SUPPORT_TURN_ON, \ MEDIA_TYPE_CHANNEL, MEDIA_TYPE_URL -from homeassistant.components.media_player.samsungtv import setup_platform, \ +from homeassistant.components.samsungtv.media_player import setup_platform, \ CONF_TIMEOUT, SamsungTVDevice, SUPPORT_SAMSUNGTV from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_ON, \ CONF_MAC, STATE_OFF @@ -71,7 +71,7 @@ class TestSamsungTv(unittest.TestCase): def test_setup(self, samsung_mock, wol_mock): """Testing setup of platform.""" with mock.patch( - 'homeassistant.components.media_player.samsungtv.socket'): + 'homeassistant.components.samsungtv.media_player.socket'): add_entities = mock.Mock() setup_platform( self.hass, WORKING_CONFIG, add_entities) @@ -81,7 +81,7 @@ class TestSamsungTv(unittest.TestCase): def test_setup_discovery(self, samsung_mock, wol_mock): """Testing setup of platform with discovery.""" with mock.patch( - 'homeassistant.components.media_player.samsungtv.socket'): + 'homeassistant.components.samsungtv.media_player.socket'): add_entities = mock.Mock() setup_platform(self.hass, {}, add_entities, discovery_info=DISCOVERY_INFO) @@ -89,11 +89,11 @@ class TestSamsungTv(unittest.TestCase): @MockDependency('samsungctl') @MockDependency('wakeonlan') @mock.patch( - 'homeassistant.components.media_player.samsungtv._LOGGER.warning') + 'homeassistant.components.samsungtv.media_player._LOGGER.warning') def test_setup_none(self, samsung_mock, wol_mock, mocked_warn): """Testing setup of platform with no data.""" with mock.patch( - 'homeassistant.components.media_player.samsungtv.socket'): + 'homeassistant.components.samsungtv.media_player.socket'): add_entities = mock.Mock() setup_platform(self.hass, {}, add_entities, discovery_info=None) @@ -214,7 +214,7 @@ class TestSamsungTv(unittest.TestCase): self.device.send_key.assert_called_once_with('KEY_POWEROFF') @mock.patch( - 'homeassistant.components.media_player.samsungtv._LOGGER.debug') + 'homeassistant.components.samsungtv.media_player._LOGGER.debug') def test_turn_off_os_error(self, mocked_debug): """Test for turn_off with OSError.""" _remote = mock.Mock() diff --git a/tests/components/season/__init__.py b/tests/components/season/__init__.py new file mode 100644 index 00000000000..91002d8d918 --- /dev/null +++ b/tests/components/season/__init__.py @@ -0,0 +1 @@ +"""Tests for the season component.""" diff --git a/tests/components/sensor/test_season.py b/tests/components/season/test_sensor.py similarity index 99% rename from tests/components/sensor/test_season.py rename to tests/components/season/test_sensor.py index 20432857aa3..eab4d708630 100644 --- a/tests/components/sensor/test_season.py +++ b/tests/components/season/test_sensor.py @@ -4,7 +4,7 @@ import unittest from datetime import datetime from homeassistant.setup import setup_component -import homeassistant.components.sensor.season as season +import homeassistant.components.season.sensor as season from tests.common import get_test_home_assistant diff --git a/tests/components/sensor/test_api_streams.py b/tests/components/sensor/test_api_streams.py deleted file mode 100644 index e7978ce0fb8..00000000000 --- a/tests/components/sensor/test_api_streams.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Test cases for the API stream sensor.""" -import asyncio -import logging - -from homeassistant.bootstrap import async_setup_component -from tests.common import assert_setup_component - - -@asyncio.coroutine -def test_api_streams(hass): - """Test API streams.""" - log = logging.getLogger('homeassistant.components.api') - - with assert_setup_component(1): - yield from async_setup_component(hass, 'sensor', { - 'sensor': { - 'platform': 'api_streams', - } - }) - - state = hass.states.get('sensor.connected_clients') - assert state.state == '0' - - log.debug('STREAM 1 ATTACHED') - yield from hass.async_block_till_done() - - state = hass.states.get('sensor.connected_clients') - assert state.state == '1' - - log.debug('STREAM 1 ATTACHED') - yield from hass.async_block_till_done() - - state = hass.states.get('sensor.connected_clients') - assert state.state == '2' - - log.debug('STREAM 1 RESPONSE CLOSED') - yield from hass.async_block_till_done() - - state = hass.states.get('sensor.connected_clients') - assert state.state == '1' - - -@asyncio.coroutine -def test_websocket_api(hass): - """Test API streams.""" - log = logging.getLogger('homeassistant.components.websocket_api') - - with assert_setup_component(1): - yield from async_setup_component(hass, 'sensor', { - 'sensor': { - 'platform': 'api_streams', - } - }) - - state = hass.states.get('sensor.connected_clients') - assert state.state == '0' - - log.debug('WS %s: %s', id(log), 'Connected') - yield from hass.async_block_till_done() - - state = hass.states.get('sensor.connected_clients') - assert state.state == '1' - - log.debug('WS %s: %s', id(log), 'Connected') - yield from hass.async_block_till_done() - - state = hass.states.get('sensor.connected_clients') - assert state.state == '2' - - log.debug('WS %s: %s', id(log), 'Closed connection') - yield from hass.async_block_till_done() - - state = hass.states.get('sensor.connected_clients') - assert state.state == '1' diff --git a/tests/components/sensor/test_entur_public_transport.py b/tests/components/sensor/test_entur_public_transport.py deleted file mode 100644 index 20b50ce9ddd..00000000000 --- a/tests/components/sensor/test_entur_public_transport.py +++ /dev/null @@ -1,66 +0,0 @@ -"""The tests for the entur platform.""" -from datetime import datetime -import unittest -from unittest.mock import patch - -from enturclient.api import RESOURCE -from enturclient.consts import ATTR_EXPECTED_AT, ATTR_ROUTE, ATTR_STOP_ID -import requests_mock - -from homeassistant.components.sensor.entur_public_transport import ( - CONF_EXPAND_PLATFORMS, CONF_STOP_IDS) -from homeassistant.setup import setup_component -import homeassistant.util.dt as dt_util - -from tests.common import get_test_home_assistant, load_fixture - -VALID_CONFIG = { - 'platform': 'entur_public_transport', - CONF_EXPAND_PLATFORMS: False, - CONF_STOP_IDS: [ - 'NSR:StopPlace:548', - 'NSR:Quay:48550', - ] -} - -FIXTURE_FILE = 'entur_public_transport.json' -TEST_TIMESTAMP = datetime(2018, 10, 10, 7, tzinfo=dt_util.UTC) - - -class TestEnturPublicTransportSensor(unittest.TestCase): - """Test the entur platform.""" - - def setUp(self): - """Initialize values for this testcase class.""" - self.hass = get_test_home_assistant() - - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() - - @requests_mock.Mocker() - @patch( - 'homeassistant.components.sensor.entur_public_transport.dt_util.now', - return_value=TEST_TIMESTAMP) - def test_setup(self, mock_req, mock_patch): - """Test for correct sensor setup with state and proper attributes.""" - mock_req.post(RESOURCE, - text=load_fixture(FIXTURE_FILE), - status_code=200) - self.assertTrue( - setup_component(self.hass, 'sensor', {'sensor': VALID_CONFIG})) - - state = self.hass.states.get('sensor.entur_bergen_stasjon') - assert state.state == '28' - assert state.attributes.get(ATTR_STOP_ID) == 'NSR:StopPlace:548' - assert state.attributes.get(ATTR_ROUTE) == "59 Bergen" - assert state.attributes.get(ATTR_EXPECTED_AT) \ - == '2018-10-10T09:28:00+0200' - - state = self.hass.states.get('sensor.entur_fiskepiren_platform_2') - assert state.state == '0' - assert state.attributes.get(ATTR_STOP_ID) == 'NSR:Quay:48550' - assert state.attributes.get(ATTR_ROUTE) \ - == "5 Stavanger Airport via Forum" - assert state.attributes.get(ATTR_EXPECTED_AT) \ - == '2018-10-10T09:00:00+0200' diff --git a/tests/components/sigfox/__init__.py b/tests/components/sigfox/__init__.py new file mode 100644 index 00000000000..f0054389136 --- /dev/null +++ b/tests/components/sigfox/__init__.py @@ -0,0 +1 @@ +"""Tests for the sigfox component.""" diff --git a/tests/components/sensor/test_sigfox.py b/tests/components/sigfox/test_sensor.py similarity index 97% rename from tests/components/sensor/test_sigfox.py rename to tests/components/sigfox/test_sensor.py index c785d272f55..17706e263f3 100644 --- a/tests/components/sensor/test_sigfox.py +++ b/tests/components/sigfox/test_sensor.py @@ -3,7 +3,7 @@ import re import requests_mock import unittest -from homeassistant.components.sensor.sigfox import ( +from homeassistant.components.sigfox.sensor import ( API_URL, CONF_API_LOGIN, CONF_API_PASSWORD) from homeassistant.setup import setup_component from tests.common import get_test_home_assistant diff --git a/tests/components/simulated/__init__.py b/tests/components/simulated/__init__.py new file mode 100644 index 00000000000..501fbab603a --- /dev/null +++ b/tests/components/simulated/__init__.py @@ -0,0 +1 @@ +"""Tests for the simulated component.""" diff --git a/tests/components/sensor/test_simulated.py b/tests/components/simulated/test_sensor.py similarity index 96% rename from tests/components/sensor/test_simulated.py rename to tests/components/simulated/test_sensor.py index c51e281d123..f97fa7b070f 100644 --- a/tests/components/sensor/test_simulated.py +++ b/tests/components/simulated/test_sensor.py @@ -3,7 +3,7 @@ import unittest from tests.common import get_test_home_assistant -from homeassistant.components.sensor.simulated import ( +from homeassistant.components.simulated.sensor import ( CONF_AMP, CONF_FWHM, CONF_MEAN, CONF_PERIOD, CONF_PHASE, CONF_SEED, CONF_UNIT, CONF_RELATIVE_TO_EPOCH, DEFAULT_AMP, DEFAULT_FWHM, DEFAULT_MEAN, DEFAULT_NAME, DEFAULT_PHASE, DEFAULT_SEED, DEFAULT_RELATIVE_TO_EPOCH) diff --git a/tests/components/binary_sensor/test_sleepiq.py b/tests/components/sleepiq/test_binary_sensor.py similarity index 96% rename from tests/components/binary_sensor/test_sleepiq.py rename to tests/components/sleepiq/test_binary_sensor.py index 524093d76b3..66748f1379c 100644 --- a/tests/components/binary_sensor/test_sleepiq.py +++ b/tests/components/sleepiq/test_binary_sensor.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock import requests_mock from homeassistant.setup import setup_component -from homeassistant.components.binary_sensor import sleepiq +from homeassistant.components.sleepiq import binary_sensor as sleepiq from tests.components.sleepiq.test_init import mock_responses from tests.common import get_test_home_assistant diff --git a/tests/components/sensor/test_sleepiq.py b/tests/components/sleepiq/test_sensor.py similarity index 96% rename from tests/components/sensor/test_sleepiq.py rename to tests/components/sleepiq/test_sensor.py index c667a6a2cdf..8b5c039011f 100644 --- a/tests/components/sensor/test_sleepiq.py +++ b/tests/components/sleepiq/test_sensor.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock import requests_mock from homeassistant.setup import setup_component -from homeassistant.components.sensor import sleepiq +import homeassistant.components.sleepiq.sensor as sleepiq from tests.components.sleepiq.test_init import mock_responses from tests.common import get_test_home_assistant diff --git a/tests/components/smartthings/test_cover.py b/tests/components/smartthings/test_cover.py index 7e41237e3e7..fb90882eae8 100644 --- a/tests/components/smartthings/test_cover.py +++ b/tests/components/smartthings/test_cover.py @@ -142,7 +142,10 @@ async def test_set_cover_position_unsupported(hass, device_factory): COVER_DOMAIN, SERVICE_SET_COVER_POSITION, {ATTR_POSITION: 50}, blocking=True) - # Ensure API was notcalled + state = hass.states.get('cover.shade') + assert ATTR_CURRENT_POSITION not in state.attributes + + # Ensure API was not called # pylint: disable=protected-access assert device._api.post_device_command.call_count == 0 # type: ignore diff --git a/tests/components/smtp/__init__.py b/tests/components/smtp/__init__.py new file mode 100644 index 00000000000..a99f7991ff9 --- /dev/null +++ b/tests/components/smtp/__init__.py @@ -0,0 +1 @@ +"""Tests for the smtp component.""" diff --git a/tests/components/notify/test_smtp.py b/tests/components/smtp/test_notify.py similarity index 96% rename from tests/components/notify/test_smtp.py rename to tests/components/smtp/test_notify.py index fa6c5003288..e946efe61e6 100644 --- a/tests/components/notify/test_smtp.py +++ b/tests/components/smtp/test_notify.py @@ -2,13 +2,13 @@ import unittest from unittest.mock import patch -from homeassistant.components.notify import smtp +from homeassistant.components.smtp.notify import MailNotificationService from tests.common import get_test_home_assistant import re -class MockSMTP(smtp.MailNotificationService): +class MockSMTP(MailNotificationService): """Test SMTP object that doesn't need a working server.""" def _send_email(self, msg): diff --git a/tests/components/sonarr/__init__.py b/tests/components/sonarr/__init__.py new file mode 100644 index 00000000000..573575cedc0 --- /dev/null +++ b/tests/components/sonarr/__init__.py @@ -0,0 +1 @@ +"""Tests for the sonarr component.""" diff --git a/tests/components/sensor/test_sonarr.py b/tests/components/sonarr/test_sensor.py similarity index 99% rename from tests/components/sensor/test_sonarr.py rename to tests/components/sonarr/test_sensor.py index e8c7c17d006..1d2933a795b 100644 --- a/tests/components/sensor/test_sonarr.py +++ b/tests/components/sonarr/test_sensor.py @@ -5,7 +5,7 @@ from datetime import datetime import pytest -from homeassistant.components.sensor import sonarr +import homeassistant.components.sonarr.sensor as sonarr from tests.common import get_test_home_assistant diff --git a/tests/components/soundtouch/__init__.py b/tests/components/soundtouch/__init__.py new file mode 100644 index 00000000000..8cd8088b9d9 --- /dev/null +++ b/tests/components/soundtouch/__init__.py @@ -0,0 +1 @@ +"""Tests for the soundtouch component.""" diff --git a/tests/components/media_player/test_soundtouch.py b/tests/components/soundtouch/test_media_player.py similarity index 99% rename from tests/components/media_player/test_soundtouch.py rename to tests/components/soundtouch/test_media_player.py index 2a763bfc706..87f41b11d95 100644 --- a/tests/components/media_player/test_soundtouch.py +++ b/tests/components/soundtouch/test_media_player.py @@ -5,7 +5,7 @@ from unittest import mock from libsoundtouch.device import SoundTouchDevice as STD, Status, Volume, \ Preset, Config -from homeassistant.components.media_player import soundtouch +from homeassistant.components.soundtouch import media_player as soundtouch from homeassistant.const import ( STATE_OFF, STATE_PAUSED, STATE_PLAYING) from tests.common import get_test_home_assistant diff --git a/tests/components/sql/__init__.py b/tests/components/sql/__init__.py new file mode 100644 index 00000000000..5981fdbd24e --- /dev/null +++ b/tests/components/sql/__init__.py @@ -0,0 +1 @@ +"""Tests for the sql component.""" diff --git a/tests/components/sensor/test_sql.py b/tests/components/sql/test_sensor.py similarity index 96% rename from tests/components/sensor/test_sql.py rename to tests/components/sql/test_sensor.py index c966af653d2..3e8af25299d 100644 --- a/tests/components/sensor/test_sql.py +++ b/tests/components/sql/test_sensor.py @@ -3,7 +3,7 @@ import unittest import pytest import voluptuous as vol -from homeassistant.components.sensor.sql import validate_sql_select +from homeassistant.components.sql.sensor import validate_sql_select from homeassistant.setup import setup_component from homeassistant.const import STATE_UNKNOWN diff --git a/tests/components/srp_energy/__init__.py b/tests/components/srp_energy/__init__.py new file mode 100644 index 00000000000..2a278cf1d38 --- /dev/null +++ b/tests/components/srp_energy/__init__.py @@ -0,0 +1 @@ +"""Tests for the srp_energy component.""" diff --git a/tests/components/sensor/test_srp_energy.py b/tests/components/srp_energy/test_sensor.py similarity index 100% rename from tests/components/sensor/test_srp_energy.py rename to tests/components/srp_energy/test_sensor.py diff --git a/tests/components/startca/__init__.py b/tests/components/startca/__init__.py new file mode 100644 index 00000000000..f2eaf7079b4 --- /dev/null +++ b/tests/components/startca/__init__.py @@ -0,0 +1 @@ +"""Tests for the startca component.""" diff --git a/tests/components/sensor/test_startca.py b/tests/components/startca/test_sensor.py similarity index 99% rename from tests/components/sensor/test_startca.py rename to tests/components/startca/test_sensor.py index eb5ca2203e0..79e1d3cab13 100644 --- a/tests/components/sensor/test_startca.py +++ b/tests/components/startca/test_sensor.py @@ -1,7 +1,7 @@ """Tests for the Start.ca sensor platform.""" import asyncio from homeassistant.bootstrap import async_setup_component -from homeassistant.components.sensor.startca import StartcaData +from homeassistant.components.startca.sensor import StartcaData from homeassistant.helpers.aiohttp_client import async_get_clientsession diff --git a/tests/components/statistics/__init__.py b/tests/components/statistics/__init__.py new file mode 100644 index 00000000000..fdd623ec02c --- /dev/null +++ b/tests/components/statistics/__init__.py @@ -0,0 +1 @@ +"""Tests for the statistics component.""" diff --git a/tests/components/sensor/test_statistics.py b/tests/components/statistics/test_sensor.py similarity index 97% rename from tests/components/sensor/test_statistics.py rename to tests/components/statistics/test_sensor.py index 1bd3ee73616..580b11f5ccd 100644 --- a/tests/components/sensor/test_statistics.py +++ b/tests/components/statistics/test_sensor.py @@ -5,7 +5,7 @@ import statistics import pytest from homeassistant.setup import setup_component -from homeassistant.components.sensor.statistics import StatisticsSensor +from homeassistant.components.statistics.sensor import StatisticsSensor from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, STATE_UNKNOWN) from homeassistant.util import dt as dt_util @@ -165,7 +165,7 @@ class TestStatisticsSensor(unittest.TestCase): def mock_now(): return mock_data['return_time'] - with patch('homeassistant.components.sensor.statistics.dt_util.utcnow', + with patch('homeassistant.components.statistics.sensor.dt_util.utcnow', new=mock_now): assert setup_component(self.hass, 'sensor', { 'sensor': { @@ -201,7 +201,7 @@ class TestStatisticsSensor(unittest.TestCase): def mock_now(): return mock_data['return_time'] - with patch('homeassistant.components.sensor.statistics.dt_util.utcnow', + with patch('homeassistant.components.statistics.sensor.dt_util.utcnow', new=mock_now): assert setup_component(self.hass, 'sensor', { 'sensor': { @@ -284,7 +284,7 @@ class TestStatisticsSensor(unittest.TestCase): # enable the recorder init_recorder_component(self.hass) - with patch('homeassistant.components.sensor.statistics.dt_util.utcnow', + with patch('homeassistant.components.statistics.sensor.dt_util.utcnow', new=mock_now), \ patch.object(StatisticsSensor, '_purge_old', mock_purge): # store some values diff --git a/tests/components/stream/test_init.py b/tests/components/stream/test_init.py new file mode 100644 index 00000000000..7f68bf1e7bf --- /dev/null +++ b/tests/components/stream/test_init.py @@ -0,0 +1,103 @@ +"""The tests for stream.""" +from unittest.mock import patch, MagicMock + +import pytest + +from homeassistant.const import CONF_FILENAME +from homeassistant.components.stream.const import ( + DOMAIN, SERVICE_RECORD, CONF_STREAM_SOURCE, CONF_LOOKBACK, ATTR_STREAMS) +from homeassistant.exceptions import HomeAssistantError +from homeassistant.setup import async_setup_component + +from tests.common import mock_coro + + +async def test_record_service_invalid_file(hass): + """Test record service call with invalid file.""" + await async_setup_component(hass, 'stream', { + 'stream': {} + }) + data = { + CONF_STREAM_SOURCE: 'rtsp://my.video', + CONF_FILENAME: '/my/invalid/path' + } + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, SERVICE_RECORD, data, blocking=True) + + +async def test_record_service_init_stream(hass): + """Test record service call with invalid file.""" + await async_setup_component(hass, 'stream', { + 'stream': {} + }) + data = { + CONF_STREAM_SOURCE: 'rtsp://my.video', + CONF_FILENAME: '/my/invalid/path' + } + with patch('homeassistant.components.stream.Stream') as stream_mock, \ + patch.object(hass.config, 'is_allowed_path', return_value=True): + # Setup stubs + stream_mock.return_value.outputs = {} + + # Call Service + await hass.services.async_call( + DOMAIN, SERVICE_RECORD, data, blocking=True) + + # Assert + assert stream_mock.called + + +async def test_record_service_existing_record_session(hass): + """Test record service call with invalid file.""" + await async_setup_component(hass, 'stream', { + 'stream': {} + }) + source = 'rtsp://my.video' + data = { + CONF_STREAM_SOURCE: source, + CONF_FILENAME: '/my/invalid/path' + } + + # Setup stubs + stream_mock = MagicMock() + stream_mock.return_value.outputs = {'recorder': MagicMock()} + hass.data[DOMAIN][ATTR_STREAMS][source] = stream_mock + + with patch.object(hass.config, 'is_allowed_path', return_value=True), \ + pytest.raises(HomeAssistantError): + # Call Service + await hass.services.async_call( + DOMAIN, SERVICE_RECORD, data, blocking=True) + + +async def test_record_service_lookback(hass): + """Test record service call with invalid file.""" + await async_setup_component(hass, 'stream', { + 'stream': {} + }) + data = { + CONF_STREAM_SOURCE: 'rtsp://my.video', + CONF_FILENAME: '/my/invalid/path', + CONF_LOOKBACK: 4 + } + + with patch('homeassistant.components.stream.Stream') as stream_mock, \ + patch.object(hass.config, 'is_allowed_path', return_value=True): + # Setup stubs + hls_mock = MagicMock() + hls_mock.num_segments = 3 + hls_mock.target_duration = 2 + hls_mock.recv.return_value = mock_coro() + stream_mock.return_value.outputs = { + 'hls': hls_mock + } + + # Call Service + await hass.services.async_call( + DOMAIN, SERVICE_RECORD, data, blocking=True) + + assert stream_mock.called + stream_mock.return_value.add_provider.assert_called_once_with( + 'recorder') + assert hls_mock.recv.called diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py new file mode 100644 index 00000000000..4e227e463b4 --- /dev/null +++ b/tests/components/stream/test_recorder.py @@ -0,0 +1,83 @@ +"""The tests for hls streams.""" +from datetime import timedelta +from io import BytesIO +from unittest.mock import patch + +from homeassistant.setup import async_setup_component +from homeassistant.components.stream.core import Segment +from homeassistant.components.stream.recorder import recorder_save_worker +import homeassistant.util.dt as dt_util + +from tests.common import async_fire_time_changed +from tests.components.stream.common import ( + generate_h264_video, preload_stream) + + +async def test_record_stream(hass, hass_client): + """ + Test record stream. + + Purposefully not mocking anything here to test full + integration with the stream component. + """ + await async_setup_component(hass, 'stream', { + 'stream': {} + }) + + with patch( + 'homeassistant.components.stream.recorder.recorder_save_worker'): + # Setup demo track + source = generate_h264_video() + stream = preload_stream(hass, source) + recorder = stream.add_provider('recorder') + stream.start() + + segments = 0 + while True: + segment = await recorder.recv() + if not segment: + break + segments += 1 + + stream.stop() + + assert segments == 3 + + +async def test_recorder_timeout(hass, hass_client): + """Test recorder timeout.""" + await async_setup_component(hass, 'stream', { + 'stream': {} + }) + + with patch( + 'homeassistant.components.stream.recorder.RecorderOutput.cleanup' + ) as mock_cleanup: + # Setup demo track + source = generate_h264_video() + stream = preload_stream(hass, source) + recorder = stream.add_provider('recorder') + stream.start() + + await recorder.recv() + + # Wait a minute + future = dt_util.utcnow() + timedelta(minutes=1) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + assert mock_cleanup.called + + +async def test_recorder_save(): + """Test recorder save.""" + # Setup + source = generate_h264_video() + output = BytesIO() + output.name = 'test.mp4' + + # Run + recorder_save_worker(output, [Segment(1, source, 4)]) + + # Assert + assert output.getvalue() diff --git a/tests/components/light/test_switch.py b/tests/components/switch/test_light.py similarity index 100% rename from tests/components/light/test_switch.py rename to tests/components/switch/test_light.py diff --git a/tests/components/tcp/__init__.py b/tests/components/tcp/__init__.py new file mode 100644 index 00000000000..56410765ce6 --- /dev/null +++ b/tests/components/tcp/__init__.py @@ -0,0 +1 @@ +"""Tests for tcp component.""" diff --git a/tests/components/binary_sensor/test_tcp.py b/tests/components/tcp/test_binary_sensor.py similarity index 82% rename from tests/components/binary_sensor/test_tcp.py rename to tests/components/tcp/test_binary_sensor.py index cb21d7d2c71..aeddd8d117d 100644 --- a/tests/components/binary_sensor/test_tcp.py +++ b/tests/components/tcp/test_binary_sensor.py @@ -1,12 +1,13 @@ """The tests for the TCP binary sensor platform.""" import unittest -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch +from homeassistant.components.tcp import binary_sensor as bin_tcp +import homeassistant.components.tcp.sensor as tcp from homeassistant.setup import setup_component -from homeassistant.components.binary_sensor import tcp as bin_tcp -from homeassistant.components.sensor import tcp -from tests.common import (get_test_home_assistant, assert_setup_component) -from tests.components.sensor import test_tcp + +from tests.common import assert_setup_component, get_test_home_assistant +import tests.components.tcp.test_sensor as test_tcp class TestTCPBinarySensor(unittest.TestCase): @@ -36,7 +37,7 @@ class TestTCPBinarySensor(unittest.TestCase): } }) - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_setup_platform_devices(self, mock_update): """Check the supplied config and call add_entities with sensor.""" add_entities = Mock() @@ -46,7 +47,7 @@ class TestTCPBinarySensor(unittest.TestCase): assert isinstance( add_entities.call_args[0][0][0], bin_tcp.TcpBinarySensor) - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_is_on_true(self, mock_update): """Check the return that _state is value_on.""" sensor = bin_tcp.TcpBinarySensor( @@ -55,7 +56,7 @@ class TestTCPBinarySensor(unittest.TestCase): print(sensor._state) assert sensor.is_on - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_is_on_false(self, mock_update): """Check the return that _state is not the same as value_on.""" sensor = bin_tcp.TcpBinarySensor( diff --git a/tests/components/sensor/test_tcp.py b/tests/components/tcp/test_sensor.py similarity index 91% rename from tests/components/sensor/test_tcp.py rename to tests/components/tcp/test_sensor.py index e89e8db861a..f926dfdf7ef 100644 --- a/tests/components/sensor/test_tcp.py +++ b/tests/components/tcp/test_sensor.py @@ -1,15 +1,16 @@ """The tests for the TCP sensor platform.""" +from copy import copy import socket import unittest -from copy import copy +from unittest.mock import Mock, patch from uuid import uuid4 -from unittest.mock import patch, Mock -from tests.common import (get_test_home_assistant, assert_setup_component) -from homeassistant.setup import setup_component -from homeassistant.components.sensor import tcp +import homeassistant.components.tcp.sensor as tcp from homeassistant.helpers.entity import Entity from homeassistant.helpers.template import Template +from homeassistant.setup import setup_component + +from tests.common import assert_setup_component, get_test_home_assistant TEST_CONFIG = { 'sensor': { @@ -46,7 +47,7 @@ class TestTCPSensor(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_setup_platform_valid_config(self, mock_update): """Check a valid configuration and call add_entities with sensor.""" with assert_setup_component(0, 'sensor'): @@ -67,13 +68,13 @@ class TestTCPSensor(unittest.TestCase): } }) - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_name(self, mock_update): """Return the name if set in the configuration.""" sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) assert sensor.name == TEST_CONFIG['sensor'][tcp.CONF_NAME] - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_name_not_set(self, mock_update): """Return the superclass name property if not set in configuration.""" config = copy(TEST_CONFIG['sensor']) @@ -82,7 +83,7 @@ class TestTCPSensor(unittest.TestCase): sensor = tcp.TcpSensor(self.hass, config) assert sensor.name == entity.name - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_state(self, mock_update): """Return the contents of _state.""" sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) @@ -90,14 +91,14 @@ class TestTCPSensor(unittest.TestCase): sensor._state = uuid assert sensor.state == uuid - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_unit_of_measurement(self, mock_update): """Return the configured unit of measurement.""" sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) assert sensor.unit_of_measurement == \ TEST_CONFIG['sensor'][tcp.CONF_UNIT_OF_MEASUREMENT] - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_config_valid_keys(self, *args): """Store valid keys in _config.""" sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) @@ -111,7 +112,7 @@ class TestTCPSensor(unittest.TestCase): with assert_setup_component(0, 'sensor'): assert setup_component(self.hass, 'sensor', TEST_CONFIG) - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_config_invalid_keys(self, mock_update): """Shouldn't store invalid keys in _config.""" config = copy(TEST_CONFIG['sensor']) @@ -135,7 +136,7 @@ class TestTCPSensor(unittest.TestCase): with assert_setup_component(0, 'sensor'): assert setup_component(self.hass, 'sensor', {'tcp': config}) - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_config_uses_defaults(self, mock_update): """Check if defaults were set.""" config = copy(TEST_CONFIG['sensor']) @@ -173,7 +174,7 @@ class TestTCPSensor(unittest.TestCase): with assert_setup_component(0, 'sensor'): assert setup_component(self.hass, 'sensor', {'tcp': config}) - @patch('homeassistant.components.sensor.tcp.TcpSensor.update') + @patch('homeassistant.components.tcp.sensor.TcpSensor.update') def test_init_calls_update(self, mock_update): """Call update() method during __init__().""" tcp.TcpSensor(self.hass, TEST_CONFIG) @@ -192,7 +193,7 @@ class TestTCPSensor(unittest.TestCase): @patch('socket.socket.connect', side_effect=socket.error()) def test_update_returns_if_connecting_fails(self, *args): """Return if connecting to host fails.""" - with patch('homeassistant.components.sensor.tcp.TcpSensor.update'): + with patch('homeassistant.components.tcp.sensor.TcpSensor.update'): sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) assert sensor.update() is None @@ -200,7 +201,7 @@ class TestTCPSensor(unittest.TestCase): @patch('socket.socket.send', side_effect=socket.error()) def test_update_returns_if_sending_fails(self, *args): """Return if sending fails.""" - with patch('homeassistant.components.sensor.tcp.TcpSensor.update'): + with patch('homeassistant.components.tcp.sensor.TcpSensor.update'): sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) assert sensor.update() is None @@ -209,7 +210,7 @@ class TestTCPSensor(unittest.TestCase): @patch('select.select', return_value=(False, False, False)) def test_update_returns_if_select_fails(self, *args): """Return if select fails to return a socket.""" - with patch('homeassistant.components.sensor.tcp.TcpSensor.update'): + with patch('homeassistant.components.tcp.sensor.TcpSensor.update'): sensor = tcp.TcpSensor(self.hass, TEST_CONFIG['sensor']) assert sensor.update() is None diff --git a/tests/components/teksavvy/__init__.py b/tests/components/teksavvy/__init__.py new file mode 100644 index 00000000000..8c8a0fc82ca --- /dev/null +++ b/tests/components/teksavvy/__init__.py @@ -0,0 +1 @@ +"""Tests for the teksavvy component.""" diff --git a/tests/components/sensor/test_teksavvy.py b/tests/components/teksavvy/test_sensor.py similarity index 99% rename from tests/components/sensor/test_teksavvy.py rename to tests/components/teksavvy/test_sensor.py index 2c493d04050..366f0278354 100644 --- a/tests/components/sensor/test_teksavvy.py +++ b/tests/components/teksavvy/test_sensor.py @@ -1,7 +1,7 @@ """Tests for the TekSavvy sensor platform.""" import asyncio from homeassistant.bootstrap import async_setup_component -from homeassistant.components.sensor.teksavvy import TekSavvyData +from homeassistant.components.teksavvy.sensor import TekSavvyData from homeassistant.helpers.aiohttp_client import async_get_clientsession diff --git a/tests/components/tellduslive/test_config_flow.py b/tests/components/tellduslive/test_config_flow.py index 5be9a03742e..460b33e5cd7 100644 --- a/tests/components/tellduslive/test_config_flow.py +++ b/tests/components/tellduslive/test_config_flow.py @@ -7,8 +7,9 @@ import pytest from homeassistant import data_entry_flow from homeassistant.components.tellduslive import ( - APPLICATION_NAME, DOMAIN, KEY_HOST, KEY_SCAN_INTERVAL, SCAN_INTERVAL, + APPLICATION_NAME, DOMAIN, KEY_SCAN_INTERVAL, SCAN_INTERVAL, config_flow) +from homeassistant.const import CONF_HOST from tests.common import MockConfigEntry, MockDependency, mock_coro @@ -94,7 +95,7 @@ async def test_step_import(hass, mock_tellduslive): flow = init_config_flow(hass) result = await flow.async_step_import({ - KEY_HOST: DOMAIN, + CONF_HOST: DOMAIN, KEY_SCAN_INTERVAL: 0, }) assert result['type'] == data_entry_flow.RESULT_TYPE_FORM @@ -106,7 +107,7 @@ async def test_step_import_add_host(hass, mock_tellduslive): flow = init_config_flow(hass) result = await flow.async_step_import({ - KEY_HOST: 'localhost', + CONF_HOST: 'localhost', KEY_SCAN_INTERVAL: 0, }) assert result['type'] == data_entry_flow.RESULT_TYPE_FORM @@ -117,7 +118,7 @@ async def test_step_import_no_config_file(hass, mock_tellduslive): """Test that we trigger user with no config_file configuring from import.""" flow = init_config_flow(hass) - result = await flow.async_step_import({ KEY_HOST: 'localhost', KEY_SCAN_INTERVAL: 0, }) + result = await flow.async_step_import({ CONF_HOST: 'localhost', KEY_SCAN_INTERVAL: 0, }) assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['step_id'] == 'user' @@ -129,7 +130,7 @@ async def test_step_import_load_json_matching_host(hass, mock_tellduslive): with patch('homeassistant.components.tellduslive.config_flow.load_json', return_value={'tellduslive': {}}), \ patch('os.path.isfile'): - result = await flow.async_step_import({ KEY_HOST: 'Cloud API', KEY_SCAN_INTERVAL: 0, }) + result = await flow.async_step_import({ CONF_HOST: 'Cloud API', KEY_SCAN_INTERVAL: 0, }) assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['step_id'] == 'user' @@ -141,7 +142,7 @@ async def test_step_import_load_json(hass, mock_tellduslive): with patch('homeassistant.components.tellduslive.config_flow.load_json', return_value={'localhost': {}}), \ patch('os.path.isfile'): - result = await flow.async_step_import({ KEY_HOST: 'localhost', KEY_SCAN_INTERVAL: SCAN_INTERVAL, }) + result = await flow.async_step_import({ CONF_HOST: 'localhost', KEY_SCAN_INTERVAL: SCAN_INTERVAL, }) assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result['title'] == 'localhost' assert result['data']['host'] == 'localhost' diff --git a/tests/components/template/__init__.py b/tests/components/template/__init__.py new file mode 100644 index 00000000000..a2ce6bb1a77 --- /dev/null +++ b/tests/components/template/__init__.py @@ -0,0 +1 @@ +"""Tests for template component.""" diff --git a/tests/components/binary_sensor/test_template.py b/tests/components/template/test_binary_sensor.py similarity index 99% rename from tests/components/binary_sensor/test_template.py rename to tests/components/template/test_binary_sensor.py index a1f97398616..d0bd1609a91 100644 --- a/tests/components/binary_sensor/test_template.py +++ b/tests/components/template/test_binary_sensor.py @@ -5,7 +5,7 @@ from unittest import mock from homeassistant.const import MATCH_ALL, EVENT_HOMEASSISTANT_START from homeassistant import setup -from homeassistant.components.binary_sensor import template +from homeassistant.components.template import binary_sensor as template from homeassistant.exceptions import TemplateError from homeassistant.helpers import template as template_hlpr from homeassistant.util.async_ import run_callback_threadsafe @@ -159,7 +159,7 @@ class TestBinarySensorTemplate(unittest.TestCase): state = self.hass.states.get('binary_sensor.test_template_sensor') assert state.attributes['entity_picture'] == '/local/sensor.png' - @mock.patch('homeassistant.components.binary_sensor.template.' + @mock.patch('homeassistant.components.template.binary_sensor.' 'BinarySensorTemplate._async_render') def test_match_all(self, _async_render): """Test MATCH_ALL in template.""" diff --git a/tests/components/cover/test_template.py b/tests/components/template/test_cover.py similarity index 100% rename from tests/components/cover/test_template.py rename to tests/components/template/test_cover.py diff --git a/tests/components/fan/test_template.py b/tests/components/template/test_fan.py similarity index 100% rename from tests/components/fan/test_template.py rename to tests/components/template/test_fan.py diff --git a/tests/components/light/test_template.py b/tests/components/template/test_light.py similarity index 100% rename from tests/components/light/test_template.py rename to tests/components/template/test_light.py diff --git a/tests/components/lock/test_template.py b/tests/components/template/test_lock.py similarity index 100% rename from tests/components/lock/test_template.py rename to tests/components/template/test_lock.py diff --git a/tests/components/sensor/test_template.py b/tests/components/template/test_sensor.py similarity index 100% rename from tests/components/sensor/test_template.py rename to tests/components/template/test_sensor.py diff --git a/tests/components/switch/test_template.py b/tests/components/template/test_switch.py similarity index 100% rename from tests/components/switch/test_template.py rename to tests/components/template/test_switch.py diff --git a/tests/components/threshold/__init__.py b/tests/components/threshold/__init__.py new file mode 100644 index 00000000000..7abfd4046a0 --- /dev/null +++ b/tests/components/threshold/__init__.py @@ -0,0 +1 @@ +"""Tests for threshold component.""" diff --git a/tests/components/binary_sensor/test_threshold.py b/tests/components/threshold/test_binary_sensor.py similarity index 100% rename from tests/components/binary_sensor/test_threshold.py rename to tests/components/threshold/test_binary_sensor.py diff --git a/tests/components/time_date/__init__.py b/tests/components/time_date/__init__.py new file mode 100644 index 00000000000..22734c19bbb --- /dev/null +++ b/tests/components/time_date/__init__.py @@ -0,0 +1 @@ +"""Tests for the time_date component.""" diff --git a/tests/components/sensor/test_time_date.py b/tests/components/time_date/test_sensor.py similarity index 92% rename from tests/components/sensor/test_time_date.py rename to tests/components/time_date/test_sensor.py index 98368e997d5..84331abfba1 100644 --- a/tests/components/sensor/test_time_date.py +++ b/tests/components/time_date/test_sensor.py @@ -2,7 +2,7 @@ import unittest from unittest.mock import patch -from homeassistant.components.sensor import time_date as time_date +import homeassistant.components.time_date.sensor as time_date import homeassistant.util.dt as dt_util from tests.common import get_test_home_assistant @@ -71,6 +71,10 @@ class TestTimeDateSensor(unittest.TestCase): device._update_internal_state(now) assert device.state == "@079" + device = time_date.TimeDateSensor(self.hass, 'date_time_iso') + device._update_internal_state(now) + assert device.state == "2017-05-18T00:54:00" + # pylint: disable=no-member def test_timezone_intervals(self): """Test date sensor behavior in a timezone besides UTC.""" @@ -114,3 +118,5 @@ class TestTimeDateSensor(unittest.TestCase): assert device.icon == "mdi:calendar" device = time_date.TimeDateSensor(self.hass, 'date_time') assert device.icon == "mdi:calendar-clock" + device = time_date.TimeDateSensor(self.hass, 'date_time_iso') + assert device.icon == "mdi:calendar-clock" diff --git a/tests/components/tod/__init__.py b/tests/components/tod/__init__.py new file mode 100644 index 00000000000..627b9b3cf47 --- /dev/null +++ b/tests/components/tod/__init__.py @@ -0,0 +1 @@ +"""Tests for tod component.""" diff --git a/tests/components/binary_sensor/test_tod.py b/tests/components/tod/test_binary_sensor.py similarity index 89% rename from tests/components/binary_sensor/test_tod.py rename to tests/components/tod/test_binary_sensor.py index 7af6ef95bfa..07d1a9e14cb 100644 --- a/tests/components/binary_sensor/test_tod.py +++ b/tests/components/tod/test_binary_sensor.py @@ -78,7 +78,7 @@ class TestBinarySensorTod(unittest.TestCase): } ] } - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=test_time): setup_component(self.hass, 'binary_sensor', config) @@ -100,7 +100,7 @@ class TestBinarySensorTod(unittest.TestCase): }, ] } - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=test_time): setup_component(self.hass, 'binary_sensor', config) @@ -122,7 +122,7 @@ class TestBinarySensorTod(unittest.TestCase): }, ] } - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=test_time): setup_component(self.hass, 'binary_sensor', config) @@ -131,7 +131,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=test_time + timedelta(hours=1)): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -155,7 +155,7 @@ class TestBinarySensorTod(unittest.TestCase): } ] } - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=test_time): setup_component(self.hass, 'binary_sensor', config) @@ -178,7 +178,7 @@ class TestBinarySensorTod(unittest.TestCase): } ] } - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=test_time): setup_component(self.hass, 'binary_sensor', config) @@ -188,7 +188,7 @@ class TestBinarySensorTod(unittest.TestCase): switchover_time = self.hass.config.time_zone.localize( datetime(2019, 1, 11, 4, 59, 0)).astimezone(pytz.UTC) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=switchover_time): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -197,7 +197,7 @@ class TestBinarySensorTod(unittest.TestCase): state = self.hass.states.get('binary_sensor.night') assert state.state == STATE_ON - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=switchover_time + timedelta( minutes=1, seconds=1)): @@ -229,7 +229,7 @@ class TestBinarySensorTod(unittest.TestCase): } entity_id = 'binary_sensor.day' testtime = sunrise + timedelta(seconds=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) @@ -238,7 +238,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_OFF testtime = sunrise - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -249,7 +249,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_ON testtime = sunrise + timedelta(seconds=1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -262,7 +262,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset + timedelta(seconds=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -275,7 +275,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -288,7 +288,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset + timedelta(seconds=1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -319,7 +319,7 @@ class TestBinarySensorTod(unittest.TestCase): } entity_id = 'binary_sensor.night' testtime = sunset + timedelta(minutes=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) @@ -328,7 +328,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_OFF testtime = sunset - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -339,7 +339,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_ON testtime = sunset + timedelta(minutes=1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -350,7 +350,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_ON testtime = sunrise + timedelta(minutes=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -361,7 +361,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_ON testtime = sunrise - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -374,7 +374,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_OFF testtime = sunrise + timedelta(minutes=1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -408,7 +408,7 @@ class TestBinarySensorTod(unittest.TestCase): ] } testtime = after + timedelta(seconds=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) @@ -417,7 +417,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_OFF testtime = after - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) @@ -427,7 +427,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_ON testtime = before + timedelta(seconds=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) @@ -437,7 +437,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_ON testtime = before - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) @@ -447,7 +447,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_OFF testtime = before + timedelta(seconds=1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) @@ -475,7 +475,7 @@ class TestBinarySensorTod(unittest.TestCase): ] } testtime = after + timedelta(seconds=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) @@ -484,7 +484,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_OFF testtime = after - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) @@ -516,7 +516,7 @@ class TestBinarySensorTod(unittest.TestCase): } entity_id = 'binary_sensor.day' testtime = test_time - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) @@ -525,7 +525,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_OFF testtime = sunrise + timedelta(seconds=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -536,7 +536,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_OFF testtime = sunrise - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -547,7 +547,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_ON testtime = sunrise + timedelta(seconds=1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -560,7 +560,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset + timedelta(seconds=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -573,7 +573,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -586,7 +586,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset + timedelta(seconds=1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -620,7 +620,7 @@ class TestBinarySensorTod(unittest.TestCase): } entity_id = 'binary_sensor.day' testtime = test_time - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) @@ -629,7 +629,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_OFF testtime = sunrise + timedelta(seconds=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -640,7 +640,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_OFF testtime = sunrise - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -651,7 +651,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_ON testtime = sunrise + timedelta(seconds=1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -664,7 +664,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset + timedelta(seconds=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -677,7 +677,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -690,7 +690,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset + timedelta(seconds=1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -724,7 +724,7 @@ class TestBinarySensorTod(unittest.TestCase): } entity_id = 'binary_sensor.day' testtime = sunrise + timedelta(seconds=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) @@ -733,7 +733,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_OFF testtime = sunrise - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -744,7 +744,7 @@ class TestBinarySensorTod(unittest.TestCase): assert state.state == STATE_ON testtime = sunrise + timedelta(seconds=1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -757,7 +757,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset + timedelta(seconds=-1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -770,7 +770,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -783,7 +783,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass.block_till_done() testtime = sunset + timedelta(seconds=1) - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -798,7 +798,7 @@ class TestBinarySensorTod(unittest.TestCase): self.hass, 'sunrise', dt_util.as_utc(test_time)) + timedelta(hours=-1, minutes=-30)) testtime = sunrise - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { @@ -828,7 +828,7 @@ class TestBinarySensorTod(unittest.TestCase): # Internally the entity_id = 'binary_sensor.day' testtime = test_time - with patch('homeassistant.components.binary_sensor.tod.dt_util.utcnow', + with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) diff --git a/tests/components/tomato/__init__.py b/tests/components/tomato/__init__.py new file mode 100644 index 00000000000..ac6fe559490 --- /dev/null +++ b/tests/components/tomato/__init__.py @@ -0,0 +1 @@ +"""Tests for the tomato component.""" diff --git a/tests/components/device_tracker/test_tomato.py b/tests/components/tomato/test_device_tracker.py similarity index 98% rename from tests/components/device_tracker/test_tomato.py rename to tests/components/tomato/test_device_tracker.py index 0c20350a845..0d865b2d6dc 100644 --- a/tests/components/device_tracker/test_tomato.py +++ b/tests/components/tomato/test_device_tracker.py @@ -5,7 +5,8 @@ import requests import requests_mock import voluptuous as vol -from homeassistant.components.device_tracker import DOMAIN, tomato as tomato +from homeassistant.components.device_tracker import DOMAIN +import homeassistant.components.tomato.device_tracker as tomato from homeassistant.const import (CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_PLATFORM, CONF_VERIFY_SSL) @@ -39,8 +40,8 @@ def mock_session_response(*args, **kwargs): @pytest.fixture def mock_exception_logger(): """Mock pyunifi.""" - with mock.patch('homeassistant.components.device_tracker' - '.tomato._LOGGER.exception') as mock_exception_logger: + with mock.patch('homeassistant.components.tomato.device_tracker' + '._LOGGER.exception') as mock_exception_logger: yield mock_exception_logger diff --git a/tests/components/device_tracker/test_tplink.py b/tests/components/tplink/test_device_tracker.py similarity index 96% rename from tests/components/device_tracker/test_tplink.py rename to tests/components/tplink/test_device_tracker.py index 8f226f449b0..f1d60d46762 100644 --- a/tests/components/device_tracker/test_tplink.py +++ b/tests/components/tplink/test_device_tracker.py @@ -4,7 +4,7 @@ import os import pytest from homeassistant.components import device_tracker -from homeassistant.components.device_tracker.tplink import Tplink4DeviceScanner +from homeassistant.components.tplink.device_tracker import Tplink4DeviceScanner from homeassistant.const import (CONF_PLATFORM, CONF_PASSWORD, CONF_USERNAME, CONF_HOST) import requests_mock diff --git a/tests/components/transport_nsw/__init__.py b/tests/components/transport_nsw/__init__.py new file mode 100644 index 00000000000..978a4df0b89 --- /dev/null +++ b/tests/components/transport_nsw/__init__.py @@ -0,0 +1 @@ +"""Tests for the transport_nsw component.""" diff --git a/tests/components/sensor/test_transport_nsw.py b/tests/components/transport_nsw/test_sensor.py similarity index 100% rename from tests/components/sensor/test_transport_nsw.py rename to tests/components/transport_nsw/test_sensor.py diff --git a/tests/components/trend/__init__.py b/tests/components/trend/__init__.py new file mode 100644 index 00000000000..612560a18c0 --- /dev/null +++ b/tests/components/trend/__init__.py @@ -0,0 +1 @@ +"""Tests for trend component.""" diff --git a/tests/components/binary_sensor/test_trend.py b/tests/components/trend/test_binary_sensor.py similarity index 100% rename from tests/components/binary_sensor/test_trend.py rename to tests/components/trend/test_binary_sensor.py diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 4786370f24f..140a938201b 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -9,7 +9,7 @@ import requests import homeassistant.components.http as http import homeassistant.components.tts as tts -from homeassistant.components.tts.demo import DemoProvider +from homeassistant.components.demo.tts import DemoProvider from homeassistant.components.media_player.const import ( SERVICE_PLAY_MEDIA, MEDIA_TYPE_MUSIC, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, DOMAIN as DOMAIN_MP) @@ -229,7 +229,7 @@ class TestTTS: "265944c108cbb00b2a621be5930513e03a0bb2cd_de_{0}_demo.mp3".format( opt_hash))) - @patch('homeassistant.components.tts.demo.DemoProvider.default_options', + @patch('homeassistant.components.demo.tts.DemoProvider.default_options', new_callable=PropertyMock(return_value={'voice': 'alex'})) def test_setup_component_and_test_with_service_options_def(self, def_mock): """Set up the demo platform and call service with default options.""" @@ -519,7 +519,7 @@ class TestTTS: with assert_setup_component(1, tts.DOMAIN): setup_component(self.hass, tts.DOMAIN, config) - with patch('homeassistant.components.tts.demo.DemoProvider.' + with patch('homeassistant.components.demo.tts.DemoProvider.' 'get_tts_audio', return_value=(None, None)): self.hass.services.call(tts.DOMAIN, 'demo_say', { tts.ATTR_MESSAGE: "I person is on front of your door.", @@ -531,7 +531,7 @@ class TestTTS: "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd" \ "_en_-_demo.mp3".format(self.hass.config.api.base_url) - @patch('homeassistant.components.tts.demo.DemoProvider.get_tts_audio', + @patch('homeassistant.components.demo.tts.DemoProvider.get_tts_audio', return_value=(None, None)) def test_setup_component_test_with_error_on_get_tts(self, tts_mock): """Set up demo platform with wrong get_tts_audio.""" diff --git a/tests/components/uk_transport/__init__.py b/tests/components/uk_transport/__init__.py new file mode 100644 index 00000000000..2e114fc5932 --- /dev/null +++ b/tests/components/uk_transport/__init__.py @@ -0,0 +1 @@ +"""Tests for the uk_transport component.""" diff --git a/tests/components/sensor/test_uk_transport.py b/tests/components/uk_transport/test_sensor.py similarity index 98% rename from tests/components/sensor/test_uk_transport.py rename to tests/components/uk_transport/test_sensor.py index 65e6b7f0f38..50730d89404 100644 --- a/tests/components/sensor/test_uk_transport.py +++ b/tests/components/uk_transport/test_sensor.py @@ -4,7 +4,7 @@ import re import requests_mock import unittest -from homeassistant.components.sensor.uk_transport import ( +from homeassistant.components.uk_transport.sensor import ( UkTransportSensor, ATTR_ATCOCODE, ATTR_LOCALITY, ATTR_STOP_NAME, ATTR_NEXT_BUSES, ATTR_STATION_CODE, ATTR_CALLING_AT, ATTR_NEXT_TRAINS, diff --git a/tests/components/device_tracker/test_unifi.py b/tests/components/unifi/test_device_tracker.py similarity index 97% rename from tests/components/device_tracker/test_unifi.py rename to tests/components/unifi/test_device_tracker.py index 7a791b334aa..0fb0751c5b6 100644 --- a/tests/components/device_tracker/test_unifi.py +++ b/tests/components/unifi/test_device_tracker.py @@ -8,7 +8,8 @@ import pytest import voluptuous as vol import homeassistant.util.dt as dt_util -from homeassistant.components.device_tracker import DOMAIN, unifi as unifi +from homeassistant.components.device_tracker import DOMAIN +import homeassistant.components.unifi.device_tracker as unifi from homeassistant.const import (CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PLATFORM, CONF_VERIFY_SSL, CONF_MONITORED_CONDITIONS) @@ -25,7 +26,7 @@ def mock_ctrl(): @pytest.fixture def mock_scanner(): """Mock UnifyScanner.""" - with mock.patch('homeassistant.components.device_tracker.unifi' + with mock.patch('homeassistant.components.unifi.device_tracker' '.UnifiScanner') as scanner: yield scanner diff --git a/tests/components/unifi_direct/__init__.py b/tests/components/unifi_direct/__init__.py new file mode 100644 index 00000000000..7f8d0fa29f7 --- /dev/null +++ b/tests/components/unifi_direct/__init__.py @@ -0,0 +1 @@ +"""Tests for the unifi_direct component.""" diff --git a/tests/components/device_tracker/test_unifi_direct.py b/tests/components/unifi_direct/test_device_tracker.py similarity index 96% rename from tests/components/device_tracker/test_unifi_direct.py rename to tests/components/unifi_direct/test_device_tracker.py index 1b1dc1a7cb5..ba40a09aa59 100644 --- a/tests/components/device_tracker/test_unifi_direct.py +++ b/tests/components/unifi_direct/test_device_tracker.py @@ -11,7 +11,7 @@ from homeassistant.components import device_tracker from homeassistant.components.device_tracker import ( CONF_CONSIDER_HOME, CONF_TRACK_NEW, CONF_AWAY_HIDE, CONF_NEW_DEVICE_DEFAULTS) -from homeassistant.components.device_tracker.unifi_direct import ( +from homeassistant.components.unifi_direct.device_tracker import ( DOMAIN, CONF_PORT, PLATFORM_SCHEMA, _response_to_json, get_scanner) from homeassistant.const import (CONF_PLATFORM, CONF_PASSWORD, CONF_USERNAME, CONF_HOST) @@ -19,8 +19,8 @@ from homeassistant.const import (CONF_PLATFORM, CONF_PASSWORD, CONF_USERNAME, from tests.common import ( assert_setup_component, mock_component, load_fixture) -scanner_path = 'homeassistant.components.device_tracker.' + \ - 'unifi_direct.UnifiDeviceScanner' +scanner_path = 'homeassistant.components.unifi_direct.device_tracker.' + \ + 'UnifiDeviceScanner' @pytest.fixture(autouse=True) diff --git a/tests/components/universal/__init__.py b/tests/components/universal/__init__.py new file mode 100644 index 00000000000..9a814402b9c --- /dev/null +++ b/tests/components/universal/__init__.py @@ -0,0 +1 @@ +"""Tests for the universal component.""" diff --git a/tests/components/media_player/test_universal.py b/tests/components/universal/test_media_player.py similarity index 99% rename from tests/components/media_player/test_universal.py rename to tests/components/universal/test_media_player.py index ee86735a063..e773c560510 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/universal/test_media_player.py @@ -10,7 +10,7 @@ import homeassistant.components.switch as switch import homeassistant.components.input_number as input_number import homeassistant.components.input_select as input_select import homeassistant.components.media_player as media_player -import homeassistant.components.media_player.universal as universal +import homeassistant.components.universal.media_player as universal from homeassistant.util.async_ import run_coroutine_threadsafe from tests.common import mock_service, get_test_home_assistant diff --git a/tests/components/upc_connect/__init__.py b/tests/components/upc_connect/__init__.py new file mode 100644 index 00000000000..d491190d111 --- /dev/null +++ b/tests/components/upc_connect/__init__.py @@ -0,0 +1 @@ +"""Tests for the upc_connect component.""" diff --git a/tests/components/device_tracker/test_upc_connect.py b/tests/components/upc_connect/test_device_tracker.py similarity index 98% rename from tests/components/device_tracker/test_upc_connect.py rename to tests/components/upc_connect/test_device_tracker.py index 677a6d1f310..97167ad0801 100644 --- a/tests/components/device_tracker/test_upc_connect.py +++ b/tests/components/upc_connect/test_device_tracker.py @@ -5,7 +5,7 @@ from asynctest import patch import pytest from homeassistant.components.device_tracker import DOMAIN -import homeassistant.components.device_tracker.upc_connect as platform +import homeassistant.components.upc_connect.device_tracker as platform from homeassistant.const import CONF_HOST, CONF_PLATFORM from homeassistant.setup import async_setup_component @@ -65,7 +65,7 @@ async def test_setup_platform_timeout_webservice(hass, caplog, aioclient_mock): assert 'Error setting up platform' in caplog.text -@patch('homeassistant.components.device_tracker.upc_connect.' +@patch('homeassistant.components.upc_connect.device_tracker.' 'UPCDeviceScanner.async_scan_devices', return_value=async_scan_devices_mock) async def test_setup_platform(scan_mock, hass, aioclient_mock): diff --git a/tests/components/uptime/__init__.py b/tests/components/uptime/__init__.py new file mode 100644 index 00000000000..f098fd12eb9 --- /dev/null +++ b/tests/components/uptime/__init__.py @@ -0,0 +1 @@ +"""Tests for the uptime component.""" diff --git a/tests/components/sensor/test_uptime.py b/tests/components/uptime/test_sensor.py similarity index 98% rename from tests/components/sensor/test_uptime.py rename to tests/components/uptime/test_sensor.py index 00552dd9e49..2f024b36c45 100644 --- a/tests/components/sensor/test_uptime.py +++ b/tests/components/uptime/test_sensor.py @@ -5,7 +5,7 @@ from datetime import timedelta from homeassistant.util.async_ import run_coroutine_threadsafe from homeassistant.setup import setup_component -from homeassistant.components.sensor.uptime import UptimeSensor +from homeassistant.components.uptime.sensor import UptimeSensor from tests.common import get_test_home_assistant diff --git a/tests/components/usgs_earthquakes_feed/__init__.py b/tests/components/usgs_earthquakes_feed/__init__.py new file mode 100644 index 00000000000..8c5f1609ae4 --- /dev/null +++ b/tests/components/usgs_earthquakes_feed/__init__.py @@ -0,0 +1 @@ +"""Tests for the usgs_earthquakes_feed component.""" diff --git a/tests/components/geo_location/test_usgs_earthquakes_feed.py b/tests/components/usgs_earthquakes_feed/test_geo_location.py similarity index 97% rename from tests/components/geo_location/test_usgs_earthquakes_feed.py rename to tests/components/usgs_earthquakes_feed/test_geo_location.py index f0383c221c4..b2bc0bc4c25 100644 --- a/tests/components/geo_location/test_usgs_earthquakes_feed.py +++ b/tests/components/usgs_earthquakes_feed/test_geo_location.py @@ -4,11 +4,10 @@ from unittest.mock import patch, MagicMock, call from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE -from homeassistant.components.geo_location\ - .usgs_earthquakes_feed import \ - ATTR_ALERT, ATTR_EXTERNAL_ID, SCAN_INTERVAL, ATTR_PLACE, \ - ATTR_MAGNITUDE, ATTR_STATUS, ATTR_TYPE, \ - ATTR_TIME, ATTR_UPDATED, CONF_FEED_TYPE +from homeassistant.components.usgs_earthquakes_feed.geo_location import ( + ATTR_ALERT, ATTR_EXTERNAL_ID, SCAN_INTERVAL, ATTR_PLACE, + ATTR_MAGNITUDE, ATTR_STATUS, ATTR_TYPE, + ATTR_TIME, ATTR_UPDATED, CONF_FEED_TYPE) from homeassistant.const import EVENT_HOMEASSISTANT_START, \ CONF_RADIUS, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_FRIENDLY_NAME, \ ATTR_UNIT_OF_MEASUREMENT, ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index ee291439a2c..6b8705bf776 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -6,10 +6,11 @@ from unittest.mock import patch from contextlib import contextmanager from tests.common import async_fire_time_changed -from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.const import EVENT_HOMEASSISTANT_START, ATTR_ENTITY_ID from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from homeassistant.components.utility_meter.const import DOMAIN +from homeassistant.components.utility_meter.const import ( + DOMAIN, SERVICE_SELECT_TARIFF, ATTR_TARIFF) from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN _LOGGER = logging.getLogger(__name__) @@ -31,7 +32,7 @@ async def test_state(hass): 'utility_meter': { 'energy_bill': { 'source': 'sensor.energy', - } + 'tariffs': ['onpeak', 'midpeak', 'offpeak']}, } } @@ -51,11 +52,43 @@ async def test_state(hass): force_update=True) await hass.async_block_till_done() - state = hass.states.get('sensor.energy_bill') + state = hass.states.get('sensor.energy_bill_onpeak') assert state is not None - assert state.state == '1' + state = hass.states.get('sensor.energy_bill_midpeak') + assert state is not None + assert state.state == '0' + + state = hass.states.get('sensor.energy_bill_offpeak') + assert state is not None + assert state.state == '0' + + await hass.services.async_call(DOMAIN, SERVICE_SELECT_TARIFF, { + ATTR_ENTITY_ID: 'utility_meter.energy_bill', ATTR_TARIFF: 'offpeak' + }, blocking=True) + + await hass.async_block_till_done() + + now = dt_util.utcnow() + timedelta(seconds=20) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.states.async_set(entity_id, 6, {"unit_of_measurement": "kWh"}, + force_update=True) + await hass.async_block_till_done() + + state = hass.states.get('sensor.energy_bill_onpeak') + assert state is not None + assert state.state == '1' + + state = hass.states.get('sensor.energy_bill_midpeak') + assert state is not None + assert state.state == '0' + + state = hass.states.get('sensor.energy_bill_offpeak') + assert state is not None + assert state.state == '3' + async def test_net_consumption(hass): """Test utility sensor state.""" diff --git a/tests/components/uvc/__init__.py b/tests/components/uvc/__init__.py new file mode 100644 index 00000000000..6ea965a14cb --- /dev/null +++ b/tests/components/uvc/__init__.py @@ -0,0 +1 @@ +"""Tests for the uvc component.""" diff --git a/tests/components/camera/test_uvc.py b/tests/components/uvc/test_camera.py similarity index 99% rename from tests/components/camera/test_uvc.py rename to tests/components/uvc/test_camera.py index 476e612eb06..29982c250fb 100644 --- a/tests/components/camera/test_uvc.py +++ b/tests/components/uvc/test_camera.py @@ -9,7 +9,7 @@ from uvcclient import nvr from homeassistant.exceptions import PlatformNotReady from homeassistant.setup import setup_component -from homeassistant.components.camera import uvc +from homeassistant.components.uvc import camera as uvc from tests.common import get_test_home_assistant import pytest diff --git a/tests/components/version/__init__.py b/tests/components/version/__init__.py new file mode 100644 index 00000000000..c282c31945c --- /dev/null +++ b/tests/components/version/__init__.py @@ -0,0 +1 @@ +"""Tests for the version component.""" diff --git a/tests/components/sensor/test_version.py b/tests/components/version/test_sensor.py similarity index 100% rename from tests/components/sensor/test_version.py rename to tests/components/version/test_sensor.py diff --git a/tests/components/voicerss/__init__.py b/tests/components/voicerss/__init__.py new file mode 100644 index 00000000000..9c037a14465 --- /dev/null +++ b/tests/components/voicerss/__init__.py @@ -0,0 +1 @@ +"""The tests for VoiceRSS tts platforms.""" diff --git a/tests/components/tts/test_voicerss.py b/tests/components/voicerss/test_tts.py similarity index 99% rename from tests/components/tts/test_voicerss.py rename to tests/components/voicerss/test_tts.py index af4bdf3976c..cd0e20cb9fa 100644 --- a/tests/components/tts/test_voicerss.py +++ b/tests/components/voicerss/test_tts.py @@ -11,7 +11,7 @@ from homeassistant.setup import setup_component from tests.common import ( get_test_home_assistant, assert_setup_component, mock_service) -from .test_init import mutagen_mock # noqa +from tests.components.tts.test_init import mutagen_mock # noqa class TestTTSVoiceRSSPlatform: diff --git a/tests/components/binary_sensor/test_vultr.py b/tests/components/vultr/test_binary_sensor.py similarity index 98% rename from tests/components/binary_sensor/test_vultr.py rename to tests/components/vultr/test_binary_sensor.py index 3dcba033d73..fba002af53a 100644 --- a/tests/components/binary_sensor/test_vultr.py +++ b/tests/components/vultr/test_binary_sensor.py @@ -7,7 +7,7 @@ import requests_mock import pytest import voluptuous as vol -from homeassistant.components.binary_sensor import vultr +from homeassistant.components.vultr import binary_sensor as vultr from homeassistant.components import vultr as base_vultr from homeassistant.components.vultr import ( ATTR_ALLOWED_BANDWIDTH, ATTR_AUTO_BACKUPS, ATTR_IPV4_ADDRESS, diff --git a/tests/components/sensor/test_vultr.py b/tests/components/vultr/test_sensor.py similarity index 99% rename from tests/components/sensor/test_vultr.py rename to tests/components/vultr/test_sensor.py index 333e4938ba4..2ecc682bfb1 100644 --- a/tests/components/sensor/test_vultr.py +++ b/tests/components/vultr/test_sensor.py @@ -7,7 +7,7 @@ import pytest import requests_mock import voluptuous as vol -from homeassistant.components.sensor import vultr +import homeassistant.components.vultr.sensor as vultr from homeassistant.components import vultr as base_vultr from homeassistant.components.vultr import CONF_SUBSCRIPTION from homeassistant.const import ( diff --git a/tests/components/switch/test_vultr.py b/tests/components/vultr/test_switch.py similarity index 99% rename from tests/components/switch/test_vultr.py rename to tests/components/vultr/test_switch.py index f5e94e3e1b1..9b95b9a164c 100644 --- a/tests/components/switch/test_vultr.py +++ b/tests/components/vultr/test_switch.py @@ -7,7 +7,7 @@ import requests_mock import pytest import voluptuous as vol -from homeassistant.components.switch import vultr +from homeassistant.components.vultr import switch as vultr from homeassistant.components import vultr as base_vultr from homeassistant.components.vultr import ( ATTR_ALLOWED_BANDWIDTH, ATTR_AUTO_BACKUPS, ATTR_IPV4_ADDRESS, diff --git a/tests/components/switch/test_wake_on_lan.py b/tests/components/wake_on_lan/test_switch.py similarity index 100% rename from tests/components/switch/test_wake_on_lan.py rename to tests/components/wake_on_lan/test_switch.py diff --git a/tests/components/websocket_api/test_auth.py b/tests/components/websocket_api/test_auth.py index e2c6e303326..cd940708497 100644 --- a/tests/components/websocket_api/test_auth.py +++ b/tests/components/websocket_api/test_auth.py @@ -1,7 +1,8 @@ """Test auth of websocket API.""" from unittest.mock import patch -from homeassistant.components.websocket_api.const import URL +from homeassistant.components.websocket_api.const import ( + URL, SIGNAL_WEBSOCKET_CONNECTED, SIGNAL_WEBSOCKET_DISCONNECTED) from homeassistant.components.websocket_api.auth import ( TYPE_AUTH, TYPE_AUTH_INVALID, TYPE_AUTH_OK, TYPE_AUTH_REQUIRED) @@ -24,6 +25,28 @@ async def test_auth_via_msg(no_auth_websocket_client, legacy_auth): assert msg['type'] == TYPE_AUTH_OK +async def test_auth_events(hass, no_auth_websocket_client, legacy_auth): + """Test authenticating.""" + connected_evt = [] + hass.helpers.dispatcher.async_dispatcher_connect( + SIGNAL_WEBSOCKET_CONNECTED, + lambda: connected_evt.append(1)) + disconnected_evt = [] + hass.helpers.dispatcher.async_dispatcher_connect( + SIGNAL_WEBSOCKET_DISCONNECTED, + lambda: disconnected_evt.append(1)) + + await test_auth_via_msg(no_auth_websocket_client, legacy_auth) + + assert len(connected_evt) == 1 + assert not disconnected_evt + + await no_auth_websocket_client.close() + await hass.async_block_till_done() + + assert len(disconnected_evt) == 1 + + async def test_auth_via_msg_incorrect_pass(no_auth_websocket_client): """Test authenticating.""" with patch('homeassistant.components.websocket_api.auth.' @@ -41,6 +64,29 @@ async def test_auth_via_msg_incorrect_pass(no_auth_websocket_client): assert msg['message'] == 'Invalid access token or password' +async def test_auth_events_incorrect_pass(hass, no_auth_websocket_client): + """Test authenticating.""" + connected_evt = [] + hass.helpers.dispatcher.async_dispatcher_connect( + SIGNAL_WEBSOCKET_CONNECTED, + lambda: connected_evt.append(1)) + disconnected_evt = [] + hass.helpers.dispatcher.async_dispatcher_connect( + SIGNAL_WEBSOCKET_DISCONNECTED, + lambda: disconnected_evt.append(1)) + + await test_auth_via_msg_incorrect_pass(no_auth_websocket_client) + + assert not connected_evt + assert not disconnected_evt + + await no_auth_websocket_client.close() + await hass.async_block_till_done() + + assert not connected_evt + assert not disconnected_evt + + async def test_pre_auth_only_auth_allowed(no_auth_websocket_client): """Verify that before authentication, only auth messages are allowed.""" await no_auth_websocket_client.send_json({ diff --git a/tests/components/websocket_api/test_init.py b/tests/components/websocket_api/test_init.py index 272ac3870ed..08ea655fdf0 100644 --- a/tests/components/websocket_api/test_init.py +++ b/tests/components/websocket_api/test_init.py @@ -4,6 +4,7 @@ from unittest.mock import patch, Mock from aiohttp import WSMsgType import pytest +import voluptuous as vol from homeassistant.components.websocket_api import const, messages @@ -90,3 +91,26 @@ async def test_handler_failing(hass, websocket_client): assert msg['type'] == const.TYPE_RESULT assert not msg['success'] assert msg['error']['code'] == const.ERR_UNKNOWN_ERROR + + +async def test_invalid_vol(hass, websocket_client): + """Test a command that raises invalid vol error.""" + hass.components.websocket_api.async_register_command( + 'bla', Mock(side_effect=TypeError), + messages.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + 'type': 'bla', + vol.Required('test_config'): str + })) + + await websocket_client.send_json({ + 'id': 5, + 'type': 'bla', + 'test_config': 5 + }) + + msg = await websocket_client.receive_json() + assert msg['id'] == 5 + assert msg['type'] == const.TYPE_RESULT + assert not msg['success'] + assert msg['error']['code'] == const.ERR_INVALID_FORMAT + assert 'expected str for dictionary value' in msg['error']['message'] diff --git a/tests/components/websocket_api/test_sensor.py b/tests/components/websocket_api/test_sensor.py new file mode 100644 index 00000000000..b02cc53f38d --- /dev/null +++ b/tests/components/websocket_api/test_sensor.py @@ -0,0 +1,30 @@ +"""Test cases for the API stream sensor.""" + +from homeassistant.bootstrap import async_setup_component + +from tests.common import assert_setup_component +from .test_auth import test_auth_via_msg + + +async def test_websocket_api(hass, no_auth_websocket_client, legacy_auth): + """Test API streams.""" + with assert_setup_component(1): + await async_setup_component(hass, 'sensor', { + 'sensor': { + 'platform': 'websocket_api', + } + }) + + state = hass.states.get('sensor.connected_clients') + assert state.state == '0' + + await test_auth_via_msg(no_auth_websocket_client, legacy_auth) + + state = hass.states.get('sensor.connected_clients') + assert state.state == '1' + + await no_auth_websocket_client.close() + await hass.async_block_till_done() + + state = hass.states.get('sensor.connected_clients') + assert state.state == '0' diff --git a/tests/components/workday/__init__.py b/tests/components/workday/__init__.py new file mode 100644 index 00000000000..b731fce2edc --- /dev/null +++ b/tests/components/workday/__init__.py @@ -0,0 +1 @@ +"""Tests the HASS workday binary sensor.""" diff --git a/tests/components/binary_sensor/test_workday.py b/tests/components/workday/test_binary_sensor.py similarity index 98% rename from tests/components/binary_sensor/test_workday.py rename to tests/components/workday/test_binary_sensor.py index 5aa5a5dad5c..6895032bc94 100644 --- a/tests/components/binary_sensor/test_workday.py +++ b/tests/components/workday/test_binary_sensor.py @@ -2,14 +2,14 @@ from datetime import date from unittest.mock import patch -from homeassistant.components.binary_sensor.workday import day_to_string +from homeassistant.components.workday.binary_sensor import day_to_string from homeassistant.setup import setup_component from tests.common import ( get_test_home_assistant, assert_setup_component) -FUNCTION_PATH = 'homeassistant.components.binary_sensor.workday.get_date' +FUNCTION_PATH = 'homeassistant.components.workday.binary_sensor.get_date' class TestWorkdaySetup: diff --git a/tests/components/worldclock/__init__.py b/tests/components/worldclock/__init__.py new file mode 100644 index 00000000000..49ef84bd2fe --- /dev/null +++ b/tests/components/worldclock/__init__.py @@ -0,0 +1 @@ +"""Tests for the worldclock component.""" diff --git a/tests/components/sensor/test_worldclock.py b/tests/components/worldclock/test_sensor.py similarity index 100% rename from tests/components/sensor/test_worldclock.py rename to tests/components/worldclock/test_sensor.py diff --git a/tests/components/wsdot/__init__.py b/tests/components/wsdot/__init__.py new file mode 100644 index 00000000000..2f28c29ae8d --- /dev/null +++ b/tests/components/wsdot/__init__.py @@ -0,0 +1 @@ +"""Tests for the wsdot component.""" diff --git a/tests/components/sensor/test_wsdot.py b/tests/components/wsdot/test_sensor.py similarity index 95% rename from tests/components/sensor/test_wsdot.py rename to tests/components/wsdot/test_sensor.py index b90529693b6..2538bf303ee 100644 --- a/tests/components/sensor/test_wsdot.py +++ b/tests/components/wsdot/test_sensor.py @@ -6,8 +6,8 @@ import unittest import requests_mock from tests.common import get_test_home_assistant, load_fixture -from homeassistant.components.sensor import wsdot -from homeassistant.components.sensor.wsdot import ( +import homeassistant.components.wsdot.sensor as wsdot +from homeassistant.components.wsdot.sensor import ( ATTR_DESCRIPTION, ATTR_TIME_UPDATED, CONF_API_KEY, CONF_ID, CONF_NAME, CONF_TRAVEL_TIMES, RESOURCE, SCAN_INTERVAL) from homeassistant.setup import setup_component diff --git a/tests/components/wunderground/__init__.py b/tests/components/wunderground/__init__.py new file mode 100644 index 00000000000..d3f839a35f6 --- /dev/null +++ b/tests/components/wunderground/__init__.py @@ -0,0 +1 @@ +"""Tests for the wunderground component.""" diff --git a/tests/components/sensor/test_wunderground.py b/tests/components/wunderground/test_sensor.py similarity index 98% rename from tests/components/sensor/test_wunderground.py rename to tests/components/wunderground/test_sensor.py index 3f490b4ab12..02d8842dd69 100644 --- a/tests/components/sensor/test_wunderground.py +++ b/tests/components/wunderground/test_sensor.py @@ -4,7 +4,7 @@ import aiohttp from pytest import raises -from homeassistant.components.sensor import wunderground +import homeassistant.components.wunderground.sensor as wunderground from homeassistant.const import TEMP_CELSIUS, LENGTH_INCHES, STATE_UNKNOWN from homeassistant.exceptions import PlatformNotReady from homeassistant.setup import async_setup_component diff --git a/tests/components/xiaomi/__init__.py b/tests/components/xiaomi/__init__.py new file mode 100644 index 00000000000..46404fc6f32 --- /dev/null +++ b/tests/components/xiaomi/__init__.py @@ -0,0 +1 @@ +"""Tests for the xiaomi component.""" diff --git a/tests/components/device_tracker/test_xiaomi.py b/tests/components/xiaomi/test_device_tracker.py similarity index 96% rename from tests/components/device_tracker/test_xiaomi.py rename to tests/components/xiaomi/test_device_tracker.py index 7b141159256..57a794c2f3d 100644 --- a/tests/components/device_tracker/test_xiaomi.py +++ b/tests/components/xiaomi/test_device_tracker.py @@ -4,8 +4,9 @@ from asynctest import mock, patch import requests -from homeassistant.components.device_tracker import DOMAIN, xiaomi as xiaomi -from homeassistant.components.device_tracker.xiaomi import get_scanner +from homeassistant.components.device_tracker import DOMAIN +import homeassistant.components.xiaomi.device_tracker as xiaomi +from homeassistant.components.xiaomi.device_tracker import get_scanner from homeassistant.const import (CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PLATFORM) @@ -150,7 +151,7 @@ def mocked_requests(*args, **kwargs): @patch( - 'homeassistant.components.device_tracker.xiaomi.XiaomiDeviceScanner', + 'homeassistant.components.xiaomi.device_tracker.XiaomiDeviceScanner', return_value=mock.MagicMock()) async def test_config(xiaomi_mock, hass): """Testing minimal configuration.""" @@ -172,7 +173,7 @@ async def test_config(xiaomi_mock, hass): @patch( - 'homeassistant.components.device_tracker.xiaomi.XiaomiDeviceScanner', + 'homeassistant.components.xiaomi.device_tracker.XiaomiDeviceScanner', return_value=mock.MagicMock()) async def test_config_full(xiaomi_mock, hass): """Testing full configuration.""" diff --git a/tests/components/yamaha/__init__.py b/tests/components/yamaha/__init__.py new file mode 100644 index 00000000000..0df69c55380 --- /dev/null +++ b/tests/components/yamaha/__init__.py @@ -0,0 +1 @@ +"""Tests for the yamaha component.""" diff --git a/tests/components/media_player/test_yamaha.py b/tests/components/yamaha/test_media_player.py similarity index 97% rename from tests/components/media_player/test_yamaha.py rename to tests/components/yamaha/test_media_player.py index 13d5a785e7f..8056cdd2f80 100644 --- a/tests/components/media_player/test_yamaha.py +++ b/tests/components/yamaha/test_media_player.py @@ -4,7 +4,7 @@ from unittest.mock import patch, MagicMock from homeassistant.setup import setup_component import homeassistant.components.media_player as mp -from homeassistant.components.media_player import yamaha +from homeassistant.components.yamaha import media_player as yamaha from tests.common import get_test_home_assistant diff --git a/tests/components/yandextts/__init__.py b/tests/components/yandextts/__init__.py new file mode 100644 index 00000000000..54968b3605f --- /dev/null +++ b/tests/components/yandextts/__init__.py @@ -0,0 +1 @@ +"""The tests for YandexTTS tts platforms.""" diff --git a/tests/components/tts/test_yandextts.py b/tests/components/yandextts/test_tts.py similarity index 99% rename from tests/components/tts/test_yandextts.py rename to tests/components/yandextts/test_tts.py index 70c75b1f2ed..dd382271338 100644 --- a/tests/components/tts/test_yandextts.py +++ b/tests/components/yandextts/test_tts.py @@ -10,7 +10,7 @@ from homeassistant.components.media_player.const import ( from tests.common import ( get_test_home_assistant, assert_setup_component, mock_service) -from .test_init import mutagen_mock # noqa +from tests.components.tts.test_init import mutagen_mock # noqa class TestTTSYandexPlatform: diff --git a/tests/components/yessssms/__init__.py b/tests/components/yessssms/__init__.py new file mode 100644 index 00000000000..bf8e562009b --- /dev/null +++ b/tests/components/yessssms/__init__.py @@ -0,0 +1 @@ +"""Tests for the yessssms component.""" diff --git a/tests/components/notify/test_yessssms.py b/tests/components/yessssms/test_notify.py similarity index 90% rename from tests/components/notify/test_yessssms.py rename to tests/components/yessssms/test_notify.py index 42dd7be1aca..837fee43f05 100644 --- a/tests/components/notify/test_yessssms.py +++ b/tests/components/yessssms/test_notify.py @@ -1,7 +1,7 @@ """The tests for the notify yessssms platform.""" import unittest import requests_mock -from homeassistant.components.notify import yessssms +import homeassistant.components.yessssms.notify as yessssms class TestNotifyYesssSMS(unittest.TestCase): @@ -28,7 +28,7 @@ class TestNotifyYesssSMS(unittest.TestCase): message = "Testing YesssSMS platform :)" - with self.assertLogs("homeassistant.components.notify", + with self.assertLogs("homeassistant.components.yessssms.notify", level='ERROR'): self.yessssms.send_message(message) self.assertTrue(mock.called) @@ -37,7 +37,7 @@ class TestNotifyYesssSMS(unittest.TestCase): def test_empty_message_error(self): """Test for an empty SMS message error.""" message = "" - with self.assertLogs("homeassistant.components.notify", + with self.assertLogs("homeassistant.components.yessssms.notify", level='ERROR'): self.yessssms.send_message(message) @@ -54,7 +54,7 @@ class TestNotifyYesssSMS(unittest.TestCase): message = "Testing YesssSMS platform :)" - with self.assertLogs("homeassistant.components.notify", + with self.assertLogs("homeassistant.components.yessssms.notify", level='ERROR'): self.yessssms.send_message(message) self.assertTrue(mock.called) @@ -71,7 +71,7 @@ class TestNotifyYesssSMS(unittest.TestCase): message = "Testing YesssSMS platform :)" - with self.assertLogs("homeassistant.components.notify", + with self.assertLogs("homeassistant.components.yessssms.notify", level='ERROR'): self.yessssms.send_message(message) self.assertTrue(mock.called) @@ -83,7 +83,7 @@ class TestNotifyYesssSMS(unittest.TestCase): # pylint: disable=protected-access self.yessssms.yesss._suspended = True - with self.assertLogs("homeassistant.components.notify", + with self.assertLogs("homeassistant.components.yessssms.notify", level='ERROR') as context: self.yessssms.send_message(message) self.assertIn("Account is suspended, cannot send SMS.", @@ -124,7 +124,7 @@ class TestNotifyYesssSMS(unittest.TestCase): status_code=200, ) - with self.assertLogs("homeassistant.components.notify", + with self.assertLogs("homeassistant.components.yessssms.notify", level='INFO') as context: self.yessssms.send_message(message) self.assertIn("SMS sent", context.output[0]) @@ -143,7 +143,7 @@ class TestNotifyYesssSMS(unittest.TestCase): # pylint: disable=protected-access self.yessssms._recipient = "" - with self.assertLogs("homeassistant.components.notify", + with self.assertLogs("homeassistant.components.yessssms.notify", level='ERROR') as context: self.yessssms.send_message(message) @@ -179,7 +179,7 @@ class TestNotifyYesssSMS(unittest.TestCase): message = "Testing YesssSMS platform :)" - with self.assertLogs("homeassistant.components.notify", + with self.assertLogs("homeassistant.components.yessssms.notify", level='ERROR') as context: self.yessssms.send_message(message) @@ -199,7 +199,7 @@ class TestNotifyYesssSMS(unittest.TestCase): message = "Testing YesssSMS platform :)" - with self.assertLogs("homeassistant.components.notify", + with self.assertLogs("homeassistant.components.yessssms.notify", level='ERROR') as context: self.yessssms.send_message(message) diff --git a/tests/components/yr/__init__.py b/tests/components/yr/__init__.py new file mode 100644 index 00000000000..d85c8ab9758 --- /dev/null +++ b/tests/components/yr/__init__.py @@ -0,0 +1 @@ +"""Tests for the yr component.""" diff --git a/tests/components/sensor/test_yr.py b/tests/components/yr/test_sensor.py similarity index 95% rename from tests/components/sensor/test_yr.py rename to tests/components/yr/test_sensor.py index 4c2118f43f3..d0b4cc44dd2 100644 --- a/tests/components/sensor/test_yr.py +++ b/tests/components/yr/test_sensor.py @@ -20,7 +20,7 @@ def test_default_setup(hass, aioclient_mock): config = {'platform': 'yr', 'elevation': 0} hass.allow_pool = True - with patch('homeassistant.components.sensor.yr.dt_util.utcnow', + with patch('homeassistant.components.yr.sensor.dt_util.utcnow', return_value=NOW), assert_setup_component(1): yield from async_setup_component(hass, 'sensor', {'sensor': config}) @@ -46,7 +46,7 @@ def test_custom_setup(hass, aioclient_mock): 'fog', 'windSpeed']} hass.allow_pool = True - with patch('homeassistant.components.sensor.yr.dt_util.utcnow', + with patch('homeassistant.components.yr.sensor.dt_util.utcnow', return_value=NOW), assert_setup_component(1): yield from async_setup_component(hass, 'sensor', {'sensor': config}) @@ -88,7 +88,7 @@ def test_forecast_setup(hass, aioclient_mock): 'fog', 'windSpeed']} hass.allow_pool = True - with patch('homeassistant.components.sensor.yr.dt_util.utcnow', + with patch('homeassistant.components.yr.sensor.dt_util.utcnow', return_value=NOW), assert_setup_component(1): yield from async_setup_component(hass, 'sensor', {'sensor': config}) diff --git a/tests/components/yweather/__init__.py b/tests/components/yweather/__init__.py new file mode 100644 index 00000000000..6ae50651310 --- /dev/null +++ b/tests/components/yweather/__init__.py @@ -0,0 +1 @@ +"""Tests for the yweather component.""" diff --git a/tests/components/sensor/test_yweather.py b/tests/components/yweather/test_sensor.py similarity index 100% rename from tests/components/sensor/test_yweather.py rename to tests/components/yweather/test_sensor.py diff --git a/tests/components/weather/test_yweather.py b/tests/components/yweather/test_weather.py similarity index 100% rename from tests/components/weather/test_yweather.py rename to tests/components/yweather/test_weather.py diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index de05c89bbb0..cd0f615973d 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -6,7 +6,8 @@ from homeassistant.components.zha.core.const import ( DOMAIN, DATA_ZHA, COMPONENTS ) from homeassistant.components.zha.core.gateway import ZHAGateway -from homeassistant.components.zha.core.gateway import establish_device_mappings +from homeassistant.components.zha.core.registries import \ + establish_device_mappings from homeassistant.components.zha.core.channels.registry \ import populate_channel_registry from .common import async_setup_entry @@ -36,7 +37,9 @@ async def zha_gateway_fixture(hass): hass.data[DATA_ZHA].get(component, {}) ) zha_storage = await async_get_registry(hass) - return ZHAGateway(hass, {}, zha_storage) + gateway = ZHAGateway(hass, {}) + gateway.zha_storage = zha_storage + return gateway @pytest.fixture(autouse=True) diff --git a/tests/components/zha/test_api.py b/tests/components/zha/test_api.py index 5858c7560d9..3a30405f22e 100644 --- a/tests/components/zha/test_api.py +++ b/tests/components/zha/test_api.py @@ -1,5 +1,4 @@ """Test ZHA API.""" -from unittest.mock import Mock import pytest from homeassistant.components.switch import DOMAIN from homeassistant.components.zha.api import ( @@ -18,7 +17,7 @@ async def zha_client(hass, config_entry, zha_gateway, hass_ws_client): from zigpy.zcl.clusters.general import OnOff, Basic # load the ZHA API - async_load_api(hass, Mock(), zha_gateway) + async_load_api(hass) # create zigpy device await async_init_zigpy_device( diff --git a/tests/components/zha/test_binary_sensor.py b/tests/components/zha/test_binary_sensor.py index d0763b8fb10..1d6b4fd3e01 100644 --- a/tests/components/zha/test_binary_sensor.py +++ b/tests/components/zha/test_binary_sensor.py @@ -54,15 +54,14 @@ async def test_binary_sensor(hass, config_entry, zha_gateway): zone_cluster = zigpy_device_zone.endpoints.get( 1).ias_zone zone_entity_id = make_entity_id(DOMAIN, zigpy_device_zone, zone_cluster) - zone_zha_device = zha_gateway.get_device(str(zigpy_device_zone.ieee)) + zone_zha_device = zha_gateway.get_device(zigpy_device_zone.ieee) # occupancy binary_sensor occupancy_cluster = zigpy_device_occupancy.endpoints.get( 1).occupancy occupancy_entity_id = make_entity_id( DOMAIN, zigpy_device_occupancy, occupancy_cluster) - occupancy_zha_device = zha_gateway.get_device( - str(zigpy_device_occupancy.ieee)) + occupancy_zha_device = zha_gateway.get_device(zigpy_device_occupancy.ieee) # dimmable binary_sensor remote_on_off_cluster = zigpy_device_remote.endpoints.get( @@ -72,7 +71,7 @@ async def test_binary_sensor(hass, config_entry, zha_gateway): remote_entity_id = make_entity_id(DOMAIN, zigpy_device_remote, remote_on_off_cluster, use_suffix=False) - remote_zha_device = zha_gateway.get_device(str(zigpy_device_remote.ieee)) + remote_zha_device = zha_gateway.get_device(zigpy_device_remote.ieee) # test that the sensors exist and are in the unavailable state assert hass.states.get(zone_entity_id).state == STATE_UNAVAILABLE diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index a70e0e5ea40..6f31f1bcad3 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -31,7 +31,7 @@ async def test_fan(hass, config_entry, zha_gateway): cluster = zigpy_device.endpoints.get(1).fan entity_id = make_entity_id(DOMAIN, zigpy_device, cluster) - zha_device = zha_gateway.get_device(str(zigpy_device.ieee)) + zha_device = zha_gateway.get_device(zigpy_device.ieee) # test that the fan was created and that it is unavailable assert hass.states.get(entity_id).state == STATE_UNAVAILABLE diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 0ccad52d6aa..e9d6370575b 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -51,7 +51,7 @@ async def test_light(hass, config_entry, zha_gateway, monkeypatch): on_off_entity_id = make_entity_id(DOMAIN, zigpy_device_on_off, on_off_device_on_off_cluster, use_suffix=False) - on_off_zha_device = zha_gateway.get_device(str(zigpy_device_on_off.ieee)) + on_off_zha_device = zha_gateway.get_device(zigpy_device_on_off.ieee) # dimmable light level_device_on_off_cluster = zigpy_device_level.endpoints.get(1).on_off @@ -65,7 +65,7 @@ async def test_light(hass, config_entry, zha_gateway, monkeypatch): level_entity_id = make_entity_id(DOMAIN, zigpy_device_level, level_device_on_off_cluster, use_suffix=False) - level_zha_device = zha_gateway.get_device(str(zigpy_device_level.ieee)) + level_zha_device = zha_gateway.get_device(zigpy_device_level.ieee) # test that the lights were created and that they are unavailable assert hass.states.get(on_off_entity_id).state == STATE_UNAVAILABLE diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index c348ef0d0a7..ec6af7f4aa1 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -114,8 +114,7 @@ async def async_build_devices(hass, zha_gateway, config_entry, cluster_ids): 1).in_clusters[cluster_id] device_info["entity_id"] = make_entity_id( DOMAIN, zigpy_device, device_info["cluster"]) - device_info["zha_device"] = zha_gateway.get_device( - str(zigpy_device.ieee)) + device_info["zha_device"] = zha_gateway.get_device(zigpy_device.ieee) return device_infos diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index 1fc21e34cd8..b0bbc103a9e 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -28,7 +28,7 @@ async def test_switch(hass, config_entry, zha_gateway): cluster = zigpy_device.endpoints.get(1).on_off entity_id = make_entity_id(DOMAIN, zigpy_device, cluster) - zha_device = zha_gateway.get_device(str(zigpy_device.ieee)) + zha_device = zha_gateway.get_device(zigpy_device.ieee) # test that the switch was created and that its state is unavailable assert hass.states.get(entity_id).state == STATE_UNAVAILABLE diff --git a/tests/fixtures/entur_public_transport.json b/tests/fixtures/entur_public_transport.json deleted file mode 100644 index 24eafe94b23..00000000000 --- a/tests/fixtures/entur_public_transport.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "data": { - "stopPlaces": [ - { - "id": "NSR:StopPlace:548", - "name": "Bergen stasjon", - "estimatedCalls": [ - { - "realtime": false, - "aimedArrivalTime": "2018-10-10T09:28:00+0200", - "aimedDepartureTime": "2018-10-10T09:28:00+0200", - "expectedArrivalTime": "2018-10-10T09:28:00+0200", - "expectedDepartureTime": "2018-10-10T09:28:00+0200", - "requestStop": false, - "notices": [], - "destinationDisplay": { - "frontText": "Bergen" - }, - "serviceJourney": { - "journeyPattern": { - "line": { - "id": "NSB:Line:45", - "name": "Vossabanen", - "transportMode": "rail", - "publicCode": "59" - } - } - } - }, - { - "realtime": false, - "aimedArrivalTime": "2018-10-10T09:35:00+0200", - "aimedDepartureTime": "2018-10-10T09:35:00+0200", - "expectedArrivalTime": "2018-10-10T09:35:00+0200", - "expectedDepartureTime": "2018-10-10T09:35:00+0200", - "requestStop": false, - "notices": [], - "destinationDisplay": { - "frontText": "Arna" - }, - "serviceJourney": { - "journeyPattern": { - "line": { - "id": "NSB:Line:45", - "name": "Vossabanen", - "transportMode": "rail", - "publicCode": "58" - } - } - } - } - ] - } - ], - "quays": [ - { - "id": "NSR:Quay:48550", - "name": "Fiskepiren", - "publicCode": "2", - "latitude": 59.960904, - "longitude": 10.882942, - "estimatedCalls": [ - { - "realtime": false, - "aimedArrivalTime": "2018-10-10T09:00:00+0200", - "aimedDepartureTime": "2018-10-10T09:00:00+0200", - "expectedArrivalTime": "2018-10-10T09:00:00+0200", - "expectedDepartureTime": "2018-10-10T09:00:00+0200", - "requestStop": false, - "notices": [], - "destinationDisplay": { - "frontText": "Stavanger Airport via Forum" - }, - "serviceJourney": { - "journeyPattern": { - "line": { - "id": "KOL:Line:2900_234", - "name": "Flybussen", - "transportMode": "bus", - "publicCode": "5" - } - } - } - }, - { - "realtime": false, - "aimedArrivalTime": "2018-10-10T09:06:00+0200", - "aimedDepartureTime": "2018-10-10T09:06:00+0200", - "expectedArrivalTime": "2018-10-10T09:06:00+0200", - "expectedDepartureTime": "2018-10-10T09:06:00+0200", - "requestStop": false, - "notices": [], - "destinationDisplay": { - "frontText": "Stavanger" - }, - "serviceJourney": { - "journeyPattern": { - "line": { - "id": "KOL:Line:1000_234", - "name": "1", - "transportMode": "bus", - "publicCode": "1" - } - } - } - } - ] - } - ] - } -} \ No newline at end of file diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index d83d32c88e3..4a883fbf2fd 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -4,6 +4,7 @@ import enum import os from socket import _GLOBAL_DEFAULT_TIMEOUT from unittest.mock import Mock, patch +import uuid import homeassistant import pytest @@ -963,3 +964,24 @@ def test_entity_id_allow_old_validation(caplog): assert "Found invalid entity_id {}".format(value) in caplog.text assert len(cv.INVALID_ENTITY_IDS_FOUND) == 2 + + +def test_uuid4_hex(caplog): + """Test uuid validation.""" + schema = vol.Schema(cv.uuid4_hex) + + for value in ['Not a hex string', '0', 0]: + with pytest.raises(vol.Invalid): + schema(value) + + with pytest.raises(vol.Invalid): + # the 13th char should be 4 + schema('a03d31b22eee1acc9b90eec40be6ed23') + + with pytest.raises(vol.Invalid): + # the 17th char should be 8-a + schema('a03d31b22eee4acc7b90eec40be6ed23') + + hex = uuid.uuid4().hex + assert schema(hex) == hex + assert schema(hex.upper()) == hex diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index d79f84d416d..383cd05a009 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -1,6 +1,7 @@ """Test the entity helper.""" # pylint: disable=protected-access import asyncio +import threading from datetime import timedelta from unittest.mock import MagicMock, patch, PropertyMock @@ -225,11 +226,10 @@ def test_async_schedule_update_ha_state(hass): assert update_call is True -@asyncio.coroutine -def test_async_parallel_updates_with_zero(hass): +async def test_async_parallel_updates_with_zero(hass): """Test parallel updates with 0 (disabled).""" updates = [] - test_lock = asyncio.Event(loop=hass.loop) + test_lock = asyncio.Event() class AsyncEntity(entity.Entity): @@ -239,37 +239,73 @@ def test_async_parallel_updates_with_zero(hass): self.hass = hass self._count = count - @asyncio.coroutine - def async_update(self): + async def async_update(self): """Test update.""" updates.append(self._count) - yield from test_lock.wait() + await test_lock.wait() ent_1 = AsyncEntity("sensor.test_1", 1) ent_2 = AsyncEntity("sensor.test_2", 2) - ent_1.async_schedule_update_ha_state(True) - ent_2.async_schedule_update_ha_state(True) + try: + ent_1.async_schedule_update_ha_state(True) + ent_2.async_schedule_update_ha_state(True) - while True: - if len(updates) == 2: - break - yield from asyncio.sleep(0, loop=hass.loop) + while True: + if len(updates) >= 2: + break + await asyncio.sleep(0) - assert len(updates) == 2 - assert updates == [1, 2] - - test_lock.set() + assert len(updates) == 2 + assert updates == [1, 2] + finally: + test_lock.set() -@asyncio.coroutine -def test_async_parallel_updates_with_one(hass): +async def test_async_parallel_updates_with_zero_on_sync_update(hass): + """Test parallel updates with 0 (disabled).""" + updates = [] + test_lock = threading.Event() + + class AsyncEntity(entity.Entity): + + def __init__(self, entity_id, count): + """Initialize Async test entity.""" + self.entity_id = entity_id + self.hass = hass + self._count = count + + def update(self): + """Test update.""" + updates.append(self._count) + if not test_lock.wait(timeout=1): + # if timeout populate more data to fail the test + updates.append(self._count) + + ent_1 = AsyncEntity("sensor.test_1", 1) + ent_2 = AsyncEntity("sensor.test_2", 2) + + try: + ent_1.async_schedule_update_ha_state(True) + ent_2.async_schedule_update_ha_state(True) + + while True: + if len(updates) >= 2: + break + await asyncio.sleep(0) + + assert len(updates) == 2 + assert updates == [1, 2] + finally: + test_lock.set() + await asyncio.sleep(0) + + +async def test_async_parallel_updates_with_one(hass): """Test parallel updates with 1 (sequential).""" updates = [] - test_lock = asyncio.Lock(loop=hass.loop) - test_semaphore = asyncio.Semaphore(1, loop=hass.loop) - - yield from test_lock.acquire() + test_lock = asyncio.Lock() + test_semaphore = asyncio.Semaphore(1) class AsyncEntity(entity.Entity): @@ -280,59 +316,71 @@ def test_async_parallel_updates_with_one(hass): self._count = count self.parallel_updates = test_semaphore - @asyncio.coroutine - def async_update(self): + async def async_update(self): """Test update.""" updates.append(self._count) - yield from test_lock.acquire() + await test_lock.acquire() ent_1 = AsyncEntity("sensor.test_1", 1) ent_2 = AsyncEntity("sensor.test_2", 2) ent_3 = AsyncEntity("sensor.test_3", 3) - ent_1.async_schedule_update_ha_state(True) - ent_2.async_schedule_update_ha_state(True) - ent_3.async_schedule_update_ha_state(True) + await test_lock.acquire() - while True: - if len(updates) == 1: - break - yield from asyncio.sleep(0, loop=hass.loop) + try: + ent_1.async_schedule_update_ha_state(True) + ent_2.async_schedule_update_ha_state(True) + ent_3.async_schedule_update_ha_state(True) - assert len(updates) == 1 - assert updates == [1] + while True: + if len(updates) >= 1: + break + await asyncio.sleep(0) - test_lock.release() + assert len(updates) == 1 + assert updates == [1] - while True: - if len(updates) == 2: - break - yield from asyncio.sleep(0, loop=hass.loop) + updates.clear() + test_lock.release() + await asyncio.sleep(0) - assert len(updates) == 2 - assert updates == [1, 2] + while True: + if len(updates) >= 1: + break + await asyncio.sleep(0) - test_lock.release() + assert len(updates) == 1 + assert updates == [2] - while True: - if len(updates) == 3: - break - yield from asyncio.sleep(0, loop=hass.loop) + updates.clear() + test_lock.release() + await asyncio.sleep(0) - assert len(updates) == 3 - assert updates == [1, 2, 3] + while True: + if len(updates) >= 1: + break + await asyncio.sleep(0) - test_lock.release() + assert len(updates) == 1 + assert updates == [3] + + updates.clear() + test_lock.release() + await asyncio.sleep(0) + + finally: + # we may have more than one lock need to release in case test failed + for _ in updates: + test_lock.release() + await asyncio.sleep(0) + test_lock.release() -@asyncio.coroutine -def test_async_parallel_updates_with_two(hass): +async def test_async_parallel_updates_with_two(hass): """Test parallel updates with 2 (parallel).""" updates = [] - test_lock = asyncio.Lock(loop=hass.loop) - test_semaphore = asyncio.Semaphore(2, loop=hass.loop) - - yield from test_lock.acquire() + test_lock = asyncio.Lock() + test_semaphore = asyncio.Semaphore(2) class AsyncEntity(entity.Entity): @@ -354,34 +402,48 @@ def test_async_parallel_updates_with_two(hass): ent_3 = AsyncEntity("sensor.test_3", 3) ent_4 = AsyncEntity("sensor.test_4", 4) - ent_1.async_schedule_update_ha_state(True) - ent_2.async_schedule_update_ha_state(True) - ent_3.async_schedule_update_ha_state(True) - ent_4.async_schedule_update_ha_state(True) + await test_lock.acquire() - while True: - if len(updates) == 2: - break - yield from asyncio.sleep(0, loop=hass.loop) + try: - assert len(updates) == 2 - assert updates == [1, 2] + ent_1.async_schedule_update_ha_state(True) + ent_2.async_schedule_update_ha_state(True) + ent_3.async_schedule_update_ha_state(True) + ent_4.async_schedule_update_ha_state(True) - test_lock.release() - yield from asyncio.sleep(0, loop=hass.loop) - test_lock.release() + while True: + if len(updates) >= 2: + break + await asyncio.sleep(0) - while True: - if len(updates) == 4: - break - yield from asyncio.sleep(0, loop=hass.loop) + assert len(updates) == 2 + assert updates == [1, 2] - assert len(updates) == 4 - assert updates == [1, 2, 3, 4] + updates.clear() + test_lock.release() + await asyncio.sleep(0) + test_lock.release() + await asyncio.sleep(0) - test_lock.release() - yield from asyncio.sleep(0, loop=hass.loop) - test_lock.release() + while True: + if len(updates) >= 2: + break + await asyncio.sleep(0) + + assert len(updates) == 2 + assert updates == [3, 4] + + updates.clear() + test_lock.release() + await asyncio.sleep(0) + test_lock.release() + await asyncio.sleep(0) + finally: + # we may have more than one lock need to release in case test failed + for _ in updates: + test_lock.release() + await asyncio.sleep(0) + test_lock.release() @asyncio.coroutine diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 163261a4b81..6da3293d597 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -324,7 +324,7 @@ def test_setup_dependencies_platform(hass): loader.set_component(hass, 'test_component2', MockModule('test_component2')) loader.set_component( - hass, 'test_domain.test_component', + hass, 'test_component.test_domain', MockPlatform(dependencies=['test_component', 'test_component2'])) component = EntityComponent(_LOGGER, DOMAIN, hass) diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index e985771e486..6cf0bb0eeeb 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -251,80 +251,126 @@ def test_updated_state_used_for_entity_id(hass): assert entity_ids[0] == "test_domain.living_room" -@asyncio.coroutine -def test_parallel_updates_async_platform(hass): - """Warn we log when platform setup takes a long time.""" +async def test_parallel_updates_async_platform(hass): + """Test async platform does not have parallel_updates limit by default.""" platform = MockPlatform() - @asyncio.coroutine - def mock_update(*args, **kwargs): - pass - - platform.async_setup_platform = mock_update - loader.set_component(hass, 'test_domain.platform', platform) component = EntityComponent(_LOGGER, DOMAIN, hass) component._platforms = {} - yield from component.async_setup({ + await component.async_setup({ DOMAIN: { 'platform': 'platform', } }) handle = list(component._platforms.values())[-1] - assert handle.parallel_updates is None + class AsyncEntity(MockEntity): + """Mock entity that has async_update.""" -@asyncio.coroutine -def test_parallel_updates_async_platform_with_constant(hass): - """Warn we log when platform setup takes a long time.""" + async def async_update(self): + pass + + entity = AsyncEntity() + await handle.async_add_entities([entity]) + assert entity.parallel_updates is None + + +async def test_parallel_updates_async_platform_with_constant(hass): + """Test async platform can set parallel_updates limit.""" + platform = MockPlatform() + platform.PARALLEL_UPDATES = 2 + + loader.set_component(hass, 'test_domain.platform', platform) + + component = EntityComponent(_LOGGER, DOMAIN, hass) + component._platforms = {} + + await component.async_setup({ + DOMAIN: { + 'platform': 'platform', + } + }) + + handle = list(component._platforms.values())[-1] + + assert handle.parallel_updates == 2 + + class AsyncEntity(MockEntity): + """Mock entity that has async_update.""" + + async def async_update(self): + pass + + entity = AsyncEntity() + await handle.async_add_entities([entity]) + assert entity.parallel_updates is not None + assert entity.parallel_updates._value == 2 + + +async def test_parallel_updates_sync_platform(hass): + """Test sync platform parallel_updates default set to 1.""" platform = MockPlatform() - @asyncio.coroutine - def mock_update(*args, **kwargs): - pass - - platform.async_setup_platform = mock_update - platform.PARALLEL_UPDATES = 1 - loader.set_component(hass, 'test_domain.platform', platform) component = EntityComponent(_LOGGER, DOMAIN, hass) component._platforms = {} - yield from component.async_setup({ + await component.async_setup({ DOMAIN: { 'platform': 'platform', } }) handle = list(component._platforms.values())[-1] + assert handle.parallel_updates is None - assert handle.parallel_updates is not None + class SyncEntity(MockEntity): + """Mock entity that has update.""" + + async def update(self): + pass + + entity = SyncEntity() + await handle.async_add_entities([entity]) + assert entity.parallel_updates is not None + assert entity.parallel_updates._value == 1 -@asyncio.coroutine -def test_parallel_updates_sync_platform(hass): - """Warn we log when platform setup takes a long time.""" - platform = MockPlatform(setup_platform=lambda *args: None) +async def test_parallel_updates_sync_platform_with_constant(hass): + """Test sync platform can set parallel_updates limit.""" + platform = MockPlatform() + platform.PARALLEL_UPDATES = 2 loader.set_component(hass, 'test_domain.platform', platform) component = EntityComponent(_LOGGER, DOMAIN, hass) component._platforms = {} - yield from component.async_setup({ + await component.async_setup({ DOMAIN: { 'platform': 'platform', } }) handle = list(component._platforms.values())[-1] + assert handle.parallel_updates == 2 - assert handle.parallel_updates is not None + class SyncEntity(MockEntity): + """Mock entity that has update.""" + + async def update(self): + pass + + entity = SyncEntity() + await handle.async_add_entities([entity]) + assert entity.parallel_updates is not None + assert entity.parallel_updates._value == 2 @asyncio.coroutine diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index 5c04f085c86..10b053528ab 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -5,7 +5,7 @@ import unittest from unittest.mock import patch import homeassistant.core as ha -import homeassistant.components as core_components +from homeassistant.setup import async_setup_component from homeassistant.const import (SERVICE_TURN_ON, SERVICE_TURN_OFF) from homeassistant.util.async_ import run_coroutine_threadsafe from homeassistant.util import dt as dt_util @@ -88,8 +88,8 @@ class TestStateHelpers(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Run when tests are started.""" self.hass = get_test_home_assistant() - run_coroutine_threadsafe(core_components.async_setup( - self.hass, {}), self.hass.loop).result() + run_coroutine_threadsafe(async_setup_component( + self.hass, 'homeassistant', {}), self.hass.loop).result() def tearDown(self): # pylint: disable=invalid-name """Stop when tests are finished.""" diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 3febd4037ad..e0aeb09976d 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -15,6 +15,7 @@ from homeassistant.const import ( LENGTH_METERS, TEMP_CELSIUS, MASS_GRAMS, + PRESSURE_PA, VOLUME_LITERS, MATCH_ALL, ) @@ -33,7 +34,7 @@ class TestHelpersTemplate(unittest.TestCase): self.hass = get_test_home_assistant() self.hass.config.units = UnitSystem('custom', TEMP_CELSIUS, LENGTH_METERS, VOLUME_LITERS, - MASS_GRAMS) + MASS_GRAMS, PRESSURE_PA) # pylint: disable=invalid-name def tearDown(self): @@ -96,6 +97,16 @@ class TestHelpersTemplate(unittest.TestCase): '{{ states.sensor.temperature.state | multiply(10) | round }}', self.hass).render() + assert '12.7' == \ + template.Template( + '{{ states.sensor.temperature.state | round(1, "floor") }}', + self.hass).render() + + assert '12.8' == \ + template.Template( + '{{ states.sensor.temperature.state | round(1, "ceil") }}', + self.hass).render() + def test_rounding_value_get_original_value_on_error(self): """Test rounding value get original value on error.""" assert 'None' == \ diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 324db971583..32532761ccf 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -192,7 +192,7 @@ async def test_remove_entry(hass, manager): async_remove_entry=mock_remove_entry )) loader.set_component( - hass, 'light.test', + hass, 'test.light', MockPlatform(async_setup_entry=mock_setup_entry_platform)) MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager) diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py index 8b3b057bfc0..ab759f03058 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -102,7 +102,7 @@ class AiohttpClientMocker: async def match_request(self, method, url, *, data=None, auth=None, params=None, headers=None, allow_redirects=None, - timeout=None, json=None): + timeout=None, json=None, cookies=None): """Match a request against pre-registered requests.""" data = data or json url = URL(url) diff --git a/tests/util/test_pressure.py b/tests/util/test_pressure.py new file mode 100644 index 00000000000..a3e6efb3754 --- /dev/null +++ b/tests/util/test_pressure.py @@ -0,0 +1,66 @@ +"""Test homeassistant pressure utility functions.""" +import unittest +import pytest + +from homeassistant.const import (PRESSURE_PA, PRESSURE_HPA, PRESSURE_MBAR, + PRESSURE_INHG, PRESSURE_PSI) +import homeassistant.util.pressure as pressure_util + +INVALID_SYMBOL = 'bob' +VALID_SYMBOL = PRESSURE_PA + + +class TestPressureUtil(unittest.TestCase): + """Test the pressure utility functions.""" + + def test_convert_same_unit(self): + """Test conversion from any unit to same unit.""" + assert pressure_util.convert(2, PRESSURE_PA, PRESSURE_PA) == 2 + assert pressure_util.convert(3, PRESSURE_HPA, PRESSURE_HPA) == 3 + assert pressure_util.convert(4, PRESSURE_MBAR, PRESSURE_MBAR) == 4 + assert pressure_util.convert(5, PRESSURE_INHG, PRESSURE_INHG) == 5 + + def test_convert_invalid_unit(self): + """Test exception is thrown for invalid units.""" + with pytest.raises(ValueError): + pressure_util.convert(5, INVALID_SYMBOL, VALID_SYMBOL) + + with pytest.raises(ValueError): + pressure_util.convert(5, VALID_SYMBOL, INVALID_SYMBOL) + + def test_convert_nonnumeric_value(self): + """Test exception is thrown for nonnumeric type.""" + with pytest.raises(TypeError): + pressure_util.convert('a', PRESSURE_HPA, PRESSURE_INHG) + + def test_convert_from_hpascals(self): + """Test conversion from hPA to other units.""" + hpascals = 1000 + self.assertAlmostEqual( + pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_PSI), + 14.5037743897) + self.assertAlmostEqual( + pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_INHG), + 29.5299801647) + self.assertAlmostEqual( + pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_PA), + 100000) + self.assertAlmostEqual( + pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_MBAR), + 1000) + + def test_convert_from_inhg(self): + """Test conversion from inHg to other units.""" + inhg = 30 + self.assertAlmostEqual( + pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_PSI), + 14.7346266155) + self.assertAlmostEqual( + pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_HPA), + 1015.9167) + self.assertAlmostEqual( + pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_PA), + 101591.67) + self.assertAlmostEqual( + pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_MBAR), + 1015.9167) diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index 31b2d49b4ec..533ce3c0a15 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -10,10 +10,12 @@ from homeassistant.const import ( LENGTH_METERS, LENGTH_KILOMETERS, MASS_GRAMS, + PRESSURE_PA, VOLUME_LITERS, TEMP_CELSIUS, LENGTH, MASS, + PRESSURE, TEMPERATURE, VOLUME ) @@ -30,19 +32,23 @@ class TestUnitSystem(unittest.TestCase): """Test errors are raised when invalid units are passed in.""" with pytest.raises(ValueError): UnitSystem(SYSTEM_NAME, INVALID_UNIT, LENGTH_METERS, VOLUME_LITERS, - MASS_GRAMS) + MASS_GRAMS, PRESSURE_PA) with pytest.raises(ValueError): UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, INVALID_UNIT, VOLUME_LITERS, - MASS_GRAMS) + MASS_GRAMS, PRESSURE_PA) with pytest.raises(ValueError): UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, INVALID_UNIT, - MASS_GRAMS) + MASS_GRAMS, PRESSURE_PA) with pytest.raises(ValueError): UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, VOLUME_LITERS, - INVALID_UNIT) + INVALID_UNIT, PRESSURE_PA) + + with pytest.raises(ValueError): + UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, VOLUME_LITERS, + MASS_GRAMS, INVALID_UNIT) def test_invalid_value(self): """Test no conversion happens if value is non-numeric.""" @@ -50,6 +56,10 @@ class TestUnitSystem(unittest.TestCase): METRIC_SYSTEM.length('25a', LENGTH_KILOMETERS) with pytest.raises(TypeError): METRIC_SYSTEM.temperature('50K', TEMP_CELSIUS) + with pytest.raises(TypeError): + METRIC_SYSTEM.volume('50L', VOLUME_LITERS) + with pytest.raises(TypeError): + METRIC_SYSTEM.pressure('50Pa', PRESSURE_PA) def test_as_dict(self): """Test that the as_dict() method returns the expected dictionary.""" @@ -57,7 +67,8 @@ class TestUnitSystem(unittest.TestCase): LENGTH: LENGTH_KILOMETERS, TEMPERATURE: TEMP_CELSIUS, VOLUME: VOLUME_LITERS, - MASS: MASS_GRAMS + MASS: MASS_GRAMS, + PRESSURE: PRESSURE_PA } assert expected == METRIC_SYSTEM.as_dict() @@ -108,12 +119,39 @@ class TestUnitSystem(unittest.TestCase): assert 3.106855 == \ IMPERIAL_SYSTEM.length(5, METRIC_SYSTEM.length_unit) + def test_pressure_same_unit(self): + """Test no conversion happens if to unit is same as from unit.""" + assert 5 == \ + METRIC_SYSTEM.pressure(5, METRIC_SYSTEM.pressure_unit) + + def test_pressure_unknown_unit(self): + """Test no conversion happens if unknown unit.""" + with pytest.raises(ValueError): + METRIC_SYSTEM.pressure(5, 'K') + + def test_pressure_to_metric(self): + """Test pressure conversion to metric system.""" + assert 25 == \ + METRIC_SYSTEM.pressure(25, METRIC_SYSTEM.pressure_unit) + self.assertAlmostEqual( + METRIC_SYSTEM.pressure(14.7, IMPERIAL_SYSTEM.pressure_unit), + 101352.932, places=1) + + def test_pressure_to_imperial(self): + """Test pressure conversion to imperial system.""" + assert 77 == \ + IMPERIAL_SYSTEM.pressure(77, IMPERIAL_SYSTEM.pressure_unit) + self.assertAlmostEqual( + IMPERIAL_SYSTEM.pressure(101352.932, METRIC_SYSTEM.pressure_unit), + 14.7, places=4) + def test_properties(self): """Test the unit properties are returned as expected.""" assert LENGTH_KILOMETERS == METRIC_SYSTEM.length_unit assert TEMP_CELSIUS == METRIC_SYSTEM.temperature_unit assert MASS_GRAMS == METRIC_SYSTEM.mass_unit assert VOLUME_LITERS == METRIC_SYSTEM.volume_unit + assert PRESSURE_PA == METRIC_SYSTEM.pressure_unit def test_is_metric(self): """Test the is metric flag.""" diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py index 2eab75cb92a..46dc3c045b2 100644 --- a/tests/util/test_yaml.py +++ b/tests/util/test_yaml.py @@ -95,7 +95,7 @@ class TestYaml(unittest.TestCase): def test_include_dir_list(self, mock_walk): """Test include dir list yaml.""" mock_walk.return_value = [ - ['/tmp', [], ['one.yaml', 'two.yaml']], + ['/tmp', [], ['two.yaml', 'one.yaml']], ] with patch_yaml_files({ @@ -105,7 +105,7 @@ class TestYaml(unittest.TestCase): conf = "key: !include_dir_list /tmp" with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) - assert sorted(doc["key"]) == sorted(["one", "two"]) + assert doc["key"] == sorted(["one", "two"]) @patch('homeassistant.util.yaml.os.walk') def test_include_dir_list_recursive(self, mock_walk): diff --git a/tox.ini b/tox.ini index 8423141df60..b8995d9e877 100644 --- a/tox.ini +++ b/tox.ini @@ -42,4 +42,4 @@ deps = -r{toxinidir}/requirements_test.txt -c{toxinidir}/homeassistant/package_constraints.txt commands = - /bin/bash -c 'mypy homeassistant/*.py homeassistant/{auth,util}/ homeassistant/helpers/{__init__,aiohttp_client,area_registry,condition,deprecation,dispatcher,entity_values,entityfilter,icon,intent,json,location,signal,state,sun,temperature,translation,typing}.py' + /bin/bash -c 'TYPING_FILES=$(cat mypyrc); mypy $TYPING_FILES' diff --git a/virtualization/Docker/Dockerfile.dev b/virtualization/Docker/Dockerfile.dev index 03d6ab47c24..90c9eee3485 100644 --- a/virtualization/Docker/Dockerfile.dev +++ b/virtualization/Docker/Dockerfile.dev @@ -51,7 +51,7 @@ COPY homeassistant/const.py homeassistant/const.py # Prefetch dependencies for tox COPY homeassistant/package_constraints.txt homeassistant/package_constraints.txt -RUN tox -e py36 --notest +RUN tox -e py37 --notest # END: Development additions