1
0
mirror of https://github.com/esphome/esphome.git synced 2026-03-23 19:56:45 +01:00

Merge pull request #1452 from esphome/bump-1.16.0b1

1.16.0b1
This commit is contained in:
Jesse Hills 2021-01-09 07:48:27 +13:00 committed by GitHub
commit cac3055261
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
210 changed files with 9869 additions and 1201 deletions

View File

@ -25,3 +25,4 @@ indent_size = 2
[*.{yaml,yml}]
indent_style = space
indent_size = 2
quote_type = single

View File

@ -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

View File

@ -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

View File

@ -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 }}

View File

@ -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
View File

@ -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
View File

@ -0,0 +1,11 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "run",
"type": "shell",
"command": "python3 -m esphome config dashboard",
"problemMatcher": []
}
]
}

View File

@ -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

View File

@ -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'

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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_);

View File

@ -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};

View File

@ -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:

View 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]])

View File

@ -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;

View File

@ -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";
}

View File

@ -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,

View 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

View 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

View 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))

View File

@ -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':

View 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

View 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

View 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

View File

@ -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:

View File

@ -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;

View File

@ -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(); }

View File

@ -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)

View File

@ -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'

View File

View 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

View 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

View 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)

View File

@ -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: {

View File

@ -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]))

View File

@ -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. "

View File

@ -36,7 +36,10 @@ const uint8_t FUJITSU_GENERAL_FAN_HIGH_BYTE10 = 0x01;
const uint8_t FUJITSU_GENERAL_FAN_MEDIUM_BYTE10 = 0x02;
const uint8_t FUJITSU_GENERAL_FAN_LOW_BYTE10 = 0x03;
const uint8_t FUJITSU_GENERAL_FAN_SILENT_BYTE10 = 0x04;
const uint8_t FUJITSU_GENERAL_SWING_MASK_BYTE10 = 0b00010000;
const uint8_t FUJITSU_GENERAL_SWING_NONE_BYTE10 = 0x00;
const uint8_t FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 = 0x01;
const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 = 0x02;
const uint8_t FUJITSU_GENERAL_SWING_BOTH_BYTE10 = 0x03;
const uint8_t FUJITSU_GENERAL_BASE_BYTE10 = 0x00;
const uint8_t FUJITSU_GENERAL_BASE_BYTE11 = 0x00;
@ -74,7 +77,12 @@ const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000;
const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY = 38000;
FujitsuGeneralClimate::FujitsuGeneralClimate() : ClimateIR(FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1) {}
FujitsuGeneralClimate::FujitsuGeneralClimate()
: ClimateIR(
FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1.0f, true, true,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL,
climate::CLIMATE_SWING_BOTH}) {}
void FujitsuGeneralClimate::transmit_state() {
if (this->mode == climate::CLIMATE_MODE_OFF) {
@ -101,8 +109,8 @@ void FujitsuGeneralClimate::transmit_state() {
remote_state[15] = FUJITSU_GENERAL_BASE_BYTE15;
// Set temperature
uint8_t safecelsius = std::max((uint8_t) this->target_temperature, FUJITSU_GENERAL_TEMP_MIN);
safecelsius = std::min(safecelsius, FUJITSU_GENERAL_TEMP_MAX);
auto safecelsius =
(uint8_t) roundf(clamp(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX));
remote_state[8] = (byte) safecelsius - 16;
remote_state[8] = remote_state[8] << 4;
@ -119,18 +127,52 @@ void FujitsuGeneralClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
remote_state[9] = FUJITSU_GENERAL_MODE_HEAT_BYTE9;
break;
case climate::CLIMATE_MODE_DRY:
remote_state[9] = FUJITSU_GENERAL_MODE_DRY_BYTE9;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
remote_state[9] = FUJITSU_GENERAL_MODE_FAN_BYTE9;
break;
case climate::CLIMATE_MODE_AUTO:
default:
remote_state[9] = FUJITSU_GENERAL_MODE_AUTO_BYTE9;
break;
// TODO: CLIMATE_MODE_FAN_ONLY, CLIMATE_MODE_DRY, CLIMATE_MODE_10C are missing in esphome
// TODO: CLIMATE_MODE_10C are missing in esphome
}
// TODO: missing support for fan speed
remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10;
// Set fan
switch (this->fan_mode) {
case climate::CLIMATE_FAN_HIGH:
remote_state[10] = FUJITSU_GENERAL_FAN_HIGH_BYTE10;
break;
case climate::CLIMATE_FAN_MEDIUM:
remote_state[10] = FUJITSU_GENERAL_FAN_MEDIUM_BYTE10;
break;
case climate::CLIMATE_FAN_LOW:
remote_state[10] = FUJITSU_GENERAL_FAN_LOW_BYTE10;
break;
case climate::CLIMATE_FAN_AUTO:
default:
remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10;
break;
}
// TODO: missing support for swing
// remote_state[10] = (byte) remote_state[10] | FUJITSU_GENERAL_SWING_MASK_BYTE10;
// Set swing
switch (this->swing_mode) {
case climate::CLIMATE_SWING_VERTICAL:
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 << 4);
break;
case climate::CLIMATE_SWING_HORIZONTAL:
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 << 4);
break;
case climate::CLIMATE_SWING_BOTH:
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_BOTH_BYTE10 << 4);
break;
case climate::CLIMATE_SWING_OFF:
default:
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_NONE_BYTE10 << 4);
break;
}
// TODO: missing support for outdoor unit low noise
// remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14;

View File

View 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

View 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))

View 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)

View 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

View 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

View File

@ -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}};

View File

@ -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')

View File

@ -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();

View File

@ -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_;

View File

@ -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) {

View File

@ -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.
*

View File

View 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))

View 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

View 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

View 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

View 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

View File

@ -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):

View File

@ -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);

View File

@ -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')],

View File

@ -108,7 +108,11 @@ void MAX7219Component::display() {
// Send the data to the chip
for (uint8_t i = 0; i < this->num_chips_; i++) {
for (uint8_t j = 0; j < 8; j++) {
pixels[j] = this->max_displaybuffer_[i * 8 + j];
if (this->reverse_) {
pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j];
} else {
pixels[j] = this->max_displaybuffer_[i * 8 + j];
}
}
this->send64pixels(i, pixels);
}
@ -128,7 +132,7 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color colo
this->max_displaybuffer_.resize(x + 1, this->bckgrnd_);
}
if (y >= this->get_height_internal() || y < 0) // If pixel is outside display then dont draw
if ((y >= this->get_height_internal()) || (y < 0) || (x < 0)) // If pixel is outside display then dont draw
return;
uint16_t pos = x; // X is starting at 0 top left

View File

@ -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_;

View 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])

View 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, &reg_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

View 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

View 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])

View 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, &reg_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

View 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

View File

View 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)

View 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

View 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

View 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

View File

View 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

View 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

View 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)

View File

@ -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)

View File

@ -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)

View File

@ -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);
}

View File

@ -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,8 +17,9 @@ 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))
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
def to_code(config):
@ -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)

View File

@ -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);

View File

@ -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);

View File

@ -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])

View File

@ -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

View File

@ -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
0x01, // normal mode
0x14, // zero timeout (not in virtual card mode)
0x01,
});
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
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) {
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
})) {
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
0x01, // max 1 card
0x00, // baud rate ISO14443A (106 kbit/s)
});
if (!success) {
if (!this->write_command_({
PN532_COMMAND_INLISTPASSIVETARGET,
0x01, // max 1 card
0x00, // baud rate ISO14443A (106 kbit/s)
})) {
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
0x1, // RF Field
0x0 // Off
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

View File

@ -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

View 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)

View 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

View 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

View 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)

View 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

View 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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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))

View 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)

View File

@ -0,0 +1,44 @@
import esphome.codegen as cg
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 rc522_spi_ns, RC522
DEPENDENCIES = ['rc522_spi']
CONF_RC522_ID = 'rc522_id'
def validate_uid(value):
value = cv.string_strict(value)
for x in value.split('-'):
if len(x) != 2:
raise cv.Invalid("Each part (separated by '-') of the UID must be two characters "
"long.")
try:
x = int(x, 16)
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
RC522BinarySensor = rc522_spi_ns.class_('RC522BinarySensor', binary_sensor.BinarySensor)
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(RC522BinarySensor),
cv.GenerateID(CONF_RC522_ID): cv.use_id(RC522),
cv.Required(CONF_UID): validate_uid,
})
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield binary_sensor.register_binary_sensor(var, config)
hub = yield cg.get_variable(config[CONF_RC522_ID])
cg.add(hub.register_tag(var))
addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split('-')]
cg.add(var.set_uid(addr))

View File

