Add files via upload

This commit is contained in:
DivanX10
2023-07-11 17:18:06 +03:00
committed by GitHub
parent b622adb53c
commit c4d89a0579
3 changed files with 1101 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart, sensor, text_sensor, number, select, button
from esphome.automation import maybe_simple_id
from esphome.const import CONF_ID
from esphome.const import (
CONF_ID,
CONF_NUMBER,
CONF_CURRENT,
CONF_VALUE,
CONF_INITIAL_VALUE,
CONF_MAX_VALUE,
CONF_MIN_VALUE,
CONF_STEP,
CONF_UNIT_OF_MEASUREMENT,
CONF_ACCURACY_DECIMALS,
CONF_RESTORE_VALUE,
CONF_ICON,
CONF_DEBUG,
CONF_OPTIONS,
)
DEPENDENCIES = ['uart','sensor']
AUTO_LOAD = ['sensor','text_sensor','number','select','button']
philips_series_5400_ns = cg.esphome_ns.namespace('philips_series_5400')
PhilipsSeries5400 = philips_series_5400_ns.class_('PhilipsSeries5400', cg.Component)
PhilipsSeries5400Select = philips_series_5400_ns.class_('PhilipsSeries5400Select', select.Select)
PhilipsSeries5400Number = philips_series_5400_ns.class_('PhilipsSeries5400Number', number.Number, cg.Component)
PhilipsSeries5400Button = philips_series_5400_ns.class_('PhilipsSeries5400Button', button.Button, cg.Component)
DISPLAY_UART_ID = 'display_uart'
MAINBOARD_UART_ID = 'mainboard_uart'
STATE_SENSOR = 'state_sensor'
MESS_B0 = "B0_msg"
MESS_B5 = "B5_msg"
MESS_BA = "BA_msg"
MESS_90 = "90_msg"
MESS_91 = "91_msg"
MESS_93 = "93_msg"
TEXT_PRODUCT = "Display_product"
COFFEE_VOL = "Coffee_volume"
MILK_VOL = "Milk_volume"
UNIT_MIL = "ml"
STERENGTH = "Strength_select"
CUPS = "Cups_select"
PRODUCT = "Product_select"
START_KEY = "Start_key"
ICON_CURRENT_STATE = "mdi:state-machine"
ICON_COFFEE_VOL = "mdi:coffee"
ICON_MILK_VOL = "mdi:coffee-outline"
ICON_PRODUCT = "mdi:coffee-maker-check"
ICON_STERENGTH = "mdi:spoon-sugar"
ICON_CUPS = "mdi:beaker-plus"
ICON_START_KEY = "mdi:coffee-to-go"
ICON_TEXT_PRODUCT = "mdi:list-status"
OPTIONS_STERENGTH = [
"Soft",
"Medium",
"Strong",
"Ground"
]
OPTIONS_PRODUCT = [
"Espresso",
"Coffee to go",
"Black Coffee",
"Luingo",
"Сaffe Crema",
"Ristretto",
"Americano",
"Coffee With Milk",
"Latte",
"Milk Coffee to go",
"Latte Macchiato",
"Сappuccino",
"Milk foam",
"Hot water"
]
OPTIONS_CUPS = [
"One cup",
"Two cups",
"ExtraShot",
]
CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(PhilipsSeries5400),
cv.Required(DISPLAY_UART_ID): cv.use_id(uart.UARTComponent),
cv.Required(MAINBOARD_UART_ID): cv.use_id(uart.UARTComponent),
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
cv.Optional(CONF_DEBUG, default=False): cv.boolean,
cv.Optional(STATE_SENSOR): sensor.sensor_schema(icon=ICON_CURRENT_STATE),
cv.Optional(MESS_B0): text_sensor.text_sensor_schema(),
cv.Optional(MESS_B5): text_sensor.text_sensor_schema(),
cv.Optional(MESS_BA): text_sensor.text_sensor_schema(),
cv.Optional(MESS_90): text_sensor.text_sensor_schema(),
cv.Optional(MESS_91): text_sensor.text_sensor_schema(),
cv.Optional(MESS_93): text_sensor.text_sensor_schema(),
cv.Optional(TEXT_PRODUCT): text_sensor.text_sensor_schema(icon=ICON_TEXT_PRODUCT),
# объем кофе Required
cv.Optional(COFFEE_VOL): number.NUMBER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(PhilipsSeries5400Number),
cv.Optional(CONF_VALUE, default=100): cv.positive_int,
cv.Optional(CONF_MAX_VALUE, default=500): cv.positive_int,
cv.Optional(CONF_MIN_VALUE, default=20): cv.positive_int,
cv.Optional(CONF_STEP, default=10): cv.positive_int,
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_MIL): str,
cv.Optional(CONF_ACCURACY_DECIMALS, default=0): cv.positive_int,
cv.Optional(CONF_RESTORE_VALUE, default=True): cv.boolean,
cv.Optional(CONF_ICON, default=ICON_COFFEE_VOL): cv.icon,
}
),
# объем молока
cv.Optional(MILK_VOL): number.NUMBER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(PhilipsSeries5400Number),
cv.Optional(CONF_VALUE, default=100):cv.positive_int,
cv.Optional(CONF_MAX_VALUE, default=500): cv.positive_int,
cv.Optional(CONF_MIN_VALUE, default=40): cv.positive_int,
cv.Optional(CONF_STEP, default=10): cv.positive_int,
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_MIL): str,
cv.Optional(CONF_ACCURACY_DECIMALS, default=0): cv.positive_int,
cv.Optional(CONF_RESTORE_VALUE, default=True): cv.boolean,
cv.Optional(CONF_ICON, default=ICON_MILK_VOL): cv.icon,
}
),
# крепость/помол
cv.Optional(STERENGTH): select.SELECT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(PhilipsSeries5400Select),
cv.Optional(CONF_ICON, default=ICON_STERENGTH): cv.icon,
cv.Optional(CONF_OPTIONS,default=OPTIONS_STERENGTH):cv.All(
cv.ensure_list(cv.string_strict),
cv.Length(min=1),
),
}
),
# количество чашек
cv.Optional(CUPS): select.SELECT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(PhilipsSeries5400Select),
cv.Optional(CONF_ICON, default=ICON_CUPS): cv.icon,
cv.Optional(CONF_OPTIONS,default=OPTIONS_CUPS):cv.All(
cv.ensure_list(cv.string_strict),
cv.Length(min=1),
),
}
),
# напиток
cv.Optional(PRODUCT): select.SELECT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(PhilipsSeries5400Select),
cv.Optional(CONF_ICON, default=ICON_PRODUCT): cv.icon,
cv.Optional(CONF_OPTIONS,default=OPTIONS_PRODUCT):cv.All(
cv.ensure_list(cv.string_strict),
cv.Length(min=1),
),
}
),
# кнопка запуска приготовления
cv.Optional(START_KEY): button.BUTTON_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(PhilipsSeries5400Button),
cv.Optional(CONF_ICON, default=ICON_START_KEY): cv.icon,
}
),
})
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
display = await cg.get_variable(config[DISPLAY_UART_ID])
cg.add(var.register_display_uart(display))
mainboard = await cg.get_variable(config[MAINBOARD_UART_ID])
cg.add(var.register_mainboard_uart(mainboard))
if STATE_SENSOR in config:
sens = await sensor.new_sensor(config[STATE_SENSOR])
cg.add(var.register_work_sensor(sens))
if MESS_B0 in config:
sens = await text_sensor.new_text_sensor(config[MESS_B0])
cg.add(var.set_sens_b0(sens))
if MESS_B5 in config:
sens = await text_sensor.new_text_sensor(config[MESS_B5])
cg.add(var.set_sens_b5(sens))
if MESS_BA in config:
sens = await text_sensor.new_text_sensor(config[MESS_BA])
cg.add(var.set_sens_ba(sens))
if MESS_90 in config:
sens = await text_sensor.new_text_sensor(config[MESS_90])
cg.add(var.set_sens_90(sens))
if MESS_91 in config:
sens = await text_sensor.new_text_sensor(config[MESS_91])
cg.add(var.set_sens_91(sens))
if MESS_93 in config:
sens = await text_sensor.new_text_sensor(config[MESS_93])
cg.add(var.set_sens_93(sens))
if TEXT_PRODUCT in config:
sens = await text_sensor.new_text_sensor(config[TEXT_PRODUCT])
cg.add(var.set_sens_product(sens))
if COFFEE_VOL in config:
sens = await number.new_number(config[COFFEE_VOL],min_value=config[COFFEE_VOL][CONF_MIN_VALUE], max_value=config[COFFEE_VOL][CONF_MAX_VALUE], step=config[COFFEE_VOL][CONF_STEP])
cg.add(var.set_vol_number(sens))
if MILK_VOL in config:
sens = await number.new_number(config[MILK_VOL],min_value=config[MILK_VOL][CONF_MIN_VALUE], max_value=config[MILK_VOL][CONF_MAX_VALUE], step=config[MILK_VOL][CONF_STEP])
cg.add(var.set_milk_number(sens))
if STERENGTH in config:
sel = await select.new_select(config[STERENGTH],options=config[STERENGTH][CONF_OPTIONS])
cg.add(var.set_grind_select(sel))
if CUPS in config:
sel = await select.new_select(config[CUPS], options=config[CUPS][CONF_OPTIONS])
cg.add(var.set_cups_select(sel))
if PRODUCT in config:
sel = await select.new_select(config[PRODUCT], options=config[PRODUCT][CONF_OPTIONS])
cg.add(var.set_product_select(sel))
if START_KEY in config:
but = await button.new_button(config[START_KEY])
cg.add(var.set_start_button(but))
if CONF_RESTORE_VALUE in config:
cg.add(var.set_store_settings(config[CONF_RESTORE_VALUE]))
if CONF_DEBUG in config:
cg.add(var.set_debug_settings(config[CONF_DEBUG]))

