diff --git a/extras/test/ButtonTests/button_tests.cpp b/extras/test/ButtonTests/button_tests.cpp new file mode 100644 index 00000000..580e6653 --- /dev/null +++ b/extras/test/ButtonTests/button_tests.cpp @@ -0,0 +1,464 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include +#include + +using ::testing::Return; + +class ActionHandlerMock : public Supla::ActionHandler { + public: + MOCK_METHOD(void, handleAction, (int, int), (override)); +}; + +class TimeInterfaceStub2 : public TimeInterface { + public: + TimeInterfaceStub2() : value(0) {} + + virtual unsigned long millis() override { + return value; + } + + void advance(int advanceMs) { + value += advanceMs; + } + + unsigned long value; +}; + +TEST(ButtonTests, OnPressAndOnRelease) { + TimeInterfaceStub2 time; + DigitalInterfaceMock ioMock; + ActionHandlerMock mock1; + + EXPECT_CALL(ioMock, pinMode(5, INPUT)); + EXPECT_CALL(ioMock, digitalRead(5)) + .WillOnce(Return(0)) // #1 + .WillOnce(Return(1)) // #2 time 0 - first read + .WillOnce(Return(1)) // #3 second read, should be ignored + .WillOnce(Return(1)) // #4 third read, should trigger on_press + .WillOnce(Return(1)) // #5 time 90 + .WillOnce(Return(0)) // #6 time 100 + .WillOnce(Return(0)) // #7 time 110 + .WillOnce(Return(0)) // #8 time 150 + ; + + EXPECT_CALL(mock1, handleAction(Supla::ON_PRESS, 1)).Times(1); + EXPECT_CALL(mock1, handleAction(Supla::ON_CHANGE, 2)).Times(2); + EXPECT_CALL(mock1, handleAction(Supla::ON_RELEASE, 3)).Times(1); + + Supla::Control::Button button(5, false, false); + button.onInit(); // #1 + button.addAction(1, mock1, Supla::ON_PRESS); + button.addAction(2, mock1, Supla::ON_CHANGE); + button.addAction(3, mock1, Supla::ON_RELEASE); + + time.advance(1000); + button.onTimer(); // #2 + time.advance(10); + button.onTimer(); // #3 within filtering time - nothing should happen + time.advance(20); + button.onTimer(); // #4 ON_PRESS + time.advance(60); + button.onTimer(); // #5 + time.advance(10); + button.onTimer(); // #6 new state candidate + time.advance(10); + button.onTimer(); // #7 + time.advance(20); + button.onTimer(); // #8 on release + time.advance(30); + button.onTimer(); // # + time.advance(10); + button.onTimer(); + time.advance(10); + button.onTimer(); +} + +TEST(ButtonTests, OnHold) { + TimeInterfaceStub2 time; + DigitalInterfaceMock ioMock; + ActionHandlerMock mock1; + + EXPECT_CALL(ioMock, pinMode(5, INPUT)); + EXPECT_CALL(ioMock, digitalRead(5)) + .WillOnce(Return(0)) // #1 onInit + .WillOnce(Return(1)) // #2 time 0 - first read + .WillOnce(Return(1)) // #3 + .WillOnce(Return(1)) // #4 + .WillOnce(Return(1)) // #5 + .WillOnce(Return(1)) // #6 + .WillOnce(Return(0)) // #7 + .WillOnce(Return(0)) // #8 + .WillOnce(Return(0)) // #9 + // second round + .WillOnce(Return(1)) // #2 time 0 - first read + .WillOnce(Return(1)) // #3 + .WillOnce(Return(1)) // #4 + .WillOnce(Return(1)) // #5 + .WillOnce(Return(1)) // #6 + .WillOnce(Return(0)) // #7 + .WillOnce(Return(0)) // #8 + .WillOnce(Return(0)) // #9 + ; + + + EXPECT_CALL(mock1, handleAction(Supla::ON_PRESS, 1)).Times(2); + EXPECT_CALL(mock1, handleAction(Supla::ON_CHANGE, 2)).Times(4); + EXPECT_CALL(mock1, handleAction(Supla::ON_RELEASE, 3)).Times(2); + EXPECT_CALL(mock1, handleAction(Supla::ON_HOLD, 4)).Times(2); + EXPECT_CALL(mock1, handleAction(Supla::ON_LONG_CLICK_0, 5)).Times(2); + + Supla::Control::Button button(5, false, false); + button.setHoldTime(500); + button.setMulticlickTime(300); + button.onInit(); + button.addAction(1, mock1, Supla::ON_PRESS); + button.addAction(2, mock1, Supla::ON_CHANGE); + button.addAction(3, mock1, Supla::ON_RELEASE); + button.addAction(4, mock1, Supla::ON_HOLD); + button.addAction(5, mock1, Supla::ON_LONG_CLICK_0); + button.addAction(6, mock1, Supla::ON_LONG_CLICK_1); + button.addAction(7, mock1, Supla::ON_LONG_CLICK_2); + button.addAction(8, mock1, Supla::ON_LONG_CLICK_3); + + + for (int i = 0; i < 2; i++) { + time.advance(1000); + button.onTimer(); // #2 + time.advance(30); + button.onTimer(); // #3 ON_PRESS + time.advance(60); + button.onTimer(); // #4 + time.advance(450); + button.onTimer(); // #5 ON_HOLD + time.advance(10000); + button.onTimer(); // #6 + time.advance(10); + button.onTimer(); // #7 new state candidate + time.advance(100); + button.onTimer(); // #8 ON_RELEASE + time.advance(500); + button.onTimer(); // #9 + } +} + +TEST(ButtonTests, OnHoldRepeated) { + TimeInterfaceStub2 time; + DigitalInterfaceMock ioMock; + ActionHandlerMock mock1; + + EXPECT_CALL(ioMock, pinMode(5, INPUT)); + EXPECT_CALL(ioMock, digitalRead(5)) + .WillOnce(Return(0)) // #1 onInit + .WillOnce(Return(1)) // #2 time 0 - first read + .WillOnce(Return(1)) // #3 + .WillOnce(Return(1)) // #4 + .WillOnce(Return(1)) // #5 + .WillOnce(Return(1)) // #6 + .WillOnce(Return(1)) // #7 + .WillOnce(Return(1)) // #8 + .WillOnce(Return(0)) // #9 + .WillOnce(Return(0)) // #10 + ; + + + EXPECT_CALL(mock1, handleAction(Supla::ON_PRESS, 1)).Times(1); + EXPECT_CALL(mock1, handleAction(Supla::ON_CHANGE, 2)).Times(2); + EXPECT_CALL(mock1, handleAction(Supla::ON_RELEASE, 3)).Times(1); + EXPECT_CALL(mock1, handleAction(Supla::ON_HOLD, 4)).Times(3); + EXPECT_CALL(mock1, handleAction(Supla::ON_LONG_CLICK_0, 5)).Times(0); + + Supla::Control::Button button(5, false, false); + button.setHoldTime(500); + button.repeatOnHoldEvery(100); + button.onInit(); + button.addAction(1, mock1, Supla::ON_PRESS); + button.addAction(2, mock1, Supla::ON_CHANGE); + button.addAction(3, mock1, Supla::ON_RELEASE); + button.addAction(4, mock1, Supla::ON_HOLD); + button.addAction(5, mock1, Supla::ON_LONG_CLICK_0); + + time.advance(1000); + button.onTimer(); // #2 + time.advance(30); + button.onTimer(); // #3 ON_PRESS + time.advance(60); + button.onTimer(); // #4 + time.advance(450); + button.onTimer(); // #5 ON_HOLD + time.advance(110); + button.onTimer(); // #6 ON_HOLD + time.advance(10); + button.onTimer(); // #7 + time.advance(100); + button.onTimer(); // #8 ON_HOLD + time.advance(10); + button.onTimer(); // #9 new state candidate + time.advance(30); + button.onTimer(); // #10 ON_RELEASE +} + +TEST(ButtonTests, Multiclick) { + TimeInterfaceStub2 time; + DigitalInterfaceMock ioMock; + ActionHandlerMock mock1; + + EXPECT_CALL(ioMock, pinMode(5, INPUT)); + EXPECT_CALL(ioMock, digitalRead(5)) + .WillOnce(Return(0)) // #1 onInit + .WillOnce(Return(1)) // #2 time 0 - first read + .WillOnce(Return(1)) // #3 click 1 + .WillOnce(Return(0)) // #4 + .WillOnce(Return(0)) // #5 release + .WillOnce(Return(1)) // #6 + .WillOnce(Return(1)) // #7 click 2 + .WillOnce(Return(0)) // #8 + .WillOnce(Return(0)) // #9 release + .WillOnce(Return(0)) // #10 some time -> ON_CLICK_2 + ; + + + EXPECT_CALL(mock1, handleAction(Supla::ON_CLICK_2, 1)).Times(1); + EXPECT_CALL(mock1, handleAction(Supla::ON_HOLD, 4)).Times(0); + + Supla::Control::Button button(5, false, false); + button.setHoldTime(700); + button.setMulticlickTime(300); + button.onInit(); + button.addAction(4, mock1, Supla::ON_HOLD); + button.addAction(1, mock1, Supla::ON_CLICK_1); + button.addAction(1, mock1, Supla::ON_CLICK_2); + button.addAction(1, mock1, Supla::ON_CLICK_3); + button.addAction(1, mock1, Supla::ON_CLICK_4); + button.addAction(1, mock1, Supla::ON_CLICK_5); + button.addAction(1, mock1, Supla::ON_CLICK_6); + button.addAction(1, mock1, Supla::ON_CLICK_7); + + time.advance(1000); + button.onTimer(); // #2 + time.advance(30); // filtering + button.onTimer(); // #3 click 1 + time.advance(60); // debounce + button.onTimer(); // #4 + time.advance(30); // filtering + button.onTimer(); // #5 release + time.advance(60); // debounce + button.onTimer(); // #6 + time.advance(30); // filtering + button.onTimer(); // #7 click 2 + time.advance(60); // debounce + button.onTimer(); // #8 + time.advance(30); // filtering + button.onTimer(); // #9 release + time.advance(500); + button.onTimer(); // #10 ON_CLICK_2 +} + +TEST(ButtonTests, OnHoldSendsLongMulticlick) { + TimeInterfaceStub2 time; + DigitalInterfaceMock ioMock; + ActionHandlerMock mock1; + + EXPECT_CALL(ioMock, pinMode(5, INPUT)); + EXPECT_CALL(ioMock, digitalRead(5)) + .WillOnce(Return(0)) // #1 onInit + .WillOnce(Return(1)) // #2 time 0 - first read + .WillOnce(Return(1)) // #3 click 1 + .WillOnce(Return(1)) // #4 ON_HOLD + .WillOnce(Return(0)) // #5 + .WillOnce(Return(0)) // #6 release + .WillOnce(Return(1)) // #7 + .WillOnce(Return(1)) // #8 click 1 + .WillOnce(Return(0)) // #9 + .WillOnce(Return(0)) // #10 release + .WillOnce(Return(1)) // #11 + .WillOnce(Return(1)) // #12 click 2 + .WillOnce(Return(0)) // #13 + .WillOnce(Return(0)) // #14 release + .WillOnce(Return(0)) // #15 some time -> ON_LONG_CLICK_2 + ; + + + EXPECT_CALL(mock1, handleAction(Supla::ON_CLICK_2, 1)).Times(0); + EXPECT_CALL(mock1, handleAction(Supla::ON_HOLD, 4)).Times(1); + EXPECT_CALL(mock1, handleAction(Supla::ON_LONG_CLICK_2, 5)).Times(1); + + Supla::Control::Button button(5, false, false); + button.setHoldTime(700); + button.setMulticlickTime(300); + button.onInit(); // #1 + button.addAction(4, mock1, Supla::ON_HOLD); + button.addAction(1, mock1, Supla::ON_CLICK_1); + button.addAction(1, mock1, Supla::ON_CLICK_2); + button.addAction(1, mock1, Supla::ON_CLICK_3); + button.addAction(1, mock1, Supla::ON_CLICK_4); + button.addAction(1, mock1, Supla::ON_CLICK_5); + button.addAction(1, mock1, Supla::ON_CLICK_6); + button.addAction(1, mock1, Supla::ON_CLICK_7); + button.addAction(6, mock1, Supla::ON_LONG_CLICK_1); + button.addAction(5, mock1, Supla::ON_LONG_CLICK_2); + button.addAction(7, mock1, Supla::ON_LONG_CLICK_3); + + time.advance(1000); + button.onTimer(); // #2 + time.advance(30); // filtering + button.onTimer(); // #3 click 1 + time.advance(800); // + button.onTimer(); // #4 ON_HOLD + time.advance(60); + button.onTimer(); + time.advance(30); // filtering + button.onTimer(); // #5 release + time.advance(60); // debounce + button.onTimer(); // #6 + time.advance(30); // filtering + button.onTimer(); // #7 click 2 + time.advance(60); // debounce + button.onTimer(); // #8 + time.advance(30); // filtering + button.onTimer(); // #9 release + time.advance(60); + button.onTimer(); // #10 + time.advance(30); // filtering + button.onTimer(); // #11 click 2 + time.advance(60); // debounce + button.onTimer(); // #12 + time.advance(30); // filtering + button.onTimer(); // #13 release + time.advance(500); + button.onTimer(); // #14 ON_LONG_CLICK_2 +} + +TEST(ButtonTests, BistableMulticlick) { + TimeInterfaceStub2 time; + DigitalInterfaceMock ioMock; + ActionHandlerMock mock1; + + EXPECT_CALL(ioMock, pinMode(5, INPUT)); + EXPECT_CALL(ioMock, digitalRead(5)) + .WillOnce(Return(0)) // #1 onInit + .WillOnce(Return(1)) // #2 time 0 - first read + .WillOnce(Return(1)) // #3 click 1 + .WillOnce(Return(0)) // #4 + .WillOnce(Return(0)) // #5 release + .WillOnce(Return(1)) // #6 + .WillOnce(Return(1)) // #7 click 2 + .WillOnce(Return(0)) // #8 + .WillOnce(Return(0)) // #9 release + .WillOnce(Return(0)) // #10 some time -> ON_CLICK_2 + ; + + + EXPECT_CALL(mock1, handleAction(Supla::ON_CLICK_4, 1)).Times(1); + EXPECT_CALL(mock1, handleAction(Supla::ON_HOLD, 4)).Times(0); + + Supla::Control::Button button(5, false, false); + button.setHoldTime(700); // should be ignored + button.setMulticlickTime(300, true); + + button.onInit(); + button.addAction(4, mock1, Supla::ON_HOLD); + button.addAction(1, mock1, Supla::ON_CLICK_1); + button.addAction(1, mock1, Supla::ON_CLICK_2); + button.addAction(1, mock1, Supla::ON_CLICK_3); + button.addAction(1, mock1, Supla::ON_CLICK_4); + button.addAction(1, mock1, Supla::ON_CLICK_5); + button.addAction(1, mock1, Supla::ON_CLICK_6); + button.addAction(1, mock1, Supla::ON_CLICK_7); + + time.advance(1000); + button.onTimer(); // #2 + time.advance(30); // filtering + button.onTimer(); // #3 click 1 + time.advance(60); // debounce + button.onTimer(); // #4 + time.advance(30); // filtering + button.onTimer(); // #5 release + time.advance(60); // debounce + button.onTimer(); // #6 + time.advance(30); // filtering + button.onTimer(); // #7 click 2 + time.advance(60); // debounce + button.onTimer(); // #8 + time.advance(30); // filtering + button.onTimer(); // #9 release + time.advance(500); + button.onTimer(); // #10 ON_CLICK_4 +} + +TEST(ButtonTests, OnHoldDoesntWorkOnBistableButton) { + TimeInterfaceStub2 time; + DigitalInterfaceMock ioMock; + ActionHandlerMock mock1; + + EXPECT_CALL(ioMock, pinMode(5, INPUT)); + EXPECT_CALL(ioMock, digitalRead(5)) + .WillOnce(Return(0)) // #1 onInit + .WillOnce(Return(1)) // #2 time 0 - first read + .WillOnce(Return(1)) // #3 click 1 + .WillOnce(Return(1)) // #4 ON_HOLD shouldn't happen - there will be ON_CLICK_1 instead + .WillOnce(Return(0)) // #5 + .WillOnce(Return(0)) // #6 + .WillOnce(Return(1)) // #7 + .WillOnce(Return(1)) // #8 + .WillOnce(Return(0)) // #9 + .WillOnce(Return(0)) // #10 release + .WillOnce(Return(0)) // #11 some time -> no ON_CLICK_3 + ; + + + EXPECT_CALL(mock1, handleAction(Supla::ON_CLICK_1, 1)).Times(1); + EXPECT_CALL(mock1, handleAction(Supla::ON_CLICK_3, 1)).Times(1); + EXPECT_CALL(mock1, handleAction(Supla::ON_HOLD, 4)).Times(0); + + Supla::Control::Button button(5, false, false); + button.setHoldTime(700); // should be ignored + button.setMulticlickTime(300, true); + button.onInit(); + button.addAction(4, mock1, Supla::ON_HOLD); + button.addAction(1, mock1, Supla::ON_CLICK_1); + button.addAction(1, mock1, Supla::ON_CLICK_2); + button.addAction(1, mock1, Supla::ON_CLICK_3); + button.addAction(1, mock1, Supla::ON_CLICK_4); + button.addAction(1, mock1, Supla::ON_CLICK_5); + button.addAction(1, mock1, Supla::ON_CLICK_6); + button.addAction(1, mock1, Supla::ON_CLICK_7); + + time.advance(1000); + button.onTimer(); // #2 + time.advance(30); // filtering + button.onTimer(); // #3 click 1 + time.advance(800); // + button.onTimer(); // #4 ON_HOLD + time.advance(60); + button.onTimer(); + time.advance(30); // filtering + button.onTimer(); // #5 release + time.advance(60); // debounce + button.onTimer(); // #6 + time.advance(30); // filtering + button.onTimer(); // #7 click 2 + time.advance(60); // debounce + button.onTimer(); // #8 + time.advance(30); // filtering + button.onTimer(); // #9 release + time.advance(500); + button.onTimer(); // #10 ON_CLICK_2 +} + diff --git a/extras/test/CMakeLists.txt b/extras/test/CMakeLists.txt index d1d31b8c..a715ac5e 100644 --- a/extras/test/CMakeLists.txt +++ b/extras/test/CMakeLists.txt @@ -46,6 +46,7 @@ file(GLOB TEST_SRC RgbwDimmerTests/*.cpp ButtonTests/*.cpp SuplaDeviceTests/*.cpp + CorrectionTests/*cpp ) file(GLOB DOUBLE_SRC doubles/*.cpp) diff --git a/extras/test/ChannelTests/channel_tests.cpp b/extras/test/ChannelTests/channel_tests.cpp index 2dc55f6e..37096ea9 100644 --- a/extras/test/ChannelTests/channel_tests.cpp +++ b/extras/test/ChannelTests/channel_tests.cpp @@ -21,6 +21,7 @@ #include #include #include +#include class ActionHandlerMock : public Supla::ActionHandler { @@ -481,3 +482,32 @@ TEST(ChannelTests, RgbwChannelWithLocalActions) { ch1.setNewValue(10, 20, 30, 90, 81); ch1.setNewValue(10, 20, 30, 90, 81); } + +TEST(ChannelTests, SetNewValueWithCorrection) { + Supla::Channel channel1; + Supla::Channel channel2; + + EXPECT_DOUBLE_EQ(channel1.getValueDouble(), 0); + + double pi = 3.1415; + channel1.setNewValue(pi); + EXPECT_DOUBLE_EQ(channel1.getValueDouble(), pi); + + channel1.setCorrection(3); + EXPECT_DOUBLE_EQ(channel1.getValueDouble(), pi); + + // Now correction should be applied + channel1.setNewValue(pi); + EXPECT_DOUBLE_EQ(channel1.getValueDouble(), pi + 3); + + + double e = 2.71828; + + channel2.setCorrection(2, true); + + channel2.setNewValue(pi, e); + EXPECT_NEAR(channel2.getValueDoubleFirst(), pi, 0.001); + EXPECT_NEAR(channel2.getValueDoubleSecond(), e + 2, 0.001); // value with correction + + Supla::Correction::clear(); // cleanup +} diff --git a/extras/test/ConditionTests/on_invalid_tests.cpp b/extras/test/ConditionTests/on_invalid_tests.cpp new file mode 100644 index 00000000..9cf9bbb0 --- /dev/null +++ b/extras/test/ConditionTests/on_invalid_tests.cpp @@ -0,0 +1,79 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include + +#include +#include +#include +#include + + +class ActionHandlerMock : public Supla::ActionHandler { + public: + MOCK_METHOD(void, handleAction, (int, int), (override)); +}; + + +using ::testing::_; + +TEST(OnInvalidTests, OnInvalidCondition) { + ActionHandlerMock ahMock; + const int action1 = 15; + const int action2 = 16; + const int action3 = 17; + const int action4 = 18; + const int action5 = 19; + const int action6 = 20; + + EXPECT_CALL(ahMock, handleAction(Supla::ON_CHANGE, action3)).Times(1); + EXPECT_CALL(ahMock, handleAction(Supla::ON_CHANGE, action6)).Times(1); + + Supla::ChannelElement channelElement; + auto channel = channelElement.getChannel(); + + auto cond = OnInvalid(); + cond->setSource(channelElement); + cond->setClient(ahMock); + + + channel->setType(SUPLA_CHANNELTYPE_THERMOMETER); + + channel->setNewValue(0.0); + // channel should be initialized to 0, so condition is not met + cond->handleAction(Supla::ON_CHANGE, action1); + + // 100 is valid , nothing should happen + channel->setNewValue(100.0); + cond->handleAction(Supla::ON_CHANGE, action2); + + // invalid valid, should trigger action + channel->setNewValue(-275.0); + cond->handleAction(Supla::ON_CHANGE, action3); + + // it is still invalid, so nothing should happen + cond->handleAction(Supla::ON_CHANGE, action4); + + // nothing should happen + channel->setNewValue(25.0); + cond->handleAction(Supla::ON_CHANGE, action5); + + // invalid valid, should trigger action + channel->setNewValue(-275.0); + cond->handleAction(Supla::ON_CHANGE, action6); + +} diff --git a/extras/test/ConditionTests/on_less_tests.cpp b/extras/test/ConditionTests/on_less_tests.cpp index 9de9744c..64f3da02 100644 --- a/extras/test/ConditionTests/on_less_tests.cpp +++ b/extras/test/ConditionTests/on_less_tests.cpp @@ -184,7 +184,7 @@ TEST(ConditionTests, handleActionTestsForDouble2) { const int action3 = 17; EXPECT_CALL(ahMock, handleAction(Supla::ON_CHANGE, action1)); - EXPECT_CALL(ahMock, handleAction(Supla::ON_CHANGE, action3)); + EXPECT_CALL(ahMock, handleAction(Supla::ON_CHANGE, action3)).Times(2); Supla::ChannelElement channelElement; auto channel = channelElement.getChannel(); @@ -207,7 +207,7 @@ TEST(ConditionTests, handleActionTestsForDouble2) { channel->setNewValue(-275.0); cond->handleAction(Supla::ON_CHANGE, action2); - + // going from "invalid" to valid value meeting contidion should trigger action channel->setNewValue(-15.01); cond->handleAction(Supla::ON_CHANGE, action3); diff --git a/extras/test/CorrectionTests/correction_tests.cpp b/extras/test/CorrectionTests/correction_tests.cpp new file mode 100644 index 00000000..881908a8 --- /dev/null +++ b/extras/test/CorrectionTests/correction_tests.cpp @@ -0,0 +1,56 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include + +using namespace Supla; + +TEST(CorrectionTests, CorrectionGetCheck) { + Correction::add(5, 2.5); + + EXPECT_EQ(Correction::get(5), 2.5); + EXPECT_EQ(Correction::get(5, true), 0); + EXPECT_EQ(Correction::get(1), 0); + + Correction::add(1, 3.14); + EXPECT_EQ(Correction::get(1), 3.14); + EXPECT_EQ(Correction::get(5), 2.5); + + Correction::clear(); + + EXPECT_EQ(Correction::get(1), 0); + EXPECT_EQ(Correction::get(5), 0); +} + +TEST(CorrectionTests, CorrectionGetCheckForSecondary) { + Correction::add(5, 2.5, true); + + EXPECT_EQ(Correction::get(5), 0); + EXPECT_EQ(Correction::get(5, true), 2.5); + EXPECT_EQ(Correction::get(1), 0); + + Correction::add(1, 3.14); + EXPECT_EQ(Correction::get(1), 3.14); + EXPECT_EQ(Correction::get(5, true), 2.5); + + Correction::clear(); + + EXPECT_EQ(Correction::get(1), 0); + EXPECT_EQ(Correction::get(5), 0); +} + diff --git a/readme.md b/readme.md index a80f3eca..ebc92636 100644 --- a/readme.md +++ b/readme.md @@ -8,25 +8,20 @@ SuplaDevice is a library for [Arduino IDE](https://www.arduino.cc/en/main/softwa ## Library installation -There are few options how to install library in Arduino IDE. Here is one example: -1. Download SuplaDevice repository as a zip file (click green "Code" button on github repository) -2. Extract downloaded zip file -3. Copy whole SuplaDevice subfolder to a location where Arduino keeps libraries (in Arduino IDE open File->Preferences and there is a sketch location folder - libraries are kept in "libraries" subfolder) -4. You should be able to open SuplaDevice exmaples in Arduino IDE +Please use library manager in Arduino IDE to install newest SuplaDevice library. ## Hardware requirements ### Arduino Mega -SuplaDevice works with Arduino Mega boards. Currently Arduino Uno is not supported because of RAM limitations. It should work on other Arduino boards with at least 8 kB of RAM. +SuplaDevice works with Arduino Mega boards. Arduino Uno is not supported because of RAM limitations. It should work on other Arduino boards with at least 8 kB of RAM. Following network interfaces are supported: * Ethernet Shield with W5100 chipset * ENC28J60 (not recommended - see Supported hardware section) -Warning: WiFi shields are currently not supported +Warning: WiFi shields are currently not supported. -### ESP8266 -ESP8266 boards are supported. Network connection is done via internal WiFi. Tested with ESP8266 boards 2.6.3. -Most probably it will work with other ESP8266 compatible boards. +### ESP8266 and ESP8285 +ESP8266 and ESP8285 boards are supported. Network connection is done via WiFi. ### ESP32 Experimental support for ESP32 boards is provided. Some issues seen with reconnection to WiFi router which requires further analysis. @@ -38,9 +33,9 @@ Before you start, you will need to: 2. install support for your board 3. install driver for your USB to serial converter device (it can be external device, or build in on your board) 4. make sure that communication over serial interface with your board is working (i.e. check some example Arduino application) -5. download and install this librarary by copying SuplaDevice folder into your Arduino library folder +5. download and install this librarary -Steps 1-4 are standard Arudino IDE setup procedures not related to SuplaDevice library. You can find many tutorials on Internet with detailed instructions. Tutorials doesn't have to be related in any way with Supla. +Above steps are standard Arudino IDE setup procedures not related to SuplaDevice library. You can find many tutorials on Internet with detailed instructions. Tutorials doesn't have to be related in any way with Supla. After step 5 you should see Supla example applications in Arduino IDE examples. Select one and have fun! Example file requires adjustments before you compile them and upload to your board. Please read all comments in example and make proper adjustments. @@ -55,7 +50,7 @@ or some other problem with network, program will stuck on initialization and wil Second warning: UIPEthernet library is consuming few hundred of bytes of RAM memory more, compared to standard Ethernet library. Supported network interface for ESP8266: -* There is a native WiFi controller. Include `` and add `Supla::ESPWifi wifi(ssid, password);` as a global variable and provide SSID and password in constructor. +* There is a native WiFi controller. Include `` and add `Supla::ESPWifi wifi(ssid, password);` as a global variable and provide SSID and password in a constructor. Warning: by default connection with Supla server is encrypted. Default settings of SSL consumes big amount of RAM. To disable SSL connection, use: `wifi.enableSSL(false);` @@ -69,21 +64,22 @@ If you specify Supla's server certificate thumbprint there will be additional ve Supported network interface for ESP32: -* There is a native WiFi controller. Include `` and add `Supla::ESP32Wifi wifi(ssid, password);` as a global variable and provide SSID and password in constructor. +* There is a native WiFi controller. Include `` and add `Supla::ESPWifi wifi(ssid, password);` as a global variable and provide SSID and password in a constructor. ### Exmaples Each example can run on Arduino Mega, ESP8266, or ESP32 board - unless mentioned otherwise in comments. Please read comments in example files and uncomment proper library for your network interface. -SuplaSomfy - this example is not updated yet. - ### Folder structure * `supla-common` - Supla protocol definitions and structures. There are also methods to handle low level communication with Supla server, like message coding, decoding, sending and receiving. Those files are common with `supla-core` and the same code is run on multiple Supla platforms and services * `supla/network` - implementation of network interfaces for supported boards * `supla/sensor` - implementation of Supla sensor channels (thermometers, open/close sensors, etc.) +* `supla/storage` - implementation of persistant storage interfaces used by some Elements (i.e. keeping impulse counter data) * `supla/control` - implementation of Supla control channels (various combinations of relays, buttons, action triggers) * `supla/clock` - time services used in library (i.e. RTC) +* `supla/conditions` - classes that are used to check runtime dependencies between channels (i.e. turn on relay when humidity is below 40%) +* `supla/pv` - supported integrations with inverters used with photovoltaic * `supla` - all common classes are defined in main `supla` folder. You can find there classes that create framework on which all other components work. Some functions from above folders have dependencies to external libraries. Please check documentation included in header files. @@ -101,9 +97,9 @@ All elements have to be constructed before `SuplaDevice.begin()` method is calle Supla channel number is assigned to each elemement with channel in an order of creation of objects. First channel will get number 0, second 1, etc. Supla server will not accept registration of device when order of channels is changed, or some channel is removed. In such case, you should remove device from Supla Cloud and register it again from scratch. `Element` class defines follwoing virtual methods that are called by SuplaDevice: -1. `onInit` - called first within `SuplaDevice.begin()` method. It should initialize your element. -2. `onLoadState` - called second within `SuplaDevice.begin()` method. It reads configuration data from persistent memory storage. -3. `onSaveState` - called in `SuplaDevice.iterate()` - it saves state data to persistant storage. It is not called on each iteration. `Storage` class makes sure that storing to memory does not happen to often and time delay between saves depends on implementation. +1. `onLoadState` - called first within `SuplaDevice.begin()` method. It reads configuration data from persistent memory storage. +2. `onInit` - called second within `SuplaDevice.begin()` method. It should initialize your element - all GPIO settings should be done there and proper state of channel should be set. +3. `onSaveState` - called in `SuplaDevice.iterate()` - it saves state data to persistant storage. It is not called on each iteration. `Storage` class makes sure that storing to memory does not happen too often and time delay between saves depends on implementation. 3. `iterateAlways` - called on each iteration of `SuplaDevice.iterate()` method, regardless of network/connection status. Be careful - some other methods called in `SuplaDevice.iterate()` method may block program execution for some time (even few seconds) - i.e. trying to establish connection with Supla server is blocking - in case server is not accessible, it will iterfere with `iterateAlways` method. So time critical functions should not be put here. 4. `iterateConnected` - called on each iterateion of `SuplaDevice.iterate()` method when device is connected and properly registered in Supla server. This method usually checks if there is some new data to be send to server (i.e. new temperature reading) and sends it. 5. `onTimer` - called every 10 ms after enabling in `SuplaDevice.begin()` @@ -203,11 +199,60 @@ All channels from old version of library should be removed and created again in ## Supported channels ### Sensors -Sensor category is for all elements/channels that reads something and provides data to Supla serwer. - +Sensor category is for all elements/channels that reads something and provides data to Supla server. All sensors are in `Supla::Sensor` namespace: + +* `Binary` - two state sensor: on/off, enabled/disabled, open/closesd, etc. It reads GPIO state: LOW/HIGH +* `VirtualBinary` - similar to `Binary` but it use settable variable in memory to show state +* `Thermometer` - base class for thermometer sensors +* `DS18B20` - DS18B20 thermometer +* `Si7021` - S17021 thermometer +* `Si7021Sonoff` - Si7021 thermometer for Sonoff +* `MAX6675_K` - MAX6675_K thermometer +* `ThermHygroMeter` - base class for sensors capable of measuring temperature and humidity +* `DHT` - DHT11 and DHT22 support +* `SHT3x` - SHT3x support +* `ThermHygroPressMeter` - base class for sensors capable of measuring temperature, humidity, and pressure +* `BME280` - BME280 support +* `Distance` - base class for distance sensors +* `HC_SR04` - HC_SR04 distance meter +* `Pressure` - base class for presure meters +* `Wind` - base class for wind meters (speed) +* `Rain` - base class for rain meters +* `Weight` - base class for weight meters +* `ImpulseCounter` - calculates impulses on a given GPIO +* `ElectricityMeter` - base class for electricity meters +* `PZEMv2` - PZEMv2 one phase electricity meter +* `PZEMv3` - PZEMv3 one phase electricity meter +* `ThreePhasePZEMv3` - 3x PZEMv3 for measuring three phases +* `EspFreeHeap` - provides free heap memory on ESP8266 as a Channel ### Control Control category is for all elements/channels that are used to control something, i.e. relays, buttons, RGBW. +Classes in this category are in namespace `Supla::Control`: + +* `BistableRelay` - SuplaDevice sends short impulses on GPIO to trigger change of bistable relay. It requires additional GPIO input to read status of relay +* `BistableRollerShutter` - Roller shutter implementation to control external roller shutter controllers that work in a similar way to bistable relays +* `Button` - allows to use button connected to GPIO to control other elements in device. Supports multiclicks, long click, etc +* `DimmerBase` - base class for dimmers +* `DimmerLeds` - PWM based implementation for dimmer +* `InternalPinOutput` - allows to control GPIO without showing it to Supla as a channel +* `LightRelay` - extension of Relay class that allows to monitor and configure lifespan of light source +* `PinStatusLed` - allows to duplicate GPIO state to another GPIO which can have connected LED to show status +* `Relay` - allows to control relay through GPIO +* `RGBBase` - base class for RGB control +* `RGBLeds` - PWM based implementation for RGB lights +* `RGBWBase` - base class for RGBW control +* `RollerShutter` - controller for roller shutters +* `SequenceButton` - extension of button which allows to trigger actions based on specific sequence/rythm +* `SimpleButton` - button that allows only press and release detection with lower memory footprint +* `VirtualRelay` - relay which keeps its state in memory and doesn't affect any GPIO + +### Photovoltaic inverter +SuplaDevice provides integrations for following inverters: + +* `Afore` +* `Fronius` +* `SolarEdge` ## Supported persistant memory storage Storage class is used as an abstraction for different persistant memory devices. Some elements/channels will not work properly without storage and some will have limitted functionalities. I.e. `ImpulseCounter` requires storage to save counter value, so it could be restored after reset, or `RollerShutter` requires storage to keep openin/closing times and current shutter possition. Currently two variants of storage classes are supported. @@ -233,14 +278,6 @@ or with SW SPI: Supla::FramSpi fram(SCK_PIN, MISO_PIN, MOSI_PIN, FRAM_CS, SUPLA_STORAGE_OFFSET); ``` -## History - -Version 2.3.0 - - -## Credits - - ## License diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 96e7d10e..f25e96ca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ set(SRCS supla/element.cpp supla/local_action.cpp supla/channel_element.cpp + supla/correction.cpp supla/storage/storage.cpp @@ -19,6 +20,7 @@ set(SRCS supla/control/rgb_leds.cpp supla/control/dimmer_leds.cpp supla/control/simple_button.cpp + supla/control/button.cpp supla/condition.cpp supla/conditions/on_less.cpp @@ -28,6 +30,7 @@ set(SRCS supla/conditions/on_between.cpp supla/conditions/on_between_eq.cpp supla/conditions/on_equal.cpp + supla/conditions/on_invalid.cpp SuplaDevice.cpp supla/network/network.cpp diff --git a/src/supla/channel.cpp b/src/supla/channel.cpp index 395f9335..e9baafe6 100644 --- a/src/supla/channel.cpp +++ b/src/supla/channel.cpp @@ -21,6 +21,7 @@ #include "supla-common/srpc.h" #include "tools.h" #include "events.h" +#include "correction.h" namespace Supla { @@ -47,6 +48,9 @@ Channel::~Channel() { } void Channel::setNewValue(double dbl) { + // Apply channel value correction + dbl += Correction::get(getChannelNumber()); + char newValue[SUPLA_CHANNELVALUE_SIZE]; if (sizeof(double) == 8) { memcpy(newValue, &dbl, 8); @@ -61,6 +65,10 @@ void Channel::setNewValue(double dbl) { } void Channel::setNewValue(double temp, double humi) { + // Apply channel value corrections + temp += Correction::get(getChannelNumber()); + humi += Correction::get(getChannelNumber(), true); + char newValue[SUPLA_CHANNELVALUE_SIZE]; _supla_int_t t = temp * 1000.00; _supla_int_t h = humi * 1000.00; @@ -332,4 +340,8 @@ void Channel::setValidityTimeSec(unsigned _supla_int_t timeSec) { if (validityTimeSec > 0) unsetFlag(SUPLA_CHANNEL_FLAG_CHANNELSTATE); } +void Channel::setCorrection(double correction, bool forSecondaryValue) { + Correction::add(getChannelNumber(), correction, forSecondaryValue); +} + }; // namespace Supla diff --git a/src/supla/channel.h b/src/supla/channel.h index a595bba5..decbd5dd 100644 --- a/src/supla/channel.h +++ b/src/supla/channel.h @@ -68,6 +68,7 @@ class Channel : public LocalAction { void clearUpdateReady(); void sendUpdate(void *srpc); virtual TSuplaChannelExtendedValue *getExtValue(); + void setCorrection(double correction, bool forSecondaryValue = false); static unsigned long lastCommunicationTimeMs; static TDS_SuplaRegisterDevice_E reg_dev; diff --git a/src/supla/condition.cpp b/src/supla/condition.cpp index 87128f4f..fbf12158 100644 --- a/src/supla/condition.cpp +++ b/src/supla/condition.cpp @@ -71,7 +71,7 @@ void Supla::Condition::handleAction(int event, int action) { isValid = useAlternativeMeasurement ? value >= 0 : value >= -273; break; } - if (isValid && checkConditionFor(value)) { + if (checkConditionFor(value, isValid)) { client->handleAction(event, action); } } @@ -82,13 +82,13 @@ bool Supla::Condition::deleteClient() { return true; } -bool Supla::Condition::checkConditionFor(double val) { - if (!alreadyFired && condition(val)) { +bool Supla::Condition::checkConditionFor(double val, bool isValid) { + if (!alreadyFired && condition(val, isValid)) { alreadyFired = true; return true; } if (alreadyFired) { - if (!condition(val)) { + if (!condition(val, isValid)) { alreadyFired = false; } } diff --git a/src/supla/condition.h b/src/supla/condition.h index d5d31f90..1eed6e33 100644 --- a/src/supla/condition.h +++ b/src/supla/condition.h @@ -36,10 +36,10 @@ class Condition : public ActionHandler { void handleAction(int event, int action); bool deleteClient(); - virtual bool checkConditionFor(double val); + virtual bool checkConditionFor(double val, bool isValid = true); protected: - virtual bool condition(double val) = 0; + virtual bool condition(double val, bool isValid = true) = 0; double threshold; bool alreadyFired; @@ -58,5 +58,6 @@ Supla::Condition *OnGreaterEq(double threshold, bool useAlternativeMeasurement = Supla::Condition *OnBetween(double threshold1, double threshold2, bool useAlternativeMeasurement = false); Supla::Condition *OnBetweenEq(double threshold1, double threshold2, bool useAlternativeMeasurement = false); Supla::Condition *OnEqual(double threshold, bool useAlternativeMeasurement = false); +Supla::Condition *OnInvalid(bool useAlternativeMeasurement = false); #endif diff --git a/src/supla/conditions/on_between.cpp b/src/supla/conditions/on_between.cpp index 9a1ef83e..f161c82e 100644 --- a/src/supla/conditions/on_between.cpp +++ b/src/supla/conditions/on_between.cpp @@ -22,8 +22,11 @@ class OnBetweenCond : public Supla::Condition { : Supla::Condition(threshold1, useAlternativeMeasurement), threshold2(threshold2) { } - bool condition(double val) { - return val > threshold && val < threshold2; + bool condition(double val, bool isValid) { + if (isValid) { + return val > threshold && val < threshold2; + } + return false; } double threshold2; diff --git a/src/supla/conditions/on_between_eq.cpp b/src/supla/conditions/on_between_eq.cpp index 8e535db1..e5b96bd9 100644 --- a/src/supla/conditions/on_between_eq.cpp +++ b/src/supla/conditions/on_between_eq.cpp @@ -22,8 +22,11 @@ class OnBetweenEqCond : public Supla::Condition { : Supla::Condition(threshold1, useAlternativeMeasurement), threshold2(threshold2) { } - bool condition(double val) { - return val >= threshold && val <= threshold2; + bool condition(double val, bool isValid) { + if (isValid) { + return val >= threshold && val <= threshold2; + } + return false; } double threshold2; diff --git a/src/supla/conditions/on_equal.cpp b/src/supla/conditions/on_equal.cpp index 1d7e6d25..bd72813c 100644 --- a/src/supla/conditions/on_equal.cpp +++ b/src/supla/conditions/on_equal.cpp @@ -22,8 +22,11 @@ class OnEqualCond : public Supla::Condition { : Supla::Condition(threshold, useAlternativeMeasurement) { } - bool condition(double val) { - return val == threshold; + bool condition(double val, bool isValid) { + if (isValid) { + return val == threshold; + } + return false; } }; diff --git a/src/supla/conditions/on_greater.cpp b/src/supla/conditions/on_greater.cpp index 675a667f..6a77635f 100644 --- a/src/supla/conditions/on_greater.cpp +++ b/src/supla/conditions/on_greater.cpp @@ -22,8 +22,11 @@ class OnGreaterCond : public Supla::Condition { : Supla::Condition(threshold, useAlternativeMeasurement) { } - bool condition(double val) { - return val > threshold; + bool condition(double val, bool isValid) { + if (isValid) { + return val > threshold; + } + return false; } }; diff --git a/src/supla/conditions/on_greater_eq.cpp b/src/supla/conditions/on_greater_eq.cpp index 37aaf920..c6eeed49 100644 --- a/src/supla/conditions/on_greater_eq.cpp +++ b/src/supla/conditions/on_greater_eq.cpp @@ -22,8 +22,11 @@ class OnGreaterEqCond : public Supla::Condition { : Supla::Condition(threshold, useAlternativeMeasurement) { } - bool condition(double val) { - return val >= threshold; + bool condition(double val, bool isValid) { + if (isValid) { + return val >= threshold; + } + return false; } }; diff --git a/src/supla/conditions/on_invalid.cpp b/src/supla/conditions/on_invalid.cpp new file mode 100644 index 00000000..aacf947e --- /dev/null +++ b/src/supla/conditions/on_invalid.cpp @@ -0,0 +1,39 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "../condition.h" + +class OnInvalidCond : public Supla::Condition { + public: + OnInvalidCond(bool useAlternativeMeasurement) + : Supla::Condition(0, useAlternativeMeasurement) { + } + + bool condition(double val, bool isValid) { + return !isValid; + } + +}; + + +Supla::Condition *OnInvalid(bool useAlternativeMeasurement) { + return new OnInvalidCond(useAlternativeMeasurement); +} + + + + + diff --git a/src/supla/conditions/on_less.cpp b/src/supla/conditions/on_less.cpp index 37541f52..49c63547 100644 --- a/src/supla/conditions/on_less.cpp +++ b/src/supla/conditions/on_less.cpp @@ -22,8 +22,11 @@ class OnLessCond : public Supla::Condition { : Supla::Condition(threshold, useAlternativeMeasurement) { } - bool condition(double val) { - return val < threshold; + bool condition(double val, bool isValid) { + if (isValid) { + return val < threshold; + } + return false; } }; diff --git a/src/supla/conditions/on_less_eq.cpp b/src/supla/conditions/on_less_eq.cpp index 359e8058..06459a6f 100644 --- a/src/supla/conditions/on_less_eq.cpp +++ b/src/supla/conditions/on_less_eq.cpp @@ -22,8 +22,11 @@ class OnLessEqCond : public Supla::Condition { : Supla::Condition(threshold, useAlternativeMeasurement) { } - bool condition(double val) { - return val <= threshold; + bool condition(double val, bool isValid) { + if (isValid) { + return val <= threshold; + } + return false; } }; diff --git a/src/supla/control/button.cpp b/src/supla/control/button.cpp index 9971de8c..9dfc0155 100644 --- a/src/supla/control/button.cpp +++ b/src/supla/control/button.cpp @@ -55,7 +55,7 @@ void Supla::Control::Button::onTimer() { runAction(ON_HOLD); ++holdSend; } - } else if (clickCounter > 0 && (bistable || stateResult == RELEASED)) { + } else if ((bistable || stateResult == RELEASED)) { if (multiclickTimeMs == 0) { holdSend = 0; clickCounter = 0; @@ -97,7 +97,46 @@ void Supla::Control::Button::onTimer() { if (clickCounter >= 10) { runAction(ON_CRAZY_CLICKER); } - } + } else { + switch (clickCounter) { + // for LONG_CLICK counter was incremented once by ON_HOLD + case 1: + runAction(ON_LONG_CLICK_0); + break; + case 2: + runAction(ON_LONG_CLICK_1); + break; + case 3: + runAction(ON_LONG_CLICK_2); + break; + case 4: + runAction(ON_LONG_CLICK_3); + break; + case 5: + runAction(ON_LONG_CLICK_4); + break; + case 6: + runAction(ON_LONG_CLICK_5); + break; + case 7: + runAction(ON_LONG_CLICK_6); + break; + case 8: + runAction(ON_LONG_CLICK_7); + break; + case 9: + runAction(ON_LONG_CLICK_8); + break; + case 10: + runAction(ON_LONG_CLICK_9); + break; + case 11: + runAction(ON_LONG_CLICK_10); + break; + } + + + } holdSend = 0; clickCounter = 0; } diff --git a/src/supla/correction.cpp b/src/supla/correction.cpp new file mode 100644 index 00000000..de498cc5 --- /dev/null +++ b/src/supla/correction.cpp @@ -0,0 +1,67 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "correction.h" + +void Supla::Correction::add(uint8_t channelNumber, double correction, bool forSecondaryValue) { + new Correction(channelNumber, correction, forSecondaryValue); +} + +double Supla::Correction::get(uint8_t channelNumber, bool forSecondaryValue) { + auto ptr = first; + while (ptr) { + if (ptr->channelNumber == channelNumber && ptr->forSecondaryValue == forSecondaryValue) { + return ptr->correction; + } + ptr = ptr->next; + } + return 0; +} + +Supla::Correction::Correction(uint8_t channelNumber, double correction, bool forSecondaryValue) : + channelNumber(channelNumber), correction(correction), forSecondaryValue(forSecondaryValue), next(nullptr) { + if (first == nullptr) { + first = this; + } else { + Correction *ptr = first; + while (ptr && ptr->next) { + ptr = ptr->next; + } + ptr->next = this; + } +} + +Supla::Correction::~Correction() { + if (first == this) { + first = next; + return; + } + + auto ptr = first; + while (ptr->next != this) { + ptr = ptr->next; + } + + ptr->next = ptr->next->next; +} + +void Supla::Correction::clear() { + while (first) { + delete first; + } +} + +Supla::Correction *Supla::Correction::first = nullptr; diff --git a/src/supla/correction.h b/src/supla/correction.h new file mode 100644 index 00000000..717ab36d --- /dev/null +++ b/src/supla/correction.h @@ -0,0 +1,42 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef __correction_h +#define __correction_h + +#include + +namespace Supla { + + class Correction { + public: + static void add(uint8_t channelNumber, double correction, bool forSecondaryValue = false); + static double get(uint8_t channelNumber, bool forSecondaryValue = false); + static void clear(); + + protected: + Correction(uint8_t channelNumber, double correction, bool forSecondaryValue); + ~Correction(); + + static Correction *first; + Correction *next; + uint8_t channelNumber; + double correction; + bool forSecondaryValue; + }; + +}; +#endif diff --git a/src/supla/events.h b/src/supla/events.h index a65b0a4c..fc94e1d5 100644 --- a/src/supla/events.h +++ b/src/supla/events.h @@ -40,7 +40,18 @@ enum Event { ON_SEQUENCE_DOESNT_MATCH, // triggered by SequenceButton ON_TURN_ON, ON_TURN_OFF, - ON_SECONDARY_CHANNEL_CHANGE + ON_SECONDARY_CHANNEL_CHANGE, + ON_LONG_CLICK_0, + ON_LONG_CLICK_1, + ON_LONG_CLICK_2, + ON_LONG_CLICK_3, + ON_LONG_CLICK_4, + ON_LONG_CLICK_5, + ON_LONG_CLICK_6, + ON_LONG_CLICK_7, + ON_LONG_CLICK_8, + ON_LONG_CLICK_9, + ON_LONG_CLICK_10, }; };