mirror of
https://github.com/esphome/esphome.git
synced 2025-09-19 11:42:20 +01:00
Merge remote-tracking branch 'upstream/dev' into zwave_proxy
This commit is contained in:
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@@ -17,7 +17,7 @@ runs:
|
||||
steps:
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@v6.0.0
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
|
2
.github/workflows/auto-label-pr.yml
vendored
2
.github/workflows/auto-label-pr.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Auto Label PR
|
||||
uses: actions/github-script@v7.0.1
|
||||
uses: actions/github-script@v8.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
|
6
.github/workflows/ci-api-proto.yml
vendored
6
.github/workflows/ci-api-proto.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@v6.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
fi
|
||||
- if: failure()
|
||||
name: Review PR
|
||||
uses: actions/github-script@v7.0.1
|
||||
uses: actions/github-script@v8.0.0
|
||||
with:
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
esphome/components/api/api_pb2_service.*
|
||||
- if: success()
|
||||
name: Dismiss review
|
||||
uses: actions/github-script@v7.0.1
|
||||
uses: actions/github-script@v8.0.0
|
||||
with:
|
||||
script: |
|
||||
let reviews = await github.rest.pulls.listReviews({
|
||||
|
6
.github/workflows/ci-clang-tidy-hash.yml
vendored
6
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
uses: actions/checkout@v5.0.0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@v6.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
- if: failure()
|
||||
name: Request changes
|
||||
uses: actions/github-script@v7.0.1
|
||||
uses: actions/github-script@v8.0.0
|
||||
with:
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
|
||||
- if: success()
|
||||
name: Dismiss review
|
||||
uses: actions/github-script@v7.0.1
|
||||
uses: actions/github-script@v8.0.0
|
||||
with:
|
||||
script: |
|
||||
let reviews = await github.rest.pulls.listReviews({
|
||||
|
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@v6.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Set up Docker Buildx
|
||||
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
@@ -156,7 +156,7 @@ jobs:
|
||||
. venv/bin/activate
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5.5.0
|
||||
uses: codecov/codecov-action@v5.5.1
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Save Python virtual environment cache
|
||||
@@ -217,7 +217,7 @@ jobs:
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python 3.13
|
||||
id: python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@v6.0.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Restore Python virtual environment
|
||||
|
@@ -25,7 +25,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Request reviews from component codeowners
|
||||
uses: actions/github-script@v7.0.1
|
||||
uses: actions/github-script@v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
|
2
.github/workflows/external-component-bot.yml
vendored
2
.github/workflows/external-component-bot.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add external component comment
|
||||
uses: actions/github-script@v7.0.1
|
||||
uses: actions/github-script@v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
2
.github/workflows/issue-codeowner-notify.yml
vendored
2
.github/workflows/issue-codeowner-notify.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify codeowners for component issues
|
||||
uses: actions/github-script@v7.0.1
|
||||
uses: actions/github-script@v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
|
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@v6.0.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Build
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
pip3 install build
|
||||
python3 -m build
|
||||
- name: Publish
|
||||
uses: pypa/gh-action-pypi-publish@v1.12.4
|
||||
uses: pypa/gh-action-pypi-publish@v1.13.0
|
||||
with:
|
||||
skip-existing: true
|
||||
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@v6.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
@@ -220,7 +220,7 @@ jobs:
|
||||
- deploy-manifest
|
||||
steps:
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@v7.0.1
|
||||
uses: actions/github-script@v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
script: |
|
||||
@@ -246,7 +246,7 @@ jobs:
|
||||
environment: ${{ needs.init.outputs.deploy_env }}
|
||||
steps:
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@v7.0.1
|
||||
uses: actions/github-script@v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
|
||||
script: |
|
||||
|
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9.1.0
|
||||
- uses: actions/stale@v10.0.0
|
||||
with:
|
||||
days-before-pr-stale: 90
|
||||
days-before-pr-close: 7
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9.1.0
|
||||
- uses: actions/stale@v10.0.0
|
||||
with:
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
|
2
.github/workflows/status-check-labels.yml
vendored
2
.github/workflows/status-check-labels.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- merge-after-release
|
||||
steps:
|
||||
- name: Check for ${{ matrix.label }} label
|
||||
uses: actions/github-script@v7.0.1
|
||||
uses: actions/github-script@v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
||||
|
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
path: lib/home-assistant
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@v6.0.0
|
||||
with:
|
||||
python-version: 3.13
|
||||
|
||||
|
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.12.11
|
||||
rev: v0.12.12
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
29
CODEOWNERS
29
CODEOWNERS
@@ -88,7 +88,7 @@ esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/button/* @esphome/core
|
||||
esphome/components/bytebuffer/* @clydebarrow
|
||||
esphome/components/camera/* @DT-art1 @bdraco
|
||||
esphome/components/camera/* @bdraco @DT-art1
|
||||
esphome/components/camera_encoder/* @DT-art1
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/cap1188/* @mreditor97
|
||||
@@ -145,9 +145,9 @@ esphome/components/es8156/* @kbx81
|
||||
esphome/components/es8311/* @kahrendt @kroimon
|
||||
esphome/components/es8388/* @P4uLT
|
||||
esphome/components/esp32/* @esphome/core
|
||||
esphome/components/esp32_ble/* @Rapsssito @bdraco @jesserockz
|
||||
esphome/components/esp32_ble/* @bdraco @jesserockz @Rapsssito
|
||||
esphome/components/esp32_ble_client/* @bdraco @jesserockz
|
||||
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
|
||||
esphome/components/esp32_ble_server/* @clydebarrow @jesserockz @Rapsssito
|
||||
esphome/components/esp32_ble_tracker/* @bdraco
|
||||
esphome/components/esp32_camera_web_server/* @ayufan
|
||||
esphome/components/esp32_can/* @Sympatron
|
||||
@@ -167,7 +167,7 @@ esphome/components/ezo_pmp/* @carlos-sarmiento
|
||||
esphome/components/factory_reset/* @anatoly-savchenkov
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
esphome/components/feedback/* @ianchi
|
||||
esphome/components/fingerprint_grow/* @OnFreund @alexborro @loongyh
|
||||
esphome/components/fingerprint_grow/* @alexborro @loongyh @OnFreund
|
||||
esphome/components/font/* @clydebarrow @esphome/core
|
||||
esphome/components/fs3000/* @kahrendt
|
||||
esphome/components/ft5x06/* @clydebarrow
|
||||
@@ -203,7 +203,7 @@ esphome/components/heatpumpir/* @rob-deutsch
|
||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
esphome/components/hm3301/* @freekode
|
||||
esphome/components/hmac_md5/* @dwmw2
|
||||
esphome/components/homeassistant/* @OttoWinter @esphome/core
|
||||
esphome/components/homeassistant/* @esphome/core @OttoWinter
|
||||
esphome/components/homeassistant/number/* @landonr
|
||||
esphome/components/homeassistant/switch/* @Links2004
|
||||
esphome/components/honeywell_hih_i2c/* @Benichou34
|
||||
@@ -228,7 +228,7 @@ esphome/components/iaqcore/* @yozik04
|
||||
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
|
||||
esphome/components/improv_base/* @esphome/core
|
||||
esphome/components/improv_serial/* @esphome/core
|
||||
esphome/components/ina226/* @Sergio303 @latonita
|
||||
esphome/components/ina226/* @latonita @Sergio303
|
||||
esphome/components/ina260/* @mreditor97
|
||||
esphome/components/ina2xx_base/* @latonita
|
||||
esphome/components/ina2xx_i2c/* @latonita
|
||||
@@ -277,8 +277,8 @@ esphome/components/max7219digit/* @rspaargaren
|
||||
esphome/components/max9611/* @mckaymatthew
|
||||
esphome/components/mcp23008/* @jesserockz
|
||||
esphome/components/mcp23017/* @jesserockz
|
||||
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
|
||||
esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz
|
||||
esphome/components/mcp23s08/* @jesserockz @SenexCrenshaw
|
||||
esphome/components/mcp23s17/* @jesserockz @SenexCrenshaw
|
||||
esphome/components/mcp23x08_base/* @jesserockz
|
||||
esphome/components/mcp23x17_base/* @jesserockz
|
||||
esphome/components/mcp23xxx_base/* @jesserockz
|
||||
@@ -299,6 +299,7 @@ esphome/components/mics_4514/* @jesserockz
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/midea_ir/* @dudanov
|
||||
esphome/components/mipi_dsi/* @clydebarrow
|
||||
esphome/components/mipi_rgb/* @clydebarrow
|
||||
esphome/components/mipi_spi/* @clydebarrow
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/mixer/speaker/* @kahrendt
|
||||
@@ -342,7 +343,7 @@ esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/packet_transport/* @clydebarrow
|
||||
esphome/components/pca6416a/* @Mat931
|
||||
esphome/components/pca9554/* @clydebarrow @hwstar
|
||||
esphome/components/pca9554/* @bdraco @clydebarrow @hwstar
|
||||
esphome/components/pcf85063/* @brogon
|
||||
esphome/components/pcf8563/* @KoenBreeman
|
||||
esphome/components/pi4ioe5v6408/* @jesserockz
|
||||
@@ -353,9 +354,9 @@ esphome/components/pm2005/* @andrewjswan
|
||||
esphome/components/pmsa003i/* @sjtrny
|
||||
esphome/components/pmsx003/* @ximex
|
||||
esphome/components/pmwcs3/* @SeByDocKy
|
||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532/* @jesserockz @OttoWinter
|
||||
esphome/components/pn532_i2c/* @jesserockz @OttoWinter
|
||||
esphome/components/pn532_spi/* @jesserockz @OttoWinter
|
||||
esphome/components/pn7150/* @jesserockz @kbx81
|
||||
esphome/components/pn7150_i2c/* @jesserockz @kbx81
|
||||
esphome/components/pn7160/* @jesserockz @kbx81
|
||||
@@ -364,7 +365,7 @@ esphome/components/pn7160_spi/* @jesserockz @kbx81
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/preferences/* @esphome/core
|
||||
esphome/components/psram/* @esphome/core
|
||||
esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter
|
||||
esphome/components/pulse_meter/* @cstaahl @stevebaxter @TrentHouliston
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/pylontech/* @functionpointer
|
||||
esphome/components/qmp6988/* @andrewpc
|
||||
@@ -405,7 +406,7 @@ esphome/components/sensirion_common/* @martgras
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/sfa30/* @ghsensdev
|
||||
esphome/components/sgp40/* @SenexCrenshaw
|
||||
esphome/components/sgp4x/* @SenexCrenshaw @martgras
|
||||
esphome/components/sgp4x/* @martgras @SenexCrenshaw
|
||||
esphome/components/shelly_dimmer/* @edge90 @rnauber
|
||||
esphome/components/sht3xd/* @mrtoy-me
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
|
@@ -396,27 +396,29 @@ def check_permissions(port: str):
|
||||
)
|
||||
|
||||
|
||||
def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | str:
|
||||
def upload_program(
|
||||
config: ConfigType, args: ArgsProtocol, devices: list[str]
|
||||
) -> tuple[int, str | None]:
|
||||
host = devices[0]
|
||||
try:
|
||||
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
||||
if getattr(module, "upload_program")(config, args, host):
|
||||
return 0
|
||||
return 0, host
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if get_port_type(host) == "SERIAL":
|
||||
check_permissions(host)
|
||||
|
||||
exit_code = 1
|
||||
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
|
||||
file = getattr(args, "file", None)
|
||||
return upload_using_esptool(config, host, file, args.upload_speed)
|
||||
exit_code = upload_using_esptool(config, host, file, args.upload_speed)
|
||||
elif CORE.target_platform == PLATFORM_RP2040 or CORE.is_libretiny:
|
||||
exit_code = upload_using_platformio(config, host)
|
||||
# else: Unknown target platform, exit_code remains 1
|
||||
|
||||
if CORE.target_platform in (PLATFORM_RP2040):
|
||||
return upload_using_platformio(config, host)
|
||||
|
||||
if CORE.is_libretiny:
|
||||
return upload_using_platformio(config, host)
|
||||
|
||||
return 1 # Unknown target platform
|
||||
return exit_code, host if exit_code == 0 else None
|
||||
|
||||
ota_conf = {}
|
||||
for ota_item in config.get(CONF_OTA, []):
|
||||
@@ -433,10 +435,10 @@ def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | s
|
||||
|
||||
remote_port = int(ota_conf[CONF_PORT])
|
||||
password = ota_conf.get(CONF_PASSWORD, "")
|
||||
binary = args.file if getattr(args, "file", None) is not None else CORE.firmware_bin
|
||||
|
||||
# Check if we should use MQTT for address resolution
|
||||
# This happens when no device was specified, or the current host is "MQTT"/"OTA"
|
||||
devices: list[str] = args.device or []
|
||||
if (
|
||||
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
|
||||
and (not devices or host in ("MQTT", "OTA"))
|
||||
@@ -447,17 +449,23 @@ def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | s
|
||||
):
|
||||
from esphome import mqtt
|
||||
|
||||
host = mqtt.get_esphome_device_ip(
|
||||
config, args.username, args.password, args.client_id
|
||||
)
|
||||
devices = [
|
||||
mqtt.get_esphome_device_ip(
|
||||
config, args.username, args.password, args.client_id
|
||||
)
|
||||
]
|
||||
|
||||
if getattr(args, "file", None) is not None:
|
||||
return espota2.run_ota(host, remote_port, password, args.file)
|
||||
|
||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
return espota2.run_ota(devices, remote_port, password, binary)
|
||||
|
||||
|
||||
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
|
||||
try:
|
||||
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
||||
if getattr(module, "show_logs")(config, args, devices):
|
||||
return 0
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if "logger" not in config:
|
||||
raise EsphomeError("Logger is not configured!")
|
||||
|
||||
@@ -551,17 +559,11 @@ def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
purpose="uploading",
|
||||
)
|
||||
|
||||
# Try each device until one succeeds
|
||||
exit_code = 1
|
||||
for device in devices:
|
||||
_LOGGER.info("Uploading to %s", device)
|
||||
exit_code = upload_program(config, args, device)
|
||||
if exit_code == 0:
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
return 0
|
||||
if len(devices) > 1:
|
||||
_LOGGER.warning("Failed to upload to %s", device)
|
||||
|
||||
exit_code, _ = upload_program(config, args, devices)
|
||||
if exit_code == 0:
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
else:
|
||||
_LOGGER.warning("Failed to upload to %s", devices)
|
||||
return exit_code
|
||||
|
||||
|
||||
@@ -614,19 +616,11 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
purpose="uploading",
|
||||
)
|
||||
|
||||
# Try each device for upload until one succeeds
|
||||
successful_device: str | None = None
|
||||
for device in devices:
|
||||
_LOGGER.info("Uploading to %s", device)
|
||||
exit_code = upload_program(config, args, device)
|
||||
if exit_code == 0:
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
successful_device = device
|
||||
break
|
||||
if len(devices) > 1:
|
||||
_LOGGER.warning("Failed to upload to %s", device)
|
||||
|
||||
if successful_device is None:
|
||||
exit_code, successful_device = upload_program(config, args, devices)
|
||||
if exit_code == 0:
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
else:
|
||||
_LOGGER.warning("Failed to upload to %s", devices)
|
||||
return exit_code
|
||||
|
||||
if args.no_logs:
|
||||
|
@@ -64,7 +64,7 @@ void AbsoluteHumidityComponent::loop() {
|
||||
ESP_LOGW(TAG, "No valid state from humidity sensor!");
|
||||
}
|
||||
this->publish_state(NAN);
|
||||
this->status_set_warning("Unable to calculate absolute humidity.");
|
||||
this->status_set_warning(LOG_STR("Unable to calculate absolute humidity."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -241,6 +241,8 @@ float ADCSensor::sample_autorange_() {
|
||||
cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
|
||||
|
||||
err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
|
||||
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
|
||||
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
|
||||
#else
|
||||
adc_cali_line_fitting_config_t cali_config = {
|
||||
.unit_id = this->adc_unit_,
|
||||
@@ -251,10 +253,14 @@ float ADCSensor::sample_autorange_() {
|
||||
#endif
|
||||
};
|
||||
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
|
||||
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
|
||||
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
|
||||
#endif
|
||||
|
||||
int raw;
|
||||
err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
|
||||
ESP_LOGVV(TAG, "Autorange atten=%d: Raw ADC read %s, value=%d (err=%d)", atten,
|
||||
(err == ESP_OK) ? "SUCCESS" : "FAILED", raw, err);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
|
||||
@@ -275,8 +281,10 @@ float ADCSensor::sample_autorange_() {
|
||||
err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv);
|
||||
if (err == ESP_OK) {
|
||||
voltage = voltage_mv / 1000.0f;
|
||||
ESP_LOGVV(TAG, "Autorange atten=%d: CALIBRATED - raw=%d -> %dmV -> %.6fV", atten, raw, voltage_mv, voltage);
|
||||
} else {
|
||||
voltage = raw * 3.3f / 4095.0f;
|
||||
ESP_LOGVV(TAG, "Autorange atten=%d: UNCALIBRATED FALLBACK - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
|
||||
}
|
||||
// Clean up calibration handle
|
||||
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
|
||||
@@ -287,6 +295,7 @@ float ADCSensor::sample_autorange_() {
|
||||
#endif
|
||||
} else {
|
||||
voltage = raw * 3.3f / 4095.0f;
|
||||
ESP_LOGVV(TAG, "Autorange atten=%d: NO CALIBRATION - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
|
||||
}
|
||||
|
||||
return {raw, voltage};
|
||||
@@ -324,18 +333,32 @@ float ADCSensor::sample_autorange_() {
|
||||
}
|
||||
|
||||
const int adc_half = 2048;
|
||||
uint32_t c12 = std::min(raw12, adc_half);
|
||||
uint32_t c6 = adc_half - std::abs(raw6 - adc_half);
|
||||
uint32_t c2 = adc_half - std::abs(raw2 - adc_half);
|
||||
uint32_t c0 = std::min(4095 - raw0, adc_half);
|
||||
uint32_t csum = c12 + c6 + c2 + c0;
|
||||
const uint32_t c12 = std::min(raw12, adc_half);
|
||||
|
||||
const int32_t c6_signed = adc_half - std::abs(raw6 - adc_half);
|
||||
const uint32_t c6 = (c6_signed > 0) ? c6_signed : 0; // Clamp to prevent underflow
|
||||
|
||||
const int32_t c2_signed = adc_half - std::abs(raw2 - adc_half);
|
||||
const uint32_t c2 = (c2_signed > 0) ? c2_signed : 0; // Clamp to prevent underflow
|
||||
|
||||
const uint32_t c0 = std::min(4095 - raw0, adc_half);
|
||||
const uint32_t csum = c12 + c6 + c2 + c0;
|
||||
|
||||
ESP_LOGVV(TAG, "Autorange summary:");
|
||||
ESP_LOGVV(TAG, " Raw readings: 12db=%d, 6db=%d, 2.5db=%d, 0db=%d", raw12, raw6, raw2, raw0);
|
||||
ESP_LOGVV(TAG, " Voltages: 12db=%.6f, 6db=%.6f, 2.5db=%.6f, 0db=%.6f", mv12, mv6, mv2, mv0);
|
||||
ESP_LOGVV(TAG, " Coefficients: c12=%u, c6=%u, c2=%u, c0=%u, sum=%u", c12, c6, c2, c0, csum);
|
||||
|
||||
if (csum == 0) {
|
||||
ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
|
||||
return NAN;
|
||||
}
|
||||
|
||||
return (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
|
||||
const float final_result = (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
|
||||
ESP_LOGV(TAG, "Autorange final: (%.6f*%u + %.6f*%u + %.6f*%u + %.6f*%u)/%u = %.6fV", mv12, c12, mv6, c6, mv2, c2, mv0,
|
||||
c0, csum, final_result);
|
||||
|
||||
return final_result;
|
||||
}
|
||||
|
||||
} // namespace adc
|
||||
|
@@ -96,7 +96,7 @@ void AHT10Component::read_data_() {
|
||||
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
|
||||
}
|
||||
if (this->read(data, 6) != i2c::ERROR_OK) {
|
||||
this->status_set_warning("Read failed, will retry");
|
||||
this->status_set_warning(LOG_STR("Read failed, will retry"));
|
||||
this->restart_read_();
|
||||
return;
|
||||
}
|
||||
@@ -113,7 +113,7 @@ void AHT10Component::read_data_() {
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Invalid humidity, retrying");
|
||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
}
|
||||
this->restart_read_();
|
||||
return;
|
||||
@@ -144,7 +144,7 @@ void AHT10Component::update() {
|
||||
return;
|
||||
this->start_time_ = millis();
|
||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
this->restart_read_();
|
||||
|
@@ -825,6 +825,7 @@ message GetTimeResponse {
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 epoch_seconds = 1;
|
||||
string timezone = 2;
|
||||
}
|
||||
|
||||
// ==================== USER-DEFINES SERVICES ====================
|
||||
|
@@ -115,7 +115,7 @@ void APIConnection::start() {
|
||||
APIError err = this->helper_->init();
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
this->log_warning_("Helper init failed", err);
|
||||
this->log_warning_(LOG_STR("Helper init failed"), err);
|
||||
return;
|
||||
}
|
||||
this->client_info_.peername = helper_->getpeername();
|
||||
@@ -162,7 +162,7 @@ void APIConnection::loop() {
|
||||
break;
|
||||
} else if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
this->log_warning_("Reading failed", err);
|
||||
this->log_warning_(LOG_STR("Reading failed"), err);
|
||||
return;
|
||||
} else {
|
||||
this->last_traffic_ = now;
|
||||
@@ -1073,8 +1073,14 @@ void APIConnection::camera_image(const CameraImageRequest &msg) {
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
||||
if (homeassistant::global_homeassistant_time != nullptr)
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
||||
#ifdef USE_TIME_TIMEZONE
|
||||
if (!value.timezone.empty() && value.timezone != homeassistant::global_homeassistant_time->get_timezone()) {
|
||||
homeassistant::global_homeassistant_time->set_timezone(value.timezone);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1584,7 +1590,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
||||
return false;
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
this->log_warning_("Packet write failed", err);
|
||||
this->log_warning_(LOG_STR("Packet write failed"), err);
|
||||
return false;
|
||||
}
|
||||
// Do not set last_traffic_ on send
|
||||
@@ -1771,7 +1777,7 @@ void APIConnection::process_batch_() {
|
||||
std::span<const PacketInfo>(packet_info, packet_count));
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
on_fatal_error();
|
||||
this->log_warning_("Batch write failed", err);
|
||||
this->log_warning_(LOG_STR("Batch write failed"), err);
|
||||
}
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1849,11 +1855,14 @@ void APIConnection::process_state_subscriptions_() {
|
||||
}
|
||||
#endif // USE_API_HOMEASSISTANT_STATES
|
||||
|
||||
void APIConnection::log_warning_(const char *message, APIError err) {
|
||||
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), message, api_error_to_str(err), errno);
|
||||
void APIConnection::log_warning_(const LogString *message, APIError err) {
|
||||
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), LOG_STR_ARG(message),
|
||||
LOG_STR_ARG(api_error_to_logstr(err)), errno);
|
||||
}
|
||||
|
||||
void APIConnection::log_socket_operation_failed_(APIError err) { this->log_warning_("Socket operation failed", err); }
|
||||
void APIConnection::log_socket_operation_failed_(APIError err) {
|
||||
this->log_warning_(LOG_STR("Socket operation failed"), err);
|
||||
}
|
||||
|
||||
} // namespace esphome::api
|
||||
#endif
|
||||
|
@@ -738,7 +738,7 @@ class APIConnection final : public APIServerConnection {
|
||||
}
|
||||
|
||||
// Helper function to log API errors with errno
|
||||
void log_warning_(const char *message, APIError err);
|
||||
void log_warning_(const LogString *message, APIError err);
|
||||
// Specific helper for duplicated error message
|
||||
void log_socket_operation_failed_(APIError err);
|
||||
};
|
||||
|
@@ -23,59 +23,59 @@ static const char *const TAG = "api.frame_helper";
|
||||
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
||||
#endif
|
||||
|
||||
const char *api_error_to_str(APIError err) {
|
||||
const LogString *api_error_to_logstr(APIError err) {
|
||||
// not using switch to ensure compiler doesn't try to build a big table out of it
|
||||
if (err == APIError::OK) {
|
||||
return "OK";
|
||||
return LOG_STR("OK");
|
||||
} else if (err == APIError::WOULD_BLOCK) {
|
||||
return "WOULD_BLOCK";
|
||||
return LOG_STR("WOULD_BLOCK");
|
||||
} else if (err == APIError::BAD_INDICATOR) {
|
||||
return "BAD_INDICATOR";
|
||||
return LOG_STR("BAD_INDICATOR");
|
||||
} else if (err == APIError::BAD_DATA_PACKET) {
|
||||
return "BAD_DATA_PACKET";
|
||||
return LOG_STR("BAD_DATA_PACKET");
|
||||
} else if (err == APIError::TCP_NODELAY_FAILED) {
|
||||
return "TCP_NODELAY_FAILED";
|
||||
return LOG_STR("TCP_NODELAY_FAILED");
|
||||
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
|
||||
return "TCP_NONBLOCKING_FAILED";
|
||||
return LOG_STR("TCP_NONBLOCKING_FAILED");
|
||||
} else if (err == APIError::CLOSE_FAILED) {
|
||||
return "CLOSE_FAILED";
|
||||
return LOG_STR("CLOSE_FAILED");
|
||||
} else if (err == APIError::SHUTDOWN_FAILED) {
|
||||
return "SHUTDOWN_FAILED";
|
||||
return LOG_STR("SHUTDOWN_FAILED");
|
||||
} else if (err == APIError::BAD_STATE) {
|
||||
return "BAD_STATE";
|
||||
return LOG_STR("BAD_STATE");
|
||||
} else if (err == APIError::BAD_ARG) {
|
||||
return "BAD_ARG";
|
||||
return LOG_STR("BAD_ARG");
|
||||
} else if (err == APIError::SOCKET_READ_FAILED) {
|
||||
return "SOCKET_READ_FAILED";
|
||||
return LOG_STR("SOCKET_READ_FAILED");
|
||||
} else if (err == APIError::SOCKET_WRITE_FAILED) {
|
||||
return "SOCKET_WRITE_FAILED";
|
||||
return LOG_STR("SOCKET_WRITE_FAILED");
|
||||
} else if (err == APIError::OUT_OF_MEMORY) {
|
||||
return "OUT_OF_MEMORY";
|
||||
return LOG_STR("OUT_OF_MEMORY");
|
||||
} else if (err == APIError::CONNECTION_CLOSED) {
|
||||
return "CONNECTION_CLOSED";
|
||||
return LOG_STR("CONNECTION_CLOSED");
|
||||
}
|
||||
#ifdef USE_API_NOISE
|
||||
else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||
return "BAD_HANDSHAKE_PACKET_LEN";
|
||||
return LOG_STR("BAD_HANDSHAKE_PACKET_LEN");
|
||||
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
|
||||
return "HANDSHAKESTATE_READ_FAILED";
|
||||
return LOG_STR("HANDSHAKESTATE_READ_FAILED");
|
||||
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
|
||||
return "HANDSHAKESTATE_WRITE_FAILED";
|
||||
return LOG_STR("HANDSHAKESTATE_WRITE_FAILED");
|
||||
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
|
||||
return "HANDSHAKESTATE_BAD_STATE";
|
||||
return LOG_STR("HANDSHAKESTATE_BAD_STATE");
|
||||
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
|
||||
return "CIPHERSTATE_DECRYPT_FAILED";
|
||||
return LOG_STR("CIPHERSTATE_DECRYPT_FAILED");
|
||||
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
|
||||
return "CIPHERSTATE_ENCRYPT_FAILED";
|
||||
return LOG_STR("CIPHERSTATE_ENCRYPT_FAILED");
|
||||
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
|
||||
return "HANDSHAKESTATE_SETUP_FAILED";
|
||||
return LOG_STR("HANDSHAKESTATE_SETUP_FAILED");
|
||||
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
|
||||
return "HANDSHAKESTATE_SPLIT_FAILED";
|
||||
return LOG_STR("HANDSHAKESTATE_SPLIT_FAILED");
|
||||
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
|
||||
return "BAD_HANDSHAKE_ERROR_BYTE";
|
||||
return LOG_STR("BAD_HANDSHAKE_ERROR_BYTE");
|
||||
}
|
||||
#endif
|
||||
return "UNKNOWN";
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
|
||||
// Default implementation for loop - handles sending buffered data
|
||||
|
@@ -66,7 +66,7 @@ enum class APIError : uint16_t {
|
||||
#endif
|
||||
};
|
||||
|
||||
const char *api_error_to_str(APIError err);
|
||||
const LogString *api_error_to_logstr(APIError err);
|
||||
|
||||
class APIFrameHelper {
|
||||
public:
|
||||
|
@@ -10,10 +10,18 @@
|
||||
#include <cstring>
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
static const char *const TAG = "api.noise";
|
||||
#ifdef USE_ESP8266
|
||||
static const char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
|
||||
#else
|
||||
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
||||
#endif
|
||||
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
||||
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
|
||||
@@ -27,42 +35,42 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
||||
#endif
|
||||
|
||||
/// Convert a noise error code to a readable error
|
||||
std::string noise_err_to_str(int err) {
|
||||
const LogString *noise_err_to_logstr(int err) {
|
||||
if (err == NOISE_ERROR_NO_MEMORY)
|
||||
return "NO_MEMORY";
|
||||
return LOG_STR("NO_MEMORY");
|
||||
if (err == NOISE_ERROR_UNKNOWN_ID)
|
||||
return "UNKNOWN_ID";
|
||||
return LOG_STR("UNKNOWN_ID");
|
||||
if (err == NOISE_ERROR_UNKNOWN_NAME)
|
||||
return "UNKNOWN_NAME";
|
||||
return LOG_STR("UNKNOWN_NAME");
|
||||
if (err == NOISE_ERROR_MAC_FAILURE)
|
||||
return "MAC_FAILURE";
|
||||
return LOG_STR("MAC_FAILURE");
|
||||
if (err == NOISE_ERROR_NOT_APPLICABLE)
|
||||
return "NOT_APPLICABLE";
|
||||
return LOG_STR("NOT_APPLICABLE");
|
||||
if (err == NOISE_ERROR_SYSTEM)
|
||||
return "SYSTEM";
|
||||
return LOG_STR("SYSTEM");
|
||||
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
|
||||
return "REMOTE_KEY_REQUIRED";
|
||||
return LOG_STR("REMOTE_KEY_REQUIRED");
|
||||
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
|
||||
return "LOCAL_KEY_REQUIRED";
|
||||
return LOG_STR("LOCAL_KEY_REQUIRED");
|
||||
if (err == NOISE_ERROR_PSK_REQUIRED)
|
||||
return "PSK_REQUIRED";
|
||||
return LOG_STR("PSK_REQUIRED");
|
||||
if (err == NOISE_ERROR_INVALID_LENGTH)
|
||||
return "INVALID_LENGTH";
|
||||
return LOG_STR("INVALID_LENGTH");
|
||||
if (err == NOISE_ERROR_INVALID_PARAM)
|
||||
return "INVALID_PARAM";
|
||||
return LOG_STR("INVALID_PARAM");
|
||||
if (err == NOISE_ERROR_INVALID_STATE)
|
||||
return "INVALID_STATE";
|
||||
return LOG_STR("INVALID_STATE");
|
||||
if (err == NOISE_ERROR_INVALID_NONCE)
|
||||
return "INVALID_NONCE";
|
||||
return LOG_STR("INVALID_NONCE");
|
||||
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
|
||||
return "INVALID_PRIVATE_KEY";
|
||||
return LOG_STR("INVALID_PRIVATE_KEY");
|
||||
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
|
||||
return "INVALID_PUBLIC_KEY";
|
||||
return LOG_STR("INVALID_PUBLIC_KEY");
|
||||
if (err == NOISE_ERROR_INVALID_FORMAT)
|
||||
return "INVALID_FORMAT";
|
||||
return LOG_STR("INVALID_FORMAT");
|
||||
if (err == NOISE_ERROR_INVALID_SIGNATURE)
|
||||
return "INVALID_SIGNATURE";
|
||||
return to_string(err);
|
||||
return LOG_STR("INVALID_SIGNATURE");
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
|
||||
/// Initialize the frame helper, returns OK if successful.
|
||||
@@ -75,7 +83,11 @@ APIError APINoiseFrameHelper::init() {
|
||||
// init prologue
|
||||
size_t old_size = prologue_.size();
|
||||
prologue_.resize(old_size + PROLOGUE_INIT_LEN);
|
||||
#ifdef USE_ESP8266
|
||||
memcpy_P(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||
#else
|
||||
std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||
#endif
|
||||
|
||||
state_ = State::CLIENT_HELLO;
|
||||
return APIError::OK;
|
||||
@@ -83,18 +95,18 @@ APIError APINoiseFrameHelper::init() {
|
||||
// Helper for handling handshake frame errors
|
||||
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
|
||||
if (aerr == APIError::BAD_INDICATOR) {
|
||||
send_explicit_handshake_reject_("Bad indicator byte");
|
||||
send_explicit_handshake_reject_(LOG_STR("Bad indicator byte"));
|
||||
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||
send_explicit_handshake_reject_("Bad handshake packet len");
|
||||
send_explicit_handshake_reject_(LOG_STR("Bad handshake packet len"));
|
||||
}
|
||||
return aerr;
|
||||
}
|
||||
|
||||
// Helper for handling noise library errors
|
||||
APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) {
|
||||
APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func_name, APIError api_err) {
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str());
|
||||
HELPER_LOG("%s failed: %s", LOG_STR_ARG(func_name), LOG_STR_ARG(noise_err_to_logstr(err)));
|
||||
return api_err;
|
||||
}
|
||||
return APIError::OK;
|
||||
@@ -279,11 +291,11 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
}
|
||||
|
||||
if (frame.empty()) {
|
||||
send_explicit_handshake_reject_("Empty handshake message");
|
||||
send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
|
||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||
} else if (frame[0] != 0x00) {
|
||||
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
|
||||
send_explicit_handshake_reject_("Bad handshake error byte");
|
||||
send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
|
||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||
}
|
||||
|
||||
@@ -293,8 +305,10 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
||||
if (err != 0) {
|
||||
// Special handling for MAC failure
|
||||
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? "Handshake MAC failure" : "Handshake error");
|
||||
return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED);
|
||||
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure")
|
||||
: LOG_STR("Handshake error"));
|
||||
return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"),
|
||||
APIError::HANDSHAKESTATE_READ_FAILED);
|
||||
}
|
||||
|
||||
aerr = check_handshake_finished_();
|
||||
@@ -307,8 +321,8 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
|
||||
|
||||
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
|
||||
APIError aerr_write =
|
||||
handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED);
|
||||
APIError aerr_write = handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"),
|
||||
APIError::HANDSHAKESTATE_WRITE_FAILED);
|
||||
if (aerr_write != APIError::OK)
|
||||
return aerr_write;
|
||||
buffer[0] = 0x00; // success
|
||||
@@ -331,15 +345,31 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
|
||||
void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
|
||||
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
|
||||
std::vector<uint8_t> data;
|
||||
data.resize(reason.length() + 1);
|
||||
data.resize(reason_len + 1);
|
||||
data[0] = 0x01; // failure
|
||||
|
||||
// Copy error message from PROGMEM
|
||||
if (reason_len > 0) {
|
||||
memcpy_P(data.data() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
|
||||
}
|
||||
#else
|
||||
// Normal memory access
|
||||
const char *reason_str = LOG_STR_ARG(reason);
|
||||
size_t reason_len = strlen(reason_str);
|
||||
std::vector<uint8_t> data;
|
||||
data.resize(reason_len + 1);
|
||||
data[0] = 0x01; // failure
|
||||
|
||||
// Copy error message in bulk
|
||||
if (!reason.empty()) {
|
||||
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
|
||||
if (reason_len > 0) {
|
||||
std::memcpy(data.data() + 1, reason_str, reason_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
// temporarily remove failed state
|
||||
auto orig_state = state_;
|
||||
@@ -368,7 +398,8 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
|
||||
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
||||
APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED);
|
||||
APIError decrypt_err =
|
||||
handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
|
||||
if (decrypt_err != APIError::OK)
|
||||
return decrypt_err;
|
||||
|
||||
@@ -450,7 +481,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
||||
4 + packet.payload_size + frame_footer_size_);
|
||||
|
||||
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||
APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED);
|
||||
APIError aerr =
|
||||
handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
@@ -504,25 +536,27 @@ APIError APINoiseFrameHelper::init_handshake_() {
|
||||
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
|
||||
|
||||
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
|
||||
APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
APIError aerr =
|
||||
handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
const auto &psk = ctx_->get_psk();
|
||||
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
|
||||
aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
|
||||
APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
|
||||
aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
// set_prologue copies it into handshakestate, so we can get rid of it now
|
||||
prologue_ = {};
|
||||
|
||||
err = noise_handshakestate_start(handshake_);
|
||||
aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
return APIError::OK;
|
||||
@@ -540,7 +574,8 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
|
||||
return APIError::HANDSHAKESTATE_BAD_STATE;
|
||||
}
|
||||
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
|
||||
APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED);
|
||||
APIError aerr =
|
||||
handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
|
@@ -32,9 +32,9 @@ class APINoiseFrameHelper final : public APIFrameHelper {
|
||||
APIError write_frame_(const uint8_t *data, uint16_t len);
|
||||
APIError init_handshake_();
|
||||
APIError check_handshake_finished_();
|
||||
void send_explicit_handshake_reject_(const std::string &reason);
|
||||
void send_explicit_handshake_reject_(const LogString *reason);
|
||||
APIError handle_handshake_frame_error_(APIError aerr);
|
||||
APIError handle_noise_error_(int err, const char *func_name, APIError api_err);
|
||||
APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err);
|
||||
|
||||
// Pointers first (4 bytes each)
|
||||
NoiseHandshakeState *handshake_{nullptr};
|
||||
|
@@ -10,6 +10,10 @@
|
||||
#include <cstring>
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
static const char *const TAG = "api.plaintext";
|
||||
@@ -197,11 +201,20 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
// We must send at least 3 bytes to be read, so we add
|
||||
// a message after the indicator byte to ensures its long
|
||||
// enough and can aid in debugging.
|
||||
const char msg[] = "\x00"
|
||||
"Bad indicator byte";
|
||||
static constexpr uint8_t INDICATOR_MSG_SIZE = 19;
|
||||
#ifdef USE_ESP8266
|
||||
static const char MSG_PROGMEM[] PROGMEM = "\x00"
|
||||
"Bad indicator byte";
|
||||
char msg[INDICATOR_MSG_SIZE];
|
||||
memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE);
|
||||
iov[0].iov_base = (void *) msg;
|
||||
iov[0].iov_len = 19;
|
||||
this->write_raw_(iov, 1, 19);
|
||||
#else
|
||||
static const char MSG[] = "\x00"
|
||||
"Bad indicator byte";
|
||||
iov[0].iov_base = (void *) MSG;
|
||||
#endif
|
||||
iov[0].iov_len = INDICATOR_MSG_SIZE;
|
||||
this->write_raw_(iov, 1, INDICATOR_MSG_SIZE);
|
||||
}
|
||||
return aerr;
|
||||
}
|
||||
|
@@ -907,6 +907,16 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
this->timezone = value.as_string();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
@@ -917,8 +927,14 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
|
||||
void GetTimeResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->epoch_seconds); }
|
||||
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->epoch_seconds);
|
||||
buffer.encode_string(2, this->timezone_ref_);
|
||||
}
|
||||
void GetTimeResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_fixed32(1, this->epoch_seconds);
|
||||
size.add_length(1, this->timezone_ref_.size());
|
||||
}
|
||||
#ifdef USE_API_SERVICES
|
||||
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->name_ref_);
|
||||
|
@@ -1177,11 +1177,14 @@ class GetTimeRequest final : public ProtoMessage {
|
||||
class GetTimeResponse final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 37;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 5;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 14;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "get_time_response"; }
|
||||
#endif
|
||||
uint32_t epoch_seconds{0};
|
||||
std::string timezone{};
|
||||
StringRef timezone_ref_{};
|
||||
void set_timezone(const StringRef &ref) { this->timezone_ref_ = ref; }
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1190,6 +1193,7 @@ class GetTimeResponse final : public ProtoDecodableMessage {
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
#ifdef USE_API_SERVICES
|
||||
class ListEntitiesServicesArgument final : public ProtoMessage {
|
||||
|
@@ -1113,7 +1113,17 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||
}
|
||||
#endif
|
||||
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
|
||||
void GetTimeResponse::dump_to(std::string &out) const { dump_field(out, "epoch_seconds", this->epoch_seconds); }
|
||||
void GetTimeResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "GetTimeResponse");
|
||||
dump_field(out, "epoch_seconds", this->epoch_seconds);
|
||||
out.append(" timezone: ");
|
||||
if (!this->timezone_ref_.empty()) {
|
||||
out.append("'").append(this->timezone_ref_.c_str()).append("'");
|
||||
} else {
|
||||
out.append("'").append(this->timezone).append("'");
|
||||
}
|
||||
out.append("\n");
|
||||
}
|
||||
#ifdef USE_API_SERVICES
|
||||
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
|
||||
|
@@ -16,6 +16,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
DEVICE_CLASS_REACTIVE_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_CURRENT_AC,
|
||||
ICON_LIGHTBULB,
|
||||
@@ -78,6 +79,7 @@ CONFIG_SCHEMA = (
|
||||
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
||||
icon=ICON_LIGHTBULB,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_REACTIVE_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||
|
@@ -17,10 +17,12 @@ from esphome.const import (
|
||||
CONF_REACTIVE_POWER,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_APPARENT_POWER,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
DEVICE_CLASS_REACTIVE_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
@@ -100,13 +102,13 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
|
||||
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
||||
icon=ICON_LIGHTBULB,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
device_class=DEVICE_CLASS_REACTIVE_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT_AMPS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
device_class=DEVICE_CLASS_APPARENT_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||
|
@@ -12,7 +12,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
|
||||
|
||||
#define ERROR_CHECK(err) \
|
||||
if ((err) != i2c::ERROR_OK) { \
|
||||
this->status_set_warning("Failed to communicate"); \
|
||||
this->status_set_warning(LOG_STR("Failed to communicate")); \
|
||||
return; \
|
||||
}
|
||||
|
||||
|
@@ -493,7 +493,7 @@ void BedJetHub::dump_config() {
|
||||
" ble_client.app_id: %d\n"
|
||||
" ble_client.conn_id: %d",
|
||||
this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
|
||||
for (auto *child : this->children_) {
|
||||
ESP_LOGCONFIG(TAG, " - %s", child->describe().c_str());
|
||||
|
@@ -149,7 +149,7 @@ void BL0942::setup() {
|
||||
this->write_reg_(BL0942_REG_USR_WRPROT, 0);
|
||||
|
||||
if (this->read_reg_(BL0942_REG_MODE) != mode)
|
||||
this->status_set_warning("BL0942 setup failed!");
|
||||
this->status_set_warning(LOG_STR("BL0942 setup failed!"));
|
||||
|
||||
this->flush();
|
||||
}
|
||||
|
@@ -11,17 +11,35 @@ namespace captive_portal {
|
||||
static const char *const TAG = "captive_portal";
|
||||
|
||||
void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
|
||||
AsyncResponseStream *stream = request->beginResponseStream("application/json");
|
||||
stream->addHeader("cache-control", "public, max-age=0, must-revalidate");
|
||||
AsyncResponseStream *stream = request->beginResponseStream(F("application/json"));
|
||||
stream->addHeader(F("cache-control"), F("public, max-age=0, must-revalidate"));
|
||||
#ifdef USE_ESP8266
|
||||
stream->print(F("{\"mac\":\""));
|
||||
stream->print(get_mac_address_pretty().c_str());
|
||||
stream->print(F("\",\"name\":\""));
|
||||
stream->print(App.get_name().c_str());
|
||||
stream->print(F("\",\"aps\":[{}"));
|
||||
#else
|
||||
stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str());
|
||||
#endif
|
||||
|
||||
for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
|
||||
if (scan.get_is_hidden())
|
||||
continue;
|
||||
|
||||
// Assumes no " in ssid, possible unicode isses?
|
||||
// Assumes no " in ssid, possible unicode isses?
|
||||
#ifdef USE_ESP8266
|
||||
stream->print(F(",{\"ssid\":\""));
|
||||
stream->print(scan.get_ssid().c_str());
|
||||
stream->print(F("\",\"rssi\":"));
|
||||
stream->print(scan.get_rssi());
|
||||
stream->print(F(",\"lock\":"));
|
||||
stream->print(scan.get_with_auth());
|
||||
stream->print(F("}"));
|
||||
#else
|
||||
stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(),
|
||||
scan.get_with_auth());
|
||||
#endif
|
||||
}
|
||||
stream->print(F("]}"));
|
||||
request->send(stream);
|
||||
@@ -34,7 +52,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
|
||||
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
|
||||
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
|
||||
wifi::global_wifi_component->start_scanning();
|
||||
request->redirect("/?save");
|
||||
request->redirect(F("/?save"));
|
||||
}
|
||||
|
||||
void CaptivePortal::setup() {
|
||||
@@ -53,18 +71,23 @@ void CaptivePortal::start() {
|
||||
this->dns_server_ = make_unique<DNSServer>();
|
||||
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||
this->dns_server_->start(53, "*", ip);
|
||||
this->dns_server_->start(53, F("*"), ip);
|
||||
// Re-enable loop() when DNS server is started
|
||||
this->enable_loop();
|
||||
#endif
|
||||
|
||||
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
|
||||
if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
|
||||
req->send(404, "text/html", "File not found");
|
||||
req->send(404, F("text/html"), F("File not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
String url = F("http://");
|
||||
url += wifi::global_wifi_component->wifi_soft_ap_ip().str().c_str();
|
||||
#else
|
||||
auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().str();
|
||||
#endif
|
||||
req->redirect(url.c_str());
|
||||
});
|
||||
|
||||
@@ -73,19 +96,19 @@ void CaptivePortal::start() {
|
||||
}
|
||||
|
||||
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
|
||||
if (req->url() == "/") {
|
||||
if (req->url() == F("/")) {
|
||||
#ifndef USE_ESP8266
|
||||
auto *response = req->beginResponse(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
|
||||
auto *response = req->beginResponse(200, F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
|
||||
#else
|
||||
auto *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
|
||||
auto *response = req->beginResponse_P(200, F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
|
||||
#endif
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader(F("Content-Encoding"), F("gzip"));
|
||||
req->send(response);
|
||||
return;
|
||||
} else if (req->url() == "/config.json") {
|
||||
} else if (req->url() == F("/config.json")) {
|
||||
this->handle_config(req);
|
||||
return;
|
||||
} else if (req->url() == "/wifisave") {
|
||||
} else if (req->url() == F("/wifisave")) {
|
||||
this->handle_wifisave(req);
|
||||
return;
|
||||
}
|
||||
|
@@ -45,11 +45,11 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
return false;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
if (request->url() == "/")
|
||||
if (request->url() == F("/"))
|
||||
return true;
|
||||
if (request->url() == "/config.json")
|
||||
if (request->url() == F("/config.json"))
|
||||
return true;
|
||||
if (request->url() == "/wifisave")
|
||||
if (request->url() == F("/wifisave"))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -152,7 +152,7 @@ void CCS811Component::send_env_data_() {
|
||||
void CCS811Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CCS811");
|
||||
LOG_I2C_DEVICE(this)
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "CO2 Sensor", this->co2_);
|
||||
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_);
|
||||
LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_)
|
||||
|
@@ -64,7 +64,7 @@ bool DallasTemperatureSensor::read_scratch_pad_() {
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - reading scratch pad failed bus reset", this->get_name().c_str());
|
||||
this->status_set_warning("bus reset failed");
|
||||
this->status_set_warning(LOG_STR("bus reset failed"));
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -124,7 +124,7 @@ bool DallasTemperatureSensor::check_scratch_pad_() {
|
||||
crc8(this->scratch_pad_, 8));
|
||||
#endif
|
||||
if (!chksum_validity) {
|
||||
this->status_set_warning("scratch pad checksum invalid");
|
||||
this->status_set_warning(LOG_STR("scratch pad checksum invalid"));
|
||||
ESP_LOGD(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
|
||||
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
|
||||
this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
|
||||
|
@@ -52,7 +52,7 @@ void DebugComponent::on_shutdown() {
|
||||
char buffer[REBOOT_MAX_LEN]{};
|
||||
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
|
||||
if (component != nullptr) {
|
||||
strncpy(buffer, component->get_component_source(), REBOOT_MAX_LEN - 1);
|
||||
strncpy(buffer, LOG_STR_ARG(component->get_component_log_str()), REBOOT_MAX_LEN - 1);
|
||||
buffer[REBOOT_MAX_LEN - 1] = '\0';
|
||||
}
|
||||
ESP_LOGD(TAG, "Storing reboot source: %s", buffer);
|
||||
|
@@ -54,13 +54,13 @@ struct ISRPinArg {
|
||||
|
||||
ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->pin = this->pin_;
|
||||
arg->pin = this->get_pin_num();
|
||||
arg->flags = gpio::FLAG_NONE;
|
||||
arg->inverted = inverted_;
|
||||
arg->inverted = this->pin_flags_.inverted;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
arg->use_rtc = rtc_gpio_is_valid_gpio(this->pin_);
|
||||
arg->use_rtc = rtc_gpio_is_valid_gpio(this->get_pin_num());
|
||||
if (arg->use_rtc)
|
||||
arg->rtc_pin = rtc_io_number_get(this->pin_);
|
||||
arg->rtc_pin = rtc_io_number_get(this->get_pin_num());
|
||||
#endif
|
||||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
@@ -69,23 +69,23 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
|
||||
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
|
||||
switch (type) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
idf_type = inverted_ ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE;
|
||||
idf_type = this->pin_flags_.inverted ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_FALLING_EDGE:
|
||||
idf_type = inverted_ ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE;
|
||||
idf_type = this->pin_flags_.inverted ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_ANY_EDGE:
|
||||
idf_type = GPIO_INTR_ANYEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_LOW_LEVEL:
|
||||
idf_type = inverted_ ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
|
||||
idf_type = this->pin_flags_.inverted ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
|
||||
break;
|
||||
case gpio::INTERRUPT_HIGH_LEVEL:
|
||||
idf_type = inverted_ ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL;
|
||||
idf_type = this->pin_flags_.inverted ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL;
|
||||
break;
|
||||
}
|
||||
gpio_set_intr_type(pin_, idf_type);
|
||||
gpio_intr_enable(pin_);
|
||||
gpio_set_intr_type(this->get_pin_num(), idf_type);
|
||||
gpio_intr_enable(this->get_pin_num());
|
||||
if (!isr_service_installed) {
|
||||
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
|
||||
if (res != ESP_OK) {
|
||||
@@ -94,31 +94,31 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
|
||||
}
|
||||
isr_service_installed = true;
|
||||
}
|
||||
gpio_isr_handler_add(pin_, func, arg);
|
||||
gpio_isr_handler_add(this->get_pin_num(), func, arg);
|
||||
}
|
||||
|
||||
std::string ESP32InternalGPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast<uint32_t>(pin_));
|
||||
snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast<uint32_t>(this->pin_));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void ESP32InternalGPIOPin::setup() {
|
||||
gpio_config_t conf{};
|
||||
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
|
||||
conf.mode = flags_to_mode(flags_);
|
||||
conf.pull_up_en = flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
conf.pull_down_en = flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
|
||||
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(this->pin_);
|
||||
conf.mode = flags_to_mode(this->flags_);
|
||||
conf.pull_up_en = this->flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
conf.pull_down_en = this->flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
|
||||
conf.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_config(&conf);
|
||||
if (flags_ & gpio::FLAG_OUTPUT) {
|
||||
gpio_set_drive_capability(pin_, drive_strength_);
|
||||
if (this->flags_ & gpio::FLAG_OUTPUT) {
|
||||
gpio_set_drive_capability(this->get_pin_num(), this->get_drive_strength());
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
// can't call gpio_config here because that logs in esp-idf which may cause issues
|
||||
gpio_set_direction(pin_, flags_to_mode(flags));
|
||||
gpio_set_direction(this->get_pin_num(), flags_to_mode(flags));
|
||||
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
|
||||
if ((flags & gpio::FLAG_PULLUP) && (flags & gpio::FLAG_PULLDOWN)) {
|
||||
pull_mode = GPIO_PULLUP_PULLDOWN;
|
||||
@@ -127,12 +127,16 @@ void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
} else if (flags & gpio::FLAG_PULLDOWN) {
|
||||
pull_mode = GPIO_PULLDOWN_ONLY;
|
||||
}
|
||||
gpio_set_pull_mode(pin_, pull_mode);
|
||||
gpio_set_pull_mode(this->get_pin_num(), pull_mode);
|
||||
}
|
||||
|
||||
bool ESP32InternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
|
||||
void ESP32InternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
|
||||
void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
|
||||
bool ESP32InternalGPIOPin::digital_read() {
|
||||
return bool(gpio_get_level(this->get_pin_num())) != this->pin_flags_.inverted;
|
||||
}
|
||||
void ESP32InternalGPIOPin::digital_write(bool value) {
|
||||
gpio_set_level(this->get_pin_num(), value != this->pin_flags_.inverted ? 1 : 0);
|
||||
}
|
||||
void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(this->get_pin_num()); }
|
||||
|
||||
} // namespace esp32
|
||||
|
||||
|
@@ -7,12 +7,18 @@
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
|
||||
// Static assertions to ensure our bit-packed fields can hold the enum values
|
||||
static_assert(GPIO_NUM_MAX <= 256, "gpio_num_t has too many values for uint8_t");
|
||||
static_assert(GPIO_DRIVE_CAP_MAX <= 4, "gpio_drive_cap_t has too many values for 2-bit field");
|
||||
|
||||
class ESP32InternalGPIOPin : public InternalGPIOPin {
|
||||
public:
|
||||
void set_pin(gpio_num_t pin) { pin_ = pin; }
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_drive_strength(gpio_drive_cap_t drive_strength) { drive_strength_ = drive_strength; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
void set_pin(gpio_num_t pin) { this->pin_ = static_cast<uint8_t>(pin); }
|
||||
void set_inverted(bool inverted) { this->pin_flags_.inverted = inverted; }
|
||||
void set_drive_strength(gpio_drive_cap_t drive_strength) {
|
||||
this->pin_flags_.drive_strength = static_cast<uint8_t>(drive_strength);
|
||||
}
|
||||
void set_flags(gpio::Flags flags) { this->flags_ = flags; }
|
||||
|
||||
void setup() override;
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
@@ -21,17 +27,26 @@ class ESP32InternalGPIOPin : public InternalGPIOPin {
|
||||
std::string dump_summary() const override;
|
||||
void detach_interrupt() const override;
|
||||
ISRInternalGPIOPin to_isr() const override;
|
||||
uint8_t get_pin() const override { return (uint8_t) pin_; }
|
||||
gpio::Flags get_flags() const override { return flags_; }
|
||||
bool is_inverted() const override { return inverted_; }
|
||||
uint8_t get_pin() const override { return this->pin_; }
|
||||
gpio::Flags get_flags() const override { return this->flags_; }
|
||||
bool is_inverted() const override { return this->pin_flags_.inverted; }
|
||||
gpio_num_t get_pin_num() const { return static_cast<gpio_num_t>(this->pin_); }
|
||||
gpio_drive_cap_t get_drive_strength() const { return static_cast<gpio_drive_cap_t>(this->pin_flags_.drive_strength); }
|
||||
|
||||
protected:
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
gpio_num_t pin_;
|
||||
gpio_drive_cap_t drive_strength_;
|
||||
gpio::Flags flags_;
|
||||
bool inverted_;
|
||||
// Memory layout: 8 bytes total on 32-bit systems
|
||||
// - 3 bytes for members below
|
||||
// - 1 byte padding for alignment
|
||||
// - 4 bytes for vtable pointer
|
||||
uint8_t pin_; // GPIO pin number (0-255, actual max ~54 on ESP32)
|
||||
gpio::Flags flags_; // GPIO flags (1 byte)
|
||||
struct PinFlags {
|
||||
uint8_t inverted : 1; // Invert pin logic (1 bit)
|
||||
uint8_t drive_strength : 2; // Drive strength 0-3 (2 bits)
|
||||
uint8_t reserved : 5; // Reserved for future use (5 bits)
|
||||
} pin_flags_; // Total: 1 byte
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static bool isr_service_installed;
|
||||
};
|
||||
|
@@ -58,8 +58,8 @@ extern "C" void resetPins() { // NOLINT
|
||||
|
||||
#ifdef USE_ESP8266_EARLY_PIN_INIT
|
||||
for (int i = 0; i < 16; i++) {
|
||||
uint8_t mode = ESPHOME_ESP8266_GPIO_INITIAL_MODE[i];
|
||||
uint8_t level = ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i];
|
||||
uint8_t mode = progmem_read_byte(&ESPHOME_ESP8266_GPIO_INITIAL_MODE[i]);
|
||||
uint8_t level = progmem_read_byte(&ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i]);
|
||||
if (mode != 255)
|
||||
pinMode(i, mode); // NOLINT
|
||||
if (level != 255)
|
||||
|
@@ -199,11 +199,11 @@ async def add_pin_initial_states_array():
|
||||
|
||||
cg.add_global(
|
||||
cg.RawExpression(
|
||||
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] = {{{initial_modes_s}}}"
|
||||
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] PROGMEM = {{{initial_modes_s}}}"
|
||||
)
|
||||
)
|
||||
cg.add_global(
|
||||
cg.RawExpression(
|
||||
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] = {{{initial_levels_s}}}"
|
||||
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] PROGMEM = {{{initial_levels_s}}}"
|
||||
)
|
||||
)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#ifdef USE_ESP8266
|
||||
|
||||
#include <c_types.h>
|
||||
#include <cinttypes>
|
||||
extern "C" {
|
||||
#include "spi_flash.h"
|
||||
}
|
||||
@@ -119,16 +120,16 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
|
||||
|
||||
class ESP8266PreferenceBackend : public ESPPreferenceBackend {
|
||||
public:
|
||||
size_t offset = 0;
|
||||
uint32_t type = 0;
|
||||
uint16_t offset = 0;
|
||||
uint8_t length_words = 0; // Max 255 words (1020 bytes of data)
|
||||
bool in_flash = false;
|
||||
size_t length_words = 0;
|
||||
|
||||
bool save(const uint8_t *data, size_t len) override {
|
||||
if (bytes_to_words(len) != length_words) {
|
||||
return false;
|
||||
}
|
||||
size_t buffer_size = length_words + 1;
|
||||
size_t buffer_size = static_cast<size_t>(length_words) + 1;
|
||||
std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]()); // Note the () for zero-initialization
|
||||
memcpy(buffer.get(), data, len);
|
||||
buffer[length_words] = calculate_crc(buffer.get(), buffer.get() + length_words, type);
|
||||
@@ -142,7 +143,7 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend {
|
||||
if (bytes_to_words(len) != length_words) {
|
||||
return false;
|
||||
}
|
||||
size_t buffer_size = length_words + 1;
|
||||
size_t buffer_size = static_cast<size_t>(length_words) + 1;
|
||||
std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]());
|
||||
bool ret = in_flash ? load_from_flash(offset, buffer.get(), buffer_size)
|
||||
: load_from_rtc(offset, buffer.get(), buffer_size);
|
||||
@@ -176,15 +177,19 @@ class ESP8266Preferences : public ESPPreferences {
|
||||
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
|
||||
uint32_t length_words = bytes_to_words(length);
|
||||
if (length_words > 255) {
|
||||
ESP_LOGE(TAG, "Preference too large: %" PRIu32 " words > 255", length_words);
|
||||
return {};
|
||||
}
|
||||
if (in_flash) {
|
||||
uint32_t start = current_flash_offset;
|
||||
uint32_t end = start + length_words + 1;
|
||||
if (end > ESP8266_FLASH_STORAGE_SIZE)
|
||||
return {};
|
||||
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
pref->offset = start;
|
||||
pref->offset = static_cast<uint16_t>(start);
|
||||
pref->type = type;
|
||||
pref->length_words = length_words;
|
||||
pref->length_words = static_cast<uint8_t>(length_words);
|
||||
pref->in_flash = true;
|
||||
current_flash_offset = end;
|
||||
return {pref};
|
||||
@@ -210,9 +215,9 @@ class ESP8266Preferences : public ESPPreferences {
|
||||
uint32_t rtc_offset = in_normal ? start + 32 : start - 96;
|
||||
|
||||
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
pref->offset = rtc_offset;
|
||||
pref->offset = static_cast<uint16_t>(rtc_offset);
|
||||
pref->type = type;
|
||||
pref->length_words = length_words;
|
||||
pref->length_words = static_cast<uint8_t>(length_words);
|
||||
pref->in_flash = false;
|
||||
current_offset += length_words + 1;
|
||||
return pref;
|
||||
|
@@ -30,19 +30,19 @@ void ESPHomeOTAComponent::setup() {
|
||||
|
||||
this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||
if (this->server_ == nullptr) {
|
||||
this->log_socket_error_("creation");
|
||||
this->log_socket_error_(LOG_STR("creation"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
int enable = 1;
|
||||
int err = this->server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
this->log_socket_error_("reuseaddr");
|
||||
this->log_socket_error_(LOG_STR("reuseaddr"));
|
||||
// we can still continue
|
||||
}
|
||||
err = this->server_->setblocking(false);
|
||||
if (err != 0) {
|
||||
this->log_socket_error_("non-blocking");
|
||||
this->log_socket_error_(LOG_STR("non-blocking"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -51,21 +51,21 @@ void ESPHomeOTAComponent::setup() {
|
||||
|
||||
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
|
||||
if (sl == 0) {
|
||||
this->log_socket_error_("set sockaddr");
|
||||
this->log_socket_error_(LOG_STR("set sockaddr"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
err = this->server_->bind((struct sockaddr *) &server, sizeof(server));
|
||||
if (err != 0) {
|
||||
this->log_socket_error_("bind");
|
||||
this->log_socket_error_(LOG_STR("bind"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
err = this->server_->listen(4);
|
||||
if (err != 0) {
|
||||
this->log_socket_error_("listen");
|
||||
this->log_socket_error_(LOG_STR("listen"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -114,17 +114,17 @@ void ESPHomeOTAComponent::handle_handshake_() {
|
||||
return;
|
||||
int err = this->client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
this->log_socket_error_("nodelay");
|
||||
this->log_socket_error_(LOG_STR("nodelay"));
|
||||
this->cleanup_connection_();
|
||||
return;
|
||||
}
|
||||
err = this->client_->setblocking(false);
|
||||
if (err != 0) {
|
||||
this->log_socket_error_("non-blocking");
|
||||
this->log_socket_error_(LOG_STR("non-blocking"));
|
||||
this->cleanup_connection_();
|
||||
return;
|
||||
}
|
||||
this->log_start_("handshake");
|
||||
this->log_start_(LOG_STR("handshake"));
|
||||
this->client_connect_time_ = App.get_loop_component_start_time();
|
||||
this->magic_buf_pos_ = 0; // Reset magic buffer position
|
||||
}
|
||||
@@ -150,7 +150,7 @@ void ESPHomeOTAComponent::handle_handshake_() {
|
||||
if (read <= 0) {
|
||||
// Error or connection closed
|
||||
if (read == -1) {
|
||||
this->log_socket_error_("reading magic bytes");
|
||||
this->log_socket_error_(LOG_STR("reading magic bytes"));
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Remote closed during handshake");
|
||||
}
|
||||
@@ -209,7 +209,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
|
||||
// Read features - 1 byte
|
||||
if (!this->readall_(buf, 1)) {
|
||||
this->log_read_error_("features");
|
||||
this->log_read_error_(LOG_STR("features"));
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
ota_features = buf[0]; // NOLINT
|
||||
@@ -288,7 +288,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
|
||||
// Read size, 4 bytes MSB first
|
||||
if (!this->readall_(buf, 4)) {
|
||||
this->log_read_error_("size");
|
||||
this->log_read_error_(LOG_STR("size"));
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
ota_size = 0;
|
||||
@@ -302,7 +302,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
// starting the update, set the warning status and notify
|
||||
// listeners. This ensures that port scanners do not
|
||||
// accidentally trigger the update process.
|
||||
this->log_start_("update");
|
||||
this->log_start_(LOG_STR("update"));
|
||||
this->status_set_warning();
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
|
||||
@@ -320,7 +320,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
|
||||
// Read binary MD5, 32 bytes
|
||||
if (!this->readall_(buf, 32)) {
|
||||
this->log_read_error_("MD5 checksum");
|
||||
this->log_read_error_(LOG_STR("MD5 checksum"));
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
sbuf[32] = '\0';
|
||||
@@ -393,7 +393,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
|
||||
// Read ACK
|
||||
if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
|
||||
this->log_read_error_("ack");
|
||||
this->log_read_error_(LOG_STR("ack"));
|
||||
// do not go to error, this is not fatal
|
||||
}
|
||||
|
||||
@@ -477,12 +477,14 @@ float ESPHomeOTAComponent::get_setup_priority() const { return setup_priority::A
|
||||
uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
|
||||
void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
|
||||
|
||||
void ESPHomeOTAComponent::log_socket_error_(const char *msg) { ESP_LOGW(TAG, "Socket %s: errno %d", msg, errno); }
|
||||
void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) {
|
||||
ESP_LOGW(TAG, "Socket %s: errno %d", LOG_STR_ARG(msg), errno);
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::log_read_error_(const char *what) { ESP_LOGW(TAG, "Read %s failed", what); }
|
||||
void ESPHomeOTAComponent::log_read_error_(const LogString *what) { ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what)); }
|
||||
|
||||
void ESPHomeOTAComponent::log_start_(const char *phase) {
|
||||
ESP_LOGD(TAG, "Starting %s from %s", phase, this->client_->getpeername().c_str());
|
||||
void ESPHomeOTAComponent::log_start_(const LogString *phase) {
|
||||
ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), this->client_->getpeername().c_str());
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::cleanup_connection_() {
|
||||
|
@@ -2,10 +2,11 @@
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -31,9 +32,9 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||
void handle_data_();
|
||||
bool readall_(uint8_t *buf, size_t len);
|
||||
bool writeall_(const uint8_t *buf, size_t len);
|
||||
void log_socket_error_(const char *msg);
|
||||
void log_read_error_(const char *what);
|
||||
void log_start_(const char *phase);
|
||||
void log_socket_error_(const LogString *msg);
|
||||
void log_read_error_(const LogString *what);
|
||||
void log_start_(const LogString *phase);
|
||||
void cleanup_connection_();
|
||||
void yield_and_feed_watchdog_();
|
||||
|
||||
|
@@ -492,7 +492,7 @@ void EthernetComponent::start_connect_() {
|
||||
global_eth_component->ipv6_count_ = 0;
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
this->connect_begin_ = millis();
|
||||
this->status_set_warning("waiting for IP configuration");
|
||||
this->status_set_warning(LOG_STR("waiting for IP configuration"));
|
||||
|
||||
esp_err_t err;
|
||||
err = esp_netif_set_hostname(this->eth_netif_, App.get_name().c_str());
|
||||
|
@@ -11,22 +11,22 @@ static const uint8_t NUMBER_OF_READ_RETRIES = 5;
|
||||
void GDK101Component::update() {
|
||||
uint8_t data[2];
|
||||
if (!this->read_dose_1m_(data)) {
|
||||
this->status_set_warning("Failed to read dose 1m");
|
||||
this->status_set_warning(LOG_STR("Failed to read dose 1m"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->read_dose_10m_(data)) {
|
||||
this->status_set_warning("Failed to read dose 10m");
|
||||
this->status_set_warning(LOG_STR("Failed to read dose 10m"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->read_status_(data)) {
|
||||
this->status_set_warning("Failed to read status");
|
||||
this->status_set_warning(LOG_STR("Failed to read status"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->read_measurement_duration_(data)) {
|
||||
this->status_set_warning("Failed to read measurement duration");
|
||||
this->status_set_warning(LOG_STR("Failed to read measurement duration"));
|
||||
return;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
|
@@ -6,6 +6,23 @@ namespace gpio {
|
||||
|
||||
static const char *const TAG = "gpio.binary_sensor";
|
||||
|
||||
static const LogString *interrupt_type_to_string(gpio::InterruptType type) {
|
||||
switch (type) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
return LOG_STR("RISING_EDGE");
|
||||
case gpio::INTERRUPT_FALLING_EDGE:
|
||||
return LOG_STR("FALLING_EDGE");
|
||||
case gpio::INTERRUPT_ANY_EDGE:
|
||||
return LOG_STR("ANY_EDGE");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
static const LogString *gpio_mode_to_string(bool use_interrupt) {
|
||||
return use_interrupt ? LOG_STR("interrupt") : LOG_STR("polling");
|
||||
}
|
||||
|
||||
void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) {
|
||||
bool new_state = arg->isr_pin_.digital_read();
|
||||
if (new_state != arg->last_state_) {
|
||||
@@ -51,25 +68,9 @@ void GPIOBinarySensor::setup() {
|
||||
void GPIOBinarySensor::dump_config() {
|
||||
LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this);
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
const char *mode = this->use_interrupt_ ? "interrupt" : "polling";
|
||||
ESP_LOGCONFIG(TAG, " Mode: %s", mode);
|
||||
ESP_LOGCONFIG(TAG, " Mode: %s", LOG_STR_ARG(gpio_mode_to_string(this->use_interrupt_)));
|
||||
if (this->use_interrupt_) {
|
||||
const char *interrupt_type;
|
||||
switch (this->interrupt_type_) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
interrupt_type = "RISING_EDGE";
|
||||
break;
|
||||
case gpio::INTERRUPT_FALLING_EDGE:
|
||||
interrupt_type = "FALLING_EDGE";
|
||||
break;
|
||||
case gpio::INTERRUPT_ANY_EDGE:
|
||||
interrupt_type = "ANY_EDGE";
|
||||
break;
|
||||
default:
|
||||
interrupt_type = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Interrupt Type: %s", interrupt_type);
|
||||
ESP_LOGCONFIG(TAG, " Interrupt Type: %s", LOG_STR_ARG(interrupt_type_to_string(this->interrupt_type_)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,6 +4,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome::gpio_expander {
|
||||
@@ -11,18 +12,27 @@ namespace esphome::gpio_expander {
|
||||
/// @brief A class to cache the read state of a GPIO expander.
|
||||
/// This class caches reads between GPIO Pins which are on the same bank.
|
||||
/// This means that for reading whole Port (ex. 8 pins) component needs only one
|
||||
/// I2C/SPI read per main loop call. It assumes, that one bit in byte identifies one GPIO pin
|
||||
/// I2C/SPI read per main loop call. It assumes that one bit in byte identifies one GPIO pin.
|
||||
///
|
||||
/// Template parameters:
|
||||
/// T - Type which represents internal register. Could be uint8_t or uint16_t. Adjust to
|
||||
/// match size of your internal GPIO bank register.
|
||||
/// N - Number of pins
|
||||
template<typename T, T N> class CachedGpioExpander {
|
||||
/// T - Type which represents internal bank register. Could be uint8_t or uint16_t.
|
||||
/// Choose based on how your I/O expander reads pins:
|
||||
/// * uint8_t: For chips that read banks separately (8 pins at a time)
|
||||
/// Examples: MCP23017 (2x8-bit banks), TCA9555 (2x8-bit banks)
|
||||
/// * uint16_t: For chips that read all pins at once (up to 16 pins)
|
||||
/// Examples: PCF8574/8575 (8/16 pins), PCA9554/9555 (8/16 pins)
|
||||
/// N - Total number of pins (maximum 65535)
|
||||
/// P - Type for pin number parameters (automatically selected based on N:
|
||||
/// uint8_t for N<=256, uint16_t for N>256). Can be explicitly specified
|
||||
/// if needed (e.g., for components like SN74HC165 with >256 pins)
|
||||
template<typename T, uint16_t N, typename P = typename std::conditional<(N > 256), uint16_t, uint8_t>::type>
|
||||
class CachedGpioExpander {
|
||||
public:
|
||||
/// @brief Read the state of the given pin. This will invalidate the cache for the given pin number.
|
||||
/// @param pin Pin number to read
|
||||
/// @return Pin state
|
||||
bool digital_read(T pin) {
|
||||
const uint8_t bank = pin / BANK_SIZE;
|
||||
bool digital_read(P pin) {
|
||||
const P bank = pin / BANK_SIZE;
|
||||
const T pin_mask = (1 << (pin % BANK_SIZE));
|
||||
// Check if specific pin cache is valid
|
||||
if (this->read_cache_valid_[bank] & pin_mask) {
|
||||
@@ -38,21 +48,31 @@ template<typename T, T N> class CachedGpioExpander {
|
||||
return this->digital_read_cache(pin);
|
||||
}
|
||||
|
||||
void digital_write(T pin, bool value) { this->digital_write_hw(pin, value); }
|
||||
void digital_write(P pin, bool value) { this->digital_write_hw(pin, value); }
|
||||
|
||||
protected:
|
||||
/// @brief Call component low level function to read GPIO state from device
|
||||
virtual bool digital_read_hw(T pin) = 0;
|
||||
/// @brief Call component read function from internal cache.
|
||||
virtual bool digital_read_cache(T pin) = 0;
|
||||
/// @brief Call component low level function to write GPIO state to device
|
||||
virtual void digital_write_hw(T pin, bool value) = 0;
|
||||
/// @brief Read GPIO bank from hardware into internal state
|
||||
/// @param pin Pin number (used to determine which bank to read)
|
||||
/// @return true if read succeeded, false on communication error
|
||||
/// @note This does NOT return the pin state. It returns whether the read operation succeeded.
|
||||
/// The actual pin state should be returned by digital_read_cache().
|
||||
virtual bool digital_read_hw(P pin) = 0;
|
||||
|
||||
/// @brief Get cached pin value from internal state
|
||||
/// @param pin Pin number to read
|
||||
/// @return Pin state (true = HIGH, false = LOW)
|
||||
virtual bool digital_read_cache(P pin) = 0;
|
||||
|
||||
/// @brief Write GPIO state to hardware
|
||||
/// @param pin Pin number to write
|
||||
/// @param value Pin state to write (true = HIGH, false = LOW)
|
||||
virtual void digital_write_hw(P pin, bool value) = 0;
|
||||
|
||||
/// @brief Invalidate cache. This function should be called in component loop().
|
||||
void reset_pin_cache_() { memset(this->read_cache_valid_, 0x00, CACHE_SIZE_BYTES); }
|
||||
|
||||
static constexpr uint8_t BITS_PER_BYTE = 8;
|
||||
static constexpr uint8_t BANK_SIZE = sizeof(T) * BITS_PER_BYTE;
|
||||
static constexpr uint16_t BITS_PER_BYTE = 8;
|
||||
static constexpr uint16_t BANK_SIZE = sizeof(T) * BITS_PER_BYTE;
|
||||
static constexpr size_t BANKS = N / BANK_SIZE;
|
||||
static constexpr size_t CACHE_SIZE_BYTES = BANKS * sizeof(T);
|
||||
|
||||
|
@@ -57,7 +57,7 @@ void GroveGasMultichannelV2Component::update() {
|
||||
void GroveGasMultichannelV2Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Grove Multichannel Gas Sensor V2");
|
||||
LOG_I2C_DEVICE(this)
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Nitrogen Dioxide", this->nitrogen_dioxide_sensor_);
|
||||
LOG_SENSOR(" ", "Ethanol", this->ethanol_sensor_);
|
||||
LOG_SENSOR(" ", "Carbon Monoxide", this->carbon_monoxide_sensor_);
|
||||
|
@@ -20,7 +20,7 @@ static const size_t MAX_BUTTONS = 4; // max number of buttons scanned
|
||||
|
||||
#define ERROR_CHECK(err) \
|
||||
if ((err) != i2c::ERROR_OK) { \
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); \
|
||||
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); \
|
||||
return; \
|
||||
}
|
||||
|
||||
|
@@ -42,7 +42,7 @@ void HLW8012Component::dump_config() {
|
||||
" Current resistor: %.1f mΩ\n"
|
||||
" Voltage Divider: %.1f",
|
||||
this->change_mode_every_, this->current_resistor_ * 1000.0f, this->voltage_divider_);
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current", this->current_sensor_);
|
||||
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||
|
@@ -15,7 +15,7 @@ static const char *const TAG = "honeywellabp2";
|
||||
void HONEYWELLABP2Sensor::read_sensor_data() {
|
||||
if (this->read(raw_data_, 7) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
this->status_set_warning("couldn't read sensor data");
|
||||
this->status_set_warning(LOG_STR("couldn't read sensor data"));
|
||||
return;
|
||||
}
|
||||
float press_counts = encode_uint24(raw_data_[1], raw_data_[2], raw_data_[3]); // calculate digital pressure counts
|
||||
@@ -31,7 +31,7 @@ void HONEYWELLABP2Sensor::read_sensor_data() {
|
||||
void HONEYWELLABP2Sensor::start_measurement() {
|
||||
if (this->write(i2c_cmd_, 3) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
this->status_set_warning("couldn't start measurement");
|
||||
this->status_set_warning(LOG_STR("couldn't start measurement"));
|
||||
return;
|
||||
}
|
||||
this->measurement_running_ = true;
|
||||
@@ -40,7 +40,7 @@ void HONEYWELLABP2Sensor::start_measurement() {
|
||||
bool HONEYWELLABP2Sensor::is_measurement_ready() {
|
||||
if (this->read(raw_data_, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
this->status_set_warning("couldn't check measurement");
|
||||
this->status_set_warning(LOG_STR("couldn't check measurement"));
|
||||
return false;
|
||||
}
|
||||
if ((raw_data_[0] & (0x1 << STATUS_BIT_BUSY)) > 0) {
|
||||
@@ -53,7 +53,7 @@ bool HONEYWELLABP2Sensor::is_measurement_ready() {
|
||||
void HONEYWELLABP2Sensor::measurement_timeout() {
|
||||
ESP_LOGE(TAG, "Timeout!");
|
||||
this->measurement_running_ = false;
|
||||
this->status_set_warning("measurement timed out");
|
||||
this->status_set_warning(LOG_STR("measurement timed out"));
|
||||
}
|
||||
|
||||
float HONEYWELLABP2Sensor::get_pressure() { return this->last_pressure_; }
|
||||
|
@@ -39,18 +39,22 @@ ErrorCode I2CDevice::read_register16(uint16_t a_register, uint8_t *data, size_t
|
||||
}
|
||||
|
||||
ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, size_t len) const {
|
||||
std::vector<uint8_t> v{};
|
||||
v.push_back(a_register);
|
||||
v.insert(v.end(), data, data + len);
|
||||
return bus_->write_readv(this->address_, v.data(), v.size(), nullptr, 0);
|
||||
SmallBufferWithHeapFallback<17> buffer_alloc; // Most I2C writes are <= 16 bytes
|
||||
uint8_t *buffer = buffer_alloc.get(len + 1);
|
||||
|
||||
buffer[0] = a_register;
|
||||
std::copy(data, data + len, buffer + 1);
|
||||
return this->bus_->write_readv(this->address_, buffer, len + 1, nullptr, 0);
|
||||
}
|
||||
|
||||
ErrorCode I2CDevice::write_register16(uint16_t a_register, const uint8_t *data, size_t len) const {
|
||||
std::vector<uint8_t> v(len + 2);
|
||||
v[0] = a_register >> 8;
|
||||
v[1] = a_register;
|
||||
std::copy(data, data + len, v.begin() + 2);
|
||||
return bus_->write_readv(this->address_, v.data(), v.size(), nullptr, 0);
|
||||
SmallBufferWithHeapFallback<18> buffer_alloc; // Most I2C writes are <= 16 bytes + 2 for register
|
||||
uint8_t *buffer = buffer_alloc.get(len + 2);
|
||||
|
||||
buffer[0] = a_register >> 8;
|
||||
buffer[1] = a_register;
|
||||
std::copy(data, data + len, buffer + 2);
|
||||
return this->bus_->write_readv(this->address_, buffer, len + 2, nullptr, 0);
|
||||
}
|
||||
|
||||
bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) {
|
||||
|
@@ -2,6 +2,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -10,6 +11,22 @@
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
|
||||
/// @brief Helper class for efficient buffer allocation - uses stack for small sizes, heap for large
|
||||
template<size_t STACK_SIZE> class SmallBufferWithHeapFallback {
|
||||
public:
|
||||
uint8_t *get(size_t size) {
|
||||
if (size <= STACK_SIZE) {
|
||||
return this->stack_buffer_;
|
||||
}
|
||||
this->heap_buffer_ = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
|
||||
return this->heap_buffer_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t stack_buffer_[STACK_SIZE];
|
||||
std::unique_ptr<uint8_t[]> heap_buffer_;
|
||||
};
|
||||
|
||||
/// @brief Error codes returned by I2CBus and I2CDevice methods
|
||||
enum ErrorCode {
|
||||
NO_ERROR = 0, ///< No error found during execution of method
|
||||
@@ -74,14 +91,17 @@ class I2CBus {
|
||||
for (size_t i = 0; i != count; i++) {
|
||||
total_len += read_buffers[i].len;
|
||||
}
|
||||
std::vector<uint8_t> buffer(total_len);
|
||||
auto err = this->write_readv(address, nullptr, 0, buffer.data(), total_len);
|
||||
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc; // Most I2C reads are small
|
||||
uint8_t *buffer = buffer_alloc.get(total_len);
|
||||
|
||||
auto err = this->write_readv(address, nullptr, 0, buffer, total_len);
|
||||
if (err != ERROR_OK)
|
||||
return err;
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i != count; i++) {
|
||||
if (read_buffers[i].len != 0) {
|
||||
std::memcpy(read_buffers[i].data, buffer.data() + pos, read_buffers[i].len);
|
||||
std::memcpy(read_buffers[i].data, buffer + pos, read_buffers[i].len);
|
||||
pos += read_buffers[i].len;
|
||||
}
|
||||
}
|
||||
@@ -91,11 +111,21 @@ class I2CBus {
|
||||
ESPDEPRECATED("This method is deprecated and will be removed in ESPHome 2026.3.0. Use write_readv() instead.",
|
||||
"2025.9.0")
|
||||
ErrorCode writev(uint8_t address, const WriteBuffer *write_buffers, size_t count, bool stop = true) {
|
||||
std::vector<uint8_t> buffer{};
|
||||
size_t total_len = 0;
|
||||
for (size_t i = 0; i != count; i++) {
|
||||
buffer.insert(buffer.end(), write_buffers[i].data, write_buffers[i].data + write_buffers[i].len);
|
||||
total_len += write_buffers[i].len;
|
||||
}
|
||||
return this->write_readv(address, buffer.data(), buffer.size(), nullptr, 0);
|
||||
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc; // Most I2C writes are small
|
||||
uint8_t *buffer = buffer_alloc.get(total_len);
|
||||
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i != count; i++) {
|
||||
std::memcpy(buffer + pos, write_buffers[i].data, write_buffers[i].len);
|
||||
pos += write_buffers[i].len;
|
||||
}
|
||||
|
||||
return this->write_readv(address, buffer, total_len, nullptr, 0);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@@ -4,6 +4,9 @@ from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
@@ -62,12 +65,15 @@ I2S_ROLE_OPTIONS = {
|
||||
CONF_SECONDARY: i2s_role_t.I2S_ROLE_SLAVE, # NOLINT
|
||||
}
|
||||
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h (SOC_I2S_NUM)
|
||||
I2S_PORTS = {
|
||||
VARIANT_ESP32: 2,
|
||||
VARIANT_ESP32S2: 1,
|
||||
VARIANT_ESP32S3: 2,
|
||||
VARIANT_ESP32C3: 1,
|
||||
VARIANT_ESP32C5: 1,
|
||||
VARIANT_ESP32C6: 1,
|
||||
VARIANT_ESP32H2: 1,
|
||||
VARIANT_ESP32P4: 3,
|
||||
}
|
||||
|
||||
|
@@ -33,7 +33,7 @@ void KMeterISOComponent::setup() {
|
||||
}
|
||||
|
||||
uint8_t read_buf[4] = {1};
|
||||
if (!this->read_register(KMETER_ERROR_STATUS_REG, read_buf, 1)) {
|
||||
if (!this->read_bytes(KMETER_ERROR_STATUS_REG, read_buf, 1)) {
|
||||
ESP_LOGCONFIG(TAG, "Could not read from the device.");
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
|
@@ -11,19 +11,21 @@ static const char *const TAG = "light";
|
||||
|
||||
// Helper functions to reduce code size for logging
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
|
||||
static void log_validation_warning(const char *name, const char *param_name, float val, float min, float max) {
|
||||
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, param_name, val, min, max);
|
||||
static void log_validation_warning(const char *name, const LogString *param_name, float val, float min, float max) {
|
||||
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_ARG(param_name), val, min, max);
|
||||
}
|
||||
|
||||
static void log_feature_not_supported(const char *name, const char *feature) {
|
||||
ESP_LOGW(TAG, "'%s': %s not supported", name, feature);
|
||||
static void log_feature_not_supported(const char *name, const LogString *feature) {
|
||||
ESP_LOGW(TAG, "'%s': %s not supported", name, LOG_STR_ARG(feature));
|
||||
}
|
||||
|
||||
static void log_color_mode_not_supported(const char *name, const char *feature) {
|
||||
ESP_LOGW(TAG, "'%s': color mode does not support setting %s", name, feature);
|
||||
static void log_color_mode_not_supported(const char *name, const LogString *feature) {
|
||||
ESP_LOGW(TAG, "'%s': color mode does not support setting %s", name, LOG_STR_ARG(feature));
|
||||
}
|
||||
|
||||
static void log_invalid_parameter(const char *name, const char *message) { ESP_LOGW(TAG, "'%s': %s", name, message); }
|
||||
static void log_invalid_parameter(const char *name, const LogString *message) {
|
||||
ESP_LOGW(TAG, "'%s': %s", name, LOG_STR_ARG(message));
|
||||
}
|
||||
#else
|
||||
#define log_validation_warning(name, param_name, val, min, max)
|
||||
#define log_feature_not_supported(name, feature)
|
||||
@@ -201,19 +203,19 @@ LightColorValues LightCall::validate_() {
|
||||
|
||||
// Brightness exists check
|
||||
if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) {
|
||||
log_feature_not_supported(name, "brightness");
|
||||
log_feature_not_supported(name, LOG_STR("brightness"));
|
||||
this->set_flag_(FLAG_HAS_BRIGHTNESS, false);
|
||||
}
|
||||
|
||||
// Transition length possible check
|
||||
if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS)) {
|
||||
log_feature_not_supported(name, "transitions");
|
||||
log_feature_not_supported(name, LOG_STR("transitions"));
|
||||
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
||||
}
|
||||
|
||||
// Color brightness exists check
|
||||
if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) {
|
||||
log_color_mode_not_supported(name, "RGB brightness");
|
||||
log_color_mode_not_supported(name, LOG_STR("RGB brightness"));
|
||||
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, false);
|
||||
}
|
||||
|
||||
@@ -221,7 +223,7 @@ LightColorValues LightCall::validate_() {
|
||||
if ((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) ||
|
||||
(this->has_blue() && this->blue_ > 0.0f)) {
|
||||
if (!(color_mode & ColorCapability::RGB)) {
|
||||
log_color_mode_not_supported(name, "RGB color");
|
||||
log_color_mode_not_supported(name, LOG_STR("RGB color"));
|
||||
this->set_flag_(FLAG_HAS_RED, false);
|
||||
this->set_flag_(FLAG_HAS_GREEN, false);
|
||||
this->set_flag_(FLAG_HAS_BLUE, false);
|
||||
@@ -231,21 +233,21 @@ LightColorValues LightCall::validate_() {
|
||||
// White value exists check
|
||||
if (this->has_white() && this->white_ > 0.0f &&
|
||||
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
||||
log_color_mode_not_supported(name, "white value");
|
||||
log_color_mode_not_supported(name, LOG_STR("white value"));
|
||||
this->set_flag_(FLAG_HAS_WHITE, false);
|
||||
}
|
||||
|
||||
// Color temperature exists check
|
||||
if (this->has_color_temperature() &&
|
||||
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
||||
log_color_mode_not_supported(name, "color temperature");
|
||||
log_color_mode_not_supported(name, LOG_STR("color temperature"));
|
||||
this->set_flag_(FLAG_HAS_COLOR_TEMPERATURE, false);
|
||||
}
|
||||
|
||||
// Cold/warm white value exists check
|
||||
if ((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) {
|
||||
if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
||||
log_color_mode_not_supported(name, "cold/warm white value");
|
||||
log_color_mode_not_supported(name, LOG_STR("cold/warm white value"));
|
||||
this->set_flag_(FLAG_HAS_COLD_WHITE, false);
|
||||
this->set_flag_(FLAG_HAS_WARM_WHITE, false);
|
||||
}
|
||||
@@ -255,7 +257,7 @@ LightColorValues LightCall::validate_() {
|
||||
if (this->has_##name_()) { \
|
||||
auto val = this->name_##_; \
|
||||
if (val < (min) || val > (max)) { \
|
||||
log_validation_warning(name, LOG_STR_LITERAL(upper_name), val, (min), (max)); \
|
||||
log_validation_warning(name, LOG_STR(upper_name), val, (min), (max)); \
|
||||
this->name_##_ = clamp(val, (min), (max)); \
|
||||
} \
|
||||
}
|
||||
@@ -319,7 +321,7 @@ LightColorValues LightCall::validate_() {
|
||||
|
||||
// Flash length check
|
||||
if (this->has_flash_() && this->flash_length_ == 0) {
|
||||
log_invalid_parameter(name, "flash length must be greater than zero");
|
||||
log_invalid_parameter(name, LOG_STR("flash length must be greater than zero"));
|
||||
this->set_flag_(FLAG_HAS_FLASH, false);
|
||||
}
|
||||
|
||||
@@ -338,13 +340,13 @@ LightColorValues LightCall::validate_() {
|
||||
}
|
||||
|
||||
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
|
||||
log_invalid_parameter(name, "effect cannot be used with transition/flash");
|
||||
log_invalid_parameter(name, LOG_STR("effect cannot be used with transition/flash"));
|
||||
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
||||
this->set_flag_(FLAG_HAS_FLASH, false);
|
||||
}
|
||||
|
||||
if (this->has_flash_() && this->has_transition_()) {
|
||||
log_invalid_parameter(name, "flash cannot be used with transition");
|
||||
log_invalid_parameter(name, LOG_STR("flash cannot be used with transition"));
|
||||
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
||||
}
|
||||
|
||||
@@ -361,7 +363,7 @@ LightColorValues LightCall::validate_() {
|
||||
}
|
||||
|
||||
if (this->has_transition_() && !supports_transition) {
|
||||
log_feature_not_supported(name, "transitions");
|
||||
log_feature_not_supported(name, LOG_STR("transitions"));
|
||||
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
||||
}
|
||||
|
||||
@@ -371,7 +373,7 @@ LightColorValues LightCall::validate_() {
|
||||
bool target_state = this->has_state() ? this->state_ : v.is_on();
|
||||
if (!this->has_flash_() && !target_state) {
|
||||
if (this->has_effect_()) {
|
||||
log_invalid_parameter(name, "cannot start effect when turning off");
|
||||
log_invalid_parameter(name, LOG_STR("cannot start effect when turning off"));
|
||||
this->set_flag_(FLAG_HAS_EFFECT, false);
|
||||
} else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) {
|
||||
// Auto turn off effect
|
||||
|
@@ -12,6 +12,7 @@
|
||||
#include "light_transformer.h"
|
||||
|
||||
#include <vector>
|
||||
#include <strings.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
@@ -246,19 +246,40 @@ void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const
|
||||
this->log_callback_.add(std::move(callback));
|
||||
}
|
||||
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
// ESP8266: PSTR() cannot be used in array initializers, so we need to declare
|
||||
// each string separately as a global constant first
|
||||
static const char LOG_LEVEL_NONE[] PROGMEM = "NONE";
|
||||
static const char LOG_LEVEL_ERROR[] PROGMEM = "ERROR";
|
||||
static const char LOG_LEVEL_WARN[] PROGMEM = "WARN";
|
||||
static const char LOG_LEVEL_INFO[] PROGMEM = "INFO";
|
||||
static const char LOG_LEVEL_CONFIG[] PROGMEM = "CONFIG";
|
||||
static const char LOG_LEVEL_DEBUG[] PROGMEM = "DEBUG";
|
||||
static const char LOG_LEVEL_VERBOSE[] PROGMEM = "VERBOSE";
|
||||
static const char LOG_LEVEL_VERY_VERBOSE[] PROGMEM = "VERY_VERBOSE";
|
||||
|
||||
static const LogString *const LOG_LEVELS[] = {
|
||||
reinterpret_cast<const LogString *>(LOG_LEVEL_NONE), reinterpret_cast<const LogString *>(LOG_LEVEL_ERROR),
|
||||
reinterpret_cast<const LogString *>(LOG_LEVEL_WARN), reinterpret_cast<const LogString *>(LOG_LEVEL_INFO),
|
||||
reinterpret_cast<const LogString *>(LOG_LEVEL_CONFIG), reinterpret_cast<const LogString *>(LOG_LEVEL_DEBUG),
|
||||
reinterpret_cast<const LogString *>(LOG_LEVEL_VERBOSE), reinterpret_cast<const LogString *>(LOG_LEVEL_VERY_VERBOSE),
|
||||
};
|
||||
#else
|
||||
static const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
|
||||
#endif
|
||||
|
||||
void Logger::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Logger:\n"
|
||||
" Max Level: %s\n"
|
||||
" Initial Level: %s",
|
||||
LOG_LEVELS[ESPHOME_LOG_LEVEL], LOG_LEVELS[this->current_level_]);
|
||||
LOG_STR_ARG(LOG_LEVELS[ESPHOME_LOG_LEVEL]), LOG_STR_ARG(LOG_LEVELS[this->current_level_]));
|
||||
#ifndef USE_HOST
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Log Baud Rate: %" PRIu32 "\n"
|
||||
" Hardware UART: %s",
|
||||
this->baud_rate_, get_uart_selection_());
|
||||
this->baud_rate_, LOG_STR_ARG(get_uart_selection_()));
|
||||
#endif
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
if (this->log_buffer_) {
|
||||
@@ -267,14 +288,14 @@ void Logger::dump_config() {
|
||||
#endif
|
||||
|
||||
for (auto &it : this->log_levels_) {
|
||||
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.first.c_str(), LOG_LEVELS[it.second]);
|
||||
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.first.c_str(), LOG_STR_ARG(LOG_LEVELS[it.second]));
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::set_log_level(uint8_t level) {
|
||||
if (level > ESPHOME_LOG_LEVEL) {
|
||||
level = ESPHOME_LOG_LEVEL;
|
||||
ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
|
||||
ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_STR_ARG(LOG_LEVELS[ESPHOME_LOG_LEVEL]));
|
||||
}
|
||||
this->current_level_ = level;
|
||||
this->level_callback_.call(level);
|
||||
|
@@ -226,7 +226,7 @@ class Logger : public Component {
|
||||
}
|
||||
|
||||
#ifndef USE_HOST
|
||||
const char *get_uart_selection_();
|
||||
const LogString *get_uart_selection_();
|
||||
#endif
|
||||
|
||||
// Group 4-byte aligned members first
|
||||
|
@@ -190,20 +190,28 @@ void HOT Logger::write_msg_(const char *msg) {
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
#endif
|
||||
|
||||
const char *const UART_SELECTIONS[] = {
|
||||
"UART0", "UART1",
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
"UART2",
|
||||
case UART_SELECTION_UART2:
|
||||
return LOG_STR("UART2");
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
"USB_CDC",
|
||||
case UART_SELECTION_USB_CDC:
|
||||
return LOG_STR("USB_CDC");
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
"USB_SERIAL_JTAG",
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
return LOG_STR("USB_SERIAL_JTAG");
|
||||
#endif
|
||||
};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
#endif
|
||||
|
@@ -35,9 +35,17 @@ void Logger::pre_setup() {
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
case UART_SELECTION_UART0_SWAP:
|
||||
default:
|
||||
return LOG_STR("UART0_SWAP");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
#endif
|
||||
|
@@ -51,9 +51,19 @@ void Logger::pre_setup() {
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const char *const UART_SELECTIONS[] = {"DEFAULT", "UART0", "UART1", "UART2"};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_DEFAULT:
|
||||
return LOG_STR("DEFAULT");
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
case UART_SELECTION_UART2:
|
||||
default:
|
||||
return LOG_STR("UART2");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
|
@@ -29,9 +29,20 @@ void Logger::pre_setup() {
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
return LOG_STR("USB_CDC");
|
||||
#endif
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
#endif // USE_RP2040
|
||||
|
@@ -54,7 +54,7 @@ void Logger::pre_setup() {
|
||||
#endif
|
||||
}
|
||||
if (!device_is_ready(uart_dev)) {
|
||||
ESP_LOGE(TAG, "%s is not ready.", get_uart_selection_());
|
||||
ESP_LOGE(TAG, "%s is not ready.", LOG_STR_ARG(get_uart_selection_()));
|
||||
} else {
|
||||
this->uart_dev_ = uart_dev;
|
||||
}
|
||||
@@ -77,9 +77,20 @@ void HOT Logger::write_msg_(const char *msg) {
|
||||
uart_poll_out(this->uart_dev_, '\n');
|
||||
}
|
||||
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
return LOG_STR("UART0");
|
||||
case UART_SELECTION_UART1:
|
||||
return LOG_STR("UART1");
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
return LOG_STR("USB_CDC");
|
||||
#endif
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
|
@@ -6,7 +6,7 @@ namespace m5stack_8angle {
|
||||
void M5Stack8AngleSwitchBinarySensor::update() {
|
||||
int8_t out = this->parent_->read_switch();
|
||||
if (out == -1) {
|
||||
this->status_set_warning("Could not read binary sensor state from M5Stack 8Angle.");
|
||||
this->status_set_warning(LOG_STR("Could not read binary sensor state from M5Stack 8Angle."));
|
||||
return;
|
||||
}
|
||||
this->publish_state(out != 0);
|
||||
|
@@ -7,7 +7,7 @@ void M5Stack8AngleKnobSensor::update() {
|
||||
if (this->parent_ != nullptr) {
|
||||
int32_t raw_pos = this->parent_->read_knob_pos_raw(this->channel_, this->bits_);
|
||||
if (raw_pos == -1) {
|
||||
this->status_set_warning("Could not read knob position from M5Stack 8Angle.");
|
||||
this->status_set_warning(LOG_STR("Could not read knob position from M5Stack 8Angle."));
|
||||
return;
|
||||
}
|
||||
if (this->raw_) {
|
||||
|
@@ -22,7 +22,7 @@ void MAX17043Component::update() {
|
||||
|
||||
if (this->voltage_sensor_ != nullptr) {
|
||||
if (!this->read_byte_16(MAX17043_VCELL, &raw_voltage)) {
|
||||
this->status_set_warning("Unable to read MAX17043_VCELL");
|
||||
this->status_set_warning(LOG_STR("Unable to read MAX17043_VCELL"));
|
||||
} else {
|
||||
float voltage = (1.25 * (float) (raw_voltage >> 4)) / 1000.0;
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
@@ -31,7 +31,7 @@ void MAX17043Component::update() {
|
||||
}
|
||||
if (this->battery_remaining_sensor_ != nullptr) {
|
||||
if (!this->read_byte_16(MAX17043_SOC, &raw_percent)) {
|
||||
this->status_set_warning("Unable to read MAX17043_SOC");
|
||||
this->status_set_warning(LOG_STR("Unable to read MAX17043_SOC"));
|
||||
} else {
|
||||
float percent = (float) ((raw_percent >> 8) + 0.003906f * (raw_percent & 0x00ff));
|
||||
this->battery_remaining_sensor_->publish_state(percent);
|
||||
|
@@ -11,6 +11,7 @@ from esphome.const import (
|
||||
CONF_OUTPUT,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["gpio_expander"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
|
@@ -22,14 +22,29 @@ void MCP23016::setup() {
|
||||
this->write_reg_(MCP23016_IODIR0, 0xFF);
|
||||
this->write_reg_(MCP23016_IODIR1, 0xFF);
|
||||
}
|
||||
bool MCP23016::digital_read(uint8_t pin) {
|
||||
uint8_t bit = pin % 8;
|
||||
|
||||
void MCP23016::loop() {
|
||||
// Invalidate cache at the start of each loop
|
||||
this->reset_pin_cache_();
|
||||
}
|
||||
bool MCP23016::digital_read_hw(uint8_t pin) {
|
||||
uint8_t reg_addr = pin < 8 ? MCP23016_GP0 : MCP23016_GP1;
|
||||
uint8_t value = 0;
|
||||
this->read_reg_(reg_addr, &value);
|
||||
return value & (1 << bit);
|
||||
if (!this->read_reg_(reg_addr, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the appropriate part of input_mask_
|
||||
if (pin < 8) {
|
||||
this->input_mask_ = (this->input_mask_ & 0xFF00) | value;
|
||||
} else {
|
||||
this->input_mask_ = (this->input_mask_ & 0x00FF) | (uint16_t(value) << 8);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void MCP23016::digital_write(uint8_t pin, bool value) {
|
||||
|
||||
bool MCP23016::digital_read_cache(uint8_t pin) { return this->input_mask_ & (1 << pin); }
|
||||
void MCP23016::digital_write_hw(uint8_t pin, bool value) {
|
||||
uint8_t reg_addr = pin < 8 ? MCP23016_OLAT0 : MCP23016_OLAT1;
|
||||
this->update_reg_(pin, value, reg_addr);
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/gpio_expander/cached_gpio.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp23016 {
|
||||
@@ -24,19 +25,22 @@ enum MCP23016GPIORegisters {
|
||||
MCP23016_IOCON1 = 0x0B,
|
||||
};
|
||||
|
||||
class MCP23016 : public Component, public i2c::I2CDevice {
|
||||
class MCP23016 : public Component, public i2c::I2CDevice, public gpio_expander::CachedGpioExpander<uint8_t, 16> {
|
||||
public:
|
||||
MCP23016() = default;
|
||||
|
||||
void setup() override;
|
||||
|
||||
bool digital_read(uint8_t pin);
|
||||
void digital_write(uint8_t pin, bool value);
|
||||
void loop() override;
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
// Virtual methods from CachedGpioExpander
|
||||
bool digital_read_hw(uint8_t pin) override;
|
||||
bool digital_read_cache(uint8_t pin) override;
|
||||
void digital_write_hw(uint8_t pin, bool value) override;
|
||||
|
||||
// read a given register
|
||||
bool read_reg_(uint8_t reg, uint8_t *value);
|
||||
// write a value to a given register
|
||||
@@ -46,6 +50,8 @@ class MCP23016 : public Component, public i2c::I2CDevice {
|
||||
|
||||
uint8_t olat_0_{0x00};
|
||||
uint8_t olat_1_{0x00};
|
||||
// Cache for input values (16-bit combined for both banks)
|
||||
uint16_t input_mask_{0x00};
|
||||
};
|
||||
|
||||
class MCP23016GPIOPin : public GPIOPin {
|
||||
|
@@ -8,7 +8,7 @@ static const char *const TAG = "mcp23x08_base";
|
||||
|
||||
bool MCP23X08Base::digital_read_hw(uint8_t pin) {
|
||||
if (!this->read_reg(mcp23x08_base::MCP23X08_GPIO, &this->input_mask_)) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@@ -11,13 +11,13 @@ bool MCP23X17Base::digital_read_hw(uint8_t pin) {
|
||||
uint8_t data;
|
||||
if (pin < 8) {
|
||||
if (!this->read_reg(mcp23x17_base::MCP23X17_GPIOA, &data)) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return false;
|
||||
}
|
||||
this->input_mask_ = encode_uint16(this->input_mask_ >> 8, data);
|
||||
} else {
|
||||
if (!this->read_reg(mcp23x17_base::MCP23X17_GPIOB, &data)) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return false;
|
||||
}
|
||||
this->input_mask_ = encode_uint16(data, this->input_mask_ & 0xFF);
|
||||
|
@@ -5,6 +5,30 @@
|
||||
#include "esphome/core/version.h"
|
||||
#include "mdns_component.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
// Macro to define strings in PROGMEM on ESP8266, regular memory on other platforms
|
||||
#define MDNS_STATIC_CONST_CHAR(name, value) static const char name[] PROGMEM = value
|
||||
// Helper to get string from PROGMEM - returns a temporary std::string
|
||||
// Only define this function if we have services that will use it
|
||||
#if defined(USE_API) || defined(USE_PROMETHEUS) || defined(USE_WEBSERVER) || defined(USE_MDNS_EXTRA_SERVICES)
|
||||
static std::string mdns_string_p(const char *src) {
|
||||
char buf[64];
|
||||
strncpy_P(buf, src, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
return std::string(buf);
|
||||
}
|
||||
#define MDNS_STR(name) mdns_string_p(name)
|
||||
#else
|
||||
// If no services are configured, we still need the fallback service but it uses string literals
|
||||
#define MDNS_STR(name) std::string(name)
|
||||
#endif
|
||||
#else
|
||||
// On non-ESP8266 platforms, use regular const char*
|
||||
#define MDNS_STATIC_CONST_CHAR(name, value) static constexpr const char *name = value
|
||||
#define MDNS_STR(name) name
|
||||
#endif
|
||||
|
||||
#ifdef USE_API
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#endif
|
||||
@@ -21,6 +45,32 @@ static const char *const TAG = "mdns";
|
||||
#define USE_WEBSERVER_PORT 80 // NOLINT
|
||||
#endif
|
||||
|
||||
// Define all constant strings using the macro
|
||||
MDNS_STATIC_CONST_CHAR(SERVICE_ESPHOMELIB, "_esphomelib");
|
||||
MDNS_STATIC_CONST_CHAR(SERVICE_TCP, "_tcp");
|
||||
MDNS_STATIC_CONST_CHAR(SERVICE_PROMETHEUS, "_prometheus-http");
|
||||
MDNS_STATIC_CONST_CHAR(SERVICE_HTTP, "_http");
|
||||
|
||||
MDNS_STATIC_CONST_CHAR(TXT_FRIENDLY_NAME, "friendly_name");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_VERSION, "version");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_MAC, "mac");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_PLATFORM, "platform");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_BOARD, "board");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_NETWORK, "network");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION, "api_encryption");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION_SUPPORTED, "api_encryption_supported");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_PROJECT_NAME, "project_name");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_PROJECT_VERSION, "project_version");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_PACKAGE_IMPORT_URL, "package_import_url");
|
||||
|
||||
MDNS_STATIC_CONST_CHAR(PLATFORM_ESP8266, "ESP8266");
|
||||
MDNS_STATIC_CONST_CHAR(PLATFORM_ESP32, "ESP32");
|
||||
MDNS_STATIC_CONST_CHAR(PLATFORM_RP2040, "RP2040");
|
||||
|
||||
MDNS_STATIC_CONST_CHAR(NETWORK_WIFI, "wifi");
|
||||
MDNS_STATIC_CONST_CHAR(NETWORK_ETHERNET, "ethernet");
|
||||
MDNS_STATIC_CONST_CHAR(NETWORK_THREAD, "thread");
|
||||
|
||||
void MDNSComponent::compile_records_() {
|
||||
this->hostname_ = App.get_name();
|
||||
|
||||
@@ -50,8 +100,8 @@ void MDNSComponent::compile_records_() {
|
||||
if (api::global_api_server != nullptr) {
|
||||
this->services_.emplace_back();
|
||||
auto &service = this->services_.back();
|
||||
service.service_type = "_esphomelib";
|
||||
service.proto = "_tcp";
|
||||
service.service_type = MDNS_STR(SERVICE_ESPHOMELIB);
|
||||
service.proto = MDNS_STR(SERVICE_TCP);
|
||||
service.port = api::global_api_server->get_port();
|
||||
|
||||
const std::string &friendly_name = App.get_friendly_name();
|
||||
@@ -82,47 +132,47 @@ void MDNSComponent::compile_records_() {
|
||||
txt_records.reserve(txt_count);
|
||||
|
||||
if (!friendly_name_empty) {
|
||||
txt_records.emplace_back(MDNSTXTRecord{"friendly_name", friendly_name});
|
||||
txt_records.push_back({MDNS_STR(TXT_FRIENDLY_NAME), friendly_name});
|
||||
}
|
||||
txt_records.emplace_back(MDNSTXTRecord{"version", ESPHOME_VERSION});
|
||||
txt_records.emplace_back(MDNSTXTRecord{"mac", get_mac_address()});
|
||||
txt_records.push_back({MDNS_STR(TXT_VERSION), ESPHOME_VERSION});
|
||||
txt_records.push_back({MDNS_STR(TXT_MAC), get_mac_address()});
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
txt_records.emplace_back(MDNSTXTRecord{"platform", "ESP8266"});
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_ESP8266)});
|
||||
#elif defined(USE_ESP32)
|
||||
txt_records.emplace_back(MDNSTXTRecord{"platform", "ESP32"});
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_ESP32)});
|
||||
#elif defined(USE_RP2040)
|
||||
txt_records.emplace_back(MDNSTXTRecord{"platform", "RP2040"});
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_RP2040)});
|
||||
#elif defined(USE_LIBRETINY)
|
||||
txt_records.emplace_back(MDNSTXTRecord{"platform", lt_cpu_get_model_name()});
|
||||
#endif
|
||||
|
||||
txt_records.emplace_back(MDNSTXTRecord{"board", ESPHOME_BOARD});
|
||||
txt_records.push_back({MDNS_STR(TXT_BOARD), ESPHOME_BOARD});
|
||||
|
||||
#if defined(USE_WIFI)
|
||||
txt_records.emplace_back(MDNSTXTRecord{"network", "wifi"});
|
||||
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_WIFI)});
|
||||
#elif defined(USE_ETHERNET)
|
||||
txt_records.emplace_back(MDNSTXTRecord{"network", "ethernet"});
|
||||
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_ETHERNET)});
|
||||
#elif defined(USE_OPENTHREAD)
|
||||
txt_records.emplace_back(MDNSTXTRecord{"network", "thread"});
|
||||
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_THREAD)});
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
static constexpr const char *NOISE_ENCRYPTION = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
|
||||
MDNS_STATIC_CONST_CHAR(NOISE_ENCRYPTION, "Noise_NNpsk0_25519_ChaChaPoly_SHA256");
|
||||
if (api::global_api_server->get_noise_ctx()->has_psk()) {
|
||||
txt_records.emplace_back(MDNSTXTRecord{"api_encryption", NOISE_ENCRYPTION});
|
||||
txt_records.push_back({MDNS_STR(TXT_API_ENCRYPTION), MDNS_STR(NOISE_ENCRYPTION)});
|
||||
} else {
|
||||
txt_records.emplace_back(MDNSTXTRecord{"api_encryption_supported", NOISE_ENCRYPTION});
|
||||
txt_records.push_back({MDNS_STR(TXT_API_ENCRYPTION_SUPPORTED), MDNS_STR(NOISE_ENCRYPTION)});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
txt_records.emplace_back(MDNSTXTRecord{"project_name", ESPHOME_PROJECT_NAME});
|
||||
txt_records.emplace_back(MDNSTXTRecord{"project_version", ESPHOME_PROJECT_VERSION});
|
||||
txt_records.push_back({MDNS_STR(TXT_PROJECT_NAME), ESPHOME_PROJECT_NAME});
|
||||
txt_records.push_back({MDNS_STR(TXT_PROJECT_VERSION), ESPHOME_PROJECT_VERSION});
|
||||
#endif // ESPHOME_PROJECT_NAME
|
||||
|
||||
#ifdef USE_DASHBOARD_IMPORT
|
||||
txt_records.emplace_back(MDNSTXTRecord{"package_import_url", dashboard_import::get_package_import_url()});
|
||||
txt_records.push_back({MDNS_STR(TXT_PACKAGE_IMPORT_URL), dashboard_import::get_package_import_url()});
|
||||
#endif
|
||||
}
|
||||
#endif // USE_API
|
||||
@@ -130,16 +180,16 @@ void MDNSComponent::compile_records_() {
|
||||
#ifdef USE_PROMETHEUS
|
||||
this->services_.emplace_back();
|
||||
auto &prom_service = this->services_.back();
|
||||
prom_service.service_type = "_prometheus-http";
|
||||
prom_service.proto = "_tcp";
|
||||
prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS);
|
||||
prom_service.proto = MDNS_STR(SERVICE_TCP);
|
||||
prom_service.port = USE_WEBSERVER_PORT;
|
||||
#endif
|
||||
|
||||
#ifdef USE_WEBSERVER
|
||||
this->services_.emplace_back();
|
||||
auto &web_service = this->services_.back();
|
||||
web_service.service_type = "_http";
|
||||
web_service.proto = "_tcp";
|
||||
web_service.service_type = MDNS_STR(SERVICE_HTTP);
|
||||
web_service.proto = MDNS_STR(SERVICE_TCP);
|
||||
web_service.port = USE_WEBSERVER_PORT;
|
||||
#endif
|
||||
|
||||
|
@@ -222,6 +222,12 @@ def delay(ms):
|
||||
|
||||
|
||||
class DriverChip:
|
||||
"""
|
||||
A class representing a MIPI DBI driver chip model.
|
||||
The parameters supplied as defaults will be used to provide default values for the display configuration.
|
||||
Setting swap_xy to cv.UNDEFINED will indicate that the model does not support swapping X and Y axes.
|
||||
"""
|
||||
|
||||
models: dict[str, Self] = {}
|
||||
|
||||
def __init__(
|
||||
@@ -232,7 +238,7 @@ class DriverChip:
|
||||
):
|
||||
name = name.upper()
|
||||
self.name = name
|
||||
self.initsequence = initsequence or defaults.get("init_sequence")
|
||||
self.initsequence = initsequence
|
||||
self.defaults = defaults
|
||||
DriverChip.models[name] = self
|
||||
|
||||
@@ -246,6 +252,17 @@ class DriverChip:
|
||||
return models
|
||||
|
||||
def extend(self, name, **kwargs) -> "DriverChip":
|
||||
"""
|
||||
Extend the current model with additional parameters or a modified init sequence.
|
||||
Parameters supplied here will override the defaults of the current model.
|
||||
if the initsequence is not provided, the current model's initsequence will be used.
|
||||
If add_init_sequence is provided, it will be appended to the current initsequence.
|
||||
:param name:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
initsequence = list(kwargs.pop("initsequence", self.initsequence))
|
||||
initsequence.extend(kwargs.pop("add_init_sequence", ()))
|
||||
defaults = self.defaults.copy()
|
||||
if (
|
||||
CONF_WIDTH in defaults
|
||||
@@ -260,23 +277,39 @@ class DriverChip:
|
||||
):
|
||||
defaults[CONF_NATIVE_HEIGHT] = defaults[CONF_HEIGHT]
|
||||
defaults.update(kwargs)
|
||||
return DriverChip(name, initsequence=self.initsequence, **defaults)
|
||||
return self.__class__(name, initsequence=tuple(initsequence), **defaults)
|
||||
|
||||
def get_default(self, key, fallback: Any = False) -> Any:
|
||||
return self.defaults.get(key, fallback)
|
||||
|
||||
@property
|
||||
def transforms(self) -> set[str]:
|
||||
"""
|
||||
Return the available transforms for this model.
|
||||
"""
|
||||
if self.get_default("no_transform", False):
|
||||
return set()
|
||||
if self.get_default(CONF_SWAP_XY) != cv.UNDEFINED:
|
||||
return {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY}
|
||||
return {CONF_MIRROR_X, CONF_MIRROR_Y}
|
||||
|
||||
def option(self, name, fallback=False) -> cv.Optional:
|
||||
return cv.Optional(name, default=self.get_default(name, fallback))
|
||||
|
||||
def rotation_as_transform(self, config) -> bool:
|
||||
"""
|
||||
Check if a rotation can be implemented in hardware using the MADCTL register.
|
||||
A rotation of 180 is always possible, 90 and 270 are possible if the model supports swapping X and Y.
|
||||
A rotation of 180 is always possible if x and y mirroring are supported, 90 and 270 are possible if the model supports swapping X and Y.
|
||||
"""
|
||||
transforms = self.transforms
|
||||
rotation = config.get(CONF_ROTATION, 0)
|
||||
return rotation and (
|
||||
self.get_default(CONF_SWAP_XY) != cv.UNDEFINED or rotation == 180
|
||||
)
|
||||
if rotation == 0 or not transforms:
|
||||
return False
|
||||
if rotation == 180:
|
||||
return CONF_MIRROR_X in transforms and CONF_MIRROR_Y in transforms
|
||||
if rotation == 90:
|
||||
return CONF_SWAP_XY in transforms and CONF_MIRROR_X in transforms
|
||||
return CONF_SWAP_XY in transforms and CONF_MIRROR_Y in transforms
|
||||
|
||||
def get_dimensions(self, config) -> tuple[int, int, int, int]:
|
||||
if CONF_DIMENSIONS in config:
|
||||
@@ -301,10 +334,10 @@ class DriverChip:
|
||||
|
||||
# if mirroring axes and there are offsets, also mirror the offsets to cater for situations where
|
||||
# the offset is asymmetric
|
||||
if transform[CONF_MIRROR_X]:
|
||||
if transform.get(CONF_MIRROR_X):
|
||||
native_width = self.get_default(CONF_NATIVE_WIDTH, width + offset_width * 2)
|
||||
offset_width = native_width - width - offset_width
|
||||
if transform[CONF_MIRROR_Y]:
|
||||
if transform.get(CONF_MIRROR_Y):
|
||||
native_height = self.get_default(
|
||||
CONF_NATIVE_HEIGHT, height + offset_height * 2
|
||||
)
|
||||
@@ -314,7 +347,7 @@ class DriverChip:
|
||||
90,
|
||||
270,
|
||||
)
|
||||
if transform[CONF_SWAP_XY] is True or rotated:
|
||||
if transform.get(CONF_SWAP_XY) is True or rotated:
|
||||
width, height = height, width
|
||||
offset_height, offset_width = offset_width, offset_height
|
||||
return width, height, offset_width, offset_height
|
||||
@@ -324,27 +357,50 @@ class DriverChip:
|
||||
transform = config.get(
|
||||
CONF_TRANSFORM,
|
||||
{
|
||||
CONF_MIRROR_X: self.get_default(CONF_MIRROR_X, False),
|
||||
CONF_MIRROR_Y: self.get_default(CONF_MIRROR_Y, False),
|
||||
CONF_SWAP_XY: self.get_default(CONF_SWAP_XY, False),
|
||||
CONF_MIRROR_X: self.get_default(CONF_MIRROR_X),
|
||||
CONF_MIRROR_Y: self.get_default(CONF_MIRROR_Y),
|
||||
CONF_SWAP_XY: self.get_default(CONF_SWAP_XY),
|
||||
},
|
||||
)
|
||||
# fill in defaults if not provided
|
||||
mirror_x = transform.get(CONF_MIRROR_X, self.get_default(CONF_MIRROR_X))
|
||||
mirror_y = transform.get(CONF_MIRROR_Y, self.get_default(CONF_MIRROR_Y))
|
||||
swap_xy = transform.get(CONF_SWAP_XY, self.get_default(CONF_SWAP_XY))
|
||||
transform[CONF_MIRROR_X] = mirror_x
|
||||
transform[CONF_MIRROR_Y] = mirror_y
|
||||
transform[CONF_SWAP_XY] = swap_xy
|
||||
|
||||
# Can we use the MADCTL register to set the rotation?
|
||||
if can_transform and CONF_TRANSFORM not in config:
|
||||
rotation = config[CONF_ROTATION]
|
||||
if rotation == 180:
|
||||
transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X]
|
||||
transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y]
|
||||
transform[CONF_MIRROR_X] = not mirror_x
|
||||
transform[CONF_MIRROR_Y] = not mirror_y
|
||||
elif rotation == 90:
|
||||
transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY]
|
||||
transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X]
|
||||
transform[CONF_SWAP_XY] = not swap_xy
|
||||
transform[CONF_MIRROR_X] = not mirror_x
|
||||
else:
|
||||
transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY]
|
||||
transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y]
|
||||
transform[CONF_SWAP_XY] = not swap_xy
|
||||
transform[CONF_MIRROR_Y] = not mirror_y
|
||||
transform[CONF_TRANSFORM] = True
|
||||
return transform
|
||||
|
||||
def add_madctl(self, sequence: list, config: dict):
|
||||
# Add the MADCTL command to the sequence based on the configuration.
|
||||
use_flip = config.get(CONF_USE_AXIS_FLIPS)
|
||||
madctl = 0
|
||||
transform = self.get_transform(config)
|
||||
if transform[CONF_MIRROR_X]:
|
||||
madctl |= MADCTL_XFLIP if use_flip else MADCTL_MX
|
||||
if transform[CONF_MIRROR_Y]:
|
||||
madctl |= MADCTL_YFLIP if use_flip else MADCTL_MY
|
||||
if transform.get(CONF_SWAP_XY) is True: # Exclude Undefined
|
||||
madctl |= MADCTL_MV
|
||||
if config[CONF_COLOR_ORDER] == MODE_BGR:
|
||||
madctl |= MADCTL_BGR
|
||||
sequence.append((MADCTL, madctl))
|
||||
return madctl
|
||||
|
||||
def get_sequence(self, config) -> tuple[tuple[int, ...], int]:
|
||||
"""
|
||||
Create the init sequence for the display.
|
||||
@@ -367,21 +423,9 @@ class DriverChip:
|
||||
pixel_mode = PIXEL_MODES[pixel_mode]
|
||||
sequence.append((PIXFMT, pixel_mode))
|
||||
|
||||
# Does the chip use the flipping bits for mirroring rather than the reverse order bits?
|
||||
use_flip = config.get(CONF_USE_AXIS_FLIPS)
|
||||
madctl = 0
|
||||
transform = self.get_transform(config)
|
||||
if self.rotation_as_transform(config):
|
||||
LOGGER.info("Using hardware transform to implement rotation")
|
||||
if transform.get(CONF_MIRROR_X):
|
||||
madctl |= MADCTL_XFLIP if use_flip else MADCTL_MX
|
||||
if transform.get(CONF_MIRROR_Y):
|
||||
madctl |= MADCTL_YFLIP if use_flip else MADCTL_MY
|
||||
if transform.get(CONF_SWAP_XY) is True: # Exclude Undefined
|
||||
madctl |= MADCTL_MV
|
||||
if config[CONF_COLOR_ORDER] == MODE_BGR:
|
||||
madctl |= MADCTL_BGR
|
||||
sequence.append((MADCTL, madctl))
|
||||
madctl = self.add_madctl(sequence, config)
|
||||
if config[CONF_INVERT_COLORS]:
|
||||
sequence.append((INVON,))
|
||||
else:
|
||||
|
2
esphome/components/mipi_rgb/__init__.py
Normal file
2
esphome/components/mipi_rgb/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
DOMAIN = "mipi_rgb"
|
321
esphome/components/mipi_rgb/display.py
Normal file
321
esphome/components/mipi_rgb/display.py
Normal file
@@ -0,0 +1,321 @@
|
||||
import importlib
|
||||
import pkgutil
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import display, spi
|
||||
from esphome.components.const import (
|
||||
BYTE_ORDER_BIG,
|
||||
BYTE_ORDER_LITTLE,
|
||||
CONF_BYTE_ORDER,
|
||||
CONF_DRAW_ROUNDING,
|
||||
)
|
||||
from esphome.components.display import CONF_SHOW_TEST_CARD
|
||||
from esphome.components.esp32 import const, only_on_variant
|
||||
from esphome.components.mipi import (
|
||||
COLOR_ORDERS,
|
||||
CONF_DE_PIN,
|
||||
CONF_HSYNC_BACK_PORCH,
|
||||
CONF_HSYNC_FRONT_PORCH,
|
||||
CONF_HSYNC_PULSE_WIDTH,
|
||||
CONF_PCLK_PIN,
|
||||
CONF_PIXEL_MODE,
|
||||
CONF_USE_AXIS_FLIPS,
|
||||
CONF_VSYNC_BACK_PORCH,
|
||||
CONF_VSYNC_FRONT_PORCH,
|
||||
CONF_VSYNC_PULSE_WIDTH,
|
||||
MODE_BGR,
|
||||
PIXEL_MODE_16BIT,
|
||||
PIXEL_MODE_18BIT,
|
||||
DriverChip,
|
||||
dimension_schema,
|
||||
map_sequence,
|
||||
power_of_two,
|
||||
requires_buffer,
|
||||
)
|
||||
from esphome.components.rpi_dpi_rgb.display import (
|
||||
CONF_PCLK_FREQUENCY,
|
||||
CONF_PCLK_INVERTED,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BLUE,
|
||||
CONF_COLOR_ORDER,
|
||||
CONF_CS_PIN,
|
||||
CONF_DATA_PINS,
|
||||
CONF_DATA_RATE,
|
||||
CONF_DC_PIN,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_ENABLE_PIN,
|
||||
CONF_GREEN,
|
||||
CONF_HSYNC_PIN,
|
||||
CONF_ID,
|
||||
CONF_IGNORE_STRAPPING_WARNING,
|
||||
CONF_INIT_SEQUENCE,
|
||||
CONF_INVERT_COLORS,
|
||||
CONF_LAMBDA,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_MODEL,
|
||||
CONF_NUMBER,
|
||||
CONF_RED,
|
||||
CONF_RESET_PIN,
|
||||
CONF_ROTATION,
|
||||
CONF_SPI_ID,
|
||||
CONF_SWAP_XY,
|
||||
CONF_TRANSFORM,
|
||||
CONF_VSYNC_PIN,
|
||||
CONF_WIDTH,
|
||||
)
|
||||
from esphome.final_validate import full_config
|
||||
|
||||
from ..spi import CONF_SPI_MODE, SPI_DATA_RATE_SCHEMA, SPI_MODE_OPTIONS, SPIComponent
|
||||
from . import models
|
||||
|
||||
DEPENDENCIES = ["esp32", "psram"]
|
||||
|
||||
mipi_rgb_ns = cg.esphome_ns.namespace("mipi_rgb")
|
||||
mipi_rgb = mipi_rgb_ns.class_("MipiRgb", display.Display, cg.Component)
|
||||
mipi_rgb_spi = mipi_rgb_ns.class_(
|
||||
"MipiRgbSpi", mipi_rgb, display.Display, cg.Component, spi.SPIDevice
|
||||
)
|
||||
ColorOrder = display.display_ns.enum("ColorMode")
|
||||
|
||||
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
|
||||
|
||||
DriverChip("CUSTOM")
|
||||
|
||||
# Import all models dynamically from the models package
|
||||
|
||||
for module_info in pkgutil.iter_modules(models.__path__):
|
||||
importlib.import_module(f".models.{module_info.name}", package=__package__)
|
||||
|
||||
MODELS = DriverChip.get_models()
|
||||
|
||||
|
||||
def data_pin_validate(value):
|
||||
"""
|
||||
It is safe to use strapping pins as RGB output data bits, as they are outputs only,
|
||||
and not initialised until after boot.
|
||||
"""
|
||||
if not isinstance(value, dict):
|
||||
try:
|
||||
return DATA_PIN_SCHEMA(
|
||||
{CONF_NUMBER: value, CONF_IGNORE_STRAPPING_WARNING: True}
|
||||
)
|
||||
except cv.Invalid:
|
||||
pass
|
||||
return DATA_PIN_SCHEMA(value)
|
||||
|
||||
|
||||
def data_pin_set(length):
|
||||
return cv.All(
|
||||
[data_pin_validate],
|
||||
cv.Length(min=length, max=length, msg=f"Exactly {length} data pins required"),
|
||||
)
|
||||
|
||||
|
||||
def model_schema(config):
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
if transforms := model.transforms:
|
||||
transform = cv.Schema({cv.Required(x): cv.boolean for x in transforms})
|
||||
for x in (CONF_SWAP_XY, CONF_MIRROR_X, CONF_MIRROR_Y):
|
||||
if x not in transforms:
|
||||
transform = transform.extend(
|
||||
{cv.Optional(x): cv.invalid(f"{x} not supported by this model")}
|
||||
)
|
||||
else:
|
||||
transform = cv.invalid("This model does not support transforms")
|
||||
|
||||
# RPI model does not use an init sequence, indicates with empty list
|
||||
if model.initsequence is None:
|
||||
# Custom model requires an init sequence
|
||||
iseqconf = cv.Required(CONF_INIT_SEQUENCE)
|
||||
uses_spi = True
|
||||
else:
|
||||
iseqconf = cv.Optional(CONF_INIT_SEQUENCE)
|
||||
uses_spi = CONF_INIT_SEQUENCE in config or len(model.initsequence) != 0
|
||||
swap_xy = config.get(CONF_TRANSFORM, {}).get(CONF_SWAP_XY, False)
|
||||
|
||||
# Dimensions are optional if the model has a default width and the swap_xy transform is not overridden
|
||||
cv_dimensions = (
|
||||
cv.Optional if model.get_default(CONF_WIDTH) and not swap_xy else cv.Required
|
||||
)
|
||||
pixel_modes = (PIXEL_MODE_16BIT, PIXEL_MODE_18BIT, "16", "18")
|
||||
schema = display.FULL_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
model.option(CONF_RESET_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
|
||||
cv.GenerateID(): cv.declare_id(mipi_rgb_spi if uses_spi else mipi_rgb),
|
||||
cv_dimensions(CONF_DIMENSIONS): dimension_schema(
|
||||
model.get_default(CONF_DRAW_ROUNDING, 1)
|
||||
),
|
||||
model.option(CONF_ENABLE_PIN, cv.UNDEFINED): cv.ensure_list(
|
||||
pins.gpio_output_pin_schema
|
||||
),
|
||||
model.option(CONF_COLOR_ORDER, MODE_BGR): cv.enum(COLOR_ORDERS, upper=True),
|
||||
model.option(CONF_DRAW_ROUNDING, 2): power_of_two,
|
||||
model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of(
|
||||
*pixel_modes, lower=True
|
||||
),
|
||||
model.option(CONF_TRANSFORM, cv.UNDEFINED): transform,
|
||||
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
|
||||
model.option(CONF_INVERT_COLORS, False): cv.boolean,
|
||||
model.option(CONF_USE_AXIS_FLIPS, True): cv.boolean,
|
||||
model.option(CONF_PCLK_FREQUENCY, "40MHz"): cv.All(
|
||||
cv.frequency, cv.Range(min=4e6, max=100e6)
|
||||
),
|
||||
model.option(CONF_PCLK_INVERTED, True): cv.boolean,
|
||||
iseqconf: cv.ensure_list(map_sequence),
|
||||
model.option(CONF_BYTE_ORDER, BYTE_ORDER_BIG): cv.one_of(
|
||||
BYTE_ORDER_LITTLE, BYTE_ORDER_BIG, lower=True
|
||||
),
|
||||
model.option(CONF_HSYNC_PULSE_WIDTH): cv.int_,
|
||||
model.option(CONF_HSYNC_BACK_PORCH): cv.int_,
|
||||
model.option(CONF_HSYNC_FRONT_PORCH): cv.int_,
|
||||
model.option(CONF_VSYNC_PULSE_WIDTH): cv.int_,
|
||||
model.option(CONF_VSYNC_BACK_PORCH): cv.int_,
|
||||
model.option(CONF_VSYNC_FRONT_PORCH): cv.int_,
|
||||
model.option(CONF_DATA_PINS): cv.Any(
|
||||
data_pin_set(16),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_RED): data_pin_set(5),
|
||||
cv.Required(CONF_GREEN): data_pin_set(6),
|
||||
cv.Required(CONF_BLUE): data_pin_set(5),
|
||||
}
|
||||
),
|
||||
),
|
||||
model.option(
|
||||
CONF_DE_PIN, cv.UNDEFINED
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
model.option(CONF_PCLK_PIN): pins.internal_gpio_output_pin_schema,
|
||||
model.option(CONF_HSYNC_PIN): pins.internal_gpio_output_pin_schema,
|
||||
model.option(CONF_VSYNC_PIN): pins.internal_gpio_output_pin_schema,
|
||||
model.option(CONF_RESET_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
if uses_spi:
|
||||
schema = schema.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent),
|
||||
model.option(CONF_DC_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
|
||||
model.option(CONF_DATA_RATE, "1MHz"): SPI_DATA_RATE_SCHEMA,
|
||||
model.option(CONF_SPI_MODE, "MODE0"): cv.enum(
|
||||
SPI_MODE_OPTIONS, upper=True
|
||||
),
|
||||
model.option(CONF_CS_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
return schema
|
||||
|
||||
|
||||
def _config_schema(config):
|
||||
config = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)(config)
|
||||
schema = model_schema(config)
|
||||
return cv.All(
|
||||
schema,
|
||||
only_on_variant(supported=[const.VARIANT_ESP32S3]),
|
||||
cv.only_with_esp_idf,
|
||||
)(config)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = _config_schema
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
global_config = full_config.get()
|
||||
|
||||
from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
|
||||
|
||||
if not requires_buffer(config) and LVGL_DOMAIN not in global_config:
|
||||
# If no drawing methods are configured, and LVGL is not enabled, show a test card
|
||||
config[CONF_SHOW_TEST_CARD] = True
|
||||
if CONF_SPI_ID in config:
|
||||
config = spi.final_validate_device_schema(
|
||||
"mipi_rgb", require_miso=False, require_mosi=True
|
||||
)(config)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
width, height, _offset_width, _offset_height = model.get_dimensions(config)
|
||||
var = cg.new_Pvariable(config[CONF_ID], width, height)
|
||||
cg.add(var.set_model(model.name))
|
||||
if enable_pin := config.get(CONF_ENABLE_PIN):
|
||||
enable = [await cg.gpio_pin_expression(pin) for pin in enable_pin]
|
||||
cg.add(var.set_enable_pins(enable))
|
||||
|
||||
if CONF_SPI_ID in config:
|
||||
await spi.register_spi_device(var, config)
|
||||
sequence, madctl = model.get_sequence(config)
|
||||
cg.add(var.set_init_sequence(sequence))
|
||||
cg.add(var.set_madctl(madctl))
|
||||
|
||||
cg.add(var.set_color_mode(COLOR_ORDERS[config[CONF_COLOR_ORDER]]))
|
||||
cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS]))
|
||||
cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH]))
|
||||
cg.add(var.set_hsync_back_porch(config[CONF_HSYNC_BACK_PORCH]))
|
||||
cg.add(var.set_hsync_front_porch(config[CONF_HSYNC_FRONT_PORCH]))
|
||||
cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH]))
|
||||
cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH]))
|
||||
cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH]))
|
||||
cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED]))
|
||||
cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY]))
|
||||
index = 0
|
||||
dpins = []
|
||||
if CONF_RED in config[CONF_DATA_PINS]:
|
||||
red_pins = config[CONF_DATA_PINS][CONF_RED]
|
||||
green_pins = config[CONF_DATA_PINS][CONF_GREEN]
|
||||
blue_pins = config[CONF_DATA_PINS][CONF_BLUE]
|
||||
if config[CONF_COLOR_ORDER] == "BGR":
|
||||
dpins.extend(red_pins)
|
||||
dpins.extend(green_pins)
|
||||
dpins.extend(blue_pins)
|
||||
else:
|
||||
dpins.extend(blue_pins)
|
||||
dpins.extend(green_pins)
|
||||
dpins.extend(red_pins)
|
||||
# swap bytes to match big-endian format
|
||||
dpins = dpins[8:16] + dpins[0:8]
|
||||
else:
|
||||
dpins = config[CONF_DATA_PINS]
|
||||
for index, pin in enumerate(dpins):
|
||||
data_pin = await cg.gpio_pin_expression(pin)
|
||||
cg.add(var.add_data_pin(data_pin, index))
|
||||
|
||||
if dc_pin := config.get(CONF_DC_PIN):
|
||||
dc = await cg.gpio_pin_expression(dc_pin)
|
||||
cg.add(var.set_dc_pin(dc))
|
||||
|
||||
if reset_pin := config.get(CONF_RESET_PIN):
|
||||
reset = await cg.gpio_pin_expression(reset_pin)
|
||||
cg.add(var.set_reset_pin(reset))
|
||||
|
||||
if model.rotation_as_transform(config):
|
||||
config[CONF_ROTATION] = 0
|
||||
|
||||
if de_pin := config.get(CONF_DE_PIN):
|
||||
pin = await cg.gpio_pin_expression(de_pin)
|
||||
cg.add(var.set_de_pin(pin))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PCLK_PIN])
|
||||
cg.add(var.set_pclk_pin(pin))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_HSYNC_PIN])
|
||||
cg.add(var.set_hsync_pin(pin))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_VSYNC_PIN])
|
||||
cg.add(var.set_vsync_pin(pin))
|
||||
|
||||
await display.register_display(var, config)
|
||||
if lamb := config.get(CONF_LAMBDA):
|
||||
lambda_ = await cg.process_lambda(
|
||||
lamb, [(display.DisplayRef, "it")], return_type=cg.void
|
||||
)
|
||||
cg.add(var.set_writer(lambda_))
|
388
esphome/components/mipi_rgb/mipi_rgb.cpp
Normal file
388
esphome/components/mipi_rgb/mipi_rgb.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||
#include "mipi_rgb.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esp_lcd_panel_rgb.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mipi_rgb {
|
||||
|
||||
static const uint8_t DELAY_FLAG = 0xFF;
|
||||
static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top
|
||||
static constexpr uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left
|
||||
static constexpr uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes
|
||||
static constexpr uint8_t MADCTL_ML = 0x10; // Bit 4 Refresh bottom to top
|
||||
static constexpr uint8_t MADCTL_BGR = 0x08; // Bit 3 Blue-Green-Red pixel order
|
||||
static constexpr uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horizontally
|
||||
static constexpr uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically
|
||||
|
||||
void MipiRgb::setup_enables_() {
|
||||
if (!this->enable_pins_.empty()) {
|
||||
for (auto *pin : this->enable_pins_) {
|
||||
pin->setup();
|
||||
pin->digital_write(true);
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_SPI
|
||||
void MipiRgbSpi::setup() {
|
||||
this->setup_enables_();
|
||||
this->spi_setup();
|
||||
this->write_init_sequence_();
|
||||
this->common_setup_();
|
||||
}
|
||||
void MipiRgbSpi::write_command_(uint8_t value) {
|
||||
this->enable();
|
||||
if (this->dc_pin_ == nullptr) {
|
||||
this->write(value, 9);
|
||||
} else {
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(value);
|
||||
this->dc_pin_->digital_write(true);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void MipiRgbSpi::write_data_(uint8_t value) {
|
||||
this->enable();
|
||||
if (this->dc_pin_ == nullptr) {
|
||||
this->write(value | 0x100, 9);
|
||||
} else {
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_byte(value);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* this relies upon the init sequence being well-formed, which is guaranteed by the Python init code.
|
||||
*/
|
||||
|
||||
void MipiRgbSpi::write_init_sequence_() {
|
||||
size_t index = 0;
|
||||
auto &vec = this->init_sequence_;
|
||||
while (index != vec.size()) {
|
||||
if (vec.size() - index < 2) {
|
||||
this->mark_failed("Malformed init sequence");
|
||||
return;
|
||||
}
|
||||
uint8_t cmd = vec[index++];
|
||||
uint8_t x = vec[index++];
|
||||
if (x == DELAY_FLAG) {
|
||||
ESP_LOGD(TAG, "Delay %dms", cmd);
|
||||
delay(cmd);
|
||||
} else {
|
||||
uint8_t num_args = x & 0x7F;
|
||||
if (vec.size() - index < num_args) {
|
||||
this->mark_failed("Malformed init sequence");
|
||||
return;
|
||||
}
|
||||
if (cmd == SLEEP_OUT) {
|
||||
delay(120); // NOLINT
|
||||
}
|
||||
const auto *ptr = vec.data() + index;
|
||||
ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, num_args,
|
||||
format_hex_pretty(ptr, num_args, '.', false).c_str());
|
||||
index += num_args;
|
||||
this->write_command_(cmd);
|
||||
while (num_args-- != 0)
|
||||
this->write_data_(*ptr++);
|
||||
if (cmd == SLEEP_OUT)
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
// this->spi_teardown(); // SPI not needed after this
|
||||
this->init_sequence_.clear();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
void MipiRgbSpi::dump_config() {
|
||||
MipiRgb::dump_config();
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" SPI Data rate: %uMHz"
|
||||
"\n Mirror X: %s"
|
||||
"\n Mirror Y: %s"
|
||||
"\n Swap X/Y: %s"
|
||||
"\n Color Order: %s",
|
||||
(unsigned) (this->data_rate_ / 1000000), YESNO(this->madctl_ & (MADCTL_XFLIP | MADCTL_MX)),
|
||||
YESNO(this->madctl_ & (MADCTL_YFLIP | MADCTL_MY | MADCTL_ML)), YESNO(this->madctl_ & MADCTL_MV),
|
||||
this->madctl_ & MADCTL_BGR ? "BGR" : "RGB");
|
||||
}
|
||||
|
||||
#endif // USE_SPI
|
||||
|
||||
void MipiRgb::setup() {
|
||||
this->setup_enables_();
|
||||
this->common_setup_();
|
||||
}
|
||||
|
||||
void MipiRgb::common_setup_() {
|
||||
esp_lcd_rgb_panel_config_t config{};
|
||||
config.flags.fb_in_psram = 1;
|
||||
config.bounce_buffer_size_px = this->width_ * 10;
|
||||
config.num_fbs = 1;
|
||||
config.timings.h_res = this->width_;
|
||||
config.timings.v_res = this->height_;
|
||||
config.timings.hsync_pulse_width = this->hsync_pulse_width_;
|
||||
config.timings.hsync_back_porch = this->hsync_back_porch_;
|
||||
config.timings.hsync_front_porch = this->hsync_front_porch_;
|
||||
config.timings.vsync_pulse_width = this->vsync_pulse_width_;
|
||||
config.timings.vsync_back_porch = this->vsync_back_porch_;
|
||||
config.timings.vsync_front_porch = this->vsync_front_porch_;
|
||||
config.timings.flags.pclk_active_neg = this->pclk_inverted_;
|
||||
config.timings.pclk_hz = this->pclk_frequency_;
|
||||
config.clk_src = LCD_CLK_SRC_PLL160M;
|
||||
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
||||
for (size_t i = 0; i != data_pin_count; i++) {
|
||||
config.data_gpio_nums[i] = this->data_pins_[i]->get_pin();
|
||||
}
|
||||
config.data_width = data_pin_count;
|
||||
config.disp_gpio_num = -1;
|
||||
config.hsync_gpio_num = this->hsync_pin_->get_pin();
|
||||
config.vsync_gpio_num = this->vsync_pin_->get_pin();
|
||||
if (this->de_pin_) {
|
||||
config.de_gpio_num = this->de_pin_->get_pin();
|
||||
} else {
|
||||
config.de_gpio_num = -1;
|
||||
}
|
||||
config.pclk_gpio_num = this->pclk_pin_->get_pin();
|
||||
esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
|
||||
if (err == ESP_OK)
|
||||
err = esp_lcd_panel_reset(this->handle_);
|
||||
if (err == ESP_OK)
|
||||
err = esp_lcd_panel_init(this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
auto msg = str_sprintf("lcd setup failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed(msg.c_str());
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "MipiRgb setup complete");
|
||||
}
|
||||
|
||||
void MipiRgb::loop() {
|
||||
if (this->handle_ != nullptr)
|
||||
esp_lcd_rgb_panel_restart(this->handle_);
|
||||
}
|
||||
|
||||
void MipiRgb::update() {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
if (this->auto_clear_enabled_) {
|
||||
this->clear();
|
||||
}
|
||||
if (this->show_test_card_) {
|
||||
this->test_card();
|
||||
} else if (this->page_ != nullptr) {
|
||||
this->page_->get_writer()(*this);
|
||||
} else if (this->writer_.has_value()) {
|
||||
(*this->writer_)(*this);
|
||||
} else {
|
||||
this->stop_poller();
|
||||
}
|
||||
if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
|
||||
return;
|
||||
ESP_LOGV(TAG, "x_low %d, y_low %d, x_high %d, y_high %d", this->x_low_, this->y_low_, this->x_high_, this->y_high_);
|
||||
int w = this->x_high_ - this->x_low_ + 1;
|
||||
int h = this->y_high_ - this->y_low_ + 1;
|
||||
this->write_to_display_(this->x_low_, this->y_low_, w, h, reinterpret_cast<const uint8_t *>(this->buffer_),
|
||||
this->x_low_, this->y_low_, this->width_ - w - this->x_low_);
|
||||
// invalidate watermarks
|
||||
this->x_low_ = this->width_;
|
||||
this->y_low_ = this->height_;
|
||||
this->x_high_ = 0;
|
||||
this->y_high_ = 0;
|
||||
}
|
||||
|
||||
void MipiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
|
||||
if (w <= 0 || h <= 0 || this->is_failed())
|
||||
return;
|
||||
// if color mapping is required, pass the buck.
|
||||
// note that endianness is not considered here - it is assumed to match!
|
||||
if (bitness != display::COLOR_BITNESS_565) {
|
||||
Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad);
|
||||
this->write_to_display_(x_start, y_start, w, h, reinterpret_cast<const uint8_t *>(this->buffer_), x_start, y_start,
|
||||
this->width_ - w - x_start);
|
||||
} else {
|
||||
this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
|
||||
}
|
||||
}
|
||||
|
||||
void MipiRgb::write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
|
||||
int x_pad) {
|
||||
esp_err_t err = ESP_OK;
|
||||
auto stride = (x_offset + w + x_pad) * 2;
|
||||
ptr += y_offset * stride + x_offset * 2; // skip to the first pixel
|
||||
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
|
||||
if (x_offset == 0 && x_pad == 0) {
|
||||
err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr);
|
||||
} else {
|
||||
// draw line by line
|
||||
for (int y = 0; y != h; y++) {
|
||||
err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, ptr);
|
||||
if (err != ESP_OK)
|
||||
break;
|
||||
ptr += stride; // next line
|
||||
}
|
||||
}
|
||||
if (err != ESP_OK)
|
||||
ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
bool MipiRgb::check_buffer_() {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
if (this->buffer_ != nullptr)
|
||||
return true;
|
||||
// this is dependent on the enum values.
|
||||
RAMAllocator<uint16_t> allocator;
|
||||
this->buffer_ = allocator.allocate(this->height_ * this->width_);
|
||||
if (this->buffer_ == nullptr) {
|
||||
this->mark_failed("Could not allocate buffer for display!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MipiRgb::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->get_clipping().inside(x, y) || this->is_failed())
|
||||
return;
|
||||
|
||||
switch (this->rotation_) {
|
||||
case display::DISPLAY_ROTATION_0_DEGREES:
|
||||
break;
|
||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||
std::swap(x, y);
|
||||
x = this->width_ - x - 1;
|
||||
break;
|
||||
case display::DISPLAY_ROTATION_180_DEGREES:
|
||||
x = this->width_ - x - 1;
|
||||
y = this->height_ - y - 1;
|
||||
break;
|
||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||
std::swap(x, y);
|
||||
y = this->height_ - y - 1;
|
||||
break;
|
||||
}
|
||||
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
|
||||
return;
|
||||
}
|
||||
if (!this->check_buffer_())
|
||||
return;
|
||||
size_t pos = (y * this->width_) + x;
|
||||
uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
|
||||
uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
|
||||
uint16_t new_color = hi_byte | (lo_byte << 8); // big endian
|
||||
if (this->buffer_[pos] == new_color)
|
||||
return;
|
||||
this->buffer_[pos] = new_color;
|
||||
// low and high watermark may speed up drawing from buffer
|
||||
if (x < this->x_low_)
|
||||
this->x_low_ = x;
|
||||
if (y < this->y_low_)
|
||||
this->y_low_ = y;
|
||||
if (x > this->x_high_)
|
||||
this->x_high_ = x;
|
||||
if (y > this->y_high_)
|
||||
this->y_high_ = y;
|
||||
}
|
||||
void MipiRgb::fill(Color color) {
|
||||
if (!this->check_buffer_())
|
||||
return;
|
||||
auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_);
|
||||
uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
|
||||
uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
|
||||
uint16_t new_color = lo_byte | (hi_byte << 8); // little endian
|
||||
std::fill_n(ptr_16, this->width_ * this->height_, new_color);
|
||||
}
|
||||
|
||||
int MipiRgb::get_width() {
|
||||
switch (this->rotation_) {
|
||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||
return this->get_height_internal();
|
||||
case display::DISPLAY_ROTATION_0_DEGREES:
|
||||
case display::DISPLAY_ROTATION_180_DEGREES:
|
||||
default:
|
||||
return this->get_width_internal();
|
||||
}
|
||||
}
|
||||
|
||||
int MipiRgb::get_height() {
|
||||
switch (this->rotation_) {
|
||||
case display::DISPLAY_ROTATION_0_DEGREES:
|
||||
case display::DISPLAY_ROTATION_180_DEGREES:
|
||||
return this->get_height_internal();
|
||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||
default:
|
||||
return this->get_width_internal();
|
||||
}
|
||||
}
|
||||
|
||||
static std::string get_pin_name(GPIOPin *pin) {
|
||||
if (pin == nullptr)
|
||||
return "None";
|
||||
return pin->dump_summary();
|
||||
}
|
||||
|
||||
void MipiRgb::dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset) {
|
||||
for (uint8_t i = start; i != end; i++) {
|
||||
ESP_LOGCONFIG(TAG, " %s pin %d: %s", name, offset++, this->data_pins_[i]->dump_summary().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void MipiRgb::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"MIPI_RGB LCD"
|
||||
"\n Model: %s"
|
||||
"\n Width: %u"
|
||||
"\n Height: %u"
|
||||
"\n Rotation: %d degrees"
|
||||
"\n HSync Pulse Width: %u"
|
||||
"\n HSync Back Porch: %u"
|
||||
"\n HSync Front Porch: %u"
|
||||
"\n VSync Pulse Width: %u"
|
||||
"\n VSync Back Porch: %u"
|
||||
"\n VSync Front Porch: %u"
|
||||
"\n Invert Colors: %s"
|
||||
"\n Pixel Clock: %dMHz"
|
||||
"\n Reset Pin: %s"
|
||||
"\n DE Pin: %s"
|
||||
"\n PCLK Pin: %s"
|
||||
"\n HSYNC Pin: %s"
|
||||
"\n VSYNC Pin: %s",
|
||||
this->model_, this->width_, this->height_, this->rotation_, this->hsync_pulse_width_,
|
||||
this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_, this->vsync_back_porch_,
|
||||
this->vsync_front_porch_, YESNO(this->invert_colors_), this->pclk_frequency_ / 1000000,
|
||||
get_pin_name(this->reset_pin_).c_str(), get_pin_name(this->de_pin_).c_str(),
|
||||
get_pin_name(this->pclk_pin_).c_str(), get_pin_name(this->hsync_pin_).c_str(),
|
||||
get_pin_name(this->vsync_pin_).c_str());
|
||||
|
||||
if (this->madctl_ & MADCTL_BGR) {
|
||||
this->dump_pins_(8, 13, "Blue", 0);
|
||||
this->dump_pins_(13, 16, "Green", 0);
|
||||
this->dump_pins_(0, 3, "Green", 3);
|
||||
this->dump_pins_(3, 8, "Red", 0);
|
||||
} else {
|
||||
this->dump_pins_(8, 13, "Red", 0);
|
||||
this->dump_pins_(13, 16, "Green", 0);
|
||||
this->dump_pins_(0, 3, "Green", 3);
|
||||
this->dump_pins_(3, 8, "Blue", 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mipi_rgb
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP32_VARIANT_ESP32S3
|
127
esphome/components/mipi_rgb/mipi_rgb.h
Normal file
127
esphome/components/mipi_rgb/mipi_rgb.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#ifdef USE_SPI
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace mipi_rgb {
|
||||
|
||||
constexpr static const char *const TAG = "display.mipi_rgb";
|
||||
const uint8_t SW_RESET_CMD = 0x01;
|
||||
const uint8_t SLEEP_OUT = 0x11;
|
||||
const uint8_t SDIR_CMD = 0xC7;
|
||||
const uint8_t MADCTL_CMD = 0x36;
|
||||
const uint8_t INVERT_OFF = 0x20;
|
||||
const uint8_t INVERT_ON = 0x21;
|
||||
const uint8_t DISPLAY_ON = 0x29;
|
||||
const uint8_t CMD2_BKSEL = 0xFF;
|
||||
const uint8_t CMD2_BK0[5] = {0x77, 0x01, 0x00, 0x00, 0x10};
|
||||
|
||||
class MipiRgb : public display::Display {
|
||||
public:
|
||||
MipiRgb(int width, int height) : width_(width), height_(height) {}
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void update() override;
|
||||
void fill(Color color);
|
||||
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
|
||||
void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
|
||||
int x_pad);
|
||||
bool check_buffer_();
|
||||
|
||||
display::ColorOrder get_color_mode() { return this->color_mode_; }
|
||||
void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; }
|
||||
void set_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; }
|
||||
void set_madctl(uint8_t madctl) { this->madctl_ = madctl; }
|
||||
|
||||
void add_data_pin(InternalGPIOPin *data_pin, size_t index) { this->data_pins_[index] = data_pin; };
|
||||
void set_de_pin(InternalGPIOPin *de_pin) { this->de_pin_ = de_pin; }
|
||||
void set_pclk_pin(InternalGPIOPin *pclk_pin) { this->pclk_pin_ = pclk_pin; }
|
||||
void set_vsync_pin(InternalGPIOPin *vsync_pin) { this->vsync_pin_ = vsync_pin; }
|
||||
void set_hsync_pin(InternalGPIOPin *hsync_pin) { this->hsync_pin_ = hsync_pin; }
|
||||
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void set_width(uint16_t width) { this->width_ = width; }
|
||||
void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; }
|
||||
void set_pclk_inverted(bool inverted) { this->pclk_inverted_ = inverted; }
|
||||
void set_model(const char *model) { this->model_ = model; }
|
||||
int get_width() override;
|
||||
int get_height() override;
|
||||
void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; }
|
||||
void set_hsync_front_porch(uint16_t hsync_front_porch) { this->hsync_front_porch_ = hsync_front_porch; }
|
||||
void set_hsync_pulse_width(uint16_t hsync_pulse_width) { this->hsync_pulse_width_ = hsync_pulse_width; }
|
||||
void set_vsync_pulse_width(uint16_t vsync_pulse_width) { this->vsync_pulse_width_ = vsync_pulse_width; }
|
||||
void set_vsync_back_porch(uint16_t vsync_back_porch) { this->vsync_back_porch_ = vsync_back_porch; }
|
||||
void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; }
|
||||
void set_enable_pins(std::vector<GPIOPin *> enable_pins) { this->enable_pins_ = std::move(enable_pins); }
|
||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
||||
int get_width_internal() override { return this->width_; }
|
||||
int get_height_internal() override { return this->height_; }
|
||||
void dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset);
|
||||
void dump_config() override;
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
|
||||
// this will be horribly slow.
|
||||
protected:
|
||||
void setup_enables_();
|
||||
void common_setup_();
|
||||
InternalGPIOPin *de_pin_{nullptr};
|
||||
InternalGPIOPin *pclk_pin_{nullptr};
|
||||
InternalGPIOPin *hsync_pin_{nullptr};
|
||||
InternalGPIOPin *vsync_pin_{nullptr};
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
InternalGPIOPin *data_pins_[16] = {};
|
||||
uint16_t hsync_pulse_width_ = 10;
|
||||
uint16_t hsync_back_porch_ = 10;
|
||||
uint16_t hsync_front_porch_ = 20;
|
||||
uint16_t vsync_pulse_width_ = 10;
|
||||
uint16_t vsync_back_porch_ = 10;
|
||||
uint16_t vsync_front_porch_ = 10;
|
||||
uint32_t pclk_frequency_ = 16 * 1000 * 1000;
|
||||
bool pclk_inverted_{true};
|
||||
uint8_t madctl_{};
|
||||
const char *model_{"Unknown"};
|
||||
bool invert_colors_{};
|
||||
display::ColorOrder color_mode_{display::COLOR_ORDER_BGR};
|
||||
size_t width_;
|
||||
size_t height_;
|
||||
uint16_t *buffer_{nullptr};
|
||||
std::vector<GPIOPin *> enable_pins_{};
|
||||
uint16_t x_low_{1};
|
||||
uint16_t y_low_{1};
|
||||
uint16_t x_high_{0};
|
||||
uint16_t y_high_{0};
|
||||
|
||||
esp_lcd_panel_handle_t handle_{};
|
||||
};
|
||||
|
||||
#ifdef USE_SPI
|
||||
class MipiRgbSpi : public MipiRgb,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
MipiRgbSpi(int width, int height) : MipiRgb(width, height) {}
|
||||
|
||||
void set_init_sequence(const std::vector<uint8_t> &init_sequence) { this->init_sequence_ = init_sequence; }
|
||||
void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; }
|
||||
void setup() override;
|
||||
|
||||
protected:
|
||||
void write_command_(uint8_t value);
|
||||
void write_data_(uint8_t value);
|
||||
void write_init_sequence_();
|
||||
void dump_config();
|
||||
|
||||
GPIOPin *dc_pin_{nullptr};
|
||||
std::vector<uint8_t> init_sequence_;
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace mipi_rgb
|
||||
} // namespace esphome
|
||||
#endif
|
24
esphome/components/mipi_rgb/models/guition.py
Normal file
24
esphome/components/mipi_rgb/models/guition.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from .st7701s import st7701s
|
||||
|
||||
st7701s.extend(
|
||||
"GUITION-4848S040",
|
||||
width=480,
|
||||
height=480,
|
||||
data_rate="2MHz",
|
||||
cs_pin=39,
|
||||
de_pin=18,
|
||||
hsync_pin=16,
|
||||
vsync_pin=17,
|
||||
pclk_pin=21,
|
||||
pclk_frequency="12MHz",
|
||||
pixel_mode="18bit",
|
||||
mirror_x=True,
|
||||
mirror_y=True,
|
||||
data_pins={
|
||||
"red": [11, 12, 13, 14, 0],
|
||||
"green": [8, 20, 3, 46, 9, 10],
|
||||
"blue": [4, 5, 6, 7, 15],
|
||||
},
|
||||
# Additional configuration for Guition 4848S040, 16 bit bus config
|
||||
add_init_sequence=((0xCD, 0x00),),
|
||||
)
|
@@ -0,0 +1,228 @@
|
||||
from esphome.config_validation import UNDEFINED
|
||||
|
||||
from .st7701s import ST7701S
|
||||
|
||||
# fmt: off
|
||||
ST7701S(
|
||||
"T-PANEL-S3",
|
||||
width=480,
|
||||
height=480,
|
||||
color_order="BGR",
|
||||
invert_colors=False,
|
||||
swap_xy=UNDEFINED,
|
||||
spi_mode="MODE3",
|
||||
cs_pin={"xl9535": None, "number": 17},
|
||||
reset_pin={"xl9535": None, "number": 5},
|
||||
hsync_pin=39,
|
||||
vsync_pin=40,
|
||||
pclk_pin=41,
|
||||
data_pins={
|
||||
"red": [12, 13, 42, 46, 45],
|
||||
"green": [6, 7, 8, 9, 10, 11],
|
||||
"blue": [1, 2, 3, 4, 5],
|
||||
},
|
||||
hsync_front_porch=20,
|
||||
hsync_back_porch=0,
|
||||
hsync_pulse_width=2,
|
||||
vsync_front_porch=30,
|
||||
vsync_back_porch=1,
|
||||
vsync_pulse_width=8,
|
||||
pclk_frequency="6MHz",
|
||||
pclk_inverted=False,
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xEF, 0x08), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
(0xC0, 0x3B, 0x00), (0xC1, 0x0B, 0x02), (0xC2, 0x30, 0x02, 0x37), (0xCC, 0x10),
|
||||
(0xB0, 0x00, 0x0F, 0x16, 0x0E, 0x11, 0x07, 0x09, 0x09, 0x08, 0x23, 0x05, 0x11, 0x0F, 0x28, 0x2D, 0x18),
|
||||
(0xB1, 0x00, 0x0F, 0x16, 0x0E, 0x11, 0x07, 0x09, 0x08, 0x09, 0x23, 0x05, 0x11, 0x0F, 0x28, 0x2D, 0x18),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11),
|
||||
(0xB0, 0x4D), (0xB1, 0x33), (0xB2, 0x87), (0xB5, 0x4B), (0xB7, 0x8C), (0xB8, 0x20), (0xC1, 0x78),
|
||||
(0xC2, 0x78), (0xD0, 0x88), (0xE0, 0x00, 0x00, 0x02),
|
||||
(0xE1, 0x02, 0xF0, 0x00, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x00, 0x44, 0x44),
|
||||
(0xE2, 0x10, 0x10, 0x40, 0x40, 0xF2, 0xF0, 0x00, 0x00, 0xF2, 0xF0, 0x00, 0x00),
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x11), (0xE4, 0x44, 0x44),
|
||||
(0xE5, 0x07, 0xEF, 0xF0, 0xF0, 0x09, 0xF1, 0xF0, 0xF0, 0x03, 0xF3, 0xF0, 0xF0, 0x05, 0xED, 0xF0, 0xF0),
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x11), (0xE7, 0x44, 0x44),
|
||||
(0xE8, 0x08, 0xF0, 0xF0, 0xF0, 0x0A, 0xF2, 0xF0, 0xF0, 0x04, 0xF4, 0xF0, 0xF0, 0x06, 0xEE, 0xF0, 0xF0),
|
||||
(0xEB, 0x00, 0x00, 0xE4, 0xE4, 0x44, 0x88, 0x40),
|
||||
(0xEC, 0x78, 0x00),
|
||||
(0xED, 0x20, 0xF9, 0x87, 0x76, 0x65, 0x54, 0x4F, 0xFF, 0xFF, 0xF4, 0x45, 0x56, 0x67, 0x78, 0x9F, 0x02),
|
||||
(0xEF, 0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
t_rgb = ST7701S(
|
||||
"T-RGB-2.1",
|
||||
width=480,
|
||||
height=480,
|
||||
color_order="BGR",
|
||||
pixel_mode="18bit",
|
||||
invert_colors=False,
|
||||
swap_xy=UNDEFINED,
|
||||
spi_mode="MODE3",
|
||||
cs_pin={"xl9535": None, "number": 3},
|
||||
de_pin=45,
|
||||
hsync_pin=47,
|
||||
vsync_pin=41,
|
||||
pclk_pin=42,
|
||||
data_pins={
|
||||
"red": [7, 6, 5, 3, 2],
|
||||
"green": [14, 13, 12, 11, 10, 9],
|
||||
"blue": [21, 18, 17, 16, 15],
|
||||
},
|
||||
hsync_front_porch=50,
|
||||
hsync_pulse_width=1,
|
||||
hsync_back_porch=30,
|
||||
vsync_front_porch=20,
|
||||
vsync_pulse_width=1,
|
||||
vsync_back_porch=30,
|
||||
pclk_frequency="12MHz",
|
||||
pclk_inverted=False,
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
|
||||
(0xC0, 0x3B, 0x00),
|
||||
(0xC1, 0x0B, 0x02),
|
||||
(0xC2, 0x07, 0x02),
|
||||
(0xCC, 0x10),
|
||||
(0xCD, 0x08),
|
||||
|
||||
(0xB0,
|
||||
0x00, 0x11, 0x16, 0x0e,
|
||||
0x11, 0x06, 0x05, 0x09,
|
||||
0x08, 0x21, 0x06, 0x13,
|
||||
0x10, 0x29, 0x31, 0x18),
|
||||
|
||||
(0xB1,
|
||||
0x00, 0x11, 0x16, 0x0e,
|
||||
0x11, 0x07, 0x05, 0x09,
|
||||
0x09, 0x21, 0x05, 0x13,
|
||||
0x11, 0x2a, 0x31, 0x18),
|
||||
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11),
|
||||
|
||||
(0xB0, 0x6D),
|
||||
(0xB1, 0x37),
|
||||
(0xB2, 0x81),
|
||||
(0xB3, 0x80),
|
||||
(0xB5, 0x43),
|
||||
(0xB7, 0x85),
|
||||
(0xB8, 0x20),
|
||||
|
||||
(0xC1, 0x78),
|
||||
(0xC2, 0x78),
|
||||
(0xC3, 0x8C),
|
||||
|
||||
(0xD0, 0x88),
|
||||
|
||||
(0xE0, 0x00, 0x00, 0x02),
|
||||
(0xE1,
|
||||
0x03, 0xA0, 0x00, 0x00,
|
||||
0x04, 0xA0, 0x00, 0x00,
|
||||
0x00, 0x20, 0x20),
|
||||
|
||||
(0xE2,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00),
|
||||
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x00),
|
||||
(0xE4, 0x22, 0x00),
|
||||
|
||||
(0xE5,
|
||||
0x05, 0xEC, 0xA0, 0xA0,
|
||||
0x07, 0xEE, 0xA0, 0xA0,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00),
|
||||
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x00),
|
||||
(0xE7, 0x22, 0x00),
|
||||
|
||||
(0xE8,
|
||||
0x06, 0xED, 0xA0, 0xA0,
|
||||
0x08, 0xEF, 0xA0, 0xA0,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00),
|
||||
|
||||
(0xEB, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x10),
|
||||
|
||||
(0xED,
|
||||
0xFF, 0xFF, 0xFF, 0xBA,
|
||||
0x0A, 0xBF, 0x45, 0xFF,
|
||||
0xFF, 0x54, 0xFB, 0xA0,
|
||||
0xAB, 0xFF, 0xFF, 0xFF),
|
||||
|
||||
(0xEF, 0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F),
|
||||
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13),
|
||||
(0xEF, 0x08),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10)
|
||||
)
|
||||
|
||||
)
|
||||
t_rgb.extend(
|
||||
"T-RGB-2.8",
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13),
|
||||
(0xEF, 0x08),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
(0xC0, 0x3B, 0x00),
|
||||
(0xC1, 0x10, 0x0C),
|
||||
(0xC2, 0x07, 0x0A),
|
||||
(0xC7, 0x00),
|
||||
(0xC7, 0x10),
|
||||
(0xCD, 0x08),
|
||||
(0xB0,
|
||||
0x05, 0x12, 0x98, 0x0e, 0x0F,
|
||||
0x07, 0x07, 0x09, 0x09, 0x23,
|
||||
0x05, 0x52, 0x0F, 0x67, 0x2C, 0x11),
|
||||
(0xB1,
|
||||
0x0B, 0x11, 0x97, 0x0C, 0x12,
|
||||
0x06, 0x06, 0x08, 0x08, 0x22,
|
||||
0x03, 0x51, 0x11, 0x66, 0x2B, 0x0F),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11),
|
||||
(0xB0, 0x5D),
|
||||
(0xB1, 0x2D),
|
||||
(0xB2, 0x81),
|
||||
(0xB3, 0x80),
|
||||
(0xB5, 0x4E),
|
||||
(0xB7, 0x85),
|
||||
(0xB8, 0x20),
|
||||
(0xC1, 0x78),
|
||||
(0xC2, 0x78),
|
||||
(0xD0, 0x88),
|
||||
(0xE0, 0x00, 0x00, 0x02),
|
||||
(0xE1,
|
||||
0x06, 0x30, 0x08, 0x30, 0x05,
|
||||
0x30, 0x07, 0x30, 0x00, 0x33,
|
||||
0x33),
|
||||
(0xE2,
|
||||
0x11, 0x11, 0x33, 0x33, 0xf4,
|
||||
0x00, 0x00, 0x00, 0xf4, 0x00,
|
||||
0x00, 0x00),
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x11),
|
||||
(0xE4, 0x44, 0x44),
|
||||
(0xE5,
|
||||
0x0d, 0xf5, 0x30, 0xf0, 0x0f,
|
||||
0xf7, 0x30, 0xf0, 0x09, 0xf1,
|
||||
0x30, 0xf0, 0x0b, 0xf3, 0x30, 0xf0),
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x11),
|
||||
(0xE7, 0x44, 0x44),
|
||||
(0xE8,
|
||||
0x0c, 0xf4, 0x30, 0xf0,
|
||||
0x0e, 0xf6, 0x30, 0xf0,
|
||||
0x08, 0xf0, 0x30, 0xf0,
|
||||
0x0a, 0xf2, 0x30, 0xf0),
|
||||
(0xe9, 0x36),
|
||||
(0xEB, 0x00, 0x01, 0xe4, 0xe4, 0x44, 0x88, 0x40),
|
||||
(0xED,
|
||||
0xff, 0x10, 0xaf, 0x76,
|
||||
0x54, 0x2b, 0xcf, 0xff,
|
||||
0xff, 0xfc, 0xb2, 0x45,
|
||||
0x67, 0xfa, 0x01, 0xff),
|
||||
(0xEF, 0x08, 0x08, 0x08, 0x45, 0x3f, 0x54),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
)
|
||||
)
|
||||
|
9
esphome/components/mipi_rgb/models/rpi.py
Normal file
9
esphome/components/mipi_rgb/models/rpi.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
from esphome.config_validation import UNDEFINED
|
||||
|
||||
# A driver chip for Raspberry Pi MIPI RGB displays. These require no init sequence
|
||||
DriverChip(
|
||||
"RPI",
|
||||
swap_xy=UNDEFINED,
|
||||
initsequence=(),
|
||||
)
|
214
esphome/components/mipi_rgb/models/st7701s.py
Normal file
214
esphome/components/mipi_rgb/models/st7701s.py
Normal file
@@ -0,0 +1,214 @@
|
||||
from esphome.components.mipi import (
|
||||
MADCTL,
|
||||
MADCTL_ML,
|
||||
MADCTL_XFLIP,
|
||||
MODE_BGR,
|
||||
DriverChip,
|
||||
)
|
||||
from esphome.config_validation import UNDEFINED
|
||||
from esphome.const import CONF_COLOR_ORDER, CONF_HEIGHT, CONF_MIRROR_X, CONF_MIRROR_Y
|
||||
|
||||
SDIR_CMD = 0xC7
|
||||
|
||||
|
||||
class ST7701S(DriverChip):
|
||||
# The ST7701s does not use the standard MADCTL bits for x/y mirroring
|
||||
def add_madctl(self, sequence: list, config: dict):
|
||||
transform = self.get_transform(config)
|
||||
madctl = 0x00
|
||||
if config[CONF_COLOR_ORDER] == MODE_BGR:
|
||||
madctl |= 0x08
|
||||
if transform.get(CONF_MIRROR_Y):
|
||||
madctl |= MADCTL_ML
|
||||
sequence.append((MADCTL, madctl))
|
||||
sdir = 0
|
||||
if transform.get(CONF_MIRROR_X):
|
||||
sdir |= 0x04
|
||||
madctl |= MADCTL_XFLIP
|
||||
sequence.append((SDIR_CMD, sdir))
|
||||
return madctl
|
||||
|
||||
@property
|
||||
def transforms(self) -> set[str]:
|
||||
"""
|
||||
The ST7701 never supports axis swapping, and mirroring the y-axis only works for full height.
|
||||
"""
|
||||
if self.get_default(CONF_HEIGHT) != 864:
|
||||
return {CONF_MIRROR_X}
|
||||
return {CONF_MIRROR_X, CONF_MIRROR_Y}
|
||||
|
||||
|
||||
# fmt: off
|
||||
st7701s = ST7701S(
|
||||
"ST7701S",
|
||||
width=480,
|
||||
height=864,
|
||||
swap_xy=UNDEFINED,
|
||||
hsync_front_porch=20,
|
||||
hsync_back_porch=10,
|
||||
hsync_pulse_width=10,
|
||||
vsync_front_porch=10,
|
||||
vsync_back_porch=10,
|
||||
vsync_pulse_width=10,
|
||||
pclk_frequency="16MHz",
|
||||
pclk_inverted=True,
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10), # Page 0
|
||||
(0xC0, 0x3B, 0x00), (0xC1, 0x0D, 0x02), (0xC2, 0x31, 0x05),
|
||||
(0xB0, 0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18,),
|
||||
(0xB1, 0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08, 0x08, 0x22, 0x04, 0x11, 0x11, 0xA9, 0x32, 0x18,),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11), # page 1
|
||||
(0xB0, 0x60), (0xB1, 0x32), (0xB2, 0x07), (0xB3, 0x80), (0xB5, 0x49), (0xB7, 0x85), (0xB8, 0x21), (0xC1, 0x78),
|
||||
(0xC2, 0x78), (0xE0, 0x00, 0x1B, 0x02),
|
||||
(0xE1, 0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44),
|
||||
(0xE2, 0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00),
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x11),
|
||||
(0xE4, 0x44, 0x44),
|
||||
(0xE5, 0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0, 0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0,),
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x11), (0xE7, 0x44, 0x44),
|
||||
(0xE8, 0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, 0x0F, 0xEE, 0xD8, 0xA0,),
|
||||
(0xEB, 0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40), (0xEC, 0x3C, 0x00),
|
||||
(0xED, 0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA,),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), # Page 3
|
||||
(0xE5, 0xE4),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10), # Page 0
|
||||
(0xCD, 0x08),
|
||||
)
|
||||
)
|
||||
|
||||
st7701s.extend(
|
||||
"MAKERFABS-4",
|
||||
width=480,
|
||||
height=480,
|
||||
color_order="RGB",
|
||||
invert_colors=True,
|
||||
pixel_mode="18bit",
|
||||
cs_pin=1,
|
||||
de_pin={
|
||||
"number": 45,
|
||||
"ignore_strapping_warning": True
|
||||
},
|
||||
hsync_pin=5,
|
||||
vsync_pin=4,
|
||||
pclk_pin=21,
|
||||
data_pins={
|
||||
"red": [39, 40, 41, 42, 2],
|
||||
"green": [0, 9, 14, 47, 48, 3],
|
||||
"blue": [6, 7, 15, 16, 8]
|
||||
}
|
||||
)
|
||||
|
||||
st7701s.extend(
|
||||
"SEEED-INDICATOR-D1",
|
||||
width=480,
|
||||
height=480,
|
||||
mirror_x=True,
|
||||
mirror_y=True,
|
||||
invert_colors=True,
|
||||
pixel_mode="18bit",
|
||||
spi_mode="MODE3",
|
||||
data_rate="2MHz",
|
||||
hsync_front_porch=10,
|
||||
hsync_pulse_width=8,
|
||||
hsync_back_porch=50,
|
||||
vsync_front_porch=10,
|
||||
vsync_pulse_width=8,
|
||||
vsync_back_porch=20,
|
||||
cs_pin={"pca9554": None, "number": 4},
|
||||
de_pin=18,
|
||||
hsync_pin=16,
|
||||
vsync_pin=17,
|
||||
pclk_pin=21,
|
||||
pclk_inverted=False,
|
||||
data_pins={
|
||||
"red": [4, 3, 2, 1, 0],
|
||||
"green": [10, 9, 8, 7, 6, 5],
|
||||
"blue": [15, 14, 13, 12, 11]
|
||||
},
|
||||
)
|
||||
|
||||
st7701s.extend(
|
||||
"UEDX48480021-MD80ET",
|
||||
width=480,
|
||||
height=480,
|
||||
pixel_mode="18bit",
|
||||
cs_pin=18,
|
||||
reset_pin=8,
|
||||
de_pin=17,
|
||||
vsync_pin={"number": 3, "ignore_strapping_warning": True},
|
||||
hsync_pin={"number": 46, "ignore_strapping_warning": True},
|
||||
pclk_pin=9,
|
||||
data_pins={
|
||||
"red": [40, 41, 42, 2, 1],
|
||||
"green": [21, 47, 48, 45, 38, 39],
|
||||
"blue": [10, 11, {"number": 12, "allow_other_uses": True}, {"number": 13, "allow_other_uses": True}, 14]
|
||||
},
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xEF, 0x08), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
|
||||
(0xC0, 0x3B, 0x00), (0xC1, 0x0B, 0x02), (0xC2, 0x07, 0x02), (0xC7, 0x00), (0xCC, 0x10), (0xCD, 0x08),
|
||||
(0xB0, 0x00, 0x11, 0x16, 0x0E, 0x11, 0x06, 0x05, 0x09, 0x08, 0x21, 0x06, 0x13, 0x10, 0x29, 0x31, 0x18),
|
||||
(0xB1, 0x00, 0x11, 0x16, 0x0E, 0x11, 0x07, 0x05, 0x09, 0x09, 0x21, 0x05, 0x13, 0x11, 0x2A, 0x31, 0x18),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11),
|
||||
(0xB0, 0x6D), (0xB1, 0x37), (0xB2, 0x8B), (0xB3, 0x80), (0xB5, 0x43), (0xB7, 0x85),
|
||||
(0xB8, 0x20), (0xC0, 0x09), (0xC1, 0x78), (0xC2, 0x78), (0xD0, 0x88),
|
||||
(0xE0, 0x00, 0x00, 0x02),
|
||||
(0xE1, 0x03, 0xA0, 0x00, 0x00, 0x04, 0xA0, 0x00, 0x00, 0x00, 0x20, 0x20),
|
||||
(0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x00),
|
||||
(0xE4, 0x22, 0x00),
|
||||
(0xE5, 0x05, 0xEC, 0xF6, 0xCA, 0x07, 0xEE, 0xF6, 0xCA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x00),
|
||||
(0xE7, 0x22, 0x00),
|
||||
(0xE8, 0x06, 0xED, 0xF6, 0xCA, 0x08, 0xEF, 0xF6, 0xCA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xE9, 0x36, 0x00),
|
||||
(0xEB, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00),
|
||||
(0xED, 0xFF, 0xFF, 0xFF, 0xBA, 0x0A, 0xFF, 0x45, 0xFF, 0xFF, 0x54, 0xFF, 0xA0, 0xAB, 0xFF, 0xFF, 0xFF),
|
||||
(0xEF, 0x08, 0x08, 0x08, 0x45, 0x3F, 0x54),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xE8, 0x00, 0x0E), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x00),
|
||||
(0x11, 0x00), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xE8, 0x00, 0x0C),
|
||||
(0xE8, 0x00, 0x00), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x00)
|
||||
)
|
||||
)
|
||||
|
||||
st7701s.extend(
|
||||
"ZX2D10GE01R-V4848",
|
||||
width=480,
|
||||
height=480,
|
||||
pixel_mode="18bit",
|
||||
cs_pin=21,
|
||||
de_pin=39,
|
||||
vsync_pin=48,
|
||||
hsync_pin=40,
|
||||
pclk_pin={"number": 45, "ignore_strapping_warning": True},
|
||||
pclk_frequency="15MHz",
|
||||
pclk_inverted=True,
|
||||
hsync_pulse_width=10,
|
||||
hsync_back_porch=10,
|
||||
hsync_front_porch=10,
|
||||
vsync_pulse_width=2,
|
||||
vsync_back_porch=12,
|
||||
vsync_front_porch=14,
|
||||
data_pins={
|
||||
"red": [10, 16, 9, 15, 46],
|
||||
"green": [8, 13, 18, 12, 11, 17],
|
||||
"blue": [{"number": 47, "allow_other_uses": True}, {"number": 41, "allow_other_uses": True}, 0, 42, 14]
|
||||
},
|
||||
initsequence=(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xEF, 0x08), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x10), (0xC0, 0x3B, 0x00),
|
||||
(0xC1, 0x0B, 0x02), (0xC2, 0x07, 0x02), (0xCC, 0x10), (0xCD, 0x08),
|
||||
(0xB0, 0x00, 0x11, 0x16, 0x0e, 0x11, 0x06, 0x05, 0x09, 0x08, 0x21, 0x06, 0x13, 0x10, 0x29, 0x31, 0x18),
|
||||
(0xB1, 0x00, 0x11, 0x16, 0x0e, 0x11, 0x07, 0x05, 0x09, 0x09, 0x21, 0x05, 0x13, 0x11, 0x2a, 0x31, 0x18),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11), (0xB0, 0x6d), (0xB1, 0x37), (0xB2, 0x81), (0xB3, 0x80), (0xB5, 0x43),
|
||||
(0xB7, 0x85), (0xB8, 0x20), (0xC1, 0x78), (0xC2, 0x78), (0xD0, 0x88), (0xE0, 0x00, 0x00, 0x02),
|
||||
(0xE1, 0x03, 0xA0, 0x00, 0x00, 0x04, 0xA0, 0x00, 0x00, 0x00, 0x20, 0x20),
|
||||
(0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xE3, 0x00, 0x00, 0x11, 0x00), (0xE4, 0x22, 0x00),
|
||||
(0xE5, 0x05, 0xEC, 0xA0, 0xA0, 0x07, 0xEE, 0xA0, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xE6, 0x00, 0x00, 0x11, 0x00), (0xE7, 0x22, 0x00),
|
||||
(0xE8, 0x06, 0xED, 0xA0, 0xA0, 0x08, 0xEF, 0xA0, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xEB, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00),
|
||||
(0xED, 0xFF, 0xFF, 0xFF, 0xBA, 0x0A, 0xBF, 0x45, 0xFF, 0xFF, 0x54, 0xFB, 0xA0, 0xAB, 0xFF, 0xFF, 0xFF),
|
||||
(0xEF, 0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F),
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13), (0xEF, 0x08), (0xFF, 0x77, 0x01, 0x00, 0x00, 0x00)
|
||||
)
|
||||
)
|
64
esphome/components/mipi_rgb/models/waveshare.py
Normal file
64
esphome/components/mipi_rgb/models/waveshare.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
from esphome.config_validation import UNDEFINED
|
||||
|
||||
from .st7701s import st7701s
|
||||
|
||||
wave_4_3 = DriverChip(
|
||||
"ESP32-S3-TOUCH-LCD-4.3",
|
||||
swap_xy=UNDEFINED,
|
||||
initsequence=(),
|
||||
width=800,
|
||||
height=480,
|
||||
pclk_frequency="16MHz",
|
||||
reset_pin={"ch422g": None, "number": 3},
|
||||
enable_pin={"ch422g": None, "number": 2},
|
||||
de_pin=5,
|
||||
hsync_pin={"number": 46, "ignore_strapping_warning": True},
|
||||
vsync_pin={"number": 3, "ignore_strapping_warning": True},
|
||||
pclk_pin=7,
|
||||
pclk_inverted=True,
|
||||
hsync_front_porch=210,
|
||||
hsync_pulse_width=30,
|
||||
hsync_back_porch=30,
|
||||
vsync_front_porch=4,
|
||||
vsync_pulse_width=4,
|
||||
vsync_back_porch=4,
|
||||
data_pins={
|
||||
"red": [1, 2, 42, 41, 40],
|
||||
"green": [39, 0, 45, 48, 47, 21],
|
||||
"blue": [14, 38, 18, 17, 10],
|
||||
},
|
||||
)
|
||||
wave_4_3.extend(
|
||||
"ESP32-S3-TOUCH-LCD-7-800X480",
|
||||
enable_pin=[{"ch422g": None, "number": 2}, {"ch422g": None, "number": 6}],
|
||||
hsync_back_porch=8,
|
||||
hsync_front_porch=8,
|
||||
hsync_pulse_width=4,
|
||||
vsync_back_porch=16,
|
||||
vsync_front_porch=16,
|
||||
vsync_pulse_width=4,
|
||||
)
|
||||
|
||||
st7701s.extend(
|
||||
"WAVESHARE-4-480x480",
|
||||
data_rate="2MHz",
|
||||
spi_mode="MODE3",
|
||||
color_order="BGR",
|
||||
pixel_mode="18bit",
|
||||
width=480,
|
||||
height=480,
|
||||
invert_colors=True,
|
||||
cs_pin=42,
|
||||
de_pin=40,
|
||||
hsync_pin=38,
|
||||
vsync_pin=39,
|
||||
pclk_pin=41,
|
||||
pclk_frequency="12MHz",
|
||||
pclk_inverted=False,
|
||||
data_pins={
|
||||
"red": [46, 3, 8, 18, 17],
|
||||
"green": [14, 13, 12, 11, 10, 9],
|
||||
"blue": [5, 45, 48, 47, 21],
|
||||
},
|
||||
)
|
@@ -58,8 +58,13 @@ void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
|
||||
if (this->sensor_->get_force_update())
|
||||
root[MQTT_FORCE_UPDATE] = true;
|
||||
|
||||
if (this->sensor_->get_state_class() != STATE_CLASS_NONE)
|
||||
root[MQTT_STATE_CLASS] = state_class_to_string(this->sensor_->get_state_class());
|
||||
if (this->sensor_->get_state_class() != STATE_CLASS_NONE) {
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
root[MQTT_STATE_CLASS] = (const __FlashStringHelper *) state_class_to_string(this->sensor_->get_state_class());
|
||||
#else
|
||||
root[MQTT_STATE_CLASS] = LOG_STR_ARG(state_class_to_string(this->sensor_->get_state_class()));
|
||||
#endif
|
||||
}
|
||||
|
||||
config.command_topic = false;
|
||||
}
|
||||
|
@@ -19,13 +19,14 @@ void MS5611Component::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(100); // NOLINT
|
||||
for (uint8_t offset = 0; offset < 6; offset++) {
|
||||
if (!this->read_byte_16(MS5611_CMD_READ_PROM + (offset * 2), &this->prom_[offset])) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
this->set_timeout(100, [this]() {
|
||||
for (uint8_t offset = 0; offset < 6; offset++) {
|
||||
if (!this->read_byte_16(MS5611_CMD_READ_PROM + (offset * 2), &this->prom_[offset])) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
void MS5611Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MS5611:");
|
||||
|
@@ -11,7 +11,10 @@ static const char *const TAG = "nextion.upload";
|
||||
bool Nextion::upload_end_(bool successful) {
|
||||
if (successful) {
|
||||
ESP_LOGD(TAG, "Upload successful");
|
||||
delay(1500); // NOLINT
|
||||
for (uint8_t i = 0; i <= 5; i++) {
|
||||
delay(1000); // NOLINT
|
||||
App.feed_wdt(); // Feed the watchdog timer.
|
||||
}
|
||||
App.safe_reboot();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Upload failed");
|
||||
|
@@ -14,6 +14,7 @@ from esphome.const import (
|
||||
|
||||
CODEOWNERS = ["@Mat931"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["gpio_expander"]
|
||||
MULTI_CONF = True
|
||||
pca6416a_ns = cg.esphome_ns.namespace("pca6416a")
|
||||
|
||||
|
@@ -51,6 +51,11 @@ void PCA6416AComponent::setup() {
|
||||
this->status_has_error());
|
||||
}
|
||||
|
||||
void PCA6416AComponent::loop() {
|
||||
// Invalidate cache at the start of each loop
|
||||
this->reset_pin_cache_();
|
||||
}
|
||||
|
||||
void PCA6416AComponent::dump_config() {
|
||||
if (this->has_pullup_) {
|
||||
ESP_LOGCONFIG(TAG, "PCAL6416A:");
|
||||
@@ -63,15 +68,25 @@ void PCA6416AComponent::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
bool PCA6416AComponent::digital_read(uint8_t pin) {
|
||||
uint8_t bit = pin % 8;
|
||||
bool PCA6416AComponent::digital_read_hw(uint8_t pin) {
|
||||
uint8_t reg_addr = pin < 8 ? PCA6416A_INPUT0 : PCA6416A_INPUT1;
|
||||
uint8_t value = 0;
|
||||
this->read_register_(reg_addr, &value);
|
||||
return value & (1 << bit);
|
||||
if (!this->read_register_(reg_addr, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the appropriate part of input_mask_
|
||||
if (pin < 8) {
|
||||
this->input_mask_ = (this->input_mask_ & 0xFF00) | value;
|
||||
} else {
|
||||
this->input_mask_ = (this->input_mask_ & 0x00FF) | (uint16_t(value) << 8);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PCA6416AComponent::digital_write(uint8_t pin, bool value) {
|
||||
bool PCA6416AComponent::digital_read_cache(uint8_t pin) { return this->input_mask_ & (1 << pin); }
|
||||
|
||||
void PCA6416AComponent::digital_write_hw(uint8_t pin, bool value) {
|
||||
uint8_t reg_addr = pin < 8 ? PCA6416A_OUTPUT0 : PCA6416A_OUTPUT1;
|
||||
this->update_register_(pin, value, reg_addr);
|
||||
}
|
||||
|
@@ -3,20 +3,20 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/gpio_expander/cached_gpio.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pca6416a {
|
||||
|
||||
class PCA6416AComponent : public Component, public i2c::I2CDevice {
|
||||
class PCA6416AComponent : public Component,
|
||||
public i2c::I2CDevice,
|
||||
public gpio_expander::CachedGpioExpander<uint8_t, 16> {
|
||||
public:
|
||||
PCA6416AComponent() = default;
|
||||
|
||||
/// Check i2c availability and setup masks
|
||||
void setup() override;
|
||||
/// Helper function to read the value of a pin.
|
||||
bool digital_read(uint8_t pin);
|
||||
/// Helper function to write the value of a pin.
|
||||
void digital_write(uint8_t pin, bool value);
|
||||
void loop() override;
|
||||
/// Helper function to set the pin mode of a pin.
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
|
||||
@@ -25,6 +25,11 @@ class PCA6416AComponent : public Component, public i2c::I2CDevice {
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
// Virtual methods from CachedGpioExpander
|
||||
bool digital_read_hw(uint8_t pin) override;
|
||||
bool digital_read_cache(uint8_t pin) override;
|
||||
void digital_write_hw(uint8_t pin, bool value) override;
|
||||
|
||||
bool read_register_(uint8_t reg, uint8_t *value);
|
||||
bool write_register_(uint8_t reg, uint8_t value);
|
||||
void update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr);
|
||||
@@ -32,6 +37,8 @@ class PCA6416AComponent : public Component, public i2c::I2CDevice {
|
||||
/// The mask to write as output state - 1 means HIGH, 0 means LOW
|
||||
uint8_t output_0_{0x00};
|
||||
uint8_t output_1_{0x00};
|
||||
/// Cache for input values (16-bit combined for both banks)
|
||||
uint16_t input_mask_{0x00};
|
||||
/// Storage for last I2C error seen
|
||||
esphome::i2c::ErrorCode last_error_;
|
||||
/// Only the PCAL6416A has pull-up resistors
|
||||
|
@@ -11,7 +11,8 @@ from esphome.const import (
|
||||
CONF_OUTPUT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@hwstar", "@clydebarrow"]
|
||||
CODEOWNERS = ["@hwstar", "@clydebarrow", "@bdraco"]
|
||||
AUTO_LOAD = ["gpio_expander"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
CONF_PIN_COUNT = "pin_count"
|
||||
|
@@ -37,10 +37,9 @@ void PCA9554Component::setup() {
|
||||
}
|
||||
|
||||
void PCA9554Component::loop() {
|
||||
// The read_inputs_() method will cache the input values from the chip.
|
||||
this->read_inputs_();
|
||||
// Clear all the previously read flags.
|
||||
this->was_previously_read_ = 0x00;
|
||||
// Invalidate the cache at the start of each loop.
|
||||
// The actual read will happen on demand when digital_read() is called
|
||||
this->reset_pin_cache_();
|
||||
}
|
||||
|
||||
void PCA9554Component::dump_config() {
|
||||
@@ -54,21 +53,17 @@ void PCA9554Component::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
bool PCA9554Component::digital_read(uint8_t pin) {
|
||||
// Note: We want to try and avoid doing any I2C bus read transactions here
|
||||
// to conserve I2C bus bandwidth. So what we do is check to see if we
|
||||
// have seen a read during the time esphome is running this loop. If we have,
|
||||
// we do an I2C bus transaction to get the latest value. If we haven't
|
||||
// we return a cached value which was read at the time loop() was called.
|
||||
if (this->was_previously_read_ & (1 << pin))
|
||||
this->read_inputs_(); // Force a read of a new value
|
||||
// Indicate we saw a read request for this pin in case a
|
||||
// read happens later in the same loop.
|
||||
this->was_previously_read_ |= (1 << pin);
|
||||
bool PCA9554Component::digital_read_hw(uint8_t pin) {
|
||||
// Read all pins from hardware into input_mask_
|
||||
return this->read_inputs_(); // Return true if I2C read succeeded, false on error
|
||||
}
|
||||
|
||||
bool PCA9554Component::digital_read_cache(uint8_t pin) {
|
||||
// Return the cached pin state from input_mask_
|
||||
return this->input_mask_ & (1 << pin);
|
||||
}
|
||||
|
||||
void PCA9554Component::digital_write(uint8_t pin, bool value) {
|
||||
void PCA9554Component::digital_write_hw(uint8_t pin, bool value) {
|
||||
if (value) {
|
||||
this->output_mask_ |= (1 << pin);
|
||||
} else {
|
||||
@@ -127,8 +122,7 @@ bool PCA9554Component::write_register_(uint8_t reg, uint16_t value) {
|
||||
|
||||
float PCA9554Component::get_setup_priority() const { return setup_priority::IO; }
|
||||
|
||||
// Run our loop() method very early in the loop, so that we cache read values before
|
||||
// before other components call our digital_read() method.
|
||||
// Run our loop() method early to invalidate cache before any other components access the pins
|
||||
float PCA9554Component::get_loop_priority() const { return 9.0f; } // Just after WIFI
|
||||
|
||||
void PCA9554GPIOPin::setup() { pin_mode(flags_); }
|
||||
|
@@ -3,22 +3,21 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/gpio_expander/cached_gpio.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pca9554 {
|
||||
|
||||
class PCA9554Component : public Component, public i2c::I2CDevice {
|
||||
class PCA9554Component : public Component,
|
||||
public i2c::I2CDevice,
|
||||
public gpio_expander::CachedGpioExpander<uint16_t, 16> {
|
||||
public:
|
||||
PCA9554Component() = default;
|
||||
|
||||
/// Check i2c availability and setup masks
|
||||
void setup() override;
|
||||
/// Poll for input changes periodically
|
||||
/// Invalidate cache at start of each loop
|
||||
void loop() override;
|
||||
/// Helper function to read the value of a pin.
|
||||
bool digital_read(uint8_t pin);
|
||||
/// Helper function to write the value of a pin.
|
||||
void digital_write(uint8_t pin, bool value);
|
||||
/// Helper function to set the pin mode of a pin.
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
|
||||
@@ -32,9 +31,13 @@ class PCA9554Component : public Component, public i2c::I2CDevice {
|
||||
|
||||
protected:
|
||||
bool read_inputs_();
|
||||
|
||||
bool write_register_(uint8_t reg, uint16_t value);
|
||||
|
||||
// Virtual methods from CachedGpioExpander
|
||||
bool digital_read_hw(uint8_t pin) override;
|
||||
bool digital_read_cache(uint8_t pin) override;
|
||||
void digital_write_hw(uint8_t pin, bool value) override;
|
||||
|
||||
/// number of bits the expander has
|
||||
size_t pin_count_{8};
|
||||
/// width of registers
|
||||
@@ -45,8 +48,6 @@ class PCA9554Component : public Component, public i2c::I2CDevice {
|
||||
uint16_t output_mask_{0x00};
|
||||
/// The state of the actual input pin states - 1 means HIGH, 0 means LOW
|
||||
uint16_t input_mask_{0x00};
|
||||
/// Flags to check if read previously during this loop
|
||||
uint16_t was_previously_read_ = {0x00};
|
||||
/// Storage for last I2C error seen
|
||||
esphome::i2c::ErrorCode last_error_;
|
||||
};
|
||||
|
@@ -11,6 +11,7 @@ from esphome.const import (
|
||||
CONF_OUTPUT,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["gpio_expander"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
|
@@ -16,6 +16,10 @@ void PCF8574Component::setup() {
|
||||
this->write_gpio_();
|
||||
this->read_gpio_();
|
||||
}
|
||||
void PCF8574Component::loop() {
|
||||
// Invalidate the cache at the start of each loop
|
||||
this->reset_pin_cache_();
|
||||
}
|
||||
void PCF8574Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "PCF8574:");
|
||||
LOG_I2C_DEVICE(this)
|
||||
@@ -24,17 +28,19 @@ void PCF8574Component::dump_config() {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
}
|
||||
bool PCF8574Component::digital_read(uint8_t pin) {
|
||||
this->read_gpio_();
|
||||
return this->input_mask_ & (1 << pin);
|
||||
bool PCF8574Component::digital_read_hw(uint8_t pin) {
|
||||
// Read all pins from hardware into input_mask_
|
||||
return this->read_gpio_(); // Return true if I2C read succeeded, false on error
|
||||
}
|
||||
void PCF8574Component::digital_write(uint8_t pin, bool value) {
|
||||
|
||||
bool PCF8574Component::digital_read_cache(uint8_t pin) { return this->input_mask_ & (1 << pin); }
|
||||
|
||||
void PCF8574Component::digital_write_hw(uint8_t pin, bool value) {
|
||||
if (value) {
|
||||
this->output_mask_ |= (1 << pin);
|
||||
} else {
|
||||
this->output_mask_ &= ~(1 << pin);
|
||||
}
|
||||
|
||||
this->write_gpio_();
|
||||
}
|
||||
void PCF8574Component::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||
@@ -91,6 +97,9 @@ bool PCF8574Component::write_gpio_() {
|
||||
}
|
||||
float PCF8574Component::get_setup_priority() const { return setup_priority::IO; }
|
||||
|
||||
// Run our loop() method early to invalidate cache before any other components access the pins
|
||||
float PCF8574Component::get_loop_priority() const { return 9.0f; } // Just after WIFI
|
||||
|
||||
void PCF8574GPIOPin::setup() { pin_mode(flags_); }
|
||||
void PCF8574GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||
bool PCF8574GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||
|
@@ -3,11 +3,16 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/gpio_expander/cached_gpio.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pcf8574 {
|
||||
|
||||
class PCF8574Component : public Component, public i2c::I2CDevice {
|
||||
// PCF8574(8 pins)/PCF8575(16 pins) always read/write all pins in a single I2C transaction
|
||||
// so we use uint16_t as bank type to ensure all pins are in one bank and cached together
|
||||
class PCF8574Component : public Component,
|
||||
public i2c::I2CDevice,
|
||||
public gpio_expander::CachedGpioExpander<uint16_t, 16> {
|
||||
public:
|
||||
PCF8574Component() = default;
|
||||
|
||||
@@ -15,20 +20,22 @@ class PCF8574Component : public Component, public i2c::I2CDevice {
|
||||
|
||||
/// Check i2c availability and setup masks
|
||||
void setup() override;
|
||||
/// Helper function to read the value of a pin.
|
||||
bool digital_read(uint8_t pin);
|
||||
/// Helper function to write the value of a pin.
|
||||
void digital_write(uint8_t pin, bool value);
|
||||
/// Invalidate cache at start of each loop
|
||||
void loop() override;
|
||||
/// Helper function to set the pin mode of a pin.
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
|
||||
float get_setup_priority() const override;
|
||||
float get_loop_priority() const override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
bool read_gpio_();
|
||||
bool digital_read_hw(uint8_t pin) override;
|
||||
bool digital_read_cache(uint8_t pin) override;
|
||||
void digital_write_hw(uint8_t pin, bool value) override;
|
||||
|
||||
bool read_gpio_();
|
||||
bool write_gpio_();
|
||||
|
||||
/// Mask for the pin mode - 1 means output, 0 means input
|
||||
|
@@ -68,7 +68,7 @@ bool PI4IOE5V6408Component::read_gpio_outputs_() {
|
||||
|
||||
uint8_t data;
|
||||
if (!this->read_byte(PI4IOE5V6408_REGISTER_OUT_SET, &data)) {
|
||||
this->status_set_warning("Failed to read output register");
|
||||
this->status_set_warning(LOG_STR("Failed to read output register"));
|
||||
return false;
|
||||
}
|
||||
this->output_mask_ = data;
|
||||
@@ -82,7 +82,7 @@ bool PI4IOE5V6408Component::read_gpio_modes_() {
|
||||
|
||||
uint8_t data;
|
||||
if (!this->read_byte(PI4IOE5V6408_REGISTER_IO_DIR, &data)) {
|
||||
this->status_set_warning("Failed to read GPIO modes");
|
||||
this->status_set_warning(LOG_STR("Failed to read GPIO modes"));
|
||||
return false;
|
||||
}
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
@@ -99,7 +99,7 @@ bool PI4IOE5V6408Component::digital_read_hw(uint8_t pin) {
|
||||
|
||||
uint8_t data;
|
||||
if (!this->read_byte(PI4IOE5V6408_REGISTER_IN_STATE, &data)) {
|
||||
this->status_set_warning("Failed to read GPIO state");
|
||||
this->status_set_warning(LOG_STR("Failed to read GPIO state"));
|
||||
return false;
|
||||
}
|
||||
this->input_mask_ = data;
|
||||
@@ -117,7 +117,7 @@ void PI4IOE5V6408Component::digital_write_hw(uint8_t pin, bool value) {
|
||||
this->output_mask_ &= ~(1 << pin);
|
||||
}
|
||||
if (!this->write_byte(PI4IOE5V6408_REGISTER_OUT_SET, this->output_mask_)) {
|
||||
this->status_set_warning("Failed to write output register");
|
||||
this->status_set_warning(LOG_STR("Failed to write output register"));
|
||||
return;
|
||||
}
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
@@ -131,15 +131,15 @@ bool PI4IOE5V6408Component::write_gpio_modes_() {
|
||||
return false;
|
||||
|
||||
if (!this->write_byte(PI4IOE5V6408_REGISTER_IO_DIR, this->mode_mask_)) {
|
||||
this->status_set_warning("Failed to write GPIO modes");
|
||||
this->status_set_warning(LOG_STR("Failed to write GPIO modes"));
|
||||
return false;
|
||||
}
|
||||
if (!this->write_byte(PI4IOE5V6408_REGISTER_PULL_SELECT, this->pull_up_down_mask_)) {
|
||||
this->status_set_warning("Failed to write GPIO pullup/pulldown");
|
||||
this->status_set_warning(LOG_STR("Failed to write GPIO pullup/pulldown"));
|
||||
return false;
|
||||
}
|
||||
if (!this->write_byte(PI4IOE5V6408_REGISTER_PULL_ENABLE, this->pull_enable_mask_)) {
|
||||
this->status_set_warning("Failed to write GPIO pull enable");
|
||||
this->status_set_warning(LOG_STR("Failed to write GPIO pull enable"));
|
||||
return false;
|
||||
}
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user