@ -0,0 +1,857 @@
#include "rc522_spi.h"
#include "esphome/core/log.h"
// Based on:
// - https://github.com/miguelbalboa/rfid
namespace esphome {
namespace rc522_spi {
static const char *TAG = "rc522_spi";
static const uint8_t RESET_COUNT = 5;
void format_uid(char *buf, const uint8_t *uid, uint8_t uid_length) {
int offset = 0;
for (uint8_t i = 0; i < uid_length; i++) {
const char *format = "%02X";
if (i + 1 < uid_length)
format = "%02X-";
offset += sprintf(buf + offset, format, uid[i]);
}
}
void RC522::setup() {
spi_setup();
initialize_pending_ = true;
// Pull device out of power down / reset state.
// First set the resetPowerDownPin as digital input, to check the MFRC522 power down mode.
if (reset_pin_ != nullptr) {
reset_pin_->pin_mode(INPUT);
if (reset_pin_->digital_read() == LOW) { // The MFRC522 chip is in power down mode.
ESP_LOGV(TAG, "Power down mode detected. Hard resetting...");
reset_pin_->pin_mode(OUTPUT); // Now set the resetPowerDownPin as digital output.
reset_pin_->digital_write(LOW); // Make sure we have a clean LOW state.
delayMicroseconds(2); // 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μsl
reset_pin_->digital_write(HIGH); // Exit power down mode. This triggers a hard reset.
// Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs.
// Let us be generous: 50ms.
reset_timeout_ = millis();
return;
}
}
// Setup a soft reset
reset_count_ = RESET_COUNT;
reset_timeout_ = millis();
}
void RC522::initialize_() {
// Per originall code, wait 50 ms
if (millis() - reset_timeout_ < 50)
return;
// Reset baud rates
ESP_LOGV(TAG, "Initialize");
pcd_write_register_(TX_MODE_REG, 0x00);
pcd_write_register_(RX_MODE_REG, 0x00);
// Reset ModWidthReg
pcd_write_register_(MOD_WIDTH_REG, 0x26);
// When communicating with a PICC we need a timeout if something goes wrong.
// f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo].
// TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg.
pcd_write_register_(T_MODE_REG, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all
// communication modes at all speeds
// TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs.
pcd_write_register_(T_PRESCALER_REG, 0xA9);
pcd_write_register_(T_RELOAD_REG_H, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout.
pcd_write_register_(T_RELOAD_REG_L, 0xE8);
// Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting
pcd_write_register_(TX_ASK_REG, 0x40);
pcd_write_register_(MODE_REG, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC
// command to 0x6363 (ISO 14443-3 part 6.2.4)
pcd_antenna_on_(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset)
initialize_pending_ = false;
}
void RC522::dump_config() {
ESP_LOGCONFIG(TAG, "RC522:");
switch (this->error_code_) {
case NONE:
break;
case RESET_FAILED:
ESP_LOGE(TAG, "Reset command failed!");
break;
}
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" RESET Pin: ", this->reset_pin_);
LOG_UPDATE_INTERVAL(this);
for (auto *child : this->binary_sensors_) {
LOG_BINARY_SENSOR(" ", "Tag", child);
}
}
void RC522::loop() {
// First check reset is needed
if (reset_count_ > 0) {
pcd_reset_();
return;
}
if (initialize_pending_) {
initialize_();
return;
}
if (millis() - update_wait_ < this->update_interval_)
return;
auto status = picc_is_new_card_present_();
if (status == STATUS_ERROR) // No card
{
ESP_LOGE(TAG, "Error");
// mark_failed();
return;
}
if (status != STATUS_OK) // We can receive STATUS_TIMEOUT when no card, or unexpected status.
return;
// Try process card
if (!picc_read_card_serial_()) {
ESP_LOGW(TAG, "Requesting tag read failed!");
return;
};
if (uid_.size < 4) {
return;
ESP_LOGW(TAG, "Read serial size: %d", uid_.size);
}
update_wait_ = millis();
bool report = true;
// 1. Go through all triggers
for (auto *trigger : this->triggers_)
trigger->process(uid_.uiduint8_t, uid_.size);
// 2. Find a binary sensor
for (auto *tag : this->binary_sensors_) {
if (tag->process(uid_.uiduint8_t, uid_.size)) {
// 2.1 if found, do not dump
report = false;
}
}
if (report) {
char buf[32];
format_uid(buf, uid_.uiduint8_t, uid_.size);
ESP_LOGD(TAG, "Found new tag '%s'", buf);
}
}
void RC522::update() {
for (auto *obj : this->binary_sensors_)
obj->on_scan_end();
}
/**
* Performs a soft reset on the MFRC522 chip and waits for it to be ready again.
*/
void RC522::pcd_reset_() {
// The datasheet does not mention how long the SoftRest command takes to complete.
// But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg)
// Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let
// us be generous: 50ms.
if (millis() - reset_timeout_ < 50)
return;
if (reset_count_ == RESET_COUNT) {
ESP_LOGV(TAG, "Soft reset...");
// Issue the SoftReset command.
pcd_write_register_(COMMAND_REG, PCD_SOFT_RESET);
}
// Expect the PowerDown bit in CommandReg to be cleared (max 3x50ms)
if ((pcd_read_register_(COMMAND_REG) & (1 << 4)) == 0) {
reset_count_ = 0;
ESP_LOGI(TAG, "Device online.");
// Wait for initialize
reset_timeout_ = millis();
return;
}
if (--reset_count_ == 0) {
ESP_LOGE(TAG, "Unable to reset RC522.");
mark_failed();
}
}
/**
* Turns the antenna on by enabling pins TX1 and TX2.
* After a reset these pins are disabled.
*/
void RC522::pcd_antenna_on_() {
uint8_t value = pcd_read_register_(TX_CONTROL_REG);
if ((value & 0x03) != 0x03) {
pcd_write_register_(TX_CONTROL_REG, value | 0x03);
}
}
/**
* Reads a uint8_t from the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
uint8_t RC522::pcd_read_register_(PcdRegister reg ///< The register to read from. One of the PCD_Register enums.
) {
uint8_t value;
enable();
transfer_byte(0x80 | reg);
value = read_byte();
disable();
ESP_LOGVV(TAG, "read_register_(%x) -> %x", reg, value);
return value;
}
/**
* Reads a number of uint8_ts from the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
void RC522::pcd_read_register_(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums.
uint8_t count, ///< The number of uint8_ts to read
uint8_t *values, ///< uint8_t array to store the values in.
uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated.
) {
std::string buf;
buf = "Rx";
char cstrb[20];
if (count == 0) {
return;
}
// Serial.print(F("Reading ")); Serial.print(count); Serial.println(F(" uint8_ts from register."));
uint8_t address = 0x80 | reg; // MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3.
uint8_t index = 0; // Index in values array.
enable();
count--; // One read is performed outside of the loop
write_byte(address); // Tell MFRC522 which address we want to read
if (rx_align) { // Only update bit positions rxAlign..7 in values[0]
// Create bit mask for bit positions rxAlign..7
uint8_t mask = 0xFF << rx_align;
// Read value and tell that we want to read the same address again.
uint8_t value = transfer_byte(address);
// Apply mask to both current value of values[0] and the new data in value.
values[0] = (values[0] & ~mask) | (value & mask);
index++;
sprintf(cstrb, " %x", values[0]);
buf.append(cstrb);
}
while (index < count) {
values[index] = transfer_byte(address); // Read value and tell that we want to read the same address again.
sprintf(cstrb, " %x", values[index]);
buf.append(cstrb);
index++;
}
values[index] = transfer_byte(0); // Read the final uint8_t. Send 0 to stop reading.
buf = buf + " ";
sprintf(cstrb, "%x", values[index]);
buf.append(cstrb);
ESP_LOGVV(TAG, "read_register_array_(%x, %d, , %d) -> %s", reg, count, rx_align, buf.c_str());
disable();
}
void RC522::pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums.
uint8_t value ///< The value to write.
) {
enable();
// MSB == 0 is for writing. LSB is not used in address. Datasheet section 8.1.2.3.
transfer_byte(reg);
transfer_byte(value);
disable();
}
/**
* Writes a number of uint8_ts to the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
void RC522::pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums.
uint8_t count, ///< The number of uint8_ts to write to the register
uint8_t *values ///< The values to write. uint8_t array.
) {
std::string buf;
buf = "Tx";
enable();
transfer_byte(reg);
char cstrb[20];
for (uint8_t index = 0; index < count; index++) {
transfer_byte(values[index]);
sprintf(cstrb, " %x", values[index]);
buf.append(cstrb);
}
disable();
ESP_LOGVV(TAG, "write_register_(%x, %d) -> %s", reg, count, buf.c_str());
}
/**
* Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or
* selection. 7 bit frame. Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT -
* probably due do bad antenna design.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::picc_request_a_(
uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in
uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK.
) {
return picc_reqa_or_wupa_(PICC_CMD_REQA, buffer_atqa, buffer_size);
}
/**
* Transmits REQA or WUPA commands.
* Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna
* design.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::picc_reqa_or_wupa_(
uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA
uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in
uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK.
) {
uint8_t valid_bits;
RC522::StatusCode status;
if (buffer_atqa == nullptr || *buffer_size < 2) { // The ATQA response is 2 uint8_ts long.
return STATUS_NO_ROOM;
}
pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared.
valid_bits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only)
// uint8_t. TxLastBits = BitFramingReg[2..0]
status = pcd_transceive_data_(&command, 1, buffer_atqa, buffer_size, &valid_bits);
if (status != STATUS_OK)
return status;
if (*buffer_size != 2 || valid_bits != 0) { // ATQA must be exactly 16 bits.
ESP_LOGVV(TAG, "picc_reqa_or_wupa_() -> STATUS_ERROR");
return STATUS_ERROR;
}
return STATUS_OK;
}
/**
* Sets the bits given in mask in register reg.
*/
void RC522::pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums.
uint8_t mask ///< The bits to set.
) {
uint8_t tmp = pcd_read_register_(reg);
pcd_write_register_(reg, tmp | mask); // set bit mask
}
/**
* Clears the bits given in mask from register reg.
*/
void RC522::pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums.
uint8_t mask ///< The bits to clear.
) {
uint8_t tmp = pcd_read_register_(reg);
pcd_write_register_(reg, tmp & (~mask)); // clear bit mask
}
/**
* Executes the Transceive command.
* CRC validation can only be done if backData and backLen are specified.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::pcd_transceive_data_(
uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO.
uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO.
uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command.
uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned.
uint8_t
*valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. Default nullptr.
uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0.
bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be
///< validated.
) {
uint8_t wait_i_rq = 0x30; // RxIRq and IdleIRq
auto ret = pcd_communicate_with_picc_(PCD_TRANSCEIVE, wait_i_rq, send_data, send_len, back_data, back_len, valid_bits,
rx_align, check_crc);
if (ret == STATUS_OK && *back_len == 5)
ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ) -> %d [%x, %x, %x, %x, %x]", send_len, ret, back_data[0],
back_data[1], back_data[2], back_data[3], back_data[4]);
else
ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ... ) -> %d", send_len, ret);
return ret;
}
/**
* Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO.
* CRC validation can only be done if backData and backLen are specified.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::pcd_communicate_with_picc_(
uint8_t command, ///< The command to execute. One of the PCD_Command enums.
uint8_t wait_i_rq, ///< The bits in the ComIrqReg register that signals successful completion of the command.
uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO.
uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO.
uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command.
uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned.
uint8_t *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits.
uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0.
bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be
///< validated.
) {
ESP_LOGVV(TAG, "pcd_communicate_with_picc_(%d, %d,... %d)", command, wait_i_rq, check_crc);
// Prepare values for BitFramingReg
uint8_t tx_last_bits = valid_bits ? *valid_bits : 0;
uint8_t bit_framing =
(rx_align << 4) + tx_last_bits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]
pcd_write_register_(COMMAND_REG, PCD_IDLE); // Stop any active command.
pcd_write_register_(COM_IRQ_REG, 0x7F); // Clear all seven interrupt request bits
pcd_write_register_(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization
pcd_write_register_(FIFO_DATA_REG, send_len, send_data); // Write sendData to the FIFO
pcd_write_register_(BIT_FRAMING_REG, bit_framing); // Bit adjustments
pcd_write_register_(COMMAND_REG, command); // Execute the command
if (command == PCD_TRANSCEIVE) {
pcd_set_register_bit_mask_(BIT_FRAMING_REG, 0x80); // StartSend=1, transmission of data starts
}
// Wait for the command to complete.
// In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops
// transmitting. Each iteration of the do-while-loop takes 17.86μs.
// TODO check/modify for other architectures than Arduino Uno 16bit
uint16_t i;
for (i = 2000; i > 0; i--) {
uint8_t n = pcd_read_register_(
COM_IRQ_REG); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq
if (n & wait_i_rq) { // One of the interrupts that signal success has been set.
break;
}
if (n & 0x01) { // Timer interrupt - nothing received in 25ms
return STATUS_TIMEOUT;
}
}
// 35.7ms and nothing happend. Communication with the MFRC522 might be down.
if (i == 0) {
return STATUS_TIMEOUT;
}
// Stop now if any errors except collisions were detected.
uint8_t error_reg_value = pcd_read_register_(
ERROR_REG); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr
if (error_reg_value & 0x13) { // BufferOvfl ParityErr ProtocolErr
return STATUS_ERROR;
}
uint8_t valid_bits_local = 0;
// If the caller wants data back, get it from the MFRC522.
if (back_data && back_len) {
uint8_t n = pcd_read_register_(FIFO_LEVEL_REG); // Number of uint8_ts in the FIFO
if (n > *back_len) {
return STATUS_NO_ROOM;
}
*back_len = n; // Number of uint8_ts returned
pcd_read_register_(FIFO_DATA_REG, n, back_data, rx_align); // Get received data from FIFO
valid_bits_local =
pcd_read_register_(CONTROL_REG) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last
// received uint8_t. If this value is 000b, the whole uint8_t is valid.
if (valid_bits) {
*valid_bits = valid_bits_local;
}
}
// Tell about collisions
if (error_reg_value & 0x08) { // CollErr
return STATUS_COLLISION;
}
// Perform CRC_A validation if requested.
if (back_data && back_len && check_crc) {
// In this case a MIFARE Classic NAK is not OK.
if (*back_len == 1 && valid_bits_local == 4) {
return STATUS_MIFARE_NACK;
}
// We need at least the CRC_A value and all 8 bits of the last uint8_t must be received.
if (*back_len < 2 || valid_bits_local != 0) {
return STATUS_CRC_WRONG;
}
// Verify CRC_A - do our own calculation and store the control in controlBuffer.
uint8_t control_buffer[2];
RC522::StatusCode status = pcd_calculate_crc_(&back_data[0], *back_len - 2, &control_buffer[0]);
if (status != STATUS_OK) {
return status;
}
if ((back_data[*back_len - 2] != control_buffer[0]) || (back_data[*back_len - 1] != control_buffer[1])) {
return STATUS_CRC_WRONG;
}
}
return STATUS_OK;
}
/**
* Use the CRC coprocessor in the MFRC522 to calculate a CRC_A.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::pcd_calculate_crc_(
uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation.
uint8_t length, ///< In: The number of uint8_ts to transfer.
uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first.
) {
ESP_LOGVV(TAG, "pcd_calculate_crc_(..., %d, ...)", length);
pcd_write_register_(COMMAND_REG, PCD_IDLE); // Stop any active command.
pcd_write_register_(DIV_IRQ_REG, 0x04); // Clear the CRCIRq interrupt request bit
pcd_write_register_(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization
pcd_write_register_(FIFO_DATA_REG, length, data); // Write data to the FIFO
pcd_write_register_(COMMAND_REG, PCD_CALC_CRC); // Start the calculation
// Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73μs.
// TODO check/modify for other architectures than Arduino Uno 16bit
// Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us.
for (uint16_t i = 5000; i > 0; i--) {
// DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved
uint8_t n = pcd_read_register_(DIV_IRQ_REG);
if (n & 0x04) { // CRCIRq bit set - calculation done
pcd_write_register_(COMMAND_REG, PCD_IDLE); // Stop calculating CRC for new content in the FIFO.
// Transfer the result from the registers to the result buffer
result[0] = pcd_read_register_(CRC_RESULT_REG_L);
result[1] = pcd_read_register_(CRC_RESULT_REG_H);
ESP_LOGVV(TAG, "pcd_calculate_crc_() STATUS_OK");
return STATUS_OK;
}
}
ESP_LOGVV(TAG, "pcd_calculate_crc_() TIMEOUT");
// 89ms passed and nothing happend. Communication with the MFRC522 might be down.
return STATUS_TIMEOUT;
}
/**
* Returns STATUS_OK if a PICC responds to PICC_CMD_REQA.
* Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::picc_is_new_card_present_() {
uint8_t buffer_atqa[2];
uint8_t buffer_size = sizeof(buffer_atqa);
// Reset baud rates
pcd_write_register_(TX_MODE_REG, 0x00);
pcd_write_register_(RX_MODE_REG, 0x00);
// Reset ModWidthReg
pcd_write_register_(MOD_WIDTH_REG, 0x26);
auto result = picc_request_a_(buffer_atqa, &buffer_size);
ESP_LOGV(TAG, "picc_is_new_card_present_() -> %d", result);
return result;
}
/**
* Simple wrapper around PICC_Select.
* Returns true if a UID could be read.
* Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first.
* The read UID is available in the class variable uid.
*
* @return bool
*/
bool RC522::picc_read_card_serial_() {
RC522::StatusCode result = picc_select_(&this->uid_);
ESP_LOGVV(TAG, "picc_select_(...) -> %d", result);
return (result == STATUS_OK);
}
/**
* Transmits SELECT/ANTICOLLISION commands to select a single PICC.
* Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or
* PICC_WakeupA(). On success:
* - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the
* ISO/IEC 14443-3 draft.)
* - The UID size and value of the chosen PICC is returned in *uid along with the SAK.
*
* A PICC UID consists of 4, 7 or 10 uint8_ts.
* Only 4 uint8_ts can be specified in a SELECT command, so for the longer UIDs two or three iterations are used:
* UID size Number of UID uint8_ts Cascade levels Example of PICC
* ======== =================== ============== ===============
* single 4 1 MIFARE Classic
* double 7 2 MIFARE Ultralight
* triple 10 3 Not currently in use?
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::picc_select_(
Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID.
uint8_t valid_bits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply
///< uid->size.
) {
bool uid_complete;
bool select_done;
bool use_cascade_tag;
uint8_t cascade_level = 1;
RC522::StatusCode result;
uint8_t count;
uint8_t check_bit;
uint8_t index;
uint8_t uid_index; // The first index in uid->uiduint8_t[] that is used in the current Cascade Level.
int8_t current_level_known_bits; // The number of known UID bits in the current Cascade Level.
uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 uint8_t standard frame + 2 uint8_ts CRC_A
uint8_t buffer_used; // The number of uint8_ts used in the buffer, ie the number of uint8_ts to transfer to the FIFO.
uint8_t rx_align; // Used in BitFramingReg. Defines the bit position for the first bit received.
uint8_t tx_last_bits; // Used in BitFramingReg. The number of valid bits in the last transmitted uint8_t.
uint8_t *response_buffer;
uint8_t response_length;
// Description of buffer structure:
// uint8_t 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3
// uint8_t 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete
// uint8_ts,
// Low nibble: Extra bits. uint8_t 2: UID-data or CT See explanation below. CT means Cascade Tag. uint8_t
// 3: UID-data uint8_t 4: UID-data uint8_t 5: UID-data uint8_t 6: BCC Block Check Character - XOR of
// uint8_ts 2-5 uint8_t 7: CRC_A uint8_t 8: CRC_A The BCC and CRC_A are only transmitted if we know all the UID bits
// of the current Cascade Level.
//
// Description of uint8_ts 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels)
// UID size Cascade level uint8_t2 uint8_t3 uint8_t4 uint8_t5
// ======== ============= ===== ===== ===== =====
// 4 uint8_ts 1 uid0 uid1 uid2 uid3
// 7 uint8_ts 1 CT uid0 uid1 uid2
// 2 uid3 uid4 uid5 uid6
// 10 uint8_ts 1 CT uid0 uid1 uid2
// 2 CT uid3 uid4 uid5
// 3 uid6 uid7 uid8 uid9
// Sanity checks
if (valid_bits > 80) {
return STATUS_INVALID;
}
ESP_LOGVV(TAG, "picc_select_(&, %d)", valid_bits);
// Prepare MFRC522
pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared.
// Repeat Cascade Level loop until we have a complete UID.
uid_complete = false;
while (!uid_complete) {
// Set the Cascade Level in the SEL uint8_t, find out if we need to use the Cascade Tag in uint8_t 2.
switch (cascade_level) {
case 1:
buffer[0] = PICC_CMD_SEL_CL1;
uid_index = 0;
use_cascade_tag = valid_bits && uid->size > 4; // When we know that the UID has more than 4 uint8_ts
break;
case 2:
buffer[0] = PICC_CMD_SEL_CL2;
uid_index = 3;
use_cascade_tag = valid_bits && uid->size > 7; // When we know that the UID has more than 7 uint8_ts
break;
case 3:
buffer[0] = PICC_CMD_SEL_CL3;
uid_index = 6;
use_cascade_tag = false; // Never used in CL3.
break;
default:
return STATUS_INTERNAL_ERROR;
break;
}
// How many UID bits are known in this Cascade Level?
current_level_known_bits = valid_bits - (8 * uid_index);
if (current_level_known_bits < 0) {
current_level_known_bits = 0;
}
// Copy the known bits from uid->uiduint8_t[] to buffer[]
index = 2; // destination index in buffer[]
if (use_cascade_tag) {
buffer[index++] = PICC_CMD_CT;
}
uint8_t uint8_ts_to_copy = current_level_known_bits / 8 +
(current_level_known_bits % 8
? 1
: 0); // The number of uint8_ts needed to represent the known bits for this level.
if (uint8_ts_to_copy) {
uint8_t maxuint8_ts =
use_cascade_tag ? 3 : 4; // Max 4 uint8_ts in each Cascade Level. Only 3 left if we use the Cascade Tag
if (uint8_ts_to_copy > maxuint8_ts) {
uint8_ts_to_copy = maxuint8_ts;
}
for (count = 0; count < uint8_ts_to_copy; count++) {
buffer[index++] = uid->uiduint8_t[uid_index + count];
}
}
// Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits
if (use_cascade_tag) {
current_level_known_bits += 8;
}
// Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations.
select_done = false;
while (!select_done) {
// Find out how many bits and uint8_ts to send and receive.
if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT.
if (response_length < 4) {
ESP_LOGW(TAG, "Not enough data received.");
return STATUS_INVALID;
}
// Serial.print(F("SELECT: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC);
buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole uint8_ts
// Calculate BCC - Block Check Character
buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5];
// Calculate CRC_A
result = pcd_calculate_crc_(buffer, 7, &buffer[7]);
if (result != STATUS_OK) {
return result;
}
tx_last_bits = 0; // 0 => All 8 bits are valid.
buffer_used = 9;
// Store response in the last 3 uint8_ts of buffer (BCC and CRC_A - not needed after tx)
response_buffer = &buffer[6];
response_length = 3;
} else { // This is an ANTICOLLISION.
// Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC);
tx_last_bits = current_level_known_bits % 8;
count = current_level_known_bits / 8; // Number of whole uint8_ts in the UID part.
index = 2 + count; // Number of whole uint8_ts: SEL + NVB + UIDs
buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid Bits
buffer_used = index + (tx_last_bits ? 1 : 0);
// Store response in the unused part of buffer
response_buffer = &buffer[index];
response_length = sizeof(buffer) - index;
}
// Set bit adjustments
rx_align = tx_last_bits; // Having a separate variable is overkill. But it makes the next line easier to read.
pcd_write_register_(
BIT_FRAMING_REG,
(rx_align << 4) + tx_last_bits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]
// Transmit the buffer and receive the response.
result = pcd_transceive_data_(buffer, buffer_used, response_buffer, &response_length, &tx_last_bits, rx_align);
if (result == STATUS_COLLISION) { // More than one PICC in the field => collision.
uint8_t value_of_coll_reg = pcd_read_register_(
COLL_REG); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0]
if (value_of_coll_reg & 0x20) { // CollPosNotValid
return STATUS_COLLISION; // Without a valid collision position we cannot continue
}
uint8_t collision_pos = value_of_coll_reg & 0x1F; // Values 0-31, 0 means bit 32.
if (collision_pos == 0) {
collision_pos = 32;
}
if (collision_pos <= current_level_known_bits) { // No progress - should not happen
return STATUS_INTERNAL_ERROR;
}
// Choose the PICC with the bit set.
current_level_known_bits = collision_pos;
count = current_level_known_bits % 8; // The bit to modify
check_bit = (current_level_known_bits - 1) % 8;
index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); // First uint8_t is index 0.
if (response_length > 2) // Note: Otherwise buffer[index] might be not initialized
buffer[index] |= (1 << check_bit);
} else if (result != STATUS_OK) {
return result;
} else { // STATUS_OK
if (current_level_known_bits >= 32) { // This was a SELECT.
select_done = true; // No more anticollision
// We continue below outside the while.
} else { // This was an ANTICOLLISION.
// We now have all 32 bits of the UID in this Cascade Level
current_level_known_bits = 32;
// Run loop again to do the SELECT.
}
}
} // End of while (!selectDone)
// We do not check the CBB - it was constructed by us above.
// Copy the found UID uint8_ts from buffer[] to uid->uiduint8_t[]
index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[]
uint8_ts_to_copy = (buffer[2] == PICC_CMD_CT) ? 3 : 4;
for (count = 0; count < uint8_ts_to_copy; count++) {
uid->uiduint8_t[uid_index + count] = buffer[index++];
}
// Check response SAK (Select Acknowledge)
if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 uint8_t + CRC_A).
return STATUS_ERROR;
}
// Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those uint8_ts are not needed
// anymore.
result = pcd_calculate_crc_(response_buffer, 1, &buffer[2]);
if (result != STATUS_OK) {
return result;
}
if ((buffer[2] != response_buffer[1]) || (buffer[3] != response_buffer[2])) {
return STATUS_CRC_WRONG;
}
if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes
cascade_level++;
} else {
uid_complete = true;
uid->sak = response_buffer[0];
}
} // End of while (!uidComplete)
// Set correct uid->size
uid->size = 3 * cascade_level + 1;
return STATUS_OK;
}
bool RC522BinarySensor::process(const uint8_t *data, uint8_t len) {
if (len != this->uid_.size())
return false;
for (uint8_t i = 0; i < len; i++) {
if (data[i] != this->uid_[i])
return false;
}
this->publish_state(true);
this->found_ = true;
return true;
}
void RC522Trigger::process(const uint8_t *uid, uint8_t uid_length) {
char buf[32];
format_uid(buf, uid, uid_length);
this->trigger(std::string(buf));
}
} // namespace rc522_spi
} // namespace esphome

View File

@ -0,0 +1,301 @@
/**
* Library based on https://github.com/miguelbalboa/rfid
* and adapted to ESPHome by @glmnet
*
* original authors Dr.Leong, Miguel Balboa, Søren Thing Andersen, Tom Clement, many more! See GitLog.
*
*
*/
#pragma once
#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 rc522_spi {
class RC522BinarySensor;
class RC522Trigger;
class RC522 : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_4MHZ> {
public:
void setup() override;
void dump_config() override;
void update() override;
float get_setup_priority() const override { return setup_priority::DATA; };
void loop() override;
void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
void register_trigger(RC522Trigger *trig) { this->triggers_.push_back(trig); }
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
protected:
enum PcdRegister : uint8_t {
// Page 0: Command and status
// 0x00 // reserved for future use
COMMAND_REG = 0x01 << 1, // starts and stops command execution
COM_I_EN_REG = 0x02 << 1, // enable and disable interrupt request control bits
DIV_I_EN_REG = 0x03 << 1, // enable and disable interrupt request control bits
COM_IRQ_REG = 0x04 << 1, // interrupt request bits
DIV_IRQ_REG = 0x05 << 1, // interrupt request bits
ERROR_REG = 0x06 << 1, // error bits showing the error status of the last command executed
STATUS1_REG = 0x07 << 1, // communication status bits
STATUS2_REG = 0x08 << 1, // receiver and transmitter status bits
FIFO_DATA_REG = 0x09 << 1, // input and output of 64 uint8_t FIFO buffer
FIFO_LEVEL_REG = 0x0A << 1, // number of uint8_ts stored in the FIFO buffer
WATER_LEVEL_REG = 0x0B << 1, // level for FIFO underflow and overflow warning
CONTROL_REG = 0x0C << 1, // miscellaneous control registers
BIT_FRAMING_REG = 0x0D << 1, // adjustments for bit-oriented frames
COLL_REG = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface
// 0x0F // reserved for future use
// Page 1: Command
// 0x10 // reserved for future use
MODE_REG = 0x11 << 1, // defines general modes for transmitting and receiving
TX_MODE_REG = 0x12 << 1, // defines transmission data rate and framing
RX_MODE_REG = 0x13 << 1, // defines reception data rate and framing
TX_CONTROL_REG = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2
TX_ASK_REG = 0x15 << 1, // controls the setting of the transmission modulation
TX_SEL_REG = 0x16 << 1, // selects the internal sources for the antenna driver
RX_SEL_REG = 0x17 << 1, // selects internal receiver settings
RX_THRESHOLD_REG = 0x18 << 1, // selects thresholds for the bit decoder
DEMOD_REG = 0x19 << 1, // defines demodulator settings
// 0x1A // reserved for future use
// 0x1B // reserved for future use
MF_TX_REG = 0x1C << 1, // controls some MIFARE communication transmit parameters
MF_RX_REG = 0x1D << 1, // controls some MIFARE communication receive parameters
// 0x1E // reserved for future use
SERIAL_SPEED_REG = 0x1F << 1, // selects the speed of the serial UART interface
// Page 2: Configuration
// 0x20 // reserved for future use
CRC_RESULT_REG_H = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation
CRC_RESULT_REG_L = 0x22 << 1,
// 0x23 // reserved for future use
MOD_WIDTH_REG = 0x24 << 1, // controls the ModWidth setting?
// 0x25 // reserved for future use
RF_CFG_REG = 0x26 << 1, // configures the receiver gain
GS_N_REG = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation
CW_GS_P_REG = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation
MOD_GS_P_REG = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation
T_MODE_REG = 0x2A << 1, // defines settings for the internal timer
T_PRESCALER_REG = 0x2B << 1, // the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg.
T_RELOAD_REG_H = 0x2C << 1, // defines the 16-bit timer reload value
T_RELOAD_REG_L = 0x2D << 1,
T_COUNTER_VALUE_REG_H = 0x2E << 1, // shows the 16-bit timer value
T_COUNTER_VALUE_REG_L = 0x2F << 1,
// Page 3: Test Registers
// 0x30 // reserved for future use
TEST_SEL1_REG = 0x31 << 1, // general test signal configuration
TEST_SEL2_REG = 0x32 << 1, // general test signal configuration
TEST_PIN_EN_REG = 0x33 << 1, // enables pin output driver on pins D1 to D7
TEST_PIN_VALUE_REG = 0x34 << 1, // defines the values for D1 to D7 when it is used as an I/O bus
TEST_BUS_REG = 0x35 << 1, // shows the status of the internal test bus
AUTO_TEST_REG = 0x36 << 1, // controls the digital self-test
VERSION_REG = 0x37 << 1, // shows the software version
ANALOG_TEST_REG = 0x38 << 1, // controls the pins AUX1 and AUX2
TEST_DA_C1_REG = 0x39 << 1, // defines the test value for TestDAC1
TEST_DA_C2_REG = 0x3A << 1, // defines the test value for TestDAC2
TEST_ADC_REG = 0x3B << 1 // shows the value of ADC I and Q channels
// 0x3C // reserved for production tests
// 0x3D // reserved for production tests
// 0x3E // reserved for production tests
// 0x3F // reserved for production tests
};
// MFRC522 commands. Described in chapter 10 of the datasheet.
enum PcdCommand : uint8_t {
PCD_IDLE = 0x00, // no action, cancels current command execution
PCD_MEM = 0x01, // stores 25 uint8_ts into the internal buffer
PCD_GENERATE_RANDOM_ID = 0x02, // generates a 10-uint8_t random ID number
PCD_CALC_CRC = 0x03, // activates the CRC coprocessor or performs a self-test
PCD_TRANSMIT = 0x04, // transmits data from the FIFO buffer
PCD_NO_CMD_CHANGE = 0x07, // no command change, can be used to modify the CommandReg register bits without
// affecting the command, for example, the PowerDown bit
PCD_RECEIVE = 0x08, // activates the receiver circuits
PCD_TRANSCEIVE =
0x0C, // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission
PCD_MF_AUTHENT = 0x0E, // performs the MIFARE standard authentication as a reader
PCD_SOFT_RESET = 0x0F // resets the MFRC522
};
// Commands sent to the PICC.
enum PiccCommand : uint8_t {
// The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4)
PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for
// anticollision or selection. 7 bit frame.
PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and
// prepare for anticollision or selection. 7 bit frame.
PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision.
PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1
PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2
PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3
PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT.
PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset.
// The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9)
// Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on
// the sector.
// The read/write commands can also be used for MIFARE Ultralight.
PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A
PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B
PICC_CMD_MF_READ =
0x30, // Reads one 16 uint8_t block from the authenticated sector of the PICC. Also used for MIFARE Ultralight.
PICC_CMD_MF_WRITE = 0xA0, // Writes one 16 uint8_t block to the authenticated sector of the PICC. Called
// "COMPATIBILITY WRITE" for MIFARE Ultralight.
PICC_CMD_MF_DECREMENT =
0xC0, // Decrements the contents of a block and stores the result in the internal data register.
PICC_CMD_MF_INCREMENT =
0xC1, // Increments the contents of a block and stores the result in the internal data register.
PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the internal data register.
PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal data register to a block.
// The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6)
// The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight.
PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 uint8_t page to the PICC.
};
// Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more.
// last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered
enum StatusCode : uint8_t {
STATUS_OK, // Success
STATUS_ERROR, // Error in communication
STATUS_COLLISION, // Collission detected
STATUS_TIMEOUT, // Timeout in communication.
STATUS_NO_ROOM, // A buffer is not big enough.
STATUS_INTERNAL_ERROR, // Internal error in the code. Should not happen ;-)
STATUS_INVALID, // Invalid argument.
STATUS_CRC_WRONG, // The CRC_A does not match
STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK.
};
// A struct used for passing the UID of a PICC.
using Uid = struct {
uint8_t size; // Number of uint8_ts in the UID. 4, 7 or 10.
uint8_t uiduint8_t[10];
uint8_t sak; // The SAK (Select acknowledge) uint8_t returned from the PICC after successful selection.
};
Uid uid_;
uint32_t update_wait_{0};
void pcd_reset_();
void initialize_();
void pcd_antenna_on_();
uint8_t pcd_read_register_(PcdRegister reg ///< The register to read from. One of the PCD_Register enums.
);
/**
* Reads a number of uint8_ts from the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
void pcd_read_register_(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums.
uint8_t count, ///< The number of uint8_ts to read
uint8_t *values, ///< uint8_t array to store the values in.
uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated.
);
void pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums.
uint8_t value ///< The value to write.
);
/**
* Writes a number of uint8_ts to the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
void pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums.
uint8_t count, ///< The number of uint8_ts to write to the register
uint8_t *values ///< The values to write. uint8_t array.
);
StatusCode picc_request_a_(
uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in
uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK.
);
StatusCode picc_reqa_or_wupa_(
uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA
uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in
uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK.
);
void pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums.
uint8_t mask ///< The bits to set.
);
void pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums.
uint8_t mask ///< The bits to clear.
);
StatusCode pcd_transceive_data_(uint8_t *send_data, uint8_t send_len, uint8_t *back_data, uint8_t *back_len,
uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false);
StatusCode pcd_communicate_with_picc_(uint8_t command, uint8_t wait_i_rq, uint8_t *send_data, uint8_t send_len,
uint8_t *back_data = nullptr, uint8_t *back_len = nullptr,
uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false);
StatusCode pcd_calculate_crc_(
uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation.
uint8_t length, ///< In: The number of uint8_ts to transfer.
uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first.
);
RC522::StatusCode picc_is_new_card_present_();
bool picc_read_card_serial_();
StatusCode picc_select_(
Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID.
uint8_t valid_bits = 0 ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also
///< supply uid->size.
);
/** Read a data frame from the RC522 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> r_c522_read_data_();
GPIOPin *reset_pin_{nullptr};
uint8_t reset_count_{0};
uint32_t reset_timeout_{0};
bool initialize_pending_{false};
std::vector<RC522BinarySensor *> binary_sensors_;
std::vector<RC522Trigger *> triggers_;
enum RC522Error {
NONE = 0,
RESET_FAILED,
} error_code_{NONE};
};
class RC522BinarySensor : 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);
void on_scan_end() {
if (!this->found_) {
this->publish_state(false);
}
this->found_ = false;
}
protected:
std::vector<uint8_t> uid_;
bool found_{false};
};
class RC522Trigger : public Trigger<std::string> {
public:
void process(const uint8_t *uid, uint8_t uid_length);
};
#ifndef MFRC522_SPICLOCK
#define MFRC522_SPICLOCK SPI_CLOCK_DIV4 // MFRC522 accept upto 10MHz
#endif
} // namespace rc522_spi
} // namespace esphome

View File

@ -55,13 +55,13 @@ void RCSwitchBase::sync(RemoteTransmitData *dst) const {
}
void RCSwitchBase::transmit(RemoteTransmitData *dst, uint64_t code, uint8_t len) const {
dst->set_carrier_frequency(0);
this->sync(dst);
for (int16_t i = len - 1; i >= 0; i--) {
if (code & ((uint64_t) 1 << i))
this->one(dst);
else
this->zero(dst);
}
this->sync(dst);
}
bool RCSwitchBase::expect_one(RemoteReceiveData &src) const {

View File

@ -1,8 +1,18 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_CODE, CONF_LOW, CONF_SYNC, CONF_HIGH
from esphome.components import uart
from esphome.const import (
CONF_CODE,
CONF_HIGH,
CONF_ID,
CONF_LENGTH,
CONF_LOW,
CONF_PROTOCOL,
CONF_RAW,
CONF_SYNC,
CONF_TRIGGER_ID,
)
DEPENDENCIES = ['uart']
CODEOWNERS = ['@jesserockz']
@ -11,21 +21,39 @@ rf_bridge_ns = cg.esphome_ns.namespace('rf_bridge')
RFBridgeComponent = rf_bridge_ns.class_('RFBridgeComponent', cg.Component, uart.UARTDevice)
RFBridgeData = rf_bridge_ns.struct('RFBridgeData')
RFBridgeAdvancedData = rf_bridge_ns.struct('RFBridgeAdvancedData')
RFBridgeReceivedCodeTrigger = rf_bridge_ns.class_('RFBridgeReceivedCodeTrigger',
automation.Trigger.template(RFBridgeData))
RFBridgeReceivedAdvancedCodeTrigger = rf_bridge_ns.class_(
'RFBridgeReceivedAdvancedCodeTrigger',
automation.Trigger.template(RFBridgeAdvancedData),
)
RFBridgeSendCodeAction = rf_bridge_ns.class_('RFBridgeSendCodeAction', automation.Action)
RFBridgeSendAdvancedCodeAction = rf_bridge_ns.class_(
'RFBridgeSendAdvancedCodeAction', automation.Action)
RFBridgeLearnAction = rf_bridge_ns.class_('RFBridgeLearnAction', automation.Action)
RFBridgeStartAdvancedSniffingAction = rf_bridge_ns.class_(
'RFBridgeStartAdvancedSniffingAction', automation.Action)
RFBridgeStopAdvancedSniffingAction = rf_bridge_ns.class_(
'RFBridgeStopAdvancedSniffingAction', automation.Action)
RFBridgeSendRawAction = rf_bridge_ns.class_('RFBridgeSendRawAction', automation.Action)
CONF_ON_CODE_RECEIVED = 'on_code_received'
CONF_ON_ADVANCED_CODE_RECEIVED = 'on_advanced_code_received'
CONFIG_SCHEMA = cv.All(cv.Schema({
cv.GenerateID(): cv.declare_id(RFBridgeComponent),
cv.Optional(CONF_ON_CODE_RECEIVED): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RFBridgeReceivedCodeTrigger),
}),
cv.Optional(CONF_ON_ADVANCED_CODE_RECEIVED): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RFBridgeReceivedAdvancedCodeTrigger),
}),
}).extend(uart.UART_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA))
@ -38,6 +66,12 @@ def to_code(config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
yield automation.build_automation(trigger, [(RFBridgeData, 'data')], conf)
for conf in config.get(CONF_ON_ADVANCED_CODE_RECEIVED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
yield automation.build_automation(
trigger, [(RFBridgeAdvancedData, 'data')], conf
)
RFBRIDGE_SEND_CODE_SCHEMA = cv.Schema({
cv.GenerateID(): cv.use_id(RFBridgeComponent),
@ -64,13 +98,81 @@ def rf_bridge_send_code_to_code(config, action_id, template_args, args):
yield var
RFBRIDGE_LEARN_SCHEMA = cv.Schema({
RFBRIDGE_ID_SCHEMA = cv.Schema({
cv.GenerateID(): cv.use_id(RFBridgeComponent)
})
@automation.register_action('rf_bridge.learn', RFBridgeLearnAction, RFBRIDGE_LEARN_SCHEMA)
@automation.register_action('rf_bridge.learn', RFBridgeLearnAction, RFBRIDGE_ID_SCHEMA)
def rf_bridge_learnx_to_code(config, action_id, template_args, args):
paren = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_args, paren)
yield var
@automation.register_action(
'rf_bridge.start_advanced_sniffing',
RFBridgeStartAdvancedSniffingAction,
RFBRIDGE_ID_SCHEMA,
)
def rf_bridge_start_advanced_sniffing_to_code(config, action_id, template_args, args):
paren = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_args, paren)
yield var
@automation.register_action(
'rf_bridge.stop_advanced_sniffing',
RFBridgeStopAdvancedSniffingAction,
RFBRIDGE_ID_SCHEMA,
)
def rf_bridge_stop_advanced_sniffing_to_code(config, action_id, template_args, args):
paren = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_args, paren)
yield var
RFBRIDGE_SEND_ADVANCED_CODE_SCHEMA = cv.Schema({
cv.GenerateID(): cv.use_id(RFBridgeComponent),
cv.Required(CONF_LENGTH): cv.templatable(cv.hex_uint8_t),
cv.Required(CONF_PROTOCOL): cv.templatable(cv.hex_uint8_t),
cv.Required(CONF_CODE): cv.templatable(cv.string),
})
@automation.register_action(
'rf_bridge.send_advanced_code',
RFBridgeSendAdvancedCodeAction,
RFBRIDGE_SEND_ADVANCED_CODE_SCHEMA
)
def rf_bridge_send_advanced_code_to_code(config, action_id, template_args, args):
paren = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_args, paren)
template_ = yield cg.templatable(config[CONF_LENGTH], args, cg.uint16)
cg.add(var.set_length(template_))
template_ = yield cg.templatable(config[CONF_PROTOCOL], args, cg.uint16)
cg.add(var.set_protocol(template_))
template_ = yield cg.templatable(config[CONF_CODE], args, cg.std_string)
cg.add(var.set_code(template_))
yield var
RFBRIDGE_SEND_RAW_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(RFBridgeComponent),
cv.Required(CONF_RAW): cv.templatable(cv.string),
}
)
@automation.register_action(
'rf_bridge.send_raw',
RFBridgeSendRawAction,
RFBRIDGE_SEND_RAW_SCHEMA
)
def rf_bridge_send_raw_to_code(config, action_id, template_args, args):
paren = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_args, paren)
template_ = yield cg.templatable(config[CONF_RAW], args, cg.std_string)
cg.add(var.set_raw(template_))
yield var

View File

@ -20,13 +20,15 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
this->rx_buffer_.push_back(byte);
const uint8_t *raw = &this->rx_buffer_[0];
ESP_LOGVV(TAG, "Processing byte: 0x%02X", byte);
// Byte 0: Start
if (at == 0)
return byte == RF_CODE_START;
// Byte 1: Action
if (at == 1)
return byte >= RF_CODE_ACK && byte <= RF_CODE_RFOUT;
return byte >= RF_CODE_ACK && byte <= RF_CODE_RFIN_BUCKET;
uint8_t action = raw[1];
switch (action) {
@ -37,8 +39,8 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
ESP_LOGD(TAG, "Learning timeout");
break;
case RF_CODE_LEARN_OK:
case RF_CODE_RFIN:
if (at < RF_MESSAGE_SIZE + 2)
case RF_CODE_RFIN: {
if (byte != RF_CODE_STOP || at < RF_MESSAGE_SIZE + 2)
return true;
RFBridgeData data;
@ -52,8 +54,52 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
ESP_LOGD(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low,
data.high, data.code);
this->callback_.call(data);
this->data_callback_.call(data);
break;
}
case RF_CODE_LEARN_OK_NEW:
case RF_CODE_ADVANCED_RFIN: {
if (byte != RF_CODE_STOP) {
return at < (raw[2] + 3);
}
RFBridgeAdvancedData data{};
data.length = raw[2];
data.protocol = raw[3];
char next_byte[2];
for (uint8_t i = 0; i < data.length - 1; i++) {
sprintf(next_byte, "%02X", raw[4 + i]);
data.code += next_byte;
}
ESP_LOGD(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length,
data.protocol, data.code.c_str());
this->advanced_data_callback_.call(data);
break;
}
case RF_CODE_RFIN_BUCKET: {
if (byte != RF_CODE_STOP) {
return true;
}
uint8_t buckets = raw[2] << 1;
std::string str;
char next_byte[2];
for (uint32_t i = 0; i <= at; i++) {
sprintf(next_byte, "%02X", raw[i]);
str += next_byte;
if ((i > 3) && buckets) {
buckets--;
}
if ((i < 3) || (buckets % 2) || (i == at - 1)) {
str += " ";
}
}
ESP_LOGD(TAG, "Received RFBridge Bucket: %s", str.c_str());
break;
}
default:
ESP_LOGW(TAG, "Unknown action: 0x%02X", action);
break;
@ -68,6 +114,15 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
return false;
}
void RFBridgeComponent::write_byte_str_(std::string codes) {
uint8_t code;
int size = codes.length();
for (int i = 0; i < size; i += 2) {
code = strtol(codes.substr(i, 2).c_str(), nullptr, 16);
this->write(code);
}
}
void RFBridgeComponent::loop() {
const uint32_t now = millis();
if (now - this->last_bridge_byte_ > 50) {
@ -105,6 +160,18 @@ void RFBridgeComponent::send_code(RFBridgeData data) {
this->flush();
}
void RFBridgeComponent::send_advanced_code(RFBridgeAdvancedData data) {
ESP_LOGD(TAG, "Sending advanced code: length=0x%02X protocol=0x%02X code=0x%s", data.length, data.protocol,
data.code.c_str());
this->write(RF_CODE_START);
this->write(RF_CODE_RFOUT_NEW);
this->write(data.length & 0xFF);
this->write(data.protocol & 0xFF);
this->write_byte_str_(data.code);
this->write(RF_CODE_STOP);
this->flush();
}
void RFBridgeComponent::learn() {
ESP_LOGD(TAG, "Learning mode");
this->write(RF_CODE_START);
@ -118,5 +185,28 @@ void RFBridgeComponent::dump_config() {
this->check_uart_settings(19200);
}
void RFBridgeComponent::start_advanced_sniffing() {
ESP_LOGD(TAG, "Advanced Sniffing on");
this->write(RF_CODE_START);
this->write(RF_CODE_SNIFFING_ON);
this->write(RF_CODE_STOP);
this->flush();
}
void RFBridgeComponent::stop_advanced_sniffing() {
ESP_LOGD(TAG, "Advanced Sniffing off");
this->write(RF_CODE_START);
this->write(RF_CODE_SNIFFING_OFF);
this->write(RF_CODE_STOP);
this->flush();
}
void RFBridgeComponent::send_raw(std::string raw_code) {
ESP_LOGD(TAG, "Sending Raw Code: %s", raw_code.c_str());
this->write_byte_str_(raw_code);
this->flush();
}
} // namespace rf_bridge
} // namespace esphome

View File

@ -15,6 +15,7 @@ static const uint8_t RF_CODE_LEARN_KO = 0xA2;
static const uint8_t RF_CODE_LEARN_OK = 0xA3;
static const uint8_t RF_CODE_RFIN = 0xA4;
static const uint8_t RF_CODE_RFOUT = 0xA5;
static const uint8_t RF_CODE_ADVANCED_RFIN = 0xA6;
static const uint8_t RF_CODE_SNIFFING_ON = 0xA6;
static const uint8_t RF_CODE_SNIFFING_OFF = 0xA7;
static const uint8_t RF_CODE_RFOUT_NEW = 0xA8;
@ -22,6 +23,7 @@ static const uint8_t RF_CODE_LEARN_NEW = 0xA9;
static const uint8_t RF_CODE_LEARN_KO_NEW = 0xAA;
static const uint8_t RF_CODE_LEARN_OK_NEW = 0xAB;
static const uint8_t RF_CODE_RFOUT_BUCKET = 0xB0;
static const uint8_t RF_CODE_RFIN_BUCKET = 0xB1;
static const uint8_t RF_CODE_STOP = 0x55;
static const uint8_t RF_DEBOUNCE = 200;
@ -32,25 +34,40 @@ struct RFBridgeData {
uint32_t code;
};
struct RFBridgeAdvancedData {
uint8_t length;
uint8_t protocol;
std::string code;
};
class RFBridgeComponent : public uart::UARTDevice, public Component {
public:
void loop() override;
void dump_config() override;
void add_on_code_received_callback(std::function<void(RFBridgeData)> callback) {
this->callback_.add(std::move(callback));
this->data_callback_.add(std::move(callback));
}
void add_on_advanced_code_received_callback(std::function<void(RFBridgeAdvancedData)> callback) {
this->advanced_data_callback_.add(std::move(callback));
}
void send_code(RFBridgeData data);
void send_advanced_code(RFBridgeAdvancedData data);
void learn();
void start_advanced_sniffing();
void stop_advanced_sniffing();
void send_raw(std::string code);
protected:
void ack_();
void decode_();
bool parse_bridge_byte_(uint8_t byte);
void write_byte_str_(std::string codes);
std::vector<uint8_t> rx_buffer_;
uint32_t last_bridge_byte_{0};
CallbackManager<void(RFBridgeData)> callback_;
CallbackManager<void(RFBridgeData)> data_callback_;
CallbackManager<void(RFBridgeAdvancedData)> advanced_data_callback_;
};
class RFBridgeReceivedCodeTrigger : public Trigger<RFBridgeData> {
@ -60,6 +77,13 @@ class RFBridgeReceivedCodeTrigger : public Trigger<RFBridgeData> {
}
};
class RFBridgeReceivedAdvancedCodeTrigger : public Trigger<RFBridgeAdvancedData> {
public:
explicit RFBridgeReceivedAdvancedCodeTrigger(RFBridgeComponent *parent) {
parent->add_on_advanced_code_received_callback([this](RFBridgeAdvancedData data) { this->trigger(data); });
}
};
template<typename... Ts> class RFBridgeSendCodeAction : public Action<Ts...> {
public:
RFBridgeSendCodeAction(RFBridgeComponent *parent) : parent_(parent) {}
@ -81,6 +105,25 @@ template<typename... Ts> class RFBridgeSendCodeAction : public Action<Ts...> {
RFBridgeComponent *parent_;
};
template<typename... Ts> class RFBridgeSendAdvancedCodeAction : public Action<Ts...> {
public:
RFBridgeSendAdvancedCodeAction(RFBridgeComponent *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(uint8_t, length)
TEMPLATABLE_VALUE(uint8_t, protocol)
TEMPLATABLE_VALUE(std::string, code)
void play(Ts... x) {
RFBridgeAdvancedData data{};
data.length = this->length_.value(x...);
data.protocol = this->protocol_.value(x...);
data.code = this->code_.value(x...);
this->parent_->send_advanced_code(data);
}
protected:
RFBridgeComponent *parent_;
};
template<typename... Ts> class RFBridgeLearnAction : public Action<Ts...> {
public:
RFBridgeLearnAction(RFBridgeComponent *parent) : parent_(parent) {}
@ -91,5 +134,36 @@ template<typename... Ts> class RFBridgeLearnAction : public Action<Ts...> {
RFBridgeComponent *parent_;
};
template<typename... Ts> class RFBridgeStartAdvancedSniffingAction : public Action<Ts...> {
public:
RFBridgeStartAdvancedSniffingAction(RFBridgeComponent *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->start_advanced_sniffing(); }
protected:
RFBridgeComponent *parent_;
};
template<typename... Ts> class RFBridgeStopAdvancedSniffingAction : public Action<Ts...> {
public:
RFBridgeStopAdvancedSniffingAction(RFBridgeComponent *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->stop_advanced_sniffing(); }
protected:
RFBridgeComponent *parent_;
};
template<typename... Ts> class RFBridgeSendRawAction : public Action<Ts...> {
public:
RFBridgeSendRawAction(RFBridgeComponent *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(std::string, raw)
void play(Ts... x) { this->parent_->send_raw(this->raw_.value(x...)); }
protected:
RFBridgeComponent *parent_;
};
} // namespace rf_bridge
} // namespace esphome

View File

@ -94,10 +94,12 @@ void ICACHE_RAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensor
if ((new_state & arg->resolution & STATE_HAS_INCREMENTED) != 0) {
if (arg->counter < arg->max_value)
arg->counter++;
arg->on_clockwise_callback_.call();
}
if ((new_state & arg->resolution & STATE_HAS_DECREMENTED) != 0) {
if (arg->counter > arg->min_value)
arg->counter--;
arg->on_anticlockwise_callback_.call();
}
arg->state = new_state;

View File

@ -27,6 +27,9 @@ struct RotaryEncoderSensorStore {
int32_t last_read{0};
uint8_t state{0};
CallbackManager<void()> on_clockwise_callback_;
CallbackManager<void()> on_anticlockwise_callback_;
static void gpio_intr(RotaryEncoderSensorStore *arg);
};
@ -62,6 +65,14 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component {
float get_setup_priority() const override;
void add_on_clockwise_callback(std::function<void()> callback) {
this->store_.on_clockwise_callback_.add(std::move(callback));
}
void add_on_anticlockwise_callback(std::function<void()> callback) {
this->store_.on_anticlockwise_callback_.add(std::move(callback));
}
protected:
GPIOPin *pin_a_;
GPIOPin *pin_b_;
@ -81,5 +92,19 @@ template<typename... Ts> class RotaryEncoderSetValueAction : public Action<Ts...
RotaryEncoderSensor *encoder_;
};
class RotaryEncoderClockwiseTrigger : public Trigger<> {
public:
explicit RotaryEncoderClockwiseTrigger(RotaryEncoderSensor *parent) {
parent->add_on_clockwise_callback([this]() { this->trigger(); });
}
};
class RotaryEncoderAnticlockwiseTrigger : public Trigger<> {
public:
explicit RotaryEncoderAnticlockwiseTrigger(RotaryEncoderSensor *parent) {
parent->add_on_anticlockwise_callback([this]() { this->trigger(); });
}
};
} // namespace rotary_encoder
} // namespace esphome

View File

@ -3,7 +3,7 @@ import esphome.config_validation as cv
from esphome import pins, automation
from esphome.components import sensor
from esphome.const import CONF_ID, CONF_RESOLUTION, CONF_MIN_VALUE, CONF_MAX_VALUE, UNIT_STEPS, \
ICON_ROTATE_RIGHT, CONF_VALUE, CONF_PIN_A, CONF_PIN_B
ICON_ROTATE_RIGHT, CONF_VALUE, CONF_PIN_A, CONF_PIN_B, CONF_TRIGGER_ID
rotary_encoder_ns = cg.esphome_ns.namespace('rotary_encoder')
RotaryEncoderResolution = rotary_encoder_ns.enum('RotaryEncoderResolution')
@ -14,11 +14,18 @@ RESOLUTIONS = {
}
CONF_PIN_RESET = 'pin_reset'
CONF_ON_CLOCKWISE = 'on_clockwise'
CONF_ON_ANTICLOCKWISE = 'on_anticlockwise'
RotaryEncoderSensor = rotary_encoder_ns.class_('RotaryEncoderSensor', sensor.Sensor, cg.Component)
RotaryEncoderSetValueAction = rotary_encoder_ns.class_('RotaryEncoderSetValueAction',
automation.Action)
RotaryEncoderClockwiseTrigger = rotary_encoder_ns.class_('RotaryEncoderClockwiseTrigger',
automation.Trigger)
RotaryEncoderAnticlockwiseTrigger = rotary_encoder_ns.class_('RotaryEncoderAnticlockwiseTrigger',
automation.Trigger)
def validate_min_max_value(config):
if CONF_MIN_VALUE in config and CONF_MAX_VALUE in config:
@ -40,6 +47,12 @@ CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_STEPS, ICON_ROTATE_RIGHT, 0).ex
cv.Optional(CONF_RESOLUTION, default=1): cv.enum(RESOLUTIONS, int=True),
cv.Optional(CONF_MIN_VALUE): cv.int_,
cv.Optional(CONF_MAX_VALUE): cv.int_,
cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RotaryEncoderClockwiseTrigger),
}),
cv.Optional(CONF_ON_ANTICLOCKWISE): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RotaryEncoderAnticlockwiseTrigger),
}),
}).extend(cv.COMPONENT_SCHEMA), validate_min_max_value)
@ -61,6 +74,13 @@ def to_code(config):
if CONF_MAX_VALUE in config:
cg.add(var.set_max_value(config[CONF_MAX_VALUE]))
for conf in config.get(CONF_ON_CLOCKWISE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
yield automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ANTICLOCKWISE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
yield automation.build_automation(trigger, [], conf)
@automation.register_action('sensor.rotary_encoder.set_value', RotaryEncoderSetValueAction,
cv.Schema({

View File

@ -44,13 +44,21 @@ void SCD30Component::setup() {
uint16_t(raw_firmware_version[0] & 0xFF));
/// Sensor initialization
if (!this->write_command_(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, 0)) {
if (!this->write_command_(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) {
ESP_LOGE(TAG, "Sensor SCD30 error starting continuous measurements.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
return;
}
if (this->temperature_offset_ != 0) {
if (!this->write_command_(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) {
ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
return;
}
}
// The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on
if (this->altitude_compensation_ != 0xFFFF) {
if (!this->write_command_(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
@ -94,6 +102,8 @@ void SCD30Component::dump_config() {
ESP_LOGCONFIG(TAG, " Altitude compensation: %dm", this->altitude_compensation_);
}
ESP_LOGCONFIG(TAG, " Automatic self calibration: %s", ONOFF(this->enable_asc_));
ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %dmBar", this->ambient_pressure_compensation_);
ESP_LOGCONFIG(TAG, " Temperature offset: %.2f °C", this->temperature_offset_);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);

View File

@ -15,6 +15,10 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice {
void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
void set_automatic_self_calibration(bool asc) { enable_asc_ = asc; }
void set_altitude_compensation(uint16_t altitude) { altitude_compensation_ = altitude; }
void set_ambient_pressure_compensation(float pressure) {
ambient_pressure_compensation_ = (uint16_t)(pressure * 1000);
}
void set_temperature_offset(float offset) { temperature_offset_ = offset; }
void setup() override;
void update() override;
@ -35,6 +39,8 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice {
} error_code_{UNKNOWN};
bool enable_asc_{true};
uint16_t altitude_compensation_{0xFFFF};
uint16_t ambient_pressure_compensation_{0x0000};
float temperature_offset_{0.0};
sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};

View File

@ -13,6 +13,8 @@ SCD30Component = scd30_ns.class_('SCD30Component', cg.PollingComponent, i2c.I2CD
CONF_AUTOMATIC_SELF_CALIBRATION = 'automatic_self_calibration'
CONF_ALTITUDE_COMPENSATION = 'altitude_compensation'
CONF_AMBIENT_PRESSURE_COMPENSATION = 'ambient_pressure_compensation'
CONF_TEMPERATURE_OFFSET = 'temperature_offset'
def remove_altitude_suffix(value):
@ -29,6 +31,8 @@ CONFIG_SCHEMA = cv.Schema({
cv.Optional(CONF_ALTITUDE_COMPENSATION): cv.All(remove_altitude_suffix,
cv.int_range(min=0, max=0xFFFF,
max_included=False)),
cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION, default=0): cv.pressure,
cv.Optional(CONF_TEMPERATURE_OFFSET): cv.temperature,
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x61))
@ -41,6 +45,12 @@ def to_code(config):
if CONF_ALTITUDE_COMPENSATION in config:
cg.add(var.set_altitude_compensation(config[CONF_ALTITUDE_COMPENSATION]))
if CONF_AMBIENT_PRESSURE_COMPENSATION in config:
cg.add(var.set_ambient_pressure_compensation(config[CONF_AMBIENT_PRESSURE_COMPENSATION]))
if CONF_TEMPERATURE_OFFSET in config:
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
if CONF_CO2 in config:
sens = yield sensor.new_sensor(config[CONF_CO2])
cg.add(var.set_co2_sensor(sens))

View File

@ -4,13 +4,14 @@ from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components.output import FloatOutput
from esphome.const import CONF_ID, CONF_IDLE_LEVEL, CONF_MAX_LEVEL, CONF_MIN_LEVEL, CONF_OUTPUT, \
CONF_LEVEL, CONF_RESTORE
CONF_LEVEL, CONF_RESTORE, CONF_TRANSITION_LENGTH
servo_ns = cg.esphome_ns.namespace('servo')
Servo = servo_ns.class_('Servo', cg.Component)
ServoWriteAction = servo_ns.class_('ServoWriteAction', automation.Action)
ServoDetachAction = servo_ns.class_('ServoDetachAction', automation.Action)
CONF_AUTO_DETACH_TIME = 'auto_detach_time'
MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema({
cv.Required(CONF_ID): cv.declare_id(Servo),
@ -19,6 +20,8 @@ CONFIG_SCHEMA = cv.Schema({
cv.Optional(CONF_IDLE_LEVEL, default='7.5%'): cv.percentage,
cv.Optional(CONF_MAX_LEVEL, default='12%'): cv.percentage,
cv.Optional(CONF_RESTORE, default=False): cv.boolean,
cv.Optional(CONF_AUTO_DETACH_TIME, default='0s'): cv.positive_time_period_milliseconds,
cv.Optional(CONF_TRANSITION_LENGTH, default='0s'): cv.positive_time_period_milliseconds
}).extend(cv.COMPONENT_SCHEMA)
@ -32,6 +35,8 @@ def to_code(config):
cg.add(var.set_idle_level(config[CONF_IDLE_LEVEL]))
cg.add(var.set_max_level(config[CONF_MAX_LEVEL]))
cg.add(var.set_restore(config[CONF_RESTORE]))
cg.add(var.set_auto_detach_time(config[CONF_AUTO_DETACH_TIME]))
cg.add(var.set_transition_length(config[CONF_TRANSITION_LENGTH]))
@automation.register_action('servo.write', ServoWriteAction, cv.Schema({

View File

@ -13,6 +13,64 @@ void Servo::dump_config() {
ESP_LOGCONFIG(TAG, " Idle Level: %.1f%%", this->idle_level_ * 100.0f);
ESP_LOGCONFIG(TAG, " Min Level: %.1f%%", this->min_level_ * 100.0f);
ESP_LOGCONFIG(TAG, " Max Level: %.1f%%", this->max_level_ * 100.0f);
ESP_LOGCONFIG(TAG, " auto detach time: %d ms", this->auto_detach_time_);
ESP_LOGCONFIG(TAG, " run duration: %d ms", this->transition_length_);
}
void Servo::loop() {
// check if auto_detach_time_ is set and servo reached target
if (this->auto_detach_time_ && this->state_ == STATE_TARGET_REACHED) {
if (millis() - this->start_millis_ > this->auto_detach_time_) {
this->detach();
this->start_millis_ = 0;
this->state_ = STATE_DETACHED;
ESP_LOGD(TAG, "Servo detached on auto_detach_time");
}
}
if (this->target_value_ != this->current_value_ && this->state_ == STATE_ATTACHED) {
if (this->transition_length_) {
float new_value;
float travel_diff = this->target_value_ - this->source_value_;
uint32_t target_runtime = target_runtime = abs((int) ((travel_diff) * this->transition_length_ * 1.0f / 2.0f));
uint32_t current_runtime = millis() - this->start_millis_;
float percentage_run = current_runtime * 1.0f / target_runtime * 1.0f;
if (percentage_run > 1.0f) {
percentage_run = 1.0f;
}
new_value = this->target_value_ - (1.0f - percentage_run) * (this->target_value_ - this->source_value_);
this->internal_write(new_value);
} else {
this->internal_write(this->target_value_);
}
}
if (this->target_value_ == this->current_value_ && this->state_ == STATE_ATTACHED) {
this->state_ = STATE_TARGET_REACHED;
this->start_millis_ = millis(); // set current stamp for potential auto_detach_time_ check
ESP_LOGD(TAG, "Servo reached target");
}
}
void Servo::write(float value) {
value = clamp(value, -1.0f, 1.0f);
this->target_value_ = value;
this->source_value_ = this->current_value_;
this->state_ = STATE_ATTACHED;
this->start_millis_ = millis();
ESP_LOGD(TAG, "Servo new target: %f", value);
}
void Servo::internal_write(float value) {
value = clamp(value, -1.0f, 1.0f);
float level;
if (value < 0.0)
level = lerp(-value, this->idle_level_, this->min_level_);
else
level = lerp(value, this->idle_level_, this->max_level_);
this->output_->set_level(level);
if (this->target_value_ == this->current_value_) {
this->save_level_(level);
}
this->current_value_ = value;
}
} // namespace servo

View File

@ -14,18 +14,9 @@ extern uint32_t global_servo_id;
class Servo : public Component {
public:
void set_output(output::FloatOutput *output) { output_ = output; }
void write(float value) {
value = clamp(value, -1.0f, 1.0f);
float level;
if (value < 0.0)
level = lerp(-value, this->idle_level_, this->min_level_);
else
level = lerp(value, this->idle_level_, this->max_level_);
this->output_->set_level(level);
this->save_level_(level);
}
void loop() override;
void write(float value);
void internal_write(float value);
void detach() {
this->output_->set_level(0.0f);
this->save_level_(0.0f);
@ -48,6 +39,8 @@ class Servo : public Component {
void set_idle_level(float idle_level) { idle_level_ = idle_level; }
void set_max_level(float max_level) { max_level_ = max_level; }
void set_restore(bool restore) { restore_ = restore; }
void set_auto_detach_time(uint32_t auto_detach_time) { auto_detach_time_ = auto_detach_time; }
void set_transition_length(uint32_t transition_length) { transition_length_ = transition_length; }
protected:
void save_level_(float v) { this->rtc_.save(&v); }
@ -57,7 +50,19 @@ class Servo : public Component {
float idle_level_ = 0.0750f;
float max_level_ = 0.1200f;
bool restore_{false};
uint32_t auto_detach_time_ = 0;
uint32_t transition_length_ = 0;
ESPPreferenceObject rtc_;
uint8_t state_;
float target_value_ = 0;
float source_value_ = 0;
float current_value_ = 0;
uint32_t start_millis_ = 0;
enum State {
STATE_ATTACHED = 0,
STATE_DETACHED = 1,
STATE_TARGET_REACHED = 2,
};
};
template<typename... Ts> class ServoWriteAction : public Action<Ts...> {

View File

@ -34,8 +34,9 @@ def to_code(config):
cg.add(var.set_clock_pin(clock_pin))
latch_pin = yield cg.gpio_pin_expression(config[CONF_LATCH_PIN])
cg.add(var.set_latch_pin(latch_pin))
oe_pin = yield cg.gpio_pin_expression(config[CONF_OE_PIN])
cg.add(var.set_oe_pin(oe_pin))
if CONF_OE_PIN in config:
oe_pin = yield cg.gpio_pin_expression(config[CONF_OE_PIN])
cg.add(var.set_oe_pin(oe_pin))
cg.add(var.set_sr_count(config[CONF_SR_COUNT]))

View File

@ -55,6 +55,9 @@ enum SPIDataRate : uint32_t {
DATA_RATE_2MHZ = 2000000,
DATA_RATE_4MHZ = 4000000,
DATA_RATE_8MHZ = 8000000,
DATA_RATE_10MHZ = 10000000,
DATA_RATE_20MHZ = 20000000,
DATA_RATE_40MHZ = 40000000,
};
class SPIComponent : public Component {

View File

@ -0,0 +1,45 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import display
from esphome.const import CONF_BRIGHTNESS, CONF_EXTERNAL_VCC, CONF_LAMBDA, CONF_MODEL, \
CONF_RESET_PIN
from esphome.core import coroutine
CODEOWNERS = ['@kbx81']
ssd1322_base_ns = cg.esphome_ns.namespace('ssd1322_base')
SSD1322 = ssd1322_base_ns.class_('SSD1322', cg.PollingComponent, display.DisplayBuffer)
SSD1322Model = ssd1322_base_ns.enum('SSD1322Model')
MODELS = {
'SSD1322_256X64': SSD1322Model.SSD1322_MODEL_256_64,
}
SSD1322_MODEL = cv.enum(MODELS, upper=True, space="_")
SSD1322_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({
cv.Required(CONF_MODEL): SSD1322_MODEL,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
cv.Optional(CONF_EXTERNAL_VCC): cv.boolean,
}).extend(cv.polling_component_schema('1s'))
@coroutine
def setup_ssd1322(var, config):
yield cg.register_component(var, config)
yield display.register_display(var, config)
cg.add(var.set_model(config[CONF_MODEL]))
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_BRIGHTNESS in config:
cg.add(var.init_brightness(config[CONF_BRIGHTNESS]))
if CONF_EXTERNAL_VCC in config:
cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC]))
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_))

View File

@ -0,0 +1,204 @@
#include "ssd1322_base.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace ssd1322_base {
static const char *TAG = "ssd1322";
static const uint8_t SSD1322_MAX_CONTRAST = 255;
static const uint8_t SSD1322_COLORMASK = 0x0f;
static const uint8_t SSD1322_COLORSHIFT = 4;
static const uint8_t SSD1322_PIXELSPERBYTE = 2;
static const uint8_t SSD1322_ENABLEGRAYSCALETABLE = 0x00;
static const uint8_t SSD1322_SETCOLUMNADDRESS = 0x15;
static const uint8_t SSD1322_WRITERAM = 0x5C;
static const uint8_t SSD1322_READRAM = 0x5D;
static const uint8_t SSD1322_SETROWADDRESS = 0x75;
static const uint8_t SSD1322_SETREMAP = 0xA0;
static const uint8_t SSD1322_SETSTARTLINE = 0xA1;
static const uint8_t SSD1322_SETOFFSET = 0xA2;
static const uint8_t SSD1322_SETMODEALLOFF = 0xA4;
static const uint8_t SSD1322_SETMODEALLON = 0xA5;
static const uint8_t SSD1322_SETMODENORMAL = 0xA6;
static const uint8_t SSD1322_SETMODEINVERTED = 0xA7;
static const uint8_t SSD1322_ENABLEPARTIALDISPLAY = 0xA8;
static const uint8_t SSD1322_EXITPARTIALDISPLAY = 0xA9;
static const uint8_t SSD1322_SETFUNCTIONSELECTION = 0xAB;
static const uint8_t SSD1322_SETDISPLAYOFF = 0xAE;
static const uint8_t SSD1322_SETDISPLAYON = 0xAF;
static const uint8_t SSD1322_SETPHASELENGTH = 0xB1;
static const uint8_t SSD1322_SETFRONTCLOCKDIVIDER = 0xB3;
static const uint8_t SSD1322_DISPLAYENHANCEMENTA = 0xB4;
static const uint8_t SSD1322_SETGPIO = 0xB5;
static const uint8_t SSD1322_SETSECONDPRECHARGEPERIOD = 0xB6;
static const uint8_t SSD1322_SETGRAYSCALETABLE = 0xB8;
static const uint8_t SSD1322_SELECTDEFAULTLINEARGRAYSCALETABLE = 0xB9;
static const uint8_t SSD1322_SETPRECHARGEVOLTAGE = 0xBB;
static const uint8_t SSD1322_SETVCOMHVOLTAGE = 0xBE;
static const uint8_t SSD1322_SETCONTRAST = 0xC1;
static const uint8_t SSD1322_MASTERCURRENTCONTROL = 0xC7;
static const uint8_t SSD1322_SETMULTIPLEXRATIO = 0xCA;
static const uint8_t SSD1322_DISPLAYENHANCEMENTB = 0xD1;
static const uint8_t SSD1322_SETCOMMANDLOCK = 0xFD;
static const uint8_t SSD1322_SETCOMMANDLOCK_UNLOCK = 0x12;
static const uint8_t SSD1322_SETCOMMANDLOCK_LOCK = 0x16;
void SSD1322::setup() {
this->init_internal_(this->get_buffer_length_());
this->command(SSD1322_SETCOMMANDLOCK);
this->data(SSD1322_SETCOMMANDLOCK_UNLOCK);
this->turn_off();
this->command(SSD1322_SETFRONTCLOCKDIVIDER);
this->data(0x91);
this->command(SSD1322_SETMULTIPLEXRATIO);
this->data(0x3F);
this->command(SSD1322_SETOFFSET);
this->data(0x00);
this->command(SSD1322_SETSTARTLINE);
this->data(0x00);
this->command(SSD1322_SETREMAP);
this->data(0x14);
this->data(0x11);
this->command(SSD1322_SETGPIO);
this->data(0x00);
this->command(SSD1322_SETFUNCTIONSELECTION);
this->data(0x01);
this->command(SSD1322_DISPLAYENHANCEMENTA);
this->data(0xA0);
this->data(0xFD);
this->command(SSD1322_MASTERCURRENTCONTROL);
this->data(0x0F);
this->command(SSD1322_SETPHASELENGTH);
this->data(0xE2);
this->command(SSD1322_DISPLAYENHANCEMENTB);
this->data(0x82);
this->data(0x20);
this->command(SSD1322_SETPRECHARGEVOLTAGE);
this->data(0x1F);
this->command(SSD1322_SETSECONDPRECHARGEPERIOD);
this->data(0x08);
this->command(SSD1322_SETVCOMHVOLTAGE);
this->data(0x07);
this->command(SSD1322_SETMODENORMAL);
this->command(SSD1322_EXITPARTIALDISPLAY);
// this->command(SSD1322_SELECTDEFAULTLINEARGRAYSCALETABLE);
this->command(SSD1322_SETGRAYSCALETABLE);
// gamma ~2.2
this->data(24);
this->data(29);
this->data(36);
this->data(43);
this->data(51);
this->data(60);
this->data(70);
this->data(81);
this->data(93);
this->data(105);
this->data(118);
this->data(132);
this->data(147);
this->data(163);
this->data(180);
this->command(SSD1322_ENABLEGRAYSCALETABLE);
set_brightness(this->brightness_);
this->fill(COLOR_BLACK); // clear display - ensures we do not see garbage at power-on
this->display(); // ...write buffer, which actually clears the display's memory
this->turn_on(); // display ON
}
void SSD1322::display() {
this->command(SSD1322_SETCOLUMNADDRESS); // set column address
this->data(0x1C); // set column start address
this->data(0x5B); // set column end address
this->command(SSD1322_SETROWADDRESS); // set row address
this->data(0x00); // set row start address
this->data(0x3F); // set last row
this->command(SSD1322_WRITERAM); // write
this->write_display_data();
}
void SSD1322::update() {
this->do_update_();
this->display();
}
void SSD1322::set_brightness(float brightness) {
this->brightness_ = clamp(brightness, 0, 1);
// now write the new brightness level to the display
this->command(SSD1322_SETCONTRAST);
this->data(int(SSD1322_MAX_CONTRAST * (this->brightness_)));
}
bool SSD1322::is_on() { return this->is_on_; }
void SSD1322::turn_on() {
this->command(SSD1322_SETDISPLAYON);
this->is_on_ = true;
}
void SSD1322::turn_off() {
this->command(SSD1322_SETDISPLAYOFF);
this->is_on_ = false;
}
int SSD1322::get_height_internal() {
switch (this->model_) {
case SSD1322_MODEL_256_64:
return 64;
default:
return 0;
}
}
int SSD1322::get_width_internal() {
switch (this->model_) {
case SSD1322_MODEL_256_64:
return 256;
default:
return 0;
}
}
size_t SSD1322::get_buffer_length_() {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / SSD1322_PIXELSPERBYTE;
}
void HOT SSD1322::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;
uint32_t color4 = color.to_grayscale4();
// where should the bits go in the big buffer array? math...
uint16_t pos = (x / SSD1322_PIXELSPERBYTE) + (y * this->get_width_internal() / SSD1322_PIXELSPERBYTE);
uint8_t shift = (1u - (x % SSD1322_PIXELSPERBYTE)) * SSD1322_COLORSHIFT;
// ensure 'color4' is valid (only 4 bits aka 1 nibble) and shift the bits left when necessary
color4 = (color4 & SSD1322_COLORMASK) << shift;
// first mask off the nibble we must change...
this->buffer_[pos] &= (~SSD1322_COLORMASK >> shift);
// ...then lay the new nibble back on top. done!
this->buffer_[pos] |= color4;
}
void SSD1322::fill(Color color) {
const uint32_t color4 = color.to_grayscale4();
uint8_t fill = (color4 & SSD1322_COLORMASK) | ((color4 & SSD1322_COLORMASK) << SSD1322_COLORSHIFT);
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill;
}
void SSD1322::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
const char *SSD1322::model_str_() {
switch (this->model_) {
case SSD1322_MODEL_256_64:
return "SSD1322 256x64";
default:
return "Unknown";
}
}
} // namespace ssd1322_base
} // namespace esphome

View File

@ -0,0 +1,53 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/esphal.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace ssd1322_base {
enum SSD1322Model {
SSD1322_MODEL_256_64 = 0,
};
class SSD1322 : public PollingComponent, public display::DisplayBuffer {
public:
void setup() override;
void display();
void update() override;
void set_model(SSD1322Model model) { this->model_ = model; }
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void init_brightness(float brightness) { this->brightness_ = brightness; }
void set_brightness(float brightness);
bool is_on();
void turn_on();
void turn_off();
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
void fill(Color color) override;
protected:
virtual void command(uint8_t value) = 0;
virtual void data(uint8_t value) = 0;
virtual void write_display_data() = 0;
void init_reset_();
void draw_absolute_pixel_internal(int x, int y, Color color) override;
int get_height_internal() override;
int get_width_internal() override;
size_t get_buffer_length_();
const char *model_str_();
SSD1322Model model_{SSD1322_MODEL_256_64};
GPIOPin *reset_pin_{nullptr};
bool is_on_{false};
float brightness_{1.0};
};
} // namespace ssd1322_base
} // namespace esphome

View File

@ -0,0 +1,28 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi, ssd1322_base
from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES
CODEOWNERS = ['@kbx81']
AUTO_LOAD = ['ssd1322_base']
DEPENDENCIES = ['spi']
ssd1322_spi = cg.esphome_ns.namespace('ssd1322_spi')
SPISSD1322 = ssd1322_spi.class_('SPISSD1322', ssd1322_base.SSD1322, spi.SPIDevice)
CONFIG_SCHEMA = cv.All(ssd1322_base.SSD1322_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(SPISSD1322),
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=False)),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield ssd1322_base.setup_ssd1322(var, config)
yield spi.register_spi_device(var, config)
dc = yield cg.gpio_pin_expression(config[CONF_DC_PIN])
cg.add(var.set_dc_pin(dc))

View File

@ -0,0 +1,72 @@
#include "ssd1322_spi.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace ssd1322_spi {
static const char *TAG = "ssd1322_spi";
void SPISSD1322::setup() {
ESP_LOGCONFIG(TAG, "Setting up SPI SSD1322...");
this->spi_setup();
this->dc_pin_->setup(); // OUTPUT
if (this->cs_)
this->cs_->setup(); // OUTPUT
this->init_reset_();
delay(500); // NOLINT
SSD1322::setup();
}
void SPISSD1322::dump_config() {
LOG_DISPLAY("", "SPI SSD1322", this);
ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_());
if (this->cs_)
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_);
LOG_UPDATE_INTERVAL(this);
}
void SPISSD1322::command(uint8_t value) {
if (this->cs_)
this->cs_->digital_write(true);
this->dc_pin_->digital_write(false);
delay(1);
this->enable();
if (this->cs_)
this->cs_->digital_write(false);
this->write_byte(value);
if (this->cs_)
this->cs_->digital_write(true);
this->disable();
}
void SPISSD1322::data(uint8_t value) {
if (this->cs_)
this->cs_->digital_write(true);
this->dc_pin_->digital_write(true);
delay(1);
this->enable();
if (this->cs_)
this->cs_->digital_write(false);
this->write_byte(value);
if (this->cs_)
this->cs_->digital_write(true);
this->disable();
}
void HOT SPISSD1322::write_display_data() {
if (this->cs_)
this->cs_->digital_write(true);
this->dc_pin_->digital_write(true);
if (this->cs_)
this->cs_->digital_write(false);
delay(1);
this->enable();
this->write_array(this->buffer_, this->get_buffer_length_());
if (this->cs_)
this->cs_->digital_write(true);
this->disable();
}
} // namespace ssd1322_spi
} // namespace esphome

View File

@ -0,0 +1,30 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ssd1322_base/ssd1322_base.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace ssd1322_spi {
class SPISSD1322 : public ssd1322_base::SSD1322,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
spi::DATA_RATE_8MHZ> {
public:
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
void setup() override;
void dump_config() override;
protected:
void command(uint8_t value) override;
void data(uint8_t value) override;
void write_display_data() override;
GPIOPin *dc_pin_;
};
} // namespace ssd1322_spi
} // namespace esphome

View File

@ -6,6 +6,8 @@ from esphome.const import CONF_BRIGHTNESS, CONF_EXTERNAL_VCC, CONF_LAMBDA, CONF_
CONF_RESET_PIN
from esphome.core import coroutine
CODEOWNERS = ['@kbx81']
ssd1325_base_ns = cg.esphome_ns.namespace('ssd1325_base')
SSD1325 = ssd1325_base_ns.class_('SSD1325', cg.PollingComponent, display.DisplayBuffer)
SSD1325Model = ssd1325_base_ns.enum('SSD1325Model')

View File

@ -4,6 +4,8 @@ from esphome import pins
from esphome.components import spi, ssd1325_base
from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES
CODEOWNERS = ['@kbx81']
AUTO_LOAD = ['ssd1325_base']
DEPENDENCIES = ['spi']

View File

@ -0,0 +1,41 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import display
from esphome.const import CONF_BRIGHTNESS, CONF_LAMBDA, CONF_MODEL, CONF_RESET_PIN
from esphome.core import coroutine
CODEOWNERS = ['@kbx81']
ssd1327_base_ns = cg.esphome_ns.namespace('ssd1327_base')
SSD1327 = ssd1327_base_ns.class_('SSD1327', cg.PollingComponent, display.DisplayBuffer)
SSD1327Model = ssd1327_base_ns.enum('SSD1327Model')
MODELS = {
'SSD1327_128X128': SSD1327Model.SSD1327_MODEL_128_128,
}
SSD1327_MODEL = cv.enum(MODELS, upper=True, space="_")
SSD1327_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({
cv.Required(CONF_MODEL): SSD1327_MODEL,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
}).extend(cv.polling_component_schema('1s'))
@coroutine
def setup_ssd1327(var, config):
yield cg.register_component(var, config)
yield display.register_display(var, config)
cg.add(var.set_model(config[CONF_MODEL]))
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_BRIGHTNESS in config:
cg.add(var.init_brightness(config[CONF_BRIGHTNESS]))
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_))

View File

@ -0,0 +1,178 @@
#include "ssd1327_base.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace ssd1327_base {
static const char *TAG = "ssd1327";
static const uint8_t SSD1327_MAX_CONTRAST = 127;
static const uint8_t SSD1327_COLORMASK = 0x0f;
static const uint8_t SSD1327_COLORSHIFT = 4;
static const uint8_t SSD1327_PIXELSPERBYTE = 2;
static const uint8_t SSD1327_SETCOLUMNADDRESS = 0x15;
static const uint8_t SSD1327_SETROWADDRESS = 0x75;
static const uint8_t SSD1327_SETCONTRAST = 0x81;
static const uint8_t SSD1327_SETREMAP = 0xA0;
static const uint8_t SSD1327_SETSTARTLINE = 0xA1;
static const uint8_t SSD1327_SETOFFSET = 0xA2;
static const uint8_t SSD1327_NORMALDISPLAY = 0xA4;
static const uint8_t SSD1327_DISPLAYALLON = 0xA5;
static const uint8_t SSD1327_DISPLAYALLOFF = 0xA6;
static const uint8_t SSD1327_INVERTDISPLAY = 0xA7;
static const uint8_t SSD1327_SETMULTIPLEX = 0xA8;
static const uint8_t SSD1327_FUNCTIONSELECTIONA = 0xAB;
static const uint8_t SSD1327_DISPLAYOFF = 0xAE;
static const uint8_t SSD1327_DISPLAYON = 0xAF;
static const uint8_t SSD1327_SETPHASELENGTH = 0xB1;
static const uint8_t SSD1327_SETFRONTCLOCKDIVIDER = 0xB3;
static const uint8_t SSD1327_SETGPIO = 0xB5;
static const uint8_t SSD1327_SETSECONDPRECHARGEPERIOD = 0xB6;
static const uint8_t SSD1327_SETGRAYSCALETABLE = 0xB8;
static const uint8_t SSD1327_SELECTDEFAULTLINEARGRAYSCALETABLE = 0xB9;
static const uint8_t SSD1327_SETPRECHARGEVOLTAGE = 0xBC;
static const uint8_t SSD1327_SETVCOMHVOLTAGE = 0xBE;
static const uint8_t SSD1327_FUNCTIONSELECTIONB = 0xD5;
static const uint8_t SSD1327_SETCOMMANDLOCK = 0xFD;
static const uint8_t SSD1327_HORIZONTALSCROLLRIGHTSETUP = 0x26;
static const uint8_t SSD1327_HORIZONTALSCROLLLEFTSETUP = 0x27;
static const uint8_t SSD1327_DEACTIVATESCROLL = 0x2E;
static const uint8_t SSD1327_ACTIVATESCROLL = 0x2F;
void SSD1327::setup() {
this->init_internal_(this->get_buffer_length_());
this->turn_off(); // display OFF
this->command(SSD1327_SETFRONTCLOCKDIVIDER); // set osc division
this->command(0xF1); // 145
this->command(SSD1327_SETMULTIPLEX); // multiplex ratio
this->command(0x7f); // duty = height - 1
this->command(SSD1327_SETOFFSET); // set display offset
this->command(0x00); // 0
this->command(SSD1327_SETSTARTLINE); // set start line
this->command(0x00); // ...
this->command(SSD1327_SETREMAP); // set segment remapping
this->command(0x53); // COM bottom-up, split odd/even, enable column and nibble remapping
this->command(SSD1327_SETGRAYSCALETABLE);
// gamma ~2.2
this->command(0);
this->command(1);
this->command(2);
this->command(3);
this->command(6);
this->command(8);
this->command(12);
this->command(16);
this->command(20);
this->command(26);
this->command(32);
this->command(39);
this->command(46);
this->command(54);
this->command(63);
this->command(SSD1327_SETPHASELENGTH);
this->command(0x55);
this->command(SSD1327_SETVCOMHVOLTAGE); // Set High Voltage Level of COM Pin
this->command(0x1C);
this->command(SSD1327_NORMALDISPLAY); // set display mode
set_brightness(this->brightness_);
this->fill(COLOR_BLACK); // clear display - ensures we do not see garbage at power-on
this->display(); // ...write buffer, which actually clears the display's memory
this->turn_on(); // display ON
}
void SSD1327::display() {
this->command(SSD1327_SETCOLUMNADDRESS); // set column address
this->command(0x00); // set column start address
this->command(0x3F); // set column end address
this->command(SSD1327_SETROWADDRESS); // set row address
this->command(0x00); // set row start address
this->command(127); // set last row
this->write_display_data();
}
void SSD1327::update() {
if (!this->is_failed()) {
this->do_update_();
this->display();
}
}
void SSD1327::set_brightness(float brightness) {
// validation
this->brightness_ = clamp(brightness, 0, 1);
// now write the new brightness level to the display
this->command(SSD1327_SETCONTRAST);
this->command(int(SSD1327_MAX_CONTRAST * (this->brightness_)));
}
bool SSD1327::is_on() { return this->is_on_; }
void SSD1327::turn_on() {
this->command(SSD1327_DISPLAYON);
this->is_on_ = true;
}
void SSD1327::turn_off() {
this->command(SSD1327_DISPLAYOFF);
this->is_on_ = false;
}
int SSD1327::get_height_internal() {
switch (this->model_) {
case SSD1327_MODEL_128_128:
return 128;
default:
return 0;
}
}
int SSD1327::get_width_internal() {
switch (this->model_) {
case SSD1327_MODEL_128_128:
return 128;
default:
return 0;
}
}
size_t SSD1327::get_buffer_length_() {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / SSD1327_PIXELSPERBYTE;
}
void HOT SSD1327::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;
uint32_t color4 = color.to_grayscale4();
// where should the bits go in the big buffer array? math...
uint16_t pos = (x / SSD1327_PIXELSPERBYTE) + (y * this->get_width_internal() / SSD1327_PIXELSPERBYTE);
uint8_t shift = (x % SSD1327_PIXELSPERBYTE) * SSD1327_COLORSHIFT;
// ensure 'color4' is valid (only 4 bits aka 1 nibble) and shift the bits left when necessary
color4 = (color4 & SSD1327_COLORMASK) << shift;
// first mask off the nibble we must change...
this->buffer_[pos] &= (~SSD1327_COLORMASK >> shift);
// ...then lay the new nibble back on top. done!
this->buffer_[pos] |= color4;
}
void SSD1327::fill(Color color) {
const uint32_t color4 = color.to_grayscale4();
uint8_t fill = (color4 & SSD1327_COLORMASK) | ((color4 & SSD1327_COLORMASK) << SSD1327_COLORSHIFT);
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill;
}
void SSD1327::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
const char *SSD1327::model_str_() {
switch (this->model_) {
case SSD1327_MODEL_128_128:
return "SSD1327 128x128";
default:
return "Unknown";
}
}
} // namespace ssd1327_base
} // namespace esphome

View File

@ -0,0 +1,52 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/esphal.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace ssd1327_base {
enum SSD1327Model {
SSD1327_MODEL_128_128 = 0,
};
class SSD1327 : public PollingComponent, public display::DisplayBuffer {
public:
void setup() override;
void display();
void update() override;
void set_model(SSD1327Model model) { this->model_ = model; }
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void init_brightness(float brightness) { this->brightness_ = brightness; }
void set_brightness(float brightness);
bool is_on();
void turn_on();
void turn_off();
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
void fill(Color color) override;
protected:
virtual void command(uint8_t value) = 0;
virtual void write_display_data() = 0;
void init_reset_();
void draw_absolute_pixel_internal(int x, int y, Color color) override;
int get_height_internal() override;
int get_width_internal() override;
size_t get_buffer_length_();
const char *model_str_();
SSD1327Model model_{SSD1327_MODEL_128_128};
GPIOPin *reset_pin_{nullptr};
bool is_on_{false};
float brightness_{1.0};
};
} // namespace ssd1327_base
} // namespace esphome

View File

@ -0,0 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import ssd1327_base, i2c
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES
CODEOWNERS = ['@kbx81']
AUTO_LOAD = ['ssd1327_base']
DEPENDENCIES = ['i2c']
ssd1327_i2c = cg.esphome_ns.namespace('ssd1327_i2c')
I2CSSD1327 = ssd1327_i2c.class_('I2CSSD1327', ssd1327_base.SSD1327, i2c.I2CDevice)
CONFIG_SCHEMA = cv.All(ssd1327_base.SSD1327_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(I2CSSD1327),
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x3D)),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield ssd1327_base.setup_ssd1327(var, config)
yield i2c.register_i2c_device(var, config)

View File

@ -0,0 +1,44 @@
#include "ssd1327_i2c.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ssd1327_i2c {
static const char *TAG = "ssd1327_i2c";
void I2CSSD1327::setup() {
ESP_LOGCONFIG(TAG, "Setting up I2C SSD1327...");
this->init_reset_();
this->parent_->raw_begin_transmission(this->address_);
if (!this->parent_->raw_end_transmission(this->address_)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
SSD1327::setup();
}
void I2CSSD1327::dump_config() {
LOG_DISPLAY("", "I2C SSD1327", this);
LOG_I2C_DEVICE(this);
ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_());
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_UPDATE_INTERVAL(this);
if (this->error_code_ == COMMUNICATION_FAILED) {
ESP_LOGE(TAG, "Communication with SSD1327 failed!");
}
}
void I2CSSD1327::command(uint8_t value) { this->write_byte(0x00, value); }
void HOT I2CSSD1327::write_display_data() {
for (uint32_t i = 0; i < this->get_buffer_length_();) {
uint8_t data[16];
for (uint8_t &j : data)
j = this->buffer_[i++];
this->write_bytes(0x40, data, sizeof(data));
}
}
} // namespace ssd1327_i2c
} // namespace esphome

View File

@ -0,0 +1,23 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ssd1327_base/ssd1327_base.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace ssd1327_i2c {
class I2CSSD1327 : public ssd1327_base::SSD1327, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
protected:
void command(uint8_t value) override;
void write_display_data() override;
enum ErrorCode { NONE = 0, COMMUNICATION_FAILED } error_code_{NONE};
};
} // namespace ssd1327_i2c
} // namespace esphome

View File

@ -0,0 +1,28 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi, ssd1327_base
from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES
CODEOWNERS = ['@kbx81']
AUTO_LOAD = ['ssd1327_base']
DEPENDENCIES = ['spi']
ssd1327_spi = cg.esphome_ns.namespace('ssd1327_spi')
SPISSD1327 = ssd1327_spi.class_('SPISSD1327', ssd1327_base.SSD1327, spi.SPIDevice)
CONFIG_SCHEMA = cv.All(ssd1327_base.SSD1327_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(SPISSD1327),
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=False)),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield ssd1327_base.setup_ssd1327(var, config)
yield spi.register_spi_device(var, config)
dc = yield cg.gpio_pin_expression(config[CONF_DC_PIN])
cg.add(var.set_dc_pin(dc))

View File

@ -0,0 +1,59 @@
#include "ssd1327_spi.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace ssd1327_spi {
static const char *TAG = "ssd1327_spi";
void SPISSD1327::setup() {
ESP_LOGCONFIG(TAG, "Setting up SPI SSD1327...");
this->spi_setup();
this->dc_pin_->setup(); // OUTPUT
if (this->cs_)
this->cs_->setup(); // OUTPUT
this->init_reset_();
delay(500); // NOLINT
SSD1327::setup();
}
void SPISSD1327::dump_config() {
LOG_DISPLAY("", "SPI SSD1327", this);
ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_());
if (this->cs_)
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_);
LOG_UPDATE_INTERVAL(this);
}
void SPISSD1327::command(uint8_t value) {
if (this->cs_)
this->cs_->digital_write(true);
this->dc_pin_->digital_write(false);
delay(1);
this->enable();
if (this->cs_)
this->cs_->digital_write(false);
this->write_byte(value);
if (this->cs_)
this->cs_->digital_write(true);
this->disable();
}
void HOT SPISSD1327::write_display_data() {
if (this->cs_)
this->cs_->digital_write(true);
this->dc_pin_->digital_write(true);
if (this->cs_)
this->cs_->digital_write(false);
delay(1);
this->enable();
this->write_array(this->buffer_, this->get_buffer_length_());
if (this->cs_)
this->cs_->digital_write(true);
this->disable();
}
} // namespace ssd1327_spi
} // namespace esphome

View File

@ -0,0 +1,29 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ssd1327_base/ssd1327_base.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace ssd1327_spi {
class SPISSD1327 : public ssd1327_base::SSD1327,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
spi::DATA_RATE_8MHZ> {
public:
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
void setup() override;
void dump_config() override;
protected:
void command(uint8_t value) override;
void write_display_data() override;
GPIOPin *dc_pin_;
};
} // namespace ssd1327_spi
} // namespace esphome

View File

@ -0,0 +1,32 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import display
from esphome.const import CONF_BRIGHTNESS, CONF_LAMBDA, CONF_RESET_PIN
from esphome.core import coroutine
CODEOWNERS = ['@kbx81']
ssd1331_base_ns = cg.esphome_ns.namespace('ssd1331_base')
SSD1331 = ssd1331_base_ns.class_('SSD1331', cg.PollingComponent, display.DisplayBuffer)
SSD1331_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
}).extend(cv.polling_component_schema('1s'))
@coroutine
def setup_ssd1331(var, config):
yield cg.register_component(var, config)
yield display.register_display(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))
if CONF_BRIGHTNESS in config:
cg.add(var.init_brightness(config[CONF_BRIGHTNESS]))
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_))

View File

@ -0,0 +1,155 @@
#include "ssd1331_base.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace ssd1331_base {
static const char *TAG = "ssd1331";
static const uint16_t BLACK = 0;
static const uint16_t WHITE = 0xffff;
static const uint16_t SSD1331_COLORMASK = 0xffff;
static const uint8_t SSD1331_MAX_CONTRASTA = 0x91;
static const uint8_t SSD1331_MAX_CONTRASTB = 0x50;
static const uint8_t SSD1331_MAX_CONTRASTC = 0x7D;
static const uint8_t SSD1331_BYTESPERPIXEL = 2;
// SSD1331 Commands
static const uint8_t SSD1331_DRAWLINE = 0x21; // Draw line
static const uint8_t SSD1331_DRAWRECT = 0x22; // Draw rectangle
static const uint8_t SSD1331_FILL = 0x26; // Fill enable/disable
static const uint8_t SSD1331_SETCOLUMN = 0x15; // Set column address
static const uint8_t SSD1331_SETROW = 0x75; // Set row adress
static const uint8_t SSD1331_CONTRASTA = 0x81; // Set contrast for color A
static const uint8_t SSD1331_CONTRASTB = 0x82; // Set contrast for color B
static const uint8_t SSD1331_CONTRASTC = 0x83; // Set contrast for color C
static const uint8_t SSD1331_MASTERCURRENT = 0x87; // Master current control
static const uint8_t SSD1331_SETREMAP = 0xA0; // Set re-map & data format
static const uint8_t SSD1331_STARTLINE = 0xA1; // Set display start line
static const uint8_t SSD1331_DISPLAYOFFSET = 0xA2; // Set display offset
static const uint8_t SSD1331_NORMALDISPLAY = 0xA4; // Set display to normal mode
static const uint8_t SSD1331_DISPLAYALLON = 0xA5; // Set entire display ON
static const uint8_t SSD1331_DISPLAYALLOFF = 0xA6; // Set entire display OFF
static const uint8_t SSD1331_INVERTDISPLAY = 0xA7; // Invert display
static const uint8_t SSD1331_SETMULTIPLEX = 0xA8; // Set multiplex ratio
static const uint8_t SSD1331_SETMASTER = 0xAD; // Set master configuration
static const uint8_t SSD1331_DISPLAYOFF = 0xAE; // Display OFF (sleep mode)
static const uint8_t SSD1331_DISPLAYON = 0xAF; // Normal Brightness Display ON
static const uint8_t SSD1331_POWERMODE = 0xB0; // Power save mode
static const uint8_t SSD1331_PRECHARGE = 0xB1; // Phase 1 and 2 period adjustment
static const uint8_t SSD1331_CLOCKDIV = 0xB3; // Set display clock divide ratio/oscillator frequency
static const uint8_t SSD1331_PRECHARGEA = 0x8A; // Set second pre-charge speed for color A
static const uint8_t SSD1331_PRECHARGEB = 0x8B; // Set second pre-charge speed for color B
static const uint8_t SSD1331_PRECHARGEC = 0x8C; // Set second pre-charge speed for color C
static const uint8_t SSD1331_PRECHARGELEVEL = 0xBB; // Set pre-charge voltage
static const uint8_t SSD1331_VCOMH = 0xBE; // Set Vcomh voltge
void SSD1331::setup() {
this->init_internal_(this->get_buffer_length_());
this->command(SSD1331_DISPLAYOFF); // 0xAE
this->command(SSD1331_SETREMAP); // 0xA0
this->command(0x72); // RGB Color
this->command(SSD1331_STARTLINE); // 0xA1
this->command(0x0);
this->command(SSD1331_DISPLAYOFFSET); // 0xA2
this->command(0x0);
this->command(SSD1331_NORMALDISPLAY); // 0xA4
this->command(SSD1331_SETMULTIPLEX); // 0xA8
this->command(0x3F); // 0x3F 1/64 duty
this->command(SSD1331_SETMASTER); // 0xAD
this->command(0x8E);
this->command(SSD1331_POWERMODE); // 0xB0
this->command(0x0B);
this->command(SSD1331_PRECHARGE); // 0xB1
this->command(0x31);
this->command(SSD1331_CLOCKDIV); // 0xB3
this->command(0xF0); // 7:4 = Oscillator Frequency, 3:0 = CLK Div Ratio, (A[3:0]+1 = 1..16)
this->command(SSD1331_PRECHARGEA); // 0x8A
this->command(0x64);
this->command(SSD1331_PRECHARGEB); // 0x8B
this->command(0x78);
this->command(SSD1331_PRECHARGEC); // 0x8C
this->command(0x64);
this->command(SSD1331_PRECHARGELEVEL); // 0xBB
this->command(0x3A);
this->command(SSD1331_VCOMH); // 0xBE
this->command(0x3E);
this->command(SSD1331_MASTERCURRENT); // 0x87
this->command(0x06);
set_brightness(this->brightness_);
this->fill(BLACK); // clear display - ensures we do not see garbage at power-on
this->display(); // ...write buffer, which actually clears the display's memory
this->turn_on(); // display ON
}
void SSD1331::display() {
this->command(SSD1331_SETCOLUMN); // set column address
this->command(0x00); // set column start address
this->command(0x5F); // set column end address
this->command(SSD1331_SETROW); // set row address
this->command(0x00); // set row start address
this->command(0x3F); // set last row
this->write_display_data();
}
void SSD1331::update() {
this->do_update_();
this->display();
}
void SSD1331::set_brightness(float brightness) {
// validation
this->brightness_ = clamp(brightness, 0, 1);
// now write the new brightness level to the display
this->command(SSD1331_CONTRASTA); // 0x81
this->command(int(SSD1331_MAX_CONTRASTA * (this->brightness_)));
this->command(SSD1331_CONTRASTB); // 0x82
this->command(int(SSD1331_MAX_CONTRASTB * (this->brightness_)));
this->command(SSD1331_CONTRASTC); // 0x83
this->command(int(SSD1331_MAX_CONTRASTC * (this->brightness_)));
}
bool SSD1331::is_on() { return this->is_on_; }
void SSD1331::turn_on() {
this->command(SSD1331_DISPLAYON);
this->is_on_ = true;
}
void SSD1331::turn_off() {
this->command(SSD1331_DISPLAYOFF);
this->is_on_ = false;
}
int SSD1331::get_height_internal() { return 64; }
int SSD1331::get_width_internal() { return 96; }
size_t SSD1331::get_buffer_length_() {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) * size_t(SSD1331_BYTESPERPIXEL);
}
void HOT SSD1331::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;
const uint32_t color565 = color.to_rgb_565();
// where should the bits go in the big buffer array? math...
uint16_t pos = (x + y * this->get_width_internal()) * SSD1331_BYTESPERPIXEL;
this->buffer_[pos++] = (color565 >> 8) & 0xff;
this->buffer_[pos] = color565 & 0xff;
}
void SSD1331::fill(Color color) {
const uint32_t color565 = color.to_rgb_565();
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
if (i & 1) {
this->buffer_[i] = color565 & 0xff;
} else {
this->buffer_[i] = (color565 >> 8) & 0xff;
}
}
void SSD1331::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
} // namespace ssd1331_base
} // namespace esphome

View File

@ -0,0 +1,45 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/esphal.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace ssd1331_base {
class SSD1331 : public PollingComponent, public display::DisplayBuffer {
public:
void setup() override;
void display();
void update() override;
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void init_brightness(float brightness) { this->brightness_ = brightness; }
void set_brightness(float brightness);
bool is_on();
void turn_on();
void turn_off();
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
void fill(Color color) override;
protected:
virtual void command(uint8_t value) = 0;
virtual void write_display_data() = 0;
void init_reset_();
void draw_absolute_pixel_internal(int x, int y, Color color) override;
int get_height_internal() override;
int get_width_internal() override;
size_t get_buffer_length_();
GPIOPin *reset_pin_{nullptr};
bool is_on_{false};
float brightness_{1.0};
};
} // namespace ssd1331_base
} // namespace esphome

View File

@ -0,0 +1,28 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi, ssd1331_base
from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES
CODEOWNERS = ['@kbx81']
AUTO_LOAD = ['ssd1331_base']
DEPENDENCIES = ['spi']
ssd1331_spi = cg.esphome_ns.namespace('ssd1331_spi')
SPISSD1331 = ssd1331_spi.class_('SPISSD1331', ssd1331_base.SSD1331, spi.SPIDevice)
CONFIG_SCHEMA = cv.All(ssd1331_base.SSD1331_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(SPISSD1331),
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema()),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield ssd1331_base.setup_ssd1331(var, config)
yield spi.register_spi_device(var, config)
dc = yield cg.gpio_pin_expression(config[CONF_DC_PIN])
cg.add(var.set_dc_pin(dc))

View File

@ -0,0 +1,58 @@
#include "ssd1331_spi.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace ssd1331_spi {
static const char *TAG = "ssd1331_spi";
void SPISSD1331::setup() {
ESP_LOGCONFIG(TAG, "Setting up SPI SSD1331...");
this->spi_setup();
this->dc_pin_->setup(); // OUTPUT
if (this->cs_)
this->cs_->setup(); // OUTPUT
this->init_reset_();
delay(500); // NOLINT
SSD1331::setup();
}
void SPISSD1331::dump_config() {
LOG_DISPLAY("", "SPI SSD1331", this);
if (this->cs_)
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_);
LOG_UPDATE_INTERVAL(this);
}
void SPISSD1331::command(uint8_t value) {
if (this->cs_)
this->cs_->digital_write(true);
this->dc_pin_->digital_write(false);
delay(1);
this->enable();
if (this->cs_)
this->cs_->digital_write(false);
this->write_byte(value);
if (this->cs_)
this->cs_->digital_write(true);
this->disable();
}
void HOT SPISSD1331::write_display_data() {
if (this->cs_)
this->cs_->digital_write(true);
this->dc_pin_->digital_write(true);
if (this->cs_)
this->cs_->digital_write(false);
delay(1);
this->enable();
this->write_array(this->buffer_, this->get_buffer_length_());
if (this->cs_)
this->cs_->digital_write(true);
this->disable();
}
} // namespace ssd1331_spi
} // namespace esphome

View File

@ -0,0 +1,29 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ssd1331_base/ssd1331_base.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace ssd1331_spi {
class SPISSD1331 : public ssd1331_base::SSD1331,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
spi::DATA_RATE_8MHZ> {
public:
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
void setup() override;
void dump_config() override;
protected:
void command(uint8_t value) override;
void write_display_data() override;
GPIOPin *dc_pin_;
};
} // namespace ssd1331_spi
} // namespace esphome

View File

@ -5,6 +5,8 @@ from esphome.components import display
from esphome.const import CONF_BRIGHTNESS, CONF_LAMBDA, CONF_MODEL, CONF_RESET_PIN
from esphome.core import coroutine
CODEOWNERS = ['@kbx81']
ssd1351_base_ns = cg.esphome_ns.namespace('ssd1351_base')
SSD1351 = ssd1351_base_ns.class_('SSD1351', cg.PollingComponent, display.DisplayBuffer)
SSD1351Model = ssd1351_base_ns.enum('SSD1351Model')

View File

@ -4,6 +4,8 @@ from esphome import pins
from esphome.components import spi, ssd1351_base
from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES
CODEOWNERS = ['@kbx81']
AUTO_LOAD = ['ssd1351_base']
DEPENDENCIES = ['spi']

View File

@ -0,0 +1,2 @@
import esphome.codegen as cg
st7735_ns = cg.esphome_ns.namespace('st7735')

View File

@ -0,0 +1,75 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi
from esphome.components import display
from esphome.core import coroutine
from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_MODEL, CONF_RESET_PIN, CONF_PAGES
from . import st7735_ns
CODEOWNERS = ['@SenexCrenshaw']
DEPENDENCIES = ['spi']
CONF_DEVICEWIDTH = 'devicewidth'
CONF_DEVICEHEIGHT = 'deviceheight'
CONF_ROWSTART = 'rowstart'
CONF_COLSTART = 'colstart'
CONF_EIGHTBITCOLOR = 'eightbitcolor'
CONF_USEBGR = 'usebgr'
SPIST7735 = st7735_ns.class_('ST7735', cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice)
ST7735Model = st7735_ns.enum('ST7735Model')
MODELS = {
'INITR_GREENTAB': ST7735Model.ST7735_INITR_GREENTAB,
'INITR_REDTAB': ST7735Model.ST7735_INITR_REDTAB,
'INITR_BLACKTAB': ST7735Model.ST7735_INITR_BLACKTAB,
'INITR_MINI160X80': ST7735Model.ST7735_INITR_MINI_160X80,
'INITR_18BLACKTAB': ST7735Model.ST7735_INITR_18BLACKTAB,
'INITR_18REDTAB': ST7735Model.ST7735_INITR_18REDTAB
}
ST7735_MODEL = cv.enum(MODELS, upper=True, space="_")
ST7735_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({
cv.Required(CONF_MODEL): ST7735_MODEL,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema
}).extend(cv.polling_component_schema('1s'))
CONFIG_SCHEMA = cv.All(ST7735_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(SPIST7735),
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_DEVICEWIDTH): cv.int_,
cv.Required(CONF_DEVICEHEIGHT): cv.int_,
cv.Required(CONF_COLSTART): cv.int_,
cv.Required(CONF_ROWSTART): cv.int_,
cv.Optional(CONF_EIGHTBITCOLOR, default=False): cv.boolean,
cv.Optional(CONF_USEBGR, default=False): cv.boolean,
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema()),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
@coroutine
def setup_st7735(var, config):
yield cg.register_component(var, config)
yield display.register_display(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))
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_))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], config[CONF_MODEL], config[CONF_DEVICEWIDTH],
config[CONF_DEVICEHEIGHT], config[CONF_COLSTART], config[CONF_ROWSTART],
config[CONF_EIGHTBITCOLOR], config[CONF_USEBGR])
yield setup_st7735(var, config)
yield spi.register_spi_device(var, config)
dc = yield cg.gpio_pin_expression(config[CONF_DC_PIN])
cg.add(var.set_dc_pin(dc))

View File

@ -0,0 +1,483 @@
#include "st7735.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace st7735 {
static const uint8_t ST_CMD_DELAY = 0x80; // special signifier for command lists
static const uint8_t ST77XX_NOP = 0x00;
static const uint8_t ST77XX_SWRESET = 0x01;
static const uint8_t ST77XX_RDDID = 0x04;
static const uint8_t ST77XX_RDDST = 0x09;
static const uint8_t ST77XX_SLPIN = 0x10;
static const uint8_t ST77XX_SLPOUT = 0x11;
static const uint8_t ST77XX_PTLON = 0x12;
static const uint8_t ST77XX_NORON = 0x13;
static const uint8_t ST77XX_INVOFF = 0x20;
static const uint8_t ST77XX_INVON = 0x21;
static const uint8_t ST77XX_DISPOFF = 0x28;
static const uint8_t ST77XX_DISPON = 0x29;
static const uint8_t ST77XX_CASET = 0x2A;
static const uint8_t ST77XX_RASET = 0x2B;
static const uint8_t ST77XX_RAMWR = 0x2C;
static const uint8_t ST77XX_RAMRD = 0x2E;
static const uint8_t ST77XX_PTLAR = 0x30;
static const uint8_t ST77XX_TEOFF = 0x34;
static const uint8_t ST77XX_TEON = 0x35;
static const uint8_t ST77XX_MADCTL = 0x36;
static const uint8_t ST77XX_COLMOD = 0x3A;
static const uint8_t ST77XX_MADCTL_MY = 0x80;
static const uint8_t ST77XX_MADCTL_MX = 0x40;
static const uint8_t ST77XX_MADCTL_MV = 0x20;
static const uint8_t ST77XX_MADCTL_ML = 0x10;
static const uint8_t ST77XX_MADCTL_RGB = 0x00;
static const uint8_t ST77XX_RDID1 = 0xDA;
static const uint8_t ST77XX_RDID2 = 0xDB;
static const uint8_t ST77XX_RDID3 = 0xDC;
static const uint8_t ST77XX_RDID4 = 0xDD;
// Some register settings
static const uint8_t ST7735_MADCTL_BGR = 0x08;
static const uint8_t ST7735_MADCTL_MH = 0x04;
static const uint8_t ST7735_FRMCTR1 = 0xB1;
static const uint8_t ST7735_FRMCTR2 = 0xB2;
static const uint8_t ST7735_FRMCTR3 = 0xB3;
static const uint8_t ST7735_INVCTR = 0xB4;
static const uint8_t ST7735_DISSET5 = 0xB6;
static const uint8_t ST7735_PWCTR1 = 0xC0;
static const uint8_t ST7735_PWCTR2 = 0xC1;
static const uint8_t ST7735_PWCTR3 = 0xC2;
static const uint8_t ST7735_PWCTR4 = 0xC3;
static const uint8_t ST7735_PWCTR5 = 0xC4;
static const uint8_t ST7735_VMCTR1 = 0xC5;
static const uint8_t ST7735_PWCTR6 = 0xFC;
static const uint8_t ST7735_GMCTRP1 = 0xE0;
static const uint8_t ST7735_GMCTRN1 = 0xE1;
// clang-format off
static const uint8_t PROGMEM
BCMD[] = { // Init commands for 7735B screens
18, // 18 commands in list:
ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, no args, w/delay
50, // 50 ms delay
ST77XX_SLPOUT, ST_CMD_DELAY, // 2: Out of sleep mode, no args, w/delay
255, // 255 = max (500 ms) delay
ST77XX_COLMOD, 1+ST_CMD_DELAY, // 3: Set color mode, 1 arg + delay:
0x05, // 16-bit color
10, // 10 ms delay
ST7735_FRMCTR1, 3+ST_CMD_DELAY, // 4: Frame rate control, 3 args + delay:
0x00, // fastest refresh
0x06, // 6 lines front porch
0x03, // 3 lines back porch
10, // 10 ms delay
ST77XX_MADCTL, 1, // 5: Mem access ctl (directions), 1 arg:
0x08, // Row/col addr, bottom-top refresh
ST7735_DISSET5, 2, // 6: Display settings #5, 2 args:
0x15, // 1 clk cycle nonoverlap, 2 cycle gate
// rise, 3 cycle osc equalize
0x02, // Fix on VTL
ST7735_INVCTR, 1, // 7: Display inversion control, 1 arg:
0x0, // Line inversion
ST7735_PWCTR1, 2+ST_CMD_DELAY, // 8: Power control, 2 args + delay:
0x02, // GVDD = 4.7V
0x70, // 1.0uA
10, // 10 ms delay
ST7735_PWCTR2, 1, // 9: Power control, 1 arg, no delay:
0x05, // VGH = 14.7V, VGL = -7.35V
ST7735_PWCTR3, 2, // 10: Power control, 2 args, no delay:
0x01, // Opamp current small
0x02, // Boost frequency
ST7735_VMCTR1, 2+ST_CMD_DELAY, // 11: Power control, 2 args + delay:
0x3C, // VCOMH = 4V
0x38, // VCOML = -1.1V
10, // 10 ms delay
ST7735_PWCTR6, 2, // 12: Power control, 2 args, no delay:
0x11, 0x15,
ST7735_GMCTRP1,16, // 13: Gamma Adjustments (pos. polarity), 16 args + delay:
0x09, 0x16, 0x09, 0x20, // (Not entirely necessary, but provides
0x21, 0x1B, 0x13, 0x19, // accurate colors)
0x17, 0x15, 0x1E, 0x2B,
0x04, 0x05, 0x02, 0x0E,
ST7735_GMCTRN1,16+ST_CMD_DELAY, // 14: Gamma Adjustments (neg. polarity), 16 args + delay:
0x0B, 0x14, 0x08, 0x1E, // (Not entirely necessary, but provides
0x22, 0x1D, 0x18, 0x1E, // accurate colors)
0x1B, 0x1A, 0x24, 0x2B,
0x06, 0x06, 0x02, 0x0F,
10, // 10 ms delay
ST77XX_CASET, 4, // 15: Column addr set, 4 args, no delay:
0x00, 0x02, // XSTART = 2
0x00, 0x81, // XEND = 129
ST77XX_RASET, 4, // 16: Row addr set, 4 args, no delay:
0x00, 0x02, // XSTART = 1
0x00, 0x81, // XEND = 160
ST77XX_NORON, ST_CMD_DELAY, // 17: Normal display on, no args, w/delay
10, // 10 ms delay
ST77XX_DISPON, ST_CMD_DELAY, // 18: Main screen turn on, no args, delay
255 }, // 255 = max (500 ms) delay
RCMD1[] = { // 7735R init, part 1 (red or green tab)
15, // 15 commands in list:
ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, 0 args, w/delay
150, // 150 ms delay
ST77XX_SLPOUT, ST_CMD_DELAY, // 2: Out of sleep mode, 0 args, w/delay
255, // 500 ms delay
ST7735_FRMCTR1, 3, // 3: Framerate ctrl - normal mode, 3 arg:
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
ST7735_FRMCTR2, 3, // 4: Framerate ctrl - idle mode, 3 args:
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
ST7735_FRMCTR3, 6, // 5: Framerate - partial mode, 6 args:
0x01, 0x2C, 0x2D, // Dot inversion mode
0x01, 0x2C, 0x2D, // Line inversion mode
ST7735_INVCTR, 1, // 6: Display inversion ctrl, 1 arg:
0x07, // No inversion
ST7735_PWCTR1, 3, // 7: Power control, 3 args, no delay:
0xA2,
0x02, // -4.6V
0x84, // AUTO mode
ST7735_PWCTR2, 1, // 8: Power control, 1 arg, no delay:
0xC5, // VGH25=2.4C VGSEL=-10 VGH=3 * AVDD
ST7735_PWCTR3, 2, // 9: Power control, 2 args, no delay:
0x0A, // Opamp current small
0x00, // Boost frequency
ST7735_PWCTR4, 2, // 10: Power control, 2 args, no delay:
0x8A, // BCLK/2,
0x2A, // opamp current small & medium low
ST7735_PWCTR5, 2, // 11: Power control, 2 args, no delay:
0x8A, 0xEE,
ST7735_VMCTR1, 1, // 12: Power control, 1 arg, no delay:
0x0E,
ST77XX_INVOFF, 0, // 13: Don't invert display, no args
ST77XX_MADCTL, 1, // 14: Mem access ctl (directions), 1 arg:
0xC8, // row/col addr, bottom-top refresh
ST77XX_COLMOD, 1, // 15: set color mode, 1 arg, no delay:
0x05 }, // 16-bit color
RCMD2GREEN[] = { // 7735R init, part 2 (green tab only)
2, // 2 commands in list:
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
0x00, 0x02, // XSTART = 0
0x00, 0x7F+0x02, // XEND = 127
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
0x00, 0x01, // XSTART = 0
0x00, 0x9F+0x01 }, // XEND = 159
RCMD2RED[] = { // 7735R init, part 2 (red tab only)
2, // 2 commands in list:
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x7F, // XEND = 127
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x9F }, // XEND = 159
RCMD2GREEN144[] = { // 7735R init, part 2 (green 1.44 tab)
2, // 2 commands in list:
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x7F, // XEND = 127
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x7F }, // XEND = 127
RCMD2GREEN160X80[] = { // 7735R init, part 2 (mini 160x80)
2, // 2 commands in list:
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x4F, // XEND = 79
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x9F }, // XEND = 159
RCMD3[] = { // 7735R init, part 3 (red or green tab)
4, // 4 commands in list:
ST7735_GMCTRP1, 16 , // 1: Gamma Adjustments (pos. polarity), 16 args + delay:
0x02, 0x1c, 0x07, 0x12, // (Not entirely necessary, but provides
0x37, 0x32, 0x29, 0x2d, // accurate colors)
0x29, 0x25, 0x2B, 0x39,
0x00, 0x01, 0x03, 0x10,
ST7735_GMCTRN1, 16 , // 2: Gamma Adjustments (neg. polarity), 16 args + delay:
0x03, 0x1d, 0x07, 0x06, // (Not entirely necessary, but provides
0x2E, 0x2C, 0x29, 0x2D, // accurate colors)
0x2E, 0x2E, 0x37, 0x3F,
0x00, 0x00, 0x02, 0x10,
ST77XX_NORON, ST_CMD_DELAY, // 3: Normal display on, no args, w/delay
10, // 10 ms delay
ST77XX_DISPON, ST_CMD_DELAY, // 4: Main screen turn on, no args w/delay
100 }; // 100 ms delay
// clang-format on
static const char *TAG = "st7735";
ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, boolean eightbitcolor,
boolean usebgr) {
model_ = model;
this->width_ = width;
this->height_ = height;
this->colstart_ = colstart;
this->rowstart_ = rowstart;
this->eightbitcolor_ = eightbitcolor;
this->usebgr_ = usebgr;
}
void ST7735::setup() {
ESP_LOGCONFIG(TAG, "Setting up ST7735...");
this->spi_setup();
this->dc_pin_->setup(); // OUTPUT
this->cs_->setup(); // OUTPUT
this->dc_pin_->digital_write(true);
this->cs_->digital_write(true);
this->init_reset_();
delay(100); // NOLINT
ESP_LOGD(TAG, " START");
dump_config();
ESP_LOGD(TAG, " END");
display_init_(RCMD1);
if (this->model_ == INITR_GREENTAB) {
display_init_(RCMD2GREEN);
colstart_ == 0 ? colstart_ = 2 : colstart_;
rowstart_ == 0 ? rowstart_ = 1 : rowstart_;
} else if ((this->model_ == INITR_144GREENTAB) || (this->model_ == INITR_HALLOWING)) {
height_ == 0 ? height_ = ST7735_TFTHEIGHT_128 : height_;
width_ == 0 ? width_ = ST7735_TFTWIDTH_128 : width_;
display_init_(RCMD2GREEN144);
colstart_ == 0 ? colstart_ = 2 : colstart_;
rowstart_ == 0 ? rowstart_ = 3 : rowstart_;
} else if (this->model_ == INITR_MINI_160X80) {
height_ == 0 ? height_ = ST7735_TFTHEIGHT_160 : height_;
width_ == 0 ? width_ = ST7735_TFTWIDTH_80 : width_;
display_init_(RCMD2GREEN160X80);
colstart_ = 24;
rowstart_ = 0; // For default rotation 0
} else {
// colstart, rowstart left at default '0' values
display_init_(RCMD2RED);
}
display_init_(RCMD3);
uint8_t data = 0;
if (this->model_ != INITR_HALLOWING) {
uint8_t data = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY;
}
if (this->usebgr_) {
data = data | ST7735_MADCTL_BGR;
} else {
data = data | ST77XX_MADCTL_RGB;
}
sendcommand_(ST77XX_MADCTL, &data, 1);
this->init_internal_(this->get_buffer_length());
memset(this->buffer_, 0x00, this->get_buffer_length());
}
void ST7735::update() {
this->do_update_();
this->write_display_data_();
}
int ST7735::get_height_internal() { return height_; }
int ST7735::get_width_internal() { return width_; }
size_t ST7735::get_buffer_length() {
if (this->eightbitcolor_) {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal());
}
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) * 2;
}
void HOT ST7735::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;
if (this->eightbitcolor_) {
const uint32_t color332 = color.to_332();
uint16_t pos = (x + y * this->get_width_internal());
this->buffer_[pos] = color332;
} else {
const uint32_t color565 = color.to_565();
uint16_t pos = (x + y * this->get_width_internal()) * 2;
this->buffer_[pos++] = (color565 >> 8) & 0xff;
this->buffer_[pos] = color565 & 0xff;
}
}
void ST7735::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
const char *ST7735::model_str_() {
switch (this->model_) {
case INITR_GREENTAB:
return "ST7735 GREENTAB";
case INITR_REDTAB:
return "ST7735 REDTAB";
case INITR_BLACKTAB:
return "ST7735 BLACKTAB";
case INITR_MINI_160X80:
return "ST7735 MINI160x80";
default:
return "Unknown";
}
}
void ST7735::display_init_(const uint8_t *addr) {
uint8_t num_commands, cmd, num_args;
uint16_t ms;
num_commands = pgm_read_byte(addr++); // Number of commands to follow
while (num_commands--) { // For each command...
cmd = pgm_read_byte(addr++); // Read command
num_args = pgm_read_byte(addr++); // Number of args to follow
ms = num_args & ST_CMD_DELAY; // If hibit set, delay follows args
num_args &= ~ST_CMD_DELAY; // Mask out delay bit
this->sendcommand_(cmd, addr, num_args);
addr += num_args;
if (ms) {
ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
if (ms == 255)
ms = 500; // If 255, delay for 500 ms
delay(ms);
}
}
}
void ST7735::dump_config() {
LOG_DISPLAY("", "ST7735", this);
ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_());
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGD(TAG, " Buffer Size: %zu", this->get_buffer_length());
ESP_LOGD(TAG, " Height: %d", this->height_);
ESP_LOGD(TAG, " Width: %d", this->width_);
ESP_LOGD(TAG, " ColStart: %d", this->colstart_);
ESP_LOGD(TAG, " RowStart: %d", this->rowstart_);
LOG_UPDATE_INTERVAL(this);
}
void HOT ST7735::writecommand_(uint8_t value) {
this->enable();
this->dc_pin_->digital_write(false);
this->write_byte(value);
this->dc_pin_->digital_write(true);
this->disable();
}
void HOT ST7735::writedata_(uint8_t value) {
this->dc_pin_->digital_write(true);
this->enable();
this->write_byte(value);
this->disable();
}
void HOT ST7735::sendcommand_(uint8_t cmd, const uint8_t *data_bytes, uint8_t num_data_bytes) {
this->writecommand_(cmd);
this->senddata_(data_bytes, num_data_bytes);
}
void HOT ST7735::senddata_(const uint8_t *data_bytes, uint8_t num_data_bytes) {
this->dc_pin_->digital_write(true); // pull DC high to indicate data
this->cs_->digital_write(false);
this->enable();
for (uint8_t i = 0; i < num_data_bytes; i++) {
this->transfer_byte(pgm_read_byte(data_bytes++)); // write byte - SPI library
}
this->cs_->digital_write(true);
this->disable();
}
void HOT ST7735::write_display_data_() {
uint16_t offsetx = colstart_;
uint16_t offsety = rowstart_;
uint16_t x1 = offsetx;
uint16_t x2 = x1 + get_width_internal() - 1;
uint16_t y1 = offsety;
uint16_t y2 = y1 + get_height_internal() - 1;
this->enable();
// set column(x) address
this->dc_pin_->digital_write(false);
this->write_byte(ST77XX_CASET);
this->dc_pin_->digital_write(true);
this->spi_master_write_addr_(x1, x2);
// set Page(y) address
this->dc_pin_->digital_write(false);
this->write_byte(ST77XX_RASET);
this->dc_pin_->digital_write(true);
this->spi_master_write_addr_(y1, y2);
// Memory Write
this->dc_pin_->digital_write(false);
this->write_byte(ST77XX_RAMWR);
this->dc_pin_->digital_write(true);
if (this->eightbitcolor_) {
for (int line = 0; line < this->get_buffer_length(); line = line + this->get_width_internal()) {
for (int index = 0; index < this->get_width_internal(); ++index) {
auto color = Color(this->buffer_[index + line], Color::ColorOrder::COLOR_ORDER_RGB,
Color::ColorBitness::COLOR_BITNESS_332, true)
.to_565();
this->write_byte((color >> 8) & 0xff);
this->write_byte(color & 0xff);
}
}
} else {
this->write_array(this->buffer_, this->get_buffer_length());
}
}
void ST7735::spi_master_write_addr_(uint16_t addr1, uint16_t addr2) {
static uint8_t BYTE[4];
BYTE[0] = (addr1 >> 8) & 0xFF;
BYTE[1] = addr1 & 0xFF;
BYTE[2] = (addr2 >> 8) & 0xFF;
BYTE[3] = addr2 & 0xFF;
this->dc_pin_->digital_write(true);
this->write_array(BYTE, 4);
}
void ST7735::spi_master_write_color_(uint16_t color, uint16_t size) {
static uint8_t BYTE[1024];
int index = 0;
for (int i = 0; i < size; i++) {
BYTE[index++] = (color >> 8) & 0xFF;
BYTE[index++] = color & 0xFF;
}
this->dc_pin_->digital_write(true);
return write_array(BYTE, size * 2);
}
} // namespace st7735
} // namespace esphome

View File

@ -0,0 +1,87 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace st7735 {
static const uint8_t ST7735_TFTWIDTH_128 = 128; // for 1.44 and mini^M
static const uint8_t ST7735_TFTWIDTH_80 = 80; // for mini^M
static const uint8_t ST7735_TFTHEIGHT_128 = 128; // for 1.44" display^M
static const uint8_t ST7735_TFTHEIGHT_160 = 160; // for 1.8" and mini display^M
// some flags for initR() :(
static const uint8_t INITR_GREENTAB = 0x00;
static const uint8_t INITR_REDTAB = 0x01;
static const uint8_t INITR_BLACKTAB = 0x02;
static const uint8_t INITR_144GREENTAB = 0x01;
static const uint8_t INITR_MINI_160X80 = 0x04;
static const uint8_t INITR_HALLOWING = 0x05;
static const uint8_t INITR_18GREENTAB = INITR_GREENTAB;
static const uint8_t INITR_18REDTAB = INITR_REDTAB;
static const uint8_t INITR_18BLACKTAB = INITR_BLACKTAB;
enum ST7735Model {
ST7735_INITR_GREENTAB = INITR_GREENTAB,
ST7735_INITR_REDTAB = INITR_REDTAB,
ST7735_INITR_BLACKTAB = INITR_BLACKTAB,
ST7735_INITR_MINI_160X80 = INITR_MINI_160X80,
ST7735_INITR_18BLACKTAB = INITR_18BLACKTAB,
ST7735_INITR_18REDTAB = INITR_18REDTAB
};
class ST7735 : public PollingComponent,
public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_8MHZ> {
public:
ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, boolean eightbitcolor, boolean usebgr);
void dump_config() override;
void setup() override;
void display();
void update() override;
void set_model(ST7735Model model) { this->model_ = model; }
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
void set_reset_pin(GPIOPin *value) { this->reset_pin_ = value; }
void set_dc_pin(GPIOPin *value) { dc_pin_ = value; }
size_t get_buffer_length();
protected:
void sendcommand_(uint8_t cmd, const uint8_t *data_bytes, uint8_t num_data_bytes);
void senddata_(const uint8_t *data_bytes, uint8_t num_data_bytes);
void writecommand_(uint8_t value);
void writedata_(uint8_t value);
void write_display_data_();
void init_reset_();
void display_init_(const uint8_t *addr);
void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void draw_absolute_pixel_internal(int x, int y, Color color) override;
void spi_master_write_addr_(uint16_t addr1, uint16_t addr2);
void spi_master_write_color_(uint16_t color, uint16_t size);
int get_width_internal() override;
int get_height_internal() override;
const char *model_str_();
ST7735Model model_{ST7735_INITR_18BLACKTAB};
uint8_t colstart_ = 0, rowstart_ = 0;
boolean eightbitcolor_ = false;
boolean usebgr_ = false;
int16_t width_ = 80, height_ = 80; // Watch heap size
GPIOPin *reset_pin_{nullptr};
GPIOPin *dc_pin_{nullptr};
};
} // namespace st7735
} // namespace esphome

View File

@ -6,6 +6,8 @@ from esphome.const import CONF_BACKLIGHT_PIN, CONF_BRIGHTNESS, CONF_CS_PIN, CONF
CONF_LAMBDA, CONF_RESET_PIN
from . import st7789v_ns
CODEOWNERS = ['@kbx81']
DEPENDENCIES = ['spi']
ST7789V = st7789v_ns.class_('ST7789V', cg.PollingComponent, spi.SPIDevice,

View File

@ -28,6 +28,7 @@ def validate_acceleration(value):
try:
value = float(value)
except ValueError:
# pylint: disable=raise-missing-from
raise cv.Invalid(f"Expected acceleration as floating point number, got {value}")
if value <= 0:
@ -48,6 +49,7 @@ def validate_speed(value):
try:
value = float(value)
except ValueError:
# pylint: disable=raise-missing-from
raise cv.Invalid(f"Expected speed as floating point number, got {value}")
if value <= 0:

View File

@ -0,0 +1 @@
CODEOWNERS = ['@0hax']

View File

@ -0,0 +1,34 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, uart
from esphome.const import CONF_ID, CONF_SENSOR, ICON_FLASH, UNIT_WATT_HOURS
DEPENDENCIES = ['uart']
teleinfo_ns = cg.esphome_ns.namespace('teleinfo')
TeleInfo = teleinfo_ns.class_('TeleInfo', cg.PollingComponent, uart.UARTDevice)
CONF_TAG_NAME = "tag_name"
TELEINFO_TAG_SCHEMA = cv.Schema({
cv.Required(CONF_TAG_NAME): cv.string,
cv.Required(CONF_SENSOR): sensor.sensor_schema(UNIT_WATT_HOURS, ICON_FLASH, 0)
})
CONF_TAGS = "tags"
CONF_HISTORICAL_MODE = "historical_mode"
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(TeleInfo),
cv.Optional(CONF_HISTORICAL_MODE, default=False): cv.boolean,
cv.Optional(CONF_TAGS): cv.ensure_list(TELEINFO_TAG_SCHEMA),
}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], config[CONF_HISTORICAL_MODE])
yield cg.register_component(var, config)
yield uart.register_uart_device(var, config)
if CONF_TAGS in config:
for tag in config[CONF_TAGS]:
sens = yield sensor.new_sensor(tag[CONF_SENSOR])
cg.add(var.register_teleinfo_sensor(tag[CONF_TAG_NAME], sens))

View File

@ -0,0 +1,184 @@
#include "teleinfo.h"
#include "esphome/core/log.h"
namespace esphome {
namespace teleinfo {
static const char *TAG = "teleinfo";
/* Helpers */
static int get_field(char *dest, char *buf_start, char *buf_end, int sep) {
char *field_end;
int len;
field_end = static_cast<char *>(memchr(buf_start, sep, buf_end - buf_start));
if (!field_end)
return 0;
len = field_end - buf_start;
strncpy(dest, buf_start, len);
dest[len] = '\0';
return len;
}
/* TeleInfo methods */
bool TeleInfo::check_crc_(const char *grp, const char *grp_end) {
int grp_len = grp_end - grp;
uint8_t raw_crc = grp[grp_len - 1];
uint8_t crc_tmp = 0;
int i;
for (i = 0; i < grp_len - checksum_area_end_; i++)
crc_tmp += grp[i];
crc_tmp &= 0x3F;
crc_tmp += 0x20;
if (raw_crc != crc_tmp) {
ESP_LOGE(TAG, "bad crc: got %d except %d", raw_crc, crc_tmp);
return false;
}
return true;
}
bool TeleInfo::read_chars_until_(bool drop, uint8_t c) {
uint8_t received;
int j = 0;
while (available() > 0 && j < 128) {
j++;
received = read();
if (received == c)
return true;
if (drop)
continue;
/*
* Internal buffer is full, switch to OFF mode.
* Data will be retrieved on next update.
*/
if (buf_index_ >= (MAX_BUF_SIZE - 1)) {
ESP_LOGW(TAG, "Internal buffer full");
state_ = OFF;
return false;
}
buf_[buf_index_++] = received;
}
return false;
}
void TeleInfo::setup() { state_ = OFF; }
void TeleInfo::update() {
if (state_ == OFF) {
buf_index_ = 0;
state_ = ON;
}
}
void TeleInfo::loop() {
switch (state_) {
case OFF:
break;
case ON:
/* Dequeue chars until start frame (0x2) */
if (read_chars_until_(true, 0x2))
state_ = START_FRAME_RECEIVED;
break;
case START_FRAME_RECEIVED:
/* Dequeue chars until end frame (0x3) */
if (read_chars_until_(false, 0x3))
state_ = END_FRAME_RECEIVED;
break;
case END_FRAME_RECEIVED:
char *buf_finger;
char *grp_end;
char *buf_end;
int field_len;
buf_finger = buf_;
buf_end = buf_ + buf_index_;
/* Each frame is composed of multiple groups starting by 0xa(Line Feed) and ending by
* 0xd ('\r').
*
* Historical mode: each group contains tag, data and a CRC separated by 0x20 (Space)
* 0xa | Tag | 0x20 | Data | 0x20 | CRC | 0xd
* ^^^^^^^^^^^^^^^^^^^^
* Checksum is computed on the above in historical mode.
*
* Standard mode: each group contains tag, data and a CRC separated by 0x9 (\t)
* 0xa | Tag | 0x9 | Data | 0x9 | CRC | 0xd
* ^^^^^^^^^^^^^^^^^^^^^^^^^
* Checksum is computed on the above in standard mode.
*/
while ((buf_finger = static_cast<char *>(memchr(buf_finger, (int) 0xa, buf_index_ - 1))) &&
((buf_finger - buf_) < buf_index_)) {
/* Point to the first char of the group after 0xa */
buf_finger += 1;
/* Group len */
grp_end = static_cast<char *>(memchr(buf_finger, 0xd, buf_end - buf_finger));
if (!grp_end) {
ESP_LOGE(TAG, "No group found");
break;
}
if (!check_crc_(buf_finger, grp_end))
break;
/* Get tag */
field_len = get_field(tag_, buf_finger, grp_end, separator_);
if (!field_len || field_len >= MAX_TAG_SIZE) {
ESP_LOGE(TAG, "Invalid tag.");
break;
}
/* Advance buf_finger to after the tag and the separator. */
buf_finger += field_len + 1;
/* Get value (after next separator) */
field_len = get_field(val_, buf_finger, grp_end, separator_);
if (!field_len || field_len >= MAX_VAL_SIZE) {
ESP_LOGE(TAG, "Invalid Value");
break;
}
/* Advance buf_finger to end of group */
buf_finger += field_len + 1 + 1 + 1;
publish_value_(std::string(tag_), std::string(val_));
}
state_ = OFF;
break;
}
}
void TeleInfo::publish_value_(std::string tag, std::string val) {
/* It will return 0 if tag is not a float. */
auto newval = parse_float(val);
for (auto element : teleinfo_sensors_)
if (tag == element->tag)
element->sensor->publish_state(*newval);
}
void TeleInfo::dump_config() {
ESP_LOGCONFIG(TAG, "TeleInfo:");
for (auto element : teleinfo_sensors_)
LOG_SENSOR(" ", element->tag, element->sensor);
this->check_uart_settings(baud_rate_, 1, uart::UART_CONFIG_PARITY_EVEN, 7);
}
TeleInfo::TeleInfo(bool historical_mode) {
if (historical_mode) {
/*
* Historical mode doesn't contain last separator between checksum and data.
*/
checksum_area_end_ = 2;
separator_ = 0x20;
baud_rate_ = 1200;
} else {
checksum_area_end_ = 1;
separator_ = 0x9;
baud_rate_ = 9600;
}
}
void TeleInfo::register_teleinfo_sensor(const char *tag, sensor::Sensor *sensor) {
const TeleinfoSensorElement *teleinfo_sensor = new TeleinfoSensorElement{tag, sensor};
teleinfo_sensors_.push_back(teleinfo_sensor);
}
} // namespace teleinfo
} // namespace esphome

View File

@ -0,0 +1,51 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace teleinfo {
/*
* 198 bytes should be enough to contain a full session in historical mode with
* three phases. But go with 1024 just to be sure.
*/
static const uint8_t MAX_TAG_SIZE = 64;
static const uint16_t MAX_VAL_SIZE = 256;
static const uint16_t MAX_BUF_SIZE = 1024;
struct TeleinfoSensorElement {
const char *tag;
sensor::Sensor *sensor;
};
class TeleInfo : public PollingComponent, public uart::UARTDevice {
public:
TeleInfo(bool historical_mode);
void register_teleinfo_sensor(const char *tag, sensor::Sensor *sensors);
void loop() override;
void setup() override;
void update() override;
void dump_config() override;
std::vector<const TeleinfoSensorElement *> teleinfo_sensors_{};
protected:
uint32_t baud_rate_;
int checksum_area_end_;
int separator_;
char buf_[MAX_BUF_SIZE];
uint32_t buf_index_{0};
char tag_[MAX_TAG_SIZE];
char val_[MAX_VAL_SIZE];
enum State {
OFF,
ON,
START_FRAME_RECEIVED,
END_FRAME_RECEIVED,
} state_{OFF};
bool read_chars_until_(bool drop, uint8_t c);
bool check_crc_(const char *grp, const char *grp_end);
void publish_value_(std::string tag, std::string val);
};
} // namespace teleinfo
} // namespace esphome

