From 2630c8e0973c5d9efdcd950bb6fba00c87582113 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Wed, 29 Mar 2023 15:58:26 +0200 Subject: [PATCH] Delete xdrv_27_ESP32_shutter.ino --- .../xdrv_27_ESP32_shutter.ino | 2052 ----------------- 1 file changed, 2052 deletions(-) delete mode 100644 tasmota/tasmota_xdrv_driver/xdrv_27_ESP32_shutter.ino diff --git a/tasmota/tasmota_xdrv_driver/xdrv_27_ESP32_shutter.ino b/tasmota/tasmota_xdrv_driver/xdrv_27_ESP32_shutter.ino deleted file mode 100644 index df6ed725b..000000000 --- a/tasmota/tasmota_xdrv_driver/xdrv_27_ESP32_shutter.ino +++ /dev/null @@ -1,2052 +0,0 @@ -/* - xdrv_27_Shutter[i].ino - Shutter/Blind support for Tasmota - - Copyright (C) 2023 Stefan Bode - - 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 . -*/ - -// Start temporarly extra tests for overriding USE_SHUTTER_ESP32 **** -// Remove once tests complete -#if defined(ESP32) && defined(USE_SHUTTER_ESP32) -// End ************************************************************** - -//#ifdef ESP32 -#ifdef USE_SHUTTER -/*********************************************************************************************\ - * Shutter or Blind support using two consecutive relays - * Shutters for ESP32 with max eight shutters using more RAM and Settings from filesystem -\*********************************************************************************************/ - -#define XDRV_27 27 -#ifndef SHUTTER_STEPPER - #define SHUTTER_STEPPER -#endif - -#ifndef SHUTTER_RELAY_OPERATION_TIME - #define SHUTTER_RELAY_OPERATION_TIME 100 // wait for direction relay 0.1sec before power up main relay -#endif - -#ifndef MOTOR_STOP_TIME - #define MOTOR_STOP_TIME 500 // wait 0.5 second after stop to do any other action. e.g. move in the opposite direction -#endif - -//#define SHUTTER_UNITTEST - -#define D_SHUTTER "SHUTTER" - -// Allow up to 16 shutters on ESP32 -#undef MAX_SHUTTERS_ESP32 -#define MAX_SHUTTERS_ESP32 16 - -// -const uint32_t SHUTTER_VERSION = 0x01010000; // Latest driver version (See settings deltas below) - -typedef struct { - uint32_t positionmatrix; - uint32_t tiltmatrix; - uint8_t shutter_number; -} tButtonSettings; - -// Global structure containing shutter saved variables -struct SHUTTERSETTINGS { - uint32_t crc32; // To detect file changes - uint32_t version; // To detect driver function changes - uint8_t shutter_accuracy; - uint8_t shutter_mode; - uint16_t shutter_motorstop; - uint16_t open_velocity_max; - int8_t shutter_tilt_config[5][MAX_SHUTTERS_ESP32]; - int8_t shutter_tilt_pos[MAX_SHUTTERS_ESP32]; - uint16_t shutter_opentime[MAX_SHUTTERS_ESP32]; - uint16_t shutter_closetime[MAX_SHUTTERS_ESP32]; - int16_t shuttercoeff[5][MAX_SHUTTERS_ESP32]; - uint8_t shutter_options[MAX_SHUTTERS_ESP32]; - uint8_t shutter_set50percent[MAX_SHUTTERS_ESP32]; - uint8_t shutter_position[MAX_SHUTTERS_ESP32]; - uint8_t shutter_startrelay[MAX_SHUTTERS_ESP32]; - uint8_t shutter_motordelay[MAX_SHUTTERS_ESP32]; - uint16_t shutter_pwmrange[2][MAX_SHUTTERS_ESP32]; - tButtonSettings shutter_button[MAX_SHUTTERS_ESP32*2]; -} ShutterSettings; - -const uint16_t RESOLUTION = 1000; // incresed to 1000 in 8.5 to ramp servos -const uint8_t STEPS_PER_SECOND = 20; // FUNC_EVERY_50_MSECOND -const uint16_t pwm_servo_max = 500; -const uint16_t pwm_servo_min = 90; - -uint8_t calibrate_pos[6] = {0,30,50,70,90,100}; -uint16_t messwerte[5] = {30,50,70,90,100}; - -int32_t velocity_max = 0; -int32_t velocity_change_per_step_max = 0; -int32_t min_runtime_ms = 0; -int32_t current_stop_way = 0; -int32_t next_possible_stop_position = 0; -int32_t current_real_position = 0; -int32_t current_pwm_velocity = 0; - -const uint8_t MAX_MODES = 8; -enum Shutterposition_mode {SHT_UNDEF, SHT_TIME, SHT_TIME_UP_DOWN, SHT_TIME_GARAGE, SHT_COUNTER, SHT_PWM_VALUE, SHT_PWM_TIME,SHT_AUTOCONFIG}; -enum Shutterswitch_mode {SHT_SWITCH, SHT_PULSE,}; -enum ShutterButtonStates { SHT_NOT_PRESSED, SHT_PRESSED_MULTI, SHT_PRESSED_HOLD, SHT_PRESSED_IMMEDIATE, SHT_PRESSED_EXT_HOLD, SHT_PRESSED_MULTI_SIMULTANEOUS, SHT_PRESSED_HOLD_SIMULTANEOUS, SHT_PRESSED_EXT_HOLD_SIMULTANEOUS,}; - -const char kShutterCommands[] PROGMEM = D_PRFX_SHUTTER "|" - D_CMND_SHUTTER_OPEN "|" D_CMND_SHUTTER_CLOSE "|" D_CMND_SHUTTER_TOGGLE "|" D_CMND_SHUTTER_TOGGLEDIR "|" D_CMND_SHUTTER_STOP "|" D_CMND_SHUTTER_POSITION "|" - D_CMND_SHUTTER_OPENTIME "|" D_CMND_SHUTTER_CLOSETIME "|" D_CMND_SHUTTER_RELAY "|" D_CMND_SHUTTER_MODE "|" D_CMND_SHUTTER_PWMRANGE "|" - D_CMND_SHUTTER_SETHALFWAY "|" D_CMND_SHUTTER_SETCLOSE "|" D_CMND_SHUTTER_SETOPEN "|" D_CMND_SHUTTER_INVERT "|" D_CMND_SHUTTER_CLIBRATION "|" - D_CMND_SHUTTER_MOTORDELAY "|" D_CMND_SHUTTER_FREQUENCY "|" D_CMND_SHUTTER_BUTTON "|" D_CMND_SHUTTER_LOCK "|" D_CMND_SHUTTER_ENABLEENDSTOPTIME "|" D_CMND_SHUTTER_INVERTWEBBUTTONS "|" - D_CMND_SHUTTER_STOPOPEN "|" D_CMND_SHUTTER_STOPCLOSE "|" D_CMND_SHUTTER_STOPTOGGLE "|" D_CMND_SHUTTER_STOPTOGGLEDIR "|" D_CMND_SHUTTER_STOPPOSITION "|" D_CMND_SHUTTER_INCDEC "|" - D_CMND_SHUTTER_UNITTEST "|" D_CMND_SHUTTER_TILTCONFIG "|" D_CMND_SHUTTER_SETTILT "|" D_CMND_SHUTTER_TILTINCDEC "|" D_CMND_SHUTTER_MOTORSTOP; - -void (* const ShutterCommand[])(void) PROGMEM = { - &CmndShutterOpen, &CmndShutterClose, &CmndShutterToggle, &CmndShutterToggleDir, &CmndShutterStop, &CmndShutterPosition, - &CmndShutterOpenTime, &CmndShutterCloseTime, &CmndShutterRelay, &CmndShutterMode, &CmndShutterPwmRange, - &CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterSetOpen, &CmndShutterInvert, &CmndShutterCalibration , &CmndShutterMotorDelay, - &CmndShutterFrequency, &CmndShutterButton, &CmndShutterLock, &CmndShutterEnableEndStopTime, &CmndShutterInvertWebButtons, - &CmndShutterStopOpen, &CmndShutterStopClose, &CmndShutterStopToggle, &CmndShutterStopToggleDir, &CmndShutterStopPosition, &CmndShutterIncDec, - &CmndShutterUnitTest,&CmndShutterTiltConfig,&CmndShutterSetTilt,&CmndShutterTiltIncDec,&CmndShutterMotorStop}; - - const char JSON_SHUTTER_POS[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Position\":%d,\"Direction\":%d,\"Target\":%d,\"Tilt\":%d}"; - const char JSON_SHUTTER_BUTTON[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Button%d\":%d}"; - -#include - -Ticker TickerShutter; - -struct SHUTTER { - uint32_t time; // operating time of the shutter in 0.05sec - int32_t open_max; // max value on maximum open calculated - int32_t target_position; // position to go to - int32_t start_position; // position before a movement is started. init at start - int32_t real_position; // value between 0 and Shutter[i].open_max - uint16_t open_time; // duration to open the Shutter[i]. 112 = 11.2sec - uint16_t close_time; // duration to close the Shutter[i]. 112 = 11.2sec - uint16_t close_velocity; // in relation to open velocity. higher value = faster - int8_t direction; // 1 == UP , 0 == stop; -1 == down - int8_t lastdirection; // last direction (1 == UP , -1 == down) - uint8_t switch_mode; // how to switch relays: SHT_SWITCH, SHT_PULSE - int8_t motordelay; // initial motorstarttime in 0.05sec. Also uses for ramp at steppers and servos, negative if motor stops late - int16_t pwm_velocity; // frequency of PWN for stepper motors or PWM duty cycle change for PWM servo - uint16_t pwm_value; // dutyload of PWM 0..1023 on ESP8266 - uint16_t close_velocity_max; // maximum of PWM change during closeing. Defines velocity on opening. Steppers and Servos only - int32_t accelerator; // speed of ramp-up, ramp down of shutters with velocity control. Steppers and Servos only - int8_t tilt_config[5]; // tilt_min, tilt_max, duration, tilt_closed_value, tilt_opened_value - int8_t tilt_real_pos; // -90 to 90 - int8_t tilt_target_pos; // target positon for movements of the tilt - int8_t tilt_start_pos; // saved start position before shutter moves - uint8_t tilt_velocity; // degree rotation per step 0.05sec - int8_t tiltmoving; // 0 operating move, 1 = operating tilt - uint16_t venetian_delay = 0; // Delay in steps before venetian shutter start physical moving. Based on tilt position - uint16_t min_realPositionChange = 0; // minimum change of the position before the shutter operates. different for PWM and time based operations - uint16_t min_TiltChange = 0; // minimum change of the tilt before the shutter operates. different for PWM and time based operations - uint16_t last_reported_time = 0; // get information on skipped 50ms loop() slots - uint32_t last_stop_time = 0; // record the last time the relay was switched off -} Shutter[MAX_SHUTTERS_ESP32]; - -struct SHUTTERGLOBAL { - power_t RelayShutterMask = 0; // bit mask with 11 at the position of relays that belong to at least ONE shutter - power_t RelayOldMask = 0; // bitmatrix that contain the last known state of all relays. Required to detemine the manual changed relay. - power_t RelayCurrentMask = 0; // bitmatrix that contain the current state of all relays - uint8_t position_mode = 0; // how to calculate actual position: SHT_TIME, SHT_COUNTER, SHT_PWM_VALUE, SHT_PWM_TIME - uint8_t skip_relay_change; // avoid overrun at endstops - uint8_t start_reported = 0; // indicates of the shutter start was reported through MQTT JSON - uint16_t open_velocity_max = RESOLUTION; // maximum of PWM change during opening. Defines velocity on opening. Steppers and Servos only -} ShutterGlobal; - -#define SHT_DIV_ROUND(__A, __B) (((__A) + (__B)/2) / (__B)) - -/*********************************************************************************************\ - * Driver Settings load and save -\*********************************************************************************************/ - -uint32_t ShutterSettingsCrc32(void) { - // Use Tasmota CRC calculation function - return GetCfgCrc32((uint8_t*)&ShutterSettings +4, sizeof(ShutterSettings) -4); // Skip crc32 -} - -void ShutterSettingsDefault(void) { - // Init default values in case file is not found - - AddLog(LOG_LEVEL_INFO, PSTR("Shutter: " D_USE_DEFAULTS)); - - memset(&ShutterSettings, 0x00, sizeof(ShutterSettings)); - ShutterSettings.version = SHUTTER_VERSION; - // Init any other parameter in struct ShutterSettings - ShutterSettings.open_velocity_max = ShutterGlobal.open_velocity_max; - ShutterSettings.shutter_accuracy = Settings->shutter_accuracy; - ShutterSettings.shutter_mode = Settings->shutter_mode; - ShutterSettings.shutter_motorstop = Settings->shutter_motorstop; - for (uint32_t i = 0; i < MAX_SHUTTERS; i++) { - // copy values from settings - for (uint32_t j = 0; j < 5; j++) { - if (j<2) ShutterSettings.shutter_pwmrange[j][i] = Settings->shutter_pwmrange[j][i]; - ShutterSettings.shutter_tilt_config[j][i] = Settings->shutter_tilt_config[j][i]; - ShutterSettings.shuttercoeff[j][i] = Settings->shuttercoeff[j][i]; - } - ShutterSettings.shutter_tilt_pos[i] = Settings->shutter_tilt_pos[i]; - ShutterSettings.shutter_opentime[i] = Settings->shutter_opentime[i]; - ShutterSettings.shutter_closetime[i] = Settings->shutter_closetime[i]; - ShutterSettings.shutter_options[i] = Settings->shutter_options[i]; - ShutterSettings.shutter_set50percent[i] = Settings->shutter_set50percent[i]; - ShutterSettings.shutter_position[i] = Settings->shutter_position[i]; - ShutterSettings.shutter_startrelay[i] = Settings->shutter_startrelay[i]; - ShutterSettings.shutter_motordelay[i] = Settings->shutter_motordelay[i]; - } - for (uint32_t i = 0; i < MAX_SHUTTER_KEYS; i++) { - ShutterSettings.shutter_button[i].positionmatrix = Settings->shutter_button[i]; - ShutterSettings.shutter_button[i].shutter_number = (uint8_t)(Settings->shutter_button[i] & 0x03); - } - for (uint32_t i = MAX_SHUTTERS; i < MAX_SHUTTERS_ESP32; i++) { - ShutterSettings.shutter_set50percent[i] = 50; - ShutterSettings.shutter_opentime[i] = 100; - ShutterSettings.shutter_closetime[i] = 100; - ShutterSettings.shutter_pwmrange[0][i] = pwm_servo_min; - ShutterSettings.shutter_pwmrange[1][i] = pwm_servo_max; - } -} - -void ShutterSettingsDelta(void) { - // Fix possible setting deltas - - if (ShutterSettings.version != SHUTTER_VERSION) { // Fix version dependent changes -/* - if (ShutterSettings.version < 0x01010100) { - AddLog(LOG_LEVEL_INFO, PSTR("DRV: Update oldest version restore")); - - } - if (ShutterSettings.version < 0x01010101) { - AddLog(LOG_LEVEL_INFO, PSTR("DRV: Update old version restore")); - - } -*/ - // Set current version and save settings - ShutterSettings.version = SHUTTER_VERSION; - ShutterSettingsSave(); - } -} - -void ShutterSettingsLoad(void) { - // Called from FUNC_PRE_INIT once at restart - - // Init default values in case file is not found - ShutterSettingsDefault(); - - // Try to load file /.drvset122 - char filename[20]; - // Use for sensors: -// snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_SENSOR), XSNS_27); - // Use for drivers: - snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_27); - - AddLog(LOG_LEVEL_INFO, PSTR("SHUTTER: About to load settings from file %s"), filename); - -#ifdef USE_UFILESYS - if (TfsLoadFile(filename, (uint8_t*)&ShutterSettings, sizeof(ShutterSettings))) { - // Fix possible setting deltas - ShutterSettingsDelta(); - } else { - // File system not ready: No flash space reserved for file system - AddLog(LOG_LEVEL_INFO, PSTR("DRV: ERROR File system not ready or file not found")); - } -#else - AddLog(LOG_LEVEL_INFO, PSTR("DRV: ERROR File system not enabled")); -#endif // USE_UFILESYS - - ShutterSettings.crc32 = ShutterSettingsCrc32(); -} - -void ShutterSettingsSave(void) { - // Called from FUNC_SAVE_SETTINGS every SaveData second and at restart - - if (ShutterSettingsCrc32() != ShutterSettings.crc32) { - // Try to save file /.drvset122 - ShutterSettings.crc32 = ShutterSettingsCrc32(); - - char filename[20]; - // Use for sensors: -// snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_SENSOR), XSNS_27); - // Use for drivers: - snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_27); - - AddLog(LOG_LEVEL_INFO, PSTR("SHUTTER: About to save settings to file %s"), filename); - -#ifdef USE_UFILESYS - if (!TfsSaveFile(filename, (const uint8_t*)&ShutterSettings, sizeof(ShutterSettings))) { - // File system not ready: No flash space reserved for file system - AddLog(LOG_LEVEL_INFO, PSTR("DRV: ERROR File system not ready or unable to save file")); - } -#else - AddLog(LOG_LEVEL_INFO, PSTR("SHUTTER: ERROR File system not enabled")); -#endif // USE_UFILESYS - } -} - -bool ShutterStatus(void) { - if (Settings->flag3.shutter_mode) { // SetOption80 - (Shutter) Enable shutter support (1) - Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS13_SHUTTER "\":{")); - for (uint32_t i = 0; i < MAX_SHUTTERS_ESP32; i++) { - if (0 == ShutterSettings.shutter_startrelay[i]) { break; } - if (i > 0) { ResponseAppend_P(PSTR(",")); } - ResponseAppend_P(PSTR("\"" D_STATUS13_SHUTTER "%d\":{\"Relay1\":%d,\"Relay2\":%d,\"Open\":%d,\"Close\":%d," - "\"50perc\":%d,\"Delay\":%d,\"Opt\":\"%s\"," - "\"Calib\":[%d,%d,%d,%d,%d]," - "\"Mode\":\"%d\"}"), - i, ShutterSettings.shutter_startrelay[i], ShutterSettings.shutter_startrelay[i] +1, ShutterSettings.shutter_opentime[i], ShutterSettings.shutter_closetime[i], - ShutterSettings.shutter_set50percent[i], ShutterSettings.shutter_motordelay[i], GetBinary8(Settings->shutter_options[i], 4).c_str(), - ShutterSettings.shuttercoeff[0][i], ShutterSettings.shuttercoeff[1][i], ShutterSettings.shuttercoeff[2][i], ShutterSettings.shuttercoeff[3][i], ShutterSettings.shuttercoeff[4][i], - ShutterSettings.shutter_mode); - } - ResponseJsonEndEnd(); - return true; - } - return false; -} - -void ShutterLogPos(uint32_t i) -{ - char stemp2[10]; - dtostrfd((float)Shutter[i].time / STEPS_PER_SECOND, 2, stemp2); - AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Shtr%d Real %d, Start %d, Stop %d, Dir %d, Delay %d, Rtc %s [s], Freq %d, PWM %d, Tilt %d"), - i+1, Shutter[i].real_position, Shutter[i].start_position, Shutter[i].target_position, Shutter[i].direction, Shutter[i].motordelay, stemp2, - Shutter[i].pwm_velocity, Shutter[i].pwm_value,Shutter[i].tilt_real_pos); -} - -void ExecuteCommandPowerShutter(uint32_t device, uint32_t state, uint32_t source) -{ - // first implementation for virtual relays. Avoid switching relay numbers that do not exist. - if (device <= TasmotaGlobal.devices_present) ExecuteCommandPower(device,state,source); -} - -void ShutterUpdateVelocity(uint8_t i) -{ - // No Logging allowed. Part of RTC Timer - // will be calles through RTC every 50ms. - // do not allow accellerator to stop movement - Shutter[i].pwm_velocity = tmax(velocity_change_per_step_max, Shutter[i].pwm_velocity+Shutter[i].accelerator); - Shutter[i].pwm_velocity = tmin(Shutter[i].direction==1 ? ShutterGlobal.open_velocity_max : Shutter[i].close_velocity_max,Shutter[i].pwm_velocity); - // respect hard coded SDK limit of PWM_MIN on PWM frequency. - if (ShutterGlobal.position_mode == SHT_COUNTER) { - Shutter[i].pwm_velocity = tmax(PWM_MIN,Shutter[i].pwm_velocity); - } -} - -void ShutterRtc50mS(void) -{ -#ifdef ESP32 - bool pwm_apply = false; // ESP32 only, do we need to apply PWM changes -#endif - // No Logging allowed. RTC Timer - for (uint8_t i = 0; i < TasmotaGlobal.shutters_present; i++) { - if (Shutter[i].direction) { - // update position data before increasing counter - Shutter[i].real_position = ShutterCalculatePosition(i); - Shutter[i].time++; - ShutterCalculateAccelerator(i); - switch (ShutterGlobal.position_mode) { - case SHT_PWM_VALUE: - ShutterUpdateVelocity(i); - Shutter[i].real_position += Shutter[i].direction > 0 ? Shutter[i].pwm_velocity : (Shutter[i].direction < 0 ? -Shutter[i].pwm_velocity : 0); - Shutter[i].pwm_value = SHT_DIV_ROUND((ShutterSettings.shutter_pwmrange[1][i]-ShutterSettings.shutter_pwmrange[0][i]) * Shutter[i].real_position , Shutter[i].open_max)+ShutterSettings.shutter_pwmrange[0][i]; - analogWrite(Pin(GPIO_PWM1, i), Shutter[i].pwm_value); - break; - - case SHT_COUNTER: - if (Shutter[i].accelerator) { - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Accelerator i=%d -> %d"),i, Shutter[i].accelerator); - ShutterUpdateVelocity(i); - digitalWrite(Pin(GPIO_PWM1, i), LOW); - #ifdef ESP8266 - // Convert frequency into clock cycles - uint32_t cc = microsecondsToClockCycles(1000000UL) / Shutter[i].pwm_velocity; - startWaveformClockCycles(Pin(GPIO_PWM1, i), cc/2, cc/2, 0, -1, 0, false); - #endif // ESP8266 - #ifdef ESP32 - analogWriteFreq(Shutter[i].pwm_velocity,Pin(GPIO_PWM1, i)); - TasmotaGlobal.pwm_value[i] = 512; - pwm_apply = true; - #endif // ESP32 - } - break; - } - } // if (Shutter[i].direction) - } -#ifdef ESP32 - if (pwm_apply) { PwmApplyGPIO(false); } -#endif -} - -int32_t ShutterPercentToRealPosition(int16_t percent, uint32_t index) -{ - if (ShutterSettings.shutter_set50percent[index] != 50) { - return (percent <= 5) ? ShutterSettings.shuttercoeff[2][index] * percent*10 : (ShutterSettings.shuttercoeff[1][index] * percent + (ShutterSettings.shuttercoeff[0][index]*10))*10; - } else { - int64_t realpos; - // check against DIV 0 - for (uint32_t j = 0; j < 5; j++) { - if (0 == ShutterSettings.shuttercoeff[j][index]) { - AddLog(LOG_LEVEL_ERROR, PSTR("SHT: RESET/INIT CALIBRATION MATRIX DIV 0")); - for (uint32_t k = 0; k < 5; k++) { - ShutterSettings.shuttercoeff[k][index] = SHT_DIV_ROUND(calibrate_pos[k+1] * 1000, calibrate_pos[5]); - } - } - } - for (uint32_t k = 0; k < 5; k++) { - if ((percent * 10) >= ShutterSettings.shuttercoeff[k][index]) { - realpos = SHT_DIV_ROUND(Shutter[index].open_max * calibrate_pos[k+1], 100); - //AddLog(LOG_LEVEL_ERROR, PSTR("SHT: Realposition TEMP1: %d, %d %%, coeff %d"), realpos, percent, ShutterSettings.shuttercoeff[k][index]); - } else { - //AddLog(LOG_LEVEL_ERROR, PSTR("SHT: Shutter[%d].open_max: %d"),index, Shutter[index].open_max); - if (0 == k) { - realpos = SHT_DIV_ROUND((int64_t)percent * Shutter[index].open_max * calibrate_pos[k+1], ShutterSettings.shuttercoeff[k][index]*10 ); - //AddLog(LOG_LEVEL_ERROR, PSTR("SHT: Realposition TEMP3: %d, %d %%, coeff %d"), realpos, percent, ShutterSettings.shuttercoeff[k][index]); - } else { - //uint32_t addon = ( percent*10 - ShutterSettings.shuttercoeff[k-1][index] ) * Shutter[index].open_max * (calibrate_pos[k+1] - calibrate_pos[k]) / (ShutterSettings.shuttercoeff[k][index] -ShutterSettings.shuttercoeff[k-1][index]) / 100; - //AddLog(LOG_LEVEL_ERROR, PSTR("SHT: Realposition TEMP2: %d, %d %%, coeff %d"), addon, (calibrate_pos[k+1] - calibrate_pos[k]), (ShutterSettings.shuttercoeff[k][index] -ShutterSettings.shuttercoeff[k-1][index])); - realpos += SHT_DIV_ROUND(((int64_t)percent*10 - ShutterSettings.shuttercoeff[k-1][index] ) * Shutter[index].open_max * (calibrate_pos[k+1] - calibrate_pos[k]), (ShutterSettings.shuttercoeff[k][index] - ShutterSettings.shuttercoeff[k-1][index])*100); - } - break; - } - } - return realpos < 0 ? 0 : realpos; - } -} - -uint8_t ShutterRealToPercentPosition(int32_t realpos, uint32_t index) -{ - if (ShutterSettings.shutter_set50percent[index] != 50) { - return (ShutterSettings.shuttercoeff[2][index] * 5 > realpos/10) ? SHT_DIV_ROUND(realpos/10, ShutterSettings.shuttercoeff[2][index]) : SHT_DIV_ROUND(realpos/10-ShutterSettings.shuttercoeff[0][index]*10, ShutterSettings.shuttercoeff[1][index]); - } else { - int64_t realpercent; - for (uint32_t j = 0; j < 5; j++) { - if (realpos >= Shutter[index].open_max * calibrate_pos[j+1] / 100) { - realpercent = SHT_DIV_ROUND(ShutterSettings.shuttercoeff[j][index], 10); - //AddLog(LOG_LEVEL_ERROR, PSTR("SHT: Realpercent TEMP1: %d %%, %d, coeff %d"), realpercent, realpos, Shutter[index].open_max * calibrate_pos[j+1] / 100); - } else { - //AddLog(LOG_LEVEL_ERROR, PSTR("SHT: Shutter[%d].open_max: %d"),index, Shutter[index].open_max); - if (0 == j) { - realpercent = SHT_DIV_ROUND(((int64_t)realpos - SHT_DIV_ROUND(Shutter[index].open_max * calibrate_pos[j], 100)) * ShutterSettings.shuttercoeff[j][index], calibrate_pos[j+1]/10*Shutter[index].open_max); - } else { - //uint16_t addon = ( realpos - (Shutter[index].open_max * calibrate_pos[j] / 100) ) * 10 * (ShutterSettings.shuttercoeff[j][index] - ShutterSettings.shuttercoeff[j-1][index]) / (calibrate_pos[j+1] - calibrate_pos[j])/Shutter[index].open_max; - //uint16_t addon = ( realpercent*10 - ShutterSettings.shuttercoeff[j-1][index] ) * Shutter[index].open_max * (calibrate_pos[j+1] - calibrate_pos[j]) / (ShutterSettings.shuttercoeff[j][index] -ShutterSettings.shuttercoeff[j-1][index]) / 100; - //AddLog(LOG_LEVEL_ERROR, PSTR("SHT: Realpercent TEMP2: %d %%, delta %d, %d, coeff %d"), addon,( realpos - (Shutter[index].open_max * calibrate_pos[j] / 100) ) , (calibrate_pos[j+1] - calibrate_pos[j])* Shutter[index].open_max/100, (ShutterSettings.shuttercoeff[j][index] -ShutterSettings.shuttercoeff[j-1][index])); - realpercent += SHT_DIV_ROUND(((int64_t)realpos - SHT_DIV_ROUND(Shutter[index].open_max * calibrate_pos[j], 100)) * (ShutterSettings.shuttercoeff[j][index] - ShutterSettings.shuttercoeff[j-1][index]), (calibrate_pos[j+1] - calibrate_pos[j])/10*Shutter[index].open_max) ; - } - break; - } - } - return realpercent < 0 ? 0 : realpercent; - } -} - -void ShutterInit(void) -{ - TasmotaGlobal.shutters_present = 0; - ShutterGlobal.RelayShutterMask = 0; - //Initialize to get relay that changed - ShutterGlobal.RelayOldMask = TasmotaGlobal.power; - - - // if shutter 4 is unused - if (ShutterSettings.shutter_startrelay[MAX_SHUTTERS_ESP32 -1] == 0) { - ShutterGlobal.open_velocity_max = ShutterSettings.shuttercoeff[4][3] > 0 ? ShutterSettings.shuttercoeff[4][3] : ShutterGlobal.open_velocity_max; - } - for (uint32_t i = 0; i < MAX_SHUTTERS_ESP32; i++) { - // set startrelay to 1 on first init, but only to shutter 1. 90% usecase - if (ShutterSettings.shutter_startrelay[i] && (ShutterSettings.shutter_startrelay[i] <= 32 )) { - bool relay_in_interlock = false; - TasmotaGlobal.shutters_present++; - - // Add the two relays to the mask to knaw they belong to shutters - ShutterGlobal.RelayShutterMask |= 3 << (ShutterSettings.shutter_startrelay[i] -1) ; - - // All shutters must have same mode. Switch OR Pulse. N - switch (Settings->pulse_timer[i]) { - case 0: - Shutter[i].switch_mode = SHT_SWITCH; - break; - default: - Shutter[i].switch_mode = SHT_PULSE; - break; - } - - // Check if the relay is in an INTERLOCK group. required to set the right mode or - // verify that on SHT_TIME INTERLOCK is set - for (uint32_t j = 0; j < MAX_INTERLOCKS * Settings->flag.interlock; j++) { // CMND_INTERLOCK - Enable/disable interlock - //AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Interlock state i=%d %d, flag %d, Shuttermask %d, MaskedIL %d"),i, ShutterSettings.interlock[i], ShutterSettings.flag.interlock,ShutterGlobal.RelayShutterMask, ShutterSettings.interlock[i]&ShutterGlobal.RelayShutterMask); - if (Settings->interlock[j] && (Settings->interlock[j] & ShutterGlobal.RelayShutterMask)) { - //AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Relay in Interlock group")); - relay_in_interlock = true; - } - } - - if (ShutterSettings.shutter_mode == SHT_AUTOCONFIG || ShutterSettings.shutter_mode == SHT_UNDEF) { - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Mode undef.. calculate...")); - ShutterGlobal.position_mode = SHT_TIME; - if (!relay_in_interlock) { - // temporary to maintain old functionality - if (ShutterSettings.shutter_mode == SHT_UNDEF) { - ShutterGlobal.position_mode = SHT_TIME_UP_DOWN; - } - if (PinUsed(GPIO_PWM1, i) && PinUsed(GPIO_CNTR1, i)) { - ShutterGlobal.position_mode = SHT_COUNTER; - } - } - } else { - ShutterGlobal.position_mode = ShutterSettings.shutter_mode; - } - AddLog(LOG_LEVEL_INFO, PSTR("SHT: ShutterMode: %d"), ShutterGlobal.position_mode); - // main function for stepper and servos to control velocity and acceleration. - TickerShutter.attach_ms(50, ShutterRtc50mS ); - - // default the 50 percent should not have any impact without changing it. set to 60 - ShutterSettings.shutter_set50percent[i] = (ShutterSettings.shutter_set50percent[i] > 0) ? ShutterSettings.shutter_set50percent[i] : 50; - - // use 10 sec. as default to allow everybody to play without deep initialize - Shutter[i].open_time = ShutterSettings.shutter_opentime[i] = (ShutterSettings.shutter_opentime[i] > 0) ? ShutterSettings.shutter_opentime[i] : 100; - Shutter[i].close_time = ShutterSettings.shutter_closetime[i] = (ShutterSettings.shutter_closetime[i] > 0) ? ShutterSettings.shutter_closetime[i] : 100; - - // Update Calculation 20 because time interval is 0.05 sec ans time is in 0.1sec - Shutter[i].open_max = STEPS_PER_SECOND * RESOLUTION * Shutter[i].open_time / 10; - Shutter[i].close_velocity = Shutter[i].open_max / Shutter[i].close_time / 2 ; - - // calculate a ramp slope at the first 5 percent to compensate that shutters move with down part later than the upper part - if (ShutterSettings.shutter_set50percent[i] != 50) { - ShutterSettings.shuttercoeff[1][i] = Shutter[i].open_max/10 * (100 - ShutterSettings.shutter_set50percent[i] ) / 5000 ; - ShutterSettings.shuttercoeff[0][i] = Shutter[i].open_max/100 - (ShutterSettings.shuttercoeff[1][i] * 10); - ShutterSettings.shuttercoeff[2][i] = (int32_t)(ShutterSettings.shuttercoeff[0][i]*10 + 5 * ShutterSettings.shuttercoeff[1][i]) / 5; - //AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Shtr%d Shutter[i].open_max %d, 50perc:%d, 0:%d, 1:%d 2:%d"), i, Shutter[i].open_max, ShutterSettings.shutter_set50percent[i], ShutterSettings.shuttercoeff[0][i],ShutterSettings.shuttercoeff[1][i],ShutterSettings.shuttercoeff[2][i]); - } - ShutterGlobal.RelayShutterMask |= 3 << (ShutterSettings.shutter_startrelay[i] -1); - - Shutter[i].real_position = ShutterPercentToRealPosition(ShutterSettings.shutter_position[i], i); - - Shutter[i].start_position = Shutter[i].target_position = Shutter[i].real_position; - Shutter[i].motordelay = ShutterSettings.shutter_motordelay[i]; - Shutter[i].lastdirection = (50 < ShutterSettings.shutter_position[i]) ? 1 : -1; - - // Venetian Blind - // ensure min is smaller than max - ShutterSettings.shutter_tilt_config[2][i] = ShutterSettings.shutter_tilt_config[0][i] >= ShutterSettings.shutter_tilt_config[1][i]?0:ShutterSettings.shutter_tilt_config[2][i]; - //copy config to shutter - for (uint8_t k=0; k<5; k++) { - Shutter[i].tilt_config[k] = ShutterSettings.shutter_tilt_config[k][i]; - } - // wipe open/close position if duration is 0 - if (Shutter[i].tilt_config[2]==0) { - Shutter[i].tilt_config[3] = Shutter[i].tilt_config[4] = 0; - } - Shutter[i].tilt_target_pos = Shutter[i].tilt_real_pos = ShutterSettings.shutter_tilt_pos[i]; - - Shutter[i].tilt_velocity = Shutter[i].tilt_config[2] > 0 ? ((Shutter[i].tilt_config[1]-Shutter[i].tilt_config[0])/Shutter[i].tilt_config[2])+1 : 1; - - Shutter[i].close_velocity_max = ShutterGlobal.open_velocity_max*Shutter[i].open_time / Shutter[i].close_time; - - Shutter[i].min_realPositionChange = 2 * tmax(ShutterGlobal.open_velocity_max, Shutter[i].close_velocity_max); - Shutter[i].min_TiltChange = 2 * Shutter[i].tilt_velocity; - - switch (ShutterGlobal.position_mode) { - case SHT_PWM_VALUE: - ShutterGlobal.open_velocity_max = RESOLUTION; - // Initiate pwm range with defaults if not already set. - ShutterSettings.shutter_pwmrange[0][i] = ShutterSettings.shutter_pwmrange[0][i] > 0 ? ShutterSettings.shutter_pwmrange[0][i] : pwm_servo_min; - ShutterSettings.shutter_pwmrange[1][i] = ShutterSettings.shutter_pwmrange[1][i] > 0 ? ShutterSettings.shutter_pwmrange[1][i] : pwm_servo_max; - Shutter[i].min_realPositionChange = 0; - Shutter[i].min_TiltChange = 0; - break; - case SHT_TIME: - // Test is the relays are in interlock mode. Disable shuttermode if error - if (!relay_in_interlock) { - TasmotaGlobal.shutters_present = 0, - AddLog(LOG_LEVEL_ERROR, PSTR("SHT: ERROR: Shtr%d Relays are not in INTERLOCK. Pls read documentation. Shutter DISABLE. Fix and REBOOT"), i+1); - return; - } - break; - } - AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Shtr%d min realpos_chg: %d, min tilt_chg %d"), i+1, Shutter[i].min_realPositionChange, Shutter[i].min_TiltChange); - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d Openvel %d, Closevel: %d"), i+1, ShutterGlobal.open_velocity_max, Shutter[i].close_velocity_max); - AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Shtr%d Init. Pos %d, Inv %d, Locked %d, Endstop enab %d, webButt inv %d, Motordel: %d"), - i+1, Shutter[i].real_position, - (ShutterSettings.shutter_options[i] & 1) ? 1 : 0, (ShutterSettings.shutter_options[i] & 2) ? 1 : 0, (ShutterSettings.shutter_options[i] & 4) ? 1 : 0, (ShutterSettings.shutter_options[i] & 8) ? 1 : 0, Shutter[i].motordelay); - - } else { - // terminate loop at first INVALID Shutter[i]. - break; - } - ShutterLimitRealAndTargetPositions(i); - ShutterSettings.shutter_accuracy = 1; - ShutterSettings.shutter_mode = ShutterGlobal.position_mode; - // initialize MotorStop time with 500ms if not set - // typical not set start values are 0 and 65535 - if (ShutterSettings.shutter_motorstop > 5000 || ShutterSettings.shutter_motorstop == 0) { - ShutterSettings.shutter_motorstop = 500; - } - } -} - -void ShutterReportPosition(bool always, uint32_t index) -{ - Response_P(PSTR("{")); - uint32_t i = 0; - uint32_t n = TasmotaGlobal.shutters_present; - uint8_t shutter_running = 0; - for (i; i < n; i++) { - if (Shutter[i].direction != 0) { - shutter_running++; - } - } - - // Allow function exit if nothing to report (99.9% use case) - if (!always && !shutter_running) return; - - if( index != MAX_SHUTTERS_ESP32) { - i = index; - n = index+1; - } else { - i = 0; - } - for (i; i < n; i++) { - //AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Shtr%d Real Pos %d"), i+1,Shutter[i].real_position); - - if (Shutter[i].direction != 0) { - ShutterLogPos(i); - shutter_running++; - } - if (i && index == MAX_SHUTTERS_ESP32) { ResponseAppend_P(PSTR(",")); } - uint32_t position = ShutterRealToPercentPosition(Shutter[i].real_position, i); - uint32_t target = ShutterRealToPercentPosition(Shutter[i].target_position, i); - ResponseAppend_P(JSON_SHUTTER_POS, i+1, (ShutterSettings.shutter_options[i] & 1) ? 100-position : position, Shutter[i].direction,(ShutterSettings.shutter_options[i] & 1) ? 100-target : target, Shutter[i].tilt_real_pos ); - } - ResponseJsonEnd(); - if (always || shutter_running) { - MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER)); // RulesProcess() now re-entry protected - } - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: rules_flag.shutter_moving: %d, moved %d"), TasmotaGlobal.rules_flag.shutter_moving, TasmotaGlobal.rules_flag.shutter_moved); -} - -void ShutterLimitRealAndTargetPositions(uint32_t i) -{ - if (Shutter[i].real_position<0) Shutter[i].real_position = 0; - if (Shutter[i].real_position>Shutter[i].open_max) Shutter[i].real_position = Shutter[i].open_max; - if (Shutter[i].target_position<0) Shutter[i].target_position = 0; - if (Shutter[i].target_position>Shutter[i].open_max) Shutter[i].target_position = Shutter[i].open_max; -} - -void ShutterCalculateAccelerator(uint8_t i) -{ - // No Logging allowed. Part of RTC Timer - if (Shutter[i].direction != 0) { - switch (ShutterGlobal.position_mode) { - case SHT_COUNTER: - case SHT_PWM_VALUE: - current_real_position = Shutter[i].real_position; - current_pwm_velocity = Shutter[i].pwm_velocity; - // calculate max velocity allowed in this direction - velocity_max = Shutter[i].direction == 1 ? ShutterGlobal.open_velocity_max : Shutter[i].close_velocity_max; - // calculate max change of velocyty based on the defined motordelay in steps - velocity_change_per_step_max = velocity_max / (Shutter[i].motordelay>0 ? Shutter[i].motordelay : 1); - // minimumtime required from current velocity to stop - min_runtime_ms = current_pwm_velocity * 1000 / STEPS_PER_SECOND / velocity_change_per_step_max; - // decellaration way from current velocity - current_stop_way = min_runtime_ms * STEPS_PER_SECOND * (current_pwm_velocity + velocity_change_per_step_max) * Shutter[i].direction / 2 / ShutterGlobal.open_velocity_max - (Shutter[i].accelerator<0?Shutter[i].direction*1000*current_pwm_velocity/ShutterGlobal.open_velocity_max:0); - next_possible_stop_position = current_real_position + current_stop_way ; - // ensure that the accelerotor kicks in at the first overrun of the target position - if ( Shutter[i].accelerator < 0 || next_possible_stop_position * Shutter[i].direction > Shutter[i].target_position * Shutter[i].direction ) { - // if startet to early because of 0.05sec maximum accuracy and final position is to far away (200) accelerate a bit less - if (next_possible_stop_position * Shutter[i].direction+200 < Shutter[i].target_position * Shutter[i].direction) { - Shutter[i].accelerator = -velocity_change_per_step_max*9/10; - } else { - // in any case increase accelleration if overrun is detected during decelleration - if (next_possible_stop_position * Shutter[i].direction > Shutter[i].target_position * Shutter[i].direction && Shutter[i].accelerator < 0) { - Shutter[i].accelerator = -velocity_change_per_step_max*11/10; - } else { - // as long as the calculated end position is ok stay with proposed decelleration - Shutter[i].accelerator = -velocity_change_per_step_max; - } - } - // detect during the acceleration phase the point final speed is reached - } else if ( Shutter[i].accelerator > 0 && current_pwm_velocity == velocity_max) { - Shutter[i].accelerator = 0; - } - break; - } - } -} - -void ShutterDecellerateForStop(uint8_t i) -{ -#ifdef ESP32 - bool pwm_apply = false; // ESP32 only, do we need to apply PWM changes -#endif - switch (ShutterGlobal.position_mode) { - case SHT_PWM_VALUE: - case SHT_COUNTER: - int16_t missing_steps; - Shutter[i].accelerator = -(ShutterGlobal.open_velocity_max / (Shutter[i].motordelay>4 ? (Shutter[i].motordelay*11)/10 : 4) ); - - while (Shutter[i].pwm_velocity > -2*Shutter[i].accelerator && Shutter[i].pwm_velocity != PWM_MIN) { - delay(50); - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Velocity %ld, Delta %d"), Shutter[i].pwm_velocity, Shutter[i].accelerator ); - // Control will be done in RTC Ticker. - } - if (ShutterGlobal.position_mode == SHT_COUNTER){ - missing_steps = ((Shutter[i].target_position-Shutter[i].start_position)*Shutter[i].direction*ShutterGlobal.open_velocity_max/RESOLUTION/STEPS_PER_SECOND) - RtcSettings.pulse_counter[i]; - //prepare for stop PWM - Shutter[i].accelerator = 0; - Shutter[i].pwm_velocity = 0; - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Remain %d count %d -> target %d, dir %d"), missing_steps, RtcSettings.pulse_counter[i], (uint32_t)(Shutter[i].target_position-Shutter[i].start_position)*Shutter[i].direction*ShutterGlobal.open_velocity_max/RESOLUTION/STEPS_PER_SECOND, Shutter[i].direction); - while (RtcSettings.pulse_counter[i] < (uint32_t)(Shutter[i].target_position-Shutter[i].start_position)*Shutter[i].direction*ShutterGlobal.open_velocity_max/RESOLUTION/STEPS_PER_SECOND && missing_steps > 0) { - } -#ifdef ESP8266 - analogWrite(Pin(GPIO_PWM1, i), 0); // removed with 8.3 because of reset caused by watchog -#endif -#ifdef ESP32 - TasmotaGlobal.pwm_value[i] = 0; - pwm_apply = true; -#endif // ESP32 - Shutter[i].real_position = ShutterCalculatePosition(i); - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Remain steps %d"), missing_steps); - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Real %d, Pulsecount %d, tobe %d, Start %d"), Shutter[i].real_position,RtcSettings.pulse_counter[i], (uint32_t)(Shutter[i].target_position-Shutter[i].start_position)*Shutter[i].direction*ShutterGlobal.open_velocity_max/RESOLUTION/STEPS_PER_SECOND, Shutter[i].start_position); - } - Shutter[i].direction = 0; - Shutter[i].pwm_velocity = 0; - break; - } -#ifdef ESP32 - if (pwm_apply) { PwmApplyGPIO(false); } -#endif -} - -void ShutterPowerOff(uint8_t i) -{ - AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Stop %d Mode %d time %d"), i+1,Shutter[i].switch_mode, Shutter[i].time); // fix log to indicate correct shutter number - ShutterDecellerateForStop(i); - uint8_t cur_relay = ShutterSettings.shutter_startrelay[i] + (Shutter[i].direction == 1 ? 0 : (uint8_t)(ShutterGlobal.position_mode == SHT_TIME)) ; - if (Shutter[i].direction !=0) { - Shutter[i].direction = 0; - } - if (Shutter[i].real_position == Shutter[i].start_position) { - //AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Update target tilt shutter %d from %d to %d"), i+1, Shutter[i].tilt_target_pos , Shutter[i].tilt_real_pos); - Shutter[i].tilt_target_pos = Shutter[i].tilt_real_pos; - } - TasmotaGlobal.rules_flag.shutter_moved = 1; - switch (Shutter[i].switch_mode) { - case SHT_SWITCH: - for (int8_t k=0;k<2;k++) { - if ((1 << (ShutterSettings.shutter_startrelay[i]+k-1)) & TasmotaGlobal.power) { - ExecuteCommandPowerShutter(ShutterSettings.shutter_startrelay[i]+k, 0, SRC_SHUTTER); - } - } - break; - case SHT_PULSE: - // we have a momentary switch here. Needs additional pulse on same relay after the end - if ((SRC_PULSETIMER == TasmotaGlobal.last_source || SRC_SHUTTER == TasmotaGlobal.last_source || SRC_WEBGUI == TasmotaGlobal.last_source)) { - ExecuteCommandPowerShutter(cur_relay, 1, SRC_SHUTTER); - // switch off direction relay to make it power less - if (((1 << (ShutterSettings.shutter_startrelay[i])) & TasmotaGlobal.power) && ShutterSettings.shutter_startrelay[i]+1 != cur_relay) { - ExecuteCommandPowerShutter(ShutterSettings.shutter_startrelay[i]+1, 0, SRC_SHUTTER); - } - } else { - TasmotaGlobal.last_source = SRC_SHUTTER; - } - break; - } - // Store current PWM value to ensure proper position after reboot. - switch (ShutterGlobal.position_mode) { - case SHT_PWM_VALUE: - Shutter[i].pwm_value = SHT_DIV_ROUND((ShutterSettings.shutter_pwmrange[1][i]-ShutterSettings.shutter_pwmrange[0][i]) * Shutter[i].target_position , Shutter[i].open_max)+ShutterSettings.shutter_pwmrange[0][i]; - analogWrite(Pin(GPIO_PWM1, i), Shutter[i].pwm_value); - AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: PWM final %d"),Shutter[i].pwm_value); - char scmnd[20]; - #ifdef SHUTTER_CLEAR_PWM_ONSTOP - // free the PWM servo lock on stop. - analogWrite(Pin(GPIO_PWM1, i), 0); - #endif - break; - } - if (Settings->save_data) { - TasmotaGlobal.save_data_counter = Settings->save_data; - } - //delay(MOTOR_STOP_TIME); - Shutter[i].last_stop_time = millis(); -} - -void ShutterWaitForMotorStop(uint8_t index) -{ - Shutter[index-1].last_stop_time = millis(); - ShutterWaitForMotorStart(index); -} - -void ShutterWaitForMotorStart(uint8_t index) -{ - while (millis() < Shutter[index-1].last_stop_time + ShutterSettings.shutter_motorstop) { - loop(); - } - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Stoptime done")); -} - -void ShutterUpdatePosition(void) -{ - char scommand[CMDSZ]; - char stopic[TOPSZ]; - for (uint32_t i = 0; i < TasmotaGlobal.shutters_present; i++) { - if (Shutter[i].direction != 0) { - if (!ShutterGlobal.start_reported) { - ShutterReportPosition(true, i); - ShutterGlobal.start_reported = 1; - } - int32_t deltatime = Shutter[i].time-Shutter[i].last_reported_time; - Shutter[i].last_reported_time = Shutter[i].time+1; - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d Time %d(%d), cStop %d, cVelo %d, mVelo %d, aVelo %d, mRun %d, aPos %d, aPos2 %d, nStop %d, Trgt %d, mVelo %d, Dir %d, Tilt %d, TrgtTilt: %d, Tiltmove: %d"), - i+1, Shutter[i].time, deltatime, current_stop_way, current_pwm_velocity, velocity_max, Shutter[i].accelerator, min_runtime_ms, current_real_position,Shutter[i].real_position, - next_possible_stop_position, Shutter[i].target_position, velocity_change_per_step_max, Shutter[i].direction,Shutter[i].tilt_real_pos, Shutter[i].tilt_target_pos, - Shutter[i].tiltmoving); - if ( ((Shutter[i].real_position * Shutter[i].direction >= Shutter[i].target_position * Shutter[i].direction && Shutter[i].tiltmoving==0) || - ((int16_t)Shutter[i].tilt_real_pos * Shutter[i].direction * Shutter[i].tilt_config[2] >= (int16_t)Shutter[i].tilt_target_pos * Shutter[i].direction * Shutter[i].tilt_config[2] && Shutter[i].tiltmoving==1)) - || (ShutterGlobal.position_mode == SHT_COUNTER && Shutter[i].accelerator <0 && Shutter[i].pwm_velocity+Shutter[i].accelerator %d, moving: %d"),Shutter[i].tilt_real_pos,Shutter[i].tilt_target_pos,Shutter[i].tiltmoving); - if (abs(Shutter[i].tilt_real_pos - Shutter[i].tilt_target_pos) > Shutter[i].min_TiltChange && Shutter[i].tiltmoving == 0) { - AddLog(LOG_LEVEL_INFO, PSTR("SHT: Tilt not match %d -> %d"),Shutter[i].tilt_real_pos,Shutter[i].tilt_target_pos); - char databuf[1] = ""; - XdrvMailbox.data = databuf; - XdrvMailbox.payload = -99; - XdrvMailbox.index = i+1; - Shutter[i].tiltmoving = 1; - CmndShutterPosition(); - return; - } else { - ShutterSettings.shutter_tilt_pos[i] = Shutter[i].tilt_real_pos; - } - ShutterLogPos(i); - - if (!Settings->flag4.only_json_message) { // SetOption90 - Disable non-json MQTT response - // sending MQTT result to broker - snprintf_P(scommand, sizeof(scommand),PSTR(D_SHUTTER "%d"), i+1); - GetTopic_P(stopic, STAT, TasmotaGlobal.mqtt_topic, scommand); - Response_P("%d", (ShutterSettings.shutter_options[i] & 1) ? 100 - ShutterSettings.shutter_position[i]: ShutterSettings.shutter_position[i]); - MqttPublish(stopic, Settings->flag.mqtt_power_retain); // CMND_POWERRETAIN - } - - ShutterReportPosition(true, i); - TasmotaGlobal.rules_flag.shutter_moved = 1; - } - } - } -} - -bool ShutterState(uint32_t device) -{ - if (device > 4) { return false; } - device--; - device &= 3; - return (Settings->flag3.shutter_mode && // SetOption80 - Enable shutter support - (ShutterGlobal.RelayShutterMask & (1 << (ShutterSettings.shutter_startrelay[device]-1))) ); -} - -void ShutterAllowPreStartProcedure(uint8_t i) { - // Tricky!!! Execute command status 2 while in the 10 sec loop and you'll end up in an exception - // What PreStartProcedure do you want to execute here? - // Anyway, as long var1 != 99 this is skipped (luckily) -#ifdef USE_RULES - uint32_t uptime_Local=0; - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Delay Start? var%d <99>=<%s>, max10s?"),i+1, rules_vars[i]); - uptime_Local = TasmotaGlobal.uptime; - while (uptime_Local+10 > TasmotaGlobal.uptime && (String)rules_vars[i] == "99") { - loop(); - } - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Delay Start. Done")); -#endif // USE_RULES -} - -void ShutterStartInit(uint32_t i, int32_t direction, int32_t target_pos) -{ - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: dir %d, delta1 %d, delta2 %d"),direction, (Shutter[i].open_max - Shutter[i].real_position) / Shutter[i].close_velocity, Shutter[i].real_position / Shutter[i].close_velocity); - if ( ( ( (1 == direction) && ((Shutter[i].open_max - Shutter[i].real_position) <= Shutter[i].min_realPositionChange)) - || ( (-1 == direction) && (Shutter[i].real_position <= Shutter[i].min_realPositionChange)) ) - && abs(Shutter[i].tilt_real_pos-Shutter[i].tilt_target_pos) <= Shutter[i].min_TiltChange) { - ShutterGlobal.skip_relay_change = 1; - } else { - Shutter[i].pwm_velocity = 0; - ShutterWaitForMotorStart(i+1); - switch (ShutterGlobal.position_mode) { -#ifdef SHUTTER_STEPPER - case SHT_COUNTER: -#ifdef ESP8266 - analogWriteFreq(Shutter[i].pwm_velocity); - analogWrite(Pin(GPIO_PWM1, i), 0); -#endif -#ifdef ESP32 - analogWriteFreq(PWM_MIN,Pin(GPIO_PWM1, i)); - TasmotaGlobal.pwm_value[i] = 0; - PwmApplyGPIO(false); -#endif - RtcSettings.pulse_counter[i] = 0; - break; -#endif - } - - Shutter[i].accelerator = ShutterGlobal.open_velocity_max / (Shutter[i].motordelay>0 ? Shutter[i].motordelay : 1); - Shutter[i].target_position = target_pos; - Shutter[i].start_position = Shutter[i].real_position; - TasmotaGlobal.rules_flag.shutter_moving = 1; - ShutterAllowPreStartProcedure(i); - Shutter[i].time = Shutter[i].last_reported_time = 0; - - ShutterGlobal.skip_relay_change = 0; - TasmotaGlobal.rules_flag.shutter_moved = 0; - ShutterGlobal.start_reported = 0; - Shutter[i].tilt_real_pos = tmax(tmin(Shutter[i].tilt_real_pos,Shutter[i].tilt_config[1]),Shutter[i].tilt_config[0]); - Shutter[i].tilt_start_pos = Shutter[i].tilt_real_pos; - if (Shutter[i].tilt_config[1]-Shutter[i].tilt_config[0] != 0) { - Shutter[i].venetian_delay = SHT_DIV_ROUND((direction > 0 ? Shutter[i].tilt_config[1]-Shutter[i].tilt_real_pos : Shutter[i].tilt_real_pos-Shutter[i].tilt_config[0]) * Shutter[i].tilt_config[2], Shutter[i].tilt_config[1]-Shutter[i].tilt_config[0]); - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: real %d, start %d, counter %d,freq_max %d, dir %d, freq %d"),Shutter[i].real_position, Shutter[i].start_position ,RtcSettings.pulse_counter[i],ShutterGlobal.open_velocity_max , direction ,ShutterGlobal.open_velocity_max ); - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: VenetianDelay: %d, Pos: %d, Dir: %d, Delta: %d, Dur: %d, StartP: %d, TgtP: %d"), - Shutter[i].venetian_delay, Shutter[i].tilt_real_pos,direction,(Shutter[i].tilt_config[1]-Shutter[i].tilt_config[0]), Shutter[i].tilt_config[2],Shutter[i].tilt_start_pos,Shutter[i].tilt_target_pos); - } - - // avoid file system writes during move to minimize missing steps - if (Settings->save_data) { - uint32_t move_duration = (direction > 0) ? Shutter[i].open_time : Shutter[i].close_time; - TasmotaGlobal.save_data_counter = Settings->save_data + (move_duration / 10) +1; - } - } - //AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Start shtr%d from %d to %d in dir: %d"), i, Shutter[i].start_position, Shutter[i].target_position, direction); - - Shutter[i].direction = direction; // Last action. This causes RTC to start. -} - -int32_t ShutterCalculatePosition(uint32_t i) -{ - // No Logging allowed. Part of RTC Timer - if (Shutter[i].direction != 0) { - switch (ShutterGlobal.position_mode) { - case SHT_COUNTER: - return ((int64_t)RtcSettings.pulse_counter[i]*Shutter[i].direction*STEPS_PER_SECOND * RESOLUTION / ShutterGlobal.open_velocity_max)+Shutter[i].start_position; - break; - case SHT_TIME: - case SHT_TIME_UP_DOWN: - case SHT_TIME_GARAGE: - if (Shutter[i].tilt_config[2] > 0) { - if (Shutter[i].time <= Shutter[i].venetian_delay) { - Shutter[i].tilt_real_pos = (Shutter[i].tilt_start_pos + ((Shutter[i].direction * (int16_t)Shutter[i].time * (Shutter[i].tilt_config[1]-Shutter[i].tilt_config[0])) / Shutter[i].tilt_config[2])); - } else { - Shutter[i].tilt_real_pos = Shutter[i].direction == 1 ? Shutter[i].tilt_config[1] : Shutter[i].tilt_config[0]; - } - } - return Shutter[i].start_position + ( (Shutter[i].time - tmin(Shutter[i].venetian_delay+Shutter[i].motordelay, Shutter[i].time)) * (Shutter[i].direction > 0 ? RESOLUTION : -Shutter[i].close_velocity)); - break; - case SHT_PWM_TIME: - break; - case SHT_PWM_VALUE: - return Shutter[i].real_position; - break; - default: - break; - } - } else { - return Shutter[i].real_position; - } - return 0; // Never reaches here, Satisfy compiler -} - -void ShutterRelayChanged(void) -{ - - // ShutterGlobal.RelayCurrentMask = binary relay that was recently changed and cause an Action - // powerstate_local = binary powermatrix and relays from shutter: 0..3 - // relays_changed = bool if one of the relays that belong to the shutter changed not by shutter or pulsetimer - char stemp1[10]; - - for (uint32_t i = 0; i < TasmotaGlobal.shutters_present; i++) { - power_t powerstate_local = (TasmotaGlobal.power >> (ShutterSettings.shutter_startrelay[i] -1)) & 3; - // SRC_IGNORE added because INTERLOCK function bite causes this as last source for changing the relay. - //uint8 manual_relays_changed = ((ShutterGlobal.RelayCurrentMask >> (ShutterSettings.shutter_startrelay[i] -1)) & 3) && SRC_IGNORE != TasmotaGlobal.last_source && SRC_SHUTTER != TasmotaGlobal.last_source && SRC_PULSETIMER != TasmotaGlobal.last_source ; - uint8 manual_relays_changed = ((ShutterGlobal.RelayCurrentMask >> (ShutterSettings.shutter_startrelay[i] -1)) & 3) && SRC_SHUTTER != TasmotaGlobal.last_source && SRC_PULSETIMER != TasmotaGlobal.last_source ; - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d, Source %s, Powerstate %ld, RelayMask %d, ManualChange %d"), - // i+1, GetTextIndexed(stemp1, sizeof(stemp1), TasmotaGlobal.last_source, kCommandSource), powerstate_local,ShutterGlobal.RelayCurrentMask,manual_relays_changed); - if (manual_relays_changed) { - //ShutterGlobal.skip_relay_change = true; - ShutterLimitRealAndTargetPositions(i); - switch (Shutter[i].switch_mode ) { - case SHT_PULSE: - if (Shutter[i].direction != 0 && powerstate_local) { - Shutter[i].target_position = Shutter[i].real_position; - powerstate_local = 0; - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d, Switch OFF motor. Target %ld, Source %s, Powerstate %ld, RelayMask %d, ManualChange %d"), - i+1, Shutter[i].target_position, GetTextIndexed(stemp1, sizeof(stemp1), TasmotaGlobal.last_source, kCommandSource), powerstate_local,ShutterGlobal.RelayCurrentMask,manual_relays_changed); - } - break; - default: - TasmotaGlobal.last_source = SRC_SHUTTER; // avoid switch off in the next loop - if (Shutter[i].direction != 0 ) Shutter[i].target_position = Shutter[i].real_position; - } - if (powerstate_local > 0) { - Shutter[i].tiltmoving = 0; - } - switch (ShutterGlobal.position_mode) { - // enum Shutterposition_mode {SHT_TIME, SHT_TIME_UP_DOWN, SHT_TIME_GARAGE, SHT_COUNTER, SHT_PWM_VALUE, SHT_PWM_TIME,}; - case SHT_TIME_UP_DOWN: - case SHT_COUNTER: - case SHT_PWM_VALUE: - case SHT_PWM_TIME: - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: power off manual change")); - ShutterPowerOff(i); - switch (powerstate_local) { - case 1: - ShutterStartInit(i, 1, Shutter[i].open_max); - break; - case 3: - ShutterStartInit(i, -1, 0); - break; - default: - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d Switch OFF motor."),i); - Shutter[i].target_position = Shutter[i].real_position; - } - break; - case SHT_TIME: - switch (powerstate_local) { - case 1: - ShutterStartInit(i, 1, Shutter[i].open_max); - break; - case 2: - ShutterStartInit(i, -1, 0); - break; - default: - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d Switch OFF motor."),i); - Shutter[i].target_position = Shutter[i].real_position; - } - break; - case SHT_TIME_GARAGE: - switch (powerstate_local) { - case 1: - ShutterStartInit(i, Shutter[i].lastdirection*-1 , Shutter[i].lastdirection == 1 ? 0 : Shutter[i].open_max); - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d Garage. NewTarget %d"), i, Shutter[i].target_position); - break; - default: - Shutter[i].target_position = Shutter[i].real_position; - } - - - } // switch (ShutterGlobal.position_mode) - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d, Target %ld, Power: %d, tiltmv: %d"), i+1, Shutter[i].target_position, powerstate_local,Shutter[i].tiltmoving); - } // if (manual_relays_changed) - } // for (uint32_t i = 0; i < TasmotaGlobal.shutters_present; i++) -} - -bool ShutterButtonIsSimultaneousHold(uint32_t button_index, uint32_t shutter_index) -{ - // check for simultaneous shutter button hold - uint32 min_shutterbutton_hold_timer = -1; // -1 == max(uint32) - for (uint32_t i = 0; i < MAX_SHUTTERS_ESP32*2 ; i++) { - if ((button_index != i) && (ShutterSettings.shutter_button[i].positionmatrix & (1<<31)) && (ShutterSettings.shutter_button[i].shutter_number == shutter_index) && (Button.hold_timer[i] < min_shutterbutton_hold_timer)) - min_shutterbutton_hold_timer = Button.hold_timer[i]; - } - return ((-1 != min_shutterbutton_hold_timer) && (min_shutterbutton_hold_timer > (Button.hold_timer[button_index]>>1))); -} - -bool ShutterButtonHandlerMulti(void) -{ - uint8_t button = XdrvMailbox.payload; - uint32_t button_index = XdrvMailbox.index; - uint8_t shutter_index = ShutterSettings.shutter_button[button_index].shutter_number; - uint8_t button_press_counter = Button.press_counter[button_index] ; - - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("MULTI: SHT: Shtr%d, Button %d, hold %d, dir %d, index %d, payload %d, last state %d, press counter %d, window %d"), - shutter_index+1, button_index+1, Button.hold_timer[button_index],Shutter[shutter_index].direction,XdrvMailbox.index,XdrvMailbox.payload, - Button.last_state[button_index], Button.press_counter[button_index], Button.window_timer[button_index]); - - // multipress event handle back to main procedure - if (Button.press_counter[button_index]>4) return false; - - uint8_t pos_press_index = Button.press_counter[button_index]-1; - - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d, Button %d = %d (single=1, double=2, tripple=3, hold=4)"), shutter_index+1, button_index+1, pos_press_index+1); - XdrvMailbox.index = shutter_index +1; - TasmotaGlobal.last_source = SRC_BUTTON; - XdrvMailbox.data_len = 0; - char databuf[1] = ""; - XdrvMailbox.data = databuf; - XdrvMailbox.command = NULL; - uint8_t position = (ShutterSettings.shutter_button[button_index].positionmatrix>>(6*pos_press_index + 2)) & 0x03f; - XdrvMailbox.payload = position = (position-1)<<1; - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d -> %d"), shutter_index+1, position); - if (102 == position) { - XdrvMailbox.payload = XdrvMailbox.index; - CmndShutterToggle(); - } else { - if (position == ShutterRealToPercentPosition(Shutter[XdrvMailbox.index-1].real_position, XdrvMailbox.index-1) ) { - Shutter[XdrvMailbox.index -1].tilt_target_pos = position==0? Shutter[XdrvMailbox.index -1].tilt_config[0]:(position==100?Shutter[XdrvMailbox.index -1].tilt_config[1]:Shutter[XdrvMailbox.index -1].tilt_target_pos); - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d -> Endpoint movement detected at %d. Set Tilt: %d"), shutter_index+1, position, Shutter[XdrvMailbox.index -1].tilt_target_pos); - } - Button.press_counter[button_index] = 0; - CmndShutterPosition(); - } - if (ShutterSettings.shutter_button[button_index].positionmatrix & ((0x01<<26)< 0 || (button != Button.last_state[button_index] || Button.window_timer[button_index] > 0)) { - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d, Button %d, hold %d, dir %d, index %d, payload %d, last state %d, press counter %d, window %d"), - shutter_index+1, button_index+1, Button.hold_timer[button_index],Shutter[shutter_index].direction,XdrvMailbox.index,XdrvMailbox.payload, - Button.last_state[button_index], Button.press_counter[button_index], Button.window_timer[button_index]); - - if (Button.hold_timer[button_index] > 100 ) { - Button.hold_timer[button_index] = 1; // Reset button hold counter to stay below hold trigger - } - } - - // handle on button release: start shutter on shortpress and stop running shutter after longpress. - if (NOT_PRESSED == button - && Shutter[shutter_index].direction != 0 // only act on shutters activly moving - && Button.hold_timer[button_index] > 0) // kick in on first release of botton. do not check for multipress - { - XdrvMailbox.index = shutter_index +1; - XdrvMailbox.payload = -99; // reset any payload to invalid - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("Stop moving shutter")); - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Shtr%d, Button %d, hold %d, dir %d, index %d, payload %d"), shutter_index+1, button_index+1, Button.hold_timer[button_index],Shutter[shutter_index].direction,XdrvMailbox.index,XdrvMailbox.payload); - CmndShutterStop(); - Button.press_counter[button_index] = 0; - return true; - } - - - //long press detected. Start moving shutter into direction - if (PRESSED == button - && Shutter[shutter_index].direction == 0 //shutter in STOP Position - && Button.window_timer[button_index] == 0 //time for waiting for multipress window expired - && Button.press_counter[button_index] > 0) //only execute if at least pressed ONECE. 0==disable - { - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("Start shutter after long press")); - Button.press_counter[button_index] = 4; - ShutterButtonHandlerMulti(); - } - return false; -} - -void ShutterSetPosition(uint32_t device, uint32_t position) -{ - char svalue[32]; // Command and number parameter - snprintf_P(svalue, sizeof(svalue), PSTR(D_PRFX_SHUTTER D_CMND_SHUTTER_POSITION "%d %d"), device, position); - ExecuteCommand(svalue, SRC_SHUTTER); -} - -void ShutterToggle(bool dir) -{ - AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Toggle: %d, i %d, dir %d, lastdir %d"), XdrvMailbox.payload, XdrvMailbox.index, dir, Shutter[XdrvMailbox.index-1].lastdirection); - if ((1 == XdrvMailbox.index) && (XdrvMailbox.payload != -99)) { - XdrvMailbox.index = XdrvMailbox.payload; - } - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - uint32_t index = XdrvMailbox.index-1; - if (dir) { - XdrvMailbox.payload = (Shutter[index].direction==0 ? ((Shutter[index].lastdirection > 0) ? 0 : 100) : (Shutter[index].direction > 0) ? 0 : 100); - } - else { - XdrvMailbox.payload = (50 < ShutterRealToPercentPosition(Shutter[index].real_position, index)) ? 0 : 100; - } - XdrvMailbox.data_len = 0; - TasmotaGlobal.last_source = SRC_WEBGUI; - CmndShutterPosition(); - } -} - -/*********************************************************************************************\ - * Commands -\*********************************************************************************************/ - -void CmndShutterOpen(void) -{ - //AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Payload open: %d, i %d"), XdrvMailbox.payload, XdrvMailbox.index); - if ((!XdrvMailbox.usridx) && (XdrvMailbox.payload != -99)) { - XdrvMailbox.index = XdrvMailbox.payload; - } - XdrvMailbox.payload = 100; - TasmotaGlobal.last_source = SRC_WEBGUI; - CmndShutterPosition(); -} - -void CmndShutterStopOpen(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - uint32_t index = XdrvMailbox.index-1; - if (Shutter[index].direction) { - CmndShutterStop(); - } else { - CmndShutterOpen(); - } - } -} - -void CmndShutterClose(void) -{ - //AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Payload close: %d, i %d"), XdrvMailbox.payload, XdrvMailbox.index); - if ((!XdrvMailbox.usridx) && (XdrvMailbox.payload != -99)) { - XdrvMailbox.index = XdrvMailbox.payload; - } - XdrvMailbox.payload = 0; - TasmotaGlobal.last_source = SRC_WEBGUI; - CmndShutterPosition(); -} - -void CmndShutterStopClose(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - uint32_t index = XdrvMailbox.index-1; - if (Shutter[index].direction) { - CmndShutterStop(); - } else { - CmndShutterClose(); - } - } -} - -void CmndShutterToggle(void) -{ - ShutterToggle(false); -} - -void CmndShutterToggleDir(void) -{ - ShutterToggle(true); -} - -void CmndShutterStopToggle(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - uint32_t index = XdrvMailbox.index-1; - if (Shutter[index].direction) { - CmndShutterStop(); - } else { - CmndShutterToggle(); - } - } -} - -void CmndShutterStopToggleDir(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - uint32_t index = XdrvMailbox.index-1; - if (Shutter[index].direction) { - CmndShutterStop(); - } else { - CmndShutterToggleDir(); - } - } -} - -void CmndShutterStop(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - //AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Try Stop %d: dir: %d"), XdrvMailbox.index, Shutter[XdrvMailbox.index -1].direction); - if (!(ShutterSettings.shutter_options[XdrvMailbox.index-1] & 2)) { - if ((!XdrvMailbox.usridx) && (XdrvMailbox.payload != -99)) { - XdrvMailbox.index = XdrvMailbox.payload; - } - uint32_t i = XdrvMailbox.index -1; - if (Shutter[i].direction != 0) { - - AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Stop %d: dir: %d"), XdrvMailbox.index, Shutter[i].direction); - Shutter[i].target_position = Shutter[i].real_position; - } - if (XdrvMailbox.command) - ResponseCmndDone(); - ShutterUpdatePosition(); - } else { - if (XdrvMailbox.command) - ResponseCmndIdxChar("Locked"); - } - } -} - -void CmndShutterIncDec(void) -{ - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Change in: payload %s (%d), payload %d, idx %d, src %d"), XdrvMailbox.data , XdrvMailbox.data_len, XdrvMailbox.payload , XdrvMailbox.index, TasmotaGlobal.last_source ); - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - if (XdrvMailbox.data_len > 0) { - XdrvMailbox.payload = ShutterRealToPercentPosition(Shutter[XdrvMailbox.index-1].target_position, XdrvMailbox.index-1)+XdrvMailbox.payload; - // limit position to boundaries - XdrvMailbox.payload = XdrvMailbox.payload < 0 ? 0 : (XdrvMailbox.payload > 100 ? 100 : XdrvMailbox.payload); - CmndShutterPosition(); - } - } -} - -void CmndShutterPosition(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - if (!(ShutterSettings.shutter_options[XdrvMailbox.index-1] & 2)) { - uint32_t index = XdrvMailbox.index-1; - //limit the payload - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Pos. payload <%s> (%d), payload %d, idx %d (%d), src %d"), XdrvMailbox.data , XdrvMailbox.data_len, XdrvMailbox.payload , XdrvMailbox.index, XdrvMailbox.usridx, TasmotaGlobal.last_source ); - - // value 0 with data_len > 0 can mean Open - // special handling fo UP,DOWN,TOGGLE,STOP command comming with payload -99 - // STOP will come with payload 0 because predefined value in TASMOTA - if ((XdrvMailbox.data_len > 3) && (XdrvMailbox.payload <= 0)) { - // set len to 0 to avoid loop on close where payload is 0 - XdrvMailbox.data_len = 0; - if ( ((Shutter[index].direction==0) && !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_STOPOPEN))) { - CmndShutterOpen(); - return; - } - if ( ((Shutter[index].direction==0) && !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_STOPCLOSE))) { - CmndShutterClose(); - return; - } - if (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLE)) { - CmndShutterToggle(); - return; - } - if (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLEDIR)) { - CmndShutterToggleDir(); - return; - } - if (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_STOP) || ((Shutter[index].direction) && (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_STOPOPEN) || !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_STOPCLOSE)))) { - // Back to normal: all -99 if not a clear position - XdrvMailbox.payload = -99; - CmndShutterStop(); - return; - } - } - - // if position is either 0 or 100 reset the tilt to avoid tilt moving at the end - if (XdrvMailbox.payload == 0 && ShutterRealToPercentPosition(Shutter[index].real_position, index) > 0 ) {Shutter[index].tilt_target_pos = Shutter[index].tilt_config[4];} - if (XdrvMailbox.payload == 100 && ShutterRealToPercentPosition(Shutter[index].real_position, index) < 100) {Shutter[index].tilt_target_pos = Shutter[index].tilt_config[3];} - - int8_t target_pos_percent = (XdrvMailbox.payload < 0) ? (XdrvMailbox.payload == -99 ? ShutterRealToPercentPosition(Shutter[index].real_position, index) : 0) : ((XdrvMailbox.payload > 100) ? 100 : XdrvMailbox.payload); - // webgui still send also on inverted shutter the native position. - target_pos_percent = ((ShutterSettings.shutter_options[index] & 1) && (SRC_WEBGUI != TasmotaGlobal.last_source)) ? 100 - target_pos_percent : target_pos_percent; - if (XdrvMailbox.payload != -99) { - //target_pos_percent = (ShutterSettings.shutter_options[index] & 1) ? 100 - target_pos_percent : target_pos_percent; - Shutter[index].target_position = ShutterPercentToRealPosition(target_pos_percent, index); - //Shutter[i].accelerator[index] = ShutterGlobal.open_velocity_max / ((Shutter[i].motordelay[index] > 0) ? Shutter[i].motordelay[index] : 1); - //Shutter[i].target_position[index] = XdrvMailbox.payload < 5 ? ShutterSettings.shuttercoeff[2][index] * XdrvMailbox.payload : ShutterSettings.shuttercoeff[1][index] * XdrvMailbox.payload + ShutterSettings.shuttercoeff[0,index]; - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: lastsource %d:, real %d, target %d, tiltreal: %d, tilttarget: %d, payload %d"), TasmotaGlobal.last_source, Shutter[index].real_position ,Shutter[index].target_position,Shutter[index].tilt_real_pos, Shutter[index].tilt_target_pos,target_pos_percent); - } - if ( (target_pos_percent >= 0) && (target_pos_percent <= 100) && - (abs(Shutter[index].target_position - Shutter[index].real_position ) > Shutter[index].min_realPositionChange || - abs(Shutter[index].tilt_target_pos - Shutter[index].tilt_real_pos ) > Shutter[index].min_TiltChange) ) { - if (ShutterSettings.shutter_options[index] & 4) { - if (0 == target_pos_percent && Shutter[index].real_position > 0) Shutter[index].target_position -= 1 * RESOLUTION * STEPS_PER_SECOND; - if (100 == target_pos_percent && Shutter[index].real_position < Shutter[index].open_max) Shutter[index].target_position += 1 * RESOLUTION * STEPS_PER_SECOND; - } - int8_t new_shutterdirection; - if (abs(Shutter[index].target_position - Shutter[index].real_position ) > Shutter[index].min_realPositionChange) { - new_shutterdirection = Shutter[index].real_position < Shutter[index].target_position ? 1 : -1; - Shutter[index].tiltmoving = 0; - } else { - new_shutterdirection = Shutter[index].tilt_real_pos < Shutter[index].tilt_target_pos ? 1 : -1; - Shutter[index].tiltmoving = 1; - } - - if (Shutter[index].direction == -new_shutterdirection) { - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Stop shutter to reverse direction")); - ShutterPowerOff(index); - } - if (Shutter[index].direction != new_shutterdirection) { - ShutterStartInit(index, new_shutterdirection, Shutter[index].target_position); - uint8_t save_direction = Shutter[index].direction; - Shutter[index].direction = 0; // set temporary direction = 0 to avoid RTC timer sarting. Some delay may happen before shutter starts moving - switch (ShutterGlobal.position_mode) { - case SHT_COUNTER: - case SHT_PWM_TIME: - case SHT_PWM_VALUE: - case SHT_TIME_UP_DOWN: - if (!ShutterGlobal.skip_relay_change) { - // Code for shutters with circuit safe configuration, switch the direction Relay - ExecuteCommandPowerShutter(ShutterSettings.shutter_startrelay[index] +1, new_shutterdirection == 1 ? 0 : 1, SRC_SHUTTER); - delay(SHUTTER_RELAY_OPERATION_TIME); - // power on - ExecuteCommandPowerShutter(ShutterSettings.shutter_startrelay[index], 1, SRC_SHUTTER); - } - //if (ShutterGlobal.position_mode != SHT_TIME_UP_DOWN) ExecuteCommandPowerShutter(ShutterSettings.shutter_startrelay[index]+2, 1, SRC_SHUTTER); - break; - case SHT_TIME: - if (!ShutterGlobal.skip_relay_change) { - if ( (TasmotaGlobal.power >> (ShutterSettings.shutter_startrelay[index] -1)) & 3 > 0 ) { - ExecuteCommandPowerShutter(ShutterSettings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 1 : 0), Shutter[index].switch_mode == SHT_SWITCH ? 0 : 1, SRC_SHUTTER); - } - ExecuteCommandPowerShutter(ShutterSettings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 0 : 1), 1, SRC_SHUTTER); - } - break; - case SHT_TIME_GARAGE: - if (!ShutterGlobal.skip_relay_change) { - if (new_shutterdirection == Shutter[index].lastdirection) { - AddLog(LOG_LEVEL_INFO, PSTR("SHT: Garage not move in this direction: %d"), Shutter[index].switch_mode == SHT_PULSE); - for (uint8_t k=0 ; k <= (uint8_t)(Shutter[index].switch_mode == SHT_PULSE) ; k++) { - ExecuteCommandPowerShutter(ShutterSettings.shutter_startrelay[index], 1, SRC_SHUTTER); - //delay(MOTOR_STOP_TIME); - ShutterWaitForMotorStop(index); - ExecuteCommandPowerShutter(ShutterSettings.shutter_startrelay[index], 0, SRC_SHUTTER); - //delay(MOTOR_STOP_TIME); - ShutterWaitForMotorStop(index); - } - // reset shutter time to avoid 2 seconds above count as runtime - Shutter[index].time = 0; - } // if (new_shutterdirection == Shutter[i].lastdirection[index]) - ExecuteCommandPowerShutter(ShutterSettings.shutter_startrelay[index], 1, SRC_SHUTTER); - } // if (!ShutterGlobal.skip_relay_change) - break; - } // switch (ShutterGlobal.position_mode) - Shutter[index].direction = save_direction; - ShutterGlobal.RelayCurrentMask = 0; - } // if (Shutter[i].direction[index] != new_shutterdirection) - } else { - target_pos_percent = ShutterRealToPercentPosition(Shutter[index].real_position, index); - } - index = (!XdrvMailbox.usridx && !XdrvMailbox.data_len)?MAX_SHUTTERS_ESP32:index; - ShutterReportPosition(true, index); - ShutterGlobal.start_reported = 1; - XdrvMailbox.index = index +1; // Fix random index for ShutterClose - } else { - ShutterReportPosition(true, MAX_SHUTTERS_ESP32); - if (XdrvMailbox.command) - ResponseCmndIdxChar("Locked"); - } - } -} - -void CmndShutterStopPosition(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - uint32_t index = XdrvMailbox.index-1; - if (Shutter[index].direction) { - XdrvMailbox.payload = -99; - CmndShutterStop(); - } else { - CmndShutterPosition(); - } - } -} - -void CmndShutterOpenTime(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - if (XdrvMailbox.data_len > 0) { - ShutterSettings.shutter_opentime[XdrvMailbox.index -1] = (uint16_t)(10 * CharToFloat(XdrvMailbox.data)); - ShutterInit(); - } -/* - char time_chr[10]; - dtostrfd((float)(ShutterSettings.shutter_opentime[XdrvMailbox.index -1]) / 10, 1, time_chr); - ResponseCmndIdxChar(time_chr); -*/ - ResponseCmndIdxFloat((float)(ShutterSettings.shutter_opentime[XdrvMailbox.index -1]) / 10, 1); - } -} - -void CmndShutterCloseTime(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - if (XdrvMailbox.data_len > 0) { - ShutterSettings.shutter_closetime[XdrvMailbox.index -1] = (uint16_t)(10 * CharToFloat(XdrvMailbox.data)); - ShutterInit(); - } -/* - char time_chr[10]; - dtostrfd((float)(ShutterSettings.shutter_closetime[XdrvMailbox.index -1]) / 10, 1, time_chr); - ResponseCmndIdxChar(time_chr); -*/ - ResponseCmndIdxFloat((float)(ShutterSettings.shutter_closetime[XdrvMailbox.index -1]) / 10, 1); - } -} - -void CmndShutterMotorDelay(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - if (XdrvMailbox.data_len > 0) { - ShutterSettings.shutter_motordelay[XdrvMailbox.index -1] = (uint8_t)(STEPS_PER_SECOND * CharToFloat(XdrvMailbox.data)); - ShutterInit(); - //AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Shtr Init1. realdelay %d"),Shutter[XdrvMailbox.index -1].motordelay); - } -/* - char time_chr[10]; - dtostrfd((float)(Shutter[XdrvMailbox.index -1].motordelay) / STEPS_PER_SECOND, 2, time_chr); - ResponseCmndIdxChar(time_chr); -*/ - ResponseCmndIdxFloat((float)(Shutter[XdrvMailbox.index -1].motordelay) / STEPS_PER_SECOND, 2); - } -} - -void CmndShutterMode(void) -{ - if (!XdrvMailbox.usridx) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= MAX_MODES)) { - ShutterSettings.shutter_mode = ShutterGlobal.position_mode = XdrvMailbox.payload; - ShutterInit(); - } - ResponseCmndNumber(ShutterGlobal.position_mode); - } -} - -void CmndShutterRelay(void) -{ - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 32) && (XdrvMailbox.index <= MAX_SHUTTERS_ESP32)) { - //ShutterSettings.shutter_startrelay[XdrvMailbox.index -1] = XdrvMailbox.payload; - if (XdrvMailbox.payload > 0) { - ShutterGlobal.RelayShutterMask |= 3 << (XdrvMailbox.payload - 1); - } else { - ShutterGlobal.RelayShutterMask ^= 3 << (ShutterSettings.shutter_startrelay[XdrvMailbox.index -1] - 1); - } - AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: relold:%d index:%d, mode:%d, relaymask: %ld"), - ShutterSettings.shutter_startrelay[XdrvMailbox.index -1] , XdrvMailbox.index ,ShutterSettings.shutter_mode, ShutterGlobal.RelayShutterMask ); - if (ShutterSettings.shutter_startrelay[XdrvMailbox.index -1] == 0 && XdrvMailbox.index == 1 && ShutterSettings.shutter_mode == SHT_UNDEF) { - // first shutter was not defined, maybe init - ShutterSettings.shutter_mode = SHT_AUTOCONFIG; - AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Autoconfig")); - } - ShutterSettings.shutter_startrelay[XdrvMailbox.index -1] = XdrvMailbox.payload; - - ShutterInit(); - // if payload is 0 to disable the relay there must be a reboot. Otherwhise does not work - } - uint32_t start = (!XdrvMailbox.usridx && !XdrvMailbox.data_len)?0:XdrvMailbox.index -1; - uint32_t end = (!XdrvMailbox.usridx && !XdrvMailbox.data_len)?TasmotaGlobal.shutters_present:XdrvMailbox.index; - // {"ShutterRelay1":"1","ShutterRelay2":"3","ShutterRelay3":"5"} - Response_P(PSTR("{")); - for (uint32_t i = start; i < end; i++) { - ResponseAppend_P(PSTR("%s\"" D_PRFX_SHUTTER D_CMND_SHUTTER_RELAY "%d\":%d"), (i>start)?",":"", i+1, ShutterSettings.shutter_startrelay[i]); - } - ResponseAppend_P(PSTR("}")); -} - -void CmndShutterButton(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SHUTTERS_ESP32)) { - uint32_t setting = 0; - // (setting>>31)&(0x01) : enabled - // (setting>>30)&(0x01) : mqtt broadcast to all index - // (setting>>29)&(0x01) : mqtt broadcast hold - // (setting>>28)&(0x01) : mqtt broadcast tripple press - // (setting>>27)&(0x01) : mqtt broadcast double press - // (setting>>26)&(0x01) : mqtt broadcast single press - // (setting>>20)&(0x3f) : shutter_position hold; 0 disabled, 1..101 == 0..100%, 102 == toggle - // (setting>>14)&(0x3f) : shutter_position tripple press 0 disabled, 1..101 == 0..100%, 102 == toggle - // (setting>> 8)&(0x3f) : shutter_position double press 0 disabled, 1..101 == 0..100%, 102 == toggle - // (setting>> 2)&(0x3f) : shutter_position single press 0 disabled, 1..101 == 0..100%, 102 == toggle - // (setting>> 0)&(0x03) : shutter_index - if (XdrvMailbox.data_len > 0) { - uint32_t i = 0; - uint32_t button_index = 0; - bool done = false; - bool isShortCommand = false; - char *str_ptr; - - char data_copy[strlen(XdrvMailbox.data) +1]; - strncpy(data_copy, XdrvMailbox.data, sizeof(data_copy)); // Duplicate data as strtok_r will modify it. - // Loop through the data string, splitting on ' ' seperators. - for (char *str = strtok_r(data_copy, " ", &str_ptr); str && i < (1+4+4+1); str = strtok_r(nullptr, " ", &str_ptr), i++) { - int field; - switch (str[0]) { - case '-': - field = -1; - break; - case 't': - field = 102; - break; - default: - field = atoi(str); - break; - } - switch (i) { - case 0: - if ((field >= -1) && (field<=MAX_SHUTTERS_ESP32*2)) { - button_index = (field<=0)?(-1):field; - done = (button_index==-1); - } else - done = true; - break; - case 1: - if (!strcmp_P(str, PSTR("up"))) { - setting |= (((100>>1)+1)<<2) | (((50>>1)+1)<<8) | (((75>>1)+1)<<14) | (((100>>1)+1)<<20); - isShortCommand = true; - break; - } else if (!strcmp_P(str, PSTR("down"))) { - setting |= (((0>>1)+1)<<2) | (((50>>1)+1)<<8) | (((25>>1)+1)<<14) | (((0>>1)+1)<<20); - isShortCommand = true; - break; - } else if (!strcmp_P(str, PSTR("updown"))) { - setting |= (((100>>1)+1)<<2) | (((0>>1)+1)<<8) | (((50>>1)+1)<<14); - isShortCommand = true; - break; - } else if (!strcmp_P(str, PSTR("toggle"))) { - setting |= (((102>>1)+1)<<2) | (((50>>1)+1)<<8); - isShortCommand = true; - break; - } - case 2: - if (isShortCommand) { - if ((field==1) && (setting & (0x3F<<(2+6*3)))) - // if short command up or down (hold press position set) then also enable MQTT broadcast - setting |= (0x3<<29); - done = true; - break; - } - case 3: - case 4: - if ((field >= -1) && (field<=102)) - setting |= (((field>>1)+1)<<(i*6 + (2-6))); - break; - case 5: - case 6: - case 7: - case 8: - case 9: - if (field==1) - setting |= (1<<(i + (26-5))); - break; - } - if (done) break; - } - - if (button_index) { - if (button_index==-1) { - // remove all buttons for this shutter - for (uint32_t i=0 ; i < MAX_SHUTTERS_ESP32*2 ; i++) - if (ShutterSettings.shutter_button[i].shutter_number == XdrvMailbox.index-1) - ShutterSettings.shutter_button[i].positionmatrix = 0; - ShutterSettings.shutter_button[i].shutter_number=0; - } else { - if (setting) { - // anything was set - setting |= (1<<31); - ShutterSettings.shutter_button[button_index-1].shutter_number = XdrvMailbox.index-1; - } - ShutterSettings.shutter_button[button_index-1].positionmatrix = setting; - } - } - } - char setting_chr[30*MAX_SHUTTER_KEYS] = "-", *setting_chr_ptr = setting_chr; - for (uint32_t i=0 ; i < MAX_SHUTTERS_ESP32*2 ; i++) { - setting = ShutterSettings.shutter_button[i].positionmatrix; - if ((setting&(1<<31)) && (ShutterSettings.shutter_button[i].shutter_number == XdrvMailbox.index-1)) { - if (*setting_chr_ptr == 0) - setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR("|")); - setting_chr_ptr += snprintf_P(setting_chr_ptr, 3, PSTR("%d"), i+1); - - for (uint32_t j=0 ; j < 4 ; j++) { - int8_t pos = (((setting>> (2+6*j))&(0x3f))-1)<<1; - if (0 <= pos) - if (102 == pos) { - setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR(" t")); - } else { - setting_chr_ptr += snprintf_P(setting_chr_ptr, 5, PSTR(" %d"), pos); - } - else - setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR(" -")); - } - for (uint32_t j=0 ; j < 5 ; j++) { - bool mqtt = ((setting>>(26+j))&(0x01)!=0); - if (mqtt) - setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR(" 1")); - else - setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR(" -")); - } - } - } - ShutterSettingsSave(); - ResponseCmndIdxChar(setting_chr); - } -} - -void CmndShutterSetHalfway(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) { - ShutterSettings.shutter_set50percent[XdrvMailbox.index -1] = (ShutterSettings.shutter_options[XdrvMailbox.index -1] & 1) ? 100 - XdrvMailbox.payload : XdrvMailbox.payload; - ShutterSettings.shuttercoeff[0][XdrvMailbox.index -1] = 0; - ShutterInit(); - } - ResponseCmndIdxNumber((ShutterSettings.shutter_options[XdrvMailbox.index -1] & 1) ? 100 - ShutterSettings.shutter_set50percent[XdrvMailbox.index -1] : ShutterSettings.shutter_set50percent[XdrvMailbox.index -1]); - } -} - -void CmndShutterFrequency(void) -{ - if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 20000)) { - ShutterGlobal.open_velocity_max = XdrvMailbox.payload; - if (TasmotaGlobal.shutters_present < 4) { - ShutterSettings.shuttercoeff[4][3] = ShutterGlobal.open_velocity_max; - } - ShutterInit(); - } - ResponseCmndNumber(ShutterGlobal.open_velocity_max); -} - -void CmndShutterSetClose(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - Shutter[XdrvMailbox.index -1].real_position = 0; - Shutter[XdrvMailbox.index -1].tilt_real_pos = Shutter[XdrvMailbox.index -1].tilt_config[0]; - ShutterStartInit(XdrvMailbox.index -1, 0, 0); - ShutterSettings.shutter_position[XdrvMailbox.index -1] = 0; - ResponseCmndIdxChar(D_CONFIGURATION_RESET); - } -} - -void CmndShutterSetOpen(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - Shutter[XdrvMailbox.index -1].real_position = Shutter[XdrvMailbox.index -1].open_max; - Shutter[XdrvMailbox.index -1].tilt_real_pos = Shutter[XdrvMailbox.index -1].tilt_config[1]; - ShutterStartInit(XdrvMailbox.index -1, 0, Shutter[XdrvMailbox.index -1].open_max); - ShutterSettings.shutter_position[XdrvMailbox.index -1] = 100; - ResponseCmndIdxChar(D_CONFIGURATION_RESET); - } -} - -void CmndShutterPwmRange(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - if (XdrvMailbox.data_len > 0) { - uint8_t i = 0; - char *str_ptr; - - char data_copy[strlen(XdrvMailbox.data) +1]; - strncpy(data_copy, XdrvMailbox.data, sizeof(data_copy)); // Duplicate data as strtok_r will modify it. - // Loop through the data string, splitting on ' ' seperators. - for (char *str = strtok_r(data_copy, " ", &str_ptr); str && i < 2; str = strtok_r(nullptr, " ", &str_ptr), i++) { - uint16_t field = atoi(str); - // The fields in a data string can only range from 1-30000. - // and following value must be higher than previous one - if ((field <= 0) || (field > 1023)) { - break; - } - ShutterSettings.shutter_pwmrange[i][XdrvMailbox.index -1] = field; - } - AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Shtr%d Init1. pwmmin %d, pwmmax %d"), XdrvMailbox.index , ShutterSettings.shutter_pwmrange[0][XdrvMailbox.index -1], ShutterSettings.shutter_pwmrange[1][XdrvMailbox.index -1]); - ShutterInit(); - ResponseCmndIdxChar(XdrvMailbox.data); - } else { - char setting_chr[30] = "0"; - snprintf_P(setting_chr, sizeof(setting_chr), PSTR("Shutter %d: min:%d max:%d"), XdrvMailbox.index, ShutterSettings.shutter_pwmrange[0][XdrvMailbox.index -1], ShutterSettings.shutter_pwmrange[1][XdrvMailbox.index -1]); - ResponseCmndIdxChar(setting_chr); - } - } -} - -void CmndShutterCalibration(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - if (XdrvMailbox.data_len > 0) { - uint8_t i = 0; - char *str_ptr; - - char data_copy[strlen(XdrvMailbox.data) +1]; - strncpy(data_copy, XdrvMailbox.data, sizeof(data_copy)); // Duplicate data as strtok_r will modify it. - // Loop through the data string, splitting on ' ' seperators. - for (char *str = strtok_r(data_copy, " ", &str_ptr); str && i < 5; str = strtok_r(nullptr, " ", &str_ptr), i++) { - int field = atoi(str); - // The fields in a data string can only range from 1-30000. - // and following value must be higher than previous one - if ((field <= 0) || (field > 30000) || ( (i>0) && (field <= messwerte[i-1]) ) ) { - break; - } - messwerte[i] = field; - } - ShutterSettings.shutter_set50percent[XdrvMailbox.index -1] = 50; - for (i = 0; i < 5; i++) { - ShutterSettings.shuttercoeff[i][XdrvMailbox.index -1] = SHT_DIV_ROUND((uint32_t)messwerte[i] * 1000, messwerte[4]); - AddLog(LOG_LEVEL_DEBUG, PSTR("SHT: Shuttercoeff %d, i %d, Value %d, MeasuredValue %d"), i,XdrvMailbox.index -1,ShutterSettings.shuttercoeff[i][XdrvMailbox.index -1], messwerte[i]); - } - ShutterInit(); - ResponseCmndIdxChar(XdrvMailbox.data); - } else { - char setting_chr[30] = "0"; - snprintf_P(setting_chr, sizeof(setting_chr), PSTR("%d %d %d %d %d"), ShutterSettings.shuttercoeff[0][XdrvMailbox.index -1], ShutterSettings.shuttercoeff[1][XdrvMailbox.index -1], ShutterSettings.shuttercoeff[2][XdrvMailbox.index -1], ShutterSettings.shuttercoeff[3][XdrvMailbox.index -1], ShutterSettings.shuttercoeff[4][XdrvMailbox.index -1]); - ResponseCmndIdxChar(setting_chr); - } - } -} - -void ShutterOptionsSetHelper(uint16_t option) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - if (XdrvMailbox.payload == 0) { - ShutterSettings.shutter_options[XdrvMailbox.index -1] &= ~(option); - } else if (XdrvMailbox.payload == 1) { - ShutterSettings.shutter_options[XdrvMailbox.index -1] |= (option); - } - ResponseCmndIdxNumber((ShutterSettings.shutter_options[XdrvMailbox.index -1] & option) ? 1 : 0); - } -} - -void CmndShutterInvert(void) -{ - ShutterOptionsSetHelper(1); -} - -void CmndShutterLock(void) -{ - ShutterOptionsSetHelper(2); -} - -void CmndShutterEnableEndStopTime(void) -{ - ShutterOptionsSetHelper(4); -} - -void CmndShutterInvertWebButtons(void) -{ - ShutterOptionsSetHelper(8); -} - -void CmndShutterSetTilt(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - if (XdrvMailbox.payload != -99 ) { - Shutter[XdrvMailbox.index -1].tilt_target_pos = tmin(tmax(XdrvMailbox.payload, Shutter[XdrvMailbox.index -1].tilt_config[0]), Shutter[XdrvMailbox.index -1].tilt_config[1]); - } - // assuming OPEN=100=tilt_config[3]/CLOSE=0=tilt_config[4] - if (XdrvMailbox.data_len > 3 && XdrvMailbox.payload >= 0 ) { - Shutter[XdrvMailbox.index -1].tilt_target_pos = Shutter[XdrvMailbox.index -1].tilt_config[XdrvMailbox.payload?3:4]; - } - } - XdrvMailbox.data[0] = '\0'; - AddLog(LOG_LEVEL_INFO, PSTR("SHT: TiltTarget %d, payload %d"), Shutter[XdrvMailbox.index -1].tilt_target_pos,XdrvMailbox.payload); - Shutter[XdrvMailbox.index -1].tiltmoving = 1; - // Avoid shutterposition try to interpret "open/close or payload" - XdrvMailbox.data_len = 0; - XdrvMailbox.payload = -99; - CmndShutterPosition(); -} - -void CmndShutterTiltConfig(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) { - if (XdrvMailbox.data_len > 0) { - uint8_t i = 0; - char *str_ptr; - char data_copy[strlen(XdrvMailbox.data) +1]; - strncpy(data_copy, XdrvMailbox.data, sizeof(data_copy)); // Duplicate data as strtok_r will modify it. - // Loop through the data string, splitting on ' ' seperators. - for (char *str = strtok_r(data_copy, " ", &str_ptr); str && i < 6; str = strtok_r(nullptr, " ", &str_ptr), i++) { - Shutter[XdrvMailbox.index -1].tilt_config[i] = ShutterSettings.shutter_tilt_config[i][XdrvMailbox.index -1] = atoi(str); - } - // avoid negative runtime - ShutterSettings.shutter_tilt_config[2][XdrvMailbox.index -1] = Shutter[XdrvMailbox.index -1].tilt_config[2] = Shutter[XdrvMailbox.index -1].tilt_config[2] >= 0 ? Shutter[XdrvMailbox.index -1].tilt_config[2] : 127; - ShutterInit(); - } - char setting_chr[30] = "0"; - snprintf_P(setting_chr, sizeof(setting_chr), PSTR("%d %d %d %d %d"), Shutter[XdrvMailbox.index -1].tilt_config[0], Shutter[XdrvMailbox.index -1].tilt_config[1],Shutter[XdrvMailbox.index -1].tilt_config[2],Shutter[XdrvMailbox.index -1].tilt_config[3],Shutter[XdrvMailbox.index -1].tilt_config[4]); - ResponseCmndIdxChar(setting_chr); - AddLog(LOG_LEVEL_INFO, PSTR("SHT: TiltConfig %d, min: %d, max %d, runtime %d, close_pos: %d, open_pos: %d"), XdrvMailbox.index ,Shutter[XdrvMailbox.index -1].tilt_config[0], Shutter[XdrvMailbox.index -1].tilt_config[1],Shutter[XdrvMailbox.index -1].tilt_config[2],Shutter[XdrvMailbox.index -1].tilt_config[3],Shutter[XdrvMailbox.index -1].tilt_config[4]); - } -} - -void CmndShutterTiltIncDec(void) -{ - //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Change in: payload %s (%d), payload %d, idx %d, src %d"), XdrvMailbox.data , XdrvMailbox.data_len, XdrvMailbox.payload , XdrvMailbox.index, TasmotaGlobal.last_source ); - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present) && XdrvMailbox.data_len > 0) { - XdrvMailbox.payload = Shutter[XdrvMailbox.index -1].tilt_target_pos+XdrvMailbox.payload; - CmndShutterSetTilt(); - } else { - ResponseCmndIdxNumber(XdrvMailbox.payload); - } -} - -void CmndShutterMotorStop(void) -{ - if (!XdrvMailbox.usridx) { - if ((XdrvMailbox.payload >= 0) ) { - ShutterSettings.shutter_motorstop = XdrvMailbox.payload; - ShutterInit(); - } - ResponseCmndNumber(ShutterSettings.shutter_motorstop); - } -} - -/*********************************************************************************************\ - * Interface -\*********************************************************************************************/ - -bool Xdrv27(uint32_t function) -{ - bool result = false; - - if (Settings->flag3.shutter_mode) { // SetOption80 - Enable shutter support - uint8_t counter = XdrvMailbox.index==0?1:XdrvMailbox.index; - uint8_t counterend = XdrvMailbox.index==0?TasmotaGlobal.shutters_present:XdrvMailbox.index; - int32_t rescue_payload = XdrvMailbox.payload; - uint32_t rescue_data_len = XdrvMailbox.data_len; - switch (function) { - case FUNC_SAVE_SETTINGS: - ShutterSettingsSave(); - break; - case FUNC_PRE_INIT: - ShutterSettingsLoad(); - ShutterInit(); - break; - case FUNC_EVERY_50_MSECOND: - ShutterUpdatePosition(); - break; - case FUNC_EVERY_SECOND: - //case FUNC_EVERY_250_MSECOND: - ShutterReportPosition(false, MAX_SHUTTERS_ESP32); - break; - case FUNC_COMMAND: - for (uint8_t i = counter; i <= counterend; i++) { - XdrvMailbox.index = i; - XdrvMailbox.payload = rescue_payload; - XdrvMailbox.data_len = rescue_data_len; - result = DecodeCommand(kShutterCommands, ShutterCommand); - } - break; - case FUNC_JSON_APPEND: - for (uint8_t i = 0; i < TasmotaGlobal.shutters_present; i++) { - uint8_t position = (ShutterSettings.shutter_options[i] & 1) ? 100 - ShutterSettings.shutter_position[i] : ShutterSettings.shutter_position[i]; - uint8_t target = (ShutterSettings.shutter_options[i] & 1) ? 100 - ShutterRealToPercentPosition(Shutter[i].target_position, i) : ShutterRealToPercentPosition(Shutter[i].target_position, i); - - ResponseAppend_P(","); - ResponseAppend_P(JSON_SHUTTER_POS, i+1, position, Shutter[i].direction,target, Shutter[i].tilt_real_pos); -#ifdef USE_DOMOTICZ - if ((0 == TasmotaGlobal.tele_period) && (0 == i)) { - DomoticzSensor(DZ_SHUTTER, position); - } -#endif // USE_DOMOTICZ - } - break; - case FUNC_SET_POWER: - char stemp1[10]; - // extract the number of the relay that was switched and save for later in Update Position. - ShutterGlobal.RelayCurrentMask = XdrvMailbox.index ^ ShutterGlobal.RelayOldMask; - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Switched relay %d by %s"), ShutterGlobal.RelayCurrentMask,GetTextIndexed(stemp1, sizeof(stemp1), TasmotaGlobal.last_source, kCommandSource)); - ShutterRelayChanged(); - ShutterGlobal.RelayOldMask = XdrvMailbox.index; - break; - case FUNC_SET_DEVICE_POWER: - if (ShutterGlobal.skip_relay_change ) { - uint8_t i; - for (i = 0; i < TasmotaGlobal.devices_present; i++) { - if (ShutterGlobal.RelayCurrentMask &1) { - break; - } - ShutterGlobal.RelayCurrentMask >>= 1; - } - //AddLog(LOG_LEVEL_ERROR, PSTR("SHT: Skip relay change %d"), i+1); - result = true; - ShutterGlobal.skip_relay_change = 0; - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Skipping switch off relay %d"), i); - ExecuteCommandPowerShutter(i+1, 0, SRC_SHUTTER); - } - break; - case FUNC_BUTTON_MULTI_PRESSED: - if (XdrvMailbox.index < MAX_SHUTTERS_ESP32*2 && ShutterSettings.shutter_button[XdrvMailbox.index].positionmatrix & (1<<31)) { - result = ShutterButtonHandlerMulti(); - } - break; - case FUNC_BUTTON_PRESSED: - if (XdrvMailbox.index < MAX_SHUTTERS_ESP32*2 && ShutterSettings.shutter_button[XdrvMailbox.index].positionmatrix & (1<<31)) { - ShutterButtonHandler(); - result = false; - } - break; - } - } - return result; -} - -#endif //USE_SHUTTER - -#ifdef SHUTTER_UNITTEST -void CmndShutterUnitTest(void) { - int16_t input_percent[10] = {-5,0,10,26,35,55,80,99,100,105}; - int16_t output_percent[10] = {0,0,10,26,35,55,80,99,100,100}; - uint32_t result_percent[2][2][10] = {{{0,0,24000,62400,84000,132000,192000,237600,240000,240000}, - {0,0,360000,936000,1260000,1980000,2880000,3564000,3600000,3600000}}, - {{0,0,76296,100000,113333,174299,205795,237983,240000,240000}, - {0,0,1144444,1500000,1700000,2614488,3086929,3569748,3600000,3600000}}}; - - uint32_t result = 0; - char svalue[50]; // Command and number parameter - ShutterSettings.shuttercoeff[0][0] = 0; - for (uint8_t i=0; i<2 ; i++){ - snprintf_P(svalue, sizeof(svalue), PSTR(D_PRFX_SHUTTER D_CMND_SHUTTER_OPENTIME "%d %d"), 1, 12); - ExecuteCommand(svalue, SRC_SHUTTER); - ShutterInit(); - for (uint8_t j=0; j<2 ; j++){ - for (uint8_t k=0; k<10 ; k++){ - result += (result_percent[i][j][k] == ShutterPercentToRealPosition(input_percent[k] , 0) ? 0 : 1); - AddLog(LOG_LEVEL_ERROR, PSTR("SHT: ShutterPercentToRealPosition error %d: %d <-> %d"),result, ShutterPercentToRealPosition(input_percent[k] , 0), result_percent[i][j][k]); - } - snprintf_P(svalue, sizeof(svalue), PSTR(D_PRFX_SHUTTER D_CMND_SHUTTER_OPENTIME "%d %d"), 1, 180); - ExecuteCommand(svalue, SRC_SHUTTER); - } - snprintf_P(svalue, sizeof(svalue), PSTR(D_PRFX_SHUTTER D_CMND_SHUTTER_CLIBRATION "%d %s"), 1, "15 83 105 185 210"); - ExecuteCommand(svalue, SRC_SHUTTER); - } - if (!result){ - AddLog(LOG_LEVEL_ERROR, PSTR("SHT: ShutterPercentToRealPosition: PASS")); - } else { - AddLog(LOG_LEVEL_ERROR, PSTR("SHT: ShutterPercentToRealPosition: FAIL")); - } - ShutterSettings.shuttercoeff[0][0] = 0; - for (uint8_t i=0; i<2 ; i++){ - snprintf_P(svalue, sizeof(svalue), PSTR(D_PRFX_SHUTTER D_CMND_SHUTTER_OPENTIME "%d %d"), 1, 12); - ExecuteCommand(svalue, SRC_SHUTTER); - ShutterInit(); - for (uint8_t j=0; j<2 ; j++){ - for (uint8_t k=0; k<10 ; k++){ - result += (output_percent[k] == ShutterRealToPercentPosition(result_percent[i][j][k] , 0) ? 0 : 1); - AddLog(LOG_LEVEL_ERROR, PSTR("SHT: ShutterRealToPercentPosition error %d: %d <-> %d"),result, ShutterRealToPercentPosition(result_percent[i][j][k] , 0), output_percent[k]); - } - snprintf_P(svalue, sizeof(svalue), PSTR(D_PRFX_SHUTTER D_CMND_SHUTTER_OPENTIME "%d %d"), 1, 180); - ExecuteCommand(svalue, SRC_SHUTTER); - } - snprintf_P(svalue, sizeof(svalue), PSTR(D_PRFX_SHUTTER D_CMND_SHUTTER_CLIBRATION "%d %s"), 1, "15 83 105 185 210"); - ExecuteCommand(svalue, SRC_SHUTTER); - } - if (!result){ - AddLog(LOG_LEVEL_ERROR, PSTR("SHT: ShutterRealToPercentPosition: PASS")); - } else { - AddLog(LOG_LEVEL_ERROR, PSTR("SHT: ShutterRealToPercentPosition: FAIL")); - } -} -#else -void CmndShutterUnitTest(void) {} -#endif - -#endif // ESP32