├── .dockerignore ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── buildx.yml │ ├── check_paths.yml │ └── python_linting.yml ├── .gitignore ├── .markdownlint.yaml ├── .pylintrc ├── CHANGELOG.md ├── CODEOWNERS ├── Dockerfile ├── LICENSE ├── README.md ├── screenshots ├── paypal-donate-button.png └── telegram_message_sw_update.jpg └── src ├── requirements.txt └── teslamate_telegram_bot.py /.dockerignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | **/.classpath 3 | **/.dockerignore 4 | **/.env 5 | **/.git 6 | **/.gitignore 7 | **/.project 8 | **/.settings 9 | **/.toolstarget 10 | **/.vs 11 | **/.vscode 12 | **/*.*proj.user 13 | **/*.dbmdl 14 | **/*.jfm 15 | **/azds.yaml 16 | **/bin 17 | **/charts 18 | **/docker-compose* 19 | **/Dockerfile* 20 | **/node_modules 21 | **/npm-debug.log 22 | **/obj 23 | **/secrets.dev.yaml 24 | **/values.dev.yaml 25 | README.md -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZE9EHN48GYWMN&source=url 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: "pip" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | 9 | - package-ecosystem: "docker" 10 | directory: "/" 11 | schedule: 12 | interval: "monthly" 13 | 14 | - package-ecosystem: "github-actions" 15 | directory: "/" 16 | schedule: 17 | interval: "monthly" 18 | -------------------------------------------------------------------------------- /.github/workflows/buildx.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 3 * * *" # Every day at 3:00 AM 7 | push: 8 | branches: 9 | - "master" 10 | tags: 11 | - "v*" 12 | paths: 13 | - "**/*" 14 | - "!.github/**" # Important: Exclude PRs related to .github from auto-run 15 | - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run 16 | - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run 17 | env: 18 | REGISTRY_IMAGE: teslamatetelegrambot/teslamatetelegrambot 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | check_paths: 25 | uses: ./.github/workflows/check_paths.yml 26 | 27 | build: 28 | needs: check_paths 29 | if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' 30 | runs-on: ubuntu-latest 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | platform: 35 | - linux/amd64 36 | - linux/arm/v7 37 | - linux/arm64 38 | steps: 39 | - 40 | name: Prepare 41 | run: | 42 | platform=${{ matrix.platform }} 43 | echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV 44 | - 45 | name: Checkout 46 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 47 | - 48 | name: Docker meta 49 | id: meta 50 | uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f #v5.8.0 51 | with: 52 | images: ${{ env.REGISTRY_IMAGE }} 53 | - 54 | name: Set up QEMU 55 | uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 #v3.6.0 56 | - 57 | name: Set up Docker Buildx 58 | uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 #v3.11.1 59 | - 60 | name: Login to Docker Hub 61 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef #v3.6.0 62 | with: 63 | username: ${{ secrets.DOCKERHUB_USERNAME }} 64 | password: ${{ secrets.DOCKER_PASSWORD }} 65 | - 66 | name: Build and push by digest 67 | id: build 68 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 #v6.18.0 69 | with: 70 | context: . 71 | platforms: ${{ matrix.platform }} 72 | labels: ${{ steps.meta.outputs.labels }} 73 | outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true 74 | - 75 | name: Export digest 76 | run: | 77 | mkdir -p /tmp/digests 78 | digest="${{ steps.build.outputs.digest }}" 79 | touch "/tmp/digests/${digest#sha256:}" 80 | - 81 | name: Upload digest 82 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 #v5.0.0 83 | with: 84 | name: digests-${{ env.PLATFORM_PAIR }} 85 | path: /tmp/digests/* 86 | if-no-files-found: error 87 | retention-days: 1 88 | 89 | merge: 90 | runs-on: ubuntu-latest 91 | needs: 92 | - build 93 | steps: 94 | - 95 | name: Download digests 96 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 #v6.0.0 97 | with: 98 | path: /tmp/digests 99 | pattern: digests-* 100 | merge-multiple: true 101 | - 102 | name: Set up Docker Buildx 103 | uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 #v3.11.1 104 | - 105 | name: Docker meta 106 | id: meta 107 | uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f #v5.8.0 108 | with: 109 | images: ${{ env.REGISTRY_IMAGE }} 110 | tags: | 111 | type=schedule,pattern=edge 112 | type=semver,pattern={{version}} 113 | type=semver,pattern={{major}}.{{minor}} 114 | type=edge 115 | - 116 | name: Login to Docker Hub 117 | if: github.event_name != 'pull_request' 118 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef #v3.6.0 119 | with: 120 | username: ${{ secrets.DOCKERHUB_USERNAME }} 121 | password: ${{ secrets.DOCKER_PASSWORD }} 122 | - 123 | name: Create manifest list and push 124 | working-directory: /tmp/digests 125 | run: | 126 | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ 127 | $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) 128 | - 129 | name: Inspect image 130 | run: | 131 | docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} 132 | -------------------------------------------------------------------------------- /.github/workflows/check_paths.yml: -------------------------------------------------------------------------------- 1 | name: Check paths 2 | 3 | on: 4 | workflow_call: 5 | # Map the workflow outputs to job outputs 6 | outputs: 7 | githubfolder: 8 | description: "changes to .github folder" 9 | value: ${{ jobs.check_paths.githubfolder }} 10 | push: 11 | paths: 12 | - "**/*" 13 | - "!.github/**" # Important: Exclude PRs related to .github from auto-run 14 | - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run 15 | - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run 16 | branches: ["ci"] 17 | pull_request_target: 18 | branches: ["master"] 19 | paths: 20 | - "**/*" 21 | - "!.github/**" # Important: Exclude PRs related to .github from auto-run 22 | - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run 23 | - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-run 24 | 25 | permissions: 26 | contents: read 27 | 28 | jobs: 29 | check_paths: 30 | runs-on: ubuntu-latest 31 | outputs: 32 | githubfolder: ${{ steps.filter.outputs.githubfolder }} 33 | steps: 34 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 35 | 36 | - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 37 | id: filter 38 | with: 39 | base: "master" # needed to set as a called workflow does not have direct access to repository.default_branch 40 | filters: | 41 | githubfolder: 42 | - '.github/**' 43 | -------------------------------------------------------------------------------- /.github/workflows/python_linting.yml: -------------------------------------------------------------------------------- 1 | name: Python Linting 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - "master" 9 | paths: 10 | - "**/*" 11 | - "!.github/**" # Important: Exclude PRs related to .github from auto-run 12 | - "!.github/workflows/**" # Important: Exclude PRs related to .github/workflows from auto-run 13 | - "!.github/actions/**" # Important: Exclude PRs related to .github/actions from auto-ru 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | check_paths: 20 | uses: ./.github/workflows/check_paths.yml 21 | 22 | lint: 23 | needs: check_paths 24 | if: needs.check_paths.outputs.githubfolder != 'true' || github.event_name == 'schedule' 25 | runs-on: ubuntu-latest 26 | steps: 27 | - 28 | name: Checkout 29 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 30 | - 31 | name: Set up Python 32 | uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c #v6.0.0 33 | with: 34 | python-version: '3.12' 35 | - 36 | name: Install dependencies 37 | run: | 38 | python -m pip install --upgrade pip 39 | pip install flake8 40 | pip install pylint 41 | pip install -r src/requirements.txt 42 | - 43 | name: Lint with flake8 44 | run: | 45 | flake8 src --count --select=E9,F63,F7,F82 --show-source --statistics 46 | flake8 src --count --max-complexity=10 --max-line-length=120 --statistics 47 | - 48 | name: Lint with Pylint 49 | run: | 50 | pylint src 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | docker-compose.yml 3 | docker-compose-debug.yml 4 | 5 | #.gitkeep files exluded 6 | !.gitkeep 7 | 8 | ##Python 9 | 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | wheels/ 32 | share/python-wheels/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | MANIFEST 37 | 38 | # PyInstaller 39 | # Usually these files are written by a python script from a template 40 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 41 | *.manifest 42 | *.spec 43 | 44 | # Installer logs 45 | pip-log.txt 46 | pip-delete-this-directory.txt 47 | 48 | # Unit test / coverage reports 49 | htmlcov/ 50 | .tox/ 51 | .nox/ 52 | .coverage 53 | .coverage.* 54 | .cache 55 | nosetests.xml 56 | coverage.xml 57 | *.cover 58 | *.py,cover 59 | .hypothesis/ 60 | .pytest_cache/ 61 | cover/ 62 | 63 | # Translations 64 | *.mo 65 | *.pot 66 | 67 | # Django stuff: 68 | *.log 69 | local_settings.py 70 | db.sqlite3 71 | db.sqlite3-journal 72 | 73 | # Flask stuff: 74 | instance/ 75 | .webassets-cache 76 | 77 | # Scrapy stuff: 78 | .scrapy 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # PyBuilder 84 | .pybuilder/ 85 | target/ 86 | 87 | # Jupyter Notebook 88 | .ipynb_checkpoints 89 | 90 | # IPython 91 | profile_default/ 92 | ipython_config.py 93 | 94 | # pyenv 95 | # For a library or package, you might want to ignore these files since the code is 96 | # intended to run in multiple environments; otherwise, check them in: 97 | # .python-version 98 | 99 | # pipenv 100 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 101 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 102 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 103 | # install all needed dependencies. 104 | #Pipfile.lock 105 | 106 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 107 | __pypackages__/ 108 | 109 | # Celery stuff 110 | celerybeat-schedule 111 | celerybeat.pid 112 | 113 | # SageMath parsed files 114 | *.sage.py 115 | 116 | # Environments 117 | .env 118 | .venv 119 | env/ 120 | venv/ 121 | ENV/ 122 | env.bak/ 123 | venv.bak/ 124 | 125 | # Spyder project settings 126 | .spyderproject 127 | .spyproject 128 | 129 | # Rope project settings 130 | .ropeproject 131 | 132 | # mkdocs documentation 133 | /site 134 | 135 | # mypy 136 | .mypy_cache/ 137 | .dmypy.json 138 | dmypy.json 139 | 140 | # Pyre type checker 141 | .pyre/ 142 | 143 | # pytype static type analyzer 144 | .pytype/ 145 | 146 | # Cython debug symbols 147 | cython_debug/ 148 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | default: true 3 | MD013: 4 | line_length: 500 5 | MD024: 6 | siblings_only: true # heading duplication is allowed for non-sibling headings (common in changelogs) 7 | MD033: 8 | allowed_elements: ["img", "p", "a"] 9 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [FORMAT] 2 | max-line-length=120 3 | 4 | [DESIGN] 5 | # Minimum number of public methods for a class (see R0903). 6 | min-public-methods=0 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ### New Features 6 | 7 | ### Enhancements 8 | 9 | #### Build, CI, internal 10 | 11 | - build(deps): bump docker/setup-buildx-action from 3.8.0 to 3.10.0 (#63) 12 | - build(deps): bump actions/download-artifact from 4.1.8 to 4.1.9 (#64) 13 | - build(deps): bump docker/setup-qemu-action from 3.3.0 to 3.6.0 (#65) 14 | - build(deps): bump docker/metadata-action from 5.6.1 to 5.7.0 (#66) 15 | - build(deps): bump actions/upload-artifact from 4.6.0 to 4.6.1 (#67) 16 | - style: add flake8 disable comments for global state variable 17 | - build(deps): bump actions/setup-python from 5.4.0 to 5.5.0 (#68) 18 | - build(deps): bump actions/download-artifact from 4.1.9 to 4.2.1 (#69) 19 | - build(deps): bump docker/login-action from 3.3.0 to 3.4.0 (#70) 20 | - build(deps): bump docker/build-push-action from 6.13.0 to 6.15.0 (#71) 21 | - build(deps): bump actions/upload-artifact from 4.6.1 to 4.6.2 (#72) 22 | - build(deps): bump python-telegram-bot from 21.10 to 22.0 (#73) 23 | - style: update flake8 disable comments for global state variable to comply with flake8 7.2.0 24 | - build(deps): bump actions/setup-python from 5.5.0 to 5.6.0 (#76) 25 | - build(deps): bump docker/build-push-action from 6.15.0 to 6.16.0 (#74) 26 | - build(deps): bump docker/build-push-action from 6.15.0 to 6.16.0 (#75) 27 | - build(deps): bump docker/build-push-action from 6.16.0 to 6.18.0 (#77) 28 | - build(deps): bump python-telegram-bot from 22.0 to 22.1 (#78) 29 | - build(deps): bump docker/setup-buildx-action from 3.10.0 to 3.11.1 (#79) 30 | - build(deps): bump python-telegram-bot from 22.1 to 22.2 (#80) 31 | - build(deps): bump docker/login-action from 3.4.0 to 3.5.0 (#82) 32 | - build(deps): bump python-telegram-bot from 22.2 to 22.3 (#81) 33 | - build(deps): bump actions/checkout from 4.2.2 to 5.0.0 (#83) 34 | - build(deps): bump docker/metadata-action from 5.7.0 to 5.8.0 (#84) 35 | - build(deps): bump actions/download-artifact from 4.3.0 to 5.0.0 (#85) 36 | - build(deps): bump docker/login-action from 3.5.0 to 3.6.0 (#86) 37 | - build(deps): bump actions/setup-python from 5.6.0 to 6.0.0 (#87) 38 | - build(deps): bump python-telegram-bot from 22.3 to 22.5 (#88) 39 | - build(deps): bump python from 3.13-slim-bookworm to 3.14-slim-bookworm (#91) 40 | - build(deps): bump actions/upload-artifact from 4.6.2 to 5.0.0 (#89) 41 | - build(deps): bump actions/download-artifact from 5.0.0 to 6.0.0 (#90) 42 | 43 | ### Bug Fixes 44 | 45 | ## [0.7.8] - 2025-02-01 46 | 47 | ### New Features 48 | 49 | ### Enhancements 50 | 51 | #### Build, CI, internal 52 | 53 | - build(deps): bump docker/setup-buildx-action from 3.7.1 to 3.8.0 (#55) 54 | - build(deps): bump python-telegram-bot from 21.7 to 21.9 (#57) 55 | - build(deps): bump actions/upload-artifact from 4.4.3 to 4.5.0 (#56) 56 | - build(deps): bump python-telegram-bot from 21.9 to 21.10 (#58) 57 | - build(deps): bump actions/upload-artifact from 4.5.0 to 4.6.0 (#59) 58 | - build(deps): bump actions/setup-python from 5.3.0 to 5.4.0 (#60) 59 | - build(deps): bump docker/setup-qemu-action from 3.2.0 to 3.3.0 (#61) 60 | - build(deps): bump docker/build-push-action from 6.10.0 to 6.13.0 (#62) 61 | 62 | ### Bug Fixes 63 | 64 | ## [0.7.7] - 2024-12-02 65 | 66 | ### New Features 67 | 68 | ### Enhancements 69 | 70 | - doc: Remove unnecessary build configuration in docker compose 71 | 72 | #### Build, CI, internal 73 | 74 | - build(deps): bump paho-mqtt from 2.0.0 to 2.1.0 (#39) 75 | - build(deps): bump python-telegram-bot from 21.0.1 to 21.1.1 (#40) 76 | - build(deps): bump python-telegram-bot from 21.1.1 to 21.2 (#42) 77 | - build(deps): bump docker/build-push-action from 5 to 6 (#44) 78 | - build(deps): bump python-telegram-bot from 21.2 to 21.3 (#43) 79 | - build(deps): bump python-telegram-bot from 21.3 to 21.4 (#45) 80 | - build(deps): bump python-telegram-bot from 21.4 to 21.6 (#46) 81 | - ci: specify python version in GitHub workflow 82 | - ci: pin github action dependencies to protect against supply chain attacks, refactor to use common check_paths workflow 83 | - ci(fix): handle empty path filter output 84 | - style: UPPER_CASE naming style for constant name 85 | - build(deps): bump actions/setup-python from 5.2.0 to 5.3.0 (#47) 86 | - build(deps): bump actions/checkout from 4.1.7 to 4.2.2 (#48) 87 | - build(deps): bump actions/upload-artifact from 4.4.0 to 4.4.3 (#49) 88 | - build(deps): bump docker/setup-buildx-action from 3.6.1 to 3.7.1 (#50) 89 | - build(deps): bump python from 3.12-slim-bookworm to 3.13-slim-bookworm (#51) 90 | - build(deps): bump python-telegram-bot from 21.6 to 21.7 (#52) 91 | - build(deps): bump docker/build-push-action from 6.9.0 to 6.10.0 (#53) 92 | - build(deps): bump docker/metadata-action from 5.5.1 to 5.6.1 (#54) 93 | 94 | ### Bug Fixes 95 | 96 | ## [0.7.6] - 2024-04-12 97 | 98 | ### New Features 99 | 100 | - feat: support MQTT_NAMESPACE via optional environment variable (#38) 101 | 102 | ### Enhancements 103 | 104 | - doc: remove version tag in example docker compose as it is obsolete in docker 25.05 105 | - build(deps): bump dorny/paths-filter from 3.0.1 to 3.0.2 (#37) 106 | 107 | ### Bug Fixes 108 | 109 | ## [0.7.5] - 2024-03-21 110 | 111 | ### New Features 112 | 113 | - feat: allow negative chat_id, which means group chats (#35) 114 | - feat: rewrite type check for some environment variables including 2 minute wait before retry (#36) 115 | 116 | ### Enhancements 117 | 118 | ### Bug Fixes 119 | 120 | ## [0.7.4] - 2024-03-21 121 | 122 | ### New Features 123 | 124 | ### Enhancements 125 | 126 | ### Bug Fixes 127 | 128 | - fix: resolve UnboundLocalError if update version is empty (#34) 129 | 130 | ## [0.7.3] - 2024-03-21 131 | 132 | ### New Features 133 | 134 | ### Enhancements 135 | 136 | - style: correct typo in filename 137 | 138 | ### Bug Fixes 139 | 140 | - fix: correct type check for some environment variables (#33, thanks @freinbichler for reporting) 141 | 142 | ## [0.7.2] - 2024-03-21 143 | 144 | ### New Features 145 | 146 | - feat: send telegram message when bot started and stopped (not working for docker stop) 147 | - feat: add emoticons to messages 148 | - feat: car_id can be set via optional environment variable (see readme) 149 | 150 | ### Enhancements 151 | 152 | - feat: decrease checking interval to 30 seconds to reduce system load 153 | - feat: check some environment variables for valid values 154 | - fix: do not send a message if an empty update SW version is received after a successful update of the car SW 155 | 156 | ### Bug Fixes 157 | 158 | ## [0.7.1] - 2024-03-19 159 | 160 | ### New Features 161 | 162 | ### Enhancements 163 | 164 | ### Bug Fixes 165 | 166 | - fix: correct use of async functions 167 | 168 | ## [0.7.0] - 2024-03-18 169 | 170 | ### New Features 171 | 172 | ### Enhancements 173 | 174 | - feat: use logging instead of simple print 175 | - feat: introduce a global state and send messages depending on the state, improve logging 176 | - ci: add python linting workflow 177 | - ci: set max line length for flake8 python linting workflow 178 | - ci: install requirements before linting 179 | - stlye: fix flake8 findings 180 | - stlye: fix pylint findings 181 | 182 | ### Bug Fixes 183 | 184 | ## [0.6.4] - 2024-03-17 185 | 186 | ### New Features 187 | 188 | ### Enhancements 189 | 190 | - doc: update Docker installation instructions in the README and change to docker compose v2 191 | - build: reduce image size by removing unnecessary packages 192 | 193 | ### Bug Fixes 194 | 195 | - fix: correct number of positional arguments for on_connect() since mqtt5 196 | 197 | ## [0.6.3] - 2024-03-16 198 | 199 | ### New Features 200 | 201 | ### Enhancements 202 | 203 | - build: improve non-root user creation in dockerfile 204 | - style: remove global variables for bot and chat_id, ensure UPPERCASE for constants, update imports 205 | - ci: distribute build across multiple runners 206 | 207 | ### Bug Fixes 208 | 209 | fix: Subscription to teslamate_topic_update_version added 210 | 211 | ## [0.6.2] - 2024-03-16 212 | 213 | ### New Features 214 | 215 | - feat: Specify which SW update is available (#21) 216 | 217 | ### Enhancements 218 | 219 | - ci: use Environment File instead of deprecated set-output 220 | - ci: correct use of environment file outputs 221 | 222 | ### Bug Fixes 223 | 224 | - fix: remove double bot message 225 | 226 | ## [0.6.1] - 2024-03-15 227 | 228 | ### New Features 229 | 230 | ### Enhancements 231 | 232 | ### Bug Fixes 233 | 234 | - fix: re-add notification for available SW update only 235 | 236 | ## [0.6.0] - 2024-03-15 237 | 238 | ### New Features 239 | 240 | ### Enhancements 241 | 242 | - doc: show docker pulls in readme 243 | - ci: bump actions/checkout to v4 244 | - ci: bump docker/setup-qemu-action to v3 245 | - ci: Enable dependabot for GitHub Actions 246 | - ci: bump actions/cache to v4 247 | - ci: bump docker/login-action to v3 248 | - ci: Enable dependabot for pip requirements 249 | - build: Update Python base image to version 3.11-slim-bookworm 250 | - build: reduce the size of the Docker image by cleaning the APT cache 251 | - build: use copy instead of add in dockerfile 252 | - build: remove non-existent deb package from docker file 253 | - refactor: improve maintainability by extracting methods, extract environment variable handling, default value handling, add docstrings 254 | - ci: enable dependabot for docker dependencies 255 | - build: Bump python from 3.11-slim-bookworm to 3.12-slim-bookworm (#32) 256 | - feat: update paho-mqtt dependencie to 2.0.0 257 | - chore: Bump python-telegram-bot from 13.5 to 21.0.1 258 | 259 | ### Bug Fixes 260 | 261 | ## [0.5.3] - 2021-05-05 262 | 263 | ### New Features 264 | 265 | ### Enhancements 266 | 267 | - updateded requirements 268 | 269 | ### Bug Fixes 270 | 271 | ## [0.5.2] - 2021-05-05 272 | 273 | ### New Features 274 | 275 | ### Enhancements 276 | 277 | ### Bug Fixes 278 | 279 | - docker build dependencies 280 | 281 | ## [0.5.1] - 2021-05-05 282 | 283 | ### New Features 284 | 285 | ### Enhancements 286 | 287 | - MQTT authentication optional usable, see #23 288 | 289 | ## [0.5.0] - 2020-12-03 290 | 291 | ### New Features 292 | 293 | ### Enhancements 294 | 295 | - deployment as docker on docker hub, see #8 296 | - Internal: Docker-Action: use official docker buildx, see #14 297 | 298 | ## [0.4.0] - 2020-09-04 299 | 300 | ### New Features 301 | 302 | ### Enhancements 303 | 304 | - deployment as docker, see #6 305 | 306 | ### Bug Fixes 307 | 308 | ## [0.3.0] - 2020-09-04 309 | 310 | ### New Features 311 | 312 | ### Enhancements 313 | 314 | - store configuration in seperate files, see #4 315 | - possibility to exit with STRG+C 316 | - bit better messages on connection 317 | 318 | ### Bug Fixes 319 | 320 | ## [0.2.0] - 2020-09-04 321 | 322 | ### New Features 323 | 324 | ### Enhancements 325 | 326 | - Implemetation in python, as there are libraries for mqtt and telegram bot available for cool features in the future 327 | 328 | ### Bug Fixes 329 | 330 | ## [0.1.0] - 2020-09-04 331 | 332 | - Initial release with proof of concept, see #1 333 | 334 | [Unreleased]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.7.8...HEAD 335 | [0.7.8]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.7.7...v0.7.8 336 | [0.7.7]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.7.6...v0.7.7 337 | [0.7.6]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.7.5...v0.7.6 338 | [0.7.5]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.7.4...v0.7.5 339 | [0.7.4]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.7.3...v0.7.4 340 | [0.7.3]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.7.2...v0.7.3 341 | [0.7.2]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.7.1...v0.7.2 342 | [0.7.1]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.7.0...v0.7.1 343 | [0.7.0]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.6.4...v0.7.0 344 | [0.6.4]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.6.3...v0.6.4 345 | [0.6.3]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.6.2...v0.6.3 346 | [0.6.2]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.6.1...v0.6.2 347 | [0.6.1]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.6.0...v0.6.1 348 | [0.6.0]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.5.3...v0.6.0 349 | [0.5.3]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.5.2...v0.5.3 350 | [0.5.2]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.5.1...v0.5.2 351 | [0.5.1]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.5.0...v0.5.1 352 | [0.5.0]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.4.0...v0.5.0 353 | [0.4.0]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.3.0...v0.4.0 354 | [0.3.0]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.2.0...v0.3.0 355 | [0.2.0]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/v0.1.0...v0.2.0 356 | [0.1.0]: https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/compare/e76bb37d3...v0.1.0 357 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in the repo. 5 | 6 | 7 | # Order is important. The last matching pattern has the most precedence. 8 | # So if a pull request only touches javascript files, only these owners 9 | # will be requested to review. 10 | 11 | 12 | # You can also use email addresses if you prefer. 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.14-slim-bookworm 2 | 3 | # Keeps Python from generating .pyc files in the container 4 | ENV PYTHONDONTWRITEBYTECODE 1 5 | 6 | # Turns off buffering for easier container logging 7 | ENV PYTHONUNBUFFERED 1 8 | 9 | # Upgrade pip 10 | RUN python -m pip install --upgrade pip 11 | 12 | # Install the application dependencies 13 | WORKDIR /requirements 14 | COPY src/requirements.txt . 15 | RUN python -m pip install -r requirements.txt 16 | 17 | # Copy the application code 18 | WORKDIR /app 19 | COPY . /app 20 | 21 | # Creates a non-root user to run the application and adds permission to access the /app folder 22 | RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app 23 | USER appuser 24 | 25 | # Run the application 26 | CMD ["python", "./src/teslamate_telegram_bot.py"] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 JakobLichterfeld 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TeslaMate Telegram Bot 2 | 3 | [![CI](https://github.com/JakobLichterfeld/TeslaMate-Telegram-Bot/actions/workflows/buildx.yml/badge.svg)](https://github.com/JakobLichterfeld/TeslaMate-Telegram-Bot/actions/workflows/buildx.yml) 4 | [![version](https://img.shields.io/docker/v/teslamatetelegrambot/teslamatetelegrambot/latest)](https://hub.docker.com/r/teslamatetelegrambot/teslamatetelegrambot) 5 | [![docker pulls](https://img.shields.io/docker/pulls/teslamatetelegrambot/teslamatetelegrambot?color=%23099cec)](https://hub.docker.com/r/teslamatetelegrambot/teslamatetelegrambot) 6 | [![image size](https://img.shields.io/docker/image-size/teslamatetelegrambot/teslamatetelegrambot/latest)](https://hub.docker.com/r/teslamatetelegrambot/teslamatetelegrambot) 7 | [![Donate](https://img.shields.io/badge/Donate-PayPal-informational.svg?logo=paypal)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZE9EHN48GYWMN&source=url) 8 | 9 | This is a telegram bot written in Python to notify by Telegram message when a new SW update for your Tesla is available. It uses the MQTT topic which [TeslaMate](https://github.com/adriankumpf/teslamate) offers. 10 | 11 | ## Screenshots 12 | 13 |

14 | Telegram Message: SW Update available 15 |

16 | 17 | ## Table of contents 18 | 19 | - [TeslaMate Telegram Bot](#teslamate-telegram-bot) 20 | - [Screenshots](#screenshots) 21 | - [Table of contents](#table-of-contents) 22 | - [Features](#features) 23 | - [Requirements](#requirements) 24 | - [Installation](#installation) 25 | - [Update](#update) 26 | - [Contributing](#contributing) 27 | - [Donation](#donation) 28 | - [Disclaimer](#disclaimer) 29 | 30 | ## Features 31 | 32 | - [x] Sends a telegram message to you if an update for your tesla is available 33 | 34 | ## Requirements 35 | 36 | - A Machine that's always on and runs [TeslaMate](https://github.com/adriankumpf/teslamate) 37 | - Docker _(if you are new to Docker, see [Installing Docker](https://docs.docker.com/engine/install/) and [Docker Compose](https://docs.docker.com/compose/install/linux/))_ 38 | - External internet access, to send telegram messages. 39 | - A mobile with [Telegram](https://telegram.org/) client installed 40 | - your own Telegram Bot, see [Creating a new telegram bot](https://core.telegram.org/bots#6-botfather) 41 | - your own Telegram chat ID, see [get your telegram chat ID](https://docs.influxdata.com/kapacitor/v1.5/event_handlers/telegram/#get-your-telegram-chat-id) 42 | 43 | ## Installation 44 | 45 | Make sure you fulfill the [Requirements](#requirements). 46 | 47 | It is recommended to backup your data first. 48 | 49 | This document provides the necessary steps for installation of TeslaMate Telegram Bot on an any system that runs Docker. 50 | 51 | This setup is recommended only if you are running TeslaMate Telegram Bot **on your home network**, as otherwise your telegram API tokens might be at risk. 52 | 53 | 1. Create a file called `docker-compose.yml` with the following content (adopt with your own values): 54 | 55 | ```yml title="docker-compose.yml" 56 | services: 57 | teslamatetelegrambot: 58 | image: teslamatetelegrambot/teslamatetelegrambot:latest 59 | restart: unless-stopped 60 | environment: 61 | # - CAR_ID=1 # optional, defaults to 1 62 | - MQTT_BROKER_HOST=IP_Address # defaults to 127.0.0.1 63 | # - MQTT_BROKER_PORT=1883 #optional, defaults to 1883 64 | # - MQTT_BROKER_USERNAME=username #optional, only needed when broker has authentication enabled 65 | # - MQTT_BROKER_PASSWORD=password #optional, only needed when broker has authentication enabled 66 | # - MQTT_NAMESPACE=namespace # optional, only needed when you specified MQTT_NAMESPACE on your TeslaMate installation 67 | - TELEGRAM_BOT_API_KEY=secret_api_key 68 | - TELEGRAM_BOT_CHAT_ID=secret_chat_id 69 | ports: 70 | - 1883 71 | ``` 72 | 73 | 2. Build and start the docker container with `docker compose up`. To run the containers in the background add the `-d` flag: 74 | 75 | ```bash 76 | docker compose up -d 77 | ``` 78 | 79 | ## Update 80 | 81 | Check out the [release notes](https://github.com/JakobLichterfeld/TeslaMate_Telegram_Bot/releases) before upgrading! 82 | 83 | Pull the new images: 84 | 85 | ```bash 86 | docker compose pull 87 | ``` 88 | 89 | and restart the stack with `docker compose up`. To run the containers in the background add the `-d` flag: 90 | 91 | ```bash 92 | docker compose up -d 93 | ``` 94 | 95 | ## Contributing 96 | 97 | All contributions are welcome and greatly appreciated! 98 | 99 | ## Donation 100 | 101 | Maintaining this project isn't effortless, or free. If you would like to kick in and help me cover those costs, that would be awesome. If you don't, no problem; just share your love and show your support. 102 | 103 |

104 | 105 | Donate with PayPal 106 | 107 |

108 | 109 | ## Disclaimer 110 | 111 | Please note that the use of the Tesla API in general and this software in particular is not endorsed by Tesla. Use at your own risk. 112 | -------------------------------------------------------------------------------- /screenshots/paypal-donate-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobLichterfeld/TeslaMate-Telegram-Bot/4064a9a8fd688bc2159eb3b9116b58c460f3651c/screenshots/paypal-donate-button.png -------------------------------------------------------------------------------- /screenshots/telegram_message_sw_update.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobLichterfeld/TeslaMate-Telegram-Bot/4064a9a8fd688bc2159eb3b9116b58c460f3651c/screenshots/telegram_message_sw_update.jpg -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | paho-mqtt==2.1.0 2 | python-telegram-bot==22.5 3 | -------------------------------------------------------------------------------- /src/teslamate_telegram_bot.py: -------------------------------------------------------------------------------- 1 | """ A simple Telegram bot that listens to MQTT messages from Teslamate 2 | and sends them to a Telegram chat.""" 3 | import os 4 | import sys 5 | import logging 6 | import asyncio 7 | import paho.mqtt.client as mqtt 8 | from telegram import Bot 9 | from telegram.constants import ParseMode 10 | 11 | ############################################################################## 12 | 13 | # Default values 14 | CAR_ID_DEFAULT = 1 15 | MQTT_BROKER_HOST_DEFAULT = '127.0.0.1' 16 | MQTT_BROKER_PORT_DEFAULT = 1883 17 | MQTT_BROKER_KEEPALIVE = 60 18 | MQTT_BROKER_USERNAME_DEFAULT = '' 19 | MQTT_BROKER_PASSWORD_DEFAULT = '' 20 | MQTT_NAMESPACE_DEFAULT = '' 21 | 22 | # Environment variables 23 | TELEGRAM_BOT_API_KEY = 'TELEGRAM_BOT_API_KEY' 24 | TELEGRAM_BOT_CHAT_ID = 'TELEGRAM_BOT_CHAT_ID' 25 | MQTT_BROKER_USERNAME = 'MQTT_BROKER_USERNAME' 26 | MQTT_BROKER_PASSWORD = 'MQTT_BROKER_PASSWORD' 27 | MQTT_BROKER_HOST = 'MQTT_BROKER_HOST' 28 | MQTT_BROKER_PORT = 'MQTT_BROKER_PORT' 29 | MQTT_NAMESPACE = 'MQTT_NAMESPACE' 30 | CAR_ID = 'CAR_ID' 31 | 32 | ############################################################################## 33 | 34 | # Logging 35 | # Configure the logging module to output info level logs and above 36 | logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") 37 | 38 | 39 | # Global state 40 | class State: 41 | """ A class to hold the global state of the application.""" 42 | def __init__(self): 43 | self.update_available = False # Flag to indicate if an update is available 44 | self.update_available_message_sent = False # Flag to indicate if the message has been sent 45 | self.update_version = "unknown" # The version of the update 46 | 47 | 48 | # Global state 49 | state = State() 50 | 51 | 52 | def get_env_variable(var_name, default_value=None): 53 | """ Get the environment variable or return a default value""" 54 | logging.debug("Getting environment variable %s", var_name) 55 | var_value = os.getenv(var_name, default_value) 56 | logging.debug("Environment variable %s: %s", var_name, var_value) 57 | if var_value is None and var_name in [TELEGRAM_BOT_API_KEY, TELEGRAM_BOT_CHAT_ID]: 58 | error_message_get_env_variable = f"Error: Please set the environment variable {var_name} and try again." 59 | raise EnvironmentError(error_message_get_env_variable) 60 | return var_value 61 | 62 | 63 | # MQTT topics 64 | try: 65 | car_id = int(get_env_variable(CAR_ID, CAR_ID_DEFAULT)) 66 | except ValueError as value_error_car_id: 67 | ERROR_MESSAGE_CAR_ID = (f"Error: Please set the environment variable {CAR_ID} " 68 | f"to a valid number and try again." 69 | ) 70 | raise EnvironmentError(ERROR_MESSAGE_CAR_ID) from value_error_car_id 71 | 72 | 73 | namespace = get_env_variable(MQTT_NAMESPACE, MQTT_NAMESPACE_DEFAULT) 74 | if namespace: 75 | logging.info("Using MQTT namespace: %s", namespace) 76 | teslamate_mqtt_topic_base = f"teslamate/{namespace}/cars/{car_id}/" 77 | else: 78 | teslamate_mqtt_topic_base = f"teslamate/cars/{car_id}/" 79 | 80 | TESLAMATE_MQTT_TOPIC_UPDATE_AVAILABLE = teslamate_mqtt_topic_base + "update_available" 81 | TESLAMATE_MQTT_TOPIC_UPDATE_VERSION = teslamate_mqtt_topic_base + "update_version" 82 | 83 | 84 | def on_connect(client, userdata, flags, reason_code, properties=None): # pylint: disable=unused-argument 85 | """ The callback for when the client receives a CONNACK response from the server.""" 86 | logging.debug("Connected with result code: %s", reason_code) 87 | if reason_code == "Unsupported protocol version": 88 | logging.error("Unsupported protocol version") 89 | sys.exit(1) 90 | if reason_code == "Client identifier not valid": 91 | logging.error("Client identifier not valid") 92 | sys.exit(1) 93 | if reason_code == 0: 94 | logging.info("Connected successfully to MQTT broker") 95 | else: 96 | logging.error("Connection failed") 97 | sys.exit(1) 98 | 99 | # Subscribing in on_connect() means that if we lose the connection and 100 | # reconnect then subscriptions will be renewed. 101 | logging.info("Subscribing to MQTT topics:") 102 | 103 | client.subscribe(TESLAMATE_MQTT_TOPIC_UPDATE_AVAILABLE) 104 | logging.info("Subscribed to MQTT topic: %s", TESLAMATE_MQTT_TOPIC_UPDATE_AVAILABLE) 105 | 106 | client.subscribe(TESLAMATE_MQTT_TOPIC_UPDATE_VERSION) 107 | logging.info("Subscribed to MQTT topic: %s", TESLAMATE_MQTT_TOPIC_UPDATE_VERSION) 108 | 109 | logging.info("Subscribed to all MQTT topics.") 110 | 111 | logging.info("Waiting for MQTT messages...") 112 | 113 | 114 | def on_message(client, userdata, msg): # pylint: disable=unused-argument 115 | """ The callback for when a PUBLISH message is received from the server.""" 116 | global state # pylint: disable=global-variable-not-assigned, # noqa: F824 117 | logging.debug("Received message: %s %s", msg.topic, msg.payload.decode()) 118 | 119 | if msg.topic == TESLAMATE_MQTT_TOPIC_UPDATE_VERSION: 120 | state.update_version = msg.payload.decode() 121 | logging.info("Update to version %s available.", state.update_version) 122 | 123 | if msg.topic == TESLAMATE_MQTT_TOPIC_UPDATE_AVAILABLE: 124 | state.update_available = msg.payload.decode() == "true" 125 | if msg.payload.decode() == "true": 126 | logging.info("A new SW update to version: %s for your Tesla is available!", state.update_version) 127 | if msg.payload.decode() == "false": 128 | logging.debug("No SW update available.") 129 | state.update_available_message_sent = False # Reset the message sent flag 130 | 131 | 132 | def setup_mqtt_client(): 133 | """ Setup the MQTT client """ 134 | logging.info("Setting up the MQTT client...") 135 | client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) 136 | client.on_connect = on_connect 137 | client.on_message = on_message 138 | 139 | username = get_env_variable(MQTT_BROKER_USERNAME, MQTT_BROKER_USERNAME_DEFAULT) 140 | password = get_env_variable(MQTT_BROKER_PASSWORD, MQTT_BROKER_PASSWORD_DEFAULT) 141 | client.username_pw_set(username, password) 142 | 143 | host = get_env_variable(MQTT_BROKER_HOST, MQTT_BROKER_HOST_DEFAULT) 144 | try: 145 | port = int(get_env_variable(MQTT_BROKER_PORT, MQTT_BROKER_PORT_DEFAULT)) 146 | except ValueError as value_error_mqtt_broker_port: 147 | error_message_mqtt_broker_port = (f"Error: Please set the environment variable {MQTT_BROKER_PORT} " 148 | f"to a valid number and try again." 149 | ) 150 | raise EnvironmentError(error_message_mqtt_broker_port) from value_error_mqtt_broker_port 151 | logging.info("Connect to MQTT broker at %s:%s", host, port) 152 | client.connect(host, port, MQTT_BROKER_KEEPALIVE) 153 | 154 | return client 155 | 156 | 157 | def setup_telegram_bot(): 158 | """ Setup the Telegram bot """ 159 | logging.info("Setting up the Telegram bot...") 160 | bot = Bot(get_env_variable(TELEGRAM_BOT_API_KEY)) 161 | try: 162 | chat_id = int(get_env_variable(TELEGRAM_BOT_CHAT_ID)) 163 | except ValueError as value_error_chat_id: 164 | error_message_chat_id = (f"Error: Please set the environment variable {TELEGRAM_BOT_CHAT_ID} " 165 | f"to a valid number and try again." 166 | ) 167 | raise EnvironmentError(error_message_chat_id) from value_error_chat_id 168 | 169 | logging.info("Connected to Telegram bot successfully.") 170 | return bot, chat_id 171 | 172 | 173 | async def check_state_and_send_messages(bot, chat_id): 174 | """ Check the state and send messages if necessary """ 175 | logging.debug("Checking state and sending messages...") 176 | global state # pylint: disable=global-variable-not-assigned, # noqa: F824 177 | 178 | if state.update_available and not state.update_available_message_sent: 179 | logging.debug("Update available and message not sent.") 180 | if state.update_version not in ("unknown", ""): 181 | logging.info("A new SW update to version: %s for your Tesla is available!", state.update_version) 182 | message_text = "" \ 183 | "SW Update 🎁" \ 184 | "\n" \ 185 | "A new SW update to version: " \ 186 | + state.update_version \ 187 | + " for your Tesla is available!" 188 | await send_telegram_message_to_chat_id(bot, chat_id, message_text) 189 | 190 | # Mark the message as sent 191 | state.update_available_message_sent = True 192 | logging.debug("Message sent flag set.") 193 | 194 | 195 | async def send_telegram_message_to_chat_id(bot, chat_id, message_text_to_send): 196 | """ Send a message to a chat ID """ 197 | logging.debug("Sending message.") 198 | await bot.send_message( 199 | chat_id, 200 | text=message_text_to_send, 201 | parse_mode=ParseMode.HTML, 202 | ) 203 | logging.debug("Message sent.") 204 | 205 | 206 | # Main function 207 | async def main(): 208 | """ Main function""" 209 | logging.info("Starting the Teslamate Telegram Bot.") 210 | try: 211 | client = setup_mqtt_client() 212 | bot, chat_id = setup_telegram_bot() 213 | start_message = "" \ 214 | "Teslamate Telegram Bot started ✅" \ 215 | "\n" \ 216 | "and will notify as soon as a new SW version is available." 217 | await send_telegram_message_to_chat_id(bot, chat_id, start_message) 218 | 219 | client.loop_start() 220 | try: 221 | while True: 222 | await check_state_and_send_messages(bot, chat_id) 223 | 224 | logging.debug("Sleeping for 30 second.") 225 | await asyncio.sleep(30) 226 | except KeyboardInterrupt: 227 | logging.info("Exiting after receiving SIGINT (Ctrl+C) signal.") 228 | except EnvironmentError as e: 229 | logging.error(e) 230 | logging.info("Sleeping for 2 minutes before exiting or restarting, depending on your restart policy.") 231 | await asyncio.sleep(120) 232 | 233 | # clean exit 234 | logging.info("Disconnecting from MQTT broker.") 235 | client.disconnect() 236 | logging.info("Disconnected from MQTT broker.") 237 | client.loop_stop() 238 | logging.info("Exiting the Teslamate Telegram bot.") 239 | stop_message = "" \ 240 | "Teslamate Telegram Bot stopped. 🛑" \ 241 | "\n " 242 | await send_telegram_message_to_chat_id(bot, chat_id, stop_message) 243 | await bot.close() 244 | 245 | 246 | # Entry point 247 | if __name__ == "__main__": 248 | asyncio.run(main()) 249 | --------------------------------------------------------------------------------