From 7f64b6e04bc23b130f55bf0dbf43f3402907a62c Mon Sep 17 00:00:00 2001 From: Gabriele Lauricella Date: Sun, 12 Mar 2023 09:22:40 +0100 Subject: [PATCH] Enhanced TM1650 with 7-Segment commands like TM1637 (#18109) * Enhanced TM1650 with 7-Segment commands like TM1637 * Fix point for display type 2 --- .../tasmota_xdrv_driver/xdrv_13_display.ino | 2 +- .../tasmota_xdsp_display/xdsp_20_tm1650.ino | 1148 +++++++++++++++-- 2 files changed, 1017 insertions(+), 133 deletions(-) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino index f55947bce..47bfd7c62 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino @@ -2147,7 +2147,7 @@ void CmndDisplayText(void) { #ifndef USE_DISPLAY_MODES1TO5 DisplayText(); #else - if(Settings->display_model == 15) { + if(Settings->display_model == 15 || Settings->display_model == 20) { XdspCall(FUNC_DISPLAY_SEVENSEG_TEXT); } else if (!Settings->display_mode) { DisplayText(); diff --git a/tasmota/tasmota_xdsp_display/xdsp_20_tm1650.ino b/tasmota/tasmota_xdsp_display/xdsp_20_tm1650.ino index 1d7540895..9677abf09 100644 --- a/tasmota/tasmota_xdsp_display/xdsp_20_tm1650.ino +++ b/tasmota/tasmota_xdsp_display/xdsp_20_tm1650.ino @@ -2,6 +2,7 @@ xdsp_20_tm1650.ino - TM1650 four-digit seven-segment LED display controller support for Tasmota Copyright (C) 2021 Stefan Oskamp, Theo Arends, Anatoli Arkhipenko + Copyright (C) 2023 Gabriele Lauricella This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,15 +33,139 @@ connected. This wiring of the XY-Clock has been reflected in the time format. Other clocks using a TM1650 might be wired differently. */ + #ifdef USE_I2C #ifdef USE_DISPLAY #ifdef USE_DISPLAY_TM1650 -#include + +/*********************************************************************************************\ + This driver enables the display of numbers (both integers and floats) and basic text + on the inexpensive TM1650 seven-segment modules. + + Raw segments can also be displayed. + + In addition, it is also possible to set brightness (8 levels), clear the display, scroll text, + display a rudimentary bar graph, and a Clock (12 hr and 24 hr). + + To use, compile Tasmota with USE_I2C, USE_DISPLAY and USE_DISPLAY_TM1650, or build the tasmota-display env. + + For TM1650: + Connect the TM1650 display module's pins to any free GPIOs of the ESP8266 module + and assign the pins as follows from Tasmota's GUI: + + DIO hardware pin --> "I2C SCA" + CLK hardware pin --> "I2C SCL" + + Once the GPIO configuration is saved and the ESP8266/ESP32 module restarts, + set the Display Model to 20 and Display Mode to 0 + using the command "Backlog DisplayModel 20 ; DisplayMode 0;" + Before using it set the Display Type, if you have an XY-Clock set it to 0 otherwise if you + have a 303WifiL01 set it to 2 using the "DisplayType 0" command. + + + After the ESP8266/ESP32 module restarts again, turn ON the display with the command "Power 1" + + Now, the following "Display" commands can be used: + + + DisplayClear + + Clears the display, command: "DisplayClear" + + + DisplayNumber num [,position {0-(Settings->display_width-1))} [,leading_zeros {0|1} [,length {1 to Settings->display_width}]]] + + Clears and then displays number without decimal. command e.g., "DisplayNumber 1234" + Control 'leading zeros', 'length' and 'position' with "DisplayNumber 1234, , , " + 'leading zeros' can be 1 or 0 (default), 'length' can be 1 to Settings->display_width, 'position' can be 0 (left-most) to Settings->display_width (right-most). + See function description below for more details. + + DisplayNumberNC num [,position {0-(Settings->display_width-1))} [,leading_zeros {0|1} [,length {1 to Settings->display_width}]]] + + Display integer number as above, but without clearing first. e.g., "DisplayNumberNC 1234". Usage is same as above. + + + + DisplayFloat num [,position {0-(Settings->display_width-1)} [,precision {0-Settings->display_width} [,length {1 to Settings->display_width}]]] + + Clears and then displays float (with decimal point) command e.g., "DisplayFloat 12.34" + See function description below for more details. + + + + DisplayFloatNC num [,position {0-(Settings->display_width-1)} [,precision {0-Settings->display_width} [,length {1 to Settings->display_width}]]] + + Displays float (with decimal point) as above, but without clearing first. command e.g., "DisplayFloatNC 12.34" + See function description below for more details. + + + + DisplayRaw position {0-(Settings->display_width-1)},length {1 to Settings->display_width}, num1 [, num2[, num3[, num4[, ...upto Settings->display_width numbers]]]]] + + Takes upto Settings->display_width comma-separated integers (0-255) and displays raw segments. Each number represents a + 7-segment digit. Each 8-bit number represents individual segments of a digit. + For example, the command "DisplayRaw 0, 4, 255, 255, 255, 255" would display "[8.8.8.8.]" + + + + DisplayText text [, position {0-(Settings->display_width-1)} [,length {1 to Settings->display_width}]] + + Clears and then displays basic text. command e.g., "DisplayText ajith vasudevan" + Control 'length' and 'position' with "DisplayText , , " + 'length' can be 1 to Settings->display_width, 'position' can be 0 (left-most) to Settings->display_width-1 (right-most) + A caret(^) or backtick(`) symbol in the text input is dispayed as the degrees(°) symbol. This is useful for + displaying Temperature! For example, the command "DisplayText 22.5^" will display "22.5°". + + + DisplayTextNC text [, position {0-Settings->display_width-1} [,length {1 to Settings->display_width}]] + + Clears first, then displays text. Usage is same as above. + + + + DisplayScrollText text [, num_loops] + + Displays scrolling text indefinitely, until another Display- command (other than DisplayScrollText + or DisplayScrollDelay is issued). Optionally, stop scrolling after num_loops iterations. + + + + DisplayScrollDelay delay {0-15} // default = 4 + + Sets the speed of text scroll. Smaller delay = faster scrolling. + + + + DisplayLevel num {0-100} + + Display a horizontal bar graph (0-100) command e.g., "DisplayLevel 50" will display [|||| ] + + + + DisplayClock 1|2|0 + + Displays a clock. + Commands "DisplayClock 1" // 12 hr format + "DisplayClock 2" // 24 hr format + "DisplayClock 0" // turn off clock + + +In addition, if you compile using USE_DISPLAY_MODES1TO5, setting DisplayMode to 1 shows the time, +setting it to 2 shows the date and setting it to 3 alternates between time and date (using "DisplayRefresh [1..7]" +for the time and seconds you want to show the time before displaying the date) + +\*********************************************************************************************/ + #define XDSP_20 20 #define XI2C_74 74 // See I2CDEVICES.md +#define CMD_MAX_LEN 55 +#define LEVEL_MIN 0 +#define LEVEL_MAX 100 +#define SCROLL_MAX_LEN 50 + #define TM1650_CONTROL_BASE 0x24 // I2C address to control left-most digit. #define TM1650_DISPLAY_BASE 0x34 // I2C address to display something in the left-most digit. #define TM1650_DIGITS 4 // One TM1650 can only control modules with up to four digits. @@ -56,6 +181,7 @@ #define TM1650_CONTROL_BRIGHTNESS 4 // Bits 4...6 #define TM1650_CONTROL_RESERVED3 7 +#include static unsigned char TM1650Control[TM1650_DIGITS] = {0, 0, 0, 0}; static unsigned char TM1650Display[TM1650_DIGITS] = {0, 0, 0, 0}; @@ -64,57 +190,69 @@ static const byte TM1650Font[128] { //0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x10 +// ! " ' ( ) - 0x00, 0x82, 0x21, 0x00, 0x00, 0x00, 0x00, 0x02, 0x39, 0x0F, 0x00, 0x00, 0x00, 0x40, 0x80, 0x00, // 0x20 +//0 1 2 3 4 5 6 7 8 9 = ? 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7f, 0x6f, 0x00, 0x00, 0x00, 0x48, 0x00, 0x53, // 0x30 +// A B C D E F G H I J L N O 0x00, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x6F, 0x76, 0x06, 0x1E, 0x00, 0x38, 0x00, 0x54, 0x3F, // 0x40 - 0x73, 0x67, 0x50, 0x6D, 0x78, 0x3E, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x39, 0x00, 0x0F, 0x00, 0x08, // 0x50 +//P Q R S T U Y [ ] ^=° _ + 0x73, 0x67, 0x50, 0x6D, 0x78, 0x3E, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x39, 0x00, 0x0F, 0x63, 0x08, // 0x50 +//`=° a b c d e f g h i j l n o 0x63, 0x5F, 0x7C, 0x58, 0x5E, 0x7B, 0x71, 0x6F, 0x74, 0x02, 0x1E, 0x00, 0x06, 0x00, 0x54, 0x5C, // 0x60 +//p q r s t u y { | } 0x73, 0x67, 0x50, 0x6D, 0x78, 0x1C, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x39, 0x30, 0x0F, 0x00, 0x00 // 0x70 }; -const uint8_t TM1650Remap[] = { - 0x00, 0x20, 0x80, 0xA0, 0x04, 0x24, 0x84, 0xA4, 0x08, 0x28, 0x88, 0xA8, 0x0C, 0x2C, 0x8C, 0xAC, - 0x10, 0x30, 0x90, 0xB0, 0x14, 0x34, 0x94, 0xB4, 0x18, 0x38, 0x98, 0xB8, 0x1C, 0x3C, 0x9C, 0xBC, - 0x40, 0x60, 0xC0, 0xE0, 0x44, 0x64, 0xC4, 0xE4, 0x48, 0x68, 0xC8, 0xE8, 0x4C, 0x6C, 0xCC, 0xEC, - 0x50, 0x70, 0xD0, 0xF0, 0x54, 0x74, 0xD4, 0xF4, 0x58, 0x78, 0xD8, 0xF8, 0x5C, 0x7C, 0xDC, 0xFC, - 0x02, 0x22, 0x82, 0xA2, 0x06, 0x26, 0x86, 0xA6, 0x0A, 0x2A, 0x8A, 0xAA, 0x0E, 0x2E, 0x8E, 0xAE, - 0x12, 0x32, 0x92, 0xB2, 0x16, 0x36, 0x96, 0xB6, 0x1A, 0x3A, 0x9A, 0xBA, 0x1E, 0x3E, 0x9E, 0xBE, - 0x42, 0x62, 0xC2, 0xE2, 0x46, 0x66, 0xC6, 0xE6, 0x4A, 0x6A, 0xCA, 0xEA, 0x4E, 0x6E, 0xCE, 0xEE, - 0x52, 0x72, 0xD2, 0xF2, 0x56, 0x76, 0xD6, 0xF6, 0x5A, 0x7A, 0xDA, 0xFA, 0x5E, 0x7E, 0xDE, 0xFE, - 0x01, 0x21, 0x81, 0xA1, 0x05, 0x25, 0x85, 0xA5, 0x09, 0x29, 0x89, 0xA9, 0x0D, 0x2D, 0x8D, 0xAD, - 0x11, 0x31, 0x91, 0xB1, 0x15, 0x35, 0x95, 0xB5, 0x19, 0x39, 0x99, 0xB9, 0x1D, 0x3D, 0x9D, 0xBD, - 0x41, 0x61, 0xC1, 0xE1, 0x45, 0x65, 0xC5, 0xE5, 0x49, 0x69, 0xC9, 0xE9, 0x4D, 0x6D, 0xCD, 0xED, - 0x51, 0x71, 0xD1, 0xF1, 0x55, 0x75, 0xD5, 0xF5, 0x59, 0x79, 0xD9, 0xF9, 0x5D, 0x7D, 0xDD, 0xFD, - 0x03, 0x23, 0x83, 0xA3, 0x07, 0x27, 0x87, 0xA7, 0x0B, 0x2B, 0x8B, 0xAB, 0x0F, 0x2F, 0x8F, 0xAF, - 0x13, 0x33, 0x93, 0xB3, 0x17, 0x37, 0x97, 0xB7, 0x1B, 0x3B, 0x9B, 0xBB, 0x1F, 0x3F, 0x9F, 0xBF, - 0x43, 0x63, 0xC3, 0xE3, 0x47, 0x67, 0xC7, 0xE7, 0x4B, 0x6B, 0xCB, 0xEB, 0x4F, 0x6F, 0xCF, 0xEF, - 0x53, 0x73, 0xD3, 0xF3, 0x57, 0x77, 0xD7, 0xF7, 0x5B, 0x7B, 0xDB, 0xFB, 0x5F, 0x7F, 0xDF, 0xFF +enum display_options_types +{ + T_XY_CLOCK, // XY-Clock - white PCB with a perpendicular small daughter board + NOTUSED, + T_303WIFILC01 // 303WifiLC01 - green clock }; -/*********************************************************************************************/ - -void TM1650InitMode(void) +struct { - TM1650Dim(); - TM1650Clear(); -} // void TM1650InitMode(void) + char scroll_text[CMD_MAX_LEN]; + char msg[60]; + char model_name[8]; + uint8_t scroll_delay = 4; + uint8_t scroll_index = 0; + uint8_t iteration = 0; + uint8_t scroll_counter = 0; + uint8_t scroll_counter_max = 3; + uint8_t display_type = XDSP_20; + bool init_driver_done = false; + bool scroll = false; + bool show_clock = false; + bool clock_24 = false; + bool clock_blynk_dots = true; +} TM1650Data; + +/*********************************************************************************************\ +* Init function +\*********************************************************************************************/ void TM1650Init(uint8_t mode) { switch(mode) { case DISPLAY_INIT_MODE: - TM1650InitMode(); + TM1650Dim(); + TM1650ClearDisplay(); break; case DISPLAY_INIT_PARTIAL: case DISPLAY_INIT_FULL: - TM1650InitMode(); + TM1650Dim(); + TM1650ClearDisplay(); break; } -} // void TM1650Init(uint8_t mode) +} +/*********************************************************************************************\ +* Init Drive function +\*********************************************************************************************/ void TM1650InitDriver(void) { - // AddLog(LOG_LEVEL_DEBUG, PSTR("M1650InitDriver()")); if (!TasmotaGlobal.i2c_enabled) { return; } @@ -130,37 +268,50 @@ void TM1650InitDriver(void) I2cSetActiveFound(Settings->display_address[0], "TM1650"); Settings->display_cols[0] = 4; - Settings->display_cols[0] = 1; + Settings->display_rows = 1; Settings->display_width = Settings->display_cols[0]; Settings->display_height = Settings->display_rows; - TM1650InitMode(); + if (T_XY_CLOCK == Settings->display_options.type) + { + strcpy_P(TM1650Data.model_name, PSTR("XY-Clock")); + } + else if (NOTUSED == Settings->display_options.type) + { + strcpy_P(TM1650Data.model_name, PSTR("NOTUSED")); + } + else if (T_303WIFILC01 == Settings->display_options.type) + { + strcpy_P(TM1650Data.model_name, PSTR("303WiFiLC01")); + } - AddLog(LOG_LEVEL_INFO, PSTR("DSP: TM1650")); + TM1650Dim(); + TM1650ClearDisplay(); + + AddLog(LOG_LEVEL_INFO, PSTR("DSP: TM1650 \"%s\" with %d digits (type %d)"), TM1650Data.model_name, Settings->display_width, Settings->display_options.type); + TM1650Data.init_driver_done = true; } -} // void TM1650InitDriver(void) +} void TM1650DisplayOn () { - // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1650DisplayOn()")); for (int i = 0; i < TM1650_DIGITS; i++) { TM1650Control[i] |= _BV(TM1650_CONTROL_ON); Wire.beginTransmission(TM1650_CONTROL_BASE + i); Wire.write(TM1650Control[i]); Wire.endTransmission(); } -} // void TM1650DisplayOn () +} void TM1650DisplayOff () { - // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1650DisplayOff()")); for (int i = 0; i < TM1650_DIGITS; i++) { TM1650Control[i] &= ~_BV(TM1650_CONTROL_ON); Wire.beginTransmission(TM1650_CONTROL_BASE + i); Wire.write(TM1650Control[i]); Wire.endTransmission(); } -} // void TM1650DisplayOff () +} void TM1650DisplayOnOff() { @@ -170,11 +321,10 @@ void TM1650DisplayOnOff() else { TM1650DisplayOff(); } -} // void TM1650DisplayOnOff() +} void TM1650SetBrightness (unsigned int level) { - // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1650SetBrightness()")); if (level > 0b111) level = 0b111; for (int i = 0; i < TM1650_DIGITS; i++) { @@ -183,41 +333,14 @@ void TM1650SetBrightness (unsigned int level) Wire.write(TM1650Control[i]); Wire.endTransmission(); } -} // void TM1650SetBrightness (unsigned int level) +} -void TM1650Dim(void) -{ - int b = GetDisplayDimmer16(); - if (b < 2) { - TM1650DisplayOff(); - } - else if (b > 14) { - TM1650DisplayOn(); - TM1650SetBrightness(0); // In TM1650, brightness 0 means max brightness (level 8). - TM1650DisplayOn(); - } - else { - // Map 2...14 to 1...7: - TM1650SetBrightness(b >> 1); - TM1650DisplayOn(); - } -} // void TM1650Dim(void) -void TM1650Clear (void) +void TM1650DisplayText (char *text) // Text shall match regex (([^.]?\.?){0,4}\0), e.g., 123.4 or 8.8.8.8 for full digit { - // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1650Clear()")); - for (int i = 0; i < TM1650_DIGITS; i++) { - TM1650Display[i] = 0; - Wire.beginTransmission(TM1650_DISPLAY_BASE + i); - Wire.write(TM1650Display[i]); - Wire.endTransmission(); - } -} // void TM1650Clear (void) - -void TM1650DisplayText (char *text) // Text shall match regex (([^.]?\.?){0,4}\0), e.g., 123.4 -{ - // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1650DisplayText(\"%s\")"), text); + //AddLog(LOG_LEVEL_DEBUG, PSTR("TM1650DisplayText(\"%s\")"), text); for (int i = 0; i < TM1650_DIGITS; i++) { + if (*text != 0) { if (*text == '.') { TM1650Display[i] = 0; // Blank this digit, set the dot below. @@ -232,42 +355,791 @@ void TM1650DisplayText (char *text) // Text shall match regex (([^.]?\.?){0,4}\ TM1650Display[i] |= _BV(TM1650_DISPLAY_DOT); } - if (Settings->display_options.type == 2) { - TM1650Display[i] = TM1650Remap[TM1650Display[i]]; // 303WIFILC01 board has special wiring + if (T_303WIFILC01 == Settings->display_options.type) { + TM1650Display[i] = swapbits(TM1650Display[i]); // 303WIFILC01 board has special wiring } - } // if (not at end of text) - else { // No more text. + } + else { TM1650Display[i] = 0; // Clear digits after the text. } Wire.beginTransmission(TM1650_DISPLAY_BASE + i); Wire.write(TM1650Display[i]); Wire.endTransmission(); - } // for (all digits) -} // void TM1650DisplayText (char *text) + } +} + +uint8_t swapbits(uint8_t n) +{ + // For model 303WiFiLC01 board has different segments wiring + // PGFEDCBA -> BFAEDCGP + + //seg EDC //seg A //seg B + return (n & 0x1C) | ((n & 0x01) << 5) | ((n & 0x02) << 6) + | ((n & 0x20) << 1) | ((n & 0x40) >> 5) | ((n & 0x80) >> 7); + //seg F //seg G //seg P +} + +void TM1650DisplayRaw (char *text) // Text shall match regex (([^.]?\.?){0,4}\0), e.g., 123.4 +{ + for (int i = 0; i < TM1650_DIGITS; i++) { + char c = *text++; + TM1650Display[i] = c; + //AddLog(LOG_LEVEL_DEBUG, PSTR("Raw Digit(%d - %d - %c)"), i, TM1650Display[i], TM1650Display[i]); + Wire.beginTransmission(TM1650_DISPLAY_BASE + i); + Wire.write(TM1650Display[i]); + Wire.endTransmission(); + } +} + + +/*********************************************************************************************\ +* Displays number without decimal, with/without leading zeros, specifying start-position +* and length, optionally skipping clearing display before displaying the number. +* commands: DisplayNumber num [,position {0-(Settings->display_width-1)} [,leading_zeros {0|1} [,length {1 to Settings->display_width}]]] +* DisplayNumberNC num [,position {0-(Settings->display_width-1)} [,leading_zeros {0|1} [,length {1 to Settings->display_width}]]] // "NC" --> "No Clear" +\*********************************************************************************************/ + +bool CmndTM1650Number(bool clear) +{ + char sNum[CMD_MAX_LEN]; + char sLeadingzeros[CMD_MAX_LEN]; + char sPosition[CMD_MAX_LEN]; + char sLength[CMD_MAX_LEN]; + uint8_t length = 0; + bool leadingzeros = false; + uint8_t position = 0; + + uint32_t num = 0; + + switch (ArgC()) + { + case 4: + subStr(sLength, XdrvMailbox.data, ",", 4); + length = atoi(sLength); + case 3: + subStr(sLeadingzeros, XdrvMailbox.data, ",", 3); + leadingzeros = atoi(sLeadingzeros); + case 2: + subStr(sPosition, XdrvMailbox.data, ",", 2); + position = atoi(sPosition); + case 1: + subStr(sNum, XdrvMailbox.data, ",", 1); + num = TextToInt(sNum); + } + + if ((position < 0) || (position > (Settings->display_width - 1))) + position = 0; + + AddLog(LOG_LEVEL_DEBUG, PSTR("TM5: num %d, pos %d, lead %d, len %d"), num, position, leadingzeros, length); + + if (clear) + TM1650ClearDisplay(); + + char txt[30]; + snprintf_P(txt, sizeof(txt), PSTR("%d"), num); + if (!length) + length = strlen(txt); + if ((length < 0) || (length > Settings->display_width)) + length = Settings->display_width; + + char pad = (leadingzeros ? '0' : ' '); + uint32_t i = position; + + char text[TM1650_DIGITS + 1]; + + //write empty pos + for (uint32_t j=0; j < position; j++) + { + if (j >= Settings->display_width) + break; + text[j] = ' '; + } + + //write pads + for (; i < position + (length - strlen(txt)); i++) + { + if (i >= Settings->display_width) + break; + text[i] = pad; + } + + //write digits + for (uint32_t j = 0; i < position + length; i++, j++) + { + if (i >= Settings->display_width) + break; + if (txt[j] == 0) + break; + if (txt[j] == '.') { + i--; + continue; + } + text[i] = txt[j]; + } + + text[i] = 0; //string termination + + TM1650DisplayText(text); + return true; +} + + +/*********************************************************************************************\ +* Displays number with decimal, specifying position, precision and length, +* optionally skipping clearing display before displaying the number. +* commands: DisplayFloat num [,position {0-(Settings->display_width-1)} [,precision {0-Settings->display_width} [,length {1 to Settings->display_width}]]] +* DisplayFloatNC num [,position {0-(Settings->display_width-1)} [,precision {0-Settings->display_width} [,length {1 to Settings->display_width}]]] // "NC" --> "No Clear" +\*********************************************************************************************/ + +bool CmndTM1650Float(bool clear) +{ + + char sNum[CMD_MAX_LEN]; + char sPrecision[CMD_MAX_LEN]; + char sPosition[CMD_MAX_LEN]; + char sLength[CMD_MAX_LEN]; + uint8_t length = 0; + uint8_t precision = Settings->display_width-1; + uint8_t position = 0; + + float fnum = 0.0f; + + switch (ArgC()) + { + case 4: + subStr(sLength, XdrvMailbox.data, ",", 4); + length = atoi(sLength); + case 3: + subStr(sPrecision, XdrvMailbox.data, ",", 3); + precision = atoi(sPrecision); + case 2: + subStr(sPosition, XdrvMailbox.data, ",", 2); + position = atoi(sPosition); + case 1: + subStr(sNum, XdrvMailbox.data, ",", 1); + fnum = CharToFloat(sNum); + } + + if ((position < 0) || (position > (Settings->display_width))) + position = 0; + if ((precision < 0) || (precision >= Settings->display_width)) + precision = Settings->display_width-1; + + if (clear) + TM1650ClearDisplay(); + + char txt[30]; + ext_snprintf_P(txt, sizeof(txt), PSTR("%*_f"), precision, &fnum); + + if (!length) + length = strlen(txt); + if ((length <= 0) || (length > Settings->display_width)) + length = Settings->display_width; + + AddLog(LOG_LEVEL_DEBUG, PSTR("TM5: num %4_f, pos %d, prec %d, len %d, txt %s"), &fnum, position, precision, length, txt); + + uint32_t i = position; + uint32_t dots = 0; + char text[TM1650_DIGITS + 1 + 1]; + + //write empty pos + for (uint32_t j=0; j < position; j++) + { + if (j >= Settings->display_width) + break; + text[j] = ' '; + } + + if (T_XY_CLOCK == Settings->display_options.type) { + + for (uint32_t j = 0; i < position + length + dots; i++, j++) + { + if (txt[j] == '.') dots++; + if (i >= Settings->display_width + dots) + break; + if (txt[j] == 0) + break; + if (txt[j] == '.') { + if(i==2) { + //2nd dot ok + } + else if(i==3){ + //3rd dot but move do 1st + text[i] = text[i-1]; + text[i-1] = text[i-2]; + text[i-2] = txt[j]; + continue; + } + else { + //dot on 1st or 4th + AddLog(LOG_LEVEL_INFO, PSTR("TM5: Can't display this float")); + return false; + } + } + text[i] = txt[j]; + } + + } + else if (T_303WIFILC01 == Settings->display_options.type) { + //todo + AddLog(LOG_LEVEL_INFO, PSTR("TM5: DisplayFloat not implemented yet for display type: 303WifiClock")); + return false; + } + + text[i] = 0; //string termination + + TM1650DisplayText(text); + return true; +} + +// /*********************************************************************************************\ +// * Clears the display +// * Command: DisplayClear +// \*********************************************************************************************/ +bool CmndTM1650Clear(void) +{ + TM1650ClearDisplay(); + sprintf(TM1650Data.msg, PSTR("Cleared")); + XdrvMailbox.data = TM1650Data.msg; + return true; +} + +// /*********************************************************************************************\ +// * Clears the display +// \*********************************************************************************************/ +void TM1650ClearDisplay (void) +{ + for (int i = 0; i < TM1650_DIGITS; i++) { + TM1650Display[i] = 0; + Wire.beginTransmission(TM1650_DISPLAY_BASE + i); + Wire.write(TM1650Display[i]); + Wire.endTransmission(); + } +} + +/*********************************************************************************************\ +* Display scrolling text +* Command: DisplayScrollText text +\*********************************************************************************************/ +bool CmndTM1650ScrollText(void) +{ + + char sString[SCROLL_MAX_LEN + 1]; + char sMaxLoopCount[CMD_MAX_LEN]; + uint8_t maxLoopCount = 0; + + switch (ArgC()) + { + case 2: + subStr(sMaxLoopCount, XdrvMailbox.data, ",", 2); + maxLoopCount = atoi(sMaxLoopCount); + case 1: + subStr(sString, XdrvMailbox.data, ",", 1); + } + + if (maxLoopCount < 0) + maxLoopCount = 0; + + //AddLog(LOG_LEVEL_DEBUG, PSTR("TM5: sString %s, maxLoopCount %d"), sString, maxLoopCount); + + TM1650Data.scroll_counter_max = maxLoopCount; + + if (strlen(sString) > SCROLL_MAX_LEN) + { + snprintf(TM1650Data.msg, sizeof(TM1650Data.msg), PSTR("Text too long. Length should be less than %d"), SCROLL_MAX_LEN); + XdrvMailbox.data = TM1650Data.msg; + return false; + } + else + { + snprintf(TM1650Data.scroll_text, sizeof(TM1650Data.scroll_text), PSTR(" ")); + snprintf(TM1650Data.scroll_text, Settings->display_width + sizeof(TM1650Data.scroll_text), PSTR(" %s"), &sString); + TM1650Data.scroll_text[strlen(sString) + Settings->display_width] = 0; + TM1650Data.scroll_index = 0; + TM1650Data.scroll = true; + TM1650Data.scroll_counter = 0; + return true; + } +} + +/*********************************************************************************************\ +* Sets the scroll delay for scrolling text. +* Command: DisplayScrollDelay delay {0-15} // default = 4 +\*********************************************************************************************/ +bool CmndTM1650ScrollDelay(void) +{ + if (ArgC() == 0) + { + XdrvMailbox.payload = TM1650Data.scroll_delay; + return true; + } + if (TM1650Data.scroll_delay < 0) + TM1650Data.scroll_delay = 0; + TM1650Data.scroll_delay = XdrvMailbox.payload; + return true; +} + +/*********************************************************************************************\ +* Scrolls a given string. Called every 50ms +\*********************************************************************************************/ +void TM1650ScrollText(void) +{ + if(!TM1650Data.scroll) return; + TM1650Data.iteration++; + if (TM1650Data.scroll_delay) + TM1650Data.iteration = TM1650Data.iteration % TM1650Data.scroll_delay; + else + TM1650Data.iteration = 0; + if (TM1650Data.iteration) + return; + + if (TM1650Data.scroll_index > strlen(TM1650Data.scroll_text)) + { + TM1650Data.scroll_index = 0; + TM1650Data.scroll_counter++; + if(TM1650Data.scroll_counter_max != 0 && (TM1650Data.scroll_counter >= TM1650Data.scroll_counter_max)) { + TM1650Data.scroll = false; + return; + } + } + + char text[CMD_MAX_LEN + 2 + 1]; + uint32_t i; + uint32_t j; + for (i = 0, j = TM1650Data.scroll_index; i < 1 + strlen(TM1650Data.scroll_text); i++, j++) + { + if (i > (Settings->display_width - 1)) + { + break; + } + text[i] = TM1650Data.scroll_text[j]; + } + + text[i] = 0; //string termination + + TM1650DisplayText(text); + TM1650Data.scroll_index++; +} + +/*********************************************************************************************\ +* Displays a horizontal bar graph. Takes a percentage number (0-100) as input +* Command: DisplayLevel level {0-100} +\*********************************************************************************************/ +bool CmndTM1650Level(void) +{ + uint16_t val = XdrvMailbox.payload; + if ((val < LEVEL_MIN) || (val > LEVEL_MAX)) + { + Response_P(PSTR("{\"Error\":\"Level should be a number in the range [%d, %d]\"}"), LEVEL_MIN, LEVEL_MAX); + return false; + } + + char text[TM1650_DIGITS + 1]; + for (uint32_t i = 0; i < TM1650_DIGITS; i++) + { + text[i]=0; + } + + uint8_t totalBars = 2 * Settings->display_width; + AddLog(LOG_LEVEL_DEBUG, PSTR("TM5: CmndTM1650Level totalBars=%d"), totalBars); + float barsToDisplay = totalBars * val / 100.0f; + char txt[5]; + ext_snprintf_P(txt, sizeof(txt), PSTR("%*_f"), 1, &barsToDisplay); + AddLog(LOG_LEVEL_DEBUG, PSTR("TM5: CmndTM1650Level barsToDisplay=%s"), txt); + char s[4]; + ext_snprintf_P(s, sizeof(s), PSTR("%0_f"), &barsToDisplay); + uint8_t numBars = atoi(s); + AddLog(LOG_LEVEL_DEBUG, PSTR("TM5: CmndTM1650Level numBars %d"), numBars); + + TM1650ClearDisplay(); + + uint8_t digit = numBars / 2; + uint8_t remainder = numBars % 2; + + for (uint32_t i = 0; i < TM1650_DIGITS; i++) + { + if (i >= Settings->display_width) + break; + if(idisplay_width-1)},length {1 to Settings->display_width}, a [, b[, c[, d[...upto Settings->display_width]]]] +* where a,b,c,d... are upto Settings->display_width numbers in the range 0-255, each number (byte) +* corresponding to a single 7-segment digit. Within each byte, bit 0 is segment A, +* bit 1 is segment B etc. The function may either set the entire display +* or any desired part using the length and position parameters. +\*********************************************************************************************/ +bool CmndTM1650Raw(void) +{ + char text[TM1650_DIGITS + 1]; + for (uint32_t i = 0; i < TM1650_DIGITS; i++) + { + text[i]=0; + } + + uint8_t DATA[4] = {0, 0, 0, 0}; + + char as[CMD_MAX_LEN]; + char bs[CMD_MAX_LEN]; + char cs[CMD_MAX_LEN]; + char ds[CMD_MAX_LEN]; + + + char sLength[CMD_MAX_LEN]; + char sPos[CMD_MAX_LEN]; + + uint32_t position = 0; + uint32_t length = 0; + + switch (ArgC()) + { + case 6: + subStr(ds, XdrvMailbox.data, ",", 6); + DATA[3] = atoi(ds); + case 5: + subStr(cs, XdrvMailbox.data, ",", 5); + DATA[2] = atoi(cs); + case 4: + subStr(bs, XdrvMailbox.data, ",", 4); + DATA[1] = atoi(bs); + case 3: + subStr(as, XdrvMailbox.data, ",", 3); + DATA[0] = atoi(as); + case 2: + subStr(sLength, XdrvMailbox.data, ",", 2); + length = atoi(sLength); + case 1: + subStr(sPos, XdrvMailbox.data, ",", 1); + position = atoi(sPos); + } + + if (!length) + length = ArgC() - 2; + if (length < 0 || length > Settings->display_width) + length = Settings->display_width; + if (position < 0 || position > (Settings->display_width - 1)) + position = 0; + + AddLog(LOG_LEVEL_DEBUG, PSTR("TM5: a %d, b %d, c %d, d %d, len %d, pos %d"), + DATA[0], DATA[1], DATA[2], DATA[3], length, position); + + for (uint32_t i = position; i < position + length; i++) + { + if (i >= Settings->display_width) + break; + text[i] = DATA[i - position]; + } + + TM1650DisplayRaw(text); + + return true; +} + +/*********************************************************************************************\ +* Display a given string. +* Text can be placed at arbitrary location on the display using the length and +* position parameters without affecting the rest of the display. +* Command: DisplayText text [, position {0-(Settings->display_width-1)} [,length {1 to Settings->display_width}]] +\*********************************************************************************************/ +bool CmndTM1650Text(bool clear) +{ + char text[CMD_MAX_LEN + 2 + 1]; + char sString[CMD_MAX_LEN + 2 + 1]; + char sPosition[CMD_MAX_LEN]; + char sLength[CMD_MAX_LEN]; + uint8_t length = 0; + uint8_t position = 0; + uint8_t dots = 0; + uint8_t strLen = 0; + + switch (ArgC()) + { + case 3: + subStr(sLength, XdrvMailbox.data, ",", 3); + length = atoi(sLength); + case 2: + subStr(sPosition, XdrvMailbox.data, ",", 2); + position = atoi(sPosition); + case 1: + subStr(sString, XdrvMailbox.data, ",", 1); + } + + if ((position < 0) || (position > (Settings->display_width - 1))) + position = 0; + + strLen = strlen(sString); + + if (!length) + length = Settings->display_width; + if ((length < 0) || (length > Settings->display_width)) + length = Settings->display_width; + + if (clear) + TM1650ClearDisplay(); + + uint32_t s = 0; + uint32_t i = 0; + + for (i = 0; i < (strLen + position); i++) + { + if ((i >= (length + dots + position)) || dots > 4) { + break; + } + + if(i 2)) + return false; + + if (val == 1) { + TM1650Data.show_clock = true; + TM1650Data.clock_24 = false; + } + else if (val == 2) { + TM1650Data.show_clock = true; + TM1650Data.clock_24 = true; + } else { + TM1650Data.show_clock = false; + TM1650Data.clock_24 = false; + } + + TM1650ClearDisplay(); + return true; +} + + +/*********************************************************************************************\ +* refreshes the time if clock is displayed +\*********************************************************************************************/ +void TM1650ShowTime() +{ + TM1650Data.iteration++; + if (20 != TM1650Data.iteration) { + // every 20*50ms = 1000 ms should be enough + return; + } + TM1650Data.iteration = 0; + + char text[TM1650_DIGITS + 2 + 1]; + int i = 0; + uint8_t hour = RtcTime.hour; + uint8_t min = RtcTime.minute; + + if (!TM1650Data.clock_24) + { + if (hour > 12) + hour -= 12; + if (hour == 0) + hour = 12; + } + + text[i++] = '0' + hour / 10; + text[i++] = '0' + hour % 10; + + if (T_XY_CLOCK == Settings->display_options.type) { + text[i++] = '0' + min / 10; + if(TM1650Data.clock_blynk_dots) text[i++] = '.'; // Lower half of the colon, depending on how the LEDs are connected to the TM1650 in the XY-Clock. + text[i++] = '0' + min % 10; + if(TM1650Data.clock_blynk_dots) text[i++] = '.'; // Upper half of the colon. + } + else if (T_303WIFILC01 == Settings->display_options.type) { + if(TM1650Data.clock_blynk_dots) text[i++] = '.'; // Colon for 303WIFILC01 + text[i++] = '0' + min / 10; + text[i++] = '0' + min % 10; + } + + text[i++] = 0; + + if (!TM1650Data.clock_24 && text[0] == '0') + { + text[0] = ' '; + } + + TM1650Data.clock_blynk_dots = !TM1650Data.clock_blynk_dots; + TM1650DisplayText(text); +} + +/*********************************************************************************************\ +* This function is called for all Display functions. +\*********************************************************************************************/ +bool TM1650MainFunc(uint8_t fn) +{ + bool result = false; + if(fn != FUNC_DISPLAY_SCROLLDELAY) TM1650Data.scroll = false; + if (XdrvMailbox.data_len > CMD_MAX_LEN) + { + Response_P(PSTR("{\"Error\":\"Command text too long. Please limit it to %d characters\"}"), CMD_MAX_LEN); + return false; + } + + switch (fn) + { + case FUNC_DISPLAY_CLEAR: + result = CmndTM1650Clear(); + break; + case FUNC_DISPLAY_NUMBER: + result = CmndTM1650Number(true); + break; + case FUNC_DISPLAY_NUMBERNC: + result = CmndTM1650Number(false); + break; + case FUNC_DISPLAY_FLOAT: + result = CmndTM1650Float(true); + break; + case FUNC_DISPLAY_FLOATNC: + result = CmndTM1650Float(false); + break; + case FUNC_DISPLAY_RAW: + result = CmndTM1650Raw(); + break; + case FUNC_DISPLAY_SEVENSEG_TEXT: + result = CmndTM1650Text(true); + break; + case FUNC_DISPLAY_SEVENSEG_TEXTNC: + result = CmndTM1650Text(false); + break; + case FUNC_DISPLAY_LEVEL: + result = CmndTM1650Level(); + break; + case FUNC_DISPLAY_SCROLLTEXT: + result = CmndTM1650ScrollText(); + break; + case FUNC_DISPLAY_SCROLLDELAY: + result = CmndTM1650ScrollDelay(); + break; + case FUNC_DISPLAY_CLOCK: + result = CmndTM1650Clock(); + break; + } + + return result; +} + + +void TM1650Dim(void) +{ + int brightness = GetDisplayDimmer16(); + if (brightness < 2) { + TM1650DisplayOff(); + } + else if (brightness > 14) { + TM1650DisplayOn(); + TM1650SetBrightness(0); // In TM1650, brightness 0 means max brightness (level 8). + TM1650DisplayOn(); + } + else { + // Map 2...14 to 1...7: + TM1650SetBrightness(brightness >> 1); + TM1650DisplayOn(); + } +} + + +/*********************************************************************************************/ + +#ifdef USE_DISPLAY_MODES1TO5 void TM1650Time(void) { - // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1650Time()")); char text[TM1650_DIGITS + 2 + 1]; int i = 0; text[i++] = '0' + RtcTime.hour / 10; text[i++] = '0' + RtcTime.hour % 10; - if (Settings->display_options.type == 2) { - text[i++] = '.'; // Colon for 303WIFILC01 + if (T_XY_CLOCK == Settings->display_options.type) { text[i++] = '0' + RtcTime.minute / 10; + if(TM1650Data.clock_blynk_dots) text[i++] = '.'; // Lower half of the colon, depending on how the LEDs are connected to the TM1650 in the XY-Clock. text[i++] = '0' + RtcTime.minute % 10; + if(TM1650Data.clock_blynk_dots) text[i++] = '.'; // Upper half of the colon. } - else { + else if (T_303WIFILC01 == Settings->display_options.type) { + if(TM1650Data.clock_blynk_dots) text[i++] = '.'; // Colon for 303WIFILC01 text[i++] = '0' + RtcTime.minute / 10; - text[i++] = '.'; // Lower half of the colon, depending on how the LEDs are connected to the TM1650 in the XY-Clock. text[i++] = '0' + RtcTime.minute % 10; - text[i++] = '.'; // Upper half of the colon. } + text[i++] = 0; + + TM1650Data.clock_blynk_dots = !TM1650Data.clock_blynk_dots; TM1650DisplayText(text); -} // void TM1650Time(void) +} + +void TM1650Date(void) +{ + char text[TM1650_DIGITS + 2 + 1]; + int i = 0; + + text[i++] = '0' + RtcTime.day_of_month / 10; + text[i++] = '0' + RtcTime.day_of_month % 10; + + if (T_XY_CLOCK == Settings->display_options.type) { + text[i++] = '0' + RtcTime.month / 10; + text[i++] = '0' + RtcTime.month % 10; + text[i++] = '.'; // Lower half of the colon, depending on how the LEDs are connected to the TM1650 in the XY-Clock. + } + else if (T_303WIFILC01 == Settings->display_options.type ) { + text[i++] = '.'; // Colon for 303WIFILC01 + text[i++] = '0' + RtcTime.month / 10; + text[i++] = '0' + RtcTime.month % 10; + } + + text[i++] = 0; + + TM1650DisplayText(text); +} void TM1650Refresh(void) // Every second { @@ -276,22 +1148,30 @@ void TM1650Refresh(void) // Every second case 1: // Time TM1650Time(); break; - case 2: // Local - case 4: // Mqtt - // TM1650PrintLog(); + case 2: // Date + TM1650Date(); break; - case 3: // Local + case 3: // Time/Date + if (TasmotaGlobal.uptime % Settings->display_refresh) + { + TM1650Time(); + } + else + { + TM1650Date(); + } + break; + case 4: case 5: -// // Mqtt -// if (!TM1650PrintLog()) { -// TM1650Time(); -// } + // not in use break; } } -} // void TM1650Refresh(void) +} +#endif // USE_DISPLAY_MODES1TO5 + /*********************************************************************************************\ * Interface \*********************************************************************************************/ @@ -302,58 +1182,62 @@ bool Xdsp20(uint32_t function) bool result = false; - if (FUNC_DISPLAY_INIT_DRIVER == function) { + if (FUNC_DISPLAY_INIT_DRIVER == function) + { TM1650InitDriver(); } - else if (XDSP_20 == Settings->display_model) { + else if ((TM1650Data.init_driver_done || FUNC_DISPLAY_MODEL == function) + && (XDSP_20 == Settings->display_model)) { switch (function) { - case FUNC_DISPLAY_MODEL: - result = true; - break; + case FUNC_DISPLAY_EVERY_50_MSECOND: + if (disp_power && !Settings->display_mode) + { + if (TM1650Data.scroll) + { + TM1650ScrollText(); + } + if (TM1650Data.show_clock) + { + TM1650ShowTime(); + } + } + break; case FUNC_DISPLAY_INIT: TM1650Init(dsp_init); break; - case FUNC_DISPLAY_POWER: - TM1650DisplayOnOff(); - break; - case FUNC_DISPLAY_DIM: - TM1650Dim(); - break; - case FUNC_DISPLAY_CLEAR: - TM1650Clear(); - break; - case FUNC_DISPLAY_DRAW_STRING: - TM1650DisplayText(dsp_str); - break; #ifdef USE_DISPLAY_MODES1TO5 case FUNC_DISPLAY_EVERY_SECOND: TM1650Refresh(); break; #endif // USE_DISPLAY_MODES1TO5 -// case FUNC_DISPLAY_DRAW_HLINE: -// break; -// case FUNC_DISPLAY_DRAW_VLINE: -// break; -// case FUNC_DISPLAY_DRAW_CIRCLE: -// break; -// case FUNC_DISPLAY_FILL_CIRCLE: -// break; -// case FUNC_DISPLAY_DRAW_RECTANGLE: -// break; -// case FUNC_DISPLAY_FILL_RECTANGLE: -// break; -// case FUNC_DISPLAY_DRAW_FRAME: -// break; -// case FUNC_DISPLAY_TEXT_SIZE: -// break; -// case FUNC_DISPLAY_FONT_SIZE: -// break; -// case FUNC_DISPLAY_ROTATION: -// break; - } // switch (function) - } // else if (display model matches) + case FUNC_DISPLAY_MODEL: + result = true; + break; + case FUNC_DISPLAY_SEVENSEG_TEXT: + case FUNC_DISPLAY_CLEAR: + case FUNC_DISPLAY_NUMBER: + case FUNC_DISPLAY_FLOAT: + case FUNC_DISPLAY_NUMBERNC: + case FUNC_DISPLAY_FLOATNC: + case FUNC_DISPLAY_RAW: + case FUNC_DISPLAY_LEVEL: + case FUNC_DISPLAY_SEVENSEG_TEXTNC: + case FUNC_DISPLAY_SCROLLTEXT: + case FUNC_DISPLAY_SCROLLDELAY: + case FUNC_DISPLAY_CLOCK: + result = TM1650MainFunc(function); + break; + case FUNC_DISPLAY_DIM: + TM1650Dim(); + break; + case FUNC_DISPLAY_POWER: + TM1650DisplayOnOff(); + break; + + } + } return result; -} // bool Xdsp20(uint32_t function) +} #endif // USE_DISPLAY_TM1650 #endif // USE_DISPLAY