From 057d43561ce31651dd24ced90208b972ae2d46e5 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Sun, 7 Apr 2019 04:47:22 +0000 Subject: [PATCH] initial commit of home assistant config --- .HA_VERSION | 1 + .gitignore | 37 + README.md | 3 + alarm.json | 1 + automations/garage_entry_light.yaml | 107 + automations/hassio.yaml | 19 + automations/living_room.yaml | 29 + automations/security.yaml | 68 + configuration.yaml | 33 + custom_components/alexa_media/__init__.py | 373 + custom_components/alexa_media/const.py | 31 + custom_components/alexa_media/media_player.py | 604 + custom_components/alexa_media/notify.py | 181 + custom_components/xboxone/media_player.py | 665 + lovelace-gen.py | 168 + lovelace/00_overview.yaml | 33 + lovelace/10_lights.yaml | 33 + lovelace/20_security.yaml | 43 + lovelace/30_media.yaml | 50 + lovelace/40_cameras.yaml | 45 + lovelace/50_network.yaml | 13 + lovelace/column-card/column-card.js | 138 + lovelace/main.yaml | 19 + .../mini-media-player/mini-media-player.js | 1 + lovelace/monster-card/monster-card.js | 122 + lovelace/presence-card.yaml | 15 + .../vertical-stack-in-card.js | 100 + packages/beds.yaml | 4 + packages/climate.yaml | 13 + packages/garage.yaml | 6 + packages/general.yaml | 79 + packages/homekit.yaml | 17 + packages/internet_monitor.yaml | 1 + packages/ios.yaml | 10 + packages/lovelace.yaml | 4 + packages/media_players.yaml | 23 + packages/presence.yaml | 48 + packages/security.yaml | 89 + packages/sensors.yaml | 14 + packages/utilities.yaml | 5 + packages/zones.yaml | 12 + packages/zwave.yaml | 7 + panels/zwavegraph2.html | 296 + tensorflow/.DS_Store | Bin 0 -> 6148 bytes tensorflow/graph/.DS_Store | Bin 0 -> 6148 bytes tensorflow/graph/README.md | 5 + tensorflow/object_detection/.DS_Store | Bin 0 -> 6148 bytes tensorflow/object_detection/__init__.py | 0 .../data/ava_label_map_v2.1.pbtxt | 240 + .../data/fgvc_2854_classes_label_map.pbtxt | 14270 ++++++++++++++++ .../data/kitti_label_map.pbtxt | 9 + .../data/mscoco_label_map.pbtxt | 400 + .../data/oid_bbox_trainable_label_map.pbtxt | 2725 +++ ...ct_detection_challenge_500_label_map.pbtxt | 2500 +++ .../data/pascal_label_map.pbtxt | 99 + .../object_detection/data/pet_label_map.pbtxt | 184 + .../object_detection/protos/__init__.py | 0 .../protos/anchor_generator.proto | 17 + .../protos/anchor_generator_pb2.py | 102 + .../protos/argmax_matcher.proto | 29 + .../protos/argmax_matcher_pb2.py | 104 + .../protos/bipartite_matcher.proto | 11 + .../protos/bipartite_matcher_pb2.py | 69 + .../object_detection/protos/box_coder.proto | 19 + .../object_detection/protos/box_coder_pb2.py | 114 + .../protos/box_predictor.proto | 202 + .../protos/box_predictor_pb2.py | 686 + tensorflow/object_detection/protos/eval.proto | 79 + .../object_detection/protos/eval_pb2.py | 220 + .../object_detection/protos/faster_rcnn.proto | 182 + .../protos/faster_rcnn_box_coder.proto | 17 + .../protos/faster_rcnn_box_coder_pb2.py | 90 + .../protos/faster_rcnn_pb2.py | 375 + .../protos/graph_rewriter.proto | 23 + .../protos/graph_rewriter_pb2.py | 123 + .../protos/grid_anchor_generator.proto | 34 + .../protos/grid_anchor_generator_pb2.py | 118 + .../object_detection/protos/hyperparams.proto | 115 + .../protos/hyperparams_pb2.py | 605 + .../protos/image_resizer.proto | 59 + .../protos/image_resizer_pb2.py | 260 + .../protos/input_reader.proto | 123 + .../protos/input_reader_pb2.py | 346 + .../protos/keypoint_box_coder.proto | 19 + .../protos/keypoint_box_coder_pb2.py | 97 + .../object_detection/protos/losses.proto | 164 + .../object_detection/protos/losses_pb2.py | 755 + .../object_detection/protos/matcher.proto | 15 + .../object_detection/protos/matcher_pb2.py | 90 + .../protos/mean_stddev_box_coder.proto | 10 + .../protos/mean_stddev_box_coder_pb2.py | 69 + .../object_detection/protos/model.proto | 14 + .../object_detection/protos/model_pb2.py | 90 + .../protos/multiscale_anchor_generator.proto | 26 + .../protos/multiscale_anchor_generator_pb2.py | 104 + .../object_detection/protos/optimizer.proto | 91 + .../object_detection/protos/optimizer_pb2.py | 626 + .../object_detection/protos/pipeline.proto | 22 + .../object_detection/protos/pipeline_pb2.py | 116 + .../protos/post_processing.proto | 49 + .../protos/post_processing_pb2.py | 180 + .../protos/preprocessor.proto | 420 + .../protos/preprocessor_pb2.py | 2078 +++ .../protos/region_similarity_calculator.proto | 33 + .../region_similarity_calculator_pb2.py | 244 + .../protos/square_box_coder.proto | 14 + .../protos/square_box_coder_pb2.py | 83 + tensorflow/object_detection/protos/ssd.proto | 170 + .../protos/ssd_anchor_generator.proto | 55 + .../protos/ssd_anchor_generator_pb2.py | 153 + tensorflow/object_detection/protos/ssd_pb2.py | 371 + .../protos/string_int_label_map.proto | 24 + .../protos/string_int_label_map_pb2.py | 123 + .../object_detection/protos/train.proto | 118 + .../object_detection/protos/train_pb2.py | 253 + tensorflow/object_detection/utils/__init__.py | 0 .../object_detection/utils/category_util.py | 72 + .../utils/category_util_test.py | 54 + .../object_detection/utils/config_util.py | 936 + .../utils/config_util_test.py | 866 + .../object_detection/utils/context_manager.py | 40 + .../utils/context_manager_test.py | 33 + .../object_detection/utils/dataset_util.py | 88 + .../utils/dataset_util_test.py | 37 + .../object_detection/utils/json_utils.py | 87 + .../object_detection/utils/json_utils_test.py | 97 + .../object_detection/utils/label_map_util.py | 235 + .../utils/label_map_util_test.py | 334 + .../utils/learning_schedules.py | 175 + .../utils/learning_schedules_test.py | 156 + tensorflow/object_detection/utils/metrics.py | 190 + .../object_detection/utils/metrics_test.py | 143 + .../object_detection/utils/np_box_list.py | 133 + .../object_detection/utils/np_box_list_ops.py | 554 + .../utils/np_box_list_ops_test.py | 414 + .../utils/np_box_list_test.py | 135 + .../utils/np_box_mask_list.py | 63 + .../utils/np_box_mask_list_ops.py | 399 + .../utils/np_box_mask_list_ops_test.py | 191 + .../utils/np_box_mask_list_test.py | 182 + .../object_detection/utils/np_box_ops.py | 97 + .../object_detection/utils/np_box_ops_test.py | 68 + .../object_detection/utils/np_mask_ops.py | 119 + .../utils/np_mask_ops_test.py | 88 + .../utils/object_detection_evaluation.py | 889 + .../utils/object_detection_evaluation_test.py | 687 + tensorflow/object_detection/utils/ops.py | 1138 ++ tensorflow/object_detection/utils/ops_test.py | 1535 ++ .../utils/per_image_evaluation.py | 584 + .../utils/per_image_evaluation_test.py | 561 + .../utils/per_image_vrd_evaluation.py | 224 + .../utils/per_image_vrd_evaluation_test.py | 94 + .../object_detection/utils/shape_utils.py | 367 + .../utils/shape_utils_test.py | 337 + .../object_detection/utils/static_shape.py | 71 + .../utils/static_shape_test.py | 50 + .../object_detection/utils/test_case.py | 104 + .../object_detection/utils/test_utils.py | 223 + .../object_detection/utils/test_utils_test.py | 89 + .../utils/variables_helper.py | 147 + .../utils/variables_helper_test.py | 221 + .../utils/visualization_utils.py | 890 + .../utils/visualization_utils_test.py | 305 + .../object_detection/utils/vrd_evaluation.py | 581 + .../utils/vrd_evaluation_test.py | 257 + themes/dark-green.yaml | 33 + ui-lovelace.yaml | 163 + 167 files changed, 50326 insertions(+) create mode 100644 .HA_VERSION create mode 100644 .gitignore create mode 100644 README.md create mode 100644 alarm.json create mode 100644 automations/garage_entry_light.yaml create mode 100644 automations/hassio.yaml create mode 100644 automations/living_room.yaml create mode 100644 automations/security.yaml create mode 100644 configuration.yaml create mode 100644 custom_components/alexa_media/__init__.py create mode 100644 custom_components/alexa_media/const.py create mode 100644 custom_components/alexa_media/media_player.py create mode 100644 custom_components/alexa_media/notify.py create mode 100644 custom_components/xboxone/media_player.py create mode 100644 lovelace-gen.py create mode 100644 lovelace/00_overview.yaml create mode 100644 lovelace/10_lights.yaml create mode 100644 lovelace/20_security.yaml create mode 100644 lovelace/30_media.yaml create mode 100644 lovelace/40_cameras.yaml create mode 100644 lovelace/50_network.yaml create mode 100644 lovelace/column-card/column-card.js create mode 100644 lovelace/main.yaml create mode 100644 lovelace/mini-media-player/mini-media-player.js create mode 100644 lovelace/monster-card/monster-card.js create mode 100644 lovelace/presence-card.yaml create mode 100644 lovelace/vertical-stack-in-card/vertical-stack-in-card.js create mode 100644 packages/beds.yaml create mode 100644 packages/climate.yaml create mode 100644 packages/garage.yaml create mode 100644 packages/general.yaml create mode 100644 packages/homekit.yaml create mode 100644 packages/internet_monitor.yaml create mode 100644 packages/ios.yaml create mode 100644 packages/lovelace.yaml create mode 100644 packages/media_players.yaml create mode 100644 packages/presence.yaml create mode 100644 packages/security.yaml create mode 100644 packages/sensors.yaml create mode 100644 packages/utilities.yaml create mode 100644 packages/zones.yaml create mode 100644 packages/zwave.yaml create mode 100644 panels/zwavegraph2.html create mode 100644 tensorflow/.DS_Store create mode 100644 tensorflow/graph/.DS_Store create mode 100644 tensorflow/graph/README.md create mode 100644 tensorflow/object_detection/.DS_Store create mode 100644 tensorflow/object_detection/__init__.py create mode 100644 tensorflow/object_detection/data/ava_label_map_v2.1.pbtxt create mode 100644 tensorflow/object_detection/data/fgvc_2854_classes_label_map.pbtxt create mode 100644 tensorflow/object_detection/data/kitti_label_map.pbtxt create mode 100644 tensorflow/object_detection/data/mscoco_label_map.pbtxt create mode 100644 tensorflow/object_detection/data/oid_bbox_trainable_label_map.pbtxt create mode 100644 tensorflow/object_detection/data/oid_object_detection_challenge_500_label_map.pbtxt create mode 100644 tensorflow/object_detection/data/pascal_label_map.pbtxt create mode 100644 tensorflow/object_detection/data/pet_label_map.pbtxt create mode 100644 tensorflow/object_detection/protos/__init__.py create mode 100644 tensorflow/object_detection/protos/anchor_generator.proto create mode 100644 tensorflow/object_detection/protos/anchor_generator_pb2.py create mode 100644 tensorflow/object_detection/protos/argmax_matcher.proto create mode 100644 tensorflow/object_detection/protos/argmax_matcher_pb2.py create mode 100644 tensorflow/object_detection/protos/bipartite_matcher.proto create mode 100644 tensorflow/object_detection/protos/bipartite_matcher_pb2.py create mode 100644 tensorflow/object_detection/protos/box_coder.proto create mode 100644 tensorflow/object_detection/protos/box_coder_pb2.py create mode 100644 tensorflow/object_detection/protos/box_predictor.proto create mode 100644 tensorflow/object_detection/protos/box_predictor_pb2.py create mode 100644 tensorflow/object_detection/protos/eval.proto create mode 100644 tensorflow/object_detection/protos/eval_pb2.py create mode 100644 tensorflow/object_detection/protos/faster_rcnn.proto create mode 100644 tensorflow/object_detection/protos/faster_rcnn_box_coder.proto create mode 100644 tensorflow/object_detection/protos/faster_rcnn_box_coder_pb2.py create mode 100644 tensorflow/object_detection/protos/faster_rcnn_pb2.py create mode 100644 tensorflow/object_detection/protos/graph_rewriter.proto create mode 100644 tensorflow/object_detection/protos/graph_rewriter_pb2.py create mode 100644 tensorflow/object_detection/protos/grid_anchor_generator.proto create mode 100644 tensorflow/object_detection/protos/grid_anchor_generator_pb2.py create mode 100644 tensorflow/object_detection/protos/hyperparams.proto create mode 100644 tensorflow/object_detection/protos/hyperparams_pb2.py create mode 100644 tensorflow/object_detection/protos/image_resizer.proto create mode 100644 tensorflow/object_detection/protos/image_resizer_pb2.py create mode 100644 tensorflow/object_detection/protos/input_reader.proto create mode 100644 tensorflow/object_detection/protos/input_reader_pb2.py create mode 100644 tensorflow/object_detection/protos/keypoint_box_coder.proto create mode 100644 tensorflow/object_detection/protos/keypoint_box_coder_pb2.py create mode 100644 tensorflow/object_detection/protos/losses.proto create mode 100644 tensorflow/object_detection/protos/losses_pb2.py create mode 100644 tensorflow/object_detection/protos/matcher.proto create mode 100644 tensorflow/object_detection/protos/matcher_pb2.py create mode 100644 tensorflow/object_detection/protos/mean_stddev_box_coder.proto create mode 100644 tensorflow/object_detection/protos/mean_stddev_box_coder_pb2.py create mode 100644 tensorflow/object_detection/protos/model.proto create mode 100644 tensorflow/object_detection/protos/model_pb2.py create mode 100644 tensorflow/object_detection/protos/multiscale_anchor_generator.proto create mode 100644 tensorflow/object_detection/protos/multiscale_anchor_generator_pb2.py create mode 100644 tensorflow/object_detection/protos/optimizer.proto create mode 100644 tensorflow/object_detection/protos/optimizer_pb2.py create mode 100644 tensorflow/object_detection/protos/pipeline.proto create mode 100644 tensorflow/object_detection/protos/pipeline_pb2.py create mode 100644 tensorflow/object_detection/protos/post_processing.proto create mode 100644 tensorflow/object_detection/protos/post_processing_pb2.py create mode 100644 tensorflow/object_detection/protos/preprocessor.proto create mode 100644 tensorflow/object_detection/protos/preprocessor_pb2.py create mode 100644 tensorflow/object_detection/protos/region_similarity_calculator.proto create mode 100644 tensorflow/object_detection/protos/region_similarity_calculator_pb2.py create mode 100644 tensorflow/object_detection/protos/square_box_coder.proto create mode 100644 tensorflow/object_detection/protos/square_box_coder_pb2.py create mode 100644 tensorflow/object_detection/protos/ssd.proto create mode 100644 tensorflow/object_detection/protos/ssd_anchor_generator.proto create mode 100644 tensorflow/object_detection/protos/ssd_anchor_generator_pb2.py create mode 100644 tensorflow/object_detection/protos/ssd_pb2.py create mode 100644 tensorflow/object_detection/protos/string_int_label_map.proto create mode 100644 tensorflow/object_detection/protos/string_int_label_map_pb2.py create mode 100644 tensorflow/object_detection/protos/train.proto create mode 100644 tensorflow/object_detection/protos/train_pb2.py create mode 100644 tensorflow/object_detection/utils/__init__.py create mode 100644 tensorflow/object_detection/utils/category_util.py create mode 100644 tensorflow/object_detection/utils/category_util_test.py create mode 100644 tensorflow/object_detection/utils/config_util.py create mode 100644 tensorflow/object_detection/utils/config_util_test.py create mode 100644 tensorflow/object_detection/utils/context_manager.py create mode 100644 tensorflow/object_detection/utils/context_manager_test.py create mode 100644 tensorflow/object_detection/utils/dataset_util.py create mode 100644 tensorflow/object_detection/utils/dataset_util_test.py create mode 100644 tensorflow/object_detection/utils/json_utils.py create mode 100644 tensorflow/object_detection/utils/json_utils_test.py create mode 100644 tensorflow/object_detection/utils/label_map_util.py create mode 100644 tensorflow/object_detection/utils/label_map_util_test.py create mode 100644 tensorflow/object_detection/utils/learning_schedules.py create mode 100644 tensorflow/object_detection/utils/learning_schedules_test.py create mode 100644 tensorflow/object_detection/utils/metrics.py create mode 100644 tensorflow/object_detection/utils/metrics_test.py create mode 100644 tensorflow/object_detection/utils/np_box_list.py create mode 100644 tensorflow/object_detection/utils/np_box_list_ops.py create mode 100644 tensorflow/object_detection/utils/np_box_list_ops_test.py create mode 100644 tensorflow/object_detection/utils/np_box_list_test.py create mode 100644 tensorflow/object_detection/utils/np_box_mask_list.py create mode 100644 tensorflow/object_detection/utils/np_box_mask_list_ops.py create mode 100644 tensorflow/object_detection/utils/np_box_mask_list_ops_test.py create mode 100644 tensorflow/object_detection/utils/np_box_mask_list_test.py create mode 100644 tensorflow/object_detection/utils/np_box_ops.py create mode 100644 tensorflow/object_detection/utils/np_box_ops_test.py create mode 100644 tensorflow/object_detection/utils/np_mask_ops.py create mode 100644 tensorflow/object_detection/utils/np_mask_ops_test.py create mode 100644 tensorflow/object_detection/utils/object_detection_evaluation.py create mode 100644 tensorflow/object_detection/utils/object_detection_evaluation_test.py create mode 100644 tensorflow/object_detection/utils/ops.py create mode 100644 tensorflow/object_detection/utils/ops_test.py create mode 100644 tensorflow/object_detection/utils/per_image_evaluation.py create mode 100644 tensorflow/object_detection/utils/per_image_evaluation_test.py create mode 100644 tensorflow/object_detection/utils/per_image_vrd_evaluation.py create mode 100644 tensorflow/object_detection/utils/per_image_vrd_evaluation_test.py create mode 100644 tensorflow/object_detection/utils/shape_utils.py create mode 100644 tensorflow/object_detection/utils/shape_utils_test.py create mode 100644 tensorflow/object_detection/utils/static_shape.py create mode 100644 tensorflow/object_detection/utils/static_shape_test.py create mode 100644 tensorflow/object_detection/utils/test_case.py create mode 100644 tensorflow/object_detection/utils/test_utils.py create mode 100644 tensorflow/object_detection/utils/test_utils_test.py create mode 100644 tensorflow/object_detection/utils/variables_helper.py create mode 100644 tensorflow/object_detection/utils/variables_helper_test.py create mode 100644 tensorflow/object_detection/utils/visualization_utils.py create mode 100644 tensorflow/object_detection/utils/visualization_utils_test.py create mode 100644 tensorflow/object_detection/utils/vrd_evaluation.py create mode 100644 tensorflow/object_detection/utils/vrd_evaluation_test.py create mode 100644 themes/dark-green.yaml create mode 100644 ui-lovelace.yaml diff --git a/.HA_VERSION b/.HA_VERSION new file mode 100644 index 0000000..dd376a7 --- /dev/null +++ b/.HA_VERSION @@ -0,0 +1 @@ +0.91.1 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab51cdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Generic ignores +*.log +*.db +*.db-shm +*.db-wal +*.pyc +._* +__pycache__ + +# Directory (contents) ignores +.cloud +.storage +deps +tts +tensorflow/graph/faster_rcnn_inception_v2_coco_2018_01_28 +www + +# Specific file ignores +.homekit.state +.ios.conf +.xbox-token.json +.uuid +alexa_media*.pickle +ip_bans.yaml +known_devices.yaml +options.xml +OZW_Log.txt +pyozw.sqlite +secrets.yaml +zwcfg_* +zwscene.xml + +# Ignore add-on files +notebooks + +# Ignore files created by IDE's +.vscode diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed2beb7 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Jason's Home Assistant Config + +This readme is pretty bare bones, isn't it? Well give me some time! I did **JUST** upload this you know. People who listened to me on the [Home Assistant Podcast](https://hasspodcast.io/ha047/) didn't seem to believe me when I said my config was pretty bare-bones, so here it is. \ No newline at end of file diff --git a/alarm.json b/alarm.json new file mode 100644 index 0000000..c5ba5a5 --- /dev/null +++ b/alarm.json @@ -0,0 +1 @@ +{"state": "disarmed", "timeoutat": null, "returnto": "disarmed"} \ No newline at end of file diff --git a/automations/garage_entry_light.yaml b/automations/garage_entry_light.yaml new file mode 100644 index 0000000..706f704 --- /dev/null +++ b/automations/garage_entry_light.yaml @@ -0,0 +1,107 @@ +############################################################## +- alias: Garage Door Opened Hallway Light + trigger: + - platform: state + entity_id: binary_sensor.garage_door + to: 'on' + + condition: + - condition: state + entity_id: switch.downstairs_hallway_light + state: 'off' + + action: + - service: switch.turn_on + entity_id: switch.downstairs_hallway_light + +############################################################## +- alias: Garage Door Closed Hallway Light + trigger: + - platform: state + entity_id: binary_sensor.garage_door + to: 'off' + for: + seconds: 30 + + condition: + - condition: state + entity_id: binary_sensor.garage_door + state: 'off' + - condition: state + entity_id: switch.downstairs_hallway_light + state: 'on' + + action: + - service: switch.turn_off + entity_id: switch.downstairs_hallway_light + +############################################################## +- alias: Garage Door Opened Garage Light + trigger: + - platform: state + entity_id: binary_sensor.garage_door + to: 'on' + - platform: state + entity_id: cover.large_garage_door + to: 'open' + - platform: state + entity_id: cover.small_garage_door + to: 'open' + + condition: + - condition: and + conditions: + - condition: state + entity_id: switch.garage_light + state: 'off' + # After Dark or Cloudy + - condition: or + conditions: + - condition: sun + after: sunset + - condition: sun + before: sunrise + - condition: numeric_state + entity_id: sensor.dark_sky_cloud_coverage + above: 80 + + action: + - service: switch.turn_on + entity_id: switch.garage_light + +############################################################## +- alias: Garage Door Closed Garage Light + trigger: + - platform: state + entity_id: binary_sensor.garage_door + to: 'off' + for: + seconds: 30 + - platform: state + entity_id: cover.large_garage_door + to: 'closed' + for: + seconds: 30 + - platform: state + entity_id: cover.small_garage_door + to: 'closed' + for: + seconds: 30 + + condition: + - condition: state + entity_id: binary_sensor.garage_door + state: 'off' + - condition: state + entity_id: cover.large_garage_door + state: 'closed' + - condition: state + entity_id: cover.small_garage_door + state: 'closed' + - condition: state + entity_id: switch.garage_light + state: 'on' + + action: + - service: switch.turn_off + entity_id: switch.garage_light diff --git a/automations/hassio.yaml b/automations/hassio.yaml new file mode 100644 index 0000000..e71670e --- /dev/null +++ b/automations/hassio.yaml @@ -0,0 +1,19 @@ +- id: letsencrypt-renewal + alias: "Let's Encrypt Renewal" + trigger: + - platform: time + at: '00:00:00' + action: + - service: hassio.addon_restart + data: + addon: core_letsencrypt + +- id: homekit-start + alias: "Start HomeKit" + trigger: + - platform: event + event_type: zwave.network_ready + - platform: event + event_type: zwave.network_complete + action: + - service: homekit.start diff --git a/automations/living_room.yaml b/automations/living_room.yaml new file mode 100644 index 0000000..104d311 --- /dev/null +++ b/automations/living_room.yaml @@ -0,0 +1,29 @@ +- id: livingroom-triple-tap-switch-down + alias: "Triple Tap Living Room Switch Down" + trigger: + - platform: event + event_type: zwave.scene_activated + event_data: + entity_id: zwave.living_room_wall_dimmer + scene_id: 2 + scene_data: 7920 + action: + - service: light.turn_off + entity_id: light.living_room_fan + - service: media_player.turn_off + entity_id: media_player.living_room_xbox + +- id: livingroom-triple-tap-switch-up + alias: "Triple Tap Living Room Switch Up" + trigger: + - platform: event + event_type: zwave.scene_activated + event_data: + entity_id: zwave.living_room_wall_dimmer + scene_id: 1 + scene_data: 7920 + action: + - service: light.turn_on + entity_id: light.living_room_fan + - service: media_player.turn_on + entity_id: media_player.living_room_xbox diff --git a/automations/security.yaml b/automations/security.yaml new file mode 100644 index 0000000..2817450 --- /dev/null +++ b/automations/security.yaml @@ -0,0 +1,68 @@ +- alias: Tensorflow Scan + trigger: + - platform: state + entity_id: + - binary_sensor.backyard_field_detection + - binary_sensor.driveway_field_detection + - binary_sensor.foyer_field_detection + - binary_sensor.garage_field_detection + - binary_sensor.backyard_line_crossing + - binary_sensor.driveway_line_crossing + - binary_sensor.garage_line_crossing + action: + - service: image_processing.scan + data_template: + entity_id: "image_processing.tensorflow_{{ trigger.entity_id.split('.')[1].split('_')[0] }}" + +- alias: Notify Motion + trigger: + - platform: numeric_state + entity_id: + - image_processing.tensorflow_backyard + - image_processing.tensorflow_driveway + - image_processing.tensorflow_foyer + - image_processing.tensorflow_garage + above: 0 + action: + - service: camera.record + data_template: + entity_id: "camera.{{ trigger.entity_id.split('.')[1].split('_')[1] }}" + filename: "/share/videos/{{ trigger.entity_id.split('.')[1].split('_')[1] }}_{{ now().strftime('%Y%m%d_%H%M%S') }}.mp4" + duration: 20 + lookback: 10 + - condition: or + conditions: + - condition: template + value_template: "{{ not is_state('group.family_presence', 'home') }}" + - condition: template + value_template: > + {% for s in ['garage', 'foyer'] if s in 'image_processing.tensorflow_garage' %} + True + {% else %} + False + {% endfor %} + - service: notify.ios_jk_phones + data_template: + message: "{{ trigger.entity_id.split('.')[1].split('_')[1]|capitalize }} motion detected!" + data: + attachment: + url: 'https://REDACTED/local/motion/{{ trigger.entity_id.split(".")[1].split("_")[1] }}_latest.jpg' + content-type: jpeg + hide-thumbnail: true + - condition: and + conditions: + - condition: state + entity_id: input_boolean.voice_alerts + state: 'on' + - service: media_player.alexa_tts + data_template: + entity_id: + - media_player.kitchen_echo + - media_player.master_bedroom_dot + - media_player.bonus_room_dot + message: > + {% if is_state('group.family_presence', 'home') %} + You have a visitor in your {{ trigger.entity_id.split('.')[1].split('_')[1] }}. + {% else %} + Warning. Unknown presence detected. The authorities will be notified. + {% endif %} diff --git a/configuration.yaml b/configuration.yaml new file mode 100644 index 0000000..8166e3c --- /dev/null +++ b/configuration.yaml @@ -0,0 +1,33 @@ +homeassistant: + # Name of the location where Home Assistant is running + name: Home + # Location required to calculate the time the sun rises and sets + latitude: !secret latitude + longitude: !secret longitude + # Impacts weather/sunrise data (altitude above sea level in meters) + elevation: !secret elevation + # metric for Metric, imperial for Imperial + unit_system: imperial + # Pick yours from here: http://en.wikipedia.org/wiki/List_of_tz_database_time_zones + time_zone: America/New_York + packages: !include_dir_named packages + whitelist_external_dirs: + - /config/www/motion + - /share/camera + - /share/videos + +# Enables the frontend +frontend: + javascript_version: latest + themes: !include_dir_named themes + +# Lovelace +lovelace: + mode: yaml + +# Text to speech +tts: + - platform: google + +automation: !include_dir_merge_list automations +script: !include_dir_merge_named scripts diff --git a/custom_components/alexa_media/__init__.py b/custom_components/alexa_media/__init__.py new file mode 100644 index 0000000..82eb5df --- /dev/null +++ b/custom_components/alexa_media/__init__.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +""" +Support to interface with Alexa Devices. + +For more details about this platform, please refer to the documentation at +https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639 +""" +import logging + +import voluptuous as vol + +from homeassistant import util +from homeassistant.const import ( + CONF_EMAIL, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_URL) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.event import track_time_interval +from homeassistant.helpers.discovery import load_platform +from .const import ( + ALEXA_COMPONENTS, CONF_DEBUG, CONF_ACCOUNTS, CONF_INCLUDE_DEVICES, + CONF_EXCLUDE_DEVICES, DATA_ALEXAMEDIA, DOMAIN, MIN_TIME_BETWEEN_SCANS, + MIN_TIME_BETWEEN_FORCED_SCANS, SCAN_INTERVAL, SERVICE_UPDATE_LAST_CALLED, + ATTR_EMAIL +) + +# from .config_flow import configured_instances + +REQUIREMENTS = ['alexapy==0.3.0'] + +__version__ = '1.2.1' + +_LOGGER = logging.getLogger(__name__) + + +ACCOUNT_CONFIG_SCHEMA = vol.Schema({ + vol.Required(CONF_EMAIL): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_URL): cv.string, + vol.Optional(CONF_DEBUG, default=False): cv.boolean, + vol.Optional(CONF_INCLUDE_DEVICES, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_EXCLUDE_DEVICES, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): + cv.time_period, +}) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Optional(CONF_ACCOUNTS): + vol.All(cv.ensure_list, [ACCOUNT_CONFIG_SCHEMA]), + }), +}, extra=vol.ALLOW_EXTRA) + +LAST_CALL_UPDATE_SCHEMA = vol.Schema({ + vol.Optional(ATTR_EMAIL, default=[]): + vol.All(cv.ensure_list, [cv.string]), +}) + + +def hide_email(email): + """Obfuscate email.""" + part = email.split('@') + return "{}{}{}@{}".format(part[0][0], + "*"*(len(part[0])-2), + part[0][-1], + part[1]) + + +def hide_serial(item): + """Obfuscate serial.""" + if item is None: + return "" + if isinstance(item, dict): + response = item.copy() + serial = item['serialNumber'] + response['serialNumber'] = hide_serial(serial) + elif isinstance(item, str): + response = "{}{}{}".format(item[0], + "*"*(len(item)-4), + item[-3:]) + return response + + +def setup(hass, config, discovery_info=None): + """Set up the Alexa domain.""" + if DATA_ALEXAMEDIA not in hass.data: + hass.data[DATA_ALEXAMEDIA] = {} + hass.data[DATA_ALEXAMEDIA]['accounts'] = {} + from alexapy import AlexaLogin + + config = config.get(DOMAIN) + for account in config[CONF_ACCOUNTS]: + # if account[CONF_EMAIL] in configured_instances(hass): + # continue + + email = account.get(CONF_EMAIL) + password = account.get(CONF_PASSWORD) + url = account.get(CONF_URL) + hass.data[DOMAIN]['accounts'][email] = {"config": []} + login = AlexaLogin(url, email, password, hass.config.path, + account.get(CONF_DEBUG)) + + test_login_status(hass, account, login, + setup_platform_callback) + return True + + +async def setup_platform_callback(hass, config, login, callback_data): + """Handle response from configurator. + + Args: + callback_data (json): Returned data from configurator passed through + request_configuration and configuration_callback + """ + _LOGGER.debug(("Status: %s got captcha: %s securitycode: %s" + " Claimsoption: %s VerificationCode: %s"), + login.status, + callback_data.get('captcha'), + callback_data.get('securitycode'), + callback_data.get('claimsoption'), + callback_data.get('verificationcode')) + login.login(captcha=callback_data.get('captcha'), + securitycode=callback_data.get('securitycode'), + claimsoption=callback_data.get('claimsoption'), + verificationcode=callback_data.get('verificationcode')) + test_login_status(hass, config, login, + setup_platform_callback) + + +def request_configuration(hass, config, login, setup_platform_callback): + """Request configuration steps from the user using the configurator.""" + configurator = hass.components.configurator + + async def configuration_callback(callback_data): + """Handle the submitted configuration.""" + hass.async_add_job(setup_platform_callback, hass, config, + login, callback_data) + status = login.status + email = login.email + # Get Captcha + if (status and 'captcha_image_url' in status and + status['captcha_image_url'] is not None): + config_id = configurator.request_config( + "Alexa Media Player - Captcha - {}".format(email), + configuration_callback, + description=('Please enter the text for the captcha.' + ' Please enter anything if the image is missing.' + ), + description_image=status['captcha_image_url'], + submit_caption="Confirm", + fields=[{'id': 'captcha', 'name': 'Captcha'}] + ) + elif (status and 'securitycode_required' in status and + status['securitycode_required']): # Get 2FA code + config_id = configurator.request_config( + "Alexa Media Player - 2FA - {}".format(email), + configuration_callback, + description=('Please enter your Two-Factor Security code.'), + submit_caption="Confirm", + fields=[{'id': 'securitycode', 'name': 'Security Code'}] + ) + elif (status and 'claimspicker_required' in status and + status['claimspicker_required']): # Get picker method + options = status['claimspicker_message'] + if options: + config_id = configurator.request_config( + "Alexa Media Player - Verification Method - {}".format(email), + configuration_callback, + description=('Please select the verification method. ' + '(e.g., sms or email).
{}').format( + options + ), + submit_caption="Confirm", + fields=[{'id': 'claimsoption', 'name': 'Option'}] + ) + else: + configuration_callback({}) + elif (status and 'verificationcode_required' in status and + status['verificationcode_required']): # Get picker method + config_id = configurator.request_config( + "Alexa Media Player - Verification Code - {}".format(email), + configuration_callback, + description=('Please enter received verification code.'), + submit_caption="Confirm", + fields=[{'id': 'verificationcode', 'name': 'Verification Code'}] + ) + else: # Check login + config_id = configurator.request_config( + "Alexa Media Player - Begin - {}".format(email), + configuration_callback, + description=('Please hit confirm to begin login attempt.'), + submit_caption="Confirm", + fields=[] + ) + hass.data[DOMAIN]['accounts'][email]['config'].append(config_id) + if 'error_message' in status and status['error_message']: + configurator.notify_errors( # use sync to delay next pop + config_id, + status['error_message']) + if len(hass.data[DOMAIN]['accounts'][email]['config']) > 1: + configurator.async_request_done((hass.data[DOMAIN] + ['accounts'][email]['config']).pop(0)) + + +def test_login_status(hass, config, login, + setup_platform_callback): + """Test the login status and spawn requests for info.""" + if 'login_successful' in login.status and login.status['login_successful']: + _LOGGER.debug("Setting up Alexa devices") + hass.async_add_job(setup_alexa, hass, config, + login) + return + if ('captcha_required' in login.status and + login.status['captcha_required']): + _LOGGER.debug("Creating configurator to request captcha") + elif ('securitycode_required' in login.status and + login.status['securitycode_required']): + _LOGGER.debug("Creating configurator to request 2FA") + elif ('claimspicker_required' in login.status and + login.status['claimspicker_required']): + _LOGGER.debug("Creating configurator to select verification option") + elif ('verificationcode_required' in login.status and + login.status['verificationcode_required']): + _LOGGER.debug("Creating configurator to enter verification code") + elif ('login_failed' in login.status and + login.status['login_failed']): + _LOGGER.debug("Creating configurator to start new login attempt") + hass.async_add_job(request_configuration, hass, config, login, + setup_platform_callback + ) + + +def setup_alexa(hass, config, login_obj): + """Set up a alexa api based on host parameter.""" + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) + def update_devices(): + """Ping Alexa API to identify all devices, bluetooth, and last called device. + + This will add new devices and services when discovered. By default this + runs every SCAN_INTERVAL seconds unless another method calls it. While + throttled at MIN_TIME_BETWEEN_SCANS, care should be taken to reduce the + number of runs to avoid flooding. Slow changing states should be + checked here instead of in spawned components like media_player since + this object is one per account. + Each AlexaAPI call generally results in one webpage request. + """ + from alexapy import AlexaAPI + existing_serials = (hass.data[DATA_ALEXAMEDIA] + ['accounts'] + [email] + ['entities'] + ['media_player'].keys()) + existing_entities = (hass.data[DATA_ALEXAMEDIA] + ['accounts'] + [email] + ['entities'] + ['media_player'].values()) + devices = AlexaAPI.get_devices(login_obj) + bluetooth = AlexaAPI.get_bluetooth(login_obj) + _LOGGER.debug("%s: Found %s devices, %s bluetooth", + hide_email(email), + len(devices) if devices is not None else '', + len(bluetooth) if bluetooth is not None else '') + if ((devices is None or bluetooth is None) + and not hass.data[DOMAIN]['accounts'][email]['config']): + _LOGGER.debug("Alexa API disconnected; attempting to relogin") + login_obj.login_with_cookie() + test_login_status(hass, config, login_obj, setup_platform_callback) + return + + new_alexa_clients = [] # list of newly discovered device names + excluded = [] + included = [] + for device in devices: + if include and device['accountName'] not in include: + included.append(device['accountName']) + continue + elif exclude and device['accountName'] in exclude: + excluded.append(device['accountName']) + continue + + for b_state in bluetooth['bluetoothStates']: + if device['serialNumber'] == b_state['deviceSerialNumber']: + device['bluetooth_state'] = b_state + + (hass.data[DATA_ALEXAMEDIA] + ['accounts'] + [email] + ['devices'] + ['media_player'] + [device['serialNumber']]) = device + + if device['serialNumber'] not in existing_serials: + new_alexa_clients.append(device['accountName']) + _LOGGER.debug("%s: Existing: %s New: %s;" + " Filtered by: include_devices: %s exclude_devices:%s", + hide_email(email), + list(existing_entities), + new_alexa_clients, + included, + excluded) + + if new_alexa_clients: + for component in ALEXA_COMPONENTS: + load_platform(hass, component, DOMAIN, {}, config) + + # Process last_called data to fire events + update_last_called(login_obj) + + def update_last_called(login_obj): + """Update the last called device for the login_obj. + + This will store the last_called in hass.data and also fire an event + to notify listeners. + """ + from alexapy import AlexaAPI + last_called = AlexaAPI.get_last_device_serial(login_obj) + _LOGGER.debug("%s: Updated last_called: %s", + hide_email(email), + hide_serial(last_called)) + stored_data = hass.data[DATA_ALEXAMEDIA]['accounts'][email] + if (('last_called' in stored_data and + last_called != stored_data['last_called']) or + ('last_called' not in stored_data and + last_called is not None)): + _LOGGER.debug("%s: last_called changed: %s to %s", + hide_email(email), + hide_serial(stored_data['last_called'] if + 'last_called' in stored_data else None), + hide_serial(last_called)) + hass.bus.fire(('{}_{}'.format(DOMAIN, hide_email(email)))[0:32], + {'last_called_change': last_called}) + (hass.data[DATA_ALEXAMEDIA] + ['accounts'] + [email] + ['last_called']) = last_called + + def last_call_handler(call): + """Handle last call service request. + + Args: + call.ATTR_EMAIL: List of case-sensitive Alexa email addresses. If None + all accounts are updated. + """ + requested_emails = call.data.get(ATTR_EMAIL) + _LOGGER.debug("Service update_last_called for: %s", requested_emails) + for email, account_dict in (hass.data + [DATA_ALEXAMEDIA]['accounts'].items()): + if requested_emails and email not in requested_emails: + continue + login_obj = account_dict['login_obj'] + update_last_called(login_obj) + + include = config.get(CONF_INCLUDE_DEVICES) + exclude = config.get(CONF_EXCLUDE_DEVICES) + scan_interval = config.get(CONF_SCAN_INTERVAL) + email = login_obj.email + (hass.data[DOMAIN]['accounts'][email]['login_obj']) = login_obj + (hass.data[DOMAIN]['accounts'][email]['devices']) = {'media_player': {}} + (hass.data[DOMAIN]['accounts'][email]['entities']) = {'media_player': {}} + update_devices() + track_time_interval(hass, lambda now: update_devices(), scan_interval) + hass.services.register(DOMAIN, SERVICE_UPDATE_LAST_CALLED, + last_call_handler, schema=LAST_CALL_UPDATE_SCHEMA) + + # Clear configurator. We delay till here to avoid leaving a modal orphan + for config_id in hass.data[DOMAIN]['accounts'][email]['config']: + configurator = hass.components.configurator + configurator.async_request_done(config_id) + hass.data[DOMAIN]['accounts'][email]['config'] = [] + return True \ No newline at end of file diff --git a/custom_components/alexa_media/const.py b/custom_components/alexa_media/const.py new file mode 100644 index 0000000..b89334e --- /dev/null +++ b/custom_components/alexa_media/const.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +""" +Support to interface with Alexa Devices. + +For more details about this platform, please refer to the documentation at +https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639 +""" +from datetime import timedelta + +DOMAIN = 'alexa_media' +DATA_ALEXAMEDIA = 'alexa_media' + +PLAY_SCAN_INTERVAL = 20 +SCAN_INTERVAL = timedelta(seconds=60) +MIN_TIME_BETWEEN_SCANS = SCAN_INTERVAL +MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) + +ALEXA_COMPONENTS = [ + 'media_player' +] + +CONF_ACCOUNTS = 'accounts' +CONF_DEBUG = 'debug' +CONF_INCLUDE_DEVICES = 'include_devices' +CONF_EXCLUDE_DEVICES = 'exclude_devices' +SERVICE_ALEXA_TTS = 'alexa_tts' +SERVICE_UPDATE_LAST_CALLED = 'update_last_called' +ATTR_MESSAGE = 'message' +ATTR_EMAIL = 'email' \ No newline at end of file diff --git a/custom_components/alexa_media/media_player.py b/custom_components/alexa_media/media_player.py new file mode 100644 index 0000000..8551888 --- /dev/null +++ b/custom_components/alexa_media/media_player.py @@ -0,0 +1,604 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +""" +Support to interface with Alexa Devices. + +For more details about this platform, please refer to the documentation at +https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639 +""" +import logging + +import voluptuous as vol +from homeassistant import util +from homeassistant.components.media_player import (MEDIA_PLAYER_SCHEMA, + MediaPlayerDevice) +from homeassistant.components.media_player.const import ( + DOMAIN, + MEDIA_TYPE_MUSIC, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET) +from homeassistant.const import (STATE_IDLE, STATE_PAUSED, STATE_PLAYING, + STATE_STANDBY) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.event import call_later +from homeassistant.helpers.service import extract_entity_ids + +from .const import ATTR_MESSAGE, PLAY_SCAN_INTERVAL, SERVICE_ALEXA_TTS + +from . import ( + DOMAIN as ALEXA_DOMAIN, + DATA_ALEXAMEDIA, + MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS, + hide_email, hide_serial) +SUPPORT_ALEXA = (SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | + SUPPORT_NEXT_TRACK | SUPPORT_STOP | + SUPPORT_VOLUME_SET | SUPPORT_PLAY | + SUPPORT_PLAY_MEDIA | SUPPORT_TURN_OFF | SUPPORT_TURN_ON | + SUPPORT_VOLUME_MUTE | SUPPORT_PAUSE | + SUPPORT_SELECT_SOURCE) +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = [ALEXA_DOMAIN] + +ALEXA_TTS_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ + vol.Required(ATTR_MESSAGE): cv.string, +}) + + +def setup_platform(hass, config, add_devices_callback, + discovery_info=None): + """Set up the Alexa media player platform.""" + def tts_handler(call): + for alexa in service_to_entities(call): + if call.service == SERVICE_ALEXA_TTS: + message = call.data.get(ATTR_MESSAGE) + alexa.send_tts(message) + + def service_to_entities(call): + """Return the known devices that a service call mentions.""" + entity_ids = extract_entity_ids(hass, call) + if entity_ids: + devices = [] + for account, account_dict in (hass.data[DATA_ALEXAMEDIA] + ['accounts'].items()): + devices = devices + list(account_dict + ['entities']['media_player'].values()) + _LOGGER.debug("Account: %s Devices: %s", + hide_email(account), + devices) + entities = [entity for entity in devices + if entity.entity_id in entity_ids] + else: + entities = None + + return entities + + devices = [] + for account, account_dict in (hass.data[DATA_ALEXAMEDIA] + ['accounts'].items()): + for key, device in account_dict['devices']['media_player'].items(): + if key not in account_dict['entities']['media_player']: + alexa_client = AlexaClient(device, + account_dict['login_obj'], + hass) + devices.append(alexa_client) + (hass.data[DATA_ALEXAMEDIA] + ['accounts'] + [account] + ['entities'] + ['media_player'][key]) = alexa_client + _LOGGER.debug("Adding %s", devices) + add_devices_callback(devices, True) + hass.services.register(DOMAIN, SERVICE_ALEXA_TTS, tts_handler, + schema=ALEXA_TTS_SCHEMA) + + +class AlexaClient(MediaPlayerDevice): + """Representation of a Alexa device.""" + + def __init__(self, device, login, hass): + """Initialize the Alexa device.""" + from alexapy import AlexaAPI + + # Class info + self._login = login + self.alexa_api = AlexaAPI(self, login) + self.auth = AlexaAPI.get_authentication(login) + self.alexa_api_session = login.session + self.account = hide_email(login.email) + + # Logged in info + self._authenticated = None + self._can_access_prime_music = None + self._customer_email = None + self._customer_id = None + self._customer_name = None + self._set_authentication_details(self.auth) + + # Device info + self._device = None + self._device_name = None + self._device_serial_number = None + self._device_type = None + self._device_family = None + self._device_owner_customer_id = None + self._software_version = None + self._available = None + self._capabilities = [] + self._cluster_members = [] + # Media + self._session = None + self._media_duration = None + self._media_image_url = None + self._media_title = None + self._media_pos = None + self._media_album_name = None + self._media_artist = None + self._media_player_state = None + self._media_is_muted = None + self._media_vol_level = None + self._previous_volume = None + self._source = None + self._source_list = [] + # Last Device + self._last_called = None + # Polling state + self._should_poll = True + self._last_update = 0 + self.refresh(device) + # Register event handler on bus + hass.bus.listen(('{}_{}'.format(ALEXA_DOMAIN, + hide_email(login.email)))[0:32], + self._handle_event) + + def _handle_event(self, event): + """Handle events. + + Each MediaClient reports if it's the last_called MediaClient. All + devices on account update to handle starting music with other Alexas. + Last_called events are only sent if it's a new device or timestamp. + """ + if (event.data['last_called_change']['serialNumber'] == + self.device_serial_number): + _LOGGER.debug("%s is last_called: %s", self.name, + hide_serial(self.device_serial_number)) + self._last_called = True + self._last_called = False + # Without polling, we must schedule the HA update. + # https://developers.home-assistant.io/docs/en/entity_index.html#subscribing-to-updates + self.schedule_update_ha_state(force_refresh=True) + + def _clear_media_details(self): + """Set all Media Items to None.""" + # General + self._media_duration = None + self._media_image_url = None + self._media_title = None + self._media_pos = None + self._media_album_name = None + self._media_artist = None + self._media_player_state = None + self._media_is_muted = None + self._media_vol_level = None + + def _set_authentication_details(self, auth): + """Set Authentication based off auth.""" + self._authenticated = auth['authenticated'] + self._can_access_prime_music = auth['canAccessPrimeMusicContent'] + self._customer_email = auth['customerEmail'] + self._customer_id = auth['customerId'] + self._customer_name = auth['customerName'] + + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) + def refresh(self, device=None): + """Refresh device data. + + This is a per device refresh and for many Alexa devices can result in + many refreshes from each individual device. This will call the + AlexaAPI directly. + + Args: + device (json): A refreshed device json from Amazon. For efficiency, + an individual device does not refresh if it's reported + as offline. + """ + if device is not None: + self._device = device + self._device_name = device['accountName'] + self._device_family = device['deviceFamily'] + self._device_type = device['deviceType'] + self._device_serial_number = device['serialNumber'] + self._device_owner_customer_id = device['deviceOwnerCustomerId'] + self._software_version = device['softwareVersion'] + self._available = device['online'] + self._capabilities = device['capabilities'] + self._cluster_members = device['clusterMembers'] + self._bluetooth_state = device['bluetooth_state'] + if self._available is True: + _LOGGER.debug("%s: Refreshing %s", self.account, self.name) + self._source = self._get_source() + self._source_list = self._get_source_list() + self._last_called = self._get_last_called() + session = self.alexa_api.get_state() + else: + session = None + self._clear_media_details() + # update the session if it exists; not doing relogin here + if session is not None: + self._session = session + if self._session is None: + return + if 'playerInfo' in self._session: + self._session = self._session['playerInfo'] + if self._session['state'] is not None: + self._media_player_state = self._session['state'] + self._media_pos = (self._session['progress']['mediaProgress'] + if (self._session['progress'] is not None + and 'mediaProgress' in + self._session['progress']) + else None) + self._media_is_muted = (self._session['volume']['muted'] + if (self._session['volume'] is not None + and 'muted' in + self._session['volume']) + else None) + self._media_vol_level = (self._session['volume'] + ['volume'] / 100 + if(self._session['volume'] is not None + and 'volume' in + self._session['volume']) + else None) + self._media_title = (self._session['infoText']['title'] + if (self._session['infoText'] is not None + and 'title' in + self._session['infoText']) + else None) + self._media_artist = (self._session['infoText']['subText1'] + if (self._session['infoText'] is not None + and 'subText1' in + self._session['infoText']) + else None) + self._media_album_name = (self._session['infoText']['subText2'] + if (self._session['infoText'] is not + None and 'subText2' in + self._session['infoText']) + else None) + self._media_image_url = (self._session['mainArt']['url'] + if (self._session['mainArt'] is not + None and 'url' in + self._session['mainArt']) + else None) + self._media_duration = (self._session['progress'] + ['mediaLength'] + if (self._session['progress'] is not + None and 'mediaLength' in + self._session['progress']) + else None) + + @property + def source(self): + """Return the current input source.""" + return self._source + + @property + def source_list(self): + """List of available input sources.""" + return self._source_list + + def select_source(self, source): + """Select input source.""" + if source == 'Local Speaker': + self.alexa_api.disconnect_bluetooth() + self._source = 'Local Speaker' + elif self._bluetooth_state['pairedDeviceList'] is not None: + for devices in self._bluetooth_state['pairedDeviceList']: + if devices['friendlyName'] == source: + self.alexa_api.set_bluetooth(devices['address']) + self._source = source + + def _get_source(self): + source = 'Local Speaker' + if self._bluetooth_state['pairedDeviceList'] is not None: + for device in self._bluetooth_state['pairedDeviceList']: + if device['connected'] is True: + return device['friendlyName'] + return source + + def _get_source_list(self): + sources = [] + if self._bluetooth_state['pairedDeviceList'] is not None: + for devices in self._bluetooth_state['pairedDeviceList']: + sources.append(devices['friendlyName']) + return ['Local Speaker'] + sources + + def _get_last_called(self): + last_called_serial = (None if self.hass is None else + (self.hass.data[DATA_ALEXAMEDIA] + ['accounts'] + [self._login.email] + ['last_called'] + ['serialNumber'])) + _LOGGER.debug("%s: Last_called check: self: %s reported: %s", + self._device_name, + hide_serial(self._device_serial_number), + hide_serial(last_called_serial)) + if (last_called_serial is not None and + self._device_serial_number == last_called_serial): + return True + return False + + @property + def available(self): + """Return the availability of the client.""" + return self._available + + @property + def unique_id(self): + """Return the id of this Alexa client.""" + return self.device_serial_number + + @property + def name(self): + """Return the name of the device.""" + return self._device_name + + @property + def device_serial_number(self): + """Return the machine identifier of the device.""" + return self._device_serial_number + + @property + def device(self): + """Return the device, if any.""" + return self._device + + @property + def session(self): + """Return the session, if any.""" + return self._session + + @property + def state(self): + """Return the state of the device.""" + if self._media_player_state == 'PLAYING': + return STATE_PLAYING + if self._media_player_state == 'PAUSED': + return STATE_PAUSED + if self._media_player_state == 'IDLE': + return STATE_IDLE + return STATE_STANDBY + + def update(self): + """Get the latest details on a media player. + + Because media players spend the majority of time idle, an adaptive + update should be used to avoid flooding Amazon focusing on known + play states. An initial version included an update_devices call on + every update. However, this quickly floods the network for every new + device added. This should only call refresh() to call the AlexaAPI. + """ + if (self._device is None or self.entity_id is None): + # Device has not initialized yet + return + self.refresh(no_throttle=True) + if self.state in [STATE_PLAYING]: + self._should_poll = False # disable polling since manual update + if(self._last_update == 0 or util.dt.as_timestamp(util.utcnow()) - + util.dt.as_timestamp(self._last_update) + > PLAY_SCAN_INTERVAL): + _LOGGER.debug("%s playing; scheduling update in %s seconds", + self.name, PLAY_SCAN_INTERVAL) + call_later(self.hass, PLAY_SCAN_INTERVAL, lambda _: + self.schedule_update_ha_state(force_refresh=True)) + elif self._should_poll: # Not playing, one last poll + self._should_poll = False + _LOGGER.debug("Disabling polling and scheduling last update in 300" + " seconds for %s", + self.name) + call_later(self.hass, 300, lambda _: + self.schedule_update_ha_state(force_refresh=True)) + self._last_update = util.utcnow() + self.schedule_update_ha_state() + + @property + def media_content_type(self): + """Return the content type of current playing media.""" + if self.state in [STATE_PLAYING, STATE_PAUSED]: + return MEDIA_TYPE_MUSIC + return STATE_STANDBY + + @property + def media_artist(self): + """Return the artist of current playing media, music track only.""" + return self._media_artist + + @property + def media_album_name(self): + """Return the album name of current playing media, music track only.""" + return self._media_album_name + + @property + def media_duration(self): + """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_pos + + @property + def media_position_updated_at(self): + """When was the position of the current playing media valid.""" + return self._last_update + + @property + def media_image_url(self): + """Return the image URL of current playing media.""" + return self._media_image_url + + @property + def media_title(self): + """Return the title of current playing media.""" + return self._media_title + + @property + def device_family(self): + """Return the make of the device (ex. Echo, Other).""" + return self._device_family + + @property + def supported_features(self): + """Flag media player features that are supported.""" + return SUPPORT_ALEXA + + def set_volume_level(self, volume): + """Set volume level, range 0..1.""" + if not self.available: + return + self.alexa_api.set_volume(volume) + self._media_vol_level = volume + self.update() + + @property + def volume_level(self): + """Return the volume level of the client (0..1).""" + return self._media_vol_level + + @property + def is_volume_muted(self): + """Return boolean if volume is currently muted.""" + if self.volume_level == 0: + return True + return False + + def mute_volume(self, mute): + """Mute the volume. + + Since we can't actually mute, we'll: + - On mute, store volume and set volume to 0 + - On unmute, set volume to previously stored volume + """ + if not (self.state == STATE_PLAYING and self.available): + return + + self._media_is_muted = mute + if mute: + self._previous_volume = self.volume_level + self.alexa_api.set_volume(0) + else: + if self._previous_volume is not None: + self.alexa_api.set_volume(self._previous_volume) + else: + self.alexa_api.set_volume(50) + self.update() + + def media_play(self): + """Send play command.""" + if not (self.state in [STATE_PLAYING, STATE_PAUSED] + and self.available): + return + self.alexa_api.play() + self.update() + + def media_pause(self): + """Send pause command.""" + if not (self.state in [STATE_PLAYING, STATE_PAUSED] + and self.available): + return + self.alexa_api.pause() + self.update() + + def turn_off(self): + """Turn the client off. + + While Alexa's do not have on/off capability, we can use this as another + trigger to do updates. For turning off, we can clear media_details. + """ + self._should_poll = False + self.media_pause() + self._clear_media_details() + + def turn_on(self): + """Turn the client on. + + While Alexa's do not have on/off capability, we can use this as another + trigger to do updates. + """ + self._should_poll = True + self.media_pause() + + def media_next_track(self): + """Send next track command.""" + if not (self.state in [STATE_PLAYING, STATE_PAUSED] + and self.available): + return + self.alexa_api.next() + self.update() + + def media_previous_track(self): + """Send previous track command.""" + if not (self.state in [STATE_PLAYING, STATE_PAUSED] + and self.available): + return + self.alexa_api.previous() + self.update() + + def send_tts(self, message): + """Send TTS to Device. + + NOTE: Does not work on WHA Groups. + """ + self.alexa_api.send_tts(message, customer_id=self._customer_id) + + def send_announcement(self, message, **kwargs): + """Send announcement to the media player.""" + self.alexa_api.send_announcement(message, + customer_id=self._customer_id, + **kwargs) + + def send_mobilepush(self, message, **kwargs): + """Send push to the media player's associated mobile devices.""" + self.alexa_api.send_mobilepush(message, + customer_id=self._customer_id, + **kwargs) + + def play_media(self, media_type, media_id, enqueue=None, **kwargs): + """Send the play_media command to the media player.""" + if media_type == "music": + self.alexa_api.send_tts("Sorry, text to speech can only be called " + " with the media player alexa tts service") + elif media_type == "sequence": + self.alexa_api.send_sequence(media_id, + customer_id=self._customer_id, + **kwargs) + elif media_type == "routine": + self.alexa_api.run_routine(media_id) + else: + self.alexa_api.play_music(media_type, media_id, + customer_id=self._customer_id, **kwargs) + self.update() + + @property + def device_state_attributes(self): + """Return the scene state attributes.""" + attr = { + 'available': self._available, + 'last_called': self._last_called + } + return attr + + @property + def should_poll(self): + """Return the polling state.""" + return self._should_poll \ No newline at end of file diff --git a/custom_components/alexa_media/notify.py b/custom_components/alexa_media/notify.py new file mode 100644 index 0000000..f37e606 --- /dev/null +++ b/custom_components/alexa_media/notify.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +""" +Alexa Devices notification service. + +For more details about this platform, please refer to the documentation at +https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639 +""" +import logging + +from homeassistant.components.notify import ( + ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, + BaseNotificationService +) + +from . import ( + DOMAIN as ALEXA_DOMAIN, + DATA_ALEXAMEDIA, + hide_email, hide_serial) + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = [ALEXA_DOMAIN] + +EVENT_NOTIFY = "notify" + + +def get_service(hass, config, discovery_info=None): + # pylint: disable=unused-argument + """Get the demo notification service.""" + return AlexaNotificationService(hass) + + +class AlexaNotificationService(BaseNotificationService): + """Implement Alexa Media Player notification service.""" + + def __init__(self, hass): + """Initialize the service.""" + self.hass = hass + + def convert(self, names, type_="entities", filter_matches=False): + """Return a list of converted Alexa devices based on names. + + Names may be matched either by serialNumber, accountName, or + Homeassistant entity_id and can return any of the above plus entities + + Parameters + ---------- + names : list(string) + A list of names to convert + type : string + The type to return entities, entity_ids, serialnumbers, names + filter_matches : bool + Whether non-matching items are removed from the returned list. + + Returns + ------- + list(string) + List of home assistant entity_ids + + """ + devices = [] + if isinstance(names, str): + names = [names] + for item in names: + matched = False + for alexa in self.devices: + _LOGGER.debug("Testing item: %s against (%s, %s, %s, %s)", + item, + alexa, + alexa.name, + hide_serial(alexa.unique_id), + alexa.entity_id) + if item in (alexa, alexa.name, alexa.unique_id, + alexa.entity_id): + if type_ == "entities": + converted = alexa + elif type_ == "serialnumbers": + converted = alexa.unique_id + elif type_ == "names": + converted = alexa.name + elif type_ == "entity_ids": + converted = alexa.entity_id + devices.append(converted) + matched = True + _LOGGER.debug("Converting: %s to (%s): %s", + item, + type_, + converted) + if not filter_matches and not matched: + devices.append(item) + return devices + + # This can't be enabled because notify is setup before the media_player + # once a method is determined to wait till media_player, this can be used + # @property + # def targets(self): + # """Return a dictionary of Alexa devices.""" + # devices = {} + # # if ('accounts' not in self.hass.data[DATA_ALEXAMEDIA] or + # # self.hass.data[DATA_ALEXAMEDIA]['accounts'].items()): + # # return devices + # for account, account_dict in (self.hass.data[DATA_ALEXAMEDIA] + # ['accounts'].items()): + # for alexa in account_dict['entities']['media_player'].values(): + # devices[alexa.name] = alexa + # _LOGGER.debug("Account: %s Devices: %s Raw:%s", + # hide_email(account), + # devices, + # account_dict) + # return devices + + @property + def devices(self): + """Return a dictionary of Alexa devices.""" + devices = [] + if ('accounts' not in self.hass.data[DATA_ALEXAMEDIA] and + not self.hass.data[DATA_ALEXAMEDIA]['accounts'].items()): + return devices + for _, account_dict in (self.hass.data[DATA_ALEXAMEDIA] + ['accounts'].items()): + devices = devices + list(account_dict + ['entities']['media_player'].values()) + return devices + + def send_message(self, message="", **kwargs): + """Send a message to a Alexa device.""" + _LOGGER.info("Message: %s, kwargs: %s", + message, + kwargs) + kwargs['message'] = message + targets = kwargs.get(ATTR_TARGET) + title = (kwargs.get(ATTR_TITLE) if ATTR_TITLE in kwargs + else ATTR_TITLE_DEFAULT) + data = kwargs.get(ATTR_DATA) + if isinstance(targets, str): + targets = [targets] + entities = self.convert(targets, type_="entities") + try: + entities.extend(self.hass.components.group.expand_entity_ids( + entities)) + except ValueError: + _LOGGER.info("Invalid Home Assistant entity in %s", entities) + if data['type'] == "tts": + targets = self.convert(entities, type_="entities", + filter_matches=True) + _LOGGER.debug("TTS entities: %s", targets) + for alexa in targets: + _LOGGER.info("TTS by %s : %s", alexa, message) + alexa.send_tts(message) + elif data['type'] == "announce": + targets = self.convert(entities, type_="serialnumbers", + filter_matches=True) + _LOGGER.debug("Announce targets: %s entities: %s", + list(map(hide_serial, targets)), + entities) + for account, account_dict in (self.hass.data[DATA_ALEXAMEDIA] + ['accounts'].items()): + for alexa in (account_dict['entities'] + ['media_player'].values()): + if alexa.unique_id in targets and alexa.available: + _LOGGER.info(("%s: Announce by %s to " + "targets: %s: %s"), + hide_email(account), + alexa, + list(map(hide_serial, targets)), + message) + alexa.send_announcement(message, + targets=targets, + title=title, + method=(data['method'] if + 'method' in data + else 'all')) + break + elif data['type'] == "push": + targets = self.convert(entities, type_="entities", + filter_matches=True) + for alexa in targets: + _LOGGER.info("Push by %s : %s %s", alexa, title, message) + alexa.send_mobilepush(message, title=title) \ No newline at end of file diff --git a/custom_components/xboxone/media_player.py b/custom_components/xboxone/media_player.py new file mode 100644 index 0000000..90b6e9e --- /dev/null +++ b/custom_components/xboxone/media_player.py @@ -0,0 +1,665 @@ +""" +Support for functionality to interact with the Xbox One gaming console via SmartGlass protocol. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/media_player.xboxone/ + +CREDITS: +- This module is based on media_player.firetv component, initially created by @happyleavesaoc +- Original code: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/media_player/firetv.py +""" +import logging +import functools +import requests +import voluptuous as vol +from urllib.parse import urljoin + +from homeassistant.components.media_player import ( + MediaPlayerDevice, PLATFORM_SCHEMA) + +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_MUTE, SUPPORT_PLAY, + MEDIA_TYPE_MUSIC, MEDIA_TYPE_VIDEO, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_CHANNEL) +from homeassistant.const import ( + STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, STATE_ON, + CONF_HOST, CONF_PORT, CONF_SSL, CONF_NAME, CONF_DEVICE, CONF_AUTHENTICATION, + CONF_IP_ADDRESS) +import homeassistant.util.dt as dt_util +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +SUPPORT_XBOXONE = SUPPORT_PAUSE | \ + SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \ + SUPPORT_NEXT_TRACK | SUPPORT_SELECT_SOURCE | SUPPORT_PLAY | \ + SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE + +REQUIRED_SERVER_VERSION = '0.9.8' + +DEFAULT_SSL = False +DEFAULT_HOST = 'localhost' +DEFAULT_NAME = 'Xbox One SmartGlass' +DEFAULT_PORT = 5557 +DEFAULT_AUTHENTICATION = True + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_DEVICE): cv.string, + vol.Optional(CONF_IP_ADDRESS, default=''): cv.string, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional(CONF_AUTHENTICATION, default=DEFAULT_AUTHENTICATION): cv.boolean, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Xbox One platform.""" + name = config.get(CONF_NAME) + ssl = config.get(CONF_SSL) + host = config.get(CONF_HOST) + port = config.get(CONF_PORT) + liveid = config.get(CONF_DEVICE) + ip = config.get(CONF_IP_ADDRESS) + auth = config.get(CONF_AUTHENTICATION) + + proto = 'https' if ssl else 'http' + base_url = '{0}://{1}:{2}'.format(proto, host, port) + + add_devices([XboxOneDevice(base_url, liveid, ip, name, auth)]) + + +class XboxOne: + def __init__(self, base_url, liveid, ip, auth): + self.is_server_up = False + self.is_server_correct_version = True + + self.base_url = base_url + self.liveid = liveid + self._ip = ip + self._auth = auth + self._available = False + self._connected = False + self._media_status = None + self._console_status = None + self._volume_controls = None + self._pins = None + + def get(self, endpoint, *args, **kwargs): + endpoint = endpoint.replace('', self.liveid) + full_url = urljoin(self.base_url, endpoint) + return requests.get(full_url, *args, **kwargs) + + @property + def available(self): + return self._available + + @property + def connected(self): + return self._connected + + @property + def console_status(self): + return self._console_status + + @property + def media_status(self): + return self._media_status + + @property + def volume_controls(self): + volume_controls = self._volume_controls + if not volume_controls: + return None + + controls = volume_controls.get('avr') or volume_controls.get('tv') + if not controls: + return None + + return { + 'mute': controls['buttons']['btn.vol_mute']['url'], + 'up': controls['buttons']['btn.vol_up']['url'], + 'down': controls['buttons']['btn.vol_down']['url'], + } + + @property + def media_playback_state(self): + if self.media_status: + return self.media_status.get('playback_status') + + @property + def media_type(self): + if self.media_status: + return self.media_status.get('media_type') + + @property + def media_position(self): + if self.media_status: + position = self.media_status.get('position') + # Convert from nanoseconds + if isinstance(position, int) and position >= 10000000: + return position / 10000000 + + @property + def media_duration(self): + if self.media_status: + media_end = self.media_status.get('media_end') + # Convert from nanoseconds + if isinstance(media_end, int) and media_end >= 10000000: + return media_end / 10000000 + + @property + def media_title(self): + if self.media_status: + return self.media_status.get('metadata', {}).get('title') + + @property + def active_app(self): + if self.console_status: + active_titles = self.console_status.get('active_titles') + app = [a.get('name') for a in active_titles if a.get('has_focus')] + if len(app): + return app[0] + + @property + def active_app_image(self): + if self.console_status: + active_titles = self.console_status.get('active_titles') + app = [a.get('image') for a in active_titles if a.get('has_focus')] + if len(app): + return app[0] or None + + @property + def active_app_type(self): + if self.console_status: + active_titles = self.console_status.get('active_titles') + app = [a.get('type') for a in active_titles if a.get('has_focus')] + if len(app): + return app[0] + + @property + def all_apps(self): + apps = { + 'Home': 'ms-xbox-dashboard://home?view=home', + 'TV': 'ms-xbox-livetv://' + } + + if not self._pins and self._check_authentication(): + self._pins = self.get('/web/pins').json() + + if self._pins: + try: + for item in self._pins['ListItems']: + if item['Item']['ContentType'] == 'DApp' and item['Item']['Title'] not in apps.keys(): + apps[item['Item']['Title']] = 'appx:{0}!App'.format(item['Item']['ItemId']) + except: + pass + + if self.console_status: + active_titles = self.console_status.get('active_titles') + for app in active_titles: + if app.get('has_focus') and app.get('name') not in apps.keys(): + apps[app.get('name')] = app.get('aum') + return apps + + def _check_authentication(self): + try: + response = self.get('/auth').json() + if response.get('authenticated'): + return True + + response = self.get('/auth/refresh').json() + if response.get('success'): + return True + + except requests.exceptions.RequestException: + _LOGGER.error('Unreachable /auth endpoint') + return False + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + + _LOGGER.error('Refreshing authentication tokens failed!') + return False + + def _refresh_devicelist(self): + params = None + if self._ip: + params = {'addr': self._ip} + self.get('/device', params=params) + + def _connect(self): + if self._auth and not self._check_authentication(): + return False + try: + url = '/device//connect' + params = {} + if not self._auth: + params['anonymous'] = True + response = self.get(url, params=params).json() + if not response.get('success'): + _LOGGER.error('Failed to connect to console {0}: {1}'.format(self.liveid, str(response))) + return False + except requests.exceptions.RequestException: + _LOGGER.error('Unreachable /connect endpoint') + return False + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + return None + + return True + + def _get_device_info(self): + try: + response = self.get('/device/').json() + if not response.get('success'): + _LOGGER.debug('Console {0} not available'.format(self.liveid)) + return None + except requests.exceptions.RequestException: + _LOGGER.error('Unreachable device info / endpoint') + return None + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + return None + + return response['device'] + + def _update_console_status(self): + try: + response = self.get('/device//console_status').json() + if not response.get('success'): + _LOGGER.error('Console {0} not available'.format(self.liveid)) + return None + except requests.exceptions.RequestException: + _LOGGER.error('Unreachable /console_status endpoint') + return None + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + return None + + self._console_status = response['console_status'] + + def _update_media_status(self): + try: + response = self.get('/device//media_status').json() + if not response.get('success'): + _LOGGER.error('Console {0} not available'.format(self.liveid)) + return None + except requests.exceptions.RequestException: + _LOGGER.error('Unreachable /media_status endpoint') + return None + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + return None + + self._media_status = response['media_status'] + + def _update_volume_controls(self): + if self._volume_controls: + return + + try: + response = self.get('/device//ir').json() + if not response.get('success'): + _LOGGER.error('Console {0} not available'.format(self.liveid)) + return None + except requests.exceptions.RequestException: + _LOGGER.error('Unreachable /ir endpoint') + return None + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + return None + + self._volume_controls = response + + def poweron(self): + try: + url = '/device//poweron' + params = None + if self._ip: + params = { 'addr': self._ip } + response = self.get(url, params=params).json() + if not response.get('success'): + _LOGGER.error('Failed to poweron {0}'.format(self.liveid)) + return None + except requests.exceptions.RequestException: + _LOGGER.error('Unreachable /poweron endpoint') + return None + + return response + + def poweroff(self): + try: + response = self.get('/device//poweroff').json() + if not response.get('success'): + _LOGGER.error('Failed to poweroff {0}'.format(self.liveid)) + return None + except requests.exceptions.RequestException: + _LOGGER.error('Failed to call poweroff for {0}'.format(self.liveid)) + return None + + return response + + def ir_command(self, device, command): + try: + response = self.get('/device//ir').json() + if not response.get('success'): + return None + except requests.exceptions.RequestException: + _LOGGER.error('Failed to get enabled media commands for {0}'.format(self.liveid)) + return None + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + return + + enabled_commands = response.get(device).get('buttons') + if command not in enabled_commands: + _LOGGER.error('Provided command {0} not enabled for current ir device'.format(command)) + return None + else: + button_url = enabled_commands.get(command).get('url') + + try: + response = self.get('{0}'.format(button_url)).json() + if not response.get('success'): + return None + except requests.exceptions.RequestException: + _LOGGER.error('Failed to get enabled ir commands for {0}'.format(self.liveid)) + return None + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + + return response + + def media_command(self, command): + try: + response = self.get('/device//media').json() + if not response.get('success'): + return None + except requests.exceptions.RequestException: + _LOGGER.error('Failed to get enabled media commands for {0}'.format(self.liveid)) + return None + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + return None + + enabled_commands = response.get('commands') + if command not in enabled_commands: + _LOGGER.error('Provided command {0} not enabled for current media'.format(command)) + return None + + try: + response = self.get('/device//media/{0}'.format(command)).json() + if not response.get('success'): + return None + except requests.exceptions.RequestException: + _LOGGER.error('Failed to get enabled media commands for {0}'.format(self.liveid)) + return None + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + return None + + return response + + def volume_command(self, command): + if not self._volume_controls: + return None + + url = self._volume_controls.get(command) + + if not url: + return None + + try: + response = self.get(url).json() + if not response.get('success'): + return None + except requests.exceptions.RequestException: + _LOGGER.error('Failed to get enabled volume commands for {0}'.format(self.liveid)) + return None + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + return None + + return response + + def launch_title(self, launch_uri): + try: + apps = self.all_apps + if launch_uri in apps.keys(): + launch_uri = apps[launch_uri] + response = self.get('/device//launch/{0}'.format(launch_uri)).json() + if not response.get('success'): + return None + except requests.exceptions.RequestException: + _LOGGER.error('Failed to launch title \'{0}\' for {1}'.format(launch_uri, self.liveid)) + return None + except Exception as e: + _LOGGER.error('Unknown Error: %s', e) + return None + + return response + + def _check_server(self): + if not self.is_server_correct_version: + return False + + try: + resp = self.get('/versions').json() + version = resp['versions']['xbox-smartglass-rest'] + if version != REQUIRED_SERVER_VERSION: + self.is_server_correct_version = False + _LOGGER.error("Invalid xbox-smartglass-rest version: %s. Required: %s", + version, REQUIRED_SERVER_VERSION) + except requests.exceptions.RequestException: + self.is_server_up = False + return False + + self.is_server_up = True + return True + + def refresh(self): + """ + Enumerate devices and refresh status info + """ + + if not self._check_server(): + return + + self._check_authentication() + self._refresh_devicelist() + + device_info = self._get_device_info() + if not device_info or device_info.get('device_status') == 'Unavailable': + self._available = False + self._connected = False + self._console_status = None + self._media_status = None + self._volume_controls = None + else: + self._available = True + + connection_state = device_info.get('connection_state') + if connection_state == 'Connected': + self._connected = True + else: + success = self._connect() + if not success: + _LOGGER.error('Failed to connect to {0}'.format(self.liveid)) + self._connected = False + else: + self._connected = True + + if self.available and self.connected: + self._update_console_status() + self._update_media_status() + self._update_volume_controls() + + +class XboxOneDevice(MediaPlayerDevice): + """Representation of an Xbox One device on the network.""" + + def __init__(self, base_url, liveid, ip, name, auth): + """Initialize the Xbox One device.""" + self._xboxone = XboxOne(base_url, liveid, ip, auth) + self._name = name + self._liveid = liveid + self._state = STATE_UNKNOWN + self._running_apps = None + self._current_app = None + + @property + def name(self): + """Return the device name.""" + return self._name + + @property + def unique_id(self): + """Console Live ID""" + return self._liveid + + @property + def should_poll(self): + """Device should be polled.""" + return True + + @property + def supported_features(self): + """Flag media player features that are supported.""" + active_support = SUPPORT_XBOXONE + if self.state not in [STATE_PLAYING, STATE_PAUSED]\ + and (self._xboxone.active_app_type not in ['Application', 'App'] or self._xboxone.active_app == 'Home'): + active_support &= ~SUPPORT_NEXT_TRACK & ~SUPPORT_PREVIOUS_TRACK + if not self._xboxone.volume_controls: + active_support &= ~SUPPORT_VOLUME_MUTE & ~SUPPORT_VOLUME_STEP + return active_support + + @property + def state(self): + """Return the state of the player.""" + playback_state = { + 'Closed': STATE_IDLE, + 'Changing': STATE_IDLE, + 'Stopped': STATE_IDLE, + 'Playing': STATE_PLAYING, + 'Paused': STATE_PAUSED + }.get(self._xboxone.media_playback_state) + + if playback_state: + state = playback_state + elif self._xboxone.connected or self._xboxone.available: + if self._xboxone.active_app_type in ['Application', 'App'] or self._xboxone.active_app == 'Home': + state = STATE_ON + else: + state = STATE_UNKNOWN + else: + state = STATE_OFF + + return state + + @property + def media_content_type(self): + """Media content type""" + if self.state in [STATE_PLAYING, STATE_PAUSED]: + return { + 'Music': MEDIA_TYPE_MUSIC, + 'Video': MEDIA_TYPE_VIDEO + }.get(self._xboxone.media_type) + + @property + def media_duration(self): + """Duration in seconds""" + if self.state in [STATE_PLAYING, STATE_PAUSED]: + return self._xboxone.media_duration + + @property + def media_position(self): + """Position in seconds""" + if self.state in [STATE_PLAYING, STATE_PAUSED]: + return self._xboxone.media_position + + @property + def media_position_updated_at(self): + """Last valid time of media position""" + if self.state in [STATE_PLAYING, STATE_PAUSED]: + return dt_util.utcnow() + + @property + def media_image_url(self): + """Image url of current playing media.""" + return self._xboxone.active_app_image + + @property + def media_title(self): + """When media is playing, print title (if any) - otherwise, print app name""" + if self.state in [STATE_PLAYING, STATE_PAUSED]: + return self._xboxone.media_title + return self._xboxone.active_app + + @property + def source(self): + """Return the current app.""" + return self._xboxone.active_app + + @property + def source_list(self): + """Return a list of running apps.""" + return list(self._xboxone.all_apps.keys()) + + def update(self): + """Get the latest date and update device state.""" + self._xboxone.refresh() + + def turn_on(self): + """Turn on the device.""" + self._xboxone.poweron() + + def turn_off(self): + """Turn off the device.""" + self._xboxone.poweroff() + + def mute_volume(self, mute): + """Mute the volume.""" + self._xboxone.volume_command('mute') + + def volume_up(self): + """Turn volume up for media player.""" + self._xboxone.volume_command('up') + + def volume_down(self): + """Turn volume down for media player.""" + self._xboxone.volume_command('down') + + def media_play(self): + """Send play command.""" + self._xboxone.media_command('play') + + def media_pause(self): + """Send pause command.""" + self._xboxone.media_command('pause') + + def media_stop(self): + self._xboxone.media_command('stop') + + def media_play_pause(self): + """Send play/pause command.""" + self._xboxone.media_command('play_pause') + + def media_previous_track(self): + """Send previous track command.""" + if self._xboxone.active_app == 'TV': + self._xboxone.ir_command('stb', 'btn.ch_down') + else: + self._xboxone.media_command('prev_track') + + def media_next_track(self): + """Send next track command.""" + if self._xboxone.active_app == 'TV': + self._xboxone.ir_command('stb', 'btn.ch_up') + else: + self._xboxone.media_command('next_track') + + def select_source(self, source): + """Select input source.""" + self._xboxone.launch_title(source) diff --git a/lovelace-gen.py b/lovelace-gen.py new file mode 100644 index 0000000..0acd464 --- /dev/null +++ b/lovelace-gen.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 + +# The MIT License (MIT) + +# Copyright (c) 2018 Thomas Lovén + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +Generate ui-lovelace.yaml from lovelace/main.yaml +""" + +import sys +import os +import yaml +import shutil +import time +import jinja2 +import requests +import json + +indir = "lovelace" +infile = "main.yaml" +secretsfile = "secrets.yaml" + +outfile = "ui-lovelace.yaml" + +wwwdir = "www" +resourcedir = "lovelace" +timestamp = time.time(); +states = {} + +helpstring = """ +usage: lovelace-gen.py + Generates ui-lovelace.yaml from lovelace/main.yaml + +Special commands: + !include + Is replaced by the contents of the file lovelace/ + !secret + Is replaced by the value from secrets.yaml for . + !resource [/] + Copies the file lovelace/ to www/lovelace/ and is replaced with /local/lovelace/ +""" + +def include_statement(loader, node): + global indir, states + filename = loader.construct_scalar(node) + with open("{}/{}".format(indir, filename), 'r') as fp: + data = fp.read() + template = jinja2.Template(data) + retval = yaml.load(template.render(states=states)) + return retval +yaml.add_constructor('!include', include_statement) + +def secret_statement(loader, node): + with open(secretsfile, 'r') as fp: + data = fp.read() + data = yaml.load(data) + if not node.value in data: + raise yaml.scanner.ScannerError('Could not find secret {}'. format(node.value)) + return data[node.value] +yaml.add_constructor('!secret', secret_statement) + +def resource_statement(loader, node): + global indir, wwwdir, resourcedir, timestamp + version = '' + path = os.path.join(indir, loader.construct_scalar(node)) + if '?' in path: + version = '&'+path.split('?')[1] + path = path.split('?')[0] + if not os.path.exists(path): + raise yaml.scanner.ScannerError('Could not find resource file {}'. format(path)) + basename = os.path.basename(path) + newpath = os.path.join(wwwdir, resourcedir, basename) + includepath = os.path.join('/local/', resourcedir, basename) + + os.makedirs(os.path.join(wwwdir, resourcedir), exist_ok=True) + shutil.copyfile(path, newpath) + return includepath + '?' + str(timestamp) + version + +yaml.add_constructor('!resource', resource_statement) + +def get_states(base_url, password=""): + global states + headers = {} + if password: + headers['x-ha-access'] = password + r = requests.get(base_url+"/api/states", headers=headers) + if r.status_code != 200: + print("Could not fetch states", file=sys.stderr); + return + states = {} + for s in r.json(): + domain = s['entity_id'].split('.')[0] + entity = s['entity_id'].split('.')[1] + if not domain in states: + states[domain] = {} + states[domain][entity] = s + + +def main(argv): + global infile, outfile, indir, states + + if len(argv) > 1: + if len(argv) > 3: + print(helpstring) + sys.exit(1) + base_url = argv[1] + password = argv[2] if len(argv) > 2 else "" + get_states(base_url, password) + + infile = "{}/{}".format(indir, infile) + + if not os.path.isdir(indir): + print("Directory {} not found.".format(indir), file=sys.stderr) + print("Run `{} help` for help.".format(argv[0]), file=sys.stderr) + sys.exit(2) + if not os.path.exists(infile): + print("File {} does not exist.".format(infile), file=sys.stderr) + print("Run `{} help` for help.".format(argv[0]), file=sys.stderr) + sys.exit(2) + + + try: + with open(infile, 'r') as fp: + data = fp.read() + template = jinja2.Template(data) + data = yaml.load(template.render(states=states)) + except Exception as e: + print("Something went wrong.", file=sys.stderr) + print(e, file=sys.stderr) + print("Run `{} help` for help.".format(argv[0]), file=sys.stderr) + sys.exit(3) + + try: + with open(outfile, 'w') as fp: + fp.write(""" +# This file is automatically generated by lovelace-gen.py +# https://github.com/thomasloven/homeassistant-lovelace-gen +# Any changes made to it will be overwritten the next time the script is run. + +""") + fp.write(yaml.dump(data, allow_unicode=True)) + except: + print("Could not write to output file.", file=sys.stderr) + print("Run {} -h for help.".format(argv[0]), file=sys.stderr) + sys.exit(4) + + +if __name__ == '__main__': + main(sys.argv) \ No newline at end of file diff --git a/lovelace/00_overview.yaml b/lovelace/00_overview.yaml new file mode 100644 index 0000000..f2d1140 --- /dev/null +++ b/lovelace/00_overview.yaml @@ -0,0 +1,33 @@ +title: Overview +path: overview +icon: mdi:information-outline +cards: + - type: vertical-stack + cards: + - !include presence-card.yaml + - type: custom:monster-card + show_empty: false + card: + type: entities + title: Lights On + filter: + include: + - domain: light + state: 'on' + - domain: switch + entity_id: "switch.*_light*" + state: 'on' + options: + icon: mdi:lightbulb + - type: entities + title: Climate Control + entities: + - climate.downstairs + - climate.upstairs + - fan.dyson_fan + - climate.dyson_fan + - type: entities + title: Garage + entities: + - cover.large_garage_door + - cover.small_garage_door diff --git a/lovelace/10_lights.yaml b/lovelace/10_lights.yaml new file mode 100644 index 0000000..6e8d490 --- /dev/null +++ b/lovelace/10_lights.yaml @@ -0,0 +1,33 @@ +title: Lights +path: lights +icon: mdi:lightbulb-on +cards: + - type: entities + title: Downstairs Lights + entities: + - entity: switch.foyer_light + icon: mdi:lightbulb + - entity: switch.art_room_light + icon: mdi:lightbulb + - light.living_room_fan + - entity: switch.kitchen_light + icon: mdi:lightbulb + - entity: switch.breakfast_nook_light + icon: mdi:lightbulb + - entity: switch.downstairs_hallway_light + icon: mdi:lightbulb + - entity: switch.garage_light + icon: mdi:lightbulb + - type: entities + title: Upstairs Lights + entities: + - entity: switch.front_staircase_light + icon: mdi:lightbulb + - entity: switch.master_bedroom_light + icon: mdi:lightbulb + - entity: switch.upstairs_hallway_light + icon: mdi:lightbulb + - entity: switch.bonus_room_light + icon: mdi:lightbulb + - entity: switch.back_staircase_light + icon: mdi:lightbulb diff --git a/lovelace/20_security.yaml b/lovelace/20_security.yaml new file mode 100644 index 0000000..4ffd282 --- /dev/null +++ b/lovelace/20_security.yaml @@ -0,0 +1,43 @@ +title: Security +path: security +icon: mdi:shield-home +cards: + - type: vertical-stack + cards: + - type: custom:vertical-stack-in-card + cards: + - type: entities + entities: + - entity: input_boolean.show_security + name: Show Security Status + icon: mdi:shield-home + - entity: input_boolean.voice_alerts + name: Voice Alerts + icon: mdi:amazon-alexa + - type: alarm-panel + entity: alarm_control_panel.alarm + # title: Alarm + # style: '--alarm-color-disarmed: var(--label-badge-blue);' + states: + - arm_home + - arm_away + - type: conditional + conditions: + - entity: input_boolean.show_security + state: "on" + card: + type: custom:monster-card + show_empty: false + card: + type: entities + filter: + include: + - entity_id: "binary_sensor.*_door" + options: + secondary_info: last-changed + - entity_id: "binary_sensor.*_field_detection" + options: + secondary_info: last-changed + - entity_id: "binary_sensor.*_line_crossing" + options: + secondary_info: last-changed \ No newline at end of file diff --git a/lovelace/30_media.yaml b/lovelace/30_media.yaml new file mode 100644 index 0000000..4522fa1 --- /dev/null +++ b/lovelace/30_media.yaml @@ -0,0 +1,50 @@ +title: Media +path: media +icon: mdi:youtube-tv +cards: + - type: vertical-stack + cards: + - type: entities + title: Xbox + entities: + - entity: media_player.living_room_xbox + type: "custom:mini-media-player" + icon: "mdi:xbox" + group: true + power_color: true + show_source: true + volume_stateless: true + - entity: media_player.master_bedroom_xbox + type: "custom:mini-media-player" + icon: "mdi:xbox" + group: true + power_color: true + show_source: true + volume_stateless: true + - type: entities + title: Alexa + entities: + - entity: media_player.kitchen_echo + type: "custom:mini-media-player" + icon: "mdi:amazon-alexa" + group: true + power_color: true + show_source: true + - entity: media_player.master_bedroom_dot + type: "custom:mini-media-player" + icon: "mdi:amazon-alexa" + group: true + power_color: true + show_source: true + - entity: media_player.bonus_room_dot + type: "custom:mini-media-player" + icon: "mdi:amazon-alexa" + group: true + power_color: true + show_source: true + - entity: media_player.REDACTED_dot + type: "custom:mini-media-player" + icon: "mdi:amazon-alexa" + group: true + power_color: true + show_source: true diff --git a/lovelace/40_cameras.yaml b/lovelace/40_cameras.yaml new file mode 100644 index 0000000..8b97b4b --- /dev/null +++ b/lovelace/40_cameras.yaml @@ -0,0 +1,45 @@ +title: Cameras +path: cameras +icon: mdi:cctv +cards: + - type: picture-glance + title: Front Door + camera_image: camera.front_door + entities: + - binary_sensor.front_door + - binary_sensor.front_door_motion + - binary_sensor.front_door_person_detection + - binary_sensor.front_door_sound_detection + - type: picture-glance + title: Foyer + camera_image: camera.foyer + entities: + - binary_sensor.front_door + - binary_sensor.foyer_field_detection + - type: picture-glance + title: Backyard + camera_image: camera.backyard + entities: + - binary_sensor.back_door + - binary_sensor.backyard_line_crossing + - binary_sensor.backyard_field_detection + - type: picture-glance + title: Driveway + camera_image: camera.driveway + entities: + - binary_sensor.driveway_line_crossing + - binary_sensor.driveway_field_detection + - type: picture-glance + title: Garage + camera_image: camera.garage + entities: + - binary_sensor.garage_door + - binary_sensor.garage_line_crossing + - binary_sensor.garage_field_detection + - type: entities + title: Object Detection + entities: + - image_processing.tensorflow_foyer + - image_processing.tensorflow_backyard + - image_processing.tensorflow_driveway + - image_processing.tensorflow_garage diff --git a/lovelace/50_network.yaml b/lovelace/50_network.yaml new file mode 100644 index 0000000..528955b --- /dev/null +++ b/lovelace/50_network.yaml @@ -0,0 +1,13 @@ +title: Network +path: network +icon: mdi:lan +cards: + - type: history-graph + entities: + - entity: sensor.speedtest_download + name: Download Speed + - entity: sensor.speedtest_upload + name: Upload Speed + - entity: sensor.speedtest_ping + name: Ping + hours_to_show: 168 diff --git a/lovelace/column-card/column-card.js b/lovelace/column-card/column-card.js new file mode 100644 index 0000000..f0a5a92 --- /dev/null +++ b/lovelace/column-card/column-card.js @@ -0,0 +1,138 @@ +class ColumnCard extends HTMLElement { + constructor() { + super(); + // Make use of shadowRoot to avoid conflicts when reusing + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = ` + + `; + + this.cols = document.createElement('div'); + this.cols.setAttribute("id", "columns"); + this.shadowRoot.appendChild(this.cols); + + this.columns = 0; + this._cards = []; + } + + setConfig(config) { + this.config = config; + if (!config || !config.cards || !Array.isArray(config.cards)) { + throw new Error('Card config incorrect'); + } + + // The cards are created here in order to allow finding their height later + // hui-view.js recreated the cards whenever the number of columns change, but that didn't work for me. + // There might be some bad side effects of this, but I haven't encountered any so far. + // Heads up, though... + this._cards = config.cards.map((item) => { + let element; + if (item.type.startsWith("custom:")){ + element = document.createElement(`${item.type.substr("custom:".length)}`); + } else { + element = document.createElement(`hui-${item.type}-card`); + } + element.setConfig(item); + if(this.hass) + element.hass = this.hass; + return element; + }); + + window.addEventListener('resize', (event) => {this._updateColumns()}); + window.setTimeout((event) => {this._updateColumns()}, 10); + } + + _updateColumns() { + let numcols = Math.max(1,Math.floor(this.cols.clientWidth/300)); + if(numcols != this.columns) { + this.columns = numcols + this._createColumns(); + } + } + + _createColumns() { + // This code is copied more or less verbatim from hui-view.js in home-assistant. + // https://github.com/home-assistant/home-assistant-polymer/blob/master/src/panels/lovelace/hui-view.js + // The credit for anything good you find here goes to the home-assistant team. + const root = this.cols; + // Remove old columns + while (root.lastChild) { + root.removeChild(root.lastChild); + } + + // Prepare a number of new columns + let columns = []; + const columnEntityCount = []; + for(let i = 0; i < this.columns; i++) { + columns.push([]); + columnEntityCount.push(0); + } + + function getColumnIndex(size) { + // Find the shortest column + let minIndex = 0; + for (let i = 0; i < columnEntityCount.length; i++) { + if (columnEntityCount[i] < 5) { + minIndex = i; + break; + } + if (columnEntityCount[i] < columnEntityCount[minIndex]) { + minIndex = i; + } + } + columnEntityCount[minIndex] += size; + return minIndex; + } + + // Go through each card and find which column to place it in (the shortest one) + this._cards.forEach((el) => { + this.appendChild(el); + const cardSize = typeof el.getCardSize === 'function' ? el.getCardSize() : 1; + columns[getColumnIndex(cardSize)].push(el); + }); + + this.columnEntityCount = columnEntityCount + + // Remove any empty columns + columns = columns.filter(val => val.length > 0); + + // Actually place the cards in the columns + columns.forEach((column) => { + const columnEl = document.createElement('div'); + columnEl.classList.add('column'); + column.forEach(el => columnEl.appendChild(el)); + root.appendChild(columnEl); + }); + } + + set hass(hass) { + // console.log(hass); + this._cards.forEach(item => { + item.hass = hass; + }); + } + + getCardSize() { + return Math.max(this.columnEntityCount); + } +} +customElements.define('column-card', ColumnCard); diff --git a/lovelace/main.yaml b/lovelace/main.yaml new file mode 100644 index 0000000..d5a11cb --- /dev/null +++ b/lovelace/main.yaml @@ -0,0 +1,19 @@ +resources: + - url: !resource column-card/column-card.js + type: js + - url: !resource mini-media-player/mini-media-player.js + type: module + - url: !resource monster-card/monster-card.js + type: js + - url: !resource vertical-stack-in-card/vertical-stack-in-card.js + type: js + +title: Home + +views: + - !include 00_overview.yaml + - !include 10_lights.yaml + - !include 20_security.yaml + - !include 30_media.yaml + - !include 40_cameras.yaml + - !include 50_network.yaml diff --git a/lovelace/mini-media-player/mini-media-player.js b/lovelace/mini-media-player/mini-media-player.js new file mode 100644 index 0000000..62a01b9 --- /dev/null +++ b/lovelace/mini-media-player/mini-media-player.js @@ -0,0 +1 @@ +function _templateObject38(){const a=_taggedTemplateLiteral(["\n "]);return _templateObject38=function(){return a},a}function _templateObject37(){const a=_taggedTemplateLiteral([""]);return _templateObject37=function(){return a},a}function _templateObject36(){const a=_taggedTemplateLiteral(["\n \n ","\n ","\n "]);return _templateObject36=function(){return a},a}function _templateObject35(){const a=_taggedTemplateLiteral(["\n
\n ","\n
"]);return _templateObject35=function(){return a},a}function _templateObject34(){const a=_taggedTemplateLiteral([""]);return _templateObject34=function(){return a},a}function _templateObject33(){const a=_taggedTemplateLiteral(["\n \n ","\n ","\n "]);return _templateObject33=function(){return a},a}function _templateObject32(){const a=_taggedTemplateLiteral(["\n \n \n \n ","\n \n \n \n \n ","\n \n "]);return _templateObject32=function(){return a},a}function _templateObject31(){const a=_taggedTemplateLiteral(["\n
\n \n \n
\n \n SEND\n \n
\n
"]);return _templateObject31=function(){return a},a}function _templateObject30(){const a=_taggedTemplateLiteral(["\n
\n ","\n ","\n ","\n
"]);return _templateObject30=function(){return a},a}function _templateObject29(){const a=_taggedTemplateLiteral(["\n
\n
\n ","\n
\n \n \n
"]);return _templateObject29=function(){return a},a}function _templateObject28(){const a=_taggedTemplateLiteral(["\n ","\n ","\n ",""]);return _templateObject28=function(){return a},a}function _templateObject27(){const a=_taggedTemplateLiteral(["\n ","\n
\n ","\n ","\n
"]);return _templateObject27=function(){return a},a}function _templateObject26(){const a=_taggedTemplateLiteral(["",""]);return _templateObject26=function(){return a},a}function _templateObject25(){const a=_taggedTemplateLiteral(["\n \n \n \n ","\n \n \n \n \n ","\n \n "]);return _templateObject25=function(){return a},a}function _templateObject24(){const a=_taggedTemplateLiteral(["(master)"]);return _templateObject24=function(){return a},a}function _templateObject23(){const a=_taggedTemplateLiteral(["\n \n ","\n ","\n "]);return _templateObject23=function(){return a},a}function _templateObject22(){const a=_taggedTemplateLiteral(["Leave"]);return _templateObject22=function(){return a},a}function _templateObject21(){const a=_taggedTemplateLiteral(["Ungroup"]);return _templateObject21=function(){return a},a}function _templateObject20(){const a=_taggedTemplateLiteral(["\n
\n Group speakers\n ","\n
\n \n ","\n \n \n Group all\n \n
\n
"]);return _templateObject20=function(){return a},a}function _templateObject19(){const a=_taggedTemplateLiteral(["\n \n "]);return _templateObject19=function(){return a},a}function _templateObject18(){const a=_taggedTemplateLiteral([""]);return _templateObject18=function(){return a},a}function _templateObject17(){const a=_taggedTemplateLiteral([""]);return _templateObject17=function(){return a},a}function _templateObject16(){const a=_taggedTemplateLiteral([""]);return _templateObject16=function(){return a},a}function _templateObject15(){const a=_taggedTemplateLiteral([""]);return _templateObject15=function(){return a},a}function _templateObject14(){const a=_taggedTemplateLiteral([""]);return _templateObject14=function(){return a},a}function _templateObject13(){const a=_taggedTemplateLiteral(["\n
\n ","\n
\n ","\n ","\n ","\n ","\n
\n
"]);return _templateObject13=function(){return a},a}function _templateObject12(){const a=_taggedTemplateLiteral(["\n \n ","\n "]);return _templateObject12=function(){return a},a}function _templateObject11(){const a=_taggedTemplateLiteral(["\n \n "]);return _templateObject11=function(){return a},a}function _templateObject10(){const a=_taggedTemplateLiteral(["",""]);return _templateObject10=function(){return a},a}function _templateObject9(){const a=_taggedTemplateLiteral(["",""]);return _templateObject9=function(){return a},a}function _templateObject8(){const a=_taggedTemplateLiteral(["\n
\n
\n ","\n
\n
"]);return _templateObject8=function(){return a},a}function _templateObject7(){const a=_taggedTemplateLiteral(["\n
\n ","\n ","\n
"]);return _templateObject7=function(){return a},a}function _templateObject6(){const a=_taggedTemplateLiteral(["\n \n "]);return _templateObject6=function(){return a},a}function _templateObject5(){const a=_taggedTemplateLiteral(["\n \n "]);return _templateObject5=function(){return a},a}function _templateObject4(){const a=_taggedTemplateLiteral(["\n
\n \n
"]);return _templateObject4=function(){return a},a}function _templateObject3(){const a=_taggedTemplateLiteral(["\n
\n
"]);return _templateObject3=function(){return a},a}function _templateObject2(){const a=_taggedTemplateLiteral(["
"]);return _templateObject2=function(){return a},a}function _templateObject(){const a=_taggedTemplateLiteral(["\n ","\n \n
\n ","\n
\n
","
\n
\n
\n ","\n
\n
\n ","\n
\n ","\n
\n
\n ","\n
\n
\n
\n
\n ","\n
\n ","\n ","\n ","\n ","\n
\n
\n ","\n
"]);return _templateObject=function(){return a},a}function _taggedTemplateLiteral(a,b){return b||(b=a.slice(0)),Object.freeze(Object.defineProperties(a,{raw:{value:Object.freeze(b)}}))}function _objectSpread(a){for(var b=1;ba.parentNode.removeChild(a))}function F(a){return parseFloat(a)||0}function X(a){for(var b=[],c=1;c"function"==typeof a&&da.has(a),fa=void 0!==window.customElements&&void 0!==window.customElements.polyfillWrapFlushCallback,ga=function(a,b){let c=2"),ia=new RegExp("".concat(ha,"|").concat(r)),a=(()=>{const a=document.createElement("div");return a.setAttribute("style","{{bad value}}"),"{{bad value}}"!==a.getAttribute("style")})();class c{constructor(b,c){this.parts=[],this.element=c;let f=-1,g=0;const j=[],k=c=>{const e=c.content,i=document.createTreeWalker(e,133,null,!1);for(let l,m;i.nextNode();){f++,l=m;const n=m=i.currentNode;if(1===n.nodeType){if(n.hasAttributes()){const c=n.attributes;let e=0;for(let a=0;aa.indexOf(ha))continue;const b=n.parentNode,d=a.split(ia),c=d.length-1;g+=c;for(let a=0;a-1!==a.index,h=()=>document.createComment(""),d=/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F \x09\x0a\x0c\x0d"'>=\/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;class o{constructor(a,b,c){this._parts=[],this.template=a,this.processor=b,this.options=c}update(a){let b=0;for(const c of this._parts)void 0!==c&&c.setValue(a[b]),b++;for(const b of this._parts)void 0!==b&&b.commit()}_clone(){const a=fa?this.template.element.content.cloneNode(!0):document.importNode(this.template.element.content,!0),b=this.template.parts;let c=0,d=0;const e=a=>{const f=document.createTreeWalker(a,133,null,!1);for(let g=f.nextNode();c");!(f=(-1"style"===c?"".concat(b,"style$").concat(d):a)),c+=f?r:ha}return c+=this.strings[b]}getTemplateElement(){const a=document.createElement("template");return a.innerHTML=this.getHTML(),a}}const i=a=>null===a||"object"!=typeof a&&"function"!=typeof a;class f{constructor(a,b,c){this.dirty=!0,this.element=a,this.name=b,this.strings=c,this.parts=[];for(let d=0;d{this.value===a&&(this.setValue(b),this.commit())})}clear(){let a=0this.handleEvent(a)}setValue(a){this._pendingValue=a}commit(){for(;ea(this._pendingValue);){const a=this._pendingValue;this._pendingValue=e,a(this)}if(this._pendingValue!==e){const a=this._pendingValue,b=this.value,c=null==a||null!=b&&(a.capture!==b.capture||a.once!==b.once||a.passive!==b.passive);c&&this.element.removeEventListener(this.eventName,this._boundHandleEvent,this._options),this._options=k(a),null!=a&&(null==b||c)&&this.element.addEventListener(this.eventName,this._boundHandleEvent,this._options),this.value=a,this._pendingValue=e}}handleEvent(a){"function"==typeof this.value?this.value.call(this.eventContext||this.element,a):this.value.handleEvent(a)}}const k=a=>a&&(u?{capture:a.capture,passive:a.passive,once:a.once}:a.capture),w=new class{handleAttributeExpressions(a,c,d,e){const h=c[0];return"."===h?new b(a,c.slice(1),d).parts:"@"===h?[new v(a,c.slice(1),e.eventContext)]:"?"===h?[new g(a,c.slice(1),d)]:new f(a,c,d).parts}handleTextExpression(a){return new p(a)}},y=new Map,E=new WeakMap,O=function(a){for(var b=arguments.length,c=Array(1{let b=a.nodeType===Node.DOCUMENT_FRAGMENT_NODE?0:1;for(const c=document.createTreeWalker(a,N,null,!1);c.nextNode();)b++;return b},A=function(a){let b=1"".concat(a,"--").concat(b);let M=!0;void 0===window.ShadyCSS?M=!1:void 0===window.ShadyCSS.prepareTemplateDom&&(console.warn("Incompatible ShadyCSS version detected.Please update to at least @webcomponents/webcomponentsjs@2.0.2 and@webcomponents/shadycss@1.3.1."),M=!1);const P=a=>b=>{const d=C(b.type,a);let e=y.get(d);void 0===e&&(e=new Map,y.set(d,e));let f=e.get(b.strings);if(void 0===f){const d=b.getTemplateElement();M&&window.ShadyCSS.prepareTemplateDom(d,a),f=new c(b,d),e.set(b.strings,f)}return f},B=["html","svg"],z=new Set,L=(a,b,c)=>{z.add(c);const d=a.querySelectorAll("style");if(0!==d.length){const f=document.createElement("style");for(let a=0;a{B.forEach(b=>{const c=y.get(C(b,a));void 0!==c&&c.forEach(a=>{const b=a.element.content,c=new Set;Array.from(b.querySelectorAll("style")).forEach(a=>{c.add(a)}),S(a,c)})})})(c),function(a,b){let d=2null!==a,j=a=>a?"":null,T=(a,b)=>b!==a&&(b==b||a==a),q={attribute:!0,type:String,reflect:!1,hasChanged:T},U=new Promise(a=>a(!0)),G=1;class D extends HTMLElement{constructor(){super(),this._updateState=0,this._instanceProperties=void 0,this._updatePromise=U,this._changedProperties=new Map,this._reflectingProperties=void 0,this.initialize()}static get observedAttributes(){this._finalize();const a=[];for(const c of this._classProperties){var b=_slicedToArray(c,2);const d=b[0],e=b[1],f=this._attributeNameForProperty(d,e);void 0!==f&&(this._attributeToPropertyMap.set(f,d),a.push(f))}return a}static createProperty(a){let b=1this._classProperties.set(b,a))}if(this._classProperties.set(a,b),this.prototype.hasOwnProperty(a))return;const c="symbol"==typeof a?Symbol():"__".concat(a);Object.defineProperty(this.prototype,a,{get(){return this[c]},set(d){const e=this[a];this[c]=d,this._requestPropertyUpdate(a,e,b)},configurable:!0,enumerable:!0})}static _finalize(){if(!(this.hasOwnProperty("_finalized")&&this._finalized)){const a=Object.getPrototypeOf(this);"function"==typeof a._finalize&&a._finalize(),this._finalized=!0,this._attributeToPropertyMap=new Map;const b=this.properties,c=[...Object.getOwnPropertyNames(b),...("function"==typeof Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(b):[])];for(const a of c)this.createProperty(a,b[a])}}static _attributeNameForProperty(a,b){const c=void 0!==b&&b.attribute;return!1===c?void 0:"string"==typeof c?c:"string"==typeof a?a.toLowerCase():void 0}static _valueHasChanged(a,b){let c=2a=b),await b,this._validate(),a(!this._hasRequestedUpdate)}return this.updateComplete}get _hasRequestedUpdate(){return this._updateState&4}_validate(){if(this._instanceProperties&&this._applyInstanceProperties(),this.shouldUpdate(this._changedProperties)){const a=this._changedProperties;this.update(a),this._markUpdated(),this._updateState&G||(this._updateState|=1,this.firstUpdated(a)),this.updated(a)}else this._markUpdated()}_markUpdated(){this._changedProperties=new Map,this._updateState&=-5}get updateComplete(){return this._updatePromise}shouldUpdate(){return!0}update(){if(void 0!==this._reflectingProperties&&0{const d=c.scopeName,e=E.has(b);if(((a,b,c)=>{let d=E.get(b);void 0===d&&(ga(b,b.firstChild),E.set(b,d=new p(Object.assign({templateFactory:H},c))),d.appendInto(b)),d.setValue(a),d.commit()})(a,b,Object.assign({templateFactory:P(d)},c)),b instanceof ShadowRoot&&M&&a instanceof n){if(!z.has(d)){const a=E.get(b).value;L(b,a.template,d)}e||window.ShadyCSS.styleElement(b.host)}};var W=function(){function a(a,b){var c=-1;return a.some(function(a,d){return a[0]===b&&(c=d,!0)}),c}return"undefined"==typeof Map?function(){function b(){this.__entries__=[]}return Object.defineProperty(b.prototype,"size",{get:function a(){return this.__entries__.length},enumerable:!0,configurable:!0}),b.prototype.get=function(b){var c=a(this.__entries__,b),d=this.__entries__[c];return d&&d[1]},b.prototype.set=function(b,c){var d=a(this.__entries__,b);~d?this.__entries__[d][1]=c:this.__entries__.push([b,c])},b.prototype.delete=function(b){var c=this.__entries__,d=a(c,b);~d&&c.splice(d,1)},b.prototype.has=function(b){return!!~a(this.__entries__,b)},b.prototype.clear=function(){this.__entries__.splice(0)},b.prototype.forEach=function(a,b){void 0===b&&(b=null);for(var c,d=0,f=this.__entries__;da.has(b))&&this.entity)return this.active=this._isActive(),this.config.show_progress&&this._checkProgress(),!0}firstUpdated(){new ra(a=>{for(const b of a)window.requestAnimationFrame(()=>{this.config.scroll_info&&this._computeOverflow(),this._resizeTimer||(this._computeRect(b),this._resizeTimer=setTimeout(()=>{this._resizeTimer=null,this._computeRect(this._resizeEntry)},250)),this._resizeEntry=b})}).observe(this.shadowRoot.querySelector(".player")),setTimeout(()=>this.initial=!1,250)}updated(){this.config.scroll_info&&this._computeOverflow()}render(){let a=0this._handleMore(a),this._renderArtwork(e),c.title,!this.active,this._renderIcon(e),c.short_info||!this.active,this._computeName(),this._renderMediaInfo(),this._renderPowerStrip(),c.volume_stateless,!c.collapse&&this.active?this._renderControlRow():"",c.media_buttons?this._renderButtons():"",c.media_list?this._renderList():"",c.show_tts?this._renderTts():"",this.edit?this._renderGroupList():"",c.show_progress&&this._showProgress?this._renderProgress():"")}_computeContent(){return this.entity.attributes.media_content_type||"none"}_computeName(){return this.config.name||this.entity.attributes.friendly_name}_computeArtwork(){const a=this.entity.attributes.entity_picture,b=a&&""!==a&&"none"!==this.config.artwork&&this.active&&!this.idle;return b&&a!==this.picture&&(this._fetchThumbnail(),this.picture=a),b&&this.thumbnail}_computeIcon(){return this.config.icon?this.config.icon:this.entity.attributes.icon||ta.DEFAULT}_computeOverflow(){const a=this.shadowRoot.querySelector(".marquee");if(a){const b=a.clientWidth>a.parentNode.clientWidth;this.overflow=b&&this.active&&7.5+a.clientWidth/50}}_computeRect(a){const b=a.contentRect,c=b.left,d=b.width;this.break=350>d+2*c}_renderArtwork(a){if(this.thumbnail||this.config.background){const b=!this.config.background||a&&"default"!==this.config.artwork?this.thumbnail:"url(".concat(this.config.background,")");return O(_templateObject2(),b)}}_renderIcon(a){if(!this.config.hide_icon)return this.active&&a&&"default"===this.config.artwork?O(_templateObject3(),this.config.artwork_border,this.thumbnail,this.entity.state):O(_templateObject4(),this._computeIcon())}_renderPowerButton(){return O(_templateObject5(),ta.POWER,a=>this._handlePower(a),this.config.power_color&&(this.active||this.idle))}_renderButton(a,b){let c=2this._callService(a,b,c))}_renderMediaInfo(){if(!this.config.hide_media_info){const a=sa.map(a=>_objectSpread({text:this._getAttribute(a.attr),prefix:""},a)).filter(a=>a.text);return O(_templateObject7(),this._overflow,this._overflow,this.config.scroll_info?O(_templateObject8(),a.map(a=>O(_templateObject9(),"attr__"+a.attr,a.prefix+a.text))):"",a.map(a=>O(_templateObject10(),"attr__"+a.attr,a.prefix+a.text)))}}_renderProgress(){return O(_templateObject11(),this.position,this.entity.attributes.media_duration)}_renderLabel(a){let b=1=this.entity.attributes.sonos_group.length;return O(_templateObject19(),ta.GROUP,a,this.edit,a=>this._handleGroupButton(a))}_renderGroupList(){const a=this.config.sonos_grouping,b=this.entity.attributes.sonos_group||[],c=b[0]||this.config.entity,d=c===this.config.entity;return O(_templateObject20(),a.map(a=>this._renderGroupListItem(a,b,c)),2>b.length,a=>this._handleGroupItemChange(a,d?b:this.config.entity,!1),d?O(_templateObject21()):O(_templateObject22()),!d,b=>this._handleGroupItemChange(b,a.map(a=>a.entity_id),!0))}_renderGroupListItem(a,b,c){const d=a.entity_id===this.config.entity||b.includes(a.entity_id),e=a.entity_id===this.config.entity||c!==this.config.entity;return O(_templateObject23(),d,e,b=>this._handleGroupItemChange(b,a.entity_id,!d),a.name,a.entity_id===c?O(_templateObject24()):"")}_renderSource(){let a=0a.stopPropagation(),this.config.show_source,this.source||d,ta.DROPDOWN,a,a=>this._handleSource(a),c.map(a=>O(_templateObject26(),a,a)))}}_renderControlRow(){return O(_templateObject27(),this.config.hide_volume?"":this._renderVolControls(),this.config.show_shuffle?this._renderShuffleButton():"",this.config.hide_controls?"":this._renderMediaControls())}_renderMediaControls(){return O(_templateObject28(),this._renderButton(ta.PREV,"media_previous_track"),this._renderButton(ta.PLAY[this._isPlaying()],"media_play_pause"),this._renderButton(ta.NEXT,"media_next_track"))}_renderVolControls(){const a=this.entity.attributes.is_volume_muted||!1;return this.config.volume_stateless?this._renderVolButtons(a):this._renderVolSlider(a)}_renderMuteButton(a){if(!this.config.hide_mute){switch(this.config.replace_mute){case"play":return this._renderButton(ta.PLAY[this._isPlaying()],"media_play_pause");case"stop":return this._renderButton(ta.STOP,"media_stop");case"next":return this._renderButton(ta.NEXT,"media_next_track");}return this._renderButton(ta.MUTE[a],"volume_mute",{is_volume_muted:!a})}}_renderVolSlider(){let a=!!(0this._handleVolumeChange(a),a=>a.stopPropagation(),this.config.max_volume,b)}_renderVolButtons(){let a=!!(0a.stopPropagation(),a=>this._handleTts(a))}_renderList(){const a=this.config.media_list;return O(_templateObject32(),!0,a=>a.stopPropagation(),"Select media...",ta.MENU,a=>this._handleSelect(a,"media_list",a.target.getAttribute("value")),a.map((a,b)=>O(_templateObject33(),b,a.name,a.icon?O(_templateObject34(),a.icon):"")))}_renderButtons(){const a=this.config.media_buttons;return O(_templateObject35(),a.map((a,b)=>O(_templateObject36(),a=>this._handleSelect(a,"media_buttons",b),a.name,a.icon?O(_templateObject37(),a.icon):"")))}_callService(a,b,c){let d=3this.position=this._currentProgress,1e3)):this._positionTracker&&(clearInterval(this._positionTracker),this._positionTracker=null),this.position=this._currentProgress}get _showProgress(){return(this._isPlaying()||this._isPaused())&&this.active&&"media_duration"in this.entity.attributes&&"media_position"in this.entity.attributes&&"media_position_updated_at"in this.entity.attributes}get _currentProgress(){const a=this.entity.attributes.media_position_updated_at;return this.entity.attributes.media_position+(Date.now()-new Date(a).getTime())/1e3}_isPaused(){return"paused"===this.entity.state}_isPlaying(){return"playing"===this.entity.state}_isIdle(){return"idle"===this.entity.state}_isActive(){!!(0b.consider_idle_after||(this._inactiveTracker||(this._inactiveTracker=setTimeout(()=>{this.position=0,this._inactiveTracker=null},1e3*(b.consider_idle_after-d))),!1)}_getAttribute(a){return this.entity.attributes[a]||""}_getLabel(a){let b=1': (x, y) => x > y, + '<': (x, y) => x < y, + '<=': (x, y) => x <= y, + '>=': (x, y) => x >= y, + '=': (x, y) => x === y, + }; + let operator = '='; + let y = b; + let x = a; + if (!isNaN(a) && typeof (b) == 'string' + && b.split(" ").length > 1) { + [operator, y] = b.split(' ', 2); + x = parseFloat(a); + } + return _compare[operator](x, y); + } + const entities = new Map(); + filters.forEach((filter) => { + const filters = []; + if (filter.domain) { + filters.push(stateObj => stateObj.entity_id.split('.', 1)[0] === filter.domain); + } + if (filter.attributes) { + Object.keys(filter.attributes).forEach(key => { + filters.push(stateObj => _complexCompare(stateObj.attributes[key], filter.attributes[key])); + }); + } + if (filter.entity_id) { + filters.push(stateObj => _filterEntityId(stateObj, filter.entity_id)); + } + if (filter.name) { + filters.push(stateObj => _filterName(stateObj, filter.name)); + } + if (filter.state) { + filters.push(stateObj => _complexCompare(stateObj.state, filter.state)); + } + + const options = filter.options ? filter.options : {} + + Object.keys(hass.states).sort().forEach(key => { + if (filters.every(filterFunc => filterFunc(hass.states[key]))) { + entities.set(hass.states[key].entity_id, Object.assign({ "entity": hass.states[key].entity_id }, options)); + } + }); + }); + return Array.from(entities.values()); + } + + setConfig(config) { + if (!config.filter.include || !Array.isArray(config.filter.include)) { + throw new Error('Please define filters'); + } + + if (this.lastChild) this.removeChild(this.lastChild); + + const cardConfig = Object.assign({}, config); + if (!cardConfig.card) cardConfig.card = {}; + if (config.card.entities) delete config.card.entities; + if (!cardConfig.card.type) cardConfig.card.type = 'entities'; + + const element = document.createElement(`hui-${cardConfig.card.type}-card`); + this.appendChild(element); + + this._config = cardConfig; + } + + set hass(hass) { + const config = this._config; + let entities = this._getEntities(hass, config.filter.include); + if (config.filter.exclude) { + const excludeEntities = this._getEntities(hass, config.filter.exclude).map(entity => entity.entity); + entities = entities.filter(entity => !excludeEntities.includes(entity.entity)); + } + + + + if (entities.length === 0 && config.show_empty === false) { + this.style.display = 'none'; + } else { + if (config.when && (hass.states[config.when.entity].state == config.when.state) || !config.when) { + this.style.display = 'block'; + } else { + this.style.display = 'none'; + } + } + + if (!config.card.entities || config.card.entities.length !== entities.length || + !config.card.entities.every((value, index) => value.entity === entities[index].entity)) { + config.card.entities = entities; + this.lastChild.setConfig(config.card); + } + this.lastChild.hass = hass; + } + + getCardSize() { + return 'getCardSize' in this.lastChild ? this.lastChild.getCardSize() : 1; + } +} + +customElements.define('monster-card', MonsterCard); diff --git a/lovelace/presence-card.yaml b/lovelace/presence-card.yaml new file mode 100644 index 0000000..798298b --- /dev/null +++ b/lovelace/presence-card.yaml @@ -0,0 +1,15 @@ +type: horizontal-stack +cards: + - type: picture-entity + entity: sensor.USER1_presence + image: /local/USER1_cropped_away.jpg + state_image: + "Home": /local/USER1_cropped.jpg + show_name: false + + - type: picture-entity + entity: sensor.USER2_presence + image: /local/USER2_cropped_away.jpg + state_image: + "Home": /local/USER2_cropped.jpg + show_name: false diff --git a/lovelace/vertical-stack-in-card/vertical-stack-in-card.js b/lovelace/vertical-stack-in-card/vertical-stack-in-card.js new file mode 100644 index 0000000..fbd9fde --- /dev/null +++ b/lovelace/vertical-stack-in-card/vertical-stack-in-card.js @@ -0,0 +1,100 @@ +class VerticalStackInCard extends HTMLElement { + constructor() { + super(); + // Make use of shadowRoot to avoid conflicts when reusing + this.attachShadow({ mode: 'open' }); + } + + setConfig(config) { + if (!config || !config.cards || !Array.isArray(config.cards)) { + throw new Error('Card config incorrect'); + } + + this.style.boxShadow = "0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.15)"; + this.style.borderRadius = "2px"; + this.style.background = "var(--paper-card-background-color)"; + + const root = this.shadowRoot; + while (root.lastChild) { + root.removeChild(root.lastChild); + } + + this._refCards = []; + if (config.title) { + const title = document.createElement("div"); + title.className = "header"; + title.style = "font-family: var(--paper-font-headline_-_font-family); -webkit-font-smoothing: var(--paper-font-headline_-_-webkit-font-smoothing); font-size: var(--paper-font-headline_-_font-size); font-weight: var(--paper-font-headline_-_font-weight); letter-spacing: var(--paper-font-headline_-_letter-spacing); line-height: var(--paper-font-headline_-_line-height);text-rendering: var(--paper-font-common-expensive-kerning_-_text-rendering);opacity: var(--dark-primary-opacity);padding: 24px 16px 0px 16px"; + title.innerHTML = '
' + config.title + '
'; + root.appendChild(title); + } + + let element; + config.cards.forEach(item => { + if (item.type.startsWith("custom:")){ + element = document.createElement(`${item.type.substr("custom:".length)}`); + } else { + element = document.createElement(`hui-${item.type}-card`); + } + element.setConfig(item); + root.appendChild(element); + this._refCards.push(element); + }); + } + + set hass(hass) { + if (this._refCards) { + this._refCards.forEach((card) => { + card.hass = hass; + }); + } + } + + connectedCallback() { + this._refCards.forEach((element) => { + let fn = () => { + this._card(element); + }; + + if(element.updateComplete) { + element.updateComplete.then(fn); + } else { + fn(); + } + }); + } + + _card(element) { + if (element.shadowRoot) { + if (!element.shadowRoot.querySelector('ha-card')) { + let searchEles = element.shadowRoot.getElementById("root"); + if (!searchEles) { + searchEles = element.shadowRoot.getElementById("card"); + } + if (!searchEles) return; + searchEles = searchEles.childNodes; + for (let i = 0; i < searchEles.length; i++) { + searchEles[i].style.margin = "0px"; + this._card(searchEles[i]); + } + } else { + element.shadowRoot.querySelector('ha-card').style.boxShadow = 'none'; + } + } else { + let searchEles = element.childNodes; + for (let i = 0; i < searchEles.length; i++) { + searchEles[i].style.margin = "0px"; + this._card(searchEles[i]); + } + } + } + + getCardSize() { + let totalSize = 0; + this._refCards.forEach((element) => { + totalSize += typeof element.getCardSize === 'function' ? element.getCardSize() : 1; + }); + return totalSize; + } +} + +customElements.define('vertical-stack-in-card', VerticalStackInCard); diff --git a/packages/beds.yaml b/packages/beds.yaml new file mode 100644 index 0000000..7cc73eb --- /dev/null +++ b/packages/beds.yaml @@ -0,0 +1,4 @@ +# Master Bed +sleepiq: + username: !secret email + password: !secret sleepiq_password diff --git a/packages/climate.yaml b/packages/climate.yaml new file mode 100644 index 0000000..7a11e3e --- /dev/null +++ b/packages/climate.yaml @@ -0,0 +1,13 @@ +# Nest +nest: + client_id: !secret nest_client_id + client_secret: !secret nest_client_secret + +# Dyson +dyson: + username: !secret email + password: !secret dyson_password + language: US + devices: + - device_id: PE7-US-HGA0460A + device_ip: !secret dyson_ip diff --git a/packages/garage.yaml b/packages/garage.yaml new file mode 100644 index 0000000..14cb68e --- /dev/null +++ b/packages/garage.yaml @@ -0,0 +1,6 @@ +# MyQ +cover: + - platform: myq + username: !secret email + password: !secret myq_password + type: chamberlain diff --git a/packages/general.yaml b/packages/general.yaml new file mode 100644 index 0000000..716c1c2 --- /dev/null +++ b/packages/general.yaml @@ -0,0 +1,79 @@ +hassio: + +# Enables configuration UI +config: + +http: + use_x_forwarded_for: true + trusted_proxies: + - 172.16.0.0/12 + +cloudflare: + email: !secret email + api_key: !secret cloudflare_api_key + zone: !secret domain + records: + - !secret cloudflare_record + +# Cloud +#cloud: + +# Enable API +api: + +# Enable Alexa Smart Home +alexa: + smart_home: + filter: + exclude_entities: + - climate.upstairs + - climate.downstairs + - media_player.everywhere + - media_player.kitchen_echo + - media_player.master_bedroom_dot + - media_player.bonus_room_dot + - media_player.REDACTED_dot + +# Checks for available updates +# Note: This component will send some information about your system to +# the developers to assist with development of Home Assistant. +# For more information, please see: +# https://home-assistant.io/blog/2016/10/25/explaining-the-updater/ +updater: + # Optional, allows Home Assistant developers to focus on popular components. + # include_used_components: true + +# Discover some devices automatically +discovery: + ignore: + - google_cast + +# Allows you to issue voice commands from the frontend in enabled browsers +conversation: + +logger: + default: warning + logs: + homeassistant.components.stream: info + homeassistant.components.media_player.xboxone: info + libav: warning + +recorder: + db_url: !secret mysql_hass_url + purge_keep_days: 1095 + purge_interval: 0 + +# Enables support for tracking state changes over time +history: + +# View all events in a logbook +logbook: + +# Enables a map showing the location of tracked devices +map: + +panel_custom: + - name: zwavegraph2 + sidebar_title: ZWave Graph2 + sidebar_icon: mdi:access-point-network + url_path: zwave \ No newline at end of file diff --git a/packages/homekit.yaml b/packages/homekit.yaml new file mode 100644 index 0000000..cd6c6fe --- /dev/null +++ b/packages/homekit.yaml @@ -0,0 +1,17 @@ +# HomeKit +homekit: + auto_start: false + filter: + include_domains: + - light + - climate + - media_player + - switch + - cover + entity_config: + media_player.living_room_xbox: + feature_list: + - feature: on_off + media_player.master_bedroom_xbox: + feature_list: + - feature: on_off diff --git a/packages/internet_monitor.yaml b/packages/internet_monitor.yaml new file mode 100644 index 0000000..4523017 --- /dev/null +++ b/packages/internet_monitor.yaml @@ -0,0 +1 @@ +speedtestdotnet: diff --git a/packages/ios.yaml b/packages/ios.yaml new file mode 100644 index 0000000..0ae9617 --- /dev/null +++ b/packages/ios.yaml @@ -0,0 +1,10 @@ +# ios +ios: + +# Notify +notify: + - name: ios_jk_phones + platform: group + services: + - service: ios_USER1s_iphone + - service: ios_USER2s_iphone diff --git a/packages/lovelace.yaml b/packages/lovelace.yaml new file mode 100644 index 0000000..8075ab2 --- /dev/null +++ b/packages/lovelace.yaml @@ -0,0 +1,4 @@ +input_boolean: + show_security: + name: Show Security + initial: off diff --git a/packages/media_players.yaml b/packages/media_players.yaml new file mode 100644 index 0000000..6ee7c7a --- /dev/null +++ b/packages/media_players.yaml @@ -0,0 +1,23 @@ +# Media Players +media_player: + - platform: xboxone + device: !secret xbox1_id + ip_address: !secret xbox1_ip + name: Master Bedroom Xbox + authentication: false + - platform: xboxone + device: !secret xbox2_id + ip_address: !secret xbox2_ip + name: Living Room Xbox + authentication: false + - platform: androidtv + host: !secret living_room_tv_ip + name: "Sony Bravia" + adb_server_ip: 127.0.0.1 + adb_server_port: 5037 + +alexa_media: + accounts: + - email: !secret amazon_email + password: !secret amazon_password + url: "amazon.com" diff --git a/packages/presence.yaml b/packages/presence.yaml new file mode 100644 index 0000000..a622890 --- /dev/null +++ b/packages/presence.yaml @@ -0,0 +1,48 @@ +# UniFi Controller +device_tracker: + - platform: unifi + host: !secret full_domain + username: hass + password: !secret hass_user_pass + ssid_filter: + - !secret ssid + - !secret ssid_guest + +group: + USER1_presence: + entities: + - device_tracker.USER1s_iphone + - device_tracker.USER1s_iphone_wifi + USER2_presence: + entities: + - device_tracker.USER2s_iphone + - device_tracker.USER2s_iphone_wifi + family_presence: + entities: + - device_tracker.USER1s_iphone + - device_tracker.USER1s_iphone_wifi + - device_tracker.USER2s_iphone + - device_tracker.USER2s_iphone_wifi + +sensor: + - platform: template + sensors: + USER1_presence: + value_template: > + {% if is_state('group.USER1_presence', 'home') %} + Home + {% elif is_state('device_tracker.USER1s_iphone', 'not_home') %} + Away + {% else %} + {{ states('device_tracker.USER1s_iphone') }} + {% endif %} + USER2_presence: + value_template: > + {% if is_state('group.USER2_presence', 'home') %} + Home + {% elif is_state('device_tracker.USER2s_iphone', 'not_home') %} + Away + {% else %} + {{ states('device_tracker.USER2s_iphone') }} + {% endif %} + \ No newline at end of file diff --git a/packages/security.yaml b/packages/security.yaml new file mode 100644 index 0000000..b37d8ff --- /dev/null +++ b/packages/security.yaml @@ -0,0 +1,89 @@ +alarm_control_panel: + - platform: manual + name: "Alarm" + code: !secret alarm_code + pending_time: 60 + trigger_time: 120 + delay_time: 30 + disarm_after_trigger: false + disarmed: + trigger_time: 0 + armed_home: + pending_time: 0 + delay_time: 0 + +konnected: + access_token: !secret konnected_access_token + devices: + - id: !secret konnected_mac + binary_sensors: + - pin: 1 + type: door + name: Garage Door + - pin: 2 + type: door + name: Back Door + - pin: 5 + type: door + name: Front Door + +stream: + +camera: + - platform: generic + name: Garage + still_image_url: !secret garage_cam_img_url + stream_source: !secret garage_cam_url + - platform: generic + name: Foyer + still_image_url: !secret foyer_cam_img_url + stream_source: !secret foyer_cam_url + - platform: generic + name: Backyard + still_image_url: !secret backyard_cam_img_url + stream_source: !secret backyard_cam_url + - platform: generic + name: Driveway + still_image_url: !secret driveway_cam_img_url + stream_source: !secret driveway_cam_url + +binary_sensor: + - platform: hikvision + host: !secret garage_cam_ip + username: hass + password: !secret hass_user_pass + - platform: hikvision + host: !secret foyer_cam_ip + username: hass + password: !secret hass_user_pass + - platform: hikvision + host: !secret backyard_cam_ip + username: hass + password: !secret hass_user_pass + - platform: hikvision + host: !secret driveway_cam_ip + username: hass + password: !secret hass_user_pass + +image_processing: + - platform: tensorflow + confidence: 90 + scan_interval: 10000 + source: + - entity_id: camera.garage + - entity_id: camera.foyer + - entity_id: camera.backyard + - entity_id: camera.driveway + file_out: + - "/config/www/motion/{{ camera_entity.split('.')[1] }}_latest.jpg" + - "/share/camera/{{ camera_entity.split('.')[1] }}_{{ now().strftime('%Y%m%d_%H%M%S') }}.jpg" + model: + graph: /config/tensorflow/graph/faster_rcnn_inception_v2_coco_2018_01_28/frozen_inference_graph.pb + categories: + - person + - car + +input_boolean: + voice_alerts: + name: Voice Alerts + initial: off diff --git a/packages/sensors.yaml b/packages/sensors.yaml new file mode 100644 index 0000000..13621c2 --- /dev/null +++ b/packages/sensors.yaml @@ -0,0 +1,14 @@ +# Sensors +sun: +sensor: + - platform: season + - platform: darksky + api_key: !secret darksky_api_key + monitored_conditions: + - summary + - icon + - cloud_cover + - humidity + - precip_probability + - temperature + - nearest_storm_distance diff --git a/packages/utilities.yaml b/packages/utilities.yaml new file mode 100644 index 0000000..446fdb8 --- /dev/null +++ b/packages/utilities.yaml @@ -0,0 +1,5 @@ +# Duke Energy +# sensor: +# - platform: duke_energy +# username: !secret email +# password: !secret duke_password diff --git a/packages/zones.yaml b/packages/zones.yaml new file mode 100644 index 0000000..3150d72 --- /dev/null +++ b/packages/zones.yaml @@ -0,0 +1,12 @@ +zone: + - name: Red Ventures + latitude: !secret latitude_rv + longitude: !secret longitude_rv + radius: 250 + icon: mdi:office-building + + - name: Library + latitude: !secret latitude_library + longitude: !secret longitude_library + radius: 250 + icon: mdi:library diff --git a/packages/zwave.yaml b/packages/zwave.yaml new file mode 100644 index 0000000..25daa2c --- /dev/null +++ b/packages/zwave.yaml @@ -0,0 +1,7 @@ +zwave: + usb_path: /dev/ttyACM0 + network_key: !secret zwave_network_key + device_config: + light.living_room_fan: + refresh_value: true + delay: 1 diff --git a/panels/zwavegraph2.html b/panels/zwavegraph2.html new file mode 100644 index 0000000..d244506 --- /dev/null +++ b/panels/zwavegraph2.html @@ -0,0 +1,296 @@ + + + + + + + \ No newline at end of file diff --git a/tensorflow/.DS_Store b/tensorflow/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..635fab16f823d8ec941b601141762f32997f9494 GIT binary patch literal 6148 zcmeHK%TB{U3>-r%Rq7>2j{6J!gH=_&pdSFVK|oaz1m&I^zs>joB8qb0(gWC%J-f4Z zwwXg*1CaH5y8;>j3%VjcG>lEp)kk&_jH1{x9^1G5ao2UnQSKC>wR1e+6?b^SpuRze z9S*q19w*$OC!Rh3`m(7Xk$ftU3Zw$5Kq~N$6kyL*TPz(jrUI!zD)6m}EbEZkCu( z#BL{FEFDrEGo}Kmz)*op-!|I+ujv!>|F9@`sX!|5rxcLcX1iJQm7=$fUQT;$q2JQK qjJcN1V6B*Ft(Y5a#aBP_imrKH1A9j+CvWA%{3D>cq@@DqP~a0t${p(f literal 0 HcmV?d00001 diff --git a/tensorflow/graph/.DS_Store b/tensorflow/graph/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..85c3a00e1984f540759745dfbe065680b0991c25 GIT binary patch literal 6148 zcmeHKyG{c^3>-s>NHi%ax1=OgDOXYQ1$@8(7my-FIYc1!b^JEt2Z+;!mI7?ap0(HB z)lP9f1CZt0W)92%OsR@^S2GsfR~^Y&M1)A?8IO2DLv67iWuFdc?G>JIj{{x@^?RPQ zS0K^HyCS2PrK`a^|*1+D85sG*z(Mu&p4DoWt zOXStS-qFh;F?>keEHR;o-Olrir9-M?+GHRZ7&36~?o9jtn)<{1KP*Zw8At~H6az9@ z-L95=QuNl*K*k{m}}`Y){2SNis@)8zWkO~bj|%5*gIM|<5o`09|7ehB^me) G20j498y`FX literal 0 HcmV?d00001 diff --git a/tensorflow/graph/README.md b/tensorflow/graph/README.md new file mode 100644 index 0000000..51cd781 --- /dev/null +++ b/tensorflow/graph/README.md @@ -0,0 +1,5 @@ +# TensorFlow Graph + +Download and extract an object detection graph to this folder from the [TensorFlow Detection Model Zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md#coco-trained-models). + +I am using the `faster_rcnn_inception_v2_coco` model. \ No newline at end of file diff --git a/tensorflow/object_detection/.DS_Store b/tensorflow/object_detection/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0.object_detection.protos.WeightSharedConvolutionalBoxPredictorH\x00\x42\x15\n\x13\x62ox_predictor_oneof\"c\n\x08MaskHead\x12\x17\n\x0bmask_height\x18\x01 \x01(\x05:\x02\x31\x35\x12\x16\n\nmask_width\x18\x02 \x01(\x05:\x02\x31\x35\x12&\n\x18masks_are_class_agnostic\x18\x03 \x01(\x08:\x04true\"\xc6\x03\n\x19\x43onvolutionalBoxPredictor\x12>\n\x10\x63onv_hyperparams\x18\x01 \x01(\x0b\x32$.object_detection.protos.Hyperparams\x12\x14\n\tmin_depth\x18\x02 \x01(\x05:\x01\x30\x12\x14\n\tmax_depth\x18\x03 \x01(\x05:\x01\x30\x12&\n\x1bnum_layers_before_predictor\x18\x04 \x01(\x05:\x01\x30\x12\x19\n\x0buse_dropout\x18\x05 \x01(\x08:\x04true\x12%\n\x18\x64ropout_keep_probability\x18\x06 \x01(\x02:\x03\x30.8\x12\x16\n\x0bkernel_size\x18\x07 \x01(\x05:\x01\x31\x12\x18\n\rbox_code_size\x18\x08 \x01(\x05:\x01\x34\x12&\n\x17\x61pply_sigmoid_to_scores\x18\t \x01(\x08:\x05\x66\x61lse\x12%\n\x1a\x63lass_prediction_bias_init\x18\n \x01(\x02:\x01\x30\x12\x1c\n\ruse_depthwise\x18\x0b \x01(\x08:\x05\x66\x61lse\x12\x34\n\tmask_head\x18\x0c \x01(\x0b\x32!.object_detection.protos.MaskHead\"\x82\x06\n%WeightSharedConvolutionalBoxPredictor\x12>\n\x10\x63onv_hyperparams\x18\x01 \x01(\x0b\x32$.object_detection.protos.Hyperparams\x12&\n\x1bnum_layers_before_predictor\x18\x04 \x01(\x05:\x01\x30\x12\x10\n\x05\x64\x65pth\x18\x02 \x01(\x05:\x01\x30\x12\x16\n\x0bkernel_size\x18\x07 \x01(\x05:\x01\x33\x12\x18\n\rbox_code_size\x18\x08 \x01(\x05:\x01\x34\x12%\n\x1a\x63lass_prediction_bias_init\x18\n \x01(\x02:\x01\x30\x12\x1a\n\x0buse_dropout\x18\x0b \x01(\x08:\x05\x66\x61lse\x12%\n\x18\x64ropout_keep_probability\x18\x0c \x01(\x02:\x03\x30.8\x12%\n\x16share_prediction_tower\x18\r \x01(\x08:\x05\x66\x61lse\x12\x1c\n\ruse_depthwise\x18\x0e \x01(\x08:\x05\x66\x61lse\x12\x34\n\tmask_head\x18\x0f \x01(\x0b\x32!.object_detection.protos.MaskHead\x12p\n\x0fscore_converter\x18\x10 \x01(\x0e\x32M.object_detection.protos.WeightSharedConvolutionalBoxPredictor.ScoreConverter:\x08IDENTITY\x12v\n\x18\x62ox_encodings_clip_range\x18\x11 \x01(\x0b\x32T.object_detection.protos.WeightSharedConvolutionalBoxPredictor.BoxEncodingsClipRange\x1a\x31\n\x15\x42oxEncodingsClipRange\x12\x0b\n\x03min\x18\x01 \x01(\x02\x12\x0b\n\x03max\x18\x02 \x01(\x02\"+\n\x0eScoreConverter\x12\x0c\n\x08IDENTITY\x10\x00\x12\x0b\n\x07SIGMOID\x10\x01\"\x92\x04\n\x14MaskRCNNBoxPredictor\x12<\n\x0e\x66\x63_hyperparams\x18\x01 \x01(\x0b\x32$.object_detection.protos.Hyperparams\x12\x1a\n\x0buse_dropout\x18\x02 \x01(\x08:\x05\x66\x61lse\x12%\n\x18\x64ropout_keep_probability\x18\x03 \x01(\x02:\x03\x30.5\x12\x18\n\rbox_code_size\x18\x04 \x01(\x05:\x01\x34\x12>\n\x10\x63onv_hyperparams\x18\x05 \x01(\x0b\x32$.object_detection.protos.Hyperparams\x12%\n\x16predict_instance_masks\x18\x06 \x01(\x08:\x05\x66\x61lse\x12\'\n\x1amask_prediction_conv_depth\x18\x07 \x01(\x05:\x03\x32\x35\x36\x12 \n\x11predict_keypoints\x18\x08 \x01(\x08:\x05\x66\x61lse\x12\x17\n\x0bmask_height\x18\t \x01(\x05:\x02\x31\x35\x12\x16\n\nmask_width\x18\n \x01(\x05:\x02\x31\x35\x12*\n\x1fmask_prediction_num_conv_layers\x18\x0b \x01(\x05:\x01\x32\x12\'\n\x18masks_are_class_agnostic\x18\x0c \x01(\x08:\x05\x66\x61lse\x12\'\n\x18share_box_across_classes\x18\r \x01(\x08:\x05\x66\x61lse\"\xf9\x01\n\x10RfcnBoxPredictor\x12>\n\x10\x63onv_hyperparams\x18\x01 \x01(\x0b\x32$.object_detection.protos.Hyperparams\x12\"\n\x17num_spatial_bins_height\x18\x02 \x01(\x05:\x01\x33\x12!\n\x16num_spatial_bins_width\x18\x03 \x01(\x05:\x01\x33\x12\x13\n\x05\x64\x65pth\x18\x04 \x01(\x05:\x04\x31\x30\x32\x34\x12\x18\n\rbox_code_size\x18\x05 \x01(\x05:\x01\x34\x12\x17\n\x0b\x63rop_height\x18\x06 \x01(\x05:\x02\x31\x32\x12\x16\n\ncrop_width\x18\x07 \x01(\x05:\x02\x31\x32') + , + dependencies=[object__detection_dot_protos_dot_hyperparams__pb2.DESCRIPTOR,]) + + + +_WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR_SCORECONVERTER = _descriptor.EnumDescriptor( + name='ScoreConverter', + full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.ScoreConverter', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='IDENTITY', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SIGMOID', index=1, number=1, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=1804, + serialized_end=1847, +) +_sym_db.RegisterEnumDescriptor(_WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR_SCORECONVERTER) + + +_BOXPREDICTOR = _descriptor.Descriptor( + name='BoxPredictor', + full_name='object_detection.protos.BoxPredictor', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='convolutional_box_predictor', full_name='object_detection.protos.BoxPredictor.convolutional_box_predictor', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mask_rcnn_box_predictor', full_name='object_detection.protos.BoxPredictor.mask_rcnn_box_predictor', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='rfcn_box_predictor', full_name='object_detection.protos.BoxPredictor.rfcn_box_predictor', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='weight_shared_convolutional_box_predictor', full_name='object_detection.protos.BoxPredictor.weight_shared_convolutional_box_predictor', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='box_predictor_oneof', full_name='object_detection.protos.BoxPredictor.box_predictor_oneof', + index=0, containing_type=None, fields=[]), + ], + serialized_start=116, + serialized_end=516, +) + + +_MASKHEAD = _descriptor.Descriptor( + name='MaskHead', + full_name='object_detection.protos.MaskHead', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='mask_height', full_name='object_detection.protos.MaskHead.mask_height', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=15, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mask_width', full_name='object_detection.protos.MaskHead.mask_width', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=15, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='masks_are_class_agnostic', full_name='object_detection.protos.MaskHead.masks_are_class_agnostic', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=True, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=518, + serialized_end=617, +) + + +_CONVOLUTIONALBOXPREDICTOR = _descriptor.Descriptor( + name='ConvolutionalBoxPredictor', + full_name='object_detection.protos.ConvolutionalBoxPredictor', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='conv_hyperparams', full_name='object_detection.protos.ConvolutionalBoxPredictor.conv_hyperparams', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='min_depth', full_name='object_detection.protos.ConvolutionalBoxPredictor.min_depth', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='max_depth', full_name='object_detection.protos.ConvolutionalBoxPredictor.max_depth', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='num_layers_before_predictor', full_name='object_detection.protos.ConvolutionalBoxPredictor.num_layers_before_predictor', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='use_dropout', full_name='object_detection.protos.ConvolutionalBoxPredictor.use_dropout', index=4, + number=5, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=True, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='dropout_keep_probability', full_name='object_detection.protos.ConvolutionalBoxPredictor.dropout_keep_probability', index=5, + number=6, type=2, cpp_type=6, label=1, + has_default_value=True, default_value=float(0.8), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='kernel_size', full_name='object_detection.protos.ConvolutionalBoxPredictor.kernel_size', index=6, + number=7, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='box_code_size', full_name='object_detection.protos.ConvolutionalBoxPredictor.box_code_size', index=7, + number=8, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=4, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='apply_sigmoid_to_scores', full_name='object_detection.protos.ConvolutionalBoxPredictor.apply_sigmoid_to_scores', index=8, + number=9, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='class_prediction_bias_init', full_name='object_detection.protos.ConvolutionalBoxPredictor.class_prediction_bias_init', index=9, + number=10, type=2, cpp_type=6, label=1, + has_default_value=True, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='use_depthwise', full_name='object_detection.protos.ConvolutionalBoxPredictor.use_depthwise', index=10, + number=11, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mask_head', full_name='object_detection.protos.ConvolutionalBoxPredictor.mask_head', index=11, + number=12, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=620, + serialized_end=1074, +) + + +_WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR_BOXENCODINGSCLIPRANGE = _descriptor.Descriptor( + name='BoxEncodingsClipRange', + full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.BoxEncodingsClipRange', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='min', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.BoxEncodingsClipRange.min', index=0, + number=1, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='max', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.BoxEncodingsClipRange.max', index=1, + number=2, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1753, + serialized_end=1802, +) + +_WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR = _descriptor.Descriptor( + name='WeightSharedConvolutionalBoxPredictor', + full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='conv_hyperparams', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.conv_hyperparams', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='num_layers_before_predictor', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.num_layers_before_predictor', index=1, + number=4, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='depth', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.depth', index=2, + number=2, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='kernel_size', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.kernel_size', index=3, + number=7, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=3, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='box_code_size', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.box_code_size', index=4, + number=8, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=4, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='class_prediction_bias_init', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.class_prediction_bias_init', index=5, + number=10, type=2, cpp_type=6, label=1, + has_default_value=True, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='use_dropout', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.use_dropout', index=6, + number=11, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='dropout_keep_probability', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.dropout_keep_probability', index=7, + number=12, type=2, cpp_type=6, label=1, + has_default_value=True, default_value=float(0.8), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='share_prediction_tower', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.share_prediction_tower', index=8, + number=13, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='use_depthwise', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.use_depthwise', index=9, + number=14, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mask_head', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.mask_head', index=10, + number=15, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='score_converter', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.score_converter', index=11, + number=16, type=14, cpp_type=8, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='box_encodings_clip_range', full_name='object_detection.protos.WeightSharedConvolutionalBoxPredictor.box_encodings_clip_range', index=12, + number=17, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR_BOXENCODINGSCLIPRANGE, ], + enum_types=[ + _WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR_SCORECONVERTER, + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1077, + serialized_end=1847, +) + + +_MASKRCNNBOXPREDICTOR = _descriptor.Descriptor( + name='MaskRCNNBoxPredictor', + full_name='object_detection.protos.MaskRCNNBoxPredictor', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='fc_hyperparams', full_name='object_detection.protos.MaskRCNNBoxPredictor.fc_hyperparams', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='use_dropout', full_name='object_detection.protos.MaskRCNNBoxPredictor.use_dropout', index=1, + number=2, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='dropout_keep_probability', full_name='object_detection.protos.MaskRCNNBoxPredictor.dropout_keep_probability', index=2, + number=3, type=2, cpp_type=6, label=1, + has_default_value=True, default_value=float(0.5), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='box_code_size', full_name='object_detection.protos.MaskRCNNBoxPredictor.box_code_size', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=4, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='conv_hyperparams', full_name='object_detection.protos.MaskRCNNBoxPredictor.conv_hyperparams', index=4, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='predict_instance_masks', full_name='object_detection.protos.MaskRCNNBoxPredictor.predict_instance_masks', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mask_prediction_conv_depth', full_name='object_detection.protos.MaskRCNNBoxPredictor.mask_prediction_conv_depth', index=6, + number=7, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=256, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='predict_keypoints', full_name='object_detection.protos.MaskRCNNBoxPredictor.predict_keypoints', index=7, + number=8, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mask_height', full_name='object_detection.protos.MaskRCNNBoxPredictor.mask_height', index=8, + number=9, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=15, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mask_width', full_name='object_detection.protos.MaskRCNNBoxPredictor.mask_width', index=9, + number=10, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=15, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mask_prediction_num_conv_layers', full_name='object_detection.protos.MaskRCNNBoxPredictor.mask_prediction_num_conv_layers', index=10, + number=11, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=2, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='masks_are_class_agnostic', full_name='object_detection.protos.MaskRCNNBoxPredictor.masks_are_class_agnostic', index=11, + number=12, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='share_box_across_classes', full_name='object_detection.protos.MaskRCNNBoxPredictor.share_box_across_classes', index=12, + number=13, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1850, + serialized_end=2380, +) + + +_RFCNBOXPREDICTOR = _descriptor.Descriptor( + name='RfcnBoxPredictor', + full_name='object_detection.protos.RfcnBoxPredictor', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='conv_hyperparams', full_name='object_detection.protos.RfcnBoxPredictor.conv_hyperparams', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='num_spatial_bins_height', full_name='object_detection.protos.RfcnBoxPredictor.num_spatial_bins_height', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=3, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='num_spatial_bins_width', full_name='object_detection.protos.RfcnBoxPredictor.num_spatial_bins_width', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=3, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='depth', full_name='object_detection.protos.RfcnBoxPredictor.depth', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=1024, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='box_code_size', full_name='object_detection.protos.RfcnBoxPredictor.box_code_size', index=4, + number=5, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=4, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='crop_height', full_name='object_detection.protos.RfcnBoxPredictor.crop_height', index=5, + number=6, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=12, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='crop_width', full_name='object_detection.protos.RfcnBoxPredictor.crop_width', index=6, + number=7, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=12, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2383, + serialized_end=2632, +) + +_BOXPREDICTOR.fields_by_name['convolutional_box_predictor'].message_type = _CONVOLUTIONALBOXPREDICTOR +_BOXPREDICTOR.fields_by_name['mask_rcnn_box_predictor'].message_type = _MASKRCNNBOXPREDICTOR +_BOXPREDICTOR.fields_by_name['rfcn_box_predictor'].message_type = _RFCNBOXPREDICTOR +_BOXPREDICTOR.fields_by_name['weight_shared_convolutional_box_predictor'].message_type = _WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR +_BOXPREDICTOR.oneofs_by_name['box_predictor_oneof'].fields.append( + _BOXPREDICTOR.fields_by_name['convolutional_box_predictor']) +_BOXPREDICTOR.fields_by_name['convolutional_box_predictor'].containing_oneof = _BOXPREDICTOR.oneofs_by_name['box_predictor_oneof'] +_BOXPREDICTOR.oneofs_by_name['box_predictor_oneof'].fields.append( + _BOXPREDICTOR.fields_by_name['mask_rcnn_box_predictor']) +_BOXPREDICTOR.fields_by_name['mask_rcnn_box_predictor'].containing_oneof = _BOXPREDICTOR.oneofs_by_name['box_predictor_oneof'] +_BOXPREDICTOR.oneofs_by_name['box_predictor_oneof'].fields.append( + _BOXPREDICTOR.fields_by_name['rfcn_box_predictor']) +_BOXPREDICTOR.fields_by_name['rfcn_box_predictor'].containing_oneof = _BOXPREDICTOR.oneofs_by_name['box_predictor_oneof'] +_BOXPREDICTOR.oneofs_by_name['box_predictor_oneof'].fields.append( + _BOXPREDICTOR.fields_by_name['weight_shared_convolutional_box_predictor']) +_BOXPREDICTOR.fields_by_name['weight_shared_convolutional_box_predictor'].containing_oneof = _BOXPREDICTOR.oneofs_by_name['box_predictor_oneof'] +_CONVOLUTIONALBOXPREDICTOR.fields_by_name['conv_hyperparams'].message_type = object__detection_dot_protos_dot_hyperparams__pb2._HYPERPARAMS +_CONVOLUTIONALBOXPREDICTOR.fields_by_name['mask_head'].message_type = _MASKHEAD +_WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR_BOXENCODINGSCLIPRANGE.containing_type = _WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR +_WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR.fields_by_name['conv_hyperparams'].message_type = object__detection_dot_protos_dot_hyperparams__pb2._HYPERPARAMS +_WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR.fields_by_name['mask_head'].message_type = _MASKHEAD +_WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR.fields_by_name['score_converter'].enum_type = _WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR_SCORECONVERTER +_WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR.fields_by_name['box_encodings_clip_range'].message_type = _WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR_BOXENCODINGSCLIPRANGE +_WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR_SCORECONVERTER.containing_type = _WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR +_MASKRCNNBOXPREDICTOR.fields_by_name['fc_hyperparams'].message_type = object__detection_dot_protos_dot_hyperparams__pb2._HYPERPARAMS +_MASKRCNNBOXPREDICTOR.fields_by_name['conv_hyperparams'].message_type = object__detection_dot_protos_dot_hyperparams__pb2._HYPERPARAMS +_RFCNBOXPREDICTOR.fields_by_name['conv_hyperparams'].message_type = object__detection_dot_protos_dot_hyperparams__pb2._HYPERPARAMS +DESCRIPTOR.message_types_by_name['BoxPredictor'] = _BOXPREDICTOR +DESCRIPTOR.message_types_by_name['MaskHead'] = _MASKHEAD +DESCRIPTOR.message_types_by_name['ConvolutionalBoxPredictor'] = _CONVOLUTIONALBOXPREDICTOR +DESCRIPTOR.message_types_by_name['WeightSharedConvolutionalBoxPredictor'] = _WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR +DESCRIPTOR.message_types_by_name['MaskRCNNBoxPredictor'] = _MASKRCNNBOXPREDICTOR +DESCRIPTOR.message_types_by_name['RfcnBoxPredictor'] = _RFCNBOXPREDICTOR +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +BoxPredictor = _reflection.GeneratedProtocolMessageType('BoxPredictor', (_message.Message,), dict( + DESCRIPTOR = _BOXPREDICTOR, + __module__ = 'object_detection.protos.box_predictor_pb2' + # @@protoc_insertion_point(class_scope:object_detection.protos.BoxPredictor) + )) +_sym_db.RegisterMessage(BoxPredictor) + +MaskHead = _reflection.GeneratedProtocolMessageType('MaskHead', (_message.Message,), dict( + DESCRIPTOR = _MASKHEAD, + __module__ = 'object_detection.protos.box_predictor_pb2' + # @@protoc_insertion_point(class_scope:object_detection.protos.MaskHead) + )) +_sym_db.RegisterMessage(MaskHead) + +ConvolutionalBoxPredictor = _reflection.GeneratedProtocolMessageType('ConvolutionalBoxPredictor', (_message.Message,), dict( + DESCRIPTOR = _CONVOLUTIONALBOXPREDICTOR, + __module__ = 'object_detection.protos.box_predictor_pb2' + # @@protoc_insertion_point(class_scope:object_detection.protos.ConvolutionalBoxPredictor) + )) +_sym_db.RegisterMessage(ConvolutionalBoxPredictor) + +WeightSharedConvolutionalBoxPredictor = _reflection.GeneratedProtocolMessageType('WeightSharedConvolutionalBoxPredictor', (_message.Message,), dict( + + BoxEncodingsClipRange = _reflection.GeneratedProtocolMessageType('BoxEncodingsClipRange', (_message.Message,), dict( + DESCRIPTOR = _WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR_BOXENCODINGSCLIPRANGE, + __module__ = 'object_detection.protos.box_predictor_pb2' + # @@protoc_insertion_point(class_scope:object_detection.protos.WeightSharedConvolutionalBoxPredictor.BoxEncodingsClipRange) + )) + , + DESCRIPTOR = _WEIGHTSHAREDCONVOLUTIONALBOXPREDICTOR, + __module__ = 'object_detection.protos.box_predictor_pb2' + # @@protoc_insertion_point(class_scope:object_detection.protos.WeightSharedConvolutionalBoxPredictor) + )) +_sym_db.RegisterMessage(WeightSharedConvolutionalBoxPredictor) +_sym_db.RegisterMessage(WeightSharedConvolutionalBoxPredictor.BoxEncodingsClipRange) + +MaskRCNNBoxPredictor = _reflection.GeneratedProtocolMessageType('MaskRCNNBoxPredictor', (_message.Message,), dict( + DESCRIPTOR = _MASKRCNNBOXPREDICTOR, + __module__ = 'object_detection.protos.box_predictor_pb2' + # @@protoc_insertion_point(class_scope:object_detection.protos.MaskRCNNBoxPredictor) + )) +_sym_db.RegisterMessage(MaskRCNNBoxPredictor) + +RfcnBoxPredictor = _reflection.GeneratedProtocolMessageType('RfcnBoxPredictor', (_message.Message,), dict( + DESCRIPTOR = _RFCNBOXPREDICTOR, + __module__ = 'object_detection.protos.box_predictor_pb2' + # @@protoc_insertion_point(class_scope:object_detection.protos.RfcnBoxPredictor) + )) +_sym_db.RegisterMessage(RfcnBoxPredictor) + + +# @@protoc_insertion_point(module_scope) diff --git a/tensorflow/object_detection/protos/eval.proto b/tensorflow/object_detection/protos/eval.proto new file mode 100644 index 0000000..ed5a3a9 --- /dev/null +++ b/tensorflow/object_detection/protos/eval.proto @@ -0,0 +1,79 @@ +syntax = "proto2"; + +package object_detection.protos; + +// Message for configuring DetectionModel evaluation jobs (eval.py). +message EvalConfig { + optional uint32 batch_size = 25 [default=1]; + // Number of visualization images to generate. + optional uint32 num_visualizations = 1 [default=10]; + + // Number of examples to process of evaluation. + optional uint32 num_examples = 2 [default=5000, deprecated=true]; + + // How often to run evaluation. + optional uint32 eval_interval_secs = 3 [default=300]; + + // Maximum number of times to run evaluation. If set to 0, will run forever. + optional uint32 max_evals = 4 [default=0, deprecated=true]; + + // Whether the TensorFlow graph used for evaluation should be saved to disk. + optional bool save_graph = 5 [default=false]; + + // Path to directory to store visualizations in. If empty, visualization + // images are not exported (only shown on Tensorboard). + optional string visualization_export_dir = 6 [default=""]; + + // BNS name of the TensorFlow master. + optional string eval_master = 7 [default=""]; + + // Type of metrics to use for evaluation. + repeated string metrics_set = 8; + + // Path to export detections to COCO compatible JSON format. + optional string export_path = 9 [default='']; + + // Option to not read groundtruth labels and only export detections to + // COCO-compatible JSON file. + optional bool ignore_groundtruth = 10 [default=false]; + + // Use exponential moving averages of variables for evaluation. + // TODO(rathodv): When this is false make sure the model is constructed + // without moving averages in restore_fn. + optional bool use_moving_averages = 11 [default=false]; + + // Whether to evaluate instance masks. + // Note that since there is no evaluation code currently for instance + // segmenation this option is unused. + optional bool eval_instance_masks = 12 [default=false]; + + // Minimum score threshold for a detected object box to be visualized + optional float min_score_threshold = 13 [default=0.5]; + + // Maximum number of detections to visualize + optional int32 max_num_boxes_to_visualize = 14 [default=20]; + + // When drawing a single detection, each label is by default visualized as + //