maxint-rd / TM16xx

Arduino TM16xx library for LED & KEY and LED Matrix modules based on TM1638, TM1637, TM1640 and similar chips. Simply use print() on 7-segment and use Adafruit GFX on matrix.
177 stars 35 forks source link

Monitoring TM1628 #45

Closed mutilator closed 10 months ago

mutilator commented 10 months ago

Is there any way to use this library to listen to the serial comms. to my TM1628.

Sort of this library in reverse so I can make some of my 'dumb' devices 'smarter'. Read what's displayed on the 7 segments, listen for and make buttons presses on the grid, view other LED that are lit on the device, etc..

I'd like passively listen for everything and interpret the results. Ideally also send button presses from the library as well.

This is an older Royal Sovereign BDH-450 dehumidifier I'd like to remotely monitor and control.

I created fonts for the segments & LED based on playing around.

Segment 0 is the status LED for this device Segment 1 is the right digit Segment 2 is the left digit

Segment bitmap

 -- 1 --
|       |
2       0
 -- 3 --
6       7
|       |
 -- 4 --  .5

Segment display maps

byte SEG_DIGITS[] = {
  0b11101011, // 0
  0b10000001, // 1
  0b11011010, // 2
  0b11011001, // 3
  0b10110001, // 4
  0b01111001, // 5
  0b01111011, // 6
  0b11000001, // 7
  0b11111011, // 8
  0b11110001, // 9
  0b11110011, // A
  0b11111011, // B
  0b01101010, // C
  0b11101011, // D
  0b01111010, // E
  0b01110010  // F
};

Status LED for machine

byte STATUS_LIGHTS[] = {
  0b00000001, // FULL RESERVOIR
  0b00000010, // DEFROST
  0b00000100, // ?
  0b00001000, // ?
  0b00010000, // FAN - LOW
  0b00100000, // FAN - MEDIUM
  0b01000000, // FAN - HIGH
  0b10000000, // POWER
};

Using the getbutton listeners it returns the following Down Arrow - Button 1 Up Arrow - Button 2 Fan Speed - Button 6 Power - Button 7

mutilator commented 10 months ago

After a bit of playing around I wrote something from scratch so I can read the data but I think emulated button presses are out of the question because I can't respond to the 'are you pressing buttons' command because the TM1628 is also responding.

image

maxint-rd commented 10 months ago

Hello, interesting project! It seems really useful to be able to have some remote control to your dehumidifier. My own dehumidifier is not too smart either, so on a daily basis I have to go check it's light to see if the reservoir is full. Would be nice to get a notification on my phone!

So your dehumidifier has a TM1628? In the main readme of this library I'm collecting a list of real world devices using a TM16xx chip. Perhaps you can share a picture of the PCB, showing the chip?

Unfortunately this library only works in one direction and can only be used to drive TM16xx chips. It cannot emulate a TM16xx chip, but you may find it useful to drive a test circuit and analyze its workings. BTW. In issue #37 someone asked a similar question regarding the TM1651 and I gave some more elaborate responses.

Good to see you already implemented something and that you have successfully captured the display data and translated it to a readable form. Well done!

With regard to emulating the button presses, I see some potential. I think this requires additional hardware and cannot be done just in software. You first need to see which of the keyboard lines of the actual TM1628 are connected to the buttons (K1/K2 vs. KS1-KS8). Based on that you would need to solder something to the keyboard lines to make an electrical connection. Once you want to generate a remote button press, you briefly connect these lines to trigger the key-scanning process of the TM1628.

On page 12 of the english datasheet you can find the Application Circuit diagram. Since the TM1628 is meant for common cathode displays, that would suggest that the keyscan checks to see if the KS1-KS8 ouputs are set high by a reading the K1/K2 input lines. Perhaps having a logic mosfet between these will work; eg. between KS1 and K1. Alternatively you could try an opto-coupler, but that may have too high resistance when switched on. Of course you could also use a relais, but those are more bulky/expensive. Before destroying the dehumidifier, I suggest you to first make a test display using some sacrificial TM1628 chips to experiment with.

mutilator commented 10 months ago

Thanks for the quick response. Yea I had tried an opto-coupler and it wasn't working. I ended up using some tiny 3.3v relays and it worked fine, luckily only 4 buttons total.

Here are the images of both sides of the board. 2024-01-20 15 43 20 2024-01-20 15 43 37

Also here is the code I used to output the screenshot above in-case anyone else finds this later and wants to play.

It's running on an ESP-32 module

// Datasheet for TM1628 - https://aitendo3.sakura.ne.jp/aitendo_data/product_img/ic/LED-driver/TM1628english.pdf
#define PIN_DATA    26
#define PIN_CLOCK   18
#define PIN_STROBE  19

byte SEG_DIGITS[] = {
  0b11101011, // 0
  0b10000001, // 1
  0b11011010, // 2
  0b11011001, // 3
  0b10110001, // 4
  0b01111001, // 5
  0b01111011, // 6
  0b11000001, // 7
  0b11111011, // 8
  0b11110001, // 9
  0b11110011, // A
  0b11111011, // B
  0b01101010, // C
  0b11101011, // D
  0b01111010, // E
  0b01110010  // F
};

byte BIT_POWER = 0b10000000;
byte BIT_FAN_HIGH = 0b01000000;
byte BIT_FAN_MEDIUM = 0b00100000;
byte BIT_FAN_LOW = 0b00010000;
byte BIT_DEFROST = 0b00000010;
byte BIT_TANK_FULL = 0b00000001;

