mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 05:24:53 +01:00
Merge branch 'dev' into bump-1.16.0b1
This commit is contained in:
commit
0a841fcc50
210 changed files with 9869 additions and 1201 deletions
|
@ -25,3 +25,4 @@ indent_size = 2
|
|||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
quote_type = single
|
7
.github/workflows/ci-docker.yml
vendored
7
.github/workflows/ci-docker.yml
vendored
|
@ -18,6 +18,7 @@ jobs:
|
|||
name: Build docker containers
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["hassio", "docker"]
|
||||
|
@ -37,9 +38,9 @@ jobs:
|
|||
dockerfile="docker/Dockerfile"
|
||||
fi
|
||||
|
||||
echo "::set-env name=BUILD_FROM::${build_from}"
|
||||
echo "::set-env name=BUILD_TO::${build_to}"
|
||||
echo "::set-env name=DOCKERFILE::${dockerfile}"
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:dev" || true
|
||||
|
|
41
.github/workflows/ci.yml
vendored
41
.github/workflows/ci.yml
vendored
|
@ -11,45 +11,6 @@ on:
|
|||
pull_request:
|
||||
|
||||
jobs:
|
||||
# A fast overview job that checks only changed files
|
||||
overview:
|
||||
runs-on: ubuntu-latest
|
||||
container: esphome/esphome-lint:latest
|
||||
steps:
|
||||
# Also fetch history and dev branch so that we can check which files changed
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Fetch dev branch
|
||||
run: git fetch origin dev
|
||||
|
||||
# Cache the .pio directory with (primarily) library dependencies
|
||||
- name: Cache .pio lib_deps
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: .pio
|
||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
||||
restore-keys: |
|
||||
lint-cpp-pio-
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Run a quick lint over all changed files
|
||||
run: script/quicklint
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-clang-format:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
|
@ -83,6 +44,7 @@ jobs:
|
|||
container: esphome/esphome-lint:latest
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
|
@ -146,6 +108,7 @@ jobs:
|
|||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
|
|
12
.github/workflows/release-dev.yml
vendored
12
.github/workflows/release-dev.yml
vendored
|
@ -41,6 +41,7 @@ jobs:
|
|||
container: esphome/esphome-lint:latest
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
|
@ -104,6 +105,7 @@ jobs:
|
|||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
|
@ -187,7 +189,7 @@ jobs:
|
|||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_SHA:0:7}"
|
||||
echo "::set-env name=TAG::${TAG}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="2.6.0"
|
||||
|
@ -202,9 +204,9 @@ jobs:
|
|||
dockerfile="docker/Dockerfile"
|
||||
fi
|
||||
|
||||
echo "::set-env name=BUILD_FROM::${build_from}"
|
||||
echo "::set-env name=BUILD_TO::${build_to}"
|
||||
echo "::set-env name=DOCKERFILE::${dockerfile}"
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:dev" || true
|
||||
|
@ -241,7 +243,7 @@ jobs:
|
|||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_SHA:0:7}"
|
||||
echo "::set-env name=TAG::${TAG}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
|
|
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
@ -40,6 +40,7 @@ jobs:
|
|||
container: esphome/esphome-lint:latest
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
|
@ -103,6 +104,7 @@ jobs:
|
|||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
|
@ -207,7 +209,7 @@ jobs:
|
|||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
echo "::set-env name=TAG::${TAG}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="2.6.0"
|
||||
|
@ -229,10 +231,10 @@ jobs:
|
|||
fi
|
||||
|
||||
# Set env variables so these values don't need to be calculated again
|
||||
echo "::set-env name=BUILD_FROM::${build_from}"
|
||||
echo "::set-env name=BUILD_TO::${build_to}"
|
||||
echo "::set-env name=DOCKERFILE::${dockerfile}"
|
||||
echo "::set-env name=CACHE_TAG::${cache_tag}"
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:${CACHE_TAG}" || true
|
||||
|
@ -277,7 +279,7 @@ jobs:
|
|||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
echo "::set-env name=TAG::${TAG}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -81,7 +81,8 @@ venv.bak/
|
|||
.pioenvs
|
||||
.piolibdeps
|
||||
.pio
|
||||
.vscode
|
||||
.vscode/
|
||||
!.vscode/tasks.json
|
||||
CMakeListsPrivate.txt
|
||||
CMakeLists.txt
|
||||
|
||||
|
@ -119,4 +120,4 @@ config/
|
|||
tests/build/
|
||||
tests/.esphome/
|
||||
/.temp-clang-tidy.cpp
|
||||
/.idea/
|
||||
.pio/
|
||||
|
|
11
.vscode/tasks.json
vendored
Normal file
11
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "run",
|
||||
"type": "shell",
|
||||
"command": "python3 -m esphome config dashboard",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
30
CODEOWNERS
30
CODEOWNERS
|
@ -13,10 +13,13 @@ esphome/core/* @esphome/core
|
|||
# Integrations
|
||||
esphome/components/ac_dimmer/* @glmnet
|
||||
esphome/components/adc/* @esphome/core
|
||||
esphome/components/animation/* @syndlex
|
||||
esphome/components/api/* @OttoWinter
|
||||
esphome/components/async_tcp/* @OttoWinter
|
||||
esphome/components/atc_mithermometer/* @ahpohl
|
||||
esphome/components/bang_bang/* @OttoWinter
|
||||
esphome/components/binary_sensor/* @esphome/core
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
|
@ -27,6 +30,7 @@ esphome/components/debug/* @OttoWinter
|
|||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
esphome/components/ezo/* @ssieb
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gpio/* @esphome/core
|
||||
|
@ -38,12 +42,19 @@ esphome/components/json/* @OttoWinter
|
|||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/mcp23s08/* @SenexCrenshaw
|
||||
esphome/components/mcp23s17/* @SenexCrenshaw
|
||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pn532/* @OttoWinter
|
||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/rc522_spi/* @glmnet
|
||||
esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
|
@ -52,12 +63,28 @@ esphome/components/sensor/* @esphome/core
|
|||
esphome/components/shutdown/* @esphome/core
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
esphome/components/ssd1322_spi/* @kbx81
|
||||
esphome/components/ssd1325_base/* @kbx81
|
||||
esphome/components/ssd1325_spi/* @kbx81
|
||||
esphome/components/ssd1327_base/* @kbx81
|
||||
esphome/components/ssd1327_i2c/* @kbx81
|
||||
esphome/components/ssd1327_spi/* @kbx81
|
||||
esphome/components/ssd1331_base/* @kbx81
|
||||
esphome/components/ssd1331_spi/* @kbx81
|
||||
esphome/components/ssd1351_base/* @kbx81
|
||||
esphome/components/ssd1351_spi/* @kbx81
|
||||
esphome/components/st7735/* @SenexCrenshaw
|
||||
esphome/components/st7789v/* @kbx81
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/tcl112/* @glmnet
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
esphome/components/tm1637/* @glmnet
|
||||
esphome/components/tmp102/* @timsavage
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
esphome/components/tuya/climate/* @jesserockz
|
||||
esphome/components/tuya/sensor/* @jesserockz
|
||||
|
@ -67,3 +94,4 @@ esphome/components/ultrasonic/* @OttoWinter
|
|||
esphome/components/version/* @esphome/core
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
|
|
|
@ -12,6 +12,9 @@ RUN pip3 install --no-cache-dir -e .
|
|||
# Settings for dashboard
|
||||
ENV USERNAME="" PASSWORD=""
|
||||
|
||||
# Expose the dashboard to Docker
|
||||
EXPOSE 6052
|
||||
|
||||
# The directory the user should mount their configuration files to
|
||||
WORKDIR /config
|
||||
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
|
||||
|
|
|
@ -235,6 +235,9 @@ def setup_log(debug=False, quiet=False):
|
|||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
|
||||
try:
|
||||
import colorama
|
||||
colorama.init(strip=True)
|
||||
|
||||
from colorlog import ColoredFormatter
|
||||
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
|
||||
colorfmt,
|
||||
|
|
|
@ -181,7 +181,7 @@ class APIClient(threading.Thread):
|
|||
self._address)
|
||||
_LOGGER.warning("(If this error persists, please set a static IP address: "
|
||||
"https://esphome.io/components/wifi.html#manual-ips)")
|
||||
raise APIConnectionError(err)
|
||||
raise APIConnectionError(err) from err
|
||||
|
||||
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
@ -346,12 +346,12 @@ class APIClient(threading.Thread):
|
|||
raise APIConnectionError("No socket!")
|
||||
try:
|
||||
val = self._socket.recv(amount - len(ret))
|
||||
except AttributeError:
|
||||
raise APIConnectionError("Socket was closed")
|
||||
except AttributeError as err:
|
||||
raise APIConnectionError("Socket was closed") from err
|
||||
except socket.timeout:
|
||||
continue
|
||||
except OSError as err:
|
||||
raise APIConnectionError(f"Error while receiving data: {err}")
|
||||
raise APIConnectionError(f"Error while receiving data: {err}") from err
|
||||
ret += val
|
||||
return ret
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
|||
return cv.Schema([schema])(value)
|
||||
except cv.Invalid as err2:
|
||||
if 'extra keys not allowed' in str(err2) and len(err2.path) == 2:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise err
|
||||
if 'Unable to find action' in str(err):
|
||||
raise err2
|
||||
|
|
|
@ -8,6 +8,9 @@ static const char *TAG = "ade7953";
|
|||
|
||||
void ADE7953::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADE7953:");
|
||||
if (this->has_irq_) {
|
||||
ESP_LOGCONFIG(TAG, " IRQ Pin: GPIO%u", this->irq_pin_number_);
|
||||
}
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
|
||||
|
|
|
@ -9,6 +9,10 @@ namespace ade7953 {
|
|||
|
||||
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
||||
public:
|
||||
void set_irq_pin(uint8_t irq_pin) {
|
||||
has_irq_ = true;
|
||||
irq_pin_number_ = irq_pin;
|
||||
}
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
|
||||
void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
|
||||
|
@ -20,6 +24,11 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
|||
}
|
||||
|
||||
void setup() override {
|
||||
if (this->has_irq_) {
|
||||
auto pin = GPIOPin(this->irq_pin_number_, INPUT);
|
||||
this->irq_pin_ = &pin;
|
||||
this->irq_pin_->setup();
|
||||
}
|
||||
this->set_timeout(100, [this]() {
|
||||
this->ade_write_<uint8_t>(0x0010, 0x04);
|
||||
this->ade_write_<uint8_t>(0x00FE, 0xAD);
|
||||
|
@ -55,6 +64,9 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool has_irq_ = false;
|
||||
uint8_t irq_pin_number_;
|
||||
GPIOPin *irq_pin_{nullptr};
|
||||
bool is_setup_{false};
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_a_sensor_{nullptr};
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, i2c
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID, CONF_VOLTAGE, \
|
||||
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
ace7953_ns = cg.esphome_ns.namespace('ade7953')
|
||||
ADE7953 = ace7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
|
||||
ade7953_ns = cg.esphome_ns.namespace('ade7953')
|
||||
ADE7953 = ade7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONF_IRQ_PIN = 'irq_pin'
|
||||
CONF_CURRENT_A = 'current_a'
|
||||
CONF_CURRENT_B = 'current_b'
|
||||
CONF_ACTIVE_POWER_A = 'active_power_a'
|
||||
|
@ -16,7 +18,7 @@ CONF_ACTIVE_POWER_B = 'active_power_b'
|
|||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||
|
||||
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
|
||||
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||
|
@ -30,6 +32,9 @@ def to_code(config):
|
|||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_IRQ_PIN in config:
|
||||
cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
|
||||
|
||||
for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A,
|
||||
CONF_ACTIVE_POWER_B]:
|
||||
if key not in config:
|
||||
|
|
94
esphome/components/animation/__init__.py
Normal file
94
esphome/components/animation/__init__.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
import logging
|
||||
|
||||
from esphome import core
|
||||
from esphome.components import display, font
|
||||
import esphome.components.image as espImage
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE
|
||||
from esphome.core import CORE, HexInt
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['display']
|
||||
MULTI_CONF = True
|
||||
|
||||
Animation_ = display.display_ns.class_('Animation')
|
||||
|
||||
CONF_RAW_DATA_ID = 'raw_data_id'
|
||||
|
||||
ANIMATION_SCHEMA = cv.Schema({
|
||||
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
||||
cv.Required(CONF_FILE): cv.file_,
|
||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
||||
cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(espImage.IMAGE_TYPE, upper=True),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
|
||||
|
||||
CODEOWNERS = ['@syndlex']
|
||||
|
||||
|
||||
def to_code(config):
|
||||
from PIL import Image
|
||||
|
||||
path = CORE.relative_config_path(config[CONF_FILE])
|
||||
try:
|
||||
image = Image.open(path)
|
||||
except Exception as e:
|
||||
raise core.EsphomeError(f"Could not load image file {path}: {e}")
|
||||
|
||||
width, height = image.size
|
||||
frames = image.n_frames
|
||||
if CONF_RESIZE in config:
|
||||
image.thumbnail(config[CONF_RESIZE])
|
||||
width, height = image.size
|
||||
else:
|
||||
if width > 500 or height > 500:
|
||||
_LOGGER.warning("The image you requested is very big. Please consider using"
|
||||
" the resize parameter.")
|
||||
|
||||
if config[CONF_TYPE] == 'GRAYSCALE':
|
||||
data = [0 for _ in range(height * width * frames)]
|
||||
pos = 0
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert('L', dither=Image.NONE)
|
||||
pixels = list(frame.getdata())
|
||||
for pix in pixels:
|
||||
data[pos] = pix
|
||||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == 'RGB24':
|
||||
data = [0 for _ in range(height * width * 3 * frames)]
|
||||
pos = 0
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert('RGB')
|
||||
pixels = list(frame.getdata())
|
||||
for pix in pixels:
|
||||
data[pos] = pix[0]
|
||||
pos += 1
|
||||
data[pos] = pix[1]
|
||||
pos += 1
|
||||
data[pos] = pix[2]
|
||||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == 'BINARY':
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range((height * width8 // 8) * frames)]
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert('1', dither=Image.NONE)
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if frame.getpixel((x, y)):
|
||||
continue
|
||||
pos = x + y * width8 + (height * width8 * frameIndex)
|
||||
data[pos // 8] |= 0x80 >> (pos % 8)
|
||||
|
||||
rhs = [HexInt(x) for x in data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, frames,
|
||||
espImage.IMAGE_TYPE[config[CONF_TYPE]])
|
|
@ -679,7 +679,7 @@ enum ClimateSwingMode {
|
|||
CLIMATE_SWING_OFF = 0;
|
||||
CLIMATE_SWING_BOTH = 1;
|
||||
CLIMATE_SWING_VERTICAL = 2;
|
||||
CLIMATE_SWINT_HORIZONTAL = 3;
|
||||
CLIMATE_SWING_HORIZONTAL = 3;
|
||||
}
|
||||
enum ClimateAction {
|
||||
CLIMATE_ACTION_OFF = 0;
|
||||
|
|
|
@ -154,8 +154,8 @@ template<> const char *proto_enum_to_string<enums::ClimateSwingMode>(enums::Clim
|
|||
return "CLIMATE_SWING_BOTH";
|
||||
case enums::CLIMATE_SWING_VERTICAL:
|
||||
return "CLIMATE_SWING_VERTICAL";
|
||||
case enums::CLIMATE_SWINT_HORIZONTAL:
|
||||
return "CLIMATE_SWINT_HORIZONTAL";
|
||||
case enums::CLIMATE_SWING_HORIZONTAL:
|
||||
return "CLIMATE_SWING_HORIZONTAL";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ enum ClimateSwingMode : uint32_t {
|
|||
CLIMATE_SWING_OFF = 0,
|
||||
CLIMATE_SWING_BOTH = 1,
|
||||
CLIMATE_SWING_VERTICAL = 2,
|
||||
CLIMATE_SWINT_HORIZONTAL = 3,
|
||||
CLIMATE_SWING_HORIZONTAL = 3,
|
||||
};
|
||||
enum ClimateAction : uint32_t {
|
||||
CLIMATE_ACTION_OFF = 0,
|
||||
|
|
0
esphome/components/atc_mithermometer/__init__.py
Normal file
0
esphome/components/atc_mithermometer/__init__.py
Normal file
137
esphome/components/atc_mithermometer/atc_mithermometer.cpp
Normal file
137
esphome/components/atc_mithermometer/atc_mithermometer.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
#include "atc_mithermometer.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace atc_mithermometer {
|
||||
|
||||
static const char *TAG = "atc_mithermometer";
|
||||
|
||||
void ATCMiThermometer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ATC MiThermometer");
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
|
||||
}
|
||||
|
||||
bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (device.address_uint64() != this->address_) {
|
||||
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
|
||||
|
||||
bool success = false;
|
||||
for (auto &service_data : device.get_service_datas()) {
|
||||
auto res = parse_header(service_data);
|
||||
if (res->is_duplicate) {
|
||||
continue;
|
||||
}
|
||||
if (!(parse_message(service_data.data, *res))) {
|
||||
continue;
|
||||
}
|
||||
if (!(report_results(res, device.address_str()))) {
|
||||
continue;
|
||||
}
|
||||
if (res->temperature.has_value() && this->temperature_ != nullptr)
|
||||
this->temperature_->publish_state(*res->temperature);
|
||||
if (res->humidity.has_value() && this->humidity_ != nullptr)
|
||||
this->humidity_->publish_state(*res->humidity);
|
||||
if (res->battery_level.has_value() && this->battery_level_ != nullptr)
|
||||
this->battery_level_->publish_state(*res->battery_level);
|
||||
if (res->battery_voltage.has_value() && this->battery_voltage_ != nullptr)
|
||||
this->battery_voltage_->publish_state(*res->battery_voltage);
|
||||
success = true;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
optional<ParseResult> ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) {
|
||||
ParseResult result;
|
||||
if (!service_data.uuid.contains(0x1A, 0x18)) {
|
||||
ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes.");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto raw = service_data.data;
|
||||
|
||||
static uint8_t last_frame_count = 0;
|
||||
if (last_frame_count == raw[12]) {
|
||||
ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast<int>(last_frame_count));
|
||||
result.is_duplicate = true;
|
||||
return {};
|
||||
}
|
||||
last_frame_count = raw[12];
|
||||
result.is_duplicate = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ATCMiThermometer::parse_message(const std::vector<uint8_t> &message, ParseResult &result) {
|
||||
// Byte 0-5 mac in correct order
|
||||
// Byte 6-7 Temperature in uint16
|
||||
// Byte 8 Humidity in percent
|
||||
// Byte 9 Battery in percent
|
||||
// Byte 10-11 Battery in mV uint16_t
|
||||
// Byte 12 frame packet counter
|
||||
|
||||
const uint8_t *data = message.data();
|
||||
const int data_length = 13;
|
||||
|
||||
if (message.size() != data_length) {
|
||||
ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
// temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C
|
||||
const int16_t temperature = uint16_t(data[7]) | (uint16_t(data[6]) << 8);
|
||||
result.temperature = temperature / 10.0f;
|
||||
|
||||
// humidity, 1 byte, 8-bit unsigned integer, 1.0 %
|
||||
result.humidity = data[8];
|
||||
|
||||
// battery, 1 byte, 8-bit unsigned integer, 1.0 %
|
||||
result.battery_level = data[9];
|
||||
|
||||
// battery, 2 bytes, 16-bit unsigned integer, 0.001 V
|
||||
const int16_t battery_voltage = uint16_t(data[11]) | (uint16_t(data[10]) << 8);
|
||||
result.battery_voltage = battery_voltage / 1.0e3f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ATCMiThermometer::report_results(const optional<ParseResult> &result, const std::string &address) {
|
||||
if (!result.has_value()) {
|
||||
ESP_LOGVV(TAG, "report_results(): no results available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address.c_str());
|
||||
|
||||
if (result->temperature.has_value()) {
|
||||
ESP_LOGD(TAG, " Temperature: %.1f °C", *result->temperature);
|
||||
}
|
||||
if (result->humidity.has_value()) {
|
||||
ESP_LOGD(TAG, " Humidity: %.0f %%", *result->humidity);
|
||||
}
|
||||
if (result->battery_level.has_value()) {
|
||||
ESP_LOGD(TAG, " Battery Level: %.0f %%", *result->battery_level);
|
||||
}
|
||||
if (result->battery_voltage.has_value()) {
|
||||
ESP_LOGD(TAG, " Battery Voltage: %.3f V", *result->battery_voltage);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace atc_mithermometer
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
48
esphome/components/atc_mithermometer/atc_mithermometer.h
Normal file
48
esphome/components/atc_mithermometer/atc_mithermometer.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace atc_mithermometer {
|
||||
|
||||
struct ParseResult {
|
||||
optional<float> temperature;
|
||||
optional<float> humidity;
|
||||
optional<float> battery_level;
|
||||
optional<float> battery_voltage;
|
||||
bool is_duplicate;
|
||||
int raw_offset;
|
||||
};
|
||||
|
||||
class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
void set_address(uint64_t address) { address_ = address; };
|
||||
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
||||
void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
|
||||
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
|
||||
|
||||
protected:
|
||||
uint64_t address_;
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
sensor::Sensor *battery_level_{nullptr};
|
||||
sensor::Sensor *battery_voltage_{nullptr};
|
||||
|
||||
optional<ParseResult> parse_header(const esp32_ble_tracker::ServiceData &service_data);
|
||||
bool parse_message(const std::vector<uint8_t> &message, ParseResult &result);
|
||||
bool report_results(const optional<ParseResult> &result, const std::string &address);
|
||||
};
|
||||
|
||||
} // namespace atc_mithermometer
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
45
esphome/components/atc_mithermometer/sensor.py
Normal file
45
esphome/components/atc_mithermometer/sensor.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, esp32_ble_tracker
|
||||
from esphome.const import CONF_BATTERY_LEVEL, CONF_BATTERY_VOLTAGE, CONF_MAC_ADDRESS, \
|
||||
CONF_HUMIDITY, CONF_TEMPERATURE, CONF_ID, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, \
|
||||
ICON_BATTERY, ICON_THERMOMETER, ICON_WATER_PERCENT
|
||||
|
||||
CODEOWNERS = ['@ahpohl']
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
|
||||
atc_mithermometer_ns = cg.esphome_ns.namespace('atc_mithermometer')
|
||||
ATCMiThermometer = atc_mithermometer_ns.class_('ATCMiThermometer',
|
||||
esp32_ble_tracker.ESPBTDeviceListener,
|
||||
cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_BATTERY, 3),
|
||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity(sens))
|
||||
if CONF_BATTERY_LEVEL in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
cg.add(var.set_battery_level(sens))
|
||||
if CONF_BATTERY_VOLTAGE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
|
||||
cg.add(var.set_battery_voltage(sens))
|
|
@ -104,6 +104,7 @@ def parse_multi_click_timing_str(value):
|
|||
try:
|
||||
state = cv.boolean(parts[0])
|
||||
except cv.Invalid:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
|
||||
|
||||
if parts[1] != 'for':
|
||||
|
|
124
esphome/components/canbus/__init__.py
Normal file
124
esphome/components/canbus/__init__.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.core import CORE, coroutine
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_DATA
|
||||
|
||||
CODEOWNERS = ['@mvturnho', '@danielschramm']
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CONF_CAN_ID = 'can_id'
|
||||
CONF_USE_EXTENDED_ID = 'use_extended_id'
|
||||
CONF_CANBUS_ID = 'canbus_id'
|
||||
CONF_BIT_RATE = 'bit_rate'
|
||||
CONF_ON_FRAME = 'on_frame'
|
||||
CONF_CANBUS_SEND = 'canbus.send'
|
||||
|
||||
|
||||
def validate_id(id_value, id_ext):
|
||||
if not id_ext:
|
||||
if id_value > 0x7ff:
|
||||
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
|
||||
|
||||
|
||||
def validate_raw_data(value):
|
||||
if isinstance(value, str):
|
||||
return value.encode('utf-8')
|
||||
if isinstance(value, list):
|
||||
return cv.Schema([cv.hex_uint8_t])(value)
|
||||
raise cv.Invalid("data must either be a string wrapped in quotes or a list of bytes")
|
||||
|
||||
|
||||
canbus_ns = cg.esphome_ns.namespace('canbus')
|
||||
CanbusComponent = canbus_ns.class_('CanbusComponent', cg.Component)
|
||||
CanbusTrigger = canbus_ns.class_('CanbusTrigger',
|
||||
automation.Trigger.template(cg.std_vector.template(cg.uint8)),
|
||||
cg.Component)
|
||||
CanSpeed = canbus_ns.enum('CAN_SPEED')
|
||||
|
||||
CAN_SPEEDS = {
|
||||
'5KBPS': CanSpeed.CAN_5KBPS,
|
||||
'10KBPS': CanSpeed.CAN_10KBPS,
|
||||
'20KBPS': CanSpeed.CAN_20KBPS,
|
||||
'31K25BPS': CanSpeed.CAN_31K25BPS,
|
||||
'33KBPS': CanSpeed.CAN_33KBPS,
|
||||
'40KBPS': CanSpeed.CAN_40KBPS,
|
||||
'50KBPS': CanSpeed.CAN_50KBPS,
|
||||
'80KBPS': CanSpeed.CAN_80KBPS,
|
||||
'83K3BPS': CanSpeed.CAN_83K3BPS,
|
||||
'95KBPS': CanSpeed.CAN_95KBPS,
|
||||
'100KBPS': CanSpeed.CAN_100KBPS,
|
||||
'125KBPS': CanSpeed.CAN_125KBPS,
|
||||
'200KBPS': CanSpeed.CAN_200KBPS,
|
||||
'250KBPS': CanSpeed.CAN_250KBPS,
|
||||
'500KBPS': CanSpeed.CAN_500KBPS,
|
||||
'1000KBPS': CanSpeed.CAN_1000KBPS,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CanbusComponent),
|
||||
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff),
|
||||
cv.Optional(CONF_BIT_RATE, default='125KBPS'): cv.enum(CAN_SPEEDS, upper=True),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_FRAME): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
|
||||
cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
}),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_canbus_core_(var, config):
|
||||
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
|
||||
yield cg.register_component(var, config)
|
||||
cg.add(var.set_can_id([config[CONF_CAN_ID]]))
|
||||
cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]]))
|
||||
cg.add(var.set_bitrate(CAN_SPEEDS[config[CONF_BIT_RATE]]))
|
||||
|
||||
for conf in config.get(CONF_ON_FRAME, []):
|
||||
can_id = conf[CONF_CAN_ID]
|
||||
ext_id = conf[CONF_USE_EXTENDED_ID]
|
||||
validate_id(can_id, ext_id)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id)
|
||||
yield cg.register_component(trigger, conf)
|
||||
yield automation.build_automation(trigger, [(cg.std_vector.template(cg.uint8), 'x')], conf)
|
||||
|
||||
|
||||
@coroutine
|
||||
def register_canbus(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.new_Pvariable(config[CONF_ID], var)
|
||||
yield setup_canbus_core_(var, config)
|
||||
|
||||
|
||||
# Actions
|
||||
@automation.register_action(CONF_CANBUS_SEND,
|
||||
canbus_ns.class_('CanbusSendAction', automation.Action),
|
||||
cv.maybe_simple_value({
|
||||
cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent),
|
||||
cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
}, key=CONF_DATA))
|
||||
def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_CANBUS_ID])
|
||||
|
||||
if CONF_CAN_ID in config:
|
||||
can_id = yield cg.templatable(config[CONF_CAN_ID], args, cg.uint32)
|
||||
cg.add(var.set_can_id(can_id))
|
||||
|
||||
use_extended_id = yield cg.templatable(config[CONF_USE_EXTENDED_ID], args, cg.uint32)
|
||||
cg.add(var.set_use_extended_id(use_extended_id))
|
||||
|
||||
data = config[CONF_DATA]
|
||||
if isinstance(data, bytes):
|
||||
data = [int(x) for x in data]
|
||||
if cg.is_template(data):
|
||||
templ = yield cg.templatable(data, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(var.set_data_template(templ))
|
||||
else:
|
||||
cg.add(var.set_data_static(data))
|
||||
yield var
|
87
esphome/components/canbus/canbus.cpp
Normal file
87
esphome/components/canbus/canbus.cpp
Normal file
|
@ -0,0 +1,87 @@
|
|||
#include "canbus.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace canbus {
|
||||
|
||||
static const char *TAG = "canbus";
|
||||
|
||||
void Canbus::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Canbus...");
|
||||
if (!this->setup_internal()) {
|
||||
ESP_LOGE(TAG, "setup error!");
|
||||
this->mark_failed();
|
||||
}
|
||||
}
|
||||
|
||||
void Canbus::dump_config() {
|
||||
if (this->use_extended_id_) {
|
||||
ESP_LOGCONFIG(TAG, "config extended id=0x%08x", this->can_id_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "config standard id=0x%03x", this->can_id_);
|
||||
}
|
||||
}
|
||||
|
||||
void Canbus::send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
|
||||
struct CanFrame can_message;
|
||||
|
||||
uint8_t size = static_cast<uint8_t>(data.size());
|
||||
if (use_extended_id) {
|
||||
ESP_LOGD(TAG, "send extended id=0x%08x size=%d", can_id, size);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "send extended id=0x%03x size=%d", can_id, size);
|
||||
}
|
||||
if (size > CAN_MAX_DATA_LENGTH)
|
||||
size = CAN_MAX_DATA_LENGTH;
|
||||
can_message.can_data_length_code = size;
|
||||
can_message.can_id = can_id;
|
||||
can_message.use_extended_id = use_extended_id;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
can_message.data[i] = data[i];
|
||||
ESP_LOGVV(TAG, " data[%d]=%02x", i, can_message.data[i]);
|
||||
}
|
||||
|
||||
this->send_message(&can_message);
|
||||
}
|
||||
|
||||
void Canbus::add_trigger(CanbusTrigger *trigger) {
|
||||
if (trigger->use_extended_id_) {
|
||||
ESP_LOGVV(TAG, "add trigger for extended canid=0x%08x", trigger->can_id_);
|
||||
} else {
|
||||
ESP_LOGVV(TAG, "add trigger for std canid=0x%03x", trigger->can_id_);
|
||||
}
|
||||
this->triggers_.push_back(trigger);
|
||||
};
|
||||
|
||||
void Canbus::loop() {
|
||||
struct CanFrame can_message;
|
||||
// readmessage
|
||||
if (this->read_message(&can_message) == canbus::ERROR_OK) {
|
||||
if (can_message.use_extended_id) {
|
||||
ESP_LOGD(TAG, "received can message extended can_id=0x%x size=%d", can_message.can_id,
|
||||
can_message.can_data_length_code);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "received can message std can_id=0x%x size=%d", can_message.can_id,
|
||||
can_message.can_data_length_code);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
// show data received
|
||||
for (int i = 0; i < can_message.can_data_length_code; i++) {
|
||||
ESP_LOGV(TAG, " can_message.data[%d]=%02x", i, can_message.data[i]);
|
||||
data.push_back(can_message.data[i]);
|
||||
}
|
||||
|
||||
// fire all triggers
|
||||
for (auto trigger : this->triggers_) {
|
||||
if ((trigger->can_id_ == can_message.can_id) && (trigger->use_extended_id_ == can_message.use_extended_id)) {
|
||||
trigger->trigger(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace canbus
|
||||
} // namespace esphome
|
134
esphome/components/canbus/canbus.h
Normal file
134
esphome/components/canbus/canbus.h
Normal file
|
@ -0,0 +1,134 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace canbus {
|
||||
|
||||
enum Error : uint8_t {
|
||||
ERROR_OK = 0,
|
||||
ERROR_FAIL = 1,
|
||||
ERROR_ALLTXBUSY = 2,
|
||||
ERROR_FAILINIT = 3,
|
||||
ERROR_FAILTX = 4,
|
||||
ERROR_NOMSG = 5
|
||||
};
|
||||
|
||||
enum CanSpeed : uint8_t {
|
||||
CAN_5KBPS,
|
||||
CAN_10KBPS,
|
||||
CAN_20KBPS,
|
||||
CAN_31K25BPS,
|
||||
CAN_33KBPS,
|
||||
CAN_40KBPS,
|
||||
CAN_50KBPS,
|
||||
CAN_80KBPS,
|
||||
CAN_83K3BPS,
|
||||
CAN_95KBPS,
|
||||
CAN_100KBPS,
|
||||
CAN_125KBPS,
|
||||
CAN_200KBPS,
|
||||
CAN_250KBPS,
|
||||
CAN_500KBPS,
|
||||
CAN_1000KBPS
|
||||
};
|
||||
|
||||
class CanbusTrigger;
|
||||
template<typename... Ts> class CanbusSendAction;
|
||||
|
||||
/* CAN payload length definitions according to ISO 11898-1 */
|
||||
static const uint8_t CAN_MAX_DATA_LENGTH = 8;
|
||||
|
||||
/*
|
||||
Can Frame describes a normative CAN Frame
|
||||
The RTR = Remote Transmission Request is implemented in every CAN controller but rarely used
|
||||
So currently the flag is passed to and from the hardware but currently ignored to the user application.
|
||||
*/
|
||||
struct CanFrame {
|
||||
bool use_extended_id = false;
|
||||
bool remote_transmission_request = false;
|
||||
uint32_t can_id; /* 29 or 11 bit CAN_ID */
|
||||
uint8_t can_data_length_code; /* frame payload length in byte (0 .. CAN_MAX_DATA_LENGTH) */
|
||||
uint8_t data[CAN_MAX_DATA_LENGTH] __attribute__((aligned(8)));
|
||||
};
|
||||
|
||||
class Canbus : public Component {
|
||||
public:
|
||||
Canbus(){};
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
void loop() override;
|
||||
|
||||
void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data);
|
||||
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
||||
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
|
||||
void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
|
||||
|
||||
void add_trigger(CanbusTrigger *trigger);
|
||||
|
||||
protected:
|
||||
template<typename... Ts> friend class CanbusSendAction;
|
||||
std::vector<CanbusTrigger *> triggers_{};
|
||||
uint32_t can_id_;
|
||||
bool use_extended_id_;
|
||||
CanSpeed bit_rate_;
|
||||
|
||||
virtual bool setup_internal();
|
||||
virtual Error send_message(struct CanFrame *frame);
|
||||
virtual Error read_message(struct CanFrame *frame);
|
||||
};
|
||||
|
||||
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
|
||||
public:
|
||||
void set_data_template(const std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||
this->data_func_ = func;
|
||||
this->static_ = false;
|
||||
}
|
||||
void set_data_static(const std::vector<uint8_t> &data) {
|
||||
this->data_static_ = data;
|
||||
this->static_ = true;
|
||||
}
|
||||
|
||||
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
||||
|
||||
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
|
||||
auto use_extended_id =
|
||||
this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
|
||||
if (this->static_) {
|
||||
this->parent_->send_data(can_id, use_extended_id, this->data_static_);
|
||||
} else {
|
||||
auto val = this->data_func_(x...);
|
||||
this->parent_->send_data(can_id, use_extended_id, val);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
optional<uint32_t> can_id_{};
|
||||
optional<bool> use_extended_id_{};
|
||||
bool static_{false};
|
||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||
std::vector<uint8_t> data_static_{};
|
||||
};
|
||||
|
||||
class CanbusTrigger : public Trigger<std::vector<uint8_t>>, public Component {
|
||||
friend class Canbus;
|
||||
|
||||
public:
|
||||
explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const bool use_extended_id)
|
||||
: parent_(parent), can_id_(can_id), use_extended_id_(use_extended_id){};
|
||||
void setup() override { this->parent_->add_trigger(this); }
|
||||
|
||||
protected:
|
||||
Canbus *parent_;
|
||||
uint32_t can_id_;
|
||||
bool use_extended_id_;
|
||||
};
|
||||
|
||||
} // namespace canbus
|
||||
} // namespace esphome
|
|
@ -12,8 +12,10 @@ void DaikinClimate::transmit_state() {
|
|||
0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00};
|
||||
|
||||
remote_state[21] = this->operation_mode_();
|
||||
remote_state[24] = this->fan_speed_();
|
||||
remote_state[22] = this->temperature_();
|
||||
uint16_t fan_speed = this->fan_speed_();
|
||||
remote_state[24] = fan_speed >> 8;
|
||||
remote_state[25] = fan_speed & 0xff;
|
||||
|
||||
// Calculate checksum
|
||||
for (int i = 16; i < 34; i++) {
|
||||
|
@ -90,25 +92,38 @@ uint8_t DaikinClimate::operation_mode_() {
|
|||
return operating_mode;
|
||||
}
|
||||
|
||||
uint8_t DaikinClimate::fan_speed_() {
|
||||
uint8_t fan_speed;
|
||||
uint16_t DaikinClimate::fan_speed_() {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DAIKIN_FAN_1;
|
||||
fan_speed = DAIKIN_FAN_1 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
fan_speed = DAIKIN_FAN_3;
|
||||
fan_speed = DAIKIN_FAN_3 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
fan_speed = DAIKIN_FAN_5;
|
||||
fan_speed = DAIKIN_FAN_5 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
fan_speed = DAIKIN_FAN_AUTO;
|
||||
fan_speed = DAIKIN_FAN_AUTO << 8;
|
||||
}
|
||||
|
||||
// If swing is enabled switch first 4 bits to 1111
|
||||
return this->swing_mode == climate::CLIMATE_SWING_VERTICAL ? fan_speed | 0xF : fan_speed;
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
fan_speed |= 0x0F00;
|
||||
break;
|
||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||
fan_speed |= 0x000F;
|
||||
break;
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
fan_speed |= 0x0F0F;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return fan_speed;
|
||||
}
|
||||
|
||||
uint8_t DaikinClimate::temperature_() {
|
||||
|
@ -159,13 +174,19 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
|
|||
this->target_temperature = temperature >> 1;
|
||||
}
|
||||
uint8_t fan_mode = frame[8];
|
||||
if (fan_mode & 0xF)
|
||||
uint8_t swing_mode = frame[9];
|
||||
if (fan_mode & 0xF && swing_mode & 0xF)
|
||||
this->swing_mode = climate::CLIMATE_SWING_BOTH;
|
||||
else if (fan_mode & 0xF)
|
||||
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||
else if (swing_mode & 0xF)
|
||||
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||
else
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
switch (fan_mode & 0xF0) {
|
||||
case DAIKIN_FAN_1:
|
||||
case DAIKIN_FAN_2:
|
||||
case DAIKIN_FAN_SILENT:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case DAIKIN_FAN_3:
|
||||
|
|
|
@ -21,6 +21,7 @@ const uint8_t DAIKIN_MODE_ON = 0x01;
|
|||
|
||||
// Fan Speed
|
||||
const uint8_t DAIKIN_FAN_AUTO = 0xA0;
|
||||
const uint8_t DAIKIN_FAN_SILENT = 0xB0;
|
||||
const uint8_t DAIKIN_FAN_1 = 0x30;
|
||||
const uint8_t DAIKIN_FAN_2 = 0x40;
|
||||
const uint8_t DAIKIN_FAN_3 = 0x50;
|
||||
|
@ -46,13 +47,14 @@ class DaikinClimate : public climate_ir::ClimateIR {
|
|||
DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true,
|
||||
std::vector<climate::ClimateFanMode>{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
|
||||
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
|
||||
std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
|
||||
std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
|
||||
|
||||
protected:
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void transmit_state() override;
|
||||
uint8_t operation_mode_();
|
||||
uint8_t fan_speed_();
|
||||
uint16_t fan_speed_();
|
||||
uint8_t temperature_();
|
||||
// Handle received IR Buffer
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
|
|
|
@ -299,7 +299,7 @@ void DisplayBuffer::printf(int x, int y, Font *font, TextAlign align, const char
|
|||
void DisplayBuffer::printf(int x, int y, Font *font, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, COLOR_ON, TextAlign::CENTER_LEFT, format, arg);
|
||||
this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void DisplayBuffer::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
|
||||
|
@ -474,6 +474,51 @@ ImageType Image::get_type() const { return this->type_; }
|
|||
Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
|
||||
: width_(width), height_(height), type_(type), data_start_(data_start) {}
|
||||
|
||||
bool Animation::get_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
return false;
|
||||
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
||||
const uint32_t frame_index = this->height_ * width_8 * this->current_frame_;
|
||||
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
|
||||
return false;
|
||||
const uint32_t pos = x + y * width_8 + frame_index;
|
||||
return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||
}
|
||||
Color Animation::get_color_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
return 0;
|
||||
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
|
||||
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
|
||||
return 0;
|
||||
const uint32_t pos = (x + y * this->width_ + frame_index) * 3;
|
||||
const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) |
|
||||
(pgm_read_byte(this->data_start_ + pos + 1) << 8) |
|
||||
(pgm_read_byte(this->data_start_ + pos + 0) << 16);
|
||||
return Color(color32);
|
||||
}
|
||||
Color Animation::get_grayscale_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
return 0;
|
||||
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
|
||||
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
|
||||
return 0;
|
||||
const uint32_t pos = (x + y * this->width_ + frame_index);
|
||||
const uint8_t gray = pgm_read_byte(this->data_start_ + pos);
|
||||
return Color(gray | gray << 8 | gray << 16 | gray << 24);
|
||||
}
|
||||
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
|
||||
: Image(data_start, width, height, type), animation_frame_count_(animation_frame_count) {
|
||||
current_frame_ = 0;
|
||||
}
|
||||
int Animation::get_animation_frame_count() const { return this->animation_frame_count_; }
|
||||
int Animation::get_current_frame() const { return this->current_frame_; }
|
||||
void Animation::next_frame() {
|
||||
this->current_frame_++;
|
||||
if (this->current_frame_ >= animation_frame_count_) {
|
||||
this->current_frame_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {}
|
||||
void DisplayPage::show() { this->parent_->show_page(this); }
|
||||
void DisplayPage::show_next() { this->next_->show(); }
|
||||
|
|
|
@ -388,9 +388,9 @@ class Font {
|
|||
class Image {
|
||||
public:
|
||||
Image(const uint8_t *data_start, int width, int height, ImageType type);
|
||||
bool get_pixel(int x, int y) const;
|
||||
Color get_color_pixel(int x, int y) const;
|
||||
Color get_grayscale_pixel(int x, int y) const;
|
||||
virtual bool get_pixel(int x, int y) const;
|
||||
virtual Color get_color_pixel(int x, int y) const;
|
||||
virtual Color get_grayscale_pixel(int x, int y) const;
|
||||
int get_width() const;
|
||||
int get_height() const;
|
||||
ImageType get_type() const;
|
||||
|
@ -402,6 +402,22 @@ class Image {
|
|||
const uint8_t *data_start_;
|
||||
};
|
||||
|
||||
class Animation : public Image {
|
||||
public:
|
||||
Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
|
||||
bool get_pixel(int x, int y) const override;
|
||||
Color get_color_pixel(int x, int y) const override;
|
||||
Color get_grayscale_pixel(int x, int y) const override;
|
||||
|
||||
int get_animation_frame_count() const;
|
||||
int get_current_frame() const;
|
||||
void next_frame();
|
||||
|
||||
protected:
|
||||
int current_frame_;
|
||||
int animation_frame_count_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(DisplayPage *, page)
|
||||
|
|
|
@ -2,7 +2,8 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_NAME, CONF_PIN, CONF_SCL, CONF_SDA, \
|
||||
ESP_PLATFORM_ESP32, CONF_DATA_PINS, CONF_RESET_PIN, CONF_RESOLUTION, CONF_BRIGHTNESS
|
||||
ESP_PLATFORM_ESP32, CONF_DATA_PINS, CONF_RESET_PIN, CONF_RESOLUTION, CONF_BRIGHTNESS, \
|
||||
CONF_CONTRAST
|
||||
|
||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
|
||||
DEPENDENCIES = ['api']
|
||||
|
@ -47,7 +48,6 @@ CONF_IDLE_FRAMERATE = 'idle_framerate'
|
|||
CONF_JPEG_QUALITY = 'jpeg_quality'
|
||||
CONF_VERTICAL_FLIP = 'vertical_flip'
|
||||
CONF_HORIZONTAL_MIRROR = 'horizontal_mirror'
|
||||
CONF_CONTRAST = 'contrast'
|
||||
CONF_SATURATION = 'saturation'
|
||||
CONF_TEST_PATTERN = 'test_pattern'
|
||||
|
||||
|
|
0
esphome/components/ezo/__init__.py
Normal file
0
esphome/components/ezo/__init__.py
Normal file
86
esphome/components/ezo/ezo.cpp
Normal file
86
esphome/components/ezo/ezo.cpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#include "ezo.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ezo {
|
||||
|
||||
static const char *TAG = "ezo.sensor";
|
||||
|
||||
static const uint16_t EZO_STATE_WAIT = 1;
|
||||
static const uint16_t EZO_STATE_SEND_TEMP = 2;
|
||||
static const uint16_t EZO_STATE_WAIT_TEMP = 4;
|
||||
|
||||
void EZOSensor::dump_config() {
|
||||
LOG_SENSOR("", "EZO", this);
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed())
|
||||
ESP_LOGE(TAG, "Communication with EZO circuit failed!");
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void EZOSensor::update() {
|
||||
if (this->state_ & EZO_STATE_WAIT) {
|
||||
ESP_LOGE(TAG, "update overrun, still waiting for previous response");
|
||||
return;
|
||||
}
|
||||
uint8_t c = 'R';
|
||||
this->write_bytes_raw(&c, 1);
|
||||
this->state_ |= EZO_STATE_WAIT;
|
||||
this->start_time_ = millis();
|
||||
this->wait_time_ = 900;
|
||||
}
|
||||
|
||||
void EZOSensor::loop() {
|
||||
uint8_t buf[20];
|
||||
if (!(this->state_ & EZO_STATE_WAIT)) {
|
||||
if (this->state_ & EZO_STATE_SEND_TEMP) {
|
||||
int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_);
|
||||
this->write_bytes_raw(buf, len);
|
||||
this->state_ = EZO_STATE_WAIT | EZO_STATE_WAIT_TEMP;
|
||||
this->start_time_ = millis();
|
||||
this->wait_time_ = 300;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (millis() - this->start_time_ < this->wait_time_)
|
||||
return;
|
||||
buf[0] = 0;
|
||||
if (!this->read_bytes_raw(buf, 20)) {
|
||||
ESP_LOGE(TAG, "read error");
|
||||
this->state_ = 0;
|
||||
return;
|
||||
}
|
||||
switch (buf[0]) {
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
ESP_LOGE(TAG, "device returned a syntax error");
|
||||
break;
|
||||
case 254:
|
||||
return; // keep waiting
|
||||
case 255:
|
||||
ESP_LOGE(TAG, "device returned no data");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "device returned an unknown response: %d", buf[0]);
|
||||
break;
|
||||
}
|
||||
if (this->state_ & EZO_STATE_WAIT_TEMP) {
|
||||
this->state_ = 0;
|
||||
return;
|
||||
}
|
||||
this->state_ &= ~EZO_STATE_WAIT;
|
||||
if (buf[0] != 1)
|
||||
return;
|
||||
|
||||
float val = strtof((char *) &buf[1], nullptr);
|
||||
this->publish_state(val);
|
||||
}
|
||||
|
||||
void EZOSensor::set_tempcomp_value(float temp) {
|
||||
this->tempcomp_ = temp;
|
||||
this->state_ |= EZO_STATE_SEND_TEMP;
|
||||
}
|
||||
|
||||
} // namespace ezo
|
||||
} // namespace esphome
|
28
esphome/components/ezo/ezo.h
Normal file
28
esphome/components/ezo/ezo.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ezo {
|
||||
|
||||
/// This class implements support for the EZO circuits in i2c mode
|
||||
class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; };
|
||||
|
||||
void set_tempcomp_value(float temp);
|
||||
|
||||
protected:
|
||||
unsigned long start_time_ = 0;
|
||||
unsigned long wait_time_ = 0;
|
||||
uint16_t state_ = 0;
|
||||
float tempcomp_;
|
||||
};
|
||||
|
||||
} // namespace ezo
|
||||
} // namespace esphome
|
23
esphome/components/ezo/sensor.py
Normal file
23
esphome/components/ezo/sensor.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ['@ssieb']
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
ezo_ns = cg.esphome_ns.namespace('ezo')
|
||||
|
||||
EZOSensor = ezo_ns.class_('EZOSensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(EZOSensor),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(None))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
|
@ -38,7 +38,7 @@ class FastLEDLightOutput : public light::AddressableLight {
|
|||
return *this->controller_;
|
||||
}
|
||||
|
||||
template<ESPIChipsets CHIPSET, uint8_t DATA_PIN, uint8_t CLOCK_PIN, EOrder RGB_ORDER, uint8_t SPI_DATA_RATE>
|
||||
template<ESPIChipsets CHIPSET, uint8_t DATA_PIN, uint8_t CLOCK_PIN, EOrder RGB_ORDER, uint32_t SPI_DATA_RATE>
|
||||
CLEDController &add_leds(int num_leds) {
|
||||
switch (CHIPSET) {
|
||||
case LPD8806: {
|
||||
|
|
|
@ -2,7 +2,8 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import fastled_base
|
||||
from esphome.const import CONF_CHIPSET, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_NUM_LEDS, CONF_RGB_ORDER
|
||||
from esphome.const import CONF_CHIPSET, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_DATA_RATE, \
|
||||
CONF_NUM_LEDS, CONF_RGB_ORDER
|
||||
|
||||
AUTO_LOAD = ['fastled_base']
|
||||
|
||||
|
@ -21,15 +22,24 @@ CONFIG_SCHEMA = fastled_base.BASE_SCHEMA.extend({
|
|||
cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
|
||||
cv.Required(CONF_DATA_PIN): pins.output_pin,
|
||||
cv.Required(CONF_CLOCK_PIN): pins.output_pin,
|
||||
cv.Optional(CONF_DATA_RATE): cv.frequency,
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = yield fastled_base.new_fastled_light(config)
|
||||
|
||||
rgb_order = None
|
||||
if CONF_RGB_ORDER in config:
|
||||
rgb_order = cg.RawExpression(config[CONF_RGB_ORDER])
|
||||
rgb_order = cg.RawExpression(config[CONF_RGB_ORDER] if CONF_RGB_ORDER in config else "RGB")
|
||||
data_rate = None
|
||||
|
||||
if CONF_DATA_RATE in config:
|
||||
data_rate_khz = int(config[CONF_DATA_RATE] / 1000)
|
||||
if data_rate_khz < 1000:
|
||||
data_rate = cg.RawExpression(f"DATA_RATE_KHZ({data_rate_khz})")
|
||||
else:
|
||||
data_rate_mhz = int(data_rate_khz / 1000)
|
||||
data_rate = cg.RawExpression(f"DATA_RATE_MHZ({data_rate_mhz})")
|
||||
template_args = cg.TemplateArguments(cg.RawExpression(config[CONF_CHIPSET]),
|
||||
config[CONF_DATA_PIN], config[CONF_CLOCK_PIN], rgb_order)
|
||||
config[CONF_DATA_PIN], config[CONF_CLOCK_PIN], rgb_order,
|
||||
data_rate)
|
||||
cg.add(var.add_leds(template_args, config[CONF_NUM_LEDS]))
|
||||
|
|
|
@ -42,9 +42,9 @@ def validate_glyphs(value):
|
|||
def validate_pillow_installed(value):
|
||||
try:
|
||||
import PIL
|
||||
except ImportError:
|
||||
except ImportError as err:
|
||||
raise cv.Invalid("Please install the pillow python package to use this feature. "
|
||||
"(pip install pillow)")
|
||||
"(pip install pillow)") from err
|
||||
|
||||
if PIL.__version__[0] < '4':
|
||||
raise cv.Invalid("Please update your pillow installation to at least 4.0.x. "
|
||||
|
|
|
@ -36,7 +36,10 @@ const uint8_t FUJITSU_GENERAL_FAN_HIGH_BYTE10 = 0x01;
|
|||
const uint8_t FUJITSU_GENERAL_FAN_MEDIUM_BYTE10 = 0x02;
|
||||
const uint8_t FUJITSU_GENERAL_FAN_LOW_BYTE10 = 0x03;
|
||||
const uint8_t FUJITSU_GENERAL_FAN_SILENT_BYTE10 = 0x04;
|
||||
const uint8_t FUJITSU_GENERAL_SWING_MASK_BYTE10 = 0b00010000;
|
||||
const uint8_t FUJITSU_GENERAL_SWING_NONE_BYTE10 = 0x00;
|
||||
const uint8_t FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 = 0x01;
|
||||
const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 = 0x02;
|
||||
const uint8_t FUJITSU_GENERAL_SWING_BOTH_BYTE10 = 0x03;
|
||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE10 = 0x00;
|
||||
|
||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE11 = 0x00;
|
||||
|
@ -74,7 +77,12 @@ const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000;
|
|||
|
||||
const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY = 38000;
|
||||
|
||||
FujitsuGeneralClimate::FujitsuGeneralClimate() : ClimateIR(FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1) {}
|
||||
FujitsuGeneralClimate::FujitsuGeneralClimate()
|
||||
: ClimateIR(
|
||||
FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1.0f, true, true,
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL,
|
||||
climate::CLIMATE_SWING_BOTH}) {}
|
||||
|
||||
void FujitsuGeneralClimate::transmit_state() {
|
||||
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||
|
@ -101,8 +109,8 @@ void FujitsuGeneralClimate::transmit_state() {
|
|||
remote_state[15] = FUJITSU_GENERAL_BASE_BYTE15;
|
||||
|
||||
// Set temperature
|
||||
uint8_t safecelsius = std::max((uint8_t) this->target_temperature, FUJITSU_GENERAL_TEMP_MIN);
|
||||
safecelsius = std::min(safecelsius, FUJITSU_GENERAL_TEMP_MAX);
|
||||
auto safecelsius =
|
||||
(uint8_t) roundf(clamp(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX));
|
||||
remote_state[8] = (byte) safecelsius - 16;
|
||||
remote_state[8] = remote_state[8] << 4;
|
||||
|
||||
|
@ -119,18 +127,52 @@ void FujitsuGeneralClimate::transmit_state() {
|
|||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state[9] = FUJITSU_GENERAL_MODE_HEAT_BYTE9;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
remote_state[9] = FUJITSU_GENERAL_MODE_DRY_BYTE9;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
remote_state[9] = FUJITSU_GENERAL_MODE_FAN_BYTE9;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
default:
|
||||
remote_state[9] = FUJITSU_GENERAL_MODE_AUTO_BYTE9;
|
||||
break;
|
||||
// TODO: CLIMATE_MODE_FAN_ONLY, CLIMATE_MODE_DRY, CLIMATE_MODE_10C are missing in esphome
|
||||
// TODO: CLIMATE_MODE_10C are missing in esphome
|
||||
}
|
||||
|
||||
// TODO: missing support for fan speed
|
||||
// Set fan
|
||||
switch (this->fan_mode) {
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
remote_state[10] = FUJITSU_GENERAL_FAN_HIGH_BYTE10;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
remote_state[10] = FUJITSU_GENERAL_FAN_MEDIUM_BYTE10;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
remote_state[10] = FUJITSU_GENERAL_FAN_LOW_BYTE10;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10;
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: missing support for swing
|
||||
// remote_state[10] = (byte) remote_state[10] | FUJITSU_GENERAL_SWING_MASK_BYTE10;
|
||||
// Set swing
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 << 4);
|
||||
break;
|
||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 << 4);
|
||||
break;
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_BOTH_BYTE10 << 4);
|
||||
break;
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
default:
|
||||
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_NONE_BYTE10 << 4);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: missing support for outdoor unit low noise
|
||||
// remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14;
|
||||
|
|
0
esphome/components/hbridge/__init__.py
Normal file
0
esphome/components/hbridge/__init__.py
Normal file
76
esphome/components/hbridge/hbridge_light_output.h
Normal file
76
esphome/components/hbridge/hbridge_light_output.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/components/light/light_output.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hbridge {
|
||||
|
||||
// Using PollingComponent as the updates are more consistent and reduces flickering
|
||||
class HBridgeLightOutput : public PollingComponent, public light::LightOutput {
|
||||
public:
|
||||
HBridgeLightOutput() : PollingComponent(1) {}
|
||||
|
||||
void set_pina_pin(output::FloatOutput *pina_pin) { pina_pin_ = pina_pin; }
|
||||
void set_pinb_pin(output::FloatOutput *pinb_pin) { pinb_pin_ = pinb_pin; }
|
||||
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true); // Dimming
|
||||
traits.set_supports_rgb(false);
|
||||
traits.set_supports_rgb_white_value(true); // hbridge color
|
||||
traits.set_supports_color_temperature(false);
|
||||
return traits;
|
||||
}
|
||||
|
||||
void setup() override { this->forward_direction_ = false; }
|
||||
|
||||
void update() override {
|
||||
// This method runs around 60 times per second
|
||||
// We cannot do the PWM ourselves so we are reliant on the hardware PWM
|
||||
if (!this->forward_direction_) { // First LED Direction
|
||||
this->pinb_pin_->set_level(this->duty_off_);
|
||||
this->pina_pin_->set_level(this->pina_duty_);
|
||||
this->forward_direction_ = true;
|
||||
} else { // Second LED Direction
|
||||
this->pina_pin_->set_level(this->duty_off_);
|
||||
this->pinb_pin_->set_level(this->pinb_duty_);
|
||||
this->forward_direction_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
void write_state(light::LightState *state) override {
|
||||
float bright;
|
||||
state->current_values_as_brightness(&bright);
|
||||
|
||||
state->set_gamma_correct(0);
|
||||
float red, green, blue, white;
|
||||
state->current_values_as_rgbw(&red, &green, &blue, &white);
|
||||
|
||||
if ((white / bright) > 0.55) {
|
||||
this->pina_duty_ = (bright * (1 - (white / bright)));
|
||||
this->pinb_duty_ = bright;
|
||||
} else if (white < 0.45) {
|
||||
this->pina_duty_ = bright;
|
||||
this->pinb_duty_ = white;
|
||||
} else {
|
||||
this->pina_duty_ = bright;
|
||||
this->pinb_duty_ = bright;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
output::FloatOutput *pina_pin_;
|
||||
output::FloatOutput *pinb_pin_;
|
||||
float pina_duty_ = 0;
|
||||
float pinb_duty_ = 0;
|
||||
float duty_off_ = 0;
|
||||
bool forward_direction_ = false;
|
||||
};
|
||||
|
||||
} // namespace hbridge
|
||||
} // namespace esphome
|
24
esphome/components/hbridge/light.py
Normal file
24
esphome/components/hbridge/light.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import light, output
|
||||
from esphome.const import CONF_OUTPUT_ID, CONF_PIN_A, CONF_PIN_B
|
||||
|
||||
hbridge_ns = cg.esphome_ns.namespace('hbridge')
|
||||
HBridgeLightOutput = hbridge_ns.class_('HBridgeLightOutput', cg.PollingComponent, light.LightOutput)
|
||||
|
||||
CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(HBridgeLightOutput),
|
||||
cv.Required(CONF_PIN_A): cv.use_id(output.FloatOutput),
|
||||
cv.Required(CONF_PIN_B): cv.use_id(output.FloatOutput),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield light.register_light(var, config)
|
||||
|
||||
hside = yield cg.get_variable(config[CONF_PIN_A])
|
||||
cg.add(var.set_pina_pin(hside))
|
||||
lside = yield cg.get_variable(config[CONF_PIN_B])
|
||||
cg.add(var.set_pinb_pin(lside))
|
0
esphome/components/hitachi_ac344/__init__.py
Normal file
0
esphome/components/hitachi_ac344/__init__.py
Normal file
18
esphome/components/hitachi_ac344/climate.py
Normal file
18
esphome/components/hitachi_ac344/climate.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import climate_ir
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ['climate_ir']
|
||||
|
||||
hitachi_ac344_ns = cg.esphome_ns.namespace('hitachi_ac344')
|
||||
HitachiClimate = hitachi_ac344_ns.class_('HitachiClimate', climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(HitachiClimate),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield climate_ir.register_climate_ir(var, config)
|
365
esphome/components/hitachi_ac344/hitachi_ac344.cpp
Normal file
365
esphome/components/hitachi_ac344/hitachi_ac344.cpp
Normal file
|
@ -0,0 +1,365 @@
|
|||
#include "hitachi_ac344.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hitachi_ac344 {
|
||||
|
||||
static const char *TAG = "climate.hitachi_ac344";
|
||||
|
||||
void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, const uint8_t data) {
|
||||
if (offset >= 8 || !nbits)
|
||||
return; // Short circuit as it won't change.
|
||||
// Calculate the mask for the supplied value.
|
||||
uint8_t mask = UINT8_MAX >> (8 - ((nbits > 8) ? 8 : nbits));
|
||||
// Calculate the mask & clear the space for the data.
|
||||
// Clear the destination bits.
|
||||
*dst &= ~(uint8_t)(mask << offset);
|
||||
// Merge in the data.
|
||||
*dst |= ((data & mask) << offset);
|
||||
}
|
||||
|
||||
void set_bit(uint8_t *const data, const uint8_t position, const bool on) {
|
||||
uint8_t mask = 1 << position;
|
||||
if (on)
|
||||
*data |= mask;
|
||||
else
|
||||
*data &= ~mask;
|
||||
}
|
||||
|
||||
uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) {
|
||||
for (uint16_t i = 1; i < length; i += 2) {
|
||||
// Code done this way to avoid a compiler warning bug.
|
||||
uint8_t inv = ~*(ptr + i - 1);
|
||||
*(ptr + i) = inv;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool HitachiClimate::get_power_() { return remote_state_[HITACHI_AC344_POWER_BYTE] == HITACHI_AC344_POWER_ON; }
|
||||
|
||||
void HitachiClimate::set_power_(bool on) {
|
||||
set_button_(HITACHI_AC344_BUTTON_POWER);
|
||||
remote_state_[HITACHI_AC344_POWER_BYTE] = on ? HITACHI_AC344_POWER_ON : HITACHI_AC344_POWER_OFF;
|
||||
}
|
||||
|
||||
uint8_t HitachiClimate::get_mode_() { return remote_state_[HITACHI_AC344_MODE_BYTE] & 0xF; }
|
||||
|
||||
void HitachiClimate::set_mode_(uint8_t mode) {
|
||||
uint8_t new_mode = mode;
|
||||
switch (mode) {
|
||||
// Fan mode sets a special temp.
|
||||
case HITACHI_AC344_MODE_FAN:
|
||||
set_temp_(HITACHI_AC344_TEMP_FAN, false);
|
||||
break;
|
||||
case HITACHI_AC344_MODE_HEAT:
|
||||
case HITACHI_AC344_MODE_COOL:
|
||||
case HITACHI_AC344_MODE_DRY:
|
||||
break;
|
||||
default:
|
||||
new_mode = HITACHI_AC344_MODE_COOL;
|
||||
}
|
||||
set_bits(&remote_state_[HITACHI_AC344_MODE_BYTE], 0, 4, new_mode);
|
||||
if (new_mode != HITACHI_AC344_MODE_FAN)
|
||||
set_temp_(previous_temp_);
|
||||
set_fan_(get_fan_()); // Reset the fan speed after the mode change.
|
||||
set_power_(true);
|
||||
}
|
||||
|
||||
void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) {
|
||||
uint8_t temp;
|
||||
temp = std::min(celsius, HITACHI_AC344_TEMP_MAX);
|
||||
temp = std::max(temp, HITACHI_AC344_TEMP_MIN);
|
||||
set_bits(&remote_state_[HITACHI_AC344_TEMP_BYTE], HITACHI_AC344_TEMP_OFFSET, HITACHI_AC344_TEMP_SIZE, temp);
|
||||
if (previous_temp_ > temp)
|
||||
set_button_(HITACHI_AC344_BUTTON_TEMP_DOWN);
|
||||
else if (previous_temp_ < temp)
|
||||
set_button_(HITACHI_AC344_BUTTON_TEMP_UP);
|
||||
if (set_previous)
|
||||
previous_temp_ = temp;
|
||||
}
|
||||
|
||||
uint8_t HitachiClimate::get_fan_() { return remote_state_[HITACHI_AC344_FAN_BYTE] >> 4 & 0xF; }
|
||||
|
||||
void HitachiClimate::set_fan_(uint8_t speed) {
|
||||
uint8_t new_speed = std::max(speed, HITACHI_AC344_FAN_MIN);
|
||||
uint8_t fan_max = HITACHI_AC344_FAN_MAX;
|
||||
|
||||
// Only 2 x low speeds in Dry mode or Auto
|
||||
if (get_mode_() == HITACHI_AC344_MODE_DRY && speed == HITACHI_AC344_FAN_AUTO) {
|
||||
fan_max = HITACHI_AC344_FAN_AUTO;
|
||||
} else if (get_mode_() == HITACHI_AC344_MODE_DRY) {
|
||||
fan_max = HITACHI_AC344_FAN_MAX_DRY;
|
||||
} else if (get_mode_() == HITACHI_AC344_MODE_FAN && speed == HITACHI_AC344_FAN_AUTO) {
|
||||
// Fan Mode does not have auto. Set to safe low
|
||||
new_speed = HITACHI_AC344_FAN_MIN;
|
||||
}
|
||||
|
||||
new_speed = std::min(new_speed, fan_max);
|
||||
// Handle the setting the button value if we are going to change the value.
|
||||
if (new_speed != get_fan_())
|
||||
set_button_(HITACHI_AC344_BUTTON_FAN);
|
||||
// Set the values
|
||||
|
||||
set_bits(&remote_state_[HITACHI_AC344_FAN_BYTE], 4, 4, new_speed);
|
||||
remote_state_[9] = 0x92;
|
||||
|
||||
// When fan is at min/max, additional bytes seem to be set
|
||||
if (new_speed == HITACHI_AC344_FAN_MIN)
|
||||
remote_state_[9] = 0x98;
|
||||
remote_state_[29] = 0x01;
|
||||
}
|
||||
|
||||
void HitachiClimate::set_swing_v_toggle_(bool on) {
|
||||
uint8_t button = get_button_(); // Get the current button value.
|
||||
if (on)
|
||||
button = HITACHI_AC344_BUTTON_SWINGV; // Set the button to SwingV.
|
||||
else if (button == HITACHI_AC344_BUTTON_SWINGV) // Asked to unset it
|
||||
// It was set previous, so use Power as a default
|
||||
button = HITACHI_AC344_BUTTON_POWER;
|
||||
set_button_(button);
|
||||
}
|
||||
|
||||
bool HitachiClimate::get_swing_v_toggle_() { return get_button_() == HITACHI_AC344_BUTTON_SWINGV; }
|
||||
|
||||
void HitachiClimate::set_swing_v_(bool on) {
|
||||
set_swing_v_toggle_(on); // Set the button value.
|
||||
set_bit(&remote_state_[HITACHI_AC344_SWINGV_BYTE], HITACHI_AC344_SWINGV_OFFSET, on);
|
||||
}
|
||||
|
||||
bool HitachiClimate::get_swing_v_() {
|
||||
return GETBIT8(remote_state_[HITACHI_AC344_SWINGV_BYTE], HITACHI_AC344_SWINGV_OFFSET);
|
||||
}
|
||||
|
||||
void HitachiClimate::set_swing_h_(uint8_t position) {
|
||||
if (position > HITACHI_AC344_SWINGH_LEFT_MAX)
|
||||
return set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE);
|
||||
set_bits(&remote_state_[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE, position);
|
||||
set_button_(HITACHI_AC344_BUTTON_SWINGH);
|
||||
}
|
||||
|
||||
uint8_t HitachiClimate::get_swing_h_() {
|
||||
return GETBITS8(remote_state_[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE);
|
||||
}
|
||||
|
||||
uint8_t HitachiClimate::get_button_() { return remote_state_[HITACHI_AC344_BUTTON_BYTE]; }
|
||||
|
||||
void HitachiClimate::set_button_(uint8_t button) { remote_state_[HITACHI_AC344_BUTTON_BYTE] = button; }
|
||||
|
||||
void HitachiClimate::transmit_state() {
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
set_mode_(HITACHI_AC344_MODE_COOL);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
set_mode_(HITACHI_AC344_MODE_DRY);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
set_mode_(HITACHI_AC344_MODE_HEAT);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
set_mode_(HITACHI_AC344_MODE_AUTO);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
set_mode_(HITACHI_AC344_MODE_FAN);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
set_power_(false);
|
||||
break;
|
||||
}
|
||||
|
||||
set_temp_(static_cast<uint8_t>(this->target_temperature));
|
||||
|
||||
switch (this->fan_mode) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
set_fan_(HITACHI_AC344_FAN_LOW);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
set_fan_(HITACHI_AC344_FAN_MEDIUM);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
set_fan_(HITACHI_AC344_FAN_HIGH);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_ON:
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
set_fan_(HITACHI_AC344_FAN_AUTO);
|
||||
}
|
||||
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
set_swing_v_(true);
|
||||
set_swing_h_(HITACHI_AC344_SWINGH_AUTO);
|
||||
break;
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
set_swing_v_(true);
|
||||
set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE);
|
||||
break;
|
||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||
set_swing_v_(false);
|
||||
set_swing_h_(HITACHI_AC344_SWINGH_AUTO);
|
||||
break;
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
set_swing_v_(false);
|
||||
set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: find change value to set button, now always set to power button
|
||||
set_button_(HITACHI_AC344_BUTTON_POWER);
|
||||
|
||||
invert_byte_pairs(remote_state_ + 3, HITACHI_AC344_STATE_LENGTH - 3);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
data->set_carrier_frequency(HITACHI_AC344_FREQ);
|
||||
|
||||
uint8_t repeat = 0;
|
||||
for (uint8_t r = 0; r <= repeat; r++) {
|
||||
// Header
|
||||
data->item(HITACHI_AC344_HDR_MARK, HITACHI_AC344_HDR_SPACE);
|
||||
// Data
|
||||
for (uint8_t i : remote_state_) {
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
data->mark(HITACHI_AC344_BIT_MARK);
|
||||
bool bit = i & (1 << j);
|
||||
data->space(bit ? HITACHI_AC344_ONE_SPACE : HITACHI_AC344_ZERO_SPACE);
|
||||
}
|
||||
}
|
||||
// Footer
|
||||
data->item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_MIN_GAP);
|
||||
}
|
||||
transmit.perform();
|
||||
|
||||
dump_state_("Sent", remote_state_);
|
||||
}
|
||||
|
||||
bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) {
|
||||
uint8_t power = remote_state[HITACHI_AC344_POWER_BYTE];
|
||||
ESP_LOGV(TAG, "Power: %02X %02X", remote_state[HITACHI_AC344_POWER_BYTE], power);
|
||||
uint8_t mode = remote_state[HITACHI_AC344_MODE_BYTE] & 0xF;
|
||||
ESP_LOGV(TAG, "Mode: %02X %02X", remote_state[HITACHI_AC344_MODE_BYTE], mode);
|
||||
if (power == HITACHI_AC344_POWER_ON) {
|
||||
switch (mode) {
|
||||
case HITACHI_AC344_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case HITACHI_AC344_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case HITACHI_AC344_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case HITACHI_AC344_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
break;
|
||||
case HITACHI_AC344_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HitachiClimate::parse_temperature_(const uint8_t remote_state[]) {
|
||||
uint8_t temperature =
|
||||
GETBITS8(remote_state[HITACHI_AC344_TEMP_BYTE], HITACHI_AC344_TEMP_OFFSET, HITACHI_AC344_TEMP_SIZE);
|
||||
this->target_temperature = temperature;
|
||||
ESP_LOGV(TAG, "Temperature: %02X %02u %04f", remote_state[HITACHI_AC344_TEMP_BYTE], temperature,
|
||||
this->target_temperature);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HitachiClimate::parse_fan_(const uint8_t remote_state[]) {
|
||||
uint8_t fan_mode = remote_state[HITACHI_AC344_FAN_BYTE] >> 4 & 0xF;
|
||||
ESP_LOGV(TAG, "Fan: %02X %02X", remote_state[HITACHI_AC344_FAN_BYTE], fan_mode);
|
||||
switch (fan_mode) {
|
||||
case HITACHI_AC344_FAN_MIN:
|
||||
case HITACHI_AC344_FAN_LOW:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case HITACHI_AC344_FAN_MEDIUM:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case HITACHI_AC344_FAN_HIGH:
|
||||
case HITACHI_AC344_FAN_MAX:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case HITACHI_AC344_FAN_AUTO:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) {
|
||||
uint8_t swing_modeh =
|
||||
GETBITS8(remote_state[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE);
|
||||
ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh);
|
||||
|
||||
if ((swing_modeh & 0x7) == 0x0) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||
} else if ((swing_modeh & 0x3) == 0x3) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
} else {
|
||||
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
// Validate header
|
||||
if (!data.expect_item(HITACHI_AC344_HDR_MARK, HITACHI_AC344_HDR_SPACE)) {
|
||||
ESP_LOGVV(TAG, "Header fail");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t recv_state[HITACHI_AC344_STATE_LENGTH] = {0};
|
||||
// Read all bytes.
|
||||
for (uint8_t pos = 0; pos < HITACHI_AC344_STATE_LENGTH; pos++) {
|
||||
// Read bit
|
||||
for (int8_t bit = 0; bit < 8; bit++) {
|
||||
if (data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ONE_SPACE))
|
||||
recv_state[pos] |= 1 << bit;
|
||||
else if (!data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ZERO_SPACE)) {
|
||||
ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate footer
|
||||
if (!data.expect_mark(HITACHI_AC344_BIT_MARK)) {
|
||||
ESP_LOGVV(TAG, "Footer fail");
|
||||
return false;
|
||||
}
|
||||
|
||||
dump_state_("Recv", recv_state);
|
||||
|
||||
// parse mode
|
||||
this->parse_mode_(recv_state);
|
||||
// parse temperature
|
||||
this->parse_temperature_(recv_state);
|
||||
// parse fan
|
||||
this->parse_fan_(recv_state);
|
||||
// parse swingv
|
||||
this->parse_swing_(recv_state);
|
||||
this->publish_state();
|
||||
for (uint8_t i = 0; i < HITACHI_AC344_STATE_LENGTH; i++)
|
||||
remote_state_[i] = recv_state[i];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HitachiClimate::dump_state_(const char action[], uint8_t state[]) {
|
||||
for (uint16_t i = 0; i < HITACHI_AC344_STATE_LENGTH - 10; i += 10) {
|
||||
ESP_LOGV(TAG, "%s: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", action, state[i + 0], state[i + 1],
|
||||
state[i + 2], state[i + 3], state[i + 4], state[i + 5], state[i + 6], state[i + 7], state[i + 8],
|
||||
state[i + 9]);
|
||||
}
|
||||
ESP_LOGV(TAG, "%s: %02X %02X %02X", action, state[40], state[41], state[42]);
|
||||
}
|
||||
|
||||
} // namespace hitachi_ac344
|
||||
} // namespace esphome
|
122
esphome/components/hitachi_ac344/hitachi_ac344.h
Normal file
122
esphome/components/hitachi_ac344/hitachi_ac344.h
Normal file
|
@ -0,0 +1,122 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/climate_ir/climate_ir.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hitachi_ac344 {
|
||||
|
||||
const uint16_t HITACHI_AC344_HDR_MARK = 3300; // ac
|
||||
const uint16_t HITACHI_AC344_HDR_SPACE = 1700; // ac
|
||||
const uint16_t HITACHI_AC344_BIT_MARK = 400;
|
||||
const uint16_t HITACHI_AC344_ONE_SPACE = 1250;
|
||||
const uint16_t HITACHI_AC344_ZERO_SPACE = 500;
|
||||
const uint32_t HITACHI_AC344_MIN_GAP = 100000; // just a guess.
|
||||
const uint16_t HITACHI_AC344_FREQ = 38000; // Hz.
|
||||
|
||||
const uint8_t HITACHI_AC344_BUTTON_BYTE = 11;
|
||||
const uint8_t HITACHI_AC344_BUTTON_POWER = 0x13;
|
||||
const uint8_t HITACHI_AC344_BUTTON_SLEEP = 0x31;
|
||||
const uint8_t HITACHI_AC344_BUTTON_MODE = 0x41;
|
||||
const uint8_t HITACHI_AC344_BUTTON_FAN = 0x42;
|
||||
const uint8_t HITACHI_AC344_BUTTON_TEMP_DOWN = 0x43;
|
||||
const uint8_t HITACHI_AC344_BUTTON_TEMP_UP = 0x44;
|
||||
const uint8_t HITACHI_AC344_BUTTON_SWINGV = 0x81;
|
||||
const uint8_t HITACHI_AC344_BUTTON_SWINGH = 0x8C;
|
||||
const uint8_t HITACHI_AC344_BUTTON_MILDEWPROOF = 0xE2;
|
||||
|
||||
const uint8_t HITACHI_AC344_TEMP_BYTE = 13;
|
||||
const uint8_t HITACHI_AC344_TEMP_OFFSET = 2;
|
||||
const uint8_t HITACHI_AC344_TEMP_SIZE = 6;
|
||||
const uint8_t HITACHI_AC344_TEMP_MIN = 16; // 16C
|
||||
const uint8_t HITACHI_AC344_TEMP_MAX = 32; // 32C
|
||||
const uint8_t HITACHI_AC344_TEMP_FAN = 27; // 27C
|
||||
|
||||
const uint8_t HITACHI_AC344_TIMER_BYTE = 15;
|
||||
|
||||
const uint8_t HITACHI_AC344_MODE_BYTE = 25;
|
||||
const uint8_t HITACHI_AC344_MODE_FAN = 1;
|
||||
const uint8_t HITACHI_AC344_MODE_COOL = 3;
|
||||
const uint8_t HITACHI_AC344_MODE_DRY = 5;
|
||||
const uint8_t HITACHI_AC344_MODE_HEAT = 6;
|
||||
const uint8_t HITACHI_AC344_MODE_AUTO = 7;
|
||||
|
||||
const uint8_t HITACHI_AC344_FAN_BYTE = HITACHI_AC344_MODE_BYTE;
|
||||
const uint8_t HITACHI_AC344_FAN_MIN = 1;
|
||||
const uint8_t HITACHI_AC344_FAN_LOW = 2;
|
||||
const uint8_t HITACHI_AC344_FAN_MEDIUM = 3;
|
||||
const uint8_t HITACHI_AC344_FAN_HIGH = 4;
|
||||
const uint8_t HITACHI_AC344_FAN_AUTO = 5;
|
||||
const uint8_t HITACHI_AC344_FAN_MAX = 6;
|
||||
const uint8_t HITACHI_AC344_FAN_MAX_DRY = 2;
|
||||
|
||||
const uint8_t HITACHI_AC344_POWER_BYTE = 27;
|
||||
const uint8_t HITACHI_AC344_POWER_ON = 0xF1;
|
||||
const uint8_t HITACHI_AC344_POWER_OFF = 0xE1;
|
||||
|
||||
const uint8_t HITACHI_AC344_SWINGH_BYTE = 35;
|
||||
const uint8_t HITACHI_AC344_SWINGH_OFFSET = 0; // Mask 0b00000xxx
|
||||
const uint8_t HITACHI_AC344_SWINGH_SIZE = 3; // Mask 0b00000xxx
|
||||
const uint8_t HITACHI_AC344_SWINGH_AUTO = 0; // 0b000
|
||||
const uint8_t HITACHI_AC344_SWINGH_RIGHT_MAX = 1; // 0b001
|
||||
const uint8_t HITACHI_AC344_SWINGH_RIGHT = 2; // 0b010
|
||||
const uint8_t HITACHI_AC344_SWINGH_MIDDLE = 3; // 0b011
|
||||
const uint8_t HITACHI_AC344_SWINGH_LEFT = 4; // 0b100
|
||||
const uint8_t HITACHI_AC344_SWINGH_LEFT_MAX = 5; // 0b101
|
||||
|
||||
const uint8_t HITACHI_AC344_SWINGV_BYTE = 37;
|
||||
const uint8_t HITACHI_AC344_SWINGV_OFFSET = 5; // Mask 0b00x00000
|
||||
|
||||
const uint8_t HITACHI_AC344_MILDEWPROOF_BYTE = HITACHI_AC344_SWINGV_BYTE;
|
||||
const uint8_t HITACHI_AC344_MILDEWPROOF_OFFSET = 2; // Mask 0b00000x00
|
||||
|
||||
const uint16_t HITACHI_AC344_STATE_LENGTH = 43;
|
||||
const uint16_t HITACHI_AC344_BITS = HITACHI_AC344_STATE_LENGTH * 8;
|
||||
|
||||
#define GETBIT8(a, b) (a & ((uint8_t) 1 << b))
|
||||
#define GETBITS8(data, offset, size) (((data) & (((uint8_t) UINT8_MAX >> (8 - (size))) << (offset))) >> (offset))
|
||||
|
||||
class HitachiClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
HitachiClimate()
|
||||
: climate_ir::ClimateIR(
|
||||
HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true,
|
||||
std::vector<climate::ClimateFanMode>{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
|
||||
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
|
||||
std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {}
|
||||
|
||||
protected:
|
||||
uint8_t remote_state_[HITACHI_AC344_STATE_LENGTH]{0x01, 0x10, 0x00, 0x40, 0x00, 0xFF, 0x00, 0xCC, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
|
||||
0x80, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t previous_temp_{27};
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void transmit_state() override;
|
||||
bool get_power_();
|
||||
void set_power_(bool on);
|
||||
uint8_t get_mode_();
|
||||
void set_mode_(uint8_t mode);
|
||||
void set_temp_(uint8_t celsius, bool set_previous = false);
|
||||
uint8_t get_fan_();
|
||||
void set_fan_(uint8_t speed);
|
||||
void set_swing_v_toggle_(bool on);
|
||||
bool get_swing_v_toggle_();
|
||||
void set_swing_v_(bool on);
|
||||
bool get_swing_v_();
|
||||
void set_swing_h_(uint8_t position);
|
||||
uint8_t get_swing_h_();
|
||||
uint8_t get_button_();
|
||||
void set_button_(uint8_t button);
|
||||
// Handle received IR Buffer
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
bool parse_mode_(const uint8_t remote_state[]);
|
||||
bool parse_temperature_(const uint8_t remote_state[]);
|
||||
bool parse_fan_(const uint8_t remote_state[]);
|
||||
bool parse_swing_(const uint8_t remote_state[]);
|
||||
bool parse_state_frame_(const uint8_t frame[]);
|
||||
void dump_state_(const char action[], uint8_t remote_state[]);
|
||||
};
|
||||
|
||||
} // namespace hitachi_ac344
|
||||
} // namespace esphome
|
|
@ -17,7 +17,7 @@ class AQICalculator : public AbstractAQICalculator {
|
|||
|
||||
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 51}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
||||
|
||||
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 45}, {36, 55}, {56, 150}, {151, 250}, {251, 500}};
|
||||
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 35}, {36, 55}, {56, 150}, {151, 250}, {251, 500}};
|
||||
|
||||
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
|
||||
{255, 354}, {355, 424}, {425, 604}};
|
||||
|
|
|
@ -43,8 +43,8 @@ def validate_url(value):
|
|||
value = cv.string(value)
|
||||
try:
|
||||
parsed = list(urlparse.urlparse(value))
|
||||
except Exception:
|
||||
raise cv.Invalid('Invalid URL')
|
||||
except Exception as err:
|
||||
raise cv.Invalid('Invalid URL') from err
|
||||
|
||||
if not parsed[0] or not parsed[1]:
|
||||
raise cv.Invalid('URL must have a URL scheme and host')
|
||||
|
|
|
@ -12,9 +12,20 @@ void HttpRequestComponent::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " User-Agent: %s", this->useragent_);
|
||||
}
|
||||
|
||||
void HttpRequestComponent::set_url(std::string url) {
|
||||
this->url_ = url;
|
||||
this->secure_ = url.compare(0, 6, "https:") == 0;
|
||||
|
||||
if (!this->last_url_.empty() && this->url_ != this->last_url_) {
|
||||
// Close connection if url has been changed
|
||||
this->client_.setReuse(false);
|
||||
this->client_.end();
|
||||
}
|
||||
this->client_.setReuse(true);
|
||||
}
|
||||
|
||||
void HttpRequestComponent::send() {
|
||||
bool begin_status = false;
|
||||
this->client_.setReuse(true);
|
||||
const String url = this->url_.c_str();
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
begin_status = this->client_.begin(url);
|
||||
|
@ -78,7 +89,10 @@ WiFiClient *HttpRequestComponent::get_wifi_client_() {
|
|||
}
|
||||
#endif
|
||||
|
||||
void HttpRequestComponent::close() { this->client_.end(); }
|
||||
void HttpRequestComponent::close() {
|
||||
this->last_url_ = this->url_;
|
||||
this->client_.end();
|
||||
}
|
||||
|
||||
const char *HttpRequestComponent::get_string() {
|
||||
static const String STR = this->client_.getString();
|
||||
|
|
|
@ -27,10 +27,7 @@ class HttpRequestComponent : public Component {
|
|||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void set_url(std::string url) {
|
||||
this->url_ = url;
|
||||
this->secure_ = url.compare(0, 6, "https:") == 0;
|
||||
}
|
||||
void set_url(std::string url);
|
||||
void set_method(const char *method) { this->method_ = method; }
|
||||
void set_useragent(const char *useragent) { this->useragent_ = useragent; }
|
||||
void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
|
||||
|
@ -43,6 +40,7 @@ class HttpRequestComponent : public Component {
|
|||
protected:
|
||||
HTTPClient client_{};
|
||||
std::string url_;
|
||||
std::string last_url_;
|
||||
const char *method_;
|
||||
const char *useragent_{nullptr};
|
||||
bool secure_;
|
||||
|
|
|
@ -56,8 +56,8 @@ void I2CComponent::raw_begin_transmission(uint8_t address) {
|
|||
ESP_LOGVV(TAG, "Beginning Transmission to 0x%02X:", address);
|
||||
this->wire_->beginTransmission(address);
|
||||
}
|
||||
bool I2CComponent::raw_end_transmission(uint8_t address) {
|
||||
uint8_t status = this->wire_->endTransmission();
|
||||
bool I2CComponent::raw_end_transmission(uint8_t address, bool send_stop) {
|
||||
uint8_t status = this->wire_->endTransmission(send_stop);
|
||||
ESP_LOGVV(TAG, " Transmission ended. Status code: 0x%02X", status);
|
||||
|
||||
switch (status) {
|
||||
|
|
|
@ -94,7 +94,7 @@ class I2CComponent : public Component {
|
|||
void raw_begin_transmission(uint8_t address);
|
||||
|
||||
/// End a write transmission to an address, return true if successful.
|
||||
bool raw_end_transmission(uint8_t address);
|
||||
bool raw_end_transmission(uint8_t address, bool send_stop = true);
|
||||
|
||||
/** Request data from an address with a number of (8-bit) bytes.
|
||||
*
|
||||
|
@ -173,6 +173,17 @@ class I2CDevice {
|
|||
|
||||
I2CRegister reg(uint8_t a_register) { return {this, a_register}; }
|
||||
|
||||
/// Begin a write transmission.
|
||||
void raw_begin_transmission() { this->parent_->raw_begin_transmission(this->address_); };
|
||||
|
||||
/// End a write transmission, return true if successful.
|
||||
bool raw_end_transmission(bool send_stop = true) {
|
||||
return this->parent_->raw_end_transmission(this->address_, send_stop);
|
||||
};
|
||||
|
||||
/// Write len amount of bytes from data. begin_transmission_ must be called before this.
|
||||
void raw_write(const uint8_t *data, uint8_t len) { this->parent_->raw_write(this->address_, data, len); };
|
||||
|
||||
/** Read len amount of bytes from a register into data. Optionally with a conversion time after
|
||||
* writing the register value to the bus.
|
||||
*
|
||||
|
|
0
esphome/components/ili9341/__init__.py
Normal file
0
esphome/components/ili9341/__init__.py
Normal file
61
esphome/components/ili9341/display.py
Normal file
61
esphome/components/ili9341/display.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import display, spi
|
||||
from esphome.const import CONF_DC_PIN, \
|
||||
CONF_ID, CONF_LAMBDA, CONF_MODEL, CONF_PAGES, CONF_RESET_PIN
|
||||
|
||||
DEPENDENCIES = ['spi']
|
||||
|
||||
CONF_LED_PIN = 'led_pin'
|
||||
|
||||
ili9341_ns = cg.esphome_ns.namespace('ili9341')
|
||||
ili9341 = ili9341_ns.class_('ILI9341Display', cg.PollingComponent, spi.SPIDevice,
|
||||
display.DisplayBuffer)
|
||||
ILI9341M5Stack = ili9341_ns.class_('ILI9341M5Stack', ili9341)
|
||||
ILI9341TFT24 = ili9341_ns.class_('ILI9341TFT24', ili9341)
|
||||
|
||||
ILI9341Model = ili9341_ns.enum('ILI9341Model')
|
||||
|
||||
MODELS = {
|
||||
'M5STACK': ILI9341Model.M5STACK,
|
||||
'TFT_2.4': ILI9341Model.TFT_24,
|
||||
}
|
||||
|
||||
ILI9341_MODEL = cv.enum(MODELS, upper=True, space="_")
|
||||
|
||||
CONFIG_SCHEMA = cv.All(display.FULL_DISPLAY_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(ili9341),
|
||||
cv.Required(CONF_MODEL): ILI9341_MODEL,
|
||||
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_LED_PIN): pins.gpio_output_pin_schema,
|
||||
}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema()),
|
||||
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
if config[CONF_MODEL] == 'M5STACK':
|
||||
lcd_type = ILI9341M5Stack
|
||||
if config[CONF_MODEL] == 'TFT_2.4':
|
||||
lcd_type = ILI9341TFT24
|
||||
rhs = lcd_type.new()
|
||||
var = cg.Pvariable(config[CONF_ID], rhs)
|
||||
|
||||
yield cg.register_component(var, config)
|
||||
yield display.register_display(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
dc = yield cg.gpio_pin_expression(config[CONF_DC_PIN])
|
||||
cg.add(var.set_dc_pin(dc))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')],
|
||||
return_type=cg.void)
|
||||
cg.add(var.set_writer(lambda_))
|
||||
if CONF_RESET_PIN in config:
|
||||
reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN])
|
||||
cg.add(var.set_reset_pin(reset))
|
||||
if CONF_LED_PIN in config:
|
||||
led_pin = yield cg.gpio_pin_expression(config[CONF_LED_PIN])
|
||||
cg.add(var.set_led_pin(led_pin))
|
83
esphome/components/ili9341/ili9341_defines.h
Normal file
83
esphome/components/ili9341/ili9341_defines.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
namespace ili9341 {
|
||||
|
||||
// Color definitions
|
||||
// clang-format off
|
||||
static const uint8_t MADCTL_MY = 0x80; ///< Bit 7 Bottom to top
|
||||
static const uint8_t MADCTL_MX = 0x40; ///< Bit 6 Right to left
|
||||
static const uint8_t MADCTL_MV = 0x20; ///< Bit 5 Reverse Mode
|
||||
static const uint8_t MADCTL_ML = 0x10; ///< Bit 4 LCD refresh Bottom to top
|
||||
static const uint8_t MADCTL_RGB = 0x00; ///< Bit 3 Red-Green-Blue pixel order
|
||||
static const uint8_t MADCTL_BGR = 0x08; ///< Bit 3 Blue-Green-Red pixel order
|
||||
static const uint8_t MADCTL_MH = 0x04; ///< Bit 2 LCD refresh right to left
|
||||
// clang-format on
|
||||
|
||||
static const uint16_t ILI9341_TFTWIDTH = 320; ///< ILI9341 max TFT width
|
||||
static const uint16_t ILI9341_TFTHEIGHT = 240; ///< ILI9341 max TFT height
|
||||
|
||||
// All ILI9341 specific commands some are used by init()
|
||||
static const uint8_t ILI9341_NOP = 0x00;
|
||||
static const uint8_t ILI9341_SWRESET = 0x01;
|
||||
static const uint8_t ILI9341_RDDID = 0x04;
|
||||
static const uint8_t ILI9341_RDDST = 0x09;
|
||||
|
||||
static const uint8_t ILI9341_SLPIN = 0x10;
|
||||
static const uint8_t ILI9341_SLPOUT = 0x11;
|
||||
static const uint8_t ILI9341_PTLON = 0x12;
|
||||
static const uint8_t ILI9341_NORON = 0x13;
|
||||
|
||||
static const uint8_t ILI9341_RDMODE = 0x0A;
|
||||
static const uint8_t ILI9341_RDMADCTL = 0x0B;
|
||||
static const uint8_t ILI9341_RDPIXFMT = 0x0C;
|
||||
static const uint8_t ILI9341_RDIMGFMT = 0x0A;
|
||||
static const uint8_t ILI9341_RDSELFDIAG = 0x0F;
|
||||
|
||||
static const uint8_t ILI9341_INVOFF = 0x20;
|
||||
static const uint8_t ILI9341_INVON = 0x21;
|
||||
static const uint8_t ILI9341_GAMMASET = 0x26;
|
||||
static const uint8_t ILI9341_DISPOFF = 0x28;
|
||||
static const uint8_t ILI9341_DISPON = 0x29;
|
||||
|
||||
static const uint8_t ILI9341_CASET = 0x2A;
|
||||
static const uint8_t ILI9341_PASET = 0x2B;
|
||||
static const uint8_t ILI9341_RAMWR = 0x2C;
|
||||
static const uint8_t ILI9341_RAMRD = 0x2E;
|
||||
|
||||
static const uint8_t ILI9341_PTLAR = 0x30;
|
||||
static const uint8_t ILI9341_VSCRDEF = 0x33;
|
||||
static const uint8_t ILI9341_MADCTL = 0x36;
|
||||
static const uint8_t ILI9341_VSCRSADD = 0x37;
|
||||
static const uint8_t ILI9341_PIXFMT = 0x3A;
|
||||
|
||||
static const uint8_t ILI9341_WRDISBV = 0x51;
|
||||
static const uint8_t ILI9341_RDDISBV = 0x52;
|
||||
static const uint8_t ILI9341_WRCTRLD = 0x53;
|
||||
|
||||
static const uint8_t ILI9341_FRMCTR1 = 0xB1;
|
||||
static const uint8_t ILI9341_FRMCTR2 = 0xB2;
|
||||
static const uint8_t ILI9341_FRMCTR3 = 0xB3;
|
||||
static const uint8_t ILI9341_INVCTR = 0xB4;
|
||||
static const uint8_t ILI9341_DFUNCTR = 0xB6;
|
||||
|
||||
static const uint8_t ILI9341_PWCTR1 = 0xC0;
|
||||
static const uint8_t ILI9341_PWCTR2 = 0xC1;
|
||||
static const uint8_t ILI9341_PWCTR3 = 0xC2;
|
||||
static const uint8_t ILI9341_PWCTR4 = 0xC3;
|
||||
static const uint8_t ILI9341_PWCTR5 = 0xC4;
|
||||
static const uint8_t ILI9341_VMCTR1 = 0xC5;
|
||||
static const uint8_t ILI9341_VMCTR2 = 0xC7;
|
||||
|
||||
static const uint8_t ILI9341_RDID4 = 0xD3;
|
||||
static const uint8_t ILI9341_RDINDEX = 0xD9;
|
||||
static const uint8_t ILI9341_RDID1 = 0xDA;
|
||||
static const uint8_t ILI9341_RDID2 = 0xDB;
|
||||
static const uint8_t ILI9341_RDID3 = 0xDC;
|
||||
static const uint8_t ILI9341_RDIDX = 0xDD; // TBC
|
||||
|
||||
static const uint8_t ILI9341_GMCTRP1 = 0xE0;
|
||||
static const uint8_t ILI9341_GMCTRN1 = 0xE1;
|
||||
|
||||
} // namespace ili9341
|
||||
} // namespace esphome
|
240
esphome/components/ili9341/ili9341_display.cpp
Normal file
240
esphome/components/ili9341/ili9341_display.cpp
Normal file
|
@ -0,0 +1,240 @@
|
|||
#include "ili9341_display.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ili9341 {
|
||||
|
||||
static const char *TAG = "ili9341";
|
||||
|
||||
void ILI9341Display::setup_pins_() {
|
||||
this->init_internal_(this->get_buffer_length_());
|
||||
this->dc_pin_->setup(); // OUTPUT
|
||||
this->dc_pin_->digital_write(false);
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup(); // OUTPUT
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
if (this->led_pin_ != nullptr) {
|
||||
this->led_pin_->setup();
|
||||
this->led_pin_->digital_write(true);
|
||||
}
|
||||
this->spi_setup();
|
||||
|
||||
this->reset_();
|
||||
}
|
||||
|
||||
void ILI9341Display::dump_config() {
|
||||
LOG_DISPLAY("", "ili9341", this);
|
||||
ESP_LOGCONFIG(TAG, " Width: %d, Height: %d, Rotation: %d", this->width_, this->height_, this->rotation_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||
LOG_PIN(" Backlight Pin: ", this->led_pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
float ILI9341Display::get_setup_priority() const { return setup_priority::PROCESSOR; }
|
||||
void ILI9341Display::command(uint8_t value) {
|
||||
this->start_command_();
|
||||
this->write_byte(value);
|
||||
this->end_command_();
|
||||
}
|
||||
|
||||
void ILI9341Display::reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
void ILI9341Display::data(uint8_t value) {
|
||||
this->start_data_();
|
||||
this->write_byte(value);
|
||||
this->end_data_();
|
||||
}
|
||||
|
||||
void ILI9341Display::send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes) {
|
||||
this->command(command_byte); // Send the command byte
|
||||
this->start_data_();
|
||||
this->write_array(data_bytes, num_data_bytes);
|
||||
this->end_data_();
|
||||
}
|
||||
|
||||
uint8_t ILI9341Display::read_command(uint8_t command_byte, uint8_t index) {
|
||||
uint8_t data = 0x10 + index;
|
||||
this->send_command(0xD9, &data, 1); // Set Index Register
|
||||
uint8_t result;
|
||||
this->start_command_();
|
||||
this->write_byte(command_byte);
|
||||
this->start_data_();
|
||||
do {
|
||||
result = this->read_byte();
|
||||
} while (index--);
|
||||
this->end_data_();
|
||||
return result;
|
||||
}
|
||||
|
||||
void ILI9341Display::update() {
|
||||
this->do_update_();
|
||||
this->display_();
|
||||
}
|
||||
|
||||
void ILI9341Display::display_() {
|
||||
// we will only update the changed window to the display
|
||||
int w = this->x_high_ - this->x_low_ + 1;
|
||||
int h = this->y_high_ - this->y_low_ + 1;
|
||||
|
||||
set_addr_window_(this->x_low_, this->y_low_, w, h);
|
||||
this->start_data_();
|
||||
uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_);
|
||||
for (uint16_t row = 0; row < h; row++) {
|
||||
for (uint16_t col = 0; col < w; col++) {
|
||||
uint32_t pos = start_pos + (row * width_) + col;
|
||||
|
||||
uint16_t color = convert_to_16bit_color_(buffer_[pos]);
|
||||
this->write_byte(color >> 8);
|
||||
this->write_byte(color);
|
||||
}
|
||||
}
|
||||
this->end_data_();
|
||||
|
||||
// invalidate watermarks
|
||||
this->x_low_ = this->width_;
|
||||
this->y_low_ = this->height_;
|
||||
this->x_high_ = 0;
|
||||
this->y_high_ = 0;
|
||||
}
|
||||
|
||||
uint16_t ILI9341Display::convert_to_16bit_color_(uint8_t color_8bit) {
|
||||
int r = color_8bit >> 5;
|
||||
int g = (color_8bit >> 2) & 0x07;
|
||||
int b = color_8bit & 0x03;
|
||||
uint16_t color = (r * 0x04) << 11;
|
||||
color |= (g * 0x09) << 5;
|
||||
color |= (b * 0x0A);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
uint8_t ILI9341Display::convert_to_8bit_color_(uint16_t color_16bit) {
|
||||
// convert 16bit color to 8 bit buffer
|
||||
uint8_t r = color_16bit >> 11;
|
||||
uint8_t g = (color_16bit >> 5) & 0x3F;
|
||||
uint8_t b = color_16bit & 0x1F;
|
||||
|
||||
return ((b / 0x0A) | ((g / 0x09) << 2) | ((r / 0x04) << 5));
|
||||
}
|
||||
|
||||
void ILI9341Display::fill(Color color) {
|
||||
auto color565 = color.to_rgb_565();
|
||||
memset(this->buffer_, convert_to_8bit_color_(color565), this->get_buffer_length_());
|
||||
this->x_low_ = 0;
|
||||
this->y_low_ = 0;
|
||||
this->x_high_ = this->get_width_internal() - 1;
|
||||
this->y_high_ = this->get_height_internal() - 1;
|
||||
}
|
||||
|
||||
void ILI9341Display::fill_internal_(Color color) {
|
||||
this->set_addr_window_(0, 0, this->get_width_internal(), this->get_height_internal());
|
||||
this->start_data_();
|
||||
|
||||
auto color565 = color.to_rgb_565();
|
||||
for (uint32_t i = 0; i < (this->get_width_internal()) * (this->get_height_internal()); i++) {
|
||||
this->write_byte(color565 >> 8);
|
||||
this->write_byte(color565);
|
||||
buffer_[i] = 0;
|
||||
}
|
||||
this->end_data_();
|
||||
}
|
||||
|
||||
void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
|
||||
return;
|
||||
|
||||
// low and high watermark may speed up drawing from buffer
|
||||
this->x_low_ = (x < this->x_low_) ? x : this->x_low_;
|
||||
this->y_low_ = (y < this->y_low_) ? y : this->y_low_;
|
||||
this->x_high_ = (x > this->x_high_) ? x : this->x_high_;
|
||||
this->y_high_ = (y > this->y_high_) ? y : this->y_high_;
|
||||
|
||||
uint32_t pos = (y * width_) + x;
|
||||
auto color565 = color.to_rgb_565();
|
||||
buffer_[pos] = convert_to_8bit_color_(color565);
|
||||
}
|
||||
|
||||
// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
|
||||
// values per bit is huge
|
||||
uint32_t ILI9341Display::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); }
|
||||
|
||||
void ILI9341Display::start_command_() {
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
}
|
||||
|
||||
void ILI9341Display::end_command_() { this->disable(); }
|
||||
void ILI9341Display::start_data_() {
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->enable();
|
||||
}
|
||||
void ILI9341Display::end_data_() { this->disable(); }
|
||||
|
||||
void ILI9341Display::init_lcd_(const uint8_t *init_cmd) {
|
||||
uint8_t cmd, x, num_args;
|
||||
const uint8_t *addr = init_cmd;
|
||||
while ((cmd = pgm_read_byte(addr++)) > 0) {
|
||||
x = pgm_read_byte(addr++);
|
||||
num_args = x & 0x7F;
|
||||
send_command(cmd, addr, num_args);
|
||||
addr += num_args;
|
||||
if (x & 0x80)
|
||||
delay(150); // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
void ILI9341Display::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t w, uint16_t h) {
|
||||
uint16_t x2 = (x1 + w - 1), y2 = (y1 + h - 1);
|
||||
this->command(ILI9341_CASET); // Column address set
|
||||
this->start_data_();
|
||||
this->write_byte(x1 >> 8);
|
||||
this->write_byte(x1);
|
||||
this->write_byte(x2 >> 8);
|
||||
this->write_byte(x2);
|
||||
this->end_data_();
|
||||
this->command(ILI9341_PASET); // Row address set
|
||||
this->start_data_();
|
||||
this->write_byte(y1 >> 8);
|
||||
this->write_byte(y1);
|
||||
this->write_byte(y2 >> 8);
|
||||
this->write_byte(y2);
|
||||
this->end_data_();
|
||||
this->command(ILI9341_RAMWR); // Write to RAM
|
||||
}
|
||||
|
||||
void ILI9341Display::invert_display_(bool invert) { this->command(invert ? ILI9341_INVON : ILI9341_INVOFF); }
|
||||
|
||||
int ILI9341Display::get_width_internal() { return this->width_; }
|
||||
int ILI9341Display::get_height_internal() { return this->height_; }
|
||||
|
||||
// M5Stack display
|
||||
void ILI9341M5Stack::initialize() {
|
||||
this->init_lcd_(INITCMD_M5STACK);
|
||||
this->width_ = 320;
|
||||
this->height_ = 240;
|
||||
this->invert_display_(true);
|
||||
this->fill_internal_(COLOR_BLACK);
|
||||
}
|
||||
|
||||
// 24_TFT display
|
||||
void ILI9341TFT24::initialize() {
|
||||
this->init_lcd_(INITCMD_TFT);
|
||||
this->width_ = 240;
|
||||
this->height_ = 320;
|
||||
this->fill_internal_(COLOR_BLACK);
|
||||
}
|
||||
|
||||
} // namespace ili9341
|
||||
} // namespace esphome
|
92
esphome/components/ili9341/ili9341_display.h
Normal file
92
esphome/components/ili9341/ili9341_display.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/components/display/display_buffer.h"
|
||||
#include "ili9341_defines.h"
|
||||
#include "ili9341_init.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ili9341 {
|
||||
|
||||
enum ILI9341Model {
|
||||
M5STACK = 0,
|
||||
TFT_24,
|
||||
};
|
||||
|
||||
class ILI9341Display : public PollingComponent,
|
||||
public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> {
|
||||
public:
|
||||
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
||||
float get_setup_priority() const override;
|
||||
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
||||
void set_led_pin(GPIOPin *led) { this->led_pin_ = led; }
|
||||
void set_model(ILI9341Model model) { this->model_ = model; }
|
||||
|
||||
void command(uint8_t value);
|
||||
void data(uint8_t value);
|
||||
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
|
||||
uint8_t read_command(uint8_t command_byte, uint8_t index);
|
||||
virtual void initialize() = 0;
|
||||
|
||||
void update() override;
|
||||
|
||||
void fill(Color color) override;
|
||||
|
||||
void dump_config() override;
|
||||
void setup() override {
|
||||
this->setup_pins_();
|
||||
this->initialize();
|
||||
}
|
||||
|
||||
protected:
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
void setup_pins_();
|
||||
|
||||
void init_lcd_(const uint8_t *init_cmd);
|
||||
void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
|
||||
void invert_display_(bool invert);
|
||||
void reset_();
|
||||
void fill_internal_(Color color);
|
||||
void display_();
|
||||
uint16_t convert_to_16bit_color_(uint8_t color_8bit);
|
||||
uint8_t convert_to_8bit_color_(uint16_t color_16bit);
|
||||
|
||||
ILI9341Model model_;
|
||||
int16_t width_{320}; ///< Display width as modified by current rotation
|
||||
int16_t height_{240}; ///< Display height as modified by current rotation
|
||||
uint16_t x_low_{0};
|
||||
uint16_t y_low_{0};
|
||||
uint16_t x_high_{0};
|
||||
uint16_t y_high_{0};
|
||||
|
||||
uint32_t get_buffer_length_();
|
||||
int get_width_internal() override;
|
||||
int get_height_internal() override;
|
||||
|
||||
void start_command_();
|
||||
void end_command_();
|
||||
void start_data_();
|
||||
void end_data_();
|
||||
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
GPIOPin *led_pin_{nullptr};
|
||||
GPIOPin *dc_pin_;
|
||||
GPIOPin *busy_pin_{nullptr};
|
||||
};
|
||||
|
||||
//----------- M5Stack display --------------
|
||||
class ILI9341M5Stack : public ILI9341Display {
|
||||
public:
|
||||
void initialize() override;
|
||||
};
|
||||
|
||||
//----------- ILI9341_24_TFT display --------------
|
||||
class ILI9341TFT24 : public ILI9341Display {
|
||||
public:
|
||||
void initialize() override;
|
||||
};
|
||||
} // namespace ili9341
|
||||
} // namespace esphome
|
70
esphome/components/ili9341/ili9341_init.h
Normal file
70
esphome/components/ili9341/ili9341_init.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ili9341 {
|
||||
|
||||
// clang-format off
|
||||
static const uint8_t PROGMEM INITCMD_M5STACK[] = {
|
||||
0xEF, 3, 0x03, 0x80, 0x02,
|
||||
0xCF, 3, 0x00, 0xC1, 0x30,
|
||||
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
||||
0xE8, 3, 0x85, 0x00, 0x78,
|
||||
0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
|
||||
0xF7, 1, 0x20,
|
||||
0xEA, 2, 0x00, 0x00,
|
||||
ILI9341_PWCTR1 , 1, 0x23, // Power control VRH[5:0]
|
||||
ILI9341_PWCTR2 , 1, 0x10, // Power control SAP[2:0];BT[3:0]
|
||||
ILI9341_VMCTR1 , 2, 0x3e, 0x28, // VCM control
|
||||
ILI9341_VMCTR2 , 1, 0x86, // VCM control2
|
||||
ILI9341_MADCTL , 1, MADCTL_BGR, // Memory Access Control
|
||||
ILI9341_VSCRSADD, 1, 0x00, // Vertical scroll zero
|
||||
ILI9341_PIXFMT , 1, 0x55,
|
||||
ILI9341_FRMCTR1 , 2, 0x00, 0x13,
|
||||
ILI9341_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
|
||||
0xF2, 1, 0x00, // 3Gamma Function Disable
|
||||
ILI9341_GAMMASET , 1, 0x01, // Gamma curve selected
|
||||
ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
|
||||
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03,
|
||||
0x0E, 0x09, 0x00,
|
||||
ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
|
||||
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C,
|
||||
0x31, 0x36, 0x0F,
|
||||
ILI9341_SLPOUT , 0x80, // Exit Sleep
|
||||
ILI9341_DISPON , 0x80, // Display on
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_TFT[] = {
|
||||
0xEF, 3, 0x03, 0x80, 0x02,
|
||||
0xCF, 3, 0x00, 0xC1, 0x30,
|
||||
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
||||
0xE8, 3, 0x85, 0x00, 0x78,
|
||||
0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
|
||||
0xF7, 1, 0x20,
|
||||
0xEA, 2, 0x00, 0x00,
|
||||
ILI9341_PWCTR1 , 1, 0x23, // Power control VRH[5:0]
|
||||
ILI9341_PWCTR2 , 1, 0x10, // Power control SAP[2:0];BT[3:0]
|
||||
ILI9341_VMCTR1 , 2, 0x3e, 0x28, // VCM control
|
||||
ILI9341_VMCTR2 , 1, 0x86, // VCM control2
|
||||
ILI9341_MADCTL , 1, 0x48, // Memory Access Control
|
||||
ILI9341_VSCRSADD, 1, 0x00, // Vertical scroll zero
|
||||
ILI9341_PIXFMT , 1, 0x55,
|
||||
ILI9341_FRMCTR1 , 2, 0x00, 0x18,
|
||||
ILI9341_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
|
||||
0xF2, 1, 0x00, // 3Gamma Function Disable
|
||||
ILI9341_GAMMASET , 1, 0x01, // Gamma curve selected
|
||||
ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
|
||||
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03,
|
||||
0x0E, 0x09, 0x00,
|
||||
ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
|
||||
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C,
|
||||
0x31, 0x36, 0x0F,
|
||||
ILI9341_SLPOUT , 0x80, // Exit Sleep
|
||||
ILI9341_DISPON , 0x80, // Display on
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
// clang-format on
|
||||
} // namespace ili9341
|
||||
} // namespace esphome
|
|
@ -4,14 +4,14 @@ from esphome import core
|
|||
from esphome.components import display, font
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE, CONF_DITHER
|
||||
from esphome.core import CORE, HexInt
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['display']
|
||||
MULTI_CONF = True
|
||||
|
||||
|
||||
ImageType = display.display_ns.enum('ImageType')
|
||||
IMAGE_TYPE = {
|
||||
'BINARY': ImageType.IMAGE_TYPE_BINARY,
|
||||
|
@ -28,6 +28,7 @@ IMAGE_SCHEMA = cv.Schema({
|
|||
cv.Required(CONF_FILE): cv.file_,
|
||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
||||
cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(IMAGE_TYPE, upper=True),
|
||||
cv.Optional(CONF_DITHER, default='NONE'): cv.one_of("NONE", "FLOYDSTEINBERG", upper=True),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
})
|
||||
|
||||
|
@ -53,8 +54,9 @@ def to_code(config):
|
|||
_LOGGER.warning("The image you requested is very big. Please consider using"
|
||||
" the resize parameter.")
|
||||
|
||||
dither = Image.NONE if config[CONF_DITHER] == 'NONE' else Image.FLOYDSTEINBERG
|
||||
if config[CONF_TYPE] == 'GRAYSCALE':
|
||||
image = image.convert('L', dither=Image.NONE)
|
||||
image = image.convert('L', dither=dither)
|
||||
pixels = list(image.getdata())
|
||||
data = [0 for _ in range(height * width)]
|
||||
pos = 0
|
||||
|
@ -76,7 +78,7 @@ def to_code(config):
|
|||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == 'BINARY':
|
||||
image = image.convert('1', dither=Image.NONE)
|
||||
image = image.convert('1', dither=dither)
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range(height * width8 // 8)]
|
||||
for y in range(height):
|
||||
|
|
|
@ -44,7 +44,7 @@ void MAX31855Sensor::read_data_() {
|
|||
const uint32_t mem = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3] << 0;
|
||||
|
||||
// Verify we got data
|
||||
if (mem != 0 && mem != 0xFFFFFFFF) {
|
||||
if (mem != 0xFFFFFFFF) {
|
||||
this->status_clear_error();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "No data received from MAX31855 (0x%08X). Check wiring!", mem);
|
||||
|
|
|
@ -11,6 +11,7 @@ CONF_SCROLL_DWELL = 'scroll_dwell'
|
|||
CONF_SCROLL_DELAY = 'scroll_delay'
|
||||
CONF_SCROLL_ENABLE = 'scroll_enable'
|
||||
CONF_SCROLL_MODE = 'scroll_mode'
|
||||
CONF_REVERSE_ENABLE = 'reverse_enable'
|
||||
|
||||
SCROLL_MODES = {
|
||||
'CONTINUOUS': 0,
|
||||
|
@ -39,6 +40,7 @@ CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({
|
|||
cv.Optional(CONF_SCROLL_SPEED, default='250ms'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_SCROLL_DELAY, default='1000ms'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_SCROLL_DWELL, default='1000ms'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_REVERSE_ENABLE, default=False): cv.boolean,
|
||||
}).extend(cv.polling_component_schema('500ms')).extend(spi.spi_device_schema(cs_pin_required=True))
|
||||
|
||||
|
||||
|
@ -56,6 +58,7 @@ def to_code(config):
|
|||
cg.add(var.set_scroll_delay(config[CONF_SCROLL_DELAY]))
|
||||
cg.add(var.set_scroll(config[CONF_SCROLL_ENABLE]))
|
||||
cg.add(var.set_scroll_mode(config[CONF_SCROLL_MODE]))
|
||||
cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE]))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(MAX7219ComponentRef, 'it')],
|
||||
|
|
|
@ -108,8 +108,12 @@ void MAX7219Component::display() {
|
|||
// Send the data to the chip
|
||||
for (uint8_t i = 0; i < this->num_chips_; i++) {
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
if (this->reverse_) {
|
||||
pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j];
|
||||
} else {
|
||||
pixels[j] = this->max_displaybuffer_[i * 8 + j];
|
||||
}
|
||||
}
|
||||
this->send64pixels(i, pixels);
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +132,7 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color colo
|
|||
this->max_displaybuffer_.resize(x + 1, this->bckgrnd_);
|
||||
}
|
||||
|
||||
if (y >= this->get_height_internal() || y < 0) // If pixel is outside display then dont draw
|
||||
if ((y >= this->get_height_internal()) || (y < 0) || (x < 0)) // If pixel is outside display then dont draw
|
||||
return;
|
||||
|
||||
uint16_t pos = x; // X is starting at 0 top left
|
||||
|
|
|
@ -52,6 +52,7 @@ class MAX7219Component : public PollingComponent,
|
|||
void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; };
|
||||
void set_scroll(bool on_off) { this->scroll_ = on_off; };
|
||||
void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; };
|
||||
void set_reverse(bool on_off) { this->reverse_ = on_off; };
|
||||
|
||||
void send_char(byte chip, byte data);
|
||||
void send64pixels(byte chip, const byte pixels[8]);
|
||||
|
@ -87,6 +88,7 @@ class MAX7219Component : public PollingComponent,
|
|||
uint8_t intensity_; /// Intensity of the display from 0 to 15 (most)
|
||||
uint8_t num_chips_;
|
||||
bool scroll_;
|
||||
bool reverse_;
|
||||
bool update_{false};
|
||||
uint16_t scroll_speed_;
|
||||
uint16_t scroll_delay_;
|
||||
|
|
58
esphome/components/mcp23s08/__init__.py
Normal file
58
esphome/components/mcp23s08/__init__.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import spi
|
||||
from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED
|
||||
|
||||
CODEOWNERS = ['@SenexCrenshaw']
|
||||
|
||||
DEPENDENCIES = ['spi']
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_DEVICEADDRESS = "deviceaddress"
|
||||
|
||||
mcp23S08_ns = cg.esphome_ns.namespace('mcp23s08')
|
||||
mcp23S08GPIOMode = mcp23S08_ns.enum('MCP23S08GPIOMode')
|
||||
mcp23S08_GPIO_MODES = {
|
||||
'INPUT': mcp23S08GPIOMode.MCP23S08_INPUT,
|
||||
'INPUT_PULLUP': mcp23S08GPIOMode.MCP23S08_INPUT_PULLUP,
|
||||
'OUTPUT': mcp23S08GPIOMode.MCP23S08_OUTPUT,
|
||||
}
|
||||
|
||||
mcp23S08 = mcp23S08_ns.class_('MCP23S08', cg.Component, spi.SPIDevice)
|
||||
mcp23S08GPIOPin = mcp23S08_ns.class_('MCP23S08GPIOPin', cg.GPIOPin)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.Required(CONF_ID): cv.declare_id(mcp23S08),
|
||||
cv.Optional(CONF_DEVICEADDRESS, default=0): cv.uint8_t,
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema())
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_device_address(config[CONF_DEVICEADDRESS]))
|
||||
yield cg.register_component(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
||||
|
||||
|
||||
CONF_MCP23S08 = 'mcp23s08'
|
||||
|
||||
mcp23S08_OUTPUT_PIN_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(CONF_MCP23S08): cv.use_id(mcp23S08),
|
||||
cv.Required(CONF_NUMBER): cv.int_,
|
||||
cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(mcp23S08_GPIO_MODES, upper=True),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
})
|
||||
mcp23S08_INPUT_PIN_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(CONF_MCP23S08): cv.use_id(mcp23S08),
|
||||
cv.Required(CONF_NUMBER): cv.int_range(0, 7),
|
||||
cv.Optional(CONF_MODE, default="INPUT"): cv.enum(mcp23S08_GPIO_MODES, upper=True),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
})
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23S08,
|
||||
(mcp23S08_OUTPUT_PIN_SCHEMA, mcp23S08_INPUT_PIN_SCHEMA))
|
||||
def mcp23S08_pin_to_code(config):
|
||||
parent = yield cg.get_variable(config[CONF_MCP23S08])
|
||||
yield mcp23S08GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED])
|
121
esphome/components/mcp23s08/mcp23s08.cpp
Normal file
121
esphome/components/mcp23s08/mcp23s08.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
#include "mcp23s08.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp23s08 {
|
||||
|
||||
static const char *TAG = "mcp23s08";
|
||||
|
||||
void MCP23S08::set_device_address(uint8_t device_addr) {
|
||||
if (device_addr != 0) {
|
||||
this->device_opcode_ |= ((device_addr & 0x03) << 1);
|
||||
}
|
||||
}
|
||||
|
||||
void MCP23S08::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MCP23S08...");
|
||||
this->spi_setup();
|
||||
this->enable();
|
||||
|
||||
this->transfer_byte(MCP23S08_IODIR);
|
||||
this->transfer_byte(0xFF);
|
||||
for (uint8_t i = 0; i < MCP23S08_OLAT; i++) {
|
||||
this->transfer_byte(0x00);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void MCP23S08::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MCP23S08:");
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
}
|
||||
|
||||
float MCP23S08::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
bool MCP23S08::digital_read(uint8_t pin) {
|
||||
if (pin > 7) {
|
||||
return false;
|
||||
}
|
||||
uint8_t bit = pin % 8;
|
||||
uint8_t reg_addr = MCP23S08_GPIO;
|
||||
uint8_t value = 0;
|
||||
this->read_reg(reg_addr, &value);
|
||||
return value & (1 << bit);
|
||||
}
|
||||
|
||||
void MCP23S08::digital_write(uint8_t pin, bool value) {
|
||||
if (pin > 7) {
|
||||
return;
|
||||
}
|
||||
uint8_t reg_addr = MCP23S08_OLAT;
|
||||
this->update_reg(pin, value, reg_addr);
|
||||
}
|
||||
|
||||
void MCP23S08::pin_mode(uint8_t pin, uint8_t mode) {
|
||||
uint8_t iodir = MCP23S08_IODIR;
|
||||
uint8_t gppu = MCP23S08_GPPU;
|
||||
switch (mode) {
|
||||
case MCP23S08_INPUT:
|
||||
this->update_reg(pin, true, iodir);
|
||||
break;
|
||||
case MCP23S08_INPUT_PULLUP:
|
||||
this->update_reg(pin, true, iodir);
|
||||
this->update_reg(pin, true, gppu);
|
||||
break;
|
||||
case MCP23S08_OUTPUT:
|
||||
this->update_reg(pin, false, iodir);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MCP23S08::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) {
|
||||
uint8_t bit = pin % 8;
|
||||
uint8_t reg_value = 0;
|
||||
if (reg_addr == MCP23S08_OLAT) {
|
||||
reg_value = this->olat_;
|
||||
} else {
|
||||
this->read_reg(reg_addr, ®_value);
|
||||
}
|
||||
|
||||
if (pin_value)
|
||||
reg_value |= 1 << bit;
|
||||
else
|
||||
reg_value &= ~(1 << bit);
|
||||
|
||||
this->write_reg(reg_addr, reg_value);
|
||||
|
||||
if (reg_addr == MCP23S08_OLAT) {
|
||||
this->olat_ = reg_value;
|
||||
}
|
||||
}
|
||||
|
||||
bool MCP23S08::write_reg(uint8_t reg, uint8_t value) {
|
||||
this->enable();
|
||||
this->transfer_byte(this->device_opcode_);
|
||||
this->transfer_byte(reg);
|
||||
this->transfer_byte(value);
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) {
|
||||
uint8_t data;
|
||||
this->enable();
|
||||
this->transfer_byte(this->device_opcode_ | 1);
|
||||
this->transfer_byte(reg);
|
||||
*value = this->transfer_byte(0);
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
MCP23S08GPIOPin::MCP23S08GPIOPin(MCP23S08 *parent, uint8_t pin, uint8_t mode, bool inverted)
|
||||
: GPIOPin(pin, mode, inverted), parent_(parent) {}
|
||||
void MCP23S08GPIOPin::setup() { this->pin_mode(this->mode_); }
|
||||
void MCP23S08GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); }
|
||||
bool MCP23S08GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||
void MCP23S08GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||
|
||||
} // namespace mcp23s08
|
||||
} // namespace esphome
|
74
esphome/components/mcp23s08/mcp23s08.h
Normal file
74
esphome/components/mcp23s08/mcp23s08.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp23s08 {
|
||||
|
||||
/// Modes for MCP23S08 pins
|
||||
enum MCP23S08GPIOMode : uint8_t {
|
||||
MCP23S08_INPUT = INPUT, // 0x00
|
||||
MCP23S08_INPUT_PULLUP = INPUT_PULLUP, // 0x02
|
||||
MCP23S08_OUTPUT = OUTPUT // 0x01
|
||||
};
|
||||
|
||||
enum MCP23S08GPIORegisters {
|
||||
// A side
|
||||
MCP23S08_IODIR = 0x00,
|
||||
MCP23S08_IPOL = 0x01,
|
||||
MCP23S08_GPINTEN = 0x02,
|
||||
MCP23S08_DEFVAL = 0x03,
|
||||
MCP23S08_INTCON = 0x04,
|
||||
MCP23S08_IOCON = 0x05,
|
||||
MCP23S08_GPPU = 0x06,
|
||||
MCP23S08_INTF = 0x07,
|
||||
MCP23S08_INTCAP = 0x08,
|
||||
MCP23S08_GPIO = 0x09,
|
||||
MCP23S08_OLAT = 0x0A,
|
||||
};
|
||||
|
||||
class MCP23S08 : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_10MHZ> {
|
||||
public:
|
||||
MCP23S08() = default;
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
bool digital_read(uint8_t pin);
|
||||
void digital_write(uint8_t pin, bool value);
|
||||
void pin_mode(uint8_t pin, uint8_t mode);
|
||||
|
||||
void set_device_address(uint8_t device_addr);
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
// read a given register
|
||||
bool read_reg(uint8_t reg, uint8_t *value);
|
||||
// write a value to a given register
|
||||
bool write_reg(uint8_t reg, uint8_t value);
|
||||
// update registers with given pin value.
|
||||
void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a);
|
||||
|
||||
protected:
|
||||
uint8_t device_opcode_ = 0x40;
|
||||
uint8_t olat_{0x00};
|
||||
};
|
||||
|
||||
class MCP23S08GPIOPin : public GPIOPin {
|
||||
public:
|
||||
MCP23S08GPIOPin(MCP23S08 *parent, uint8_t pin, uint8_t mode, bool inverted = false);
|
||||
|
||||
void setup() override;
|
||||
void pin_mode(uint8_t mode) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
|
||||
protected:
|
||||
MCP23S08 *parent_;
|
||||
};
|
||||
|
||||
} // namespace mcp23s08
|
||||
} // namespace esphome
|
58
esphome/components/mcp23s17/__init__.py
Normal file
58
esphome/components/mcp23s17/__init__.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import spi
|
||||
from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED
|
||||
|
||||
CODEOWNERS = ['@SenexCrenshaw']
|
||||
|
||||
DEPENDENCIES = ['spi']
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_DEVICEADDRESS = "deviceaddress"
|
||||
|
||||
mcp23S17_ns = cg.esphome_ns.namespace('mcp23s17')
|
||||
mcp23S17GPIOMode = mcp23S17_ns.enum('MCP23S17GPIOMode')
|
||||
mcp23S17_GPIO_MODES = {
|
||||
'INPUT': mcp23S17GPIOMode.MCP23S17_INPUT,
|
||||
'INPUT_PULLUP': mcp23S17GPIOMode.MCP23S17_INPUT_PULLUP,
|
||||
'OUTPUT': mcp23S17GPIOMode.MCP23S17_OUTPUT,
|
||||
}
|
||||
|
||||
mcp23S17 = mcp23S17_ns.class_('MCP23S17', cg.Component, spi.SPIDevice)
|
||||
mcp23S17GPIOPin = mcp23S17_ns.class_('MCP23S17GPIOPin', cg.GPIOPin)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.Required(CONF_ID): cv.declare_id(mcp23S17),
|
||||
cv.Optional(CONF_DEVICEADDRESS, default=0): cv.uint8_t,
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema())
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_device_address(config[CONF_DEVICEADDRESS]))
|
||||
yield cg.register_component(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
||||
|
||||
|
||||
CONF_MCP23S17 = 'mcp23s17'
|
||||
|
||||
mcp23S17_OUTPUT_PIN_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(CONF_MCP23S17): cv.use_id(mcp23S17),
|
||||
cv.Required(CONF_NUMBER): cv.int_,
|
||||
cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(mcp23S17_GPIO_MODES, upper=True),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
})
|
||||
mcp23S17_INPUT_PIN_SCHEMA = cv.Schema({
|
||||
cv.Required(CONF_MCP23S17): cv.use_id(mcp23S17),
|
||||
cv.Required(CONF_NUMBER): cv.int_range(0, 15),
|
||||
cv.Optional(CONF_MODE, default="INPUT"): cv.enum(mcp23S17_GPIO_MODES, upper=True),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
})
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23S17,
|
||||
(mcp23S17_OUTPUT_PIN_SCHEMA, mcp23S17_INPUT_PIN_SCHEMA))
|
||||
def mcp23S17_pin_to_code(config):
|
||||
parent = yield cg.get_variable(config[CONF_MCP23S17])
|
||||
yield mcp23S17GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED])
|
126
esphome/components/mcp23s17/mcp23s17.cpp
Normal file
126
esphome/components/mcp23s17/mcp23s17.cpp
Normal file
|
@ -0,0 +1,126 @@
|
|||
#include "mcp23s17.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp23s17 {
|
||||
|
||||
static const char *TAG = "mcp23s17";
|
||||
|
||||
void MCP23S17::set_device_address(uint8_t device_addr) {
|
||||
if (device_addr != 0) {
|
||||
this->device_opcode_ |= ((device_addr & 0b111) << 1);
|
||||
}
|
||||
}
|
||||
|
||||
void MCP23S17::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MCP23S17...");
|
||||
this->spi_setup();
|
||||
|
||||
this->enable();
|
||||
uint8_t cmd = 0b01000000;
|
||||
this->transfer_byte(cmd);
|
||||
this->transfer_byte(0x18);
|
||||
this->transfer_byte(0x0A);
|
||||
this->transfer_byte(this->device_opcode_);
|
||||
this->transfer_byte(0);
|
||||
this->transfer_byte(0xFF);
|
||||
this->transfer_byte(0xFF);
|
||||
|
||||
for (uint8_t i = 0; i < 20; i++) {
|
||||
this->transfer_byte(0);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void MCP23S17::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MCP23S17:");
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
}
|
||||
|
||||
float MCP23S17::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
bool MCP23S17::digital_read(uint8_t pin) {
|
||||
uint8_t bit = pin % 8;
|
||||
uint8_t reg_addr = pin < 8 ? MCP23S17_GPIOA : MCP23S17_GPIOB;
|
||||
uint8_t value = 0;
|
||||
this->read_reg(reg_addr, &value);
|
||||
return value & (1 << bit);
|
||||
}
|
||||
|
||||
void MCP23S17::digital_write(uint8_t pin, bool value) {
|
||||
uint8_t reg_addr = pin < 8 ? MCP23S17_OLATA : MCP23S17_OLATB;
|
||||
this->update_reg(pin, value, reg_addr);
|
||||
}
|
||||
|
||||
void MCP23S17::pin_mode(uint8_t pin, uint8_t mode) {
|
||||
uint8_t iodir = pin < 8 ? MCP23S17_IODIRA : MCP23S17_IODIRB;
|
||||
uint8_t gppu = pin < 8 ? MCP23S17_GPPUA : MCP23S17_GPPUB;
|
||||
switch (mode) {
|
||||
case MCP23S17_INPUT:
|
||||
this->update_reg(pin, true, iodir);
|
||||
break;
|
||||
case MCP23S17_INPUT_PULLUP:
|
||||
this->update_reg(pin, true, iodir);
|
||||
this->update_reg(pin, true, gppu);
|
||||
break;
|
||||
case MCP23S17_OUTPUT:
|
||||
this->update_reg(pin, false, iodir);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MCP23S17::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) {
|
||||
uint8_t bit = pin % 8;
|
||||
uint8_t reg_value = 0;
|
||||
if (reg_addr == MCP23S17_OLATA) {
|
||||
reg_value = this->olat_a_;
|
||||
} else if (reg_addr == MCP23S17_OLATB) {
|
||||
reg_value = this->olat_b_;
|
||||
} else {
|
||||
this->read_reg(reg_addr, ®_value);
|
||||
}
|
||||
|
||||
if (pin_value)
|
||||
reg_value |= 1 << bit;
|
||||
else
|
||||
reg_value &= ~(1 << bit);
|
||||
|
||||
this->write_reg(reg_addr, reg_value);
|
||||
|
||||
if (reg_addr == MCP23S17_OLATA) {
|
||||
this->olat_a_ = reg_value;
|
||||
} else if (reg_addr == MCP23S17_OLATB) {
|
||||
this->olat_b_ = reg_value;
|
||||
}
|
||||
}
|
||||
|
||||
bool MCP23S17::read_reg(uint8_t reg, uint8_t *value) {
|
||||
this->enable();
|
||||
this->transfer_byte(this->device_opcode_ | 1);
|
||||
this->transfer_byte(reg);
|
||||
*value = this->transfer_byte(0xFF);
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MCP23S17::write_reg(uint8_t reg, uint8_t value) {
|
||||
this->enable();
|
||||
this->transfer_byte(this->device_opcode_);
|
||||
this->transfer_byte(reg);
|
||||
this->transfer_byte(value);
|
||||
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
MCP23S17GPIOPin::MCP23S17GPIOPin(MCP23S17 *parent, uint8_t pin, uint8_t mode, bool inverted)
|
||||
: GPIOPin(pin, mode, inverted), parent_(parent) {}
|
||||
void MCP23S17GPIOPin::setup() { this->pin_mode(this->mode_); }
|
||||
void MCP23S17GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); }
|
||||
bool MCP23S17GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||
void MCP23S17GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||
|
||||
} // namespace mcp23s17
|
||||
} // namespace esphome
|
87
esphome/components/mcp23s17/mcp23s17.h
Normal file
87
esphome/components/mcp23s17/mcp23s17.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp23s17 {
|
||||
|
||||
/// Modes for MCP23S17 pins
|
||||
enum MCP23S17GPIOMode : uint8_t {
|
||||
MCP23S17_INPUT = INPUT, // 0x00
|
||||
MCP23S17_INPUT_PULLUP = INPUT_PULLUP, // 0x02
|
||||
MCP23S17_OUTPUT = OUTPUT // 0x01
|
||||
};
|
||||
|
||||
enum MCP23S17GPIORegisters {
|
||||
// A side
|
||||
MCP23S17_IODIRA = 0x00,
|
||||
MCP23S17_IPOLA = 0x02,
|
||||
MCP23S17_GPINTENA = 0x04,
|
||||
MCP23S17_DEFVALA = 0x06,
|
||||
MCP23S17_INTCONA = 0x08,
|
||||
MCP23S17_IOCONA = 0x0A,
|
||||
MCP23S17_GPPUA = 0x0C,
|
||||
MCP23S17_INTFA = 0x0E,
|
||||
MCP23S17_INTCAPA = 0x10,
|
||||
MCP23S17_GPIOA = 0x12,
|
||||
MCP23S17_OLATA = 0x14,
|
||||
// B side
|
||||
MCP23S17_IODIRB = 0x01,
|
||||
MCP23S17_IPOLB = 0x03,
|
||||
MCP23S17_GPINTENB = 0x05,
|
||||
MCP23S17_DEFVALB = 0x07,
|
||||
MCP23S17_INTCONB = 0x09,
|
||||
MCP23S17_IOCONB = 0x0B,
|
||||
MCP23S17_GPPUB = 0x0D,
|
||||
MCP23S17_INTFB = 0x0F,
|
||||
MCP23S17_INTCAPB = 0x11,
|
||||
MCP23S17_GPIOB = 0x13,
|
||||
MCP23S17_OLATB = 0x15,
|
||||
};
|
||||
|
||||
class MCP23S17 : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
public:
|
||||
MCP23S17() = default;
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_device_address(uint8_t device_addr);
|
||||
|
||||
bool digital_read(uint8_t pin);
|
||||
void digital_write(uint8_t pin, bool value);
|
||||
void pin_mode(uint8_t pin, uint8_t mode);
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
// read a given register
|
||||
bool read_reg(uint8_t reg, uint8_t *value);
|
||||
// write a value to a given register
|
||||
bool write_reg(uint8_t reg, uint8_t value);
|
||||
// update registers with given pin value.
|
||||
void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a);
|
||||
|
||||
protected:
|
||||
uint8_t device_opcode_ = 0x40;
|
||||
uint8_t olat_a_{0x00};
|
||||
uint8_t olat_b_{0x00};
|
||||
};
|
||||
|
||||
class MCP23S17GPIOPin : public GPIOPin {
|
||||
public:
|
||||
MCP23S17GPIOPin(MCP23S17 *parent, uint8_t pin, uint8_t mode, bool inverted = false);
|
||||
|
||||
void setup() override;
|
||||
void pin_mode(uint8_t mode) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
|
||||
protected:
|
||||
MCP23S17 *parent_;
|
||||
};
|
||||
|
||||
} // namespace mcp23s17
|
||||
} // namespace esphome
|
0
esphome/components/mcp2515/__init__.py
Normal file
0
esphome/components/mcp2515/__init__.py
Normal file
47
esphome/components/mcp2515/canbus.py
Normal file
47
esphome/components/mcp2515/canbus.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import spi, canbus
|
||||
from esphome.const import CONF_ID, CONF_MODE
|
||||
from esphome.components.canbus import CanbusComponent
|
||||
|
||||
CODEOWNERS = ['@mvturnho', '@danielschramm']
|
||||
DEPENDENCIES = ['spi']
|
||||
|
||||
CONF_CLOCK = 'clock'
|
||||
|
||||
mcp2515_ns = cg.esphome_ns.namespace('mcp2515')
|
||||
mcp2515 = mcp2515_ns.class_('MCP2515', CanbusComponent, spi.SPIDevice)
|
||||
CanClock = mcp2515_ns.enum('CAN_CLOCK')
|
||||
McpMode = mcp2515_ns.enum('CANCTRL_REQOP_MODE')
|
||||
|
||||
CAN_CLOCK = {
|
||||
'8MHZ': CanClock.MCP_8MHZ,
|
||||
'16MHZ': CanClock.MCP_16MHZ,
|
||||
'20MHZ': CanClock.MCP_20MHZ,
|
||||
}
|
||||
|
||||
MCP_MODE = {
|
||||
'NORMAL': McpMode.CANCTRL_REQOP_NORMAL,
|
||||
'LOOPBACK': McpMode.CANCTRL_REQOP_LOOPBACK,
|
||||
'LISTENONLY': McpMode.CANCTRL_REQOP_LISTENONLY,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = canbus.CONFIG_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(mcp2515),
|
||||
cv.Optional(CONF_CLOCK, default='8MHZ'): cv.enum(CAN_CLOCK, upper=True),
|
||||
cv.Optional(CONF_MODE, default='NORMAL'): cv.enum(MCP_MODE, upper=True),
|
||||
}).extend(spi.spi_device_schema(True))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = mcp2515.new()
|
||||
var = cg.Pvariable(config[CONF_ID], rhs)
|
||||
yield canbus.register_canbus(var, config)
|
||||
if CONF_CLOCK in config:
|
||||
canclock = CAN_CLOCK[config[CONF_CLOCK]]
|
||||
cg.add(var.set_mcp_clock(canclock))
|
||||
if CONF_MODE in config:
|
||||
mode = MCP_MODE[config[CONF_MODE]]
|
||||
cg.add(var.set_mcp_mode(mode))
|
||||
|
||||
yield spi.register_spi_device(var, config)
|
612
esphome/components/mcp2515/mcp2515.cpp
Normal file
612
esphome/components/mcp2515/mcp2515.cpp
Normal file
|
@ -0,0 +1,612 @@
|
|||
#include "mcp2515.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp2515 {
|
||||
|
||||
static const char *TAG = "mcp2515";
|
||||
|
||||
const struct MCP2515::TxBnRegs MCP2515::TXB[N_TXBUFFERS] = {{MCP_TXB0CTRL, MCP_TXB0SIDH, MCP_TXB0DATA},
|
||||
{MCP_TXB1CTRL, MCP_TXB1SIDH, MCP_TXB1DATA},
|
||||
{MCP_TXB2CTRL, MCP_TXB2SIDH, MCP_TXB2DATA}};
|
||||
|
||||
const struct MCP2515::RxBnRegs MCP2515::RXB[N_RXBUFFERS] = {{MCP_RXB0CTRL, MCP_RXB0SIDH, MCP_RXB0DATA, CANINTF_RX0IF},
|
||||
{MCP_RXB1CTRL, MCP_RXB1SIDH, MCP_RXB1DATA, CANINTF_RX1IF}};
|
||||
|
||||
bool MCP2515::setup_internal() {
|
||||
this->spi_setup();
|
||||
|
||||
if (this->reset_() == canbus::ERROR_FAIL)
|
||||
return false;
|
||||
this->set_bitrate_(this->bit_rate_, this->mcp_clock_);
|
||||
this->set_mode_(this->mcp_mode_);
|
||||
ESP_LOGV(TAG, "setup done");
|
||||
return true;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::reset_() {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_RESET);
|
||||
this->disable();
|
||||
ESP_LOGV(TAG, "reset_()");
|
||||
delay(10);
|
||||
|
||||
ESP_LOGV(TAG, "reset() CLEAR ALL TXB registers");
|
||||
|
||||
uint8_t zeros[14];
|
||||
memset(zeros, 0, sizeof(zeros));
|
||||
set_registers_(MCP_TXB0CTRL, zeros, 14);
|
||||
set_registers_(MCP_TXB1CTRL, zeros, 14);
|
||||
set_registers_(MCP_TXB2CTRL, zeros, 14);
|
||||
ESP_LOGD(TAG, "reset() CLEARED TXB registers");
|
||||
|
||||
set_register_(MCP_RXB0CTRL, 0);
|
||||
set_register_(MCP_RXB1CTRL, 0);
|
||||
|
||||
set_register_(MCP_CANINTE, CANINTF_RX0IF | CANINTF_RX1IF | CANINTF_ERRIF | CANINTF_MERRF);
|
||||
|
||||
modify_register_(MCP_RXB0CTRL, RXB_CTRL_RXM_MASK | RXB_0_CTRL_BUKT, RXB_CTRL_RXM_STDEXT | RXB_0_CTRL_BUKT);
|
||||
modify_register_(MCP_RXB1CTRL, RXB_CTRL_RXM_MASK, RXB_CTRL_RXM_STDEXT);
|
||||
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
uint8_t MCP2515::read_register_(const REGISTER reg) {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_READ);
|
||||
this->transfer_byte(reg);
|
||||
uint8_t ret = this->transfer_byte(0x00);
|
||||
this->disable();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MCP2515::read_registers_(const REGISTER reg, uint8_t values[], const uint8_t n) {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_READ);
|
||||
this->transfer_byte(reg);
|
||||
// this->transfer_array(values, n);
|
||||
// mcp2515 has auto - increment of address - pointer
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
values[i] = this->transfer_byte(0x00);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void MCP2515::set_register_(const REGISTER reg, const uint8_t value) {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_WRITE);
|
||||
this->transfer_byte(reg);
|
||||
this->transfer_byte(value);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void MCP2515::set_registers_(const REGISTER reg, uint8_t values[], const uint8_t n) {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_WRITE);
|
||||
this->transfer_byte(reg);
|
||||
// this->transfer_array(values, n);
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
this->transfer_byte(values[i]);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void MCP2515::modify_register_(const REGISTER reg, const uint8_t mask, const uint8_t data) {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_BITMOD);
|
||||
this->transfer_byte(reg);
|
||||
this->transfer_byte(mask);
|
||||
this->transfer_byte(data);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
uint8_t MCP2515::get_status_() {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_READ_STATUS);
|
||||
uint8_t i = this->transfer_byte(0x00);
|
||||
this->disable();
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) {
|
||||
modify_register_(MCP_CANCTRL, CANCTRL_REQOP, mode);
|
||||
|
||||
unsigned long end_time = millis() + 10;
|
||||
bool mode_match = false;
|
||||
while (millis() < end_time) {
|
||||
uint8_t new_mode = read_register_(MCP_CANSTAT);
|
||||
new_mode &= CANSTAT_OPMOD;
|
||||
mode_match = new_mode == mode;
|
||||
if (mode_match) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return mode_match ? canbus::ERROR_OK : canbus::ERROR_FAIL;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) {
|
||||
canbus::Error res;
|
||||
uint8_t cfg3;
|
||||
|
||||
if (divisor == CLKOUT_DISABLE) {
|
||||
/* Turn off CLKEN */
|
||||
modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, 0x00);
|
||||
|
||||
/* Turn on CLKOUT for SOF */
|
||||
modify_register_(MCP_CNF3, CNF3_SOF, CNF3_SOF);
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
/* Set the prescaler (CLKPRE) */
|
||||
modify_register_(MCP_CANCTRL, CANCTRL_CLKPRE, divisor);
|
||||
|
||||
/* Turn on CLKEN */
|
||||
modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, CANCTRL_CLKEN);
|
||||
|
||||
/* Turn off CLKOUT for SOF */
|
||||
modify_register_(MCP_CNF3, CNF3_SOF, 0x00);
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
void MCP2515::prepare_id_(uint8_t *buffer, const bool extended, const uint32_t id) {
|
||||
uint16_t canid = (uint16_t)(id & 0x0FFFF);
|
||||
|
||||
if (extended) {
|
||||
buffer[MCP_EID0] = (uint8_t)(canid & 0xFF);
|
||||
buffer[MCP_EID8] = (uint8_t)(canid >> 8);
|
||||
canid = (uint16_t)(id >> 16);
|
||||
buffer[MCP_SIDL] = (uint8_t)(canid & 0x03);
|
||||
buffer[MCP_SIDL] += (uint8_t)((canid & 0x1C) << 3);
|
||||
buffer[MCP_SIDL] |= TXB_EXIDE_MASK;
|
||||
buffer[MCP_SIDH] = (uint8_t)(canid >> 5);
|
||||
} else {
|
||||
buffer[MCP_SIDH] = (uint8_t)(canid >> 3);
|
||||
buffer[MCP_SIDL] = (uint8_t)((canid & 0x07) << 5);
|
||||
buffer[MCP_EID0] = 0;
|
||||
buffer[MCP_EID8] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::set_filter_mask_(const MASK mask, const bool extended, const uint32_t ul_data) {
|
||||
canbus::Error res = set_mode_(CANCTRL_REQOP_CONFIG);
|
||||
if (res != canbus::ERROR_OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
uint8_t tbufdata[4];
|
||||
prepare_id_(tbufdata, extended, ul_data);
|
||||
|
||||
REGISTER reg;
|
||||
switch (mask) {
|
||||
case MASK0:
|
||||
reg = MCP_RXM0SIDH;
|
||||
break;
|
||||
case MASK1:
|
||||
reg = MCP_RXM1SIDH;
|
||||
break;
|
||||
default:
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
|
||||
set_registers_(reg, tbufdata, 4);
|
||||
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::set_filter_(const RXF num, const bool extended, const uint32_t ul_data) {
|
||||
canbus::Error res = set_mode_(CANCTRL_REQOP_CONFIG);
|
||||
if (res != canbus::ERROR_OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
REGISTER reg;
|
||||
|
||||
switch (num) {
|
||||
case RXF0:
|
||||
reg = MCP_RXF0SIDH;
|
||||
break;
|
||||
case RXF1:
|
||||
reg = MCP_RXF1SIDH;
|
||||
break;
|
||||
case RXF2:
|
||||
reg = MCP_RXF2SIDH;
|
||||
break;
|
||||
case RXF3:
|
||||
reg = MCP_RXF3SIDH;
|
||||
break;
|
||||
case RXF4:
|
||||
reg = MCP_RXF4SIDH;
|
||||
break;
|
||||
case RXF5:
|
||||
reg = MCP_RXF5SIDH;
|
||||
break;
|
||||
default:
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
|
||||
uint8_t tbufdata[4];
|
||||
prepare_id_(tbufdata, extended, ul_data);
|
||||
set_registers_(reg, tbufdata, 4);
|
||||
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::send_message_(TXBn txbn, struct canbus::CanFrame *frame) {
|
||||
const struct TxBnRegs *txbuf = &TXB[txbn];
|
||||
|
||||
uint8_t data[13];
|
||||
|
||||
prepare_id_(data, frame->use_extended_id, frame->can_id);
|
||||
data[MCP_DLC] =
|
||||
frame->remote_transmission_request ? (frame->can_data_length_code | RTR_MASK) : frame->can_data_length_code;
|
||||
memcpy(&data[MCP_DATA], frame->data, frame->can_data_length_code);
|
||||
set_registers_(txbuf->SIDH, data, 5 + frame->can_data_length_code);
|
||||
modify_register_(txbuf->CTRL, TXB_TXREQ, TXB_TXREQ);
|
||||
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::send_message(struct canbus::CanFrame *frame) {
|
||||
if (frame->can_data_length_code > canbus::CAN_MAX_DATA_LENGTH) {
|
||||
return canbus::ERROR_FAILTX;
|
||||
}
|
||||
TXBn tx_buffers[N_TXBUFFERS] = {TXB0, TXB1, TXB2};
|
||||
|
||||
for (auto &tx_buffer : tx_buffers) {
|
||||
const struct TxBnRegs *txbuf = &TXB[tx_buffer];
|
||||
uint8_t ctrlval = read_register_(txbuf->CTRL);
|
||||
if ((ctrlval & TXB_TXREQ) == 0) {
|
||||
return send_message_(tx_buffer, frame);
|
||||
}
|
||||
}
|
||||
|
||||
return canbus::ERROR_FAILTX;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::read_message_(RXBn rxbn, struct canbus::CanFrame *frame) {
|
||||
const struct RxBnRegs *rxb = &RXB[rxbn];
|
||||
|
||||
uint8_t tbufdata[5];
|
||||
|
||||
read_registers_(rxb->SIDH, tbufdata, 5);
|
||||
|
||||
uint32_t id = (tbufdata[MCP_SIDH] << 3) + (tbufdata[MCP_SIDL] >> 5);
|
||||
bool use_extended_id = false;
|
||||
bool remote_transmission_request = false;
|
||||
|
||||
if ((tbufdata[MCP_SIDL] & TXB_EXIDE_MASK) == TXB_EXIDE_MASK) {
|
||||
id = (id << 2) + (tbufdata[MCP_SIDL] & 0x03);
|
||||
id = (id << 8) + tbufdata[MCP_EID8];
|
||||
id = (id << 8) + tbufdata[MCP_EID0];
|
||||
// id |= canbus::CAN_EFF_FLAG;
|
||||
use_extended_id = true;
|
||||
}
|
||||
|
||||
uint8_t dlc = (tbufdata[MCP_DLC] & DLC_MASK);
|
||||
if (dlc > canbus::CAN_MAX_DATA_LENGTH) {
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
|
||||
uint8_t ctrl = read_register_(rxb->CTRL);
|
||||
if (ctrl & RXB_CTRL_RTR) {
|
||||
// id |= canbus::CAN_RTR_FLAG;
|
||||
remote_transmission_request = true;
|
||||
}
|
||||
|
||||
frame->can_id = id;
|
||||
frame->can_data_length_code = dlc;
|
||||
frame->use_extended_id = use_extended_id;
|
||||
frame->remote_transmission_request = remote_transmission_request;
|
||||
|
||||
read_registers_(rxb->DATA, frame->data, dlc);
|
||||
|
||||
modify_register_(MCP_CANINTF, rxb->CANINTF_RXnIF, 0);
|
||||
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::read_message(struct canbus::CanFrame *frame) {
|
||||
canbus::Error rc;
|
||||
uint8_t stat = get_status_();
|
||||
|
||||
if (stat & STAT_RX0IF) {
|
||||
rc = read_message_(RXB0, frame);
|
||||
} else if (stat & STAT_RX1IF) {
|
||||
rc = read_message_(RXB1, frame);
|
||||
} else {
|
||||
rc = canbus::ERROR_NOMSG;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool MCP2515::check_receive_() {
|
||||
uint8_t res = get_status_();
|
||||
return (res & STAT_RXIF_MASK) != 0;
|
||||
}
|
||||
|
||||
bool MCP2515::check_error_() {
|
||||
uint8_t eflg = get_error_flags_();
|
||||
return (eflg & EFLG_ERRORMASK) != 0;
|
||||
}
|
||||
|
||||
uint8_t MCP2515::get_error_flags_() { return read_register_(MCP_EFLG); }
|
||||
|
||||
void MCP2515::clear_rx_n_ovr_flags_() { modify_register_(MCP_EFLG, EFLG_RX0OVR | EFLG_RX1OVR, 0); }
|
||||
|
||||
uint8_t MCP2515::get_int_() { return read_register_(MCP_CANINTF); }
|
||||
|
||||
void MCP2515::clear_int_() { set_register_(MCP_CANINTF, 0); }
|
||||
|
||||
uint8_t MCP2515::get_int_mask_() { return read_register_(MCP_CANINTE); }
|
||||
|
||||
void MCP2515::clear_tx_int_() { modify_register_(MCP_CANINTF, (CANINTF_TX0IF | CANINTF_TX1IF | CANINTF_TX2IF), 0); }
|
||||
|
||||
void MCP2515::clear_rx_n_ovr_() {
|
||||
uint8_t eflg = get_error_flags_();
|
||||
if (eflg != 0) {
|
||||
clear_rx_n_ovr_flags_();
|
||||
clear_int_();
|
||||
// modify_register_(MCP_CANINTF, CANINTF_ERRIF, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void MCP2515::clear_merr_() {
|
||||
// modify_register_(MCP_EFLG, EFLG_RX0OVR | EFLG_RX1OVR, 0);
|
||||
// clear_int_();
|
||||
modify_register_(MCP_CANINTF, CANINTF_MERRF, 0);
|
||||
}
|
||||
|
||||
void MCP2515::clear_errif_() {
|
||||
// modify_register_(MCP_EFLG, EFLG_RX0OVR | EFLG_RX1OVR, 0);
|
||||
// clear_int_();
|
||||
modify_register_(MCP_CANINTF, CANINTF_ERRIF, 0);
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed) { return this->set_bitrate_(can_speed, MCP_16MHZ); }
|
||||
|
||||
canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clock) {
|
||||
canbus::Error error = set_mode_(CANCTRL_REQOP_CONFIG);
|
||||
if (error != canbus::ERROR_OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
uint8_t set, cfg1, cfg2, cfg3;
|
||||
set = 1;
|
||||
switch (can_clock) {
|
||||
case (MCP_8MHZ):
|
||||
switch (can_speed) {
|
||||
case (canbus::CAN_5KBPS): // 5KBPS
|
||||
cfg1 = MCP_8MHZ_5KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_5KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_5KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_10KBPS): // 10KBPS
|
||||
cfg1 = MCP_8MHZ_10KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_10KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_10KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_20KBPS): // 20KBPS
|
||||
cfg1 = MCP_8MHZ_20KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_20KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_20KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_31K25BPS): // 31.25KBPS
|
||||
cfg1 = MCP_8MHZ_31K25BPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_31K25BPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_31K25BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_33KBPS): // 33.333KBPS
|
||||
cfg1 = MCP_8MHZ_33K3BPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_33K3BPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_33K3BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_40KBPS): // 40Kbps
|
||||
cfg1 = MCP_8MHZ_40KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_40KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_40KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_50KBPS): // 50Kbps
|
||||
cfg1 = MCP_8MHZ_50KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_50KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_50KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_80KBPS): // 80Kbps
|
||||
cfg1 = MCP_8MHZ_80KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_80KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_80KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_100KBPS): // 100Kbps
|
||||
cfg1 = MCP_8MHZ_100KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_100KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_100KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_125KBPS): // 125Kbps
|
||||
cfg1 = MCP_8MHZ_125KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_125KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_125KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_200KBPS): // 200Kbps
|
||||
cfg1 = MCP_8MHZ_200KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_200KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_200KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_250KBPS): // 250Kbps
|
||||
cfg1 = MCP_8MHZ_250KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_250KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_250KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_500KBPS): // 500Kbps
|
||||
cfg1 = MCP_8MHZ_500KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_500KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_500KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_1000KBPS): // 1Mbps
|
||||
cfg1 = MCP_8MHZ_1000KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_1000KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_1000KBPS_CFG3;
|
||||
break;
|
||||
default:
|
||||
set = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case (MCP_16MHZ):
|
||||
switch (can_speed) {
|
||||
case (canbus::CAN_5KBPS): // 5Kbps
|
||||
cfg1 = MCP_16MHZ_5KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_5KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_5KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_10KBPS): // 10Kbps
|
||||
cfg1 = MCP_16MHZ_10KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_10KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_10KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_20KBPS): // 20Kbps
|
||||
cfg1 = MCP_16MHZ_20KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_20KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_20KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_33KBPS): // 33.333Kbps
|
||||
cfg1 = MCP_16MHZ_33K3BPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_33K3BPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_33K3BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_40KBPS): // 40Kbps
|
||||
cfg1 = MCP_16MHZ_40KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_40KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_40KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_50KBPS): // 50Kbps
|
||||
cfg2 = MCP_16MHZ_50KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_50KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_80KBPS): // 80Kbps
|
||||
cfg1 = MCP_16MHZ_80KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_80KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_80KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_83K3BPS): // 83.333Kbps
|
||||
cfg1 = MCP_16MHZ_83K3BPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_83K3BPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_83K3BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_100KBPS): // 100Kbps
|
||||
cfg1 = MCP_16MHZ_100KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_100KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_100KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_125KBPS): // 125Kbps
|
||||
cfg1 = MCP_16MHZ_125KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_125KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_125KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_200KBPS): // 200Kbps
|
||||
cfg1 = MCP_16MHZ_200KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_200KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_200KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_250KBPS): // 250Kbps
|
||||
cfg1 = MCP_16MHZ_250KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_250KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_250KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_500KBPS): // 500Kbps
|
||||
cfg1 = MCP_16MHZ_500KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_500KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_500KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_1000KBPS): // 1Mbps
|
||||
cfg1 = MCP_16MHZ_1000KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_1000KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_1000KBPS_CFG3;
|
||||
break;
|
||||
default:
|
||||
set = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case (MCP_20MHZ):
|
||||
switch (can_speed) {
|
||||
case (canbus::CAN_33KBPS): // 33.333Kbps
|
||||
cfg1 = MCP_20MHZ_33K3BPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_33K3BPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_33K3BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_40KBPS): // 40Kbps
|
||||
cfg1 = MCP_20MHZ_40KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_40KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_40KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_50KBPS): // 50Kbps
|
||||
cfg1 = MCP_20MHZ_50KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_50KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_50KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_80KBPS): // 80Kbps
|
||||
cfg1 = MCP_20MHZ_80KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_80KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_80KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_83K3BPS): // 83.333Kbps
|
||||
cfg1 = MCP_20MHZ_83K3BPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_83K3BPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_83K3BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_100KBPS): // 100Kbps
|
||||
cfg1 = MCP_20MHZ_100KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_100KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_100KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_125KBPS): // 125Kbps
|
||||
cfg1 = MCP_20MHZ_125KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_125KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_125KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_200KBPS): // 200Kbps
|
||||
cfg1 = MCP_20MHZ_200KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_200KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_200KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_250KBPS): // 250Kbps
|
||||
cfg1 = MCP_20MHZ_250KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_250KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_250KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_500KBPS): // 500Kbps
|
||||
cfg1 = MCP_20MHZ_500KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_500KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_500KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_1000KBPS): // 1Mbps
|
||||
cfg1 = MCP_20MHZ_1000KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_1000KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_1000KBPS_CFG3;
|
||||
break;
|
||||
default:
|
||||
set = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
set = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (set) {
|
||||
set_register_(MCP_CNF1, cfg1);
|
||||
set_register_(MCP_CNF2, cfg2);
|
||||
set_register_(MCP_CNF3, cfg3);
|
||||
return canbus::ERROR_OK;
|
||||
} else {
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
}
|
||||
} // namespace mcp2515
|
||||
} // namespace esphome
|
112
esphome/components/mcp2515/mcp2515.h
Normal file
112
esphome/components/mcp2515/mcp2515.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/canbus/canbus.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "mcp2515_defs.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp2515 {
|
||||
static const uint32_t SPI_CLOCK = 10000000; // 10MHz
|
||||
|
||||
static const int N_TXBUFFERS = 3;
|
||||
static const int N_RXBUFFERS = 2;
|
||||
enum CanClock { MCP_20MHZ, MCP_16MHZ, MCP_8MHZ };
|
||||
enum MASK { MASK0, MASK1 };
|
||||
enum RXF { RXF0 = 0, RXF1 = 1, RXF2 = 2, RXF3 = 3, RXF4 = 4, RXF5 = 5 };
|
||||
enum RXBn { RXB0 = 0, RXB1 = 1 };
|
||||
enum TXBn { TXB0 = 0, TXB1 = 1, TXB2 = 2 };
|
||||
|
||||
enum CanClkOut {
|
||||
CLKOUT_DISABLE = -1,
|
||||
CLKOUT_DIV1 = 0x0,
|
||||
CLKOUT_DIV2 = 0x1,
|
||||
CLKOUT_DIV4 = 0x2,
|
||||
CLKOUT_DIV8 = 0x3,
|
||||
};
|
||||
|
||||
enum CANINTF : uint8_t {
|
||||
CANINTF_RX0IF = 0x01,
|
||||
CANINTF_RX1IF = 0x02,
|
||||
CANINTF_TX0IF = 0x04,
|
||||
CANINTF_TX1IF = 0x08,
|
||||
CANINTF_TX2IF = 0x10,
|
||||
CANINTF_ERRIF = 0x20,
|
||||
CANINTF_WAKIF = 0x40,
|
||||
CANINTF_MERRF = 0x80
|
||||
};
|
||||
|
||||
enum EFLG : uint8_t {
|
||||
EFLG_RX1OVR = (1 << 7),
|
||||
EFLG_RX0OVR = (1 << 6),
|
||||
EFLG_TXBO = (1 << 5),
|
||||
EFLG_TXEP = (1 << 4),
|
||||
EFLG_RXEP = (1 << 3),
|
||||
EFLG_TXWAR = (1 << 2),
|
||||
EFLG_RXWAR = (1 << 1),
|
||||
EFLG_EWARN = (1 << 0)
|
||||
};
|
||||
|
||||
enum STAT : uint8_t { STAT_RX0IF = (1 << 0), STAT_RX1IF = (1 << 1) };
|
||||
|
||||
static const uint8_t STAT_RXIF_MASK = STAT_RX0IF | STAT_RX1IF;
|
||||
static const uint8_t EFLG_ERRORMASK = EFLG_RX1OVR | EFLG_RX0OVR | EFLG_TXBO | EFLG_TXEP | EFLG_RXEP;
|
||||
|
||||
class MCP2515 : public canbus::Canbus,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
public:
|
||||
MCP2515(){};
|
||||
void set_mcp_clock(CanClock clock) { this->mcp_clock_ = clock; };
|
||||
void set_mcp_mode(const CanctrlReqopMode mode) { this->mcp_mode_ = mode; }
|
||||
static const struct TxBnRegs {
|
||||
REGISTER CTRL;
|
||||
REGISTER SIDH;
|
||||
REGISTER DATA;
|
||||
} TXB[N_TXBUFFERS];
|
||||
|
||||
static const struct RxBnRegs {
|
||||
REGISTER CTRL;
|
||||
REGISTER SIDH;
|
||||
REGISTER DATA;
|
||||
CANINTF CANINTF_RXnIF;
|
||||
} RXB[N_RXBUFFERS];
|
||||
|
||||
protected:
|
||||
CanClock mcp_clock_{MCP_8MHZ};
|
||||
CanctrlReqopMode mcp_mode_ = CANCTRL_REQOP_NORMAL;
|
||||
bool setup_internal() override;
|
||||
canbus::Error set_mode_(CanctrlReqopMode mode);
|
||||
|
||||
uint8_t read_register_(REGISTER reg);
|
||||
void read_registers_(REGISTER reg, uint8_t values[], uint8_t n);
|
||||
void set_register_(REGISTER reg, uint8_t value);
|
||||
void set_registers_(REGISTER reg, uint8_t values[], uint8_t n);
|
||||
void modify_register_(REGISTER reg, uint8_t mask, uint8_t data);
|
||||
|
||||
void prepare_id_(uint8_t *buffer, bool extended, uint32_t id);
|
||||
canbus::Error reset_();
|
||||
canbus::Error set_clk_out_(CanClkOut divisor);
|
||||
canbus::Error set_bitrate_(canbus::CanSpeed can_speed);
|
||||
canbus::Error set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clock);
|
||||
canbus::Error set_filter_mask_(MASK mask, bool extended, uint32_t ul_data);
|
||||
canbus::Error set_filter_(RXF num, bool extended, uint32_t ul_data);
|
||||
canbus::Error send_message_(TXBn txbn, struct canbus::CanFrame *frame);
|
||||
canbus::Error send_message(struct canbus::CanFrame *frame) override;
|
||||
canbus::Error read_message_(RXBn rxbn, struct canbus::CanFrame *frame);
|
||||
canbus::Error read_message(struct canbus::CanFrame *frame) override;
|
||||
bool check_receive_();
|
||||
bool check_error_();
|
||||
uint8_t get_error_flags_();
|
||||
void clear_rx_n_ovr_flags_();
|
||||
uint8_t get_int_();
|
||||
uint8_t get_int_mask_();
|
||||
void clear_int_();
|
||||
void clear_tx_int_();
|
||||
uint8_t get_status_();
|
||||
void clear_rx_n_ovr_();
|
||||
void clear_merr_();
|
||||
void clear_errif_();
|
||||
};
|
||||
} // namespace mcp2515
|
||||
} // namespace esphome
|
317
esphome/components/mcp2515/mcp2515_defs.h
Normal file
317
esphome/components/mcp2515/mcp2515_defs.h
Normal file
|
@ -0,0 +1,317 @@
|
|||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp2515 {
|
||||
|
||||
static const uint8_t CANCTRL_REQOP = 0xE0;
|
||||
static const uint8_t CANCTRL_ABAT = 0x10;
|
||||
static const uint8_t CANCTRL_OSM = 0x08;
|
||||
static const uint8_t CANCTRL_CLKEN = 0x04;
|
||||
static const uint8_t CANCTRL_CLKPRE = 0x03;
|
||||
|
||||
enum CanctrlReqopMode : uint8_t {
|
||||
CANCTRL_REQOP_NORMAL = 0x00,
|
||||
CANCTRL_REQOP_SLEEP = 0x20,
|
||||
CANCTRL_REQOP_LOOPBACK = 0x40,
|
||||
CANCTRL_REQOP_LISTENONLY = 0x60,
|
||||
CANCTRL_REQOP_CONFIG = 0x80,
|
||||
CANCTRL_REQOP_POWERUP = 0xE0
|
||||
};
|
||||
|
||||
enum TxbNCtrl : uint8_t {
|
||||
TXB_ABTF = 0x40,
|
||||
TXB_MLOA = 0x20,
|
||||
TXB_TXERR = 0x10,
|
||||
TXB_TXREQ = 0x08,
|
||||
TXB_TXIE = 0x04,
|
||||
TXB_TXP = 0x03
|
||||
};
|
||||
|
||||
enum INSTRUCTION : uint8_t {
|
||||
INSTRUCTION_WRITE = 0x02,
|
||||
INSTRUCTION_READ = 0x03,
|
||||
INSTRUCTION_BITMOD = 0x05,
|
||||
INSTRUCTION_LOAD_TX0 = 0x40,
|
||||
INSTRUCTION_LOAD_TX1 = 0x42,
|
||||
INSTRUCTION_LOAD_TX2 = 0x44,
|
||||
INSTRUCTION_RTS_TX0 = 0x81,
|
||||
INSTRUCTION_RTS_TX1 = 0x82,
|
||||
INSTRUCTION_RTS_TX2 = 0x84,
|
||||
INSTRUCTION_RTS_ALL = 0x87,
|
||||
INSTRUCTION_READ_RX0 = 0x90,
|
||||
INSTRUCTION_READ_RX1 = 0x94,
|
||||
INSTRUCTION_READ_STATUS = 0xA0,
|
||||
INSTRUCTION_RX_STATUS = 0xB0,
|
||||
INSTRUCTION_RESET = 0xC0
|
||||
};
|
||||
|
||||
enum REGISTER : uint8_t {
|
||||
MCP_RXF0SIDH = 0x00,
|
||||
MCP_RXF0SIDL = 0x01,
|
||||
MCP_RXF0EID8 = 0x02,
|
||||
MCP_RXF0EID0 = 0x03,
|
||||
MCP_RXF1SIDH = 0x04,
|
||||
MCP_RXF1SIDL = 0x05,
|
||||
MCP_RXF1EID8 = 0x06,
|
||||
MCP_RXF1EID0 = 0x07,
|
||||
MCP_RXF2SIDH = 0x08,
|
||||
MCP_RXF2SIDL = 0x09,
|
||||
MCP_RXF2EID8 = 0x0A,
|
||||
MCP_RXF2EID0 = 0x0B,
|
||||
MCP_CANSTAT = 0x0E,
|
||||
MCP_CANCTRL = 0x0F,
|
||||
MCP_RXF3SIDH = 0x10,
|
||||
MCP_RXF3SIDL = 0x11,
|
||||
MCP_RXF3EID8 = 0x12,
|
||||
MCP_RXF3EID0 = 0x13,
|
||||
MCP_RXF4SIDH = 0x14,
|
||||
MCP_RXF4SIDL = 0x15,
|
||||
MCP_RXF4EID8 = 0x16,
|
||||
MCP_RXF4EID0 = 0x17,
|
||||
MCP_RXF5SIDH = 0x18,
|
||||
MCP_RXF5SIDL = 0x19,
|
||||
MCP_RXF5EID8 = 0x1A,
|
||||
MCP_RXF5EID0 = 0x1B,
|
||||
MCP_TEC = 0x1C,
|
||||
MCP_REC = 0x1D,
|
||||
MCP_RXM0SIDH = 0x20,
|
||||
MCP_RXM0SIDL = 0x21,
|
||||
MCP_RXM0EID8 = 0x22,
|
||||
MCP_RXM0EID0 = 0x23,
|
||||
MCP_RXM1SIDH = 0x24,
|
||||
MCP_RXM1SIDL = 0x25,
|
||||
MCP_RXM1EID8 = 0x26,
|
||||
MCP_RXM1EID0 = 0x27,
|
||||
MCP_CNF3 = 0x28,
|
||||
MCP_CNF2 = 0x29,
|
||||
MCP_CNF1 = 0x2A,
|
||||
MCP_CANINTE = 0x2B,
|
||||
MCP_CANINTF = 0x2C,
|
||||
MCP_EFLG = 0x2D,
|
||||
MCP_TXB0CTRL = 0x30,
|
||||
MCP_TXB0SIDH = 0x31,
|
||||
MCP_TXB0SIDL = 0x32,
|
||||
MCP_TXB0EID8 = 0x33,
|
||||
MCP_TXB0EID0 = 0x34,
|
||||
MCP_TXB0DLC = 0x35,
|
||||
MCP_TXB0DATA = 0x36,
|
||||
MCP_TXB1CTRL = 0x40,
|
||||
MCP_TXB1SIDH = 0x41,
|
||||
MCP_TXB1SIDL = 0x42,
|
||||
MCP_TXB1EID8 = 0x43,
|
||||
MCP_TXB1EID0 = 0x44,
|
||||
MCP_TXB1DLC = 0x45,
|
||||
MCP_TXB1DATA = 0x46,
|
||||
MCP_TXB2CTRL = 0x50,
|
||||
MCP_TXB2SIDH = 0x51,
|
||||
MCP_TXB2SIDL = 0x52,
|
||||
MCP_TXB2EID8 = 0x53,
|
||||
MCP_TXB2EID0 = 0x54,
|
||||
MCP_TXB2DLC = 0x55,
|
||||
MCP_TXB2DATA = 0x56,
|
||||
MCP_RXB0CTRL = 0x60,
|
||||
MCP_RXB0SIDH = 0x61,
|
||||
MCP_RXB0SIDL = 0x62,
|
||||
MCP_RXB0EID8 = 0x63,
|
||||
MCP_RXB0EID0 = 0x64,
|
||||
MCP_RXB0DLC = 0x65,
|
||||
MCP_RXB0DATA = 0x66,
|
||||
MCP_RXB1CTRL = 0x70,
|
||||
MCP_RXB1SIDH = 0x71,
|
||||
MCP_RXB1SIDL = 0x72,
|
||||
MCP_RXB1EID8 = 0x73,
|
||||
MCP_RXB1EID0 = 0x74,
|
||||
MCP_RXB1DLC = 0x75,
|
||||
MCP_RXB1DATA = 0x76
|
||||
};
|
||||
|
||||
static const uint8_t CANSTAT_OPMOD = 0xE0;
|
||||
static const uint8_t CANSTAT_ICOD = 0x0E;
|
||||
|
||||
static const uint8_t CNF3_SOF = 0x80;
|
||||
|
||||
static const uint8_t TXB_EXIDE_MASK = 0x08;
|
||||
static const uint8_t DLC_MASK = 0x0F;
|
||||
static const uint8_t RTR_MASK = 0x40;
|
||||
|
||||
static const uint8_t RXB_CTRL_RXM_STD = 0x20;
|
||||
static const uint8_t RXB_CTRL_RXM_EXT = 0x40;
|
||||
static const uint8_t RXB_CTRL_RXM_STDEXT = 0x00;
|
||||
static const uint8_t RXB_CTRL_RXM_MASK = 0x60;
|
||||
static const uint8_t RXB_CTRL_RTR = 0x08;
|
||||
static const uint8_t RXB_0_CTRL_BUKT = 0x04;
|
||||
|
||||
static const uint8_t MCP_SIDH = 0;
|
||||
static const uint8_t MCP_SIDL = 1;
|
||||
static const uint8_t MCP_EID8 = 2;
|
||||
static const uint8_t MCP_EID0 = 3;
|
||||
static const uint8_t MCP_DLC = 4;
|
||||
static const uint8_t MCP_DATA = 5;
|
||||
|
||||
/*
|
||||
* Speed 8M
|
||||
*/
|
||||
static const uint8_t MCP_8MHZ_1000KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_8MHZ_1000KBPS_CFG2 = 0x80;
|
||||
static const uint8_t MCP_8MHZ_1000KBPS_CFG3 = 0x80;
|
||||
|
||||
static const uint8_t MCP_8MHZ_500KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_8MHZ_500KBPS_CFG2 = 0x90;
|
||||
static const uint8_t MCP_8MHZ_500KBPS_CFG3 = 0x82;
|
||||
|
||||
static const uint8_t MCP_8MHZ_250KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_8MHZ_250KBPS_CFG2 = 0xB1;
|
||||
static const uint8_t MCP_8MHZ_250KBPS_CFG3 = 0x85;
|
||||
|
||||
static const uint8_t MCP_8MHZ_200KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_8MHZ_200KBPS_CFG2 = 0xB4;
|
||||
static const uint8_t MCP_8MHZ_200KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_8MHZ_125KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_8MHZ_125KBPS_CFG2 = 0xB1;
|
||||
static const uint8_t MCP_8MHZ_125KBPS_CFG3 = 0x85;
|
||||
|
||||
static const uint8_t MCP_8MHZ_100KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_8MHZ_100KBPS_CFG2 = 0xB4;
|
||||
static const uint8_t MCP_8MHZ_100KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_8MHZ_80KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_8MHZ_80KBPS_CFG2 = 0xBF;
|
||||
static const uint8_t MCP_8MHZ_80KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_8MHZ_50KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_8MHZ_50KBPS_CFG2 = 0xB4;
|
||||
static const uint8_t MCP_8MHZ_50KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_8MHZ_40KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_8MHZ_40KBPS_CFG2 = 0xBF;
|
||||
static const uint8_t MCP_8MHZ_40KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_8MHZ_33K3BPS_CFG1 = 0x47;
|
||||
static const uint8_t MCP_8MHZ_33K3BPS_CFG2 = 0xE2;
|
||||
static const uint8_t MCP_8MHZ_33K3BPS_CFG3 = 0x85;
|
||||
|
||||
static const uint8_t MCP_8MHZ_31K25BPS_CFG1 = 0x07;
|
||||
static const uint8_t MCP_8MHZ_31K25BPS_CFG2 = 0xA4;
|
||||
static const uint8_t MCP_8MHZ_31K25BPS_CFG3 = 0x84;
|
||||
|
||||
static const uint8_t MCP_8MHZ_20KBPS_CFG1 = 0x07;
|
||||
static const uint8_t MCP_8MHZ_20KBPS_CFG2 = 0xBF;
|
||||
static const uint8_t MCP_8MHZ_20KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_8MHZ_10KBPS_CFG1 = 0x0F;
|
||||
static const uint8_t MCP_8MHZ_10KBPS_CFG2 = 0xBF;
|
||||
static const uint8_t MCP_8MHZ_10KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_8MHZ_5KBPS_CFG1 = 0x1F;
|
||||
static const uint8_t MCP_8MHZ_5KBPS_CFG2 = 0xBF;
|
||||
static const uint8_t MCP_8MHZ_5KBPS_CFG3 = 0x87;
|
||||
|
||||
/*
|
||||
* speed 16M
|
||||
*/
|
||||
static const uint8_t MCP_16MHZ_1000KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_16MHZ_1000KBPS_CFG2 = 0xD0;
|
||||
static const uint8_t MCP_16MHZ_1000KBPS_CFG3 = 0x82;
|
||||
|
||||
static const uint8_t MCP_16MHZ_500KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_16MHZ_500KBPS_CFG2 = 0xF0;
|
||||
static const uint8_t MCP_16MHZ_500KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_16MHZ_250KBPS_CFG1 = 0x41;
|
||||
static const uint8_t MCP_16MHZ_250KBPS_CFG2 = 0xF1;
|
||||
static const uint8_t MCP_16MHZ_250KBPS_CFG3 = 0x85;
|
||||
|
||||
static const uint8_t MCP_16MHZ_200KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_16MHZ_200KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_16MHZ_200KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_125KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_16MHZ_125KBPS_CFG2 = 0xF0;
|
||||
static const uint8_t MCP_16MHZ_125KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_16MHZ_100KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_16MHZ_100KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_16MHZ_100KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_80KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_16MHZ_80KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_16MHZ_80KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_83K3BPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_16MHZ_83K3BPS_CFG2 = 0xBE;
|
||||
static const uint8_t MCP_16MHZ_83K3BPS_CFG3 = 0x07;
|
||||
|
||||
static const uint8_t MCP_16MHZ_50KBPS_CFG1 = 0x07;
|
||||
static const uint8_t MCP_16MHZ_50KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_16MHZ_50KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_40KBPS_CFG1 = 0x07;
|
||||
static const uint8_t MCP_16MHZ_40KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_16MHZ_40KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_33K3BPS_CFG1 = 0x4E;
|
||||
static const uint8_t MCP_16MHZ_33K3BPS_CFG2 = 0xF1;
|
||||
static const uint8_t MCP_16MHZ_33K3BPS_CFG3 = 0x85;
|
||||
|
||||
static const uint8_t MCP_16MHZ_20KBPS_CFG1 = 0x0F;
|
||||
static const uint8_t MCP_16MHZ_20KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_16MHZ_20KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_10KBPS_CFG1 = 0x1F;
|
||||
static const uint8_t MCP_16MHZ_10KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_16MHZ_10KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_5KBPS_CFG1 = 0x3F;
|
||||
static const uint8_t MCP_16MHZ_5KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_16MHZ_5KBPS_CFG3 = 0x87;
|
||||
|
||||
/*
|
||||
* speed 20M
|
||||
*/
|
||||
static const uint8_t MCP_20MHZ_1000KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_20MHZ_1000KBPS_CFG2 = 0xD9;
|
||||
static const uint8_t MCP_20MHZ_1000KBPS_CFG3 = 0x82;
|
||||
|
||||
static const uint8_t MCP_20MHZ_500KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_20MHZ_500KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_20MHZ_500KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_250KBPS_CFG1 = 0x41;
|
||||
static const uint8_t MCP_20MHZ_250KBPS_CFG2 = 0xFB;
|
||||
static const uint8_t MCP_20MHZ_250KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_20MHZ_200KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_20MHZ_200KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_20MHZ_200KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_125KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_20MHZ_125KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_20MHZ_125KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_100KBPS_CFG1 = 0x04;
|
||||
static const uint8_t MCP_20MHZ_100KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_20MHZ_100KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_83K3BPS_CFG1 = 0x04;
|
||||
static const uint8_t MCP_20MHZ_83K3BPS_CFG2 = 0xFE;
|
||||
static const uint8_t MCP_20MHZ_83K3BPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_80KBPS_CFG1 = 0x04;
|
||||
static const uint8_t MCP_20MHZ_80KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_20MHZ_80KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_50KBPS_CFG1 = 0x09;
|
||||
static const uint8_t MCP_20MHZ_50KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_20MHZ_50KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_40KBPS_CFG1 = 0x09;
|
||||
static const uint8_t MCP_20MHZ_40KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_20MHZ_40KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_33K3BPS_CFG1 = 0x0B;
|
||||
static const uint8_t MCP_20MHZ_33K3BPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_20MHZ_33K3BPS_CFG3 = 0x87;
|
||||
|
||||
} // namespace mcp2515
|
||||
} // namespace esphome
|
0
esphome/components/mcp9808/__init__.py
Normal file
0
esphome/components/mcp9808/__init__.py
Normal file
81
esphome/components/mcp9808/mcp9808.cpp
Normal file
81
esphome/components/mcp9808/mcp9808.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
#include "mcp9808.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp9808 {
|
||||
|
||||
static const uint8_t MCP9808_REG_AMBIENT_TEMP = 0x05;
|
||||
static const uint8_t MCP9808_REG_MANUF_ID = 0x06;
|
||||
static const uint8_t MCP9808_REG_DEVICE_ID = 0x07;
|
||||
|
||||
static const uint16_t MCP9808_MANUF_ID = 0x0054;
|
||||
static const uint16_t MCP9808_DEV_ID = 0x0400;
|
||||
|
||||
static const uint8_t MCP9808_AMBIENT_CLEAR_FLAGS = 0x1F;
|
||||
static const uint8_t MCP9808_AMBIENT_CLEAR_SIGN = 0x0F;
|
||||
static const uint8_t MCP9808_AMBIENT_TEMP_NEGATIVE = 0x10;
|
||||
|
||||
static const char *TAG = "mcp9808";
|
||||
|
||||
void MCP9808Sensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up %s...", this->name_.c_str());
|
||||
|
||||
uint16_t manu;
|
||||
if (!this->read_byte_16(MCP9808_REG_MANUF_ID, &manu, 0) || manu != MCP9808_MANUF_ID) {
|
||||
this->mark_failed();
|
||||
ESP_LOGE(TAG, "%s manufacuturer id failed, device returned %X", this->name_.c_str(), manu);
|
||||
return;
|
||||
}
|
||||
uint16_t dev_id;
|
||||
if (!this->read_byte_16(MCP9808_REG_DEVICE_ID, &dev_id, 0) || dev_id != MCP9808_DEV_ID) {
|
||||
this->mark_failed();
|
||||
ESP_LOGE(TAG, "%s device id failed, device returned %X", this->name_.c_str(), dev_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
void MCP9808Sensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "%s:", this->name_.c_str());
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with %s failed!", this->name_.c_str());
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this);
|
||||
}
|
||||
void MCP9808Sensor::update() {
|
||||
uint16_t raw_temp;
|
||||
if (!this->read_byte_16(MCP9808_REG_AMBIENT_TEMP, &raw_temp)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
if (raw_temp == 0xFFFF) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float temp = NAN;
|
||||
uint8_t msb = (uint8_t)((raw_temp & 0xff00) >> 8);
|
||||
uint8_t lsb = raw_temp & 0x00ff;
|
||||
|
||||
msb = msb & MCP9808_AMBIENT_CLEAR_FLAGS;
|
||||
|
||||
if ((msb & MCP9808_AMBIENT_TEMP_NEGATIVE) == MCP9808_AMBIENT_TEMP_NEGATIVE) {
|
||||
msb = msb & MCP9808_AMBIENT_CLEAR_SIGN;
|
||||
temp = (256 - ((uint16_t)(msb) *16 + lsb / 16.0f)) * -1;
|
||||
} else {
|
||||
temp = (uint16_t)(msb) *16 + lsb / 16.0f;
|
||||
}
|
||||
|
||||
if (isnan(temp)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "%s: Got temperature=%.4f°C", this->name_.c_str(), temp);
|
||||
this->publish_state(temp);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
float MCP9808Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace mcp9808
|
||||
} // namespace esphome
|
20
esphome/components/mcp9808/mcp9808.h
Normal file
20
esphome/components/mcp9808/mcp9808.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp9808 {
|
||||
|
||||
class MCP9808Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void update() override;
|
||||
};
|
||||
|
||||
} // namespace mcp9808
|
||||
} // namespace esphome
|
22
esphome/components/mcp9808/sensor.py
Normal file
22
esphome/components/mcp9808/sensor.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID, ICON_THERMOMETER, UNIT_CELSIUS
|
||||
|
||||
CODEOWNERS = ['@k7hpn']
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
mcp9808_ns = cg.esphome_ns.namespace('mcp9808')
|
||||
MCP9808Sensor = mcp9808_ns.class_('MCP9808Sensor', sensor.Sensor, cg.PollingComponent,
|
||||
i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
||||
cv.GenerateID(): cv.declare_id(MCP9808Sensor),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x18))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
|
@ -1,6 +1,9 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_PASSWORD, CONF_PORT, CONF_SAFE_MODE
|
||||
from esphome.const import (
|
||||
CONF_ID, CONF_NUM_ATTEMPTS, CONF_PASSWORD,
|
||||
CONF_PORT, CONF_REBOOT_TIMEOUT, CONF_SAFE_MODE
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ['@esphome/core']
|
||||
|
@ -14,6 +17,8 @@ CONFIG_SCHEMA = cv.Schema({
|
|||
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
||||
cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port,
|
||||
cv.Optional(CONF_PASSWORD, default=''): cv.string,
|
||||
cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_NUM_ATTEMPTS, default='10'): cv.positive_not_null_int
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
|
@ -26,7 +31,7 @@ def to_code(config):
|
|||
yield cg.register_component(var, config)
|
||||
|
||||
if config[CONF_SAFE_MODE]:
|
||||
cg.add(var.start_safe_mode())
|
||||
cg.add(var.start_safe_mode(config[CONF_NUM_ATTEMPTS], config[CONF_REBOOT_TIMEOUT]))
|
||||
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library('Update', None)
|
||||
|
|
|
@ -47,7 +47,7 @@ class OTAComponent : public Component {
|
|||
/// Manually set the port OTA should listen on.
|
||||
void set_port(uint16_t port);
|
||||
|
||||
void start_safe_mode(uint8_t num_attempts = 10, uint32_t enable_time = 120000);
|
||||
void start_safe_mode(uint8_t num_attempts, uint32_t enable_time);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
|
|
|
@ -29,10 +29,9 @@ void FloatOutput::set_level(float state) {
|
|||
this->power_.unrequest();
|
||||
}
|
||||
#endif
|
||||
|
||||
float adjusted_value = (state * (this->max_power_ - this->min_power_)) + this->min_power_;
|
||||
if (this->is_inverted())
|
||||
adjusted_value = 1.0f - adjusted_value;
|
||||
state = 1.0f - state;
|
||||
float adjusted_value = (state * (this->max_power_ - this->min_power_)) + this->min_power_;
|
||||
this->write_state(adjusted_value);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
|||
from esphome import pins
|
||||
from esphome.components import display, spi
|
||||
from esphome.const import (
|
||||
CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES, CONF_RESET_PIN, CONF_CS_PIN,
|
||||
CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES, CONF_RESET_PIN, CONF_CS_PIN, CONF_CONTRAST
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['spi']
|
||||
|
@ -17,6 +17,7 @@ CONFIG_SCHEMA = cv.All(display.FULL_DISPLAY_SCHEMA.extend({
|
|||
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, # CE
|
||||
cv.Optional(CONF_CONTRAST, default=0x7f): cv.int_,
|
||||
}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema()),
|
||||
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
|
||||
|
||||
|
@ -33,6 +34,8 @@ def to_code(config):
|
|||
reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN])
|
||||
cg.add(var.set_reset_pin(reset))
|
||||
|
||||
cg.add(var.set_contrast(config[CONF_CONTRAST]))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')],
|
||||
return_type=cg.void)
|
||||
|
|
|
@ -35,8 +35,7 @@ void PCD8544::initialize() {
|
|||
this->command(this->PCD8544_SETBIAS | 0x04);
|
||||
|
||||
// contrast
|
||||
// TODO: in future version we may add a user a control over contrast
|
||||
this->command(this->PCD8544_SETVOP | 0x7f); // Experimentally determined
|
||||
this->command(this->PCD8544_SETVOP | this->contrast_);
|
||||
|
||||
// normal mode
|
||||
this->command(this->PCD8544_FUNCTIONSET);
|
||||
|
|
|
@ -29,9 +29,11 @@ class PCD8544 : public PollingComponent,
|
|||
const uint8_t PCD8544_SETTEMP = 0x04;
|
||||
const uint8_t PCD8544_SETBIAS = 0x10;
|
||||
const uint8_t PCD8544_SETVOP = 0x80;
|
||||
uint8_t contrast_;
|
||||
|
||||
void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; }
|
||||
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
||||
void set_contrast(uint8_t contrast) { this->contrast_ = contrast; }
|
||||
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
|
||||
|
||||
void command(uint8_t value);
|
||||
|
|
|
@ -1,30 +1,37 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import spi
|
||||
from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID
|
||||
from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID
|
||||
from esphome.core import coroutine
|
||||
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
DEPENDENCIES = ['spi']
|
||||
CODEOWNERS = ['@OttoWinter', '@jesserockz']
|
||||
AUTO_LOAD = ['binary_sensor']
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_PN532_ID = 'pn532_id'
|
||||
|
||||
pn532_ns = cg.esphome_ns.namespace('pn532')
|
||||
PN532 = pn532_ns.class_('PN532', cg.PollingComponent, spi.SPIDevice)
|
||||
PN532 = pn532_ns.class_('PN532', cg.PollingComponent)
|
||||
|
||||
PN532Trigger = pn532_ns.class_('PN532Trigger', automation.Trigger.template(cg.std_string))
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
PN532_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(PN532),
|
||||
cv.Optional(CONF_ON_TAG): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532Trigger),
|
||||
}),
|
||||
}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema())
|
||||
}).extend(cv.polling_component_schema('1s'))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
def CONFIG_SCHEMA(conf):
|
||||
if conf:
|
||||
raise cv.Invalid("This component has been moved in 1.16, please see the docs for updated "
|
||||
"instructions. https://esphome.io/components/binary_sensor/pn532.html")
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_pn532(var, config):
|
||||
yield cg.register_component(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
||||
|
||||
for conf in config.get(CONF_ON_TAG, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
|
|
|
@ -3,12 +3,10 @@ import esphome.config_validation as cv
|
|||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_UID, CONF_ID
|
||||
from esphome.core import HexInt
|
||||
from . import pn532_ns, PN532
|
||||
from . import pn532_ns, PN532, CONF_PN532_ID
|
||||
|
||||
DEPENDENCIES = ['pn532']
|
||||
|
||||
CONF_PN532_ID = 'pn532_id'
|
||||
|
||||
|
||||
def validate_uid(value):
|
||||
value = cv.string_strict(value)
|
||||
|
@ -18,8 +16,8 @@ def validate_uid(value):
|
|||
"long.")
|
||||
try:
|
||||
x = int(x, 16)
|
||||
except ValueError:
|
||||
raise cv.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.")
|
||||
except ValueError as err:
|
||||
raise cv.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.") from err
|
||||
if x < 0 or x > 255:
|
||||
raise cv.Invalid("Valid values for UID parts (separated by '-') are 00 to FF")
|
||||
return value
|
||||
|
|
|
@ -11,51 +11,50 @@ namespace pn532 {
|
|||
|
||||
static const char *TAG = "pn532";
|
||||
|
||||
void format_uid(char *buf, const uint8_t *uid, uint8_t uid_length) {
|
||||
std::string format_uid(std::vector<uint8_t> &uid) {
|
||||
char buf[32];
|
||||
int offset = 0;
|
||||
for (uint8_t i = 0; i < uid_length; i++) {
|
||||
for (uint8_t i = 0; i < uid.size(); i++) {
|
||||
const char *format = "%02X";
|
||||
if (i + 1 < uid_length)
|
||||
if (i + 1 < uid.size())
|
||||
format = "%02X-";
|
||||
offset += sprintf(buf + offset, format, uid[i]);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
void PN532::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up PN532...");
|
||||
this->spi_setup();
|
||||
|
||||
// Wake the chip up from power down
|
||||
// 1. Enable the SS line for at least 2ms
|
||||
// 2. Send a dummy command to get the protocol synced up
|
||||
// (this may time out, but that's ok)
|
||||
// 3. Send SAM config command with normal mode without waiting for ready bit (IRQ not initialized yet)
|
||||
// 4. Probably optional, send SAM config again, this time checking ACK and return value
|
||||
this->cs_->digital_write(false);
|
||||
delay(10);
|
||||
// Get version data
|
||||
if (!this->write_command_({PN532_COMMAND_VERSION_DATA})) {
|
||||
ESP_LOGE(TAG, "Error sending version command");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// send dummy firmware version command to get synced up
|
||||
this->pn532_write_command_check_ack_({0x02}); // get firmware version command
|
||||
// do not actually read any data, this should be OK according to datasheet
|
||||
std::vector<uint8_t> version_data;
|
||||
if (!this->read_response_(PN532_COMMAND_VERSION_DATA, version_data)) {
|
||||
ESP_LOGE(TAG, "Error getting version");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Found chip PN5%02X", version_data[0]);
|
||||
ESP_LOGD(TAG, "Firmware ver. %d.%d", version_data[1], version_data[2]);
|
||||
|
||||
this->pn532_write_command_({
|
||||
0x14, // SAM config command
|
||||
if (!this->write_command_({
|
||||
PN532_COMMAND_SAMCONFIGURATION,
|
||||
0x01, // normal mode
|
||||
0x14, // zero timeout (not in virtual card mode)
|
||||
0x01,
|
||||
});
|
||||
})) {
|
||||
ESP_LOGE(TAG, "No wakeup ack");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// do not wait for ready bit, this is a dummy command
|
||||
delay(2);
|
||||
|
||||
// Try to read ACK, if it fails it might be because there's data from a previous power cycle left
|
||||
this->read_ack_();
|
||||
// do not wait for ready bit for return data
|
||||
delay(5);
|
||||
|
||||
// read data packet for wakeup result
|
||||
auto wakeup_result = this->pn532_read_data_();
|
||||
if (wakeup_result.size() != 1) {
|
||||
std::vector<uint8_t> wakeup_result;
|
||||
if (!this->read_response_(PN532_COMMAND_SAMCONFIGURATION, wakeup_result)) {
|
||||
this->error_code_ = WAKEUP_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
|
@ -63,23 +62,21 @@ void PN532::setup() {
|
|||
|
||||
// Set up SAM (secure access module)
|
||||
uint8_t sam_timeout = std::min(255u, this->update_interval_ / 50);
|
||||
bool ret = this->pn532_write_command_check_ack_({
|
||||
0x14, // SAM config command
|
||||
if (!this->write_command_({
|
||||
PN532_COMMAND_SAMCONFIGURATION,
|
||||
0x01, // normal mode
|
||||
sam_timeout, // timeout as multiple of 50ms (actually only for virtual card mode, but shouldn't matter)
|
||||
0x01, // Enable IRQ
|
||||
});
|
||||
|
||||
if (!ret) {
|
||||
})) {
|
||||
this->error_code_ = SAM_COMMAND_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
auto sam_result = this->pn532_read_data_();
|
||||
if (sam_result.size() != 1) {
|
||||
std::vector<uint8_t> sam_result;
|
||||
if (!this->read_response_(PN532_COMMAND_SAMCONFIGURATION, sam_result)) {
|
||||
ESP_LOGV(TAG, "Invalid SAM result: (%u)", sam_result.size()); // NOLINT
|
||||
for (auto dat : sam_result) {
|
||||
for (uint8_t dat : sam_result) {
|
||||
ESP_LOGV(TAG, " 0x%02X", dat);
|
||||
}
|
||||
this->error_code_ = SAM_COMMAND_FAILED;
|
||||
|
@ -94,12 +91,11 @@ void PN532::update() {
|
|||
for (auto *obj : this->binary_sensors_)
|
||||
obj->on_scan_end();
|
||||
|
||||
bool success = this->pn532_write_command_check_ack_({
|
||||
0x4A, // INLISTPASSIVETARGET
|
||||
if (!this->write_command_({
|
||||
PN532_COMMAND_INLISTPASSIVETARGET,
|
||||
0x01, // max 1 card
|
||||
0x00, // baud rate ISO14443A (106 kbit/s)
|
||||
});
|
||||
if (!success) {
|
||||
})) {
|
||||
ESP_LOGW(TAG, "Requesting tag read failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
|
@ -107,53 +103,60 @@ void PN532::update() {
|
|||
this->status_clear_warning();
|
||||
this->requested_read_ = true;
|
||||
}
|
||||
|
||||
void PN532::loop() {
|
||||
if (!this->requested_read_ || !this->is_ready_())
|
||||
if (!this->requested_read_)
|
||||
return;
|
||||
|
||||
auto read = this->pn532_read_data_();
|
||||
std::vector<uint8_t> read;
|
||||
bool success = this->read_response_(PN532_COMMAND_INLISTPASSIVETARGET, read);
|
||||
|
||||
this->requested_read_ = false;
|
||||
|
||||
if (read.size() <= 2 || read[0] != 0x4B) {
|
||||
if (!success) {
|
||||
// Something failed
|
||||
this->current_uid_ = {};
|
||||
this->turn_off_rf_();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t num_targets = read[1];
|
||||
uint8_t num_targets = read[0];
|
||||
if (num_targets != 1) {
|
||||
// no tags found or too many
|
||||
this->current_uid_ = {};
|
||||
this->turn_off_rf_();
|
||||
return;
|
||||
}
|
||||
|
||||
// const uint8_t target_number = read[2];
|
||||
// const uint16_t sens_res = uint16_t(read[3] << 8) | read[4];
|
||||
// const uint8_t sel_res = read[5];
|
||||
const uint8_t nfcid_length = read[6];
|
||||
const uint8_t *nfcid = &read[7];
|
||||
if (read.size() < 7U + nfcid_length) {
|
||||
uint8_t nfcid_length = read[5];
|
||||
std::vector<uint8_t> nfcid(read.begin() + 6, read.begin() + 6 + nfcid_length);
|
||||
if (read.size() < 6U + nfcid_length) {
|
||||
// oops, pn532 returned invalid data
|
||||
return;
|
||||
}
|
||||
|
||||
bool report = true;
|
||||
// 1. Go through all triggers
|
||||
for (auto *trigger : this->triggers_)
|
||||
trigger->process(nfcid, nfcid_length);
|
||||
|
||||
// 2. Find a binary sensor
|
||||
for (auto *tag : this->binary_sensors_) {
|
||||
if (tag->process(nfcid, nfcid_length)) {
|
||||
// 2.1 if found, do not dump
|
||||
for (auto *bin_sens : this->binary_sensors_) {
|
||||
if (bin_sens->process(nfcid)) {
|
||||
report = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (nfcid.size() == this->current_uid_.size()) {
|
||||
bool same_uid = false;
|
||||
for (uint8_t i = 0; i < nfcid.size(); i++)
|
||||
same_uid |= nfcid[i] == this->current_uid_[i];
|
||||
if (same_uid)
|
||||
return;
|
||||
}
|
||||
|
||||
this->current_uid_ = nfcid;
|
||||
|
||||
for (auto *trigger : this->triggers_)
|
||||
trigger->process(nfcid);
|
||||
|
||||
if (report) {
|
||||
char buf[32];
|
||||
format_uid(buf, nfcid, nfcid_length);
|
||||
ESP_LOGD(TAG, "Found new tag '%s'", buf);
|
||||
ESP_LOGD(TAG, "Found new tag '%s'", format_uid(nfcid).c_str());
|
||||
}
|
||||
|
||||
this->turn_off_rf_();
|
||||
|
@ -161,195 +164,158 @@ void PN532::loop() {
|
|||
|
||||
void PN532::turn_off_rf_() {
|
||||
ESP_LOGVV(TAG, "Turning RF field OFF");
|
||||
this->pn532_write_command_check_ack_({
|
||||
0x32, // RFConfiguration
|
||||
this->write_command_({
|
||||
PN532_COMMAND_RFCONFIGURATION,
|
||||
0x1, // RF Field
|
||||
0x0 // Off
|
||||
});
|
||||
}
|
||||
|
||||
float PN532::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void PN532::pn532_write_command_(const std::vector<uint8_t> &data) {
|
||||
this->enable();
|
||||
delay(2);
|
||||
// First byte, communication mode: Write data
|
||||
this->write_byte(0x01);
|
||||
|
||||
bool PN532::write_command_(const std::vector<uint8_t> &data) {
|
||||
std::vector<uint8_t> write_data;
|
||||
// Preamble
|
||||
this->write_byte(0x00);
|
||||
write_data.push_back(0x00);
|
||||
|
||||
// Start code
|
||||
this->write_byte(0x00);
|
||||
this->write_byte(0xFF);
|
||||
write_data.push_back(0x00);
|
||||
write_data.push_back(0xFF);
|
||||
|
||||
// Length of message, TFI + data bytes
|
||||
const uint8_t real_length = data.size() + 1;
|
||||
// LEN
|
||||
this->write_byte(real_length);
|
||||
write_data.push_back(real_length);
|
||||
// LCS (Length checksum)
|
||||
this->write_byte(~real_length + 1);
|
||||
write_data.push_back(~real_length + 1);
|
||||
|
||||
// TFI (Frame Identifier, 0xD4 means to PN532, 0xD5 means from PN532)
|
||||
this->write_byte(0xD4);
|
||||
write_data.push_back(0xD4);
|
||||
// calculate checksum, TFI is part of checksum
|
||||
uint8_t checksum = 0xD4;
|
||||
|
||||
// DATA
|
||||
for (uint8_t dat : data) {
|
||||
this->write_byte(dat);
|
||||
write_data.push_back(dat);
|
||||
checksum += dat;
|
||||
}
|
||||
|
||||
// DCS (Data checksum)
|
||||
this->write_byte(~checksum + 1);
|
||||
write_data.push_back(~checksum + 1);
|
||||
// Postamble
|
||||
this->write_byte(0x00);
|
||||
write_data.push_back(0x00);
|
||||
|
||||
this->disable();
|
||||
this->write_data(write_data);
|
||||
|
||||
return this->read_ack_();
|
||||
}
|
||||
|
||||
bool PN532::pn532_write_command_check_ack_(const std::vector<uint8_t> &data) {
|
||||
// 1. write command
|
||||
this->pn532_write_command_(data);
|
||||
|
||||
// 2. wait for readiness
|
||||
if (!this->wait_ready_())
|
||||
return false;
|
||||
|
||||
// 3. read ack
|
||||
if (!this->read_ack_()) {
|
||||
ESP_LOGV(TAG, "Invalid ACK frame received from PN532!");
|
||||
bool PN532::read_response_(uint8_t command, std::vector<uint8_t> &data) {
|
||||
ESP_LOGV(TAG, "Reading response");
|
||||
uint8_t len = this->read_response_length_();
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Reading response of length %d", len);
|
||||
if (!this->read_data(data, 6 + len + 2)) {
|
||||
ESP_LOGD(TAG, "No response data");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data[1] != 0x00 && data[2] != 0x00 && data[3] != 0xFF) {
|
||||
// invalid packet
|
||||
ESP_LOGV(TAG, "read data invalid preamble!");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool valid_header = (static_cast<uint8_t>(data[4] + data[5]) == 0 && // LCS, len + lcs = 0
|
||||
data[6] == 0xD5 && // TFI - frame from PN532 to system controller
|
||||
data[7] == command + 1); // Correct command response
|
||||
|
||||
if (!valid_header) {
|
||||
ESP_LOGV(TAG, "read data invalid header!");
|
||||
return false;
|
||||
}
|
||||
|
||||
data.erase(data.begin(), data.begin() + 6); // Remove headers
|
||||
|
||||
uint8_t checksum = 0;
|
||||
for (int i = 0; i < len + 1; i++) {
|
||||
uint8_t dat = data[i];
|
||||
checksum += dat;
|
||||
}
|
||||
checksum = ~checksum + 1;
|
||||
|
||||
if (data[len + 1] != checksum) {
|
||||
ESP_LOGV(TAG, "read data invalid checksum! %02X != %02X", data[len], checksum);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data[len + 2] != 0x00) {
|
||||
ESP_LOGV(TAG, "read data invalid postamble!");
|
||||
return false;
|
||||
}
|
||||
|
||||
data.erase(data.begin(), data.begin() + 2); // Remove TFI and command code
|
||||
data.erase(data.end() - 2, data.end()); // Remove checksum and postamble
|
||||
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
ESP_LOGD(TAG, "PN532 Data Frame: (%u)", data.size()); // NOLINT
|
||||
for (uint8_t dat : data) {
|
||||
ESP_LOGD(TAG, " 0x%02X", dat);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> PN532::pn532_read_data_() {
|
||||
this->enable();
|
||||
delay(2);
|
||||
// Read data (transmission from the PN532 to the host)
|
||||
this->write_byte(0x03);
|
||||
uint8_t PN532::read_response_length_() {
|
||||
std::vector<uint8_t> data;
|
||||
if (!this->read_data(data, 6)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// sometimes preamble is not transmitted for whatever reason
|
||||
// mostly happens during startup.
|
||||
// just read the first two bytes and check if that is the case
|
||||
uint8_t header[6];
|
||||
this->read_array(header, 2);
|
||||
if (header[0] == 0x00 && header[1] == 0x00) {
|
||||
// normal packet, preamble included
|
||||
this->read_array(header + 2, 4);
|
||||
} else if (header[0] == 0x00 && header[1] == 0xFF) {
|
||||
// weird packet, preamble skipped; make it look like a normal packet
|
||||
header[0] = 0x00;
|
||||
header[1] = 0x00;
|
||||
header[2] = 0xFF;
|
||||
this->read_array(header + 3, 3);
|
||||
} else {
|
||||
if (data[1] != 0x00 && data[2] != 0x00 && data[3] != 0xFF) {
|
||||
// invalid packet
|
||||
this->disable();
|
||||
ESP_LOGV(TAG, "read data invalid preamble!");
|
||||
return {};
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool valid_header = (header[0] == 0x00 && // preamble
|
||||
header[1] == 0x00 && // start code
|
||||
header[2] == 0xFF && static_cast<uint8_t>(header[3] + header[4]) == 0 && // LCS, len + lcs = 0
|
||||
header[5] == 0xD5 // TFI - frame from PN532 to system controller
|
||||
);
|
||||
bool valid_header = (static_cast<uint8_t>(data[4] + data[5]) == 0 && // LCS, len + lcs = 0
|
||||
data[6] == 0xD5); // TFI - frame from PN532 to system controller
|
||||
|
||||
if (!valid_header) {
|
||||
this->disable();
|
||||
ESP_LOGV(TAG, "read data invalid header!");
|
||||
return {};
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ret;
|
||||
this->write_data({0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00}); // NACK - Retransmit last message
|
||||
|
||||
// full length of message, including TFI
|
||||
const uint8_t full_len = header[3];
|
||||
uint8_t full_len = data[4];
|
||||
// length of data, excluding TFI
|
||||
uint8_t len = full_len - 1;
|
||||
if (full_len == 0)
|
||||
len = 0;
|
||||
|
||||
ret.resize(len);
|
||||
this->read_array(ret.data(), len);
|
||||
|
||||
uint8_t checksum = 0xD5;
|
||||
for (uint8_t dat : ret)
|
||||
checksum += dat;
|
||||
checksum = ~checksum + 1;
|
||||
|
||||
uint8_t dcs = this->read_byte();
|
||||
if (dcs != checksum) {
|
||||
this->disable();
|
||||
ESP_LOGV(TAG, "read data invalid checksum! %02X != %02X", dcs, checksum);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (this->read_byte() != 0x00) {
|
||||
this->disable();
|
||||
ESP_LOGV(TAG, "read data invalid postamble!");
|
||||
return {};
|
||||
}
|
||||
this->disable();
|
||||
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
ESP_LOGVV(TAG, "PN532 Data Frame: (%u)", ret.size()); // NOLINT
|
||||
for (uint8_t dat : ret) {
|
||||
ESP_LOGVV(TAG, " 0x%02X", dat);
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
return len;
|
||||
}
|
||||
bool PN532::is_ready_() {
|
||||
this->enable();
|
||||
// First byte, communication mode: Read state
|
||||
this->write_byte(0x02);
|
||||
// PN532 returns a single data byte,
|
||||
// "After having sent a command, the host controller must wait for bit 0 of Status byte equals 1
|
||||
// before reading the data from the PN532."
|
||||
bool ret = this->read_byte() == 0x01;
|
||||
this->disable();
|
||||
|
||||
if (ret) {
|
||||
ESP_LOGVV(TAG, "Chip is ready!");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
bool PN532::read_ack_() {
|
||||
ESP_LOGVV(TAG, "Reading ACK...");
|
||||
this->enable();
|
||||
delay(2);
|
||||
// "Read data (transmission from the PN532 to the host) "
|
||||
this->write_byte(0x03);
|
||||
|
||||
uint8_t ack[6];
|
||||
memset(ack, 0, sizeof(ack));
|
||||
std::vector<uint8_t> data;
|
||||
if (!this->read_data(data, 6)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->read_array(ack, 6);
|
||||
this->disable();
|
||||
|
||||
bool matches = (ack[0] == 0x00 && // preamble
|
||||
ack[1] == 0x00 && // start of packet
|
||||
ack[2] == 0xFF && ack[3] == 0x00 && // ACK packet code
|
||||
ack[4] == 0xFF && ack[5] == 0x00 // postamble
|
||||
);
|
||||
bool matches = (data[1] == 0x00 && // preamble
|
||||
data[2] == 0x00 && // start of packet
|
||||
data[3] == 0xFF && data[4] == 0x00 && // ACK packet code
|
||||
data[5] == 0xFF && data[6] == 0x00); // postamble
|
||||
ESP_LOGVV(TAG, "ACK valid: %s", YESNO(matches));
|
||||
return matches;
|
||||
}
|
||||
bool PN532::wait_ready_() {
|
||||
uint32_t start_time = millis();
|
||||
while (!this->is_ready_()) {
|
||||
if (millis() - start_time > 100) {
|
||||
ESP_LOGE(TAG, "Timed out waiting for readiness from PN532!");
|
||||
return false;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
float PN532::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void PN532::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "PN532:");
|
||||
|
@ -364,7 +330,6 @@ void PN532::dump_config() {
|
|||
break;
|
||||
}
|
||||
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
for (auto *child : this->binary_sensors_) {
|
||||
|
@ -372,11 +337,11 @@ void PN532::dump_config() {
|
|||
}
|
||||
}
|
||||
|
||||
bool PN532BinarySensor::process(const uint8_t *data, uint8_t len) {
|
||||
if (len != this->uid_.size())
|
||||
bool PN532BinarySensor::process(std::vector<uint8_t> &data) {
|
||||
if (data.size() != this->uid_.size())
|
||||
return false;
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
for (uint8_t i = 0; i < data.size(); i++) {
|
||||
if (data[i] != this->uid_[i])
|
||||
return false;
|
||||
}
|
||||
|
@ -385,11 +350,7 @@ bool PN532BinarySensor::process(const uint8_t *data, uint8_t len) {
|
|||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
void PN532Trigger::process(const uint8_t *uid, uint8_t uid_length) {
|
||||
char buf[32];
|
||||
format_uid(buf, uid, uid_length);
|
||||
this->trigger(std::string(buf));
|
||||
}
|
||||
void PN532Trigger::process(std::vector<uint8_t> &data) { this->trigger(format_uid(data)); }
|
||||
|
||||
} // namespace pn532
|
||||
} // namespace esphome
|
||||
|
|
|
@ -3,17 +3,20 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn532 {
|
||||
|
||||
static const uint8_t PN532_COMMAND_VERSION_DATA = 0x02;
|
||||
static const uint8_t PN532_COMMAND_SAMCONFIGURATION = 0x14;
|
||||
static const uint8_t PN532_COMMAND_RFCONFIGURATION = 0x32;
|
||||
static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40;
|
||||
static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A;
|
||||
|
||||
class PN532BinarySensor;
|
||||
class PN532Trigger;
|
||||
|
||||
class PN532 : public PollingComponent,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_LSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_1MHZ> {
|
||||
class PN532 : public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
|
@ -28,38 +31,19 @@ class PN532 : public PollingComponent,
|
|||
void register_trigger(PN532Trigger *trig) { this->triggers_.push_back(trig); }
|
||||
|
||||
protected:
|
||||
/// Write the full command given in data to the PN532
|
||||
void pn532_write_command_(const std::vector<uint8_t> &data);
|
||||
bool pn532_write_command_check_ack_(const std::vector<uint8_t> &data);
|
||||
|
||||
/** Read a data frame from the PN532 and return the result as a vector.
|
||||
*
|
||||
* Note that is_ready needs to be checked first before requesting this method.
|
||||
*
|
||||
* On failure, an empty vector is returned.
|
||||
*/
|
||||
std::vector<uint8_t> pn532_read_data_();
|
||||
|
||||
/** Checks if the PN532 has set its ready status flag.
|
||||
*
|
||||
* Procedure goes as follows:
|
||||
* - Host sends command to PN532 "write data"
|
||||
* - Wait for readiness (until PN532 has processed command) by polling "read status"/is_ready_
|
||||
* - Parse ACK/NACK frame with "read data" byte
|
||||
*
|
||||
* - If data required, wait until device reports readiness again
|
||||
* - Then call "read data" and read certain number of bytes (length is given at offset 4 of frame)
|
||||
*/
|
||||
bool is_ready_();
|
||||
bool wait_ready_();
|
||||
|
||||
bool read_ack_();
|
||||
|
||||
void turn_off_rf_();
|
||||
bool write_command_(const std::vector<uint8_t> &data);
|
||||
bool read_response_(uint8_t command, std::vector<uint8_t> &data);
|
||||
bool read_ack_();
|
||||
uint8_t read_response_length_();
|
||||
|
||||
virtual bool write_data(const std::vector<uint8_t> &data) = 0;
|
||||
virtual bool read_data(std::vector<uint8_t> &data, uint8_t len) = 0;
|
||||
|
||||
bool requested_read_{false};
|
||||
std::vector<PN532BinarySensor *> binary_sensors_;
|
||||
std::vector<PN532Trigger *> triggers_;
|
||||
std::vector<uint8_t> current_uid_;
|
||||
enum PN532Error {
|
||||
NONE = 0,
|
||||
WAKEUP_FAILED,
|
||||
|
@ -71,7 +55,7 @@ class PN532BinarySensor : public binary_sensor::BinarySensor {
|
|||
public:
|
||||
void set_uid(const std::vector<uint8_t> &uid) { uid_ = uid; }
|
||||
|
||||
bool process(const uint8_t *data, uint8_t len);
|
||||
bool process(std::vector<uint8_t> &data);
|
||||
|
||||
void on_scan_end() {
|
||||
if (!this->found_) {
|
||||
|
@ -87,7 +71,7 @@ class PN532BinarySensor : public binary_sensor::BinarySensor {
|
|||
|
||||
class PN532Trigger : public Trigger<std::string> {
|
||||
public:
|
||||
void process(const uint8_t *uid, uint8_t uid_length);
|
||||
void process(std::vector<uint8_t> &data);
|
||||
};
|
||||
|
||||
} // namespace pn532
|
||||
|
|
21
esphome/components/pn532_i2c/__init__.py
Normal file
21
esphome/components/pn532_i2c/__init__.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, pn532
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ['pn532']
|
||||
CODEOWNERS = ['@OttoWinter', '@jesserockz']
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
pn532_i2c_ns = cg.esphome_ns.namespace('pn532_i2c')
|
||||
PN532I2C = pn532_i2c_ns.class_('PN532I2C', pn532.PN532, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(pn532.PN532_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(PN532I2C),
|
||||
}).extend(i2c.i2c_device_schema(0x24)))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield pn532.setup_pn532(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
45
esphome/components/pn532_i2c/pn532_i2c.cpp
Normal file
45
esphome/components/pn532_i2c/pn532_i2c.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include "pn532_i2c.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
// Based on:
|
||||
// - https://cdn-shop.adafruit.com/datasheets/PN532C106_Application+Note_v1.2.pdf
|
||||
// - https://www.nxp.com/docs/en/nxp/application-notes/AN133910.pdf
|
||||
// - https://www.nxp.com/docs/en/nxp/application-notes/153710.pdf
|
||||
|
||||
namespace esphome {
|
||||
namespace pn532_i2c {
|
||||
|
||||
static const char *TAG = "pn532_i2c";
|
||||
|
||||
bool PN532I2C::write_data(const std::vector<uint8_t> &data) { return this->write_bytes_raw(data.data(), data.size()); }
|
||||
|
||||
bool PN532I2C::read_data(std::vector<uint8_t> &data, uint8_t len) {
|
||||
delay(5);
|
||||
|
||||
std::vector<uint8_t> ready;
|
||||
ready.resize(1);
|
||||
uint32_t start_time = millis();
|
||||
while (true) {
|
||||
if (this->read_bytes_raw(ready.data(), 1)) {
|
||||
if (ready[0] == 0x01)
|
||||
break;
|
||||
}
|
||||
|
||||
if (millis() - start_time > 100) {
|
||||
ESP_LOGV(TAG, "Timed out waiting for readiness from PN532!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
data.resize(len + 1);
|
||||
this->read_bytes_raw(data.data(), len + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PN532I2C::dump_config() {
|
||||
PN532::dump_config();
|
||||
LOG_I2C_DEVICE(this);
|
||||
}
|
||||
|
||||
} // namespace pn532_i2c
|
||||
} // namespace esphome
|
20
esphome/components/pn532_i2c/pn532_i2c.h
Normal file
20
esphome/components/pn532_i2c/pn532_i2c.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/pn532/pn532.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn532_i2c {
|
||||
|
||||
class PN532I2C : public pn532::PN532, public i2c::I2CDevice {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
bool write_data(const std::vector<uint8_t> &data) override;
|
||||
bool read_data(std::vector<uint8_t> &data, uint8_t len) override;
|
||||
};
|
||||
|
||||
} // namespace pn532_i2c
|
||||
} // namespace esphome
|
21
esphome/components/pn532_spi/__init__.py
Normal file
21
esphome/components/pn532_spi/__init__.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import spi, pn532
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ['pn532']
|
||||
CODEOWNERS = ['@OttoWinter', '@jesserockz']
|
||||
DEPENDENCIES = ['spi']
|
||||
|
||||
pn532_spi_ns = cg.esphome_ns.namespace('pn532_spi')
|
||||
PN532Spi = pn532_spi_ns.class_('PN532Spi', pn532.PN532, spi.SPIDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(pn532.PN532_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(PN532Spi),
|
||||
}).extend(spi.spi_device_schema(cs_pin_required=True)))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield pn532.setup_pn532(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
69
esphome/components/pn532_spi/pn532_spi.cpp
Normal file
69
esphome/components/pn532_spi/pn532_spi.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#include "pn532_spi.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
// Based on:
|
||||
// - https://cdn-shop.adafruit.com/datasheets/PN532C106_Application+Note_v1.2.pdf
|
||||
// - https://www.nxp.com/docs/en/nxp/application-notes/AN133910.pdf
|
||||
// - https://www.nxp.com/docs/en/nxp/application-notes/153710.pdf
|
||||
|
||||
namespace esphome {
|
||||
namespace pn532_spi {
|
||||
|
||||
static const char *TAG = "pn532_spi";
|
||||
|
||||
void PN532Spi::setup() {
|
||||
ESP_LOGI(TAG, "PN532Spi setup started!");
|
||||
this->spi_setup();
|
||||
|
||||
this->cs_->digital_write(false);
|
||||
delay(10);
|
||||
ESP_LOGI(TAG, "SPI setup finished!");
|
||||
PN532::setup();
|
||||
}
|
||||
|
||||
bool PN532Spi::write_data(const std::vector<uint8_t> &data) {
|
||||
this->enable();
|
||||
delay(2);
|
||||
// First byte, communication mode: Write data
|
||||
this->write_byte(0x01);
|
||||
|
||||
this->write_array(data.data(), data.size());
|
||||
this->disable();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PN532Spi::read_data(std::vector<uint8_t> &data, uint8_t len) {
|
||||
this->enable();
|
||||
// First byte, communication mode: Read state
|
||||
this->write_byte(0x02);
|
||||
|
||||
uint32_t start_time = millis();
|
||||
while (true) {
|
||||
if (this->read_byte() & 0x01)
|
||||
break;
|
||||
|
||||
if (millis() - start_time > 100) {
|
||||
this->disable();
|
||||
ESP_LOGV(TAG, "Timed out waiting for readiness from PN532!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Read data (transmission from the PN532 to the host)
|
||||
this->write_byte(0x03);
|
||||
|
||||
data.resize(len);
|
||||
this->read_array(data.data(), len);
|
||||
this->disable();
|
||||
data.insert(data.begin(), 0x01);
|
||||
return true;
|
||||
};
|
||||
|
||||
void PN532Spi::dump_config() {
|
||||
PN532::dump_config();
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
}
|
||||
|
||||
} // namespace pn532_spi
|
||||
} // namespace esphome
|
24
esphome/components/pn532_spi/pn532_spi.h
Normal file
24
esphome/components/pn532_spi/pn532_spi.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/pn532/pn532.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn532_spi {
|
||||
|
||||
class PN532Spi : public pn532::PN532,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_LSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
bool write_data(const std::vector<uint8_t> &data) override;
|
||||
bool read_data(std::vector<uint8_t> &data, uint8_t len) override;
|
||||
};
|
||||
|
||||
} // namespace pn532_spi
|
||||
} // namespace esphome
|
|
@ -18,5 +18,7 @@ CONFIG_SCHEMA = cv.Schema({
|
|||
def to_code(config):
|
||||
paren = yield cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||
|
||||
cg.add_define('USE_PROMETHEUS')
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||
yield cg.register_component(var, config)
|
||||
|
|
|
@ -158,6 +158,12 @@ void PulseCounterSensor::update() {
|
|||
|
||||
ESP_LOGD(TAG, "'%s': Retrieved counter: %0.2f pulses/min", this->get_name().c_str(), value);
|
||||
this->publish_state(value);
|
||||
|
||||
if (this->total_sensor_ != nullptr) {
|
||||
current_total_ += raw;
|
||||
ESP_LOGD(TAG, "'%s': Total : %i pulses", this->get_name().c_str(), current_total_);
|
||||
this->total_sensor_->publish_state(current_total_);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
|
|
@ -54,6 +54,7 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent {
|
|||
void set_rising_edge_mode(PulseCounterCountMode mode) { storage_.rising_edge_mode = mode; }
|
||||
void set_falling_edge_mode(PulseCounterCountMode mode) { storage_.falling_edge_mode = mode; }
|
||||
void set_filter_us(uint32_t filter) { storage_.filter_us = filter; }
|
||||
void set_total_sensor(sensor::Sensor *total_sensor) { total_sensor_ = total_sensor; }
|
||||
|
||||
/// Unit of measurement is "pulses/min".
|
||||
void setup() override;
|
||||
|
@ -64,6 +65,8 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent {
|
|||
protected:
|
||||
GPIOPin *pin_;
|
||||
PulseCounterStorage storage_;
|
||||
uint32_t current_total_ = 0;
|
||||
sensor::Sensor *total_sensor_;
|
||||
};
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
|
|
@ -3,8 +3,8 @@ import esphome.config_validation as cv
|
|||
from esphome import pins
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_COUNT_MODE, CONF_FALLING_EDGE, CONF_ID, CONF_INTERNAL_FILTER, \
|
||||
CONF_PIN, CONF_RISING_EDGE, CONF_NUMBER, \
|
||||
ICON_PULSE, UNIT_PULSES_PER_MINUTE
|
||||
CONF_PIN, CONF_RISING_EDGE, CONF_NUMBER, CONF_TOTAL, \
|
||||
ICON_PULSE, UNIT_PULSES_PER_MINUTE, UNIT_PULSES
|
||||
from esphome.core import CORE
|
||||
|
||||
pulse_counter_ns = cg.esphome_ns.namespace('pulse_counter')
|
||||
|
@ -58,6 +58,8 @@ CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PULSES_PER_MINUTE, ICON_PULSE, 2).exte
|
|||
cv.Required(CONF_FALLING_EDGE): COUNT_MODE_SCHEMA,
|
||||
}), validate_count_mode),
|
||||
cv.Optional(CONF_INTERNAL_FILTER, default='13us'): validate_internal_filter,
|
||||
cv.Optional(CONF_TOTAL): sensor.sensor_schema(UNIT_PULSES, ICON_PULSE, 0),
|
||||
|
||||
}).extend(cv.polling_component_schema('60s'))
|
||||
|
||||
|
||||
|
@ -72,3 +74,7 @@ def to_code(config):
|
|||
cg.add(var.set_rising_edge_mode(count[CONF_RISING_EDGE]))
|
||||
cg.add(var.set_falling_edge_mode(count[CONF_FALLING_EDGE]))
|
||||
cg.add(var.set_filter_us(config[CONF_INTERNAL_FILTER]))
|
||||
|
||||
if CONF_TOTAL in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_TOTAL])
|
||||
cg.add(var.set_total_sensor(sens))
|
||||
|
|
39
esphome/components/rc522_spi/__init__.py
Normal file
39
esphome/components/rc522_spi/__init__.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation, pins
|
||||
from esphome.components import spi
|
||||
from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN, CONF_CS_PIN
|
||||
|
||||
CODEOWNERS = ['@glmnet']
|
||||
DEPENDENCIES = ['spi']
|
||||
AUTO_LOAD = ['binary_sensor']
|
||||
MULTI_CONF = True
|
||||
|
||||
|
||||
rc522_spi_ns = cg.esphome_ns.namespace('rc522_spi')
|
||||
RC522 = rc522_spi_ns.class_('RC522', cg.PollingComponent, spi.SPIDevice)
|
||||
RC522Trigger = rc522_spi_ns.class_('RC522Trigger', automation.Trigger.template(cg.std_string))
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(RC522),
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_ON_TAG): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger),
|
||||
}),
|
||||
}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema())
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
||||
|
||||
if CONF_RESET_PIN in config:
|
||||
reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN])
|
||||
cg.add(var.set_reset_pin(reset))
|
||||
|
||||
for conf in config.get(CONF_ON_TAG, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
cg.add(var.register_trigger(trigger))
|
||||
yield automation.build_automation(trigger, [(cg.std_string, 'x')], conf)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue