diff --git a/extras/test/RollerShutterTests/roller_shutter_tests.cpp b/extras/test/RollerShutterTests/roller_shutter_tests.cpp index ecb33d8e..77dec990 100644 --- a/extras/test/RollerShutterTests/roller_shutter_tests.cpp +++ b/extras/test/RollerShutterTests/roller_shutter_tests.cpp @@ -65,10 +65,12 @@ TEST_F(RollerShutterFixture, basicTests) { SUPLA_CHANNELFNC_CONTROLLINGTHEROLLERSHUTTER); EXPECT_EQ(rs.getChannel()->getFlags(), SUPLA_CHANNEL_FLAG_CHANNELSTATE | - SUPLA_CHANNEL_FLAG_RS_SBS_AND_STOP_ACTIONS); - EXPECT_EQ(0, memcmp(Supla::RegisterDevice::getChannelValuePtr(number), - &value, - SUPLA_CHANNELVALUE_SIZE)); + SUPLA_CHANNEL_FLAG_RS_SBS_AND_STOP_ACTIONS | + SUPLA_CHANNEL_FLAG_CALCFG_RECALIBRATE); + EXPECT_EQ(0, + memcmp(Supla::RegisterDevice::getChannelValuePtr(number), + &value, + SUPLA_CHANNELVALUE_SIZE)); } TEST_F(RollerShutterFixture, onInitHighIsOn) { @@ -136,9 +138,11 @@ TEST_F(RollerShutterFixture, notCalibratedStartup) { } TDSC_RollerShutterValue value = {}; - EXPECT_EQ(0, memcmp(Supla::RegisterDevice::getChannelValuePtr(0), - &value, - SUPLA_CHANNELVALUE_SIZE)); + value.position = -1; + TDSC_RollerShutterValue *valuePtr = + reinterpret_cast( + Supla::RegisterDevice::getChannelValuePtr(0)); + EXPECT_EQ(0, memcmp(valuePtr, &value, SUPLA_CHANNELVALUE_SIZE)); rs.handleAction(0, Supla::MOVE_DOWN); for (int i = 0; i < 10; i++) { @@ -146,13 +150,12 @@ TEST_F(RollerShutterFixture, notCalibratedStartup) { time.advance(100); } - value.position = -1; EXPECT_EQ(0, memcmp(Supla::RegisterDevice::getChannelValuePtr(0), &value, SUPLA_CHANNELVALUE_SIZE)); rs.handleAction(0, Supla::MOVE_UP); - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 100; i++) { rs.onTimer(); time.advance(100); } diff --git a/src/supla/control/roller_shutter.cpp b/src/supla/control/roller_shutter.cpp index 3c2d9656..416eca22 100644 --- a/src/supla/control/roller_shutter.cpp +++ b/src/supla/control/roller_shutter.cpp @@ -21,6 +21,9 @@ #include #include #include +#include + +#include "../actions.h" namespace Supla { namespace Control { @@ -47,6 +50,7 @@ RollerShutter::RollerShutter(int pinUp, int pinDown, bool highIsOn) channel.setDefault(SUPLA_CHANNELFNC_CONTROLLINGTHEROLLERSHUTTER); channel.setFuncList(SUPLA_BIT_FUNC_CONTROLLINGTHEROLLERSHUTTER); channel.setFlag(SUPLA_CHANNEL_FLAG_RS_SBS_AND_STOP_ACTIONS); + channel.setFlag(SUPLA_CHANNEL_FLAG_CALCFG_RECALIBRATE); } void RollerShutter::onInit() { @@ -56,11 +60,32 @@ void RollerShutter::onInit() { channel.getChannelNumber(), pinDown, highIsOn ? LOW : HIGH, io); Supla::Io::pinMode(channel.getChannelNumber(), pinUp, OUTPUT, io); Supla::Io::pinMode(channel.getChannelNumber(), pinDown, OUTPUT, io); + + if (upButton) { + upButton->onInit(); // make sure button was initialized + if (upButton->isMonostable()) { + upButton->addAction( + Supla::MOVE_UP_OR_STOP, this, Supla::CONDITIONAL_ON_PRESS); + } else if (upButton->isBistable()) { + upButton->addAction( + Supla::MOVE_UP_OR_STOP, this, Supla::CONDITIONAL_ON_CHANGE); + } + } + if (downButton) { + downButton->onInit(); // make sure button was initialized + if (downButton->isMonostable()) { + downButton->addAction( + Supla::MOVE_DOWN_OR_STOP, this, Supla::CONDITIONAL_ON_PRESS); + } else if (downButton->isBistable()) { + downButton->addAction( + Supla::MOVE_DOWN_OR_STOP, this, Supla::CONDITIONAL_ON_CHANGE); + } + } } -/* - * Protocol: - * value[0]: + /* + * Protocol: + * value[0]: * 0 - stop * 1 - down * 2 - up @@ -79,7 +104,7 @@ int32_t RollerShutter::handleNewValueFromServer( setOpenCloseTime(newClosingTime, newOpeningTime); char task = newValue->value[0]; - SUPLA_LOG_DEBUG("RollerShutter[%d] new value from server: %d", + SUPLA_LOG_DEBUG("RS[%d] new value from server: %d", channel.getChannelNumber(), task); switch (task) { case 0: { @@ -137,20 +162,12 @@ int32_t RollerShutter::handleNewValueFromServer( void RollerShutter::setOpenCloseTime(uint32_t newClosingTimeMs, uint32_t newOpeningTimeMs) { - if (newClosingTimeMs == 0) { - newClosingTimeMs = closingTimeMs; - } - if (newOpeningTimeMs == 0) { - newOpeningTimeMs = openingTimeMs; - } - if (newClosingTimeMs != closingTimeMs || newOpeningTimeMs != openingTimeMs) { closingTimeMs = newClosingTimeMs; openingTimeMs = newOpeningTimeMs; - calibrate = true; - currentPosition = UNKNOWN_POSITION; + triggerCalibration(); SUPLA_LOG_DEBUG( - "RollerShutter[%d] new time settings received. Opening time: %d ms; " + "RS[%d] new time settings received. Opening time: %d ms; " "closing time: %d ms. Starting calibration...", channel.getChannelNumber(), openingTimeMs, @@ -327,12 +344,14 @@ void RollerShutter::relayUpOff() { } void RollerShutter::startClosing() { + lastMovementStartTime = millis(); currentDirection = DOWN_DIR; relayUpOff(); // just to make sure relayDownOn(); } void RollerShutter::startOpening() { + lastMovementStartTime = millis(); currentDirection = UP_DIR; relayDownOff(); // just to make sure relayUpOn(); @@ -343,157 +362,221 @@ void RollerShutter::switchOffRelays() { relayDownOff(); } +void RollerShutter::triggerCalibration() { + calibrate = true; + currentPosition = UNKNOWN_POSITION; + setTargetPosition(0); +} + +bool RollerShutter::isCalibrationRequested() const { + return calibrate && openingTimeMs != 0 && closingTimeMs != 0; +} + +bool RollerShutter::isCalibrated() const { + return calibrate == false && openingTimeMs != 0 && closingTimeMs != 0; +} + void RollerShutter::onTimer() { - if (millis() - doNothingTime < + if (doNothingTime != 0 && millis() - doNothingTime < 500) { // doNothingTime time is used when we change // direction of roller - to stop for a moment // before enabling opposite direction return; } + doNothingTime = 0; - if (operationTimeout != 0 && - millis() - lastMovementStartTime > operationTimeout) { + if (operationTimeoutS != 0 && + millis() - lastMovementStartTime > operationTimeoutS * 1000) { setTargetPosition(STOP_POSITION); - operationTimeout = 0; + operationTimeoutS = 0; + SUPLA_LOG_DEBUG("RS[%d]: Operation timeout", channel.getChannelNumber()); } if (targetPosition == STOP_POSITION && inMove()) { stopMovement(); calibrationTime = 0; + SUPLA_LOG_DEBUG("RS[%d]: Stop movement", channel.getChannelNumber()); + } + if (targetPosition == STOP_POSITION) { + newTargetPositionAvailable = false; } - if (calibrate && targetPosition == STOP_POSITION) { - return; - } else if (calibrate) { - // If calibrationTime is not set, then it means we should start calibration - if (calibrationTime == 0) { - // If roller shutter wasn't in move when calibration is requested, we - // select direction based on requested targetPosition - operationTimeout = 0; - if (targetPosition > 50 || targetPosition == MOVE_DOWN_POSITION) { - if (currentDirection == UP_DIR) { - stopMovement(); - } else if (currentDirection == STOP_DIR) { - SUPLA_LOG_DEBUG("Calibration: closing"); - calibrationTime = closingTimeMs; - lastMovementStartTime = millis(); - if (calibrationTime == 0) { - operationTimeout = 60000; + if (isCalibrationRequested()) { + if (targetPosition != STOP_POSITION) { + // If calibrationTime is not set, then it means we should start + // calibration + if (calibrationTime == 0) { + // If roller shutter wasn't in move when calibration is requested, we + // select direction based on requested targetPosition + operationTimeoutS = 0; + if (targetPosition > 50 || targetPosition == MOVE_DOWN_POSITION) { + if (currentDirection == UP_DIR) { + stopMovement(); + } else if (currentDirection == STOP_DIR) { + SUPLA_LOG_DEBUG("RS[%d]: Calibration: closing", + channel.getChannelNumber()); + calibrationTime = closingTimeMs; + if (calibrationTime == 0) { + operationTimeoutS = 60; + } + startClosing(); } - startClosing(); - } - } else { - if (currentDirection == DOWN_DIR) { - stopMovement(); - } else if (currentDirection == STOP_DIR) { - SUPLA_LOG_DEBUG("Calibration: opening"); - calibrationTime = openingTimeMs; - lastMovementStartTime = millis(); - if (calibrationTime == 0) { - operationTimeout = 60000; + } else { + if (currentDirection == DOWN_DIR) { + stopMovement(); + } else if (currentDirection == STOP_DIR) { + SUPLA_LOG_DEBUG("RS[%d]: Calibration: opening", + channel.getChannelNumber()); + calibrationTime = openingTimeMs; + if (calibrationTime == 0) { + operationTimeoutS = 60; + } + startOpening(); } - startOpening(); + } + // + // Time used for calibaration is 10% higher then requested by user + calibrationTime *= 1.1; + if (calibrationTime > 0) { + SUPLA_LOG_DEBUG("RS[%d]: Calibration time: %d", + channel.getChannelNumber(), + calibrationTime); } } - // - // Time used for calibaration is 10% higher then requested by user - calibrationTime *= 1.1; - if (calibrationTime > 0) { - SUPLA_LOG_DEBUG("Calibration time: %d", calibrationTime); + + if (calibrationTime != 0 && + millis() - lastMovementStartTime > calibrationTime) { + SUPLA_LOG_DEBUG("RS[%d]: Calibration done", channel.getChannelNumber()); + calibrationTime = 0; + calibrate = false; + if (currentDirection == UP_DIR) { + currentPosition = 0; + } else { + currentPosition = 100; + } + stopMovement(); } } + } else if (isCalibrated()) { + if (!newTargetPositionAvailable && + currentDirection != + STOP_DIR) { // no new command available and it is moving, + // just handle roller movement/status + if (currentDirection == UP_DIR && currentPosition > 0) { + int movementDistance = lastPositionBeforeMovement; + uint32_t timeRequired = + (1.0 * openingTimeMs * movementDistance / 100.0); + float fractionOfMovemendDone = + (1.0 * (millis() - lastMovementStartTime) / timeRequired); + if (fractionOfMovemendDone > 1) { + fractionOfMovemendDone = 1; + } + currentPosition = lastPositionBeforeMovement - + movementDistance * fractionOfMovemendDone; + if (targetPosition >= 0 && currentPosition <= targetPosition) { + stopMovement(); + } + } else if (currentDirection == DOWN_DIR && currentPosition < 100) { + int movementDistance = 100 - lastPositionBeforeMovement; + uint32_t timeRequired = + (1.0 * closingTimeMs * movementDistance / 100.0); + float fractionOfMovemendDone = + (1.0 * (millis() - lastMovementStartTime) / timeRequired); + if (fractionOfMovemendDone > 1) { + fractionOfMovemendDone = 1; + } + currentPosition = lastPositionBeforeMovement + + movementDistance * fractionOfMovemendDone; + if (targetPosition >= 0 && currentPosition >= targetPosition) { + stopMovement(); + } + } - if (calibrationTime != 0 && - millis() - lastMovementStartTime > calibrationTime) { - SUPLA_LOG_DEBUG("Calibration done"); - calibrationTime = 0; - calibrate = false; - if (currentDirection == UP_DIR) { - currentPosition = 0; - } else { + if (currentPosition > 100) { currentPosition = 100; + } else if (currentPosition < 0) { + currentPosition = 0; } - stopMovement(); - } - } else if (!newTargetPositionAvailable && - currentDirection != - STOP_DIR) { // no new command available and it is moving, - // just handle roller movement/status - if (currentDirection == UP_DIR && currentPosition > 0) { - int movementDistance = lastPositionBeforeMovement; - uint32_t timeRequired = (1.0 * openingTimeMs * movementDistance / 100.0); - float fractionOfMovemendDone = - (1.0 * (millis() - lastMovementStartTime) / timeRequired); - if (fractionOfMovemendDone > 1) { - fractionOfMovemendDone = 1; - } - currentPosition = lastPositionBeforeMovement - - movementDistance * fractionOfMovemendDone; - if (targetPosition >= 0 && currentPosition <= targetPosition) { - stopMovement(); - } - } else if (currentDirection == DOWN_DIR && currentPosition < 100) { - int movementDistance = 100 - lastPositionBeforeMovement; - uint32_t timeRequired = (1.0 * closingTimeMs * movementDistance / 100.0); - float fractionOfMovemendDone = - (1.0 * (millis() - lastMovementStartTime) / timeRequired); - if (fractionOfMovemendDone > 1) { - fractionOfMovemendDone = 1; + } else if (newTargetPositionAvailable && targetPosition != STOP_POSITION) { + // new target state was set, let's handle it + int newDirection = STOP_DIR; + if (targetPosition == MOVE_UP_POSITION) { + newDirection = UP_DIR; + operationTimeoutS = 60; + } else if (targetPosition == MOVE_DOWN_POSITION) { + newDirection = DOWN_DIR; + operationTimeoutS = 60; + } else { + operationTimeoutS = 0; + int newMovementValue = targetPosition - currentPosition; + // 0 - 100 = -100 (move down); 50 - + // 20 = 30 (move up 30%), etc + if (newMovementValue > 0) { + newDirection = DOWN_DIR; // move down + } else if (newMovementValue < 0) { + newDirection = UP_DIR; // move up + } } - currentPosition = lastPositionBeforeMovement + - movementDistance * fractionOfMovemendDone; - if (targetPosition >= 0 && currentPosition >= targetPosition) { + // If new direction is the same as current move, then keep movin` + if (newDirection == currentDirection) { + newTargetPositionAvailable = false; + } else if (currentDirection == STOP_DIR) { // else start moving + newTargetPositionAvailable = false; + lastPositionBeforeMovement = currentPosition; + if (newDirection == DOWN_DIR) { + startClosing(); + } else { + startOpening(); + } + } else { // else stop before changing direction stopMovement(); } } - - if (currentPosition > 100) { - currentPosition = 100; - } else if (currentPosition < 0) { - currentPosition = 0; - } - - } else if (newTargetPositionAvailable && targetPosition != STOP_POSITION) { - // new target state was set, let's handle it - int newDirection = STOP_DIR; - if (targetPosition == MOVE_UP_POSITION) { - newDirection = UP_DIR; - operationTimeout = 60000; - } else if (targetPosition == MOVE_DOWN_POSITION) { - newDirection = DOWN_DIR; - operationTimeout = 60000; - } else { - operationTimeout = 0; - int newMovementValue = targetPosition - currentPosition; - // 0 - 100 = -100 (move down); 50 - - // 20 = 30 (move up 30%), etc - if (newMovementValue > 0) { - newDirection = DOWN_DIR; // move down - } else if (newMovementValue < 0) { - newDirection = UP_DIR; // move up - } - } - // If new direction is the same as current move, then keep movin` - if (newDirection == currentDirection) { - newTargetPositionAvailable = false; - } else if (currentDirection == STOP_DIR) { // else start moving - newTargetPositionAvailable = false; - lastPositionBeforeMovement = currentPosition; - lastMovementStartTime = millis(); - if (newDirection == DOWN_DIR) { - startClosing(); + } else { + // RS is not calibrated + currentPosition = UNKNOWN_POSITION; + if (newTargetPositionAvailable) { + int newDirection = STOP_DIR; + operationTimeoutS = 60; + if (targetPosition == MOVE_UP_POSITION) { + newDirection = UP_DIR; + } else if (targetPosition == MOVE_DOWN_POSITION) { + newDirection = DOWN_DIR; } else { - startOpening(); + // 0 - 100 = -100 (move down); 50 - + // 20 = 30 (move up 30%), etc + if (targetPosition == 0) { + newDirection = UP_DIR; // move up + } else if (targetPosition == 100) { + newDirection = DOWN_DIR; // move down + } + } + // If new direction is the same as current move, then keep movin` + if (newDirection == currentDirection) { + newTargetPositionAvailable = false; + } else if (currentDirection == STOP_DIR) { // else start moving + newTargetPositionAvailable = false; + lastPositionBeforeMovement = currentPosition; + if (newDirection == DOWN_DIR) { + startClosing(); + } else { + startOpening(); + } + } else { // else stop before changing direction + stopMovement(); + operationTimeoutS = 0; } - } else { // else stop before changing direction - stopMovement(); } } + // if (newCurrentPosition != currentPosition) { // currentPosition = newCurrentPosition; TDSC_RollerShutterValue value = {}; value.position = currentPosition; + if (calibrationTime != 0) { + value.flags |= RS_VALUE_FLAG_CALIBRATION_IN_PROGRESS; + } channel.setNewValue(value); } @@ -521,7 +604,7 @@ void RollerShutter::onLoadState() { calibrate = false; } SUPLA_LOG_DEBUG( - "RollerShutter[%d] settings restored from storage. Opening time: %d " + "RS[%d] settings restored from storage. Opening time: %d " "ms; closing time: %d ms. Position: %d", channel.getChannelNumber(), openingTimeMs, @@ -555,5 +638,26 @@ uint32_t RollerShutter::getOpeningTimeMs() const { return openingTimeMs; } +void RollerShutter::attach(Supla::Control::Button *up, + Supla::Control::Button *down) { + upButton = up; + downButton = down; +} + +int RollerShutter::handleCalcfgFromServer(TSD_DeviceCalCfgRequest *request) { + if (request) { + if (request->Command == SUPLA_CALCFG_CMD_RECALIBRATE) { + if (!request->SuperUserAuthorized) { + return SUPLA_CALCFG_RESULT_UNAUTHORIZED; + } + SUPLA_LOG_INFO("RS[%d] - CALCFG recalibrate received", + channel.getChannelNumber()); + triggerCalibration(); + return SUPLA_CALCFG_RESULT_DONE; + } + } + return SUPLA_CALCFG_RESULT_FALSE; +} + } // namespace Control } // namespace Supla diff --git a/src/supla/control/roller_shutter.h b/src/supla/control/roller_shutter.h index 73aa89fc..49e23ead 100644 --- a/src/supla/control/roller_shutter.h +++ b/src/supla/control/roller_shutter.h @@ -20,7 +20,6 @@ #include #include "../action_handler.h" -#include "../actions.h" #include "../channel_element.h" #define UNKNOWN_POSITION -1 @@ -34,6 +33,8 @@ class Io; namespace Control { +class Button; + enum Directions { STOP_DIR = 0, DOWN_DIR = 1, UP_DIR = 2 }; class RollerShutter : public ChannelElement, public ActionHandler { @@ -46,6 +47,7 @@ class RollerShutter : public ChannelElement, public ActionHandler { int32_t handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue) override; void handleAction(int event, int action) override; + int handleCalcfgFromServer(TSD_DeviceCalCfgRequest *request) override; void close(); // Sets target position to 100% void open(); // Sets target position to 0% @@ -71,6 +73,12 @@ class RollerShutter : public ChannelElement, public ActionHandler { uint32_t getClosingTimeMs() const; uint32_t getOpeningTimeMs() const; + void attach(Supla::Control::Button *up, Supla::Control::Button *down); + + void triggerCalibration(); + bool isCalibrationRequested() const; + bool isCalibrated() const; + protected: virtual void stopMovement(); virtual void relayDownOn(); @@ -96,7 +104,7 @@ class RollerShutter : public ChannelElement, public ActionHandler { uint32_t calibrationTime = 0; Supla::Io *io = nullptr; - uint16_t operationTimeout = 0; + uint16_t operationTimeoutS = 0; bool calibrate = true; // set to true when new closing/opening time is given - @@ -115,6 +123,9 @@ class RollerShutter : public ChannelElement, public ActionHandler { int8_t currentPosition = UNKNOWN_POSITION; // 0 - closed; 100 - opened int8_t targetPosition = STOP_POSITION; // 0-100 int8_t lastPositionBeforeMovement = UNKNOWN_POSITION; // 0-100 + + Supla::Control::Button *upButton = nullptr; + Supla::Control::Button *downButton = nullptr; }; } // namespace Control