├── FastLED_RGBW.h ├── HB-UNI-RGB-LED-CTRL.ino ├── Images ├── mega2560_core_back.jpg ├── mega2560_core_front.jpg ├── sample.png ├── wiring_2560.fzz ├── wiring_2560.png ├── wiring_ProMini.fzz └── wiring_ProMini.png ├── README.md ├── RGBCtrl.h ├── RGBPrograms.h └── analog.h /FastLED_RGBW.h: -------------------------------------------------------------------------------- 1 | /* FastLED_RGBW 2 | * 3 | * Hack to enable SK6812 RGBW strips to work with FastLED. 4 | * 5 | * Original code by Jim Bumgardner (http://krazydad.com). 6 | * Modified by David Madison (http://partsnotincluded.com). 7 | * 8 | */ 9 | 10 | #ifndef FastLED_RGBW_h 11 | #define FastLED_RGBW_h 12 | 13 | struct CRGBW { 14 | union { 15 | struct { 16 | union { 17 | uint8_t g; 18 | uint8_t green; 19 | }; 20 | union { 21 | uint8_t r; 22 | uint8_t red; 23 | }; 24 | union { 25 | uint8_t b; 26 | uint8_t blue; 27 | }; 28 | union { 29 | uint8_t w; 30 | uint8_t white; 31 | }; 32 | }; 33 | uint8_t raw[4]; 34 | }; 35 | 36 | CRGBW(){} 37 | 38 | 39 | CRGBW(uint8_t rd, uint8_t grn, uint8_t blu){ 40 | w = min(rd, grn); 41 | w = min(w, blu); 42 | 43 | r = rd - w; 44 | g = grn - w; 45 | b = blu - w; 46 | } 47 | 48 | inline void operator = (const CRGB c) __attribute__((always_inline)){ 49 | uint8_t w = min(c.r, c.g); 50 | w = min(w, c.b); 51 | this->r = c.r - w; 52 | this->g = c.g - w; 53 | this->b = c.b - w; 54 | this->w = w; 55 | } 56 | 57 | }; 58 | 59 | inline uint16_t getRGBWsize(uint16_t nleds){ 60 | uint16_t nbytes = nleds * 4; 61 | if(nbytes % 3 > 0) return nbytes / 3 + 1; 62 | else return nbytes / 3; 63 | } 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /HB-UNI-RGB-LED-CTRL.ino: -------------------------------------------------------------------------------- 1 | //- ----------------------------------------------------------------------------------------------------------------------- 2 | // AskSin++ 3 | // 2018-08-03 jp112sdl Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ 4 | //- ----------------------------------------------------------------------------------------------------------------------- 5 | // ci-test=yes board=328p aes=no 6 | 7 | // define this to read the device id, serial and device type from bootloader section 8 | // #define USE_OTA_BOOTLOADER 9 | 10 | #define EI_NOTEXTERNAL 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "analog.h" 17 | 18 | //#define ENABLE_RGBW // for SK6812 LEDs 19 | 20 | #define WSNUM_LEDS 5 //Anzahl angeschlossener LEDs 21 | #define WSLED_PIN 9 //GPIO Pin LED Anschluss 22 | #define WSLED_TYPE WS2812B //LED Typ 23 | #define WSCOLOR_ORDER GRB //Farbreihenfolge 24 | 25 | /* 26 | #define PWM_ENABLED 27 | #define PWM_RED_PIN 3 28 | #define PWM_GREEN_PIN 5 29 | #define PWM_BLUE_PIN 6 30 | #define PWM_WHITE_PIN 9 //Pin für weiße LED, auskommentieren, wenn keine weiße LED vorhanden ist 31 | #define PWM_WHITE_ONLY true //Wenn weiße LED vorhanden ist, soll nur diese angesteuert werden wenn die Farbe weiß ist? 32 | */ 33 | 34 | #define SLOW_PROGRAM_TIMER 30 //ms Wartezeit für den Übergang 35 | #define NORMAL_PROGRAM_TIMER 15 //ms Wartezeit für den Übergang 36 | #define FAST_PROGRAM_TIMER 0 //ms Wartezeit für den Übergang 37 | #define FIRE_PROGRAM_COOLING 55 38 | #define FIRE_PROGRAM_SPARKLING 120 39 | 40 | #if defined __AVR_ATmega2560__ 41 | #define CONFIG_BUTTON_PIN 13 42 | #else 43 | #define CONFIG_BUTTON_PIN 8 44 | #endif 45 | #define ONBOARD_LED_PIN 4 46 | 47 | #ifdef ENABLE_RGBW 48 | #include "FastLED_RGBW.h" 49 | #endif 50 | #include "RGBCtrl.h" 51 | 52 | #define PEERS_PER_CHANNEL 4 53 | 54 | using namespace as; 55 | 56 | // define all device properties 57 | const struct DeviceInfo PROGMEM devinfo = { 58 | {0xF3, 0x41, 0x01}, // Device ID 59 | "JPRGB00001", // Device Serial 60 | {0xF3, 0x41}, // Device Model 61 | 0x25, // Firmware Version 62 | as::DeviceType::Dimmer, // Device Type 63 | {0x01, 0x00} // Info Bytes 64 | }; 65 | 66 | /** 67 | Configure the used hardware 68 | */ 69 | #if defined __AVR_ATmega2560__ 70 | typedef AskSin, NoBattery, Radio, 2>> HalType; 71 | #else 72 | typedef AskSin, NoBattery, Radio, 2>> HalType; 73 | #endif 74 | 75 | DEFREGISTER(Reg0, MASTERID_REGS, 0x20, 0x21) 76 | class Ws28xxList0 : public RegList0 { 77 | public: 78 | Ws28xxList0(uint16_t addr) : RegList0(addr) {} 79 | 80 | void defaults () { 81 | clear(); 82 | } 83 | }; 84 | 85 | typedef RGBLEDChannel ChannelType; 86 | typedef RGBLEDDevice RGBLEDType; 87 | 88 | HalType hal; 89 | RGBLEDType sdev(devinfo, 0x20); 90 | ConfigButton cfgBtn(sdev); 91 | 92 | void setup () { 93 | DINIT(57600, ASKSIN_PLUS_PLUS_IDENTIFIER); 94 | sdev.init(hal); 95 | buttonISR(cfgBtn, CONFIG_BUTTON_PIN); 96 | sdev.initDone(); 97 | } 98 | 99 | void loop() { 100 | bool worked = hal.runready(); 101 | bool poll = sdev.pollRadio(); 102 | if ( worked == false && poll == false ) { 103 | #ifndef PWM_ENABLED 104 | hal.activity.savePower>(hal); 105 | #endif 106 | } 107 | 108 | sdev.handleLED(); 109 | } 110 | -------------------------------------------------------------------------------- /Images/mega2560_core_back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jp112sdl/HB-UNI-RGB-LED-CTRL/443b5a78a76bce63ca23b0e1b7d453c850c424c1/Images/mega2560_core_back.jpg -------------------------------------------------------------------------------- /Images/mega2560_core_front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jp112sdl/HB-UNI-RGB-LED-CTRL/443b5a78a76bce63ca23b0e1b7d453c850c424c1/Images/mega2560_core_front.jpg -------------------------------------------------------------------------------- /Images/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jp112sdl/HB-UNI-RGB-LED-CTRL/443b5a78a76bce63ca23b0e1b7d453c850c424c1/Images/sample.png -------------------------------------------------------------------------------- /Images/wiring_2560.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jp112sdl/HB-UNI-RGB-LED-CTRL/443b5a78a76bce63ca23b0e1b7d453c850c424c1/Images/wiring_2560.fzz -------------------------------------------------------------------------------- /Images/wiring_2560.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jp112sdl/HB-UNI-RGB-LED-CTRL/443b5a78a76bce63ca23b0e1b7d453c850c424c1/Images/wiring_2560.png -------------------------------------------------------------------------------- /Images/wiring_ProMini.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jp112sdl/HB-UNI-RGB-LED-CTRL/443b5a78a76bce63ca23b0e1b7d453c850c424c1/Images/wiring_ProMini.fzz -------------------------------------------------------------------------------- /Images/wiring_ProMini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jp112sdl/HB-UNI-RGB-LED-CTRL/443b5a78a76bce63ca23b0e1b7d453c850c424c1/Images/wiring_ProMini.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HB-UNI-RGB-LED-CTRL 2 | HomeMatic kompatibler RGB Controller für adressierbare RGB LEDs (WS28xx, Neopixel,...) 3 | 4 | 5 | **Bis ca. 144 LEDs ist ein Arduino Pro Mini (3.3V / 8 MHz) ausreichend:** 6 | ![wiring_ProMini](Images/wiring_ProMini.png) 7 | 8 | **Bei > 144 LEDs ist ein Arduino Mega 2560 erforderlich:** 9 | ![wiring_2560](Images/wiring_2560.png) 10 | 11 | Um Platz zu sparen, kann man die [Mega2560 Core](https://de.aliexpress.com/item/Mega-2560-PRO-Embed-CH340G-ATmega2560-16AU-NO-pinheaders-Compatible-for-Arduino-Mega-2560/32802420999.html) Variante einsetzen:
12 | _Auf dem abgebildeten Foto habe ich die Reset-Leiterbahn durchtrennt, so dass ich den Taster an Pin13 als Config-Taster verdrahten konnte._
13 |
14 | **Achtung: anders als abgebildet, muss - um dauerhafte Beschädigungen am CC1101 zu vermeiden - ein [LevelShifter](https://www.ebay.de/itm/252797529930) in die Datenleitungen eingebunden werden.** 15 | 16 | 17 | 18 | 19 | **Benötigte Libs:** 20 | - [FastLED](https://github.com/FastLED/FastLED) 21 | - [AskSinPP](https://github.com/pa-pa/AskSinPP) 22 | - sowie die dort aufgeführten [zusätzlichen Bibliotheken](https://github.com/pa-pa/AskSinPP#required-additional-arduino-libraries) 23 | 24 | sowie auf der Zentrale (CCU2 / CCU3 / RaspberryMatic) das [JP-HB-Devices](https://github.com/jp112sdl/JP-HB-Devices-addon) Addon. 25 | 26 | **Demo:** 27 | ![demo](Images/sample.png) 28 | -------------------------------------------------------------------------------- /RGBCtrl.h: -------------------------------------------------------------------------------- 1 | //- ----------------------------------------------------------------------------------------------------------------------- 2 | // AskSin++ 3 | // 2017-03-29 papa Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ 4 | // 2018-08-03 jp112sdl Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ 5 | //- ----------------------------------------------------------------------------------------------------------------------- 6 | 7 | #ifndef __RGBLEDXX_H__ 8 | #define __RGBLEDXX_H__ 9 | 10 | #include 11 | #include 12 | #include 13 | #define LOGIC_INACTIVE 0 14 | #define LOGIC_OR 1 15 | #define LOGIC_AND 2 16 | #define LOGIC_XOR 3 17 | #define LOGIC_NOR 4 18 | #define LOGIC_NAND 5 19 | #define LOGIC_ORINVERS 6 20 | #define LOGIC_ANDINVERS 7 21 | #define LOGIC_PLUS 8 22 | #define LOGIC_MINUS 9 23 | #define LOGIC_MUL 10 24 | #define LOGIC_PLUSINVERS 11 25 | #define LOGIC_MINUSINVERS 12 26 | #define LOGIC_MULINVERS 13 27 | #define LOGIC_INVERSPLUS 14 28 | #define LOGIC_INVERSMINUS 15 29 | #define LOGIC_INVERSMUL 16 30 | 31 | #ifdef ENABLE_RGBW 32 | CRGBW leds[WSNUM_LEDS]; 33 | CRGB *ledsRGB = (CRGB *) &leds[0]; 34 | #else 35 | CRGB leds[WSNUM_LEDS]; 36 | #endif 37 | 38 | #include "RGBPrograms.h" 39 | 40 | namespace as { 41 | 42 | DEFREGISTER(DimmerReg1, CREG_AES_ACTIVE, CREG_TRANSMITTRYMAX, CREG_OVERTEMPLEVEL, 43 | CREG_REDUCETEMPLEVEL, CREG_REDUCELEVEL, CREG_POWERUPACTION, CREG_STATUSINFO, 44 | CREG_CHARACTERISTIC, CREG_LOGICCOMBINATION) 45 | 46 | class DimmerList1 : public RegList1 { 47 | public: 48 | DimmerList1 (uint16_t addr) : RegList1(addr) {} 49 | void defaults () { 50 | clear(); 51 | // aesActive(false); 52 | transmitTryMax(6); 53 | // powerUpAction(false); 54 | statusInfoMinDly(4); 55 | statusInfoRandom(1); 56 | 57 | overTempLevel(80); 58 | reduceTempLevel(75); 59 | reduceLevel(80); 60 | characteristic(true); 61 | logicCombination(LOGIC_OR); 62 | } 63 | }; 64 | 65 | DEFREGISTER(DimmerReg3, PREG_CTRAMPONOFF, PREG_CTDELAYONOFF, PREG_CTONOFF, 66 | PREG_CONDVALUELOW, PREG_CONDVALUEHIGH, PREG_ONDELAYTIME, PREG_ONTIME, 67 | PREG_OFFDELAYTIME, PREG_OFFTIME, PREG_ACTIONTYPE, PREG_JTONOFF, 68 | PREG_JTDELAYONOFF, PREG_JTRAMPONOFF, PREG_DELAYMODE, PREG_OFFLEVEL, 69 | PREG_ONMINLEVEL, PREG_ONLEVEL, PREG_RAMPSTARTSTEP, PREG_RAMPONTIME, 70 | PREG_RAMPOFFTIME, PREG_DIMMINLEVEL, PREG_DIMMAXLEVEL, PREG_DIMSTEP, 71 | PREG_OFFDELAYSTEP, PREG_OFFDELAYNEWTIME, PREG_OFFDELAYOLDTIME, 72 | PREG_ELSEACTIONTYPE, PREG_ELSEJTONOFF, PREG_ELSEJTDELAYONOFF, 73 | PREG_ELSEJTRAMPONOFF) 74 | 75 | typedef RegList3 DimmerPeerList; 76 | 77 | class DimmerList3 : public ShortLongList { 78 | public: 79 | DimmerList3 (uint16_t addr) : ShortLongList(addr) {} 80 | void defaults() { 81 | DimmerPeerList ssl = sh(); 82 | ssl.clear(); 83 | // ssl.ctRampOn(0); 84 | // ssl.ctRampOff(0); 85 | // ssl.ctDlyOn(0); 86 | // ssl.ctDlyOff(0); 87 | // ssl.ctOn(0); 88 | // ssl.ctOff(0); 89 | ssl.ctValLo(0x32); 90 | ssl.ctValHi(0x64); 91 | // ssl.onDly(0); 92 | ssl.onTime(0xff); 93 | // ssl.offDly(0); 94 | ssl.offTime(0xff); 95 | ssl.actionType(AS_CM_ACTIONTYPE_JUMP_TO_TARGET); 96 | // ssl.offTimeMode(false); 97 | // ssl.onTimeMode(false); 98 | ssl.offDelayBlink(true); 99 | // ssl.offLevel(0); 100 | ssl.onMinLevel(20); 101 | ssl.onLevel(200); // 201 ??? 102 | ssl.rampStartStep(10); 103 | // ssl.rampOnTime(0); 104 | // ssl.rampOffTime(0); 105 | // ssl.dimMinLevel(0); 106 | ssl.dimMaxLevel(200); 107 | ssl.dimStep(5); 108 | ssl.offDelayStep(10); 109 | ssl.offDelayNewTime(5); 110 | ssl.offDelayOldTime(5); 111 | ssl.elseActionType(AS_CM_ACTIONTYPE_INACTIVE); 112 | // ssl.elseOffTimeMode(false); 113 | // ssl.elseOnTimeMode(false); 114 | ssl.elseJtOn(AS_CM_JT_ONDELAY); 115 | ssl.elseJtOff(AS_CM_JT_ONDELAY); 116 | ssl.elseJtDlyOn(AS_CM_JT_ONDELAY); 117 | ssl.elseJtDlyOff(AS_CM_JT_ONDELAY); 118 | ssl.elseJtRampOn(AS_CM_JT_ONDELAY); 119 | ssl.elseJtRampOff(AS_CM_JT_ONDELAY); 120 | 121 | ssl = lg(); 122 | ssl.clear(); 123 | // ssl.ctRampOn(0); 124 | // ssl.ctRampOff(0); 125 | // ssl.ctDlyOn(0); 126 | // ssl.ctDlyOff(0); 127 | // ssl.ctOn(0); 128 | // ssl.ctOff(0); 129 | ssl.ctValLo(0x32); 130 | ssl.ctValHi(0x64); 131 | // ssl.onDly(0); 132 | ssl.onTime(0xff); 133 | // ssl.offDly(0); 134 | ssl.offTime(0xff); 135 | ssl.actionType(AS_CM_ACTIONTYPE_JUMP_TO_TARGET); 136 | ssl.multiExec(true); 137 | // ssl.offTimeMode(false); 138 | // ssl.onTimeMode(false); 139 | ssl.offDelayBlink(true); 140 | // ssl.offLevel(0); 141 | ssl.onMinLevel(20); 142 | ssl.onLevel(200); // 201 ??? 143 | ssl.rampStartStep(10); 144 | // ssl.rampOnTime(0); 145 | // ssl.rampOffTime(0); 146 | // ssl.dimMinLevel(0); 147 | ssl.dimMaxLevel(200); 148 | ssl.dimStep(5); 149 | ssl.offDelayStep(10); 150 | ssl.offDelayNewTime(5); 151 | ssl.offDelayOldTime(5); 152 | ssl.elseActionType(AS_CM_ACTIONTYPE_INACTIVE); 153 | // ssl.elseOffTimeMode(false); 154 | // ssl.elseOnTimeMode(false); 155 | ssl.elseJtOn(AS_CM_JT_ONDELAY); 156 | ssl.elseJtOff(AS_CM_JT_ONDELAY); 157 | ssl.elseJtDlyOn(AS_CM_JT_ONDELAY); 158 | ssl.elseJtDlyOff(AS_CM_JT_ONDELAY); 159 | ssl.elseJtRampOn(AS_CM_JT_ONDELAY); 160 | ssl.elseJtRampOff(AS_CM_JT_ONDELAY); 161 | } 162 | 163 | void odd() { 164 | defaults(); 165 | DimmerPeerList ssl = sh(); 166 | ssl.jtOn(AS_CM_JT_OFFDELAY); 167 | ssl.jtOff(AS_CM_JT_OFF); 168 | ssl.jtDlyOn(AS_CM_JT_RAMPOFF); 169 | ssl.jtDlyOff(AS_CM_JT_RAMPOFF); 170 | ssl.jtRampOn(AS_CM_JT_RAMPOFF); 171 | ssl.jtRampOff(AS_CM_JT_OFF); 172 | ssl = lg(); 173 | ssl.actionType(AS_CM_ACTIONTYPE_DOWNDIM); 174 | } 175 | 176 | void even() { 177 | defaults(); 178 | DimmerPeerList ssl = sh(); 179 | ssl.jtOn(AS_CM_JT_ON); 180 | ssl.jtOff(AS_CM_JT_ONDELAY); 181 | ssl.jtDlyOn(AS_CM_JT_RAMPON); 182 | ssl.jtDlyOff(AS_CM_JT_RAMPON); 183 | ssl.jtRampOn(AS_CM_JT_ON); 184 | ssl.jtRampOff(AS_CM_JT_RAMPON); 185 | ssl = lg(); 186 | ssl.actionType(AS_CM_ACTIONTYPE_UPDIM); 187 | } 188 | 189 | void single() { 190 | defaults(); 191 | DimmerPeerList ssl = sh(); 192 | ssl.jtOn(AS_CM_JT_OFFDELAY); 193 | ssl.jtOff(AS_CM_JT_ONDELAY); 194 | ssl.jtDlyOn(AS_CM_JT_RAMPON); 195 | ssl.jtDlyOff(AS_CM_JT_RAMPOFF); 196 | ssl.jtRampOn(AS_CM_JT_ON); 197 | ssl.jtRampOff(AS_CM_JT_OFF); 198 | ssl = lg(); 199 | ssl.actionType(AS_CM_ACTIONTYPE_TOGGLEDIM_TO_COUNTER); 200 | } 201 | 202 | }; 203 | 204 | 205 | class DimmerStateMachine { 206 | 207 | #define DELAY_NO 0x00 208 | #define DELAY_INFINITE 0xffffffff 209 | 210 | class RampAlarm : public Alarm { 211 | public: 212 | DimmerStateMachine& sm; 213 | DimmerPeerList lst; 214 | uint32_t delay, tack; 215 | uint8_t destlevel; 216 | uint8_t dx; 217 | uint8_t brightness; 218 | 219 | RampAlarm (DimmerStateMachine& m) : Alarm(0), sm(m), lst(0), delay(0), tack(0), destlevel(0), dx(5), brightness(0) {} 220 | void list (DimmerPeerList l) { 221 | lst = l; 222 | delay = tack = 0; 223 | destlevel = sm.status(); 224 | } 225 | void init (uint8_t state, DimmerPeerList l) { 226 | uint8_t destlevel = state == AS_CM_JT_RAMPOFF ? 0 : 200; 227 | if ( l.valid() == true ) { 228 | destlevel = state == AS_CM_JT_RAMPOFF ? l.offLevel() : l.onLevel(); 229 | } 230 | init(sm.getDelayForState(state, l), destlevel, l.valid() ? 0 : DELAY_INFINITE, l); 231 | } 232 | void init (uint32_t ramptime, uint8_t level, uint32_t dly, DimmerPeerList l = DimmerPeerList(0)) { 233 | // DPRINT("Ramp/Level: ");DDEC(ramptime);DPRINT("/");DDECLN(level); 234 | lst = l; 235 | destlevel = level == 201 ? sm.lastonlevel : level; 236 | delay = dly; 237 | sm.updateState(destlevel == 0 ? AS_CM_JT_RAMPOFF : AS_CM_JT_RAMPON); 238 | uint8_t curlevel = sm.status(); 239 | uint32_t diff; 240 | if ( curlevel > destlevel ) { // dim down 241 | diff = curlevel - destlevel; 242 | } 243 | else { // dim up 244 | diff = destlevel - curlevel; 245 | } 246 | if ( ramptime > diff ) { 247 | dx = 1; 248 | tack = ramptime / diff; 249 | } 250 | else { 251 | tack = 1; 252 | dx = uint8_t(diff / (ramptime > 0 ? ramptime : 1)); 253 | } 254 | // DPRINT("Dx/Tack: ");DDEC(dx);DPRINT("/");DDECLN(tack); 255 | } 256 | virtual void trigger (AlarmClock& clock) { 257 | uint8_t curlevel = sm.status(); 258 | //DHEX(curlevel); DPRINT(" "); DHEXLN(destlevel); 259 | 260 | if ( sm.status() != destlevel ) { 261 | if ( curlevel > destlevel ) { // dim down 262 | uint8_t rest = curlevel - destlevel; 263 | sm.updateLevel( curlevel - (rest < dx ? rest : dx)); 264 | } 265 | else { // dim up 266 | uint8_t rest = destlevel - curlevel; 267 | sm.updateLevel( curlevel + (rest < dx ? rest : dx)); 268 | } 269 | } 270 | // we catch our destination level -> go to next state 271 | if ( sm.status() == destlevel ) { 272 | uint8_t next = sm.getNextState(); 273 | if ( delay == 0 && lst.valid() == true ) { 274 | delay = sm.getDelayForState(next, lst); 275 | } 276 | sm.setState(next, delay, lst); 277 | } 278 | else { // enable again for next ramp step 279 | set(tack); 280 | clock.add(*this); 281 | } 282 | 283 | // FastLED.setBrightness( constrain(((curlevel * 1275L) / 1000) - ((destlevel == 0) ? 6 : 0), 0, 255) ); 284 | 285 | } 286 | }; 287 | 288 | void updateLevel (uint8_t newlevel) { 289 | level = newlevel; 290 | //DPRINT("updateLevel(): "); DDECLN(newlevel); 291 | FastLED.setBrightness( constrain(((newlevel * 1275L) / 1000) - 1, 0, 255) ); 292 | // changed = true; 293 | } 294 | 295 | void updateState (uint8_t next) { 296 | if ( state != next ) { 297 | switchState(state, next); 298 | state = next; 299 | changed = true; 300 | } 301 | } 302 | 303 | void setState (uint8_t next, uint32_t delay, const DimmerPeerList& lst = DimmerPeerList(0), uint8_t deep = 0) { 304 | // check deep to prevent infinite recursion 305 | if ( next != AS_CM_JT_NONE && deep < 4) { 306 | // first cancel possible running alarm 307 | sysclock.cancel(alarm); 308 | // if state is different 309 | if (state != next) { 310 | updateState(next); 311 | } 312 | if ( state == AS_CM_JT_RAMPON || state == AS_CM_JT_RAMPOFF ) { 313 | alarm.init(state, lst); 314 | sysclock.add(alarm); 315 | } 316 | else { 317 | if (delay == DELAY_NO) { 318 | // go immediately to the next state 319 | next = getNextState(); 320 | delay = getDelayForState(next, lst); 321 | setState(next, delay, lst, ++deep); 322 | } 323 | else if (delay != DELAY_INFINITE) { 324 | alarm.list(lst); 325 | alarm.set(delay); 326 | sysclock.add(alarm); 327 | } 328 | } 329 | } 330 | } 331 | 332 | protected: 333 | uint8_t state : 4; 334 | bool changed : 1; 335 | bool toggledimup : 1; 336 | uint8_t level, lastonlevel; 337 | RampAlarm alarm; 338 | 339 | public: 340 | DimmerStateMachine() : state(AS_CM_JT_NONE), changed(false), toggledimup(true), level(0), lastonlevel(200), alarm(*this) {} 341 | virtual ~DimmerStateMachine () {} 342 | 343 | virtual void switchState(__attribute__ ((unused)) uint8_t oldstate, uint8_t newstate) { 344 | // DPRINT("Dimmer State: ");DHEX(oldstate);DPRINT(" -> ");DHEX(newstate);DPRINT(" Level: ");DHEXLN(level); 345 | if ( newstate == AS_CM_JT_ON ) { 346 | lastonlevel = level; 347 | } 348 | } 349 | 350 | void jumpToTarget(const DimmerPeerList& lst) { 351 | uint8_t next = getJumpTarget(state, lst); 352 | // DPRINT("Jmp: ");DHEX(state);DPRINT(" - ");DHEXLN(next); 353 | if ( next != AS_CM_JT_NONE ) { 354 | // get delay 355 | uint32_t dly = getDelayForState(next, lst); 356 | // switch to next 357 | setState(next, dly, lst); 358 | } 359 | } 360 | 361 | void toggleState () { 362 | if ( state == AS_CM_JT_OFF ) { 363 | setLevel(lastonlevel, 5, 0xffff); 364 | } 365 | else { 366 | setLevel(0, 5, 0xffff); 367 | } 368 | } 369 | 370 | uint8_t getNextState () { 371 | switch ( state ) { 372 | case AS_CM_JT_ONDELAY: return AS_CM_JT_RAMPON; 373 | case AS_CM_JT_RAMPON: return AS_CM_JT_ON; 374 | case AS_CM_JT_ON: return AS_CM_JT_OFFDELAY; 375 | case AS_CM_JT_OFFDELAY: return AS_CM_JT_RAMPOFF; 376 | case AS_CM_JT_RAMPOFF: return AS_CM_JT_OFF; 377 | case AS_CM_JT_OFF: return AS_CM_JT_ONDELAY; 378 | } 379 | return AS_CM_JT_NONE; 380 | } 381 | 382 | uint8_t getJumpTarget(uint8_t stat, const DimmerPeerList& lst) const { 383 | switch ( stat ) { 384 | case AS_CM_JT_ONDELAY: return lst.jtDlyOn(); 385 | case AS_CM_JT_RAMPON: return lst.jtRampOn(); 386 | case AS_CM_JT_ON: return lst.jtOn(); 387 | case AS_CM_JT_OFFDELAY: return lst.jtDlyOff(); 388 | case AS_CM_JT_RAMPOFF: return lst.jtRampOff(); 389 | case AS_CM_JT_OFF: return lst.jtOff(); 390 | } 391 | return AS_CM_JT_NONE; 392 | } 393 | 394 | uint8_t getConditionForState(uint8_t stat, const DimmerPeerList& lst) const { 395 | switch ( stat ) { 396 | case AS_CM_JT_ONDELAY: return lst.ctDlyOn(); 397 | case AS_CM_JT_RAMPON: return lst.ctRampOn(); 398 | case AS_CM_JT_ON: return lst.ctOn(); 399 | case AS_CM_JT_OFFDELAY: return lst.ctDlyOff(); 400 | case AS_CM_JT_RAMPOFF: return lst.ctRampOff(); 401 | case AS_CM_JT_OFF: return lst.ctOff(); 402 | } 403 | return AS_CM_CT_X_GE_COND_VALUE_LO; 404 | } 405 | 406 | uint32_t getDelayForState(uint8_t stat, const DimmerPeerList& lst) const { 407 | if ( lst.valid() == false ) { 408 | return getDefaultDelay(stat); 409 | } 410 | uint8_t value = 0; 411 | switch ( stat ) { 412 | case AS_CM_JT_ONDELAY: value = lst.onDly(); break; 413 | case AS_CM_JT_RAMPON: value = lst.rampOnTime(); break; 414 | case AS_CM_JT_ON: value = lst.onTime(); break; 415 | case AS_CM_JT_OFFDELAY: value = lst.offDly(); break; 416 | case AS_CM_JT_RAMPOFF: value = lst.rampOffTime(); break; 417 | case AS_CM_JT_OFF: value = lst.offTime(); break; 418 | } 419 | return AskSinBase::byteTimeCvt(value); 420 | } 421 | 422 | uint32_t getDefaultDelay(uint8_t stat) const { 423 | switch ( stat ) { 424 | case AS_CM_JT_ON: 425 | case AS_CM_JT_OFF: 426 | return DELAY_INFINITE; 427 | case AS_CM_JT_RAMPON: 428 | case AS_CM_JT_RAMPOFF: 429 | return decis2ticks(5); 430 | } 431 | return DELAY_NO; 432 | } 433 | 434 | bool delayActive () const { 435 | return sysclock.get(alarm) > 0; 436 | } 437 | 438 | void dimUp (const DimmerPeerList& lst) { 439 | uint8_t dx = lst.dimStep(); 440 | uint8_t newlevel = level + dx; 441 | if ( newlevel > lst.dimMaxLevel() ) { 442 | newlevel = lst.dimMaxLevel(); 443 | } 444 | updateState(AS_CM_JT_RAMPON); 445 | updateLevel(newlevel); 446 | updateState(AS_CM_JT_ON); 447 | } 448 | 449 | void dimDown (const DimmerPeerList& lst) { 450 | uint8_t dx = lst.dimStep(); 451 | uint8_t newlevel = level - (dx < level ? dx : level); 452 | if ( newlevel < lst.dimMinLevel() ) { 453 | newlevel = lst.dimMinLevel(); 454 | } 455 | updateState(newlevel > lst.onMinLevel() ? AS_CM_JT_RAMPON : AS_CM_JT_RAMPOFF); 456 | updateLevel(newlevel); 457 | updateState(newlevel > lst.onMinLevel() ? AS_CM_JT_ON : AS_CM_JT_OFF); 458 | } 459 | 460 | void remote (const DimmerPeerList& lst, uint8_t counter) { 461 | // perform action as defined in the list 462 | switch (lst.actionType()) { 463 | case AS_CM_ACTIONTYPE_JUMP_TO_TARGET: 464 | jumpToTarget(lst); 465 | break; 466 | case AS_CM_ACTIONTYPE_TOGGLE_TO_COUNTER: 467 | setState((counter & 0x01) == 0x01 ? AS_CM_JT_RAMPON : AS_CM_JT_RAMPOFF, DELAY_INFINITE, lst); 468 | break; 469 | case AS_CM_ACTIONTYPE_TOGGLE_INVERSE_TO_COUNTER: 470 | setState((counter & 0x01) == 0x01 ? AS_CM_JT_RAMPON : AS_CM_JT_RAMPOFF, DELAY_INFINITE, lst); 471 | break; 472 | case AS_CM_ACTIONTYPE_UPDIM: 473 | dimUp(lst); 474 | break; 475 | case AS_CM_ACTIONTYPE_DOWNDIM: 476 | dimDown(lst); 477 | break; 478 | case AS_CM_ACTIONTYPE_TOGGLEDIM: 479 | if ( toggledimup == true ) dimUp(lst); 480 | else dimDown(lst); 481 | toggledimup = ! toggledimup; 482 | break; 483 | case AS_CM_ACTIONTYPE_TOGGLEDIM_TO_COUNTER: 484 | (counter & 0x01) == 0x01 ? dimUp(lst) : dimDown(lst); 485 | break; 486 | case AS_CM_ACTIONTYPE_TOGGLEDIM_TO_COUNTER_INVERSE: 487 | (counter & 0x01) == 0x00 ? dimUp(lst) : dimDown(lst); 488 | break; 489 | } 490 | 491 | } 492 | 493 | void sensor (const DimmerPeerList& lst, uint8_t counter, uint8_t value) { 494 | uint8_t cond = getConditionForState(state, lst); 495 | bool doit = false; 496 | switch ( cond ) { 497 | case AS_CM_CT_X_GE_COND_VALUE_LO: 498 | doit = (value >= lst.ctValLo()); 499 | break; 500 | case AS_CM_CT_X_GE_COND_VALUE_HI: 501 | doit = (value >= lst.ctValHi()); 502 | break; 503 | case AS_CM_CT_X_LT_COND_VALUE_LO: 504 | doit = (value < lst.ctValLo()); 505 | break; 506 | case AS_CM_CT_X_LT_COND_VALUE_HI: 507 | doit = (value < lst.ctValHi()); 508 | break; 509 | case AS_CM_CT_COND_VALUE_LO_LE_X_LT_COND_VALUE_HI: 510 | doit = ((lst.ctValLo() <= value) && (value < lst.ctValHi())); 511 | break; 512 | case AS_CM_CT_X_LT_COND_VALUE_LO_OR_X_GE_COND_VALUE_HI: 513 | doit = ((value < lst.ctValLo()) || (value >= lst.ctValHi())); 514 | break; 515 | } 516 | if ( doit == true ) { 517 | remote(lst, counter); 518 | } 519 | else { 520 | // TODO use else jump table 521 | } 522 | } 523 | 524 | void setLevel (uint8_t level, uint16_t ramp, uint16_t delay) { 525 | DPRINT("SetLevel: "); DHEX(level); DPRINT(" "); DHEX(ramp); DPRINT(" "); DHEXLN(delay); 526 | if ( ramp == 0 ) { 527 | alarm.destlevel = level; 528 | updateLevel(level); 529 | setState(level == 0 ? AS_CM_JT_OFF : AS_CM_JT_ON, AskSinBase::intTimeCvt(delay)); 530 | FastLED.setBrightness( level ); 531 | } 532 | else { 533 | sysclock.cancel(alarm); 534 | alarm.init(AskSinBase::intTimeCvt(ramp), level, AskSinBase::intTimeCvt(delay)); 535 | sysclock.add(alarm); 536 | } 537 | } 538 | 539 | uint8_t status () const { 540 | return level; 541 | } 542 | 543 | uint8_t flags () const { 544 | uint8_t f = delayActive() ? 0x40 : 0x00; 545 | if ( alarm.destlevel < level) { 546 | f |= AS_CM_EXTSTATE_DOWN; 547 | } 548 | else if ( alarm.destlevel > level) { 549 | f |= AS_CM_EXTSTATE_UP; 550 | } 551 | return f; 552 | } 553 | }; 554 | 555 | static uint8_t physical(0); 556 | static uint8_t brightness(0); 557 | static uint8_t currentProgram(0); 558 | static uint8_t currentColor(0); 559 | 560 | template 561 | class RGBLEDChannel : public Channel, public DimmerStateMachine { 562 | 563 | uint8_t lastmsgcnt; 564 | 565 | protected: 566 | typedef Channel BaseChannel; 567 | 568 | public: 569 | RGBLEDChannel () : BaseChannel(), lastmsgcnt(0xff) {} 570 | virtual ~RGBLEDChannel() {} 571 | 572 | void setup(Device* dev, uint8_t number, uint16_t addr) { 573 | BaseChannel::setup(dev, number, addr); 574 | } 575 | 576 | bool changed () const { 577 | return DimmerStateMachine::changed; 578 | } 579 | 580 | void changed (bool c) { 581 | DimmerStateMachine::changed = c; 582 | } 583 | 584 | void patchStatus (Message& msg) { 585 | switch (this->number()) { 586 | case 1: 587 | if ( msg.length() == 0x0e ) { 588 | msg.length(0x0f); 589 | msg.data()[3] = physical; 590 | } 591 | break; 592 | case 2: 593 | if ( msg.length() == 0x0e ) { 594 | msg.data()[0] = currentColor; 595 | } 596 | break; 597 | case 3: 598 | if ( msg.length() == 0x0e ) { 599 | msg.data()[0] = currentProgram; 600 | } 601 | break; 602 | } 603 | } 604 | 605 | bool process (const Message& msg) { 606 | DPRINTLN("process Message"); 607 | return true; 608 | } 609 | 610 | bool process (const ActionSetMsg& msg) { 611 | DPRINT("process() "); DDECLN(this->number()); 612 | DPRINT("msg.value = "); DDECLN(msg.value()); 613 | DPRINT("msg.ramp = "); DDECLN(msg.ramp()); 614 | DPRINT("msg.delay= "); DDECLN(msg.delay()); 615 | switch (this->number()) { 616 | case 1: 617 | brightness = msg.value(); 618 | setLevel( msg.value(), msg.ramp(), msg.delay() ); 619 | break; 620 | case 2: 621 | currentColor = msg.value(); 622 | setColor (currentColor); 623 | break; 624 | case 3: 625 | currentProgram = msg.value(); 626 | runProgram (currentProgram); 627 | break; 628 | default: 629 | break; 630 | } 631 | return true; 632 | } 633 | 634 | bool process (const RemoteEventMsg& msg) { 635 | bool lg = msg.isLong(); 636 | Peer p(msg.peer()); 637 | uint8_t cnt = msg.counter(); 638 | typename BaseChannel::List3 l3 = BaseChannel::getList3(p); 639 | if ( l3.valid() == true ) { 640 | // l3.dump(); 641 | typename BaseChannel::List3::PeerList pl = lg ? l3.lg() : l3.sh(); 642 | // pl.dump(); 643 | if ( cnt != lastmsgcnt || (lg == true && pl.multiExec() == true) ) { 644 | lastmsgcnt = cnt; 645 | remote(pl, cnt); 646 | } 647 | return true; 648 | } 649 | return false; 650 | } 651 | 652 | void setColor(uint8_t val) { 653 | for (int i = 0; i < WSNUM_LEDS; i++) { 654 | leds[i] = CHSV((val * 1275L) / 1000, (val < 200) ? 255 : 0, 255); 655 | } 656 | } 657 | 658 | void runProgram(uint8_t val) { 659 | switch (val) { 660 | case 0: 661 | setColor(currentColor); 662 | break; 663 | case 1: 664 | RGBProgramRainbow(SLOW_PROGRAM_TIMER, brightness); 665 | break; 666 | case 2: 667 | RGBProgramRainbow(NORMAL_PROGRAM_TIMER, brightness); 668 | break; 669 | case 3: 670 | RGBProgramRainbow(FAST_PROGRAM_TIMER, brightness); 671 | break; 672 | case 4: 673 | RGBProgramFire(brightness); 674 | break; 675 | case 5: 676 | RGBProgramWaterfall(brightness); 677 | break; 678 | case 6: 679 | RGBProgramTVSimulation(brightness); 680 | break; 681 | } 682 | } 683 | 684 | bool process (const SensorEventMsg & msg) { 685 | bool lg = msg.isLong(); 686 | Peer p(msg.peer()); 687 | uint8_t cnt = msg.counter(); 688 | uint8_t value = msg.value(); 689 | typename BaseChannel::List3 l3 = BaseChannel::getList3(p); 690 | if ( l3.valid() == true ) { 691 | // l3.dump(); 692 | typename BaseChannel::List3::PeerList pl = lg ? l3.lg() : l3.sh(); 693 | // pl.dump(); 694 | sensor(pl, cnt, value); 695 | return true; 696 | } 697 | return false; 698 | } 699 | }; 700 | 701 | 702 | 703 | template 704 | class RGBLEDDevice : public MultiChannelDevice { 705 | 706 | public: 707 | typedef MultiChannelDevice DeviceType; 708 | 709 | RGBLEDDevice (const DeviceInfo& info, uint16_t addr) : DeviceType(info, addr) {} 710 | virtual ~RGBLEDDevice () {} 711 | 712 | void firstinit () { 713 | DeviceType::firstinit(); 714 | DeviceType::channel(1).getList1().logicCombination(LOGIC_OR); 715 | for ( uint8_t i = 2; i <= DeviceType::channels(); ++i ) { 716 | DeviceType::channel(i).getList1().logicCombination(LOGIC_INACTIVE); 717 | } 718 | } 719 | 720 | virtual void configChanged () { 721 | DeviceType::configChanged(); 722 | DPRINTLN("* Config Changed : List0"); 723 | for (int i = 0; i < WSNUM_LEDS; i++) { 724 | leds[i] = CRGB::Black; 725 | } 726 | FastLED.show(); 727 | 728 | #ifdef PWM_ENABLED 729 | static AnalogPWMController controler; 730 | FastLED.addLeds(&controler, leds, 1); 731 | #else 732 | #ifdef ENABLE_RGBW 733 | FastLED.addLeds(ledsRGB, getRGBWsize(WSNUM_LEDS)); 734 | #else 735 | FastLED.addLeds(leds, WSNUM_LEDS); 736 | #endif 737 | #endif 738 | 739 | DeviceType::channel(2).setColor(0); 740 | } 741 | 742 | bool handleLED() { 743 | DeviceType::channel(3).runProgram(currentProgram); 744 | FastLED.show(); 745 | } 746 | 747 | void init (HalType& hal) { 748 | DeviceType::init(hal); 749 | //if ( DeviceType::channel(1).getList1().powerUpAction() == true ) { 750 | // DeviceType::channel(1).setLevel(200, 5, 0xffff); 751 | // } 752 | //else { 753 | DeviceType::channel(1).setLevel(0, 0, 0xffff); 754 | DeviceType::channel(2).setLevel(0, 0, 0xffff); 755 | DeviceType::channel(3).setLevel(0, 0, 0xffff); 756 | // } 757 | } 758 | 759 | uint16_t combineChannels () { 760 | uint16_t value = 0; 761 | for ( uint8_t i = 1; i < DeviceType::channels(); ++i ) { 762 | uint8_t level = DeviceType::channel(i).status(); 763 | switch ( DeviceType::channel(i).getList1().logicCombination() ) { 764 | default: 765 | case LOGIC_INACTIVE: 766 | break; 767 | case LOGIC_OR: 768 | value = value > level ? value : level; 769 | break; 770 | case LOGIC_AND: 771 | value = value < level ? value : level; 772 | break; 773 | case LOGIC_XOR: 774 | value = value == 0 ? level : (level == 0 ? value : 0); 775 | break; 776 | case LOGIC_NOR: 777 | value = 200 - (value > level ? value : level); 778 | break; 779 | case LOGIC_NAND: 780 | value = 200 - (value < level ? value : level); 781 | break; 782 | case LOGIC_ORINVERS: 783 | level = 200 - level; 784 | value = value > level ? value : level; 785 | break; 786 | case LOGIC_ANDINVERS: 787 | level = 200 - level; 788 | value = value < level ? value : level; 789 | break; 790 | case LOGIC_PLUS: 791 | value += level; 792 | if ( value > 200 ) value = 200; 793 | break; 794 | case LOGIC_MINUS: 795 | if ( level > value ) value = 0; 796 | else value -= level; 797 | break; 798 | case LOGIC_MUL: 799 | value = value * level / 200; 800 | break; 801 | case LOGIC_PLUSINVERS: 802 | level = 200 - level; 803 | value += level; 804 | if ( value > 200 ) value = 200; 805 | break; 806 | break; 807 | case LOGIC_MINUSINVERS: 808 | level = 200 - level; 809 | if ( level > value ) value = 0; 810 | else value -= level; 811 | break; 812 | case LOGIC_MULINVERS: 813 | level = 200 - level; 814 | value = value * level / 200; 815 | break; 816 | case LOGIC_INVERSPLUS: 817 | value += level; 818 | if ( value > 200 ) value = 200; 819 | value = 200 - value; 820 | break; 821 | case LOGIC_INVERSMINUS: 822 | if ( level > value ) value = 0; 823 | else value -= level; 824 | value = 200 - value; 825 | break; 826 | case LOGIC_INVERSMUL: 827 | value = value * level / 200; 828 | value = 200 - value; 829 | break; 830 | } 831 | } 832 | // DHEXLN(value); 833 | return value; 834 | } 835 | 836 | }; 837 | 838 | } 839 | 840 | #endif 841 | -------------------------------------------------------------------------------- /RGBPrograms.h: -------------------------------------------------------------------------------- 1 | void RGBProgramRainbow(uint8_t speed, uint8_t brightness) { 2 | static uint8_t startIndex = 0; 3 | static unsigned long lastmillis = millis(); 4 | if (millis() - lastmillis > speed) { 5 | startIndex = startIndex + 1; 6 | for ( int i = 0; i < WSNUM_LEDS; i++) { 7 | leds[i] = ColorFromPalette( RainbowColors_p, startIndex, brightness, LINEARBLEND); 8 | } 9 | lastmillis = millis(); 10 | } 11 | } 12 | 13 | 14 | void RGBProgramFire(uint8_t brightness) { 15 | static unsigned long lastmillis = millis(); 16 | if (millis() - lastmillis > 15) { 17 | 18 | static byte heat[WSNUM_LEDS]; 19 | 20 | for ( int i = 0; i < WSNUM_LEDS; i++) { 21 | heat[i] = qsub8( heat[i], random8(0, ((FIRE_PROGRAM_COOLING * 10) / WSNUM_LEDS) + 2)); 22 | } 23 | 24 | for ( int k = WSNUM_LEDS - 1; k >= 2; k--) { 25 | heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3; 26 | } 27 | 28 | if ( random8() < FIRE_PROGRAM_SPARKLING ) { 29 | int y = random8(7); 30 | heat[y] = qadd8( heat[y], random8(160, 255) ); 31 | } 32 | 33 | for ( int j = 0; j < WSNUM_LEDS; j++) { 34 | CRGB color = HeatColor( heat[j]); 35 | int pixelnumber; 36 | 37 | pixelnumber = j; 38 | 39 | leds[pixelnumber] = color; 40 | } 41 | lastmillis = millis(); 42 | } 43 | } 44 | 45 | void RGBProgramWaterfall (uint8_t brightness) {} 46 | 47 | void RGBProgramTVSimulation (uint8_t brightness) {} 48 | 49 | -------------------------------------------------------------------------------- /analog.h: -------------------------------------------------------------------------------- 1 | #include "FastLED.h" 2 | 3 | ///@ingroup chipsets 4 | ///@{ 5 | 6 | // note - dmx simple must be included before FastSPI for this code to be enabled 7 | template class AnalogPWMController : public CPixelLEDController { 8 | public: 9 | // initialize the LED controller 10 | virtual void init() { 11 | if(RED != 99) 12 | pinMode(RED, OUTPUT); 13 | 14 | if(GREEN != 99) 15 | pinMode(GREEN, OUTPUT); 16 | 17 | if(BLUE != 99) 18 | pinMode(BLUE, OUTPUT); 19 | 20 | if(WHITE != 99) 21 | pinMode(WHITE, OUTPUT); 22 | } 23 | 24 | protected: 25 | virtual void showPixels(PixelController & pixels) { 26 | int iChannel = 1; 27 | if(pixels.has(1)) { 28 | boolean isWhite = false; 29 | if(WHITE != 99){ 30 | if(pixels.loadAndScale0() == pixels.loadAndScale1() && pixels.loadAndScale0() == pixels.loadAndScale2()) { 31 | analogWrite(WHITE, pixels.loadAndScale0()); 32 | 33 | if(RED != 99) 34 | analogWrite(RED, 0); 35 | 36 | if(GREEN != 99) 37 | analogWrite(GREEN, 0); 38 | 39 | if(BLUE != 99) 40 | analogWrite(BLUE, 0); 41 | 42 | isWhite = true; 43 | } 44 | else 45 | analogWrite(WHITE, 0); 46 | } 47 | 48 | if(isWhite == false || (isWhite == true && WHITEONLY == false)) { 49 | if(RED != 99) 50 | analogWrite(RED, pixels.loadAndScale0()); 51 | 52 | if(GREEN != 99) 53 | analogWrite(GREEN, pixels.loadAndScale1()); 54 | 55 | if(BLUE != 99) 56 | analogWrite(BLUE, pixels.loadAndScale2()); 57 | } 58 | 59 | pixels.advanceData(); 60 | pixels.stepDithering(); 61 | } 62 | } 63 | }; 64 | --------------------------------------------------------------------------------