From ae7cba2f136e526775a2db891aee9be8eeffa5d6 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:31:21 +0100 Subject: [PATCH 1/2] Add support for TM1640 based IoTTimer by Stefan Oskamp (#21376) --- CHANGELOG.md | 1 + RELEASENOTES.md | 1 + tasmota/my_user_config.h | 14 +- .../tasmota_xdrv_driver/xdrv_13_display.ino | 2 +- .../tasmota_xdsp_display/xdsp_13_tm1640.ino | 779 ++++++++++++++++++ 5 files changed, 790 insertions(+), 7 deletions(-) create mode 100644 tasmota/tasmota_xdsp_display/xdsp_13_tm1640.ino diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cef53e8b..c6a4c85bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## [14.3.0.7] ### Added +- Support for TM1640 based IoTTimer by Stefan Oskamp (#21376) ### Breaking Changed diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ee2aaedf1..f36832a88 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -130,6 +130,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm - Support for HLK-LD2410S 24GHz smart wave motion sensor [#22253](https://github.com/arendst/Tasmota/issues/22253) - Support for US AQI and EPA AQI in PMS5003x sensors [#22294](https://github.com/arendst/Tasmota/issues/22294) - Support for MS5837 pressure and temperature sensor [#22376](https://github.com/arendst/Tasmota/issues/22376) +- Support for TM1640 based IoTTimer by Stefan Oskamp [#21376](https://github.com/arendst/Tasmota/issues/21376) - HLK-LD2410 Engineering mode [#21880](https://github.com/arendst/Tasmota/issues/21880) - Mitsubishi Electric HVAC Operation time for MiElHVAC [#22334](https://github.com/arendst/Tasmota/issues/22334) - Mitsubishi Electric HVAC Outdoor Temperature for MiElHVAC [#22345](https://github.com/arendst/Tasmota/issues/22345) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index d6822db2f..5af1f9aaa 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -775,7 +775,7 @@ #define MTX_ADDRESS7 0x00 // [DisplayAddress7] I2C address of seventh 8x8 matrix module #define MTX_ADDRESS8 0x00 // [DisplayAddress8] I2C address of eigth 8x8 matrix module #define USE_DISPLAY_SEVENSEG // [DisplayModel 11] [I2cDriver47] Enable sevenseg display (I2C 0x70-0x77) (<+11k code) -// #define USE_DISPLAY_SEVENSEG_COMMON_ANODE // Enable support for common anode sevenseg displays +// #define USE_DISPLAY_SEVENSEG_COMMON_ANODE // Enable support for common anode sevenseg displays // Multiple sevenseg displays are logically arranged vertically with MTX_ADDRESS1 at y=0, // MTX_ADDRESS2 at y=1, up to MTX_ADDRESS8 at y=7 // Command: DisplayText [yn]8888 @@ -783,7 +783,7 @@ // Each segment may be address Command: DisplayText [xn]m // where n is 0..4 (4 digits and middle :) and m is decimal for bitmap of which segment to turn on. // Reference: https://cdn-learn.adafruit.com/downloads/pdf/adafruit-led-backpack.pdf - // #define SEVENSEG_ADDRESS1 0x70 // No longer used. Use MTX_ADDRESS1 - MTX_ADDRESS8 instead to specify I2C address of sevenseg displays +// #define SEVENSEG_ADDRESS1 0x70 // No longer used. Use MTX_ADDRESS1 - MTX_ADDRESS8 instead to specify I2C address of sevenseg displays // #define USE_DISPLAY_SH1106 // [DisplayModel 7] [I2cDriver6] Enable SH1106 Oled 128x64 display (I2C addresses 0x3C and 0x3D) // #define USE_DISPLAY_TM1650 // [DisplayModel 20] [I2cDriver74] Enable TM1650 display (I2C addresses 0x24 - 0x27 and 0x34 - 0x37) // #define USE_DT_VARS // Display variables that are exposed in JSON MQTT strings e.g. in TelePeriod messages. @@ -793,10 +793,12 @@ #endif // USE_I2C -// #define USE_DISPLAY // Add I2C/TM1637/MAX7219 Display Support (+2k code) -// #define USE_DISPLAY_TM1637 // [DisplayModel 15] Enable TM1637 Seven Segment Display Module (4-6 digits) -// #define USE_DISPLAY_MAX7219 // [DisplayModel 15] Enable MAX7219 Seven Segment Display Module (8 digits) -// #define USE_DISPLAY_MAX7219_MATRIX // [DisplayModel 19] Enable MAX7219 8x8 Matrix Display +//#define USE_DISPLAY // Add I2C/TM1637/MAX7219 Display Support (+2k code) +// #define USE_DISPLAY_TM1637 // [DisplayModel 15] Enable TM1637 Seven Segment Display Module (4-6 digits) +// #define USE_DISPLAY_MAX7219 // [DisplayModel 15] Enable MAX7219 Seven Segment Display Module (8 digits) +// #define USE_DISPLAY_MAX7219_MATRIX // [DisplayModel 19] Enable MAX7219 8x8 Matrix Display +// #define USE_DISPLAY_TM1640 // [DisplayModel 13] Enable TM1640 module Seven Segment Display Module (stub) +// #define USE_IOTTIMER // Enable TM1640 based IotTimer // -- Universal Display Driver --------------------------------- // #define USE_UNIVERSAL_DISPLAY // New universal display driver for both I2C and SPI diff --git a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino index a7afe6d47..33a445527 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_13_display.ino @@ -2169,7 +2169,7 @@ void CmndDisplayText(void) { void CmndDisplayClear(void) { DisplayClear(); - ResponseCmndChar(XdrvMailbox.data); + ResponseCmndDone(); } void CmndDisplayNumber(void) { diff --git a/tasmota/tasmota_xdsp_display/xdsp_13_tm1640.ino b/tasmota/tasmota_xdsp_display/xdsp_13_tm1640.ino new file mode 100644 index 000000000..dd1d868d8 --- /dev/null +++ b/tasmota/tasmota_xdsp_display/xdsp_13_tm1640.ino @@ -0,0 +1,779 @@ +/* + xdsp_13_tm1640.ino - TM1640B LED display controller support for Tasmota + + Copyright (C) 2024 Stefan Oskamp + + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_DISPLAY +#ifdef USE_DISPLAY_TM1640 +/*********************************************************************************************\ + This driver enables the display of the current time, numbers (both integers and floats) and + basic text on the IoTTimer clock. + + Template {"NAME":"IoTTimer Lo","GPIO":[32,33,0,34,3872,1312,0,0,10944,10912,640,480,608,4768],"FLAG":0,"BASE":18} + + In addition, it is possible to set brightness (seven levels plus off) and clear the display. + + To use, compile Tasmota with USE_DISPLAY, USE_DISPLAY_TM1640 and USE_IOTTIMER, or build the tasmota-display + firmware. + + Either use following template: + Template {"NAME":"IoTTimer Lo","GPIO":[32,33,0,34,3872,1312,0,0,10944,10912,640,480,608,4768],"FLAG":0,"BASE":18} + + or configure manually: + For the IoTTimer clock assign the pins as follows from Tasmota's GUI: + + GPIO12 --> "TM1640 DIN" + GPIO13 --> "TM1640 CLK" + + Once the GPIO configuration is saved and the ESP8266/ESP32 module restarts, set the Display + Model to 13 and Display Mode to 0 using the command "Backlog DisplayModel 13 ; DisplayMode 0;" + Before using it, set the Display Type to 1 (for IOTTIMER) using the "DisplayType 1" command. + + After the ESP8266 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" + + + DisplayFloat num + + Clears and then displays float (with decimal point) command e.g., "DisplayFloat 12.3" + See function description below for more details. + + + DisplayFloatNC num + + Same as DisplayFloatNC + + + DisplayClock 1|2|3|4 + + Displays a clock. + Commands "DisplayClock 1" // 12 hr format + "DisplayClock 2" // 24 hr format + "DisplayClock 3" // 12-hour without seconds + "DisplayClock 4" // 24-hour without seconds + + +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_13 13 + +#define TM1640_CMD_DATA_AUTO 0x40 +#define TM1640_CMD_DATA_FIXED 0x44 +#define TM1640_CMD_DISPLAY 0x80 +#define TM1640_CMD_ADDRESS 0xC0 + +#define TM1640_CLOCK_DELAY 1 // uSec + +#define LEVEL_MIN 0 +#define LEVEL_MAX 100 + +enum tm1640_display_options_types { + TM1640_DEFAULT, + TM1640_IOTTIMER // IOTTIMER WiFi clock +}; + +typedef struct Tm1640_t { + int8_t clock_pin; + int8_t data_pin; + bool show_clock; + bool clock_24; + bool clock_seconds; +} Tm1640_t; + +Tm1640_t* Tm1640 = nullptr; + +/*********************************************************************************************\ + * TM1640 low level functions +\*********************************************************************************************/ + +void TM1640Start (void) { + digitalWrite(Tm1640->data_pin, LOW); + digitalWrite(Tm1640->clock_pin, LOW); + delayMicroseconds(TM1640_CLOCK_DELAY); +} + +void TM1640Stop (void) { + digitalWrite(Tm1640->clock_pin, HIGH); + digitalWrite(Tm1640->data_pin, HIGH); + delayMicroseconds(TM1640_CLOCK_DELAY); +} + +void TM1640Send(uint8_t data) { + for (uint32_t i = 0; i < 8; i++) { // 8 bits + digitalWrite(Tm1640->data_pin, data & 1 ? HIGH : LOW); + delayMicroseconds(TM1640_CLOCK_DELAY); + data >>= 1; + digitalWrite(Tm1640->clock_pin, HIGH); + delayMicroseconds(TM1640_CLOCK_DELAY); + digitalWrite(Tm1640->clock_pin, LOW); + delayMicroseconds(TM1640_CLOCK_DELAY); + } + digitalWrite(Tm1640->data_pin, LOW); + delayMicroseconds(TM1640_CLOCK_DELAY); +} + +void TM1640SendData(uint8_t address, uint8_t data) { + // First, send data command using FIXED addressing: + TM1640Start(); + TM1640Send(TM1640_CMD_DATA_FIXED); + TM1640Stop(); + // Then, send address and one data byte: + TM1640Start(); + TM1640Send(TM1640_CMD_ADDRESS | address); + TM1640Send(data); + TM1640Stop(); +} + +void TM1640SendDataArray(uint8_t address, uint8_t *data, uint8_t count) { + // First, send data command using AUTO addressing: + TM1640Start(); + TM1640Send(TM1640_CMD_DATA_AUTO); + TM1640Stop(); + // Then, send address and all data bytes: + TM1640Start(); + TM1640Send(TM1640_CMD_ADDRESS | address); + while (count-- > 0) { + TM1640Send(*data++); + } + TM1640Stop(); +} + +void TM1640SetBrightness(uint8_t level) { + // level can be 0 to 8. + // 0 means off + // + // Other levels are mapped to TM1640 levels 0 ... 7 + // The mapping to the PWM level is non-linear, according to the data sheet + // level | TM1640 | PWM + // 1 | 0 | 1/16 + // 2 | 1 | 2/16 + // 3 | 2 | 4/16 + // 4 | 3 | 10/16 + // 5 | 4 | 11/16 + // 6 | 5 | 12/16 + // 7 | 6 | 13/16 + // 8 | 7 | 14/16 + uint8_t cmd = TM1640_CMD_DISPLAY | (level > 0 ? 0x8 : 0) | ((level - 1) % 8); + TM1640Start(); + TM1640Send (cmd); + TM1640Stop(); +} + +/*********************************************************************************************\ +* Init function +\*********************************************************************************************/ + +void TM1640Init(void) { + if (PinUsed(GPIO_TM1640CLK) && PinUsed(GPIO_TM1640DIN)) { + Tm1640 = (Tm1640_t*)calloc(sizeof(Tm1640_t), 1); // Need calloc to reset registers to 0/false + if (nullptr == Tm1640) { return; } + + Tm1640->clock_pin = Pin(GPIO_TM1640CLK); + Tm1640->data_pin = Pin(GPIO_TM1640DIN); + + pinMode(Tm1640->data_pin, OUTPUT); + pinMode(Tm1640->clock_pin, OUTPUT); + digitalWrite(Tm1640->clock_pin, HIGH); + digitalWrite(Tm1640->data_pin, HIGH); + + Settings->display_model = XDSP_13; + +#ifdef USE_IOTTIMER + Settings->display_options.type = TM1640_IOTTIMER; + + Settings->display_cols[0] = 9; // 4 (left) + 2 (lower right) + 3 (upper right). + Settings->display_rows = 1; + Settings->display_width = Settings->display_cols[0]; + Settings->display_height = Settings->display_rows; + + Tm1640->clock_24 = true; + Tm1640->clock_seconds = true; + + IoTTimerDim(); + IoTTimerClearDisplay(); +#endif // USE_IOTTIMER + + AddLog(LOG_LEVEL_INFO, PSTR("DSP: TM1640 with %d digits (type %d)"), Settings->display_width, Settings->display_options.type); + } +} + + +/*********************************************************************************************\ +* IotTimer +\*********************************************************************************************/ + +#ifdef USE_IOTTIMER + +/* + (specifically for its use in the IOTTIMER WiFi clock) + + The WiFi LED clock called IOTTIMER has the following characteristics: + - Controlled by an ESP-12F + - Display with four 35 mm (1 12/32 in), two 21 mm (26/32 in), and three 12 mm (~1/2 in), + seven-segment LED digits, plus special symbols (alarm, AM/PM) + - TM1640B LED controller + - R8010 RTC with CR1220 battery + - Temperature sensor M1601 + - Ambient light sensor (analog voltage) + - Buzzer + - Three buttons on the backside + - USB C port for power supply (only) + + The TM1640B chip is a controller for a sixteen-digit seven-segment (plus dot) LED display. + It is also sometimes used to control a 16 x 8 LED matrix. The controller is controlled + through a proprietary two-wire serial interface bearing some similarities with I2C. The + two wires are called CLK and DIN. We use two GPIO pins and one-microsecond sleeps to + implement the required timing. + + The wiring of the LEDs in the IOTTIMER clock has been optimized for a simple routing of the + traces on the display board. The enumeration of the digit segments is non-standard, but + consistent across all digits. The bigger digits have two LEDs per segment, controlled by + separate digit lines of the LED controller. From the software perspective, they appear as + two layers of four digits each. + + The brightness of the LEDs can be controlled in seven steps (plus off). In theory, the + brightness of the segments with two LEDs could be set in fifteen levels (plus off). + To keep things simple and to avoid brightness gradients within segments, both LEDs of a + segment will always be set to the same level. + + The intention of this display driver (together with the drivers for the other components) + is to be able to use the IOTTIMER as an alarm clock that can be fully integrated in your + home automation using Tasmota and rules. + + This driver is not a generic TM1640B driver as use cases of the TM1640B in different + devices will differ significantly. +*/ + + + + + + +#define IOTTIMER_DIGITS 16 +#define IOTTIMER_DOT_BIT 2 + +static unsigned char IoTTimerDisplay[IOTTIMER_DIGITS]; + +// Wiring of the LEDs (per digit): +// +// Seg# Bit Hex +// 07 06 40 +// 08 01 07 00 80 01 +// 02 01 02 +// 06 04 05 03 20 08 +// 05 03 04 02 10 04 +// +// Font as per wiring: +static const byte IoTTimerFont[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 +// SP ! " # $ % & ' ( ) * + , - . / + 0x00, 0xA0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF0, 0x59, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // 0x20 +//0 1 2 3 4 5 6 7 8 9 : ; < = > ? + 0xF9, 0x09, 0x73, 0x5B, 0x8B, 0xDA, 0xFA, 0x49, 0xFB, 0xDB, 0x00, 0x00, 0x00, 0x12, 0x00, 0x63, // 0x30 +// @ A B C D E F G H I J K L M N O + 0x00, 0xEB, 0xBA, 0xF0, 0x3B, 0xF2, 0xE2, 0xFA, 0xAB, 0x09, 0x19, 0x00, 0xB0, 0x00, 0xE9, 0xF9, // 0x40 +// P Q R S T U V W X Y Z [ \ ] ^(°) _ + 0xE3, 0xAB, 0x22, 0xDA, 0xB2, 0xB9, 0x00, 0x00, 0x00, 0x4B, 0x00, 0xF0, 0x00, 0x59, 0xC3, 0x10, // 0x50 +// `=° a b c d e f g h i j k l m n o + 0x01, 0x7B, 0xBA, 0x32, 0x3B, 0xF3, 0xE2, 0xDB, 0xAA, 0x08, 0x19, 0x00, 0x09, 0x00, 0x2A, 0x3A, // 0x60 +// p q r s t u v w x y z { | } ~ DEL + 0xE3, 0xAB, 0x22, 0xDA, 0xB2, 0x38, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x0B, 0x09, 0xA2, 0x00, 0x00 // 0x70 +}; + + +void IoTTimerDim(void) +{ + TM1640SetBrightness (changeUIntScale(GetDisplayDimmer(), 0, 100, 0, 8)); +} + + +void IoTTimerDisplayOn (void) +{ + IoTTimerDim(); +} + + +void IoTTimerDisplayOff (void) +{ + TM1640SetBrightness (0); +} + + +void IoTTimerDisplayOnOff(void) +{ + if (disp_power) { + IoTTimerDisplayOn(); + } + else { + IoTTimerDisplayOff(); + } +} + + +void IoTTimerClearDisplay (void) +{ + for (int i = 0; i < IOTTIMER_DIGITS; i++) { + IoTTimerDisplay[i] = 0; + } + TM1640SendDataArray(0, IoTTimerDisplay, IOTTIMER_DIGITS); +} + + +/*********************************************************************************************\ +* Init function +\*********************************************************************************************/ +void IoTTimerInit(uint8_t mode) +{ + switch(mode) { + case DISPLAY_INIT_MODE: + IoTTimerDim(); + IoTTimerClearDisplay(); + break; + case DISPLAY_INIT_PARTIAL: + case DISPLAY_INIT_FULL: + IoTTimerDim(); + IoTTimerClearDisplay(); + break; + } +} + +void IoTTimerDrawStringAt(uint32_t x, uint32_t y, const char *str, uint32_t color = 0, uint32_t flag = 0); +void IoTTimerDrawStringAt(uint32_t x, uint32_t y, const char *str, uint32_t color, uint32_t flag) { + // displaytext [x] = 0123456789 + // Considers display as 1111223334 where 1111 is large white display, + // 22 is small white display, + // 333 is green display, + // 4 is "1" = pm, "2" = alarm, "3" = both + // Following also works - notice scattered dot(.), colon(:), minus(-) or plus(+) + // displaytext 12:34:56.7.8.9ab - Show all lights including "pm" (=a) and "alarm" (=b) + // displaytext 12:34:56 - Show time + // displaytext 05-11-24 - Show date + // displaytext [x6]12.3 - Show value in green leds + // displaytext [ztS] - Clear display and show current time with seconds + + bool alarm = false; + bool pm = false; + bool dot_left_up = false; + bool dot_left_dn = false; + bool dot_right_dn = false; + bool dot_right_up_left = false; + bool dot_right_up_right = false; + bool dash_left = false; + bool dash_right = false; + + char chr; + uint32_t idx = x; + const char *pos = str; + while (*pos) { + chr = *pos & 0x7F; // We only support 0 to 127 + switch (idx) { + case 0: + IoTTimerDisplay[12] = IoTTimerDisplay[13] = IoTTimerFont[chr]; // col 0 + break; + case 1: + IoTTimerDisplay[14] = IoTTimerDisplay[15] = IoTTimerFont[chr]; // col 1 + break; + case 2: + if (('.' == chr) || (':' == chr) || ('-' == chr) || ('+' == chr)) { + if ('.' == chr) { + dot_left_dn = true; + } + else if (':' == chr) { + dot_left_up = true; + dot_left_dn = true; + } + else if ('-' == chr) { + dash_left = true; + dash_right = true; + } + else if ('+' == chr) { + dot_left_up = true; + dot_left_dn = true; + dash_left = true; + dash_right = true; + } + idx--; + } else { + IoTTimerDisplay[4] = IoTTimerDisplay[5] = IoTTimerFont[chr]; // col 2 + } + break; + case 3: + IoTTimerDisplay[11] = IoTTimerDisplay[1] = IoTTimerFont[chr]; // col 3 + break; + case 4: + if (('.' == chr) || (':' == chr) || ('-' == chr)) { + idx--; // Skip + } else { + IoTTimerDisplay[6] = IoTTimerFont[chr]; // col 4 + } + break; + case 5: + IoTTimerDisplay[7] = IoTTimerFont[chr]; // col 5 + break; + case 6: + if ('.' == chr) { + dot_right_dn = true; + idx--; + } else { + // Upper right (green) + IoTTimerDisplay[9] = IoTTimerFont[chr]; // col 6 + } + break; + case 7: + if ('.' == chr) { + dot_right_up_left = true; + idx--; + } else { + IoTTimerDisplay[10] = IoTTimerFont[chr]; // col 7 + } + break; + case 8: + if ('.' == chr) { + dot_right_up_right = true; + idx--; + } else { + IoTTimerDisplay[8] = IoTTimerFont[chr]; // col 8 + } + break; + case 9: // col 9 + if (chr & 0x01) { // 1, A, a + pm = true; + } + if (chr & 0x02) { // 2, B, b + alarm = true; + } + break; + } + idx++; + pos++; + } + + // Dots and dash + if (alarm) { + IoTTimerDisplay[12] |= 1 << IOTTIMER_DOT_BIT; // Alarm symbol + } + if (pm) { + IoTTimerDisplay[13] |= 1 << IOTTIMER_DOT_BIT; // PM + } + if (dot_left_up) { + IoTTimerDisplay[14] |= 1 << IOTTIMER_DOT_BIT; // Upper dot + } + if (dot_left_dn) { + IoTTimerDisplay[4] |= 1 << IOTTIMER_DOT_BIT; // Lower dot + } + if (dot_right_dn) { + IoTTimerDisplay[7] |= 1 << IOTTIMER_DOT_BIT; // Blue dot + } + if (dot_right_up_left) { + IoTTimerDisplay[10] |= 1 << IOTTIMER_DOT_BIT; // Green dot left + } + if (dot_right_up_right) { + IoTTimerDisplay[8] |= 1 << IOTTIMER_DOT_BIT; // Green dot right + } + if (dash_left) { + IoTTimerDisplay[5] |= 1 << IOTTIMER_DOT_BIT; + } + if (dash_right) { + IoTTimerDisplay[15] |= 1 << IOTTIMER_DOT_BIT; + } + + TM1640SendDataArray(0, IoTTimerDisplay, IOTTIMER_DIGITS); +} + + +/*********************************************************************************************\ +* Displays floating point number in the upper right sub-display of the IOTTIMER. +* Format is always "[n]n[.]n" (negative number is "-n[.]n") +\*********************************************************************************************/ +void IoTTimerShowFloat(float f) { +/* + char buffer[16]; + ext_snprintf_P(buffer, sizeof(buffer),PSTR("%1_f"), &f); + IoTTimerDrawStringAt(6, 0, buffer); +*/ + bool negative = false; + float threshold = 99.95; + if (f < 0.0) { + f = -f; + negative = true; + threshold = 9.95; + } + uint8_t precision = 0; + if (f < threshold) { + f *= 10.0; + precision++; + } + uint32_t n = (uint32_t) (f + 0.5); + char buffer[5] = { 0 }; + if (negative) { + if (n > 99) { + n = 99; + } + buffer[0] = '-'; + } else { + if (n > 999) { + n = 999; + } + if (n / 100 != 0) { + buffer[0] = '0' + n / 100; + } + } + buffer[1] = '0' + n % 100 / 10; + uint32_t idx = 2; + if (precision == 1) { + buffer[2] = '.'; + idx++; + } + buffer[idx] = '0' + n % 10; + IoTTimerDrawStringAt(6, 0, buffer); +} + + +/*********************************************************************************************\ +* Update the temperature in the upper right corner. +\*********************************************************************************************/ +void IoTTimerUpdateTemperature(void) { + IoTTimerShowFloat(ConvertTempToFahrenheit(TasmotaGlobal.temperature_celsius)); +} + + +/*********************************************************************************************\ +* Adjust the brightness based on the photo diode voltage. +\*********************************************************************************************/ +void IoTTimerAdjustBrightness(void) { + // Max ADC value is 3400 [lx], but that is only reached in direct sun light. + // 20 is already quite bright. + // Illuminance value of 0 should map to level 1 (level 0 is off) + static float filteredLevel = 1.0; + float level = (float) AdcGetLux(0) / (20.0 / 7.0) + 1.0; + if (level > 8.0) level = 8.0; + if (level < 1.0) level = 1.0; + filteredLevel = 0.9 * filteredLevel + 0.1 * (float) level; + + TM1640SetBrightness ((int) (filteredLevel + 0.5)); +} + + +#ifdef USE_DISPLAY_MODES1TO5 + +/*********************************************************************************************\ +* Show the current time +\*********************************************************************************************/ +void IoTTimerShowTime(void) { + uint8_t hour = RtcTime.hour; + uint8_t min = RtcTime.minute; + uint8_t sec = RtcTime.second; + uint8_t symbol = 0; + + if (!Tm1640->clock_24) { + if (hour > 12) { + hour -= 12; + } + if (hour == 0) { + hour = 12; + } + symbol |= 1; + } + + char buffer[16]; + snprintf_P(buffer, sizeof(buffer), PSTR("%2d:%02d"), hour, min); + if (Tm1640->clock_seconds) { + snprintf_P(buffer, sizeof(buffer), PSTR("%s:%02d"), buffer, sec); + } + IoTTimerDrawStringAt(0, 0, buffer); + + if (Settings->timer[0].arm) { + symbol |= 2; + } + + snprintf_P(buffer, sizeof(buffer), PSTR("%d"), symbol); + IoTTimerDrawStringAt(9, 0, buffer); +} + + +/*********************************************************************************************\ +* Show the current date +\*********************************************************************************************/ +void IoTTimerShowDate(void) +{ + uint8_t left = RtcTime.day_of_month; + uint8_t middle = RtcTime.month; + uint8_t right = RtcTime.year % 100; + + if (!Tm1640->clock_24) { + // Use U.S. date format. + left = RtcTime.month; + middle = RtcTime.day_of_month; + } + + char buffer[16]; + snprintf_P(buffer, sizeof(buffer), PSTR("%02d-%02d-%02d"), left, middle, right); + IoTTimerDrawStringAt(0, 0, buffer); +} + + +void IoTTimerRefresh(void) { // Every second + // Update temperature display content: + IoTTimerUpdateTemperature(); + + // Auto-adjust brightness: + IoTTimerAdjustBrightness(); + + if (Settings->display_mode) { // Mode 0 is User text + switch (Settings->display_mode) { + case 1: // Time + IoTTimerShowTime(); + break; + case 2: // Date + IoTTimerShowDate(); + break; + case 3: // Time/Date + if (TasmotaGlobal.uptime % Settings->display_refresh) + { + IoTTimerShowTime(); + } + else + { + IoTTimerShowDate(); + } + break; + case 4: + case 5: + // not in use + break; + } + } +} + +#endif // USE_DISPLAY_MODES1TO5 + + +/*********************************************************************************************\ +* Displays a clock. +* Command: DisplayClock 1 // 12-hour format +* DisplayClock 2 // 24-hour format +* DisplayClock 3 // 12-hour without seconds +* DisplayClock 4 // 24-hour without seconds +\*********************************************************************************************/ +void CmndIoTTimerClock(void) { + uint16_t val = XdrvMailbox.payload; + + if (ArgC() == 0) + val = 0; + + if ((val < 1) || (val > 4)) + return; + + if (val == 1) { + Tm1640->show_clock = true; + Tm1640->clock_24 = false; + Tm1640->clock_seconds = true; + } + else if (val == 2) { + Tm1640->show_clock = true; + Tm1640->clock_24 = true; + Tm1640->clock_seconds = true; + } + else if (val == 3) { + Tm1640->show_clock = true; + Tm1640->clock_24 = false; + Tm1640->clock_seconds = false; + } + else if (val == 4) { + Tm1640->show_clock = true; + Tm1640->clock_24 = true; + Tm1640->clock_seconds = false; + } else { + Tm1640->show_clock = false; + Tm1640->clock_24 = false; + } + + IoTTimerClearDisplay(); +} + + +#endif // USE_IOTTIMER + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdsp13(uint32_t function) { + bool result = false; + + if (FUNC_DISPLAY_INIT_DRIVER == function) { + TM1640Init(); + } + else if (Tm1640 && (XDSP_13 == Settings->display_model)) { + +#ifdef USE_IOTTIMER + + switch (function) { + case FUNC_DISPLAY_INIT: + IoTTimerInit(dsp_init); + break; +#ifdef USE_DISPLAY_MODES1TO5 + case FUNC_DISPLAY_EVERY_SECOND: + IoTTimerRefresh(); + break; +#endif // USE_DISPLAY_MODES1TO5 + case FUNC_DISPLAY_MODEL: + result = true; + break; + case FUNC_DISPLAY_CLOCK: + CmndIoTTimerClock(); + break; + case FUNC_DISPLAY_CLEAR: + IoTTimerClearDisplay(); + break; + case FUNC_DISPLAY_NUMBER: + case FUNC_DISPLAY_NUMBERNC: + case FUNC_DISPLAY_FLOAT: + case FUNC_DISPLAY_FLOATNC: + IoTTimerShowFloat(CharToFloat(XdrvMailbox.data)); + break; + case FUNC_DISPLAY_DIM: + IoTTimerDim(); + break; + case FUNC_DISPLAY_POWER: + IoTTimerDisplayOnOff(); + break; + case FUNC_DISPLAY_DRAW_STRING: + IoTTimerDrawStringAt(dsp_x, dsp_y, dsp_str, dsp_color, dsp_flag); + break; + } + +#endif // USE_IOTTIMER + + } + return result; +} + +#endif // USE_DISPLAY_TM1640 +#endif // USE_DISPLAY From fe658424b890b9f7f5d60e76915f185e756b6421 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:49:15 +0100 Subject: [PATCH 2/2] Fix date/time rotation without seconds --- tasmota/tasmota_xdsp_display/xdsp_13_tm1640.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tasmota/tasmota_xdsp_display/xdsp_13_tm1640.ino b/tasmota/tasmota_xdsp_display/xdsp_13_tm1640.ino index dd1d868d8..3f8032c22 100644 --- a/tasmota/tasmota_xdsp_display/xdsp_13_tm1640.ino +++ b/tasmota/tasmota_xdsp_display/xdsp_13_tm1640.ino @@ -605,6 +605,8 @@ void IoTTimerShowTime(void) { snprintf_P(buffer, sizeof(buffer), PSTR("%2d:%02d"), hour, min); if (Tm1640->clock_seconds) { snprintf_P(buffer, sizeof(buffer), PSTR("%s:%02d"), buffer, sec); + } else { + snprintf_P(buffer, sizeof(buffer), PSTR("%s "), buffer); // Erase seconds in case toggling between date/time } IoTTimerDrawStringAt(0, 0, buffer);