View File

@ -11,6 +11,8 @@ from esphome.const import CONF_AUTO_MODE, CONF_AWAY_CONFIG, CONF_COOL_ACTION, CO
CONF_ID, CONF_IDLE_ACTION, CONF_OFF_MODE, CONF_SENSOR, CONF_SWING_BOTH_ACTION, \
CONF_SWING_HORIZONTAL_ACTION, CONF_SWING_OFF_ACTION, CONF_SWING_VERTICAL_ACTION
CODEOWNERS = ['@kbx81']
thermostat_ns = cg.esphome_ns.namespace('thermostat')
ThermostatClimate = thermostat_ns.class_('ThermostatClimate', climate.Climate, cg.Component)
ThermostatClimateTargetTempConfig = thermostat_ns.struct('ThermostatClimateTargetTempConfig')

View File

@ -10,10 +10,11 @@ import tzlocal
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.const import CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF_HOURS, \
from esphome.const import CONF_ID, CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF_HOURS, \
CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID, \
CONF_AT, CONF_SECOND, CONF_HOUR, CONF_MINUTE
from esphome.core import coroutine, coroutine_with_priority
from esphome.automation import Condition
_LOGGER = logging.getLogger(__name__)
@ -24,6 +25,7 @@ time_ns = cg.esphome_ns.namespace('time')
RealTimeClock = time_ns.class_('RealTimeClock', cg.Component)
CronTrigger = time_ns.class_('CronTrigger', automation.Trigger.template(), cg.Component)
ESPTime = time_ns.struct('ESPTime')
TimeHasTimeCondition = time_ns.class_('TimeHasTimeCondition', Condition)
def _tz_timedelta(td):
@ -138,6 +140,7 @@ def _parse_cron_int(value, special_mapping, message):
try:
return int(value)
except ValueError:
# pylint: disable=raise-missing-from
raise cv.Invalid(message.format(value))
@ -158,6 +161,7 @@ def _parse_cron_part(part, min_value, max_value, special_mapping):
try:
repeat_n = int(repeat)
except ValueError:
# pylint: disable=raise-missing-from
raise cv.Invalid("Repeat for '/' time expression must be an integer, got {}"
.format(repeat))
return set(range(offset_n, max_value + 1, repeat_n))
@ -326,3 +330,11 @@ def register_time(time_var, config):
def to_code(config):
cg.add_define('USE_TIME')
cg.add_global(time_ns.using)
@automation.register_condition('time.has_time', TimeHasTimeCondition, cv.Schema({
cv.GenerateID(): cv.use_id(RealTimeClock),
}))
def time_has_time_to_code(config, condition_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(condition_id, template_arg, paren)

View File

@ -2,6 +2,7 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/automation.h"
#include <stdlib.h>
#include <time.h>
#include <bitset>
@ -133,5 +134,14 @@ class RealTimeClock : public Component {
std::string timezone_{};
};
template<typename... Ts> class TimeHasTimeCondition : public Condition<Ts...> {
public:
TimeHasTimeCondition(RealTimeClock *parent) : parent_(parent) {}
bool check(Ts... x) override { return this->parent_->now().is_valid(); }
protected:
RealTimeClock *parent_;
};
} // namespace time
} // namespace esphome

