| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586 |
- # $Id: 31_MilightDevice.pm 16085 2018-02-04 18:35:23Z mattwire $
- ##############################################
- #
- # 31_MilightDevice.pm (Based on 32_WifiLight.pm by hermannj)
- # FHEM module for MILIGHT lightbulbs. Supports RGB (untested), RGBW and White models.
- # Author: Matthew Wire (mattwire)
- #
- # This file is part of fhem.
- #
- # Fhem 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.
- #
- # Fhem 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 fhem. If not, see <http://www.gnu.org/licenses/>.
- #
- ##############################################################################
- package main;
- use strict;
- use warnings;
- use IO::Handle;
- use IO::Socket;
- use IO::Select;
- use Time::HiRes;
- #use Math::Round ();
- use Color;
- use SetExtensions;
- my %dim_values = (
- 0 => "dim_00",
- 1 => "dim_10",
- 2 => "dim_20",
- 3 => "dim_30",
- 4 => "dim_40",
- 5 => "dim_50",
- 6 => "dim_60",
- 7 => "dim_70",
- 8 => "dim_80",
- 9 => "dim_90",
- 10 => "dim_100",
- );
- # RGBW 3 byte commands. 3rd byte not required Bridge V3+
- my @RGBWCmdsOn = ("\x45", "\x47", "\x49", "\x4B", "\x42"); # Byte 1 for setting On
- my @RGBWCmdsOff = ("\x46", "\x48", "\x4A", "\x4C", "\x41"); # Byte 1 for setting Off
- my @RGBWCmdsWT = ("\xC5", "\xC7", "\xC9", "\xCB", "\xC2"); # Byte 1 for setting WhiteMode
- my @RGBWCmdsNt = ("\xC6", "\xC8", "\xCA", "\xCC", "\xC1"); # Byte 1 for setting NightMode
- my $RGBWCmdBri = "\x4E"; # Byte 1 for setting brightness (Byte 2 specifies level (0x02-0x1B 25 steps)
- my $RGBWCmdCol = "\x40"; # Byte 1 for setting color (Byte 2 specifies color value (0x00-0xFF (255 steps))
- my $RGBWCmdDiscoUp = "\x4D"; # Byte 1 for setting discoMode Up
- my $RGBWCmdDiscoInc = "\x44"; # Byte 1 for setting discoMode speed +
- my $RGBWCmdDiscoDec = "\x43"; # Byte 1 for setting discoMode speed -
- my $RGBWCmdEnd = "\x55"; # Byte 3
- # White 3 byte commands.
- my @WhiteCmdsOn = ("\x38", "\x3D", "\x37", "\x32", "\x35"); # Byte 1 for setting On
- my @WhiteCmdsOff = ("\x3B", "\x33", "\x3A", "\x36", "\x39"); # Byte 1 for setting Off
- my @WhiteCmdsOnFull = ("\xB8", "\xBD", "\xB7", "\xB2", "\xB5"); # Byte 1 for setting full brightness
- my @WhiteCmdsNt = ("\xBB", "\xB3", "\xBA", "\xB6", "\xB9"); # Byte 1 for setting NightMode
- my @WhiteCmdBriDn = ("\x34", "\x34", "\x34", "\x34", "\xB4"); # Byte 1 for setting Brightness down (11 steps, no direct setting)
- my @WhiteCmdBriUp = ("\x3C", "\x3C", "\x3C", "\x3C", "\xBC"); # Byte 1 for setting Brightness up (11 steps, no direct setting)
- my @WhiteCmdColDn = ("\x3F", "\x3F", "\x3F", "\x3F", "\xBF"); # Byte 1 for setting colour temp down
- my @WhiteCmdColUp = ("\x3E", "\x3E", "\x3E", "\x3E", "\xBE"); # Byte 1 for setting colour temp up
- my $WhiteCmdEnd = "\x55"; # Byte 3
- sub MilightDevice_Initialize($)
- {
- my ($hash) = @_;
- $hash->{DefFn} = "MilightDevice_Define";
- $hash->{UndefFn} = "MilightDevice_Undef";
- $hash->{ShutdownFn} = "MilightDevice_Undef";
- $hash->{SetFn} = "MilightDevice_Set";
- $hash->{GetFn} = "MilightDevice_Get";
- $hash->{AttrFn} = "MilightDevice_Attr";
- $hash->{NotifyFn} = "MilightDevice_Notify";
- $hash->{AttrList} = "IODev dimStep defaultBrightness defaultRampOn " .
- "defaultRampOff presets dimOffWhite:1,0 updateGroupDevices:1,0 " .
- "restoreAtStart:1,0 colorCast gamma lightSceneParamsToSave " .
- $readingFnAttributes;
- FHEM_colorpickerInit();
-
- }
- #####################################
- # Device State Icon for FHEMWEB: Shows a colour changing icon with dim level
- sub MilightDevice_devStateIcon($)
- {
- my($hash) = @_;
- $hash = $defs{$hash} if(ref($hash) ne 'HASH');
- return undef if(!$hash);
- return undef if($hash->{helper}->{group});
- my $name = $hash->{NAME};
- my $percent = ReadingsVal($name,"brightness","100");
- my $s = $dim_values{MilightDevice_roundfunc($percent/10)};
- # Return SVG coloured icon with toggle as default action
- return ".*:light_light_$s@#".ReadingsVal($name, "rgb", "FFFFFF").":toggle"
- if (($hash->{LEDTYPE} eq 'RGBW') || ($hash->{LEDTYPE} eq 'RGB'));
- # Return SVG icon with toggle as default action (for White bulbs)
- return ".*:light_light_$s:toggle";
- }
- #####################################
- # Define Milight device
- sub MilightDevice_Define($$)
- {
- my ($hash, $def) = @_;
- my @args = split("[ \t][ \t]*", $def);
- my ($name, $type, $ledtype, $iodev, $slot) = @args;
-
- $hash->{INIT} = 0; # Set to 1 when lamp initialised (MilightDevice_Restore)
- $hash->{LEDTYPE} = $ledtype;
- $hash->{SLOT} = $slot;
- $hash->{SLOTID} = $slot;
- if($slot eq 'A') {
- $hash->{SLOTID} = 9 if ($hash->{LEDTYPE} eq 'RGBW');
- $hash->{SLOTID} = 5 if ($hash->{LEDTYPE} eq 'White');
- $hash->{SLOTID} = 0 if ($hash->{LEDTYPE} eq 'RGB');
- }
- # Validate parameters
- return "wrong syntax: define <name> MilightDevice <devType(RGB|RGBW|White)> <IODev> <slot>" if(@args < 5);
- return "unknown LED type ($hash->{LEDTYPE}): choose one of RGB, RGBW, White" if ($hash->{LEDTYPE} !~ /RGBW|White|RGB/);
- return "Invalid slot: Select one of 1..4 / A for White" if (($hash->{SLOTID} !~ /^\d*$/) || (($hash->{SLOT} ne 'A') && (($hash->{SLOT} < 1) || ($hash->{SLOT} > 4))) && ($hash->{LEDTYPE} eq 'White'));
- return "Invalid slot: Select one of 5..8 / A for RGBW" if (($hash->{SLOTID} !~ /^\d*$/) || (($hash->{SLOT} ne 'A') && (($hash->{SLOT} < 5) || ($hash->{SLOT} > 8))) && ($hash->{LEDTYPE} eq 'RGBW'));
- return "Invalid slot: Select 0 for RGB" if (($hash->{SLOTID} !~ /^\d*$/) || ($hash->{SLOTID} != 0 && $hash->{LEDTYPE} eq 'RGB'));
- Log3 ($hash, 4, $name."_Define: $name $type $hash->{LEDTYPE} $iodev $hash->{SLOT}");
- # Verify IODev is valid
- AssignIoPort($hash, $iodev);
- if(defined($hash->{IODev}->{NAME})) {
- Log3 $name, 4, $name."_Define: I/O device is " . $hash->{IODev}->{NAME};
- } else {
- Log3 $name, 1, $name."_Define: no I/O device";
- }
- # Look for already defined device on IODev
- if ($hash->{SLOT} ne 'A' && defined($hash->{IODev}->{$hash->{SLOT}}->{NAME}))
- {
- # If defined slot does not match current device name don't allow new definition. Redefining the same device is ok though.
- #return "Slot $hash->{SLOT} already defined as $hash->{IODev}->{$hash->{SLOT}}->{NAME}" if ($hash->{IODev}->{$hash->{SLOT}}->{NAME} ne $name);
- }
- # Define device on IODev
- if ($hash->{SLOT} ne 'A')
- {
- $hash->{IODev}->{$hash->{SLOT}}->{NAME} = $name;
- #$hash->{IODev}->{$hash->{SLOT}}->{DEVNAME} = $name;
- }
- # Define Command Queue
- my @cmdQueue = [];
- $hash->{helper}->{cmdQueue} = \@cmdQueue;
-
- my $baseCmds = "on off toggle dimup dimdown";
- my $sharedCmds = "pair unpair restorePreviousState:noArg saveState:noArg restoreState:noArg";
- my $rgbCmds = "hsv rgb:colorpicker,RGB hue:colorpicker,HUE,0,1,360 saturation:slider,0,100,100 preset";
- $hash->{helper}->{COMMANDSET} = "$baseCmds discoModeUp:noArg discoSpeedUp:noArg discoSpeedDown:noArg night:noArg white:noArg toggleWhite:noArg $sharedCmds $rgbCmds"
- if ($hash->{LEDTYPE} eq 'RGBW');
- $hash->{helper}->{COMMANDSET} = "$baseCmds discoModeUp:noArg discoModeDown:noArg discoSpeedUp:noArg discoSpeedDown:noArg $sharedCmds $rgbCmds"
- if ($hash->{LEDTYPE} eq 'RGB');
-
- $hash->{helper}->{COMMANDSET} = "$baseCmds hsv ct:colorpicker,CT,3000,350,6500 night:noArg $sharedCmds"
- if ($hash->{LEDTYPE} eq 'White');
-
- my $defaultcommandset = $hash->{helper}->{COMMANDSET};
- $hash->{helper}->{COMMANDSET} .= " dim:slider,0,".MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)).",100 brightness:slider,0,".MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)).",100";
- # webCmds
- if (!defined($attr{$name}{webCmd}))
- {
- $attr{$name}{webCmd} = 'on:off:dim:hue:night:rgb ffffff:rgb ff0000:rgb 00ff00:rgb 0000ff:rgb ffff00' if ($hash->{LEDTYPE} eq 'RGBW');
- $attr{$name}{webCmd} = 'on:off:dim:hue:rgb ffffff:rgb ff0000:rgb 00ff00:rgb 0000ff:rgb ffff00' if ($hash->{LEDTYPE} eq 'RGB');
- $attr{$name}{webCmd} = 'on:off:dim:ct:night' if ($hash->{LEDTYPE} eq 'White');
- }
- $hash->{helper}->{GAMMAMAP} = MilightDevice_CreateGammaMapping($hash, 1.0);
- $hash->{helper}->{COLORMAP} = MilightDevice_ColorConverter($hash, split(',', "0,0,0,0,0,0"));
-
- # Define devStateIcon
- $attr{$name}{devStateIcon} = '{(MilightDevice_devStateIcon($name),"toggle")}' if(!defined($attr{$name}{devStateIcon}));
-
- # Event on change reading
- $attr{$name}{"event-on-change-reading"} = "state,transitionInProgress" if (!defined($attr{$name}{"event-on-change-reading"}));
- # lightScene
- if(!defined($attr{$name}{"lightSceneParamsToSave"}))
- {
- $attr{$name}{"lightSceneParamsToSave"} = "hsv" if (($hash->{LEDTYPE} eq 'RGBW')|| ($hash->{LEDTYPE} eq 'RGB'));
- $attr{$name}{"lightSceneParamsToSave"} = "brightness" if ($hash->{LEDTYPE} eq 'White');
- }
- # IODev
- $attr{$name}{IODev} = $hash->{IODev} if (!defined($attr{$name}{IODev}));
- # restoreAtStart
- if($slot eq 'A') {
- $attr{$name}{"restoreAtStart"} = 0 if (!defined($attr{$name}{"restoreAtStart"}));
- } else {
- $attr{$name}{"restoreAtStart"} = 1 if (!defined($attr{$name}{"restoreAtStart"}));
- }
-
- return undef;
- }
- sub MilightDevice_Init($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- if( AttrVal($hash->{NAME}, "gamma", "1.0") eq "1.0")
- {
- Log3 ($name, 5, $name." dimstep ".MilightDevice_roundfunc(100 / MilightDevice_DimSteps($hash))." / gamma 1.0");
- } else {
- $hash->{helper}->{COMMANDSET} =~ s/dim:slider,0,.*,100/dim:slider,0,1,100/g;
- $hash->{helper}->{COMMANDSET} =~ s/brightness:slider,0,.*,100/brightness:slider,0,1,100/g;
- Log3 $name, 5, $name." dimstep 1 / gamma ".AttrVal($hash->{NAME}, "gamma", "1.0");
- $hash->{helper}->{GAMMAMAP} = MilightDevice_CreateGammaMapping($hash, AttrVal($hash->{NAME}, "gamma", "1.0"));
- }
- # Colormap / Commandsets
- if (($hash->{LEDTYPE} eq 'RGBW') || ($hash->{LEDTYPE} eq 'RGB'))
- {
- my @a = split(',', "0,0,0,0,0,0");
- if ( defined( $attr{$name}{colorCast} ) )
- {
- @a = split(',', AttrVal($hash->{NAME}, "colorCast", "0,0,0,0,0,0"));
- @a = split(',', "0,0,0,0,0,0") unless (@a == 6);
- foreach my $tc (@a)
- {
- @a = split(',', "0,0,0,0,0,0") unless ($tc =~ m/^\s*[\-]{0,1}[0-9]+[\.]{0,1}[0-9]*\s*$/g);
- @a = split(',', "0,0,0,0,0,0") if (abs($tc) >= 30);
- }
- }
- $hash->{helper}->{COLORMAP} = MilightDevice_ColorConverter($hash, @a);
- }
- return undef;
- }
- #####################################
- # Undefine device
- sub MilightDevice_Undef(@)
- {
- my ($hash,$args) = @_;
- RemoveInternalTimer($hash);
- # Remove slot on bridge
- delete ($hash->{IODev}->{$hash->{SLOT}}->{NAME}) if ($hash->{SLOT} ne 'A');
- return undef;
- }
- #####################################
- # Set functions
- sub MilightDevice_Set(@)
- {
- my ($hash, $name, $cmd, @args) = @_;
- my $cnt = @args;
- my $ramp = 0;
- my $flags = "";
- my $event = undef;
- my $usage = "set $name ...";
- if ($hash->{IODev}->{STATE} ne "ok" && $hash->{IODev}->{STATE} ne "Initialized") {
- readingsSingleUpdate($hash, "state", "error", 1);
- $flags = "q";
- $args[2] = "" if(!defined($args[2]));
- $args[2] .= "q" if ($args[2] !~ m/.*[qQ].*/);
- # return SetExtensions($hash, $hash->{helper}->{COMMANDSET}, $name, $cmd, @args);
- # IO error, we need to keep our current state settings!
- }
- # Commands that map to other commands
- if ($cmd eq "toggle")
- {
- $cmd = ReadingsVal($name,"state","on") ne "off" ? "off" :"on";
- }
- elsif ($cmd eq "white")
- {
- $cmd = "saturation";
- $args[0] = 0;
- }
- elsif ($cmd eq "toggleWhite")
- {
- $cmd = "saturation";
- $args[0] = (ReadingsVal($name,"saturation",100) > 0) ? 0 : 100;
- }
- # Commands
- if ($cmd eq 'on')
- {
- if (defined($args[0]))
- {
- return "Usage: set $name on [seconds(0..X)]" if ($args[0] !~ /^\d+$/);
- $ramp = $args[0];
- }
- elsif (defined($attr{$name}{defaultRampOn}))
- {
- $ramp = $attr{$name}{defaultRampOn};
- }
- return MilightDevice_RGBW_On($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
- return MilightDevice_White_DimOn($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White' && AttrVal($hash->{NAME}, "dimOffWhite", 0) == 1);
- return MilightDevice_White_On($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
- return MilightDevice_RGB_On($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
- }
- elsif ($cmd eq 'off')
- {
- if (defined($args[0]))
- {
- return "Usage: set $name off [seconds(0..X)]" if ($args[0] !~ /^\d+$/);
- $ramp = $args[0];
- }
- elsif (defined($attr{$name}{defaultRampOff}))
- {
- $ramp = $attr{$name}{defaultRampOff};
- }
- return MilightDevice_RGBW_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
- return MilightDevice_White_DimOff($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White' && AttrVal($hash->{NAME}, "dimOffWhite", 0) == 1);
- return MilightDevice_White_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
- return MilightDevice_RGB_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
- }
- # Set HSV value
- elsif ($cmd eq 'hsv')
- {
- $usage = "Usage: set $name hsv <h(0..360)>,<s(0..100)>,<v(0..100)> [seconds(0..x)] [flags(l=long path|q=don't clear queue)]";
- $usage = "Usage: set $name hsv <h(3000=Warm..6500=Cool)>,<s(-)>,<v(0..100)> [seconds(0..x)] [flags(q=don't clear queue)]" if ($hash->{LEDTYPE} eq 'White');
- return $usage if ($args[0] !~ /^(\d{1,4}),(\d{1,3}),(\d{1,3})$/);
- my ($h, $s, $v) = ($1, $2, $3);
- return "Invalid hue ($h): valid range 0..360" if (!(($h >= 0) && ($h <= 360)) && ($hash->{LEDTYPE} ne 'White'));
- return "Invalid color temperature ($h): valid range 3000..6500" if (!(($h >= 3000) && ($h <= 6500)) && ($hash->{LEDTYPE} eq 'White'));
- return "Invalid saturation ($s): valid range 0..100" if (!(($s >= 0) && ($s <= 100)) && ($hash->{LEDTYPE} ne 'White'));
- return "Invalid brightness ($v): valid range 0..100" if !(($v >= 0) && ($v <= 100));
- if (defined($args[1]))
- {
- return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
- $ramp = $args[1];
- }
- if (defined($args[2]))
- {
- return $usage if ($args[2] !~ m/.*[lLqQ].*/); # Flags l=Long way round for transition, q=don't clear queue (add to end)
- $flags = $args[2];
- }
- return MilightDevice_White_Transition($hash, $h, 0, $v, $ramp, $flags) if($hash->{LEDTYPE} eq 'White');
- return MilightDevice_HSV_Transition($hash, $h, $s, $v, $ramp, $flags);
- }
- # Dim to a fixed percentage with transition if requested
- elsif ($cmd eq 'dim' || $cmd eq 'brightness')
- {
- $usage = "Usage: set $name dim <percent(0..100)> [seconds(0..x)] [flags(l=long path|q=don't clear queue)]";
- return $usage if (($args[0] !~ /^\d+$/) || ($args[0] < 0) || ($args[0] > 100)); # Decimal value for percent between 0..100
- if (defined($args[1]))
- {
- return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
- $ramp = $args[1];
- }
- if (defined($args[2]))
- {
- return $usage if ($args[2] !~ m/.*[lLqQ].*/); # Flags l=Long way round for transition, q=don't clear queue (add to end)
- $flags = $args[2];
- }
- return MilightDevice_RGBW_Dim($hash, $args[0], $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
- return MilightDevice_White_Dim($hash, $args[0], $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
- return MilightDevice_RGB_Dim($hash, $args[0], $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
- }
- # Set night mode
- elsif ($cmd eq 'night')
- {
- if (defined($args[0]))
- {
- return "Usage: set $name night";
- }
- return MilightDevice_RGBW_Night($hash) if ($hash->{LEDTYPE} eq 'RGBW');
- return MilightDevice_White_Night($hash) if ($hash->{LEDTYPE} eq 'White');
- }
- # Set hue
- elsif ($cmd eq 'hue')
- {
- $usage = "Usage: set $name hue <h(0..360)> [seconds(0..x)] [flags(l=long path|q=don't clear queue)]";
- return $usage if (($args[0] !~ /^(\d+)$/) || ($args[0] < 0) || ($args[0] > 360));
- if (defined($args[1]))
- {
- return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
- $ramp = $args[1];
- }
- if (defined($args[2]))
- {
- return $usage if ($args[2] !~ m/.*[lLqQ].*/); # Flags l=Long way round for transition, q=don't clear queue (add to end)
- $flags = $args[2];
- }
- my $sat = ReadingsVal($hash->{NAME}, "saturation", 100);
- $sat = 100 if(ReadingsVal($hash->{NAME}, "saturation", 0) == 0);
- return MilightDevice_HSV_Transition($hash, $args[0], $sat, ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), $ramp, $flags);
- }
-
- # Set color temperature
- elsif ($cmd eq 'ct')
- {
- if (defined($args[0]))
- {
- return "Usage: set $name ct <3000=Warm..6500=Cool>" if (($args[0] !~ /^\d+$/) || ($args[0] < 2500 || $args[0] > 7000));
- }
- if (defined($args[1]))
- {
- return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
- $ramp = $args[1];
- }
- if (defined($args[2]))
- {
- return $usage if ($args[2] !~ m/.*[lLqQ].*/); # Flags l=Long way round for transition, q=don't clear queue (add to end)
- $flags = $args[2];
- }
- return MilightDevice_White_SetColourTemp($hash, $args[0], $ramp, $flags);
- }
-
- # Set RGB value
- elsif( $cmd eq "rgb")
- {
- $usage = "Usage: set $name rgb RRGGBB [seconds(0..x)] [flags(l=long path|q=don't clear queue)]";
- return $usage if ($args[0] !~ /^([0-9A-Fa-f]{1,2})([0-9A-Fa-f]{1,2})([0-9A-Fa-f]{1,2})$/);
- my( $r, $g, $b ) = (hex($1), hex($2), hex($3)); #change to color.pm?
- my( $h, $s, $v ) = Color::rgb2hsv($r/255.0,$g/255.0,$b/255.0);
- $h = MilightDevice_roundfunc($h * 360);
- $s = MilightDevice_roundfunc($s * 100);
- $v = MilightDevice_roundfunc($v * 100);
- if (defined($args[1]))
- {
- return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
- $ramp = $args[1];
- }
- if (defined($args[2]))
- {
- return $usage if ($args[2] !~ m/.*[lLqQ].*/); # Flags l=Long way round for transition, q=don't clear queue (add to end)
- $flags = $args[2];
- }
- return MilightDevice_HSV_Transition($hash, $h, $s, $v, $ramp, $flags);
- }
- # Dim up by 1 "dimStep" or by a percentage with transition if requested
- elsif ($cmd eq 'dimup')
- {
- $usage = "Usage: set $name dimup [percent change(0..100)] [seconds(0..x)]";
- my $percentChange = MilightDevice_roundfunc(100 / MilightDevice_DimSteps($hash)); # Default one dimStep
- if (defined($args[0]))
- { # Percent change (0..100%)
- return $usage if (($args[0] !~ /^\d+$/) || ($args[0] < 0) || ($args[0] > 100)); # Decimal value for percent between 0..100
- $percentChange = $args[0]; # Percentage to change, will be converted in dev specific function
- }
- if (defined($args[1]))
- { # Seconds for transition (0..x)
- return $usage if (($args[1] !~ /^\d+$/) && ($args[1] >= 0)); # Decimal value for ramp > 0
- $ramp = $args[1];
- # Special case, if percent=100 adjust the ramp so it matches the actual amount required.
- # Eg. start: 80%. ramp 5seconds. Amount change: 100-80=20. Ramp time req: 20/100*5 = 1second.
- if ($percentChange == 100)
- {
- my $difference = $percentChange - ReadingsVal($hash->{NAME}, "brightness", 0);
- $ramp = ($difference/100) * $ramp;
- Log3 ($hash, 5, "$hash->{NAME}_Set: dimdown. Adjusted ramp to $ramp");
- }
- }
-
- my $newBrightness = ReadingsVal($hash->{NAME}, "brightness", 0) + $percentChange;
- $newBrightness = 100 if $newBrightness > 100;
- return MilightDevice_RGBW_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
- return MilightDevice_White_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
- return MilightDevice_RGB_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
- }
- # Dim down by 1 "dimStep" or by a percentage with transition if requested
- elsif ($cmd eq 'dimdown')
- {
- $usage = "Usage: set $name dimdown [percent change(0..100)] [seconds(0..x)]";
- my $percentChange = MilightDevice_roundfunc(100 / MilightDevice_DimSteps($hash)); # Default one dimStep
- if (defined($args[0]))
- { # Percent change (0..100%)
- return $usage if (($args[0] !~ /^\d+$/) || ($args[0] < 0) || ($args[0] > 100)); # Decimal value for percent between 0..100
- $percentChange = $args[0]; # Percentage to change, will be converted in dev specific function
- }
- if (defined($args[1]))
- { # Seconds for transition (0..x)
- return $usage if (($args[1] !~ /^\d+$/) && ($args[1] >= 0)); # Decimal value for ramp > 0
- $ramp = $args[1];
- # Special case, if percent=100 adjust the ramp so it matches the actual amount required.
- # Eg. start: 80%. ramp 5seconds. Amount change: 80. Ramp time req: 80/100*5 = 4second.
- if ($percentChange == 100)
- {
- my $difference = ReadingsVal($hash->{NAME}, "brightness", 0);
- $ramp = ($difference/100) * $ramp;
- Log3 ($hash, 5, "$hash->{NAME}_Set: dimdown. Adjusted ramp to $ramp");
- }
- }
-
- my $newBrightness = ReadingsVal($hash->{NAME}, "brightness", 0) - $percentChange;
- $newBrightness = 0 if $newBrightness < 0;
- return MilightDevice_RGBW_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
- return MilightDevice_White_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
- return MilightDevice_RGB_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
- }
- elsif ($cmd eq 'saturation')
- {
- $usage = "Usage: set $name saturation <h(0..100)> [seconds(0..x)] [flags(q=don't clear queue)]";
- return $usage if (($args[0] !~ /^\d+$/) || ($args[0] < 0) || ($args[0] > 100));
- if (defined($args[1]))
- {
- return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
- $ramp = $args[1];
- }
- if (defined($args[2]))
- {
- return $usage if ($args[2] !~ m/.*[qQ].*/); # Flags q=don't clear queue (add to end)
- $flags = $args[2];
- }
- return MilightDevice_HSV_Transition($hash, ReadingsVal($hash->{NAME}, "hue", 0), $args[0], ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), $ramp, $flags);
- }
-
- elsif ($cmd eq 'discoModeUp')
- {
- return MilightDevice_RGBW_DiscoModeStep($hash, 1);
- }
- elsif ($cmd eq 'discoModeDown')
- {
- return MilightDevice_RGBW_DiscoModeStep($hash, 0);
- }
-
- elsif ($cmd eq 'discoSpeedUp')
- {
- return MilightDevice_RGBW_DiscoModeSpeed($hash, 1);
- }
- elsif ($cmd eq 'discoSpeedDown')
- {
- return MilightDevice_RGBW_DiscoModeSpeed($hash, 0);
- }
-
- elsif ($cmd eq 'restorePreviousState')
- {
- # Restore the previous state (as store in previous* readings)
- my ($h, $s, $v) = MilightDevice_HSVFromStr($hash, ReadingsVal($hash->{NAME}, "previousState", MilightDevice_HSVToStr($hash, 0, 0, 0)));
- if($v eq 0)
- {
- if (defined($attr{$name}{defaultRampOff}))
- {
- $ramp = $attr{$name}{defaultRampOff};
- }
- return MilightDevice_RGBW_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
- return MilightDevice_White_DimOff($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White' && AttrVal($hash->{NAME}, "dimOffWhite", 0) == 1);
- return MilightDevice_White_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
- return MilightDevice_RGB_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
- }
- MilightDevice_HSV_Transition($hash, $h, $s, $v, 0, '');
- return undef;
- }
-
- elsif ($cmd eq 'saveState')
- {
- # Save the hsv state as a string
- readingsSingleUpdate($hash, "savedState", MilightDevice_HSVToStr($hash, ReadingsVal($hash->{NAME}, "hue", 0), ReadingsVal($hash->{NAME}, "saturation", 0), ReadingsVal($hash->{NAME}, "brightness", 0)), 1);
- return undef;
- }
- elsif ($cmd eq 'restoreState')
- {
- my ($h, $s, $v) = MilightDevice_HSVFromStr($hash, ReadingsVal($hash->{NAME}, "savedState", MilightDevice_HSVToStr($hash, 0, 0, 0)));
- return MilightDevice_HSV_Transition($hash, $h, $s, $v, 0, '');
- }
- elsif ($cmd eq 'preset')
- {
- my $preset = "+";
- # Default to "preset +" if no args defined
- if (defined($args[0]))
- {
- return "Usage: set $name preset <0..X|+>" if ($args[0] !~ /^(\d+|\+)$/);
- $preset = $args[0];
- }
-
- # Get presets, if not defined default to 1 preset 0,0,100.
- my @presets = split(/ /, AttrVal($hash->{NAME}, "presets", MilightDevice_HSVToStr($hash, 0, 0, 100)));
- # Load the next preset (and loop back to the first) if "+" specified.
- if ("$preset" eq "+")
- {
- $preset = (ReadingsVal($hash->{NAME}, "lastPreset", -1) + 1);
- if ($#presets < $preset) { $preset = 0; }
- }
- return "No preset defined at index $preset" if ($#presets < $preset);
- # Update reading and load preset
- readingsSingleUpdate($hash, "lastPreset", $preset, 1);
- my ($h, $s, $v) = MilightDevice_HSVFromStr($hash, $presets[$preset]);
- return MilightDevice_HSV_Transition($hash, $h, $s, $v, 0, '');
- }
- elsif ($cmd eq 'pair')
- {
- if (defined($args[0]))
- {
- return "Usage: set $name pair [seconds(0..X)(default 3)]" if ($args[0] !~ /^\d+$/);
- $ramp = $args[0];
- }
- else { $ramp = 3; } # Default pair for 3 seconds
- MilightDevice_CmdQueue_Clear($hash);
- return MilightDevice_RGBW_Pair($hash, $ramp) if ($hash->{LEDTYPE} eq 'RGBW');
- return MilightDevice_White_Pair($hash, $ramp) if ($hash->{LEDTYPE} eq 'White');
- return MilightDevice_RGB_Pair($hash, $ramp) if ($hash->{LEDTYPE} eq 'RGB');
- }
- elsif ($cmd eq 'unpair')
- {
- if (defined($args[0]))
- {
- return "Usage: set $name unpair [seconds(0..X)(default 3)]" if ($args[0] !~ /^\d+$/);
- $ramp = $args[0];
- }
- else { $ramp = 3; } # Default unpair for 3 seconds
-
- MilightDevice_CmdQueue_Clear($hash);
- return MilightDevice_RGBW_UnPair($hash, $ramp) if ($hash->{LEDTYPE} eq 'RGBW');
- return MilightDevice_White_UnPair($hash, $ramp) if ($hash->{LEDTYPE} eq 'White');
- return MilightDevice_RGB_UnPair($hash, $ramp) if ($hash->{LEDTYPE} eq 'RGB');
- }
- return SetExtensions($hash, $hash->{helper}->{COMMANDSET}, $name, $cmd, @args);
- }
- #####################################
- # Get functions
- sub MilightDevice_Get(@)
- {
- my ($hash, @args) = @_;
- my $name = $args[0];
- return "$name: get needs at least one parameter" if(@args < 2);
- my $cmd= $args[1];
- if($cmd eq "rgb" || $cmd eq "RGB") {
- return ReadingsVal($name, "rgb", "FFFFFF");
- }
- elsif($cmd eq "hsv") {
- return MilightDevice_HSVToStr($hash, ReadingsVal($hash->{NAME}, "ct", 3000), 0, ReadingsVal($hash->{NAME}, "brightness", 0)) if ($hash->{LEDTYPE} eq 'White');
- return MilightDevice_HSVToStr($hash, ReadingsVal($hash->{NAME}, "hue", 0), ReadingsVal($hash->{NAME}, "saturation", 0), ReadingsVal($hash->{NAME}, "brightness", 0));
- }
-
- return "Unknown argument $cmd, choose one of rgb:noArg hsv:noArg";
- }
- #####################################
- # Attribute functions
- sub MilightDevice_Attr(@)
- {
- my ($cmd, $device, $attribName, $attribVal) = @_;
- my $hash = $defs{$device};
- $attribVal = "" if (!defined($attribVal));
- Log3 ($hash, 4, "$hash->{NAME}_Attr: Cmd: $cmd; Attribute: $attribName; Value: $attribVal");
- if ($cmd eq 'set' && $attribName eq 'gamma')
- {
- return "gamma is required as numerical value with one decimal (eg. 0.5 or 2.2)" if ($attribVal !~ /^\d*\.\d*$/);
- $hash->{helper}->{GAMMAMAP} = MilightDevice_CreateGammaMapping($hash, $attribVal);
- if($attribVal ne "1.0")
- {
- $hash->{helper}->{COMMANDSET} =~ s/dim:slider,0,.*,100/dim:slider,0,1,100/g;
- $hash->{helper}->{COMMANDSET} =~ s/brightness:slider,0,.*,100/brightness:slider,0,1,100/g;
- }
- }
- # Allows you to modify the default number of dimSteps for a device
- elsif ($cmd eq 'set' && $attribName eq 'dimStep')
- {
- return "dimStep is required as numerical value [1..100]" if ($attribVal !~ /^\d*$/) || (($attribVal < 1) || ($attribVal > 100));
- }
- # Allows you to set a default transition time for on/off
- elsif ($cmd eq 'set' && (($attribName eq 'defaultRampOn') || ($attribName eq 'defaultRampOff')))
- {
- return "defaultRampOn/Off is required as numerical value [0..100]" if ($attribVal !~ /^[0-9]*\.?[0-9]*$/) || (($attribVal < 0) || ($attribVal > 100));
- }
- # List of presets in hsv separated by space. Loaded by set command preset X
- elsif ($cmd eq 'set' && ($attribName eq 'presets'))
- {
- return "presets is required as space separated list of hsv(h,s,v) (eg. 0,0,100, 0,100,50)" if ($attribVal !~ /^[(\d{1,3}),(\d{1,3}),(\d{1,3})(?:$|\s)]*$/);
- }
- elsif ($cmd eq 'set' && $attribName eq 'colorCast')
- {
- return "colorCast: only works with RGB(W) devices" if ($hash->{LEDTYPE} eq 'White');
- my @a = split(',', $attribVal);
- my $msg = "colorCast: correction requires red, yellow, green ,cyan, blue, magenta (each in a range of -29 .. 29)";
- return $msg unless (@a == 6);
- foreach my $tc (@a)
- {
- return $msg unless ($tc =~ m/^\s*[\-]{0,1}[0-9]+[\.]{0,1}[0-9]*\s*$/g);
- return $msg if (abs($tc) >= 30);
- }
- $hash->{helper}->{COLORMAP} = MilightDevice_ColorConverter($hash, @a);
- #MilightDevice_RGB_ColorConverter($hash, @a);
- if ($init_done && !(@{$hash->{helper}->{cmdQueue}} > 0))
- {
- my $hue = $hash->{READINGS}->{hue}->{VAL};
- my $sat = $hash->{READINGS}->{saturation}->{VAL};
- my $val = $hash->{READINGS}->{brightness}->{VAL};
- return MilightDevice_RGBW_SetHSV($hash, $hue, $sat, $val, 1) if ($hash->{LEDTYPE} eq 'RGBW');
- return MilightDevice_RGB_SetHSV($hash, $hue, $sat, $val, 1) if ($hash->{LEDTYPE} eq 'RGB');
- }
- }
- elsif ($cmd eq 'set' && $attribName eq 'defaultBrightness')
- {
- return "defaultBrighness: has to be between ".MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash))." and 100" if ($attribVal < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)) || $attribVal > 100);
- }
- return undef;
- }
- #####################################
- # Notify functions
- sub MilightDevice_Notify(@)
- {
- my ($hash,$dev) = @_;
- return MilightDevice_Restore($hash);
- }
- #####################################
- # Restore HSV settings from readings.
- # Called after initialization to synchronise lamp state with fhem.
- sub MilightDevice_Restore(@)
- {
- my ($hash) = @_;
- return if ($hash->{INIT});
- if ($init_done)
- {
- return if (AttrVal($hash->{NAME}, "restoreAtStart", 0) == 0);
- Log3 ($hash, 4, "$hash->{NAME}_Restore: Restoring saved HSV values");
- $hash->{INIT} = 1;
- # Initialize device
- MilightDevice_Init($hash);
- # Clear inProgress flag: MJW Do we still need to do this?
- readingsSingleUpdate($hash, "transitionInProgress", 0, 1);
- # Default to OFF if not defined
- my ($hue, $sat, $val);
- $hue = ReadingsVal($hash->{NAME}, "hue", 0);
- $sat = ReadingsVal($hash->{NAME}, "saturation", 0);
- $val = ReadingsVal($hash->{NAME}, "brightness", 0);
- # Restore state
- return MilightDevice_RGBW_SetHSV($hash, $hue, $sat, $val, 1) if ($hash->{LEDTYPE} eq 'RGBW');
- return MilightDevice_White_SetHSV($hash, $hue, $sat, $val, 1) if ($hash->{LEDTYPE} eq 'White');
- return MilightDevice_RGB_SetHSV($hash, $hue, $sat, $val, 1) if ($hash->{LEDTYPE} eq 'RGB');
- }
- }
- ###############################################################################
- # device specific controller functions RGB
- # LED Strip or bulb, no white, controller V2+. No longer manufactured Jan2014
- ###############################################################################
- sub MilightDevice_RGB_Pair(@)
- {
- my ($hash, $numSeconds) = @_;
- $numSeconds = 3 if (($numSeconds || 0) == 0);
- Log3 ($hash, 4, "$hash->{NAME}_RGB_Pair: RGB LED slot $hash->{SLOT} pair $numSeconds s");
- # DISCO SPEED FASTER 0x25 (SYNC/PAIR RGB Bulb within 2 seconds of Wall Switch Power being turned ON)
- my $ctrl = "\x25\x00\x55";
- for (my $i = 0; $i < $numSeconds; $i++)
- {
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 1000, undef);
- }
- return undef;
- }
- #####################################
- sub MilightDevice_RGB_UnPair(@)
- {
- my ($hash) = @_;
- my $numSeconds = 8;
- Log3 ($hash, 4, "$hash->{NAME}_RGB_UnPair: RGB LED slot $hash->{SLOT} unpair $numSeconds s");
- # DISCO SPEED FASTER 0x25 (SYNC/PAIR RGB Bulb within 2 seconds of Wall Switch Power being turned ON)
- my $ctrl = "\x25\x00\x55";
- for (my $i = 0; $i < $numSeconds; $i++)
- {
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- }
- return undef;
- }
- #####################################
- sub MilightDevice_RGB_On(@)
- {
- my ($hash, $ramp, $flags) = @_;
- my $name = $hash->{NAME};
- my $v = AttrVal($hash->{NAME}, "defaultBrightness", 36);
- Log3 ($hash, 4, "$hash->{NAME}_RGB_On: RGB slot $hash->{SLOT} set on $ramp");
- # Switch on with same brightness it was switched off with, or max if undefined.
- if (ReadingsVal($hash->{NAME}, "state", "off") eq "off")
- {
- $v = ReadingsVal($hash->{NAME}, "brightness_on", AttrVal($hash->{NAME}, "defaultBrightness", 36));
- }
- else
- {
- $v = ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36));
- }
- # When turning on, make sure we request at least minimum dim step.
- if ($v < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)))
- {
- $v = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash));
- }
- return MilightDevice_RGB_Dim($hash, $v, $ramp, $flags);
- }
- #####################################
- sub MilightDevice_RGB_Off(@)
- {
- my ($hash, $ramp, $flags) = @_;
- my $name = $hash->{NAME};
- Log3 ($hash, 4, "$hash->{NAME}_RGB_Off: RGB slot $hash->{SLOT} set off $ramp");
- # Store value of brightness before turning off
- # "on" will be of the form "on 50" where 50 is current dimlevel
- if (ReadingsVal($hash->{NAME}, "state", "off") ne "off")
- {
- readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), 1);
- MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
- # Dim down to min brightness then send off command (avoid flicker on turn on)
- MilightDevice_RGB_Dim($hash, MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)), $ramp, $flags);
- return MilightDevice_RGB_Dim($hash, 0, 0, 'qP');
- }
- else
- {
- # If we are already off just send the off command again
- return MilightDevice_RGB_Dim($hash, 0, 0, 'P');
- }
- }
- #####################################
- sub MilightDevice_RGB_Dim(@)
- {
- my ($hash, $level, $ramp, $flags) = @_;
- my $h = ReadingsVal($hash->{NAME}, "hue", 0);
- my $s = ReadingsVal($hash->{NAME}, "saturation", 0);
- Log3 ($hash, 4, "$hash->{NAME}_RGB_Dim: RGB slot $hash->{SLOT} dim $level $ramp $flags");
- return MilightDevice_HSV_Transition($hash, $h, $s, $level, $ramp, $flags);
- }
- #####################################
- sub MilightDevice_RGB_SetHSV(@)
- {
- my ($hash, $hue, $sat, $val, $repeat) = @_;
- Log3 ($hash, 4, "$hash->{NAME}_RGB_setHSV: RGB slot $hash->{SLOT} set h:$hue, s:$sat, v:$val");
- $sat = 100;
- MilightDevice_SetHSV_Readings($hash, $hue, $sat, $val);
- # apply gamma correction
- my $gammaVal = $hash->{helper}->{GAMMAMAP}[$val];
- # convert to device specs
- my ($cv, $cl, $wl) = MilightDevice_RGB_ColorConverter($hash, $hue, $sat, $gammaVal);
- Log3 ($hash, 4, "$hash->{NAME}_RGB_setHSV: RGB slot $hash->{SLOT} set levels: $cv, $cl, $wl");
-
- $repeat = 1 if (!defined($repeat));
-
- # On first load, colorLevel won't be defined, define it.
- $hash->{helper}->{colorLevel} = $cl if (!defined($hash->{helper}->{colorLevel}));
- # NOTE: All commands sent twice for reliability (it's udp with no feedback)
-
- if (($wl < 1) && ($cl < 1)) # off
- {
- # if no white or colour switch off
- IOWrite($hash, "\x21\x00\x55"); # switch off
- $hash->{helper}->{colorLevel} = 0;
- }
- else # on
- {
- if (($wl > 0) || ($cl > 0)) # Colour/White on
- {
- IOWrite($hash, "\x22\x00\x55"); # switch on
- IOWrite($hash, "\x20".chr($cv)."\x55"); # set color
- if ($repeat eq 1) {
- IOWrite($hash, "\x22\x00\x55"); # switch on
- IOWrite($hash, "\x20".chr($cv)."\x55"); # set color
- }
-
- # cl decrease
- if ($hash->{helper}->{colorLevel} > $cl)
- {
- for (my $i=$hash->{helper}->{colorLevel}; $i > $cl; $i--)
- {
- IOWrite($hash, "\x24\x00\x55"); # brightness down
- $hash->{helper}->{colorLevel} = $i - 1;
- }
- }
- # cl increase
- if ($hash->{helper}->{colorLevel} < $cl)
- {
- for (my $i=$hash->{helper}->{colorLevel}; $i < $cl; $i++)
- {
- IOWrite($hash, "\x23\x00\x55"); # brightness up
- $hash->{helper}->{colorLevel} = $i + 1;
- }
- }
- }
- }
- return undef;
- }
- #####################################
- sub MilightDevice_RGB_ColorConverter(@)
- {
- my ($hash, $h, $s, $v) = @_;
- my $color = $hash->{helper}->{COLORMAP}[$h % 360];
-
- # there are 0..9 dim level, setup correction
- my $valueSpread = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash));
- my $totalVal = MilightDevice_roundfunc($v / $valueSpread);
- # saturation 100..50: color full, white increase. 50..0 white full, color decrease
- my $colorVal = ($s >= 50) ? $totalVal : int(($s / 50 * $totalVal) +0.5);
- my $whiteVal = ($s >= 50) ? int(((100-$s) / 50 * $totalVal) +0.5) : $totalVal;
- return ($color, $colorVal, $whiteVal);
- }
- ###############################################################################
- # RGBW device specific: Bridge V3+ only.
- # Available as GU10, E14, E27, B22, led strip controller...
- ###############################################################################
- sub MilightDevice_RGBW_Pair(@)
- {
- my ($hash, $numSeconds) = @_;
- $numSeconds = 3 if (($numSeconds || 0) == 0);
- Log3 ($hash, 4, "$hash->{NAME}_RGBW_Pair: $hash->{LEDTYPE} at $hash->{CONNECTION}, slot $hash->{SLOT}: pair $numSeconds");
- # find my slot and get my group-all-on cmd
- my $ctrl = @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd;
- # Send on command once a second
- for (my $i = 0; $i < $numSeconds; $i++)
- {
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 1000, undef);
- }
- return undef;
- }
- #####################################
- sub MilightDevice_RGBW_UnPair(@)
- {
- my ($hash, $numSeconds, $releaseFromSlot) = @_;
- $numSeconds = 3 if (($numSeconds || 0) == 0);
- Log3 ($hash, 4, "$hash->{NAME}_RGBW_UnPair: $hash->{LEDTYPE} at $hash->{CONNECTION}, slot $hash->{SLOT}: unpair $numSeconds");
- # find my slot and get my group-all-on cmd
- my $ctrl = @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd;
- # Send on command every 200ms
- for (my $i = 0; $i < $numSeconds; $i++)
- {
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- }
- return undef;
- }
- #####################################
- sub MilightDevice_RGBW_On(@)
- {
- my ($hash, $ramp, $flags) = @_;
- my $name = $hash->{NAME};
- my $v = AttrVal($hash->{NAME}, "defaultBrightness", 36);
- Log3 ($hash, 4, "$hash->{NAME}_RGBW_On: Set ON; Ramp: $ramp");
- # Switch on with same brightness it was switched off with, or max if undefined.
- if (ReadingsVal($hash->{NAME}, "state", "off") eq "off" || ReadingsVal($hash->{NAME}, "state", "off") eq "night")
- {
- $v = ReadingsVal($hash->{NAME}, "brightness_on", AttrVal($hash->{NAME}, "defaultBrightness", 36));
- }
- else
- {
- $v = ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36));
- }
- # When turning on, make sure we request at least minimum dim step.
- if ($v < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)))
- {
- $v = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash));
- }
- return MilightDevice_RGBW_Dim($hash, $v, $ramp, $flags);
- }
- #####################################
- sub MilightDevice_RGBW_Off(@)
- {
- my ($hash, $ramp, $flags) = @_;
- my $name = $hash->{NAME};
- Log3 ($hash, 4, "$hash->{NAME}_RGBW_Off: Set OFF; Ramp: $ramp");
- # Store value of brightness before turning off
- # "on" will be of the form "on 50" where 50 is current dimlevel
- if (ReadingsVal($hash->{NAME}, "state", "off") ne "off" && ReadingsVal($hash->{NAME}, "state", "off") ne "night")
- {
- readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", 0), 1);
- MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
-
- # Dim down to min brightness then send off command (avoid flicker on turn on)
- MilightDevice_RGBW_Dim($hash, MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)), $ramp, $flags);
- return MilightDevice_RGBW_Dim($hash, 0, 0, 'qP');
- }
- else
- {
- # If we are already off just send the off command again
- return MilightDevice_RGBW_Dim($hash, 0, 0, 'P');
- }
- }
- #####################################
- sub MilightDevice_RGBW_Night(@)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- Log3 ($hash, 4, "$hash->{NAME}_RGBW_Night: Set NIGHTMODE");
- if(ReadingsVal($hash->{NAME}, "state", "off") ne "night") {
- if (ReadingsVal($hash->{NAME}, "brightness", 0) > 0)
- {
- readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", 4), 1);
- MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
- }
- IOWrite($hash, @RGBWCmdsOff[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd); # off
- }
- IOWrite($hash, @RGBWCmdsNt[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd); # night
- readingsSingleUpdate($hash, "state", "night", 1);
- MilightDevice_BridgeDevices_Update($hash, "state") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
- return undef;
- }
- #####################################
- sub MilightDevice_RGBW_Dim(@)
- {
- my ($hash, $v, $ramp, $flags) = @_;
- my $h = ReadingsVal($hash->{NAME}, "hue", 0);
- my $s = ReadingsVal($hash->{NAME}, "saturation", 0);
- Log3 ($hash, 4, "$hash->{NAME}_RGBW_Dim: Brightness: $v; Ramp: $ramp; Flags: ". $flags || '');
- return MilightDevice_HSV_Transition($hash, $h, $s, $v, $ramp, $flags);
- }
- #####################################
- sub MilightDevice_RGBW_SetHSV(@)
- {
- my ($hash, $hue, $sat, $val, $repeat) = @_;
- my ($cl, $wl);
-
- $repeat = 1 if (!defined($repeat));
- my $cv = $hash->{helper}->{COLORMAP}[$hue % 360];
- #check dim levels to decide wether to change color or brightness first
- my $dimup = 0;
- $dimup = 1 if($val > ReadingsVal($hash->{NAME}, "brightness", 100));
- # apply gamma correction
- my $gammaVal = $hash->{helper}->{GAMMAMAP}[$val];
- # brightness 2..27 (x02..x1b) | 25 dim levels
-
- my $cf = MilightDevice_roundfunc((($gammaVal / 100) * MilightDevice_DimSteps($hash)) + 1);
- if ($sat < 20)
- {
- $wl = $cf;
- $cl = 0;
- $sat = 0;
- }
- else
- {
- $cl = $cf;
- $wl = 0;
- $sat = 100;
- }
-
- Log3 ($hash, 5, "MilightDevice_RGBW_SetHSV: h:$hue s:$sat v:$val / cv:$cv wl:$wl cl:$cl ");
- # Set readings in FHEM
- MilightDevice_SetHSV_Readings($hash, $hue, $sat, $val);
- # NOTE: All commands sent twice for reliability (it's udp with no feedback)
- # Off is shifted to "2" above so check for < 2
- if (($wl < 2) && ($cl < 2)) # off
- {
- IOWrite($hash, @RGBWCmdsOff[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd); # group off
- IOWrite($hash, @RGBWCmdsOff[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if ($repeat eq 1); # group off
- $hash->{helper}->{whiteLevel} = 0;
- $hash->{helper}->{colorLevel} = 0;
- }
- else # on
- {
- if ($wl > 0) # white
- {
- IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($wl > 0) || ($cl > 0)); # group on
- IOWrite($hash, @RGBWCmdsWT[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd); # white
- IOWrite($hash, $RGBWCmdBri.chr($wl).$RGBWCmdEnd); # brightness
- if ($repeat eq 1) {
- IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($wl > 0) || ($cl > 0)); # group on
- IOWrite($hash, @RGBWCmdsWT[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd); # white
- IOWrite($hash, $RGBWCmdBri.chr($wl).$RGBWCmdEnd); # brightness
- }
- }
- elsif ($cl > 0) # color
- {
- IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($wl > 0) || ($cl > 0)); # group on
- if($dimup)
- {
- IOWrite($hash, $RGBWCmdCol.chr($cv).$RGBWCmdEnd); # color
- IOWrite($hash, $RGBWCmdBri.chr($cl).$RGBWCmdEnd); # brightness
- } else {
- IOWrite($hash, $RGBWCmdBri.chr($cl).$RGBWCmdEnd); # brightness
- IOWrite($hash, $RGBWCmdCol.chr($cv).$RGBWCmdEnd); # color
- }
- if ($repeat eq 1) {
- IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($wl > 0) || ($cl > 0)); # group on
- if($dimup)
- {
- IOWrite($hash, $RGBWCmdCol.chr($cv).$RGBWCmdEnd); # color
- IOWrite($hash, $RGBWCmdBri.chr($cl).$RGBWCmdEnd); # brightness
- } else {
- IOWrite($hash, $RGBWCmdBri.chr($cl).$RGBWCmdEnd); # brightness
- IOWrite($hash, $RGBWCmdCol.chr($cv).$RGBWCmdEnd); # color
- }
- }
- }
- $hash->{helper}->{colorValue} = $cv;
- $hash->{helper}->{colorLevel} = $cl;
- $hash->{helper}->{whiteLevel} = $wl;
- }
-
- return undef;
- }
- ####################################
- # RGB and RGBW types
- sub MilightDevice_RGBW_DiscoModeStep(@)
- {
- my ($hash, $step) = @_;
-
- MilightDevice_CmdQueue_Clear($hash);
-
- $step = 0 if ($step < 0);
- $step = 1 if ($step > 1);
-
- # Set readings in FHEM
- MilightDevice_SetDisco_Readings($hash, $step, ReadingsVal($hash->{NAME}, 'discoSpeed', 5));
- # NOTE: Only sending commands once, because it makes changes on each successive command
- IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($hash->{LEDTYPE} eq 'RGBW')); # group on
- IOWrite($hash, "\x22\x00\x55") if (($hash->{LEDTYPE} eq 'RGB')); # switch on
- if ($step == 1)
- {
- IOWrite($hash, $RGBWCmdDiscoUp."\x00".$RGBWCmdEnd) if (($hash->{LEDTYPE} eq 'RGBW')); # discoMode step up
- IOWrite($hash, "\x27\x00\x55") if (($hash->{LEDTYPE} eq 'RGB')); # discoMode step up
- }
- elsif ($step == 0)
- {
- IOWrite($hash, "\x28\x00\x55") if (($hash->{LEDTYPE} eq 'RGB')); # discoMode step down
- # There is no discoMode step down for RGBW
- }
-
- return undef;
- }
- #####################################
- # RGB and RGBW types
- sub MilightDevice_RGBW_DiscoModeSpeed(@)
- {
- my ($hash, $speed) = @_;
- MilightDevice_CmdQueue_Clear($hash);
-
- $speed = 0 if ($speed < 0);
- $speed = 1 if ($speed > 1);
-
- # Set readings in FHEM
- MilightDevice_SetDisco_Readings($hash, ReadingsVal($hash->{NAME}, 'discoMode', 1), $speed);
- # NOTE: Only sending commands once, because it makes changes on each successive command
- IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($hash->{LEDTYPE} eq 'RGBW')); # group on
- IOWrite($hash, "\x22\x00\x55") if (($hash->{LEDTYPE} eq 'RGB')); # switch on
- if ($speed == 1)
- {
- IOWrite($hash, $RGBWCmdDiscoInc."\x00".$RGBWCmdEnd) if ($hash->{LEDTYPE} eq 'RGBW'); # discoMode speed up
- IOWrite($hash, "\x25\x00\x55") if ($hash->{LEDTYPE} eq 'RGB'); # discoMode speed up
- }
- elsif ($speed == 0)
- {
- IOWrite($hash, $RGBWCmdDiscoDec."\x00".$RGBWCmdEnd) if ($hash->{LEDTYPE} eq 'RGBW'); # discoMode speed down
- IOWrite($hash, "\x26\x00\x55") if ($hash->{LEDTYPE} eq 'RGB'); # discoMode speed down
- }
-
- return undef;
- }
- ###############################################################################
- # White device specific: Warm/Cold White with Dim - Bridge V2+
- ###############################################################################
- sub MilightDevice_White_Pair(@)
- {
- my ($hash, $numSeconds) = @_;
- $numSeconds = 3 if (($numSeconds || 0) == 0);
- Log3 ($hash, 4, "$hash->{NAME}_White_Pair: $hash->{LEDTYPE} at $hash->{CONNECTION}, slot $hash->{SLOT}: pair $numSeconds");
- # find my slot and get my group-all-on cmd
- my $ctrl = @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd;
-
- # Send on command once a second
- for (my $i = 0; $i < $numSeconds; $i++)
- {
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 1000, undef);
- }
- return undef;
- }
- #####################################
- sub MilightDevice_White_UnPair(@)
- {
- my ($hash, $numSeconds, $releaseFromSlot) = @_;
- $numSeconds = 3 if (($numSeconds || 0) == 0);
-
- Log3 ($hash, 4, "$hash->{NAME}_White_UnPair: $hash->{LEDTYPE} at $hash->{CONNECTION}, slot $hash->{SLOT}: unpair $numSeconds");
- # find my slot and get my group-all-on cmd
- my $ctrl = @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd;
-
- for (my $i = 0; $i < $numSeconds; $i++)
- {
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- }
- return undef;
- }
- #####################################
- sub MilightDevice_White_On(@)
- {
- my ($hash, $ramp, $flags) = @_;
- my $name = $hash->{NAME};
- my $v = AttrVal($hash->{NAME}, "defaultBrightness", 36);
- Log3 ($hash, 4, "$hash->{NAME}_White_On: Set ON: Ramp: $ramp");
- # Switch on with same brightness it was switched off with, or max if undefined.
- if (ReadingsVal($hash->{NAME}, "state", "off") eq "off" || ReadingsVal($hash->{NAME}, "state", "off") eq "night")
- {
- $v = ReadingsVal($hash->{NAME}, "brightness_on", AttrVal($hash->{NAME}, "defaultBrightness", 36));
- }
- else
- {
- $v = ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36));
- }
- # When turning on, make sure we request at least minimum dim step.
- if ($v < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)))
- {
- $v = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash));
- }
- return MilightDevice_White_Dim($hash, $v, $ramp, $flags);
- }
- #####################################
- sub MilightDevice_White_Off(@)
- {
- my ($hash, $ramp, $flags) = @_;
- my $name = $hash->{NAME};
- Log3 ($hash, 4, "$hash->{NAME}_White_Off: Set OFF; Ramp: $ramp");
- # Store value of brightness before turning off
- # "on" will be of the form "on 50" where 50 is current dimlevel
- if (ReadingsVal($hash->{NAME}, "state", "off") ne "off" && ReadingsVal($hash->{NAME}, "state", "off") ne "night")
- {
- if (ReadingsVal($hash->{NAME}, "brightness", 0) > 0)
- {
- readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), 1);
- MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
- }
- # Dim down to min brightness then send off command (avoid flicker on turn on)
- MilightDevice_White_Dim($hash, MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)), $ramp, $flags);
- return MilightDevice_White_Dim($hash, 0, 0, 'q');
- }
- else
- {
- # If we are already off just send the off command again
- return MilightDevice_White_Dim($hash, 0, 0, 'P');
- }
- }
- #####################################
- sub MilightDevice_White_DimOff(@)
- {
- my ($hash, $ramp, $flags) = @_;
- my $name = $hash->{NAME};
- Log3 ($hash, 4, "$hash->{NAME}_White_DimOff: Set OFF; Ramp: $ramp");
- if (ReadingsVal($hash->{NAME}, "brightness", 0) > 0)
- {
- readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), 1);
- MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
- }
- for (my $i = 0; $i < 12; $i++)
- {
- IOWrite($hash, @WhiteCmdBriDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- }
- IOWrite($hash, @WhiteCmdsOff[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- return MilightDevice_White_Dim($hash, 0, 0, 'q');
- }
- #####################################
- sub MilightDevice_White_DimOn(@)
- {
- my ($hash, $ramp, $flags) = @_;
- my $name = $hash->{NAME};
- Log3 ($hash, 4, "$hash->{NAME}_White_DimOn: Set ON; Ramp: $ramp");
- my $v = AttrVal($hash->{NAME}, "defaultBrightness", 36);
- if (ReadingsVal($hash->{NAME}, "state", "off") eq "off" || ReadingsVal($hash->{NAME}, "state", "off") eq "night")
- {
- $v = ReadingsVal($hash->{NAME}, "brightness_on", AttrVal($hash->{NAME}, "defaultBrightness", 36));
- }
- else
- {
- $v = ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36));
- }
- # When turning on, make sure we request at least minimum dim step.
- if ($v < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)))
- {
- $v = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash));
- }
- MilightDevice_White_Dim($hash, $v, $ramp, $flags);
- for (my $i = 0; $i < ($v/(100/MilightDevice_DimSteps($hash))); $i++)
- {
- #IOWrite($hash, @WhiteCmdBriUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- }
- #$ctrl = @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd;
- #MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
- #return MilightDevice_White_Dim($hash, 0, 0, 'q');
- }
- #####################################
- sub MilightDevice_White_Night(@)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- Log3 ($hash, 4, "$hash->{NAME}_White_NIGHT: Set NIGHTMODE");
- if(ReadingsVal($hash->{NAME}, "state", "off") ne "night")
- {
- if (ReadingsVal($hash->{NAME}, "brightness", 0) > 0)
- {
- readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), 1);
- MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
- }
- IOWrite($hash, @WhiteCmdsOff[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # off
- }
- IOWrite($hash, @WhiteCmdsNt[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # night
- readingsSingleUpdate($hash, "state", "night", 1);
- MilightDevice_BridgeDevices_Update($hash, "state") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
- return undef;
- }
- #####################################
- sub MilightDevice_White_Dim(@)
- {
- my ($hash, $level, $ramp, $flags) = @_;
- Log3 ($hash, 4, "$hash->{NAME}_White_Dim: Brightness: $level; Ramp: $ramp; Flags: $flags");
- return MilightDevice_HSV_Transition($hash, ReadingsVal($hash->{NAME}, "ct", 3000), 0, $level, $ramp, $flags);
- }
- #####################################
- # $hue is colourTemperature, $val is brightness
- sub MilightDevice_White_SetHSV(@)
- {
- my ($hash, $hue, $sat, $val, $repeat) = @_;
- my $name = $hash->{NAME};
-
- $repeat = 1 if (!defined($repeat));
-
- # Validate brightness
- $val = 100 if ($val > 100);
- $val = 0 if ($val < 0);
- # Validate colour temperature
- $hue = 6500 if ($hue > 6500);
- $hue = 3000 if ($hue < 3000);
- my $oldHueStep = MilightDevice_White_ct_hwValue($hash, ReadingsVal($hash->{NAME}, "ct", 6500));
- my $newHueStep = MilightDevice_White_ct_hwValue($hash, $hue);
- $hue = MilightDevice_White_ct_hwValue($hash, $newHueStep);
- # Set colour temperature
- if ($oldHueStep != $newHueStep)
- {
- IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on
- if ($oldHueStep > $newHueStep)
- {
- Log3 ($hash, 4, "$hash->{NAME}_setColourTemp: Decrease from $oldHueStep to $newHueStep");
- for (my $i=$oldHueStep; $i > $newHueStep; $i--)
- {
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # Cooler (colourtemp up)
- }
- }
- elsif ($oldHueStep < $newHueStep)
- {
- Log3 ($hash, 4, "$hash->{NAME}_setColourTemp: Increase from $oldHueStep to $newHueStep");
- for (my $i=$oldHueStep; $i < $newHueStep; $i++)
- {
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # Warmer (colourtemp down)
- }
- }
- if(AttrVal($hash->{NAME}, "dimOffWhite", 0) == 1)
- {
- if($newHueStep == 1)
- {
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- }
- elsif($newHueStep == 11)
- {
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- }
- }
- }
- # apply gamma correction
- my $gammaVal = $hash->{helper}->{GAMMAMAP}[$val];
-
- # Calculate brightness hardware value (10 steps / 11 positions for white)
- my $maxWl = (100 / MilightDevice_DimSteps($hash));
- my $wl = MilightDevice_roundfunc($gammaVal / $maxWl);
- # On first load, whiteLevel won't be defined, define it.
- $hash->{helper}->{whiteLevel} = $wl if (!defined($hash->{helper}->{whiteLevel}));
- if (ReadingsVal($hash, "brightness", 0) > 0)
- {
- # We are transitioning from on to off so store new value of wl and stop brightness up/down being triggered below
- $hash->{helper}->{whiteLevel} = $wl;
- }
- # Store new values for colourTemperature and Brightness
- MilightDevice_SetHSV_Readings($hash, $hue, 0, $val);
- # Make sure we actually send off command if we should be off
- if ($wl == 0)
- {
- IOWrite($hash, @WhiteCmdsOff[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group off
- IOWrite($hash, @WhiteCmdsOff[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd) if ($repeat eq 1); # group off
- Log3 ($hash, 4, "$hash->{NAME}_White_setHSV: OFF");
- }
- elsif ($wl == $maxWl)
- {
- IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on
- IOWrite($hash, @WhiteCmdsOnFull[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on full
- if ($repeat eq 1) {
- IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on
- IOWrite($hash, @WhiteCmdsOnFull[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on full
- }
- Log3 ($hash, 4, "$hash->{NAME}_White_setHSV: Full Brightness");
- }
- else
- {
- # Not off or MAX brightness, so make sure we are on
- IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on
- IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd) if ($repeat eq 1); # group on
- if ($hash->{helper}->{whiteLevel} > $wl)
- {
- # Brightness level should be decreased
- Log3 ($hash, 4, "$hash->{NAME}_White_setHSV: Brightness decrease from $hash->{helper}->{whiteLevel} to $wl");
- for (my $i=$hash->{helper}->{whiteLevel}; $i > $wl; $i--)
- {
- IOWrite($hash, @WhiteCmdBriDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # brightness down
- $hash->{helper}->{whiteLevel} = $i - 1;
- }
- }
- elsif ($hash->{helper}->{whiteLevel} < $wl)
- {
- # Brightness level should be increased
- $hash->{helper}->{whiteLevel} = 1 if ($hash->{helper}->{whiteLevel} == 0);
- Log3 ($hash, 4, "$hash->{NAME}_White_setHSV: Brightness increase from $hash->{helper}->{whiteLevel} to $wl");
- for (my $i=$hash->{helper}->{whiteLevel}; $i < $wl; $i++)
- {
- IOWrite($hash, @WhiteCmdBriUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # brightness up
- $hash->{helper}->{whiteLevel} = $i + 1;
- }
- }
- else
- {
- Log3 ($hash, 4, "$hash->{NAME}_White_setHSV: ON");
- }
- }
- $hash->{helper}->{whiteLevel} = $wl;
-
- return undef;
- }
- #####################################
- sub MilightDevice_White_SetColourTemp(@)
- {
- # $hue is colourTemperature (1-11), $val is brightness (0-100%)
- my ($hash, $hue) = @_;
- my $name = $hash->{NAME};
-
- MilightDevice_CmdQueue_Clear($hash);
- # Save old value of ct
- my $oldHue = MilightDevice_White_ct_hwValue($hash, ReadingsVal($hash->{NAME}, "ct", 6500));
- # Store new values for colourTemperature and Brightness
- MilightDevice_SetHSV_Readings($hash, $hue, 0, ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36) ) );
- # Validate colourTemperature (11 steps)
- # 3000-6500 (350 per step) Warm-White to Cool White
- # Maps backwards 1=6500 11=3000
- $hue = MilightDevice_White_ct_hwValue($hash, $hue);
-
- Log3 ($hash, 4, "$hash->{NAME}_setColourTemp: $oldHue to $hue");
-
- # Set colour temperature
- if ($oldHue != $hue)
- {
- IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on
- if ($oldHue > $hue)
- {
- Log3 ($hash, 4, "$hash->{NAME}_setColourTemp: Decrease from $oldHue to $hue");
- for (my $i=$oldHue; $i > $hue; $i--)
- {
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # Cooler (colourtemp up)
- }
- }
- elsif ($oldHue < $hue)
- {
- Log3 ($hash, 4, "$hash->{NAME}_setColourTemp: Increase from $oldHue to $hue");
- for (my $i=$oldHue; $i < $hue; $i++)
- {
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # Warmer (colourtemp down)
- }
- }
- }
- if(AttrVal($hash->{NAME}, "dimOffWhite", 0) == 1)
- {
- if($hue == 1)
- {
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- }
- elsif($hue == 11)
- {
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
- }
- }
- return undef;
- }
- # Convert from 3000-6500 colourtemperature to hardware value
- sub MilightDevice_White_ct_hwValue(@)
- {
- my ($hash, $ct) = @_;
-
- # Couldn't get switch statement to work so using if
- if ($ct == 11) { return 3000; }
- elsif ($ct == 10) { return 3350; }
- elsif ($ct == 9) { return 3700; }
- elsif ($ct == 8) { return 4050; }
- elsif ($ct == 7) { return 4400; }
- elsif ($ct == 6) { return 4750; }
- elsif ($ct == 5) { return 5100; }
- elsif ($ct == 4) { return 5450; }
- elsif ($ct == 3) { return 5800; }
- elsif ($ct == 2) { return 6150; }
- elsif ($ct == 1) { return 6500; }
-
- elsif ($ct < 3350) { return 11; }
- elsif ($ct < 3700) { return 10; }
- elsif ($ct < 4050) { return 9; }
- elsif ($ct < 4400) { return 8; }
- elsif ($ct < 4750) { return 7; }
- elsif ($ct < 5100) { return 6; }
- elsif ($ct < 5450) { return 5; }
- elsif ($ct < 5800) { return 4; }
- elsif ($ct < 6150) { return 3; }
- elsif ($ct < 6500) { return 2; }
- return 1;
- }
- ###############################################################################
- # Device independent routines
- ###############################################################################
- sub MilightDevice_HSVFromStr(@)
- {
- # Convert HSV values from string in format "h,s,v"
- my ($hash, @args) = @_;
-
- if ((!defined($args[0])) || ($args[0] !~ /^(\d{1,4}),(\d{1,3}),(\d{1,3})$/))
- {
- Log3 ($hash, 3, "MilightDevice_HSVFromStr: Could not parse h,s,v values from $args[0]");
- return (0, 0, 0);
- }
- Log3 ($hash, 5, "MilightDevice_HSVFromStr: Parsed hsv string: h:$1,s:$2,v:$3");
- return ($1, $2, $3);
- }
- #####################################
- sub MilightDevice_HSVToStr(@)
- {
- # Convert HSV values to string in format "h,s,v"
- my ($hash, $h, $s, $v) = @_;
-
- $h=0 if (!defined($h));
- $s=0 if (!defined($s));
- $v=0 if (!defined($v));
-
- Log3 ($hash, 5, "MilightDevice_HSVToStr: h:$h,s:$s,v:$v");
- return "$h,$s,$v";
- }
- #####################################
- sub MilightDevice_ValidateHSV(@)
- {
- # Validate and return valid values for HSV
- my ($hash, $h, $s, $v) = @_;
- $h = 0 if ($h < 0);
- $h = 360 if ($h > 360 && $hash->{LEDTYPE} ne 'White');
- $h = 3000 if ($h < 3000 && $hash->{LEDTYPE} eq 'White');
- $h = 6500 if ($h > 6500);
- $s = 0 if ($s < 0);
- $s = 100 if ($s > 100);
- $v = 0 if ($v < 0);
- $v = 100 if ($v > 100);
-
- return ($h, $s, $v);
- }
- #####################################
- # Return number of steps for each type of bulb
- # White: 11 steps (step = 9.1)
- # RGB: 9 steps (step = 11)
- # RGBW: 25 steps (step = 4)
- sub MilightDevice_DimSteps(@)
- {
- my ($hash) = @_;
- return AttrVal($hash->{NAME}, "dimStep", 25) if ($hash->{LEDTYPE} eq 'RGBW');
- return AttrVal($hash->{NAME}, "dimStep", 11) if ($hash->{LEDTYPE} eq 'White');
- return AttrVal($hash->{NAME}, "dimStep", 9) if ($hash->{LEDTYPE} eq 'RGB');
- }
- #####################################
- # Return number of colour steps for each type of bulb
- # White: 11 steps (this is colour temperature)
- # RGB: 255 steps (not mentioned in API?)
- # RGBW: 255 steps
- sub MilightDevice_ColourSteps(@)
- {
- my ($hash) = @_;
- return 255 if ($hash->{LEDTYPE} eq 'RGBW');
- return 11 if ($hash->{LEDTYPE} eq 'White');
- return 255 if ($hash->{LEDTYPE} eq 'RGB');
- }
- #####################################
- # dispatcher
- sub MilightDevice_SetHSV(@)
- {
- my ($hash, $hue, $sat, $val, $repeat) = @_;
- MilightDevice_RGBW_SetHSV($hash, $hue, $sat, $val, $repeat) if ($hash->{LEDTYPE} eq 'RGBW');
- MilightDevice_White_SetHSV($hash, $hue, $sat, $val, $repeat) if ($hash->{LEDTYPE} eq 'White');
- MilightDevice_RGB_SetHSV($hash, $hue, $sat, $val, $repeat) if ($hash->{LEDTYPE} eq 'RGB');
- return undef;
- }
- #####################################
- sub MilightDevice_HSV_Transition(@)
- {
- my ($hash, $hue, $sat, $val, $ramp, $flags) = @_;
- my ($hueFrom, $satFrom, $valFrom, $timeFrom)=0;
-
- # Clear command queue if flag "q" not specified
- MilightDevice_CmdQueue_Clear($hash) if ($flags !~ m/.*[qQ].*/);
-
- # if queue in progress set start vals to last cached hsv target, else set start to actual hsv
- if (@{$hash->{helper}->{cmdQueue}} > 0)
- {
- $hueFrom = $hash->{helper}->{targetHue};
- $satFrom = $hash->{helper}->{targetSat};
- $valFrom = $hash->{helper}->{targetVal};
- $timeFrom = $hash->{helper}->{targetTime};
- $hueFrom = 0 if(!defined($hueFrom));
- $satFrom = 100 if(!defined($satFrom));
- $valFrom = 0 if(!defined($valFrom));
- $timeFrom = 0 if(!defined($timeFrom));
- Log3 ($hash, 5, "$hash->{NAME}_HSV_Transition: Prepare Start (cached): $hueFrom,$satFrom,$valFrom@".$timeFrom);
- }
- else
- {
- $hueFrom = ReadingsVal($hash->{NAME}, "hue", 0);
- $satFrom = ReadingsVal($hash->{NAME}, "saturation", 0);
- $valFrom = ReadingsVal($hash->{NAME}, "brightness", 0);
- $timeFrom = gettimeofday();
- Log3 ($hash, 5, "$hash->{NAME}_HSV_Transition: Prepare Start (actual): $hueFrom,$satFrom,$valFrom@".$timeFrom);
- if ($flags !~ m/.*[pP].*/ and ($hash->{LEDTYPE} eq 'RGB') || ($hash->{LEDTYPE} eq 'RGBW'))
- {
- # Store previous state if different to requested state
- if (($hueFrom != $hue) || ($satFrom != $sat) || ($valFrom != $val))
- {
- readingsSingleUpdate($hash, "previousState", MilightDevice_HSVToStr($hash, $hueFrom, $satFrom, $valFrom),1);
- }
- }
- }
- Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Current: $hueFrom,$satFrom,$valFrom");
- Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Set: $hue,$sat,$val; Ramp: $ramp; Flags: ". $flags);
- # Store target vales
- $hash->{helper}->{targetHue} = $hue;
- $hash->{helper}->{targetSat} = $sat;
- $hash->{helper}->{targetVal} = $val;
-
- # if there is no ramp we don't need transition
- if (($ramp || 0) == 0)
- {
- Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Set: $hue,$sat,$val; No Ramp");
- $hash->{helper}->{targetTime} = $timeFrom;
- return MilightDevice_CmdQueue_Add($hash, $hue, $sat, $val, undef, 0, undef);
- }
- # calculate the left and right turn length based
- # startAngle +360 -endAngle % 360 = counter clock
- # endAngle +360 -startAngle % 360 = clockwise
- my $hueTo = ($hue == 0) ? 1 : ($hue == 360) ? 359 : $hue;
- my $fadeLeft = ($hueFrom + 360 - $hue) % 360;
- my $fadeRight = ($hue + 360 - $hueFrom) % 360;
- my $direction = ($fadeLeft <=> $fadeRight); # -1 = counterclock, +1 = clockwise
- $direction = ($direction == 0)?1:$direction; # in dupt cw
- Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Colour rotation: cc(-1): $fadeLeft, cw(+1): $fadeRight; Shortest: $direction;");
- $direction *= -1 if ($flags =~ m/.*[lL].*/); # reverse if long path desired (flag l or L is set)
- my $rotation = ($direction == 1)?$fadeRight:$fadeLeft; # angle of hue rotation in based on flags
- my $sFade = abs($sat - $satFrom);
- my $vFade = abs($val - $valFrom);
- # No transition, so set immediately and ignore ramp setting
- if ($rotation == 0 && $sFade == 0 && $vFade == 0)
- {
- Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Unchanged. Set: $hue,$sat,$val; Ignoring Ramp");
-
- $hash->{helper}->{targetTime} = $timeFrom;
- return MilightDevice_CmdQueue_Add($hash, $hue, $sat, $val, undef, 0, undef);
- }
-
- my ($stepWidth, $steps, $maxSteps, $hueToSet, $hueStep, $satToSet, $satStep, $valToSet, $valStep);
- # Calculate stepWidth
- if ($rotation >= ($sFade || $vFade))
- {
- # Transition based on Hue, so max steps = colourSteps
- $stepWidth = ($ramp * 1000 / $rotation); # how long is one step (set hsv) in ms based on hue
- $maxSteps = MilightDevice_ColourSteps($hash);
- }
- elsif ($sFade >= ($rotation || $vFade))
- {
- # Transition based on Saturation, so max steps = 2 (devices don't support sat, so set to 0 or 100 mostly)
- $stepWidth = ($ramp * 1000 / $sFade); # how long is one step (set hsv) in ms based on sat
- $maxSteps = 2;
- }
- else
- {
- # Transition based on Brightness, so max steps = dimSteps
- $stepWidth = ($ramp * 1000 / $vFade); # how long is one step (set hsv) in ms based on val
- $maxSteps = MilightDevice_DimSteps($hash);
- }
-
- # Calculate number of steps, limit to max number (no point running more if they are the same)
- $steps = int($ramp * 1000 / $stepWidth);
- if ($steps > $maxSteps)
- {
- $stepWidth *= ($steps/$maxSteps);
- $steps = $maxSteps;
- }
- # Calculate number of steps, limit to max number (no point running more if they are the same)
- $steps = int($ramp * 1000 / $stepWidth);
- if ($steps > $maxSteps)
- {
- $stepWidth *= ($steps/$maxSteps);
- $steps = $maxSteps;
- }
- # Calculate minimum stepWidth
- # Min bridge delay as specified by Bridge * 3 (eg. 100*3=300ms).
- # On average min 3 commands need to be sent per step (eg. Group On; Mode; Brightness;) so this gets it approximately right
- my $minStepWidth = $hash->{IODev}->{INTERVAL} * 3;
- $stepWidth = $minStepWidth if ($stepWidth < $minStepWidth); # Make sure we have min stepWidth
-
- Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Steps: $steps; Step Interval(ms): $stepWidth");
-
- # Calculate hue step
- $hueToSet = $hueFrom; # Start at current hue
- $hueStep = $rotation / $steps * $direction;
-
- # Calculate saturation step
- $satToSet = $satFrom; # Start at current saturation
- $satStep = ($sat - $satFrom) / $steps;
-
- # Calculate brightness step
- $valToSet = $valFrom; # Start at current brightness
- $valStep = ($val - $valFrom) / $steps;
- for (my $i=1; $i <= $steps; $i++)
- {
- $hueToSet += $hueStep; # Increment new hue by step (negative step decrements)
- $hueToSet -= 360 if ($hueToSet > 360); #handle turn over zero
- $hueToSet += 360 if ($hueToSet < 0);
- $satToSet += $satStep; # Increment new saturation by step (negative step decrements)
- $valToSet += $valStep; # Increment new brightness by step (negative step decrements)
- Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Add to Queue: h:".($hueToSet).", s:".($satToSet).", v:".($valToSet)." ($i/$steps)");
- MilightDevice_CmdQueue_Add($hash, MilightDevice_roundfunc($hueToSet), MilightDevice_roundfunc($satToSet), MilightDevice_roundfunc($valToSet), undef, $stepWidth, $timeFrom + (($i-1) * $stepWidth / 1000) );
- }
- # Set target time for completion of sequence.
- # This may be slightly higher than what was requested since $stepWidth > minDelay (($steps * $stepWidth) > $ramp)
- $hash->{helper}->{targetTime} = $timeFrom + ($steps * $stepWidth / 1000);
- Log3 ($hash, 5, "$hash->{NAME}_HSV_Transition: TargetTime: $hash->{helper}->{targetTime}");
- return undef;
- }
- #####################################
- sub MilightDevice_White_Transition(@)
- {
- my ($hash, $ct, $sat, $val, $ramp, $flags) = @_;
- my ($ctFrom, $valFrom, $timeFrom)=0;
- # Clear command queue if flag "q" not specified
- MilightDevice_CmdQueue_Clear($hash) if ($flags !~ m/.*[qQ].*/);
- # if queue in progress set start vals to last cached hsv target, else set start to actual hsv
- if (@{$hash->{helper}->{cmdQueue}} > 0)
- {
- $ctFrom = $hash->{helper}->{targetCt};
- $valFrom = $hash->{helper}->{targetVal};
- $timeFrom = $hash->{helper}->{targetTime};
- $ctFrom = 3000 if(!defined($ctFrom));
- $valFrom = 0 if(!defined($valFrom));
- $timeFrom = 0 if(!defined($timeFrom));
- Log3 ($hash, 5, "$hash->{NAME}_White_Transition: Prepare Start (cached): $ctFrom,$valFrom@".$timeFrom);
- }
- else
- {
- $ctFrom = ReadingsVal($hash->{NAME}, "ct", 3000);
- $valFrom = ReadingsVal($hash->{NAME}, "brightness", 0);
- $timeFrom = gettimeofday();
- Log3 ($hash, 5, "$hash->{NAME}_White_Transition: Prepare Start (actual): $ctFrom,$valFrom@".$timeFrom);
- if ($flags !~ m/.*[pP].*/)
- {
- # Store previous state if different to requested state
- if (($ctFrom != $ct) || ($valFrom != $val))
- {
- readingsSingleUpdate($hash, "previousState", MilightDevice_HSVToStr($hash, $ctFrom, 0, $valFrom),1);
- }
- }
- }
- Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Current: $ctFrom,$valFrom");
- Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Set: $ct,$val; Ramp: $ramp; Flags: ". $flags);
- # Store target vales
- $hash->{helper}->{targetCt} = $ct;
- $hash->{helper}->{targetVal} = $val;
- # if there is no ramp we don't need transition
- if (($ramp || 0) == 0)
- {
- Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Set: $ct,$val; No Ramp");
- $hash->{helper}->{targetTime} = $timeFrom;
- return MilightDevice_CmdQueue_Add($hash, $ct, 0, $val, undef, 0, undef);
- }
- my $vFade = abs($val - $valFrom);
- my $ctFade = abs($ct - $ctFrom);
- Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Colour temp: $ctFade, Brightness: $vFade;");
- # No transition, so set immediately and ignore ramp setting
- if ($ctFade == 0 && $vFade == 0)
- {
- Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Unchanged. Set: $ct,0,$val; Ignoring Ramp");
- $hash->{helper}->{targetTime} = $timeFrom;
- return MilightDevice_CmdQueue_Add($hash, $ct, 0, $val, undef, 0, undef);
- }
- my ($stepWidth, $steps, $maxSteps, $ctToSet, $ctStep, $valToSet, $valStep);
- # Calculate stepWidth
- if ($ctFade >= $vFade)
- {
- # Transition based on ct, so max steps = colourSteps
- $stepWidth = ($ramp * 1000 / $ctFade /100); # how long is one step (set hsv) in ms based on ct
- $maxSteps = MilightDevice_ColourSteps($hash);
- }
- else
- {
- # Transition based on Brightness, so max steps = dimSteps
- $stepWidth = ($ramp * 1000 / $vFade); # how long is one step (set hsv) in ms based on val
- $maxSteps = MilightDevice_DimSteps($hash);
- }
- # Calculate number of steps, limit to max number (no point running more if they are the same)
- $steps = int($ramp * 1000 / $stepWidth);
- if ($steps > $maxSteps)
- {
- $stepWidth *= ($steps/$maxSteps);
- $steps = $maxSteps;
- }
- # Calculate number of steps, limit to max number (no point running more if they are the same)
- $steps = int($ramp * 1000 / $stepWidth);
- if ($steps > $maxSteps)
- {
- $stepWidth *= ($steps/$maxSteps);
- $steps = $maxSteps;
- }
- # Calculate minimum stepWidth
- # Min bridge delay as specified by Bridge * 3 (eg. 100*3=300ms).
- # On average min 3 commands need to be sent per step (eg. Group On; Mode; Brightness;) so this gets it approximately right
- my $minStepWidth = $hash->{IODev}->{INTERVAL} * 3;
- $stepWidth = $minStepWidth if ($stepWidth < $minStepWidth); # Make sure we have min stepWidth
- Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Steps: $steps; Step Interval(ms): $stepWidth");
- # Calculate hue step
- $ctToSet = $ctFrom; # Start at current hue
- $ctStep = ($ct - $ctFrom) / $steps;
- # Calculate brightness step
- $valToSet = $valFrom; # Start at current brightness
- $valStep = ($val - $valFrom) / $steps;
- for (my $i=1; $i <= $steps; $i++)
- {
- $ctToSet += $ctStep; # Increment new hue by step (negative step decrements)
- $valToSet += $valStep; # Increment new brightness by step (negative step decrements)
- Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Add to Queue: ct:".(int($ctToSet)).", s:0, v:".(int($valToSet))." ($i/$steps)");
- MilightDevice_CmdQueue_Add($hash, MilightDevice_roundfunc($ctToSet), 0, MilightDevice_roundfunc($valToSet), undef, $stepWidth, $timeFrom + (($i-1) * $stepWidth / 1000) );
- }
- # Set target time for completion of sequence.
- # This may be slightly higher than what was requested since $stepWidth > minDelay (($steps * $stepWidth) > $ramp)
- $hash->{helper}->{targetTime} = $timeFrom + ($steps * $stepWidth / 1000);
- Log3 ($hash, 5, "$hash->{NAME}_White_Transition: TargetTime: $hash->{helper}->{targetTime}");
- return undef;
- }
- #####################################
- sub MilightDevice_SetHSV_Readings(@)
- {
- my ($hash, $hue, $sat, $val, $val_on) = @_;
- my $name = $hash->{NAME};
-
- readingsBeginUpdate($hash); # Start update readings
-
- # Store requested values
- readingsBulkUpdate($hash, "brightness", $val);
- # Store on brightness so we can turn on at a set brightness
- readingsBulkUpdate($hash, "brightness_on", $val_on);
- if (($hash->{LEDTYPE} eq 'RGB') || ($hash->{LEDTYPE} eq 'RGBW'))
- {
- # Store previous state if different to requested state
- my $prevHue = ReadingsVal($hash->{NAME}, "hue", 0);
- my $prevSat = ReadingsVal($hash->{NAME}, "saturation", 0);
- my $prevVal = ReadingsVal($hash->{NAME}, "brightness", 0);
- if (($prevHue != $hue) || ($prevSat != $sat) || ($prevVal != $val))
- {
- readingsBulkUpdate($hash, "previousState", MilightDevice_HSVToStr($hash, $prevHue, $prevSat, $prevVal)) if ReadingsVal($hash->{NAME}, "transitionInProgress", 1) eq 0;
- }
- readingsBulkUpdate($hash, "saturation", $sat);
- readingsBulkUpdate($hash, "hue", $hue);
- readingsBulkUpdate($hash, "hsv", MilightDevice_HSVToStr($hash, $hue,$sat,$val));
-
- # Calc RGB values from HSV
- my ($r,$g,$b) = Color::hsv2rgb($hue/360.0,$sat/100.0,$val/100.0);
- $r *=255; $g *=255; $b*=255;
- # Store values
- readingsBulkUpdate($hash, "rgb", sprintf("%02X%02X%02X",$r,$g,$b)); # Int to Hex convert
- readingsBulkUpdate($hash, "discoMode", 0);
- readingsBulkUpdate($hash, "discoSpeed", 0);
- }
- elsif ($hash->{LEDTYPE} eq 'White')
- {
- readingsBulkUpdate($hash, "ct", $hue);
- readingsBulkUpdate($hash, "hsv", MilightDevice_HSVToStr($hash, $hue,0,$val));
- }
- readingsBulkUpdate($hash, "state", "on $val") if ($val > 1);
- readingsBulkUpdate($hash, "state", "off") if ($val < 2);
- readingsEndUpdate($hash, 1);
- MilightDevice_BridgeDevices_Update($hash, "bulk") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
- }
- #####################################
- sub MilightDevice_SetDisco_Readings(@)
- {
- # Step/Speed can be "1" or "0" when active
- my ($hash, $step, $speed) = @_;
- my $name = $hash->{NAME};
-
- if (($hash->{LEDTYPE} eq 'RGBW') || ($hash->{LEDTYPE} eq 'RGB'))
- {
- my $discoMode = ReadingsVal($hash->{NAME}, "discoMode", 0);
- $discoMode = "on";
-
- my $discoSpeed = ReadingsVal($hash->{NAME}, "discoSpeed", 5);
- $discoSpeed = "-" if ($speed == 0);
- $discoSpeed = "+" if ($speed == 1);
-
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "discoMode", $step);
- readingsBulkUpdate($hash, "discoSpeed", $speed);
- readingsEndUpdate($hash, 1);
- if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1)
- {
- MilightDevice_BridgeDevices_Update($hash, "discoMode");
- MilightDevice_BridgeDevices_Update($hash, "discoSpeed");
- }
- }
-
- }
- #####################################
- sub MilightDevice_ColorConverter(@)
- {
- my ($hash, $cr, $cy, $cg, $cc, $cb, $cm) = @_;
- my @colorMap;
- my $adjRed = 0 + $cr;
- my $adjYellow = 60 + $cy;
- my $adjGreen = 120 + $cg;
- my $adjCyan = 180 + $cc;
- my $adjBlue = 240 + $cb;
- my $adjLilac = 300 + $cm;
- my $devRed = 176; # (0xB0)
- #my $devYellow = 128; # (0x80)
- my $devYellow = 144;
- my $devGreen = 96; # (0x60)
- #my $devCyan = 48; # (0x30)
- my $devCyan = 56;
- my $devBlue = 16; # (0x10)
- my $devLilac = 224; # (0xE0)
- my $i= 360;
- # red to yellow
- $adjRed += 360 if ($adjRed < 0); # in case of negative adjustment
- $devRed += 256 if ($devRed < $devYellow);
- $adjYellow += 360 if ($adjYellow < $adjRed);
- for ($i = $adjRed; $i <= $adjYellow; $i++)
- {
- $colorMap[$i % 360] = ($devRed - int((($devRed - $devYellow) / ($adjYellow - $adjRed) * ($i - $adjRed)) +0.5)) % 255;
- Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
- }
- #yellow to green
- $devYellow += 256 if ($devYellow < $devGreen);
- $adjGreen += 360 if ($adjGreen < $adjYellow);
- for ($i = $adjYellow; $i <= $adjGreen; $i++)
- {
- $colorMap[$i % 360] = ($devYellow - int((($devYellow - $devGreen) / ($adjGreen - $adjYellow) * ($i - $adjYellow)) +0.5)) % 255;
- Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
- }
- #green to cyan
- $devGreen += 256 if ($devGreen < $devCyan);
- $adjCyan += 360 if ($adjCyan < $adjGreen);
- for ($i = $adjGreen; $i <= $adjCyan; $i++)
- {
- $colorMap[$i % 360] = ($devGreen - int((($devGreen - $devCyan) / ($adjCyan - $adjGreen) * ($i - $adjGreen)) +0.5)) % 255;
- Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
- }
- #cyan to blue
- $devCyan += 256 if ($devCyan < $devCyan);
- $adjBlue += 360 if ($adjBlue < $adjCyan);
- for ($i = $adjCyan; $i <= $adjBlue; $i++)
- {
- $colorMap[$i % 360] = ($devCyan - int((($devCyan - $devBlue) / ($adjBlue - $adjCyan) * ($i - $adjCyan)) +0.5)) % 255;
- Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
- }
- #blue to lilac
- $devBlue += 256 if ($devBlue < $devLilac);
- $adjLilac += 360 if ($adjLilac < $adjBlue);
- for ($i = $adjBlue; $i <= $adjLilac; $i++)
- {
- $colorMap[$i % 360] = ($devBlue - int((($devBlue - $devLilac) / ($adjLilac - $adjBlue) * ($i- $adjBlue)) +0.5)) % 255;
- Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
- }
- #lilac to red
- $devLilac += 256 if ($devLilac < $devRed);
- $adjRed += 360 if ($adjRed < $adjLilac);
- for ($i = $adjLilac; $i <= $adjRed; $i++)
- {
- $colorMap[$i % 360] = ($devLilac - int((($devLilac - $devRed) / ($adjRed - $adjLilac) * ($i - $adjLilac)) +0.5)) % 255;
- Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
- }
- return \@colorMap;
- }
- #####################################
- sub MilightDevice_CreateGammaMapping(@)
- {
- my ($hash, $gamma) = @_;
- #original wifilight gamma was inverted
- $gamma = 1/$gamma;
- my @gammaMap;
- $gammaMap[0] = 0;
- for (my $i = 1; $i <= 100; $i += 1)
- {
- my $correction = ($i / 100) ** (1 / $gamma);
- $gammaMap[$i] = $correction * 100;
- $gammaMap[$i] = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)) if($gammaMap[$i] < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)));
- Log3 ($hash, 5, "$hash->{NAME} create gammamap v-in: ".$i.", v-out: $gammaMap[$i]");
- }
- return \@gammaMap;
- }
- ###############################################################################
- # Device Command Queue
- # Triggers commands for long running transitions for a device
- ###############################################################################
- sub MilightDevice_CmdQueue_Add(@)
- {
- my ($hash, $hue, $sat, $val, $ctrl, $delay, $targetTime) = @_;
- my $cmd;
-
- # Validate input
- ($hue, $sat, $val) = MilightDevice_ValidateHSV($hash, $hue, $sat, $val);
- $cmd->{hue} = $hue;
- $cmd->{sat} = $sat;
- $cmd->{val} = $val;
- $cmd->{ctrl} = $ctrl;
- $cmd->{delay} = $delay;
- $cmd->{targetTime} = $targetTime;
- $cmd->{inProgess} = 0;
- push @{$hash->{helper}->{cmdQueue}}, $cmd;
- my $hexStr = defined($cmd->{ctrl})? unpack("H*", $cmd->{ctrl} || '') : "";
- Log3 ($hash, 4, "$hash->{NAME}_CmdQueue_Add: h: ".(defined($cmd->{hue})? $cmd->{hue}: "")."; s: ".(defined($cmd->{sat})? $cmd->{sat}: "")."; v: ".(defined($cmd->{val})? $cmd->{val}: "")."; Ctrl $hexStr; TargetTime: ".(defined($cmd->{targetTime})? $cmd->{targetTime}: "")."; QLen: ".@{$hash->{helper}->{cmdQueue}});
- my $actualCmd = @{$hash->{helper}->{cmdQueue}}[0];
- # sender busy ?
- if(defined($actualCmd))
- {
- return undef if (ref($actualCmd) ne 'HASH');
- return undef if (!defined($actualCmd->{inProgess}));
- return undef if (($actualCmd->{inProgess} || 0) == 1);
- }
- return MilightDevice_CmdQueue_Exec($hash);
- }
- #####################################
- sub MilightDevice_CmdQueue_Exec(@)
- {
- my ($hash) = @_;
- RemoveInternalTimer($hash);
- #if ($hash->{IODev}->{STATE} ne "ok" && $hash->{IODev}->{STATE} ne "Initialized") {
- # InternalTimer(gettimeofday() + 60, "MilightDevice_CmdQueue_Exec", $hash, 0);
- # return undef;
- #}
-
- my $actualCmd = @{$hash->{helper}->{cmdQueue}}[0];
- # transmission complete, remove
- shift @{$hash->{helper}->{cmdQueue}} if ($actualCmd->{inProgess});
- # next in queue
- $actualCmd = @{$hash->{helper}->{cmdQueue}}[0];
- my $nextCmd = @{$hash->{helper}->{cmdQueue}}[1];
- # return if no more elements in queue
- if (!defined($actualCmd->{inProgess}))
- {
- readingsSingleUpdate($hash, "transitionInProgress", 0, 1); # Clear transitionInProgress flag
- return undef;
- }
-
- readingsSingleUpdate($hash, "transitionInProgress", 1, 1); # Set transitionInProgress flag
- # drop frames if next frame is already scheduled for given time. do not drop if it is the last frame or if it is a control command
- while (defined($nextCmd->{targetTime}) && ($nextCmd->{targetTime} < gettimeofday()) && !$actualCmd->{ctrl})
- {
- shift @{$hash->{helper}->{cmdQueue}};
- $actualCmd = @{$hash->{helper}->{cmdQueue}}[0];
- $nextCmd = @{$hash->{helper}->{cmdQueue}}[1];
- Log3 ($hash, 4, "$hash->{NAME}_CmdQueue_Exec: Drop Frame. Queue Length: ".@{$hash->{helper}->{cmdQueue}});
- }
- Log3 ($hash, 5, "$hash->{NAME}_CmdQueue_Exec: Dropper Delay: ".($actualCmd->{targetTime} - gettimeofday())) if (defined($actualCmd->{targetTime}));
- # set hsv or if a device ctrl command is scheduled: send it and ignore hsv
- if ($actualCmd->{ctrl})
- {
- my $dbgStr = unpack("H*", $actualCmd->{ctrl});
- Log3 ($hash, 4, "$hash->{NAME}_CmdQueue_Exec: Send ctrl: $dbgStr; Queue Length: ".@{$hash->{helper}->{cmdQueue}});
- IOWrite($hash, $actualCmd->{ctrl});
- }
- else
- {
- # Send an HSV Command.
- my $repeat = 0;
- # If queue length < 2 (ie. 1) we are last command so repeat sending (takes twice as long...)
- $repeat = 1 if (@{$hash->{helper}->{cmdQueue}} < 2);
- MilightDevice_SetHSV($hash, $actualCmd->{hue}, $actualCmd->{sat}, $actualCmd->{val}, $repeat);
- }
- $actualCmd->{inProgess} = 1;
- my $next = defined($nextCmd->{targetTime})?$nextCmd->{targetTime}:gettimeofday() + ($actualCmd->{delay} / 1000);
-
- Log3 ($hash, 5, "$hash->{NAME}_CmdQueue_Exec: Next Exec: $next");
- InternalTimer($next, "MilightDevice_CmdQueue_Exec", $hash, 0);
- return undef;
- }
- #####################################
- sub MilightDevice_CmdQueue_Clear(@)
- {
- my ($hash) = @_;
- Log3 ($hash, 4, "$hash->{NAME}_CmdQueue_Clear");
- RemoveInternalTimer($hash);
- #if ($hash->{IODev}->{STATE} ne "ok" && $hash->{IODev}->{STATE} ne "Initialized") {
- # InternalTimer(gettimeofday() + 60, "MilightDevice_CmdQueue_Exec", $hash, 0);
- # return undef;
- #}
- readingsSingleUpdate($hash, "transitionInProgress", 0, 1); # Clear inProgress flag
-
- #foreach my $args (keys %intAt)
- #{
- # if (($intAt{$args}{ARG} eq $hash) && ($intAt{$args}{FN} eq 'MilightDevice_CmdQueue_Exec'))
- # {
- # Log3 ($hash, 5, "$hash->{NAME}_CmdQueue_Clear: Remove timer at: ".$intAt{$args}{TRIGGERTIME});
- # delete($intAt{$args});
- # }
- #}
- $hash->{helper}->{cmdQueue} = [];
- return undef;
- }
- #####################################
- sub MilightDevice_BridgeDevices_Update(@)
- {
- my ($hash, $attr) = @_;
- my @rdlist = ($attr);
-
- if($attr eq 'bulk')
- {
- @rdlist = ("state","brightness","brightness_on","hue", "saturation", "hsv", "rgb", "discoMode", "discoSpeed")if ($hash->{LEDTYPE} eq 'RGBW');
- @rdlist = ("state","brightness","brightness_on","ct")if ($hash->{LEDTYPE} eq 'White');
- }
- my $sl = 5;
- $sl = 1 if ($hash->{LEDTYPE} eq 'White');
- for (my $i = 0; $i < 4; $i++)
- {
- my $devname = $hash->{IODev}->{$sl+$i}->{NAME};
- next if (!defined($defs{$devname}));
- my $device = $defs{$devname};
- $devname = "?" if(!defined($devname));
- readingsSingleUpdate($device, "transitionInProgress", 1, 1);
-
- readingsBeginUpdate($device);
-
- foreach my $rdname (@rdlist)
- {
- if (exists ($device->{READINGS}{$rdname}))
- {
- readingsBulkUpdate($device, $rdname, $hash->{READINGS}{$rdname}{VAL}, 1);
- Log3 ($hash, 4, $rdname.": ".$device->{READINGS}{$rdname}{VAL}." for ".$devname);
- }
- }
- readingsEndUpdate($device, 1);
- readingsSingleUpdate($device, "transitionInProgress", 0, 1);
- }
- return undef;
- }
- sub MilightDevice_roundfunc($) {
- my ($number) = @_;
- return sprintf("%.0f", $number);
- #return Math::Round::round($number);
- }
- 1;
- =pod
- =item device
- =item summary This module represents a Milight LED Bulb or LED strip controller
- =begin html
- <a name="MilightDevice"></a>
- <h3>MilightDevice</h3>
- <ul>
- <p>This module represents a Milight LED Bulb or LED strip controller. It is controlled by a <a href="#MilightBridge">MilightBridge</a>.</p>
- <p>The Milight system is sold under various brands around the world including "LimitlessLED, EasyBulb, AppLamp"</p>
- <p>The API documentation is available here: <a href="http://www.limitlessled.com/dev/">http://www.limitlessled.com/dev/</a></p>
- <p>Requires perl module Math::Round</p>
- <a name="MilightDevice_define"></a>
- <p><b>Define</b></p>
- <ul>
- <p><code>define <name> MilightDevice <devType(RGB|RGBW|White)> <IODev> <slot></code></p>
- <p>Specifies the Milight device.<br/>
- <devType> One of RGB, RGBW, White depending on your device.<br/>
- <IODev> The <a href="#MilightBridge">MilightBridge</a> which the device is paired with.<br/>
- <slot> The slot on the <a href="#MilightBridge">MilightBridge</a> that the device is paired with or 'A' to group all slots.</p>
- </ul>
- <a name="MilightDevice_readings"></a>
- <p><b>Readings</b></p>
- <ul>
- <li>
- <b>state</b><br/>
- [on xxx|off|night]: Current state of the device / night mode (xxx = 0-100%).
- </li>
- <li>
- <b>brightness</b><br/>
- [0-100]: Current brightness level in %.
- </li>
- <li>
- <b>brightness_on</b><br/>
- [0-100]: The brightness level before the off command was sent. This allows the light to turn back on to the last brightness level.
- </li>
- <li>
- <b>rgb</b><br/>
- [FFFFFF]: HEX value for RGB.
- </li>
- <li>
- <b>previousState</b><br/>
- [hsv]: hsv value before last change. Can be used with <b>restorePreviousState</b> set command.
- </li>
- <li>
- <b>savedState</b><br/>
- [hsv]: hsv value that was saved using <b>saveState</b> set function
- </li>
- <li>
- <b>hue</b><br/>
- [0-360]: Current hue value.
- </li>
- <li>
- <b>saturation</b><br/>
- [0-100]: Current saturation value.
- </li>
- <li>
- <b>transitionInProgress</b><br/>
- [0|1]: Set to 1 if a transition is currently in progress for this device (eg. fade).
- </li>
- <li>
- <b>discoMode</b><br/>
- [0|1]: 1 if discoMode is enabled, 0 otherwise.
- </li>
- <li>
- <b>discoSpeed</b><br/>
- [0|1]: 1 if discoSpeed is increased, 0 if decreased. Does not mean much for RGBW
- </li>
- <li>
- <b>lastPreset</b><br/>
- [0..X]: Last selected preset.
- </li>
- <li>
- <b>ct</b><br/>
- [1-10]: Current colour temperature (3000=Warm,6500=Cold) for White devices.
- </li>
- </ul>
- <a name="MilightDevice_set"></a>
- <p><b>Set</b></p>
- <ul>
- <li>
- <b>on <ramp_time (seconds)></b>
- </li>
- <li>
- <b>off <ramp_time (seconds)></b>
- </li>
- <li>
- <b>toggle</b>
- </li>
- <li>
- <b>night</b>
- </li>
- <li>
- <b>dim <percent(0..100)> [seconds(0..x)] [flags(l=long path|q=don't clear queue)]</b><br/>
- Will be replaced by <i>brightness</i> at some point
- </li>
- <li>
- <b>dimup <percent change(0..100)> [seconds(0..x)]</b><br/>
- Special case: If percent change=100, seconds will be adjusted for actual change to go from current brightness.
- </li>
- <li>
- <b>dimdown <percent change(0..100)> [seconds(0..x)]</b><br/>
- Special case: If percent change=100, seconds will be adjusted for actual change to go from current brightness.
- </li>
- <li>
- <b>pair</b><br/>
- May not work properly. Sometimes it is necessary to use a remote to clear pairing first.
- </li>
- <li>
- <b>unpair</b><br/>
- May not work properly. Sometimes it is necessary to use a remote to clear pairing first.
- </li>
- <li>
- <b>restorePreviousState</b><br/>
- Set device to previous hsv state as stored in <b>previousState</b> reading.
- </li>
- <li>
- <b>saveState</b><br/>
- Save current hsv state to <b>savedState</b> reading.
- </li>
- <li>
- <b>restoreState</b><br/>
- Set device to saved hsv state as stored in <b>savedState</b> reading.
- </li>
- <li>
- <b>preset (0..X|+)</b><br/>
- Load preset (+ for next preset).
- </li>
- <li>
- <b>hsv <h(0..360)>,<s(0..100)>,<v(0..100)> [seconds(0..x)] [flags(l=long path|q=don't clear queue)]</b><br/>
- Set hsv value directly
- </li>
- <li>
- <b>rgb RRGGBB [seconds(0..x)] [flags(l=long path|q=don't clear queue)]</b><br/>
- Set rgb value directly or using colorpicker.
- </li>
- <li>
- <b>hue <(0..360)> [seconds(0..x)] [flags(l=long path|q=don't clear queue)]</b><br/>
- Set hue value.
- </li>
- <li>
- <b>saturation <s(0..100)> [seconds(0..x)] [flags(q=don't clear queue)]</b><br/>
- Set saturation value directly
- </li>
- <li>
- <b>discoModeUp</b><br/>
- Next disco Mode setting (for RGB and RGBW).
- </li>
- <li>
- <b>discoModeDown</b><br/>
- Previous disco Mode setting (for RGB).
- </li>
- <li>
- <b>discoSpeedUp</b><br/>
- Increase speed of disco mode (for RGB and RGBW).
- </li>
- <li>
- <b>discoSpeedDown</b><br/>
- Decrease speed of disco mode (for RGB and RGBW).
- </li>
- <li>
- <b>ct <3000-6500></b><br/>
- Colour temperature 3000=Warm White,6500=Cold White (10 steps) (for White devices only).
- </li>
- <li>
- <a href="#setExtensions"> set extensions</a> are supported.
- </li>
- </ul>
- <a name="MilightDevice_get"></a>
- <p><b>Get</b></p>
- <ul>
- <li>
- <b>rgb</b>
- </li>
- <li>
- <b>hsv</b>
- </li>
- </ul>
-
- <a name="MilightDevice_attr"></a>
- <p><b>Attributes</b></p>
- <ul>
- <li>
- <b>dimStep</b><br/>
- Allows you to modify the default dimStep if required.
- </li>
- <li>
- <b>defaultRampOn</b><br/>
- Set the default ramp time if not specified for on command.
- </li>
- <li>
- <b>defaultRampOff</b><br/>
- Set the default ramp time if not specified for off command.
- </li>
- <li>
- <b>presets</b><br/>
- List of hsv presets separated by spaces (eg 0,0,100 9,0,50).
- </li>
- <li>
- <b>colorCast</b><br/>
- Color shift values for red,yellow,green,cyan,blue,magenta (-29..29) for HSV color correction (eg 0,5,10,-5,0,0)
- </li>
- <li>
- <b>gamma</b><br/>
- Set gamma correction value for device (eg 0.8)
- </li>
- <li>
- <b>dimOffWhite</b><br/>
- Use a different switching logic for White bulbs to better handle packet loss.
- </li>
- <li>
- <b>updateGroupDevices</b><br/>
- Update the state of single devices switched with slot 'A'.
- </li>
- <li>
- <b>restoreAtStart</b><br/>
- Restore the state of devices at startup. Default 0 for slot 'A', 1 otherwise.
- </li>
- <li>
- <b>defaultBrightness</b><br/>
- Set the default brightness if not known. (Default: 36)
- </li>
- </ul>
- </ul>
- =end html
- =cut
|