2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*.cmd
|
||||||
|
*.bat
|
||||||
BIN
Assets/Logo.webp
Normal file
BIN
Assets/Logo.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 261 KiB |
1887
ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml
Normal file
1887
ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml
Normal file
File diff suppressed because it is too large
Load Diff
61
README.md
61
README.md
@@ -1,2 +1,59 @@
|
|||||||
# TX-Ultimate-Easy
|
# TX Ultimate Easy
|
||||||
ESPHome based custom firmware for Sonoff TX Ultimate. Easy to setup.
|
|
||||||
|
|  | Welcome to TX Ultimate Easy! This project aims to provide custom firmware for Sonoff TX Ultimate devices based on ESPHome, making it easy for even the least tech-savvy users to configure and use. Our goal is to have all the configurations manageable directly through the Home Assistant UI or via blueprints, eliminating the need to edit YAML files manually. |
|
||||||
|
| --- | :-- |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Simple Setup:** Configure your Sonoff TX Ultimate through Home Assistant UI.
|
||||||
|
- **No YAML Editing:** All settings are adjustable without touching YAML files after the initial setup.
|
||||||
|
- **User-Friendly:** Designed for users of all technical levels.
|
||||||
|
- **Customizable:** Flexibility to adapt the firmware to your needs.
|
||||||
|
- **Seamless Integration:** Works flawlessly with Home Assistant.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Sonoff TX Ultimate device
|
||||||
|
- Home Assistant installed
|
||||||
|
- ESPHome add-on installed in Home Assistant
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
To be written.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- **Via Home Assistant UI:**
|
||||||
|
All device settings can be managed through the Home Assistant interface. No need to edit YAML files directly.
|
||||||
|
|
||||||
|
- **Using Blueprints:**
|
||||||
|
Import and use blueprints to quickly configure your device for common use cases.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
To be written.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We welcome contributions from the community. If you have ideas for new features, improvements, or bug fixes, please open an issue or submit a pull request.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
This project is highly inspired by these other projects:
|
||||||
|
- [SmartHome yourself - SONOFF TX Ultimate for ESPHome (Custom Component)](https://github.com/SmartHome-yourself/sonoff-tx-ultimate-for-esphome)
|
||||||
|
- [Un loco y su tecnología - Sonoff TX Ultimate with ESPHome | Absolute power](https://www.youtube.com/watch?v=58v8oqSQgXQ)
|
||||||
|
|
||||||
|
Thanks a lot for sharing all your discoveries and ideas.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you find this project helpful, consider supporting us through Buy Me a Coffee:
|
||||||
|
|
||||||
|
[](https://www.buymeacoffee.com/edwardfirmo)
|
||||||
|
|
||||||
|
|
||||||
|
<img src="Assets/Logo.webp" alt="TX Ultimate Easy Logo" width="100"/>
|
||||||
|
|||||||
19
TX-Ultimate-Easy-ESPHome.yaml
Normal file
19
TX-Ultimate-Easy-ESPHome.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
substitutions:
|
||||||
|
name: tx-ultimate-easy
|
||||||
|
friendly_name: TX Ultimate Easy
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: !secret wifi_ssid
|
||||||
|
password: !secret wifi_password
|
||||||
|
|
||||||
|
packages:
|
||||||
|
remote_package:
|
||||||
|
url: https://github.com/edwardtfn/TX-Ultimate-Easy
|
||||||
|
ref: dev
|
||||||
|
refresh: 1h
|
||||||
|
files:
|
||||||
|
- ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml
|
||||||
|
|
||||||
|
### Uncomment the lines bellow if you have issues with OTA and cannot flash via serial at this moment. ###
|
||||||
|
#esp32:
|
||||||
|
# flash_size: 4MB
|
||||||
105
components/tx_ultimate_easy/__init__.py
Normal file
105
components/tx_ultimate_easy/__init__.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import uart
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
from esphome import automation
|
||||||
|
|
||||||
|
CODEOWNERS = ["@edwardtfn"]
|
||||||
|
DEPENDENCIES = ['uart']
|
||||||
|
|
||||||
|
CONF_TX_ULTIMATE_EASY = "tx_ultimate_easy"
|
||||||
|
|
||||||
|
CONF_UART = "uart"
|
||||||
|
|
||||||
|
CONF_ON_TOUCH_EVENT = "on_touch_event"
|
||||||
|
CONF_ON_PRESS = "on_press"
|
||||||
|
CONF_ON_RELEASE = "on_release"
|
||||||
|
CONF_ON_SWIPE_LEFT = "on_swipe_left"
|
||||||
|
CONF_ON_SWIPE_RIGHT = "on_swipe_right"
|
||||||
|
CONF_ON_MULTI_TOUCH_RELEASE = "on_multi_touch_release"
|
||||||
|
CONF_ON_LONG_TOUCH_RELEASE = "on_long_touch_release"
|
||||||
|
|
||||||
|
tx_ultimate_easy_ns = cg.esphome_ns.namespace('tx_ultimate_easy')
|
||||||
|
TouchPoint = tx_ultimate_easy_ns.struct("TouchPoint")
|
||||||
|
|
||||||
|
TxUltimateTouch = tx_ultimate_easy_ns.class_(
|
||||||
|
'TxUltimateEasy', cg.Component, uart.UARTDevice)
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
|
cv.GenerateID(): cv.declare_id(TxUltimateTouch),
|
||||||
|
|
||||||
|
cv.Required(CONF_UART): cv.use_id(uart),
|
||||||
|
|
||||||
|
cv.Optional(CONF_ON_TOUCH_EVENT): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_ON_PRESS): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_ON_SWIPE_LEFT): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_ON_SWIPE_RIGHT): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_ON_MULTI_TOUCH_RELEASE): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_ON_LONG_TOUCH_RELEASE): automation.validate_automation(single=True),
|
||||||
|
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
async def register_tx_ultimate_easy(var, config):
|
||||||
|
uart_component = await cg.get_variable(config[CONF_UART])
|
||||||
|
cg.add(var.set_uart_component(uart_component))
|
||||||
|
|
||||||
|
if CONF_ON_TOUCH_EVENT in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_trigger_touch_event(),
|
||||||
|
[(TouchPoint, "touch")],
|
||||||
|
config[CONF_ON_TOUCH_EVENT],
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_ON_PRESS in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_trigger_touch(),
|
||||||
|
[(TouchPoint, "touch")],
|
||||||
|
config[CONF_ON_PRESS],
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_ON_RELEASE in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_trigger_release(),
|
||||||
|
[(TouchPoint, "touch")],
|
||||||
|
config[CONF_ON_RELEASE],
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_ON_SWIPE_LEFT in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_trigger_swipe_left(),
|
||||||
|
[(TouchPoint, "touch")],
|
||||||
|
config[CONF_ON_SWIPE_LEFT],
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_ON_SWIPE_RIGHT in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_trigger_swipe_right(),
|
||||||
|
[(TouchPoint, "touch")],
|
||||||
|
config[CONF_ON_SWIPE_RIGHT],
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_ON_MULTI_TOUCH_RELEASE in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_trigger_multi_touch_release(),
|
||||||
|
[(TouchPoint, "touch")],
|
||||||
|
config[CONF_ON_MULTI_TOUCH_RELEASE],
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_ON_LONG_TOUCH_RELEASE in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_trigger_long_touch_release(),
|
||||||
|
[(TouchPoint, "touch")],
|
||||||
|
config[CONF_ON_LONG_TOUCH_RELEASE],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await uart.register_uart_device(var, config)
|
||||||
|
await register_tx_ultimate_easy(var, config)
|
||||||
154
components/tx_ultimate_easy/tx_ultimate_easy.cpp
Normal file
154
components/tx_ultimate_easy/tx_ultimate_easy.cpp
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "tx_ultimate_easy.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace tx_ultimate_easy {
|
||||||
|
static const char *TAG = "tx_ultimate_easy";
|
||||||
|
|
||||||
|
void TxUltimateEasy::setup() {
|
||||||
|
ESP_LOGI("log", "%s", "Tx Ultimate Easy is initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxUltimateEasy::loop() {
|
||||||
|
bool found = false;
|
||||||
|
int uart_received_bytes[15] = {};
|
||||||
|
int byte = -1;
|
||||||
|
int i = 0;
|
||||||
|
while (this->available()) {
|
||||||
|
byte = this->read();
|
||||||
|
if (byte == 170) {
|
||||||
|
handle_touch(uart_received_bytes);
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
uart_received_bytes[i] = byte;
|
||||||
|
i++;
|
||||||
|
if (byte != 0) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (found) handle_touch(uart_received_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxUltimateEasy::handle_touch(int uart_received_bytes[]) {
|
||||||
|
ESP_LOGVV(TAG, "------------");
|
||||||
|
ESP_LOGVV(TAG, "- UART-Log -");
|
||||||
|
ESP_LOGVV(TAG, "------------");
|
||||||
|
for (int i = 0; i < 15; i++) ESP_LOGVV(TAG, "UART - Log - Byte[%i]: %i", i, uart_received_bytes[i]);
|
||||||
|
if (is_valid_data(uart_received_bytes)) send_touch_(get_touch_point(uart_received_bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxUltimateEasy::dump_config() { ESP_LOGCONFIG(TAG, "Tx Ultimate Easy"); }
|
||||||
|
|
||||||
|
void TxUltimateEasy::send_touch_(TouchPoint tp) {
|
||||||
|
this->trigger_touch_event_.trigger(tp);
|
||||||
|
switch (tp.state) {
|
||||||
|
case TOUCH_STATE_RELEASE:
|
||||||
|
if (tp.x >= 17) {
|
||||||
|
tp.x = tp.x - 16;
|
||||||
|
ESP_LOGV(TAG, "Long touch - Released (x=%d)", tp.x);
|
||||||
|
this->trigger_long_touch_release_.trigger(tp);
|
||||||
|
} else {
|
||||||
|
ESP_LOGV(TAG, "Touch - Released (x=%d)", tp.x);
|
||||||
|
this->trigger_release_.trigger(tp);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOUCH_STATE_PRESS:
|
||||||
|
ESP_LOGV(TAG, "Touch - Pressed (x=%d)", tp.x);
|
||||||
|
this->trigger_touch_.trigger(tp);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOUCH_STATE_SWIPE_LEFT:
|
||||||
|
ESP_LOGV(TAG, "Swipe - Left (x=%d)", tp.x);
|
||||||
|
this->trigger_swipe_left_.trigger(tp);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOUCH_STATE_SWIPE_RIGHT:
|
||||||
|
ESP_LOGV(TAG, "Swipe - Right (x=%d)", tp.x);
|
||||||
|
this->trigger_swipe_right_.trigger(tp);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOUCH_STATE_MULTI_TOUCH:
|
||||||
|
ESP_LOGV(TAG, "Multi touch - Released");
|
||||||
|
this->trigger_multi_touch_release_.trigger(tp);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TxUltimateEasy::is_valid_data(int uart_received_bytes[]) {
|
||||||
|
bool valid = true;
|
||||||
|
if (!(uart_received_bytes[0] == 170 && uart_received_bytes[1] == 85 && uart_received_bytes[2] == 1 && uart_received_bytes[3] == 2)) return false;
|
||||||
|
|
||||||
|
int state = get_touch_state(uart_received_bytes);
|
||||||
|
if (state != TOUCH_STATE_PRESS &&
|
||||||
|
state != TOUCH_STATE_RELEASE &&
|
||||||
|
state != TOUCH_STATE_SWIPE_LEFT &&
|
||||||
|
state != TOUCH_STATE_SWIPE_RIGHT &&
|
||||||
|
state != TOUCH_STATE_MULTI_TOUCH)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (uart_received_bytes[6] < 0 && state != TOUCH_STATE_MULTI_TOUCH) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TxUltimateEasy::get_touch_position_x(int uart_received_bytes[]) {
|
||||||
|
int state = uart_received_bytes[4];
|
||||||
|
switch (state) {
|
||||||
|
case TOUCH_STATE_RELEASE:
|
||||||
|
case TOUCH_STATE_MULTI_TOUCH:
|
||||||
|
case TOUCH_STATE_SWIPE_LEFT:
|
||||||
|
case TOUCH_STATE_SWIPE_RIGHT:
|
||||||
|
return uart_received_bytes[5];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return uart_received_bytes[6];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TxUltimateEasy::get_touch_state(int uart_received_bytes[]) {
|
||||||
|
int state = uart_received_bytes[4];
|
||||||
|
if (state == TOUCH_STATE_PRESS && uart_received_bytes[5] != 0) state = TOUCH_STATE_RELEASE;
|
||||||
|
if (state == TOUCH_STATE_RELEASE && uart_received_bytes[5] == TOUCH_STATE_MULTI_TOUCH) state = TOUCH_STATE_MULTI_TOUCH;
|
||||||
|
if (state == TOUCH_STATE_SWIPE) {
|
||||||
|
if (uart_received_bytes[5] == TOUCH_STATE_SWIPE_RIGHT) state = TOUCH_STATE_SWIPE_RIGHT;
|
||||||
|
else if (uart_received_bytes[5] == TOUCH_STATE_SWIPE_LEFT) state = TOUCH_STATE_SWIPE_LEFT;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchPoint TxUltimateEasy::get_touch_point(int uart_received_bytes[]) {
|
||||||
|
TouchPoint tp;
|
||||||
|
tp.x = get_touch_position_x(uart_received_bytes);
|
||||||
|
tp.state = get_touch_state(uart_received_bytes);
|
||||||
|
tp.state_str = "Unknown";
|
||||||
|
switch (tp.state) {
|
||||||
|
case TOUCH_STATE_RELEASE:
|
||||||
|
tp.state_str = "RELEASE";
|
||||||
|
break;
|
||||||
|
case TOUCH_STATE_PRESS:
|
||||||
|
tp.state_str = "PRESS";
|
||||||
|
break;
|
||||||
|
case TOUCH_STATE_SWIPE:
|
||||||
|
tp.state_str = "SWIPE";
|
||||||
|
break;
|
||||||
|
case TOUCH_STATE_MULTI_TOUCH:
|
||||||
|
tp.state_str = "MULTI_TOUCH";
|
||||||
|
break;
|
||||||
|
case TOUCH_STATE_SWIPE_RIGHT:
|
||||||
|
tp.state_str = "SWIPE_RIGHT";
|
||||||
|
break;
|
||||||
|
case TOUCH_STATE_SWIPE_LEFT:
|
||||||
|
tp.state_str = "SWIPE_LEFT";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return tp;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tx_ultimate_easy
|
||||||
|
} // namespace esphome
|
||||||
62
components/tx_ultimate_easy/tx_ultimate_easy.h
Normal file
62
components/tx_ultimate_easy/tx_ultimate_easy.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/uart/uart.h"
|
||||||
|
#include "esphome/components/uart/uart_component.h"
|
||||||
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
#include "esphome/components/switch/switch.h"
|
||||||
|
#include "esphome/components/light/addressable_light.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace tx_ultimate_easy {
|
||||||
|
const static uint8_t TOUCH_STATE_RELEASE = 1;
|
||||||
|
const static uint8_t TOUCH_STATE_PRESS = 2;
|
||||||
|
const static uint8_t TOUCH_STATE_SWIPE = 3;
|
||||||
|
const static uint8_t TOUCH_STATE_MULTI_TOUCH = 11;
|
||||||
|
const static uint8_t TOUCH_STATE_SWIPE_RIGHT = 12;
|
||||||
|
const static uint8_t TOUCH_STATE_SWIPE_LEFT = 13;
|
||||||
|
|
||||||
|
struct TouchPoint {
|
||||||
|
int8_t x = -1;
|
||||||
|
int8_t state = -1;
|
||||||
|
std::string state_str = "Unknown";
|
||||||
|
};
|
||||||
|
|
||||||
|
class TxUltimateEasy : public uart::UARTDevice, public Component {
|
||||||
|
public:
|
||||||
|
Trigger<TouchPoint> *get_trigger_touch_event() { return &this->trigger_touch_event_; }
|
||||||
|
Trigger<TouchPoint> *get_trigger_touch() { return &this->trigger_touch_; }
|
||||||
|
Trigger<TouchPoint> *get_trigger_release() { return &this->trigger_release_; }
|
||||||
|
Trigger<TouchPoint> *get_trigger_swipe_left() { return &this->trigger_swipe_left_; }
|
||||||
|
Trigger<TouchPoint> *get_trigger_swipe_right() { return &this->trigger_swipe_right_; }
|
||||||
|
Trigger<TouchPoint> *get_trigger_multi_touch_release() { return &this->trigger_multi_touch_release_; }
|
||||||
|
Trigger<TouchPoint> *get_trigger_long_touch_release() { return &this->trigger_long_touch_release_; }
|
||||||
|
|
||||||
|
void set_uart_component(esphome::uart::UARTComponent *uart_component) { this->set_uart_parent(uart_component); }
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void loop() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void send_touch_(TouchPoint tp);
|
||||||
|
void handle_touch(int bytes[]);
|
||||||
|
|
||||||
|
TouchPoint get_touch_point(int bytes[]);
|
||||||
|
bool is_valid_data(int bytes[]);
|
||||||
|
int get_touch_position_x(int bytes[]);
|
||||||
|
int get_touch_state(int bytes[]);
|
||||||
|
|
||||||
|
Trigger<TouchPoint> trigger_touch_event_;
|
||||||
|
Trigger<TouchPoint> trigger_touch_;
|
||||||
|
Trigger<TouchPoint> trigger_release_;
|
||||||
|
Trigger<TouchPoint> trigger_swipe_left_;
|
||||||
|
Trigger<TouchPoint> trigger_swipe_right_;
|
||||||
|
Trigger<TouchPoint> trigger_multi_touch_release_;
|
||||||
|
Trigger<TouchPoint> trigger_long_touch_release_;
|
||||||
|
|
||||||
|
}; // class TxUltimateEasy
|
||||||
|
|
||||||
|
} // namespace tx_ultimate_easy
|
||||||
|
} // namespace esphome
|
||||||
Reference in New Issue
Block a user