View File

@ -0,0 +1,9 @@
"""
The TMP102 is a two-wire, serial output temperature
sensor available in a tiny SOT563 package. Requiring
no external components, the TMP102 is capable of
reading temperatures to a resolution of 0.0625°C.
https://www.sparkfun.com/datasheets/Sensors/Temperature/tmp102.pdf
"""

View File

@ -0,0 +1,31 @@
"""
The TMP102 is a two-wire, serial output temperature
sensor available in a tiny SOT563 package. Requiring
no external components, the TMP102 is capable of
reading temperatures to a resolution of 0.0625°C.
https://www.sparkfun.com/datasheets/Sensors/Temperature/tmp102.pdf
"""
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import CONF_ID, UNIT_CELSIUS, ICON_THERMOMETER
CODEOWNERS = ['@timsavage']
DEPENDENCIES = ['i2c']
tmp102_ns = cg.esphome_ns.namespace('tmp102')
TMP102Component = tmp102_ns.class_("TMP102Component", cg.PollingComponent, i2c.I2CDevice,
sensor.Sensor)
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
cv.GenerateID(): cv.declare_id(TMP102Component),
}).extend(cv.polling_component_schema("60s")).extend(i2c.i2c_device_schema(0x48))
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)

View File

@ -0,0 +1,47 @@
#include "tmp102.h"
#include "esphome/core/log.h"
namespace esphome {
namespace tmp102 {
static const char *TAG = "tmp102";
static const uint8_t TMP102_ADDRESS = 0x48;
static const uint8_t TMP102_REGISTER_TEMPERATURE = 0x00;
static const uint8_t TMP102_REGISTER_CONFIGURATION = 0x01;
static const uint8_t TMP102_REGISTER_LOW_LIMIT = 0x02;
static const uint8_t TMP102_REGISTER_HIGH_LIMIT = 0x03;
static const float TMP102_CONVERSION_FACTOR = 0.0625;
void TMP102Component::setup() { ESP_LOGCONFIG(TAG, "Setting up TMP102..."); }
void TMP102Component::dump_config() {
ESP_LOGCONFIG(TAG, "TMP102:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with TMP102 failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Temperature", this);
}
void TMP102Component::update() {
uint16_t raw_temperature;
if (!this->read_byte_16(TMP102_REGISTER_TEMPERATURE, &raw_temperature, 50)) {
this->status_set_warning();
return;
}
raw_temperature = raw_temperature >> 4;
float temperature = raw_temperature * TMP102_CONVERSION_FACTOR;
ESP_LOGD(TAG, "Got Temperature=%.1f°C", temperature);
this->publish_state(temperature);
this->status_clear_warning();
}
float TMP102Component::get_setup_priority() const { return setup_priority::DATA; }
} // namespace tmp102
} // namespace esphome

