From b4c02dd4564b4d86f944a616d171b70af16ed8bb Mon Sep 17 00:00:00 2001 From: kislovdm Date: Thu, 24 Jul 2025 20:16:10 +0300 Subject: [PATCH] refact --- coffee-philips-5400.yaml | 81 ++++++++++++++++++ components/saeco_series_6000/CMakeLists.txt | 6 ++ .../__pycache__/__init__.cpython-311.pyc | Bin 7476 -> 0 bytes components/saeco_series_6000/crc.cpp | 43 ++++++++++ components/saeco_series_6000/recipe.cpp | 59 +++++++++++++ .../saeco_series_6000/saeco_series_6000.cpp | 57 +++++------- .../saeco_series_6000/saeco_series_6000.h | 25 +++++- 7 files changed, 235 insertions(+), 36 deletions(-) create mode 100644 components/saeco_series_6000/CMakeLists.txt delete mode 100644 components/saeco_series_6000/__pycache__/__init__.cpython-311.pyc create mode 100644 components/saeco_series_6000/crc.cpp create mode 100644 components/saeco_series_6000/recipe.cpp diff --git a/coffee-philips-5400.yaml b/coffee-philips-5400.yaml index c4d6daa..91367ca 100644 --- a/coffee-philips-5400.yaml +++ b/coffee-philips-5400.yaml @@ -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{ + "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 diff --git a/components/saeco_series_6000/CMakeLists.txt b/components/saeco_series_6000/CMakeLists.txt new file mode 100644 index 0000000..a1d1475 --- /dev/null +++ b/components/saeco_series_6000/CMakeLists.txt @@ -0,0 +1,6 @@ +# Добавляем файл генерации рецептов +src_files( + saeco_series_6000.cpp + recipe.cpp + crc.cpp +) \ No newline at end of file diff --git a/components/saeco_series_6000/__pycache__/__init__.cpython-311.pyc b/components/saeco_series_6000/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index bebca0b3507ff25646520fe02e99f5970f91162f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7476 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 H^V + +namespace esphome { +namespace saeco_series_6000 { + +uint32_t SaecoSeries6000::calc_crc(const std::vector& 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 \ No newline at end of file diff --git a/components/saeco_series_6000/recipe.cpp b/components/saeco_series_6000/recipe.cpp new file mode 100644 index 0000000..657713f --- /dev/null +++ b/components/saeco_series_6000/recipe.cpp @@ -0,0 +1,59 @@ +#include "saeco_series_6000.h" +#include + +namespace esphome { +namespace saeco_series_6000 { + +std::vector 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(rec, rec + 10); +} + +} // namespace saeco_series_6000 +} // namespace esphome \ No newline at end of file diff --git a/components/saeco_series_6000/saeco_series_6000.cpp b/components/saeco_series_6000/saeco_series_6000.cpp index 51bc1dd..b1b0036 100644 --- a/components/saeco_series_6000/saeco_series_6000.cpp +++ b/components/saeco_series_6000/saeco_series_6000.cpp @@ -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& 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 SaecoSeries6000::parse_hex_string_to_bytes(const std::string& hex_string) { std::vector pkt; size_t pos = 0; @@ -339,5 +305,26 @@ void SaecoSeries6000::send_packets_to_mainboard(const std::vector pkt = {0xAA, 0xAA, 0xAA, 0x90}; + std::vector 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>{pkt}); +} + } // namespace saeco_series_6000 } // namespace esphome \ No newline at end of file diff --git a/components/saeco_series_6000/saeco_series_6000.h b/components/saeco_series_6000/saeco_series_6000.h index 4e77ac8..a54bfb2 100644 --- a/components/saeco_series_6000/saeco_series_6000.h +++ b/components/saeco_series_6000/saeco_series_6000.h @@ -26,6 +26,28 @@ class SaecoSeries6000 : public Component { void send_packets_to_mainboard(const std::vector& 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 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& data, size_t start, size_t end); + // CRC-32 для пакетов кофемашины + static uint32_t calc_crc(const std::vector& data, size_t start, size_t end); std::vector parse_hex_string_to_bytes(const std::string& hex_string); };