mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Compare commits
	
		
			351 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					431d3578a5 | ||
| 
						 | 
					5f02d86841 | ||
| 
						 | 
					a7ec57d4be | ||
| 
						 | 
					1ea5cc497f | ||
| 
						 | 
					b601cf6bc6 | ||
| 
						 | 
					5057caa7fc | ||
| 
						 | 
					95bef53d37 | ||
| 
						 | 
					ea019a057b | ||
| 
						 | 
					b860a317b9 | ||
| 
						 | 
					9591c903f7 | ||
| 
						 | 
					65fbb8bc60 | ||
| 
						 | 
					97428f2ba2 | ||
| 
						 | 
					9ab6a7b7ff | ||
| 
						 | 
					e2ad6fe3d8 | ||
| 
						 | 
					6781d08c9b | ||
| 
						 | 
					828f7946ea | ||
| 
						 | 
					23c663d5d4 | ||
| 
						 | 
					6a99789c92 | ||
| 
						 | 
					52e13164b4 | ||
| 
						 | 
					28f2582256 | ||
| 
						 | 
					652f6058d1 | ||
| 
						 | 
					717aab7c8b | ||
| 
						 | 
					5e799b5284 | ||
| 
						 | 
					9f36b25d4e | ||
| 
						 | 
					c8058e9636 | ||
| 
						 | 
					f2474c5c45 | ||
| 
						 | 
					7acc36d39d | ||
| 
						 | 
					b01db991a5 | ||
| 
						 | 
					8c849b9002 | ||
| 
						 | 
					6a0d4cb5a9 | ||
| 
						 | 
					72002ce70e | ||
| 
						 | 
					c67539cf5b | ||
| 
						 | 
					dcd3d2084d | ||
| 
						 | 
					585bb6dac8 | ||
| 
						 | 
					cac3055261 | ||
| 
						 | 
					6f7e6cc944 | ||
| 
						 | 
					0a841fcc50 | ||
| 
						 | 
					3c64c9b0e9 | ||
| 
						 | 
					34df25da39 | ||
| 
						 | 
					9586fb95d1 | ||
| 
						 | 
					3ee6348e41 | ||
| 
						 | 
					543f2c8152 | ||
| 
						 | 
					16d11be213 | ||
| 
						 | 
					e49b568fd4 | ||
| 
						 | 
					22ab830ff3 | ||
| 
						 | 
					095d3181cd | ||
| 
						 | 
					9aa14a2e83 | ||
| 
						 | 
					ac15ce576b | ||
| 
						 | 
					498b59e998 | ||
| 
						 | 
					63c420254a | ||
| 
						 | 
					765e641d08 | ||
| 
						 | 
					be16d10b7d | ||
| 
						 | 
					4b808611e9 | ||
| 
						 | 
					7afe202e20 | ||
| 
						 | 
					039810eef3 | ||
| 
						 | 
					ff43b45113 | ||
| 
						 | 
					7cd4c3bdd3 | ||
| 
						 | 
					c12c9e97c2 | ||
| 
						 | 
					b3169deda7 | ||
| 
						 | 
					0ea41e2f71 | ||
| 
						 | 
					3afb564a48 | ||
| 
						 | 
					7ff3f752e2 | ||
| 
						 | 
					d821ead92a | ||
| 
						 | 
					e42ce64127 | ||
| 
						 | 
					9d2b0b4e03 | ||
| 
						 | 
					b5e6ae0d69 | ||
| 
						 | 
					08f1eac8b2 | ||
| 
						 | 
					6ed3da33a2 | ||
| 
						 | 
					a9a00f139b | ||
| 
						 | 
					63d8071dbd | ||
| 
						 | 
					d20caa9d60 | ||
| 
						 | 
					7e40d4246c | ||
| 
						 | 
					5a2b14cfa4 | ||
| 
						 | 
					f2d218e5ad | ||
| 
						 | 
					b493d5bba5 | ||
| 
						 | 
					c9055f2aef | ||
| 
						 | 
					9fed7cab5f | ||
| 
						 | 
					eb5c4b7c4f | ||
| 
						 | 
					2ab3534a4b | ||
| 
						 | 
					9816e677a6 | ||
| 
						 | 
					fc01a70b65 | ||
| 
						 | 
					7221337442 | ||
| 
						 | 
					051a1e4772 | ||
| 
						 | 
					274741a9d5 | ||
| 
						 | 
					25c01adf51 | ||
| 
						 | 
					bf2d54c3ef | ||
| 
						 | 
					49cb8fd9d3 | ||
| 
						 | 
					e536316e3d | ||
| 
						 | 
					a6c46eb8e5 | ||
| 
						 | 
					1a270374e0 | ||
| 
						 | 
					e73eafbd88 | ||
| 
						 | 
					9fc3e05b76 | ||
| 
						 | 
					31c604331c | ||
| 
						 | 
					3fcdaaefe0 | ||
| 
						 | 
					20dd744680 | ||
| 
						 | 
					e4636b99f7 | ||
| 
						 | 
					10e7abb579 | ||
| 
						 | 
					d3f03b7acb | ||
| 
						 | 
					7e53fc9d6a | ||
| 
						 | 
					0059a6de46 | ||
| 
						 | 
					22e1758d5b | ||
| 
						 | 
					59cdc32970 | ||
| 
						 | 
					adb51cf733 | ||
| 
						 | 
					d97a9bf8e8 | ||
| 
						 | 
					f034472e2a | ||
| 
						 | 
					ed328d2df8 | ||
| 
						 | 
					1520dc8755 | ||
| 
						 | 
					bf601c3126 | ||
| 
						 | 
					ab48e4a466 | ||
| 
						 | 
					8ef0f5b047 | ||
| 
						 | 
					2c14d134be | ||
| 
						 | 
					9cd21bb5a0 | ||
| 
						 | 
					bd061ac2ee | ||
| 
						 | 
					dd3e821857 | ||
| 
						 | 
					b38b7019ea | ||
| 
						 | 
					2c71ee7853 | ||
| 
						 | 
					540c62061d | ||
| 
						 | 
					221ef07c8b | ||
| 
						 | 
					29fc7ea154 | ||
| 
						 | 
					7b157aeff1 | ||
| 
						 | 
					e50644edee | ||
| 
						 | 
					5f619e6f01 | ||
| 
						 | 
					b266fb37a3 | ||
| 
						 | 
					c9caf44c2e | ||
| 
						 | 
					0c87a9ad2c | ||
| 
						 | 
					c680b437f5 | ||
| 
						 | 
					4988349677 | ||
| 
						 | 
					e35d56defe | ||
| 
						 | 
					5c86f332b2 | ||
| 
						 | 
					002861f13b | ||
| 
						 | 
					dbec3d7c50 | ||
| 
						 | 
					89cde158d6 | ||
| 
						 | 
					d7b76aadb2 | ||
| 
						 | 
					e09fefd389 | ||
| 
						 | 
					febc485da6 | ||
| 
						 | 
					2ae709c2ba | ||
| 
						 | 
					0ccfdd4711 | ||
| 
						 | 
					0e59243b83 | ||
| 
						 | 
					01bbd04a5a | ||
| 
						 | 
					a3b2d384f5 | ||
| 
						 | 
					50238f8d72 | ||
| 
						 | 
					704470d606 | ||
| 
						 | 
					f7e6195466 | ||
| 
						 | 
					a0bb7c3ed0 | ||
| 
						 | 
					e3a6c9a6cf | ||
| 
						 | 
					99598d87a9 | ||
| 
						 | 
					b5df50893b | ||
| 
						 | 
					f46b3d15cd | ||
| 
						 | 
					041b4ec66e | ||
| 
						 | 
					703e9673c2 | ||
| 
						 | 
					e7bd93b4b0 | ||
| 
						 | 
					a401c71d3e | ||
| 
						 | 
					5bae233334 | ||
| 
						 | 
					c4edd3047f | ||
| 
						 | 
					c50da1593a | ||
| 
						 | 
					1d06426281 | ||
| 
						 | 
					9c5b693dd5 | ||
| 
						 | 
					5fecc70db1 | ||
| 
						 | 
					ff24023b39 | ||
| 
						 | 
					1a04e2d1b8 | ||
| 
						 | 
					52c4dd0e35 | ||
| 
						 | 
					ff90f6a440 | ||
| 
						 | 
					e24d5c172f | ||
| 
						 | 
					0918f452a0 | ||
| 
						 | 
					839fe49e61 | ||
| 
						 | 
					ff050d634a | ||
| 
						 | 
					228670df78 | ||
| 
						 | 
					cfe4638665 | ||
| 
						 | 
					b7352b1345 | ||
| 
						 | 
					32ae8fc2d0 | ||
| 
						 | 
					86df4c1d8d | ||
| 
						 | 
					dc4a88029c | ||
| 
						 | 
					5da9b2ede7 | ||
| 
						 | 
					29cfcfaf0f | ||
| 
						 | 
					61bfd347ea | ||
| 
						 | 
					b7436c0b22 | ||
| 
						 | 
					8a45dfac5c | ||
| 
						 | 
					69f5d8cd0f | ||
| 
						 | 
					9a57e8fcb0 | ||
| 
						 | 
					a9d75ca4f4 | ||
| 
						 | 
					ccb6fc3010 | ||
| 
						 | 
					4e9a05fe11 | ||
| 
						 | 
					8a294e4134 | ||
| 
						 | 
					aad9a539c1 | ||
| 
						 | 
					fd6ac529fb | ||
| 
						 | 
					009cea1abf | ||
| 
						 | 
					4c3c14ec32 | ||
| 
						 | 
					636c9db1e3 | ||
| 
						 | 
					71f625bbd3 | ||
| 
						 | 
					aea2e9a6bb | ||
| 
						 | 
					3f6f3c14c4 | ||
| 
						 | 
					b1d77b7c03 | ||
| 
						 | 
					49233e4734 | ||
| 
						 | 
					e2b5ecb78b | ||
| 
						 | 
					351ce67eae | ||
| 
						 | 
					44af5e439c | ||
| 
						 | 
					dbc0d500d8 | ||
| 
						 | 
					86736aa480 | ||
| 
						 | 
					57eb05c0e3 | ||
| 
						 | 
					f6fe6e6bff | ||
| 
						 | 
					18560f9430 | ||
| 
						 | 
					cb0ba647ed | ||
| 
						 | 
					f9fceb7ffc | ||
| 
						 | 
					2697c9465b | ||
| 
						 | 
					8d204655be | ||
| 
						 | 
					8414a22356 | ||
| 
						 | 
					36e4a8b444 | ||
| 
						 | 
					949c71dc97 | ||
| 
						 | 
					a6f6b8da7f | ||
| 
						 | 
					a9f123e864 | ||
| 
						 | 
					a64a505817 | ||
| 
						 | 
					352004221e | ||
| 
						 | 
					fc6a3e31c2 | ||
| 
						 | 
					a32b58fdf1 | ||
| 
						 | 
					2d50ecbecf | ||
| 
						 | 
					87f1ffec05 | ||
| 
						 | 
					f0dfde9fa1 | ||
| 
						 | 
					e36dc2d05e | ||
| 
						 | 
					08c8fa2c90 | ||
| 
						 | 
					fe6621357e | ||
| 
						 | 
					4a0067a2c5 | ||
| 
						 | 
					b270ff335d | ||
| 
						 | 
					7d2fcf59fd | ||
| 
						 | 
					d26c43103d | ||
| 
						 | 
					389889ad70 | ||
| 
						 | 
					8aa73bba10 | ||
| 
						 | 
					52639a0a7c | ||
| 
						 | 
					a1e10f384e | ||
| 
						 | 
					27d4b3b8ad | ||
| 
						 | 
					b9d55fd1ed | ||
| 
						 | 
					4c55b9c58c | ||
| 
						 | 
					51ab0f0b78 | ||
| 
						 | 
					bf0cce4ad8 | ||
| 
						 | 
					60e6366521 | ||
| 
						 | 
					072b2c445c | ||
| 
						 | 
					219fe41831 | ||
| 
						 | 
					dcc8bb83af | ||
| 
						 | 
					a8e3521f3c | ||
| 
						 | 
					706dc6d116 | ||
| 
						 | 
					84accb6df6 | ||
| 
						 | 
					8421570b18 | ||
| 
						 | 
					d355543ac9 | ||
| 
						 | 
					3a597c5aa6 | ||
| 
						 | 
					c7dddaded4 | ||
| 
						 | 
					aae4b2ea5d | ||
| 
						 | 
					310e2a0b20 | ||
| 
						 | 
					0b04d143ac | ||
| 
						 | 
					53c231a7eb | ||
| 
						 | 
					d44ce82aa1 | ||
| 
						 | 
					a055de48e4 | ||
| 
						 | 
					37b8d665fe | ||
| 
						 | 
					dd7c8dabb1 | ||
| 
						 | 
					e41a9875e3 | ||
| 
						 | 
					c5c42c4338 | ||
| 
						 | 
					531428b8b0 | ||
| 
						 | 
					ea8068e001 | ||
| 
						 | 
					7842a55c81 | ||
| 
						 | 
					51d39862b1 | ||
| 
						 | 
					bfea6ca79b | ||
| 
						 | 
					6297395018 | ||
| 
						 | 
					a5b49dbfa6 | ||
| 
						 | 
					7c0d777173 | ||
| 
						 | 
					74878276fc | ||
| 
						 | 
					226e3b1dad | ||
| 
						 | 
					7752794fc5 | ||
| 
						 | 
					b3094d6a53 | ||
| 
						 | 
					e3640e710f | ||
| 
						 | 
					2ef64b55c5 | ||
| 
						 | 
					7f6672bb37 | ||
| 
						 | 
					68a3b31628 | ||
| 
						 | 
					1b35855e68 | ||
| 
						 | 
					1e1837000d | ||
| 
						 | 
					e2d5257632 | ||
| 
						 | 
					387c75793b | ||
| 
						 | 
					4f3a74d08a | ||
| 
						 | 
					fdbc59a159 | ||
| 
						 | 
					0db37bb55c | ||
| 
						 | 
					2ff2750628 | ||
| 
						 | 
					eae5c17b87 | ||
| 
						 | 
					a59cde91ad | ||
| 
						 | 
					1f243ae37e | ||
| 
						 | 
					603f82977e | ||
| 
						 | 
					2d70422a6f | ||
| 
						 | 
					e2c8b21195 | ||
| 
						 | 
					7adaeacd0b | ||
| 
						 | 
					3aaa92fdff | ||
| 
						 | 
					5efd076c08 | ||
| 
						 | 
					1ca241615d | ||
| 
						 | 
					b8aa84002a | ||
| 
						 | 
					10cc0b1d5b | ||
| 
						 | 
					11d9c203c1 | ||
| 
						 | 
					c9ab454c3c | ||
| 
						 | 
					4a55692885 | ||
| 
						 | 
					88c129e705 | ||
| 
						 | 
					80b48f01fb | ||
| 
						 | 
					642bc91a76 | ||
| 
						 | 
					d69926ee56 | ||
| 
						 | 
					4758403d44 | ||
| 
						 | 
					4b0ec5c28a | ||
| 
						 | 
					4b2a9e5e49 | ||
| 
						 | 
					1449c51d49 | ||
| 
						 | 
					a451705e0b | ||
| 
						 | 
					2e6db39173 | ||
| 
						 | 
					373f75253c | ||
| 
						 | 
					724842084e | ||
| 
						 | 
					8f3635b167 | ||
| 
						 | 
					11605a36f7 | ||
| 
						 | 
					533f81d625 | ||
| 
						 | 
					aacb9e44e8 | ||
| 
						 | 
					c6e3f1bca6 | ||
| 
						 | 
					a933d4aeb6 | ||
| 
						 | 
					caa5b20791 | ||
| 
						 | 
					e2ad9ed746 | ||
| 
						 | 
					32c0e7c2ae | ||
| 
						 | 
					6c564c7b7f | ||
| 
						 | 
					c81e3a3be4 | ||
| 
						 | 
					6b1b9ef7ec | ||
| 
						 | 
					c26a8b8718 | ||
| 
						 | 
					4a89a475bd | ||
| 
						 | 
					8cf15c7f5c | ||
| 
						 | 
					adc76ca1b8 | ||
| 
						 | 
					8f8892440c | ||
| 
						 | 
					570843150d | ||
| 
						 | 
					f3fc9e4142 | ||
| 
						 | 
					075fcb77a8 | ||
| 
						 | 
					e5899ff717 | ||
| 
						 | 
					2fb3970027 | ||
| 
						 | 
					1a4efa1b8c | ||
| 
						 | 
					72f656ffef | ||
| 
						 | 
					b60239d5e5 | ||
| 
						 | 
					d02e280c3c | ||
| 
						 | 
					6535b0966e | ||
| 
						 | 
					82dbacbee5 | ||
| 
						 | 
					2432901974 | ||
| 
						 | 
					ebb5d58c14 | ||
| 
						 | 
					605e365405 | ||
| 
						 | 
					5ab995d8ca | ||
| 
						 | 
					4248741b11 | ||
| 
						 | 
					4b8ecc7634 | ||
| 
						 | 
					25d04c759c | ||
| 
						 | 
					b4ec84030e | ||
| 
						 | 
					29e8761373 | ||
| 
						 | 
					a04299c59e | ||
| 
						 | 
					d7bf3c51d9 | ||
| 
						 | 
					ac0b095941 | ||
| 
						 | 
					cda9bad233 | ||
| 
						 | 
					41db8a1264 | ||
| 
						 | 
					e7e785fd60 | ||
| 
						 | 
					300d3a1f46 | ||
| 
						 | 
					356554c08d | ||
| 
						 | 
					ced28ad006 | 
@@ -25,3 +25,4 @@ indent_size = 2
 | 
			
		||||
[*.{yaml,yml}]
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 2
 | 
			
		||||
quote_type = single
 | 
			
		||||
							
								
								
									
										9
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,6 +18,7 @@ jobs:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["hassio", "docker"]
 | 
			
		||||
@@ -25,7 +26,7 @@ jobs:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="2.3.4"
 | 
			
		||||
          base_version="2.6.0"
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
@@ -37,9 +38,9 @@ jobs:
 | 
			
		||||
            dockerfile="docker/Dockerfile"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          echo "::set-env name=BUILD_FROM::${build_from}"
 | 
			
		||||
          echo "::set-env name=BUILD_TO::${build_to}"
 | 
			
		||||
          echo "::set-env name=DOCKERFILE::${dockerfile}"
 | 
			
		||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
			
		||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
			
		||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "${BUILD_TO}:dev" || true
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										41
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -11,45 +11,6 @@ on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  # A fast overview job that checks only changed files
 | 
			
		||||
  overview:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    container: esphome/esphome-lint:latest
 | 
			
		||||
    steps:
 | 
			
		||||
      # Also fetch history and dev branch so that we can check which files changed
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
      - name: Fetch dev branch
 | 
			
		||||
        run: git fetch origin dev
 | 
			
		||||
 | 
			
		||||
      # Cache the .pio directory with (primarily) library dependencies
 | 
			
		||||
      - name: Cache .pio lib_deps
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: .pio
 | 
			
		||||
          key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            lint-cpp-pio-
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
        run: pio init --ide atom
 | 
			
		||||
 | 
			
		||||
      - name: Register problem matchers
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Run a quick lint over all changed files
 | 
			
		||||
        run: script/quicklint
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-clang-format:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
@@ -83,6 +44,7 @@ jobs:
 | 
			
		||||
    container: esphome/esphome-lint:latest
 | 
			
		||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        split: [1, 2, 3, 4]
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -146,6 +108,7 @@ jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							@@ -41,6 +41,7 @@ jobs:
 | 
			
		||||
    container: esphome/esphome-lint:latest
 | 
			
		||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        split: [1, 2, 3, 4]
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -104,6 +105,7 @@ jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
@@ -187,10 +189,10 @@ jobs:
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
          TAG="${GITHUB_SHA:0:7}"
 | 
			
		||||
          echo "::set-env name=TAG::${TAG}"
 | 
			
		||||
          echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="2.3.4"
 | 
			
		||||
          base_version="2.6.0"
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
@@ -202,9 +204,9 @@ jobs:
 | 
			
		||||
            dockerfile="docker/Dockerfile"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          echo "::set-env name=BUILD_FROM::${build_from}"
 | 
			
		||||
          echo "::set-env name=BUILD_TO::${build_to}"
 | 
			
		||||
          echo "::set-env name=DOCKERFILE::${dockerfile}"
 | 
			
		||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
			
		||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
			
		||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "${BUILD_TO}:dev" || true
 | 
			
		||||
@@ -241,7 +243,7 @@ jobs:
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        TAG="${GITHUB_SHA:0:7}"
 | 
			
		||||
        echo "::set-env name=TAG::${TAG}"
 | 
			
		||||
        echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										41
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -40,6 +40,7 @@ jobs:
 | 
			
		||||
    container: esphome/esphome-lint:latest
 | 
			
		||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        split: [1, 2, 3, 4]
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -103,6 +104,7 @@ jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
@@ -207,10 +209,10 @@ jobs:
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
          TAG="${GITHUB_REF#refs/tags/v}"
 | 
			
		||||
          echo "::set-env name=TAG::${TAG}"
 | 
			
		||||
          echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="2.3.4"
 | 
			
		||||
          base_version="2.6.0"
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
@@ -222,13 +224,20 @@ jobs:
 | 
			
		||||
            dockerfile="docker/Dockerfile"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
 | 
			
		||||
            cache_tag="beta"
 | 
			
		||||
          else
 | 
			
		||||
            cache_tag="latest"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          # Set env variables so these values don't need to be calculated again
 | 
			
		||||
          echo "::set-env name=BUILD_FROM::${build_from}"
 | 
			
		||||
          echo "::set-env name=BUILD_TO::${build_to}"
 | 
			
		||||
          echo "::set-env name=DOCKERFILE::${dockerfile}"
 | 
			
		||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
			
		||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
			
		||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
			
		||||
          echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "${BUILD_TO}:latest" || true
 | 
			
		||||
          docker pull "${BUILD_TO}:${CACHE_TAG}" || true
 | 
			
		||||
      - name: Register QEMU binfmt
 | 
			
		||||
        run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes
 | 
			
		||||
      - run: |
 | 
			
		||||
@@ -236,7 +245,7 @@ jobs:
 | 
			
		||||
            --build-arg "BUILD_FROM=${BUILD_FROM}" \
 | 
			
		||||
            --build-arg "BUILD_VERSION=${TAG}" \
 | 
			
		||||
            --tag "${BUILD_TO}:${TAG}" \
 | 
			
		||||
            --cache-from "${BUILD_TO}:latest" \
 | 
			
		||||
            --cache-from "${BUILD_TO}:${CACHE_TAG}" \
 | 
			
		||||
            --file "${DOCKERFILE}" \
 | 
			
		||||
            .
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
@@ -270,7 +279,7 @@ jobs:
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        TAG="${GITHUB_REF#refs/tags/v}"
 | 
			
		||||
        echo "::set-env name=TAG::${TAG}"
 | 
			
		||||
        echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
@@ -300,3 +309,19 @@ jobs:
 | 
			
		||||
          esphome/esphome-amd64:${TAG} \
 | 
			
		||||
          esphome/esphome-armv7:${TAG}
 | 
			
		||||
        docker manifest push esphome/esphome:latest
 | 
			
		||||
 | 
			
		||||
  deploy-hassio-repo:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [deploy-docker]
 | 
			
		||||
    steps:
 | 
			
		||||
      - env:
 | 
			
		||||
          TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
 | 
			
		||||
        run: |
 | 
			
		||||
          TAG="${GITHUB_REF#refs/tags/v}"
 | 
			
		||||
          curl \
 | 
			
		||||
            -u ":$TOKEN" \
 | 
			
		||||
            -X POST \
 | 
			
		||||
            -H "Accept: application/vnd.github.v3+json" \
 | 
			
		||||
            https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
 | 
			
		||||
            -d "{\"ref\":\"master\",\"inputs\":{\"version\":\"$TAG\"}}"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -81,7 +81,8 @@ venv.bak/
 | 
			
		||||
.pioenvs
 | 
			
		||||
.piolibdeps
 | 
			
		||||
.pio
 | 
			
		||||
.vscode
 | 
			
		||||
.vscode/
 | 
			
		||||
!.vscode/tasks.json
 | 
			
		||||
CMakeListsPrivate.txt
 | 
			
		||||
CMakeLists.txt
 | 
			
		||||
 | 
			
		||||
@@ -119,3 +120,4 @@ config/
 | 
			
		||||
tests/build/
 | 
			
		||||
tests/.esphome/
 | 
			
		||||
/.temp-clang-tidy.cpp
 | 
			
		||||
