diff --git a/examples/Other Examples/FadingLED/FadingLED.ino b/examples/Other Examples/FadingLED/FadingLED.ino new file mode 100644 index 0000000..9ca5a81 --- /dev/null +++ b/examples/Other Examples/FadingLED/FadingLED.ino @@ -0,0 +1,113 @@ +/********************************************************************************* + * MIT License + * + * Copyright (c) 2020-2023 Gregg E. Berman + * + * https://github.com/HomeSpan/HomeSpan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + ********************************************************************************/ + +// HomeSpan Fading-LED Example. Demonstrates use of: +// +// LedPin::fade() and LedPin::fadeStatus() methods +// +// In this sketch we control a single dimmable LED using the Home App as well as a SpanButton. +// You can control the brightness of the LED from the Home App, but the SpanButton only turns the +// LED either fully on (if it's off) or fully off (if it's already on). +// +// Rather than set the LED to a specific brightness, this sketch uses the ESP32's hardware-based fading +// functionality to fade the LED from one level to the next. We set the timing for each fade to be 2000 ms, +// proportional to the difference between the current brightness and the desired brightness. This means it +// will take a full 2 seconds to fade the LED from 0-100, but only 1 second to fade from half-brightness to +// off. + +#include "HomeSpan.h" +#include "extras/PwmPin.h" // library of various PWM functions + +//////////////////////////////////// + +struct FadingLED : Service::LightBulb { + + LedPin *ledPin; // reference to Led Pin + SpanCharacteristic *power; // reference to the On Characteristic + SpanCharacteristic *level; // reference to the Brightness Characteristic + + FadingLED(int _ledPin, int _buttonPin) : Service::LightBulb(){ + + power=new Characteristic::On(); + level=new Characteristic::Brightness(0); + ledPin=new LedPin(_ledPin); + new SpanButton(_buttonPin); + + } + + boolean update(){ + + ledPin->fade(power->getNewVal()*level->getNewVal(),2000,LedPin::PROPORTIONAL); // use fade() to set new level; timing=2 seconds, proportional scale + while(ledPin->fadeStatus()==LedPin::FADING); // wait until fading is completed + + return(true); + + } + + void button(int pin, int pressType) override { + + // Below we turn LED fully on or off depending on whether power is on + // Unlike above, we will NOT wait for the fading to complete, but will return immediately + + if(ledPin->fade(100-(power->getVal())*100,2000,LedPin::PROPORTIONAL)!=0) // use fade to either turn fully on or fully off; check return status to see if call was successful + Serial.printf("Button Press Ignored\n"); + } + + void loop() override { + + // Below we set power and level once fading from a button press is completed + + if(ledPin->fadeStatus()==LedPin::COMPLETED){ + power->setVal(1-power->getVal()); + level->setVal(power->getVal()?100:0); + } + } + +}; + +////////////////////////////////// + +void setup() { + + Serial.begin(115200); + + homeSpan.begin(Category::Lighting,"Fading LED"); + + new SpanAccessory(); + new Service::AccessoryInformation(); + new Characteristic::Identify(); + + new FadingLED(26,4); // first argument is LED Pin, second argument is PushButton Pin + +} + +////////////////////////////////////// + +void loop(){ + + homeSpan.poll(); +} diff --git a/src/src/extras/PwmPin.cpp b/src/src/extras/PwmPin.cpp index b019dfd..d5eab95 100644 --- a/src/src/extras/PwmPin.cpp +++ b/src/src/extras/PwmPin.cpp @@ -96,6 +96,8 @@ LedPin::LedPin(uint8_t pin, float level, uint16_t freq, boolean invert) : LedC(p ); ledc_fade_func_install(0); + ledc_cbs_t fadeCallbackList = {.fade_cb = fadeCallback}; // for some reason, ledc_cb_register requires the function to be wrapped in a structure + ledc_cb_register(channel->speed_mode,channel->channel,&fadeCallbackList,this); set(level); } @@ -118,21 +120,48 @@ void LedPin::set(float level){ /////////////////// -void LedPin::fade(float level, uint32_t fadeTime){ +int LedPin::fade(float level, uint32_t fadeTime, int fadeType){ if(!channel) - return; + return(1); + + if(fadeState==FADING) // fading already in progress + return(1); // return error if(level>100) level=100; - float d=level*(pow(2,(int)timer->duty_resolution)-1)/100.0; + float d=level*(pow(2,(int)timer->duty_resolution)-1)/100.0; - ledc_set_fade_time_and_start(channel->speed_mode,channel->channel,(uint32_t)d,fadeTime,LEDC_FADE_NO_WAIT); + if(fadeType==PROPORTIONAL) + fadeTime*=fabs((float)ledc_get_duty(channel->speed_mode,channel->channel)-d)/(float)(pow(2,(int)timer->duty_resolution)-1); + + fadeState=FADING; + ledc_set_fade_time_and_start(channel->speed_mode,channel->channel,d,fadeTime,LEDC_FADE_NO_WAIT); + return(0); } /////////////////// +int LedPin::fadeStatus(){ + if(fadeState==COMPLETED){ + fadeState=NOT_FADING; + return(COMPLETED); + } + + return(fadeState); +} + +/////////////////// + +bool IRAM_ATTR LedPin::fadeCallback(const ledc_cb_param_t *param, void *arg){ + ((LedPin *)arg)->fadeState=COMPLETED; + return(false); +} + +/////////////////// + + void LedPin::HSVtoRGB(float h, float s, float v, float *r, float *g, float *b ){ // The algorithm below was provided on the web at https://www.cs.rit.edu/~ncs/color/t_convert.html diff --git a/src/src/extras/PwmPin.h b/src/src/extras/PwmPin.h index 68751ca..fe5dae5 100644 --- a/src/src/extras/PwmPin.h +++ b/src/src/extras/PwmPin.h @@ -74,10 +74,27 @@ class LedC { class LedPin : public LedC { + public: + enum { + NOT_FADING, + COMPLETED, + FADING + }; + + enum { + ABSOLUTE, + PROPORTIONAL + }; + + private: + int fadeState=NOT_FADING; + static bool fadeCallback(const ledc_cb_param_t *param, void *arg); + public: LedPin(uint8_t pin, float level=0, uint16_t freq=DEFAULT_PWM_FREQ, boolean invert=false); // assigns pin to be output of one of 16 PWM channels initial level and frequency void set(float level); // sets the PWM duty to level (0-100) - void fade(float level, uint32_t fadeTime); // sets the PWM duty to level (0-100) within fadeTime in milliseconds + int fade(float level, uint32_t fadeTime, int fadeType=ABSOLUTE); // sets the PWM duty to level (0-100) within fadeTime in milliseconds, returns success (0) or fail (1) + int fadeStatus(); // returns fading state static void HSVtoRGB(float h, float s, float v, float *r, float *g, float *b ); // converts Hue/Saturation/Brightness to R/G/B }; diff --git a/src/src/extras/extras.ino b/src/src/extras/extras.ino index a982c54..072d8d9 100644 --- a/src/src/extras/extras.ino +++ b/src/src/extras/extras.ino @@ -38,13 +38,31 @@ void setup() { Serial.println("\n\nHomeSpan LED Fade Test\n"); - LedPin led(26,50); - Serial.printf("Start\n"); - led.fade(0,10000); - Serial.printf("End\n"); + LedPin red(33,0); + LedPin green(32,0); + LedPin blue(14,0); - while(1); + int redLevel=0; + for(int i=100;i<=100;i+=10){ + while(red.fadeStatus()==LedPin::FADING); + red.fade(i,1000,LedPin::PROPORTIONAL); + } + + while(1); + + + while(1){ + delay(1000); + if(red.fade(redLevel,5000)) + Serial.printf("Failed\n"); + else{ + Serial.printf("Success\n"); + redLevel=100-redLevel; + } + + } + } void loop(){