View File

@ -0,0 +1,22 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace tmp102 {
class TMP102Component : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
public:
/// Setup (reset) the sensor and check connection.
void setup() override;
void dump_config() override;
/// Update the sensor values (temperature)
void update() override;
float get_setup_priority() const override;
};
} // namespace tmp102
} // namespace esphome

View File

@ -1,16 +1,21 @@
from esphome.components import time
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.const import CONF_ID
from esphome.const import CONF_ID, CONF_TIME_ID
DEPENDENCIES = ['uart']
CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints"
tuya_ns = cg.esphome_ns.namespace('tuya')
Tuya = tuya_ns.class_('Tuya', cg.Component, uart.UARTDevice)
CONF_TUYA_ID = 'tuya_id'
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(Tuya),
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
cv.Optional(CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS): cv.ensure_list(cv.uint8_t),
}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA)
@ -18,3 +23,9 @@ def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield uart.register_uart_device(var, config)
if CONF_TIME_ID in config:
time_ = yield cg.get_variable(config[CONF_TIME_ID])
cg.add(var.set_time_id(time_))
if CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS in config:
for dp in config[CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS]:
cg.add(var.add_ignore_mcu_update_on_datapoints(dp))

View File

@ -10,18 +10,54 @@ CODEOWNERS = ['@jesserockz']
CONF_TARGET_TEMPERATURE_DATAPOINT = 'target_temperature_datapoint'
CONF_CURRENT_TEMPERATURE_DATAPOINT = 'current_temperature_datapoint'
CONF_TEMPERATURE_MULTIPLIER = 'temperature_multiplier'
CONF_CURRENT_TEMPERATURE_MULTIPLIER = 'current_temperature_multiplier'
CONF_TARGET_TEMPERATURE_MULTIPLIER = 'target_temperature_multiplier'
TuyaClimate = tuya_ns.class_('TuyaClimate', climate.Climate, cg.Component)
def validate_temperature_multipliers(value):
if CONF_TEMPERATURE_MULTIPLIER in value:
if (
CONF_CURRENT_TEMPERATURE_MULTIPLIER in value
or CONF_TARGET_TEMPERATURE_MULTIPLIER in value
):
raise cv.Invalid((f"Cannot have {CONF_TEMPERATURE_MULTIPLIER} at the same time as "
f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} and "
f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}"))
if (
CONF_CURRENT_TEMPERATURE_MULTIPLIER in value
and CONF_TARGET_TEMPERATURE_MULTIPLIER not in value
):
raise cv.Invalid((f"{CONF_TARGET_TEMPERATURE_MULTIPLIER} required if using "
f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER}"))
if (
CONF_TARGET_TEMPERATURE_MULTIPLIER in value
and CONF_CURRENT_TEMPERATURE_MULTIPLIER not in value
):
raise cv.Invalid((f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} required if using "
f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}"))
keys = (
CONF_TEMPERATURE_MULTIPLIER,
CONF_CURRENT_TEMPERATURE_MULTIPLIER,
CONF_TARGET_TEMPERATURE_MULTIPLIER
)
if all(multiplier not in value for multiplier in keys):
value[CONF_TEMPERATURE_MULTIPLIER] = 1.0
return value
CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(TuyaClimate),
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t,
cv.Optional(CONF_TARGET_TEMPERATURE_DATAPOINT): cv.uint8_t,
cv.Optional(CONF_CURRENT_TEMPERATURE_DATAPOINT): cv.uint8_t,
cv.Optional(CONF_TEMPERATURE_MULTIPLIER, default=1): cv.positive_float,
cv.Optional(CONF_TEMPERATURE_MULTIPLIER): cv.positive_float,
cv.Optional(CONF_CURRENT_TEMPERATURE_MULTIPLIER): cv.positive_float,
cv.Optional(CONF_TARGET_TEMPERATURE_MULTIPLIER): cv.positive_float,
}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(
CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT))
CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT), validate_temperature_multipliers)
def to_code(config):
@ -38,4 +74,9 @@ def to_code(config):
cg.add(var.set_target_temperature_id(config[CONF_TARGET_TEMPERATURE_DATAPOINT]))
if CONF_CURRENT_TEMPERATURE_DATAPOINT in config:
cg.add(var.set_current_temperature_id(config[CONF_CURRENT_TEMPERATURE_DATAPOINT]))
cg.add(var.set_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER]))
if CONF_TEMPERATURE_MULTIPLIER in config:
cg.add(var.set_target_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER]))
cg.add(var.set_current_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER]))
else:
cg.add(var.set_current_temperature_multiplier(config[CONF_CURRENT_TEMPERATURE_MULTIPLIER]))
cg.add(var.set_target_temperature_multiplier(config[CONF_TARGET_TEMPERATURE_MULTIPLIER]))