View File

@@ -0,0 +1,636 @@
#include <Arduino.h>
#include <stdarg.h>
#include "esphome/core/log.h"
#include "esphome/core/log.h"
#include "philips_series_5400.h"
//#define NEED_LOG
#define BUFFER_BOARD_SIZE 23 // буфер приема пакета в сторону матери
#define BUFFER_DISPL_SIZE 40 // буфер приема пакета в сторону дисплея
// для рассчета контрольной суммы
#define POLY 0x04c11db7
namespace esphome {
namespace philips_series_5400 {
//Байт 0, Байт 3, Байт 4, Cups_Max
uint8_t CoffePattern[14][4]={
{0,1,1,2},//Espresso
{0,1,2,1},//Coffee to go
{0,2,2,2},//Black Coffee
{0,2,2,2},//Luingo
{0,2,2,2},//Сaffe Crema
{0,2,2,2},//Ristretto
{1,2,3,2},//Americano
{2,2,1,2},//Coffee With Milk
{2,2,2,2},//Latte
{3,2,2,2},//Milk Coffee to go
{3,2,2,2},//Latte Macchiato
{3,2,3,2},//Сappuccino
{4,0,0,1},//Milk foam
{5,1,0,1} //Hot water
};
uint32_t crc = 0xffffffff; // буффер рассчета CRC
uint8_t BitReverse[] =
{
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
};
// старт рассчета CRC
inline void PhilipsSeries5400::start_crc(uint8_t val){
crc = 0xffffffff; // очищаем буер для следующего рассчета
add_crc(val);
}
//БЫСТРО добавить байт к рассчету crc
inline void PhilipsSeries5400::add_crc(uint8_t val){
((uint8_t*)(&crc))[3] ^= BitReverse[val];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80000000) {
crc = (uint32_t)((crc << 1) ^ POLY);
} else {
crc <<= 1;
}
}
}
//БЫСТРО закончить рассчет контрольной суммы
inline uint32_t PhilipsSeries5400::calc_crc(){
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;
}
// рассчет CRC буфера
inline uint32_t PhilipsSeries5400::calc_crc(uint8_t* data, uint8_t size){
start_crc(data[0]);
for(uint8_t i=1; i<size; i++){
add_crc(data[i]);
}
return calc_crc();
}
// 30 сивмолов лога
char debugBuff[]= "=> 00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00;00; ";
#define printBuff (debugBuff+2)
// преобразование полубайта в символ
inline char hex2str(uint8_t b){
if(b<0x0A){
return b+'0';
}
return b+('A'-0x0A);
}
// БЫСТРАЯ публикация массива в строку в формате HEX
void printHexLine(uint8_t* buff, uint8_t size, char del=';'){
if(size>40) size=40; // ограничиваем выход за пределы буфера печати
char* bu=printBuff;
for(uint8_t i=0; i<size; i++){
*bu++=del;
*bu++=hex2str(*buff / 0x10);
*bu++=hex2str(*buff++ & 0x0F);
}
*bu=0;
}
// печать HEX строки в сенсор
void PhilipsSeries5400::pubMess(TextSensor *sens,uint8_t* buff, uint8_t size){
printHexLine(buff, size, ' ');
sens->publish_state(printBuff);
}
// БЫСТРАЯ печать HEX строки в debug
void PhilipsSeries5400::_debugPrintPacket(uint8_t* data, uint8_t size, bool in){
// заполняем время пакета
char timeBuff[11]={0};
sprintf(timeBuff, "%010u", millis());
// формируем преамбулы
if (in) { // признак входящего пакета
debugBuff[0]='<';
debugBuff[1]='=';
} else { // признак исходящего пакета
debugBuff[0]='=';
debugBuff[1]='>';
}
printHexLine(data, size);
ESP_LOGE(timeBuff, debugBuff);
}
uint8_t receept[]={0x90,1,0x0A,0,0,0,0,0,0,0,0,0,0}; // буфер рецепта
const uint8_t pmb[]={0xAA,0xAA,0xAA}; // преамбула
const uint8_t post=0x55; // постамбула
// отправка пакета с рассчетом посторением к чертовой матери
void PhilipsSeries5400::send_packet_main(uint8_t* data, uint8_t size){
uint8_t cs[]={0,0,0,0};
*((uint32_t*)cs)=calc_crc(data,size);
mainboard_uart_.write_array(pmb, sizeof(pmb));
mainboard_uart_.write_array(data, size);
mainboard_uart_.write_array(cs, sizeof(cs));
mainboard_uart_.write_array(&post,1);
#ifdef NEED_LOG
_debugPrintPacket(data, size, true);
#endif
}
// отправка пакета с рассчетом посторением дисплею
void PhilipsSeries5400::send_packet_displ(uint8_t* data, uint8_t size){
uint8_t cs[]={0,0,0,0};
*((uint32_t*)cs)=calc_crc(data,size);
display_uart_.write_array(pmb, sizeof(pmb));
display_uart_.write_array(data, size);
display_uart_.write_array(cs, sizeof(cs));
display_uart_.write_array(&post,1);
#ifdef NEED_LOG
_debugPrintPacket(data, size, false);
#endif
}
void PhilipsSeries5400::coffee_test_validate(){ // показ построения рецепта в тестовом режиме
if(_debug){
coffee_build(_product, _grind, _cups, _volume, _milk);
}
}
void PhilipsSeries5400::coffee_start(uint8_t* data){ // старт приготовления кофе по рецепту
memcpy(receept+3,data,10);
if(sens_90!=nullptr){pubMess(sens_90, receept, sizeof(receept));}
if(sens_product!=nullptr){coffee_show(receept+3);}
}
void PhilipsSeries5400::coffee_build(uint8_t type, uint8_t bean, uint8_t cups, uint16_t vol, uint16_t milk){ // старт приготовления кофе
//ESP_LOGD("BUILD","%u %u %u %u %u", type, bean, cups, vol, milk);
uint8_t rec[10]={0};
if(type>=sizeof(CoffePattern)/sizeof(CoffePattern[0])){
type=0;
}
rec[0]=CoffePattern[type][0];// вид напитка 0
if(rec[0]==5 || rec[0]==4){ // у не кофе
rec[1]=3;// всегда признак "молотый"
} else {
rec[1]=bean; // крепость/молотый
}
if(cups>CoffePattern[type][3]){ // Контроль чашек, например не у всех есть экстрашот
rec[2]=CoffePattern[type][3];
} else {
rec[2]=cups;
}
rec[3]=CoffePattern[type][1];// вид напитка 1
rec[4]=CoffePattern[type][2];// вид напитка 2
if(rec[0]==1){// у американо
rec[6]=40; // объем кофе всегда 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;
}
}
coffee_start(rec);
}
bool PhilipsSeries5400::coffee_decode(const uint8_t* rec, uint8_t* type, uint8_t* bean, uint8_t* cups, uint16_t* vol, uint16_t* milk){ // старт приготовления кофе
*type=0;
*bean=rec[1];
*cups=rec[2];
*vol=0;
*milk=0;
// поиск рецепта в списке
while((*type)<(sizeof(CoffePattern)/sizeof(CoffePattern[0]))){
if(rec[0]==CoffePattern[*type][0] && rec[3]==CoffePattern[*type][1] && rec[4]==CoffePattern[*type][2]){
break;
}
(*type)++;
}
if(*type<sizeof(CoffePattern)/sizeof(CoffePattern[0])){
if(rec[0]==1){ //американо
*vol=rec[9]*0x100+rec[8]+rec[7]*0x100+rec[6];
} else { //взбитое молоко, кофе с молоком и без
*vol=rec[7]*0x100+rec[6];
if(rec[5]==2){
*milk=rec[9]*0x100+rec[8];
}
}
return true;
} else {
*type=0xFF; // не нашли такого сочетания
}
return false;// не опознали
}
void PhilipsSeries5400::coffee_show(uint8_t* data){
if(sens_product!=nullptr){ // если есть сенсор текстового отображения напитка
uint8_t type;
uint8_t bean;
uint8_t cup;
uint16_t vol;
uint16_t milk;
coffee_decode(data, &type, &bean, &cup, &vol, &milk);
//ESP_LOGD("DECODE","%d %d %d %d %d",type, bean, cup, vol, milk);
char buff[20]={0};
if(type<(sizeof(CoffePattern)/sizeof(CoffePattern[0]))){
std::string show=product->at(type).value(); // название кофе
sprintf(buff," %u ml.", vol); //объем
show = show + buff;
if(milk){// объем молока
sprintf(buff,", milk %u ml.", milk);
show = show + buff;
}
if(cups!=0){ // количество чашек
show = show + ", " + cups->at(cup).value();
}
if(type<12){ // помол имеет смвсл только у кофе
show = show + ", " + grind->at(bean).value();
}
sens_product->publish_state(show);
} else {
sens_product->publish_state("ERROR");
}
}
}
uint8_t main_cnt=0; // последний счетчик пакета FF от матери, мы должны отправлять 90 пакет с этим номером
uint8_t disp_cnt=0; // последний счетчик пакета 90 от экрана, мы должны его подтвердить пакетом FF
uint8_t old_disp_cnt=0;
// отправка пакета 9x с подменой нумерации в сторону матери
void PhilipsSeries5400::send_packet_9X(uint8_t* data, uint8_t size){
if(disp_cnt<data[1] || data[1]==0){ // запоминаем самый большой счетчик посылки, либо сбрасываем его
disp_cnt=data[1];
}
static uint8_t cnt93=0; // номер пакета 93 для посылки в мать
static uint8_t cnt91=0; // номер пакета 91 для посылки в мать
static uint8_t cnt90=0; // номер пакета 90 для посылки в мать
if(data[0]==0x93){ // пакет 93
static uint8_t old=0xFF; // буфер сравнения номера пакета от дисплея
if(old!=data[1]){ // если у пакета изменился счетчик
old=data[1];
cnt93=main_cnt;
}
data[1]=cnt93;
} else if(data[0]==0x91){
static uint8_t old=0xFF; // буфер сравнения номера пакета от дисплея
static uint8_t old_main_cnt=0xFF; // буфер сравнения номера пакета от дисплея
if(old!=data[1] && old_main_cnt!=main_cnt){ // если у пакета изменился счетчик
old=data[1];
old_main_cnt=main_cnt;
cnt91=main_cnt;
if(cnt91==cnt93){ // если счетчик равен, то изменяем его
if(data[3]==0x03){ // приготовление кофе
cnt91=cnt93+2;
} else if(data[3]==0x10){
cnt91=cnt93+1;
}
}
}
data[1]=cnt91;
} else if(data[0]==0x90){
static uint8_t old=0xFF; // буфер сравнения номера пакета от дисплея
if(old!=data[1]){ // если у пакета изменился счетчик
old=data[1];
cnt90=cnt93+1;
}
data[1]=cnt90;
}
//ESP_LOGD("9X","%x -> %x", main_cnt, data[1]);
send_packet_main(data,size); // отправляем матери пакет c номером полученным от матери
}
// отправка пакета 9x из конфига
void PhilipsSeries5400::send_packet_90(uint8_t* data, uint8_t size){
//ESP_LOGD("90","%x->%x", main_cnt, data[1]);
send_packet_main(data,size); // отправляем пакет с рассчетом
}
// отправка пакета FF c восстановленой нумерацией для успокоения дисплея
void PhilipsSeries5400::send_packet_FF(uint8_t* data, uint8_t size){ // обрабатываем этот пакет в сторону дисплея, он связан с 9X
main_cnt=data[1]; // запоминаем номер который нам продиктовала мать для следующего пакета
// корректируем ответ для дисплея
if(old_disp_cnt!=disp_cnt+1){// счетчик в ответе должен быть на 1 больше счетчика от дисплея
old_disp_cnt++;
} else if(data[1]==0){ // выключение
old_disp_cnt=0;
disp_cnt=0;
}
data[1]=old_disp_cnt;
data[3]=data[1];
//ESP_LOGD("FF","%x -> %x", main_cnt, data[1]);
send_packet_displ(data,size); // отправляем пакет с рассчетом
}
// публикация статуса
void PhilipsSeries5400::pubStat(uint8_t stat){
if(work_sensor_!=nullptr){ // только если подключен сенсор
static uint8_t old=0;
if(stat!=0 && stat!=old){ // запрет публикации повторных статусов
old=stat;
work_sensor_->publish_state(stat);
}
}
}
// проверка и при необходимости сохранение данных
void PhilipsSeries5400::checkStore(){
if(_need_store){
bool save=false;
if(stored_data.volume!=_volume){stored_data.volume=_volume; save=true;}
if(stored_data.milk!=_milk){stored_data.milk=_milk; save=true;}
if(stored_data.grind!=_grind){stored_data.grind=_grind; save=true;}
if(stored_data.cups!=_cups){stored_data.cups=_cups; save=true;}
if(stored_data.product!=_product){stored_data.product=_product; save=true;}
if(save){ //сохраняем данные
if (!storage.save(&stored_data) || !global_preferences->sync()) {
ESP_LOGE(TAG,"Store data ERROR");
} else {
ESP_LOGD(TAG,"Store presset data to FLASH");
}
}
}
}
// восстановлениеданных из флеша
void PhilipsSeries5400::reStore(){
if(_need_store){
if(storage.load(&stored_data)==1){// читаем все пресеты из флеша
ESP_LOGD(TAG,"Load preset from FLASH");
volume->publish_state(stored_data.volume);
milk->publish_state(stored_data.milk);
cups->publish_state(cups->at(stored_data.cups).value());
grind->publish_state(grind->at(stored_data.grind).value());
product->publish_state(product->at(stored_data.product).value());
} else {
ESP_LOGE(TAG,"Load preset from FLASH - ERROR");
}
}
}
void PhilipsSeries5400::setup(){
reStore();
}
void PhilipsSeries5400::loop() {
uint32_t _now=millis(); // текущее системное время
static uint32_t on_control=_now; // время последней активности обмена данными
static uint32_t coffee_ready=1; //время окончания приготовления последнего кофе
uint8_t temp; // буфер для считывания байта из порта
{ // Pipe display to mainboard
static uint8_t buffer_board[BUFFER_BOARD_SIZE];
static uint8_t count_board=0;
while (display_uart_.available() && display_uart_.read_byte(&temp)){
if((count_board>2) || (temp==0xAA /* && count_board>=0 && count_board<=2*/)) { //ждем начало пакета
buffer_board[count_board++]=temp; //собираем данные в промежуточный буфер, считаем количество, никуда не отправляем
if(count_board>=BUFFER_BOARD_SIZE){ // ограничение переполнения буфера
count_board=0;
}
} else {
count_board=0;
}
if(temp==0x55 && count_board>5 && ((buffer_board[5]+11)<=count_board)){ // получили конец пакета - отправляем только полезные данные (отбрасываем 0xAA, CRC и 0x50
if((buffer_board[3] & 0xF0)==0x90){ // пакет 9х
send_packet_9X(buffer_board+3, count_board-8);
static uint8_t in_counter=0;
if( buffer_board[4]<5){ // первые пакеты не контролим
in_counter=buffer_board[4];
} else if(in_counter<buffer_board[4]){ // защита от повторов
in_counter=buffer_board[4];
if(buffer_board[3]==0x90){
if(sens_product!=nullptr){coffee_show(buffer_board+6);} // текстовая расшифровка рецепта
if(sens_90!=nullptr){ pubMess(sens_90, buffer_board+3, count_board-8);}
} else if(buffer_board[3]==0x91){
if(buffer_board[6]==0x08){ //конец приготовления кофе
coffee_ready=millis(); //засекли момент окончания приготовления кофе
}
if(sens_91!=nullptr){ pubMess(sens_91, buffer_board+3, count_board-8);}
} else if(buffer_board[3]==0x93){
coffee_ready=0; // старт приготовления кофе
if(sens_93!=nullptr){ pubMess(sens_93, buffer_board+3, count_board-8);}
}
}
} else {
mainboard_uart_.write_array(buffer_board,count_board); // простая трансляция пакета
#ifdef NEED_LOG
_debugPrintPacket(buffer_board+3, count_board-8, true);
#endif
}
on_control=_now; // запомнить время активности протокола
count_board=0; // очищаем буфер накопления данных
break;
}
}
}
{ // Pipe to display
static uint8_t buffer_displ[BUFFER_DISPL_SIZE];
static uint8_t count_displ=0;
while (mainboard_uart_.available() && mainboard_uart_.read_byte(&temp)){
if(count_displ>2 || temp==0xAA) { //ждем начало пакета
buffer_displ[count_displ++]=temp; //собираем данные в промежуточный буфер, считаем количество, никуда не отправляем
if(count_displ>=BUFFER_DISPL_SIZE){ // ограничение переполнения буфера
count_displ=0;
}
} else {
count_displ=0;
}
if(temp==0x55 && count_displ>5 && ((buffer_displ[5]+11)<=count_displ)){ // конец пакета и пакет в порядке
if(buffer_displ[3]==0xFF){ // пакет FF
send_packet_FF(buffer_displ+3, count_displ-8); // обрабатываем этот пакет, он связан с 9X
} else { // остальные пакеты просто дублируем
display_uart_.write_array(buffer_displ,count_displ); // простая трансляция пакета
#ifdef NEED_LOG
_debugPrintPacket(buffer_displ+3, count_displ-8, false);
#endif
if(buffer_displ[3]==0xFE){ // пакет вкл-выкл
disp_cnt=0;
old_disp_cnt=0;
//ESP_LOGE("","RESET CONTROL COUNTERS");
#ifdef NEED_LOG
//_debugPrintPacket(buffer_displ+3, count_displ-8, false);
#endif
} else if((buffer_displ[3] & 0xF0) ==0xB0){ // пакет Bx
static uint8_t in_counter=0;
if(in_counter+1<=buffer_displ[4] || in_counter-buffer_displ[4]>5){ // защита от повторов
in_counter=buffer_displ[4];
if(buffer_displ[3]==0xB0){ //AA:AA:AA:B0 "Статусы"
if(buffer_displ[6]==0x0E){
if(buffer_displ[9]==0x00){pubStat(20);} //"Опорож. контейнер для коф. гущи"
else {
if((buffer_displ[9] & 0x40)!=0){pubStat(22);} //"Воды нет"
else {pubStat(17);} //"Вода есть"
if((buffer_displ[9] & 0x80)!=0){pubStat(23);} //"Извлечен"
else {pubStat(18);} //"Вставлен"
}
} else if(buffer_displ[6]==0x06){
pubStat(21); //"Вода есть","Вставлен","Зерна есть","Выберите напиток","Пустой";
} else if(buffer_displ[6]==0x0C){
if (buffer_displ[7]==0x01){pubStat(8);} //"Наслаждайтесь"
else if(buffer_displ[7]==0x02){pubStat(15);} //"Что-то (07 0C 02)"
} else if(buffer_displ[6]==0x07){
if (buffer_displ[7]==0x0E){pubStat(9);} //"Нагрев воды"
else if(buffer_displ[7]==0x0D){pubStat(10);} //"Перемалываем зерна"
else if(buffer_displ[7]==0x10){pubStat(3);} //"Наливаем молоко"
else if(buffer_displ[7]==0x11){pubStat(4);} //"Наливаем кофе"
else if(buffer_displ[7]==0x12){pubStat(5);} //"Предварительное дозирование"
else if(buffer_displ[7]==0x13){pubStat(6);} //"Создание пара для молока"
else if(buffer_displ[7]==0x14){pubStat(7);} //"Заварочный узел в положение заваривания"
else if(buffer_displ[7]==0x15){pubStat(15);} //"Наслаждайтесь"
} else if(buffer_displ[6]==0x08){
if (buffer_displ[7]==0x0E){pubStat(14);} //"Нагревание"
else if(buffer_displ[7]==0x02){pubStat(11);} //"Промывка"
else if(buffer_displ[7]==0x14){pubStat(13);} //"Что-то (07 08 14)"
else if(buffer_displ[7]==0x05){pubStat(12);} //"Зерна закончились"
else if(buffer_displ[7]==0x16){pubStat(24);} //"Удаление накипи стадия 1"
else if(buffer_displ[7]==0x18){pubStat(25);} //"даление накипи стадия 2"
} else if(buffer_displ[7]==0x00){
if (buffer_displ[6]==0x01){pubStat(1);} //"Что-то (07 01 00)"
else if(buffer_displ[6]==0x05){pubStat(2);} //"Выключено"
}
// если подключен сенсор печати пакета b0, то публикуем его
if(sens_b0!=nullptr){ pubMess(sens_b0, buffer_displ, count_displ);}
} else if(buffer_displ[3]==0xB5){ //AA:AA:AA:B5 "Error Code"
if(buffer_displ[10]==0x00){
if (buffer_displ[11]==0x00){pubStat(30);} //"0x00"
else if(buffer_displ[11]==0x0B){pubStat(31);} //"0x0B"
else if(buffer_displ[11]==0xE6){pubStat(32);} //"0xE6"
else if(buffer_displ[11]==0x80){pubStat(33);} //"0x80"
else if(buffer_displ[11]==0xCB){pubStat(34);} //"0xCB"
else if(buffer_displ[11]==0xFF){pubStat(35);} //"0xFF"
else if(buffer_displ[11]==0xA0){pubStat(36);} //"0xA0"
} else if(buffer_displ[10]==0x01){ pubStat(37);} // "Статус2 0x01"
// если подключен сенсор печати пакета b5, то публикуем его
if(sens_b5!=nullptr){ pubMess(sens_b5, buffer_displ, count_displ);}
} else { //AA:AA:AA:Bx "Все остальные Bx"
// если подключен сенсор печати пакета ba, то публикуем его
if(sens_ba!=nullptr){ pubMess(sens_b0, buffer_displ, count_displ);}
}
}
} else { // все остальное
#ifdef NEED_LOG
_debugPrintPacket(buffer_displ+3, count_displ-8, false);
#endif
}
}
count_displ=0; // пакет обработан - освободить буфер
on_control=_now; // запомнить время активности протокола
break;
}
}
}
// контроль активности протокола
if(_now-on_control>15000){ // нет обмена более 15 сек
pubStat(2); // публикуем статус - машинка выключена
on_control=_now;
}
static uint32_t sendTimer=0;
// приготовление кофе
if(coffe_route && coffee_ready && _now-sendTimer>12 && _now-coffee_ready>1000){ // запускаем только после окончания приготовления педыдущего стакана
static uint8_t pack93[]={0x93,0,1,1};
static uint8_t pack91[]={0x91,2,1,3};
if(coffe_route==1){ // построение пакетов
//ESP_LOGE("","START COFFEE ESP");
pack93[1]=main_cnt;
receept[1]=main_cnt+1;
pack91[1]=main_cnt+2;
send_packet_90(pack93, sizeof(pack93));
coffe_route++;
} else if(coffe_route==4 || coffe_route==7){ //
send_packet_90(pack93, sizeof(pack93));
coffe_route++;
} else if(coffe_route==2){
send_packet_90(receept, sizeof(receept));
if(sens_90!=nullptr){pubMess(sens_90, receept, sizeof(receept));}
coffe_route++;
} else if(coffe_route==5 || coffe_route==8){
send_packet_90(receept, sizeof(receept));
coffe_route++;
} else if(coffe_route==3 || coffe_route==6){
send_packet_90(pack91, sizeof(pack91));
coffe_route++;
} else if(coffe_route==9){
send_packet_90(pack91, sizeof(pack91));
coffe_route=0;
coffee_ready=0; // конец старта приготовления кофе
//ESP_LOGE("","END COFFEE ESP");
}
sendTimer=_now;
}
// контроль необходимости сохранения данных
static uint32_t saveTimer=0;
if(_need_store && _now-saveTimer>1000){
saveTimer=_now;
checkStore();
}
}
void PhilipsSeries5400::dump_config() {
ESP_LOGCONFIG(TAG, "Philips Series 5400");
display_uart_.check_uart_settings(115200, 1, uart::UART_CONFIG_PARITY_NONE, 8);
mainboard_uart_.check_uart_settings(115200, 1, uart::UART_CONFIG_PARITY_NONE, 8);
LOG_SENSOR(TAG, "Status sensor ", this->work_sensor_);
LOG_TEXT_SENSOR(TAG, "Message 90 sensor", this->sens_90);
LOG_TEXT_SENSOR(TAG, "Message 91 sensor", this->sens_91);
LOG_TEXT_SENSOR(TAG, "Message 93 sensor", this->sens_93);
LOG_TEXT_SENSOR(TAG, "Message B0 sensor", this->sens_b0);
LOG_TEXT_SENSOR(TAG, "Message B5 sensor", this->sens_b5);
LOG_TEXT_SENSOR(TAG, "Message BA sensor", this->sens_ba);
LOG_TEXT_SENSOR(TAG, "Drink composition", this->sens_product);
LOG_NUMBER(TAG, "Coffee size", this->volume);
LOG_NUMBER(TAG, "Milk size", this->milk);
LOG_SELECT(TAG, "Select drink", this->product);
LOG_SELECT(TAG, "Select grind", this->grind);
LOG_SELECT(TAG, "Select cups", this->cups);
LOG_BUTTON(TAG, "Prepare button", this->start_button);
ESP_LOGCONFIG(TAG, "Save settings %s", TRUEFALSE(_need_store));
ESP_LOGCONFIG(TAG, "Debug %s", TRUEFALSE(_debug));
}
} // namespace philips_series_5400
} // namespace esphome

