Modified to work with maidsite
This commit is contained in:
parent
5533a35801
commit
b851b63f2c
|
|
@ -1 +1 @@
|
|||
**/__pycache__/**
|
||||
**/__pycache__/**
|
||||
|
|
|
|||
4
.vimrc
4
.vimrc
|
|
@ -1,4 +0,0 @@
|
|||
if SpmSetProject("esphome_components") == 0
|
||||
nmap <leader>h yy'fpwd2f:f)lC;<esc>==mf^w
|
||||
nmap <leader>s 02w"aye2b02w"byej/::a
Vf{%jd/::b
f{%jpzz^wj
|
||||
endif
|
||||
12
README.md
12
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Rocka84's ESPHome Components
|
||||
|
||||
## Available Components
|
||||
|
||||
* [Jiecang Desk Controller](components/jiecang_desk_controller/)
|
||||
|
||||
# ElVit's ESPHome Components
|
||||
|
||||
## Available Components
|
||||
|
||||
* [Maidsite Desk Controller](components/maidsite_desk_controller/)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,184 +0,0 @@
|
|||
# ESPHome Jiecang Desk Controller
|
||||
|
||||
[ESPHome](https://esphome.io/) component for controlling Jiecang desk controllers via their serial protocol.
|
||||
|
||||

|
||||
|
||||
Attention: I only have one [RJ12 model](https://www.jiecang.com/product/jcb35m11c.html) to test this
|
||||
but it's likely that other Jiecang controllers are supported as their serial protocol should be compatible.
|
||||
|
||||
## Usage
|
||||
|
||||
### What you need
|
||||
|
||||
* ESPHome compatible microcontroller
|
||||
* depending on your model of desk controller
|
||||
* cable with RJ12 connector (phone cable, RJ11 may also work)
|
||||
* cable with RJ45 connector (network cable)
|
||||
|
||||
### Wiring
|
||||
|
||||
#### RJ12
|
||||
|
||||
Please double check this for your specific model!
|
||||
|
||||
pin | function
|
||||
----|---------
|
||||
1 | NC (pulled up)
|
||||
2 | GND
|
||||
3 | TX
|
||||
4 | VCC
|
||||
5 | RX
|
||||
6 | NC (pulled up)
|
||||
|
||||
#### RJ45
|
||||
|
||||
**Untested** and only for reference!
|
||||
|
||||
pin | function
|
||||
----|---------
|
||||
1 | HS3 [^1]
|
||||
2 | TX
|
||||
3 | GND
|
||||
4 | RX
|
||||
5 | VCC
|
||||
6 | HS2 [^1]
|
||||
7 | HS1 [^1]
|
||||
8 | HS0 [^1]
|
||||
|
||||
[^1]: not used here
|
||||
|
||||
#### microcontroller
|
||||
|
||||
ESP | desk
|
||||
----|-----
|
||||
GND | GND
|
||||
5V | VCC
|
||||
RX | TX
|
||||
TX | RX
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
```yaml
|
||||
esphome:
|
||||
...
|
||||
on_boot:
|
||||
# This script is required to initialize the following sensors:
|
||||
# height_pct, height_min, height_max, position1 - position4
|
||||
# You can skip this if you don't use those.
|
||||
priority: 0 # when mostly everything else is done
|
||||
then:
|
||||
- lambda: "id(my_desk).request_physical_limits();"
|
||||
- delay: 0.1s
|
||||
- lambda: "id(my_desk).request_limits();"
|
||||
- delay: 0.1s
|
||||
- lambda: "id(my_desk).request_settings();"
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/Rocka84/esphome_components/
|
||||
components: [ jiecang_desk_controller ]
|
||||
|
||||
uart:
|
||||
id: uart_bus
|
||||
tx_pin: TX
|
||||
rx_pin: RX
|
||||
baud_rate: 9600
|
||||
|
||||
logger:
|
||||
baud_rate: 0 # disable logging over uart, required when using the RX/TX pins for the controller
|
||||
|
||||
jiecang_desk_controller:
|
||||
id: my_desk
|
||||
sensors:
|
||||
height:
|
||||
name: "Height"
|
||||
numbers:
|
||||
height:
|
||||
name: "Height"
|
||||
buttons:
|
||||
move_up:
|
||||
name: "Move up"
|
||||
move_down:
|
||||
name: "Move down"
|
||||
stop:
|
||||
name: "Stop"
|
||||
position1:
|
||||
name: "Position 1"
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Step down"
|
||||
on_press:
|
||||
lambda: "id(my_desk).goto_height(84.0);"
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
* basic, sufficient for most people: [example_basic.yaml](example_basic.yaml)
|
||||
* full with all features in use: [example_full.yaml](example_full.yaml)
|
||||
|
||||
### Features
|
||||
|
||||
#### sensors
|
||||
|
||||
sensor | description
|
||||
-----------|----------------------------
|
||||
height | current height of the desk
|
||||
height_pct | height in percent
|
||||
height_min | minimal height
|
||||
height_max | maximal height
|
||||
position1 | 1st stored height
|
||||
position2 | 2nd stored height
|
||||
position3 | 3rd stored height
|
||||
position4 | 4th stored height
|
||||
|
||||
#### number entities
|
||||
|
||||
Hold current values read from the desk and set values to desk when changed.
|
||||
|
||||
sensor | description
|
||||
-----------|----------------------------
|
||||
height | current height of the desk
|
||||
|
||||
|
||||
#### buttons
|
||||
|
||||
button | description
|
||||
--------------|---------------------------
|
||||
step_up | move up desk by one step (~14mm)
|
||||
step_down | move down desk by one step (~14mm)
|
||||
move_up | move up until stopped
|
||||
move_down | move down until stopped
|
||||
stop | stop movement of desk
|
||||
position1 | move to 1st stored height
|
||||
position2 | move to 2nd stored height
|
||||
position3 | move to 3rd stored height
|
||||
position4 | move to 4th stored height
|
||||
save_position | press position button afterwards to store current height to that position
|
||||
|
||||
#### lambda methods
|
||||
|
||||
lambda method | description
|
||||
----------------------------------|---------------------------
|
||||
`id(my_desk).step_up()` | step_up desk by one step (~14mm)
|
||||
`id(my_desk).step_down()` | step_down desk by one step (~14mm)
|
||||
`id(my_desk).move_up()` | move up
|
||||
`id(my_desk).move_down()` | move down
|
||||
`id(my_desk).stop()` | stop movement of desk
|
||||
`id(my_desk).goto_position(pos)` | move to stored height `pos`
|
||||
`id(my_desk).save_position(pos)` | save current height to position `pos`
|
||||
`id(my_desk).goto_height(height)` | move to specified height
|
||||
|
||||
## Case
|
||||
|
||||
You can find a 3D-printable case in the [stl folder](stl/) or on [onshape](https://cad.onshape.com/documents/9619b6e4e11b26a3e9d82630/w/ca8259951d8b6bb3513992f7/e/2aa1144a31e5b4c252a13681?renderMode=0&uiState=665987de1c1f4a6c5d2d97a4).
|
||||
|
||||
## Sources
|
||||
|
||||
Thanks to [phord/Jarvis](https://github.com/phord/Jarvis) for reverse engineering the UART interface and most control messages!
|
||||
Thanks to [OkhammahkO](https://github.com/OkhammahkO) for collecting the scattered information at [pimp-my-desk/desk-control](https://gitlab.com/pimp-my-desk/desk-control) and for his work in the home assistant community!
|
||||
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import uart, sensor, button, number
|
||||
from esphome.const import CONF_ID, CONF_HEIGHT, CONF_UNIT_OF_MEASUREMENT, CONF_ACCURACY_DECIMALS, UNIT_CENTIMETER, UNIT_PERCENT
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
AUTO_LOAD = ['sensor', 'button', 'number']
|
||||
|
||||
jiecang_desk_controller_ns = cg.esphome_ns.namespace('jiecang_desk_controller')
|
||||
|
||||
JiecangDeskController = jiecang_desk_controller_ns.class_('JiecangDeskController', cg.Component, uart.UARTDevice)
|
||||
JiecangDeskButton = jiecang_desk_controller_ns.class_('JiecangDeskButton', button.Button, cg.Component)
|
||||
JiecangDeskNumber = jiecang_desk_controller_ns.class_('JiecangDeskNumber', number.Number, cg.Component)
|
||||
|
||||
|
||||
CONF_SENSORS = "sensors"
|
||||
CONF_BUTTONS = "buttons"
|
||||
CONF_NUMBERS = "numbers"
|
||||
CONF_UNIT = "unit"
|
||||
CONF_STEP_UP = "step_up"
|
||||
CONF_STEP_DOWN = "step_down"
|
||||
CONF_STOP = "stop"
|
||||
CONF_HEIGHT_MIN = "height_min"
|
||||
CONF_HEIGHT_MAX = "height_max"
|
||||
CONF_HEIGHT_PCT = "height_pct"
|
||||
CONF_POSITION1 = "position1"
|
||||
CONF_POSITION2 = "position2"
|
||||
CONF_POSITION3 = "position3"
|
||||
CONF_POSITION4 = "position4"
|
||||
CONF_SAVE_POSITION = "save_position"
|
||||
CONF_MOVE_UP = "move_up"
|
||||
CONF_MOVE_DOWN = "move_down"
|
||||
|
||||
|
||||
button_constants = {}
|
||||
button_constants[CONF_STEP_UP] = 0
|
||||
button_constants[CONF_STEP_DOWN] = 1
|
||||
button_constants[CONF_STOP] = 2
|
||||
button_constants[CONF_POSITION1] = 3
|
||||
button_constants[CONF_POSITION2] = 4
|
||||
button_constants[CONF_POSITION3] = 5
|
||||
button_constants[CONF_POSITION4] = 6
|
||||
button_constants[CONF_SAVE_POSITION] = 7
|
||||
button_constants[CONF_MOVE_UP] = 8
|
||||
button_constants[CONF_MOVE_DOWN] = 9
|
||||
|
||||
# Backward compatibility
|
||||
CONF_RAISE = "raise"
|
||||
CONF_LOWER = "lower"
|
||||
button_constants[CONF_RAISE] = button_constants[CONF_STEP_UP]
|
||||
button_constants[CONF_LOWER] = button_constants[CONF_STEP_DOWN]
|
||||
|
||||
|
||||
number_constants = {}
|
||||
number_constants[CONF_HEIGHT] = 0
|
||||
number_constants[CONF_HEIGHT_PCT] = 1
|
||||
|
||||
CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(JiecangDeskController),
|
||||
cv.Optional(CONF_SENSORS): cv.Schema({
|
||||
cv.Optional(CONF_HEIGHT): sensor.sensor_schema(
|
||||
accuracy_decimals = 1,
|
||||
unit_of_measurement = UNIT_CENTIMETER
|
||||
),
|
||||
cv.Optional(CONF_UNIT): sensor.sensor_schema(
|
||||
accuracy_decimals = 0
|
||||
),
|
||||
cv.Optional(CONF_HEIGHT_PCT): sensor.sensor_schema(
|
||||
accuracy_decimals = 1,
|
||||
unit_of_measurement = UNIT_PERCENT
|
||||
),
|
||||
cv.Optional(CONF_HEIGHT_MIN): sensor.sensor_schema(
|
||||
accuracy_decimals = 1,
|
||||
unit_of_measurement = UNIT_CENTIMETER
|
||||
),
|
||||
cv.Optional(CONF_HEIGHT_MAX): sensor.sensor_schema(
|
||||
accuracy_decimals = 1,
|
||||
unit_of_measurement = UNIT_CENTIMETER
|
||||
),
|
||||
cv.Optional(CONF_POSITION1): sensor.sensor_schema(
|
||||
accuracy_decimals = 1,
|
||||
unit_of_measurement = UNIT_CENTIMETER
|
||||
),
|
||||
cv.Optional(CONF_POSITION2): sensor.sensor_schema(
|
||||
accuracy_decimals = 1,
|
||||
unit_of_measurement = UNIT_CENTIMETER
|
||||
),
|
||||
cv.Optional(CONF_POSITION3): sensor.sensor_schema(
|
||||
accuracy_decimals = 1,
|
||||
unit_of_measurement = UNIT_CENTIMETER
|
||||
),
|
||||
cv.Optional(CONF_POSITION4): sensor.sensor_schema(
|
||||
accuracy_decimals = 1,
|
||||
unit_of_measurement = UNIT_CENTIMETER
|
||||
),
|
||||
}),
|
||||
cv.Optional(CONF_NUMBERS): cv.Schema({
|
||||
cv.Optional(CONF_HEIGHT): number.NUMBER_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(JiecangDeskNumber),
|
||||
cv.Optional(CONF_ACCURACY_DECIMALS, default=1): cv.int_,
|
||||
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_CENTIMETER): cv.string,
|
||||
}),
|
||||
cv.Optional(CONF_HEIGHT_PCT): number.NUMBER_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(JiecangDeskNumber),
|
||||
cv.Optional(CONF_ACCURACY_DECIMALS, default=1): cv.int_,
|
||||
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_PERCENT): cv.string,
|
||||
}),
|
||||
}),
|
||||
cv.Optional(CONF_BUTTONS): cv.Schema({
|
||||
cv.Optional(CONF_STEP_UP): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
cv.Optional(CONF_STEP_DOWN): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
cv.Optional(CONF_STOP): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
cv.Optional(CONF_POSITION1): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
cv.Optional(CONF_POSITION2): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
cv.Optional(CONF_POSITION3): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
cv.Optional(CONF_POSITION4): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
cv.Optional(CONF_SAVE_POSITION): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
cv.Optional(CONF_MOVE_UP): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
cv.Optional(CONF_MOVE_DOWN): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
# Backward compatibility
|
||||
cv.Optional(CONF_RAISE): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
cv.Optional(CONF_LOWER): button.BUTTON_SCHEMA.extend({cv.GenerateID(): cv.declare_id(JiecangDeskButton)}),
|
||||
}),
|
||||
}).extend(uart.UART_DEVICE_SCHEMA)
|
||||
|
||||
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)
|
||||
|
||||
if CONF_SENSORS in config:
|
||||
sensors = config[CONF_SENSORS]
|
||||
|
||||
if CONF_HEIGHT in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_HEIGHT])
|
||||
cg.add(var.set_sensor_height(sens))
|
||||
if CONF_UNIT in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_UNIT])
|
||||
cg.add(var.set_sensor_unit(sens))
|
||||
if CONF_HEIGHT_MIN in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_HEIGHT_MIN])
|
||||
cg.add(var.set_sensor_height_min(sens))
|
||||
if CONF_HEIGHT_MAX in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_HEIGHT_MAX])
|
||||
cg.add(var.set_sensor_height_max(sens))
|
||||
if CONF_HEIGHT_PCT in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_HEIGHT_PCT])
|
||||
cg.add(var.set_sensor_height_pct(sens))
|
||||
if CONF_POSITION1 in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_POSITION1])
|
||||
cg.add(var.set_sensor_position1(sens))
|
||||
if CONF_POSITION2 in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_POSITION2])
|
||||
cg.add(var.set_sensor_position2(sens))
|
||||
if CONF_POSITION3 in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_POSITION3])
|
||||
cg.add(var.set_sensor_position3(sens))
|
||||
if CONF_POSITION4 in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_POSITION4])
|
||||
cg.add(var.set_sensor_position4(sens))
|
||||
|
||||
if CONF_BUTTONS in config:
|
||||
buttons = config[CONF_BUTTONS]
|
||||
for button_type in buttons.keys():
|
||||
btn = await button.new_button(buttons[button_type])
|
||||
cg.add(var.add_button(btn, button_constants[button_type]))
|
||||
|
||||
if CONF_NUMBERS in config:
|
||||
numbers = config[CONF_NUMBERS]
|
||||
for number_type in numbers.keys():
|
||||
num = await number.new_number(numbers[number_type], min_value=0, max_value=100, step=.1)
|
||||
cg.add(var.add_number(num, number_constants[number_type]))
|
||||
|
||||
|
|
@ -1,314 +0,0 @@
|
|||
#include "jiecang_desk_controller.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace jiecang_desk_controller {
|
||||
|
||||
static const char *const TAG = "jiecang_desk_controller";
|
||||
|
||||
float JiecangDeskController::byte2float(int high, int low) {
|
||||
return static_cast<float>((high<<8) + low)/10;
|
||||
}
|
||||
|
||||
bool JiecangDeskController::bufferMessage(int data, unsigned int *buffer, int len) {
|
||||
// This is a very simple method of receiving messages from the desk.
|
||||
// It will fail on messages that contain the value 0x7E in their payload.
|
||||
// But it is super simple and works for the messages we care about.
|
||||
|
||||
// format: 0xF2 0xF2 [command] [param_count] [[param] ...] [checksum] 0x7E
|
||||
// checksum: sum of [command], [param_count] and all [param]s
|
||||
|
||||
static int cmd_incoming = 0; // 0: wait for F2, 1: wait for 2nd F2, 2: buffer data
|
||||
static int pos = 0;
|
||||
|
||||
if (cmd_incoming < 2 && data == 0xF2) { // start of message, must appear twice
|
||||
cmd_incoming++;
|
||||
pos = 0;
|
||||
|
||||
} else if (cmd_incoming == 1) { // no second F2 received
|
||||
cmd_incoming = 0;
|
||||
|
||||
} else if (cmd_incoming == 2) {
|
||||
if (data == 0x7E) { // end of message
|
||||
cmd_incoming = 0;
|
||||
for (;pos<len-1;pos++) { // fill rest of buffer with zeros
|
||||
buffer[pos]=0;
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if (pos >= len) { // message too long, drop it
|
||||
cmd_incoming = 0;
|
||||
|
||||
} else {
|
||||
buffer[pos++] = data; // buffer data
|
||||
}
|
||||
|
||||
} // else: received garbage
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void JiecangDeskController::handleMessage(unsigned int *message) {
|
||||
// ESP_LOGV("jiecang_desk_controller", "message %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", message[0], message[1], message[2], message[3], message[4], message[5], message[6], message[7], message[8], message[9]);
|
||||
|
||||
switch (message[0]) {
|
||||
case 0x01:
|
||||
ESP_LOGV("jiecang_desk_controller", "height 0x%0X%0X", message[2], message[3]);
|
||||
float new_height;
|
||||
new_height = byte2float(message[2], message[3]);
|
||||
if (new_height == current_height) return;
|
||||
current_height = new_height;
|
||||
if (height != nullptr) height->publish_state(current_height);
|
||||
if (number_height != nullptr) number_height->publish_state(current_height);
|
||||
|
||||
if (height_pct != nullptr && limit_max != 0)
|
||||
height_pct->publish_state((current_height - limit_min) / (limit_max - limit_min) * 100);
|
||||
if (number_height_pct != nullptr && limit_max != 0)
|
||||
number_height_pct->publish_state(roundf((current_height - limit_min) / (limit_max - limit_min) * 1000)/10);
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
ESP_LOGV("jiecang_desk_controller", "limits 0x%0X max %i min %i", message[2], (message[2] & 1), (message[2]>>4));
|
||||
|
||||
if ((message[2] & 1) == 0) { // low nibble 0 -> no max limit, use physical_max
|
||||
limit_max = physical_max;
|
||||
if (height_max != nullptr) height_max->publish_state(limit_max);
|
||||
if (number_height != nullptr) number_height->set_max_value(limit_max);
|
||||
}
|
||||
if ((message[2]>>4) == 0) { // high nibble 0 -> no min limit, use physical_min
|
||||
limit_min = physical_min;
|
||||
if (height_min != nullptr) height_min->publish_state(limit_min);
|
||||
if (number_height != nullptr) number_height->set_min_value(limit_min);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x07:
|
||||
ESP_LOGV("jiecang_desk_controller", "physical limits 0x%02X%02X 0x%02X%02X", message[2], message[3], message[4], message[5]);
|
||||
physical_max = byte2float(message[2], message[3]);
|
||||
physical_min = byte2float(message[4], message[5]);
|
||||
break;
|
||||
|
||||
case 0x21:
|
||||
ESP_LOGV("jiecang_desk_controller", "height_max 0x%02X%02X", message[2], message[3]);
|
||||
limit_max = byte2float(message[2], message[3]);
|
||||
if (height_max != nullptr) height_max->publish_state(limit_max);
|
||||
if (number_height != nullptr) number_height->set_max_value(limit_max);
|
||||
break;
|
||||
|
||||
case 0x22:
|
||||
ESP_LOGV("jiecang_desk_controller", "height_min 0x%02X%02X", message[2], message[3]);
|
||||
limit_min = byte2float(message[2], message[3]);
|
||||
if (height_min != nullptr) height_min->publish_state(limit_min);
|
||||
if (number_height != nullptr) number_height->set_min_value(limit_min);
|
||||
break;
|
||||
|
||||
case 0x25:
|
||||
ESP_LOGV("jiecang_desk_controller", "position1 0x%02X%02X", message[2], message[3]);
|
||||
if (position1 != nullptr) position1->publish_state(byte2float(message[2], message[3]));
|
||||
break;
|
||||
|
||||
case 0x26:
|
||||
ESP_LOGV("jiecang_desk_controller", "position2 0x%02X%02X", message[2], message[3]);
|
||||
if (position2 != nullptr) position2->publish_state(byte2float(message[2], message[3]));
|
||||
break;
|
||||
|
||||
case 0x27:
|
||||
ESP_LOGV("jiecang_desk_controller", "position3 0x%02X%02X", message[2], message[3]);
|
||||
if (position3 != nullptr) position3->publish_state(byte2float(message[2], message[3]));
|
||||
break;
|
||||
|
||||
case 0x28:
|
||||
ESP_LOGV("jiecang_desk_controller", "position4 0x%02X%02X", message[2], message[3]);
|
||||
if (position4 != nullptr) position4->publish_state(byte2float(message[2], message[3]));
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGV("jiecang_desk_controller", "unknown message %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", message[0], message[1], message[2], message[3], message[4], message[5], message[6], message[7], message[8], message[9]);
|
||||
}
|
||||
}
|
||||
|
||||
void JiecangDeskController::update() {
|
||||
const int max_length = 10;
|
||||
static unsigned int buffer[max_length];
|
||||
while (available()) {
|
||||
if(bufferMessage(read(), buffer, max_length)) {
|
||||
handleMessage(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JiecangDeskController::send_simple_command(unsigned char cmd) {
|
||||
write_array({ 0xF1, 0xF1, cmd, 0x00, cmd, 0x7E });
|
||||
}
|
||||
|
||||
void JiecangDeskController::add_button(button::Button *btn, int action) {
|
||||
btn->add_on_press_callback([this, action]() { this->button_press_action(action); });
|
||||
}
|
||||
|
||||
void JiecangDeskController::step_up() {
|
||||
send_simple_command(0x01);
|
||||
}
|
||||
|
||||
void JiecangDeskController::step_down() {
|
||||
send_simple_command(0x02);
|
||||
}
|
||||
|
||||
void JiecangDeskController::stop() {
|
||||
send_simple_command(0x2B);
|
||||
}
|
||||
|
||||
void JiecangDeskController::goto_position(int pos) {
|
||||
switch (pos) {
|
||||
case 1:
|
||||
send_simple_command(0x05);
|
||||
break;
|
||||
case 2:
|
||||
send_simple_command(0x06);
|
||||
break;
|
||||
case 3:
|
||||
send_simple_command(0x27);
|
||||
break;
|
||||
case 4:
|
||||
send_simple_command(0x28);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JiecangDeskController::save_position(int pos) {
|
||||
switch (pos) {
|
||||
case 1:
|
||||
send_simple_command(0x03);
|
||||
break;
|
||||
case 2:
|
||||
send_simple_command(0x04);
|
||||
break;
|
||||
case 3:
|
||||
send_simple_command(0x25);
|
||||
break;
|
||||
case 4:
|
||||
send_simple_command(0x26);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JiecangDeskController::goto_height(float height) {
|
||||
unsigned char cmd = 0x1B;
|
||||
unsigned char high_byte = ((int) height * 10) >> 8;
|
||||
unsigned char low_byte = ((int) height * 10) & 0xFF;
|
||||
|
||||
unsigned char checksum = cmd + 2 + high_byte + low_byte;
|
||||
|
||||
write_array({ 0xF1, 0xF1, cmd, 0x02, high_byte, low_byte, checksum, 0x7E });
|
||||
}
|
||||
|
||||
void JiecangDeskController::move_up() {
|
||||
goto_height(limit_max);
|
||||
}
|
||||
|
||||
void JiecangDeskController::move_down() {
|
||||
goto_height(limit_min);
|
||||
}
|
||||
|
||||
void JiecangDeskController::request_physical_limits() {
|
||||
send_simple_command(0x0C);
|
||||
}
|
||||
|
||||
void JiecangDeskController::request_limits() {
|
||||
send_simple_command(0x20);
|
||||
}
|
||||
|
||||
void JiecangDeskController::request_settings() {
|
||||
send_simple_command(0x07);
|
||||
}
|
||||
|
||||
void JiecangDeskController::button_press_action(int action) {
|
||||
ESP_LOGV("JiecangDeskController", "button_press_action %i", action);
|
||||
switch (action) {
|
||||
case BUTTON_STEP_UP:
|
||||
step_up();
|
||||
break;
|
||||
case BUTTON_STEP_DOWN:
|
||||
step_down();
|
||||
break;
|
||||
case BUTTON_STOP:
|
||||
stop();
|
||||
save_position_mode = false;
|
||||
break;
|
||||
case BUTTON_MOVE_UP:
|
||||
move_up();
|
||||
break;
|
||||
case BUTTON_MOVE_DOWN:
|
||||
move_down();
|
||||
break;
|
||||
case BUTTON_POSITION1:
|
||||
if (save_position_mode) {
|
||||
save_position_mode = false;
|
||||
save_position(1);
|
||||
return;
|
||||
}
|
||||
goto_position(1);
|
||||
break;
|
||||
case BUTTON_POSITION2:
|
||||
if (save_position_mode) {
|
||||
save_position_mode = false;
|
||||
save_position(2);
|
||||
return;
|
||||
}
|
||||
goto_position(2);
|
||||
break;
|
||||
case BUTTON_POSITION3:
|
||||
if (save_position_mode) {
|
||||
save_position_mode = false;
|
||||
save_position(3);
|
||||
return;
|
||||
}
|
||||
goto_position(3);
|
||||
break;
|
||||
case BUTTON_POSITION4:
|
||||
if (save_position_mode) {
|
||||
save_position_mode = false;
|
||||
save_position(4);
|
||||
return;
|
||||
}
|
||||
goto_position(4);
|
||||
break;
|
||||
case BUTTON_SAVE_POSITION:
|
||||
save_position_mode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JiecangDeskController::add_number(JiecangDeskNumber *number, int type) {
|
||||
switch (type) {
|
||||
case NUMBER_HEIGHT:
|
||||
number_height = number;
|
||||
break;
|
||||
case NUMBER_HEIGHT_PCT:
|
||||
number_height_pct = number;
|
||||
number_height_pct->set_min_value(0);
|
||||
number_height_pct->set_max_value(100);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
number->set_type(type);
|
||||
number->set_parent(this);
|
||||
}
|
||||
|
||||
void JiecangDeskController::number_control(int type, float value) {
|
||||
ESP_LOGV("JiecangDeskController", "number_control %i", type);
|
||||
switch (type) {
|
||||
case NUMBER_HEIGHT:
|
||||
goto_height(value);
|
||||
break;
|
||||
case NUMBER_HEIGHT_PCT:
|
||||
if (limit_max != 0) goto_height((limit_max - limit_min) * value / 100 + limit_min);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JiecangDeskButton::press_action() {}
|
||||
|
||||
} //namespace jiecang_desk_controller
|
||||
} //namespace esphome
|
||||
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <future>
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "jiecang_desk_number.h"
|
||||
|
||||
#define BUTTON_STEP_UP 0
|
||||
#define BUTTON_STEP_DOWN 1
|
||||
#define BUTTON_STOP 2
|
||||
#define BUTTON_POSITION1 3
|
||||
#define BUTTON_POSITION2 4
|
||||
#define BUTTON_POSITION3 5
|
||||
#define BUTTON_POSITION4 6
|
||||
#define BUTTON_SAVE_POSITION 7
|
||||
#define BUTTON_MOVE_UP 8
|
||||
#define BUTTON_MOVE_DOWN 9
|
||||
|
||||
#define NUMBER_HEIGHT 0
|
||||
#define NUMBER_HEIGHT_PCT 1
|
||||
|
||||
namespace esphome {
|
||||
namespace jiecang_desk_controller {
|
||||
|
||||
class JiecangDeskController : public PollingComponent, public sensor::Sensor, public uart::UARTDevice {
|
||||
private:
|
||||
float current_height = 0;
|
||||
float limit_min = 0;
|
||||
float limit_max = 0;
|
||||
float physical_min = 0;
|
||||
float physical_max = 0;
|
||||
|
||||
bool save_position_mode = false;
|
||||
|
||||
float byte2float(int high, int low);
|
||||
bool bufferMessage(int data, unsigned int *buffer, int len);
|
||||
void handleMessage(unsigned int *message);
|
||||
|
||||
public:
|
||||
void update() override;
|
||||
|
||||
void set_sensor_height(sensor::Sensor *sensor) { this->height = sensor; }
|
||||
void set_sensor_height_min(sensor::Sensor *sensor) { this->height_min = sensor; }
|
||||
void set_sensor_height_max(sensor::Sensor *sensor) { this->height_max = sensor; }
|
||||
void set_sensor_height_pct(sensor::Sensor *sensor) { this->height_pct = sensor; }
|
||||
|
||||
void set_sensor_position1(sensor::Sensor *sensor) { this->position1 = sensor; }
|
||||
void set_sensor_position2(sensor::Sensor *sensor) { this->position2 = sensor; }
|
||||
void set_sensor_position3(sensor::Sensor *sensor) { this->position3 = sensor; }
|
||||
void set_sensor_position4(sensor::Sensor *sensor) { this->position4 = sensor; }
|
||||
|
||||
void send_simple_command(unsigned char cmd);
|
||||
void add_button(button::Button *btn, int action);
|
||||
void add_number(JiecangDeskNumber *number, int type);
|
||||
|
||||
void step_up();
|
||||
void step_down();
|
||||
void stop();
|
||||
void goto_position(int pos);
|
||||
void save_position(int pos);
|
||||
void goto_height(float height);
|
||||
void move_up();
|
||||
void move_down();
|
||||
|
||||
void request_physical_limits();
|
||||
void request_limits();
|
||||
void request_settings();
|
||||
|
||||
void number_control(int type, float value);
|
||||
|
||||
protected:
|
||||
Sensor *height{nullptr};
|
||||
Sensor *height_min{nullptr};
|
||||
Sensor *height_max{nullptr};
|
||||
Sensor *height_pct{nullptr};
|
||||
|
||||
Sensor *position1{nullptr};
|
||||
Sensor *position2{nullptr};
|
||||
Sensor *position3{nullptr};
|
||||
Sensor *position4{nullptr};
|
||||
|
||||
JiecangDeskNumber *number_height{nullptr};
|
||||
JiecangDeskNumber *number_height_pct{nullptr};
|
||||
|
||||
void button_press_action(int type);
|
||||
};
|
||||
|
||||
class JiecangDeskButton : public Component, public button::Button {
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} //namespace jiecang_desk_controller
|
||||
} //namespace esphome
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
#include "jiecang_desk_number.h"
|
||||
#include "jiecang_desk_controller.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace jiecang_desk_controller {
|
||||
|
||||
static const char *const TAG = "jiecang_desk.number";
|
||||
|
||||
void JiecangDeskNumber::setup() {}
|
||||
|
||||
void JiecangDeskNumber::dump_config() {
|
||||
LOG_NUMBER("", "JiecangDeskNumber", this);
|
||||
ESP_LOGCONFIG(TAG, " type %i", type);
|
||||
}
|
||||
|
||||
void JiecangDeskNumber::control(float value) {
|
||||
this->publish_state(value);
|
||||
parent->number_control(this->type, value);
|
||||
}
|
||||
|
||||
void JiecangDeskNumber::set_min_value(float value) {
|
||||
traits.set_min_value(value);
|
||||
}
|
||||
|
||||
void JiecangDeskNumber::set_max_value(float value) {
|
||||
traits.set_max_value(value);
|
||||
}
|
||||
|
||||
|
||||
} // namespace jiecang_desk_controller
|
||||
} // namespace esphome
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace jiecang_desk_controller {
|
||||
|
||||
class JiecangDeskController;
|
||||
|
||||
class JiecangDeskNumber : public number::Number, public Component {
|
||||
private:
|
||||
int type;
|
||||
JiecangDeskController *parent;
|
||||
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_parent(JiecangDeskController *parent) { this->parent = parent; }
|
||||
void set_type(int type) { this->type = type; }
|
||||
|
||||
void set_min_value(float value);
|
||||
void set_max_value(float value);
|
||||
|
||||
protected:
|
||||
void control(float value) override;
|
||||
|
||||
};
|
||||
|
||||
} // namespace jiecang_desk_controller
|
||||
} // namespace esphome
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
# ESPHome Maidsite Desk Controller
|
||||
|
||||
[ESPHome](https://esphome.io/) component for controlling Maidsite desk controllers via their serial protocol.
|
||||
|
||||

|
||||
|
||||
Attention: I only have one [RJ12 model](https://www.maidsite.com/product/jcb35m11c.html) to test this
|
||||
but it's likely that other Maidsite controllers are supported as their serial protocol should be compatible.
|
||||
|
||||
## Usage
|
||||
|
||||
### What you need
|
||||
|
||||
* ESPHome compatible microcontroller
|
||||
* depending on your model of desk controller
|
||||
* cable with RJ12 connector (phone cable, RJ11 may also work)
|
||||
* cable with RJ45 connector (network cable)
|
||||
|
||||
### Wiring
|
||||
|
||||
#### RJ12
|
||||
|
||||
Please double check this for your specific model!
|
||||
|
||||
pin | function
|
||||
----|---------
|
||||
1 | NC (pulled up)
|
||||
2 | GND
|
||||
3 | TX
|
||||
4 | VCC
|
||||
5 | RX
|
||||
6 | NC (pulled up)
|
||||
|
||||
#### RJ45
|
||||
|
||||
**Untested** and only for reference!
|
||||
|
||||
pin | function
|
||||
----|---------
|
||||
1 | HS3 [^1]
|
||||
2 | TX
|
||||
3 | GND
|
||||
4 | RX
|
||||
5 | VCC
|
||||
6 | HS2 [^1]
|
||||
7 | HS1 [^1]
|
||||
8 | HS0 [^1]
|
||||
|
||||
[^1]: not used here
|
||||
|
||||
#### microcontroller
|
||||
|
||||
ESP | desk
|
||||
----|-----
|
||||
GND | GND
|
||||
5V | VCC
|
||||
RX | TX
|
||||
TX | RX
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
```yaml
|
||||
esphome:
|
||||
...
|
||||
on_boot:
|
||||
# This script is required to initialize the following sensors:
|
||||
# height_pct, height_min, height_max, position_m1 - position_m4
|
||||
# You can skip this if you don't use those.
|
||||
priority: 0 # when mostly everything else is done
|
||||
then:
|
||||
- lambda: "id(my_desk).request_physical_limits();"
|
||||
- delay: 0.1s
|
||||
- lambda: "id(my_desk).request_limits();"
|
||||
- delay: 0.1s
|
||||
- lambda: "id(my_desk).request_settings();"
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/ElVit/esphome_maidsite_desk/
|
||||
components: [ maidsite_desk_controller ]
|
||||
|
||||
uart:
|
||||
- id: uart_bus
|
||||
tx_pin: $pin_tx
|
||||
rx_pin: $pin_rx
|
||||
baud_rate: 9600
|
||||
|
||||
logger:
|
||||
baud_rate: 0 # disable logging over uart, required when using the RX/TX pins for the controller
|
||||
|
||||
maidsite_desk_controller:
|
||||
id: my_desk
|
||||
|
||||
sensors:
|
||||
height_abs:
|
||||
name: "Height"
|
||||
|
||||
numbers:
|
||||
height_abs:
|
||||
name: "Height"
|
||||
mode: SLIDER
|
||||
|
||||
buttons:
|
||||
stop:
|
||||
name: "Stop"
|
||||
step_up:
|
||||
name: "Step up"
|
||||
step_down:
|
||||
name: "Step down"
|
||||
goto_m1:
|
||||
name: "Move to M1"
|
||||
goto_m2:
|
||||
name: "Move to M2"
|
||||
goto_m3:
|
||||
name: "Move to M3"
|
||||
goto_m4:
|
||||
name: "Move to M4"
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Step down"
|
||||
on_press:
|
||||
lambda: "id(my_desk).goto_height(84.0);"
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
* basic, sufficient for most people: [example_basic.yaml](example_basic.yaml)
|
||||
* full with all features in use: [example_full.yaml](example_full.yaml)
|
||||
|
||||
### Features
|
||||
|
||||
#### sensors
|
||||
|
||||
sensor | description
|
||||
-------------|----------------------------
|
||||
height_abs | current height of the desk
|
||||
height_pct | height in percent
|
||||
height_min | minimal height
|
||||
height_max | maximal height
|
||||
position_m1 | 1st stored height
|
||||
position_m2 | 2nd stored height
|
||||
position_m3 | 3rd stored height
|
||||
position_m4 | 4th stored height
|
||||
|
||||
#### number entities
|
||||
|
||||
Hold current values read from the desk and set values to desk when changed.
|
||||
|
||||
sensor | description
|
||||
------------|----------------------------
|
||||
height_abs | current height of the desk
|
||||
height_pct | height in percent
|
||||
|
||||
#### buttons
|
||||
|
||||
button | description
|
||||
-----------|---------------------------
|
||||
step_up | move up desk by one step (~14mm)
|
||||
step_down | move down desk by one step (~14mm)
|
||||
goto_max | move up until stopped
|
||||
goto_min | move down until stopped
|
||||
stop | stop movement of desk
|
||||
goto_m1 | move to 1st stored height
|
||||
goto_m2 | move to 2nd stored height
|
||||
goto_m3 | move to 3rd stored height
|
||||
goto_m4 | move to 4th stored height
|
||||
save_m1 | save current height to 1th position
|
||||
save_m2 | save current height to 2th position
|
||||
save_m3 | save current height to 3th position
|
||||
save_m4 | save current height to 4th position
|
||||
|
||||
#### lambda methods
|
||||
|
||||
lambda method | description
|
||||
--------------------------------------|---------------------------
|
||||
`id(my_desk).step_up()` | step_up desk by one step (~14mm)
|
||||
`id(my_desk).step_down()` | step_down desk by one step (~14mm)
|
||||
`id(my_desk).goto_max_position()` | move up
|
||||
`id(my_desk).goto_min_position()` | move down
|
||||
`id(my_desk).stop()` | stop movement of desk
|
||||
`id(my_desk).goto_mem_position(pos)` | move to stored height `pos`
|
||||
`id(my_desk).save_mem_position(pos)` | save current height to position `pos`
|
||||
`id(my_desk).goto_height(height)` | move to specified height
|
||||
|
||||
## Case
|
||||
|
||||
You can find a 3D-printable case in the [stl folder](stl/) or on [onshape](https://cad.onshape.com/documents/9619b6e4e11b26a3e9d82630/w/ca8259951d8b6bb3513992f7/e/2aa1144a31e5b4c252a13681?renderMode=0&uiState=665987de1c1f4a6c5d2d97a4).
|
||||
|
||||
## Sources
|
||||
|
||||
Thanks to [Rocka84](https://github.com/Rocka84/esphome_components)
|
||||
Thanks to [phord/Jarvis](https://github.com/phord/Jarvis) for reverse engineering the UART interface and most control messages!
|
||||
Thanks to [OkhammahkO](https://github.com/OkhammahkO) for collecting the scattered information at [pimp-my-desk/desk-control](https://gitlab.com/pimp-my-desk/desk-control) and for his work in the home assistant community!
|
||||
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import uart, sensor, button, number
|
||||
from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT, CONF_STEP, CONF_ACCURACY_DECIMALS, CONF_ENTITY_CATEGORY, UNIT_CENTIMETER, UNIT_PERCENT, ENTITY_CATEGORY_CONFIG
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
AUTO_LOAD = ['sensor', 'button', 'number']
|
||||
|
||||
maidsite_desk_controller_ns = cg.esphome_ns.namespace('maidsite_desk_controller')
|
||||
|
||||
MaidsiteDeskController = maidsite_desk_controller_ns.class_('MaidsiteDeskController', cg.Component, uart.UARTDevice)
|
||||
MaidsiteDeskButton = maidsite_desk_controller_ns.class_('MaidsiteDeskButton', button.Button, cg.Component)
|
||||
MaidsiteDeskNumber = maidsite_desk_controller_ns.class_('MaidsiteDeskNumber', number.Number, cg.Component)
|
||||
|
||||
|
||||
CONF_SENSORS = "sensors"
|
||||
CONF_BUTTONS = "buttons"
|
||||
CONF_NUMBERS = "numbers"
|
||||
CONF_UNIT = "unit"
|
||||
CONF_STEP_UP = "step_up"
|
||||
CONF_STEP_DOWN = "step_down"
|
||||
CONF_STOP = "stop"
|
||||
CONF_GOTO_MAX = "goto_max"
|
||||
CONF_GOTO_MIN = "goto_min"
|
||||
CONF_HEIGHT_MIN = "height_min"
|
||||
CONF_HEIGHT_MAX = "height_max"
|
||||
CONF_HEIGHT_ABS = "height_abs"
|
||||
CONF_HEIGHT_PCT = "height_pct"
|
||||
CONF_POSITION_M1 = "position_m1"
|
||||
CONF_POSITION_M2 = "position_m2"
|
||||
CONF_POSITION_M3 = "position_m3"
|
||||
CONF_POSITION_M4 = "position_m4"
|
||||
CONF_GOTO_M1 = "goto_m1"
|
||||
CONF_GOTO_M2 = "goto_m2"
|
||||
CONF_GOTO_M3 = "goto_m3"
|
||||
CONF_GOTO_M4 = "goto_m4"
|
||||
CONF_SAVE_M1 = "save_m1"
|
||||
CONF_SAVE_M2 = "save_m2"
|
||||
CONF_SAVE_M3 = "save_m3"
|
||||
CONF_SAVE_M4 = "save_m4"
|
||||
|
||||
button_constants = {}
|
||||
button_constants[CONF_STEP_UP] = 0
|
||||
button_constants[CONF_STEP_DOWN] = 1
|
||||
button_constants[CONF_STOP] = 2
|
||||
button_constants[CONF_GOTO_MAX] = 3
|
||||
button_constants[CONF_GOTO_MIN] = 4
|
||||
button_constants[CONF_GOTO_M1] = 5
|
||||
button_constants[CONF_GOTO_M2] = 6
|
||||
button_constants[CONF_GOTO_M3] = 7
|
||||
button_constants[CONF_GOTO_M4] = 8
|
||||
button_constants[CONF_SAVE_M1] = 9
|
||||
button_constants[CONF_SAVE_M2] = 10
|
||||
button_constants[CONF_SAVE_M3] = 11
|
||||
button_constants[CONF_SAVE_M4] = 12
|
||||
|
||||
number_constants = {}
|
||||
number_constants[CONF_HEIGHT_ABS] = 0
|
||||
number_constants[CONF_HEIGHT_PCT] = 1
|
||||
|
||||
CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskController),
|
||||
|
||||
cv.Optional(CONF_SENSORS): cv.Schema({
|
||||
cv.Optional(CONF_UNIT): sensor.sensor_schema(accuracy_decimals = 0),
|
||||
cv.Optional(CONF_HEIGHT_ABS): sensor.sensor_schema(accuracy_decimals = 1, unit_of_measurement = UNIT_CENTIMETER),
|
||||
cv.Optional(CONF_HEIGHT_PCT): sensor.sensor_schema(accuracy_decimals = 1, unit_of_measurement = UNIT_PERCENT),
|
||||
cv.Optional(CONF_HEIGHT_MIN): sensor.sensor_schema(accuracy_decimals = 1, unit_of_measurement = UNIT_CENTIMETER),
|
||||
cv.Optional(CONF_HEIGHT_MAX): sensor.sensor_schema(accuracy_decimals = 1, unit_of_measurement = UNIT_CENTIMETER),
|
||||
cv.Optional(CONF_POSITION_M1): sensor.sensor_schema(accuracy_decimals = 1, unit_of_measurement = UNIT_CENTIMETER),
|
||||
cv.Optional(CONF_POSITION_M2): sensor.sensor_schema(accuracy_decimals = 1, unit_of_measurement = UNIT_CENTIMETER),
|
||||
cv.Optional(CONF_POSITION_M3): sensor.sensor_schema(accuracy_decimals = 1, unit_of_measurement = UNIT_CENTIMETER),
|
||||
cv.Optional(CONF_POSITION_M4): sensor.sensor_schema(accuracy_decimals = 1, unit_of_measurement = UNIT_CENTIMETER),
|
||||
}),
|
||||
cv.Optional(CONF_NUMBERS): cv.Schema({
|
||||
cv.Optional(CONF_HEIGHT_ABS): number.NUMBER_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskNumber),
|
||||
cv.Optional(CONF_ACCURACY_DECIMALS, default=1): cv.int_,
|
||||
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_CENTIMETER): cv.string,
|
||||
cv.Optional(CONF_STEP, default=1): cv.float_,
|
||||
}),
|
||||
cv.Optional(CONF_HEIGHT_PCT): number.NUMBER_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskNumber),
|
||||
cv.Optional(CONF_ACCURACY_DECIMALS, default=1): cv.int_,
|
||||
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_PERCENT): cv.string,
|
||||
cv.Optional(CONF_STEP, default=.1): cv.float_,
|
||||
}),
|
||||
}),
|
||||
cv.Optional(CONF_BUTTONS): cv.Schema({
|
||||
cv.Optional(CONF_STEP_UP): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
}),
|
||||
cv.Optional(CONF_STEP_DOWN): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
}),
|
||||
cv.Optional(CONF_STOP): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
}),
|
||||
cv.Optional(CONF_GOTO_MAX): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
}),
|
||||
cv.Optional(CONF_GOTO_MIN): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
}),
|
||||
cv.Optional(CONF_GOTO_M1): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
}),
|
||||
cv.Optional(CONF_GOTO_M2): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
}),
|
||||
cv.Optional(CONF_GOTO_M3): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
}),
|
||||
cv.Optional(CONF_GOTO_M4): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
}),
|
||||
cv.Optional(CONF_SAVE_M1): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
cv.Optional(CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG): cv.entity_category,
|
||||
}),
|
||||
cv.Optional(CONF_SAVE_M2): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
cv.Optional(CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG): cv.entity_category,
|
||||
}),
|
||||
cv.Optional(CONF_SAVE_M3): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
cv.Optional(CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG): cv.entity_category,
|
||||
}),
|
||||
cv.Optional(CONF_SAVE_M4): button.BUTTON_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(MaidsiteDeskButton),
|
||||
cv.Optional(CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG): cv.entity_category,
|
||||
}),
|
||||
}),
|
||||
}).extend(uart.UART_DEVICE_SCHEMA)
|
||||
|
||||
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)
|
||||
|
||||
if CONF_SENSORS in config:
|
||||
sensors = config[CONF_SENSORS]
|
||||
|
||||
if CONF_UNIT in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_UNIT])
|
||||
cg.add(var.set_sensor_unit(sens))
|
||||
if CONF_HEIGHT_MIN in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_HEIGHT_MIN])
|
||||
cg.add(var.set_sensor_height_min(sens))
|
||||
if CONF_HEIGHT_MAX in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_HEIGHT_MAX])
|
||||
cg.add(var.set_sensor_height_max(sens))
|
||||
if CONF_HEIGHT_ABS in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_HEIGHT_ABS])
|
||||
cg.add(var.set_sensor_height(sens))
|
||||
if CONF_HEIGHT_PCT in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_HEIGHT_PCT])
|
||||
cg.add(var.set_sensor_height_pct(sens))
|
||||
if CONF_POSITION_M1 in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_POSITION_M1])
|
||||
cg.add(var.set_sensor_position_m1(sens))
|
||||
if CONF_POSITION_M2 in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_POSITION_M2])
|
||||
cg.add(var.set_sensor_position_m2(sens))
|
||||
if CONF_POSITION_M3 in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_POSITION_M3])
|
||||
cg.add(var.set_sensor_position_m3(sens))
|
||||
if CONF_POSITION_M4 in sensors:
|
||||
sens = await sensor.new_sensor(sensors[CONF_POSITION_M4])
|
||||
cg.add(var.set_sensor_position_m4(sens))
|
||||
|
||||
if CONF_BUTTONS in config:
|
||||
buttons = config[CONF_BUTTONS]
|
||||
for button_type in buttons.keys():
|
||||
btn = await button.new_button(buttons[button_type])
|
||||
cg.add(var.add_button(btn, button_constants[button_type]))
|
||||
|
||||
if CONF_NUMBERS in config:
|
||||
numbers = config[CONF_NUMBERS]
|
||||
for number_type in numbers.keys():
|
||||
num = await number.new_number(numbers[number_type], min_value=0, max_value=100, step=.1)
|
||||
cg.add(var.add_number(num, number_constants[number_type]))
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
esphome:
|
||||
name: jiecang-desk-controller # change this!
|
||||
friendly_name: Jiecang Desk Controller # change this!
|
||||
name: maidsite-desk-controller # change this!
|
||||
friendly_name: Maidsite Desk Controller # change this!
|
||||
on_boot:
|
||||
# don't touch if you don't know what you're doing!
|
||||
priority: 0 # when mostly everything else is done
|
||||
|
|
@ -14,43 +14,47 @@ esphome:
|
|||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/Rocka84/esphome_components/
|
||||
components: [ jiecang_desk_controller ]
|
||||
url: https://github.com/ElVit/esphome_maidsite_desk/
|
||||
components: [ maidsite_desk_controller ]
|
||||
|
||||
uart:
|
||||
id: uart_bus
|
||||
tx_pin: TX
|
||||
rx_pin: RX
|
||||
baud_rate: 9600
|
||||
- id: uart_bus
|
||||
tx_pin: $pin_tx
|
||||
rx_pin: $pin_rx
|
||||
baud_rate: 9600
|
||||
|
||||
logger:
|
||||
baud_rate: 0 # disable logging over uart, required when using the RX/TX pins for the controller
|
||||
|
||||
# see full example for more options: https://github.com/Rocka84/esphome_components/blob/master/components/jiecang_desk_controller/example_full.yaml
|
||||
jiecang_desk_controller:
|
||||
# see full example for more options: https://github.com/Rocka84/esphome_components/blob/master/components/maidsite_desk_controller/example_full.yaml
|
||||
maidsite_desk_controller:
|
||||
id: my_desk
|
||||
|
||||
sensors:
|
||||
height_abs:
|
||||
name: "Height"
|
||||
|
||||
buttons:
|
||||
move_up:
|
||||
name: "Move up"
|
||||
move_down:
|
||||
name: "Move down"
|
||||
stop:
|
||||
name: "Stop"
|
||||
step_up:
|
||||
name: "Step up"
|
||||
step_down:
|
||||
name: "Step down"
|
||||
position1:
|
||||
name: "Position 1"
|
||||
position2:
|
||||
name: "Position 2"
|
||||
position3:
|
||||
name: "Position 3"
|
||||
position4:
|
||||
name: "Position 4"
|
||||
goto_max:
|
||||
name: "Move to MAX"
|
||||
goto_min:
|
||||
name: "Move to MIN"
|
||||
goto_m1:
|
||||
name: "Move to M1"
|
||||
goto_m2:
|
||||
name: "Move to M2"
|
||||
goto_m3:
|
||||
name: "Move to M3"
|
||||
goto_m4:
|
||||
name: "Move to M4"
|
||||
|
||||
numbers:
|
||||
height:
|
||||
height_abs:
|
||||
name: "Height"
|
||||
mode: SLIDER
|
||||
|
||||
# the usual stuff
|
||||
|
||||
|
|
@ -70,3 +74,4 @@ api:
|
|||
key: !secret encryption_key
|
||||
ota:
|
||||
|
||||
logger:
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
esphome:
|
||||
name: jiecang-desk-controller
|
||||
friendly_name: Jiecang Desk Controller
|
||||
name: maidsite-desk-controller
|
||||
friendly_name: Maidsite Desk Controller
|
||||
on_boot:
|
||||
# This script is required to initialize the following sensors:
|
||||
# height_pct, height_min, height_max, position1 - position4
|
||||
# height_pct, height_min, height_max, position_m1 - position_m4
|
||||
# You can skip this if you don't use those.
|
||||
priority: 0 # when mostly everything else is done
|
||||
then:
|
||||
|
|
@ -16,69 +16,79 @@ esphome:
|
|||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: rocka84_esphome_components/components/
|
||||
path: elvit_esphome_maidsite_desk/components/
|
||||
# type: git
|
||||
# url: https://github.com/Rocka84/esphome_components/
|
||||
components: [ jiecang_desk_controller ]
|
||||
# url: https://github.com/ElVit/esphome_maidsite_desk/
|
||||
components: [ maidsite_desk_controller ]
|
||||
|
||||
uart:
|
||||
id: uart_bus
|
||||
tx_pin: TX
|
||||
rx_pin: RX
|
||||
baud_rate: 9600
|
||||
- id: uart_bus
|
||||
tx_pin: $pin_tx
|
||||
rx_pin: $pin_rx
|
||||
baud_rate: 9600
|
||||
# debug:
|
||||
# direction: BOTH
|
||||
# after:
|
||||
# bytes: 9
|
||||
# sequence:
|
||||
# - lambda: UARTDebug::log_hex(direction, bytes, ',');
|
||||
|
||||
logger:
|
||||
baud_rate: 0 # disable logging over uart, required when using the RX/TX pins for the controller
|
||||
|
||||
jiecang_desk_controller:
|
||||
maidsite_desk_controller:
|
||||
id: my_desk
|
||||
|
||||
sensors:
|
||||
height:
|
||||
name: "Height"
|
||||
height_abs:
|
||||
name: "Height absolut"
|
||||
height_pct:
|
||||
name: "Height percent"
|
||||
height_min:
|
||||
name: "Height Min"
|
||||
height_max:
|
||||
name: "Height Max"
|
||||
height_pct:
|
||||
name: "Height Percent"
|
||||
position1:
|
||||
name: "Position 1"
|
||||
position2:
|
||||
name: "Position 2"
|
||||
position3:
|
||||
name: "Position 3"
|
||||
position4:
|
||||
name: "Position 4"
|
||||
position_m1:
|
||||
name: "Position M1"
|
||||
position_m2:
|
||||
name: "Position M2"
|
||||
position_m3:
|
||||
name: "Position M3"
|
||||
position_m4:
|
||||
name: "Position M4"
|
||||
|
||||
buttons:
|
||||
move_up:
|
||||
name: "Move up"
|
||||
move_down:
|
||||
name: "Move down"
|
||||
stop:
|
||||
name: "Stop"
|
||||
step_up:
|
||||
name: "Step up"
|
||||
step_down:
|
||||
name: "Step down"
|
||||
position1:
|
||||
name: "Position 1"
|
||||
position2:
|
||||
name: "Position 2"
|
||||
position3:
|
||||
name: "Position 3"
|
||||
position4:
|
||||
name: "Position 4"
|
||||
save_position:
|
||||
name: "Save Position"
|
||||
move_up:
|
||||
name: "Move up"
|
||||
move_down:
|
||||
name: "Move down"
|
||||
goto_max:
|
||||
name: "Move to MAX"
|
||||
goto_min:
|
||||
name: "Move to MIN"
|
||||
goto_m1:
|
||||
name: "Move to M1"
|
||||
goto_m2:
|
||||
name: "Move to M2"
|
||||
goto_m3:
|
||||
name: "Move to M3"
|
||||
goto_m4:
|
||||
name: "Move to M4"
|
||||
save_m1:
|
||||
name: "Save M1"
|
||||
save_m2:
|
||||
name: "Save M2"
|
||||
save_m3:
|
||||
name: "Save M3"
|
||||
save_m4:
|
||||
name: "Save M4"
|
||||
|
||||
numbers:
|
||||
height:
|
||||
name: "Height"
|
||||
height_abs:
|
||||
name: "Height absolut"
|
||||
mode: SLIDER
|
||||
height_pct:
|
||||
name: "Height Percent"
|
||||
name: "Height percent"
|
||||
mode: SLIDER
|
||||
|
||||
## lambda usage
|
||||
# button:
|
||||
|
|
@ -97,17 +107,16 @@ jiecang_desk_controller:
|
|||
# - platform: template
|
||||
# name: "Position 2"
|
||||
# on_press:
|
||||
# lambda: "id(my_desk).goto_position(2);"
|
||||
# lambda: "id(my_desk).goto_mem_position(2);"
|
||||
# - platform: template
|
||||
# name: "Save Position 4"
|
||||
# on_press:
|
||||
# lambda: "id(my_desk).save_position(4);"
|
||||
# lambda: "id(my_desk).save_mem_position(4);"
|
||||
# - platform: template
|
||||
# name: "Go to 100cm"
|
||||
# on_press:
|
||||
# lambda: "id(my_desk).goto_height(100);"
|
||||
|
||||
|
||||
# the usual stuff
|
||||
|
||||
esp8266:
|
||||
|
|
@ -126,3 +135,4 @@ api:
|
|||
key: !secret encryption_key
|
||||
ota:
|
||||
|
||||
logger:
|
||||
|
|
@ -0,0 +1,381 @@
|
|||
#include "maidsite_desk_controller.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace maidsite_desk_controller
|
||||
{
|
||||
static const char *const TAG = "maidsite_desk_controller";
|
||||
|
||||
float MaidsiteDeskController::byte2float(int high, int low)
|
||||
{
|
||||
return static_cast<float>((high << 8) + low) / 10;
|
||||
}
|
||||
|
||||
bool MaidsiteDeskController::bufferMessage(int data, unsigned int *buffer, int len)
|
||||
{
|
||||
// This is a very simple method of receiving messages from the desk.
|
||||
// It will fail on messages that contain the value 0x7E in their payload.
|
||||
// But it is super simple and works for the messages we care about.
|
||||
|
||||
// Read message:
|
||||
// format: 0xF2 0xF2 [command] [param_count] [[param] ...] [checksum] 0x7E
|
||||
// checksum: sum of [command], [param_count] and all [param]s
|
||||
|
||||
static int cmd_incoming = 0; // 0: wait for F2, 1: wait for 2nd F2, 2: buffer data
|
||||
static int pos = 0;
|
||||
|
||||
if (cmd_incoming < 2 && data == 0xF2)
|
||||
{ // start of message, must appear twice
|
||||
cmd_incoming++;
|
||||
pos = 0;
|
||||
}
|
||||
else if (cmd_incoming == 1)
|
||||
{ // no second F2 received
|
||||
cmd_incoming = 0;
|
||||
}
|
||||
else if (cmd_incoming == 2)
|
||||
{
|
||||
if (data == 0x7E)
|
||||
{ // end of message
|
||||
cmd_incoming = 0;
|
||||
for (; pos < len - 1; pos++)
|
||||
{ // fill rest of buffer with zeros
|
||||
buffer[pos] = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (pos >= len)
|
||||
{ // message too long, drop it
|
||||
cmd_incoming = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[pos++] = data; // buffer data
|
||||
}
|
||||
} // else: received garbage
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::handleMessage(unsigned int *message)
|
||||
{
|
||||
// ESP_LOGV("maidsite_desk_controller", "message %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", message[0], message[1], message[2], message[3], message[4], message[5], message[6], message[7], message[8], message[9]);
|
||||
|
||||
switch (message[0])
|
||||
{
|
||||
case 0x01:
|
||||
ESP_LOGV("maidsite_desk_controller", "sensor_height 0x%0X%0X", message[2], message[3]);
|
||||
float new_height;
|
||||
new_height = byte2float(message[2], message[3]);
|
||||
if (new_height == current_height)
|
||||
return;
|
||||
current_height = new_height;
|
||||
if (sensor_height != nullptr)
|
||||
sensor_height->publish_state(current_height);
|
||||
if (number_height_abs != nullptr)
|
||||
number_height_abs->publish_state(current_height);
|
||||
|
||||
if (sensor_height_pct != nullptr && limit_max != 0)
|
||||
sensor_height_pct->publish_state((current_height - limit_min) / (limit_max - limit_min) * 100);
|
||||
if (number_height_pct != nullptr && limit_max != 0)
|
||||
number_height_pct->publish_state(roundf((current_height - limit_min) / (limit_max - limit_min) * 1000) / 10);
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
ESP_LOGV("maidsite_desk_controller", "sensor_limits 0x%0X max %i min %i", message[2], (message[2] & 1), (message[2] >> 4));
|
||||
|
||||
if ((message[2] & 1) == 0)
|
||||
{ // low nibble 0 -> no max limit, use physical_max
|
||||
limit_max = physical_max;
|
||||
if (sensor_height_max != nullptr)
|
||||
sensor_height_max->publish_state(limit_max);
|
||||
if (number_height_abs != nullptr)
|
||||
number_height_abs->set_max_value(limit_max);
|
||||
}
|
||||
if ((message[2] >> 4) == 0)
|
||||
{ // high nibble 0 -> no min limit, use physical_min
|
||||
limit_min = physical_min;
|
||||
if (sensor_height_min != nullptr)
|
||||
sensor_height_min->publish_state(limit_min);
|
||||
if (number_height_abs != nullptr)
|
||||
number_height_abs->set_min_value(limit_min);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x07:
|
||||
ESP_LOGV("maidsite_desk_controller", "sensor_physical_limits 0x%02X%02X 0x%02X%02X", message[2], message[3], message[4], message[5]);
|
||||
physical_max = byte2float(message[2], message[3]);
|
||||
physical_min = byte2float(message[4], message[5]);
|
||||
break;
|
||||
|
||||
case 0x21:
|
||||
ESP_LOGV("maidsite_desk_controller", "sensor_height_max 0x%02X%02X", message[2], message[3]);
|
||||
limit_max = byte2float(message[2], message[3]);
|
||||
if (sensor_height_max != nullptr)
|
||||
sensor_height_max->publish_state(limit_max);
|
||||
if (number_height_abs != nullptr)
|
||||
number_height_abs->set_max_value(limit_max);
|
||||
break;
|
||||
|
||||
case 0x22:
|
||||
ESP_LOGV("maidsite_desk_controller", "sensor_height_min 0x%02X%02X", message[2], message[3]);
|
||||
limit_min = byte2float(message[2], message[3]);
|
||||
if (sensor_height_min != nullptr)
|
||||
sensor_height_min->publish_state(limit_min);
|
||||
if (number_height_abs != nullptr)
|
||||
number_height_abs->set_min_value(limit_min);
|
||||
break;
|
||||
|
||||
case 0x25:
|
||||
ESP_LOGV("maidsite_desk_controller", "sensor_position_m1 0x%02X%02X", message[2], message[3]);
|
||||
if (sensor_position_m1 != nullptr)
|
||||
sensor_position_m1->publish_state(byte2float(message[2], message[3]));
|
||||
break;
|
||||
|
||||
case 0x26:
|
||||
ESP_LOGV("maidsite_desk_controller", "sensor_position_m2 0x%02X%02X", message[2], message[3]);
|
||||
if (sensor_position_m2 != nullptr)
|
||||
sensor_position_m2->publish_state(byte2float(message[2], message[3]));
|
||||
break;
|
||||
|
||||
case 0x27:
|
||||
ESP_LOGV("maidsite_desk_controller", "sensor_position_m3 0x%02X%02X", message[2], message[3]);
|
||||
if (sensor_position_m3 != nullptr)
|
||||
sensor_position_m3->publish_state(byte2float(message[2], message[3]));
|
||||
break;
|
||||
|
||||
case 0x28:
|
||||
ESP_LOGV("maidsite_desk_controller", "sensor_position_m4 0x%02X%02X", message[2], message[3]);
|
||||
if (sensor_position_m4 != nullptr)
|
||||
sensor_position_m4->publish_state(byte2float(message[2], message[3]));
|
||||
break;
|
||||
|
||||
// case 0x0E:
|
||||
// ESP_LOGV("maidsite_desk_controller", "units 0x%02X", message[2]);
|
||||
// if (units != nullptr)
|
||||
// units->publish_state(byte2float(message[2], message[3]));
|
||||
// break;
|
||||
|
||||
default:
|
||||
ESP_LOGV("maidsite_desk_controller", "unknown message %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", message[0], message[1], message[2], message[3], message[4], message[5], message[6], message[7], message[8], message[9]);
|
||||
}
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::update()
|
||||
{
|
||||
const int max_length = 10;
|
||||
static unsigned int buffer[max_length];
|
||||
while (available())
|
||||
{
|
||||
if (bufferMessage(read(), buffer, max_length))
|
||||
{
|
||||
handleMessage(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::add_button(button::Button *btn, int action)
|
||||
{
|
||||
btn->add_on_press_callback([this, action]() { this->button_press_action(action); });
|
||||
}
|
||||
|
||||
// Write message:
|
||||
// format: 0xF1 0xF1 [command] [param_count] [[param] ...] [checksum] 0x7E
|
||||
// checksum: sum of [command], [param_count] and all [param]s
|
||||
void MaidsiteDeskController::send_simple_command(unsigned char cmd)
|
||||
{
|
||||
write_array({0xF1, 0xF1, cmd, 0x00, cmd, 0x7E});
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::send_2byte_command(unsigned char cmd, unsigned char high_byte, unsigned char low_byte)
|
||||
{
|
||||
unsigned char checksum = cmd + 2 + high_byte + low_byte;
|
||||
write_array({ 0xF1, 0xF1, cmd, 0x02, high_byte, low_byte, checksum, 0x7E });
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::step_up()
|
||||
{
|
||||
send_simple_command(0x01);
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::step_down()
|
||||
{
|
||||
send_simple_command(0x02);
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::stop()
|
||||
{
|
||||
send_simple_command(0x2B);
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::goto_mem_position(int pos)
|
||||
{
|
||||
switch (pos)
|
||||
{
|
||||
case 1:
|
||||
send_simple_command(0x05);
|
||||
break;
|
||||
case 2:
|
||||
send_simple_command(0x06);
|
||||
break;
|
||||
case 3:
|
||||
send_simple_command(0x27);
|
||||
break;
|
||||
case 4:
|
||||
send_simple_command(0x28);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::save_mem_position(int pos)
|
||||
{
|
||||
switch (pos)
|
||||
{
|
||||
case 1:
|
||||
send_simple_command(0x03);
|
||||
break;
|
||||
case 2:
|
||||
send_simple_command(0x04);
|
||||
break;
|
||||
case 3:
|
||||
send_simple_command(0x25);
|
||||
break;
|
||||
case 4:
|
||||
send_simple_command(0x26);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::request_physical_limits()
|
||||
{
|
||||
send_simple_command(0x0C);
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::request_limits()
|
||||
{
|
||||
send_simple_command(0x20);
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::request_settings()
|
||||
{
|
||||
send_simple_command(0x07);
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::request_move_to()
|
||||
{
|
||||
send_simple_command(0x1B);
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::goto_height(float height)
|
||||
{
|
||||
unsigned char high_byte = ((int)height * 10) >> 8;
|
||||
unsigned char low_byte = ((int)height * 10) & 0xFF;
|
||||
|
||||
send_2byte_command(0x80, high_byte, low_byte);
|
||||
request_move_to();
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::goto_max_position()
|
||||
{
|
||||
goto_height(limit_max);
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::goto_min_position()
|
||||
{
|
||||
goto_height(limit_min);
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::button_press_action(int action)
|
||||
{
|
||||
ESP_LOGV("MaidsiteDeskController", "button_press_action %i", action);
|
||||
switch (action)
|
||||
{
|
||||
case BUTTON_STEP_UP:
|
||||
step_up();
|
||||
break;
|
||||
case BUTTON_STEP_DOWN:
|
||||
step_down();
|
||||
break;
|
||||
case BUTTON_STOP:
|
||||
stop();
|
||||
break;
|
||||
case BUTTON_GOTO_MAX:
|
||||
goto_max_position();
|
||||
break;
|
||||
case BUTTON_GOTO_MIN:
|
||||
goto_min_position();
|
||||
break;
|
||||
case BUTTON_GOTO_M1:
|
||||
goto_mem_position(1);
|
||||
break;
|
||||
case BUTTON_GOTO_M2:
|
||||
goto_mem_position(2);
|
||||
break;
|
||||
case BUTTON_GOTO_M3:
|
||||
goto_mem_position(3);
|
||||
break;
|
||||
case BUTTON_GOTO_M4:
|
||||
goto_mem_position(4);
|
||||
break;
|
||||
case BUTTON_SAVE_M1:
|
||||
save_mem_position(1);
|
||||
delay(100);
|
||||
request_settings();
|
||||
break;
|
||||
case BUTTON_SAVE_M2:
|
||||
save_mem_position(2);
|
||||
delay(100);
|
||||
request_settings();
|
||||
break;
|
||||
case BUTTON_SAVE_M3:
|
||||
save_mem_position(3);
|
||||
delay(100);
|
||||
request_settings();
|
||||
break;
|
||||
case BUTTON_SAVE_M4:
|
||||
save_mem_position(4);
|
||||
delay(100);
|
||||
request_settings();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::add_number(MaidsiteDeskNumber *number, int type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case NUMBER_HEIGHT:
|
||||
number_height_abs = number;
|
||||
break;
|
||||
case NUMBER_HEIGHT_PCT:
|
||||
number_height_pct = number;
|
||||
number_height_pct->set_min_value(0);
|
||||
number_height_pct->set_max_value(100);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
number->set_type(type);
|
||||
number->set_parent(this);
|
||||
}
|
||||
|
||||
void MaidsiteDeskController::number_control(int type, float value)
|
||||
{
|
||||
ESP_LOGV("MaidsiteDeskController", "number_control %i", type);
|
||||
switch (type)
|
||||
{
|
||||
case NUMBER_HEIGHT:
|
||||
goto_height(value);
|
||||
break;
|
||||
case NUMBER_HEIGHT_PCT:
|
||||
if (limit_max > 0 && limit_max > limit_min)
|
||||
goto_height((limit_max - limit_min) * value / 100 + limit_min);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MaidsiteDeskButton::press_action() {}
|
||||
|
||||
} // namespace maidsite_desk_controller
|
||||
} // namespace esphome
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
#pragma once
|
||||
|
||||
#include <future>
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "maidsite_desk_number.h"
|
||||
|
||||
#define BUTTON_STEP_UP 0
|
||||
#define BUTTON_STEP_DOWN 1
|
||||
#define BUTTON_STOP 2
|
||||
#define BUTTON_GOTO_MAX 3
|
||||
#define BUTTON_GOTO_MIN 4
|
||||
#define BUTTON_GOTO_M1 5
|
||||
#define BUTTON_GOTO_M2 6
|
||||
#define BUTTON_GOTO_M3 7
|
||||
#define BUTTON_GOTO_M4 8
|
||||
#define BUTTON_SAVE_M1 9
|
||||
#define BUTTON_SAVE_M2 10
|
||||
#define BUTTON_SAVE_M3 11
|
||||
#define BUTTON_SAVE_M4 12
|
||||
|
||||
#define NUMBER_HEIGHT 0
|
||||
#define NUMBER_HEIGHT_PCT 1
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace maidsite_desk_controller
|
||||
{
|
||||
|
||||
class MaidsiteDeskController : public PollingComponent, public sensor::Sensor, public uart::UARTDevice
|
||||
{
|
||||
private:
|
||||
float current_height = 0;
|
||||
float limit_min = 0;
|
||||
float limit_max = 0;
|
||||
float physical_min = 0;
|
||||
float physical_max = 0;
|
||||
|
||||
float byte2float(int high, int low);
|
||||
bool bufferMessage(int data, unsigned int *buffer, int len);
|
||||
void handleMessage(unsigned int *message);
|
||||
|
||||
public:
|
||||
void update() override;
|
||||
|
||||
void set_sensor_height(sensor::Sensor *sensor) { this->sensor_height = sensor; }
|
||||
void set_sensor_height_min(sensor::Sensor *sensor) { this->sensor_height_min = sensor; }
|
||||
void set_sensor_height_max(sensor::Sensor *sensor) { this->sensor_height_max = sensor; }
|
||||
void set_sensor_height_pct(sensor::Sensor *sensor) { this->sensor_height_pct = sensor; }
|
||||
void set_sensor_position_m1(sensor::Sensor *sensor) { this->sensor_position_m1 = sensor; }
|
||||
void set_sensor_position_m2(sensor::Sensor *sensor) { this->sensor_position_m2 = sensor; }
|
||||
void set_sensor_position_m3(sensor::Sensor *sensor) { this->sensor_position_m3 = sensor; }
|
||||
void set_sensor_position_m4(sensor::Sensor *sensor) { this->sensor_position_m4 = sensor; }
|
||||
|
||||
void add_button(button::Button *btn, int action);
|
||||
void add_number(MaidsiteDeskNumber *number, int type);
|
||||
|
||||
void send_simple_command(unsigned char cmd);
|
||||
void send_2byte_command(unsigned char cmd, unsigned char high_byte, unsigned char low_byte);
|
||||
|
||||
void step_up();
|
||||
void step_down();
|
||||
void stop();
|
||||
void goto_mem_position(int pos);
|
||||
void save_mem_position(int pos);
|
||||
void goto_height(float height);
|
||||
void goto_max_position();
|
||||
void goto_min_position();
|
||||
|
||||
void request_physical_limits();
|
||||
void request_limits();
|
||||
void request_settings();
|
||||
void request_move_to();
|
||||
|
||||
void number_control(int type, float value);
|
||||
|
||||
protected:
|
||||
Sensor *sensor_height{nullptr};
|
||||
Sensor *sensor_height_min{nullptr};
|
||||
Sensor *sensor_height_max{nullptr};
|
||||
Sensor *sensor_height_pct{nullptr};
|
||||
|
||||
Sensor *sensor_position_m1{nullptr};
|
||||
Sensor *sensor_position_m2{nullptr};
|
||||
Sensor *sensor_position_m3{nullptr};
|
||||
Sensor *sensor_position_m4{nullptr};
|
||||
|
||||
MaidsiteDeskNumber *number_height_abs{nullptr};
|
||||
MaidsiteDeskNumber *number_height_pct{nullptr};
|
||||
|
||||
void button_press_action(int type);
|
||||
};
|
||||
|
||||
class MaidsiteDeskButton : public Component, public button::Button
|
||||
{
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace maidsite_desk_controller
|
||||
} // namespace esphome
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
#include "maidsite_desk_number.h"
|
||||
#include "maidsite_desk_controller.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace maidsite_desk_controller
|
||||
{
|
||||
|
||||
static const char *const TAG = "maidsite_desk.number";
|
||||
|
||||
void MaidsiteDeskNumber::setup() {}
|
||||
|
||||
void MaidsiteDeskNumber::dump_config()
|
||||
{
|
||||
LOG_NUMBER("", "MaidsiteDeskNumber", this);
|
||||
ESP_LOGCONFIG(TAG, " type %i", type);
|
||||
}
|
||||
|
||||
void MaidsiteDeskNumber::control(float value)
|
||||
{
|
||||
this->publish_state(value);
|
||||
parent->number_control(this->type, value);
|
||||
}
|
||||
|
||||
void MaidsiteDeskNumber::set_min_value(float value)
|
||||
{
|
||||
traits.set_min_value(value);
|
||||
}
|
||||
|
||||
void MaidsiteDeskNumber::set_max_value(float value)
|
||||
{
|
||||
traits.set_max_value(value);
|
||||
}
|
||||
|
||||
} // namespace maidsite_desk_controller
|
||||
} // namespace esphome
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace maidsite_desk_controller
|
||||
{
|
||||
|
||||
class MaidsiteDeskController;
|
||||
|
||||
class MaidsiteDeskNumber : public number::Number, public Component
|
||||
{
|
||||
private:
|
||||
int type;
|
||||
MaidsiteDeskController *parent;
|
||||
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_parent(MaidsiteDeskController *parent) { this->parent = parent; }
|
||||
void set_type(int type) { this->type = type; }
|
||||
|
||||
void set_min_value(float value);
|
||||
void set_max_value(float value);
|
||||
|
||||
protected:
|
||||
void control(float value) override;
|
||||
};
|
||||
|
||||
} // namespace maidsite_desk_controller
|
||||
} // namespace esphome
|
||||
|
|
@ -1 +0,0 @@
|
|||
components/jiecang_desk_controller/example_basic.yaml
|
||||
|
|
@ -0,0 +1 @@
|
|||
components/maidsite_desk_controller/example_basic.yaml
|
||||
Loading…
Reference in New Issue