Entrada
Já existe
um artigo sobre o Gicktime dedicado à análise do protocolo da chaleira Redmond SkyKettle. No entanto, lá eles conversaram sobre o modelo RK-M171S, aqui falaremos sobre um G200S mais funcional. Nesse modelo, o protocolo de interação mudou, devido ao qual a abordagem do autor ao artigo anterior não funciona mais, e funções adicionais da luz noturna e exibições de temperatura da cor atual foram exibidas.
Neste artigo, apresentarei os resultados de uma análise de protocolo com exemplos de código python (se alguém quiser desenvolver seu módulo / aplicativo para controlar o bule de chá). Também no final do artigo, há um link para um módulo pronto para conectar um bule de chá ao HomeAssistant (esta é minha primeira experiência em escrever python após fazer um curso on-line, para que este módulo possa e deva ser aprimorado).
Todo mundo que estiver interessado, bem-vindo ao gato.
Problemas e tarefas
Este bule possui um grande sinal de menos (exceto os indicados pelo autor do primeiro artigo): assim que o bule é removido do suporte, a hora atual é redefinida e, como resultado, a programação não pode ser usada para ferver o bule. De acordo com as idéias dos autores desta criação, sempre que você devolver a chaleira ao estande, você deverá iniciar o aplicativo proprietário e sincronizar a chaleira com um smartphone. Portanto, em vez de facilitar tarefas rotineiras, a tecnologia "inteligente" nos treina para executar ações adicionais. Mas tudo isso mudou quando o HomeAssistant apareceu em casa. Então eu decidi entender o protocolo.
As ferramentas
Sinceramente, tentei descompilar e analisar o aplicativo original, mas falhei. As ferramentas que usei não me permitiram entender a lógica da chaleira. Todos os procedimentos e funções foram obtidos por “curvas”, sem nome (por tipo a, b, c, etc.). Talvez eu não tenha experiência e habilidade suficientes. No final, fui da mesma maneira que o autor do artigo anterior. A única diferença significativa é que eu usei o modo interativo do utilitário gatttool. A vantagem é que esse modo elimina todos os tipos de "raças", sobre as quais o autor do primeiro artigo escreveu.
Como o HomeAssistant é escrito em python, escreveremos todos os outros comandos nele. Para usar o modo operacional interativo gatttool em python, a biblioteca pexpect nos ajudará, o que permite que você crie a essência de aplicativos de terceiros e monitore sua saída (conhecida como dobrada).
Prática
Voltarei a enviar o primeiro artigo sobre a descrição geral do protocolo de troca ao autor do artigo, portanto, sem mais delongas, prosseguiremos com os comandos de controle.
- Instalação e desconexão
Estabeleça uma conexão:
child = pexpect.spawn("gatttool -I -t random -b " + mac, ignore_sighup=False) child.expect(r'\[LE\]>', timeout=3) child.sendline("connect") child.expect(r'Connection successful.*\[LE\]>', timeout=3)
Aqui mac é o endereço de papoula da chaleira.
Rompemos a conexão:
child.sendline("exit")
- Inscrever-se em Notificações
Depois de estabelecer a conexão, precisamos primeiro se inscrever para receber notificações da chaleira. Sem isso, o bule perceberá os comandos, mas não poderá nos responder nada, exceto o texto "Com êxito".
child.sendline("char-write-cmd 0x000c 0100") child.expect(r'\[LE\]>')
- Entrar
child.sendline("char-write-req 0x000e 55" + iter + "ff" + key + "aa") child.expect("value: ") child.expect("\r\n") connectedStr = child.before[0:].decode("utf-8") answer = connectedStr.split()[3]
A seguir, iter é uma variável hexadecimal iterativa de número inteiro de 0 a 64 (de 0 a 100 no sistema decimal). Após cada comando (com ou sem êxito), essa variável deve ser aumentada em 1; quando atingir 64, é novamente redefinida para 0; chave - chave de autorização hexadecimal de 8 bytes (por exemplo: fffffffffffffffff).
Exemplo de resposta:
valor: 55 00 ff 01 aa
O quarto byte (01) significa que a chaleira o autorizou, caso contrário, a resposta será 00.
- Alguma mágica de rua
Após a autorização, uma solicitação "mágica" é sempre enviada, cuja essência não está clara para mim. Existe uma teoria de que é necessário "reter" o estado conectado. Alegadamente, se você não o enviar, a desconexão ocorrerá em um segundo e você precisará iniciar tudo novamente. Se você o enviar, o tempo limite aumentará significativamente, chegando a cerca de uma dúzia de segundos. Confiável confirmar isso, eu não poderia.
child.sendline("char-write-req 0x000e 55" + iter + "01aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>')
Exemplo de resposta:
valor: 55 01 01 02 1d aa
Em todas as minhas experiências, a resposta sempre foi essa.
UPD: nos comentários, eles sugeriram que não era nada mágico, mas simplesmente solicitar a versão do software; portanto, esta versão está contida na resposta. Portanto, essa solicitação geralmente pode ser removida como desnecessária.
- Sincronizar
Um comando que sincroniza a hora em um bule de chá com um relógio do servidor. Ela tem mais um efeito. Na chaleira, é possível mostrar a temperatura atual no modo inativo, piscando um LED de uma determinada cor. Esta função funciona apenas após a sincronização. Para uma descrição da própria função, consulte o parágrafo 11.
child.sendline("char-write-req 0x000e 55" + iter + "6e" + timeNow + tmz + "0000aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>')
Aqui tmz é o fuso horário no formato hexadecimal reverso (por exemplo, traduza o fuso horário +3 em segundos, depois no formato hexadecimal e obtenha hex (3 * 60 * 60) = 2a30, divida em pares e faça a saída 302a na ordem inversa). Não sei o que fazer com fusos horários negativos, ainda não testei, mas há uma suspeita de que o próximo byte tmz seja responsável por isso. Aqui timeNow é a hora atual do unixtime no formato hexadecimal reverso. O algoritmo é o mesmo: obtemos o tempo atual em segundos, convertemos para HEX, dividimos em pares e o produzimos em uma linha na ordem inversa.
Exemplo de resposta:
valor: 55 02 6e 00 aa
Em todas as minhas experiências, a resposta sempre foi essa.
- Estatísticas
A chaleira possui um medidor de eletricidade consumida, tempo total de operação e número de partidas. Se alguém não precisar desses dados, você pode pular esse item com segurança.
child.sendline("char-write-req 0x000e 55" + iter + "4700aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") Watts = hexToDec(str(statusStr.split()[11] + statusStr.split()[10] + statusStr.split()[9])) alltime = round(self._Watts/2200, 1) child.expect(r'\[LE\]>') child.sendline("char-write-req 0x000e 55" + iter + "5000aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") times = hexToDec(str(statusStr.split()[7] + statusStr.split()[6])) child.expect(r'\[LE\]>')
Watts - retorna a energia consumida em Wh * h, o tempo todo - as horas de operação da chaleira, os horários - o número de partidas da chaleira. hexToDec - uma função para converter para o formato decimal.
- Leia o modo atual de operação
child.sendline("char-write-req 0x000e 55" + iter + "06aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") answer = statusStr.split() status = str(answer[11]) temp = hexToDec(str(answer[8])) mode = str(answer[3])
Exemplo de resposta:
valor: 55 04 06 00 00 00 00 01 2a 1e 00 00 00 00 00 00 80 00 00 aa
O quarto byte é o modo operacional (modo): 00 - fervendo, 01 - aquecendo à temperatura, 03 - luz noturna. O sexto byte é a temperatura hexadecimal à qual é necessário aquecer no modo de aquecimento, no modo de ebulição é 00. O nono byte é hexadecimal a temperatura atual da água (2a = 42 Celsius). O décimo segundo byte é o estado do bule: 00 - desativado, 02 - ativado. O décimo sétimo byte é a duração da chaleira após atingir a temperatura desejada; por padrão, é 80 em hexadecimal (aparentemente, esses são alguns tipos de unidades relativas, certamente não segundos).
- Gravar o modo atual de operação
child.sendline("char-write-req 0x000e 55" + iter + "05" + mode + "00" + temp + "00000000000000000000" + howMuchBoil + "0000aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>')
Modo de parâmetro: 00 - fervendo, 01 - aquecimento à temperatura, 03 - luz noturna. O parâmetro temp é a temperatura hexagonal à qual é necessário aquecer no modo "aquecimento", no modo de ebulição é 00. O parâmetro howMuchBoil é a duração da chaleira após atingir a temperatura desejada, o padrão é 80 hexadecimal (aparentemente, essas são algumas unidades relativas , certamente não segundos).
Exemplo de resposta:
valor: 55 05 05 01 aa
O quarto byte da resposta indica o sucesso das configurações: 01 - com êxito, 00 - sem êxito.
- Executar o modo de operação atual
child.sendline("char-write-req 0x000e 55" + iter + "03aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>')
Exemplo de resposta:
valor: 55 06 03 01 aa
O quarto byte da resposta indica o sucesso da inclusão: 01 - com êxito, 00 - sem êxito.
- Parar o modo de operação atual
child.sendline("char-write-req 0x000e 55" + iter + "04aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>')
Exemplo de resposta:
valor: 55 07 04 01 aa
O quarto byte da resposta indica o sucesso do desligamento: 01 - com êxito, 00 - sem êxito.
- Exibir a temperatura atual em cores em modo inativo
child.sendline("char-write-req 0x000e 55" + iter + "37c8c8" + onoff + "aa")
O parâmetro onoff é 01 para ativar a função ou 00 para desativar a função.
Exemplo de resposta:
valor: 55 08 37 00 aa
Em todas as minhas experiências, a resposta sempre foi essa.
- Grave uma paleta de cores de vários modos de operação
Uma paleta de correspondência entre a cor do LED e a temperatura é definida no modo de exibição da temperatura atual e nos modos de aquecimento e ebulição, bem como na paleta de cores no modo de luz noturna.
child.sendline("char-write-req 0x000e 55" + iter + "32" + boilOrLight + scale_from + rand + rgb1 + scale_mid + rand + rgb_mid + scale_to + rand + rgb2 + "aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>')
O parâmetro boilOrLight é 00 se definirmos o modo de exibição da temperatura atual ou 01 se definirmos o modo noturno. O parâmetro scale_from indica o início da faixa de mudança de cor e é igual a 00 no modo de luz noturna e 28 no modo de exibição de temperatura atual (28 é 40 no formato decimal e é a partir dessa temperatura que uma mudança suave de cor começará). O parâmetro scale_mid é o meio do intervalo e é 32 no modo de luz noturna e 46 no modo de exibição de temperatura atual. O parâmetro scale_to indica o final do intervalo de cores e é 64 nos dois modos. O parâmetro rgb1 é a cor hexadecimal do início da paleta. O parâmetro rgb_mid é a cor hexadecimal do meio da paleta (eu o calculo como o meio entre as extremidades esquerda e direita, mas teoricamente você pode especificar qualquer cor, isso afetará apenas a beleza e a suavidade da mudança de cor). O parâmetro rgb2 é a cor hexadecimal do final da paleta. O parâmetro rand é um determinado parâmetro, cujo valor eu não entendi exatamente, talvez de alguma forma relacionado ao brilho da cor (exemplos de valores: e5, cc).
Exemplo de resposta:
valor: 55 09 32 00 aa
Em todas as minhas experiências, a resposta sempre foi essa.
- Leia a paleta de cores dos vários modos de operação
child.sendline("char-write-req 0x000e 55" + iter + "33" + boilOrLight + "aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") child.expect(r'\[LE\]>')
O parâmetro boilOrLight pode ser 00 - se definirmos o modo de exibição da temperatura atual ou 01 - se definirmos o modo noturno.
Exemplo de resposta:
valor: 55 10 33 01 00 7f 00 00 ff 32 7f 00 ff 00 64 7f ff 00 00 aa
Aqui, o sexto, décimo primeiro e décimo sexto bytes (7f) é o parâmetro rand do item 12. O quinto byte é scale_from, o décimo byte é scale_mid, o décimo quinto byte é scale_to. Sétimo + oitavo + nono bytes são rgb_from. Os décimos segundo + décimo terceiro + décimo quarto bytes são rgb_mid. Décimo sétimo + décimo oitavo + décimo nono bytes - rgb_to.
Conclusão
Se o gatttool não quiser se conectar ao bule (isso é possível na primeira vez em que você se conectar a dispositivos desconhecidos), tente procurar o bule usando os antes de conectar o módulo:
sudo hciconfig device reset sudo timeout 1 hcitool lescan
device - ID do seu dispositivo bluetooth (por exemplo, hci0). Verifique se o endereço da papoila da sua chaleira está na lista de dispositivos encontrados. Depois disso:
sudo hcitool lewladd mac sudo hcitool lerladd mac
mac - o endereço da papoula do seu bule
UPD6 : Melhorou significativamente o módulo de chaleira:
1. Transferiu o módulo da plataforma para o modo de integração
2. Após adicionar, você terá automaticamente 3 elementos: um aquecedor de água (temperatura atual, temperatura alvo, fervura e aquecimento), um sensor (tempo de sincronização, energia gasta, horas de operação, número de partidas) e luz (pode ser usada como lâmpada noturna e escolher qualquer cor luz de fundo)
3. O módulo está agora disponível no
GitHub .
4. O módulo suporta a instalação através do
HACS5. Exemplo de configuração:
r4s_kettler: device: 'hci0' mac: 'FF:FF:FF:FF:FF:FF' password: 'ffffffffffffffff'
Capturas de tela da nova versão UPD7 : informações irrelevantes
excluídas