1 Commits
Author SHA1 Message Date
kislovdm b4c02dd456 refact 2025-07-24 20:16:10 +03:00
7 changed files with 235 additions and 36 deletions
+81
View File
@@ -173,6 +173,64 @@ text_sensor:
icon: mdi:alert-circle-outline
update_interval: never
select:
- platform: template
name: "Тип напитка"
id: coffee_type
options:
- "Эспрессо"
- "Американо"
- "Капучино"
- "Латте"
- "Кофе с молоком"
- "Молочная пена"
- "Горячая вода"
initial_option: "Эспрессо"
optimistic: true
- platform: template
name: "Крепость"
id: coffee_strength
options:
- "Мягкий"
- "Обычный"
- "Крепкий"
- "Молотый"
initial_option: "Обычный"
optimistic: true
- platform: template
name: "Чашки"
id: coffee_cups
options:
- "1"
- "2"
- "3"
- "4"
initial_option: "1"
optimistic: true
number:
- platform: template
name: "Объем кофе (мл)"
id: coffee_volume
min_value: 20
max_value: 250
step: 10
initial_value: 100
unit_of_measurement: "ml"
optimistic: true
- platform: template
name: "Объем молока (мл)"
id: milk_volume
min_value: 0
max_value: 250
step: 10
initial_value: 0
unit_of_measurement: "ml"
optimistic: true
button:
- platform: template
name: "Американо"
@@ -185,6 +243,29 @@ button:
"AA AA AA 90 0F 0A 00 02 00 03 02 00 B4 00 00 00 67 48 E4 4B 55",
"AA AA AA 91 10 01 03 56 9A 8C B8 55"
}, 50);
- platform: template
name: "Выключение"
on_press:
then:
- lambda: |-
id(saeco_series_6000_component)->send_packets_to_mainboard(
std::vector<std::string>{
"AA AA AA FE 00 00 C8 87 1B 40 55"
}, 50);
- platform: template
name: "Приготовить напиток"
icon: mdi:coffee
on_press:
- lambda: |-
{
uint8_t type = id(coffee_type).index_of(id(coffee_type).state).value_or(0); // 0..6
uint8_t bean = id(coffee_strength).index_of(id(coffee_strength).state).value_or(0); // 0..3
uint8_t cups = id(coffee_cups).index_of(id(coffee_cups).state).value_or(0) + 1; // 1..4
uint16_t vol = (uint16_t) id(coffee_volume).state;
uint16_t milk = (uint16_t) id(milk_volume).state;
id(saeco_series_6000_component)->coffee_build(type, bean, cups, vol, milk);
}
#[20:45:34][I][saeco_series_6000:079]: Packet from Display: AA AA AA 93 0A 01 01 57 15 3A ED 55
#[20:45:34][I][saeco_series_6000:079]: Packet from Display: AA AA AA 90 0B 0A 05 03 00 02 00 00 64 00 00 00 39 BF 5B 74 55
@@ -0,0 +1,6 @@
# Добавляем файл генерации рецептов
src_files(
saeco_series_6000.cpp
recipe.cpp
crc.cpp
)
+43
View File
@@ -0,0 +1,43 @@
#include "saeco_series_6000.h"
#include <vector>
namespace esphome {
namespace saeco_series_6000 {
uint32_t SaecoSeries6000::calc_crc(const std::vector<uint8_t>& data, size_t start, size_t end) {
uint32_t crc = 0xFFFFFFFF;
const uint32_t POLY = 0x04C11DB7;
if (start >= end) return 0;
// start
uint8_t val = data[start];
((uint8_t*)(&crc))[3] ^= BitReverse[val];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80000000) {
crc = (crc << 1) ^ POLY;
} else {
crc <<= 1;
}
}
// остальные байты
for (size_t i = start + 1; i < end; i++) {
uint8_t v = data[i];
((uint8_t*)(&crc))[3] ^= BitReverse[v];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80000000) {
crc = (crc << 1) ^ POLY;
} else {
crc <<= 1;
}
}
}
uint32_t t32 = 0;
((uint8_t*)(&t32))[0] = BitReverse[((uint8_t*)(&crc))[3]];
((uint8_t*)(&t32))[1] = BitReverse[((uint8_t*)(&crc))[2]];
((uint8_t*)(&t32))[2] = BitReverse[((uint8_t*)(&crc))[1]];
((uint8_t*)(&t32))[3] = BitReverse[((uint8_t*)(&crc))[0]];
crc = t32 ^ 0xFFFFFFFF;
return crc;
}
} // namespace saeco_series_6000
} // namespace esphome
+59
View File
@@ -0,0 +1,59 @@
#include "saeco_series_6000.h"
#include <vector>
namespace esphome {
namespace saeco_series_6000 {
std::vector<uint8_t> SaecoSeries6000::build_coffee_recipe(uint8_t type, uint8_t bean, uint8_t cups, uint16_t vol, uint16_t milk) {
static const uint8_t CoffeePattern[7][4] = {
{0,1,1,2}, // Espresso
{1,2,3,2}, // Americano
{2,2,1,2}, // Cappuccino
{3,2,2,2}, // Latte
{4,0,0,1}, // Coffee with milk
{5,1,0,1}, // Milk foam
{6,0,0,1} // Hot water
};
uint8_t rec[10] = {0};
if (type >= 7) type = 0;
rec[0] = CoffeePattern[type][0];
if (rec[0] == 5 || rec[0] == 4) {
rec[1] = 3; // всегда молотый для молока и milk foam
} else {
rec[1] = bean;
}
if (cups > CoffeePattern[type][3]) {
rec[2] = CoffeePattern[type][3];
} else {
rec[2] = cups;
}
rec[3] = CoffeePattern[type][1];
rec[4] = CoffeePattern[type][2];
if (rec[0] == 1) { // Americano
rec[6] = 40;
rec[7] = 0;
rec[8] = (uint8_t)vol;
rec[9] = vol / 0x100;
rec[5] = 0;
} else {
rec[6] = (uint8_t)vol;
rec[7] = vol / 0x100;
if (rec[0] == 0 || rec[0] == 1 || rec[0] == 4 || rec[0] == 5) {
if (rec[0] == 5) {
rec[5] = 2;
} else {
rec[5] = 0;
}
rec[8] = 0;
rec[9] = 0;
} else {
rec[5] = 2;
rec[8] = (uint8_t)milk;
rec[9] = milk / 0x100;
}
}
return std::vector<uint8_t>(rec, rec + 10);
}
} // namespace saeco_series_6000
} // namespace esphome
@@ -24,6 +24,7 @@ void SaecoSeries6000::loop() {
uart_display_->write_byte(byte); // Mirror byte immediately
process_mainboard_byte(byte); // Process the "sniffed" byte
}
// Контроль активности UART (выключение через 15 сек)
static uint32_t last_check = 0;
uint32_t now = millis();
@@ -232,41 +233,6 @@ const uint8_t SaecoSeries6000::BitReverse[256] = {
0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF
};
uint32_t SaecoSeries6000::calc_crc(const std::vector<uint8_t>& data, size_t start, size_t end) {
uint32_t crc = 0xFFFFFFFF;
const uint32_t POLY = 0x04C11DB7;
if (start >= end) return 0;
// start
uint8_t val = data[start];
((uint8_t*)(&crc))[3] ^= BitReverse[val];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80000000) {
crc = (crc << 1) ^ POLY;
} else {
crc <<= 1;
}
}
// остальные байты
for (size_t i = start + 1; i < end; i++) {
uint8_t v = data[i];
((uint8_t*)(&crc))[3] ^= BitReverse[v];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80000000) {
crc = (crc << 1) ^ POLY;
} else {
crc <<= 1;
}
}
}
uint32_t t32 = 0;
((uint8_t*)(&t32))[0] = BitReverse[((uint8_t*)(&crc))[3]];
((uint8_t*)(&t32))[1] = BitReverse[((uint8_t*)(&crc))[2]];
((uint8_t*)(&t32))[2] = BitReverse[((uint8_t*)(&crc))[1]];
((uint8_t*)(&t32))[3] = BitReverse[((uint8_t*)(&crc))[0]];
crc = t32 ^ 0xFFFFFFFF;
return crc;
}
std::vector<uint8_t> SaecoSeries6000::parse_hex_string_to_bytes(const std::string& hex_string) {
std::vector<uint8_t> pkt;
size_t pos = 0;
@@ -339,5 +305,26 @@ void SaecoSeries6000::send_packets_to_mainboard(const std::vector<std::vector<ui
// Реализация SendPacketsAction полностью в header (saeco_series_6000.h)
void SaecoSeries6000::coffee_build(uint8_t type, uint8_t bean, uint8_t cups, uint16_t vol, uint16_t milk) {
// Формируем пакет для отправки (AA AA AA 90 ... CRC CRC CRC CRC 55)
std::vector<uint8_t> pkt = {0xAA, 0xAA, 0xAA, 0x90};
std::vector<uint8_t> rec = build_coffee_recipe(type, bean, cups, vol, milk);
pkt.insert(pkt.end(), rec.begin(), rec.end());
// Добавляем 4 байта CRC (заполним позже) и 0x55
pkt.resize(pkt.size() + 4 + 1, 0);
pkt[pkt.size() - 1] = 0x55;
// Пересчёт CRC
size_t preamble = 3;
size_t crc_start = preamble;
size_t crc_end = pkt.size() - 5;
uint32_t crc = calc_crc(pkt, crc_start, crc_end);
pkt[pkt.size()-2] = (crc >> 24) & 0xFF;
pkt[pkt.size()-3] = (crc >> 16) & 0xFF;
pkt[pkt.size()-4] = (crc >> 8) & 0xFF;
pkt[pkt.size()-5] = crc & 0xFF;
// Отправляем пакет
send_packets_to_mainboard(std::vector<std::vector<uint8_t>>{pkt});
}
} // namespace saeco_series_6000
} // namespace esphome
@@ -26,6 +26,28 @@ class SaecoSeries6000 : public Component {
void send_packets_to_mainboard(const std::vector<std::string>& hex_packets, uint32_t delay_ms = 10);
void send_packets_to_mainboard(const std::string& hex_packet, uint32_t delay_ms = 10);
// Типы напитков
enum CoffeeType {
ESPRESSO = 0,
AMERICANO = 1,
CAPPUCCINO = 2,
LATTE = 3,
COFFEE_WITH_MILK = 4,
MILK_FOAM = 5,
HOT_WATER = 6
};
// Крепость/помол
enum CoffeeStrength {
MILD = 0,
NORMAL = 1,
STRONG = 2,
GROUND = 3
};
// Построение и отправка пакета приготовления кофе
void coffee_build(uint8_t type, uint8_t bean, uint8_t cups, uint16_t vol, uint16_t milk);
// Генерация рецепта (только формирование данных)
static std::vector<uint8_t> build_coffee_recipe(uint8_t type, uint8_t bean, uint8_t cups, uint16_t vol, uint16_t milk);
protected:
uart::UARTComponent *uart_display_;
uart::UARTComponent *uart_mainboard_;
@@ -57,7 +79,8 @@ class SaecoSeries6000 : public Component {
void parse_b5_packet(const uint8_t* buffer, uint16_t size);
void pub_status(uint8_t status);
static const uint8_t BitReverse[256];
uint32_t calc_crc(const std::vector<uint8_t>& data, size_t start, size_t end);
// CRC-32 для пакетов кофемашины
static uint32_t calc_crc(const std::vector<uint8_t>& data, size_t start, size_t end);
std::vector<uint8_t> parse_hex_string_to_bytes(const std::string& hex_string);
};