View File

@@ -0,0 +1,240 @@
#pragma once
#ifdef ESP32
#include "esphome/core/preferences.h"
#else
# ERROR: No-no-nooy! Friend, it only for ESP32
#endif
#include "esphome.h"
#include <stdarg.h>
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/number/number.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/select/select.h"
#include "esphome/components/button/button.h"
#define POWER_STATE_TIMEOUT 500
static const char *TAG = "Philips-5400 ";
// структура для сохранения данных
struct save_data_t {
uint16_t volume=0;
uint16_t milk=0;
uint8_t grind=0;
uint8_t cups=0;
uint8_t product=0;
uint8_t free=0;
};
namespace esphome
{
namespace philips_series_5400
{
using namespace esphome::sensor;
using namespace esphome::text_sensor;
using namespace esphome::number;
using namespace esphome::select;
using namespace esphome::button;
class PhilipsSeries5400;
class PhilipsSeries5400Button : public esphome::button::Button, public esphome::Parented<PhilipsSeries5400> {
protected:
void press_action() override{
//..
}
friend class PhilipsSeries5400;
};
class PhilipsSeries5400Select : public esphome::select::Select, public esphome::Parented<PhilipsSeries5400> {
protected:
void control(const std::string &value) override{
this->state_callback_.call(value, 0);
}
friend class PhilipsSeries5400;
};
class PhilipsSeries5400Number : public esphome::number::Number, public esphome::Parented<PhilipsSeries5400> {
protected:
void control(float value) override{
this->state_callback_.call(value);
}
friend class PhilipsSeries5400;
};
class PhilipsSeries5400 : public Sensor, public esphome::Component
{
public:
void loop() override;
void dump_config() override;
void setup()override;
void register_display_uart(uart::UARTComponent *uart) { display_uart_ = uart::UARTDevice(uart);}
void register_mainboard_uart(uart::UARTComponent *uart) { mainboard_uart_ = uart::UARTDevice(uart);}
void register_work_sensor(sensor::Sensor *sens){ work_sensor_ = sens;} // установка сенсора
void set_sens_b0(TextSensor *sens){ sens_b0 = sens;}
void set_sens_b5(TextSensor *sens){ sens_b5 = sens;}
void set_sens_ba(TextSensor *sens){ sens_ba = sens;}
void set_sens_90(TextSensor *sens){ sens_90 = sens;}
void set_sens_91(TextSensor *sens){ sens_91 = sens;}
void set_sens_93(TextSensor *sens){ sens_93 = sens;}
void set_sens_product(TextSensor *sens){ sens_product = sens;}
void set_vol_number(PhilipsSeries5400Number *num){ // объем кофе
volume = num;
volume->add_on_state_callback([this](float sensor_value) {
if (!std::isnan(sensor_value)) {
_volume=sensor_value;
//ESP_LOGD(TAG,"Set coffee volume to %d", _volume);
coffee_test_validate();
}
});
if(isnan(volume->state)){
_volume=volume->traits.get_min_value();
} else {
_volume=volume->state;
if(_volume<volume->traits.get_min_value()){
_volume=volume->traits.get_min_value();
} else if(_volume>volume->traits.get_max_value()){
_volume=volume->traits.get_max_value();
}
}
volume->publish_state(_volume);
}
void set_milk_number(PhilipsSeries5400Number *num){ // объем молока
milk = num;
milk->add_on_state_callback([this](float sensor_value) {
if (!std::isnan(sensor_value)) {
_milk=sensor_value;
//ESP_LOGD(TAG,"Set milk to %d", _milk);
coffee_test_validate();
}
});
if(isnan(milk->state)){
_milk=milk->traits.get_min_value();
} else {
_milk=milk->state;
if(_milk<milk->traits.get_min_value()){
_milk=milk->traits.get_min_value();
} else if(_volume>milk->traits.get_max_value()){
_milk=milk->traits.get_max_value();
}
}
milk->publish_state(_milk);
}
void set_grind_select(PhilipsSeries5400Select *sel){ // помол/крепость
grind = sel;
grind->add_on_state_callback([this](std::string payload, size_t num) {
_grind = grind->index_of(payload).value();
//ESP_LOGD(TAG,"Set grind to %d", _grind);
coffee_test_validate();
});
_grind=grind->active_index().has_value();
if(isnan(_grind)){
_grind=0;
}
grind->publish_state(grind->at(_grind).value());
}
void set_cups_select(PhilipsSeries5400Select *sel){ // количество чашек
cups = sel;
cups->add_on_state_callback([this](std::string payload, size_t num) {
_cups = cups->index_of(payload).value();
//ESP_LOGD(TAG,"Set cups to %d", _cups);
coffee_test_validate();
});
_cups=cups->active_index().has_value();
if(isnan(_cups)){
_cups=0;
}
cups->publish_state(cups->at(_cups).value());
}
void set_product_select(PhilipsSeries5400Select *sel){ // напиток
product = sel;
product->add_on_state_callback([this](std::string payload, size_t num) {
_product = product->index_of(payload).value();
//ESP_LOGD(TAG,"Set product to %s (%d)", payload.c_str(), _product);
coffee_test_validate();
});
_product=product->active_index().has_value();
if(isnan(_product)){
_product=0;
}
product->publish_state(product->at(_product).value());
}
void set_start_button(PhilipsSeries5400Button *key){ // кнопка запуска приготовления кофе
start_button = key;
start_button->add_on_press_callback([this](void){
coffee_build(_product, _grind, _cups, _volume, _milk); //запускаем кофе
coffe_route=1; // запуск приготовления кофе
});
}
void set_store_settings(bool store) {_need_store = store;} // установка флага сохранения данных
void set_debug_settings(bool val) {_debug = val;} // установка флага отладки
void prepare(uint8_t* data){ //безусловный старт приготовления кофе пакетом
coffee_start(data);
coffe_route=1; // установить флаг исполнения
}
void send_packet_main(uint8_t* data, uint8_t size); // отправка пакета с рассчетом посторением к чертовой матери
void send_packet_displ(uint8_t* data, uint8_t size); // отправка пакета с рассчетом посторением дисплею
private:
uart::UARTDevice display_uart_;
uart::UARTDevice mainboard_uart_;
void pubMess(TextSensor *sens,uint8_t* buff, uint8_t size);
void pubStat(uint8_t stat);
sensor::Sensor *work_sensor_{nullptr};
TextSensor *sens_90{nullptr};
TextSensor *sens_91{nullptr};
TextSensor *sens_93{nullptr};
TextSensor *sens_b0{nullptr};
TextSensor *sens_b5{nullptr};
TextSensor *sens_ba{nullptr};
PhilipsSeries5400Number *volume{nullptr}; // объем кофе
uint16_t _volume=0;
PhilipsSeries5400Number *milk{nullptr}; // объем молока
uint16_t _milk=0;
PhilipsSeries5400Select *grind{nullptr}; // помол/крепость
uint8_t _grind=0;
PhilipsSeries5400Select *cups{nullptr}; // количество чашек
uint8_t _cups=0;
PhilipsSeries5400Select *product{nullptr}; // тип напитка
uint8_t _product=0;
PhilipsSeries5400Button *start_button{nullptr}; // кнопка "приготовить кофе"
uint8_t coffe_route=0; //для роутинга приготовления кофе
TextSensor *sens_product{nullptr}; // текстовый сенсор готовящегося напитка
save_data_t stored_data; // буфер для сохранения данных
bool _need_store=false; // флаг необходимости сохранения данных
bool _debug=false; // флаг режима отладки
ESPPreferenceObject storage = global_preferences->make_preference<save_data_t>(this->get_object_id_hash(), true);
void checkStore();// проверка и при необходимости сохранение данных
void reStore();// восстановлениеданных из флеша
inline void start_crc(uint8_t val); // начало рассчета CRC
inline void add_crc(uint8_t val); //добавить байт к рассчету crc
inline uint32_t calc_crc(); //закончить рассчет контрольной суммы
inline uint32_t calc_crc(uint8_t* data, uint8_t size); // рассчет CRC буфера
void send_packet_9X(uint8_t* data, uint8_t size); // отправка 90x пакетов с подменой нумерации
void send_packet_90(uint8_t* data, uint8_t size);// отправка 90x пакетов из конфига
void send_packet_FF(uint8_t* data, uint8_t size); // отправка пакета FF в ответ, он связан с 9X
void coffee_test_validate(); // показ построения рецепта в тестовом режиме
void _debugPrintPacket(uint8_t* data, uint8_t size, bool in);
void coffee_start(uint8_t* data); // старт приготовления кофе пакетом
// построение рецепта кофе в буфере данных и запуск
// type - вид напитка
// cups - количество чашек
// bean - крепкость от 2 до 0, 3- использовать мототый
// vol - объем кофе в мл
// milk - объем молока в мл
void coffee_build(uint8_t type, uint8_t bean, uint8_t cups, uint16_t vol, uint16_t milk);
// расшифровка рецепта в цифры
bool coffee_decode(const uint8_t* rec, uint8_t* type, uint8_t* bean, uint8_t* cups, uint16_t* vol, uint16_t* milk);
// текстовая расшифровка продукта в работе
void coffee_show(uint8_t* data);
friend class PhilipsSeries5400Button;
friend class PhilipsSeries5400Select;
friend class PhilipsSeries5400Number;
};
} // namespace philips_series_5400
} // namespace esphome