Перехват данных с драйвера дисплея TM1628 с помощью ESP8266
Привет Хабр! Как-то так случилось, что кто-то очень хитрый, в одном НИИ, подключил обычный бытовой увлажнитель BALLU UHB-1000 к фитотрону(ака гроубоксу). Вот и встала задача добавить управление этим устройством. Проблема изначально стояла в том, что при подаче напряжения он автоматически не включался и состояние его было неизвестно. В комплекте присутствует пульт ДУ и можно было бы просто эмулировать сигнал включения, но почитав отзывы на увлажнитель с этим пультом стало ясно что это ни к чему хорошему не приведет. Хорошо что там уже валялся такой же неработающий увлажнитель и можно смело его мучать.
Анализ
Разбираем подставку с датчиками и отделяем переднюю панель, плата управления живет в ней и соединяется шлейфом с платой датчиков в донной части. На обратной стороне платы управления видим антенны-пружинки тачкнопок.

Все микросхемы залиты лаком, но с трудом идентифицируем драйвер дисплея TM1628.

Далее качаем датащит, берем мультиметр и ищем откуда брать питание на все это богатство. Слава фиксикам, все это звонится с входов VDD и GND TM -ки напрямую, значит можно подавать 5 вольт не боясь спалить. Если смотреть с тыльной стороны первый контакт +5в, последний GND.
Вуаля! Включился и даже показывает! НО… при нажатии на вкл, мигает красным иконка пустого бака, а это значит надо прозванивать донную часть и искать на какой пин приходит с него сигнал. Находим что 3 пин в шлейфе это пин датчика идущий через резистор 4,7ком, значит можно просто воткнуть резистор в шлейф и плата будет думать что в баке есть вода и даст нам включить устройство!

Припаиваемся к антеннке нужной кнопки и вешаем на пин нашей ESP-хи через конденсатор и резистор 10ком. Будем имитировать нажатие тачкнопки просто меняя состояние пина. У меня это 0.1мкф из того что было под рукой. На просторах интернета добрые люди писали что можно и меньше, главное тестировать. Все работает, казалось бы, можно было бы остановиться на этом и запустить в работу, но черт дернул ломать его до конца, тем более что нам желательно получить устройство с обратной связью.
подключаемся к пинам -