.pio/
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
{
 | 
			
		||||
    "version": "2.0.0",
 | 
			
		||||
    "tasks": [
 | 
			
		||||
        {
 | 
			
		||||
            "label": "run",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "python3 -m esphome config dashboard",
 | 
			
		||||
            "problemMatcher": []
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -13,10 +13,13 @@ esphome/core/* @esphome/core
 | 
			
		||||
# Integrations
 | 
			
		||||
esphome/components/ac_dimmer/* @glmnet
 | 
			
		||||
esphome/components/adc/* @esphome/core
 | 
			
		||||
esphome/components/animation/* @syndlex
 | 
			
		||||
esphome/components/api/* @OttoWinter
 | 
			
		||||
esphome/components/async_tcp/* @OttoWinter
 | 
			
		||||
esphome/components/atc_mithermometer/* @ahpohl
 | 
			
		||||
esphome/components/bang_bang/* @OttoWinter
 | 
			
		||||
esphome/components/binary_sensor/* @esphome/core
 | 
			
		||||
esphome/components/canbus/* @danielschramm @mvturnho
 | 
			
		||||
esphome/components/captive_portal/* @OttoWinter
 | 
			
		||||
esphome/components/climate/* @esphome/core
 | 
			
		||||
esphome/components/climate_ir/* @glmnet
 | 
			
		||||
@@ -26,24 +29,37 @@ esphome/components/ct_clamp/* @jesserockz
 | 
			
		||||
esphome/components/debug/* @OttoWinter
 | 
			
		||||
esphome/components/dfplayer/* @glmnet
 | 
			
		||||
esphome/components/dht/* @OttoWinter
 | 
			
		||||
esphome/components/ds1307/* @badbadc0ffee
 | 
			
		||||
esphome/components/exposure_notifications/* @OttoWinter
 | 
			
		||||
esphome/components/ezo/* @ssieb
 | 
			
		||||
esphome/components/fastled_base/* @OttoWinter
 | 
			
		||||
esphome/components/globals/* @esphome/core
 | 
			
		||||
esphome/components/gpio/* @esphome/core
 | 
			
		||||
esphome/components/homeassistant/* @OttoWinter
 | 
			
		||||
esphome/components/i2c/* @esphome/core
 | 
			
		||||
esphome/components/inkplate6/* @jesserockz
 | 
			
		||||
esphome/components/integration/* @OttoWinter
 | 
			
		||||
esphome/components/interval/* @esphome/core
 | 
			
		||||
esphome/components/json/* @OttoWinter
 | 
			
		||||
esphome/components/ledc/* @OttoWinter
 | 
			
		||||
esphome/components/light/* @esphome/core
 | 
			
		||||
esphome/components/logger/* @esphome/core
 | 
			
		||||
esphome/components/mcp23s08/* @SenexCrenshaw
 | 
			
		||||
esphome/components/mcp23s17/* @SenexCrenshaw
 | 
			
		||||
esphome/components/mcp2515/* @danielschramm @mvturnho
 | 
			
		||||
esphome/components/mcp9808/* @k7hpn
 | 
			
		||||
esphome/components/network/* @esphome/core
 | 
			
		||||
esphome/components/nfc/* @jesserockz
 | 
			
		||||
esphome/components/ota/* @esphome/core
 | 
			
		||||
esphome/components/output/* @esphome/core
 | 
			
		||||
esphome/components/pid/* @OttoWinter
 | 
			
		||||
esphome/components/pn532/* @OttoWinter
 | 
			
		||||
esphome/components/pn532/* @OttoWinter @jesserockz
 | 
			
		||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
 | 
			
		||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
 | 
			
		||||
esphome/components/power_supply/* @esphome/core
 | 
			
		||||
esphome/components/rc522/* @glmnet
 | 
			
		||||
esphome/components/rc522_i2c/* @glmnet
 | 
			
		||||
esphome/components/rc522_spi/* @glmnet
 | 
			
		||||
esphome/components/restart/* @esphome/core
 | 
			
		||||
esphome/components/rf_bridge/* @jesserockz
 | 
			
		||||
esphome/components/rtttl/* @glmnet
 | 
			
		||||
@@ -52,12 +68,28 @@ esphome/components/sensor/* @esphome/core
 | 
			
		||||
esphome/components/shutdown/* @esphome/core
 | 
			
		||||
esphome/components/sim800l/* @glmnet
 | 
			
		||||
esphome/components/spi/* @esphome/core
 | 
			
		||||
esphome/components/ssd1322_base/* @kbx81
 | 
			
		||||
esphome/components/ssd1322_spi/* @kbx81
 | 
			
		||||
esphome/components/ssd1325_base/* @kbx81
 | 
			
		||||
esphome/components/ssd1325_spi/* @kbx81
 | 
			
		||||
esphome/components/ssd1327_base/* @kbx81
 | 
			
		||||
esphome/components/ssd1327_i2c/* @kbx81
 | 
			
		||||
esphome/components/ssd1327_spi/* @kbx81
 | 
			
		||||
esphome/components/ssd1331_base/* @kbx81
 | 
			
		||||
esphome/components/ssd1331_spi/* @kbx81
 | 
			
		||||
esphome/components/ssd1351_base/* @kbx81
 | 
			
		||||
esphome/components/ssd1351_spi/* @kbx81
 | 
			
		||||
esphome/components/st7735/* @SenexCrenshaw
 | 
			
		||||
esphome/components/st7789v/* @kbx81
 | 
			
		||||
esphome/components/substitutions/* @esphome/core
 | 
			
		||||
esphome/components/sun/* @OttoWinter
 | 
			
		||||
esphome/components/switch/* @esphome/core
 | 
			
		||||
esphome/components/tcl112/* @glmnet
 | 
			
		||||
esphome/components/teleinfo/* @0hax
 | 
			
		||||
esphome/components/thermostat/* @kbx81
 | 
			
		||||
esphome/components/time/* @OttoWinter
 | 
			
		||||
esphome/components/tm1637/* @glmnet
 | 
			
		||||
esphome/components/tmp102/* @timsavage
 | 
			
		||||
esphome/components/tuya/binary_sensor/* @jesserockz
 | 
			
		||||
esphome/components/tuya/climate/* @jesserockz
 | 
			
		||||
esphome/components/tuya/sensor/* @jesserockz
 | 
			
		||||
@@ -67,3 +99,5 @@ esphome/components/ultrasonic/* @OttoWinter
 | 
			
		||||
esphome/components/version/* @esphome/core
 | 
			
		||||
esphome/components/web_server_base/* @OttoWinter
 | 
			
		||||
esphome/components/whirlpool/* @glmnet
 | 
			
		||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
			
		||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
include LICENSE
 | 
			
		||||
include README.md
 | 
			
		||||
include requirements.txt
 | 
			
		||||
include esphome/dashboard/templates/*.html
 | 
			
		||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
 | 
			
		||||
recursive-include esphome *.cpp *.h *.tcc
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-base-amd64:2.3.4
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-base-amd64:2.6.0
 | 
			
		||||
FROM ${BUILD_FROM}
 | 
			
		||||
 | 
			
		||||
# First install requirements to leverage caching when requirements don't change
 | 
			
		||||
@@ -12,6 +12,13 @@ RUN pip3 install --no-cache-dir -e .
 | 
			
		||||
# Settings for dashboard
 | 
			
		||||
ENV USERNAME="" PASSWORD=""
 | 
			
		||||
 | 
			
		||||
# Expose the dashboard to Docker
 | 
			
		||||
EXPOSE 6052
 | 
			
		||||
 | 
			
		||||
# Run healthcheck (heartbeat)
 | 
			
		||||
HEALTHCHECK --interval=5m --timeout=3s \
 | 
			
		||||
  CMD curl --fail http://localhost:6052 || exit 1
 | 
			
		||||
 | 
			
		||||
# The directory the user should mount their configuration files to
 | 
			
		||||
WORKDIR /config
 | 
			
		||||
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
FROM esphome/esphome-base-amd64:2.3.4
 | 
			
		||||
FROM esphome/esphome-base-amd64:2.6.0
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
FROM esphome/esphome-lint-base:2.3.4
 | 
			
		||||
FROM esphome/esphome-lint-base:2.6.0
 | 
			
		||||
 | 
			
		||||
COPY requirements.txt requirements_test.txt /
 | 
			
		||||
RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt
 | 
			
		||||
 
 | 
			
		||||
@@ -235,6 +235,9 @@ def setup_log(debug=False, quiet=False):
 | 
			
		||||
    logging.getLogger('urllib3').setLevel(logging.WARNING)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        import colorama
 | 
			
		||||
        colorama.init(strip=True)
 | 
			
		||||
 | 
			
		||||
        from colorlog import ColoredFormatter
 | 
			
		||||
        logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
 | 
			
		||||
            colorfmt,
 | 
			
		||||
 
 | 
			
		||||
@@ -181,7 +181,7 @@ class APIClient(threading.Thread):
 | 
			
		||||
                            self._address)
 | 
			
		||||
            _LOGGER.warning("(If this error persists, please set a static IP address: "
 | 
			
		||||
                            "https://esphome.io/components/wifi.html#manual-ips)")
 | 
			
		||||
            raise APIConnectionError(err)
 | 
			
		||||
            raise APIConnectionError(err) from err
 | 
			
		||||
 | 
			
		||||
        _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
 | 
			
		||||
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
			
		||||
@@ -346,12 +346,12 @@ class APIClient(threading.Thread):
 | 
			
		||||
                raise APIConnectionError("No socket!")
 | 
			
		||||
            try:
 | 
			
		||||
                val = self._socket.recv(amount - len(ret))
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                raise APIConnectionError("Socket was closed")
 | 
			
		||||
            except AttributeError as err:
 | 
			
		||||
                raise APIConnectionError("Socket was closed") from err
 | 
			
		||||
            except socket.timeout:
 | 
			
		||||
                continue
 | 
			
		||||
            except OSError as err:
 | 
			
		||||
                raise APIConnectionError(f"Error while receiving data: {err}")
 | 
			
		||||
                raise APIConnectionError(f"Error while receiving data: {err}") from err
 | 
			
		||||
            ret += val
 | 
			
		||||
        return ret
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -84,6 +84,7 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
 | 
			
		||||
                    return cv.Schema([schema])(value)
 | 
			
		||||
                except cv.Invalid as err2:
 | 
			
		||||
                    if 'extra keys not allowed' in str(err2) and len(err2.path) == 2:
 | 
			
		||||
                        # pylint: disable=raise-missing-from
 | 
			
		||||
                        raise err
 | 
			
		||||
                    if 'Unable to find action' in str(err):
 | 
			
		||||
                        raise err2
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,9 @@ static const char *TAG = "ade7953";
 | 
			
		||||
 | 
			
		||||
void ADE7953::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ADE7953:");
 | 
			
		||||
  if (this->has_irq_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  IRQ Pin: GPIO%u", this->irq_pin_number_);
 | 
			
		||||
  }
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
  LOG_SENSOR("  ", "Voltage Sensor", this->voltage_sensor_);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,10 @@ namespace ade7953 {
 | 
			
		||||
 | 
			
		||||
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_irq_pin(uint8_t irq_pin) {
 | 
			
		||||
    has_irq_ = true;
 | 
			
		||||
    irq_pin_number_ = irq_pin;
 | 
			
		||||
  }
 | 
			
		||||
  void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
 | 
			
		||||
  void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
 | 
			
		||||
  void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
 | 
			
		||||
@@ -20,6 +24,11 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setup() override {
 | 
			
		||||
    if (this->has_irq_) {
 | 
			
		||||
      auto pin = GPIOPin(this->irq_pin_number_, INPUT);
 | 
			
		||||
      this->irq_pin_ = &pin;
 | 
			
		||||
      this->irq_pin_->setup();
 | 
			
		||||
    }
 | 
			
		||||
    this->set_timeout(100, [this]() {
 | 
			
		||||
      this->ade_write_<uint8_t>(0x0010, 0x04);
 | 
			
		||||
      this->ade_write_<uint8_t>(0x00FE, 0xAD);
 | 
			
		||||
@@ -55,6 +64,9 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool has_irq_ = false;
 | 
			
		||||
  uint8_t irq_pin_number_;
 | 
			
		||||
  GPIOPin *irq_pin_{nullptr};
 | 
			
		||||
  bool is_setup_{false};
 | 
			
		||||
  sensor::Sensor *voltage_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *current_a_sensor_{nullptr};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,16 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, i2c
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_ID, CONF_VOLTAGE, \
 | 
			
		||||
    UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
 | 
			
		||||
ace7953_ns = cg.esphome_ns.namespace('ade7953')
 | 
			
		||||
ADE7953 = ace7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
ade7953_ns = cg.esphome_ns.namespace('ade7953')
 | 
			
		||||
ADE7953 = ade7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
CONF_IRQ_PIN = 'irq_pin'
 | 
			
		||||
CONF_CURRENT_A = 'current_a'
 | 
			
		||||
CONF_CURRENT_B = 'current_b'
 | 
			
		||||
CONF_ACTIVE_POWER_A = 'active_power_a'
 | 
			
		||||
@@ -16,7 +18,7 @@ CONF_ACTIVE_POWER_B = 'active_power_b'
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(ADE7953),
 | 
			
		||||
 | 
			
		||||
    cv.Optional(CONF_IRQ_PIN): pins.input_pin,
 | 
			
		||||
    cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
 | 
			
		||||
    cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
 | 
			
		||||
    cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
 | 
			
		||||
@@ -30,6 +32,9 @@ def to_code(config):
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_IRQ_PIN in config:
 | 
			
		||||
        cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
 | 
			
		||||
 | 
			
		||||
    for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A,
 | 
			
		||||
                CONF_ACTIVE_POWER_B]:
 | 
			
		||||
        if key not in config:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										94
									
								
								esphome/components/animation/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								esphome/components/animation/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome import core
 | 
			
		||||
from esphome.components import display, font
 | 
			
		||||
import esphome.components.image as espImage
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE
 | 
			
		||||
from esphome.core import CORE, HexInt
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['display']
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
Animation_ = display.display_ns.class_('Animation')
 | 
			
		||||
 | 
			
		||||
CONF_RAW_DATA_ID = 'raw_data_id'
 | 
			
		||||
 | 
			
		||||
ANIMATION_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.Required(CONF_ID): cv.declare_id(Animation_),
 | 
			
		||||
    cv.Required(CONF_FILE): cv.file_,
 | 
			
		||||
    cv.Optional(CONF_RESIZE): cv.dimensions,
 | 
			
		||||
    cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(espImage.IMAGE_TYPE, upper=True),
 | 
			
		||||
    cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@syndlex']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    from PIL import Image
 | 
			
		||||
 | 
			
		||||
    path = CORE.relative_config_path(config[CONF_FILE])
 | 
			
		||||
    try:
 | 
			
		||||
        image = Image.open(path)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        raise core.EsphomeError(f"Could not load image file {path}: {e}")
 | 
			
		||||
 | 
			
		||||
    width, height = image.size
 | 
			
		||||
    frames = image.n_frames
 | 
			
		||||
    if CONF_RESIZE in config:
 | 
			
		||||
        image.thumbnail(config[CONF_RESIZE])
 | 
			
		||||
        width, height = image.size
 | 
			
		||||
    else:
 | 
			
		||||
        if width > 500 or height > 500:
 | 
			
		||||
            _LOGGER.warning("The image you requested is very big. Please consider using"
 | 
			
		||||
                            " the resize parameter.")
 | 
			
		||||
 | 
			
		||||
    if config[CONF_TYPE] == 'GRAYSCALE':
 | 
			
		||||
        data = [0 for _ in range(height * width * frames)]
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert('L', dither=Image.NONE)
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                data[pos] = pix
 | 
			
		||||
                pos += 1
 | 
			
		||||
 | 
			
		||||
    elif config[CONF_TYPE] == 'RGB24':
 | 
			
		||||
        data = [0 for _ in range(height * width * 3 * frames)]
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert('RGB')
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                data[pos] = pix[0]
 | 
			
		||||
                pos += 1
 | 
			
		||||
                data[pos] = pix[1]
 | 
			
		||||
                pos += 1
 | 
			
		||||
                data[pos] = pix[2]
 | 
			
		||||
                pos += 1
 | 
			
		||||
 | 
			
		||||
    elif config[CONF_TYPE] == 'BINARY':
 | 
			
		||||
        width8 = ((width + 7) // 8) * 8
 | 
			
		||||
        data = [0 for _ in range((height * width8 // 8) * frames)]
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert('1', dither=Image.NONE)
 | 
			
		||||
            for y in range(height):
 | 
			
		||||
                for x in range(width):
 | 
			
		||||
                    if frame.getpixel((x, y)):
 | 
			
		||||
                        continue
 | 
			
		||||
                    pos = x + y * width8 + (height * width8 * frameIndex)
 | 
			
		||||
                    data[pos // 8] |= 0x80 >> (pos % 8)
 | 
			
		||||
 | 
			
		||||
    rhs = [HexInt(x) for x in data]
 | 
			
		||||
    prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
 | 
			
		||||
    cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, frames,
 | 
			
		||||
                     espImage.IMAGE_TYPE[config[CONF_TYPE]])
 | 
			
		||||
@@ -3,7 +3,8 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import Condition
 | 
			
		||||
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
 | 
			
		||||
    CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT
 | 
			
		||||
    CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT, \
 | 
			
		||||
    CONF_TAG
 | 
			
		||||
from esphome.core import coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['network']
 | 
			
		||||
@@ -137,6 +138,23 @@ def homeassistant_event_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value({
 | 
			
		||||
    cv.GenerateID(): cv.use_id(APIServer),
 | 
			
		||||
    cv.Required(CONF_TAG): cv.templatable(cv.string_strict),
 | 
			
		||||
}, key=CONF_TAG)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action('homeassistant.tag_scanned', HomeAssistantServiceCallAction,
 | 
			
		||||
                            HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA)
 | 
			
		||||
def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    serv = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, serv, True)
 | 
			
		||||
    cg.add(var.set_service('esphome.tag_scanned'))
 | 
			
		||||
    templ = yield cg.templatable(config[CONF_TAG], args, cg.std_string)
 | 
			
		||||
    cg.add(var.add_data('tag_id', templ))
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_condition('api.connected', APIConnectedCondition, {})
 | 
			
		||||
def api_connected_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    yield cg.new_Pvariable(condition_id, template_arg)
 | 
			
		||||
 
 | 
			
		||||
@@ -679,7 +679,7 @@ enum ClimateSwingMode {
 | 
			
		||||
  CLIMATE_SWING_OFF = 0;
 | 
			
		||||
  CLIMATE_SWING_BOTH = 1;
 | 
			
		||||
  CLIMATE_SWING_VERTICAL = 2;
 | 
			
		||||
  CLIMATE_SWINT_HORIZONTAL = 3;
 | 
			
		||||
  CLIMATE_SWING_HORIZONTAL = 3;
 | 
			
		||||
}
 | 
			
		||||
enum ClimateAction {
 | 
			
		||||
  CLIMATE_ACTION_OFF = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -676,8 +676,10 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->client_->add(reinterpret_cast<char *>(header.data()), header.size());
 | 
			
		||||
  this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->size());
 | 
			
		||||
  this->client_->add(reinterpret_cast<char *>(header.data()), header.size(),
 | 
			
		||||
                     ASYNC_WRITE_FLAG_COPY | ASYNC_WRITE_FLAG_MORE);
 | 
			
		||||
  this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->size(),
 | 
			
		||||
                     ASYNC_WRITE_FLAG_COPY);
 | 
			
		||||
  bool ret = this->client_->send();
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -154,8 +154,8 @@ template<> const char *proto_enum_to_string<enums::ClimateSwingMode>(enums::Clim
 | 
			
		||||
      return "CLIMATE_SWING_BOTH";
 | 
			
		||||
    case enums::CLIMATE_SWING_VERTICAL:
 | 
			
		||||
      return "CLIMATE_SWING_VERTICAL";
 | 
			
		||||
    case enums::CLIMATE_SWINT_HORIZONTAL:
 | 
			
		||||
      return "CLIMATE_SWINT_HORIZONTAL";
 | 
			
		||||
    case enums::CLIMATE_SWING_HORIZONTAL:
 | 
			
		||||
      return "CLIMATE_SWING_HORIZONTAL";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ enum ClimateSwingMode : uint32_t {
 | 
			
		||||
  CLIMATE_SWING_OFF = 0,
 | 
			
		||||
  CLIMATE_SWING_BOTH = 1,
 | 
			
		||||
  CLIMATE_SWING_VERTICAL = 2,
 | 
			
		||||
  CLIMATE_SWINT_HORIZONTAL = 3,
 | 
			
		||||
  CLIMATE_SWING_HORIZONTAL = 3,
 | 
			
		||||
};
 | 
			
		||||
enum ClimateAction : uint32_t {
 | 
			
		||||
  CLIMATE_ACTION_OFF = 0,
 | 
			
		||||
 
 | 
			
		||||
@@ -62,8 +62,7 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
 | 
			
		||||
          error = true;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        uint32_t val = (uint32_t(buffer[i]) << 0) | (uint32_t(buffer[i + 1]) << 8) | (uint32_t(buffer[i + 2]) << 16) |
 | 
			
		||||
                       (uint32_t(buffer[i + 3]) << 24);
 | 
			
		||||
        uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
 | 
			
		||||
        if (!this->decode_32bit(field_id, Proto32Bit(val))) {
 | 
			
		||||
          ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ AS3935_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
 | 
			
		||||
    cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True),
 | 
			
		||||
    cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
 | 
			
		||||
    cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 22, 64, 128, int=True),
 | 
			
		||||
    cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True),
 | 
			
		||||
    cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,9 @@ void AS3935Component::setup() {
 | 
			
		||||
void AS3935Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "AS3935:");
 | 
			
		||||
  LOG_PIN("  Interrupt Pin: ", this->irq_pin_);
 | 
			
		||||
  LOG_BINARY_SENSOR("  ", "Thunder alert", this->thunder_alert_binary_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Distance", this->distance_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Lightning energy", this->energy_sensor_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,4 +27,4 @@ def to_code(config):
 | 
			
		||||
    if CONF_LIGHTNING_ENERGY in config:
 | 
			
		||||
        conf = config[CONF_LIGHTNING_ENERGY]
 | 
			
		||||
        lightning_energy_sensor = yield sensor.new_sensor(conf)
 | 
			
		||||
        cg.add(hub.set_distance_sensor(lightning_energy_sensor))
 | 
			
		||||
        cg.add(hub.set_energy_sensor(lightning_energy_sensor))
 | 
			
		||||
 
 | 
			
		||||
@@ -12,4 +12,4 @@ def to_code(config):
 | 
			
		||||
        cg.add_library('AsyncTCP-esphome', '1.1.1')
 | 
			
		||||
    elif CORE.is_esp8266:
 | 
			
		||||
        # https://github.com/OttoWinter/ESPAsyncTCP
 | 
			
		||||
        cg.add_library('ESPAsyncTCP-esphome', '1.2.2')
 | 
			
		||||
        cg.add_library('ESPAsyncTCP-esphome', '1.2.3')
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/atc_mithermometer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/atc_mithermometer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										137
									
								
								esphome/components/atc_mithermometer/atc_mithermometer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								esphome/components/atc_mithermometer/atc_mithermometer.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
			
		||||
#include "atc_mithermometer.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atc_mithermometer {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "atc_mithermometer";
 | 
			
		||||
 | 
			
		||||
void ATCMiThermometer::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ATC MiThermometer");
 | 
			
		||||
  LOG_SENSOR("  ", "Temperature", this->temperature_);
 | 
			
		||||
  LOG_SENSOR("  ", "Humidity", this->humidity_);
 | 
			
		||||
  LOG_SENSOR("  ", "Battery Level", this->battery_level_);
 | 
			
		||||
  LOG_SENSOR("  ", "Battery Voltage", this->battery_voltage_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
  if (device.address_uint64() != this->address_) {
 | 
			
		||||
    ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
 | 
			
		||||
 | 
			
		||||
  bool success = false;
 | 
			
		||||
  for (auto &service_data : device.get_service_datas()) {
 | 
			
		||||
    auto res = parse_header(service_data);
 | 
			
		||||
    if (res->is_duplicate) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (!(parse_message(service_data.data, *res))) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (!(report_results(res, device.address_str()))) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (res->temperature.has_value() && this->temperature_ != nullptr)
 | 
			
		||||
      this->temperature_->publish_state(*res->temperature);
 | 
			
		||||
    if (res->humidity.has_value() && this->humidity_ != nullptr)
 | 
			
		||||
      this->humidity_->publish_state(*res->humidity);
 | 
			
		||||
    if (res->battery_level.has_value() && this->battery_level_ != nullptr)
 | 
			
		||||
      this->battery_level_->publish_state(*res->battery_level);
 | 
			
		||||
    if (res->battery_voltage.has_value() && this->battery_voltage_ != nullptr)
 | 
			
		||||
      this->battery_voltage_->publish_state(*res->battery_voltage);
 | 
			
		||||
    success = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!success) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
optional<ParseResult> ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) {
 | 
			
		||||
  ParseResult result;
 | 
			
		||||
  if (!service_data.uuid.contains(0x1A, 0x18)) {
 | 
			
		||||
    ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes.");
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto raw = service_data.data;
 | 
			
		||||
 | 
			
		||||
  static uint8_t last_frame_count = 0;
 | 
			
		||||
  if (last_frame_count == raw[12]) {
 | 
			
		||||
    ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast<int>(last_frame_count));
 | 
			
		||||
    result.is_duplicate = true;
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
  last_frame_count = raw[12];
 | 
			
		||||
  result.is_duplicate = false;
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ATCMiThermometer::parse_message(const std::vector<uint8_t> &message, ParseResult &result) {
 | 
			
		||||
  // Byte 0-5 mac in correct order
 | 
			
		||||
  // Byte 6-7 Temperature in uint16
 | 
			
		||||
  // Byte 8 Humidity in percent
 | 
			
		||||
  // Byte 9 Battery in percent
 | 
			
		||||
  // Byte 10-11 Battery in mV uint16_t
 | 
			
		||||
  // Byte 12 frame packet counter
 | 
			
		||||
 | 
			
		||||
  const uint8_t *data = message.data();
 | 
			
		||||
  const int data_length = 13;
 | 
			
		||||
 | 
			
		||||
  if (message.size() != data_length) {
 | 
			
		||||
    ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size());
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C
 | 
			
		||||
  const int16_t temperature = uint16_t(data[7]) | (uint16_t(data[6]) << 8);
 | 
			
		||||
  result.temperature = temperature / 10.0f;
 | 
			
		||||
 | 
			
		||||
  // humidity, 1 byte, 8-bit unsigned integer, 1.0 %
 | 
			
		||||
  result.humidity = data[8];
 | 
			
		||||
 | 
			
		||||
  // battery, 1 byte, 8-bit unsigned integer,  1.0 %
 | 
			
		||||
  result.battery_level = data[9];
 | 
			
		||||
 | 
			
		||||
  // battery, 2 bytes, 16-bit unsigned integer,  0.001 V
 | 
			
		||||
  const int16_t battery_voltage = uint16_t(data[11]) | (uint16_t(data[10]) << 8);
 | 
			
		||||
  result.battery_voltage = battery_voltage / 1.0e3f;
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ATCMiThermometer::report_results(const optional<ParseResult> &result, const std::string &address) {
 | 
			
		||||
  if (!result.has_value()) {
 | 
			
		||||
    ESP_LOGVV(TAG, "report_results(): no results available.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address.c_str());
 | 
			
		||||
 | 
			
		||||
  if (result->temperature.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Temperature: %.1f °C", *result->temperature);
 | 
			
		||||
  }
 | 
			
		||||
  if (result->humidity.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Humidity: %.0f %%", *result->humidity);
 | 
			
		||||
  }
 | 
			
		||||
  if (result->battery_level.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Battery Level: %.0f %%", *result->battery_level);
 | 
			
		||||
  }
 | 
			
		||||
  if (result->battery_voltage.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Battery Voltage: %.3f V", *result->battery_voltage);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace atc_mithermometer
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										48
									
								
								esphome/components/atc_mithermometer/atc_mithermometer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								esphome/components/atc_mithermometer/atc_mithermometer.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atc_mithermometer {
 | 
			
		||||
 | 
			
		||||
struct ParseResult {
 | 
			
		||||
  optional<float> temperature;
 | 
			
		||||
  optional<float> humidity;
 | 
			
		||||
  optional<float> battery_level;
 | 
			
		||||
  optional<float> battery_voltage;
 | 
			
		||||
  bool is_duplicate;
 | 
			
		||||
  int raw_offset;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_address(uint64_t address) { address_ = address; };
 | 
			
		||||
 | 
			
		||||
  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
  void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
 | 
			
		||||
  void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
 | 
			
		||||
  void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
 | 
			
		||||
  void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint64_t address_;
 | 
			
		||||
  sensor::Sensor *temperature_{nullptr};
 | 
			
		||||
  sensor::Sensor *humidity_{nullptr};
 | 
			
		||||
  sensor::Sensor *battery_level_{nullptr};
 | 
			
		||||
  sensor::Sensor *battery_voltage_{nullptr};
 | 
			
		||||
 | 
			
		||||
  optional<ParseResult> parse_header(const esp32_ble_tracker::ServiceData &service_data);
 | 
			
		||||
  bool parse_message(const std::vector<uint8_t> &message, ParseResult &result);
 | 
			
		||||
  bool report_results(const optional<ParseResult> &result, const std::string &address);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace atc_mithermometer
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										45
									
								
								esphome/components/atc_mithermometer/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/atc_mithermometer/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, esp32_ble_tracker
 | 
			
		||||
from esphome.const import CONF_BATTERY_LEVEL, CONF_BATTERY_VOLTAGE, CONF_MAC_ADDRESS, \
 | 
			
		||||
    CONF_HUMIDITY, CONF_TEMPERATURE, CONF_ID, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, \
 | 
			
		||||
    ICON_BATTERY, ICON_THERMOMETER, ICON_WATER_PERCENT
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@ahpohl']
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['esp32_ble_tracker']
 | 
			
		||||
 | 
			
		||||
atc_mithermometer_ns = cg.esphome_ns.namespace('atc_mithermometer')
 | 
			
		||||
ATCMiThermometer = atc_mithermometer_ns.class_('ATCMiThermometer',
 | 
			
		||||
                                               esp32_ble_tracker.ESPBTDeviceListener,
 | 
			
		||||
                                               cg.Component)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(ATCMiThermometer),
 | 
			
		||||
    cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
 | 
			
		||||
    cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
 | 
			
		||||
    cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0),
 | 
			
		||||
    cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0),
 | 
			
		||||
    cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_BATTERY, 3),
 | 
			
		||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield esp32_ble_tracker.register_ble_device(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
 | 
			
		||||
 | 
			
		||||
    if CONF_TEMPERATURE in config:
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
 | 
			
		||||
        cg.add(var.set_temperature(sens))
 | 
			
		||||
    if CONF_HUMIDITY in config:
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
 | 
			
		||||
        cg.add(var.set_humidity(sens))
 | 
			
		||||
    if CONF_BATTERY_LEVEL in config:
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
 | 
			
		||||
        cg.add(var.set_battery_level(sens))
 | 
			
		||||
    if CONF_BATTERY_VOLTAGE in config:
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
 | 
			
		||||
        cg.add(var.set_battery_voltage(sens))
 | 
			
		||||
@@ -104,6 +104,7 @@ def parse_multi_click_timing_str(value):
 | 
			
		||||
    try:
 | 
			
		||||
        state = cv.boolean(parts[0])
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        # pylint: disable=raise-missing-from
 | 
			
		||||
        raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
 | 
			
		||||
 | 
			
		||||
    if parts[1] != 'for':
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										124
									
								
								esphome/components/canbus/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								esphome/components/canbus/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,124 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.core import CORE, coroutine
 | 
			
		||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_DATA
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@mvturnho', '@danielschramm']
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
CONF_CAN_ID = 'can_id'
 | 
			
		||||
CONF_USE_EXTENDED_ID = 'use_extended_id'
 | 
			
		||||
CONF_CANBUS_ID = 'canbus_id'
 | 
			
		||||
CONF_BIT_RATE = 'bit_rate'
 | 
			
		||||
CONF_ON_FRAME = 'on_frame'
 | 
			
		||||
CONF_CANBUS_SEND = 'canbus.send'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_id(id_value, id_ext):
 | 
			
		||||
    if not id_ext:
 | 
			
		||||
        if id_value > 0x7ff:
 | 
			
		||||
            raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_raw_data(value):
 | 
			
		||||
    if isinstance(value, str):
 | 
			
		||||
        return value.encode('utf-8')
 | 
			
		||||
    if isinstance(value, list):
 | 
			
		||||
        return cv.Schema([cv.hex_uint8_t])(value)
 | 
			
		||||
    raise cv.Invalid("data must either be a string wrapped in quotes or a list of bytes")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
canbus_ns = cg.esphome_ns.namespace('canbus')
 | 
			
		||||
CanbusComponent = canbus_ns.class_('CanbusComponent', cg.Component)
 | 
			
		||||
CanbusTrigger = canbus_ns.class_('CanbusTrigger',
 | 
			
		||||
                                 automation.Trigger.template(cg.std_vector.template(cg.uint8)),
 | 
			
		||||
                                 cg.Component)
 | 
			
		||||
CanSpeed = canbus_ns.enum('CAN_SPEED')
 | 
			
		||||
 | 
			
		||||
CAN_SPEEDS = {
 | 
			
		||||
    '5KBPS': CanSpeed.CAN_5KBPS,
 | 
			
		||||
    '10KBPS': CanSpeed.CAN_10KBPS,
 | 
			
		||||
    '20KBPS': CanSpeed.CAN_20KBPS,
 | 
			
		||||
    '31K25BPS': CanSpeed.CAN_31K25BPS,
 | 
			
		||||
    '33KBPS': CanSpeed.CAN_33KBPS,
 | 
			
		||||
    '40KBPS': CanSpeed.CAN_40KBPS,
 | 
			
		||||
    '50KBPS': CanSpeed.CAN_50KBPS,
 | 
			
		||||
    '80KBPS': CanSpeed.CAN_80KBPS,
 | 
			
		||||
    '83K3BPS': CanSpeed.CAN_83K3BPS,
 | 
			
		||||
    '95KBPS': CanSpeed.CAN_95KBPS,
 | 
			
		||||
    '100KBPS': CanSpeed.CAN_100KBPS,
 | 
			
		||||
    '125KBPS': CanSpeed.CAN_125KBPS,
 | 
			
		||||
    '200KBPS': CanSpeed.CAN_200KBPS,
 | 
			
		||||
    '250KBPS': CanSpeed.CAN_250KBPS,
 | 
			
		||||
    '500KBPS': CanSpeed.CAN_500KBPS,
 | 
			
		||||
    '1000KBPS': CanSpeed.CAN_1000KBPS,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(CanbusComponent),
 | 
			
		||||
    cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff),
 | 
			
		||||
    cv.Optional(CONF_BIT_RATE, default='125KBPS'): cv.enum(CAN_SPEEDS, upper=True),
 | 
			
		||||
    cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
 | 
			
		||||
    cv.Optional(CONF_ON_FRAME): automation.validate_automation({
 | 
			
		||||
        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
 | 
			
		||||
        cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff),
 | 
			
		||||
        cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
 | 
			
		||||
    }),
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
def setup_canbus_core_(var, config):
 | 
			
		||||
    validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    cg.add(var.set_can_id([config[CONF_CAN_ID]]))
 | 
			
		||||
    cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]]))
 | 
			
		||||
    cg.add(var.set_bitrate(CAN_SPEEDS[config[CONF_BIT_RATE]]))
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_FRAME, []):
 | 
			
		||||
        can_id = conf[CONF_CAN_ID]
 | 
			
		||||
        ext_id = conf[CONF_USE_EXTENDED_ID]
 | 
			
		||||
        validate_id(can_id, ext_id)
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id)
 | 
			
		||||
        yield cg.register_component(trigger, conf)
 | 
			
		||||
        yield automation.build_automation(trigger, [(cg.std_vector.template(cg.uint8), 'x')], conf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
def register_canbus(var, config):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
        var = cg.new_Pvariable(config[CONF_ID], var)
 | 
			
		||||
    yield setup_canbus_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Actions
 | 
			
		||||
@automation.register_action(CONF_CANBUS_SEND,
 | 
			
		||||
                            canbus_ns.class_('CanbusSendAction', automation.Action),
 | 
			
		||||
                            cv.maybe_simple_value({
 | 
			
		||||
                                cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent),
 | 
			
		||||
                                cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff),
 | 
			
		||||
                                cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
 | 
			
		||||
                                cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
 | 
			
		||||
                            }, key=CONF_DATA))
 | 
			
		||||
def canbus_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
			
		||||
    yield cg.register_parented(var, config[CONF_CANBUS_ID])
 | 
			
		||||
 | 
			
		||||
    if CONF_CAN_ID in config:
 | 
			
		||||
        can_id = yield cg.templatable(config[CONF_CAN_ID], args, cg.uint32)
 | 
			
		||||
        cg.add(var.set_can_id(can_id))
 | 
			
		||||
 | 
			
		||||
    use_extended_id = yield cg.templatable(config[CONF_USE_EXTENDED_ID], args, cg.uint32)
 | 
			
		||||
    cg.add(var.set_use_extended_id(use_extended_id))
 | 
			
		||||
 | 
			
		||||
    data = config[CONF_DATA]
 | 
			
		||||
    if isinstance(data, bytes):
 | 
			
		||||
        data = [int(x) for x in data]
 | 
			
		||||
    if cg.is_template(data):
 | 
			
		||||
        templ = yield cg.templatable(data, args, cg.std_vector.template(cg.uint8))
 | 
			
		||||
        cg.add(var.set_data_template(templ))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_data_static(data))
 | 
			
		||||
    yield var
 | 
			
		||||
							
								
								
									
										87
									
								
								esphome/components/canbus/canbus.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								esphome/components/canbus/canbus.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
#include "canbus.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace canbus {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "canbus";
 | 
			
		||||
 | 
			
		||||
void Canbus::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up Canbus...");
 | 
			
		||||
  if (!this->setup_internal()) {
 | 
			
		||||
    ESP_LOGE(TAG, "setup error!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Canbus::dump_config() {
 | 
			
		||||
  if (this->use_extended_id_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "config extended id=0x%08x", this->can_id_);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "config standard id=0x%03x", this->can_id_);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Canbus::send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
 | 
			
		||||
  struct CanFrame can_message;
 | 
			
		||||
 | 
			
		||||
  uint8_t size = static_cast<uint8_t>(data.size());
 | 
			
		||||
  if (use_extended_id) {
 | 
			
		||||
    ESP_LOGD(TAG, "send extended id=0x%08x size=%d", can_id, size);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGD(TAG, "send extended id=0x%03x size=%d", can_id, size);
 | 
			
		||||
  }
 | 
			
		||||
  if (size > CAN_MAX_DATA_LENGTH)
 | 
			
		||||
    size = CAN_MAX_DATA_LENGTH;
 | 
			
		||||
  can_message.can_data_length_code = size;
 | 
			
		||||
  can_message.can_id = can_id;
 | 
			
		||||
  can_message.use_extended_id = use_extended_id;
 | 
			
		||||
 | 
			
		||||
  for (int i = 0; i < size; i++) {
 | 
			
		||||
    can_message.data[i] = data[i];
 | 
			
		||||
    ESP_LOGVV(TAG, "  data[%d]=%02x", i, can_message.data[i]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->send_message(&can_message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Canbus::add_trigger(CanbusTrigger *trigger) {
 | 
			
		||||
  if (trigger->use_extended_id_) {
 | 
			
		||||
    ESP_LOGVV(TAG, "add trigger for extended canid=0x%08x", trigger->can_id_);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGVV(TAG, "add trigger for std canid=0x%03x", trigger->can_id_);
 | 
			
		||||
  }
 | 
			
		||||
  this->triggers_.push_back(trigger);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void Canbus::loop() {
 | 
			
		||||
  struct CanFrame can_message;
 | 
			
		||||
  // readmessage
 | 
			
		||||
  if (this->read_message(&can_message) == canbus::ERROR_OK) {
 | 
			
		||||
    if (can_message.use_extended_id) {
 | 
			
		||||
      ESP_LOGD(TAG, "received can message extended can_id=0x%x size=%d", can_message.can_id,
 | 
			
		||||
               can_message.can_data_length_code);
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGD(TAG, "received can message std can_id=0x%x size=%d", can_message.can_id,
 | 
			
		||||
               can_message.can_data_length_code);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<uint8_t> data;
 | 
			
		||||
 | 
			
		||||
    // show data received
 | 
			
		||||
    for (int i = 0; i < can_message.can_data_length_code; i++) {
 | 
			
		||||
      ESP_LOGV(TAG, "  can_message.data[%d]=%02x", i, can_message.data[i]);
 | 
			
		||||
      data.push_back(can_message.data[i]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // fire all triggers
 | 
			
		||||
    for (auto trigger : this->triggers_) {
 | 
			
		||||
      if ((trigger->can_id_ == can_message.can_id) && (trigger->use_extended_id_ == can_message.use_extended_id)) {
 | 
			
		||||
        trigger->trigger(data);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace canbus
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										134
									
								
								esphome/components/canbus/canbus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								esphome/components/canbus/canbus.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/optional.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace canbus {
 | 
			
		||||
 | 
			
		||||
enum Error : uint8_t {
 | 
			
		||||
  ERROR_OK = 0,
 | 
			
		||||
  ERROR_FAIL = 1,
 | 
			
		||||
  ERROR_ALLTXBUSY = 2,
 | 
			
		||||
  ERROR_FAILINIT = 3,
 | 
			
		||||
  ERROR_FAILTX = 4,
 | 
			
		||||
  ERROR_NOMSG = 5
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum CanSpeed : uint8_t {
 | 
			
		||||
  CAN_5KBPS,
 | 
			
		||||
  CAN_10KBPS,
 | 
			
		||||
  CAN_20KBPS,
 | 
			
		||||
  CAN_31K25BPS,
 | 
			
		||||
  CAN_33KBPS,
 | 
			
		||||
  CAN_40KBPS,
 | 
			
		||||
  CAN_50KBPS,
 | 
			
		||||
  CAN_80KBPS,
 | 
			
		||||
  CAN_83K3BPS,
 | 
			
		||||
  CAN_95KBPS,
 | 
			
		||||
  CAN_100KBPS,
 | 
			
		||||
  CAN_125KBPS,
 | 
			
		||||
  CAN_200KBPS,
 | 
			
		||||
  CAN_250KBPS,
 | 
			
		||||
  CAN_500KBPS,
 | 
			
		||||
  CAN_1000KBPS
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CanbusTrigger;
 | 
			
		||||
template<typename... Ts> class CanbusSendAction;
 | 
			
		||||
 | 
			
		||||
/* CAN payload length definitions according to ISO 11898-1 */
 | 
			
		||||
static const uint8_t CAN_MAX_DATA_LENGTH = 8;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Can Frame describes a normative CAN Frame
 | 
			
		||||
The RTR = Remote Transmission Request is implemented in every CAN controller but rarely used
 | 
			
		||||
So currently the flag is passed to and from the hardware but currently ignored to the user application.
 | 
			
		||||
*/
 | 
			
		||||
struct CanFrame {
 | 
			
		||||
  bool use_extended_id = false;
 | 
			
		||||
  bool remote_transmission_request = false;
 | 
			
		||||
  uint32_t can_id;              /* 29 or 11 bit CAN_ID  */
 | 
			
		||||
  uint8_t can_data_length_code; /* frame payload length in byte (0 .. CAN_MAX_DATA_LENGTH) */
 | 
			
		||||
  uint8_t data[CAN_MAX_DATA_LENGTH] __attribute__((aligned(8)));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Canbus : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  Canbus(){};
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
  void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data);
 | 
			
		||||
  void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
 | 
			
		||||
  void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
 | 
			
		||||
  void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
 | 
			
		||||
 | 
			
		||||
  void add_trigger(CanbusTrigger *trigger);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  template<typename... Ts> friend class CanbusSendAction;
 | 
			
		||||
  std::vector<CanbusTrigger *> triggers_{};
 | 
			
		||||
  uint32_t can_id_;
 | 
			
		||||
  bool use_extended_id_;
 | 
			
		||||
  CanSpeed bit_rate_;
 | 
			
		||||
 | 
			
		||||
  virtual bool setup_internal();
 | 
			
		||||
  virtual Error send_message(struct CanFrame *frame);
 | 
			
		||||
  virtual Error read_message(struct CanFrame *frame);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_data_template(const std::function<std::vector<uint8_t>(Ts...)> func) {
 | 
			
		||||
    this->data_func_ = func;
 | 
			
		||||
    this->static_ = false;
 | 
			
		||||
  }
 | 
			
		||||
  void set_data_static(const std::vector<uint8_t> &data) {
 | 
			
		||||
    this->data_static_ = data;
 | 
			
		||||
    this->static_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
 | 
			
		||||
 | 
			
		||||
  void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
 | 
			
		||||
    auto use_extended_id =
 | 
			
		||||
        this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
 | 
			
		||||
    if (this->static_) {
 | 
			
		||||
      this->parent_->send_data(can_id, use_extended_id, this->data_static_);
 | 
			
		||||
    } else {
 | 
			
		||||
      auto val = this->data_func_(x...);
 | 
			
		||||
      this->parent_->send_data(can_id, use_extended_id, val);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  optional<uint32_t> can_id_{};
 | 
			
		||||
  optional<bool> use_extended_id_{};
 | 
			
		||||
  bool static_{false};
 | 
			
		||||
  std::function<std::vector<uint8_t>(Ts...)> data_func_{};
 | 
			
		||||
  std::vector<uint8_t> data_static_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CanbusTrigger : public Trigger<std::vector<uint8_t>>, public Component {
 | 
			
		||||
  friend class Canbus;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const bool use_extended_id)
 | 
			
		||||
      : parent_(parent), can_id_(can_id), use_extended_id_(use_extended_id){};
 | 
			
		||||
  void setup() override { this->parent_->add_trigger(this); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Canbus *parent_;
 | 
			
		||||
  uint32_t can_id_;
 | 
			
		||||
  bool use_extended_id_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace canbus
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -102,10 +102,14 @@ void CCS811Component::send_env_data_() {
 | 
			
		||||
  // temperature has a 25° offset to allow negative temperatures
 | 
			
		||||
  temperature += 25;
 | 
			
		||||
 | 
			
		||||
  // only 0.5 fractions are supported (application note)
 | 
			
		||||
  auto hum_value = static_cast<uint8_t>(roundf(humidity * 2));
 | 
			
		||||
  auto temp_value = static_cast<uint8_t>(roundf(temperature * 2));
 | 
			
		||||
  this->write_bytes(0x05, {hum_value, 0x00, temp_value, 0x00});
 | 
			
		||||
  // At page 18 of:
 | 
			
		||||
  // https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf
 | 
			
		||||
  // Reference code:
 | 
			
		||||
  // https://github.com/adafruit/Adafruit_CCS811/blob/0990f5c620354d8bc087c4706bec091d8e6e5dfd/Adafruit_CCS811.cpp#L135-L142
 | 
			
		||||
  uint16_t hum_conv = static_cast<uint16_t>(lroundf(humidity * 512.0f + 0.5f));
 | 
			
		||||
  uint16_t temp_conv = static_cast<uint16_t>(lroundf(temperature * 512.0f + 0.5f));
 | 
			
		||||
  this->write_bytes(0x05, {(uint8_t)((hum_conv >> 8) & 0xff), (uint8_t)((hum_conv & 0xff)),
 | 
			
		||||
                           (uint8_t)((temp_conv >> 8) & 0xff), (uint8_t)((temp_conv & 0xff))});
 | 
			
		||||
}
 | 
			
		||||
void CCS811Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "CCS811");
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \
 | 
			
		||||
    UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_PERIODIC_TABLE_CO2
 | 
			
		||||
    UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_MOLECULE_CO2
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
 | 
			
		||||
@@ -15,7 +15,7 @@ CONF_BASELINE = 'baseline'
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(CCS811Component),
 | 
			
		||||
    cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_PERIODIC_TABLE_CO2,
 | 
			
		||||
    cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2,
 | 
			
		||||
                                                 0),
 | 
			
		||||
    cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0),
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,10 @@ void DaikinClimate::transmit_state() {
 | 
			
		||||
                              0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00};
 | 
			
		||||
 | 
			
		||||
  remote_state[21] = this->operation_mode_();
 | 
			
		||||
  remote_state[24] = this->fan_speed_();
 | 
			
		||||
  remote_state[22] = this->temperature_();
 | 
			
		||||
  uint16_t fan_speed = this->fan_speed_();
 | 
			
		||||
  remote_state[24] = fan_speed >> 8;
 | 
			
		||||
  remote_state[25] = fan_speed & 0xff;
 | 
			
		||||
 | 
			
		||||
  // Calculate checksum
 | 
			
		||||
  for (int i = 16; i < 34; i++) {
 | 
			
		||||
@@ -90,25 +92,38 @@ uint8_t DaikinClimate::operation_mode_() {
 | 
			
		||||
  return operating_mode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t DaikinClimate::fan_speed_() {
 | 
			
		||||
  uint8_t fan_speed;
 | 
			
		||||
uint16_t DaikinClimate::fan_speed_() {
 | 
			
		||||
  uint16_t fan_speed;
 | 
			
		||||
  switch (this->fan_mode) {
 | 
			
		||||
    case climate::CLIMATE_FAN_LOW:
 | 
			
		||||
      fan_speed = DAIKIN_FAN_1;
 | 
			
		||||
      fan_speed = DAIKIN_FAN_1 << 8;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_MEDIUM:
 | 
			
		||||
      fan_speed = DAIKIN_FAN_3;
 | 
			
		||||
      fan_speed = DAIKIN_FAN_3 << 8;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_HIGH:
 | 
			
		||||
      fan_speed = DAIKIN_FAN_5;
 | 
			
		||||
      fan_speed = DAIKIN_FAN_5 << 8;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_AUTO:
 | 
			
		||||
    default:
 | 
			
		||||
      fan_speed = DAIKIN_FAN_AUTO;
 | 
			
		||||
      fan_speed = DAIKIN_FAN_AUTO << 8;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // If swing is enabled switch first 4 bits to 1111
 | 
			
		||||
  return this->swing_mode == climate::CLIMATE_SWING_VERTICAL ? fan_speed | 0xF : fan_speed;
 | 
			
		||||
  switch (this->swing_mode) {
 | 
			
		||||
    case climate::CLIMATE_SWING_VERTICAL:
 | 
			
		||||
      fan_speed |= 0x0F00;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_SWING_HORIZONTAL:
 | 
			
		||||
      fan_speed |= 0x000F;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_SWING_BOTH:
 | 
			
		||||
      fan_speed |= 0x0F0F;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  return fan_speed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t DaikinClimate::temperature_() {
 | 
			
		||||
@@ -159,13 +174,19 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
 | 
			
		||||
    this->target_temperature = temperature >> 1;
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t fan_mode = frame[8];
 | 
			
		||||
  if (fan_mode & 0xF)
 | 
			
		||||
  uint8_t swing_mode = frame[9];
 | 
			
		||||
  if (fan_mode & 0xF && swing_mode & 0xF)
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_BOTH;
 | 
			
		||||
  else if (fan_mode & 0xF)
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
 | 
			
		||||
  else if (swing_mode & 0xF)
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
 | 
			
		||||
  else
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_OFF;
 | 
			
		||||
  switch (fan_mode & 0xF0) {
 | 
			
		||||
    case DAIKIN_FAN_1:
 | 
			
		||||
    case DAIKIN_FAN_2:
 | 
			
		||||
    case DAIKIN_FAN_SILENT:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_LOW;
 | 
			
		||||
      break;
 | 
			
		||||
    case DAIKIN_FAN_3:
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ const uint8_t DAIKIN_MODE_ON = 0x01;
 | 
			
		||||
 | 
			
		||||
// Fan Speed
 | 
			
		||||
const uint8_t DAIKIN_FAN_AUTO = 0xA0;
 | 
			
		||||
const uint8_t DAIKIN_FAN_SILENT = 0xB0;
 | 
			
		||||
const uint8_t DAIKIN_FAN_1 = 0x30;
 | 
			
		||||
const uint8_t DAIKIN_FAN_2 = 0x40;
 | 
			
		||||
const uint8_t DAIKIN_FAN_3 = 0x50;
 | 
			
		||||
@@ -46,13 +47,14 @@ class DaikinClimate : public climate_ir::ClimateIR {
 | 
			
		||||
            DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true,
 | 
			
		||||
            std::vector<climate::ClimateFanMode>{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
 | 
			
		||||
                                                 climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
 | 
			
		||||
            std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
 | 
			
		||||
            std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
 | 
			
		||||
                                                   climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  // Transmit via IR the state of this climate controller.
 | 
			
		||||
  void transmit_state() override;
 | 
			
		||||
  uint8_t operation_mode_();
 | 
			
		||||
  uint8_t fan_speed_();
 | 
			
		||||
  uint16_t fan_speed_();
 | 
			
		||||
  uint8_t temperature_();
 | 
			
		||||
  // Handle received IR Buffer
 | 
			
		||||
  bool on_receive(remote_base::RemoteReceiveData data) override;
 | 
			
		||||
 
 | 
			
		||||
@@ -204,31 +204,33 @@ void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign al
 | 
			
		||||
  if (ret > 0)
 | 
			
		||||
    this->print(x, y, font, color, align, buffer);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::image(int x, int y, Image *image) { this->image(x, y, COLOR_ON, image); }
 | 
			
		||||
void DisplayBuffer::image(int x, int y, Color color, Image *image, bool invert) {
 | 
			
		||||
  if (image->get_type() == BINARY) {
 | 
			
		||||
    for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
			
		||||
      for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
			
		||||
        if (invert)
 | 
			
		||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? COLOR_OFF : color);
 | 
			
		||||
        else
 | 
			
		||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color : COLOR_OFF);
 | 
			
		||||
 | 
			
		||||
void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color color_off) {
 | 
			
		||||
  switch (image->get_type()) {
 | 
			
		||||
    case IMAGE_TYPE_BINARY:
 | 
			
		||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
			
		||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
			
		||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color_on : color_off);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else if (image->get_type() == GRAYSCALE) {
 | 
			
		||||
    for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
			
		||||
      for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
			
		||||
        this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale_pixel(img_x, img_y));
 | 
			
		||||
      break;
 | 
			
		||||
    case IMAGE_TYPE_GRAYSCALE:
 | 
			
		||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
			
		||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
			
		||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale_pixel(img_x, img_y));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else if (image->get_type() == RGB) {
 | 
			
		||||
    for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
			
		||||
      for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
			
		||||
        this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y));
 | 
			
		||||
      break;
 | 
			
		||||
    case IMAGE_TYPE_RGB24:
 | 
			
		||||
      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
			
		||||
        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
			
		||||
          this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1,
 | 
			
		||||
                                    int *width, int *height) {
 | 
			
		||||
  int x_offset, baseline;
 | 
			
		||||
@@ -297,7 +299,7 @@ void DisplayBuffer::printf(int x, int y, Font *font, TextAlign align, const char
 | 
			
		||||
void DisplayBuffer::printf(int x, int y, Font *font, const char *format, ...) {
 | 
			
		||||
  va_list arg;
 | 
			
		||||
  va_start(arg, format);
 | 
			
		||||
  this->vprintf_(x, y, font, COLOR_ON, TextAlign::CENTER_LEFT, format, arg);
 | 
			
		||||
  this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg);
 | 
			
		||||
  va_end(arg);
 | 
			
		||||
}
 | 
			
		||||
void DisplayBuffer::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
 | 
			
		||||
@@ -463,15 +465,59 @@ Color Image::get_grayscale_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return 0;
 | 
			
		||||
  const uint32_t pos = (x + y * this->width_);
 | 
			
		||||
  return Color(pgm_read_byte(this->data_start_ + pos) << 24);
 | 
			
		||||
  const uint8_t gray = pgm_read_byte(this->data_start_ + pos);
 | 
			
		||||
  return Color(gray | gray << 8 | gray << 16 | gray << 24);
 | 
			
		||||
}
 | 
			
		||||
int Image::get_width() const { return this->width_; }
 | 
			
		||||
int Image::get_height() const { return this->height_; }
 | 
			
		||||
ImageType Image::get_type() const { return this->type_; }
 | 
			
		||||
Image::Image(const uint8_t *data_start, int width, int height)
 | 
			
		||||
    : width_(width), height_(height), data_start_(data_start) {}
 | 
			
		||||
Image::Image(const uint8_t *data_start, int width, int height, int type)
 | 
			
		||||
    : width_(width), height_(height), type_((ImageType) type), data_start_(data_start) {}
 | 
			
		||||
Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
 | 
			
		||||
    : width_(width), height_(height), type_(type), data_start_(data_start) {}
 | 
			
		||||
 | 
			
		||||
bool Animation::get_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return false;
 | 
			
		||||
  const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
 | 
			
		||||
  const uint32_t frame_index = this->height_ * width_8 * this->current_frame_;
 | 
			
		||||
  if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
 | 
			
		||||
    return false;
 | 
			
		||||
  const uint32_t pos = x + y * width_8 + frame_index;
 | 
			
		||||
  return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
 | 
			
		||||
}
 | 
			
		||||
Color Animation::get_color_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return 0;
 | 
			
		||||
  const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
 | 
			
		||||
  if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
 | 
			
		||||
    return 0;
 | 
			
		||||
  const uint32_t pos = (x + y * this->width_ + frame_index) * 3;
 | 
			
		||||
  const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) |
 | 
			
		||||
                           (pgm_read_byte(this->data_start_ + pos + 1) << 8) |
 | 
			
		||||
                           (pgm_read_byte(this->data_start_ + pos + 0) << 16);
 | 
			
		||||
  return Color(color32);
 | 
			
		||||
}
 | 
			
		||||
Color Animation::get_grayscale_pixel(int x, int y) const {
 | 
			
		||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
			
		||||
    return 0;
 | 
			
		||||
  const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
 | 
			
		||||
  if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
 | 
			
		||||
    return 0;
 | 
			
		||||
  const uint32_t pos = (x + y * this->width_ + frame_index);
 | 
			
		||||
  const uint8_t gray = pgm_read_byte(this->data_start_ + pos);
 | 
			
		||||
  return Color(gray | gray << 8 | gray << 16 | gray << 24);
 | 
			
		||||
}
 | 
			
		||||
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
 | 
			
		||||
    : Image(data_start, width, height, type), animation_frame_count_(animation_frame_count) {
 | 
			
		||||
  current_frame_ = 0;
 | 
			
		||||
}
 | 
			
		||||
int Animation::get_animation_frame_count() const { return this->animation_frame_count_; }
 | 
			
		||||
int Animation::get_current_frame() const { return this->current_frame_; }
 | 
			
		||||
void Animation::next_frame() {
 | 
			
		||||
  this->current_frame_++;
 | 
			
		||||
  if (this->current_frame_ >= animation_frame_count_) {
 | 
			
		||||
    this->current_frame_ = 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {}
 | 
			
		||||
void DisplayPage::show() { this->parent_->show_page(this); }
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ extern const Color COLOR_OFF;
 | 
			
		||||
/// Turn the pixel ON.
 | 
			
		||||
extern const Color COLOR_ON;
 | 
			
		||||
 | 
			
		||||
enum ImageType { BINARY = 0, GRAYSCALE = 1, RGB = 2 };
 | 
			
		||||
enum ImageType { IMAGE_TYPE_BINARY = 0, IMAGE_TYPE_GRAYSCALE = 1, IMAGE_TYPE_RGB24 = 2 };
 | 
			
		||||
 | 
			
		||||
enum DisplayRotation {
 | 
			
		||||
  DISPLAY_ROTATION_0_DEGREES = 0,
 | 
			
		||||
@@ -262,9 +262,15 @@ class DisplayBuffer {
 | 
			
		||||
      __attribute__((format(strftime, 5, 0)));
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /// Draw the `image` with the top-left corner at [x,y] to the screen.
 | 
			
		||||
  void image(int x, int y, Image *image);
 | 
			
		||||
  void image(int x, int y, Color color, Image *image, bool invert = false);
 | 
			
		||||
  /** Draw the `image` with the top-left corner at [x,y] to the screen.
 | 
			
		||||
   *
 | 
			
		||||
   * @param x The x coordinate of the upper left corner.
 | 
			
		||||
   * @param y The y coordinate of the upper left corner.
 | 
			
		||||
   * @param image The image to draw
 | 
			
		||||
   * @param color_on The color to replace in binary images for the on bits.
 | 
			
		||||
   * @param color_off The color to replace in binary images for the off bits.
 | 
			
		||||
   */
 | 
			
		||||
  void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
 | 
			
		||||
 | 
			
		||||
  /** Get the text bounds of the given string.
 | 
			
		||||
   *
 | 
			
		||||
@@ -381,11 +387,10 @@ class Font {
 | 
			
		||||
 | 
			
		||||
class Image {
 | 
			
		||||
 public:
 | 
			
		||||
  Image(const uint8_t *data_start, int width, int height);
 | 
			
		||||
  Image(const uint8_t *data_start, int width, int height, int type);
 | 
			
		||||
  bool get_pixel(int x, int y) const;
 | 
			
		||||
  Color get_color_pixel(int x, int y) const;
 | 
			
		||||
  Color get_grayscale_pixel(int x, int y) const;
 | 
			
		||||
  Image(const uint8_t *data_start, int width, int height, ImageType type);
 | 
			
		||||
  virtual bool get_pixel(int x, int y) const;
 | 
			
		||||
  virtual Color get_color_pixel(int x, int y) const;
 | 
			
		||||
  virtual Color get_grayscale_pixel(int x, int y) const;
 | 
			
		||||
  int get_width() const;
 | 
			
		||||
  int get_height() const;
 | 
			
		||||
  ImageType get_type() const;
 | 
			
		||||
@@ -393,10 +398,26 @@ class Image {
 | 
			
		||||
 protected:
 | 
			
		||||
  int width_;
 | 
			
		||||
  int height_;
 | 
			
		||||
  ImageType type_{BINARY};
 | 
			
		||||
  ImageType type_;
 | 
			
		||||
  const uint8_t *data_start_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Animation : public Image {
 | 
			
		||||
 public:
 | 
			
		||||
  Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
 | 
			
		||||
  bool get_pixel(int x, int y) const override;
 | 
			
		||||
  Color get_color_pixel(int x, int y) const override;
 | 
			
		||||
  Color get_grayscale_pixel(int x, int y) const override;
 | 
			
		||||
 | 
			
		||||
  int get_animation_frame_count() const;
 | 
			
		||||
  int get_current_frame() const;
 | 
			
		||||
  void next_frame();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  int current_frame_;
 | 
			
		||||
  int animation_frame_count_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  TEMPLATABLE_VALUE(DisplayPage *, page)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/ds1307/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/ds1307/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										105
									
								
								esphome/components/ds1307/ds1307.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								esphome/components/ds1307/ds1307.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
#include "ds1307.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
// Datasheet:
 | 
			
		||||
// - https://datasheets.maximintegrated.com/en/ds/DS1307.pdf
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ds1307 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ds1307";
 | 
			
		||||
 | 
			
		||||
void DS1307Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up DS1307...");
 | 
			
		||||
  if (!this->read_rtc_()) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DS1307Component::update() { this->read_time(); }
 | 
			
		||||
 | 
			
		||||
void DS1307Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "DS1307:");
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with DS1307 failed!");
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Timezone: '%s'", this->timezone_.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float DS1307Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
void DS1307Component::read_time() {
 | 
			
		||||
  if (!this->read_rtc_()) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (ds1307_.reg.ch) {
 | 
			
		||||
    ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  time::ESPTime rtc_time{.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10),
 | 
			
		||||
                         .minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10),
 | 
			
		||||
                         .hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10),
 | 
			
		||||
                         .day_of_week = uint8_t(ds1307_.reg.weekday),
 | 
			
		||||
                         .day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10),
 | 
			
		||||
                         .day_of_year = 1,  // ignored by recalc_timestamp_utc(false)
 | 
			
		||||
                         .month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10),
 | 
			
		||||
                         .year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000)};
 | 
			
		||||
  rtc_time.recalc_timestamp_utc(false);
 | 
			
		||||
  if (!rtc_time.is_valid()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DS1307Component::write_time() {
 | 
			
		||||
  auto now = time::RealTimeClock::utcnow();
 | 
			
		||||
  if (!now.is_valid()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Invalid system time, not syncing to RTC.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ds1307_.reg.year = (now.year - 2000) % 10;
 | 
			
		||||
  ds1307_.reg.year_10 = (now.year - 2000) / 10 % 10;
 | 
			
		||||
  ds1307_.reg.month = now.month % 10;
 | 
			
		||||
  ds1307_.reg.month_10 = now.month / 10;
 | 
			
		||||
  ds1307_.reg.day = now.day_of_month % 10;
 | 
			
		||||
  ds1307_.reg.day_10 = now.day_of_month / 10;
 | 
			
		||||
  ds1307_.reg.weekday = now.day_of_week;
 | 
			
		||||
  ds1307_.reg.hour = now.hour % 10;
 | 
			
		||||
  ds1307_.reg.hour_10 = now.hour / 10;
 | 
			
		||||
  ds1307_.reg.minute = now.minute % 10;
 | 
			
		||||
  ds1307_.reg.minute_10 = now.minute / 10;
 | 
			
		||||
  ds1307_.reg.second = now.second % 10;
 | 
			
		||||
  ds1307_.reg.second_10 = now.second / 10;
 | 
			
		||||
  ds1307_.reg.ch = false;
 | 
			
		||||
 | 
			
		||||
  this->write_rtc_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DS1307Component::read_rtc_() {
 | 
			
		||||
  if (!this->read_bytes(0, this->ds1307_.raw, sizeof(this->ds1307_.raw))) {
 | 
			
		||||
    ESP_LOGE(TAG, "Can't read I2C data.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Read  %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u  CH:%s RS:%0u SQWE:%s OUT:%s", ds1307_.reg.hour_10,
 | 
			
		||||
           ds1307_.reg.hour, ds1307_.reg.minute_10, ds1307_.reg.minute, ds1307_.reg.second_10, ds1307_.reg.second,
 | 
			
		||||
           ds1307_.reg.year_10, ds1307_.reg.year, ds1307_.reg.month_10, ds1307_.reg.month, ds1307_.reg.day_10,
 | 
			
		||||
           ds1307_.reg.day, ONOFF(ds1307_.reg.ch), ds1307_.reg.rs, ONOFF(ds1307_.reg.sqwe), ONOFF(ds1307_.reg.out));
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DS1307Component::write_rtc_() {
 | 
			
		||||
  if (!this->write_bytes(0, this->ds1307_.raw, sizeof(this->ds1307_.raw))) {
 | 
			
		||||
    ESP_LOGE(TAG, "Can't write I2C data.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Write %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u  CH:%s RS:%0u SQWE:%s OUT:%s", ds1307_.reg.hour_10,
 | 
			
		||||
           ds1307_.reg.hour, ds1307_.reg.minute_10, ds1307_.reg.minute, ds1307_.reg.second_10, ds1307_.reg.second,
 | 
			
		||||
           ds1307_.reg.year_10, ds1307_.reg.year, ds1307_.reg.month_10, ds1307_.reg.month, ds1307_.reg.day_10,
 | 
			
		||||
           ds1307_.reg.day, ONOFF(ds1307_.reg.ch), ds1307_.reg.rs, ONOFF(ds1307_.reg.sqwe), ONOFF(ds1307_.reg.out));
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
}  // namespace ds1307
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										70
									
								
								esphome/components/ds1307/ds1307.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								esphome/components/ds1307/ds1307.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/time/real_time_clock.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ds1307 {
 | 
			
		||||
 | 
			
		||||
class DS1307Component : public time::RealTimeClock, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void read_time();
 | 
			
		||||
  void write_time();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool read_rtc_();
 | 
			
		||||
  bool write_rtc_();
 | 
			
		||||
  union DS1307Reg {
 | 
			
		||||
    struct {
 | 
			
		||||
      uint8_t second : 4;
 | 
			
		||||
      uint8_t second_10 : 3;
 | 
			
		||||
      bool ch : 1;
 | 
			
		||||
 | 
			
		||||
      uint8_t minute : 4;
 | 
			
		||||
      uint8_t minute_10 : 3;
 | 
			
		||||
      uint8_t unused_1 : 1;
 | 
			
		||||
 | 
			
		||||
      uint8_t hour : 4;
 | 
			
		||||
      uint8_t hour_10 : 2;
 | 
			
		||||
      uint8_t unused_2 : 2;
 | 
			
		||||
 | 
			
		||||
      uint8_t weekday : 3;
 | 
			
		||||
      uint8_t unused_3 : 5;
 | 
			
		||||
 | 
			
		||||
      uint8_t day : 4;
 | 
			
		||||
      uint8_t day_10 : 2;
 | 
			
		||||
      uint8_t unused_4 : 2;
 | 
			
		||||
 | 
			
		||||
      uint8_t month : 4;
 | 
			
		||||
      uint8_t month_10 : 1;
 | 
			
		||||
      uint8_t unused_5 : 3;
 | 
			
		||||
 | 
			
		||||
      uint8_t year : 4;
 | 
			
		||||
      uint8_t year_10 : 4;
 | 
			
		||||
 | 
			
		||||
      uint8_t rs : 2;
 | 
			
		||||
      uint8_t unused_6 : 2;
 | 
			
		||||
      bool sqwe : 1;
 | 
			
		||||
      uint8_t unused_7 : 2;
 | 
			
		||||
      bool out : 1;
 | 
			
		||||
    } reg;
 | 
			
		||||
    mutable uint8_t raw[sizeof(reg)];
 | 
			
		||||
  } ds1307_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<DS1307Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  void play(Ts... x) override { this->parent_->write_time(); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<DS1307Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  void play(Ts... x) override { this->parent_->read_time(); }
 | 
			
		||||
};
 | 
			
		||||
}  // namespace ds1307
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										44
									
								
								esphome/components/ds1307/time.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphome/components/ds1307/time.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.components import i2c, time
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@badbadc0ffee']
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
ds1307_ns = cg.esphome_ns.namespace('ds1307')
 | 
			
		||||
DS1307Component = ds1307_ns.class_('DS1307Component', time.RealTimeClock, i2c.I2CDevice)
 | 
			
		||||
WriteAction = ds1307_ns.class_('WriteAction', automation.Action)
 | 
			
		||||
ReadAction = ds1307_ns.class_('ReadAction', automation.Action)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = time.TIME_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(DS1307Component),
 | 
			
		||||
}).extend(i2c.i2c_device_schema(0x68))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action('ds1307.write_time', WriteAction, cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.use_id(DS1307Component),
 | 
			
		||||
}))
 | 
			
		||||
def ds1307_write_time_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
			
		||||
    yield cg.register_parented(var, config[CONF_ID])
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action('ds1307.read_time', ReadAction, automation.maybe_simple_id({
 | 
			
		||||
    cv.GenerateID(): cv.use_id(DS1307Component),
 | 
			
		||||
}))
 | 
			
		||||
def ds1307_read_time_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
			
		||||
    yield cg.register_parented(var, config[CONF_ID])
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield i2c.register_i2c_device(var, config)
 | 
			
		||||
    yield time.register_time(var, config)
 | 
			
		||||
@@ -2,7 +2,8 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_NAME, CONF_PIN, CONF_SCL, CONF_SDA, \
 | 
			
		||||
    ESP_PLATFORM_ESP32, CONF_DATA_PINS, CONF_RESET_PIN, CONF_RESOLUTION, CONF_BRIGHTNESS
 | 
			
		||||
    ESP_PLATFORM_ESP32, CONF_DATA_PINS, CONF_RESET_PIN, CONF_RESOLUTION, CONF_BRIGHTNESS, \
 | 
			
		||||
    CONF_CONTRAST
 | 
			
		||||
 | 
			
		||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
 | 
			
		||||
DEPENDENCIES = ['api']
 | 
			
		||||
@@ -47,7 +48,6 @@ CONF_IDLE_FRAMERATE = 'idle_framerate'
 | 
			
		||||
CONF_JPEG_QUALITY = 'jpeg_quality'
 | 
			
		||||
CONF_VERTICAL_FLIP = 'vertical_flip'
 | 
			
		||||
CONF_HORIZONTAL_MIRROR = 'horizontal_mirror'
 | 
			
		||||
CONF_CONTRAST = 'contrast'
 | 
			
		||||
CONF_SATURATION = 'saturation'
 | 
			
		||||
CONF_TEST_PATTERN = 'test_pattern'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/ezo/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/ezo/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										86
									
								
								esphome/components/ezo/ezo.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								esphome/components/ezo/ezo.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
#include "ezo.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ezo {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ezo.sensor";
 | 
			
		||||
 | 
			
		||||
static const uint16_t EZO_STATE_WAIT = 1;
 | 
			
		||||
static const uint16_t EZO_STATE_SEND_TEMP = 2;
 | 
			
		||||
static const uint16_t EZO_STATE_WAIT_TEMP = 4;
 | 
			
		||||
 | 
			
		||||
void EZOSensor::dump_config() {
 | 
			
		||||
  LOG_SENSOR("", "EZO", this);
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with EZO circuit failed!");
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::update() {
 | 
			
		||||
  if (this->state_ & EZO_STATE_WAIT) {
 | 
			
		||||
    ESP_LOGE(TAG, "update overrun, still waiting for previous response");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t c = 'R';
 | 
			
		||||
  this->write_bytes_raw(&c, 1);
 | 
			
		||||
  this->state_ |= EZO_STATE_WAIT;
 | 
			
		||||
  this->start_time_ = millis();
 | 
			
		||||
  this->wait_time_ = 900;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::loop() {
 | 
			
		||||
  uint8_t buf[20];
 | 
			
		||||
  if (!(this->state_ & EZO_STATE_WAIT)) {
 | 
			
		||||
    if (this->state_ & EZO_STATE_SEND_TEMP) {
 | 
			
		||||
      int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_);
 | 
			
		||||
      this->write_bytes_raw(buf, len);
 | 
			
		||||
      this->state_ = EZO_STATE_WAIT | EZO_STATE_WAIT_TEMP;
 | 
			
		||||
      this->start_time_ = millis();
 | 
			
		||||
      this->wait_time_ = 300;
 | 
			
		||||
    }
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (millis() - this->start_time_ < this->wait_time_)
 | 
			
		||||
    return;
 | 
			
		||||
  buf[0] = 0;
 | 
			
		||||
  if (!this->read_bytes_raw(buf, 20)) {
 | 
			
		||||
    ESP_LOGE(TAG, "read error");
 | 
			
		||||
    this->state_ = 0;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  switch (buf[0]) {
 | 
			
		||||
    case 1:
 | 
			
		||||
      break;
 | 
			
		||||
    case 2:
 | 
			
		||||
      ESP_LOGE(TAG, "device returned a syntax error");
 | 
			
		||||
      break;
 | 
			
		||||
    case 254:
 | 
			
		||||
      return;  // keep waiting
 | 
			
		||||
    case 255:
 | 
			
		||||
      ESP_LOGE(TAG, "device returned no data");
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      ESP_LOGE(TAG, "device returned an unknown response: %d", buf[0]);
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->state_ & EZO_STATE_WAIT_TEMP) {
 | 
			
		||||
    this->state_ = 0;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->state_ &= ~EZO_STATE_WAIT;
 | 
			
		||||
  if (buf[0] != 1)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  float val = strtof((char *) &buf[1], nullptr);
 | 
			
		||||
  this->publish_state(val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EZOSensor::set_tempcomp_value(float temp) {
 | 
			
		||||
  this->tempcomp_ = temp;
 | 
			
		||||
  this->state_ |= EZO_STATE_SEND_TEMP;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ezo
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										28
									
								
								esphome/components/ezo/ezo.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/ezo/ezo.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ezo {
 | 
			
		||||
 | 
			
		||||
/// This class implements support for the EZO circuits in i2c mode
 | 
			
		||||
class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; };
 | 
			
		||||
 | 
			
		||||
  void set_tempcomp_value(float temp);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  unsigned long start_time_ = 0;
 | 
			
		||||
  unsigned long wait_time_ = 0;
 | 
			
		||||
  uint16_t state_ = 0;
 | 
			
		||||
  float tempcomp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ezo
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										23
									
								
								esphome/components/ezo/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphome/components/ezo/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@ssieb']
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
 | 
			
		||||
ezo_ns = cg.esphome_ns.namespace('ezo')
 | 
			
		||||
 | 
			
		||||
EZOSensor = ezo_ns.class_('EZOSensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(EZOSensor),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(None))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield sensor.register_sensor(var, config)
 | 
			
		||||
    yield i2c.register_i2c_device(var, config)
 | 
			
		||||
@@ -36,5 +36,7 @@ def new_fastled_light(config):
 | 
			
		||||
 | 
			
		||||
    yield light.register_light(var, config)
 | 
			
		||||
    # https://github.com/FastLED/FastLED/blob/master/library.json
 | 
			
		||||
    cg.add_library('FastLED', '3.3.3')
 | 
			
		||||
    # 3.3.3 has an issue on ESP32 with RMT and fastled_clockless:
 | 
			
		||||
    # https://github.com/esphome/issues/issues/1375
 | 
			
		||||
    cg.add_library('FastLED', '3.3.2')
 | 
			
		||||
    yield var
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ class FastLEDLightOutput : public light::AddressableLight {
 | 
			
		||||
    return *this->controller_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template<ESPIChipsets CHIPSET, uint8_t DATA_PIN, uint8_t CLOCK_PIN, EOrder RGB_ORDER, uint8_t SPI_DATA_RATE>
 | 
			
		||||
  template<ESPIChipsets CHIPSET, uint8_t DATA_PIN, uint8_t CLOCK_PIN, EOrder RGB_ORDER, uint32_t SPI_DATA_RATE>
 | 
			
		||||
  CLEDController &add_leds(int num_leds) {
 | 
			
		||||
    switch (CHIPSET) {
 | 
			
		||||
      case LPD8806: {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,8 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import fastled_base
 | 
			
		||||
from esphome.const import CONF_CHIPSET, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_NUM_LEDS, CONF_RGB_ORDER
 | 
			
		||||
from esphome.const import CONF_CHIPSET, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_DATA_RATE, \
 | 
			
		||||
        CONF_NUM_LEDS, CONF_RGB_ORDER
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ['fastled_base']
 | 
			
		||||
 | 
			
		||||
@@ -21,15 +22,24 @@ CONFIG_SCHEMA = fastled_base.BASE_SCHEMA.extend({
 | 
			
		||||
    cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
 | 
			
		||||
    cv.Required(CONF_DATA_PIN): pins.output_pin,
 | 
			
		||||
    cv.Required(CONF_CLOCK_PIN): pins.output_pin,
 | 
			
		||||
    cv.Optional(CONF_DATA_RATE): cv.frequency,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = yield fastled_base.new_fastled_light(config)
 | 
			
		||||
 | 
			
		||||
    rgb_order = None
 | 
			
		||||
    if CONF_RGB_ORDER in config:
 | 
			
		||||
        rgb_order = cg.RawExpression(config[CONF_RGB_ORDER])
 | 
			
		||||
    rgb_order = cg.RawExpression(config[CONF_RGB_ORDER] if CONF_RGB_ORDER in config else "RGB")
 | 
			
		||||
    data_rate = None
 | 
			
		||||
 | 
			
		||||
    if CONF_DATA_RATE in config:
 | 
			
		||||
        data_rate_khz = int(config[CONF_DATA_RATE] / 1000)
 | 
			
		||||
        if data_rate_khz < 1000:
 | 
			
		||||
            data_rate = cg.RawExpression(f"DATA_RATE_KHZ({data_rate_khz})")
 | 
			
		||||
        else:
 | 
			
		||||
            data_rate_mhz = int(data_rate_khz / 1000)
 | 
			
		||||
            data_rate = cg.RawExpression(f"DATA_RATE_MHZ({data_rate_mhz})")
 | 
			
		||||
    template_args = cg.TemplateArguments(cg.RawExpression(config[CONF_CHIPSET]),
 | 
			
		||||
                                         config[CONF_DATA_PIN], config[CONF_CLOCK_PIN], rgb_order)
 | 
			
		||||
                                         config[CONF_DATA_PIN], config[CONF_CLOCK_PIN], rgb_order,
 | 
			
		||||
                                         data_rate)
 | 
			
		||||
    cg.add(var.add_leds(template_args, config[CONF_NUM_LEDS]))
 | 
			
		||||
 
 | 
			
		||||
@@ -42,9 +42,9 @@ def validate_glyphs(value):
 | 
			
		||||
def validate_pillow_installed(value):
 | 
			
		||||
    try:
 | 
			
		||||
        import PIL
 | 
			
		||||
    except ImportError:
 | 
			
		||||
    except ImportError as err:
 | 
			
		||||
        raise cv.Invalid("Please install the pillow python package to use this feature. "
 | 
			
		||||
                         "(pip install pillow)")
 | 
			
		||||
                         "(pip install pillow)") from err
 | 
			
		||||
 | 
			
		||||
    if PIL.__version__[0] < '4':
 | 
			
		||||
        raise cv.Invalid("Please update your pillow installation to at least 4.0.x. "
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,10 @@ const uint8_t FUJITSU_GENERAL_FAN_HIGH_BYTE10 = 0x01;
 | 
			
		||||
const uint8_t FUJITSU_GENERAL_FAN_MEDIUM_BYTE10 = 0x02;
 | 
			
		||||
const uint8_t FUJITSU_GENERAL_FAN_LOW_BYTE10 = 0x03;
 | 
			
		||||
const uint8_t FUJITSU_GENERAL_FAN_SILENT_BYTE10 = 0x04;
 | 
			
		||||
const uint8_t FUJITSU_GENERAL_SWING_MASK_BYTE10 = 0b00010000;
 | 
			
		||||
const uint8_t FUJITSU_GENERAL_SWING_NONE_BYTE10 = 0x00;
 | 
			
		||||
const uint8_t FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 = 0x01;
 | 
			
		||||
const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 = 0x02;
 | 
			
		||||
const uint8_t FUJITSU_GENERAL_SWING_BOTH_BYTE10 = 0x03;
 | 
			
		||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE10 = 0x00;
 | 
			
		||||
 | 
			
		||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE11 = 0x00;
 | 
			
		||||
@@ -74,7 +77,12 @@ const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000;
 | 
			
		||||
 | 
			
		||||
const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY = 38000;
 | 
			
		||||
 | 
			
		||||
FujitsuGeneralClimate::FujitsuGeneralClimate() : ClimateIR(FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1) {}
 | 
			
		||||
FujitsuGeneralClimate::FujitsuGeneralClimate()
 | 
			
		||||
    : ClimateIR(
 | 
			
		||||
          FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1.0f, true, true,
 | 
			
		||||
          {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
 | 
			
		||||
          {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL,
 | 
			
		||||
           climate::CLIMATE_SWING_BOTH}) {}
 | 
			
		||||
 | 
			
		||||
void FujitsuGeneralClimate::transmit_state() {
 | 
			
		||||
  if (this->mode == climate::CLIMATE_MODE_OFF) {
 | 
			
		||||
@@ -101,8 +109,8 @@ void FujitsuGeneralClimate::transmit_state() {
 | 
			
		||||
  remote_state[15] = FUJITSU_GENERAL_BASE_BYTE15;
 | 
			
		||||
 | 
			
		||||
  // Set temperature
 | 
			
		||||
  uint8_t safecelsius = std::max((uint8_t) this->target_temperature, FUJITSU_GENERAL_TEMP_MIN);
 | 
			
		||||
  safecelsius = std::min(safecelsius, FUJITSU_GENERAL_TEMP_MAX);
 | 
			
		||||
  auto safecelsius =
 | 
			
		||||
      (uint8_t) roundf(clamp(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX));
 | 
			
		||||
  remote_state[8] = (byte) safecelsius - 16;
 | 
			
		||||
  remote_state[8] = remote_state[8] << 4;
 | 
			
		||||
 | 
			
		||||
@@ -119,18 +127,52 @@ void FujitsuGeneralClimate::transmit_state() {
 | 
			
		||||
    case climate::CLIMATE_MODE_HEAT:
 | 
			
		||||
      remote_state[9] = FUJITSU_GENERAL_MODE_HEAT_BYTE9;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_DRY:
 | 
			
		||||
      remote_state[9] = FUJITSU_GENERAL_MODE_DRY_BYTE9;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_FAN_ONLY:
 | 
			
		||||
      remote_state[9] = FUJITSU_GENERAL_MODE_FAN_BYTE9;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_AUTO:
 | 
			
		||||
    default:
 | 
			
		||||
      remote_state[9] = FUJITSU_GENERAL_MODE_AUTO_BYTE9;
 | 
			
		||||
      break;
 | 
			
		||||
      // TODO: CLIMATE_MODE_FAN_ONLY, CLIMATE_MODE_DRY, CLIMATE_MODE_10C are missing in esphome
 | 
			
		||||
      // TODO: CLIMATE_MODE_10C are missing in esphome
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO: missing support for fan speed
 | 
			
		||||
  remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10;
 | 
			
		||||
  // Set fan
 | 
			
		||||
  switch (this->fan_mode) {
 | 
			
		||||
    case climate::CLIMATE_FAN_HIGH:
 | 
			
		||||
      remote_state[10] = FUJITSU_GENERAL_FAN_HIGH_BYTE10;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_MEDIUM:
 | 
			
		||||
      remote_state[10] = FUJITSU_GENERAL_FAN_MEDIUM_BYTE10;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_LOW:
 | 
			
		||||
      remote_state[10] = FUJITSU_GENERAL_FAN_LOW_BYTE10;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_AUTO:
 | 
			
		||||
    default:
 | 
			
		||||
      remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO: missing support for swing
 | 
			
		||||
  // remote_state[10] = (byte) remote_state[10] | FUJITSU_GENERAL_SWING_MASK_BYTE10;
 | 
			
		||||
  // Set swing
 | 
			
		||||
  switch (this->swing_mode) {
 | 
			
		||||
    case climate::CLIMATE_SWING_VERTICAL:
 | 
			
		||||
      remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 << 4);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_SWING_HORIZONTAL:
 | 
			
		||||
      remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 << 4);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_SWING_BOTH:
 | 
			
		||||
      remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_BOTH_BYTE10 << 4);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_SWING_OFF:
 | 
			
		||||
    default:
 | 
			
		||||
      remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_NONE_BYTE10 << 4);
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO: missing support for outdoor unit low noise
 | 
			
		||||
  // remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,12 @@ from .. import gps_ns, GPSListener, CONF_GPS_ID, GPS
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['gps']
 | 
			
		||||
 | 
			
		||||
GPSTime = gps_ns.class_('GPSTime', time_.RealTimeClock, GPSListener)
 | 
			
		||||
GPSTime = gps_ns.class_('GPSTime', cg.PollingComponent, time_.RealTimeClock, GPSListener)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = time_.TIME_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(GPSTime),
 | 
			
		||||
    cv.GenerateID(CONF_GPS_ID): cv.use_id(GPS),
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
}).extend(cv.polling_component_schema('5min'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -9,13 +9,11 @@ namespace gps {
 | 
			
		||||
 | 
			
		||||
class GPSTime : public time::RealTimeClock, public GPSListener {
 | 
			
		||||
 public:
 | 
			
		||||
  void update() override { this->from_tiny_gps_(this->get_tiny_gps()); };
 | 
			
		||||
  void on_update(TinyGPSPlus &tiny_gps) override {
 | 
			
		||||
    if (!this->has_time_)
 | 
			
		||||
      this->from_tiny_gps_(tiny_gps);
 | 
			
		||||
  }
 | 
			
		||||
  void setup() override {
 | 
			
		||||
    this->set_interval(5 * 60 * 1000, [this]() { this->from_tiny_gps_(this->get_tiny_gps()); });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void from_tiny_gps_(TinyGPSPlus &tiny_gps);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/hbridge/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/hbridge/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										76
									
								
								esphome/components/hbridge/hbridge_light_output.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								esphome/components/hbridge/hbridge_light_output.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/output/float_output.h"
 | 
			
		||||
#include "esphome/components/light/light_output.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace hbridge {
 | 
			
		||||
 | 
			
		||||
// Using PollingComponent as the updates are more consistent and reduces flickering
 | 
			
		||||
class HBridgeLightOutput : public PollingComponent, public light::LightOutput {
 | 
			
		||||
 public:
 | 
			
		||||
  HBridgeLightOutput() : PollingComponent(1) {}
 | 
			
		||||
 | 
			
		||||
  void set_pina_pin(output::FloatOutput *pina_pin) { pina_pin_ = pina_pin; }
 | 
			
		||||
  void set_pinb_pin(output::FloatOutput *pinb_pin) { pinb_pin_ = pinb_pin; }
 | 
			
		||||
 | 
			
		||||
  light::LightTraits get_traits() override {
 | 
			
		||||
    auto traits = light::LightTraits();
 | 
			
		||||
    traits.set_supports_brightness(true);  // Dimming
 | 
			
		||||
    traits.set_supports_rgb(false);
 | 
			
		||||
    traits.set_supports_rgb_white_value(true);  // hbridge color
 | 
			
		||||
    traits.set_supports_color_temperature(false);
 | 
			
		||||
    return traits;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setup() override { this->forward_direction_ = false; }
 | 
			
		||||
 | 
			
		||||
  void update() override {
 | 
			
		||||
    // This method runs around 60 times per second
 | 
			
		||||
    // We cannot do the PWM ourselves so we are reliant on the hardware PWM
 | 
			
		||||
    if (!this->forward_direction_) {  // First LED Direction
 | 
			
		||||
      this->pinb_pin_->set_level(this->duty_off_);
 | 
			
		||||
      this->pina_pin_->set_level(this->pina_duty_);
 | 
			
		||||
      this->forward_direction_ = true;
 | 
			
		||||
    } else {  // Second LED Direction
 | 
			
		||||
      this->pina_pin_->set_level(this->duty_off_);
 | 
			
		||||
      this->pinb_pin_->set_level(this->pinb_duty_);
 | 
			
		||||
      this->forward_direction_ = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
  void write_state(light::LightState *state) override {
 | 
			
		||||
    float bright;
 | 
			
		||||
    state->current_values_as_brightness(&bright);
 | 
			
		||||
 | 
			
		||||
    state->set_gamma_correct(0);
 | 
			
		||||
    float red, green, blue, white;
 | 
			
		||||
    state->current_values_as_rgbw(&red, &green, &blue, &white);
 | 
			
		||||
 | 
			
		||||
    if ((white / bright) > 0.55) {
 | 
			
		||||
      this->pina_duty_ = (bright * (1 - (white / bright)));
 | 
			
		||||
      this->pinb_duty_ = bright;
 | 
			
		||||
    } else if (white < 0.45) {
 | 
			
		||||
      this->pina_duty_ = bright;
 | 
			
		||||
      this->pinb_duty_ = white;
 | 
			
		||||
    } else {
 | 
			
		||||
      this->pina_duty_ = bright;
 | 
			
		||||
      this->pinb_duty_ = bright;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  output::FloatOutput *pina_pin_;
 | 
			
		||||
  output::FloatOutput *pinb_pin_;
 | 
			
		||||
  float pina_duty_ = 0;
 | 
			
		||||
  float pinb_duty_ = 0;
 | 
			
		||||
  float duty_off_ = 0;
 | 
			
		||||
  bool forward_direction_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace hbridge
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										24
									
								
								esphome/components/hbridge/light.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/hbridge/light.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import light, output
 | 
			
		||||
from esphome.const import CONF_OUTPUT_ID, CONF_PIN_A, CONF_PIN_B
 | 
			
		||||
 | 
			
		||||
hbridge_ns = cg.esphome_ns.namespace('hbridge')
 | 
			
		||||
HBridgeLightOutput = hbridge_ns.class_('HBridgeLightOutput', cg.PollingComponent, light.LightOutput)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(HBridgeLightOutput),
 | 
			
		||||
    cv.Required(CONF_PIN_A): cv.use_id(output.FloatOutput),
 | 
			
		||||
    cv.Required(CONF_PIN_B): cv.use_id(output.FloatOutput),
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield light.register_light(var, config)
 | 
			
		||||
 | 
			
		||||
    hside = yield cg.get_variable(config[CONF_PIN_A])
 | 
			
		||||
    cg.add(var.set_pina_pin(hside))
 | 
			
		||||
    lside = yield cg.get_variable(config[CONF_PIN_B])
 | 
			
		||||
    cg.add(var.set_pinb_pin(lside))
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/hitachi_ac344/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/hitachi_ac344/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										18
									
								
								esphome/components/hitachi_ac344/climate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/hitachi_ac344/climate.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ['climate_ir']
 | 
			
		||||
 | 
			
		||||
hitachi_ac344_ns = cg.esphome_ns.namespace('hitachi_ac344')
 | 
			
		||||
HitachiClimate = hitachi_ac344_ns.class_('HitachiClimate', climate_ir.ClimateIR)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(HitachiClimate),
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    yield climate_ir.register_climate_ir(var, config)
 | 
			
		||||
							
								
								
									
										365
									
								
								esphome/components/hitachi_ac344/hitachi_ac344.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										365
									
								
								esphome/components/hitachi_ac344/hitachi_ac344.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,365 @@
 | 
			
		||||
#include "hitachi_ac344.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace hitachi_ac344 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "climate.hitachi_ac344";
 | 
			
		||||
 | 
			
		||||
void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, const uint8_t data) {
 | 
			
		||||
  if (offset >= 8 || !nbits)
 | 
			
		||||
    return;  // Short circuit as it won't change.
 | 
			
		||||
  // Calculate the mask for the supplied value.
 | 
			
		||||
  uint8_t mask = UINT8_MAX >> (8 - ((nbits > 8) ? 8 : nbits));
 | 
			
		||||
  // Calculate the mask & clear the space for the data.
 | 
			
		||||
  // Clear the destination bits.
 | 
			
		||||
  *dst &= ~(uint8_t)(mask << offset);
 | 
			
		||||
  // Merge in the data.
 | 
			
		||||
  *dst |= ((data & mask) << offset);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void set_bit(uint8_t *const data, const uint8_t position, const bool on) {
 | 
			
		||||
  uint8_t mask = 1 << position;
 | 
			
		||||
  if (on)
 | 
			
		||||
    *data |= mask;
 | 
			
		||||
  else
 | 
			
		||||
    *data &= ~mask;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) {
 | 
			
		||||
  for (uint16_t i = 1; i < length; i += 2) {
 | 
			
		||||
    // Code done this way to avoid a compiler warning bug.
 | 
			
		||||
    uint8_t inv = ~*(ptr + i - 1);
 | 
			
		||||
    *(ptr + i) = inv;
 | 
			
		||||
  }
 | 
			
		||||
  return ptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HitachiClimate::get_power_() { return remote_state_[HITACHI_AC344_POWER_BYTE] == HITACHI_AC344_POWER_ON; }
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::set_power_(bool on) {
 | 
			
		||||
  set_button_(HITACHI_AC344_BUTTON_POWER);
 | 
			
		||||
  remote_state_[HITACHI_AC344_POWER_BYTE] = on ? HITACHI_AC344_POWER_ON : HITACHI_AC344_POWER_OFF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t HitachiClimate::get_mode_() { return remote_state_[HITACHI_AC344_MODE_BYTE] & 0xF; }
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::set_mode_(uint8_t mode) {
 | 
			
		||||
  uint8_t new_mode = mode;
 | 
			
		||||
  switch (mode) {
 | 
			
		||||
    // Fan mode sets a special temp.
 | 
			
		||||
    case HITACHI_AC344_MODE_FAN:
 | 
			
		||||
      set_temp_(HITACHI_AC344_TEMP_FAN, false);
 | 
			
		||||
      break;
 | 
			
		||||
    case HITACHI_AC344_MODE_HEAT:
 | 
			
		||||
    case HITACHI_AC344_MODE_COOL:
 | 
			
		||||
    case HITACHI_AC344_MODE_DRY:
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      new_mode = HITACHI_AC344_MODE_COOL;
 | 
			
		||||
  }
 | 
			
		||||
  set_bits(&remote_state_[HITACHI_AC344_MODE_BYTE], 0, 4, new_mode);
 | 
			
		||||
  if (new_mode != HITACHI_AC344_MODE_FAN)
 | 
			
		||||
    set_temp_(previous_temp_);
 | 
			
		||||
  set_fan_(get_fan_());  // Reset the fan speed after the mode change.
 | 
			
		||||
  set_power_(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) {
 | 
			
		||||
  uint8_t temp;
 | 
			
		||||
  temp = std::min(celsius, HITACHI_AC344_TEMP_MAX);
 | 
			
		||||
  temp = std::max(temp, HITACHI_AC344_TEMP_MIN);
 | 
			
		||||
  set_bits(&remote_state_[HITACHI_AC344_TEMP_BYTE], HITACHI_AC344_TEMP_OFFSET, HITACHI_AC344_TEMP_SIZE, temp);
 | 
			
		||||
  if (previous_temp_ > temp)
 | 
			
		||||
    set_button_(HITACHI_AC344_BUTTON_TEMP_DOWN);
 | 
			
		||||
  else if (previous_temp_ < temp)
 | 
			
		||||
    set_button_(HITACHI_AC344_BUTTON_TEMP_UP);
 | 
			
		||||
  if (set_previous)
 | 
			
		||||
    previous_temp_ = temp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t HitachiClimate::get_fan_() { return remote_state_[HITACHI_AC344_FAN_BYTE] >> 4 & 0xF; }
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::set_fan_(uint8_t speed) {
 | 
			
		||||
  uint8_t new_speed = std::max(speed, HITACHI_AC344_FAN_MIN);
 | 
			
		||||
  uint8_t fan_max = HITACHI_AC344_FAN_MAX;
 | 
			
		||||
 | 
			
		||||
  // Only 2 x low speeds in Dry mode or Auto
 | 
			
		||||
  if (get_mode_() == HITACHI_AC344_MODE_DRY && speed == HITACHI_AC344_FAN_AUTO) {
 | 
			
		||||
    fan_max = HITACHI_AC344_FAN_AUTO;
 | 
			
		||||
  } else if (get_mode_() == HITACHI_AC344_MODE_DRY) {
 | 
			
		||||
    fan_max = HITACHI_AC344_FAN_MAX_DRY;
 | 
			
		||||
  } else if (get_mode_() == HITACHI_AC344_MODE_FAN && speed == HITACHI_AC344_FAN_AUTO) {
 | 
			
		||||
    // Fan Mode does not have auto. Set to safe low
 | 
			
		||||
    new_speed = HITACHI_AC344_FAN_MIN;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  new_speed = std::min(new_speed, fan_max);
 | 
			
		||||
  // Handle the setting the button value if we are going to change the value.
 | 
			
		||||
  if (new_speed != get_fan_())
 | 
			
		||||
    set_button_(HITACHI_AC344_BUTTON_FAN);
 | 
			
		||||
  // Set the values
 | 
			
		||||
 | 
			
		||||
  set_bits(&remote_state_[HITACHI_AC344_FAN_BYTE], 4, 4, new_speed);
 | 
			
		||||
  remote_state_[9] = 0x92;
 | 
			
		||||
 | 
			
		||||
  // When fan is at min/max, additional bytes seem to be set
 | 
			
		||||
  if (new_speed == HITACHI_AC344_FAN_MIN)
 | 
			
		||||
    remote_state_[9] = 0x98;
 | 
			
		||||
  remote_state_[29] = 0x01;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::set_swing_v_toggle_(bool on) {
 | 
			
		||||
  uint8_t button = get_button_();  // Get the current button value.
 | 
			
		||||
  if (on)
 | 
			
		||||
    button = HITACHI_AC344_BUTTON_SWINGV;          // Set the button to SwingV.
 | 
			
		||||
  else if (button == HITACHI_AC344_BUTTON_SWINGV)  // Asked to unset it
 | 
			
		||||
    // It was set previous, so use Power as a default
 | 
			
		||||
    button = HITACHI_AC344_BUTTON_POWER;
 | 
			
		||||
  set_button_(button);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HitachiClimate::get_swing_v_toggle_() { return get_button_() == HITACHI_AC344_BUTTON_SWINGV; }
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::set_swing_v_(bool on) {
 | 
			
		||||
  set_swing_v_toggle_(on);  // Set the button value.
 | 
			
		||||
  set_bit(&remote_state_[HITACHI_AC344_SWINGV_BYTE], HITACHI_AC344_SWINGV_OFFSET, on);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HitachiClimate::get_swing_v_() {
 | 
			
		||||
  return GETBIT8(remote_state_[HITACHI_AC344_SWINGV_BYTE], HITACHI_AC344_SWINGV_OFFSET);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::set_swing_h_(uint8_t position) {
 | 
			
		||||
  if (position > HITACHI_AC344_SWINGH_LEFT_MAX)
 | 
			
		||||
    return set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE);
 | 
			
		||||
  set_bits(&remote_state_[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE, position);
 | 
			
		||||
  set_button_(HITACHI_AC344_BUTTON_SWINGH);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t HitachiClimate::get_swing_h_() {
 | 
			
		||||
  return GETBITS8(remote_state_[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t HitachiClimate::get_button_() { return remote_state_[HITACHI_AC344_BUTTON_BYTE]; }
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::set_button_(uint8_t button) { remote_state_[HITACHI_AC344_BUTTON_BYTE] = button; }
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::transmit_state() {
 | 
			
		||||
  switch (this->mode) {
 | 
			
		||||
    case climate::CLIMATE_MODE_COOL:
 | 
			
		||||
      set_mode_(HITACHI_AC344_MODE_COOL);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_DRY:
 | 
			
		||||
      set_mode_(HITACHI_AC344_MODE_DRY);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_HEAT:
 | 
			
		||||
      set_mode_(HITACHI_AC344_MODE_HEAT);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_AUTO:
 | 
			
		||||
      set_mode_(HITACHI_AC344_MODE_AUTO);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_FAN_ONLY:
 | 
			
		||||
      set_mode_(HITACHI_AC344_MODE_FAN);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_OFF:
 | 
			
		||||
      set_power_(false);
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  set_temp_(static_cast<uint8_t>(this->target_temperature));
 | 
			
		||||
 | 
			
		||||
  switch (this->fan_mode) {
 | 
			
		||||
    case climate::CLIMATE_FAN_LOW:
 | 
			
		||||
      set_fan_(HITACHI_AC344_FAN_LOW);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_MEDIUM:
 | 
			
		||||
      set_fan_(HITACHI_AC344_FAN_MEDIUM);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_HIGH:
 | 
			
		||||
      set_fan_(HITACHI_AC344_FAN_HIGH);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_ON:
 | 
			
		||||
    case climate::CLIMATE_FAN_AUTO:
 | 
			
		||||
    default:
 | 
			
		||||
      set_fan_(HITACHI_AC344_FAN_AUTO);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (this->swing_mode) {
 | 
			
		||||
    case climate::CLIMATE_SWING_BOTH:
 | 
			
		||||
      set_swing_v_(true);
 | 
			
		||||
      set_swing_h_(HITACHI_AC344_SWINGH_AUTO);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_SWING_VERTICAL:
 | 
			
		||||
      set_swing_v_(true);
 | 
			
		||||
      set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_SWING_HORIZONTAL:
 | 
			
		||||
      set_swing_v_(false);
 | 
			
		||||
      set_swing_h_(HITACHI_AC344_SWINGH_AUTO);
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_SWING_OFF:
 | 
			
		||||
      set_swing_v_(false);
 | 
			
		||||
      set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE);
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO: find change value to set button, now always set to power button
 | 
			
		||||
  set_button_(HITACHI_AC344_BUTTON_POWER);
 | 
			
		||||
 | 
			
		||||
  invert_byte_pairs(remote_state_ + 3, HITACHI_AC344_STATE_LENGTH - 3);
 | 
			
		||||
 | 
			
		||||
  auto transmit = this->transmitter_->transmit();
 | 
			
		||||
  auto data = transmit.get_data();
 | 
			
		||||
  data->set_carrier_frequency(HITACHI_AC344_FREQ);
 | 
			
		||||
 | 
			
		||||
  uint8_t repeat = 0;
 | 
			
		||||
  for (uint8_t r = 0; r <= repeat; r++) {
 | 
			
		||||
    // Header
 | 
			
		||||
    data->item(HITACHI_AC344_HDR_MARK, HITACHI_AC344_HDR_SPACE);
 | 
			
		||||
    // Data
 | 
			
		||||
    for (uint8_t i : remote_state_) {
 | 
			
		||||
      for (uint8_t j = 0; j < 8; j++) {
 | 
			
		||||
        data->mark(HITACHI_AC344_BIT_MARK);
 | 
			
		||||
        bool bit = i & (1 << j);
 | 
			
		||||
        data->space(bit ? HITACHI_AC344_ONE_SPACE : HITACHI_AC344_ZERO_SPACE);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Footer
 | 
			
		||||
    data->item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_MIN_GAP);
 | 
			
		||||
  }
 | 
			
		||||
  transmit.perform();
 | 
			
		||||
 | 
			
		||||
  dump_state_("Sent", remote_state_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) {
 | 
			
		||||
  uint8_t power = remote_state[HITACHI_AC344_POWER_BYTE];
 | 
			
		||||
  ESP_LOGV(TAG, "Power: %02X %02X", remote_state[HITACHI_AC344_POWER_BYTE], power);
 | 
			
		||||
  uint8_t mode = remote_state[HITACHI_AC344_MODE_BYTE] & 0xF;
 | 
			
		||||
  ESP_LOGV(TAG, "Mode: %02X %02X", remote_state[HITACHI_AC344_MODE_BYTE], mode);
 | 
			
		||||
  if (power == HITACHI_AC344_POWER_ON) {
 | 
			
		||||
    switch (mode) {
 | 
			
		||||
      case HITACHI_AC344_MODE_COOL:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
        break;
 | 
			
		||||
      case HITACHI_AC344_MODE_DRY:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_DRY;
 | 
			
		||||
        break;
 | 
			
		||||
      case HITACHI_AC344_MODE_HEAT:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_HEAT;
 | 
			
		||||
        break;
 | 
			
		||||
      case HITACHI_AC344_MODE_AUTO:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_AUTO;
 | 
			
		||||
        break;
 | 
			
		||||
      case HITACHI_AC344_MODE_FAN:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_FAN_ONLY;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    this->mode = climate::CLIMATE_MODE_OFF;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HitachiClimate::parse_temperature_(const uint8_t remote_state[]) {
 | 
			
		||||
  uint8_t temperature =
 | 
			
		||||
      GETBITS8(remote_state[HITACHI_AC344_TEMP_BYTE], HITACHI_AC344_TEMP_OFFSET, HITACHI_AC344_TEMP_SIZE);
 | 
			
		||||
  this->target_temperature = temperature;
 | 
			
		||||
  ESP_LOGV(TAG, "Temperature: %02X %02u %04f", remote_state[HITACHI_AC344_TEMP_BYTE], temperature,
 | 
			
		||||
           this->target_temperature);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HitachiClimate::parse_fan_(const uint8_t remote_state[]) {
 | 
			
		||||
  uint8_t fan_mode = remote_state[HITACHI_AC344_FAN_BYTE] >> 4 & 0xF;
 | 
			
		||||
  ESP_LOGV(TAG, "Fan: %02X %02X", remote_state[HITACHI_AC344_FAN_BYTE], fan_mode);
 | 
			
		||||
  switch (fan_mode) {
 | 
			
		||||
    case HITACHI_AC344_FAN_MIN:
 | 
			
		||||
    case HITACHI_AC344_FAN_LOW:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_LOW;
 | 
			
		||||
      break;
 | 
			
		||||
    case HITACHI_AC344_FAN_MEDIUM:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
 | 
			
		||||
      break;
 | 
			
		||||
    case HITACHI_AC344_FAN_HIGH:
 | 
			
		||||
    case HITACHI_AC344_FAN_MAX:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_HIGH;
 | 
			
		||||
      break;
 | 
			
		||||
    case HITACHI_AC344_FAN_AUTO:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) {
 | 
			
		||||
  uint8_t swing_modeh =
 | 
			
		||||
      GETBITS8(remote_state[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE);
 | 
			
		||||
  ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh);
 | 
			
		||||
 | 
			
		||||
  if ((swing_modeh & 0x7) == 0x0) {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
 | 
			
		||||
  } else if ((swing_modeh & 0x3) == 0x3) {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_OFF;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
			
		||||
  // Validate header
 | 
			
		||||
  if (!data.expect_item(HITACHI_AC344_HDR_MARK, HITACHI_AC344_HDR_SPACE)) {
 | 
			
		||||
    ESP_LOGVV(TAG, "Header fail");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t recv_state[HITACHI_AC344_STATE_LENGTH] = {0};
 | 
			
		||||
  // Read all bytes.
 | 
			
		||||
  for (uint8_t pos = 0; pos < HITACHI_AC344_STATE_LENGTH; pos++) {
 | 
			
		||||
    // Read bit
 | 
			
		||||
    for (int8_t bit = 0; bit < 8; bit++) {
 | 
			
		||||
      if (data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ONE_SPACE))
 | 
			
		||||
        recv_state[pos] |= 1 << bit;
 | 
			
		||||
      else if (!data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ZERO_SPACE)) {
 | 
			
		||||
        ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit);
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Validate footer
 | 
			
		||||
  if (!data.expect_mark(HITACHI_AC344_BIT_MARK)) {
 | 
			
		||||
    ESP_LOGVV(TAG, "Footer fail");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  dump_state_("Recv", recv_state);
 | 
			
		||||
 | 
			
		||||
  // parse mode
 | 
			
		||||
  this->parse_mode_(recv_state);
 | 
			
		||||
  // parse temperature
 | 
			
		||||
  this->parse_temperature_(recv_state);
 | 
			
		||||
  // parse fan
 | 
			
		||||
  this->parse_fan_(recv_state);
 | 
			
		||||
  // parse swingv
 | 
			
		||||
  this->parse_swing_(recv_state);
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
  for (uint8_t i = 0; i < HITACHI_AC344_STATE_LENGTH; i++)
 | 
			
		||||
    remote_state_[i] = recv_state[i];
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HitachiClimate::dump_state_(const char action[], uint8_t state[]) {
 | 
			
		||||
  for (uint16_t i = 0; i < HITACHI_AC344_STATE_LENGTH - 10; i += 10) {
 | 
			
		||||
    ESP_LOGV(TAG, "%s: %02X %02X %02X %02X %02X  %02X %02X %02X %02X %02X", action, state[i + 0], state[i + 1],
 | 
			
		||||
             state[i + 2], state[i + 3], state[i + 4], state[i + 5], state[i + 6], state[i + 7], state[i + 8],
 | 
			
		||||
             state[i + 9]);
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "%s: %02X %02X %02X", action, state[40], state[41], state[42]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace hitachi_ac344
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										122
									
								
								esphome/components/hitachi_ac344/hitachi_ac344.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								esphome/components/hitachi_ac344/hitachi_ac344.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/components/climate_ir/climate_ir.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace hitachi_ac344 {
 | 
			
		||||
 | 
			
		||||
const uint16_t HITACHI_AC344_HDR_MARK = 3300;   // ac
 | 
			
		||||
const uint16_t HITACHI_AC344_HDR_SPACE = 1700;  // ac
 | 
			
		||||
const uint16_t HITACHI_AC344_BIT_MARK = 400;
 | 
			
		||||
const uint16_t HITACHI_AC344_ONE_SPACE = 1250;
 | 
			
		||||
const uint16_t HITACHI_AC344_ZERO_SPACE = 500;
 | 
			
		||||
const uint32_t HITACHI_AC344_MIN_GAP = 100000;  // just a guess.
 | 
			
		||||
const uint16_t HITACHI_AC344_FREQ = 38000;      // Hz.
 | 
			
		||||
 | 
			
		||||
const uint8_t HITACHI_AC344_BUTTON_BYTE = 11;
 | 
			
		||||
const uint8_t HITACHI_AC344_BUTTON_POWER = 0x13;
 | 
			
		||||
const uint8_t HITACHI_AC344_BUTTON_SLEEP = 0x31;
 | 
			
		||||
const uint8_t HITACHI_AC344_BUTTON_MODE = 0x41;
 | 
			
		||||
const uint8_t HITACHI_AC344_BUTTON_FAN = 0x42;
 | 
			
		||||
const uint8_t HITACHI_AC344_BUTTON_TEMP_DOWN = 0x43;
 | 
			
		||||
const uint8_t HITACHI_AC344_BUTTON_TEMP_UP = 0x44;
 | 
			
		||||
const uint8_t HITACHI_AC344_BUTTON_SWINGV = 0x81;
 | 
			
		||||
const uint8_t HITACHI_AC344_BUTTON_SWINGH = 0x8C;
 | 
			
		||||
const uint8_t HITACHI_AC344_BUTTON_MILDEWPROOF = 0xE2;
 | 
			
		||||
 | 
			
		||||
const uint8_t HITACHI_AC344_TEMP_BYTE = 13;
 | 
			
		||||
const uint8_t HITACHI_AC344_TEMP_OFFSET = 2;
 | 
			
		||||
const uint8_t HITACHI_AC344_TEMP_SIZE = 6;
 | 
			
		||||
const uint8_t HITACHI_AC344_TEMP_MIN = 16;  // 16C
 | 
			
		||||
const uint8_t HITACHI_AC344_TEMP_MAX = 32;  // 32C
 | 
			
		||||
const uint8_t HITACHI_AC344_TEMP_FAN = 27;  // 27C
 | 
			
		||||
 | 
			
		||||
const uint8_t HITACHI_AC344_TIMER_BYTE = 15;
 | 
			
		||||
 | 
			
		||||
const uint8_t HITACHI_AC344_MODE_BYTE = 25;
 | 
			
		||||
const uint8_t HITACHI_AC344_MODE_FAN = 1;
 | 
			
		||||
const uint8_t HITACHI_AC344_MODE_COOL = 3;
 | 
			
		||||
const uint8_t HITACHI_AC344_MODE_DRY = 5;
 | 
			
		||||
const uint8_t HITACHI_AC344_MODE_HEAT = 6;
 | 
			
		||||
const uint8_t HITACHI_AC344_MODE_AUTO = 7;
 | 
			
		||||
 | 
			
		||||
const uint8_t HITACHI_AC344_FAN_BYTE = HITACHI_AC344_MODE_BYTE;
 | 
			
		||||
const uint8_t HITACHI_AC344_FAN_MIN = 1;
 | 
			
		||||
const uint8_t HITACHI_AC344_FAN_LOW = 2;
 | 
			
		||||
const uint8_t HITACHI_AC344_FAN_MEDIUM = 3;
 | 
			
		||||
const uint8_t HITACHI_AC344_FAN_HIGH = 4;
 | 
			
		||||
const uint8_t HITACHI_AC344_FAN_AUTO = 5;
 | 
			
		||||
const uint8_t HITACHI_AC344_FAN_MAX = 6;
 | 
			
		||||
const uint8_t HITACHI_AC344_FAN_MAX_DRY = 2;
 | 
			
		||||
 | 
			
		||||
const uint8_t HITACHI_AC344_POWER_BYTE = 27;
 | 
			
		||||
const uint8_t HITACHI_AC344_POWER_ON = 0xF1;
 | 
			
		||||
const uint8_t HITACHI_AC344_POWER_OFF = 0xE1;
 | 
			
		||||
 | 
			
		||||
const uint8_t HITACHI_AC344_SWINGH_BYTE = 35;
 | 
			
		||||
const uint8_t HITACHI_AC344_SWINGH_OFFSET = 0;     // Mask 0b00000xxx
 | 
			
		||||
const uint8_t HITACHI_AC344_SWINGH_SIZE = 3;       // Mask 0b00000xxx
 | 
			
		||||
const uint8_t HITACHI_AC344_SWINGH_AUTO = 0;       // 0b000
 | 
			
		||||
const uint8_t HITACHI_AC344_SWINGH_RIGHT_MAX = 1;  // 0b001
 | 
			
		||||
const uint8_t HITACHI_AC344_SWINGH_RIGHT = 2;      // 0b010
 | 
			
		||||
const uint8_t HITACHI_AC344_SWINGH_MIDDLE = 3;     // 0b011
 | 
			
		||||
const uint8_t HITACHI_AC344_SWINGH_LEFT = 4;       // 0b100
 | 
			
		||||
const uint8_t HITACHI_AC344_SWINGH_LEFT_MAX = 5;   // 0b101
 | 
			
		||||
 | 
			
		||||
const uint8_t HITACHI_AC344_SWINGV_BYTE = 37;
 | 
			
		||||
const uint8_t HITACHI_AC344_SWINGV_OFFSET = 5;  // Mask 0b00x00000
 | 
			
		||||
 | 
			
		||||
const uint8_t HITACHI_AC344_MILDEWPROOF_BYTE = HITACHI_AC344_SWINGV_BYTE;
 | 
			
		||||
const uint8_t HITACHI_AC344_MILDEWPROOF_OFFSET = 2;  // Mask 0b00000x00
 | 
			
		||||
 | 
			
		||||
const uint16_t HITACHI_AC344_STATE_LENGTH = 43;
 | 
			
		||||
const uint16_t HITACHI_AC344_BITS = HITACHI_AC344_STATE_LENGTH * 8;
 | 
			
		||||
 | 
			
		||||
#define GETBIT8(a, b) (a & ((uint8_t) 1 << b))
 | 
			
		||||
#define GETBITS8(data, offset, size) (((data) & (((uint8_t) UINT8_MAX >> (8 - (size))) << (offset))) >> (offset))
 | 
			
		||||
 | 
			
		||||
class HitachiClimate : public climate_ir::ClimateIR {
 | 
			
		||||
 public:
 | 
			
		||||
  HitachiClimate()
 | 
			
		||||
      : climate_ir::ClimateIR(
 | 
			
		||||
            HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true,
 | 
			
		||||
            std::vector<climate::ClimateFanMode>{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
 | 
			
		||||
                                                 climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
 | 
			
		||||
            std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {}
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t remote_state_[HITACHI_AC344_STATE_LENGTH]{0x01, 0x10, 0x00, 0x40, 0x00, 0xFF, 0x00, 0xCC, 0x00, 0x00, 0x00,
 | 
			
		||||
                                                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
                                                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
 | 
			
		||||
                                                    0x80, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 | 
			
		||||
  uint8_t previous_temp_{27};
 | 
			
		||||
  // Transmit via IR the state of this climate controller.
 | 
			
		||||
  void transmit_state() override;
 | 
			
		||||
  bool get_power_();
 | 
			
		||||
  void set_power_(bool on);
 | 
			
		||||
  uint8_t get_mode_();
 | 
			
		||||
  void set_mode_(uint8_t mode);
 | 
			
		||||
  void set_temp_(uint8_t celsius, bool set_previous = false);
 | 
			
		||||
  uint8_t get_fan_();
 | 
			
		||||
  void set_fan_(uint8_t speed);
 | 
			
		||||
  void set_swing_v_toggle_(bool on);
 | 
			
		||||
  bool get_swing_v_toggle_();
 | 
			
		||||
  void set_swing_v_(bool on);
 | 
			
		||||
  bool get_swing_v_();
 | 
			
		||||
  void set_swing_h_(uint8_t position);
 | 
			
		||||
  uint8_t get_swing_h_();
 | 
			
		||||
  uint8_t get_button_();
 | 
			
		||||
  void set_button_(uint8_t button);
 | 
			
		||||
  // Handle received IR Buffer
 | 
			
		||||
  bool on_receive(remote_base::RemoteReceiveData data) override;
 | 
			
		||||
  bool parse_mode_(const uint8_t remote_state[]);
 | 
			
		||||
  bool parse_temperature_(const uint8_t remote_state[]);
 | 
			
		||||
  bool parse_fan_(const uint8_t remote_state[]);
 | 
			
		||||
  bool parse_swing_(const uint8_t remote_state[]);
 | 
			
		||||
  bool parse_state_frame_(const uint8_t frame[]);
 | 
			
		||||
  void dump_state_(const char action[], uint8_t remote_state[]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace hitachi_ac344
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -17,7 +17,7 @@ class AQICalculator : public AbstractAQICalculator {
 | 
			
		||||
 | 
			
		||||
  int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 51}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
 | 
			
		||||
 | 
			
		||||
  int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 45}, {36, 55}, {56, 150}, {151, 250}, {251, 500}};
 | 
			
		||||
  int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 35}, {36, 55}, {56, 150}, {151, 250}, {251, 500}};
 | 
			
		||||
 | 
			
		||||
  int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54},    {55, 154},  {155, 254},
 | 
			
		||||
                                                       {255, 354}, {355, 424}, {425, 604}};
 | 
			
		||||
@@ -33,7 +33,7 @@ class AQICalculator : public AbstractAQICalculator {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
 | 
			
		||||
    for (int i = 0; i < AMOUNT_OF_LEVELS - 1; i++) {
 | 
			
		||||
    for (int i = 0; i < AMOUNT_OF_LEVELS; i++) {
 | 
			
		||||
      if (value >= array[i][0] && value <= array[i][1]) {
 | 
			
		||||
        return i;
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -10,17 +10,13 @@ void HomeassistantTime::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Home Assistant Time:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Timezone: '%s'", this->timezone_.c_str());
 | 
			
		||||
}
 | 
			
		||||
float HomeassistantTime::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
void HomeassistantTime::setup() {
 | 
			
		||||
  global_homeassistant_time = this;
 | 
			
		||||
 | 
			
		||||
  this->set_interval(15 * 60 * 1000, []() {
 | 
			
		||||
    // re-request time every 15 minutes
 | 
			
		||||
    api::global_api_server->request_time();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
float HomeassistantTime::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
void HomeassistantTime::setup() { global_homeassistant_time = this; }
 | 
			
		||||
 | 
			
		||||
void HomeassistantTime::update() { api::global_api_server->request_time(); }
 | 
			
		||||
 | 
			
		||||
HomeassistantTime *global_homeassistant_time = nullptr;
 | 
			
		||||
 | 
			
		||||
}  // namespace homeassistant
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ namespace homeassistant {
 | 
			
		||||
class HomeassistantTime : public time::RealTimeClock {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void set_epoch_time(uint32_t epoch) { this->synchronize_epoch_(epoch); }
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.const import CONF_ID, CONF_TIMEOUT, CONF_ESPHOME, CONF_METHOD, \
 | 
			
		||||
    CONF_ARDUINO_VERSION, ARDUINO_VERSION_ESP8266_2_5_1, CONF_URL
 | 
			
		||||
    CONF_ARDUINO_VERSION, ARDUINO_VERSION_ESP8266, CONF_URL
 | 
			
		||||
from esphome.core import CORE, Lambda
 | 
			
		||||
from esphome.core_config import PLATFORMIO_ESP8266_LUT
 | 
			
		||||
 | 
			
		||||
@@ -34,7 +34,7 @@ def validate_framework(config):
 | 
			
		||||
        return config
 | 
			
		||||
 | 
			
		||||
    framework = PLATFORMIO_ESP8266_LUT[version] if version in PLATFORMIO_ESP8266_LUT else version
 | 
			
		||||
    if framework < ARDUINO_VERSION_ESP8266_2_5_1:
 | 
			
		||||
    if framework < ARDUINO_VERSION_ESP8266['2.5.1']:
 | 
			
		||||
        raise cv.Invalid('This component is not supported on arduino framework version below 2.5.1')
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
@@ -43,8 +43,8 @@ def validate_url(value):
 | 
			
		||||
    value = cv.string(value)
 | 
			
		||||
    try:
 | 
			
		||||
        parsed = list(urlparse.urlparse(value))
 | 
			
		||||
    except Exception:
 | 
			
		||||
        raise cv.Invalid('Invalid URL')
 | 
			
		||||
    except Exception as err:
 | 
			
		||||
        raise cv.Invalid('Invalid URL') from err
 | 
			
		||||
 | 
			
		||||
    if not parsed[0] or not parsed[1]:
 | 
			
		||||
        raise cv.Invalid('URL must have a URL scheme and host')
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,20 @@ void HttpRequestComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  User-Agent: %s", this->useragent_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpRequestComponent::set_url(std::string url) {
 | 
			
		||||
  this->url_ = url;
 | 
			
		||||
  this->secure_ = url.compare(0, 6, "https:") == 0;
 | 
			
		||||
 | 
			
		||||
  if (!this->last_url_.empty() && this->url_ != this->last_url_) {
 | 
			
		||||
    // Close connection if url has been changed
 | 
			
		||||
    this->client_.setReuse(false);
 | 
			
		||||
    this->client_.end();
 | 
			
		||||
  }
 | 
			
		||||
  this->client_.setReuse(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpRequestComponent::send() {
 | 
			
		||||
  bool begin_status = false;
 | 
			
		||||
  this->client_.setReuse(true);
 | 
			
		||||
  const String url = this->url_.c_str();
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
  begin_status = this->client_.begin(url);
 | 
			
		||||
@@ -78,7 +89,10 @@ WiFiClient *HttpRequestComponent::get_wifi_client_() {
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void HttpRequestComponent::close() { this->client_.end(); }
 | 
			
		||||
void HttpRequestComponent::close() {
 | 
			
		||||
  this->last_url_ = this->url_;
 | 
			
		||||
  this->client_.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const char *HttpRequestComponent::get_string() {
 | 
			
		||||
  static const String STR = this->client_.getString();
 | 
			
		||||
 
 | 
			
		||||
@@ -27,10 +27,7 @@ class HttpRequestComponent : public Component {
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
 | 
			
		||||
  void set_url(std::string url) {
 | 
			
		||||
    this->url_ = url;
 | 
			
		||||
    this->secure_ = url.compare(0, 6, "https:") == 0;
 | 
			
		||||
  }
 | 
			
		||||
  void set_url(std::string url);
 | 
			
		||||
  void set_method(const char *method) { this->method_ = method; }
 | 
			
		||||
  void set_useragent(const char *useragent) { this->useragent_ = useragent; }
 | 
			
		||||
  void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
 | 
			
		||||
@@ -43,6 +40,7 @@ class HttpRequestComponent : public Component {
 | 
			
		||||
 protected:
 | 
			
		||||
  HTTPClient client_{};
 | 
			
		||||
  std::string url_;
 | 
			
		||||
  std::string last_url_;
 | 
			
		||||
  const char *method_;
 | 
			
		||||
  const char *useragent_{nullptr};
 | 
			
		||||
  bool secure_;
 | 
			
		||||
 
 | 
			
		||||
@@ -56,8 +56,8 @@ void I2CComponent::raw_begin_transmission(uint8_t address) {
 | 
			
		||||
  ESP_LOGVV(TAG, "Beginning Transmission to 0x%02X:", address);
 | 
			
		||||
  this->wire_->beginTransmission(address);
 | 
			
		||||
}
 | 
			
		||||
bool I2CComponent::raw_end_transmission(uint8_t address) {
 | 
			
		||||
  uint8_t status = this->wire_->endTransmission();
 | 
			
		||||
bool I2CComponent::raw_end_transmission(uint8_t address, bool send_stop) {
 | 
			
		||||
  uint8_t status = this->wire_->endTransmission(send_stop);
 | 
			
		||||
  ESP_LOGVV(TAG, "    Transmission ended. Status code: 0x%02X", status);
 | 
			
		||||
 | 
			
		||||
  switch (status) {
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ class I2CComponent : public Component {
 | 
			
		||||
  void raw_begin_transmission(uint8_t address);
 | 
			
		||||
 | 
			
		||||
  /// End a write transmission to an address, return true if successful.
 | 
			
		||||
  bool raw_end_transmission(uint8_t address);
 | 
			
		||||
  bool raw_end_transmission(uint8_t address, bool send_stop = true);
 | 
			
		||||
 | 
			
		||||
  /** Request data from an address with a number of (8-bit) bytes.
 | 
			
		||||
   *
 | 
			
		||||
@@ -173,6 +173,17 @@ class I2CDevice {
 | 
			
		||||
 | 
			
		||||
  I2CRegister reg(uint8_t a_register) { return {this, a_register}; }
 | 
			
		||||
 | 
			
		||||
  /// Begin a write transmission.
 | 
			
		||||
  void raw_begin_transmission() { this->parent_->raw_begin_transmission(this->address_); };
 | 
			
		||||
 | 
			
		||||
  /// End a write transmission, return true if successful.
 | 
			
		||||
  bool raw_end_transmission(bool send_stop = true) {
 | 
			
		||||
    return this->parent_->raw_end_transmission(this->address_, send_stop);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /// Write len amount of bytes from data. begin_transmission_ must be called before this.
 | 
			
		||||
  void raw_write(const uint8_t *data, uint8_t len) { this->parent_->raw_write(this->address_, data, len); };
 | 
			
		||||
 | 
			
		||||
  /** Read len amount of bytes from a register into data. Optionally with a conversion time after
 | 
			
		||||
   * writing the register value to the bus.
 | 
			
		||||
   *
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/ili9341/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/ili9341/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										61
									
								
								esphome/components/ili9341/display.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								esphome/components/ili9341/display.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import display, spi
 | 
			
		||||
from esphome.const import CONF_DC_PIN, \
 | 
			
		||||
    CONF_ID, CONF_LAMBDA, CONF_MODEL, CONF_PAGES, CONF_RESET_PIN
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['spi']
 | 
			
		||||
 | 
			
		||||
CONF_LED_PIN = 'led_pin'
 | 
			
		||||
 | 
			
		||||
ili9341_ns = cg.esphome_ns.namespace('ili9341')
 | 
			
		||||
ili9341 = ili9341_ns.class_('ILI9341Display', cg.PollingComponent, spi.SPIDevice,
 | 
			
		||||
                            display.DisplayBuffer)
 | 
			
		||||
ILI9341M5Stack = ili9341_ns.class_('ILI9341M5Stack', ili9341)
 | 
			
		||||
ILI9341TFT24 = ili9341_ns.class_('ILI9341TFT24', ili9341)
 | 
			
		||||
 | 
			
		||||
ILI9341Model = ili9341_ns.enum('ILI9341Model')
 | 
			
		||||
 | 
			
		||||
MODELS = {
 | 
			
		||||
    'M5STACK': ILI9341Model.M5STACK,
 | 
			
		||||
    'TFT_2.4': ILI9341Model.TFT_24,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ILI9341_MODEL = cv.enum(MODELS, upper=True, space="_")
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(display.FULL_DISPLAY_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(ili9341),
 | 
			
		||||
    cv.Required(CONF_MODEL): ILI9341_MODEL,
 | 
			
		||||
    cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_LED_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema()),
 | 
			
		||||
                       cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    if config[CONF_MODEL] == 'M5STACK':
 | 
			
		||||
        lcd_type = ILI9341M5Stack
 | 
			
		||||
    if config[CONF_MODEL] == 'TFT_2.4':
 | 
			
		||||
        lcd_type = ILI9341TFT24
 | 
			
		||||
    rhs = lcd_type.new()
 | 
			
		||||
    var = cg.Pvariable(config[CONF_ID], rhs)
 | 
			
		||||
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield display.register_display(var, config)
 | 
			
		||||
    yield spi.register_spi_device(var, config)
 | 
			
		||||
    cg.add(var.set_model(config[CONF_MODEL]))
 | 
			
		||||
    dc = yield cg.gpio_pin_expression(config[CONF_DC_PIN])
 | 
			
		||||
    cg.add(var.set_dc_pin(dc))
 | 
			
		||||
 | 
			
		||||
    if CONF_LAMBDA in config:
 | 
			
		||||
        lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')],
 | 
			
		||||
                                          return_type=cg.void)
 | 
			
		||||
        cg.add(var.set_writer(lambda_))
 | 
			
		||||
    if CONF_RESET_PIN in config:
 | 
			
		||||
        reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN])
 | 
			
		||||
        cg.add(var.set_reset_pin(reset))
 | 
			
		||||
    if CONF_LED_PIN in config:
 | 
			
		||||
        led_pin = yield cg.gpio_pin_expression(config[CONF_LED_PIN])
 | 
			
		||||
        cg.add(var.set_led_pin(led_pin))
 | 
			
		||||
							
								
								
									
										83
									
								
								esphome/components/ili9341/ili9341_defines.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								esphome/components/ili9341/ili9341_defines.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ili9341 {
 | 
			
		||||
 | 
			
		||||
// Color definitions
 | 
			
		||||
// clang-format off
 | 
			
		||||
static const uint8_t MADCTL_MY    = 0x80;   ///< Bit 7 Bottom to top
 | 
			
		||||
static const uint8_t MADCTL_MX    = 0x40;   ///< Bit 6 Right to left
 | 
			
		||||
static const uint8_t MADCTL_MV    = 0x20;   ///< Bit 5 Reverse Mode
 | 
			
		||||
static const uint8_t MADCTL_ML    = 0x10;   ///< Bit 4 LCD refresh Bottom to top
 | 
			
		||||
static const uint8_t MADCTL_RGB   = 0x00;  ///< Bit 3 Red-Green-Blue pixel order
 | 
			
		||||
static const uint8_t MADCTL_BGR   = 0x08;  ///< Bit 3 Blue-Green-Red pixel order
 | 
			
		||||
static const uint8_t MADCTL_MH    = 0x04;   ///< Bit 2 LCD refresh right to left
 | 
			
		||||
// clang-format on
 | 
			
		||||
 | 
			
		||||
static const uint16_t ILI9341_TFTWIDTH = 320;   ///< ILI9341 max TFT width
 | 
			
		||||
static const uint16_t ILI9341_TFTHEIGHT = 240;  ///< ILI9341 max TFT height
 | 
			
		||||
 | 
			
		||||
// All ILI9341 specific commands some are used by init()
 | 
			
		||||
static const uint8_t ILI9341_NOP = 0x00;
 | 
			
		||||
static const uint8_t ILI9341_SWRESET = 0x01;
 | 
			
		||||
static const uint8_t ILI9341_RDDID = 0x04;
 | 
			
		||||
static const uint8_t ILI9341_RDDST = 0x09;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ILI9341_SLPIN = 0x10;
 | 
			
		||||
static const uint8_t ILI9341_SLPOUT = 0x11;
 | 
			
		||||
static const uint8_t ILI9341_PTLON = 0x12;
 | 
			
		||||
static const uint8_t ILI9341_NORON = 0x13;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ILI9341_RDMODE = 0x0A;
 | 
			
		||||
static const uint8_t ILI9341_RDMADCTL = 0x0B;
 | 
			
		||||
static const uint8_t ILI9341_RDPIXFMT = 0x0C;
 | 
			
		||||
static const uint8_t ILI9341_RDIMGFMT = 0x0A;
 | 
			
		||||
static const uint8_t ILI9341_RDSELFDIAG = 0x0F;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ILI9341_INVOFF = 0x20;
 | 
			
		||||
static const uint8_t ILI9341_INVON = 0x21;
 | 
			
		||||
static const uint8_t ILI9341_GAMMASET = 0x26;
 | 
			
		||||
static const uint8_t ILI9341_DISPOFF = 0x28;
 | 
			
		||||
static const uint8_t ILI9341_DISPON = 0x29;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ILI9341_CASET = 0x2A;
 | 
			
		||||
static const uint8_t ILI9341_PASET = 0x2B;
 | 
			
		||||
static const uint8_t ILI9341_RAMWR = 0x2C;
 | 
			
		||||
static const uint8_t ILI9341_RAMRD = 0x2E;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ILI9341_PTLAR = 0x30;
 | 
			
		||||
static const uint8_t ILI9341_VSCRDEF = 0x33;
 | 
			
		||||
static const uint8_t ILI9341_MADCTL = 0x36;
 | 
			
		||||
static const uint8_t ILI9341_VSCRSADD = 0x37;
 | 
			
		||||
static const uint8_t ILI9341_PIXFMT = 0x3A;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ILI9341_WRDISBV = 0x51;
 | 
			
		||||
static const uint8_t ILI9341_RDDISBV = 0x52;
 | 
			
		||||
static const uint8_t ILI9341_WRCTRLD = 0x53;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ILI9341_FRMCTR1 = 0xB1;
 | 
			
		||||
static const uint8_t ILI9341_FRMCTR2 = 0xB2;
 | 
			
		||||
static const uint8_t ILI9341_FRMCTR3 = 0xB3;
 | 
			
		||||
static const uint8_t ILI9341_INVCTR = 0xB4;
 | 
			
		||||
static const uint8_t ILI9341_DFUNCTR = 0xB6;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ILI9341_PWCTR1 = 0xC0;
 | 
			
		||||
static const uint8_t ILI9341_PWCTR2 = 0xC1;
 | 
			
		||||
static const uint8_t ILI9341_PWCTR3 = 0xC2;
 | 
			
		||||
static const uint8_t ILI9341_PWCTR4 = 0xC3;
 | 
			
		||||
static const uint8_t ILI9341_PWCTR5 = 0xC4;
 | 
			
		||||
static const uint8_t ILI9341_VMCTR1 = 0xC5;
 | 
			
		||||
static const uint8_t ILI9341_VMCTR2 = 0xC7;
 | 
			
		||||
 | 
			
		||||
static const uint8_t ILI9341_RDID4 = 0xD3;
 | 
			
		||||
static const uint8_t ILI9341_RDINDEX = 0xD9;
 | 
			
		||||
static const uint8_t ILI9341_RDID1 = 0xDA;
 | 
			
		||||
static const uint8_t ILI9341_RDID2 = 0xDB;
 | 
			
		||||
static const uint8_t ILI9341_RDID3 = 0xDC;
 | 
			
		||||
static const uint8_t ILI9341_RDIDX = 0xDD;  // TBC
 | 
			
		||||
 | 
			
		||||
static const uint8_t ILI9341_GMCTRP1 = 0xE0;
 | 
			
		||||
static const uint8_t ILI9341_GMCTRN1 = 0xE1;
 | 
			
		||||
 | 
			
		||||
}  // namespace ili9341
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										240
									
								
								esphome/components/ili9341/ili9341_display.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								esphome/components/ili9341/ili9341_display.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,240 @@
 | 
			
		||||
#include "ili9341_display.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ili9341 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "ili9341";
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::setup_pins_() {
 | 
			
		||||
  this->init_internal_(this->get_buffer_length_());
 | 
			
		||||
  this->dc_pin_->setup();  // OUTPUT
 | 
			
		||||
  this->dc_pin_->digital_write(false);
 | 
			
		||||
  if (this->reset_pin_ != nullptr) {
 | 
			
		||||
    this->reset_pin_->setup();  // OUTPUT
 | 
			
		||||
    this->reset_pin_->digital_write(true);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->led_pin_ != nullptr) {
 | 
			
		||||
    this->led_pin_->setup();
 | 
			
		||||
    this->led_pin_->digital_write(true);
 | 
			
		||||
  }
 | 
			
		||||
  this->spi_setup();
 | 
			
		||||
 | 
			
		||||
  this->reset_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::dump_config() {
 | 
			
		||||
  LOG_DISPLAY("", "ili9341", this);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Width: %d, Height: %d,  Rotation: %d", this->width_, this->height_, this->rotation_);
 | 
			
		||||
  LOG_PIN("  Reset Pin: ", this->reset_pin_);
 | 
			
		||||
  LOG_PIN("  DC Pin: ", this->dc_pin_);
 | 
			
		||||
  LOG_PIN("  Busy Pin: ", this->busy_pin_);
 | 
			
		||||
  LOG_PIN("  Backlight Pin: ", this->led_pin_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ILI9341Display::get_setup_priority() const { return setup_priority::PROCESSOR; }
 | 
			
		||||
void ILI9341Display::command(uint8_t value) {
 | 
			
		||||
  this->start_command_();
 | 
			
		||||
  this->write_byte(value);
 | 
			
		||||
  this->end_command_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::reset_() {
 | 
			
		||||
  if (this->reset_pin_ != nullptr) {
 | 
			
		||||
    this->reset_pin_->digital_write(false);
 | 
			
		||||
    delay(10);
 | 
			
		||||
    this->reset_pin_->digital_write(true);
 | 
			
		||||
    delay(10);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::data(uint8_t value) {
 | 
			
		||||
  this->start_data_();
 | 
			
		||||
  this->write_byte(value);
 | 
			
		||||
  this->end_data_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes) {
 | 
			
		||||
  this->command(command_byte);  // Send the command byte
 | 
			
		||||
  this->start_data_();
 | 
			
		||||
  this->write_array(data_bytes, num_data_bytes);
 | 
			
		||||
  this->end_data_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t ILI9341Display::read_command(uint8_t command_byte, uint8_t index) {
 | 
			
		||||
  uint8_t data = 0x10 + index;
 | 
			
		||||
  this->send_command(0xD9, &data, 1);  // Set Index Register
 | 
			
		||||
  uint8_t result;
 | 
			
		||||
  this->start_command_();
 | 
			
		||||
  this->write_byte(command_byte);
 | 
			
		||||
  this->start_data_();
 | 
			
		||||
  do {
 | 
			
		||||
    result = this->read_byte();
 | 
			
		||||
  } while (index--);
 | 
			
		||||
  this->end_data_();
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::update() {
 | 
			
		||||
  this->do_update_();
 | 
			
		||||
  this->display_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::display_() {
 | 
			
		||||
  // we will only update the changed window to the display
 | 
			
		||||
  int w = this->x_high_ - this->x_low_ + 1;
 | 
			
		||||
  int h = this->y_high_ - this->y_low_ + 1;
 | 
			
		||||
 | 
			
		||||
  set_addr_window_(this->x_low_, this->y_low_, w, h);
 | 
			
		||||
  this->start_data_();
 | 
			
		||||
  uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_);
 | 
			
		||||
  for (uint16_t row = 0; row < h; row++) {
 | 
			
		||||
    for (uint16_t col = 0; col < w; col++) {
 | 
			
		||||
      uint32_t pos = start_pos + (row * width_) + col;
 | 
			
		||||
 | 
			
		||||
      uint16_t color = convert_to_16bit_color_(buffer_[pos]);
 | 
			
		||||
      this->write_byte(color >> 8);
 | 
			
		||||
      this->write_byte(color);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->end_data_();
 | 
			
		||||
 | 
			
		||||
  // invalidate watermarks
 | 
			
		||||
  this->x_low_ = this->width_;
 | 
			
		||||
  this->y_low_ = this->height_;
 | 
			
		||||
  this->x_high_ = 0;
 | 
			
		||||
  this->y_high_ = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t ILI9341Display::convert_to_16bit_color_(uint8_t color_8bit) {
 | 
			
		||||
  int r = color_8bit >> 5;
 | 
			
		||||
  int g = (color_8bit >> 2) & 0x07;
 | 
			
		||||
  int b = color_8bit & 0x03;
 | 
			
		||||
  uint16_t color = (r * 0x04) << 11;
 | 
			
		||||
  color |= (g * 0x09) << 5;
 | 
			
		||||
  color |= (b * 0x0A);
 | 
			
		||||
 | 
			
		||||
  return color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t ILI9341Display::convert_to_8bit_color_(uint16_t color_16bit) {
 | 
			
		||||
  // convert 16bit color to 8 bit buffer
 | 
			
		||||
  uint8_t r = color_16bit >> 11;
 | 
			
		||||
  uint8_t g = (color_16bit >> 5) & 0x3F;
 | 
			
		||||
  uint8_t b = color_16bit & 0x1F;
 | 
			
		||||
 | 
			
		||||
  return ((b / 0x0A) | ((g / 0x09) << 2) | ((r / 0x04) << 5));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::fill(Color color) {
 | 
			
		||||
  auto color565 = color.to_rgb_565();
 | 
			
		||||
  memset(this->buffer_, convert_to_8bit_color_(color565), this->get_buffer_length_());
 | 
			
		||||
  this->x_low_ = 0;
 | 
			
		||||
  this->y_low_ = 0;
 | 
			
		||||
  this->x_high_ = this->get_width_internal() - 1;
 | 
			
		||||
  this->y_high_ = this->get_height_internal() - 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::fill_internal_(Color color) {
 | 
			
		||||
  this->set_addr_window_(0, 0, this->get_width_internal(), this->get_height_internal());
 | 
			
		||||
  this->start_data_();
 | 
			
		||||
 | 
			
		||||
  auto color565 = color.to_rgb_565();
 | 
			
		||||
  for (uint32_t i = 0; i < (this->get_width_internal()) * (this->get_height_internal()); i++) {
 | 
			
		||||
    this->write_byte(color565 >> 8);
 | 
			
		||||
    this->write_byte(color565);
 | 
			
		||||
    buffer_[i] = 0;
 | 
			
		||||
  }
 | 
			
		||||
  this->end_data_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) {
 | 
			
		||||
  if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  // low and high watermark may speed up drawing from buffer
 | 
			
		||||
  this->x_low_ = (x < this->x_low_) ? x : this->x_low_;
 | 
			
		||||
  this->y_low_ = (y < this->y_low_) ? y : this->y_low_;
 | 
			
		||||
  this->x_high_ = (x > this->x_high_) ? x : this->x_high_;
 | 
			
		||||
  this->y_high_ = (y > this->y_high_) ? y : this->y_high_;
 | 
			
		||||
 | 
			
		||||
  uint32_t pos = (y * width_) + x;
 | 
			
		||||
  auto color565 = color.to_rgb_565();
 | 
			
		||||
  buffer_[pos] = convert_to_8bit_color_(color565);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
 | 
			
		||||
// values per bit is huge
 | 
			
		||||
uint32_t ILI9341Display::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); }
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::start_command_() {
 | 
			
		||||
  this->dc_pin_->digital_write(false);
 | 
			
		||||
  this->enable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::end_command_() { this->disable(); }
 | 
			
		||||
void ILI9341Display::start_data_() {
 | 
			
		||||
  this->dc_pin_->digital_write(true);
 | 
			
		||||
  this->enable();
 | 
			
		||||
}
 | 
			
		||||
void ILI9341Display::end_data_() { this->disable(); }
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::init_lcd_(const uint8_t *init_cmd) {
 | 
			
		||||
  uint8_t cmd, x, num_args;
 | 
			
		||||
  const uint8_t *addr = init_cmd;
 | 
			
		||||
  while ((cmd = pgm_read_byte(addr++)) > 0) {
 | 
			
		||||
    x = pgm_read_byte(addr++);
 | 
			
		||||
    num_args = x & 0x7F;
 | 
			
		||||
    send_command(cmd, addr, num_args);
 | 
			
		||||
    addr += num_args;
 | 
			
		||||
    if (x & 0x80)
 | 
			
		||||
      delay(150);  // NOLINT
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t w, uint16_t h) {
 | 
			
		||||
  uint16_t x2 = (x1 + w - 1), y2 = (y1 + h - 1);
 | 
			
		||||
  this->command(ILI9341_CASET);  // Column address set
 | 
			
		||||
  this->start_data_();
 | 
			
		||||
  this->write_byte(x1 >> 8);
 | 
			
		||||
  this->write_byte(x1);
 | 
			
		||||
  this->write_byte(x2 >> 8);
 | 
			
		||||
  this->write_byte(x2);
 | 
			
		||||
  this->end_data_();
 | 
			
		||||
  this->command(ILI9341_PASET);  // Row address set
 | 
			
		||||
  this->start_data_();
 | 
			
		||||
  this->write_byte(y1 >> 8);
 | 
			
		||||
  this->write_byte(y1);
 | 
			
		||||
  this->write_byte(y2 >> 8);
 | 
			
		||||
  this->write_byte(y2);
 | 
			
		||||
  this->end_data_();
 | 
			
		||||
  this->command(ILI9341_RAMWR);  // Write to RAM
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ILI9341Display::invert_display_(bool invert) { this->command(invert ? ILI9341_INVON : ILI9341_INVOFF); }
 | 
			
		||||
 | 
			
		||||
int ILI9341Display::get_width_internal() { return this->width_; }
 | 
			
		||||
int ILI9341Display::get_height_internal() { return this->height_; }
 | 
			
		||||
 | 
			
		||||
//   M5Stack display
 | 
			
		||||
void ILI9341M5Stack::initialize() {
 | 
			
		||||
  this->init_lcd_(INITCMD_M5STACK);
 | 
			
		||||
  this->width_ = 320;
 | 
			
		||||
  this->height_ = 240;
 | 
			
		||||
  this->invert_display_(true);
 | 
			
		||||
  this->fill_internal_(COLOR_BLACK);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//   24_TFT display
 | 
			
		||||
void ILI9341TFT24::initialize() {
 | 
			
		||||
  this->init_lcd_(INITCMD_TFT);
 | 
			
		||||
  this->width_ = 240;
 | 
			
		||||
  this->height_ = 320;
 | 
			
		||||
  this->fill_internal_(COLOR_BLACK);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ili9341
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										92
									
								
								esphome/components/ili9341/ili9341_display.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								esphome/components/ili9341/ili9341_display.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/spi/spi.h"
 | 
			
		||||
#include "esphome/components/display/display_buffer.h"
 | 
			
		||||
#include "ili9341_defines.h"
 | 
			
		||||
#include "ili9341_init.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ili9341 {
 | 
			
		||||
 | 
			
		||||
enum ILI9341Model {
 | 
			
		||||
  M5STACK = 0,
 | 
			
		||||
  TFT_24,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ILI9341Display : public PollingComponent,
 | 
			
		||||
                       public display::DisplayBuffer,
 | 
			
		||||
                       public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
 | 
			
		||||
                                             spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
 | 
			
		||||
  void set_led_pin(GPIOPin *led) { this->led_pin_ = led; }
 | 
			
		||||
  void set_model(ILI9341Model model) { this->model_ = model; }
 | 
			
		||||
 | 
			
		||||
  void command(uint8_t value);
 | 
			
		||||
  void data(uint8_t value);
 | 
			
		||||
  void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
 | 
			
		||||
  uint8_t read_command(uint8_t command_byte, uint8_t index);
 | 
			
		||||
  virtual void initialize() = 0;
 | 
			
		||||
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  void fill(Color color) override;
 | 
			
		||||
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void setup() override {
 | 
			
		||||
    this->setup_pins_();
 | 
			
		||||
    this->initialize();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void draw_absolute_pixel_internal(int x, int y, Color color) override;
 | 
			
		||||
  void setup_pins_();
 | 
			
		||||
 | 
			
		||||
  void init_lcd_(const uint8_t *init_cmd);
 | 
			
		||||
  void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
 | 
			
		||||
  void invert_display_(bool invert);
 | 
			
		||||
  void reset_();
 | 
			
		||||
  void fill_internal_(Color color);
 | 
			
		||||
  void display_();
 | 
			
		||||
  uint16_t convert_to_16bit_color_(uint8_t color_8bit);
 | 
			
		||||
  uint8_t convert_to_8bit_color_(uint16_t color_16bit);
 | 
			
		||||
 | 
			
		||||
  ILI9341Model model_;
 | 
			
		||||
  int16_t width_{320};   ///< Display width as modified by current rotation
 | 
			
		||||
  int16_t height_{240};  ///< Display height as modified by current rotation
 | 
			
		||||
  uint16_t x_low_{0};
 | 
			
		||||
  uint16_t y_low_{0};
 | 
			
		||||
  uint16_t x_high_{0};
 | 
			
		||||
  uint16_t y_high_{0};
 | 
			
		||||
 | 
			
		||||
  uint32_t get_buffer_length_();
 | 
			
		||||
  int get_width_internal() override;
 | 
			
		||||
  int get_height_internal() override;
 | 
			
		||||
 | 
			
		||||
  void start_command_();
 | 
			
		||||
  void end_command_();
 | 
			
		||||
  void start_data_();
 | 
			
		||||
  void end_data_();
 | 
			
		||||
 | 
			
		||||
  GPIOPin *reset_pin_{nullptr};
 | 
			
		||||
  GPIOPin *led_pin_{nullptr};
 | 
			
		||||
  GPIOPin *dc_pin_;
 | 
			
		||||
  GPIOPin *busy_pin_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//-----------   M5Stack display --------------
 | 
			
		||||
class ILI9341M5Stack : public ILI9341Display {
 | 
			
		||||
 public:
 | 
			
		||||
  void initialize() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//-----------   ILI9341_24_TFT display --------------
 | 
			
		||||
class ILI9341TFT24 : public ILI9341Display {
 | 
			
		||||
 public:
 | 
			
		||||
  void initialize() override;
 | 
			
		||||
};
 | 
			
		||||
}  // namespace ili9341
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										70
									
								
								esphome/components/ili9341/ili9341_init.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								esphome/components/ili9341/ili9341_init.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ili9341 {
 | 
			
		||||
 | 
			
		||||
// clang-format off
 | 
			
		||||
static const uint8_t PROGMEM INITCMD_M5STACK[] = {
 | 
			
		||||
  0xEF, 3, 0x03, 0x80, 0x02,
 | 
			
		||||
  0xCF, 3, 0x00, 0xC1, 0x30,
 | 
			
		||||
  0xED, 4, 0x64, 0x03, 0x12, 0x81,
 | 
			
		||||
  0xE8, 3, 0x85, 0x00, 0x78,
 | 
			
		||||
  0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
 | 
			
		||||
  0xF7, 1, 0x20,
 | 
			
		||||
  0xEA, 2, 0x00, 0x00,
 | 
			
		||||
  ILI9341_PWCTR1  , 1, 0x23,             // Power control VRH[5:0]
 | 
			
		||||
  ILI9341_PWCTR2  , 1, 0x10,             // Power control SAP[2:0];BT[3:0]
 | 
			
		||||
  ILI9341_VMCTR1  , 2, 0x3e, 0x28,       // VCM control
 | 
			
		||||
  ILI9341_VMCTR2  , 1, 0x86,             // VCM control2
 | 
			
		||||
  ILI9341_MADCTL  , 1, MADCTL_BGR,       // Memory Access Control
 | 
			
		||||
  ILI9341_VSCRSADD, 1, 0x00,             // Vertical scroll zero
 | 
			
		||||
  ILI9341_PIXFMT  , 1, 0x55,
 | 
			
		||||
  ILI9341_FRMCTR1 , 2, 0x00, 0x13,
 | 
			
		||||
  ILI9341_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
 | 
			
		||||
  0xF2, 1, 0x00,                         // 3Gamma Function Disable
 | 
			
		||||
  ILI9341_GAMMASET , 1, 0x01,             // Gamma curve selected
 | 
			
		||||
  ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
 | 
			
		||||
                        0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 
 | 
			
		||||
                        0x0E, 0x09, 0x00,
 | 
			
		||||
  ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
 | 
			
		||||
                        0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 
 | 
			
		||||
                        0x31, 0x36, 0x0F,
 | 
			
		||||
  ILI9341_SLPOUT  , 0x80,                // Exit Sleep
 | 
			
		||||
  ILI9341_DISPON  , 0x80,                // Display on
 | 
			
		||||
  0x00                                   // End of list
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const uint8_t PROGMEM INITCMD_TFT[] = {
 | 
			
		||||
  0xEF, 3, 0x03, 0x80, 0x02,
 | 
			
		||||
  0xCF, 3, 0x00, 0xC1, 0x30,
 | 
			
		||||
  0xED, 4, 0x64, 0x03, 0x12, 0x81,
 | 
			
		||||
  0xE8, 3, 0x85, 0x00, 0x78,
 | 
			
		||||
  0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
 | 
			
		||||
  0xF7, 1, 0x20,
 | 
			
		||||
  0xEA, 2, 0x00, 0x00,
 | 
			
		||||
  ILI9341_PWCTR1  , 1, 0x23,             // Power control VRH[5:0]
 | 
			
		||||
  ILI9341_PWCTR2  , 1, 0x10,             // Power control SAP[2:0];BT[3:0]
 | 
			
		||||
  ILI9341_VMCTR1  , 2, 0x3e, 0x28,       // VCM control
 | 
			
		||||
  ILI9341_VMCTR2  , 1, 0x86,             // VCM control2
 | 
			
		||||
  ILI9341_MADCTL  , 1, 0x48,             // Memory Access Control
 | 
			
		||||
  ILI9341_VSCRSADD, 1, 0x00,             // Vertical scroll zero
 | 
			
		||||
  ILI9341_PIXFMT  , 1, 0x55,
 | 
			
		||||
  ILI9341_FRMCTR1 , 2, 0x00, 0x18,
 | 
			
		||||
  ILI9341_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
 | 
			
		||||
  0xF2, 1, 0x00,                         // 3Gamma Function Disable
 | 
			
		||||
  ILI9341_GAMMASET , 1, 0x01,             // Gamma curve selected
 | 
			
		||||
  ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
 | 
			
		||||
                        0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 
 | 
			
		||||
                        0x0E, 0x09, 0x00,
 | 
			
		||||
  ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
 | 
			
		||||
                        0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 
 | 
			
		||||
                        0x31, 0x36, 0x0F,
 | 
			
		||||
  ILI9341_SLPOUT  , 0x80,                // Exit Sleep
 | 
			
		||||
  ILI9341_DISPON  , 0x80,                // Display on
 | 
			
		||||
  0x00                                   // End of list
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// clang-format on
 | 
			
		||||
}  // namespace ili9341
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -4,14 +4,20 @@ from esphome import core
 | 
			
		||||
from esphome.components import display, font
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_FILE, CONF_ID, CONF_RESIZE, CONF_TYPE
 | 
			
		||||
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE, CONF_DITHER
 | 
			
		||||
from esphome.core import CORE, HexInt
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['display']
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
ImageType = {'binary': 0, 'grayscale': 1, 'rgb': 2}
 | 
			
		||||
ImageType = display.display_ns.enum('ImageType')
 | 
			
		||||
IMAGE_TYPE = {
 | 
			
		||||
    'BINARY': ImageType.IMAGE_TYPE_BINARY,
 | 
			
		||||
    'GRAYSCALE': ImageType.IMAGE_TYPE_GRAYSCALE,
 | 
			
		||||
    'RGB24': ImageType.IMAGE_TYPE_RGB24,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Image_ = display.display_ns.class_('Image')
 | 
			
		||||
 | 
			
		||||
@@ -21,7 +27,8 @@ IMAGE_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.Required(CONF_ID): cv.declare_id(Image_),
 | 
			
		||||
    cv.Required(CONF_FILE): cv.file_,
 | 
			
		||||
    cv.Optional(CONF_RESIZE): cv.dimensions,
 | 
			
		||||
    cv.Optional(CONF_TYPE): cv.string,
 | 
			
		||||
    cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(IMAGE_TYPE, upper=True),
 | 
			
		||||
    cv.Optional(CONF_DITHER, default='NONE'): cv.one_of("NONE", "FLOYDSTEINBERG", upper=True),
 | 
			
		||||
    cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@@ -37,44 +44,41 @@ def to_code(config):
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        raise core.EsphomeError(f"Could not load image file {path}: {e}")
 | 
			
		||||
 | 
			
		||||
    width, height = image.size
 | 
			
		||||
 | 
			
		||||
    if CONF_RESIZE in config:
 | 
			
		||||
        image.thumbnail(config[CONF_RESIZE])
 | 
			
		||||
 | 
			
		||||
    if CONF_TYPE in config:
 | 
			
		||||
        if config[CONF_TYPE].startswith('GRAYSCALE'):
 | 
			
		||||
            width, height = image.size
 | 
			
		||||
            image = image.convert('L', dither=Image.NONE)
 | 
			
		||||
            pixels = list(image.getdata())
 | 
			
		||||
            data = [0 for _ in range(height * width)]
 | 
			
		||||
            pos = 0
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                data[pos] = pix
 | 
			
		||||
                pos += 1
 | 
			
		||||
            rhs = [HexInt(x) for x in data]
 | 
			
		||||
            prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
 | 
			
		||||
            cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, ImageType['grayscale'])
 | 
			
		||||
        elif config[CONF_TYPE].startswith('RGB'):
 | 
			
		||||
            width, height = image.size
 | 
			
		||||
            image = image.convert('RGB')
 | 
			
		||||
            pixels = list(image.getdata())
 | 
			
		||||
            data = [0 for _ in range(height * width * 3)]
 | 
			
		||||
            pos = 0
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                data[pos] = pix[0]
 | 
			
		||||
                pos += 1
 | 
			
		||||
                data[pos] = pix[1]
 | 
			
		||||
                pos += 1
 | 
			
		||||
                data[pos] = pix[2]
 | 
			
		||||
                pos += 1
 | 
			
		||||
            rhs = [HexInt(x) for x in data]
 | 
			
		||||
            prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
 | 
			
		||||
            cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, ImageType['rgb'])
 | 
			
		||||
    else:
 | 
			
		||||
        image = image.convert('1', dither=Image.NONE)
 | 
			
		||||
        width, height = image.size
 | 
			
		||||
    else:
 | 
			
		||||
        if width > 500 or height > 500:
 | 
			
		||||
            _LOGGER.warning("The image you requested is very big. Please consider using"
 | 
			
		||||
                            " the resize parameter.")
 | 
			
		||||
 | 
			
		||||
    dither = Image.NONE if config[CONF_DITHER] == 'NONE' else Image.FLOYDSTEINBERG
 | 
			
		||||
    if config[CONF_TYPE] == 'GRAYSCALE':
 | 
			
		||||
        image = image.convert('L', dither=dither)
 | 
			
		||||
        pixels = list(image.getdata())
 | 
			
		||||
        data = [0 for _ in range(height * width)]
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for pix in pixels:
 | 
			
		||||
            data[pos] = pix
 | 
			
		||||
            pos += 1
 | 
			
		||||
 | 
			
		||||
    elif config[CONF_TYPE] == 'RGB24':
 | 
			
		||||
        image = image.convert('RGB')
 | 
			
		||||
        pixels = list(image.getdata())
 | 
			
		||||
        data = [0 for _ in range(height * width * 3)]
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for pix in pixels:
 | 
			
		||||
            data[pos] = pix[0]
 | 
			
		||||
            pos += 1
 | 
			
		||||
            data[pos] = pix[1]
 | 
			
		||||
            pos += 1
 | 
			
		||||
            data[pos] = pix[2]
 | 
			
		||||
            pos += 1
 | 
			
		||||
 | 
			
		||||
    elif config[CONF_TYPE] == 'BINARY':
 | 
			
		||||
        image = image.convert('1', dither=dither)
 | 
			
		||||
        width8 = ((width + 7) // 8) * 8
 | 
			
		||||
        data = [0 for _ in range(height * width8 // 8)]
 | 
			
		||||
        for y in range(height):
 | 
			
		||||
@@ -84,6 +88,7 @@ def to_code(config):
 | 
			
		||||
                pos = x + y * width8
 | 
			
		||||
                data[pos // 8] |= 0x80 >> (pos % 8)
 | 
			
		||||
 | 
			
		||||
        rhs = [HexInt(x) for x in data]
 | 
			
		||||
        prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
 | 
			
		||||
        cg.new_Pvariable(config[CONF_ID], prog_arr, width, height)
 | 
			
		||||
    rhs = [HexInt(x) for x in data]
 | 
			
		||||
    prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
 | 
			
		||||
    cg.new_Pvariable(config[CONF_ID], prog_arr, width, height,
 | 
			
		||||
                     IMAGE_TYPE[config[CONF_TYPE]])
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								esphome/components/inkplate6/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/inkplate6/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
CODEOWNERS = ['@jesserockz']
 | 
			
		||||
							
								
								
									
										141
									
								
								esphome/components/inkplate6/display.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								esphome/components/inkplate6/display.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import display, i2c
 | 
			
		||||
from esphome.const import CONF_FULL_UPDATE_EVERY, CONF_ID, CONF_LAMBDA, CONF_PAGES, \
 | 
			
		||||
    CONF_WAKEUP_PIN, ESP_PLATFORM_ESP32
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
 | 
			
		||||
 | 
			
		||||
CONF_DISPLAY_DATA_0_PIN = 'display_data_0_pin'
 | 
			
		||||
CONF_DISPLAY_DATA_1_PIN = 'display_data_1_pin'
 | 
			
		||||
CONF_DISPLAY_DATA_2_PIN = 'display_data_2_pin'
 | 
			
		||||
CONF_DISPLAY_DATA_3_PIN = 'display_data_3_pin'
 | 
			
		||||
CONF_DISPLAY_DATA_4_PIN = 'display_data_4_pin'
 | 
			
		||||
CONF_DISPLAY_DATA_5_PIN = 'display_data_5_pin'
 | 
			
		||||
CONF_DISPLAY_DATA_6_PIN = 'display_data_6_pin'
 | 
			
		||||
CONF_DISPLAY_DATA_7_PIN = 'display_data_7_pin'
 | 
			
		||||
 | 
			
		||||
CONF_CL_PIN = 'cl_pin'
 | 
			
		||||
CONF_CKV_PIN = 'ckv_pin'
 | 
			
		||||
CONF_GREYSCALE = 'greyscale'
 | 
			
		||||
CONF_GMOD_PIN = 'gmod_pin'
 | 
			
		||||
CONF_GPIO0_ENABLE_PIN = 'gpio0_enable_pin'
 | 
			
		||||
CONF_LE_PIN = 'le_pin'
 | 
			
		||||
CONF_OE_PIN = 'oe_pin'
 | 
			
		||||
CONF_PARTIAL_UPDATING = 'partial_updating'
 | 
			
		||||
CONF_POWERUP_PIN = 'powerup_pin'
 | 
			
		||||
CONF_SPH_PIN = 'sph_pin'
 | 
			
		||||
CONF_SPV_PIN = 'spv_pin'
 | 
			
		||||
CONF_VCOM_PIN = 'vcom_pin'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
inkplate6_ns = cg.esphome_ns.namespace('inkplate6')
 | 
			
		||||
Inkplate6 = inkplate6_ns.class_('Inkplate6', cg.PollingComponent, i2c.I2CDevice,
 | 
			
		||||
                                display.DisplayBuffer)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(display.FULL_DISPLAY_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(Inkplate6),
 | 
			
		||||
    cv.Optional(CONF_GREYSCALE, default=False): cv.boolean,
 | 
			
		||||
    cv.Optional(CONF_PARTIAL_UPDATING, default=True): cv.boolean,
 | 
			
		||||
    cv.Optional(CONF_FULL_UPDATE_EVERY, default=10): cv.uint32_t,
 | 
			
		||||
    # Control pins
 | 
			
		||||
    cv.Required(CONF_CKV_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Required(CONF_GMOD_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Required(CONF_GPIO0_ENABLE_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Required(CONF_OE_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Required(CONF_POWERUP_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Required(CONF_SPH_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Required(CONF_SPV_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Required(CONF_VCOM_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Required(CONF_WAKEUP_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_CL_PIN, default=0): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_LE_PIN, default=2): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    # Data pins
 | 
			
		||||
    cv.Optional(CONF_DISPLAY_DATA_0_PIN, default=4): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_DISPLAY_DATA_1_PIN, default=5): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_DISPLAY_DATA_2_PIN, default=18): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_DISPLAY_DATA_3_PIN, default=19): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_DISPLAY_DATA_4_PIN, default=23): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_DISPLAY_DATA_5_PIN, default=25): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_DISPLAY_DATA_6_PIN, default=26): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_DISPLAY_DATA_7_PIN, default=27): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
}).extend(cv.polling_component_schema('5s').extend(i2c.i2c_device_schema(0x48))),
 | 
			
		||||
                       cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield display.register_display(var, config)
 | 
			
		||||
    yield i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_LAMBDA in config:
 | 
			
		||||
        lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')],
 | 
			
		||||
                                          return_type=cg.void)
 | 
			
		||||
        cg.add(var.set_writer(lambda_))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_greyscale(config[CONF_GREYSCALE]))
 | 
			
		||||
    cg.add(var.set_partial_updating(config[CONF_PARTIAL_UPDATING]))
 | 
			
		||||
    cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
 | 
			
		||||
 | 
			
		||||
    ckv = yield cg.gpio_pin_expression(config[CONF_CKV_PIN])
 | 
			
		||||
    cg.add(var.set_ckv_pin(ckv))
 | 
			
		||||
 | 
			
		||||
    gmod = yield cg.gpio_pin_expression(config[CONF_GMOD_PIN])
 | 
			
		||||
    cg.add(var.set_gmod_pin(gmod))
 | 
			
		||||
 | 
			
		||||
    gpio0_enable = yield cg.gpio_pin_expression(config[CONF_GPIO0_ENABLE_PIN])
 | 
			
		||||
    cg.add(var.set_gpio0_enable_pin(gpio0_enable))
 | 
			
		||||
 | 
			
		||||
    oe = yield cg.gpio_pin_expression(config[CONF_OE_PIN])
 | 
			
		||||
    cg.add(var.set_oe_pin(oe))
 | 
			
		||||
 | 
			
		||||
    powerup = yield cg.gpio_pin_expression(config[CONF_POWERUP_PIN])
 | 
			
		||||
    cg.add(var.set_powerup_pin(powerup))
 | 
			
		||||
 | 
			
		||||
    sph = yield cg.gpio_pin_expression(config[CONF_SPH_PIN])
 | 
			
		||||
    cg.add(var.set_sph_pin(sph))
 | 
			
		||||
 | 
			
		||||
    spv = yield cg.gpio_pin_expression(config[CONF_SPV_PIN])
 | 
			
		||||
    cg.add(var.set_spv_pin(spv))
 | 
			
		||||
 | 
			
		||||
    vcom = yield cg.gpio_pin_expression(config[CONF_VCOM_PIN])
 | 
			
		||||
    cg.add(var.set_vcom_pin(vcom))
 | 
			
		||||
 | 
			
		||||
    wakeup = yield cg.gpio_pin_expression(config[CONF_WAKEUP_PIN])
 | 
			
		||||
    cg.add(var.set_wakeup_pin(wakeup))
 | 
			
		||||
 | 
			
		||||
    cl = yield cg.gpio_pin_expression(config[CONF_CL_PIN])
 | 
			
		||||
    cg.add(var.set_cl_pin(cl))
 | 
			
		||||
 | 
			
		||||
    le = yield cg.gpio_pin_expression(config[CONF_LE_PIN])
 | 
			
		||||
    cg.add(var.set_le_pin(le))
 | 
			
		||||
 | 
			
		||||
    display_data_0 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_0_PIN])
 | 
			
		||||
    cg.add(var.set_display_data_0_pin(display_data_0))
 | 
			
		||||
 | 
			
		||||
    display_data_1 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_1_PIN])
 | 
			
		||||
    cg.add(var.set_display_data_1_pin(display_data_1))
 | 
			
		||||
 | 
			
		||||
    display_data_2 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_2_PIN])
 | 
			
		||||
    cg.add(var.set_display_data_2_pin(display_data_2))
 | 
			
		||||
 | 
			
		||||
    display_data_3 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_3_PIN])
 | 
			
		||||
    cg.add(var.set_display_data_3_pin(display_data_3))
 | 
			
		||||
 | 
			
		||||
    display_data_4 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_4_PIN])
 | 
			
		||||
    cg.add(var.set_display_data_4_pin(display_data_4))
 | 
			
		||||
 | 
			
		||||
    display_data_5 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_5_PIN])
 | 
			
		||||
    cg.add(var.set_display_data_5_pin(display_data_5))
 | 
			
		||||
 | 
			
		||||
    display_data_6 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_6_PIN])
 | 
			
		||||
    cg.add(var.set_display_data_6_pin(display_data_6))
 | 
			
		||||
 | 
			
		||||
    display_data_7 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_7_PIN])
 | 
			
		||||
    cg.add(var.set_display_data_7_pin(display_data_7))
 | 
			
		||||
 | 
			
		||||
    cg.add_build_flag('-DBOARD_HAS_PSRAM')
 | 
			
		||||
							
								
								
									
										630
									
								
								esphome/components/inkplate6/inkplate.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										630
									
								
								esphome/components/inkplate6/inkplate.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,630 @@
 | 
			
		||||
#include "inkplate.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace inkplate6 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "inkplate";
 | 
			
		||||
 | 
			
		||||
void Inkplate6::setup() {
 | 
			
		||||
  this->initialize_();
 | 
			
		||||
 | 
			
		||||
  this->vcom_pin_->setup();
 | 
			
		||||
  this->powerup_pin_->setup();
 | 
			
		||||
  this->wakeup_pin_->setup();
 | 
			
		||||
  this->gpio0_enable_pin_->setup();
 | 
			
		||||
  this->gpio0_enable_pin_->digital_write(true);
 | 
			
		||||
 | 
			
		||||
  this->cl_pin_->setup();
 | 
			
		||||
  this->le_pin_->setup();
 | 
			
		||||
  this->ckv_pin_->setup();
 | 
			
		||||
  this->gmod_pin_->setup();
 | 
			
		||||
  this->oe_pin_->setup();
 | 
			
		||||
  this->sph_pin_->setup();
 | 
			
		||||
  this->spv_pin_->setup();
 | 
			
		||||
 | 
			
		||||
  this->display_data_0_pin_->setup();
 | 
			
		||||
  this->display_data_1_pin_->setup();
 | 
			
		||||
  this->display_data_2_pin_->setup();
 | 
			
		||||
  this->display_data_3_pin_->setup();
 | 
			
		||||
  this->display_data_4_pin_->setup();
 | 
			
		||||
  this->display_data_5_pin_->setup();
 | 
			
		||||
  this->display_data_6_pin_->setup();
 | 
			
		||||
  this->display_data_7_pin_->setup();
 | 
			
		||||
 | 
			
		||||
  this->clean();
 | 
			
		||||
  this->display();
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::initialize_() {
 | 
			
		||||
  uint32_t buffer_size = this->get_buffer_length_();
 | 
			
		||||
 | 
			
		||||
  if (this->partial_buffer_ != nullptr) {
 | 
			
		||||
    free(this->partial_buffer_);  // NOLINT
 | 
			
		||||
  }
 | 
			
		||||
  if (this->partial_buffer_2_ != nullptr) {
 | 
			
		||||
    free(this->partial_buffer_2_);  // NOLINT
 | 
			
		||||
  }
 | 
			
		||||
  if (this->buffer_ != nullptr) {
 | 
			
		||||
    free(this->buffer_);  // NOLINT
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->buffer_ = (uint8_t *) ps_malloc(buffer_size);
 | 
			
		||||
  if (this->buffer_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Could not allocate buffer for display!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->greyscale_) {
 | 
			
		||||
    this->partial_buffer_ = (uint8_t *) ps_malloc(buffer_size);
 | 
			
		||||
    if (this->partial_buffer_ == nullptr) {
 | 
			
		||||
      ESP_LOGE(TAG, "Could not allocate partial buffer for display!");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this->partial_buffer_2_ = (uint8_t *) ps_malloc(buffer_size * 2);
 | 
			
		||||
    if (this->partial_buffer_2_ == nullptr) {
 | 
			
		||||
      ESP_LOGE(TAG, "Could not allocate partial buffer 2 for display!");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    memset(this->partial_buffer_, 0, buffer_size);
 | 
			
		||||
    memset(this->partial_buffer_2_, 0, buffer_size * 2);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  memset(this->buffer_, 0, buffer_size);
 | 
			
		||||
}
 | 
			
		||||
float Inkplate6::get_setup_priority() const { return setup_priority::PROCESSOR; }
 | 
			
		||||
size_t Inkplate6::get_buffer_length_() {
 | 
			
		||||
  if (this->greyscale_) {
 | 
			
		||||
    return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 2u;
 | 
			
		||||
  } else {
 | 
			
		||||
    return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::update() {
 | 
			
		||||
  this->do_update_();
 | 
			
		||||
 | 
			
		||||
  if (this->full_update_every_ > 0 && this->partial_updates_ >= this->full_update_every_) {
 | 
			
		||||
    this->block_partial_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->display();
 | 
			
		||||
}
 | 
			
		||||
void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) {
 | 
			
		||||
  if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->greyscale_) {
 | 
			
		||||
    int x1 = x / 2;
 | 
			
		||||
    int x_sub = x % 2;
 | 
			
		||||
    uint32_t pos = (x1 + y * (this->get_width_internal() / 2));
 | 
			
		||||
    uint8_t current = this->buffer_[pos];
 | 
			
		||||
 | 
			
		||||
    // float px = (0.2126 * (color.red / 255.0)) + (0.7152 * (color.green / 255.0)) + (0.0722 * (color.blue / 255.0));
 | 
			
		||||
    // px = pow(px, 1.5);
 | 
			
		||||
    // uint8_t gs = (uint8_t)(px*7);
 | 
			
		||||
 | 
			
		||||
    uint8_t gs = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5;
 | 
			
		||||
    this->buffer_[pos] = (pixelMaskGLUT[x_sub] & current) | (x_sub ? gs : gs << 4);
 | 
			
		||||
 | 
			
		||||
  } else {
 | 
			
		||||
    int x1 = x / 8;
 | 
			
		||||
    int x_sub = x % 8;
 | 
			
		||||
    uint32_t pos = (x1 + y * (this->get_width_internal() / 8));
 | 
			
		||||
    uint8_t current = this->partial_buffer_[pos];
 | 
			
		||||
    this->partial_buffer_[pos] = (~pixelMaskLUT[x_sub] & current) | (color.is_on() ? 0 : pixelMaskLUT[x_sub]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::dump_config() {
 | 
			
		||||
  LOG_DISPLAY("", "Inkplate", this);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Greyscale: %s", YESNO(this->greyscale_));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Partial Updating: %s", YESNO(this->partial_updating_));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Full Update Every: %d", this->full_update_every_);
 | 
			
		||||
  // Log pins
 | 
			
		||||
  LOG_PIN("  CKV Pin: ", this->ckv_pin_);
 | 
			
		||||
  LOG_PIN("  CL Pin: ", this->cl_pin_);
 | 
			
		||||
  LOG_PIN("  GPIO0 Enable Pin: ", this->gpio0_enable_pin_);
 | 
			
		||||
  LOG_PIN("  GMOD Pin: ", this->gmod_pin_);
 | 
			
		||||
  LOG_PIN("  LE Pin: ", this->le_pin_);
 | 
			
		||||
  LOG_PIN("  OE Pin: ", this->oe_pin_);
 | 
			
		||||
  LOG_PIN("  POWERUP Pin: ", this->powerup_pin_);
 | 
			
		||||
  LOG_PIN("  SPH Pin: ", this->sph_pin_);
 | 
			
		||||
  LOG_PIN("  SPV Pin: ", this->spv_pin_);
 | 
			
		||||
  LOG_PIN("  VCOM Pin: ", this->vcom_pin_);
 | 
			
		||||
  LOG_PIN("  WAKEUP Pin: ", this->wakeup_pin_);
 | 
			
		||||
 | 
			
		||||
  LOG_PIN("  Data 0 Pin: ", this->display_data_0_pin_);
 | 
			
		||||
  LOG_PIN("  Data 1 Pin: ", this->display_data_1_pin_);
 | 
			
		||||
  LOG_PIN("  Data 2 Pin: ", this->display_data_2_pin_);
 | 
			
		||||
  LOG_PIN("  Data 3 Pin: ", this->display_data_3_pin_);
 | 
			
		||||
  LOG_PIN("  Data 4 Pin: ", this->display_data_4_pin_);
 | 
			
		||||
  LOG_PIN("  Data 5 Pin: ", this->display_data_5_pin_);
 | 
			
		||||
  LOG_PIN("  Data 6 Pin: ", this->display_data_6_pin_);
 | 
			
		||||
  LOG_PIN("  Data 7 Pin: ", this->display_data_7_pin_);
 | 
			
		||||
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::eink_off_() {
 | 
			
		||||
  ESP_LOGV(TAG, "Eink off called");
 | 
			
		||||
  unsigned long start_time = millis();
 | 
			
		||||
  if (panel_on_ == 0)
 | 
			
		||||
    return;
 | 
			
		||||
  panel_on_ = 0;
 | 
			
		||||
  this->gmod_pin_->digital_write(false);
 | 
			
		||||
  this->oe_pin_->digital_write(false);
 | 
			
		||||
 | 
			
		||||
  GPIO.out &= ~(get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin()));
 | 
			
		||||
 | 
			
		||||
  this->sph_pin_->digital_write(false);
 | 
			
		||||
  this->spv_pin_->digital_write(false);
 | 
			
		||||
 | 
			
		||||
  this->powerup_pin_->digital_write(false);
 | 
			
		||||
  this->wakeup_pin_->digital_write(false);
 | 
			
		||||
  this->vcom_pin_->digital_write(false);
 | 
			
		||||
 | 
			
		||||
  pins_z_state_();
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::eink_on_() {
 | 
			
		||||
  ESP_LOGV(TAG, "Eink on called");
 | 
			
		||||
  unsigned long start_time = millis();
 | 
			
		||||
  if (panel_on_ == 1)
 | 
			
		||||
    return;
 | 
			
		||||
  panel_on_ = 1;
 | 
			
		||||
  pins_as_outputs_();
 | 
			
		||||
  this->wakeup_pin_->digital_write(true);
 | 
			
		||||
  this->powerup_pin_->digital_write(true);
 | 
			
		||||
  this->vcom_pin_->digital_write(true);
 | 
			
		||||
 | 
			
		||||
  this->write_byte(0x01, 0x3F);
 | 
			
		||||
 | 
			
		||||
  delay(40);
 | 
			
		||||
 | 
			
		||||
  this->write_byte(0x0D, 0x80);
 | 
			
		||||
 | 
			
		||||
  delay(2);
 | 
			
		||||
 | 
			
		||||
  this->read_byte(0x00, &temperature_, 0);
 | 
			
		||||
 | 
			
		||||
  this->le_pin_->digital_write(false);
 | 
			
		||||
  this->oe_pin_->digital_write(false);
 | 
			
		||||
  this->cl_pin_->digital_write(false);
 | 
			
		||||
  this->sph_pin_->digital_write(true);
 | 
			
		||||
  this->gmod_pin_->digital_write(true);
 | 
			
		||||
  this->spv_pin_->digital_write(true);
 | 
			
		||||
  this->ckv_pin_->digital_write(false);
 | 
			
		||||
  this->oe_pin_->digital_write(true);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::fill(Color color) {
 | 
			
		||||
  ESP_LOGV(TAG, "Fill called");
 | 
			
		||||
  unsigned long start_time = millis();
 | 
			
		||||
 | 
			
		||||
  if (this->greyscale_) {
 | 
			
		||||
    uint8_t fill = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5;
 | 
			
		||||
    for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
 | 
			
		||||
      this->buffer_[i] = (fill << 4) | fill;
 | 
			
		||||
  } else {
 | 
			
		||||
    uint8_t fill = color.is_on() ? 0x00 : 0xFF;
 | 
			
		||||
    for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
 | 
			
		||||
      this->partial_buffer_[i] = fill;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Fill finished (%lums)", millis() - start_time);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::display() {
 | 
			
		||||
  ESP_LOGV(TAG, "Display called");
 | 
			
		||||
  unsigned long start_time = millis();
 | 
			
		||||
 | 
			
		||||
  if (this->greyscale_) {
 | 
			
		||||
    this->display3b_();
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->partial_updating_ && this->partial_update_()) {
 | 
			
		||||
      ESP_LOGV(TAG, "Display finished (partial) (%lums)", millis() - start_time);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this->display1b_();
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Display finished (full) (%lums)", millis() - start_time);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::display1b_() {
 | 
			
		||||
  ESP_LOGV(TAG, "Display1b called");
 | 
			
		||||
  unsigned long start_time = millis();
 | 
			
		||||
 | 
			
		||||
  for (int i = 0; i < this->get_buffer_length_(); i++) {
 | 
			
		||||
    this->buffer_[i] &= this->partial_buffer_[i];
 | 
			
		||||
    this->buffer_[i] |= this->partial_buffer_[i];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint16_t pos;
 | 
			
		||||
  uint32_t send;
 | 
			
		||||
  uint8_t data;
 | 
			
		||||
  uint8_t buffer_value;
 | 
			
		||||
  eink_on_();
 | 
			
		||||
  clean_fast_(0, 1);
 | 
			
		||||
  clean_fast_(1, 5);
 | 
			
		||||
  clean_fast_(2, 1);
 | 
			
		||||
  clean_fast_(0, 5);
 | 
			
		||||
  clean_fast_(2, 1);
 | 
			
		||||
  clean_fast_(1, 12);
 | 
			
		||||
  clean_fast_(2, 1);
 | 
			
		||||
  clean_fast_(0, 11);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Display1b start loops (%lums)", millis() - start_time);
 | 
			
		||||
  for (int k = 0; k < 3; k++) {
 | 
			
		||||
    pos = this->get_buffer_length_() - 1;
 | 
			
		||||
    vscan_start_();
 | 
			
		||||
    for (int i = 0; i < this->get_height_internal(); i++) {
 | 
			
		||||
      buffer_value = this->buffer_[pos];
 | 
			
		||||
      data = LUTB[(buffer_value >> 4) & 0x0F];
 | 
			
		||||
      send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
             (((data & B11100000) >> 5) << 25);
 | 
			
		||||
      hscan_start_(send);
 | 
			
		||||
      data = LUTB[buffer_value & 0x0F];
 | 
			
		||||
      send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
             (((data & B11100000) >> 5) << 25);
 | 
			
		||||
      GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      pos--;
 | 
			
		||||
 | 
			
		||||
      for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
 | 
			
		||||
        buffer_value = this->buffer_[pos];
 | 
			
		||||
        data = LUTB[(buffer_value >> 4) & 0x0F];
 | 
			
		||||
        send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
               (((data & B11100000) >> 5) << 25);
 | 
			
		||||
        GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
        GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
        data = LUTB[buffer_value & 0x0F];
 | 
			
		||||
        send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
               (((data & B11100000) >> 5) << 25);
 | 
			
		||||
        GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
        GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
        pos--;
 | 
			
		||||
      }
 | 
			
		||||
      GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      vscan_end_();
 | 
			
		||||
    }
 | 
			
		||||
    delayMicroseconds(230);
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Display1b first loop x %d (%lums)", 3, millis() - start_time);
 | 
			
		||||
 | 
			
		||||
  pos = this->get_buffer_length_() - 1;
 | 
			
		||||
  vscan_start_();
 | 
			
		||||
  for (int i = 0; i < this->get_height_internal(); i++) {
 | 
			
		||||
    buffer_value = this->buffer_[pos];
 | 
			
		||||
    data = LUT2[(buffer_value >> 4) & 0x0F];
 | 
			
		||||
    send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
           (((data & B11100000) >> 5) << 25);
 | 
			
		||||
    hscan_start_(send);
 | 
			
		||||
    data = LUT2[buffer_value & 0x0F];
 | 
			
		||||
    send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
           (((data & B11100000) >> 5) << 25);
 | 
			
		||||
    GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
    GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
    pos--;
 | 
			
		||||
    for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
 | 
			
		||||
      buffer_value = this->buffer_[pos];
 | 
			
		||||
      data = LUT2[(buffer_value >> 4) & 0x0F];
 | 
			
		||||
      send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
             (((data & B11100000) >> 5) << 25);
 | 
			
		||||
      GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      data = LUT2[buffer_value & 0x0F];
 | 
			
		||||
      send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
             (((data & B11100000) >> 5) << 25);
 | 
			
		||||
      GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      pos--;
 | 
			
		||||
    }
 | 
			
		||||
    GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
    GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
    vscan_end_();
 | 
			
		||||
  }
 | 
			
		||||
  delayMicroseconds(230);
 | 
			
		||||
  ESP_LOGV(TAG, "Display1b second loop (%lums)", millis() - start_time);
 | 
			
		||||
 | 
			
		||||
  vscan_start_();
 | 
			
		||||
  for (int i = 0; i < this->get_height_internal(); i++) {
 | 
			
		||||
    buffer_value = this->buffer_[pos];
 | 
			
		||||
    data = 0b00000000;
 | 
			
		||||
    send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
           (((data & B11100000) >> 5) << 25);
 | 
			
		||||
    hscan_start_(send);
 | 
			
		||||
    GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
    GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
    for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
 | 
			
		||||
      GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
    }
 | 
			
		||||
    GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
    GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
    vscan_end_();
 | 
			
		||||
  }
 | 
			
		||||
  delayMicroseconds(230);
 | 
			
		||||
  ESP_LOGV(TAG, "Display1b third loop (%lums)", millis() - start_time);
 | 
			
		||||
 | 
			
		||||
  vscan_start_();
 | 
			
		||||
  eink_off_();
 | 
			
		||||
  this->block_partial_ = false;
 | 
			
		||||
  this->partial_updates_ = 0;
 | 
			
		||||
  ESP_LOGV(TAG, "Display1b finished (%lums)", millis() - start_time);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::display3b_() {
 | 
			
		||||
  ESP_LOGV(TAG, "Display3b called");
 | 
			
		||||
  unsigned long start_time = millis();
 | 
			
		||||
 | 
			
		||||
  eink_on_();
 | 
			
		||||
  clean_fast_(0, 1);
 | 
			
		||||
  clean_fast_(1, 12);
 | 
			
		||||
  clean_fast_(2, 1);
 | 
			
		||||
  clean_fast_(0, 11);
 | 
			
		||||
  clean_fast_(2, 1);
 | 
			
		||||
  clean_fast_(1, 12);
 | 
			
		||||
  clean_fast_(2, 1);
 | 
			
		||||
  clean_fast_(0, 11);
 | 
			
		||||
 | 
			
		||||
  for (int k = 0; k < 8; k++) {
 | 
			
		||||
    uint32_t pos = this->get_buffer_length_() - 1;
 | 
			
		||||
    uint32_t send;
 | 
			
		||||
    uint8_t pix1;
 | 
			
		||||
    uint8_t pix2;
 | 
			
		||||
    uint8_t pix3;
 | 
			
		||||
    uint8_t pix4;
 | 
			
		||||
    uint8_t pixel;
 | 
			
		||||
    uint8_t pixel2;
 | 
			
		||||
 | 
			
		||||
    vscan_start_();
 | 
			
		||||
    for (int i = 0; i < this->get_height_internal(); i++) {
 | 
			
		||||
      pix1 = this->buffer_[pos--];
 | 
			
		||||
      pix2 = this->buffer_[pos--];
 | 
			
		||||
      pix3 = this->buffer_[pos--];
 | 
			
		||||
      pix4 = this->buffer_[pos--];
 | 
			
		||||
      pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) |
 | 
			
		||||
              (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0);
 | 
			
		||||
      pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) |
 | 
			
		||||
               (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0);
 | 
			
		||||
 | 
			
		||||
      send = ((pixel & B00000011) << 4) | (((pixel & B00001100) >> 2) << 18) | (((pixel & B00010000) >> 4) << 23) |
 | 
			
		||||
             (((pixel & B11100000) >> 5) << 25);
 | 
			
		||||
      hscan_start_(send);
 | 
			
		||||
      send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) |
 | 
			
		||||
             (((pixel2 & B11100000) >> 5) << 25);
 | 
			
		||||
      GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
 | 
			
		||||
      for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
 | 
			
		||||
        pix1 = this->buffer_[pos--];
 | 
			
		||||
        pix2 = this->buffer_[pos--];
 | 
			
		||||
        pix3 = this->buffer_[pos--];
 | 
			
		||||
        pix4 = this->buffer_[pos--];
 | 
			
		||||
        pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) |
 | 
			
		||||
                (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0);
 | 
			
		||||
        pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) |
 | 
			
		||||
                 (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0);
 | 
			
		||||
 | 
			
		||||
        send = ((pixel & B00000011) << 4) | (((pixel & B00001100) >> 2) << 18) | (((pixel & B00010000) >> 4) << 23) |
 | 
			
		||||
               (((pixel & B11100000) >> 5) << 25);
 | 
			
		||||
        GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
        GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
 | 
			
		||||
        send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) |
 | 
			
		||||
               (((pixel2 & B11100000) >> 5) << 25);
 | 
			
		||||
        GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
        GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      }
 | 
			
		||||
      GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      vscan_end_();
 | 
			
		||||
    }
 | 
			
		||||
    delayMicroseconds(230);
 | 
			
		||||
  }
 | 
			
		||||
  clean_fast_(2, 1);
 | 
			
		||||
  clean_fast_(3, 1);
 | 
			
		||||
  vscan_start_();
 | 
			
		||||
  eink_off_();
 | 
			
		||||
  ESP_LOGV(TAG, "Display3b finished (%lums)", millis() - start_time);
 | 
			
		||||
}
 | 
			
		||||
bool Inkplate6::partial_update_() {
 | 
			
		||||
  ESP_LOGV(TAG, "Partial update called");
 | 
			
		||||
  unsigned long start_time = millis();
 | 
			
		||||
  if (this->greyscale_)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (this->block_partial_)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  this->partial_updates_++;
 | 
			
		||||
 | 
			
		||||
  uint16_t pos = this->get_buffer_length_() - 1;
 | 
			
		||||
  uint32_t send;
 | 
			
		||||
  uint8_t data;
 | 
			
		||||
  uint8_t diffw, diffb;
 | 
			
		||||
  uint32_t n = (this->get_buffer_length_() * 2) - 1;
 | 
			
		||||
 | 
			
		||||
  for (int i = 0; i < this->get_height_internal(); i++) {
 | 
			
		||||
    for (int j = 0; j < (this->get_width_internal() / 8); j++) {
 | 
			
		||||
      diffw = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & ~(this->partial_buffer_[pos]);
 | 
			
		||||
      diffb = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & this->partial_buffer_[pos];
 | 
			
		||||
      pos--;
 | 
			
		||||
      this->partial_buffer_2_[n--] = LUTW[diffw >> 4] & LUTB[diffb >> 4];
 | 
			
		||||
      this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Partial update buffer built after (%lums)", millis() - start_time);
 | 
			
		||||
 | 
			
		||||
  eink_on_();
 | 
			
		||||
  for (int k = 0; k < 5; k++) {
 | 
			
		||||
    vscan_start_();
 | 
			
		||||
    n = (this->get_buffer_length_() * 2) - 1;
 | 
			
		||||
    for (int i = 0; i < this->get_height_internal(); i++) {
 | 
			
		||||
      data = this->partial_buffer_2_[n--];
 | 
			
		||||
      send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
             (((data & B11100000) >> 5) << 25);
 | 
			
		||||
      hscan_start_(send);
 | 
			
		||||
      for (int j = 0; j < (this->get_width_internal() / 4) - 1; j++) {
 | 
			
		||||
        data = this->partial_buffer_2_[n--];
 | 
			
		||||
        send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
               (((data & B11100000) >> 5) << 25);
 | 
			
		||||
        GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
        GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      }
 | 
			
		||||
      GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      vscan_end_();
 | 
			
		||||
    }
 | 
			
		||||
    delayMicroseconds(230);
 | 
			
		||||
    ESP_LOGV(TAG, "Partial update loop k=%d (%lums)", k, millis() - start_time);
 | 
			
		||||
  }
 | 
			
		||||
  clean_fast_(2, 2);
 | 
			
		||||
  clean_fast_(3, 1);
 | 
			
		||||
  vscan_start_();
 | 
			
		||||
  eink_off_();
 | 
			
		||||
 | 
			
		||||
  for (int i = 0; i < this->get_buffer_length_(); i++) {
 | 
			
		||||
    this->buffer_[i] = this->partial_buffer_[i];
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Partial update finished (%lums)", millis() - start_time);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::vscan_start_() {
 | 
			
		||||
  this->ckv_pin_->digital_write(true);
 | 
			
		||||
  delayMicroseconds(7);
 | 
			
		||||
  this->spv_pin_->digital_write(false);
 | 
			
		||||
  delayMicroseconds(10);
 | 
			
		||||
  this->ckv_pin_->digital_write(false);
 | 
			
		||||
  delayMicroseconds(0);
 | 
			
		||||
  this->ckv_pin_->digital_write(true);
 | 
			
		||||
  delayMicroseconds(8);
 | 
			
		||||
  this->spv_pin_->digital_write(true);
 | 
			
		||||
  delayMicroseconds(10);
 | 
			
		||||
  this->ckv_pin_->digital_write(false);
 | 
			
		||||
  delayMicroseconds(0);
 | 
			
		||||
  this->ckv_pin_->digital_write(true);
 | 
			
		||||
  delayMicroseconds(18);
 | 
			
		||||
  this->ckv_pin_->digital_write(false);
 | 
			
		||||
  delayMicroseconds(0);
 | 
			
		||||
  this->ckv_pin_->digital_write(true);
 | 
			
		||||
  delayMicroseconds(18);
 | 
			
		||||
  this->ckv_pin_->digital_write(false);
 | 
			
		||||
  delayMicroseconds(0);
 | 
			
		||||
  this->ckv_pin_->digital_write(true);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::vscan_write_() {
 | 
			
		||||
  this->ckv_pin_->digital_write(false);
 | 
			
		||||
  this->le_pin_->digital_write(true);
 | 
			
		||||
  this->le_pin_->digital_write(false);
 | 
			
		||||
  delayMicroseconds(0);
 | 
			
		||||
  this->sph_pin_->digital_write(false);
 | 
			
		||||
  this->cl_pin_->digital_write(true);
 | 
			
		||||
  this->cl_pin_->digital_write(false);
 | 
			
		||||
  this->sph_pin_->digital_write(true);
 | 
			
		||||
  this->ckv_pin_->digital_write(true);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::hscan_start_(uint32_t d) {
 | 
			
		||||
  this->sph_pin_->digital_write(false);
 | 
			
		||||
  GPIO.out_w1ts = (d) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
  GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
  this->sph_pin_->digital_write(true);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::vscan_end_() {
 | 
			
		||||
  this->ckv_pin_->digital_write(false);
 | 
			
		||||
  this->le_pin_->digital_write(true);
 | 
			
		||||
  this->le_pin_->digital_write(false);
 | 
			
		||||
  delayMicroseconds(1);
 | 
			
		||||
  this->ckv_pin_->digital_write(true);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::clean() {
 | 
			
		||||
  ESP_LOGV(TAG, "Clean called");
 | 
			
		||||
  unsigned long start_time = millis();
 | 
			
		||||
 | 
			
		||||
  eink_on_();
 | 
			
		||||
  clean_fast_(0, 1);   // White
 | 
			
		||||
  clean_fast_(0, 8);   // White to White
 | 
			
		||||
  clean_fast_(0, 1);   // White to Black
 | 
			
		||||
  clean_fast_(0, 8);   // Black to Black
 | 
			
		||||
  clean_fast_(2, 1);   // Black to White
 | 
			
		||||
  clean_fast_(1, 10);  // White to White
 | 
			
		||||
  ESP_LOGV(TAG, "Clean finished (%lums)", millis() - start_time);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
 | 
			
		||||
  ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep);
 | 
			
		||||
  unsigned long start_time = millis();
 | 
			
		||||
 | 
			
		||||
  eink_on_();
 | 
			
		||||
  uint8_t data = 0;
 | 
			
		||||
  if (c == 0)  // White
 | 
			
		||||
    data = B10101010;
 | 
			
		||||
  else if (c == 1)  // Black
 | 
			
		||||
    data = B01010101;
 | 
			
		||||
  else if (c == 2)  // Discharge
 | 
			
		||||
    data = B00000000;
 | 
			
		||||
  else if (c == 3)  // Skip
 | 
			
		||||
    data = B11111111;
 | 
			
		||||
 | 
			
		||||
  uint32_t send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) |
 | 
			
		||||
                  (((data & B11100000) >> 5) << 25);
 | 
			
		||||
 | 
			
		||||
  for (int k = 0; k < rep; k++) {
 | 
			
		||||
    vscan_start_();
 | 
			
		||||
    for (int i = 0; i < this->get_height_internal(); i++) {
 | 
			
		||||
      hscan_start_(send);
 | 
			
		||||
      GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      for (int j = 0; j < this->get_width_internal() / 8; j++) {
 | 
			
		||||
        GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
        GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
        GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
        GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      }
 | 
			
		||||
      GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
 | 
			
		||||
      vscan_end_();
 | 
			
		||||
    }
 | 
			
		||||
    delayMicroseconds(230);
 | 
			
		||||
    ESP_LOGV(TAG, "Clean fast rep loop %d finished (%lums)", k, millis() - start_time);
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Clean fast finished (%lums)", millis() - start_time);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::pins_z_state_() {
 | 
			
		||||
  this->ckv_pin_->pin_mode(INPUT);
 | 
			
		||||
  this->sph_pin_->pin_mode(INPUT);
 | 
			
		||||
 | 
			
		||||
  this->oe_pin_->pin_mode(INPUT);
 | 
			
		||||
  this->gmod_pin_->pin_mode(INPUT);
 | 
			
		||||
  this->spv_pin_->pin_mode(INPUT);
 | 
			
		||||
 | 
			
		||||
  this->display_data_0_pin_->pin_mode(INPUT);
 | 
			
		||||
  this->display_data_1_pin_->pin_mode(INPUT);
 | 
			
		||||
  this->display_data_2_pin_->pin_mode(INPUT);
 | 
			
		||||
  this->display_data_3_pin_->pin_mode(INPUT);
 | 
			
		||||
  this->display_data_4_pin_->pin_mode(INPUT);
 | 
			
		||||
  this->display_data_5_pin_->pin_mode(INPUT);
 | 
			
		||||
  this->display_data_6_pin_->pin_mode(INPUT);
 | 
			
		||||
  this->display_data_7_pin_->pin_mode(INPUT);
 | 
			
		||||
}
 | 
			
		||||
void Inkplate6::pins_as_outputs_() {
 | 
			
		||||
  this->ckv_pin_->pin_mode(OUTPUT);
 | 
			
		||||
  this->sph_pin_->pin_mode(OUTPUT);
 | 
			
		||||
 | 
			
		||||
  this->oe_pin_->pin_mode(OUTPUT);
 | 
			
		||||
  this->gmod_pin_->pin_mode(OUTPUT);
 | 
			
		||||
  this->spv_pin_->pin_mode(OUTPUT);
 | 
			
		||||
 | 
			
		||||
  this->display_data_0_pin_->pin_mode(OUTPUT);
 | 
			
		||||
  this->display_data_1_pin_->pin_mode(OUTPUT);
 | 
			
		||||
  this->display_data_2_pin_->pin_mode(OUTPUT);
 | 
			
		||||
  this->display_data_3_pin_->pin_mode(OUTPUT);
 | 
			
		||||
  this->display_data_4_pin_->pin_mode(OUTPUT);
 | 
			
		||||
  this->display_data_5_pin_->pin_mode(OUTPUT);
 | 
			
		||||
  this->display_data_6_pin_->pin_mode(OUTPUT);
 | 
			
		||||
  this->display_data_7_pin_->pin_mode(OUTPUT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace inkplate6
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										157
									
								
								esphome/components/inkplate6/inkplate.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								esphome/components/inkplate6/inkplate.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/display/display_buffer.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace inkplate6 {
 | 
			
		||||
 | 
			
		||||
class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  const uint8_t LUT2[16] = {B10101010, B10101001, B10100110, B10100101, B10011010, B10011001, B10010110, B10010101,
 | 
			
		||||
                            B01101010, B01101001, B01100110, B01100101, B01011010, B01011001, B01010110, B01010101};
 | 
			
		||||
  const uint8_t LUTW[16] = {B11111111, B11111110, B11111011, B11111010, B11101111, B11101110, B11101011, B11101010,
 | 
			
		||||
                            B10111111, B10111110, B10111011, B10111010, B10101111, B10101110, B10101011, B10101010};
 | 
			
		||||
  const uint8_t LUTB[16] = {B11111111, B11111101, B11110111, B11110101, B11011111, B11011101, B11010111, B11010101,
 | 
			
		||||
                            B01111111, B01111101, B01110111, B01110101, B01011111, B01011101, B01010111, B01010101};
 | 
			
		||||
  const uint8_t pixelMaskLUT[8] = {B00000001, B00000010, B00000100, B00001000,
 | 
			
		||||
                                   B00010000, B00100000, B01000000, B10000000};
 | 
			
		||||
  const uint8_t pixelMaskGLUT[2] = {B00001111, B11110000};
 | 
			
		||||
  const uint8_t waveform3Bit[8][8] = {{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0},
 | 
			
		||||
                                      {0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0},
 | 
			
		||||
                                      {1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0}};
 | 
			
		||||
  const uint32_t waveform[50] = {
 | 
			
		||||
      0x00000008, 0x00000008, 0x00200408, 0x80281888, 0x60a81898, 0x60a8a8a8, 0x60a8a8a8, 0x6068a868, 0x6868a868,
 | 
			
		||||
      0x6868a868, 0x68686868, 0x6a686868, 0x5a686868, 0x5a686868, 0x5a586a68, 0x5a5a6a68, 0x5a5a6a68, 0x55566a68,
 | 
			
		||||
      0x55565a64, 0x55555654, 0x55555556, 0x55555556, 0x55555556, 0x55555516, 0x55555596, 0x15555595, 0x95955595,
 | 
			
		||||
      0x95959595, 0x95949495, 0x94949495, 0x94949495, 0xa4949494, 0x9494a4a4, 0x84a49494, 0x84948484, 0x84848484,
 | 
			
		||||
      0x84848484, 0x84848484, 0xa5a48484, 0xa9a4a4a8, 0xa9a8a8a8, 0xa5a9a9a4, 0xa5a5a5a4, 0xa1a5a5a1, 0xa9a9a9a9,
 | 
			
		||||
      0xa9a9a9a9, 0xa9a9a9a9, 0xa9a9a9a9, 0x15151515, 0x11111111};
 | 
			
		||||
 | 
			
		||||
  void set_greyscale(bool greyscale) {
 | 
			
		||||
    this->greyscale_ = greyscale;
 | 
			
		||||
    this->initialize_();
 | 
			
		||||
    this->block_partial_ = true;
 | 
			
		||||
  }
 | 
			
		||||
  void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; }
 | 
			
		||||
  void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; }
 | 
			
		||||
 | 
			
		||||
  void set_display_data_0_pin(GPIOPin *data) { this->display_data_0_pin_ = data; }
 | 
			
		||||
  void set_display_data_1_pin(GPIOPin *data) { this->display_data_1_pin_ = data; }
 | 
			
		||||
  void set_display_data_2_pin(GPIOPin *data) { this->display_data_2_pin_ = data; }
 | 
			
		||||
  void set_display_data_3_pin(GPIOPin *data) { this->display_data_3_pin_ = data; }
 | 
			
		||||
  void set_display_data_4_pin(GPIOPin *data) { this->display_data_4_pin_ = data; }
 | 
			
		||||
  void set_display_data_5_pin(GPIOPin *data) { this->display_data_5_pin_ = data; }
 | 
			
		||||
  void set_display_data_6_pin(GPIOPin *data) { this->display_data_6_pin_ = data; }
 | 
			
		||||
  void set_display_data_7_pin(GPIOPin *data) { this->display_data_7_pin_ = data; }
 | 
			
		||||
 | 
			
		||||
  void set_ckv_pin(GPIOPin *ckv) { this->ckv_pin_ = ckv; }
 | 
			
		||||
  void set_cl_pin(GPIOPin *cl) { this->cl_pin_ = cl; }
 | 
			
		||||
  void set_gpio0_enable_pin(GPIOPin *gpio0_enable) { this->gpio0_enable_pin_ = gpio0_enable; }
 | 
			
		||||
  void set_gmod_pin(GPIOPin *gmod) { this->gmod_pin_ = gmod; }
 | 
			
		||||
  void set_le_pin(GPIOPin *le) { this->le_pin_ = le; }
 | 
			
		||||
  void set_oe_pin(GPIOPin *oe) { this->oe_pin_ = oe; }
 | 
			
		||||
  void set_powerup_pin(GPIOPin *powerup) { this->powerup_pin_ = powerup; }
 | 
			
		||||
  void set_sph_pin(GPIOPin *sph) { this->sph_pin_ = sph; }
 | 
			
		||||
  void set_spv_pin(GPIOPin *spv) { this->spv_pin_ = spv; }
 | 
			
		||||
  void set_vcom_pin(GPIOPin *vcom) { this->vcom_pin_ = vcom; }
 | 
			
		||||
  void set_wakeup_pin(GPIOPin *wakeup) { this->wakeup_pin_ = wakeup; }
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  void display();
 | 
			
		||||
  void clean();
 | 
			
		||||
  void fill(Color color) override;
 | 
			
		||||
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
 | 
			
		||||
  uint8_t get_panel_state() { return this->panel_on_; }
 | 
			
		||||
  bool get_greyscale() { return this->greyscale_; }
 | 
			
		||||
  bool get_partial_updating() { return this->partial_updating_; }
 | 
			
		||||
  uint8_t get_temperature() { return this->temperature_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void draw_absolute_pixel_internal(int x, int y, Color color) override;
 | 
			
		||||
  void display1b_();
 | 
			
		||||
  void display3b_();
 | 
			
		||||
  void initialize_();
 | 
			
		||||
  bool partial_update_();
 | 
			
		||||
  void clean_fast_(uint8_t c, uint8_t rep);
 | 
			
		||||
 | 
			
		||||
  void hscan_start_(uint32_t d);
 | 
			
		||||
  void vscan_end_();
 | 
			
		||||
  void vscan_start_();
 | 
			
		||||
  void vscan_write_();
 | 
			
		||||
 | 
			
		||||
  void eink_off_();
 | 
			
		||||
  void eink_on_();
 | 
			
		||||
 | 
			
		||||
  void setup_pins_();
 | 
			
		||||
  void pins_z_state_();
 | 
			
		||||
  void pins_as_outputs_();
 | 
			
		||||
 | 
			
		||||
  int get_width_internal() override { return 800; }
 | 
			
		||||
 | 
			
		||||
  int get_height_internal() override { return 600; }
 | 
			
		||||
 | 
			
		||||
  size_t get_buffer_length_();
 | 
			
		||||
 | 
			
		||||
  int get_data_pin_mask_() {
 | 
			
		||||
    int data = 0;
 | 
			
		||||
    data |= (1 << this->display_data_0_pin_->get_pin());
 | 
			
		||||
    data |= (1 << this->display_data_1_pin_->get_pin());
 | 
			
		||||
    data |= (1 << this->display_data_2_pin_->get_pin());
 | 
			
		||||
    data |= (1 << this->display_data_3_pin_->get_pin());
 | 
			
		||||
    data |= (1 << this->display_data_4_pin_->get_pin());
 | 
			
		||||
    data |= (1 << this->display_data_5_pin_->get_pin());
 | 
			
		||||
    data |= (1 << this->display_data_6_pin_->get_pin());
 | 
			
		||||
    data |= (1 << this->display_data_7_pin_->get_pin());
 | 
			
		||||
    return data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t panel_on_ = 0;
 | 
			
		||||
  uint8_t temperature_;
 | 
			
		||||
 | 
			
		||||
  uint8_t *partial_buffer_{nullptr};
 | 
			
		||||
  uint8_t *partial_buffer_2_{nullptr};
 | 
			
		||||
 | 
			
		||||
  uint32_t full_update_every_;
 | 
			
		||||
  uint32_t partial_updates_{0};
 | 
			
		||||
 | 
			
		||||
  bool block_partial_;
 | 
			
		||||
  bool greyscale_;
 | 
			
		||||
  bool partial_updating_;
 | 
			
		||||
 | 
			
		||||
  GPIOPin *display_data_0_pin_;
 | 
			
		||||
  GPIOPin *display_data_1_pin_;
 | 
			
		||||
  GPIOPin *display_data_2_pin_;
 | 
			
		||||
  GPIOPin *display_data_3_pin_;
 | 
			
		||||
  GPIOPin *display_data_4_pin_;
 | 
			
		||||
  GPIOPin *display_data_5_pin_;
 | 
			
		||||
  GPIOPin *display_data_6_pin_;
 | 
			
		||||
  GPIOPin *display_data_7_pin_;
 | 
			
		||||
 | 
			
		||||
  GPIOPin *ckv_pin_;
 | 
			
		||||
  GPIOPin *cl_pin_;
 | 
			
		||||
  GPIOPin *gpio0_enable_pin_;
 | 
			
		||||
  GPIOPin *gmod_pin_;
 | 
			
		||||
  GPIOPin *le_pin_;
 | 
			
		||||
  GPIOPin *oe_pin_;
 | 
			
		||||
  GPIOPin *powerup_pin_;
 | 
			
		||||
  GPIOPin *sph_pin_;
 | 
			
		||||
  GPIOPin *spv_pin_;
 | 
			
		||||
  GPIOPin *vcom_pin_;
 | 
			
		||||
  GPIOPin *wakeup_pin_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace inkplate6
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -149,10 +149,10 @@ struct ESPColor {
 | 
			
		||||
    return ESPColor(uint8_t((uint16_t(r) * 255U / max_rgb)), uint8_t((uint16_t(g) * 255U / max_rgb)),
 | 
			
		||||
                    uint8_t((uint16_t(b) * 255U / max_rgb)), w);
 | 
			
		||||
  }
 | 
			
		||||
  ESPColor fade_to_white(uint8_t amnt) { return ESPColor(255, 255, 255, 255) - (*this * amnt); }
 | 
			
		||||
  ESPColor fade_to_black(uint8_t amnt) { return *this * amnt; }
 | 
			
		||||
  ESPColor lighten(uint8_t delta) { return *this + delta; }
 | 
			
		||||
  ESPColor darken(uint8_t delta) { return *this - delta; }
 | 
			
		||||
  ESPColor fade_to_white(uint8_t amnt) const { return ESPColor(255, 255, 255, 255) - (*this * amnt); }
 | 
			
		||||
  ESPColor fade_to_black(uint8_t amnt) const { return *this * amnt; }
 | 
			
		||||
  ESPColor lighten(uint8_t delta) const { return *this + delta; }
 | 
			
		||||
  ESPColor darken(uint8_t delta) const { return *this - delta; }
 | 
			
		||||
 | 
			
		||||
  static const ESPColor BLACK;
 | 
			
		||||
  static const ESPColor WHITE;
 | 
			
		||||
 
 | 
			
		||||
@@ -102,13 +102,18 @@ class LightTurnOnTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  LightTurnOnTrigger(LightState *a_light) {
 | 
			
		||||
    a_light->add_new_remote_values_callback([this, a_light]() {
 | 
			
		||||
      auto is_on = a_light->current_values.is_on();
 | 
			
		||||
      if (is_on && !last_on_) {
 | 
			
		||||
      // using the remote value because of transitions we need to trigger as early as possible
 | 
			
		||||
      auto is_on = a_light->remote_values.is_on();
 | 
			
		||||
      // only trigger when going from off to on
 | 
			
		||||
      auto should_trigger = is_on && !this->last_on_;
 | 
			
		||||
      // Set new state immediately so that trigger() doesn't devolve
 | 
			
		||||
      // into infinite loop
 | 
			
		||||
      this->last_on_ = is_on;
 | 
			
		||||
      if (should_trigger) {
 | 
			
		||||
        this->trigger();
 | 
			
		||||
      }
 | 
			
		||||
      last_on_ = is_on;
 | 
			
		||||
    });
 | 
			
		||||
    last_on_ = a_light->current_values.is_on();
 | 
			
		||||
    this->last_on_ = a_light->current_values.is_on();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
@@ -118,18 +123,14 @@ class LightTurnOnTrigger : public Trigger<> {
 | 
			
		||||
class LightTurnOffTrigger : public Trigger<> {
 | 
			
		||||
 public:
 | 
			
		||||
  LightTurnOffTrigger(LightState *a_light) {
 | 
			
		||||
    a_light->add_new_remote_values_callback([this, a_light]() {
 | 
			
		||||
    a_light->add_new_target_state_reached_callback([this, a_light]() {
 | 
			
		||||
      auto is_on = a_light->current_values.is_on();
 | 
			
		||||
      if (!is_on && last_on_) {
 | 
			
		||||
      // only trigger when going from on to off
 | 
			
		||||
      if (!is_on) {
 | 
			
		||||
        this->trigger();
 | 
			
		||||
      }
 | 
			
		||||
      last_on_ = is_on;
 | 
			
		||||
    });
 | 
			
		||||
    last_on_ = a_light->current_values.is_on();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool last_on_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class AddressableSet : public Action<Ts...> {
 | 
			
		||||
 
 | 
			
		||||
@@ -145,6 +145,7 @@ void LightState::loop() {
 | 
			
		||||
  if (this->transformer_ != nullptr) {
 | 
			
		||||
    if (this->transformer_->is_finished()) {
 | 
			
		||||
      this->remote_values = this->current_values = this->transformer_->get_end_values();
 | 
			
		||||
      this->target_state_reached_callback_.call();
 | 
			
		||||
      if (this->transformer_->publish_at_end())
 | 
			
		||||
        this->publish_state();
 | 
			
		||||
      this->transformer_ = nullptr;
 | 
			
		||||
@@ -336,6 +337,9 @@ void LightCall::perform() {
 | 
			
		||||
    this->parent_->set_immediately_(v, this->publish_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!this->has_transition_()) {
 | 
			
		||||
    this->parent_->target_state_reached_callback_.call();
 | 
			
		||||
  }
 | 
			
		||||
  if (this->publish_) {
 | 
			
		||||
    this->parent_->publish_state();
 | 
			
		||||
  }
 | 
			
		||||
@@ -395,13 +399,13 @@ LightColorValues LightCall::validate_() {
 | 
			
		||||
 | 
			
		||||
  // sets RGB to 100% if only White specified
 | 
			
		||||
  if (this->white_.has_value()) {
 | 
			
		||||
    if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
 | 
			
		||||
      this->red_ = optional<float>(1.0f);
 | 
			
		||||
      this->green_ = optional<float>(1.0f);
 | 
			
		||||
      this->blue_ = optional<float>(1.0f);
 | 
			
		||||
    }
 | 
			
		||||
    // make white values binary aka 0.0f or 1.0f...this allows brightness to do its job
 | 
			
		||||
    if (traits.get_supports_color_interlock()) {
 | 
			
		||||
      if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
 | 
			
		||||
        this->red_ = optional<float>(1.0f);
 | 
			
		||||
        this->green_ = optional<float>(1.0f);
 | 
			
		||||
        this->blue_ = optional<float>(1.0f);
 | 
			
		||||
      }
 | 
			
		||||
      // make white values binary aka 0.0f or 1.0f...this allows brightness to do its job
 | 
			
		||||
      if (*this->white_ > 0.0f) {
 | 
			
		||||
        this->white_ = optional<float>(1.0f);
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -411,11 +415,13 @@ LightColorValues LightCall::validate_() {
 | 
			
		||||
  }
 | 
			
		||||
  // White to 0% if (exclusively) setting any RGB value that isn't 255,255,255
 | 
			
		||||
  else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
 | 
			
		||||
    if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f && traits.get_supports_rgb_white_value() &&
 | 
			
		||||
        traits.get_supports_color_interlock()) {
 | 
			
		||||
      this->white_ = optional<float>(1.0f);
 | 
			
		||||
    } else if (!this->white_.has_value() || !traits.get_supports_rgb_white_value()) {
 | 
			
		||||
      this->white_ = optional<float>(0.0f);
 | 
			
		||||
    if (traits.get_supports_color_interlock()) {
 | 
			
		||||
      if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f &&
 | 
			
		||||
          traits.get_supports_rgb_white_value() && traits.get_supports_color_interlock()) {
 | 
			
		||||
        this->white_ = optional<float>(1.0f);
 | 
			
		||||
      } else if (!this->white_.has_value() || !traits.get_supports_rgb_white_value()) {
 | 
			
		||||
        this->white_ = optional<float>(0.0f);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // if changing Kelvin alone, change to white light
 | 
			
		||||
@@ -752,6 +758,10 @@ void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bo
 | 
			
		||||
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
 | 
			
		||||
  this->remote_values_callback_.add(std::move(send_callback));
 | 
			
		||||
}
 | 
			
		||||
void LightState::add_new_target_state_reached_callback(std::function<void()> &&send_callback) {
 | 
			
		||||
  this->target_state_reached_callback_.add(std::move(send_callback));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LightEffect *LightState::get_active_effect_() {
 | 
			
		||||
  if (this->active_effect_index_ == 0)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
 
 | 
			
		||||
@@ -242,6 +242,13 @@ class LightState : public Nameable, public Component {
 | 
			
		||||
   */
 | 
			
		||||
  void add_new_remote_values_callback(std::function<void()> &&send_callback);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The callback is called once the state of current_values and remote_values are equal
 | 
			
		||||
   *
 | 
			
		||||
   * @param send_callback
 | 
			
		||||
   */
 | 
			
		||||
  void add_new_target_state_reached_callback(std::function<void()> &&send_callback);
 | 
			
		||||
 | 
			
		||||
  /// Return whether the light has any effects that meet the trait requirements.
 | 
			
		||||
  bool supports_effects();
 | 
			
		||||
 | 
			
		||||
@@ -318,6 +325,12 @@ class LightState : public Nameable, public Component {
 | 
			
		||||
   * starting with the beginning of the transition.
 | 
			
		||||
   */
 | 
			
		||||
  CallbackManager<void()> remote_values_callback_{};
 | 
			
		||||
 | 
			
		||||
  /** Callback to call when the state of current_values and remote_values are equal
 | 
			
		||||
   * This should be called once the state of current_values changed and equals the state of remote_values
 | 
			
		||||
   */
 | 
			
		||||
  CallbackManager<void()> target_state_reached_callback_{};
 | 
			
		||||
 | 
			
		||||
  LightOutput *output_;  ///< Store the output to allow effects to have more access.
 | 
			
		||||
  /// Whether the light value should be written in the next cycle.
 | 
			
		||||
  bool next_write_{true};
 | 
			
		||||
 
 | 
			
		||||
@@ -41,10 +41,10 @@ void MAX31855Sensor::read_data_() {
 | 
			
		||||
  this->read_array(data, 4);
 | 
			
		||||
  this->disable();
 | 
			
		||||
 | 
			
		||||
  const uint32_t mem = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3] << 0;
 | 
			
		||||
  const uint32_t mem = encode_uint32(data[0], data[1], data[2], data[3]);
 | 
			
		||||
 | 
			
		||||
  // Verify we got data
 | 
			
		||||
  if (mem != 0 && mem != 0xFFFFFFFF) {
 | 
			
		||||
  if (mem != 0xFFFFFFFF) {
 | 
			
		||||
    this->status_clear_error();
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGE(TAG, "No data received from MAX31855 (0x%08X). Check wiring!", mem);
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ CONF_SCROLL_DWELL = 'scroll_dwell'
 | 
			
		||||
CONF_SCROLL_DELAY = 'scroll_delay'
 | 
			
		||||
CONF_SCROLL_ENABLE = 'scroll_enable'
 | 
			
		||||
CONF_SCROLL_MODE = 'scroll_mode'
 | 
			
		||||
CONF_REVERSE_ENABLE = 'reverse_enable'
 | 
			
		||||
 | 
			
		||||
SCROLL_MODES = {
 | 
			
		||||
    'CONTINUOUS': 0,
 | 
			
		||||
@@ -39,6 +40,7 @@ CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({
 | 
			
		||||
    cv.Optional(CONF_SCROLL_SPEED, default='250ms'): cv.positive_time_period_milliseconds,
 | 
			
		||||
    cv.Optional(CONF_SCROLL_DELAY, default='1000ms'): cv.positive_time_period_milliseconds,
 | 
			
		||||
    cv.Optional(CONF_SCROLL_DWELL, default='1000ms'): cv.positive_time_period_milliseconds,
 | 
			
		||||
    cv.Optional(CONF_REVERSE_ENABLE, default=False): cv.boolean,
 | 
			
		||||
}).extend(cv.polling_component_schema('500ms')).extend(spi.spi_device_schema(cs_pin_required=True))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -56,6 +58,7 @@ def to_code(config):
 | 
			
		||||
    cg.add(var.set_scroll_delay(config[CONF_SCROLL_DELAY]))
 | 
			
		||||
    cg.add(var.set_scroll(config[CONF_SCROLL_ENABLE]))
 | 
			
		||||
    cg.add(var.set_scroll_mode(config[CONF_SCROLL_MODE]))
 | 
			
		||||
    cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE]))
 | 
			
		||||
 | 
			
		||||
    if CONF_LAMBDA in config:
 | 
			
		||||
        lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(MAX7219ComponentRef, 'it')],
 | 
			
		||||
 
 | 
			
		||||
@@ -108,7 +108,11 @@ void MAX7219Component::display() {
 | 
			
		||||
  // Send the data to the chip
 | 
			
		||||
  for (uint8_t i = 0; i < this->num_chips_; i++) {
 | 
			
		||||
    for (uint8_t j = 0; j < 8; j++) {
 | 
			
		||||
      pixels[j] = this->max_displaybuffer_[i * 8 + j];
 | 
			
		||||
      if (this->reverse_) {
 | 
			
		||||
        pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j];
 | 
			
		||||
      } else {
 | 
			
		||||
        pixels[j] = this->max_displaybuffer_[i * 8 + j];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this->send64pixels(i, pixels);
 | 
			
		||||
  }
 | 
			
		||||
@@ -128,7 +132,7 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color colo
 | 
			
		||||
    this->max_displaybuffer_.resize(x + 1, this->bckgrnd_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (y >= this->get_height_internal() || y < 0)  // If pixel is outside display then dont draw
 | 
			
		||||
  if ((y >= this->get_height_internal()) || (y < 0) || (x < 0))  // If pixel is outside display then dont draw
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  uint16_t pos = x;    // X is starting at 0 top left
 | 
			
		||||
@@ -229,7 +233,7 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) {
 | 
			
		||||
      b = pixels[col];
 | 
			
		||||
    } else if (this->orientation_ == 2) {
 | 
			
		||||
      for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
        b |= ((pixels[i] >> (7 - col)) << (7 - i));
 | 
			
		||||
        b |= ((pixels[i] >> (7 - col)) & 1) << i;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      b = pixels[7 - col];
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,7 @@ class MAX7219Component : public PollingComponent,
 | 
			
		||||
  void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; };
 | 
			
		||||
  void set_scroll(bool on_off) { this->scroll_ = on_off; };
 | 
			
		||||
  void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; };
 | 
			
		||||
  void set_reverse(bool on_off) { this->reverse_ = on_off; };
 | 
			
		||||
 | 
			
		||||
  void send_char(byte chip, byte data);
 | 
			
		||||
  void send64pixels(byte chip, const byte pixels[8]);
 | 
			
		||||
@@ -87,6 +88,7 @@ class MAX7219Component : public PollingComponent,
 | 
			
		||||
  uint8_t intensity_;  /// Intensity of the display from 0 to 15 (most)
 | 
			
		||||
  uint8_t num_chips_;
 | 
			
		||||
  bool scroll_;
 | 
			
		||||
  bool reverse_;
 | 
			
		||||
  bool update_{false};
 | 
			
		||||
  uint16_t scroll_speed_;
 | 
			
		||||
  uint16_t scroll_delay_;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								esphome/components/mcp23s08/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								esphome/components/mcp23s08/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import spi
 | 
			
		||||
from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@SenexCrenshaw']
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['spi']
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_DEVICEADDRESS = "deviceaddress"
 | 
			
		||||
 | 
			
		||||
mcp23S08_ns = cg.esphome_ns.namespace('mcp23s08')
 | 
			
		||||
mcp23S08GPIOMode = mcp23S08_ns.enum('MCP23S08GPIOMode')
 | 
			
		||||
mcp23S08_GPIO_MODES = {
 | 
			
		||||
    'INPUT': mcp23S08GPIOMode.MCP23S08_INPUT,
 | 
			
		||||
    'INPUT_PULLUP': mcp23S08GPIOMode.MCP23S08_INPUT_PULLUP,
 | 
			
		||||
    'OUTPUT': mcp23S08GPIOMode.MCP23S08_OUTPUT,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mcp23S08 = mcp23S08_ns.class_('MCP23S08', cg.Component, spi.SPIDevice)
 | 
			
		||||
mcp23S08GPIOPin = mcp23S08_ns.class_('MCP23S08GPIOPin', cg.GPIOPin)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.Required(CONF_ID): cv.declare_id(mcp23S08),
 | 
			
		||||
    cv.Optional(CONF_DEVICEADDRESS, default=0): cv.uint8_t,
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    cg.add(var.set_device_address(config[CONF_DEVICEADDRESS]))
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield spi.register_spi_device(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF_MCP23S08 = 'mcp23s08'
 | 
			
		||||
 | 
			
		||||
mcp23S08_OUTPUT_PIN_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(CONF_MCP23S08): cv.use_id(mcp23S08),
 | 
			
		||||
    cv.Required(CONF_NUMBER): cv.int_,
 | 
			
		||||
    cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(mcp23S08_GPIO_MODES, upper=True),
 | 
			
		||||
    cv.Optional(CONF_INVERTED, default=False): cv.boolean,
 | 
			
		||||
})
 | 
			
		||||
mcp23S08_INPUT_PIN_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(CONF_MCP23S08): cv.use_id(mcp23S08),
 | 
			
		||||
    cv.Required(CONF_NUMBER): cv.int_range(0, 7),
 | 
			
		||||
    cv.Optional(CONF_MODE, default="INPUT"): cv.enum(mcp23S08_GPIO_MODES, upper=True),
 | 
			
		||||
    cv.Optional(CONF_INVERTED, default=False): cv.boolean,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23S08,
 | 
			
		||||
                                   (mcp23S08_OUTPUT_PIN_SCHEMA, mcp23S08_INPUT_PIN_SCHEMA))
 | 
			
		||||
def mcp23S08_pin_to_code(config):
 | 
			
		||||
    parent = yield cg.get_variable(config[CONF_MCP23S08])
 | 
			
		||||
    yield mcp23S08GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED])
 | 
			
		||||
							
								
								
									
										121
									
								
								esphome/components/mcp23s08/mcp23s08.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								esphome/components/mcp23s08/mcp23s08.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
#include "mcp23s08.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mcp23s08 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "mcp23s08";
 | 
			
		||||
 | 
			
		||||
void MCP23S08::set_device_address(uint8_t device_addr) {
 | 
			
		||||
  if (device_addr != 0) {
 | 
			
		||||
    this->device_opcode_ |= ((device_addr & 0x03) << 1);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MCP23S08::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up MCP23S08...");
 | 
			
		||||
  this->spi_setup();
 | 
			
		||||
  this->enable();
 | 
			
		||||
 | 
			
		||||
  this->transfer_byte(MCP23S08_IODIR);
 | 
			
		||||
  this->transfer_byte(0xFF);
 | 
			
		||||
  for (uint8_t i = 0; i < MCP23S08_OLAT; i++) {
 | 
			
		||||
    this->transfer_byte(0x00);
 | 
			
		||||
  }
 | 
			
		||||
  this->disable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MCP23S08::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "MCP23S08:");
 | 
			
		||||
  LOG_PIN("  CS Pin: ", this->cs_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float MCP23S08::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
bool MCP23S08::digital_read(uint8_t pin) {
 | 
			
		||||
  if (pin > 7) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t bit = pin % 8;
 | 
			
		||||
  uint8_t reg_addr = MCP23S08_GPIO;
 | 
			
		||||
  uint8_t value = 0;
 | 
			
		||||
  this->read_reg(reg_addr, &value);
 | 
			
		||||
  return value & (1 << bit);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MCP23S08::digital_write(uint8_t pin, bool value) {
 | 
			
		||||
  if (pin > 7) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t reg_addr = MCP23S08_OLAT;
 | 
			
		||||
  this->update_reg(pin, value, reg_addr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MCP23S08::pin_mode(uint8_t pin, uint8_t mode) {
 | 
			
		||||
  uint8_t iodir = MCP23S08_IODIR;
 | 
			
		||||
  uint8_t gppu = MCP23S08_GPPU;
 | 
			
		||||
  switch (mode) {
 | 
			
		||||
    case MCP23S08_INPUT:
 | 
			
		||||
      this->update_reg(pin, true, iodir);
 | 
			
		||||
      break;
 | 
			
		||||
    case MCP23S08_INPUT_PULLUP:
 | 
			
		||||
      this->update_reg(pin, true, iodir);
 | 
			
		||||
      this->update_reg(pin, true, gppu);
 | 
			
		||||
      break;
 | 
			
		||||
    case MCP23S08_OUTPUT:
 | 
			
		||||
      this->update_reg(pin, false, iodir);
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MCP23S08::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) {
 | 
			
		||||
  uint8_t bit = pin % 8;
 | 
			
		||||
  uint8_t reg_value = 0;
 | 
			
		||||
  if (reg_addr == MCP23S08_OLAT) {
 | 
			
		||||
    reg_value = this->olat_;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->read_reg(reg_addr, ®_value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (pin_value)
 | 
			
		||||
    reg_value |= 1 << bit;
 | 
			
		||||
  else
 | 
			
		||||
    reg_value &= ~(1 << bit);
 | 
			
		||||
 | 
			
		||||
  this->write_reg(reg_addr, reg_value);
 | 
			
		||||
 | 
			
		||||
  if (reg_addr == MCP23S08_OLAT) {
 | 
			
		||||
    this->olat_ = reg_value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MCP23S08::write_reg(uint8_t reg, uint8_t value) {
 | 
			
		||||
  this->enable();
 | 
			
		||||
  this->transfer_byte(this->device_opcode_);
 | 
			
		||||
  this->transfer_byte(reg);
 | 
			
		||||
  this->transfer_byte(value);
 | 
			
		||||
  this->disable();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) {
 | 
			
		||||
  uint8_t data;
 | 
			
		||||
  this->enable();
 | 
			
		||||
  this->transfer_byte(this->device_opcode_ | 1);
 | 
			
		||||
  this->transfer_byte(reg);
 | 
			
		||||
  *value = this->transfer_byte(0);
 | 
			
		||||
  this->disable();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MCP23S08GPIOPin::MCP23S08GPIOPin(MCP23S08 *parent, uint8_t pin, uint8_t mode, bool inverted)
 | 
			
		||||
    : GPIOPin(pin, mode, inverted), parent_(parent) {}
 | 
			
		||||
void MCP23S08GPIOPin::setup() { this->pin_mode(this->mode_); }
 | 
			
		||||
void MCP23S08GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); }
 | 
			
		||||
bool MCP23S08GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
 | 
			
		||||
void MCP23S08GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
 | 
			
		||||
 | 
			
		||||
}  // namespace mcp23s08
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										74
									
								
								esphome/components/mcp23s08/mcp23s08.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								esphome/components/mcp23s08/mcp23s08.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/esphal.h"
 | 
			
		||||
#include "esphome/components/spi/spi.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mcp23s08 {
 | 
			
		||||
 | 
			
		||||
/// Modes for MCP23S08 pins
 | 
			
		||||
enum MCP23S08GPIOMode : uint8_t {
 | 
			
		||||
  MCP23S08_INPUT = INPUT,                // 0x00
 | 
			
		||||
  MCP23S08_INPUT_PULLUP = INPUT_PULLUP,  // 0x02
 | 
			
		||||
  MCP23S08_OUTPUT = OUTPUT               // 0x01
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum MCP23S08GPIORegisters {
 | 
			
		||||
  // A side
 | 
			
		||||
  MCP23S08_IODIR = 0x00,
 | 
			
		||||
  MCP23S08_IPOL = 0x01,
 | 
			
		||||
  MCP23S08_GPINTEN = 0x02,
 | 
			
		||||
  MCP23S08_DEFVAL = 0x03,
 | 
			
		||||
  MCP23S08_INTCON = 0x04,
 | 
			
		||||
  MCP23S08_IOCON = 0x05,
 | 
			
		||||
  MCP23S08_GPPU = 0x06,
 | 
			
		||||
  MCP23S08_INTF = 0x07,
 | 
			
		||||
  MCP23S08_INTCAP = 0x08,
 | 
			
		||||
  MCP23S08_GPIO = 0x09,
 | 
			
		||||
  MCP23S08_OLAT = 0x0A,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MCP23S08 : public Component,
 | 
			
		||||
                 public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
 | 
			
		||||
                                       spi::DATA_RATE_10MHZ> {
 | 
			
		||||
 public:
 | 
			
		||||
  MCP23S08() = default;
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  bool digital_read(uint8_t pin);
 | 
			
		||||
  void digital_write(uint8_t pin, bool value);
 | 
			
		||||
  void pin_mode(uint8_t pin, uint8_t mode);
 | 
			
		||||
 | 
			
		||||
  void set_device_address(uint8_t device_addr);
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  // read a given register
 | 
			
		||||
  bool read_reg(uint8_t reg, uint8_t *value);
 | 
			
		||||
  // write a value to a given register
 | 
			
		||||
  bool write_reg(uint8_t reg, uint8_t value);
 | 
			
		||||
  // update registers with given pin value.
 | 
			
		||||
  void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t device_opcode_ = 0x40;
 | 
			
		||||
  uint8_t olat_{0x00};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MCP23S08GPIOPin : public GPIOPin {
 | 
			
		||||
 public:
 | 
			
		||||
  MCP23S08GPIOPin(MCP23S08 *parent, uint8_t pin, uint8_t mode, bool inverted = false);
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void pin_mode(uint8_t mode) override;
 | 
			
		||||
  bool digital_read() override;
 | 
			
		||||
  void digital_write(bool value) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  MCP23S08 *parent_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace mcp23s08
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										58
									
								
								esphome/components/mcp23s17/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								esphome/components/mcp23s17/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import spi
 | 
			
		||||
from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@SenexCrenshaw']
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['spi']
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_DEVICEADDRESS = "deviceaddress"
 | 
			
		||||
 | 
			
		||||
mcp23S17_ns = cg.esphome_ns.namespace('mcp23s17')
 | 
			
		||||
mcp23S17GPIOMode = mcp23S17_ns.enum('MCP23S17GPIOMode')
 | 
			
		||||
mcp23S17_GPIO_MODES = {
 | 
			
		||||
    'INPUT': mcp23S17GPIOMode.MCP23S17_INPUT,
 | 
			
		||||
    'INPUT_PULLUP': mcp23S17GPIOMode.MCP23S17_INPUT_PULLUP,
 | 
			
		||||
    'OUTPUT': mcp23S17GPIOMode.MCP23S17_OUTPUT,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mcp23S17 = mcp23S17_ns.class_('MCP23S17', cg.Component, spi.SPIDevice)
 | 
			
		||||
mcp23S17GPIOPin = mcp23S17_ns.class_('MCP23S17GPIOPin', cg.GPIOPin)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.Required(CONF_ID): cv.declare_id(mcp23S17),
 | 
			
		||||
    cv.Optional(CONF_DEVICEADDRESS, default=0): cv.uint8_t,
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    cg.add(var.set_device_address(config[CONF_DEVICEADDRESS]))
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield spi.register_spi_device(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF_MCP23S17 = 'mcp23s17'
 | 
			
		||||
 | 
			
		||||
mcp23S17_OUTPUT_PIN_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(CONF_MCP23S17): cv.use_id(mcp23S17),
 | 
			
		||||
    cv.Required(CONF_NUMBER): cv.int_,
 | 
			
		||||
    cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(mcp23S17_GPIO_MODES, upper=True),
 | 
			
		||||
    cv.Optional(CONF_INVERTED, default=False): cv.boolean,
 | 
			
		||||
})
 | 
			
		||||
mcp23S17_INPUT_PIN_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.Required(CONF_MCP23S17): cv.use_id(mcp23S17),
 | 
			
		||||
    cv.Required(CONF_NUMBER): cv.int_range(0, 15),
 | 
			
		||||
    cv.Optional(CONF_MODE, default="INPUT"): cv.enum(mcp23S17_GPIO_MODES, upper=True),
 | 
			
		||||
    cv.Optional(CONF_INVERTED, default=False): cv.boolean,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23S17,
 | 
			
		||||
                                   (mcp23S17_OUTPUT_PIN_SCHEMA, mcp23S17_INPUT_PIN_SCHEMA))
 | 
			
		||||
def mcp23S17_pin_to_code(config):
 | 
			
		||||
    parent = yield cg.get_variable(config[CONF_MCP23S17])
 | 
			
		||||
    yield mcp23S17GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED])
 | 
			
		||||
							
								
								
									
										126
									
								
								esphome/components/mcp23s17/mcp23s17.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								esphome/components/mcp23s17/mcp23s17.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
			
		||||
#include "mcp23s17.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mcp23s17 {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "mcp23s17";
 | 
			
		||||
 | 
			
		||||
void MCP23S17::set_device_address(uint8_t device_addr) {
 | 
			
		||||
  if (device_addr != 0) {
 | 
			
		||||
    this->device_opcode_ |= ((device_addr & 0b111) << 1);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MCP23S17::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up MCP23S17...");
 | 
			
		||||
  this->spi_setup();
 | 
			
		||||
 | 
			
		||||
  this->enable();
 | 
			
		||||
  uint8_t cmd = 0b01000000;
 | 
			
		||||
  this->transfer_byte(cmd);
 | 
			
		||||
  this->transfer_byte(0x18);
 | 
			
		||||
  this->transfer_byte(0x0A);
 | 
			
		||||
  this->transfer_byte(this->device_opcode_);
 | 
			
		||||
  this->transfer_byte(0);
 | 
			
		||||
  this->transfer_byte(0xFF);
 | 
			
		||||
  this->transfer_byte(0xFF);
 | 
			
		||||
 | 
			
		||||
  for (uint8_t i = 0; i < 20; i++) {
 | 
			
		||||
    this->transfer_byte(0);
 | 
			
		||||
  }
 | 
			
		||||
  this->disable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MCP23S17::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "MCP23S17:");
 | 
			
		||||
  LOG_PIN("  CS Pin: ", this->cs_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float MCP23S17::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
bool MCP23S17::digital_read(uint8_t pin) {
 | 
			
		||||
  uint8_t bit = pin % 8;
 | 
			
		||||
  uint8_t reg_addr = pin < 8 ? MCP23S17_GPIOA : MCP23S17_GPIOB;
 | 
			
		||||
  uint8_t value = 0;
 | 
			
		||||
  this->read_reg(reg_addr, &value);
 | 
			
		||||
  return value & (1 << bit);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MCP23S17::digital_write(uint8_t pin, bool value) {
 | 
			
		||||
  uint8_t reg_addr = pin < 8 ? MCP23S17_OLATA : MCP23S17_OLATB;
 | 
			
		||||
  this->update_reg(pin, value, reg_addr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MCP23S17::pin_mode(uint8_t pin, uint8_t mode) {
 | 
			
		||||
  uint8_t iodir = pin < 8 ? MCP23S17_IODIRA : MCP23S17_IODIRB;
 | 
			
		||||
  uint8_t gppu = pin < 8 ? MCP23S17_GPPUA : MCP23S17_GPPUB;
 | 
			
		||||
  switch (mode) {
 | 
			
		||||
    case MCP23S17_INPUT:
 | 
			
		||||
      this->update_reg(pin, true, iodir);
 | 
			
		||||
      break;
 | 
			
		||||
    case MCP23S17_INPUT_PULLUP:
 | 
			
		||||
      this->update_reg(pin, true, iodir);
 | 
			
		||||
      this->update_reg(pin, true, gppu);
 | 
			
		||||
      break;
 | 
			
		||||
    case MCP23S17_OUTPUT:
 | 
			
		||||
      this->update_reg(pin, false, iodir);
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MCP23S17::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) {
 | 
			
		||||
  uint8_t bit = pin % 8;
 | 
			
		||||
  uint8_t reg_value = 0;
 | 
			
		||||
  if (reg_addr == MCP23S17_OLATA) {
 | 
			
		||||
    reg_value = this->olat_a_;
 | 
			
		||||
  } else if (reg_addr == MCP23S17_OLATB) {
 | 
			
		||||
    reg_value = this->olat_b_;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->read_reg(reg_addr, ®_value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (pin_value)
 | 
			
		||||
    reg_value |= 1 << bit;
 | 
			
		||||
  else
 | 
			
		||||
    reg_value &= ~(1 << bit);
 | 
			
		||||
 | 
			
		||||
  this->write_reg(reg_addr, reg_value);
 | 
			
		||||
 | 
			
		||||
  if (reg_addr == MCP23S17_OLATA) {
 | 
			
		||||
    this->olat_a_ = reg_value;
 | 
			
		||||
  } else if (reg_addr == MCP23S17_OLATB) {
 | 
			
		||||
    this->olat_b_ = reg_value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MCP23S17::read_reg(uint8_t reg, uint8_t *value) {
 | 
			
		||||
  this->enable();
 | 
			
		||||
  this->transfer_byte(this->device_opcode_ | 1);
 | 
			
		||||
  this->transfer_byte(reg);
 | 
			
		||||
  *value = this->transfer_byte(0xFF);
 | 
			
		||||
  this->disable();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MCP23S17::write_reg(uint8_t reg, uint8_t value) {
 | 
			
		||||
  this->enable();
 | 
			
		||||
  this->transfer_byte(this->device_opcode_);
 | 
			
		||||
  this->transfer_byte(reg);
 | 
			
		||||
  this->transfer_byte(value);
 | 
			
		||||
 | 
			
		||||
  this->disable();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MCP23S17GPIOPin::MCP23S17GPIOPin(MCP23S17 *parent, uint8_t pin, uint8_t mode, bool inverted)
 | 
			
		||||
    : GPIOPin(pin, mode, inverted), parent_(parent) {}
 | 
			
		||||
void MCP23S17GPIOPin::setup() { this->pin_mode(this->mode_); }
 | 
			
		||||
void MCP23S17GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); }
 | 
			
		||||
bool MCP23S17GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
 | 
			
		||||
void MCP23S17GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
 | 
			
		||||
 | 
			
		||||
}  // namespace mcp23s17
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										87
									
								
								esphome/components/mcp23s17/mcp23s17.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								esphome/components/mcp23s17/mcp23s17.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/esphal.h"
 | 
			
		||||
#include "esphome/components/spi/spi.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mcp23s17 {
 | 
			
		||||
 | 
			
		||||
/// Modes for MCP23S17 pins
 | 
			
		||||
enum MCP23S17GPIOMode : uint8_t {
 | 
			
		||||
  MCP23S17_INPUT = INPUT,                // 0x00
 | 
			
		||||
  MCP23S17_INPUT_PULLUP = INPUT_PULLUP,  // 0x02
 | 
			
		||||
  MCP23S17_OUTPUT = OUTPUT               // 0x01
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum MCP23S17GPIORegisters {
 | 
			
		||||
  // A side
 | 
			
		||||
  MCP23S17_IODIRA = 0x00,
 | 
			
		||||
  MCP23S17_IPOLA = 0x02,
 | 
			
		||||
  MCP23S17_GPINTENA = 0x04,
 | 
			
		||||
  MCP23S17_DEFVALA = 0x06,
 | 
			
		||||
  MCP23S17_INTCONA = 0x08,
 | 
			
		||||
  MCP23S17_IOCONA = 0x0A,
 | 
			
		||||
  MCP23S17_GPPUA = 0x0C,
 | 
			
		||||
  MCP23S17_INTFA = 0x0E,
 | 
			
		||||
  MCP23S17_INTCAPA = 0x10,
 | 
			
		||||
  MCP23S17_GPIOA = 0x12,
 | 
			
		||||
  MCP23S17_OLATA = 0x14,
 | 
			
		||||
  // B side
 | 
			
		||||
  MCP23S17_IODIRB = 0x01,
 | 
			
		||||
  MCP23S17_IPOLB = 0x03,
 | 
			
		||||
  MCP23S17_GPINTENB = 0x05,
 | 
			
		||||
  MCP23S17_DEFVALB = 0x07,
 | 
			
		||||
  MCP23S17_INTCONB = 0x09,
 | 
			
		||||
  MCP23S17_IOCONB = 0x0B,
 | 
			
		||||
  MCP23S17_GPPUB = 0x0D,
 | 
			
		||||
  MCP23S17_INTFB = 0x0F,
 | 
			
		||||
  MCP23S17_INTCAPB = 0x11,
 | 
			
		||||
  MCP23S17_GPIOB = 0x13,
 | 
			
		||||
  MCP23S17_OLATB = 0x15,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MCP23S17 : public Component,
 | 
			
		||||
                 public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
 | 
			
		||||
                                       spi::DATA_RATE_8MHZ> {
 | 
			
		||||
 public:
 | 
			
		||||
  MCP23S17() = default;
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void set_device_address(uint8_t device_addr);
 | 
			
		||||
 | 
			
		||||
  bool digital_read(uint8_t pin);
 | 
			
		||||
  void digital_write(uint8_t pin, bool value);
 | 
			
		||||
  void pin_mode(uint8_t pin, uint8_t mode);
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  // read a given register
 | 
			
		||||
  bool read_reg(uint8_t reg, uint8_t *value);
 | 
			
		||||
  // write a value to a given register
 | 
			
		||||
  bool write_reg(uint8_t reg, uint8_t value);
 | 
			
		||||
  // update registers with given pin value.
 | 
			
		||||
  void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t device_opcode_ = 0x40;
 | 
			
		||||
  uint8_t olat_a_{0x00};
 | 
			
		||||
  uint8_t olat_b_{0x00};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MCP23S17GPIOPin : public GPIOPin {
 | 
			
		||||
 public:
 | 
			
		||||
  MCP23S17GPIOPin(MCP23S17 *parent, uint8_t pin, uint8_t mode, bool inverted = false);
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void pin_mode(uint8_t mode) override;
 | 
			
		||||
  bool digital_read() override;
 | 
			
		||||
  void digital_write(bool value) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  MCP23S17 *parent_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace mcp23s17
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/mcp2515/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/mcp2515/__init__.py
									
									
									
									
									
										Normal file
									
								
							Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user