Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XL9535 I/O Expander #4899

Merged
merged 9 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -316,4 +316,5 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/* @nielsnl68 @numo68
73 changes: 73 additions & 0 deletions esphome/components/xl9535/__init__.py
@@ -0,0 +1,73 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import (
CONF_ID,
CONF_INPUT,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
CONF_OUTPUT,
)
from esphome import pins

CONF_XL9535 = "xl9535"

DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@mreditor97"]

xl9535_ns = cg.esphome_ns.namespace(CONF_XL9535)

XL9535Component = xl9535_ns.class_("XL9535Component", cg.Component, i2c.I2CDevice)
XL9535GPIOPin = xl9535_ns.class_("XL9535GPIOPin", cg.GPIOPin)

MULTI_CONF = True
CONFIG_SCHEMA = (
cv.Schema({cv.Required(CONF_ID): cv.declare_id(XL9535Component)})
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x20))
)


async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)


def validate_mode(mode):
if not (mode[CONF_INPUT] or mode[CONF_OUTPUT]) or (
mode[CONF_INPUT] and mode[CONF_OUTPUT]
):
raise cv.Invalid("Mode must be either a input or a output")
return mode


XL9535_PIN_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(XL9535GPIOPin),
cv.Required(CONF_XL9535): cv.use_id(XL9535Component),
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15),
cv.Optional(CONF_MODE, default={}): cv.All(
{
cv.Optional(CONF_INPUT, default=False): cv.boolean,
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
},
validate_mode,
),
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
}
)


@pins.PIN_SCHEMA_REGISTRY.register(CONF_XL9535, XL9535_PIN_SCHEMA)
async def xl9535_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_XL9535])

cg.add(var.set_parent(parent))
cg.add(var.set_pin(config[CONF_NUMBER]))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))

return var
122 changes: 122 additions & 0 deletions esphome/components/xl9535/xl9535.cpp
@@ -0,0 +1,122 @@
#include "xl9535.h"
#include "esphome/core/log.h"

namespace esphome {
namespace xl9535 {

static const char *const TAG = "xl9535";

void XL9535Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up XL9535...");

// Check to see if the device can read from the register
uint8_t port = 0;
if (this->read_register(XL9535_INPUT_PORT_0_REGISTER, &port, 1) != i2c::ERROR_OK) {
this->mark_failed();
return;
}
}

void XL9535Component::dump_config() {
ESP_LOGCONFIG(TAG, "XL9535:");
LOG_I2C_DEVICE(this);

if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with XL9535 failed!");
}
}

bool XL9535Component::digital_read(uint8_t pin) {
bool state = false;
uint8_t port = 0;

if (pin > 7) {
if (this->read_register(XL9535_INPUT_PORT_1_REGISTER, &port, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return state;
}

state = (port & (pin - 10)) != 0;
} else {
if (this->read_register(XL9535_INPUT_PORT_0_REGISTER, &port, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return state;
}

state = (port & pin) != 0;
}

this->status_clear_warning();
return state;
}

void XL9535Component::digital_write(uint8_t pin, bool value) {
uint8_t port = 0;
uint8_t register_data = 0;

if (pin > 7) {
if (this->read_register(XL9535_OUTPUT_PORT_1_REGISTER, &register_data, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}

register_data = register_data & (~(1 << (pin - 10)));
port = register_data | value << (pin - 10);

if (this->write_register(XL9535_OUTPUT_PORT_1_REGISTER, &port, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
} else {
if (this->read_register(XL9535_OUTPUT_PORT_0_REGISTER, &register_data, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
register_data = register_data & (~(1 << pin));
port = register_data | value << pin;

if (this->write_register(XL9535_OUTPUT_PORT_0_REGISTER, &port, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
}

this->status_clear_warning();
}

void XL9535Component::pin_mode(uint8_t pin, gpio::Flags mode) {
uint8_t port = 0;

if (pin > 7) {
this->read_register(XL9535_CONFIG_PORT_1_REGISTER, &port, 1);

if (mode == gpio::FLAG_INPUT) {
port = port | (1 << (pin - 10));
} else if (mode == gpio::FLAG_OUTPUT) {
port = port & (~(1 << (pin - 10)));
}

this->write_register(XL9535_CONFIG_PORT_1_REGISTER, &port, 1);
} else {
this->read_register(XL9535_CONFIG_PORT_0_REGISTER, &port, 1);

if (mode == gpio::FLAG_INPUT) {
port = port | (1 << pin);
} else if (mode == gpio::FLAG_OUTPUT) {
port = port & (~(1 << pin));
}

this->write_register(XL9535_CONFIG_PORT_0_REGISTER, &port, 1);
}
}

void XL9535GPIOPin::setup() { this->pin_mode(this->flags_); }

std::string XL9535GPIOPin::dump_summary() const { return str_snprintf("%u via XL9535", 15, this->pin_); }

void XL9535GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
bool XL9535GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void XL9535GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }

} // namespace xl9535
} // namespace esphome
54 changes: 54 additions & 0 deletions esphome/components/xl9535/xl9535.h
@@ -0,0 +1,54 @@
#pragma once

#include "esphome/core/component.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/hal.h"

namespace esphome {
namespace xl9535 {

enum {
XL9535_INPUT_PORT_0_REGISTER = 0x00,
XL9535_INPUT_PORT_1_REGISTER = 0x01,
XL9535_OUTPUT_PORT_0_REGISTER = 0x02,
XL9535_OUTPUT_PORT_1_REGISTER = 0x03,
XL9535_INVERSION_PORT_0_REGISTER = 0x04,
XL9535_INVERSION_PORT_1_REGISTER = 0x05,
XL9535_CONFIG_PORT_0_REGISTER = 0x06,
XL9535_CONFIG_PORT_1_REGISTER = 0x07,
};

class XL9535Component : public Component, public i2c::I2CDevice {
public:
bool digital_read(uint8_t pin);
void digital_write(uint8_t pin, bool value);
void pin_mode(uint8_t pin, gpio::Flags mode);

void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
};

class XL9535GPIOPin : public GPIOPin {
public:
void set_parent(XL9535Component *parent) { this->parent_ = parent; }
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_inverted(bool inverted) { this->inverted_ = inverted; }
void set_flags(gpio::Flags flags) { this->flags_ = flags; }

void setup() override;
std::string dump_summary() const override;
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;

protected:
XL9535Component *parent_;

uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
};

} // namespace xl9535
} // namespace esphome
13 changes: 13 additions & 0 deletions tests/test4.yaml
Expand Up @@ -384,6 +384,15 @@ binary_sensor:
pullup: true
inverted: false

- platform: gpio
name: XL9535 Pin 0
pin:
xl9535: xl9535_hub
number: 0
mode:
input: true
inverted: false

climate:
- platform: tuya
id: tuya_climate
Expand Down Expand Up @@ -745,3 +754,7 @@ voice_assistant:
max6956:
- id: max6956_1
address: 0x40

xl9535:
- id: xl9535_hub
address: 0x20