diff --git a/.drone.yml b/.drone.yml index 688641c7..98d2446f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,10 +8,35 @@ steps: - mkdir extras/test/build - cd extras/test/build - cmake .. - - make + - make -j32 - name: test image: rikorose/gcc-cmake commands: - cd extras/test/build - - ./supladevicetests + - ./supladevicetests --gtest_repeat=50 --gtest_shuffle + +- name: slack + image: plugins/slack + settings: + webhook: + from_secret: slack_webhook + channel: github + username: drone + + # here's the template :) + # notice that the repo endpoint is hardcoded to `https://github.com/`. + # you may adjust it accordingly. + template: > + {{#if build.pull }} + *{{#success build.status}}✔{{ else }}✘{{/success}} {{ uppercasefirst build.status }}*: in {{since build.created}} + {{else}} + *{{#success build.status}}✔{{ else }}✘{{/success}} {{ uppercasefirst build.status }}: Build #{{ build.number }}* (type: `{{ build.event }}`) in {{since build.created}} + {{/if}} + + Commit: at: by: {{ build.author }} + + <{{ build.link }}|Visit build page ↗> + when: + status: [success, failure] + diff --git a/examples/RGBW/RGBW.ino b/examples/RGBW/RGBW.ino index fb446e08..35d3f16e 100644 --- a/examples/RGBW/RGBW.ino +++ b/examples/RGBW/RGBW.ino @@ -15,7 +15,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include -#include +#include +#include // Choose proper network interface for your card: #ifdef ARDUINO_ARCH_AVR @@ -39,45 +40,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * Youtube example was done on older version of SuplaDevice library */ -#define RED_PIN 44 -#define GREEN_PIN 45 -#define BLUE_PIN 46 -#define BRIGHTNESS_PIN 7 -#define COLOR_BRIGHTNESS_PIN 8 - -class RgbwLeds : public Supla::Control::RGBWBase { - public: - RgbwLeds(int redPin, - int greenPin, - int bluePin, - int colorBrightnessPin, - int brightnessPin) - : redPin(redPin), - greenPin(greenPin), - bluePin(bluePin), - colorBrightnessPin(colorBrightnessPin), - brightnessPin(brightnessPin) { - } - - void setRGBWValueOnDevice(uint8_t red, - uint8_t green, - uint8_t blue, - uint8_t colorBrightness, - uint8_t brightness) { - analogWrite(brightnessPin, (brightness * 255) / 100); - analogWrite(colorBrightnessPin, (colorBrightness * 255) / 100); - analogWrite(redPin, red); - analogWrite(greenPin, green); - analogWrite(bluePin, blue); - } - - protected: - int redPin; - int greenPin; - int bluePin; - int brightnessPin; - int colorBrightnessPin; -}; +#define RED_PIN 4 +#define GREEN_PIN 5 +#define BLUE_PIN 12 +#define BRIGHTNESS_PIN 13 +#define BUTTON_PIN 0 void setup() { Serial.begin(115200); @@ -96,8 +63,16 @@ void setup() { */ // CHANNEL0 - RGB controller and dimmer (RGBW) - new RgbwLeds( - RED_PIN, GREEN_PIN, BLUE_PIN, COLOR_BRIGHTNESS_PIN, BRIGHTNESS_PIN); + auto rgbw = new Supla::Control::RGBWLeds( + RED_PIN, GREEN_PIN, BLUE_PIN, BRIGHTNESS_PIN); + + auto button = new Supla::Control::Button(BUTTON_PIN, true, true); + button->setMulticlickTime(200); + button->setHoldTime(400); + button->repeatOnHoldEvery(200); + + button->addAction(Supla::ITERATE_DIM_ALL, rgbw, Supla::ON_HOLD); + button->addAction(Supla::TOGGLE, rgbw, Supla::ON_CLICK_1); /* * SuplaDevice Initialization. diff --git a/extras/test/ButtonTests/simple_button_tests.cpp b/extras/test/ButtonTests/simple_button_tests.cpp new file mode 100644 index 00000000..6a608c65 --- /dev/null +++ b/extras/test/ButtonTests/simple_button_tests.cpp @@ -0,0 +1,194 @@ +/* + 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 TimeInterfaceStub : public TimeInterface { + public: + virtual unsigned long millis() override { + static unsigned long value = 0; + value += 1000; + return value; + } +}; + +TEST(SimpleButtonTests, NoPullupInit) { + TimeInterfaceStub time; + DigitalInterfaceMock ioMock; + ActionHandlerMock mock1; + + EXPECT_CALL(ioMock, pinMode(5, INPUT)); + EXPECT_CALL(ioMock, digitalRead(5)).WillOnce(Return(0)); + + EXPECT_CALL(mock1, handleAction).Times(0); + + Supla::Control::SimpleButton button(5, false, false); + button.onInit(); + button.addAction(1, mock1, Supla::ON_PRESS); + button.addAction(2, mock1, Supla::ON_CHANGE); + button.addAction(3, mock1, Supla::ON_RELEASE); +} + +TEST(SimpleButtonTests, PullupInitAndPress) { + TimeInterfaceMock time; + DigitalInterfaceMock ioMock; + ActionHandlerMock mock1; + + EXPECT_CALL(ioMock, pinMode(5, INPUT_PULLUP)); + EXPECT_CALL(ioMock, digitalRead(5)) + .WillOnce(Return(1)) + .WillOnce(Return(0)) // time 1000 - first read + .WillOnce(Return(0)) // second read, should be ignored + .WillOnce(Return(0)) // third read, should trigger on_press + .WillOnce(Return(0)) // time 1090 + .WillOnce(Return(1)) // time 1100 + .WillOnce(Return(1)) // time 1110 + .WillOnce(Return(1)) // time 1150 + ; + + EXPECT_CALL(time, millis) + .WillOnce(Return(1000)) // first read + .WillOnce(Return(1010)) // should be ignored by filtering time + .WillOnce(Return(1030)) // should trigger on press + .WillOnce(Return(1040)) // + .WillOnce(Return(1050)) // + .WillOnce(Return(1060)) // + .WillOnce(Return(1090)) // + .WillOnce(Return(1100)) // + .WillOnce(Return(1110)) // + .WillOnce(Return(1150)) // + ; + + + 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::SimpleButton button(5, true, true); + button.onInit(); + button.addAction(1, mock1, Supla::ON_PRESS); + button.addAction(2, mock1, Supla::ON_CHANGE); + button.addAction(3, mock1, Supla::ON_RELEASE); + + button.onTimer(); + button.onTimer(); + button.onTimer(); + button.onTimer(); + button.onTimer(); + button.onTimer(); + button.onTimer(); + button.onTimer(); + button.onTimer(); + button.onTimer(); + +} + +TEST(SimpleButtonTests, PullupInitAndPressWithoutNoiseFilter) { + TimeInterfaceMock time; + DigitalInterfaceMock ioMock; + ActionHandlerMock mock1; + + EXPECT_CALL(ioMock, pinMode(5, INPUT_PULLUP)); + EXPECT_CALL(ioMock, digitalRead(5)) + .WillOnce(Return(1)) + .WillOnce(Return(0)) + .WillOnce(Return(0)) + .WillOnce(Return(0)) + .WillOnce(Return(1)) + ; + + EXPECT_CALL(time, millis) + .WillOnce(Return(1000)) + .WillOnce(Return(1010)) + .WillOnce(Return(1100)) + .WillOnce(Return(1200)) + .WillOnce(Return(1300)) + ; + + + 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::SimpleButton button(5, true, true); + button.setSwNoiseFilterDelay(0); + button.onInit(); + button.addAction(1, mock1, Supla::ON_PRESS); + button.addAction(2, mock1, Supla::ON_CHANGE); + button.addAction(3, mock1, Supla::ON_RELEASE); + + button.onTimer(); + button.onTimer(); + button.onTimer(); + button.onTimer(); + button.onTimer(); + +} + +TEST(SimpleButtonTests, PullupInitAndPressWithoutDebounce) { + TimeInterfaceMock time; + DigitalInterfaceMock ioMock; + ActionHandlerMock mock1; + + EXPECT_CALL(ioMock, pinMode(5, INPUT_PULLUP)); + EXPECT_CALL(ioMock, digitalRead(5)) + .WillOnce(Return(1)) + + .WillOnce(Return(0)) + .WillOnce(Return(0)) + .WillOnce(Return(0)) + .WillOnce(Return(1)) + .WillOnce(Return(1)) + ; + + EXPECT_CALL(time, millis) + .WillOnce(Return(1000)) + .WillOnce(Return(1010)) + .WillOnce(Return(1100)) + .WillOnce(Return(1101)) + .WillOnce(Return(1145)) + ; + + + 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::SimpleButton button(5, true, true); + button.setDebounceDelay(0); + button.onInit(); + button.addAction(1, mock1, Supla::ON_PRESS); + button.addAction(2, mock1, Supla::ON_CHANGE); + button.addAction(3, mock1, Supla::ON_RELEASE); + + button.onTimer(); + button.onTimer(); + button.onTimer(); + button.onTimer(); + button.onTimer(); + +} + diff --git a/extras/test/CMakeLists.txt b/extras/test/CMakeLists.txt index 107bf4d8..d1d31b8c 100644 --- a/extras/test/CMakeLists.txt +++ b/extras/test/CMakeLists.txt @@ -43,6 +43,9 @@ file(GLOB TEST_SRC InternalPinOutputTests/*.cpp PinStatusLedTests/*.cpp ConditionTests/*.cpp + RgbwDimmerTests/*.cpp + ButtonTests/*.cpp + SuplaDeviceTests/*.cpp ) file(GLOB DOUBLE_SRC doubles/*.cpp) diff --git a/extras/test/ChannelTests/channel_tests.cpp b/extras/test/ChannelTests/channel_tests.cpp index 09008e7a..876ea091 100644 --- a/extras/test/ChannelTests/channel_tests.cpp +++ b/extras/test/ChannelTests/channel_tests.cpp @@ -283,8 +283,8 @@ TEST(ChannelTests, SendUpdateTest) { char array[SUPLA_CHANNELVALUE_SIZE] = {}; array[0] = 1; - EXPECT_CALL(srpc, valueChanged(nullptr, 0, ElementsAreArray(emptyArray))); - EXPECT_CALL(srpc, valueChanged(nullptr, 0, ElementsAreArray(array))); + EXPECT_CALL(srpc, valueChanged(nullptr, 0, ElementsAreArray(emptyArray), 0, 0)); + EXPECT_CALL(srpc, valueChanged(nullptr, 0, ElementsAreArray(array), 0, 0)); EXPECT_FALSE(channel.isUpdateReady()); channel.sendUpdate(nullptr); diff --git a/extras/test/ElementTests/element_tests.cpp b/extras/test/ElementTests/element_tests.cpp index 8526dc0a..4b5a7c5c 100644 --- a/extras/test/ElementTests/element_tests.cpp +++ b/extras/test/ElementTests/element_tests.cpp @@ -24,6 +24,20 @@ using ::testing::Return; using ::testing::ElementsAreArray; +class ElementTests : public ::testing::Test { + protected: + virtual void SetUp() { + Supla::Channel::lastCommunicationTimeMs = 0; + memset(&(Supla::Channel::reg_dev), 0, sizeof(Supla::Channel::reg_dev)); + } + virtual void TearDown() { + Supla::Channel::lastCommunicationTimeMs = 0; + memset(&(Supla::Channel::reg_dev), 0, sizeof(Supla::Channel::reg_dev)); + } + +}; + + class ElementWithChannel : public Supla::Element { public: Supla::Channel *getChannel() { @@ -32,7 +46,7 @@ class ElementWithChannel : public Supla::Element { Supla::Channel channel; }; -TEST(ElementTests, ElementEmptyListTests) { +TEST_F(ElementTests, ElementEmptyListTests) { EXPECT_EQ(Supla::Element::begin(), nullptr); EXPECT_EQ(Supla::Element::last(), nullptr); EXPECT_EQ(Supla::Element::getElementByChannelNumber(0), nullptr); @@ -40,7 +54,7 @@ TEST(ElementTests, ElementEmptyListTests) { EXPECT_EQ(Supla::Element::getElementByChannelNumber(10), nullptr); } -TEST(ElementTests, ElementListAdding) { +TEST_F(ElementTests, ElementListAdding) { auto el1 = new Supla::Element; EXPECT_EQ(Supla::Element::begin(), el1); @@ -93,7 +107,7 @@ TEST(ElementTests, ElementListAdding) { } -TEST(ElementTests, NoChannelElementMethods) { +TEST_F(ElementTests, NoChannelElementMethods) { Supla::Element el1; // those methods are empty, so just call to make sure that they do nothing and don't crash @@ -119,7 +133,7 @@ TEST(ElementTests, NoChannelElementMethods) { EXPECT_EQ(el1.handleCalcfgFromServer(nullptr), SUPLA_CALCFG_RESULT_NOT_SUPPORTED); } -TEST(ElementTests, ChannelElementMethods) { +TEST_F(ElementTests, ChannelElementMethods) { ElementWithChannel el1; TimeInterfaceMock time; SrpcMock srpc; @@ -163,8 +177,8 @@ TEST(ElementTests, ChannelElementMethods) { char array0[SUPLA_CHANNELVALUE_SIZE] = {}; char array1[SUPLA_CHANNELVALUE_SIZE] = {}; array1[0] = 1; - EXPECT_CALL(srpc, valueChanged(nullptr, 0, ElementsAreArray(array1))); // value at #2 - EXPECT_CALL(srpc, valueChanged(nullptr, 0, ElementsAreArray(array0))); // value at #5 + EXPECT_CALL(srpc, valueChanged(nullptr, 0, ElementsAreArray(array1), 0, 0)); // value at #2 + EXPECT_CALL(srpc, valueChanged(nullptr, 0, ElementsAreArray(array0), 0, 0)); // value at #5 EXPECT_EQ(el1.iterateConnected(nullptr), true); // #1 diff --git a/extras/test/RgbwDimmerTests/dimmer_leds_tests.cpp b/extras/test/RgbwDimmerTests/dimmer_leds_tests.cpp new file mode 100644 index 00000000..a4ef9c1a --- /dev/null +++ b/extras/test/RgbwDimmerTests/dimmer_leds_tests.cpp @@ -0,0 +1,85 @@ +/* + 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 TimeInterfaceStub : public TimeInterface { + public: + virtual unsigned long millis() override { + static unsigned long value = 0; + value += 1000; + return value; + } +}; + +TEST(DimmerLedsTests, SettingNewDimValue) { + TimeInterfaceStub time; + DigitalInterfaceMock ioMock; + + EXPECT_CALL(ioMock, pinMode(1, OUTPUT)); + EXPECT_CALL(ioMock, analogWrite(1, 0)); + + EXPECT_CALL(ioMock, analogWrite(1, (1023*10)/100)); + + Supla::Control::DimmerLeds dim(1); + + auto ch = dim.getChannel(); + // disable fading effect so we'll get instant setting value on device call + dim.setFadeEffectTime(0); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + dim.onInit(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + dim.iterateAlways(); + dim.onTimer(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + dim.setRGBW(1, 2, 3, 4, 10); + + dim.iterateAlways(); + dim.onTimer(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 10); + +} + + + diff --git a/extras/test/RgbwDimmerTests/dimmer_tests.cpp b/extras/test/RgbwDimmerTests/dimmer_tests.cpp new file mode 100644 index 00000000..00086259 --- /dev/null +++ b/extras/test/RgbwDimmerTests/dimmer_tests.cpp @@ -0,0 +1,175 @@ +/* + 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 + +using ::testing::Return; + +class DimmerBaseForTest : public Supla::Control::DimmerBase { + public: + MOCK_METHOD(void, setRGBWValueOnDevice, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t), (override)); +}; + +class TimeInterfaceStub : public TimeInterface { + public: + virtual unsigned long millis() override { + static unsigned long value = 0; + value += 1000; + return value; + } +}; + +TEST(DimmerTests, InitializationWithDefaultValues) { + TimeInterfaceStub time; + + DimmerBaseForTest dimmer; + + ASSERT_NE(dimmer.getChannel(), nullptr); + + auto ch = dimmer.getChannel(); + + EXPECT_EQ(ch->getChannelType(), SUPLA_CHANNELTYPE_DIMMER); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + dimmer.onInit(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + dimmer.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); +} + +TEST(DimmerTests, DimmerShouldIgnoreRGBValues) { + TimeInterfaceStub time; + + DimmerBaseForTest dimmer; + + auto ch = dimmer.getChannel(); + // disable fading effect so we'll get instant setting value on device call + dimmer.setFadeEffectTime(0); + + EXPECT_CALL(dimmer, setRGBWValueOnDevice(0, 0, 0, 0, 0)).Times(1); + EXPECT_CALL(dimmer, setRGBWValueOnDevice(0, 0, 0, 0, (5*1023/100))).Times(1); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + dimmer.onInit(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + dimmer.iterateAlways(); + dimmer.onTimer(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + // we call with rgbw settings, which should be passed as 0 + dimmer.setRGBW(1, 2, 3, 4, 5); + + dimmer.iterateAlways(); + dimmer.onTimer(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 5); + +} + +TEST(DimmerTests, HandleActionTests) { + // time stub will return +1000 ms on each call to millis + TimeInterfaceStub time; + + DimmerBaseForTest dimmer; + + auto ch = dimmer.getChannel(); + + dimmer.setStep(10); + dimmer.onInit(); + dimmer.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + dimmer.handleAction(1, Supla::ITERATE_DIM_ALL); + dimmer.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 10); + + dimmer.handleAction(1, Supla::ITERATE_DIM_ALL); + dimmer.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 20); + + dimmer.handleAction(1, Supla::ITERATE_DIM_ALL); + dimmer.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 30); + + dimmer.handleAction(1, Supla::ITERATE_DIM_ALL); + dimmer.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 40); + + +} + + diff --git a/extras/test/RgbwDimmerTests/rgb_base_tests.cpp b/extras/test/RgbwDimmerTests/rgb_base_tests.cpp new file mode 100644 index 00000000..a9447222 --- /dev/null +++ b/extras/test/RgbwDimmerTests/rgb_base_tests.cpp @@ -0,0 +1,175 @@ +/* + 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 + +using ::testing::Return; + +class RgbBaseForTest : public Supla::Control::RGBBase { + public: + MOCK_METHOD(void, setRGBWValueOnDevice, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t), (override)); +}; + +class TimeInterfaceStub : public TimeInterface { + public: + virtual unsigned long millis() override { + static unsigned long value = 0; + value += 1000; + return value; + } +}; + +TEST(RgbTests, InitializationWithDefaultValues) { + TimeInterfaceStub time; + + RgbBaseForTest rgb; + + ASSERT_NE(rgb.getChannel(), nullptr); + + auto ch = rgb.getChannel(); + + EXPECT_EQ(ch->getChannelType(), SUPLA_CHANNELTYPE_RGBLEDCONTROLLER); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.onInit(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); +} + +TEST(RgbTests, RgbShouldIgnoreBrightnessValue) { + TimeInterfaceStub time; + + RgbBaseForTest rgb; + + auto ch = rgb.getChannel(); + // disable fading effect so we'll get instant setting value on device call + rgb.setFadeEffectTime(0); + + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 0, 0)).Times(1); + EXPECT_CALL(rgb, setRGBWValueOnDevice((1*1023/255), (2*1023/255), (3*1023/255), (4*1023/100), 0)).Times(1); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.onInit(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + rgb.onTimer(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + // we call with 5 as brightness - it should be passed as 0 to device and to channel + rgb.setRGBW(1, 2, 3, 4, 5); + + rgb.iterateAlways(); + rgb.onTimer(); + + EXPECT_EQ(ch->getValueRed(), 1); + EXPECT_EQ(ch->getValueGreen(), 2); + EXPECT_EQ(ch->getValueBlue(), 3); + EXPECT_EQ(ch->getValueColorBrightness(), 4); + EXPECT_EQ(ch->getValueBrightness(), 0); + +} + +TEST(RgbTests, HandleActionTests) { + // time stub will return +1000 ms on each call to millis + TimeInterfaceStub time; + + RgbBaseForTest rgb; + + auto ch = rgb.getChannel(); + + rgb.setStep(10); + rgb.onInit(); + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::ITERATE_DIM_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 10); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::ITERATE_DIM_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::ITERATE_DIM_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 30); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::ITERATE_DIM_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 40); + EXPECT_EQ(ch->getValueBrightness(), 0); + + +} + + diff --git a/extras/test/RgbwDimmerTests/rgb_leds_tests.cpp b/extras/test/RgbwDimmerTests/rgb_leds_tests.cpp new file mode 100644 index 00000000..a2861b20 --- /dev/null +++ b/extras/test/RgbwDimmerTests/rgb_leds_tests.cpp @@ -0,0 +1,90 @@ +/* + 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 TimeInterfaceStub : public TimeInterface { + public: + virtual unsigned long millis() override { + static unsigned long value = 0; + value += 1000; + return value; + } +}; + +TEST(RgbLedsTests, SettingNewRGBValue) { + TimeInterfaceStub time; + DigitalInterfaceMock ioMock; + + EXPECT_CALL(ioMock, pinMode(1, OUTPUT)); + EXPECT_CALL(ioMock, pinMode(2, OUTPUT)); + EXPECT_CALL(ioMock, pinMode(3, OUTPUT)); + EXPECT_CALL(ioMock, analogWrite(1, 0)); + EXPECT_CALL(ioMock, analogWrite(2, 0)); + EXPECT_CALL(ioMock, analogWrite(3, 0)); + + EXPECT_CALL(ioMock, analogWrite(1, (1*1023/255))); + EXPECT_CALL(ioMock, analogWrite(2, (2*1023/255))); + EXPECT_CALL(ioMock, analogWrite(3, (3*1023/255))); + + Supla::Control::RGBLeds rgb(1, 2, 3); + + auto ch = rgb.getChannel(); + // disable fading effect so we'll get instant setting value on device call + rgb.setFadeEffectTime(0); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.onInit(); + rgb.onTimer(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.setRGBW(1, 2, 3, 100, 5); + + rgb.iterateAlways(); + rgb.onTimer(); + + EXPECT_EQ(ch->getValueRed(), 1); + EXPECT_EQ(ch->getValueGreen(), 2); + EXPECT_EQ(ch->getValueBlue(), 3); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 0); + +} + + diff --git a/extras/test/RgbwDimmerTests/rgbw_base_tests.cpp b/extras/test/RgbwDimmerTests/rgbw_base_tests.cpp new file mode 100644 index 00000000..de0c97a1 --- /dev/null +++ b/extras/test/RgbwDimmerTests/rgbw_base_tests.cpp @@ -0,0 +1,1129 @@ +/* + 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 + +using ::testing::Return; + +class RgbwBaseForTest : public Supla::Control::RGBWBase { + public: + MOCK_METHOD(void, setRGBWValueOnDevice, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t), (override)); +}; + +class TimeInterfaceStub : public TimeInterface { + public: + virtual unsigned long millis() override { + static unsigned long value = 0; + value += 1000; + return value; + } +}; + +TEST(RgbwDimmerTests, InitializationWithDefaultValues) { + TimeInterfaceMock time; + + EXPECT_CALL(time, millis) + .WillOnce(Return(1)) + .WillOnce(Return(500)); + + RgbwBaseForTest rgb; + + ASSERT_NE(rgb.getChannel(), nullptr); + + auto ch = rgb.getChannel(); + + EXPECT_EQ(ch->getChannelType(), SUPLA_CHANNELTYPE_DIMMERANDRGBLED); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.onInit(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + +} + + +TEST(RgbwDimmerTests, FrequentSetCommandShouldNotChangeChannelValue) { + TimeInterfaceMock time; + + EXPECT_CALL(time, millis) + .WillOnce(Return(100)) // #1 setRgb (oninit) + .WillOnce(Return(200)) // #1 iterateAlways + .WillOnce(Return(200)) // #2 manual setRgb + .WillOnce(Return(300)) // #2 iterateAlways + .WillOnce(Return(300)) // #3 manual setRgb + .WillOnce(Return(400)) // #3 iterateAlways + .WillOnce(Return(400)) // #4 manual setRgb + .WillOnce(Return(500)) // #4 iterateAlways + .WillOnce(Return(500)) // #5 manual setRgb - should change channel value + .WillOnce(Return(1000)) // #5 iterateAlways +// .WillOnce(Return(1100)) // #6 manual setRgb with toggle - it don't call millis + .WillOnce(Return(1200)) // #6 iterateAlways + ; + RgbwBaseForTest rgb; + + ASSERT_NE(rgb.getChannel(), nullptr); + + auto ch = rgb.getChannel(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + // #1 + rgb.onInit(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + // #2 + rgb.setRGBW(1, 2, 3, 4, 5, false); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + // #3 + rgb.setRGBW(11, 12, 13, 14, 15, false); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + // #4 + rgb.setRGBW(21, 22, 23, 24, 25, false); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + // #5 + rgb.setRGBW(31, 32, 33, 34, 35, false); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 31); + EXPECT_EQ(ch->getValueGreen(), 32); + EXPECT_EQ(ch->getValueBlue(), 33); + EXPECT_EQ(ch->getValueColorBrightness(), 34); + EXPECT_EQ(ch->getValueBrightness(), 35); + + // #6 - with toggle + rgb.setRGBW(41, 42, 43, 44, 45, true); + + EXPECT_EQ(ch->getValueRed(), 31); + EXPECT_EQ(ch->getValueGreen(), 32); + EXPECT_EQ(ch->getValueBlue(), 33); + EXPECT_EQ(ch->getValueColorBrightness(), 34); + EXPECT_EQ(ch->getValueBrightness(), 35); + + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 41); + EXPECT_EQ(ch->getValueGreen(), 42); + EXPECT_EQ(ch->getValueBlue(), 43); + EXPECT_EQ(ch->getValueColorBrightness(), 44); + EXPECT_EQ(ch->getValueBrightness(), 45); +} + +TEST(RgbwDimmerTests, SetValueFromServer) { + TimeInterfaceMock time; + + EXPECT_CALL(time, millis) + .WillOnce(Return(1)) + .WillOnce(Return(500)) + .WillOnce(Return(500)) + .WillOnce(Return(1000)) + .WillOnce(Return(2000)) + .WillOnce(Return(3000)) + .WillOnce(Return(4000)) + .WillOnce(Return(5000)) + ; + + RgbwBaseForTest rgb; + + auto ch = rgb.getChannel(); + + rgb.onInit(); + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + TSD_SuplaChannelNewValue msg = {}; + msg.value[5] = 0; // turn on/off + msg.value[4] = 1; // red + msg.value[3] = 2; // green + msg.value[2] = 3; // blue + msg.value[1] = 4; // colorBrightness + msg.value[0] = 5; // brightness + rgb.handleNewValueFromServer(&msg); + + // channel values should be changed only after some time passed + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + + // a little bit later, values are set to channel + EXPECT_EQ(ch->getValueRed(), 1); + EXPECT_EQ(ch->getValueGreen(), 2); + EXPECT_EQ(ch->getValueBlue(), 3); + EXPECT_EQ(ch->getValueColorBrightness(), 4); + EXPECT_EQ(ch->getValueBrightness(), 5); + +// with toggle - should turn off dimmer + msg.value[5] = 1; + msg.value[0] = 0; + rgb.handleNewValueFromServer(&msg); + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 1); + EXPECT_EQ(ch->getValueGreen(), 2); + EXPECT_EQ(ch->getValueBlue(), 3); + EXPECT_EQ(ch->getValueColorBrightness(), 4); + EXPECT_EQ(ch->getValueBrightness(), 0); + +// with toggle - should turn on dimmer and restore last brightness + msg.value[5] = 1; + msg.value[0] = 100; + rgb.handleNewValueFromServer(&msg); + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 1); + EXPECT_EQ(ch->getValueGreen(), 2); + EXPECT_EQ(ch->getValueBlue(), 3); + EXPECT_EQ(ch->getValueColorBrightness(), 4); + EXPECT_EQ(ch->getValueBrightness(), 5); + +// with toggle - should turn off rgb + msg.value[5] = 1; + msg.value[1] = 0; + msg.value[0] = 5; // restore brightness so it is consistant with last value + rgb.handleNewValueFromServer(&msg); + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 1); + EXPECT_EQ(ch->getValueGreen(), 2); + EXPECT_EQ(ch->getValueBlue(), 3); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 5); + +// with toggle - should turn on dimmer and restore last brightness + msg.value[5] = 1; + msg.value[1] = 100; + rgb.handleNewValueFromServer(&msg); + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 1); + EXPECT_EQ(ch->getValueGreen(), 2); + EXPECT_EQ(ch->getValueBlue(), 3); + EXPECT_EQ(ch->getValueColorBrightness(), 4); + EXPECT_EQ(ch->getValueBrightness(), 5); + +} + +TEST(RgbwDimmerTests, TurnOnOffToggleTests) { + TimeInterfaceMock time; + + EXPECT_CALL(time, millis) + .WillOnce(Return(1)) + .WillOnce(Return(500)) + .WillOnce(Return(1000)) + .WillOnce(Return(2000)) + .WillOnce(Return(3000)) + .WillOnce(Return(4000)) + .WillOnce(Return(5000)) + .WillOnce(Return(6000)) + .WillOnce(Return(7000)) + .WillOnce(Return(8000)) + .WillOnce(Return(9000)) + .WillOnce(Return(10000)) + ; + + RgbwBaseForTest rgb; + + auto ch = rgb.getChannel(); + + rgb.onInit(); + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.turnOn(); + // channel values should be changed only after some time passed + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + + // a little bit later, values are set to channel + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 100); + + rgb.turnOff(); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.setRGBW(0, 255, 0, 50, 40, false); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 50); + EXPECT_EQ(ch->getValueBrightness(), 40); + + rgb.toggle(); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.toggle(); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 50); + EXPECT_EQ(ch->getValueBrightness(), 40); +} + +TEST(RgbwDimmerTests, HandleActionTests) { + // time stub will return +1000 ms on each call to millis + TimeInterfaceStub time; + + RgbwBaseForTest rgb; + + auto ch = rgb.getChannel(); + + rgb.setStep(10); + rgb.onInit(); + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::TURN_ON); + // channel values should be changed only after some time passed + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.iterateAlways(); + + // a little bit later, values are set to channel + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 100); + + rgb.handleAction(1, Supla::TURN_OFF); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::BRIGHTEN_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 10); + EXPECT_EQ(ch->getValueBrightness(), 10); + + rgb.handleAction(1, Supla::BRIGHTEN_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 20); + + rgb.handleAction(1, Supla::TOGGLE); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::TOGGLE); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 20); + + rgb.handleAction(1, Supla::DIM_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 10); + EXPECT_EQ(ch->getValueBrightness(), 10); + + rgb.handleAction(1, Supla::DIM_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::DIM_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::TURN_ON); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 10); + EXPECT_EQ(ch->getValueBrightness(), 10); + + for (int i = 0; i < 20; i++) { + rgb.handleAction(1, Supla::BRIGHTEN_ALL); + }; + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 100); + + rgb.handleAction(1, Supla::BRIGHTEN_R); + rgb.handleAction(1, Supla::BRIGHTEN_R); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 20); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 100); + + rgb.handleAction(1, Supla::DIM_R); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 100); + + rgb.handleAction(1, Supla::DIM_G); + rgb.handleAction(1, Supla::DIM_G); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 235); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 100); + + rgb.handleAction(1, Supla::BRIGHTEN_G); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 100); + + + rgb.handleAction(1, Supla::BRIGHTEN_B); + rgb.handleAction(1, Supla::BRIGHTEN_B); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 20); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 100); + + rgb.handleAction(1, Supla::DIM_B); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 100); + + rgb.handleAction(1, Supla::DIM_W); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 90); + + rgb.handleAction(1, Supla::DIM_RGB); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 90); + EXPECT_EQ(ch->getValueBrightness(), 90); + + rgb.handleAction(1, Supla::BRIGHTEN_W); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 90); + EXPECT_EQ(ch->getValueBrightness(), 100); + + rgb.handleAction(1, Supla::BRIGHTEN_RGB); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 100); + + rgb.handleAction(1, Supla::DIM_ALL); + rgb.handleAction(1, Supla::TURN_OFF_RGB); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 90); + + rgb.handleAction(1, Supla::TURN_ON_RGB); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 90); + EXPECT_EQ(ch->getValueBrightness(), 90); + + rgb.handleAction(1, Supla::TURN_OFF_W); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 90); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::TURN_ON_W); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 90); + EXPECT_EQ(ch->getValueBrightness(), 90); + + rgb.handleAction(1, Supla::TOGGLE_RGB); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 90); + + rgb.handleAction(1, Supla::TOGGLE_RGB); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 90); + EXPECT_EQ(ch->getValueBrightness(), 90); + + rgb.handleAction(1, Supla::TOGGLE_W); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 90); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::TOGGLE_W); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 90); + EXPECT_EQ(ch->getValueBrightness(), 90); + + rgb.handleAction(1, Supla::TURN_ON_RGB_DIMMED); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 90); + EXPECT_EQ(ch->getValueBrightness(), 90); + + rgb.handleAction(1, Supla::TURN_ON_W_DIMMED); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 90); + EXPECT_EQ(ch->getValueBrightness(), 90); + + rgb.handleAction(1, Supla::TURN_ON_ALL_DIMMED); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 90); + EXPECT_EQ(ch->getValueBrightness(), 90); + + rgb.handleAction(1, Supla::TURN_OFF); + rgb.handleAction(1, Supla::TURN_ON_RGB_DIMMED); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::TURN_ON_W_DIMMED); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 20); + + rgb.handleAction(1, Supla::TURN_OFF); + rgb.handleAction(1, Supla::TURN_ON_ALL_DIMMED); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 20); + + rgb.handleAction(1, Supla::ITERATE_DIM_RGB); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 30); + EXPECT_EQ(ch->getValueBrightness(), 20); + + rgb.handleAction(1, Supla::ITERATE_DIM_RGB); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 40); + EXPECT_EQ(ch->getValueBrightness(), 20); + + for (int i = 0; i < 6; i++) { + rgb.handleAction(1, Supla::ITERATE_DIM_RGB); + } + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 20); + + // after hitting 100%, iterate should stay 4 iterations before it goes down + for (int i = 0; i < 4; i++) { + rgb.handleAction(1, Supla::ITERATE_DIM_RGB); + } + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 20); + + // next five iterations should change brightness to 50 + for (int i = 0; i < 5; i++) { + rgb.handleAction(1, Supla::ITERATE_DIM_RGB); + } + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 50); + EXPECT_EQ(ch->getValueBrightness(), 20); + + // next five iterations should change brightness to 0 + for (int i = 0; i < 4; i++) { + rgb.handleAction(1, Supla::ITERATE_DIM_RGB); + } + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 10); + EXPECT_EQ(ch->getValueBrightness(), 20); + + // next four iterations should keep 0 + for (int i = 0; i < 4; i++) { + rgb.handleAction(1, Supla::ITERATE_DIM_RGB); + } + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 10); + EXPECT_EQ(ch->getValueBrightness(), 20); + + rgb.handleAction(1, Supla::ITERATE_DIM_RGB); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 20); + + // turn off + rgb.handleAction(1, Supla::TURN_OFF); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + // after turn off, it should start from dimmed value + rgb.handleAction(1, Supla::ITERATE_DIM_RGB); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 10); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::ITERATE_DIM_RGB); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::ITERATE_DIM_W); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 10); + + rgb.handleAction(1, Supla::ITERATE_DIM_W); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 20); + + for (int i = 0; i < 12; i++) { + rgb.handleAction(1, Supla::ITERATE_DIM_W); + } + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 100); + + // if we iterate all, colorBrightness is copied to brightness and it operated on both values in sync + rgb.handleAction(1, Supla::ITERATE_DIM_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 30); + EXPECT_EQ(ch->getValueBrightness(), 30); + + rgb.handleAction(1, Supla::ITERATE_DIM_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 10); + EXPECT_EQ(ch->getValueGreen(), 245); + EXPECT_EQ(ch->getValueBlue(), 10); + EXPECT_EQ(ch->getValueColorBrightness(), 40); + EXPECT_EQ(ch->getValueBrightness(), 40); +} + + +TEST(RgbwDimmerTests, DefaultDimmedValue) { + // time stub will return +1000 ms on each call to millis + TimeInterfaceStub time; + + RgbwBaseForTest rgb; + + auto ch = rgb.getChannel(); + + rgb.onInit(); + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::TURN_ON_ALL_DIMMED); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 20); + + rgb.handleAction(1, Supla::TURN_OFF); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.setDefaultDimmedBrightness(64); + rgb.handleAction(1, Supla::TURN_ON_ALL_DIMMED); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 64); + EXPECT_EQ(ch->getValueBrightness(), 64); +} + +TEST(RgbwDimmerTests, IterationSteps) { + // time stub will return +1000 ms on each call to millis + TimeInterfaceStub time; + + RgbwBaseForTest rgb; + rgb.setStep(10); + + auto ch = rgb.getChannel(); + + rgb.onInit(); + rgb.iterateAlways(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgb.handleAction(1, Supla::ITERATE_DIM_ALL); + rgb.handleAction(1, Supla::ITERATE_DIM_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 20); + EXPECT_EQ(ch->getValueBrightness(), 20); + + rgb.setStep(5); + rgb.handleAction(1, Supla::ITERATE_DIM_ALL); + rgb.iterateAlways(); + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 25); + EXPECT_EQ(ch->getValueBrightness(), 25); +} + +TEST(RgbwDimmerTests, SetValueOnDeviceWithoutFading) { + // time stub will return +1000 ms on each call to millis + TimeInterfaceStub time; + ::testing::InSequence seq; + + RgbwBaseForTest rgb; + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 0, 0)); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 1023, 1023)); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 0, 0)); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 0, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 0, 0)); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 1023, (20*1023/100))); + + + auto ch = rgb.getChannel(); + + // disable fade effect - so value setting on device should happen instantly + rgb.setFadeEffectTime(0); + rgb.onInit(); + rgb.onTimer(); + rgb.turnOn(); + rgb.onTimer(); + rgb.toggle(); + rgb.onTimer(); + rgb.handleAction(1, Supla::TURN_ON_W_DIMMED); + rgb.onTimer(); + rgb.turnOff(); + rgb.onTimer(); + rgb.turnOn(); + rgb.onTimer(); + + // channel value should be still empty, since no time elapsed (no calls to iterateAlways) + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); +} + +TEST(RgbwDimmerTests, SetValueOnDeviceWithFading) { + // time stub will return +1000 ms on each call to millis + TimeInterfaceStub time; + ::testing::InSequence seq; + + RgbwBaseForTest rgb; + + // fade effect 1000 ms, time step 1000 ms + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 0, 0)); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 1023, 1023)); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 0, 0)); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 0, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 0, 0)); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 1023, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 0, 0)); + + // fade effect 10000 ms, time step 1000 ms + // because turnOn calls millis once, we actually do 2 s step in first shot + // that is why (0, 255, 0, 10, 10) is missing + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 204, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 306, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 408, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 510, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 612, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 714, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 816, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 918, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 1020, (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(0, 1023, 0, 1023, (20*1023/100))); + + // fade effect 10000 ms, time step 1000 ms + // because turnOn calls millis once, we actually do 2 s step in first shot + // setting rgb values + EXPECT_CALL(rgb, setRGBWValueOnDevice(204, 818, 0, (100*1023/100), (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(306, 802, 0, (100*1023/100), (20*1023/100))); + EXPECT_CALL(rgb, setRGBWValueOnDevice(401, (1023*200/255), 0, (100*1023/100), (20*1023/100))); + + auto ch = rgb.getChannel(); + + // time stub gives +1000 ms on each call to millis, and fade time is 1000 ms, + // so it should set value on device as it is + rgb.onInit(); + rgb.onTimer(); + rgb.turnOn(); + rgb.onTimer(); + rgb.toggle(); + rgb.onTimer(); + rgb.handleAction(1, Supla::TURN_ON_W_DIMMED); + rgb.onTimer(); + rgb.turnOff(); + rgb.onTimer(); + rgb.turnOn(); + rgb.onTimer(); + rgb.turnOff(); + rgb.onTimer(); + + // change fade effect to 10000 ms, so we'll get 1/10 steps + rgb.setFadeEffectTime(10000); + rgb.turnOn(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + + rgb.setRGBW(100, 200, 0, -1, -1, false); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + rgb.onTimer(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); +} + +class StorageMock: public Supla::Storage { + public: + MOCK_METHOD(void, scheduleSave, (unsigned long), (override)); + MOCK_METHOD(void, commit, (), (override)); + MOCK_METHOD(int, readStorage, (unsigned int, unsigned char *, int, bool), (override)); + MOCK_METHOD(int, writeStorage, (unsigned int, const unsigned char *, int), (override)); + MOCK_METHOD(bool, readState, (unsigned char *, int), (override)); + MOCK_METHOD(bool, writeState, (const unsigned char *, int), (override)); + +}; + +using ::testing::_; +using ::testing::SetArgPointee; +using ::testing::DoAll; +using ::testing::Pointee; + +TEST(RgbwDimmerTests, RgbwStorageTests) { + // time stub will return +1000 ms on each call to millis + TimeInterfaceStub time; + ::testing::InSequence seq; + + StorageMock storage; + RgbwBaseForTest rgb; + + // setRGBW should call scheduleSave on storage once + EXPECT_CALL(storage, scheduleSave(5000)); + + uint8_t red = 1; + uint8_t green = 2; + uint8_t blue = 3; + uint8_t colorBrightness = 4; + uint8_t brightness = 5; + uint8_t lastColorBrightness = 6; + uint8_t lastBrightness = 7; + + // onLoadState expectations + EXPECT_CALL(storage, readState(_, 1)) + .WillOnce(DoAll(SetArgPointee<0>(red), Return(true))) + .WillOnce(DoAll(SetArgPointee<0>(green), Return(true))) + .WillOnce(DoAll(SetArgPointee<0>(blue), Return(true))) + .WillOnce(DoAll(SetArgPointee<0>(colorBrightness), Return(true))) + .WillOnce(DoAll(SetArgPointee<0>(brightness), Return(true))) + .WillOnce(DoAll(SetArgPointee<0>(lastColorBrightness), Return(true))) + .WillOnce(DoAll(SetArgPointee<0>(lastBrightness), Return(true))) + ; + + // onSaveState expectations + EXPECT_CALL(storage, writeState(Pointee(red), 1)); + EXPECT_CALL(storage, writeState(Pointee(green), 1)); + EXPECT_CALL(storage, writeState(Pointee(blue), 1)); + EXPECT_CALL(storage, writeState(Pointee(colorBrightness), 1)); + EXPECT_CALL(storage, writeState(Pointee(brightness), 1)); + EXPECT_CALL(storage, writeState(Pointee(lastColorBrightness), 1)); + EXPECT_CALL(storage, writeState(Pointee(lastBrightness), 1)); + + rgb.setRGBW(1, 2, 3, 4, 5, false); + + rgb.onLoadState(); + rgb.onSaveState(); + +} + diff --git a/extras/test/RgbwDimmerTests/rgbw_leds_test.cpp b/extras/test/RgbwDimmerTests/rgbw_leds_test.cpp new file mode 100644 index 00000000..af398445 --- /dev/null +++ b/extras/test/RgbwDimmerTests/rgbw_leds_test.cpp @@ -0,0 +1,92 @@ +/* + 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 TimeInterfaceStub : public TimeInterface { + public: + virtual unsigned long millis() override { + static unsigned long value = 0; + value += 1000; + return value; + } +}; + +TEST(RgbwLedsTests, SettingNewRGBWValue) { + TimeInterfaceStub time; + DigitalInterfaceMock ioMock; + + EXPECT_CALL(ioMock, pinMode(1, OUTPUT)); + EXPECT_CALL(ioMock, pinMode(2, OUTPUT)); + EXPECT_CALL(ioMock, pinMode(3, OUTPUT)); + EXPECT_CALL(ioMock, pinMode(4, OUTPUT)); + EXPECT_CALL(ioMock, analogWrite(1, 0)); + EXPECT_CALL(ioMock, analogWrite(2, 0)); + EXPECT_CALL(ioMock, analogWrite(3, 0)); + EXPECT_CALL(ioMock, analogWrite(4, 0)); + + EXPECT_CALL(ioMock, analogWrite(1, (1*1023/255))); + EXPECT_CALL(ioMock, analogWrite(2, (2*1023/255))); + EXPECT_CALL(ioMock, analogWrite(3, (3*1023/255))); + EXPECT_CALL(ioMock, analogWrite(4, 1023)); + + Supla::Control::RGBWLeds rgbw(1, 2, 3, 4); + + auto ch = rgbw.getChannel(); + // disable fading effect so we'll get instant setting value on device call + rgbw.setFadeEffectTime(0); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgbw.onInit(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 0); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgbw.iterateAlways(); + rgbw.onTimer(); + + EXPECT_EQ(ch->getValueRed(), 0); + EXPECT_EQ(ch->getValueGreen(), 255); + EXPECT_EQ(ch->getValueBlue(), 0); + EXPECT_EQ(ch->getValueColorBrightness(), 0); + EXPECT_EQ(ch->getValueBrightness(), 0); + + rgbw.setRGBW(1, 2, 3, 100, 100); + + rgbw.iterateAlways(); + rgbw.onTimer(); + + EXPECT_EQ(ch->getValueRed(), 1); + EXPECT_EQ(ch->getValueGreen(), 2); + EXPECT_EQ(ch->getValueBlue(), 3); + EXPECT_EQ(ch->getValueColorBrightness(), 100); + EXPECT_EQ(ch->getValueBrightness(), 100); + +} + diff --git a/extras/test/SuplaDeviceTests/supla_device_full_startup_tests.cpp b/extras/test/SuplaDeviceTests/supla_device_full_startup_tests.cpp new file mode 100644 index 00000000..3024574e --- /dev/null +++ b/extras/test/SuplaDeviceTests/supla_device_full_startup_tests.cpp @@ -0,0 +1,234 @@ +/* + 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 +#include +#include + +using ::testing::Return; +using ::testing::_; +using ::testing::DoAll; +using ::testing::Assign; +using ::testing::ReturnPointee; + +class SuplaDeviceTests : public ::testing::Test { + protected: + virtual void SetUp() { + Supla::Channel::lastCommunicationTimeMs = 0; + memset(&(Supla::Channel::reg_dev), 0, sizeof(Supla::Channel::reg_dev)); + } + virtual void TearDown() { + Supla::Channel::lastCommunicationTimeMs = 0; + memset(&(Supla::Channel::reg_dev), 0, sizeof(Supla::Channel::reg_dev)); + } + +}; + +class TimeInterfaceStub : public TimeInterface { + public: + virtual unsigned long millis() override { + static unsigned long value = 0; + value += 1000; + return value; + } +}; + +class StorageMock2: public Supla::Storage { + public: + MOCK_METHOD(bool, init, (), (override)); + MOCK_METHOD(bool, prepareState, (bool), (override)); + MOCK_METHOD(bool, finalizeSaveState, (), (override)); + MOCK_METHOD(void, commit, (), (override)); + MOCK_METHOD(int, readStorage, (unsigned int, unsigned char *, int, bool), (override)); + MOCK_METHOD(int, writeStorage, (unsigned int, const unsigned char *, int), (override)); + +}; + +class ElementMock : public Supla::Element { + public: + MOCK_METHOD(void, onInit, (), (override)); + MOCK_METHOD(void, onLoadState, (), (override)); + MOCK_METHOD(void, onSaveState, (), (override)); + MOCK_METHOD(void, iterateAlways, (), (override)); + MOCK_METHOD(bool, iterateConnected, (void *), (override)); + MOCK_METHOD(void, onTimer, (), (override)); + MOCK_METHOD(void, onFastTimer, (), (override)); + MOCK_METHOD(int, handleNewValueFromServer, (TSD_SuplaChannelNewValue *), (override)); + MOCK_METHOD(void, handleGetChannelState, (TDSC_ChannelState &), (override)); + MOCK_METHOD(int, handleCalcfgFromServer, (TSD_DeviceCalCfgRequest *), (override)); + +}; + +class NetworkMock : public Supla::Network { + public: + NetworkMock() : Supla::Network(nullptr) {}; + MOCK_METHOD(int, read, (void *, int ), (override)); + MOCK_METHOD(int, write, (void *, int ), (override)); + MOCK_METHOD(int, connect, (const char *, int), (override)); + MOCK_METHOD(bool, connected, (), (override)); + MOCK_METHOD(void, disconnect, (), (override)); + MOCK_METHOD(void, setup, (), (override)); + MOCK_METHOD(void, setTimeout, (int), (override)); + + MOCK_METHOD(bool, isReady, (), (override)); + MOCK_METHOD(bool, iterate, (), (override)); + MOCK_METHOD(bool, ping, (void *), (override)); + +}; + +class SuplaDeviceTestsFullStartup : public SuplaDeviceTests { + protected: + SrpcMock srpc; + NetworkMock net; + TimerMock timer; + TimeInterfaceStub time; + SuplaDeviceClass sd; + ElementMock el1; + ElementMock el2; + + virtual void SetUp() { + SuplaDeviceTests::SetUp(); + + int dummy; + + EXPECT_CALL(el1, onInit()); + EXPECT_CALL(el2, onInit()); + + EXPECT_CALL(timer, initTimers()); + EXPECT_CALL(net, setup()); + EXPECT_CALL(srpc, srpc_params_init(_)); + EXPECT_CALL(srpc, srpc_init(_)).WillOnce(Return(&dummy)); + EXPECT_CALL(srpc, srpc_set_proto_version(&dummy, 12)); + + char GUID[SUPLA_GUID_SIZE] = {1}; + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {2}; + EXPECT_TRUE(sd.begin(GUID, "supla.rulez", "superman@supla.org", AUTHKEY)); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_INITIALIZED); + } + + virtual void TearDown() { + SuplaDeviceTests::TearDown(); + } +}; + +using ::testing::AtLeast; + +TEST_F(SuplaDeviceTestsFullStartup, NoNetworkShouldCallSetupAgain) { + EXPECT_CALL(net, isReady()).WillRepeatedly(Return(false)); + EXPECT_CALL(net, setup()).Times(2); + EXPECT_CALL(el1, iterateAlways()).Times(AtLeast(1)); + EXPECT_CALL(el2, iterateAlways()).Times(AtLeast(1)); + + for (int i = 0; i < 50*30; i++) sd.iterate(); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_NETWORK_DISCONNECTED); +} + +TEST_F(SuplaDeviceTestsFullStartup, FailedConnectionShouldSetupNetworkAgain) { + EXPECT_CALL(net, isReady()).WillRepeatedly(Return(true)); + EXPECT_CALL(net, connected()).WillRepeatedly(Return(false)); + EXPECT_CALL(net, connect(_, _)).WillRepeatedly(Return(0)); + EXPECT_CALL(net, disconnect()).Times(AtLeast(1)); + + EXPECT_CALL(net, setup()).Times(1); + EXPECT_CALL(el1, iterateAlways()).Times(AtLeast(1)); + EXPECT_CALL(el2, iterateAlways()).Times(AtLeast(1)); + + for (int i = 0; i < 2*31; i++) sd.iterate(); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_SERVER_DISCONNECTED); +} + +TEST_F(SuplaDeviceTestsFullStartup, SrpcFailureShouldCallDisconnect) { + EXPECT_CALL(net, isReady()).WillRepeatedly(Return(true)); + EXPECT_CALL(net, connected()).WillOnce(Return(false)).WillRepeatedly(Return(false)); + EXPECT_CALL(net, connect(_, _)).WillRepeatedly(Return(1)); + EXPECT_CALL(net, iterate()).Times(1); + EXPECT_CALL(srpc, srpc_iterate(_)).WillOnce(Return(SUPLA_RESULT_FALSE)); + + EXPECT_CALL(net, disconnect()).Times(1); + + EXPECT_CALL(el1, iterateAlways()).Times(AtLeast(1)); + EXPECT_CALL(el2, iterateAlways()).Times(AtLeast(1)); + + sd.iterate(); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_ITERATE_FAIL); +} + +TEST_F(SuplaDeviceTestsFullStartup, NoReplyForDeviceRegistrationShoudResetConnection) { + bool isConnected = false; + EXPECT_CALL(net, isReady()).WillRepeatedly(Return(true)); + EXPECT_CALL(net, connected()).WillRepeatedly(ReturnPointee(&isConnected)); + EXPECT_CALL(net, connect(_, _)).WillRepeatedly(DoAll(Assign(&isConnected, true), Return(1))); + + EXPECT_CALL(net, iterate()).Times(AtLeast(1)); + EXPECT_CALL(srpc, srpc_iterate(_)).WillRepeatedly(Return(SUPLA_RESULT_TRUE)); + + EXPECT_CALL(el1, iterateAlways()).Times(AtLeast(1)); + EXPECT_CALL(el2, iterateAlways()).Times(AtLeast(1)); + + EXPECT_CALL(net, disconnect()).WillOnce(Assign(&isConnected, false)); + + EXPECT_CALL(srpc, srpc_ds_async_registerdevice_e(_, _)).Times(2); + + for (int i = 0; i < 11; i++) sd.iterate(); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_SERVER_DISCONNECTED); + for (int i = 0; i < 2; i++) sd.iterate(); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_REGISTER_IN_PROGRESS); +} + + +TEST_F(SuplaDeviceTestsFullStartup, SuccessfulStartup) { + bool isConnected = false; + EXPECT_CALL(net, isReady()).WillRepeatedly(Return(true)); + EXPECT_CALL(net, connected()).WillRepeatedly(ReturnPointee(&isConnected)); + EXPECT_CALL(net, connect(_, _)).WillRepeatedly(DoAll(Assign(&isConnected, true), Return(1))); + + EXPECT_CALL(net, iterate()).Times(AtLeast(1)); + EXPECT_CALL(srpc, srpc_iterate(_)).WillRepeatedly(Return(SUPLA_RESULT_TRUE)); + + EXPECT_CALL(el1, iterateAlways()).Times(35); + EXPECT_CALL(el2, iterateAlways()).Times(35); + + EXPECT_CALL(srpc, srpc_ds_async_registerdevice_e(_, _)).Times(1); + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(1); + + EXPECT_CALL(net, ping(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(el1, iterateConnected(_)).Times(30).WillRepeatedly(Return(true)); + EXPECT_CALL(el2, iterateConnected(_)).Times(30).WillRepeatedly(Return(true)); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_INITIALIZED); + for (int i = 0; i < 5; i++) sd.iterate(); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_REGISTER_IN_PROGRESS); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_TRUE; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_REGISTERED_AND_READY); + + for (int i = 0; i < 30; i++) sd.iterate(); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_REGISTERED_AND_READY); +} + diff --git a/extras/test/SuplaDeviceTests/supla_device_tests.cpp b/extras/test/SuplaDeviceTests/supla_device_tests.cpp new file mode 100644 index 00000000..e6cf3ed0 --- /dev/null +++ b/extras/test/SuplaDeviceTests/supla_device_tests.cpp @@ -0,0 +1,691 @@ +/* + 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 +#include +#include + +using ::testing::Return; +using ::testing::_; +using ::testing::DoAll; +using ::testing::Assign; +using ::testing::ReturnPointee; + +class SuplaDeviceTests : public ::testing::Test { + protected: + virtual void SetUp() { + Supla::Channel::lastCommunicationTimeMs = 0; + memset(&(Supla::Channel::reg_dev), 0, sizeof(Supla::Channel::reg_dev)); + } + virtual void TearDown() { + Supla::Channel::lastCommunicationTimeMs = 0; + memset(&(Supla::Channel::reg_dev), 0, sizeof(Supla::Channel::reg_dev)); + } + +}; + +class TimeInterfaceStub : public TimeInterface { + public: + virtual unsigned long millis() override { + static unsigned long value = 0; + value += 1000; + return value; + } +}; + +TEST_F(SuplaDeviceTests, DefaultValuesTest) { + SuplaDeviceClass sd; + SrpcMock srpc; + TimerMock timer; + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_UNKNOWN); + EXPECT_EQ(sd.getClock(), nullptr); +} + +class ClockMock : public Supla::Clock { + public: + MOCK_METHOD(void, parseLocaltimeFromServer, (TSDC_UserLocalTimeResult *result), (override)); +}; + +TEST_F(SuplaDeviceTests, ClockMethods) { + SuplaDeviceClass sd; + ClockMock clock; + + ASSERT_EQ(sd.getClock(), nullptr); + sd.onGetUserLocaltimeResult(nullptr); + + sd.addClock(&clock); + ASSERT_EQ(sd.getClock(), &clock); + + EXPECT_CALL(clock, parseLocaltimeFromServer(nullptr)).Times(1); + + sd.onGetUserLocaltimeResult(nullptr); +} + +TEST_F(SuplaDeviceTests, StartWithoutNetworkInterfaceNoElements) { + SuplaDeviceClass sd; + TimerMock timer; + + ASSERT_EQ(Supla::Element::begin(), nullptr); + EXPECT_CALL(timer, initTimers()); + + EXPECT_FALSE(sd.begin()); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_MISSING_NETWORK_INTERFACE); +} + +class StorageMock2: public Supla::Storage { + public: + MOCK_METHOD(bool, init, (), (override)); + MOCK_METHOD(bool, prepareState, (bool), (override)); + MOCK_METHOD(bool, finalizeSaveState, (), (override)); + MOCK_METHOD(void, commit, (), (override)); + MOCK_METHOD(int, readStorage, (unsigned int, unsigned char *, int, bool), (override)); + MOCK_METHOD(int, writeStorage, (unsigned int, const unsigned char *, int), (override)); + +}; + +TEST_F(SuplaDeviceTests, StartWithoutNetworkInterfaceNoElementsWithStorage) { + ::testing::InSequence seq; + SuplaDeviceClass sd; + TimerMock timer; + StorageMock2 storage; + + ASSERT_EQ(Supla::Element::begin(), nullptr); + EXPECT_CALL(storage, init()); + EXPECT_CALL(storage, prepareState(true)).WillOnce(Return(true));; + EXPECT_CALL(storage, finalizeSaveState()).WillOnce(Return(false)); + + EXPECT_CALL(timer, initTimers()); + + EXPECT_FALSE(sd.begin()); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_MISSING_NETWORK_INTERFACE); +} + +TEST_F(SuplaDeviceTests, StartWithoutNetworkInterfaceNoElementsWithStorageAndDataLoadAttempt) { + ::testing::InSequence seq; + SuplaDeviceClass sd; + TimerMock timer; + StorageMock2 storage; + + ASSERT_EQ(Supla::Element::begin(), nullptr); + EXPECT_CALL(storage, init()); + EXPECT_CALL(storage, prepareState(true)).WillOnce(Return(true)); + EXPECT_CALL(storage, finalizeSaveState()).WillOnce(Return(true)); + EXPECT_CALL(storage, prepareState(false)); + + EXPECT_CALL(timer, initTimers()); + + EXPECT_FALSE(sd.begin()); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_MISSING_NETWORK_INTERFACE); +} + +class ElementMock : public Supla::Element { + public: + MOCK_METHOD(void, onInit, (), (override)); + MOCK_METHOD(void, onLoadState, (), (override)); + MOCK_METHOD(void, onSaveState, (), (override)); + MOCK_METHOD(void, iterateAlways, (), (override)); + MOCK_METHOD(bool, iterateConnected, (void *), (override)); + MOCK_METHOD(void, onTimer, (), (override)); + MOCK_METHOD(void, onFastTimer, (), (override)); + MOCK_METHOD(int, handleNewValueFromServer, (TSD_SuplaChannelNewValue *), (override)); + MOCK_METHOD(void, handleGetChannelState, (TDSC_ChannelState &), (override)); + MOCK_METHOD(int, handleCalcfgFromServer, (TSD_DeviceCalCfgRequest *), (override)); + +}; + +TEST_F(SuplaDeviceTests, StartWithoutNetworkInterfaceWithElements) { + ::testing::InSequence seq; + SuplaDeviceClass sd; + TimerMock timer; + ElementMock el1; + ElementMock el2; + + ASSERT_NE(Supla::Element::begin(), nullptr); + + EXPECT_CALL(el1, onInit()); + EXPECT_CALL(el2, onInit()); + + EXPECT_CALL(timer, initTimers()); + EXPECT_CALL(el1, onTimer()); + EXPECT_CALL(el2, onTimer()); + EXPECT_CALL(el1, onFastTimer()); + EXPECT_CALL(el2, onFastTimer()); + + + + EXPECT_FALSE(sd.begin()); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_MISSING_NETWORK_INTERFACE); + sd.onTimer(); + sd.onFastTimer(); +} + +TEST_F(SuplaDeviceTests, StartWithoutNetworkInterfaceWithElementsWithStorage) { + ::testing::InSequence seq; + StorageMock2 storage; + SuplaDeviceClass sd; + TimerMock timer; + ElementMock el1; + ElementMock el2; + + ASSERT_NE(Supla::Element::begin(), nullptr); + + EXPECT_CALL(storage, init()); + EXPECT_CALL(storage, prepareState(true)).WillOnce(Return(true)); + EXPECT_CALL(el1, onSaveState()); + EXPECT_CALL(el2, onSaveState()); + + EXPECT_CALL(storage, finalizeSaveState()).WillOnce(Return(true)); + EXPECT_CALL(storage, prepareState(false)); + EXPECT_CALL(el1, onLoadState()); + EXPECT_CALL(el2, onLoadState()); + + EXPECT_CALL(el1, onInit()); + EXPECT_CALL(el2, onInit()); + + EXPECT_CALL(timer, initTimers()); + + EXPECT_FALSE(sd.begin()); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_MISSING_NETWORK_INTERFACE); +} + +class NetworkMock : public Supla::Network { + public: + NetworkMock() : Supla::Network(nullptr) {}; + MOCK_METHOD(int, read, (void *, int ), (override)); + MOCK_METHOD(int, write, (void *, int ), (override)); + MOCK_METHOD(int, connect, (const char *, int), (override)); + MOCK_METHOD(bool, connected, (), (override)); + MOCK_METHOD(void, disconnect, (), (override)); + MOCK_METHOD(void, setup, (), (override)); + MOCK_METHOD(void, setTimeout, (int), (override)); + + MOCK_METHOD(bool, isReady, (), (override)); + MOCK_METHOD(bool, iterate, (), (override)); + MOCK_METHOD(bool, ping, (void *), (override)); + +}; + +TEST_F(SuplaDeviceTests, BeginStopsAtEmptyGUID) { + ::testing::InSequence seq; + NetworkMock net; + TimerMock timer; + + SuplaDeviceClass sd; + + EXPECT_CALL(timer, initTimers()); + + EXPECT_FALSE(sd.begin()); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_INVALID_GUID); +} + +TEST_F(SuplaDeviceTests, BeginStopsAtEmptyServer) { + ::testing::InSequence seq; + NetworkMock net; + TimerMock timer; + + SuplaDeviceClass sd; + + EXPECT_CALL(timer, initTimers()); + + char GUID[SUPLA_GUID_SIZE] = {1}; + sd.setGUID(GUID); + EXPECT_FALSE(sd.begin()); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_UNKNOWN_SERVER_ADDRESS); +} + +TEST_F(SuplaDeviceTests, BeginStopsAtEmptyEmail) { + ::testing::InSequence seq; + NetworkMock net; + TimerMock timer; + + SuplaDeviceClass sd; + + EXPECT_CALL(timer, initTimers()); + + char GUID[SUPLA_GUID_SIZE] = {1}; + sd.setGUID(GUID); + sd.setServer("supla.rulez"); + EXPECT_FALSE(sd.begin()); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_MISSING_CREDENTIALS); +} + + +TEST_F(SuplaDeviceTests, BeginStopsAtEmptyAuthkey) { + ::testing::InSequence seq; + NetworkMock net; + TimerMock timer; + + SuplaDeviceClass sd; + + EXPECT_CALL(timer, initTimers()); + + char GUID[SUPLA_GUID_SIZE] = {1}; + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {2}; + sd.setGUID(GUID); + sd.setServer("supla.rulez"); + sd.setEmail("john@supla"); + EXPECT_FALSE(sd.begin()); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_MISSING_CREDENTIALS); +} + +TEST_F(SuplaDeviceTests, SuccessfulBegin) { + ::testing::InSequence seq; + SrpcMock srpc; + NetworkMock net; + TimerMock timer; + + SuplaDeviceClass sd; + int dummy; + + EXPECT_CALL(timer, initTimers()); + EXPECT_CALL(net, setup()); + EXPECT_CALL(srpc, srpc_params_init(_)); + EXPECT_CALL(srpc, srpc_init(_)).WillOnce(Return(&dummy)); + EXPECT_CALL(srpc, srpc_set_proto_version(&dummy, 12)); + + char GUID[SUPLA_GUID_SIZE] = {1}; + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {2}; + sd.setGUID(GUID); + sd.setServer("supla.rulez"); + sd.setEmail("john@supla"); + sd.setAuthKey(AUTHKEY); + EXPECT_TRUE(sd.begin()); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_INITIALIZED); +} + + + +TEST_F(SuplaDeviceTests, SuccessfulBeginAlternative) { + ::testing::InSequence seq; + SrpcMock srpc; + NetworkMock net; + TimerMock timer; + + SuplaDeviceClass sd; + int dummy; + + EXPECT_CALL(timer, initTimers()); + EXPECT_CALL(net, setup()); + EXPECT_CALL(srpc, srpc_params_init(_)); + EXPECT_CALL(srpc, srpc_init(_)).WillOnce(Return(&dummy)); + EXPECT_CALL(srpc, srpc_set_proto_version(&dummy, 12)); + + char GUID[SUPLA_GUID_SIZE] = {1}; + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {2}; + EXPECT_TRUE(sd.begin(GUID, "supla.rulez", "superman@supla.org", AUTHKEY)); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_INITIALIZED); +} + + +TEST_F(SuplaDeviceTests, FailedBeginAlternativeOnEmptyAUTHKEY) { + ::testing::InSequence seq; + SrpcMock srpc; + NetworkMock net; + TimerMock timer; + + SuplaDeviceClass sd; + int dummy; + + EXPECT_CALL(timer, initTimers()); + + char GUID[SUPLA_GUID_SIZE] = {1}; + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0}; + EXPECT_FALSE(sd.begin(GUID, "supla.rulez", "superman@supla.org", AUTHKEY)); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_MISSING_CREDENTIALS); +} + +TEST_F(SuplaDeviceTests, TwoChannelElementsNoNetworkWithStorage) { + SrpcMock srpc; + NetworkMock net; + StorageMock2 storage; + TimerMock timer; + TimeInterfaceStub time; + SuplaDeviceClass sd; + ElementMock el1; + ElementMock el2; + int dummy; + EXPECT_CALL(storage, prepareState(true)).WillOnce(Return(true)); + EXPECT_CALL(storage, init()); + EXPECT_CALL(el1, onSaveState()); + EXPECT_CALL(el2, onSaveState()); + + EXPECT_CALL(storage, finalizeSaveState()).WillOnce(Return(true)); + EXPECT_CALL(storage, prepareState(false)); + EXPECT_CALL(el1, onLoadState()); + EXPECT_CALL(el2, onLoadState()); + + EXPECT_CALL(el1, onInit()); + EXPECT_CALL(el2, onInit()); + + EXPECT_CALL(timer, initTimers()); + EXPECT_CALL(net, setup()); + EXPECT_CALL(srpc, srpc_params_init(_)); + EXPECT_CALL(srpc, srpc_init(_)).WillOnce(Return(&dummy)); + EXPECT_CALL(srpc, srpc_set_proto_version(&dummy, 12)); + + char GUID[SUPLA_GUID_SIZE] = {1}; + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {2}; + EXPECT_TRUE(sd.begin(GUID, "supla.rulez", "superman@supla.org", AUTHKEY)); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_INITIALIZED); + EXPECT_CALL(el1, iterateAlways()).Times(2); + EXPECT_CALL(el2, iterateAlways()).Times(2); + EXPECT_CALL(net, isReady()).WillRepeatedly(Return(false)); + + EXPECT_CALL(storage, prepareState(false)); + EXPECT_CALL(el1, onSaveState()); + EXPECT_CALL(el2, onSaveState()); + EXPECT_CALL(storage, finalizeSaveState()); + + for (int i = 0; i < 2; i++) sd.iterate(); +} + +TEST_F(SuplaDeviceTests, OnVersionErrorShouldCallDisconnect) { + NetworkMock net; + TimeInterfaceStub time; + + EXPECT_CALL(net, disconnect()).Times(1); + + SuplaDeviceClass sd; + TSDC_SuplaVersionError versionError{}; + + sd.onVersionError(&versionError); + EXPECT_EQ(sd.getCurrentStatus(), STATUS_PROTOCOL_VERSION_ERROR); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultOK) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_TRUE; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_REGISTERED_AND_READY); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultBadCredentials) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_BAD_CREDENTIALS; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_BAD_CREDENTIALS); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultTemporairlyUnavailable) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_TEMPORARILY_UNAVAILABLE; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_TEMPORARILY_UNAVAILABLE); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultLocationConflict) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_LOCATION_CONFLICT; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_LOCATION_CONFLICT); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultChannelConflict) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_CHANNEL_CONFLICT; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_CHANNEL_CONFLICT); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultDeviceDisabled) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_DEVICE_DISABLED; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_DEVICE_IS_DISABLED); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultLocationDisabled) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_LOCATION_DISABLED; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_LOCATION_IS_DISABLED); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultDeviceLimitExceeded) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_DEVICE_LIMITEXCEEDED; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_DEVICE_LIMIT_EXCEEDED); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultGuidError) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_GUID_ERROR; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_INVALID_GUID); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultAuthKeyError) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_AUTHKEY_ERROR; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_INVALID_GUID); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultRegistrationDisabled) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_REGISTRATION_DISABLED; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_REGISTRATION_DISABLED); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultNoLocationAvailable) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_NO_LOCATION_AVAILABLE; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_INVALID_GUID); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultUserConflict) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = SUPLA_RESULTCODE_USER_CONFLICT; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_INVALID_GUID); +} + +TEST_F(SuplaDeviceTests, OnRegisterResultUnknownError) { + NetworkMock net; + SrpcMock srpc; + TimeInterfaceStub time; + SuplaDeviceClass sd; + + EXPECT_CALL(srpc, srpc_dcs_async_set_activity_timeout(_, _)).Times(0); + EXPECT_CALL(net, disconnect()).Times(1); + + TSD_SuplaRegisterDeviceResult register_device_result{}; + register_device_result.result_code = 666; + register_device_result.activity_timeout = 45; + register_device_result.version = 12; + register_device_result.version_min = 1; + + sd.onRegisterResult(®ister_device_result); + + EXPECT_EQ(sd.getCurrentStatus(), STATUS_UNKNOWN_ERROR); +} diff --git a/extras/test/doubles/Arduino.h b/extras/test/doubles/Arduino.h index 518485d2..b3fe6072 100644 --- a/extras/test/doubles/Arduino.h +++ b/extras/test/doubles/Arduino.h @@ -14,11 +14,16 @@ typedef std::string String; #define HIGH 1 #define LOW 0 + +#define F(string_literal) string_literal + void digitalWrite(uint8_t pin, uint8_t val); int digitalRead(uint8_t pin); +void analogWrite(uint8_t pin, int val); void pinMode(uint8_t pin, uint8_t mode); unsigned long millis(); - +void delay(unsigned long ms); +long map(long, long, long, long, long); class SerialStub { public: diff --git a/extras/test/doubles/arduino_mock.cpp b/extras/test/doubles/arduino_mock.cpp index f64576a2..4dbc4916 100644 --- a/extras/test/doubles/arduino_mock.cpp +++ b/extras/test/doubles/arduino_mock.cpp @@ -41,20 +41,35 @@ TimeInterface::~TimeInterface() { TimeInterface *TimeInterface::instance = nullptr; +void analogWrite(uint8_t pin, int val) { + assert(DigitalInterface::instance); + DigitalInterface::instance->analogWrite(pin, val); +} + void digitalWrite(uint8_t pin, uint8_t val) { + assert(DigitalInterface::instance); DigitalInterface::instance->digitalWrite(pin, val); } int digitalRead(uint8_t pin) { + assert(DigitalInterface::instance); return DigitalInterface::instance->digitalRead(pin); } void pinMode(uint8_t pin, uint8_t mode) { + assert(DigitalInterface::instance); DigitalInterface::instance->pinMode(pin, mode); } unsigned long millis() { + assert(TimeInterface::instance); return TimeInterface::instance->millis(); } +void delay(unsigned long ms) {}; + +long map(long input, long inMin, long inMax, long outMin, long outMax) { + long result = (input - inMin) * (outMax - outMin) / (inMax - inMin); + return result + outMin; +} diff --git a/extras/test/doubles/arduino_mock.h b/extras/test/doubles/arduino_mock.h index 8daf5a1e..01f7d372 100644 --- a/extras/test/doubles/arduino_mock.h +++ b/extras/test/doubles/arduino_mock.h @@ -26,6 +26,7 @@ class DigitalInterface { virtual ~DigitalInterface(); virtual void digitalWrite(uint8_t, uint8_t) = 0; virtual int digitalRead(uint8_t) = 0; + virtual void analogWrite(uint8_t pin, int val) = 0; virtual void pinMode(uint8_t, uint8_t) = 0; static DigitalInterface *instance; @@ -44,6 +45,7 @@ class TimeInterface { class DigitalInterfaceMock : public DigitalInterface { public: MOCK_METHOD(void, digitalWrite, (uint8_t, uint8_t), (override)); + MOCK_METHOD(void, analogWrite, (uint8_t, int), (override)); MOCK_METHOD(int, digitalRead, (uint8_t), (override)); MOCK_METHOD(void, pinMode, (uint8_t, uint8_t), (override)); diff --git a/extras/test/doubles/srpc_mock.cpp b/extras/test/doubles/srpc_mock.cpp index 46d5e16c..6b258c51 100644 --- a/extras/test/doubles/srpc_mock.cpp +++ b/extras/test/doubles/srpc_mock.cpp @@ -17,16 +17,84 @@ #include #include "srpc_mock.h" -_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_channel_extendedvalue_changed( +_supla_int_t srpc_ds_async_channel_extendedvalue_changed( void *_srpc, unsigned char channel_number, TSuplaChannelExtendedValue *value) { return 0; } -_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_channel_value_changed( - void *_srpc, unsigned char channel_number, char *value) { +_supla_int_t srpc_ds_async_channel_value_changed_c( + void *_srpc, unsigned char channel_number, char *value, + unsigned char offline, unsigned _supla_int_t validity_time_sec) { + assert(SrpcInterface::instance); std::vector vec(value, value + 8); - return SrpcInterface::instance->valueChanged(_srpc, channel_number, vec); + return SrpcInterface::instance->valueChanged(_srpc, channel_number, vec, offline, validity_time_sec); +} + +_supla_int_t srpc_dcs_async_set_activity_timeout( + void *_srpc, TDCS_SuplaSetActivityTimeout *dcs_set_activity_timeout) { + assert(SrpcInterface::instance); + return SrpcInterface::instance->srpc_dcs_async_set_activity_timeout(_srpc, dcs_set_activity_timeout); +} + +void srpc_params_init(TsrpcParams *params) { + assert(SrpcInterface::instance); + SrpcInterface::instance->srpc_params_init(params); +} + +_supla_int_t srpc_ds_async_set_channel_result(void *_srpc, unsigned char ChannelNumber, _supla_int_t SenderID, char Success) { + assert(SrpcInterface::instance); + return SrpcInterface::instance->srpc_ds_async_set_channel_result(_srpc, ChannelNumber, SenderID, Success); +} + +_supla_int_t srpc_ds_async_device_calcfg_result(void *_srpc, TDS_DeviceCalCfgResult *result) { + assert(SrpcInterface::instance); + return SrpcInterface::instance->srpc_ds_async_device_calcfg_result(_srpc, result); +} + +void *srpc_init(TsrpcParams *params) { + assert(SrpcInterface::instance); + return SrpcInterface::instance->srpc_init(params); +} + +void srpc_rd_free(TsrpcReceivedData *rd) { + assert(SrpcInterface::instance); + SrpcInterface::instance->srpc_rd_free(rd); +} + +char srpc_getdata(void *_srpc, TsrpcReceivedData *rd, unsigned _supla_int_t rr_id) { + assert(SrpcInterface::instance); + return SrpcInterface::instance->srpc_getdata(_srpc, rd, rr_id); +} + +char srpc_iterate(void *_srpc) { + assert(SrpcInterface::instance); + return SrpcInterface::instance->srpc_iterate(_srpc); +} + +void srpc_set_proto_version(void *_srpc, unsigned char version) { + assert(SrpcInterface::instance); + SrpcInterface::instance->srpc_set_proto_version(_srpc, version); +} + +_supla_int_t srpc_ds_async_registerdevice_e(void *_srpc, TDS_SuplaRegisterDevice_E *registerdevice) { + assert(SrpcInterface::instance); + return SrpcInterface::instance->srpc_ds_async_registerdevice_e(_srpc, registerdevice); +} + +_supla_int_t srpc_dcs_async_ping_server(void *_srpc) { + assert(SrpcInterface::instance); + return SrpcInterface::instance->srpc_dcs_async_ping_server(_srpc); +} + +_supla_int_t srpc_csd_async_channel_state_result(void *_srpc, TDSC_ChannelState *state) { + assert(SrpcInterface::instance); + return SrpcInterface::instance->srpc_csd_async_channel_state_result(_srpc, state); +} + +_supla_int_t srpc_dcs_async_get_user_localtime(void *_srpc) { + assert(SrpcInterface::instance); + return SrpcInterface::instance->srpc_dcs_async_get_user_localtime(_srpc); } SrpcInterface::SrpcInterface() { diff --git a/extras/test/doubles/srpc_mock.h b/extras/test/doubles/srpc_mock.h index 1c7d677c..68e262fb 100644 --- a/extras/test/doubles/srpc_mock.h +++ b/extras/test/doubles/srpc_mock.h @@ -19,21 +19,62 @@ #include #include +#include + #include class SrpcInterface { - public: - SrpcInterface(); - virtual ~SrpcInterface(); + public: + SrpcInterface(); + virtual ~SrpcInterface(); + + virtual _supla_int_t valueChanged(void *srpc, + unsigned char channelNumber, + std::vector value, + unsigned char offline, + unsigned _supla_int_t + validity_time_sec) = 0; + + virtual _supla_int_t srpc_dcs_async_set_activity_timeout(void *_srpc, TDCS_SuplaSetActivityTimeout *dcs_set_activity_timeout) = 0; + virtual void srpc_params_init(TsrpcParams *params) = 0; + virtual _supla_int_t srpc_ds_async_set_channel_result(void *_srpc, unsigned char ChannelNumber, _supla_int_t SenderID, char Success) = 0; + virtual _supla_int_t srpc_ds_async_device_calcfg_result(void *_srpc, TDS_DeviceCalCfgResult *result) = 0; + virtual void *srpc_init(TsrpcParams *params) = 0; + virtual void srpc_rd_free(TsrpcReceivedData *rd) = 0; + virtual char srpc_getdata(void *_srpc, TsrpcReceivedData *rd, unsigned _supla_int_t rr_id) = 0; + virtual char srpc_iterate(void *_srpc) = 0; + virtual void srpc_set_proto_version(void *_srpc, unsigned char version) = 0; + virtual _supla_int_t srpc_ds_async_registerdevice_e(void *_srpc, TDS_SuplaRegisterDevice_E *registerdevice) = 0; + virtual _supla_int_t srpc_dcs_async_ping_server(void *_srpc) = 0; + virtual _supla_int_t srpc_csd_async_channel_state_result(void *_srpc, TDSC_ChannelState *state) = 0; + virtual _supla_int_t srpc_dcs_async_get_user_localtime(void *_srpc) = 0; - virtual _supla_int_t valueChanged(void *srpc, unsigned char channelNumber, std::vector value) = 0; - - static SrpcInterface *instance; + static SrpcInterface *instance; }; class SrpcMock : public SrpcInterface { - public: - MOCK_METHOD(_supla_int_t, valueChanged, (void *, unsigned char, std::vector), (override)); + public: + MOCK_METHOD(_supla_int_t, + valueChanged, + (void *, + unsigned char, + std::vector, + unsigned char, + unsigned _supla_int_t), + (override)); + MOCK_METHOD(_supla_int_t, srpc_dcs_async_set_activity_timeout, (void *, TDCS_SuplaSetActivityTimeout *), (override)); + MOCK_METHOD(void, srpc_params_init, (TsrpcParams *), (override)); + MOCK_METHOD(_supla_int_t, srpc_ds_async_set_channel_result, (void *, unsigned char, _supla_int_t, char), (override)); + MOCK_METHOD(_supla_int_t, srpc_ds_async_device_calcfg_result, (void *, TDS_DeviceCalCfgResult *), (override)); + MOCK_METHOD((void *), srpc_init, (TsrpcParams *), (override)); + MOCK_METHOD(void, srpc_rd_free, (TsrpcReceivedData *), (override)); + MOCK_METHOD(char, srpc_getdata, (void *, TsrpcReceivedData *, unsigned _supla_int_t), (override)); + MOCK_METHOD(char, srpc_iterate, (void *), (override)); + MOCK_METHOD(void, srpc_set_proto_version, (void *, unsigned char), (override)); + MOCK_METHOD(_supla_int_t, srpc_ds_async_registerdevice_e, (void *, TDS_SuplaRegisterDevice_E *), (override)); + MOCK_METHOD(_supla_int_t, srpc_dcs_async_ping_server, (void *), (override)); + MOCK_METHOD(_supla_int_t, srpc_csd_async_channel_state_result, (void *, TDSC_ChannelState *), (override)); + MOCK_METHOD(_supla_int_t, srpc_dcs_async_get_user_localtime, (void *), (override)); }; #endif diff --git a/extras/test/doubles/timer_mock.cpp b/extras/test/doubles/timer_mock.cpp new file mode 100644 index 00000000..782dce94 --- /dev/null +++ b/extras/test/doubles/timer_mock.cpp @@ -0,0 +1,34 @@ +/* + 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 + +TimerInterface *timerInterfaceInstance = nullptr; + +TimerInterface::TimerInterface() { + timerInterfaceInstance = this; +} + +TimerInterface::~TimerInterface() { + timerInterfaceInstance = nullptr; +} + + +void Supla::initTimers() { + assert(timerInterfaceInstance); + timerInterfaceInstance->initTimers(); +} diff --git a/extras/test/doubles/timer_mock.h b/extras/test/doubles/timer_mock.h new file mode 100644 index 00000000..85ba1783 --- /dev/null +++ b/extras/test/doubles/timer_mock.h @@ -0,0 +1,32 @@ +/* + 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 + +class TimerInterface { + public: + TimerInterface(); + virtual ~TimerInterface(); + virtual void initTimers() = 0; +}; + +class TimerMock : public TimerInterface { + public: + MOCK_METHOD((void), initTimers, (), (override)); +}; + + diff --git a/library.properties b/library.properties index 87d96835..d27cea12 100644 --- a/library.properties +++ b/library.properties @@ -5,7 +5,7 @@ sentence=Library enables you to connect the device to the SUPLA automation syste paragraph=It provides easy interface for adding various sensors, relays, buttons, roller shutters, etc. that can be controlled via SUPLA Cloud and application on mobile device. url=https://github.com/SUPLA/arduino architectures=avr,esp32,esp8266 -version=2.3.2 +version=2.3.3 dependencies= core-dependencies=arduino (>=1.5.0) category=Communication diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 24760701..96e7d10e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,8 +7,19 @@ set(SRCS supla/element.cpp supla/local_action.cpp supla/channel_element.cpp + + supla/storage/storage.cpp + supla/control/internal_pin_output.cpp supla/control/pin_status_led.cpp + supla/control/rgbw_base.cpp + supla/control/rgb_base.cpp + supla/control/dimmer_base.cpp + supla/control/rgbw_leds.cpp + supla/control/rgb_leds.cpp + supla/control/dimmer_leds.cpp + supla/control/simple_button.cpp + supla/condition.cpp supla/conditions/on_less.cpp supla/conditions/on_less_eq.cpp @@ -17,6 +28,11 @@ set(SRCS supla/conditions/on_between.cpp supla/conditions/on_between_eq.cpp supla/conditions/on_equal.cpp + + SuplaDevice.cpp + supla/network/network.cpp + supla/clock/clock.cpp + ) add_library(supladevicelib SHARED ${SRCS}) diff --git a/src/SuplaDevice.cpp b/src/SuplaDevice.cpp index de04f397..53d139cf 100644 --- a/src/SuplaDevice.cpp +++ b/src/SuplaDevice.cpp @@ -15,6 +15,7 @@ */ #include +#include #include "SuplaDevice.h" #include "supla-common/IEEE754tools.h" @@ -41,11 +42,13 @@ SuplaDeviceClass::SuplaDeviceClass() : port(-1), connectionFailCounter(0), networkIsNotReadyCounter(0), - currentStatus(STATUS_UNKNOWN) { + currentStatus(STATUS_UNKNOWN), + clock(nullptr), + impl_arduino_status(nullptr) { srpc = NULL; registered = 0; - last_iterate_time = 0; - wait_for_iterate = 0; + lastIterateTime = 0; + waitForIterate = 0; } SuplaDeviceClass::~SuplaDeviceClass() { @@ -86,42 +89,50 @@ bool SuplaDeviceClass::begin(unsigned char version) { Supla::Storage::Init(); - if (Supla::Network::Instance() == NULL) { - status(STATUS_MISSING_NETWORK_INTERFACE, "Network Interface not defined!"); - return false; - } - // Supla::Storage::LoadDeviceConfig(); // Supla::Storage::LoadElementConfig(); - // Pefrorm dry run of write state to validate stored state section with current - // device configuration - Serial.println(F("Validating storage state section with current device configuration")); - Supla::Storage::PrepareState(true); - for (auto element = Supla::Element::begin(); element != nullptr; - element = element->next()) { - element->onSaveState(); - } - // If state storage validation was successful, perform read state - if (Supla::Storage::FinalizeSaveState()) { - Serial.println(F("Storage state section validation completed. Loading elements state...")); - // Iterate all elements and load state - Supla::Storage::PrepareState(); + // Pefrorm dry run of write state to validate stored state section with + // current device configuration + if (Supla::Storage::PrepareState(true)) { + Serial.println(F( + "Validating storage state section with current device configuration")); for (auto element = Supla::Element::begin(); element != nullptr; - element = element->next()) { - element->onLoadState(); + element = element->next()) { + element->onSaveState(); + delay(0); + } + // If state storage validation was successful, perform read state + if (Supla::Storage::FinalizeSaveState()) { + Serial.println( + F("Storage state section validation completed. Loading elements " + "state...")); + // Iterate all elements and load state + Supla::Storage::PrepareState(); + for (auto element = Supla::Element::begin(); element != nullptr; + element = element->next()) { + element->onLoadState(); + delay(0); + } } + } else { + Serial.println(F("Storage not found. Running without state memory")); } // Initialize elements for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { element->onInit(); + delay(0); } // Enable timers Supla::initTimers(); + if (Supla::Network::Instance() == NULL) { + status(STATUS_MISSING_NETWORK_INTERFACE, "Network Interface not defined!"); + return false; + } bool emptyGuidDetected = true; for (int i = 0; i < SUPLA_GUID_SIZE; i++) { @@ -170,7 +181,7 @@ bool SuplaDeviceClass::begin(unsigned char version) { if (strnlen(Supla::Channel::reg_dev.SoftVer, SUPLA_SOFTVER_MAXSIZE) == 0) { setString(Supla::Channel::reg_dev.SoftVer, - "User SW, lib 2.3.2", + "User SW, lib 2.3.3", SUPLA_SOFTVER_MAXSIZE); } @@ -185,7 +196,6 @@ bool SuplaDeviceClass::begin(unsigned char version) { srpc_params.user_params = this; srpc = srpc_init(&srpc_params); - Supla::Network::SetSrpc(srpc); // Set Supla protocol interface version srpc_set_proto_version(srpc, version); @@ -236,7 +246,7 @@ void SuplaDeviceClass::iterate(void) { if (!isInitialized(false)) return; unsigned long _millis = millis(); - unsigned long time_diff = abs(_millis - last_iterate_time); + unsigned long timeDiff = abs(_millis - lastIterateTime); uptime.iterate(_millis); @@ -244,6 +254,7 @@ void SuplaDeviceClass::iterate(void) { for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { element->iterateAlways(); + delay(0); } // Iterate all elements and saves state @@ -252,15 +263,15 @@ void SuplaDeviceClass::iterate(void) { for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { element->onSaveState(); + delay(0); } Supla::Storage::FinalizeSaveState(); } - if (wait_for_iterate != 0 && _millis < wait_for_iterate) { + if (waitForIterate != 0 && _millis < waitForIterate) { return; - } else { - wait_for_iterate = 0; + waitForIterate = 0; } // Restart network after >1 min of failed connection attempts @@ -276,7 +287,7 @@ void SuplaDeviceClass::iterate(void) { if (!Supla::Network::IsReady()) { uptime.setConnectionLostCause( SUPLA_LASTCONNECTIONRESETCAUSE_WIFI_CONNECTION_LOST); - wait_for_iterate = millis() + 100; + waitForIterate = _millis + 100; status(STATUS_NETWORK_DISCONNECTED, "No connection to network"); networkIsNotReadyCounter++; if (networkIsNotReadyCounter > 20) { @@ -308,7 +319,7 @@ void SuplaDeviceClass::iterate(void) { Supla::Channel::reg_dev.ServerName); Supla::Network::Disconnect(); - wait_for_iterate = millis() + 2000; + waitForIterate = _millis + 2000; connectionFailCounter++; return; } @@ -320,35 +331,50 @@ void SuplaDeviceClass::iterate(void) { status(STATUS_ITERATE_FAIL, "Iterate fail"); Supla::Network::Disconnect(); - wait_for_iterate = millis() + 5000; + waitForIterate = _millis + 5000; return; } if (registered == 0) { + // Perform registration if we are not yet registered registered = -1; + lastIterateTime = _millis; status(STATUS_REGISTER_IN_PROGRESS, "Register in progress"); if (!srpc_ds_async_registerdevice_e(srpc, &Supla::Channel::reg_dev)) { supla_log(LOG_DEBUG, "Fatal SRPC failure!"); } + } else if (registered == -1) { + // Handle registration timeout (in case of no reply received) + if (timeDiff > 10*1000) { + supla_log(LOG_DEBUG, "No reply to registration message. Resetting connection."); + status(STATUS_SERVER_DISCONNECTED, "Not connected to Supla server"); + Supla::Network::Disconnect(); + + waitForIterate = _millis + 2000; + connectionFailCounter++; + } } else if (registered == 1) { - if (Supla::Network::Ping() == false) { + // Device is registered and everything is correct + if (Supla::Network::Ping(srpc) == false) { uptime.setConnectionLostCause( SUPLA_LASTCONNECTIONRESETCAUSE_ACTIVITY_TIMEOUT); supla_log(LOG_DEBUG, "TIMEOUT - lost connection with server"); + status(STATUS_SERVER_DISCONNECTED, "Not connected to Supla server"); Supla::Network::Disconnect(); } - if (time_diff > 0) { + if (timeDiff > 0) { // Iterate all elements for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { if (!element->iterateConnected(srpc)) { break; } + delay(0); } - last_iterate_time = millis(); + lastIterateTime = _millis; } } } @@ -362,7 +388,7 @@ void SuplaDeviceClass::onVersionError(TSDC_SuplaVersionError *version_error) { Supla::Network::Disconnect(); - wait_for_iterate = millis() + 5000; + waitForIterate = millis() + 5000; } void SuplaDeviceClass::onRegisterResult( @@ -381,7 +407,7 @@ void SuplaDeviceClass::onRegisterResult( register_device_result->activity_timeout, register_device_result->version, register_device_result->version_min); - last_iterate_time = millis(); + lastIterateTime = millis(); status(STATUS_REGISTERED_AND_READY, "Registered and ready."); if (activity_timeout != ACTIVITY_TIMEOUT) { @@ -431,7 +457,7 @@ void SuplaDeviceClass::onRegisterResult( break; case SUPLA_RESULTCODE_REGISTRATION_DISABLED: - status(STATUS_INVALID_GUID, "Registration disabled!"); + status(STATUS_REGISTRATION_DISABLED, "Registration disabled!"); break; case SUPLA_RESULTCODE_NO_LOCATION_AVAILABLE: @@ -443,6 +469,7 @@ void SuplaDeviceClass::onRegisterResult( break; default: + status(STATUS_UNKNOWN_ERROR, "Unknown registration error"); supla_log(LOG_ERR, "Register result code %i", register_device_result->result_code); @@ -450,7 +477,7 @@ void SuplaDeviceClass::onRegisterResult( } Supla::Network::Disconnect(); - wait_for_iterate = millis() + 5000; + waitForIterate = millis() + 5000; } void SuplaDeviceClass::channelSetActivityTimeoutResult( @@ -501,7 +528,8 @@ void SuplaDeviceClass::setServer(const char *server) { Supla::Channel::reg_dev.ServerName, server, SUPLA_SERVER_NAME_MAXSIZE); } -void SuplaDeviceClass::onGetUserLocaltimeResult(TSDC_UserLocalTimeResult *result) { +void SuplaDeviceClass::onGetUserLocaltimeResult( + TSDC_UserLocalTimeResult *result) { if (clock) { clock->parseLocaltimeFromServer(result); } @@ -512,7 +540,7 @@ void SuplaDeviceClass::addClock(Supla::Clock *_clock) { clock = _clock; } -Supla::Clock * SuplaDeviceClass::getClock() { +Supla::Clock *SuplaDeviceClass::getClock() { return clock; } diff --git a/src/SuplaDevice.h b/src/SuplaDevice.h index 5c1067fe..b557b4d4 100644 --- a/src/SuplaDevice.h +++ b/src/SuplaDevice.h @@ -17,8 +17,6 @@ #ifndef SUPLADEVICE_H #define SUPLADEVICE_H -#include - #include "supla-common/proto.h" #include "supla/network/network.h" #include "supla/uptime.h" @@ -49,6 +47,7 @@ #define STATUS_NETWORK_DISCONNECTED 21 #define STATUS_REGISTRATION_DISABLED 22 #define STATUS_MISSING_CREDENTIALS 23 +#define STATUS_UNKNOWN_ERROR 24 typedef void (*_impl_arduino_status)(int status, const char *msg); @@ -60,8 +59,8 @@ class SuplaDeviceClass { int connectionFailCounter; int networkIsNotReadyCounter; - unsigned long last_iterate_time; - unsigned long wait_for_iterate; + unsigned long lastIterateTime; + unsigned long waitForIterate; _impl_arduino_status impl_arduino_status; int currentStatus; diff --git a/src/supla/channel.cpp b/src/supla/channel.cpp index 65a3ea03..c24e7b69 100644 --- a/src/supla/channel.cpp +++ b/src/supla/channel.cpp @@ -27,9 +27,7 @@ namespace Supla { unsigned long Channel::lastCommunicationTimeMs = 0; TDS_SuplaRegisterDevice_E Channel::reg_dev; -Channel::Channel() { - valueChanged = false; - channelNumber = -1; +Channel::Channel() : validityTimeSec(0), channelNumber(-1), valueChanged(false) { if (reg_dev.channel_count < SUPLA_CHANNELMAXCOUNT) { channelNumber = reg_dev.channel_count; @@ -214,8 +212,9 @@ void Channel::clearUpdateReady() { void Channel::sendUpdate(void *srpc) { clearUpdateReady(); - srpc_ds_async_channel_value_changed( - srpc, channelNumber, reg_dev.channels[channelNumber].value); + srpc_ds_async_channel_value_changed_c( + srpc, channelNumber, reg_dev.channels[channelNumber].value, + 0, validityTimeSec); // returns null for non-extended channels TSuplaChannelExtendedValue *extValue = getExtValue(); @@ -328,5 +327,9 @@ uint8_t Channel::getValueBrightness() { return reg_dev.channels[channelNumber].value[0]; } +void Channel::setValidityTimeSec(unsigned _supla_int_t timeSec) { + validityTimeSec = timeSec; + if (validityTimeSec > 0) unsetFlag(SUPLA_CHANNEL_FLAG_CHANNELSTATE); +} }; // namespace Supla diff --git a/src/supla/channel.h b/src/supla/channel.h index 453d6f88..75de480d 100644 --- a/src/supla/channel.h +++ b/src/supla/channel.h @@ -64,6 +64,7 @@ class Channel : public LocalAction { void setFlag(_supla_int_t flag); void unsetFlag(_supla_int_t flag); void setFuncList(_supla_int_t functions); + void setValidityTimeSec(unsigned _supla_int_t); void clearUpdateReady(); void sendUpdate(void *srpc); virtual TSuplaChannelExtendedValue *getExtValue(); @@ -76,7 +77,7 @@ class Channel : public LocalAction { bool valueChanged; int channelNumber; - + unsigned _supla_int_t validityTimeSec; }; }; // namespace Supla diff --git a/src/supla/clock/clock.cpp b/src/supla/clock/clock.cpp index ffcd3f4b..1a329e20 100644 --- a/src/supla/clock/clock.cpp +++ b/src/supla/clock/clock.cpp @@ -82,8 +82,7 @@ int Clock::getSec() { void Clock::parseLocaltimeFromServer(TSDC_UserLocalTimeResult *result) { - struct tm timeinfo; - memset(&timeinfo, 0, sizeof(timeinfo)); + struct tm timeinfo{}; Serial.print(F("Current local time: ")); Serial.print(getYear()); diff --git a/src/supla/control/button.cpp b/src/supla/control/button.cpp index 8fcb294e..9971de8c 100644 --- a/src/supla/control/button.cpp +++ b/src/supla/control/button.cpp @@ -20,10 +20,11 @@ Supla::Control::Button::Button(int pin, bool pullUp, bool invertLogic) : SimpleButton(pin, pullUp, invertLogic), holdTimeMs(0), + repeatOnHoldMs(0), multiclickTimeMs(0), lastStateChangeMs(0), clickCounter(0), - holdSend(false), + holdSend(0), bistable(false) { } @@ -50,17 +51,17 @@ void Supla::Control::Button::onTimer() { if (!stateChanged) { if (!bistable && stateResult == PRESSED) { - if (clickCounter <= 1 && holdTimeMs > 0 && timeDelta > holdTimeMs && !holdSend) { + if (clickCounter <= 1 && holdTimeMs > 0 && timeDelta > (holdTimeMs + holdSend*repeatOnHoldMs) && (repeatOnHoldMs == 0 ? !holdSend : true)) { runAction(ON_HOLD); - holdSend = true; + ++holdSend; } } else if (clickCounter > 0 && (bistable || stateResult == RELEASED)) { if (multiclickTimeMs == 0) { - holdSend = false; + holdSend = 0; clickCounter = 0; } if (multiclickTimeMs > 0 && timeDelta > multiclickTimeMs) { - if (!holdSend) { + if (holdSend == 0) { switch (clickCounter) { case 1: runAction(ON_CLICK_1); @@ -97,7 +98,7 @@ void Supla::Control::Button::onTimer() { runAction(ON_CRAZY_CLICKER); } } - holdSend = false; + holdSend = 0; clickCounter = 0; } } @@ -119,3 +120,6 @@ void Supla::Control::Button::setMulticlickTime(unsigned int timeMs, bool bistabl } } +void Supla::Control::Button::repeatOnHoldEvery(unsigned int timeMs) { + repeatOnHoldMs = timeMs; +} diff --git a/src/supla/control/button.h b/src/supla/control/button.h index a48328d5..ba31ab58 100644 --- a/src/supla/control/button.h +++ b/src/supla/control/button.h @@ -29,14 +29,16 @@ class Button : public SimpleButton { void onTimer(); void setHoldTime(unsigned int timeMs); + void repeatOnHoldEvery(unsigned int timeMs); void setMulticlickTime(unsigned int timeMs, bool bistableButton = false); protected: unsigned int holdTimeMs; + unsigned int repeatOnHoldMs; unsigned int multiclickTimeMs; unsigned long lastStateChangeMs; uint8_t clickCounter; - bool holdSend; + unsigned int holdSend; bool bistable; }; diff --git a/src/supla/control/dimmer_base.cpp b/src/supla/control/dimmer_base.cpp index 5c01e57b..88f6f1e6 100644 --- a/src/supla/control/dimmer_base.cpp +++ b/src/supla/control/dimmer_base.cpp @@ -42,3 +42,7 @@ void Supla::Control::DimmerBase::onSaveState() { sizeof(curBrightness)); Supla::Storage::WriteState((unsigned char *)&lastBrightness, sizeof(lastBrightness)); } + +void Supla::Control::DimmerBase::iterateDimmerRGBW(int rgbStep, int wStep) { + Supla::Control::RGBWBase::iterateDimmerRGBW(0, wStep); +} diff --git a/src/supla/control/dimmer_base.h b/src/supla/control/dimmer_base.h index ebbe06a5..33fde7e9 100644 --- a/src/supla/control/dimmer_base.h +++ b/src/supla/control/dimmer_base.h @@ -31,11 +31,14 @@ class DimmerBase : public RGBWBase { int blue, int colorBrightness, int brightness, - bool toggle); + bool toggle = false); void onLoadState(); void onSaveState(); + protected: + virtual void iterateDimmerRGBW(int rgbStep, int wStep); + }; }; // namespace Control diff --git a/src/supla/control/dimmer_leds.cpp b/src/supla/control/dimmer_leds.cpp new file mode 100644 index 00000000..10abcb05 --- /dev/null +++ b/src/supla/control/dimmer_leds.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 "dimmer_leds.h" + +#ifdef ARDUINO_ARCH_ESP32 +extern int esp32PwmChannelCouner; +#endif + +Supla::Control::DimmerLeds::DimmerLeds(int brightnessPin) + : brightnessPin(brightnessPin) { +} + +void Supla::Control::DimmerLeds::setRGBWValueOnDevice(uint32_t red, + uint32_t green, + uint32_t blue, + uint32_t colorBrightness, + uint32_t brightness) { + uint32_t brightnessAdj = brightness; + +#ifdef ARDUINO_ARCH_AVR + brightnessAdj = map(brightnessAdj, 0, 1023, 0, 255); +#endif + +#ifdef ARDUINO_ARCH_ESP32 + ledcWrite(brightnessPin, brightnessAdj); +#else + analogWrite(brightnessPin, brightnessAdj); +#endif +} + +void Supla::Control::DimmerLeds::onInit() { +#ifdef ARDUINO_ARCH_ESP32 + Serial.print(F("Dimmer: attaching pin ")); + Serial.print(brightnessPin); + Serial.print(F(" to PWM channel: ")); + Serial.println(esp32PwmChannelCouner); + + ledcSetup(esp32PwmChannelCouner, 12000, 10); + ledcAttachPin(brightnessPin, esp32PwmChannelCouner); + // on ESP32 we write to PWM channels instead of pins, so we copy channel + // number as pin in order to reuse variable + brightnessPin = esp32PwmChannelCouner; + esp32PwmChannelCouner++; +#else + pinMode(brightnessPin, OUTPUT); + +#ifdef ARDUINO_ARCH_ESP8266 + analogWriteRange(1024); +#endif +#endif + + Supla::Control::DimmerBase::onInit(); +} diff --git a/src/supla/control/dimmer_leds.h b/src/supla/control/dimmer_leds.h new file mode 100644 index 00000000..041639e5 --- /dev/null +++ b/src/supla/control/dimmer_leds.h @@ -0,0 +1,44 @@ +/* +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 __dimmer_leds_h +#define __dimmer_leds_h + +#include "dimmer_base.h" + +namespace Supla { +namespace Control { +class DimmerLeds : public DimmerBase { + public: + DimmerLeds(int brightnessPin); + + void setRGBWValueOnDevice(uint32_t red, + uint32_t green, + uint32_t blue, + uint32_t colorBrightness, + uint32_t brightness); + + void onInit(); + + protected: + int brightnessPin; +}; + +}; // namespace Control +}; // namespace Supla + +#endif + diff --git a/src/supla/control/rgb_base.cpp b/src/supla/control/rgb_base.cpp new file mode 100644 index 00000000..9869e5a1 --- /dev/null +++ b/src/supla/control/rgb_base.cpp @@ -0,0 +1,61 @@ +/* + 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 "rgb_base.h" +#include "../storage/storage.h" + +Supla::Control::RGBBase::RGBBase() { + channel.setType(SUPLA_CHANNELTYPE_RGBLEDCONTROLLER); + channel.setDefault(SUPLA_CHANNELFNC_RGBLIGHTING); +} + +void Supla::Control::RGBBase::setRGBW(int red, + int green, + int blue, + int colorBrightness, + int brightness, + bool toggle) { + Supla::Control::RGBWBase::setRGBW( + red, green, blue, colorBrightness, 0, toggle); +} + +void Supla::Control::RGBBase::onSaveState() { + /* + uint8_t curRed; // 0 - 255 + uint8_t curGreen; // 0 - 255 + uint8_t curBlue; // 0 - 255 + uint8_t curColorBrightness; // 0 - 100 + uint8_t lastColorBrightness; // 0 - 100 + */ + Supla::Storage::WriteState((unsigned char *)&curRed, sizeof(curRed)); + Supla::Storage::WriteState((unsigned char *)&curGreen, sizeof(curGreen)); + Supla::Storage::WriteState((unsigned char *)&curBlue, sizeof(curBlue)); + Supla::Storage::WriteState((unsigned char *)&curColorBrightness, + sizeof(curColorBrightness)); + Supla::Storage::WriteState((unsigned char *)&lastColorBrightness, + sizeof(lastColorBrightness)); +} + +void Supla::Control::RGBBase::onLoadState() { + Supla::Storage::ReadState((unsigned char *)&curRed, sizeof(curRed)); + Supla::Storage::ReadState((unsigned char *)&curGreen, sizeof(curGreen)); + Supla::Storage::ReadState((unsigned char *)&curBlue, sizeof(curBlue)); + Supla::Storage::ReadState((unsigned char *)&curColorBrightness, + sizeof(curColorBrightness)); + Supla::Storage::ReadState((unsigned char *)&lastColorBrightness, + sizeof(lastColorBrightness)); +} + diff --git a/src/supla/control/rgb_base.h b/src/supla/control/rgb_base.h index 75afb29e..2bc534d9 100644 --- a/src/supla/control/rgb_base.h +++ b/src/supla/control/rgb_base.h @@ -23,20 +23,16 @@ namespace Supla { namespace Control { class RGBBase : public RGBWBase { public: - RGBBase() { - channel.setType(SUPLA_CHANNELTYPE_RGBLEDCONTROLLER); - channel.setDefault(SUPLA_CHANNELFNC_RGBLIGHTING); - } - + RGBBase(); void setRGBW(int red, int green, int blue, int colorBrightness, int brightness, - bool toggle) { - RGBWBase::setRGBW(red, green, blue, colorBrightness, 0, toggle); - } + bool toggle = false); + void onLoadState(); + void onSaveState(); }; }; // namespace Control diff --git a/src/supla/control/rgb_leds.cpp b/src/supla/control/rgb_leds.cpp new file mode 100644 index 00000000..1efc29dd --- /dev/null +++ b/src/supla/control/rgb_leds.cpp @@ -0,0 +1,88 @@ +/* +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 "rgb_leds.h" + +#ifdef ARDUINO_ARCH_ESP32 +extern int esp32PwmChannelCouner; +#endif + +Supla::Control::RGBLeds::RGBLeds(int redPin, int greenPin, int bluePin) + : redPin(redPin), greenPin(greenPin), bluePin(bluePin) { +} + +void Supla::Control::RGBLeds::setRGBWValueOnDevice(uint32_t red, + uint32_t green, + uint32_t blue, + uint32_t colorBrightness, + uint32_t brightness) { + uint32_t redAdj = red * colorBrightness / 1023; + uint32_t greenAdj = green * colorBrightness / 1023; + uint32_t blueAdj = blue * colorBrightness / 1023; + +#ifdef ARDUINO_ARCH_AVR + redAdj = map(redAdj, 0, 1023, 0, 255); + greenAdj = map(greenAdj, 0, 1023, 0, 255); + blueAdj = map(blueAdj, 0, 1023, 0, 255); +#endif + +#ifdef ARDUINO_ARCH_ESP32 + ledcWrite(redPin, redAdj); + ledcWrite(greenPin, greenAdj); + ledcWrite(bluePin, blueAdj); +#else + analogWrite(redPin, redAdj); + analogWrite(greenPin, greenAdj); + analogWrite(bluePin, blueAdj); +#endif +} + +void Supla::Control::RGBLeds::onInit() { +#ifdef ARDUINO_ARCH_ESP32 + Serial.print(F("RGB: attaching pin ")); + Serial.print(redPin); + Serial.print(F(" to PWM channel: ")); + Serial.println(esp32PwmChannelCouner); + + ledcSetup(esp32PwmChannelCouner, 12000, 10); + ledcAttachPin(redPin, esp32PwmChannelCouner); + // on ESP32 we write to PWM channels instead of pins, so we copy channel + // number as pin in order to reuse variable + redPin = esp32PwmChannelCouner; + esp32PwmChannelCouner++; + + ledcSetup(esp32PwmChannelCouner, 12000, 10); + ledcAttachPin(greenPin, esp32PwmChannelCouner); + greenPin = esp32PwmChannelCouner; + esp32PwmChannelCouner++; + + ledcSetup(esp32PwmChannelCouner, 12000, 10); + ledcAttachPin(bluePin, esp32PwmChannelCouner); + bluePin = esp32PwmChannelCouner; + esp32PwmChannelCouner++; + +#else + pinMode(redPin, OUTPUT); + pinMode(greenPin, OUTPUT); + pinMode(bluePin, OUTPUT); + +#ifdef ARDUINO_ARCH_ESP8266 + analogWriteRange(1024); +#endif +#endif + + Supla::Control::RGBBase::onInit(); +} diff --git a/src/supla/control/rgb_leds.h b/src/supla/control/rgb_leds.h new file mode 100644 index 00000000..bebddd3c --- /dev/null +++ b/src/supla/control/rgb_leds.h @@ -0,0 +1,45 @@ +/* +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 __rgb_leds_h +#define __rgb_leds_h + +#include "rgb_base.h" + +namespace Supla { +namespace Control { +class RGBLeds : public RGBBase { + public: + RGBLeds(int redPin, int greenPin, int bluePin); + + void setRGBWValueOnDevice(uint32_t red, + uint32_t green, + uint32_t blue, + uint32_t colorBrightness, + uint32_t brightness); + + void onInit(); + + protected: + int redPin; + int greenPin; + int bluePin; +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/src/supla/control/rgbw_base.cpp b/src/supla/control/rgbw_base.cpp index 4f2845ff..228b5a44 100644 --- a/src/supla/control/rgbw_base.cpp +++ b/src/supla/control/rgbw_base.cpp @@ -24,11 +24,16 @@ #define RGBW_STATE_ON_INIT_OFF 0 #define RGBW_STATE_ON_INIT_ON 1 +#ifdef ARDUINO_ARCH_ESP32 + int esp32PwmChannelCouner = 0; +#endif + + namespace Supla { namespace Control { RGBWBase::RGBWBase() - : buttonStep(10), + : buttonStep(5), curRed(0), curGreen(255), curBlue(0), @@ -39,15 +44,15 @@ RGBWBase::RGBWBase() defaultDimmedBrightness(20), dimIterationDirection(false), iterationDelayCounter(0), - fadeEffect(1000), - hwRed(0), + fadeEffect(500), + hwRed(-1), hwGreen(0), hwBlue(0), hwColorBrightness(0), hwBrightness(0), lastTick(0), lastMsgReceivedMs(0), - stateOnInit(RGBW_STATE_ON_INIT_OFF) { + stateOnInit(RGBW_STATE_ON_INIT_RESTORE) { channel.setType(SUPLA_CHANNELTYPE_DIMMERANDRGBLED); channel.setDefault(SUPLA_CHANNELFNC_DIMMERANDRGBLIGHTING); } @@ -57,7 +62,7 @@ void RGBWBase::setRGBW(int red, int blue, int colorBrightness, int brightness, - bool toggle = false) { + bool toggle) { if (toggle) { lastMsgReceivedMs = 1; } else { @@ -93,12 +98,6 @@ void RGBWBase::setRGBW(int red, curBrightness = brightness; } - // If fade effect is disabled, then set new values to device directly - if (fadeEffect <= 0) { - setRGBWValueOnDevice( - curRed, curGreen, curBlue, curColorBrightness, curBrightness); - } - // Schedule save in 5 s after state change Supla::Storage::ScheduleSave(5000); } @@ -350,96 +349,94 @@ void RGBWBase::setFadeEffectTime(int timeMs) { } void RGBWBase::onTimer() { - // exit it fade effect is disabled - if (fadeEffect <= 0) { - return; - } unsigned long timeDiff = millis() - lastTick; lastTick = millis(); if (timeDiff > 0) { - int divider = fadeEffect / timeDiff; + double divider = 1.0* fadeEffect / timeDiff; if (divider <= 0) { divider = 1; } - uint8_t rgbStep = 255 / divider; - uint8_t brightnessStep = 100 / divider; + double step = 1023 / divider; bool valueChanged = false; - if (rgbStep < 1) { - rgbStep = 1; - } - if (brightnessStep < 1) { - brightnessStep = 1; + if (step < 1) { + step = 1; } - if (curRed > hwRed) { + int curRedAdj = map(curRed, 0, 255, 0, 1023); + int curGreenAdj = map(curGreen, 0, 255, 0 , 1023); + int curBlueAdj = map(curBlue, 0, 255, 0, 1023); + int curColorBrightnessAdj = map(curColorBrightness, 0, 100, 0, 1023); + int curBrightnessAdj = map(curBrightness, 0, 100, 0, 1023); + + if (curRedAdj > hwRed) { valueChanged = true; - hwRed += rgbStep; - if (hwRed > curRed) { - hwRed = curRed; + hwRed += step; + if (hwRed > curRedAdj) { + hwRed = curRedAdj; } - } else if (curRed < hwRed) { + } else if (curRedAdj < hwRed) { valueChanged = true; - hwRed -= rgbStep; - if (hwRed < curRed) { - hwRed = curRed; + hwRed -= step; + if (hwRed < curRedAdj) { + hwRed = curRedAdj; } } - if (curGreen > hwGreen) { + if (curGreenAdj > hwGreen) { valueChanged = true; - hwGreen += rgbStep; - if (hwGreen > curGreen) { - hwGreen = curGreen; + hwGreen += step; + if (hwGreen > curGreenAdj) { + hwGreen = curGreenAdj; } - } else if (curGreen < hwGreen) { + } else if (curGreenAdj < hwGreen) { valueChanged = true; - hwGreen -= rgbStep; - if (hwGreen < curGreen) { - hwGreen = curGreen; + hwGreen -= step; + if (hwGreen < curGreenAdj) { + hwGreen = curGreenAdj; } } - if (curBlue > hwBlue) { + if (curBlueAdj > hwBlue) { valueChanged = true; - hwBlue += rgbStep; - if (hwBlue > curBlue) { - hwBlue = curBlue; + hwBlue += step; + if (hwBlue > curBlueAdj) { + hwBlue = curBlueAdj; } - } else if (curBlue < hwBlue) { + } else if (curBlueAdj < hwBlue) { valueChanged = true; - hwBlue -= rgbStep; - if (hwBlue < curBlue) { - hwBlue = curBlue; + hwBlue -= step; + if (hwBlue < curBlueAdj) { + hwBlue = curBlueAdj; } } - if (curColorBrightness > hwColorBrightness) { + if (curColorBrightnessAdj > hwColorBrightness) { valueChanged = true; - hwColorBrightness += brightnessStep; - if (hwColorBrightness > curColorBrightness) { - hwColorBrightness = curColorBrightness; + hwColorBrightness += step; + if (hwColorBrightness > curColorBrightnessAdj) { + hwColorBrightness = curColorBrightnessAdj; } - } else if (curColorBrightness < hwColorBrightness) { + } else if (curColorBrightnessAdj < hwColorBrightness) { valueChanged = true; - hwColorBrightness -= brightnessStep; - if (hwColorBrightness < curColorBrightness) { - hwColorBrightness = curColorBrightness; + hwColorBrightness -= step; + if (hwColorBrightness < curColorBrightnessAdj) { + hwColorBrightness = curColorBrightnessAdj; } } - if (curBrightness > hwBrightness) { + if (curBrightnessAdj > hwBrightness) { valueChanged = true; - hwBrightness += brightnessStep; - if (hwBrightness > curBrightness) { - hwBrightness = curBrightness; + hwBrightness += step; + if (hwBrightness > curBrightnessAdj) { + hwBrightness = curBrightnessAdj; } - } else if (curBrightness < hwBrightness) { + } else if (curBrightnessAdj < hwBrightness) { valueChanged = true; - hwBrightness -= brightnessStep; - if (hwBrightness < curBrightness) { - hwBrightness = curBrightness; + hwBrightness -= step; + if (hwBrightness < curBrightnessAdj) { + hwBrightness = curBrightnessAdj; } } diff --git a/src/supla/control/rgbw_base.h b/src/supla/control/rgbw_base.h index 5ab19fd7..c6d3ac41 100644 --- a/src/supla/control/rgbw_base.h +++ b/src/supla/control/rgbw_base.h @@ -30,18 +30,18 @@ class RGBWBase : public ChannelElement, public ActionHandler { public: RGBWBase(); - virtual void setRGBWValueOnDevice(uint8_t red, - uint8_t green, - uint8_t blue, - uint8_t colorBrightness, - uint8_t brightness) = 0; + virtual void setRGBWValueOnDevice(uint32_t red, + uint32_t green, + uint32_t blue, + uint32_t colorBrightness, + uint32_t brightness) = 0; virtual void setRGBW(int red, int green, int blue, int colorBrightness, int brightness, - bool toggle); + bool toggle = false); int handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue); virtual void turnOn(); @@ -51,10 +51,10 @@ class RGBWBase : public ChannelElement, public ActionHandler { void setStep(int step); void setDefaultDimmedBrightness(int dimmedBrightness); void setFadeEffectTime(int timeMs); - void onTimer(); - void iterateAlways(); void onInit(); + void iterateAlways(); + void onTimer(); void onLoadState(); void onSaveState(); @@ -64,7 +64,7 @@ class RGBWBase : public ChannelElement, public ActionHandler { protected: uint8_t addWithLimit(int value, int addition, int limit = 255); - void iterateDimmerRGBW(int rgbStep, int wStep); + virtual void iterateDimmerRGBW(int rgbStep, int wStep); uint8_t buttonStep; // 10 uint8_t curRed; // 0 - 255 diff --git a/src/supla/control/rgbw_leds.cpp b/src/supla/control/rgbw_leds.cpp new file mode 100644 index 00000000..11f85f9d --- /dev/null +++ b/src/supla/control/rgbw_leds.cpp @@ -0,0 +1,105 @@ +/* +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 "rgbw_leds.h" +#include + +#ifdef ARDUINO_ARCH_ESP32 +extern int esp32PwmChannelCouner; +#endif + +Supla::Control::RGBWLeds::RGBWLeds(int redPin, + int greenPin, + int bluePin, + int brightnessPin) + : redPin(redPin), + greenPin(greenPin), + bluePin(bluePin), + brightnessPin(brightnessPin) { +} + +void Supla::Control::RGBWLeds::setRGBWValueOnDevice(uint32_t red, + uint32_t green, + uint32_t blue, + uint32_t colorBrightness, + uint32_t brightness) { + uint32_t redAdj = red * colorBrightness / 1023; + uint32_t greenAdj = green * colorBrightness / 1023; + uint32_t blueAdj = blue * colorBrightness / 1023; + uint32_t brightnessAdj = brightness; + +#ifdef ARDUINO_ARCH_AVR + redAdj = map(redAdj, 0, 1023, 0, 255); + greenAdj = map(greenAdj, 0, 1023, 0, 255); + blueAdj = map(blueAdj, 0, 1023, 0, 255); + brightnessAdj = map(brightnessAdj, 0, 1023, 0, 255); +#endif + +#ifdef ARDUINO_ARCH_ESP32 + ledcWrite(redPin, redAdj); + ledcWrite(greenPin, greenAdj); + ledcWrite(bluePin, blueAdj); + ledcWrite(brightnessPin, brightnessAdj); +#else + analogWrite(redPin, redAdj); + analogWrite(greenPin, greenAdj); + analogWrite(bluePin, blueAdj); + analogWrite(brightnessPin, brightnessAdj); +#endif +} + +void Supla::Control::RGBWLeds::onInit() { +#ifdef ARDUINO_ARCH_ESP32 + Serial.print(F("RGBW: attaching pin ")); + Serial.print(redPin); + Serial.print(F(" to PWM channel: ")); + Serial.println(esp32PwmChannelCouner); + + ledcSetup(esp32PwmChannelCouner, 12000, 10); + ledcAttachPin(redPin, esp32PwmChannelCouner); + // on ESP32 we write to PWM channels instead of pins, so we copy channel + // number as pin in order to reuse variable + redPin = esp32PwmChannelCouner; + esp32PwmChannelCouner++; + + ledcSetup(esp32PwmChannelCouner, 12000, 10); + ledcAttachPin(greenPin, esp32PwmChannelCouner); + greenPin = esp32PwmChannelCouner; + esp32PwmChannelCouner++; + + ledcSetup(esp32PwmChannelCouner, 12000, 10); + ledcAttachPin(bluePin, esp32PwmChannelCouner); + bluePin = esp32PwmChannelCouner; + esp32PwmChannelCouner++; + + ledcSetup(esp32PwmChannelCouner, 12000, 10); + ledcAttachPin(brightnessPin, esp32PwmChannelCouner); + brightnessPin = esp32PwmChannelCouner; + esp32PwmChannelCouner++; + +#else + pinMode(redPin, OUTPUT); + pinMode(greenPin, OUTPUT); + pinMode(bluePin, OUTPUT); + pinMode(brightnessPin, OUTPUT); + + #ifdef ARDUINO_ARCH_ESP8266 + analogWriteRange(1024); + #endif +#endif + + Supla::Control::RGBWBase::onInit(); +} diff --git a/src/supla/control/rgbw_leds.h b/src/supla/control/rgbw_leds.h new file mode 100644 index 00000000..b3133e99 --- /dev/null +++ b/src/supla/control/rgbw_leds.h @@ -0,0 +1,49 @@ +/* +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 __rgbw_leds_h +#define __rgbw_leds_h + +#include "rgbw_base.h" + +namespace Supla { +namespace Control { +class RGBWLeds : public RGBWBase { + public: + RGBWLeds(int redPin, + int greenPin, + int bluePin, + int brightnessPin); + + void setRGBWValueOnDevice(uint32_t red, + uint32_t green, + uint32_t blue, + uint32_t colorBrightness, + uint32_t brightness); + + void onInit(); + + protected: + int redPin; + int greenPin; + int bluePin; + int brightnessPin; +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/src/supla/control/roller_shutter.cpp b/src/supla/control/roller_shutter.cpp index 237b54aa..c249ef34 100644 --- a/src/supla/control/roller_shutter.cpp +++ b/src/supla/control/roller_shutter.cpp @@ -392,7 +392,7 @@ void RollerShutter::onTimer() { // just handle roller movement/status if (currentDirection == UP_DIR && currentPosition > 0) { int movementDistance = lastPositionBeforeMovement; - int timeRequired = (1.0 * openingTimeMs * movementDistance / 100.0); + uint32_t timeRequired = (1.0 * openingTimeMs * movementDistance / 100.0); float fractionOfMovemendDone = (1.0 * (millis() - lastMovementStartTime) / timeRequired); if (fractionOfMovemendDone > 1) { @@ -405,7 +405,7 @@ void RollerShutter::onTimer() { } } else if (currentDirection == DOWN_DIR && currentPosition < 100) { int movementDistance = 100 - lastPositionBeforeMovement; - int timeRequired = (1.0 * closingTimeMs * movementDistance / 100.0); + uint32_t timeRequired = (1.0 * closingTimeMs * movementDistance / 100.0); float fractionOfMovemendDone = (1.0 * (millis() - lastMovementStartTime) / timeRequired); if (fractionOfMovemendDone > 1) { diff --git a/src/supla/control/simple_button.cpp b/src/supla/control/simple_button.cpp index 7714f675..2bbbd387 100644 --- a/src/supla/control/simple_button.cpp +++ b/src/supla/control/simple_button.cpp @@ -30,18 +30,19 @@ Supla::Control::ButtonState::ButtonState(int pin, bool pullUp, bool invertLogic) } int Supla::Control::ButtonState::update() { - if (millis() - debounceTimeMs > debounceDelayMs) { + unsigned long curMillis = millis(); + if (debounceDelayMs == 0 || curMillis - debounceTimeMs > debounceDelayMs) { int currentState = Supla::Io::digitalRead(pin); if (currentState != prevState) { // If status is changed, then make sure that it will be kept at // least swNoiseFilterDelayMs ms to avoid noise - if (currentState != newStatusCandidate) { + if (swNoiseFilterDelayMs != 0 && currentState != newStatusCandidate) { newStatusCandidate = currentState; - filterTimeMs = millis(); - } else if (millis() - filterTimeMs > swNoiseFilterDelayMs) { + filterTimeMs = curMillis; + } else if (curMillis - filterTimeMs > swNoiseFilterDelayMs) { // If new status is kept at least swNoiseFilterDelayMs ms, then apply // change of status - debounceTimeMs = millis(); + debounceTimeMs = curMillis; prevState = currentState; if (currentState == valueOnPress()) { return TO_PRESSED; diff --git a/src/supla/network/esp_wifi.h b/src/supla/network/esp_wifi.h index adbeac69..b0d60130 100644 --- a/src/supla/network/esp_wifi.h +++ b/src/supla/network/esp_wifi.h @@ -44,7 +44,7 @@ class ESPWifi : public Supla::Network { public: ESPWifi(const char *wifiSsid = nullptr, const char *wifiPassword = nullptr, - IPAddress *ip = nullptr) + unsigned char *ip = nullptr) : Network(ip), client(nullptr), isSecured(true), wifiConfigured(false) { ssid[0] = '\0'; password[0] = '\0'; @@ -67,6 +67,7 @@ class ESPWifi : public Supla::Network { for (int i = 0; i < readSize; i++) { Serial.print(static_cast(buf)[i], HEX); Serial.print(F(" ")); + delay(0); } Serial.println(F("]")); #endif @@ -82,6 +83,7 @@ class ESPWifi : public Supla::Network { for (int i = 0; i < count; i++) { Serial.print(static_cast(buf)[i], HEX); Serial.print(F(" ")); + delay(0); } Serial.println(F("]")); #endif @@ -127,7 +129,7 @@ class ESPWifi : public Supla::Network { server, connectionPort); - // static_cast(client)->setBufferSizes(512, 512); // +// static_cast(client)->setBufferSizes(512, 512); // // EXPERIMENTAL bool result = client->connect(server, connectionPort); diff --git a/src/supla/network/ethernet_shield.h b/src/supla/network/ethernet_shield.h index 7d5e7e7a..683acf94 100644 --- a/src/supla/network/ethernet_shield.h +++ b/src/supla/network/ethernet_shield.h @@ -28,7 +28,7 @@ namespace Supla { class EthernetShield : public Supla::Network { public: - EthernetShield(uint8_t mac[6], IPAddress *ip = NULL) : Network(ip), isDeviceReady(false) { + EthernetShield(uint8_t mac[6], unsigned char *ip = NULL) : Network(ip), isDeviceReady(false) { memcpy(this->mac, mac, 6); } diff --git a/src/supla/network/network.cpp b/src/supla/network/network.cpp index 4cf98681..b27e00b7 100644 --- a/src/supla/network/network.cpp +++ b/src/supla/network/network.cpp @@ -14,6 +14,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include +#include #include "SuplaDevice.h" #include "supla-common/log.h" @@ -145,9 +146,8 @@ void message_received(void *_srpc, } } -Network::Network(IPAddress *ip) { +Network::Network(unsigned char *ip) { lastSentMs = 0; - srpc = NULL; lastPingTimeMs = 0; serverActivityTimeoutS = 30; lastResponseMs = 0; @@ -158,10 +158,14 @@ Network::Network(IPAddress *ip) { useLocalIp = false; } else { useLocalIp = true; - localIp = *ip; + memcpy(localIp, ip, 4); } } +Network::~Network() { + netIntf = nullptr; +} + bool Network::iterate() { return false; } @@ -174,11 +178,7 @@ void Network::updateLastResponse() { lastResponseMs = millis(); } -void Network::setSrpc(void *_srpc) { - srpc = _srpc; -} - -bool Network::ping() { +bool Network::ping(void *srpc) { _supla_int64_t _millis = millis(); // If time from last response is longer than "server_activity_timeout + 10 s", // then inform about failure in communication diff --git a/src/supla/network/network.h b/src/supla/network/network.h index 9316eca2..85a2ac44 100644 --- a/src/supla/network/network.h +++ b/src/supla/network/network.h @@ -17,8 +17,6 @@ #ifndef _network_interface_h #define _network_interface_h -#include - #include "supla-common/log.h" #include "supla-common/proto.h" @@ -92,14 +90,15 @@ class Network { } } - static bool Ping() { + static bool Ping(void *srpc) { if (Instance() != NULL) { - return Instance()->ping(); + return Instance()->ping(srpc); } return false; } - Network(IPAddress *ip); + Network(uint8_t ip[4]); + virtual ~Network(); virtual int read(void *buf, int count) = 0; virtual int write(void *buf, int count) = 0; virtual int connect(const char *server, int port = -1) = 0; @@ -110,7 +109,7 @@ class Network { virtual bool isReady() = 0; virtual bool iterate(); - virtual bool ping(); + virtual bool ping(void *); virtual void fillStateData(TDSC_ChannelState &channelState); @@ -129,7 +128,7 @@ class Network { void *srpc; bool useLocalIp; - IPAddress localIp; + unsigned char localIp[4]; }; // Method passed to SRPC as a callback to read raw data from network interface diff --git a/src/supla/sensor/HC_SR04.cpp b/src/supla/sensor/HC_SR04.cpp new file mode 100644 index 00000000..4639556c --- /dev/null +++ b/src/supla/sensor/HC_SR04.cpp @@ -0,0 +1,111 @@ +/* + 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 "HC_SR04.h" + +namespace Supla { +namespace Sensor { +HC_SR04::HC_SR04(int8_t trigPin, + int8_t echoPin, + int16_t minIn, + int16_t maxIn, + int16_t minOut, + int16_t maxOut) + : failCount(0), readouts{}, index(0) { + _trigPin = trigPin; + _echoPin = echoPin; + _minIn = minIn; + _maxIn = maxIn; + _minOut = minOut; + _maxOut = maxOut; +} + +void HC_SR04::onInit() { + pinMode(_trigPin, OUTPUT); + pinMode(_echoPin, INPUT); + digitalWrite(_trigPin, LOW); + delayMicroseconds(2); + + channel.setNewValue(getValue()); + channel.setNewValue(getValue()); +} + +double HC_SR04::getValue() { + noInterrupts(); + digitalWrite(_trigPin, HIGH); + delayMicroseconds(10); + digitalWrite(_trigPin, LOW); + unsigned long duration = pulseIn(_echoPin, HIGH, 60000); + interrupts(); + if (duration > 50) { + index++; + if (index > 4) index = 0; + readouts[index] = duration; + failCount = 0; + } else { + failCount++; + } + + unsigned long min = 0, max = 0, sum = 0; + int count = 0; + for (int i = 0; i < 5; i++) { + if (readouts[i] > 0) { + count++; + if (min > readouts[i] || min == 0) min = readouts[i]; + if (max < readouts[i]) max = readouts[i]; + sum += readouts[i]; + } + } + + if (count == 5) { + if (min > 0) { + sum -= min; + count--; + } + if (max > 0) { + sum -= max; + count--; + } + } + if (count > 0) { + duration = sum / count; + } + + long distance = (duration / 2.0) / 29.1; + long value = map(distance, _minIn, _maxIn, _minOut, _maxOut); + if (_minOut < _maxOut) { + value = constrain(value, _minOut, _maxOut); + } else { + value = constrain(value, _maxOut, _minOut); + } + return failCount <= 3 ? static_cast(value) / 100.0 + : DISTANCE_NOT_AVAILABLE; +} + +void HC_SR04::setMinMaxIn(int16_t minIn, int16_t maxIn) { + _minIn = minIn; + _maxIn = maxIn; +} + +void HC_SR04::setMinMaxOut(int16_t minOut, int16_t maxOut) { + _minOut = minOut; + _maxOut = maxOut; +} + +}; // namespace Sensor +}; // namespace Supla diff --git a/src/supla/sensor/HC_SR04.h b/src/supla/sensor/HC_SR04.h index ecd9ea1f..2f222ee8 100644 --- a/src/supla/sensor/HC_SR04.h +++ b/src/supla/sensor/HC_SR04.h @@ -26,60 +26,18 @@ namespace Supla { namespace Sensor { class HC_SR04 : public Distance { public: - HC_SR04(int8_t trigPin, int8_t echoPin, int16_t minIn = 0, - int16_t maxIn = 500, int16_t minOut = 0, int16_t maxOut = 500) - : failCount(0), lastDuration(0) { - _trigPin = trigPin; - _echoPin = echoPin; - _minIn = minIn; - _maxIn = maxIn; - _minOut = minOut; - _maxOut = maxOut; - } - void onInit() { - pinMode(_trigPin, OUTPUT); - pinMode(_echoPin, INPUT); - digitalWrite(_trigPin, LOW); - delayMicroseconds(2); - - channel.setNewValue(getValue()); - channel.setNewValue(getValue()); - } - - virtual double getValue() { - digitalWrite(_trigPin, HIGH); - delayMicroseconds(10); - digitalWrite(_trigPin, LOW); - unsigned long duration = pulseIn(_echoPin, HIGH, 60000); - if (duration > 50) { - lastDuration = duration; - failCount = 0; - } else { - duration = lastDuration; - failCount++; - } - - long distance = (duration / 2.0) / 29.1; - long value = map(distance, _minIn, _maxIn, _minOut, _maxOut); - if (_minOut < _maxOut) { - value = constrain(value, _minOut, _maxOut); - } else { - value = constrain(value, _maxOut, _minOut); - } - return failCount <= 3 ? (float)value / 100.0 : DISTANCE_NOT_AVAILABLE; - } - - void setMinMaxIn(int16_t minIn, int16_t maxIn) { - _minIn = minIn; - _maxIn = maxIn; - } - - void setMinMaxOut(int16_t minOut, int16_t maxOut) { - _minOut = minOut; - _maxOut = maxOut; - } - -protected: + HC_SR04(int8_t trigPin, + int8_t echoPin, + int16_t minIn = 0, + int16_t maxIn = 500, + int16_t minOut = 0, + int16_t maxOut = 500); + void onInit(); + virtual double getValue(); + void setMinMaxIn(int16_t minIn, int16_t maxIn); + void setMinMaxOut(int16_t minOut, int16_t maxOut); + + protected: int8_t _trigPin; int8_t _echoPin; int16_t _minIn; @@ -87,11 +45,11 @@ class HC_SR04 : public Distance { int16_t _minOut; int16_t _maxOut; char failCount; - bool ready; - unsigned long lastDuration; + unsigned long readouts[5]; + int index; }; -}; // namespace Sensor -}; // namespace Supla +}; // namespace Sensor +}; // namespace Supla #endif diff --git a/src/supla/storage/eeprom.cpp b/src/supla/storage/eeprom.cpp index 79cd9f7b..8bba0afb 100644 --- a/src/supla/storage/eeprom.cpp +++ b/src/supla/storage/eeprom.cpp @@ -24,19 +24,25 @@ using namespace Supla; // By default, write to EEPROM every 3 min #define SUPLA_EEPROM_WRITING_PERIOD 3*60*1000 -Eeprom::Eeprom(unsigned int storageStartingOffset) +Eeprom::Eeprom(unsigned int storageStartingOffset, int reservedSize) : Storage(storageStartingOffset), - dataChanged(false) { + dataChanged(false), reservedSize(reservedSize) { setStateSavePeriod((unsigned long)SUPLA_EEPROM_WRITING_PERIOD); } bool Eeprom::init() { +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + if (reservedSize <= 0) { #if defined(ARDUINO_ARCH_ESP8266) EEPROM.begin(1024); #elif defined(ARDUINO_ARCH_ESP32) EEPROM.begin(512); #endif + } else { + EEPROM.begin(reservedSize); + } delay(15); +#endif return Storage::init(); } diff --git a/src/supla/storage/eeprom.h b/src/supla/storage/eeprom.h index b9699ada..2dc40bfe 100644 --- a/src/supla/storage/eeprom.h +++ b/src/supla/storage/eeprom.h @@ -23,7 +23,7 @@ namespace Supla { class Eeprom : public Storage { public: - Eeprom(unsigned int storageStartingOffset = 0); + Eeprom(unsigned int storageStartingOffset = 0, int reservedSize = -1); bool init(); void commit(); @@ -31,6 +31,7 @@ class Eeprom : public Storage { int readStorage(unsigned int, unsigned char *, int, bool); int writeStorage(unsigned int, const unsigned char *, int); + int reservedSize; bool dataChanged; }; diff --git a/src/supla/storage/storage.cpp b/src/supla/storage/storage.cpp index 8e21d098..0ea03e1e 100644 --- a/src/supla/storage/storage.cpp +++ b/src/supla/storage/storage.cpp @@ -15,6 +15,7 @@ */ #include +#include #include "storage.h" @@ -63,10 +64,11 @@ bool Storage::LoadElementConfig() { return false; } -void Storage::PrepareState(bool dryRun) { +bool Storage::PrepareState(bool dryRun) { if (Instance()) { - Instance()->prepareState(dryRun); + return Instance()->prepareState(dryRun); } + return false; } bool Storage::FinalizeSaveState() { @@ -106,10 +108,15 @@ Storage::Storage(unsigned int storageStartingOffset) instance = this; } -void Storage::prepareState(bool performDryRun) { +Storage::~Storage() { + instance = nullptr; +} + +bool Storage::prepareState(bool performDryRun) { dryRun = performDryRun; newSectionSize = 0; currentStateOffset = elementStateOffset + sizeof(SectionPreamble); + return true; } bool Storage::readState(unsigned char *buf, int size) { diff --git a/src/supla/storage/storage.h b/src/supla/storage/storage.h index f25a4091..4b450437 100644 --- a/src/supla/storage/storage.h +++ b/src/supla/storage/storage.h @@ -33,12 +33,13 @@ class Storage { static bool WriteState(const unsigned char *, int); static bool LoadDeviceConfig(); static bool LoadElementConfig(); - static void PrepareState(bool dryRun = false); + static bool PrepareState(bool dryRun = false); static bool FinalizeSaveState(); static bool SaveStateAllowed(unsigned long); static void ScheduleSave(unsigned long delayMs); Storage(unsigned int storageStartingOffset = 0); + virtual ~Storage(); // Changes default state save period time virtual void setStateSavePeriod(unsigned long periodMs); @@ -49,7 +50,7 @@ class Storage { virtual bool loadDeviceConfig(); virtual bool loadElementConfig(); - virtual void prepareState(bool performDryRun); + virtual bool prepareState(bool performDryRun); virtual bool finalizeSaveState(); virtual bool saveStateAllowed(unsigned long); virtual void scheduleSave(unsigned long delayMs); diff --git a/src/supla/timer.cpp b/src/supla/timer.cpp index a0a50ec7..8a43039f 100644 --- a/src/supla/timer.cpp +++ b/src/supla/timer.cpp @@ -23,6 +23,10 @@ #include #endif +#ifdef ARDUINO_ARCH_ESP8266 +#include +#endif + namespace { #if defined(ARDUINO_ARCH_ESP8266) ETSTimer supla_esp_timer;