bool isStrobeActive = false; // If stroibe is active we're reading data
bool processByte = false; // Whether the main loop should process the data read

int bitsRead = 0; // how many bits read so far, we kick it at 8 no matter what
byte myData = 0; // Last data byte read from the serial
byte waitByteIdx = 0; // which byte of the 6 are we processing
bool gettingDisplay = false; // if we're parsing the 0xC0 command that pushes data to the display
byte aryIdx = 0; // where we're writing to the array
byte byteCache[5]; // small array in case we get data faster than we're processing it

int digitOnes = 0;
int digitTens = 0;
int powerOn = false;
int fanSpeed = 0; //0 = auto, 1 = low, 2 = med, 3 = high
int tankFull = false;
int defrosting = false;

void IRAM_ATTR strobeActive() {
  if (!digitalRead(PIN_STROBE))
  {
    isStrobeActive = true;
  } else {
    isStrobeActive = false;
  }
}

void IRAM_ATTR clockRead() {
  if (!isStrobeActive)
    return;

  bitsRead++;
  myData >>= 1;
  if (digitalRead(PIN_DATA))
    myData |= 0x80;

  if (bitsRead % 8 == 0)
  {
    byteCache[aryIdx] = myData;
    aryIdx++;
    processByte = true;
  }
}

void setup()
{
  Serial.begin(115200);
  pinMode(PIN_DATA, INPUT);
  pinMode(PIN_CLOCK, INPUT);
  pinMode(PIN_STROBE, INPUT);

  attachInterrupt(PIN_STROBE, strobeActive, CHANGE);
  attachInterrupt(PIN_CLOCK, clockRead, RISING);
}

void loop () {
  processIncomingData();
  if (millis() % 5000 == 0) // show something roughly every 5 seconds
    printStatus()
}

void printStatus()
{
  static char fs[7];
    static char pw[4];
    static char tf[6];
    static char df[4];
    switch (fanSpeed)
    {
      case 0:
        strcpy(fs,"Auto");
        break;
      case 1:
        strcpy(fs,"Low");
        break;
      case 2:
        strcpy(fs,"Medium");
        break;
      case 3:
        strcpy(fs,"High");
        break;
    }

    if (powerOn)
      strcpy(pw, "On");
    else
      strcpy(pw, "Off");

    if (tankFull)
      strcpy(tf, "Full");
    else
      strcpy(tf, "Empty");

    if (defrosting)
      strcpy(df, "Yes");
    else
      strcpy(df, "No");

    Serial.print("Current Humidity: ");
    Serial.print(digitTens);
    Serial.println(digitOnes);
    Serial.print("Power: ");
    Serial.println(pw);
    Serial.print("Fan Speed: ");
    Serial.println(fs);
    Serial.print("Water Reservoir: ");
    Serial.println(tf);
    Serial.print("Defrosting: ");
    Serial.println(df);
    Serial.println("");
    Serial.println("");
}

void processIncomingData()
{
  if (processByte)
  {
    for(int i = aryIdx - 1; i >= 0; i--)
    {
      if (gettingDisplay)
      {
        switch (waitByteIdx)
        {
          case 0: // LED STATUS_LIGHTS
            powerOn = (byteCache[i] & BIT_POWER) == BIT_POWER;
            fanSpeed = (byteCache[i] & BIT_FAN_HIGH) == BIT_FAN_HIGH?3:(byteCache[i] & BIT_FAN_MEDIUM) == BIT_FAN_MEDIUM?2:(byteCache[i] & BIT_FAN_LOW) == BIT_FAN_LOW?1:0;
            tankFull = (byteCache[i] & BIT_TANK_FULL) == BIT_TANK_FULL;
            defrosting = (byteCache[i] & BIT_DEFROST) == BIT_DEFROST;
            break;
          case 1: // empty bit? could be an issue with how we're reading data
            break;
          case 2:
            digitOnes = getDigit(byteCache[i]);
            break;
          case 3: // empty bit? could be an issue with how we're reading data
            break;
          case 4:
            digitTens = getDigit(byteCache[i]);
            break;
          case 5: // empty bit? could be an issue with how we're reading data
            gettingDisplay = false; // This is the last empty bit from the 0xC0
            break;
        }
        waitByteIdx++;
      }
      if (byteCache[i] == 0xC0) // Command 3 from datasheet
      {
        waitByteIdx = 0;
        gettingDisplay = true; // Start interpreting the following bytes as segment display
      }
      //Serial.println(byteCache[i],HEX);
    }
    aryIdx = 0;

    processByte = false;
  }
}

byte getDigit(byte myDigit)
{
  for(int i = 0; i < 9; i++)
    if (myDigit == SEG_DIGITS[i])
      return i;

  return 0; //uh give em something i guess?
}
maxint-rd commented 10 months ago

Very nice project with great success. Congratulations ! Thank you for sharing the pictures and your code. I've added a link to this issue in the main readme so that your contribution is highlighted and the issue can be closed.

mutilator commented 10 months ago

I turned this into an ESPhome component integration here: https://github.com/mutilator/esphome/tree/dev/esphome/components/bdh450

Might give someone a bit of a start on a similar path.

maxint-rd commented 10 months ago

Very nice! Good example of how to make a device smart and integrate it in your household. Thank you for sharing your code!