diff --git a/.claude/skills/pr-workflow/SKILL.md b/.claude/skills/pr-workflow/SKILL.md new file mode 100644 index 0000000000..4ec2551804 --- /dev/null +++ b/.claude/skills/pr-workflow/SKILL.md @@ -0,0 +1,96 @@ +--- +name: pr-workflow +description: Create pull requests for esphome. Use when creating PRs, submitting changes, or preparing contributions. +allowed-tools: Read, Bash, Glob, Grep +--- + +# ESPHome PR Workflow + +When creating a pull request for esphome, follow these steps: + +## 1. Create Branch from Upstream + +Always base your branch on **upstream** (not origin/fork) to ensure you have the latest code: + +```bash +git fetch upstream +git checkout -b upstream/dev +``` + +## 2. Read the PR Template + +Before creating a PR, read `.github/PULL_REQUEST_TEMPLATE.md` to understand required fields. + +## 3. Create the PR + +Use `gh pr create` with the **full template** filled in. Never skip or abbreviate sections. + +Required fields: +- **What does this implement/fix?**: Brief description of changes +- **Types of changes**: Check ONE appropriate box (Bugfix, New feature, Breaking change, etc.) +- **Related issue**: Use `fixes ` syntax if applicable +- **Pull request in esphome-docs**: Link if docs are needed +- **Test Environment**: Check platforms you tested on +- **Example config.yaml**: Include working example YAML +- **Checklist**: Verify code is tested and tests added + +## 4. Example PR Body + +```markdown +# What does this implement/fix? + + + +## Types of changes + +- [ ] Bugfix (non-breaking change which fixes an issue) +- [x] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Developer breaking change (an API change that could break external components) +- [ ] Code quality improvements to existing code or addition of tests +- [ ] Other + +**Related issue or feature (if applicable):** + +- fixes https://github.com/esphome/esphome/issues/XXX + +**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** + +- esphome/esphome-docs#XXX + +## Test Environment + +- [x] ESP32 +- [x] ESP32 IDF +- [ ] ESP8266 +- [ ] RP2040 +- [ ] BK72xx +- [ ] RTL87xx +- [ ] LN882x +- [ ] nRF52840 + +## Example entry for `config.yaml`: + +```yaml +# Example config.yaml +component_name: + id: my_component + option: value +``` + +## Checklist: + - [x] The code change is tested and works locally. + - [x] Tests have been added to verify that the new code works (under `tests/` folder). + +If user exposed functionality or configuration variables are added/changed: + - [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs). +``` + +## 5. Push and Create PR + +```bash +git push -u origin +gh pr create --repo esphome/esphome --base dev --title "[component] Brief description" +``` + +Title should be prefixed with the component name in brackets, e.g. `[safe_mode] Add feature`. diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index 75586fd854..370c8bcc46 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -22,7 +22,7 @@ runs: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: venv # yamllint disable-line rule:line-length diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 434aa388f7..81d3c826d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: venv # yamllint disable-line rule:line-length @@ -157,7 +157,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} - name: Save Python virtual environment cache if: github.ref == 'refs/heads/dev' - uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: venv key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} @@ -193,7 +193,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Restore components graph cache - uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: .temp/components_graph.json key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} @@ -223,7 +223,7 @@ jobs: echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT - name: Save components graph cache if: github.ref == 'refs/heads/dev' - uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: .temp/components_graph.json key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} @@ -245,7 +245,7 @@ jobs: python-version: "3.13" - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: venv key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} @@ -334,14 +334,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} @@ -413,14 +413,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} @@ -502,14 +502,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} @@ -735,7 +735,7 @@ jobs: - name: Restore cached memory analysis id: cache-memory-analysis if: steps.check-script.outputs.skip != 'true' - uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: memory-analysis-target.json key: ${{ steps.cache-key.outputs.cache-key }} @@ -759,7 +759,7 @@ jobs: - name: Cache platformio if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' - uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: ~/.platformio key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }} @@ -800,7 +800,7 @@ jobs: - name: Save memory analysis to cache if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success' - uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: memory-analysis-target.json key: ${{ steps.cache-key.outputs.cache-key }} @@ -847,7 +847,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Cache platformio - uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: ~/.platformio key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }} diff --git a/esphome/components/mipi_dsi/models/guition.py b/esphome/components/mipi_dsi/models/guition.py index cd566633f9..db13c7f6cc 100644 --- a/esphome/components/mipi_dsi/models/guition.py +++ b/esphome/components/mipi_dsi/models/guition.py @@ -101,4 +101,225 @@ DriverChip( (0xFF, 0x77, 0x01, 0x00, 0x00, 0x00), ] ) + +# jc8012P4A1 Driver Configuration (jd9365) +# Using parameters from esp_lcd_jd9365.h and the working full init sequence +# ---------------------------------------------------------------------------------------------------------------------- +# * Resolution: 800x1280 +# * PCLK Frequency: 60 MHz +# * DSI Lane Bit Rate: 1 Gbps (using 2-Lane DSI configuration) +# * Horizontal Timing (hsync_pulse_width=20, hsync_back_porch=20, hsync_front_porch=40) +# * Vertical Timing (vsync_pulse_width=4, vsync_back_porch=8, vsync_front_porch=20) +# ---------------------------------------------------------------------------------------------------------------------- +DriverChip( + "JC8012P4A1", + width=800, + height=1280, + hsync_back_porch=20, + hsync_pulse_width=20, + hsync_front_porch=40, + vsync_back_porch=8, + vsync_pulse_width=4, + vsync_front_porch=20, + pclk_frequency="60MHz", + lane_bit_rate="1Gbps", + swap_xy=cv.UNDEFINED, + color_order="RGB", + reset_pin=27, + initsequence=[ + (0xE0, 0x00), + (0xE1, 0x93), + (0xE2, 0x65), + (0xE3, 0xF8), + (0x80, 0x01), + (0xE0, 0x01), + (0x00, 0x00), + (0x01, 0x39), + (0x03, 0x10), + (0x04, 0x41), + (0x0C, 0x74), + (0x17, 0x00), + (0x18, 0xD7), + (0x19, 0x00), + (0x1A, 0x00), + (0x1B, 0xD7), + (0x1C, 0x00), + (0x24, 0xFE), + (0x35, 0x26), + (0x37, 0x69), + (0x38, 0x05), + (0x39, 0x06), + (0x3A, 0x08), + (0x3C, 0x78), + (0x3D, 0xFF), + (0x3E, 0xFF), + (0x3F, 0xFF), + (0x40, 0x06), + (0x41, 0xA0), + (0x43, 0x14), + (0x44, 0x0B), + (0x45, 0x30), + (0x4B, 0x04), + (0x55, 0x02), + (0x57, 0x89), + (0x59, 0x0A), + (0x5A, 0x28), + (0x5B, 0x15), + (0x5D, 0x50), + (0x5E, 0x37), + (0x5F, 0x29), + (0x60, 0x1E), + (0x61, 0x1D), + (0x62, 0x12), + (0x63, 0x1A), + (0x64, 0x08), + (0x65, 0x25), + (0x66, 0x26), + (0x67, 0x28), + (0x68, 0x49), + (0x69, 0x3A), + (0x6A, 0x43), + (0x6B, 0x3A), + (0x6C, 0x3B), + (0x6D, 0x32), + (0x6E, 0x1F), + (0x6F, 0x0E), + (0x70, 0x50), + (0x71, 0x37), + (0x72, 0x29), + (0x73, 0x1E), + (0x74, 0x1D), + (0x75, 0x12), + (0x76, 0x1A), + (0x77, 0x08), + (0x78, 0x25), + (0x79, 0x26), + (0x7A, 0x28), + (0x7B, 0x49), + (0x7C, 0x3A), + (0x7D, 0x43), + (0x7E, 0x3A), + (0x7F, 0x3B), + (0x80, 0x32), + (0x81, 0x1F), + (0x82, 0x0E), + (0xE0, 0x02), + (0x00, 0x1F), + (0x01, 0x1F), + (0x02, 0x52), + (0x03, 0x51), + (0x04, 0x50), + (0x05, 0x4B), + (0x06, 0x4A), + (0x07, 0x49), + (0x08, 0x48), + (0x09, 0x47), + (0x0A, 0x46), + (0x0B, 0x45), + (0x0C, 0x44), + (0x0D, 0x40), + (0x0E, 0x41), + (0x0F, 0x1F), + (0x10, 0x1F), + (0x11, 0x1F), + (0x12, 0x1F), + (0x13, 0x1F), + (0x14, 0x1F), + (0x15, 0x1F), + (0x16, 0x1F), + (0x17, 0x1F), + (0x18, 0x52), + (0x19, 0x51), + (0x1A, 0x50), + (0x1B, 0x4B), + (0x1C, 0x4A), + (0x1D, 0x49), + (0x1E, 0x48), + (0x1F, 0x47), + (0x20, 0x46), + (0x21, 0x45), + (0x22, 0x44), + (0x23, 0x40), + (0x24, 0x41), + (0x25, 0x1F), + (0x26, 0x1F), + (0x27, 0x1F), + (0x28, 0x1F), + (0x29, 0x1F), + (0x2A, 0x1F), + (0x2B, 0x1F), + (0x2C, 0x1F), + (0x2D, 0x1F), + (0x2E, 0x52), + (0x2F, 0x40), + (0x30, 0x41), + (0x31, 0x48), + (0x32, 0x49), + (0x33, 0x4A), + (0x34, 0x4B), + (0x35, 0x44), + (0x36, 0x45), + (0x37, 0x46), + (0x38, 0x47), + (0x39, 0x51), + (0x3A, 0x50), + (0x3B, 0x1F), + (0x3C, 0x1F), + (0x3D, 0x1F), + (0x3E, 0x1F), + (0x3F, 0x1F), + (0x40, 0x1F), + (0x41, 0x1F), + (0x42, 0x1F), + (0x43, 0x1F), + (0x44, 0x52), + (0x45, 0x40), + (0x46, 0x41), + (0x47, 0x48), + (0x48, 0x49), + (0x49, 0x4A), + (0x4A, 0x4B), + (0x4B, 0x44), + (0x4C, 0x45), + (0x4D, 0x46), + (0x4E, 0x47), + (0x4F, 0x51), + (0x50, 0x50), + (0x51, 0x1F), + (0x52, 0x1F), + (0x53, 0x1F), + (0x54, 0x1F), + (0x55, 0x1F), + (0x56, 0x1F), + (0x57, 0x1F), + (0x58, 0x40), + (0x59, 0x00), + (0x5A, 0x00), + (0x5B, 0x10), + (0x5C, 0x05), + (0x5D, 0x50), + (0x5E, 0x01), + (0x5F, 0x02), + (0x60, 0x50), + (0x61, 0x06), + (0x62, 0x04), + (0x63, 0x03), + (0x64, 0x64), + (0x65, 0x65), + (0x66, 0x0B), + (0x67, 0x73), + (0x68, 0x07), + (0x69, 0x06), + (0x6A, 0x64), + (0x6B, 0x08), + (0x6C, 0x00), + (0x6D, 0x32), + (0x6E, 0x08), + (0xE0, 0x04), + (0x2C, 0x6B), + (0x35, 0x08), + (0x37, 0x00), + (0xE0, 0x00), + ] +) # fmt: on diff --git a/esphome/components/ntc/ntc.cpp b/esphome/components/ntc/ntc.cpp index b08f84029b..cc500ba429 100644 --- a/esphome/components/ntc/ntc.cpp +++ b/esphome/components/ntc/ntc.cpp @@ -23,7 +23,7 @@ void NTC::process_(float value) { double v = this->a_ + this->b_ * lr + this->c_ * lr * lr * lr; auto temp = float(1.0 / v - 273.15); - ESP_LOGD(TAG, "'%s' - Temperature: %.1f°C", this->name_.c_str(), temp); + ESP_LOGV(TAG, "'%s' - Temperature: %.1f°C", this->name_.c_str(), temp); this->publish_state(temp); } diff --git a/esphome/components/resistance/resistance_sensor.cpp b/esphome/components/resistance/resistance_sensor.cpp index 6e57214449..706a059de3 100644 --- a/esphome/components/resistance/resistance_sensor.cpp +++ b/esphome/components/resistance/resistance_sensor.cpp @@ -39,7 +39,7 @@ void ResistanceSensor::process_(float value) { } res *= this->resistor_; - ESP_LOGD(TAG, "'%s' - Resistance %.1fΩ", this->name_.c_str(), res); + ESP_LOGV(TAG, "'%s' - Resistance %.1fΩ", this->name_.c_str(), res); this->publish_state(res); } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index c3b3fd92e8..beef766317 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -515,19 +515,26 @@ int8_t step_to_accuracy_decimals(float step) { // Store BASE64 characters as array - automatically placed in flash/ROM on embedded platforms static const char BASE64_CHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -// Helper function to find the index of a base64 character in the lookup table. +// Helper function to find the index of a base64/base64url character in the lookup table. // Returns the character's position (0-63) if found, or 0 if not found. +// Supports both standard base64 (+/) and base64url (-_) alphabets. // NOTE: This returns 0 for both 'A' (valid base64 char at index 0) and invalid characters. // This is safe because is_base64() is ALWAYS checked before calling this function, // preventing invalid characters from ever reaching here. The base64_decode function // stops processing at the first invalid character due to the is_base64() check in its // while loop condition, making this edge case harmless in practice. static inline uint8_t base64_find_char(char c) { - const void *ptr = memchr(BASE64_CHARS, c, sizeof(BASE64_CHARS)); - return ptr ? (static_cast(ptr) - BASE64_CHARS) : 0; + // Handle base64url variants: '-' maps to '+' (index 62), '_' maps to '/' (index 63) + if (c == '-') + return 62; + if (c == '_') + return 63; + const char *pos = strchr(BASE64_CHARS, c); + return pos ? (pos - BASE64_CHARS) : 0; } -static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/')); } +// Check if character is valid base64 or base64url +static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/') || (c == '-') || (c == '_')); } std::string base64_encode(const std::vector &buf) { return base64_encode(buf.data(), buf.size()); }