View File

@ -21,7 +21,7 @@ void TuyaClimate::setup() {
}
if (this->target_temperature_id_.has_value()) {
this->parent_->register_listener(*this->target_temperature_id_, [this](TuyaDatapoint datapoint) {
this->target_temperature = datapoint.value_int * this->temperature_multiplier_;
this->target_temperature = datapoint.value_int * this->target_temperature_multiplier_;
this->compute_state_();
this->publish_state();
ESP_LOGD(TAG, "MCU reported target temperature is: %.1f", this->target_temperature);
@ -29,7 +29,7 @@ void TuyaClimate::setup() {
}
if (this->current_temperature_id_.has_value()) {
this->parent_->register_listener(*this->current_temperature_id_, [this](TuyaDatapoint datapoint) {
this->current_temperature = datapoint.value_int * this->temperature_multiplier_;
this->current_temperature = datapoint.value_int * this->current_temperature_multiplier_;
this->compute_state_();
this->publish_state();
ESP_LOGD(TAG, "MCU reported current temperature is: %.1f", this->current_temperature);
@ -55,7 +55,7 @@ void TuyaClimate::control(const climate::ClimateCall &call) {
TuyaDatapoint datapoint{};
datapoint.id = *this->target_temperature_id_;
datapoint.type = TuyaDatapointType::INTEGER;
datapoint.value_int = (int) (this->target_temperature / this->temperature_multiplier_);
datapoint.value_int = (int) (this->target_temperature / this->target_temperature_multiplier_);
this->parent_->set_datapoint_value(datapoint);
ESP_LOGD(TAG, "Setting target temperature: %.1f", this->target_temperature);
}

View File

@ -18,8 +18,11 @@ class TuyaClimate : public climate::Climate, public Component {
void set_current_temperature_id(uint8_t current_temperature_id) {
this->current_temperature_id_ = current_temperature_id;
}
void set_temperature_multiplier(float temperature_multiplier) {
this->temperature_multiplier_ = temperature_multiplier;
void set_current_temperature_multiplier(float temperature_multiplier) {
this->current_temperature_multiplier_ = temperature_multiplier;
}
void set_target_temperature_multiplier(float temperature_multiplier) {
this->target_temperature_multiplier_ = temperature_multiplier;
}
void set_tuya_parent(Tuya *parent) { this->parent_ = parent; }
@ -40,7 +43,8 @@ class TuyaClimate : public climate::Climate, public Component {
optional<uint8_t> switch_id_{};
optional<uint8_t> target_temperature_id_{};
optional<uint8_t> current_temperature_id_{};
float temperature_multiplier_{1.0f};
float current_temperature_multiplier_{1.0f};
float target_temperature_multiplier_{1.0f};
};
} // namespace tuya

View File

@ -8,6 +8,7 @@ from .. import tuya_ns, CONF_TUYA_ID, Tuya
DEPENDENCIES = ['tuya']
CONF_DIMMER_DATAPOINT = "dimmer_datapoint"
CONF_MIN_VALUE_DATAPOINT = "min_value_datapoint"
TuyaLight = tuya_ns.class_('TuyaLight', light.LightOutput, cg.Component)
@ -15,6 +16,7 @@ CONFIG_SCHEMA = cv.All(light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend({
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaLight),
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t,
cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t,
cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t,
cv.Optional(CONF_MIN_VALUE): cv.int_,
cv.Optional(CONF_MAX_VALUE): cv.int_,
@ -34,6 +36,8 @@ def to_code(config):
if CONF_DIMMER_DATAPOINT in config:
cg.add(var.set_dimmer_id(config[CONF_DIMMER_DATAPOINT]))
if CONF_MIN_VALUE_DATAPOINT in config:
cg.add(var.set_min_value_datapoint_id(config[CONF_MIN_VALUE_DATAPOINT]))
if CONF_SWITCH_DATAPOINT in config:
cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT]))
if CONF_MIN_VALUE in config:

View File

@ -21,6 +21,13 @@ void TuyaLight::setup() {
call.perform();
});
}
if (min_value_datapoint_id_.has_value()) {
TuyaDatapoint datapoint{};
datapoint.id = *this->min_value_datapoint_id_;
datapoint.type = TuyaDatapointType::INTEGER;
datapoint.value_int = this->min_value_;
parent_->set_datapoint_value(datapoint);
}
}
void TuyaLight::dump_config() {

View File

@ -12,6 +12,9 @@ class TuyaLight : public Component, public light::LightOutput {
void setup() override;
void dump_config() override;
void set_dimmer_id(uint8_t dimmer_id) { this->dimmer_id_ = dimmer_id; }
void set_min_value_datapoint_id(uint8_t min_value_datapoint_id) {
this->min_value_datapoint_id_ = min_value_datapoint_id;
}
void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; }
void set_tuya_parent(Tuya *parent) { this->parent_ = parent; }
void set_min_value(uint32_t min_value) { min_value_ = min_value; }
@ -26,6 +29,7 @@ class TuyaLight : public Component, public light::LightOutput {
Tuya *parent_;
optional<uint8_t> dimmer_id_{};
optional<uint8_t> min_value_datapoint_id_{};
optional<uint8_t> switch_id_{};
uint32_t min_value_ = 0;
uint32_t max_value_ = 255;

View File

@ -6,9 +6,10 @@ namespace esphome {
namespace tuya {
static const char *TAG = "tuya";
static const int COMMAND_DELAY = 50;
void Tuya::setup() {
this->set_interval("heartbeat", 1000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
this->set_interval("heartbeat", 1000, [this] { this->schedule_empty_command_(TuyaCommandType::HEARTBEAT); });
}
void Tuya::loop() {
@ -19,6 +20,15 @@ void Tuya::loop() {
}
}
void Tuya::schedule_empty_command_(TuyaCommandType command) {
uint32_t delay = millis() - this->last_command_timestamp_;
if (delay > COMMAND_DELAY) {
send_empty_command_(command);
} else {
this->set_timeout(COMMAND_DELAY - delay, [this, command] { this->send_empty_command_(command); });
}
}
void Tuya::dump_config() {
ESP_LOGCONFIG(TAG, "Tuya:");
if (this->init_state_ != TuyaInitState::INIT_DONE) {
@ -110,6 +120,7 @@ void Tuya::handle_char_(uint8_t c) {
}
void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) {
this->last_command_timestamp_ = millis();
switch ((TuyaCommandType) command) {
case TuyaCommandType::HEARTBEAT:
ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]);
@ -119,7 +130,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
}
if (this->init_state_ == TuyaInitState::INIT_HEARTBEAT) {
this->init_state_ = TuyaInitState::INIT_PRODUCT;
this->send_empty_command_(TuyaCommandType::PRODUCT_QUERY);
this->schedule_empty_command_(TuyaCommandType::PRODUCT_QUERY);
}
break;
case TuyaCommandType::PRODUCT_QUERY: {
@ -138,7 +149,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
}
if (this->init_state_ == TuyaInitState::INIT_PRODUCT) {
this->init_state_ = TuyaInitState::INIT_CONF;
this->send_empty_command_(TuyaCommandType::CONF_QUERY);
this->schedule_empty_command_(TuyaCommandType::CONF_QUERY);
}
break;
}
@ -148,19 +159,27 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
gpio_reset_ = buffer[1];
}
if (this->init_state_ == TuyaInitState::INIT_CONF) {
// If we were following the spec to the letter we would send
// state updates until connected to both WiFi and API/MQTT.
// Instead we just claim to be connected immediately and move on.
uint8_t c[] = {0x04};
this->init_state_ = TuyaInitState::INIT_WIFI;
this->send_command_(TuyaCommandType::WIFI_STATE, c, 1);
// If mcu returned status gpio, then we can ommit sending wifi state
if (this->gpio_status_ != -1) {
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
} else {
this->init_state_ = TuyaInitState::INIT_WIFI;
this->set_timeout(COMMAND_DELAY, [this] {
// If we were following the spec to the letter we would send
// state updates until connected to both WiFi and API/MQTT.
// Instead we just claim to be connected immediately and move on.
uint8_t c[] = {0x04};
this->send_command_(TuyaCommandType::WIFI_STATE, c, 1);
});
}
}
break;
}
case TuyaCommandType::WIFI_STATE:
if (this->init_state_ == TuyaInitState::INIT_WIFI) {
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
}
break;
case TuyaCommandType::WIFI_RESET:
@ -185,6 +204,44 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
this->send_command_(TuyaCommandType::WIFI_TEST, c, 2);
break;
}
case TuyaCommandType::LOCAL_TIME_QUERY: {
#ifdef USE_TIME
if (this->time_id_.has_value()) {
auto time_id = *this->time_id_;
auto now = time_id->now();
if (now.is_valid()) {
this->set_timeout(COMMAND_DELAY, [this, now] {
uint8_t year = now.year - 2000;
uint8_t month = now.month;
uint8_t day_of_month = now.day_of_month;
uint8_t hour = now.hour;
uint8_t minute = now.minute;
uint8_t second = now.second;
// Tuya days starts from Monday, esphome uses Sunday as day 1
uint8_t day_of_week = now.day_of_week - 1;
if (day_of_week == 0) {
day_of_week = 7;
}
uint8_t c[] = {0x01, year, month, day_of_month, hour, minute, second, day_of_week};
this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8);
});
} else {
ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not valid");
// By spec we need to notify MCU that the time was not obtained
this->set_timeout(COMMAND_DELAY, [this] {
uint8_t c[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8);
});
}
} else {
ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not configured");
}
#else
ESP_LOGE(TAG, "LOCAL_TIME_QUERY is not handled");
#endif
break;
}
default:
ESP_LOGE(TAG, "invalid command (%02x) received", command);
}
@ -199,6 +256,14 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) {
datapoint.type = (TuyaDatapointType) buffer[1];
datapoint.value_uint = 0;
// drop update if datapoint is in ignore_mcu_datapoint_update list
for (auto i : this->ignore_mcu_update_on_datapoints_) {
if (datapoint.id == i) {
ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id);
return;
}
}
size_t data_size = (buffer[2] << 8) + buffer[3];
const uint8_t *data = buffer + 4;
size_t data_len = len - 4;

View File

@ -1,8 +1,13 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/components/uart/uart.h"
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#endif
namespace esphome {
namespace tuya {
@ -43,6 +48,7 @@ enum class TuyaCommandType : uint8_t {
DATAPOINT_REPORT = 0x07,
DATAPOINT_QUERY = 0x08,
WIFI_TEST = 0x0E,
LOCAL_TIME_QUERY = 0x1C,
};
enum class TuyaInitState : uint8_t {
@ -62,6 +68,12 @@ class Tuya : public Component, public uart::UARTDevice {
void dump_config() override;
void register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func);
void set_datapoint_value(TuyaDatapoint datapoint);
#ifdef USE_TIME
void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; }
#endif
void add_ignore_mcu_update_on_datapoints(uint8_t ignore_mcu_update_on_datapoints) {
this->ignore_mcu_update_on_datapoints_.push_back(ignore_mcu_update_on_datapoints);
}
protected:
void handle_char_(uint8_t c);
@ -71,14 +83,20 @@ class Tuya : public Component, public uart::UARTDevice {
void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len);
void send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len);
void send_empty_command_(TuyaCommandType command) { this->send_command_(command, nullptr, 0); }
void schedule_empty_command_(TuyaCommandType command);
#ifdef USE_TIME
optional<time::RealTimeClock *> time_id_{};
#endif
TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT;
int gpio_status_ = -1;
int gpio_reset_ = -1;
uint32_t last_command_timestamp_ = 0;
std::string product_ = "";
std::vector<TuyaDatapointListener> listeners_;
std::vector<TuyaDatapoint> datapoints_;
std::vector<uint8_t> rx_message_;
std::vector<uint8_t> ignore_mcu_update_on_datapoints_{};
};
} // namespace tuya