DIO, SCLK, STB драйвера TM1628 чтобы перехватывать то что рассказывает дисплею контроллер платы. И тут была самая трудоемкая часть работы - наблюдать за состоянием экрана при нажатиях, и собирать показания. Много было времени потрачено на оптимизацию связи и фильтрацию данных. Но это был первый такой опыт, и он тем интереснее. В итоге получилось собрать весь дисплей со всеми статусами, если кому-то понадобится то пользуйтесь на здоровье, теперь легко можно прикрутить что угодно для управления, благо на борту ESP8266 есть вайфай.
#define DIO_PIN D0 #define CLK_PIN D2 #define STB_PIN D1 const uint8_t digitPatterns[] = { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F, // 9 0xBF, //10 0xDB, // 12 0x86, // 11 0x40, // - }; const int butPin1 = D3; const int butPin2 = D4; const int butPin3 = D5; void butclick(int pin) { pinMode(pin, INPUT_PULLUP); pinMode(pin, INPUT); digitalWrite(pin, LOW); delay(40); pinMode(pin, INPUT_PULLUP); Serial.println("Button pressed on pin: " + String(pin)); } void setup() { Serial.begin(115200); pinMode(STB_PIN, INPUT); pinMode(CLK_PIN, INPUT); pinMode(DIO_PIN, INPUT); pinMode(butPin1, INPUT_PULLUP); digitalWrite(butPin1, LOW); pinMode(butPin2, INPUT_PULLUP); digitalWrite(butPin2, LOW); pinMode(butPin3, INPUT_PULLUP); digitalWrite(butPin3, LOW); Serial.println("TM1628 HUMIDIFIER DISPLAY DECODER"); } void loop() { static uint8_t lastSTB = HIGH; uint8_t currentSTB = digitalRead(STB_PIN); if (lastSTB == HIGH && currentSTB == LOW) { decodeHumidifierDisplay(); } lastSTB = currentSTB; serialEvent(); } void decodeHumidifierDisplay() { uint8_t bytes[20]; int byteCount = 0; // Capture transmission with timeout unsigned long startTime = micros(); while (digitalRead(STB_PIN) == LOW && byteCount < 20 && (micros() - startTime) < 10000) { bytes[byteCount++] = readByte(); } if (byteCount >= 10) { // Check if this is the display data packet pattern if (bytes[0] == 0x00 || bytes[0] == 0x01 || bytes[0] == 0x03) { if (bytes[1] == 0x40 || bytes[1] == 0x20) { if (bytes[2] == 0xC0) { processDisplayData(bytes, byteCount); return; } } } } } uint8_t readByte() { uint8_t data = 0; for (int i = 0; i < 8; i++) { // Wait for clock LOW unsigned long waitStart = micros(); while (digitalRead(CLK_PIN) == HIGH) { if (micros() - waitStart > 1000 || digitalRead(STB_PIN) == HIGH) return data; } // Wait for clock HIGH waitStart = micros(); while (digitalRead(CLK_PIN) == LOW) { if (micros() - waitStart > 1000 || digitalRead(STB_PIN) == HIGH) return data; } // Read bit if (digitalRead(DIO_PIN)) { data |= (1 << i); } } return data; } void processDisplayData(uint8_t* bytes, int count) { // display data format: // // info leds on b3 - ION, Steam, NoWater, TIMER icon // 0x04 - Ion icon // 0x02 - warm steam icon // 0x01 - no water icon // 0xF0 - TIMER icon // b11 && b13 - Hum power scale- // 0x00 0x00 device OFF // 0x00 0x38 - 25% // 0x00 0x3F - 50% // 0xC0 && 0x3F - 75% // 0xF8 0x3F - 100% // 0xC7 0x3F - AUTO static unsigned long lastUnknown = 0; if (count < 10) return; uint8_t leds = bytes[3]; uint8_t digit1 = bytes[7]; // first digit uint8_t digit2 = bytes[9]; // second digit uint8_t extra = (count > 10) ? bytes[10] : 0; // % led (C led comes with temperature readings) uint8_t pow1 = bytes[11]; // pow scale up uint8_t ison = bytes[12]; // 0x01 when device ON uint8_t pow2 = bytes[13]; // pow scale down // Decode digits char d1 = decodeDigit(digit1); char d2 = decodeDigit(digit2); // Determine what's being displayed based on patterns String displayType = "Unknown"; String fullDisplay = ""; if (d1 != '?' && d2 != '?') { displayType = "Value"; fullDisplay = String(d1) + String(d2); // Check for special characters if (extra == 0x01) { fullDisplay += " %"; // Percentage displayType = "Humidity"; } else { fullDisplay += " C"; // Celsius displayType = "Temperature"; } } // Only print if we have proper data if (d1 != '?' || d2 != '?' || displayType != "Unknown" ) { Serial.print("DISPLAY: "); if (leds % 2 != 0 ) Serial.print (" NO WATER " ); if (ison == 0 ) { Serial.print (" DEVICE IS OFF "); } else { if (pow1 == 0xC7 && pow2 == 0x3F) Serial.print ("AUTO " ); else if (pow1 == 0x00 && pow2 == 0x38) printf("Power 25%%\n"); else if (pow1 == 0x00 && pow2 == 0x3F) printf("Power 50%%\n"); else if (pow1 == 0xC0 && pow2 == 0x3F) printf("Power 75%%\n"); else if (pow1 == 0xF8 && pow2 == 0x3F) printf("Power 100%%\n"); if (leds > 0x01 ) { printf("ICONS: 0x%02X\n", leds); printf("ION: %s\n", (leds & 0x04) ? "ON" : "OFF"); printf("Steam: %s\n", (leds & 0x02) ? "ON" : "OFF"); printf("TIMER: %s\n", (leds & 0xF0) ? "ON" : "OFF"); } } Serial.print(displayType); Serial.print(" -> "); Serial.print(fullDisplay); Serial.print(" [Data: "); Serial.println("]"); } } char decodeDigit(uint8_t byte) { // main digit patterns for (int i = 0; i < 10; i++) { if (byte == digitPatterns[i]) { return '0' + i; } } // temperature patterns switch (byte) { case 0xBF: return '0'; // 0 with something else? case 0x86: return '1'; // 1 with something else? case 0xDB: return '2'; // 3 with something else? case 0xCF: return '3'; // 3 with something else? case 0xE6: return '4'; // 4 with something else? case 0xED: return '5'; // 5 with something else? case 0xFD: return '6'; // 6 with something else? case 0x87: return '7'; // 7 with something else? case 0xFF: return '8'; // 7 with something else? case 0xEF: return '9'; // 9 with something else? default: return '?'; } } void serialEvent() { while (Serial.available()) { String input = Serial.readStringUntil('\n'); input.trim(); if (input.startsWith("SEND ")) { float value = input.substring(5).toFloat(); if (value == 1) butclick(butPin1); if (value == 2) butclick(butPin2); if (value == 3) butclick(butPin3); } } }
Ниже пример с прикрученной вебмордой

Как-то так, всем успехов :-)
