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 bebca0b..0000000 Binary files a/components/saeco_series_6000/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/components/saeco_series_6000/crc.cpp b/components/saeco_series_6000/crc.cpp new file mode 100644 index 0000000..ba04320 --- /dev/null +++ b/components/saeco_series_6000/crc.cpp @@ -0,0 +1,43 @@ +#include "saeco_series_6000.h" +#include + +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); };