├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── issue.md │ └── feature_request.md ├── settings.yml ├── stale.yml └── workflows │ └── release.yml ├── custom_components └── combined │ ├── __init__.py │ ├── manifest.json │ └── camera.py ├── .devcontainer ├── images │ └── reopen.png ├── configuration.yaml ├── Dockerfile ├── devcontainer.json ├── custom_component_helper └── README.md ├── hacs.json ├── README.md ├── LICENSE └── .vscode └── tasks.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @ludeeus -------------------------------------------------------------------------------- /custom_components/combined/__init__.py: -------------------------------------------------------------------------------- 1 | """camera.combined camera platfrom.""" -------------------------------------------------------------------------------- /.devcontainer/images/reopen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/custom-components/combined/HEAD/.devcontainer/images/reopen.png -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Combined camera", 3 | "zip_release": true, 4 | "filename": "combined.zip", 5 | "homeassistant": "2021.5.0", 6 | "hide_default_branch": true, 7 | "render_readme": true 8 | } 9 | -------------------------------------------------------------------------------- /custom_components/combined/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "combined", 3 | "name": "Camera combined", 4 | "documentation": "https://github.com/custom-components/combined", 5 | "version": "0.0.0", 6 | "codeowners": [ 7 | "@ludeeus" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.devcontainer/configuration.yaml: -------------------------------------------------------------------------------- 1 | default_config: 2 | logger: 3 | default: error 4 | logs: 5 | custom_components.combined: debug 6 | 7 | demo: 8 | 9 | camera: 10 | platform: combined 11 | base_address: http://localhost:8123 12 | entities: 13 | - camera.demo_camera 14 | - camera.demo_camera -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Version of the custom_component** 8 | 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **log** 14 | 15 | ``` 16 | Add your logs here. 17 | ``` -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y --no-install-recommends \ 5 | git \ 6 | && apt-get clean \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | RUN python -m pip install --upgrade colorlog black pylint 10 | RUN python -m pip install --upgrade git+git://github.com/home-assistant/home-assistant.git@dev 11 | RUN cd && mkdir -p /config/custom_components 12 | 13 | 14 | WORKDIR /workspace 15 | 16 | # Set the default shell to bash instead of sh 17 | ENV SHELL /bin/bash -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | private: false 3 | has_issues: true 4 | has_projects: false 5 | has_wiki: false 6 | has_downloads: false 7 | default_branch: master 8 | allow_squash_merge: true 9 | allow_merge_commit: false 10 | allow_rebase_merge: false 11 | labels: 12 | - name: "Feature Request" 13 | color: "fbca04" 14 | - name: "Bug" 15 | color: "b60205" 16 | - name: "Wont Fix" 17 | color: "ffffff" 18 | - name: "Enhancement" 19 | color: "a2eeef" 20 | - name: "Documentation" 21 | color: "008672" 22 | - name: "Stale" 23 | color: "930191" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/vscode-remote/devcontainer.json for format details. 2 | { 3 | "context": "..", 4 | "dockerFile": "Dockerfile", 5 | "appPort": "8124:8123", 6 | "runArgs": [ 7 | "-e", 8 | "GIT_EDTIOR='code --wait'" 9 | ], 10 | "extensions": [ 11 | "ms-python.python" 12 | ], 13 | "settings": { 14 | "python.pythonPath": "/usr/local/bin/python", 15 | "python.linting.pylintEnabled": true, 16 | "python.linting.enabled": true, 17 | "python.formatting.provider": "black", 18 | "editor.formatOnPaste": false, 19 | "editor.formatOnSave": true, 20 | "editor.formatOnType": true, 21 | "files.trimTrailingWhitespace": true 22 | } 23 | } -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 14 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | #exemptLabels: 7 | # - pinned 8 | # - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: Stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false -------------------------------------------------------------------------------- /.devcontainer/custom_component_helper: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function StartHomeAssistant { 4 | echo "Copy configuration.yaml" 5 | cp -f .devcontainer/configuration.yaml /config || echo ".devcontainer/configuration.yaml are missing!" exit 1 6 | 7 | echo "Copy the custom component" 8 | rm -R /config/custom_components/ || echo "" 9 | cp -r custom_components /config/custom_components/ || echo "Could not copy the custom_component" exit 1 10 | 11 | echo "Start Home Assistant" 12 | hass -c /config 13 | } 14 | 15 | function UpdgradeHomeAssistantDev { 16 | python -m pip install --upgrade git+git://github.com/home-assistant/home-assistant.git@dev 17 | } 18 | 19 | function SetHomeAssistantVersion { 20 | read -p 'Version: ' version 21 | python -m pip install --upgrade homeassistant==$version 22 | } 23 | 24 | function HomeAssistantConfigCheck { 25 | hass -c /config --script check_config 26 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # camera.combined 2 | 3 | A camera platform that give you a combined feed of your defined camera entities. 4 | 5 | This will rotate the camera feeds, showing a 10 second feed from each camera before displaying the next one. 6 | 7 | To get started put all the files from`/custom_components/combined/` here: 8 | `/custom_components/combined/` 9 | 10 | **Example configuration.yaml:** 11 | 12 | ```yaml 13 | camera: 14 | platform: combined 15 | base_address: https://homeassistant.domain.com 16 | entities: 17 | - camera.camera1 18 | - camera.camera1 19 | ``` 20 | 21 | **Configuration variables:** 22 | 23 | key | description 24 | :--- | :--- 25 | **platform (Required)** | The camera platform name. 26 | **base_address (Required)** | The base address for yor Home Assistant instance. 27 | **entities (Required)** | A list of camera entities. 28 | **name (Optional)** | Give the camera a friendly name, defaults to `Combined`. 29 | 30 | *** 31 | 32 | [buymeacoffee.com](https://www.buymeacoffee.com/ludeeus) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Joakim Sørensen @ludeeus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Start Home Assistant on port 8124", 6 | "type": "shell", 7 | "command": "source .devcontainer/custom_component_helper && StartHomeAssistant", 8 | "group": { 9 | "kind": "test", 10 | "isDefault": true, 11 | }, 12 | "presentation": { 13 | "reveal": "always", 14 | "panel": "new" 15 | }, 16 | "problemMatcher": [] 17 | }, 18 | { 19 | "label": "Upgrade Home Assistant to latest dev", 20 | "type": "shell", 21 | "command": "source .devcontainer/custom_component_helper && UpdgradeHomeAssistantDev", 22 | "group": { 23 | "kind": "test", 24 | "isDefault": true, 25 | }, 26 | "presentation": { 27 | "reveal": "always", 28 | "panel": "new" 29 | }, 30 | "problemMatcher": [] 31 | }, 32 | { 33 | "label": "Set Home Assistant Version", 34 | "type": "shell", 35 | "command": "source .devcontainer/custom_component_helper && SetHomeAssistantVersion", 36 | "group": { 37 | "kind": "test", 38 | "isDefault": true, 39 | }, 40 | "presentation": { 41 | "reveal": "always", 42 | "panel": "new" 43 | }, 44 | "problemMatcher": [] 45 | }, 46 | { 47 | "label": "Home Assistant Config Check", 48 | "type": "shell", 49 | "command": "source .devcontainer/custom_component_helper && HomeAssistantConfigCheck", 50 | "group": { 51 | "kind": "test", 52 | "isDefault": true, 53 | }, 54 | "presentation": { 55 | "reveal": "always", 56 | "panel": "new" 57 | }, 58 | "problemMatcher": [] 59 | } 60 | ] 61 | } -------------------------------------------------------------------------------- /.devcontainer/README.md: -------------------------------------------------------------------------------- 1 | # Devcontainer 2 | 3 | _The easiest way to contribute to and/or test this repository._ 4 | 5 | ## Requirements 6 | 7 | - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) 8 | - [docker](https://docs.docker.com/install/) 9 | - [VS Code](https://code.visualstudio.com/) 10 | - [Remote - Containers (VSC Extention)](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) 11 | 12 | [More info about requirements and devcontainer in general](https://code.visualstudio.com/docs/remote/containers#_getting-started) 13 | 14 | ## How to use Devcontainer for development/test 15 | 16 | 1. Make sure your computer meets the requirements. 17 | 1. Fork this repository. 18 | 1. Clone the repository to your computer. 19 | 1. Open the repository using VS Code. 20 | 21 | When you open this repository with VSCode and your computer meets the requirements you are asked to "Reopen in Container", do that. 22 | 23 | ![reopen](images/reopen.png) 24 | 25 | If you don't see this notification, open the command pallet (ctrl+shift+p) and select `Remote-Containers: Reopen Folder in Container`. 26 | 27 | _It will now build the devcontainer._ 28 | 29 | The container have some "tasks" to help you testing your changes. 30 | 31 | ## Custom Tasks in this repository 32 | 33 | _Start "tasks" by opening the the command pallet (ctrl+shift+p) and select `Tasks: Run Task`_ 34 | 35 | Running tasks like `Start Home Assistant on port 8124` can be restarted by opening the the command pallet (ctrl+shift+p) and select `Tasks: Restart Running Task`, then select the task you want to restart. 36 | 37 | ### Start Home Assistant on port 8124 38 | 39 | This will copy the configuration and the integration files to the expected location in the container. 40 | 41 | And start up Home Assistant on [port 8124.](http://localhost:8124) 42 | 43 | ### Upgrade Home Assistant to latest dev 44 | 45 | This will upgrade Home Assistant to the latest dev version. 46 | 47 | ### Set Home Assistant Version 48 | 49 | This allows you to specify a version of Home Assistant to install inside the devcontainer. 50 | 51 | ### Home Assistant Config Check 52 | 53 | This runs a config check to make sure your config is valid. 54 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This GitHub action workflow is meant to be copyable to any repo that have the same structure. 2 | # - Your integration exist under custom_components/{INTEGRATION_NAME}/[integration files] 3 | # - You are using GitHub releases to publish new versions 4 | 5 | name: Release Workflow 6 | 7 | on: 8 | release: 9 | types: [published] 10 | 11 | jobs: 12 | release: 13 | name: Release 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: 📥 Checkout the repository 17 | uses: actions/checkout@v2 18 | 19 | - name: 🔢 Get release version 20 | id: version 21 | uses: home-assistant/actions/helpers/version@master 22 | 23 | - name: ℹ️ Get integration information 24 | id: information 25 | run: | 26 | name=$(find custom_components/ -type d -maxdepth 1 | tail -n 1 | cut -d "/" -f2) 27 | echo "::set-output name=name::$name" 28 | 29 | - name: 🖊️ Set version number 30 | run: | 31 | jq '.version = "${{ steps.version.outputs.version }}"' \ 32 | "${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}/manifest.json" > tmp \ 33 | && mv -f tmp "${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}/manifest.json" 34 | 35 | - name: 👀 Validate data 36 | run: | 37 | manifestversion=$(jq -r '.version' ${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}/manifest.json) 38 | if [ "$manifestversion" != "${{ steps.version.outputs.version }}" ]; then 39 | echo "The version in custom_components/${{ steps.information.outputs.name }}/manifest.json was not correct" 40 | echo "$manifestversion" 41 | exit 1 42 | fi 43 | 44 | - name: 📦 Create zip file for the integration 45 | run: | 46 | cd "${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}" 47 | zip ${{ steps.information.outputs.name }}.zip -r ./ 48 | 49 | - name: 📤 Upload the zip file as a release asset 50 | uses: actions/upload-release-asset@v1 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | with: 54 | upload_url: ${{ github.event.release.upload_url }} 55 | asset_path: "${{ github.workspace }}/custom_components/${{ steps.information.outputs.name }}/${{ steps.information.outputs.name }}.zip" 56 | asset_name: ${{ steps.information.outputs.name }}.zip 57 | asset_content_type: application/zip 58 | -------------------------------------------------------------------------------- /custom_components/combined/camera.py: -------------------------------------------------------------------------------- 1 | """ 2 | A platform that give you a combined feed of your defined camera entities. 3 | 4 | For more details about this component, please refer to the documentation at 5 | https://github.com/custom-components/combined 6 | """ 7 | import logging 8 | from datetime import timedelta 9 | 10 | import requests 11 | import voluptuous as vol 12 | import homeassistant.helpers.config_validation as cv 13 | from homeassistant.components.camera import PLATFORM_SCHEMA, Camera 14 | from homeassistant.util import Throttle 15 | 16 | _LOGGER = logging.getLogger(__name__) 17 | 18 | CONF_ENTITIES = "entities" 19 | CONF_BASE_ADDRESS = "base_address" 20 | CONF_NAME = "name" 21 | 22 | DEFAULT_NAME = "Combined" 23 | 24 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( 25 | { 26 | vol.Required(CONF_BASE_ADDRESS): cv.string, 27 | vol.Required(CONF_ENTITIES): vol.All(cv.ensure_list, [cv.string]), 28 | vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, 29 | } 30 | ) 31 | 32 | 33 | def setup_platform(hass, config, add_devices, discovery_info=None): 34 | """Set up the Camera platform.""" 35 | entities = config.get(CONF_ENTITIES) 36 | name = config.get(CONF_NAME) 37 | base_address = config.get(CONF_BASE_ADDRESS) 38 | camera = CombinedhCamera(hass, name, entities, base_address) 39 | add_devices([camera], True) 40 | 41 | 42 | class CombinedhCamera(Camera): 43 | """Representation of the camera.""" 44 | 45 | def __init__(self, hass, name, entities, base_address): 46 | """Initialize Camera platform.""" 47 | super().__init__() 48 | self.hass = hass 49 | self._name = name 50 | self._base_address = base_address 51 | self._entities = entities 52 | self._total = len(entities) 53 | self._count = 0 54 | self.feed = None 55 | 56 | def camera_image(self): 57 | """Return image response.""" 58 | self.fetch_new_image() 59 | return self.feed 60 | 61 | @property 62 | def is_streaming(self): 63 | return False 64 | 65 | @property 66 | def name(self): 67 | """Return the name of this camera.""" 68 | return self._name 69 | 70 | @Throttle(timedelta(seconds=5)) 71 | def fetch_new_image(self): 72 | """Fetch new image.""" 73 | _LOGGER.debug("Getting image") 74 | camera = self._entities[self._count] 75 | state = self.hass.states.get(camera) 76 | attribute = state.attributes.get("entity_picture") 77 | feed = requests.get(self._base_address + attribute, verify=False).content 78 | if self._count == (self._total - 1): 79 | self._count = 0 80 | else: 81 | self._count = self._count + 1 82 | self.feed = feed 83 | --------------------------------------------------------------------------------