View File

@ -70,14 +70,8 @@ void ULN2003::write_step_(int32_t step) {
}
case ULN2003_STEP_MODE_HALF_STEP: {
// A, AB, B, BC, C, CD, D, DA
if (i == 0 || i == 2 || i == 7)
res |= 1 << 0;
if (i == 1 || i == 2 || i == 3)
res |= 1 << 1;
if (i == 3 || i == 4 || i == 5)
res |= 1 << 2;
if (i == 5 || i == 6 || i == 7)
res |= 1 << 3;
res |= 1 << (i >> 1);
res |= 1 << (((i + 1) >> 1) & 0x3);
break;
}
case ULN2003_STEP_MODE_WAVE_DRIVE: {

View File

@ -36,6 +36,7 @@ def to_code(config):
yield cg.register_component(var, config)
cg.add(paren.set_port(config[CONF_PORT]))
cg.add_define('WEBSERVER_PORT', config[CONF_PORT])
cg.add(var.set_css_url(config[CONF_CSS_URL]))
cg.add(var.set_js_url(config[CONF_JS_URL]))
if CONF_AUTH in config:

View File

@ -31,6 +31,7 @@ void write_row(AsyncResponseStream *stream, Nameable *obj, const std::string &kl
stream->print("</td><td></td><td>");
stream->print(action.c_str());
stream->print("</td>");
stream->print("</tr>");
}
UrlMatch match_url(const std::string &url, bool only_domain = false) {

View File

@ -60,7 +60,7 @@ STA_MANUAL_IP_SCHEMA = AP_MANUAL_IP_SCHEMA.extend({
cv.Optional(CONF_DNS2, default="0.0.0.0"): cv.ipv4,
})
EAP_AUTH_SCHEMA = cv.All(cv.only_on_esp32, cv.Schema({
EAP_AUTH_SCHEMA = cv.All(cv.Schema({
cv.Optional(CONF_IDENTITY): cv.string_strict,
cv.Optional(CONF_USERNAME): cv.string_strict,
cv.Optional(CONF_PASSWORD): cv.string_strict,

View File

@ -6,6 +6,9 @@
#include <utility>
#include <algorithm>
#ifdef ESPHOME_WIFI_WPA2_EAP
#include <wpa2_enterprise.h>
#endif
extern "C" {
#include "lwip/err.h"
@ -239,6 +242,52 @@ bool WiFiComponent::wifi_sta_connect_(WiFiAP ap) {
return false;
}
// setup enterprise authentication if required
#ifdef ESPHOME_WIFI_WPA2_EAP
if (ap.get_eap().has_value()) {
// note: all certificates and keys have to be null terminated. Lengths are appended by +1 to include \0.
EAPAuth eap = ap.get_eap().value();
ret = wifi_station_set_enterprise_identity((uint8_t *) eap.identity.c_str(), eap.identity.length());
if (ret) {
ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_identity failed! %d", ret);
}
int ca_cert_len = strlen(eap.ca_cert);
int client_cert_len = strlen(eap.client_cert);
int client_key_len = strlen(eap.client_key);
if (ca_cert_len) {
ret = wifi_station_set_enterprise_ca_cert((uint8_t *) eap.ca_cert, ca_cert_len + 1);
if (ret) {
ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_ca_cert failed! %d", ret);
}
}
// workout what type of EAP this is
// validation is not required as the config tool has already validated it
if (client_cert_len && client_key_len) {
// if we have certs, this must be EAP-TLS
ret = wifi_station_set_enterprise_cert_key((uint8_t *) eap.client_cert, client_cert_len + 1,
(uint8_t *) eap.client_key, client_key_len + 1,
(uint8_t *) eap.password.c_str(), strlen(eap.password.c_str()));
if (ret) {
ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_cert_key failed! %d", ret);
}
} else {
// in the absence of certs, assume this is username/password based
ret = wifi_station_set_enterprise_username((uint8_t *) eap.username.c_str(), eap.username.length());
if (ret) {
ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_username failed! %d", ret);
}
ret = wifi_station_set_enterprise_password((uint8_t *) eap.password.c_str(), eap.password.length());
if (ret) {
ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_password failed! %d", ret);
}
}
ret = wifi_station_set_wpa2_enterprise_auth(true);
if (ret) {
ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", ret);
}
}
#endif // ESPHOME_WIFI_WPA2_EAP
this->wifi_apply_hostname_();
ETS_UART_INTR_DISABLE();

View File

@ -18,9 +18,9 @@ _LOGGER = logging.getLogger(__name__)
def validate_cryptography_installed():
try:
import cryptography
except ImportError:
except ImportError as err:
raise cv.Invalid("This settings requires the cryptography python package. "
"Please install it with `pip install cryptography`")
"Please install it with `pip install cryptography`") from err
if cryptography.__version__[0] < '2':
raise cv.Invalid("Please update your python cryptography installation to least 2.x "

View File

@ -1,8 +1,8 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, esp32_ble_tracker
from esphome.const import CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \
UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, CONF_ID
from esphome.const import CONF_BATTERY_LEVEL, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \
UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID
DEPENDENCIES = ['esp32_ble_tracker']
AUTO_LOAD = ['xiaomi_ble']
@ -16,6 +16,7 @@ CONFIG_SCHEMA = cv.Schema({
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, 1),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0),
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)
@ -32,3 +33,6 @@ def to_code(config):
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))

View File

@ -12,6 +12,7 @@ void XiaomiLYWSD02::dump_config() {
ESP_LOGCONFIG(TAG, "Xiaomi LYWSD02");
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
}
bool XiaomiLYWSD02::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
@ -44,6 +45,8 @@ bool XiaomiLYWSD02::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
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);
success = true;
}

View File

@ -20,11 +20,13 @@ class XiaomiLYWSD02 : public Component, public esp32_ble_tracker::ESPBTDeviceLis
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; }
protected:
uint64_t address_;
sensor::Sensor *temperature_{nullptr};
sensor::Sensor *humidity_{nullptr};
sensor::Sensor *battery_level_{nullptr};
};
} // namespace xiaomi_lywsd02

View File

@ -5,6 +5,8 @@ from esphome.const import CONF_BATTERY_LEVEL, CONF_HUMIDITY, CONF_MAC_ADDRESS, C
UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID, \
CONF_BINDKEY
CODEOWNERS = ['@ahpohl']
DEPENDENCIES = ['esp32_ble_tracker']
AUTO_LOAD = ['xiaomi_ble']

View File

@ -3,7 +3,7 @@ import esphome.config_validation as cv
from esphome.components import sensor, binary_sensor, esp32_ble_tracker
from esphome.const import CONF_MAC_ADDRESS, CONF_ID, CONF_BINDKEY, \
CONF_DEVICE_CLASS, CONF_LIGHT, CONF_BATTERY_LEVEL, UNIT_PERCENT, ICON_BATTERY, \
CONF_IDLE_TIME, UNIT_MINUTE, ICON_TIMELAPSE
CONF_IDLE_TIME, CONF_ILLUMINANCE, UNIT_MINUTE, UNIT_LUX, ICON_TIMELAPSE, ICON_BRIGHTNESS_5
DEPENDENCIES = ['esp32_ble_tracker']
AUTO_LOAD = ['xiaomi_ble']
@ -19,6 +19,7 @@ CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({
cv.Optional(CONF_DEVICE_CLASS, default='motion'): binary_sensor.device_class,
cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema(UNIT_MINUTE, ICON_TIMELAPSE, 0),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 0),
cv.Optional(CONF_LIGHT): binary_sensor.BINARY_SENSOR_SCHEMA.extend({
cv.Optional(CONF_DEVICE_CLASS, default='light'): binary_sensor.device_class,
}),
@ -40,6 +41,9 @@ def to_code(config):
if CONF_BATTERY_LEVEL in config:
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
cg.add(var.set_battery_level(sens))
if CONF_ILLUMINANCE in config:
sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
cg.add(var.set_illuminance(sens))
if CONF_LIGHT in config:
sens = yield binary_sensor.new_binary_sensor(config[CONF_LIGHT])
cg.add(var.set_light(sens))

View File

@ -14,6 +14,7 @@ void XiaomiMJYD02YLA::dump_config() {
LOG_BINARY_SENSOR(" ", "Light", this->is_light_);
LOG_SENSOR(" ", "Idle Time", this->idle_time_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
}
bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
@ -47,6 +48,8 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
this->idle_time_->publish_state(*res->idle_time);
if (res->battery_level.has_value() && this->battery_level_ != nullptr)
this->battery_level_->publish_state(*res->battery_level);
if (res->illuminance.has_value() && this->illuminance_ != nullptr)
this->illuminance_->publish_state(*res->illuminance);
if (res->is_light.has_value() && this->is_light_ != nullptr)
this->is_light_->publish_state(*res->is_light);
if (res->has_motion.has_value())

View File

@ -24,6 +24,7 @@ class XiaomiMJYD02YLA : public Component,
float get_setup_priority() const override { return setup_priority::DATA; }
void set_idle_time(sensor::Sensor *idle_time) { idle_time_ = idle_time; }
void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }
void set_light(binary_sensor::BinarySensor *light) { is_light_ = light; }
protected:
@ -31,6 +32,7 @@ class XiaomiMJYD02YLA : public Component,
uint8_t bindkey_[16];
sensor::Sensor *idle_time_{nullptr};
sensor::Sensor *battery_level_{nullptr};
sensor::Sensor *illuminance_{nullptr};
binary_sensor::BinarySensor *is_light_{nullptr};
};

View File

@ -675,7 +675,7 @@ def _load_config(command_line_substitutions):
try:
config = yaml_util.load_yaml(CORE.config_path)
except EsphomeError as e:
raise InvalidYAMLError(e)
raise InvalidYAMLError(e) from e
CORE.raw_config = config
try:
@ -693,7 +693,7 @@ def load_config(command_line_substitutions):
try:
return _load_config(command_line_substitutions)
except vol.Invalid as err:
raise EsphomeError(f"Error while parsing config: {err}")
raise EsphomeError(f"Error while parsing config: {err}") from err
def line_info(obj, highlight=True):

View File

@ -11,7 +11,7 @@ from string import ascii_letters, digits
import voluptuous as vol
from esphome import core
from esphome.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, \
from esphome.const import ALLOWED_NAME_CHARS, CONF_AVAILABILITY, CONF_COMMAND_TOPIC, \
CONF_DISCOVERY, CONF_ID, CONF_INTERNAL, CONF_NAME, CONF_PAYLOAD_AVAILABLE, \
CONF_PAYLOAD_NOT_AVAILABLE, CONF_RETAIN, CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, CONF_TOPIC, \
CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, \
@ -41,8 +41,6 @@ ALLOW_EXTRA = vol.ALLOW_EXTRA
UNDEFINED = vol.UNDEFINED
RequiredFieldInvalid = vol.RequiredFieldInvalid
ALLOWED_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'
RESERVED_IDS = [
# C++ keywords http://en.cppreference.com/w/cpp/keyword
'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor', 'bool', 'break',
@ -227,6 +225,7 @@ def int_(value):
try:
return int(value, base)
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid(f"Expected integer, but cannot parse {value} as an integer")
@ -426,6 +425,7 @@ def time_period_str_colon(value):
try:
parsed = [int(x) for x in value.split(':')]
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid(TIME_PERIOD_ERROR.format(value))
if len(parsed) == 2:
@ -530,6 +530,7 @@ def time_of_day(value):
try:
date = datetime.strptime(value, '%H:%M:%S %p')
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid(f"Invalid time of day: {err}")
return {
@ -551,6 +552,7 @@ def mac_address(value):
try:
parts_int.append(int(part, 16))
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid("MAC Address parts must be hexadecimal values from 00 to FF")
return core.MACAddress(*parts_int)
@ -568,6 +570,7 @@ def bind_key(value):
try:
parts_int.append(int(part, 16))
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid("Bind key must be hex values from 00 to FF")
return ''.join(f'{part:02X}' for part in parts_int)
@ -619,6 +622,7 @@ _temperature_c = float_with_unit("temperature", "(°C|° C|°|C)?")
_temperature_k = float_with_unit("temperature", "(° K|° K|K)?")
_temperature_f = float_with_unit("temperature", "(°F|° F|F)?")
decibel = float_with_unit("decibel", "(dB|dBm|db|dbm)", optional_unit=True)
pressure = float_with_unit("pressure", "(bar|Bar)", optional_unit=True)
def temperature(value):
@ -689,8 +693,8 @@ def domain(value):
return value
try:
return str(ipv4(value))
except Invalid:
raise Invalid(f"Invalid domain: {value}")
except Invalid as err:
raise Invalid(f"Invalid domain: {value}") from err
def domain_name(value):
@ -742,8 +746,8 @@ def _valid_topic(value):
value = string(value)
try:
raw_value = value.encode('utf-8')
except UnicodeError:
raise Invalid("MQTT topic name/filter must be valid UTF-8 string.")
except UnicodeError as err:
raise Invalid("MQTT topic name/filter must be valid UTF-8 string.") from err
if not raw_value:
raise Invalid("MQTT topic name/filter must not be empty.")
if len(raw_value) > 65535:
@ -795,6 +799,7 @@ def mqtt_qos(value):
try:
value = int(value)
except (TypeError, ValueError):
# pylint: disable=raise-missing-from
raise Invalid(f"MQTT Quality of Service must be integer, got {value}")
return one_of(0, 1, 2)(value)
@ -839,6 +844,7 @@ def possibly_negative_percentage(value):
else:
value = float(value)
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid("invalid number")
if value > 1:
msg = "Percentage must not be higher than 100%."
@ -1009,6 +1015,7 @@ def dimensions(value):
try:
width, height = int(value[0]), int(value[1])
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid("Width and height dimensions must be integers")
if width <= 0 or height <= 0:
raise Invalid("Width and height must at least be 1")
@ -1170,8 +1177,8 @@ class OnlyWith(Optional):
# pylint: disable=unsupported-membership-test
if (self._component in CORE.raw_config or
(CONF_PACKAGES in CORE.raw_config and
self._component in
{list(x.keys())[0] for x in CORE.raw_config[CONF_PACKAGES].values()})):
self._component in
{list(x.keys())[0] for x in CORE.raw_config[CONF_PACKAGES].values()})):
return self._default
return vol.UNDEFINED

View File

@ -1,8 +1,8 @@
"""Constants used by esphome."""
MAJOR_VERSION = 1
MINOR_VERSION = 15
PATCH_VERSION = '3'
MINOR_VERSION = 16
PATCH_VERSION = '0b1'
__short_version__ = f'{MAJOR_VERSION}.{MINOR_VERSION}'
__version__ = f'{__short_version__}.{PATCH_VERSION}'
@ -10,7 +10,7 @@ ESP_PLATFORM_ESP32 = 'ESP32'
ESP_PLATFORM_ESP8266 = 'ESP8266'
ESP_PLATFORMS = [ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266]
ALLOWED_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'
ALLOWED_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_-'
# Lookup table from ESP32 arduino framework version to latest platformio
# package with that version
# See also https://github.com/platformio/platform-espressif32/releases
@ -122,6 +122,7 @@ CONF_COMPONENTS = 'components'
CONF_CONDITION = 'condition'
CONF_CONDITION_ID = 'condition_id'
CONF_CONDUCTIVITY = 'conductivity'
CONF_CONTRAST = 'contrast'
CONF_COOL_ACTION = 'cool_action'
CONF_COOL_MODE = 'cool_mode'
CONF_COUNT_MODE = 'count_mode'
@ -136,6 +137,7 @@ CONF_DALLAS_ID = 'dallas_id'
CONF_DATA = 'data'
CONF_DATA_PIN = 'data_pin'
CONF_DATA_PINS = 'data_pins'
CONF_DATA_RATE = 'data_rate'
CONF_DATA_TEMPLATE = 'data_template'
CONF_DAYS_OF_MONTH = 'days_of_month'
CONF_DAYS_OF_WEEK = 'days_of_week'
@ -158,6 +160,7 @@ CONF_DISCOVERY = 'discovery'
CONF_DISCOVERY_PREFIX = 'discovery_prefix'
CONF_DISCOVERY_RETAIN = 'discovery_retain'
CONF_DISTANCE = 'distance'
CONF_DITHER = 'dither'
CONF_DIV_RATIO = 'div_ratio'
CONF_DNS1 = 'dns1'
CONF_DNS2 = 'dns2'
@ -263,6 +266,7 @@ CONF_KEEP_ON_TIME = 'keep_on_time'
CONF_KEEPALIVE = 'keepalive'
CONF_KEY = 'key'
CONF_LAMBDA = 'lambda'
CONF_LENGTH = 'length'
CONF_LEVEL = 'level'
CONF_LG = 'lg'
CONF_LIBRARIES = 'libraries'
@ -525,6 +529,7 @@ CONF_TO = 'to'
CONF_TOLERANCE = 'tolerance'
CONF_TOPIC = 'topic'
CONF_TOPIC_PREFIX = 'topic_prefix'
CONF_TOTAL = "total"
CONF_TRANSITION_LENGTH = 'transition_length'
CONF_TRIGGER_ID = 'trigger_id'
CONF_TRIGGER_PIN = 'trigger_pin'
@ -568,6 +573,9 @@ CONF_WIND_SPEED = 'wind_speed'
CONF_WINDOW_SIZE = 'window_size'
CONF_ZERO = 'zero'
ENV_NOGITIGNORE = 'ESPHOME_NOGITIGNORE'
ENV_QUICKWIZARD = 'ESPHOME_QUICKWIZARD'
ICON_ACCELERATION = 'mdi:axis-arrow'
ICON_ACCELERATION_X = 'mdi:axis-x-arrow'
ICON_ACCELERATION_Y = 'mdi:axis-y-arrow'
@ -624,7 +632,7 @@ UNIT_DEGREES = '°'
UNIT_EMPTY = ''
UNIT_G = 'G'
UNIT_HECTOPASCAL = 'hPa'
UNIT_HERTZ = 'hz'
UNIT_HERTZ = 'Hz'
UNIT_KELVIN = 'K'
UNIT_KILOMETER = 'km'
UNIT_KILOMETER_PER_HOUR = 'km/h'
@ -641,6 +649,7 @@ UNIT_OHM = 'Ω'
UNIT_PARTS_PER_BILLION = 'ppb'
UNIT_PARTS_PER_MILLION = 'ppm'
UNIT_PERCENT = '%'
UNIT_PULSES = "pulses"
UNIT_PULSES_PER_MINUTE = 'pulses/min'
UNIT_SECOND = 's'
UNIT_STEPS = 'steps'

View File

@ -6,6 +6,7 @@
namespace esphome {
inline static uint8_t esp_scale8(uint8_t i, uint8_t scale) { return (uint16_t(i) * (1 + uint16_t(scale))) / 256; }
inline static uint8_t esp_scale(uint8_t i, uint8_t scale, uint8_t max_value = 255) { return (max_value * i / scale); }
struct Color {
union {
@ -30,6 +31,8 @@ struct Color {
uint8_t raw[4];
uint32_t raw_32;
};
enum ColorOrder : uint8_t { COLOR_ORDER_RGB = 0, COLOR_ORDER_BGR = 1, COLOR_ORDER_GRB = 2 };
enum ColorBitness : uint8_t { COLOR_BITNESS_888 = 0, COLOR_BITNESS_565 = 1, COLOR_BITNESS_332 = 2 };
inline Color() ALWAYS_INLINE : r(0), g(0), b(0), w(0) {} // NOLINT
inline Color(float red, float green, float blue) ALWAYS_INLINE : r(uint8_t(red * 255)),
g(uint8_t(green * 255)),
@ -43,6 +46,60 @@ struct Color {
g((colorcode >> 8) & 0xFF),
b((colorcode >> 0) & 0xFF),
w((colorcode >> 24) & 0xFF) {}
inline Color(uint32_t colorcode, ColorOrder color_order, ColorBitness color_bitness = ColorBitness::COLOR_BITNESS_888,
bool right_bit_aligned = true) {
uint8_t first_color, second_color, third_color;
uint8_t first_bits = 0;
uint8_t second_bits = 0;
uint8_t third_bits = 0;
switch (color_bitness) {
case COLOR_BITNESS_888:
first_bits = 8;
second_bits = 8;
third_bits = 8;
break;
case COLOR_BITNESS_565:
first_bits = 5;
second_bits = 6;
third_bits = 5;
break;
case COLOR_BITNESS_332:
first_bits = 3;
second_bits = 3;
third_bits = 2;
break;
}
first_color = right_bit_aligned ? esp_scale(((colorcode >> (second_bits + third_bits)) & ((1 << first_bits) - 1)),
((1 << first_bits) - 1))
: esp_scale(((colorcode >> 16) & 0xFF), (1 << first_bits) - 1);
second_color = right_bit_aligned
? esp_scale(((colorcode >> third_bits) & ((1 << second_bits) - 1)), ((1 << second_bits) - 1))
: esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1));
third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & 0xFF), ((1 << third_bits) - 1))
: esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1));
switch (color_order) {
case COLOR_ORDER_RGB:
this->r = first_color;
this->g = second_color;
this->b = third_color;
break;
case COLOR_ORDER_BGR:
this->b = first_color;
this->g = second_color;
this->r = third_color;
break;
case COLOR_ORDER_GRB:
this->g = first_color;
this->r = second_color;
this->b = third_color;
break;
}
}
inline bool is_on() ALWAYS_INLINE { return this->raw_32 != 0; }
inline Color &operator=(const Color &rhs) ALWAYS_INLINE {
this->r = rhs.r;
@ -140,7 +197,40 @@ struct Color {
Color fade_to_black(uint8_t amnt) { return *this * amnt; }
Color lighten(uint8_t delta) { return *this + delta; }
Color darken(uint8_t delta) { return *this - delta; }
uint8_t to_332(ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) const {
uint16_t red_color, green_color, blue_color;
red_color = esp_scale8(this->red, ((1 << 3) - 1));
green_color = esp_scale8(this->green, ((1 << 3) - 1));
blue_color = esp_scale8(this->blue, (1 << 2) - 1);
switch (color_order) {
case COLOR_ORDER_RGB:
return red_color << 5 | green_color << 2 | blue_color;
case COLOR_ORDER_BGR:
return blue_color << 6 | green_color << 3 | red_color;
case COLOR_ORDER_GRB:
return green_color << 5 | red_color << 2 | blue_color;
}
return 0;
}
uint16_t to_565(ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) const {
uint16_t red_color, green_color, blue_color;
red_color = esp_scale8(this->red, ((1 << 5) - 1));
green_color = esp_scale8(this->green, ((1 << 6) - 1));
blue_color = esp_scale8(this->blue, (1 << 5) - 1);
switch (color_order) {
case COLOR_ORDER_RGB:
return red_color << 11 | green_color << 5 | blue_color;
case COLOR_ORDER_BGR:
return blue_color << 11 | green_color << 5 | red_color;
case COLOR_ORDER_GRB:
return green_color << 10 | red_color << 5 | blue_color;
}
return 0;
}
uint32_t to_rgb_565() const {
uint32_t color565 =
(esp_scale8(this->red, 31) << 11) | (esp_scale8(this->green, 63) << 5) | (esp_scale8(this->blue, 31) << 0);

View File

@ -43,6 +43,10 @@ bool network_is_connected() {
bool mdns_setup;
#endif
#ifndef WEBSERVER_PORT
static const uint8_t WEBSERVER_PORT = 80;
#endif
#ifdef ARDUINO_ARCH_ESP8266
void network_setup_mdns(IPAddress address, int interface) {
// Latest arduino framework breaks mDNS for AP interface
@ -65,12 +69,15 @@ void network_setup_mdns(IPAddress address, int interface) {
MDNS.addServiceTxt("esphomelib", "tcp", "mac", get_mac_address().c_str());
} else {
#endif
// Publish "http" service if not using native API.
// Publish "http" service if not using native API nor the webserver component
// This is just to have *some* mDNS service so that .local resolution works
MDNS.addService("http", "tcp", 80);
MDNS.addService("http", "tcp", WEBSERVER_PORT);
MDNS.addServiceTxt("http", "tcp", "version", ESPHOME_VERSION);
#ifdef USE_API
}
#endif
#ifdef USE_PROMETHEUS
MDNS.addService("prometheus-http", "tcp", WEBSERVER_PORT);
#endif
}
void network_tick_mdns() {

View File

@ -359,7 +359,7 @@
<p>
Names must be all <strong>lowercase</strong> and <strong>must not contain any spaces</strong>!
Characters that are allowed are: <code class="inlinecode">a-z</code>,
<code class="inlinecode">0-9</code> and <code class="inlinecode">_</code>.
<code class="inlinecode">0-9</code>, <code class="inlinecode">_</code> and <code class="inlinecode">-</code>.
</p>
<div class="input-field col s12">

View File

@ -83,19 +83,19 @@ def receive_exactly(sock, amount, msg, expect, decode=True):
try:
data += recv_decode(sock, 1, decode=decode)
except OSError as err:
raise OTAError(f"Error receiving acknowledge {msg}: {err}")
raise OTAError(f"Error receiving acknowledge {msg}: {err}") from err
try:
check_error(data, expect)
except OTAError as err:
sock.close()
raise OTAError(f"Error {msg}: {err}")
raise OTAError(f"Error {msg}: {err}") from err
while len(data) < amount:
try:
data += recv_decode(sock, amount - len(data), decode=decode)
except OSError as err:
raise OTAError(f"Error receiving {msg}: {err}")
raise OTAError(f"Error receiving {msg}: {err}") from err
return data
@ -151,7 +151,7 @@ def send_check(sock, data, msg):
sock.sendall(data)
except OSError as err:
raise OTAError(f"Error sending {msg}: {err}")
raise OTAError(f"Error sending {msg}: {err}") from err
def perform_ota(sock, password, file_handle, filename):
@ -226,7 +226,7 @@ def perform_ota(sock, password, file_handle, filename):
sock.sendall(chunk)
except OSError as err:
sys.stderr.write('\n')
raise OTAError(f"Error sending data: {err}")
raise OTAError(f"Error sending data: {err}") from err
progress.update(offset / float(file_size))
progress.done()
@ -259,7 +259,7 @@ def run_ota_impl_(remote_host, remote_port, password, filename):
remote_host)
_LOGGER.error("(If this error persists, please set a static IP address: "
"https://esphome.io/components/wifi.html#manual-ips)")
raise OTAError(err)
raise OTAError(err) from err
_LOGGER.info(" -> %s", ip)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

View File

@ -89,7 +89,7 @@ def mkdir_p(path):
pass
else:
from esphome.core import EsphomeError
raise EsphomeError(f"Error creating directories {path}: {err}")
raise EsphomeError(f"Error creating directories {path}: {err}") from err
def is_ip_address(host):
@ -110,13 +110,13 @@ def _resolve_with_zeroconf(host):
try:
zc = Zeroconf()
except Exception:
except Exception as err:
raise EsphomeError("Cannot start mDNS sockets, is this a docker container without "
"host network mode?")
"host network mode?") from err
try:
info = zc.resolve_host(host + '.')
except Exception as err:
raise EsphomeError(f"Error resolving mDNS hostname: {err}")
raise EsphomeError(f"Error resolving mDNS hostname: {err}") from err
finally:
zc.close()
if info is None:
@ -142,7 +142,7 @@ def resolve_ip_address(host):
except OSError as err:
errs.append(str(err))
raise EsphomeError("Error resolving IP address: {}"
"".format(', '.join(errs)))
"".format(', '.join(errs))) from err
def get_bool_env(var, default=False):
@ -165,10 +165,10 @@ def read_file(path):
return f_handle.read()
except OSError as err:
from esphome.core import EsphomeError
raise EsphomeError(f"Error reading file {path}: {err}")
raise EsphomeError(f"Error reading file {path}: {err}") from err
except UnicodeDecodeError as err:
from esphome.core import EsphomeError
raise EsphomeError(f"Error reading file {path}: {err}")
raise EsphomeError(f"Error reading file {path}: {err}") from err
def _write_file(path: Union[Path, str], text: Union[str, bytes]):
@ -205,9 +205,9 @@ def _write_file(path: Union[Path, str], text: Union[str, bytes]):
def write_file(path: Union[Path, str], text: str):
try:
_write_file(path, text)
except OSError:
except OSError as err:
from esphome.core import EsphomeError
raise EsphomeError(f"Could not write file at {path}")
raise EsphomeError(f"Could not write file at {path}") from err
def write_file_if_changed(path: Union[Path, str], text: str):
@ -230,7 +230,7 @@ def copy_file_if_changed(src, dst):
shutil.copy(src, dst)
except OSError as err:
from esphome.core import EsphomeError
raise EsphomeError(f"Error copying file {src} to {dst}: {err}")
raise EsphomeError(f"Error copying file {src} to {dst}: {err}") from err
def list_starts_with(list_, sub):

View File

@ -67,7 +67,7 @@ def initialize(config, subscriptions, on_message, username, password, client_id)
port = int(config[CONF_MQTT][CONF_PORT])
client.connect(host, port)
except OSError as err:
raise EsphomeError(f"Cannot connect to MQTT broker: {err}")
raise EsphomeError(f"Cannot connect to MQTT broker: {err}") from err
try:
client.loop_forever()

View File

@ -22,13 +22,13 @@ def patch_structhash():
from os import makedirs
def patched_clean_build_dir(build_dir, *args):
from platformio import util
from platformio import fs
from platformio.project.helpers import get_project_dir
platformio_ini = join(get_project_dir(), "platformio.ini")
# if project's config is modified
if isdir(build_dir) and getmtime(platformio_ini) > getmtime(build_dir):
util.rmtree_(build_dir)
fs.rmtree(build_dir)
if not isdir(build_dir):
makedirs(build_dir)
@ -205,7 +205,7 @@ def process_stacktrace(config, line, backtrace_state):
# ESP8266 Exception type
match = re.match(STACKTRACE_ESP8266_EXCEPTION_TYPE_RE, line)
if match is not None:
code = match.group(1)
code = int(match.group(1))
_LOGGER.warning("Exception type: %s", ESP8266_EXCEPTION_CODES.get(code, 'unknown'))
# ESP8266 PC/EXCVADDR
@ -273,4 +273,9 @@ class IDEData:
if cc_path is None:
return None
# replace gcc at end with addr2line
# Windows
if cc_path.endswith('.exe'):
return cc_path[:-7] + 'addr2line.exe'
return cc_path[:-3] + 'addr2line'

View File

@ -32,6 +32,7 @@ class _Schema(vol.Schema):
try:
res = extra(res)
except vol.Invalid as err:
# pylint: disable=raise-missing-from
raise ensure_multiple_invalid(err)
return res

View File

@ -11,6 +11,7 @@ from esphome.helpers import color, get_bool_env, write_file
from esphome.pins import ESP32_BOARD_PINS, ESP8266_BOARD_PINS
from esphome.storage_json import StorageJSON, ext_storage_path
from esphome.util import safe_print
from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD
CORE_BIG = r""" _____ ____ _____ ______
/ ____/ __ \| __ \| ____|
@ -106,7 +107,7 @@ def wizard_write(path, **kwargs):
storage.save(storage_path)
if get_bool_env('ESPHOME_QUICKWIZARD'):
if get_bool_env(ENV_QUICKWIZARD):
def sleep(time):
pass
else:
@ -168,10 +169,11 @@ def wizard(path):
name = cv.valid_name(name)
break
except vol.Invalid:
safe_print(color("red", "Oh noes, \"{}\" isn't a valid name. Names can only include "
"numbers, lower-case letters and underscores.".format(name)))
safe_print(color("red", f"Oh noes, \"{name}\" isn't a valid name. Names can only "
f"include numbers, lower-case letters, underscores and "
f"hyphens."))
name = strip_accents(name).lower().replace(' ', '_')
name = ''.join(c for c in name if c in cv.ALLOWED_NAME_CHARS)
name = ''.join(c for c in name if c in ALLOWED_NAME_CHARS)
safe_print("Shall I use \"{}\" as the name instead?".format(color('cyan', name)))
sleep(0.5)
name = default_input("(name [{}]): ", name)

View File

@ -4,10 +4,11 @@ import re
from esphome.config import iter_components
from esphome.const import CONF_BOARD_FLASH_MODE, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS, \
HEADER_FILE_EXTENSIONS, SOURCE_FILE_EXTENSIONS, __version__, ARDUINO_VERSION_ESP8266
HEADER_FILE_EXTENSIONS, SOURCE_FILE_EXTENSIONS, __version__, ARDUINO_VERSION_ESP8266, \
ENV_NOGITIGNORE
from esphome.core import CORE, EsphomeError
from esphome.helpers import mkdir_p, read_file, write_file_if_changed, walk_files, \
copy_file_if_changed
copy_file_if_changed, get_bool_env
from esphome.storage_json import StorageJSON, storage_path
from esphome.pins import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS
@ -284,7 +285,8 @@ def write_platformio_project():
mkdir_p(CORE.build_path)
content = get_ini_content()
write_gitignore()
if not get_bool_env(ENV_NOGITIGNORE):
write_gitignore()
write_platformio_ini(content)

View File

@ -126,6 +126,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
try:
hash(key)
except TypeError:
# pylint: disable=raise-missing-from
raise yaml.constructor.ConstructorError(
f'Invalid key "{key}" (not hashable)', key_node.start_mark)
@ -297,7 +298,7 @@ def _load_yaml_internal(fname):
try:
return loader.get_single_data() or OrderedDict()
except yaml.YAMLError as exc:
raise EsphomeError(exc)
raise EsphomeError(exc) from exc
finally:
loader.dispose()

Some files were not shown because too many files have changed in this diff Show More