31_MilightDevice.pm 97 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586
  1. # $Id: 31_MilightDevice.pm 16085 2018-02-04 18:35:23Z mattwire $
  2. ##############################################
  3. #
  4. # 31_MilightDevice.pm (Based on 32_WifiLight.pm by hermannj)
  5. # FHEM module for MILIGHT lightbulbs. Supports RGB (untested), RGBW and White models.
  6. # Author: Matthew Wire (mattwire)
  7. #
  8. # This file is part of fhem.
  9. #
  10. # Fhem is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation, either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # Fhem is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  22. #
  23. ##############################################################################
  24. package main;
  25. use strict;
  26. use warnings;
  27. use IO::Handle;
  28. use IO::Socket;
  29. use IO::Select;
  30. use Time::HiRes;
  31. #use Math::Round ();
  32. use Color;
  33. use SetExtensions;
  34. my %dim_values = (
  35. 0 => "dim_00",
  36. 1 => "dim_10",
  37. 2 => "dim_20",
  38. 3 => "dim_30",
  39. 4 => "dim_40",
  40. 5 => "dim_50",
  41. 6 => "dim_60",
  42. 7 => "dim_70",
  43. 8 => "dim_80",
  44. 9 => "dim_90",
  45. 10 => "dim_100",
  46. );
  47. # RGBW 3 byte commands. 3rd byte not required Bridge V3+
  48. my @RGBWCmdsOn = ("\x45", "\x47", "\x49", "\x4B", "\x42"); # Byte 1 for setting On
  49. my @RGBWCmdsOff = ("\x46", "\x48", "\x4A", "\x4C", "\x41"); # Byte 1 for setting Off
  50. my @RGBWCmdsWT = ("\xC5", "\xC7", "\xC9", "\xCB", "\xC2"); # Byte 1 for setting WhiteMode
  51. my @RGBWCmdsNt = ("\xC6", "\xC8", "\xCA", "\xCC", "\xC1"); # Byte 1 for setting NightMode
  52. my $RGBWCmdBri = "\x4E"; # Byte 1 for setting brightness (Byte 2 specifies level (0x02-0x1B 25 steps)
  53. my $RGBWCmdCol = "\x40"; # Byte 1 for setting color (Byte 2 specifies color value (0x00-0xFF (255 steps))
  54. my $RGBWCmdDiscoUp = "\x4D"; # Byte 1 for setting discoMode Up
  55. my $RGBWCmdDiscoInc = "\x44"; # Byte 1 for setting discoMode speed +
  56. my $RGBWCmdDiscoDec = "\x43"; # Byte 1 for setting discoMode speed -
  57. my $RGBWCmdEnd = "\x55"; # Byte 3
  58. # White 3 byte commands.
  59. my @WhiteCmdsOn = ("\x38", "\x3D", "\x37", "\x32", "\x35"); # Byte 1 for setting On
  60. my @WhiteCmdsOff = ("\x3B", "\x33", "\x3A", "\x36", "\x39"); # Byte 1 for setting Off
  61. my @WhiteCmdsOnFull = ("\xB8", "\xBD", "\xB7", "\xB2", "\xB5"); # Byte 1 for setting full brightness
  62. my @WhiteCmdsNt = ("\xBB", "\xB3", "\xBA", "\xB6", "\xB9"); # Byte 1 for setting NightMode
  63. my @WhiteCmdBriDn = ("\x34", "\x34", "\x34", "\x34", "\xB4"); # Byte 1 for setting Brightness down (11 steps, no direct setting)
  64. my @WhiteCmdBriUp = ("\x3C", "\x3C", "\x3C", "\x3C", "\xBC"); # Byte 1 for setting Brightness up (11 steps, no direct setting)
  65. my @WhiteCmdColDn = ("\x3F", "\x3F", "\x3F", "\x3F", "\xBF"); # Byte 1 for setting colour temp down
  66. my @WhiteCmdColUp = ("\x3E", "\x3E", "\x3E", "\x3E", "\xBE"); # Byte 1 for setting colour temp up
  67. my $WhiteCmdEnd = "\x55"; # Byte 3
  68. sub MilightDevice_Initialize($)
  69. {
  70. my ($hash) = @_;
  71. $hash->{DefFn} = "MilightDevice_Define";
  72. $hash->{UndefFn} = "MilightDevice_Undef";
  73. $hash->{ShutdownFn} = "MilightDevice_Undef";
  74. $hash->{SetFn} = "MilightDevice_Set";
  75. $hash->{GetFn} = "MilightDevice_Get";
  76. $hash->{AttrFn} = "MilightDevice_Attr";
  77. $hash->{NotifyFn} = "MilightDevice_Notify";
  78. $hash->{AttrList} = "IODev dimStep defaultBrightness defaultRampOn " .
  79. "defaultRampOff presets dimOffWhite:1,0 updateGroupDevices:1,0 " .
  80. "restoreAtStart:1,0 colorCast gamma lightSceneParamsToSave " .
  81. $readingFnAttributes;
  82. FHEM_colorpickerInit();
  83. }
  84. #####################################
  85. # Device State Icon for FHEMWEB: Shows a colour changing icon with dim level
  86. sub MilightDevice_devStateIcon($)
  87. {
  88. my($hash) = @_;
  89. $hash = $defs{$hash} if(ref($hash) ne 'HASH');
  90. return undef if(!$hash);
  91. return undef if($hash->{helper}->{group});
  92. my $name = $hash->{NAME};
  93. my $percent = ReadingsVal($name,"brightness","100");
  94. my $s = $dim_values{MilightDevice_roundfunc($percent/10)};
  95. # Return SVG coloured icon with toggle as default action
  96. return ".*:light_light_$s@#".ReadingsVal($name, "rgb", "FFFFFF").":toggle"
  97. if (($hash->{LEDTYPE} eq 'RGBW') || ($hash->{LEDTYPE} eq 'RGB'));
  98. # Return SVG icon with toggle as default action (for White bulbs)
  99. return ".*:light_light_$s:toggle";
  100. }
  101. #####################################
  102. # Define Milight device
  103. sub MilightDevice_Define($$)
  104. {
  105. my ($hash, $def) = @_;
  106. my @args = split("[ \t][ \t]*", $def);
  107. my ($name, $type, $ledtype, $iodev, $slot) = @args;
  108. $hash->{INIT} = 0; # Set to 1 when lamp initialised (MilightDevice_Restore)
  109. $hash->{LEDTYPE} = $ledtype;
  110. $hash->{SLOT} = $slot;
  111. $hash->{SLOTID} = $slot;
  112. if($slot eq 'A') {
  113. $hash->{SLOTID} = 9 if ($hash->{LEDTYPE} eq 'RGBW');
  114. $hash->{SLOTID} = 5 if ($hash->{LEDTYPE} eq 'White');
  115. $hash->{SLOTID} = 0 if ($hash->{LEDTYPE} eq 'RGB');
  116. }
  117. # Validate parameters
  118. return "wrong syntax: define <name> MilightDevice <devType(RGB|RGBW|White)> <IODev> <slot>" if(@args < 5);
  119. return "unknown LED type ($hash->{LEDTYPE}): choose one of RGB, RGBW, White" if ($hash->{LEDTYPE} !~ /RGBW|White|RGB/);
  120. 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'));
  121. 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'));
  122. return "Invalid slot: Select 0 for RGB" if (($hash->{SLOTID} !~ /^\d*$/) || ($hash->{SLOTID} != 0 && $hash->{LEDTYPE} eq 'RGB'));
  123. Log3 ($hash, 4, $name."_Define: $name $type $hash->{LEDTYPE} $iodev $hash->{SLOT}");
  124. # Verify IODev is valid
  125. AssignIoPort($hash, $iodev);
  126. if(defined($hash->{IODev}->{NAME})) {
  127. Log3 $name, 4, $name."_Define: I/O device is " . $hash->{IODev}->{NAME};
  128. } else {
  129. Log3 $name, 1, $name."_Define: no I/O device";
  130. }
  131. # Look for already defined device on IODev
  132. if ($hash->{SLOT} ne 'A' && defined($hash->{IODev}->{$hash->{SLOT}}->{NAME}))
  133. {
  134. # If defined slot does not match current device name don't allow new definition. Redefining the same device is ok though.
  135. #return "Slot $hash->{SLOT} already defined as $hash->{IODev}->{$hash->{SLOT}}->{NAME}" if ($hash->{IODev}->{$hash->{SLOT}}->{NAME} ne $name);
  136. }
  137. # Define device on IODev
  138. if ($hash->{SLOT} ne 'A')
  139. {
  140. $hash->{IODev}->{$hash->{SLOT}}->{NAME} = $name;
  141. #$hash->{IODev}->{$hash->{SLOT}}->{DEVNAME} = $name;
  142. }
  143. # Define Command Queue
  144. my @cmdQueue = [];
  145. $hash->{helper}->{cmdQueue} = \@cmdQueue;
  146. my $baseCmds = "on off toggle dimup dimdown";
  147. my $sharedCmds = "pair unpair restorePreviousState:noArg saveState:noArg restoreState:noArg";
  148. my $rgbCmds = "hsv rgb:colorpicker,RGB hue:colorpicker,HUE,0,1,360 saturation:slider,0,100,100 preset";
  149. $hash->{helper}->{COMMANDSET} = "$baseCmds discoModeUp:noArg discoSpeedUp:noArg discoSpeedDown:noArg night:noArg white:noArg toggleWhite:noArg $sharedCmds $rgbCmds"
  150. if ($hash->{LEDTYPE} eq 'RGBW');
  151. $hash->{helper}->{COMMANDSET} = "$baseCmds discoModeUp:noArg discoModeDown:noArg discoSpeedUp:noArg discoSpeedDown:noArg $sharedCmds $rgbCmds"
  152. if ($hash->{LEDTYPE} eq 'RGB');
  153. $hash->{helper}->{COMMANDSET} = "$baseCmds hsv ct:colorpicker,CT,3000,350,6500 night:noArg $sharedCmds"
  154. if ($hash->{LEDTYPE} eq 'White');
  155. my $defaultcommandset = $hash->{helper}->{COMMANDSET};
  156. $hash->{helper}->{COMMANDSET} .= " dim:slider,0,".MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)).",100 brightness:slider,0,".MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)).",100";
  157. # webCmds
  158. if (!defined($attr{$name}{webCmd}))
  159. {
  160. $attr{$name}{webCmd} = 'on:off:dim:hue:night:rgb ffffff:rgb ff0000:rgb 00ff00:rgb 0000ff:rgb ffff00' if ($hash->{LEDTYPE} eq 'RGBW');
  161. $attr{$name}{webCmd} = 'on:off:dim:hue:rgb ffffff:rgb ff0000:rgb 00ff00:rgb 0000ff:rgb ffff00' if ($hash->{LEDTYPE} eq 'RGB');
  162. $attr{$name}{webCmd} = 'on:off:dim:ct:night' if ($hash->{LEDTYPE} eq 'White');
  163. }
  164. $hash->{helper}->{GAMMAMAP} = MilightDevice_CreateGammaMapping($hash, 1.0);
  165. $hash->{helper}->{COLORMAP} = MilightDevice_ColorConverter($hash, split(',', "0,0,0,0,0,0"));
  166. # Define devStateIcon
  167. $attr{$name}{devStateIcon} = '{(MilightDevice_devStateIcon($name),"toggle")}' if(!defined($attr{$name}{devStateIcon}));
  168. # Event on change reading
  169. $attr{$name}{"event-on-change-reading"} = "state,transitionInProgress" if (!defined($attr{$name}{"event-on-change-reading"}));
  170. # lightScene
  171. if(!defined($attr{$name}{"lightSceneParamsToSave"}))
  172. {
  173. $attr{$name}{"lightSceneParamsToSave"} = "hsv" if (($hash->{LEDTYPE} eq 'RGBW')|| ($hash->{LEDTYPE} eq 'RGB'));
  174. $attr{$name}{"lightSceneParamsToSave"} = "brightness" if ($hash->{LEDTYPE} eq 'White');
  175. }
  176. # IODev
  177. $attr{$name}{IODev} = $hash->{IODev} if (!defined($attr{$name}{IODev}));
  178. # restoreAtStart
  179. if($slot eq 'A') {
  180. $attr{$name}{"restoreAtStart"} = 0 if (!defined($attr{$name}{"restoreAtStart"}));
  181. } else {
  182. $attr{$name}{"restoreAtStart"} = 1 if (!defined($attr{$name}{"restoreAtStart"}));
  183. }
  184. return undef;
  185. }
  186. sub MilightDevice_Init($)
  187. {
  188. my ($hash) = @_;
  189. my $name = $hash->{NAME};
  190. if( AttrVal($hash->{NAME}, "gamma", "1.0") eq "1.0")
  191. {
  192. Log3 ($name, 5, $name." dimstep ".MilightDevice_roundfunc(100 / MilightDevice_DimSteps($hash))." / gamma 1.0");
  193. } else {
  194. $hash->{helper}->{COMMANDSET} =~ s/dim:slider,0,.*,100/dim:slider,0,1,100/g;
  195. $hash->{helper}->{COMMANDSET} =~ s/brightness:slider,0,.*,100/brightness:slider,0,1,100/g;
  196. Log3 $name, 5, $name." dimstep 1 / gamma ".AttrVal($hash->{NAME}, "gamma", "1.0");
  197. $hash->{helper}->{GAMMAMAP} = MilightDevice_CreateGammaMapping($hash, AttrVal($hash->{NAME}, "gamma", "1.0"));
  198. }
  199. # Colormap / Commandsets
  200. if (($hash->{LEDTYPE} eq 'RGBW') || ($hash->{LEDTYPE} eq 'RGB'))
  201. {
  202. my @a = split(',', "0,0,0,0,0,0");
  203. if ( defined( $attr{$name}{colorCast} ) )
  204. {
  205. @a = split(',', AttrVal($hash->{NAME}, "colorCast", "0,0,0,0,0,0"));
  206. @a = split(',', "0,0,0,0,0,0") unless (@a == 6);
  207. foreach my $tc (@a)
  208. {
  209. @a = split(',', "0,0,0,0,0,0") unless ($tc =~ m/^\s*[\-]{0,1}[0-9]+[\.]{0,1}[0-9]*\s*$/g);
  210. @a = split(',', "0,0,0,0,0,0") if (abs($tc) >= 30);
  211. }
  212. }
  213. $hash->{helper}->{COLORMAP} = MilightDevice_ColorConverter($hash, @a);
  214. }
  215. return undef;
  216. }
  217. #####################################
  218. # Undefine device
  219. sub MilightDevice_Undef(@)
  220. {
  221. my ($hash,$args) = @_;
  222. RemoveInternalTimer($hash);
  223. # Remove slot on bridge
  224. delete ($hash->{IODev}->{$hash->{SLOT}}->{NAME}) if ($hash->{SLOT} ne 'A');
  225. return undef;
  226. }
  227. #####################################
  228. # Set functions
  229. sub MilightDevice_Set(@)
  230. {
  231. my ($hash, $name, $cmd, @args) = @_;
  232. my $cnt = @args;
  233. my $ramp = 0;
  234. my $flags = "";
  235. my $event = undef;
  236. my $usage = "set $name ...";
  237. if ($hash->{IODev}->{STATE} ne "ok" && $hash->{IODev}->{STATE} ne "Initialized") {
  238. readingsSingleUpdate($hash, "state", "error", 1);
  239. $flags = "q";
  240. $args[2] = "" if(!defined($args[2]));
  241. $args[2] .= "q" if ($args[2] !~ m/.*[qQ].*/);
  242. # return SetExtensions($hash, $hash->{helper}->{COMMANDSET}, $name, $cmd, @args);
  243. # IO error, we need to keep our current state settings!
  244. }
  245. # Commands that map to other commands
  246. if ($cmd eq "toggle")
  247. {
  248. $cmd = ReadingsVal($name,"state","on") ne "off" ? "off" :"on";
  249. }
  250. elsif ($cmd eq "white")
  251. {
  252. $cmd = "saturation";
  253. $args[0] = 0;
  254. }
  255. elsif ($cmd eq "toggleWhite")
  256. {
  257. $cmd = "saturation";
  258. $args[0] = (ReadingsVal($name,"saturation",100) > 0) ? 0 : 100;
  259. }
  260. # Commands
  261. if ($cmd eq 'on')
  262. {
  263. if (defined($args[0]))
  264. {
  265. return "Usage: set $name on [seconds(0..X)]" if ($args[0] !~ /^\d+$/);
  266. $ramp = $args[0];
  267. }
  268. elsif (defined($attr{$name}{defaultRampOn}))
  269. {
  270. $ramp = $attr{$name}{defaultRampOn};
  271. }
  272. return MilightDevice_RGBW_On($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
  273. return MilightDevice_White_DimOn($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White' && AttrVal($hash->{NAME}, "dimOffWhite", 0) == 1);
  274. return MilightDevice_White_On($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
  275. return MilightDevice_RGB_On($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
  276. }
  277. elsif ($cmd eq 'off')
  278. {
  279. if (defined($args[0]))
  280. {
  281. return "Usage: set $name off [seconds(0..X)]" if ($args[0] !~ /^\d+$/);
  282. $ramp = $args[0];
  283. }
  284. elsif (defined($attr{$name}{defaultRampOff}))
  285. {
  286. $ramp = $attr{$name}{defaultRampOff};
  287. }
  288. return MilightDevice_RGBW_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
  289. return MilightDevice_White_DimOff($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White' && AttrVal($hash->{NAME}, "dimOffWhite", 0) == 1);
  290. return MilightDevice_White_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
  291. return MilightDevice_RGB_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
  292. }
  293. # Set HSV value
  294. elsif ($cmd eq 'hsv')
  295. {
  296. $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)]";
  297. $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');
  298. return $usage if ($args[0] !~ /^(\d{1,4}),(\d{1,3}),(\d{1,3})$/);
  299. my ($h, $s, $v) = ($1, $2, $3);
  300. return "Invalid hue ($h): valid range 0..360" if (!(($h >= 0) && ($h <= 360)) && ($hash->{LEDTYPE} ne 'White'));
  301. return "Invalid color temperature ($h): valid range 3000..6500" if (!(($h >= 3000) && ($h <= 6500)) && ($hash->{LEDTYPE} eq 'White'));
  302. return "Invalid saturation ($s): valid range 0..100" if (!(($s >= 0) && ($s <= 100)) && ($hash->{LEDTYPE} ne 'White'));
  303. return "Invalid brightness ($v): valid range 0..100" if !(($v >= 0) && ($v <= 100));
  304. if (defined($args[1]))
  305. {
  306. return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
  307. $ramp = $args[1];
  308. }
  309. if (defined($args[2]))
  310. {
  311. return $usage if ($args[2] !~ m/.*[lLqQ].*/); # Flags l=Long way round for transition, q=don't clear queue (add to end)
  312. $flags = $args[2];
  313. }
  314. return MilightDevice_White_Transition($hash, $h, 0, $v, $ramp, $flags) if($hash->{LEDTYPE} eq 'White');
  315. return MilightDevice_HSV_Transition($hash, $h, $s, $v, $ramp, $flags);
  316. }
  317. # Dim to a fixed percentage with transition if requested
  318. elsif ($cmd eq 'dim' || $cmd eq 'brightness')
  319. {
  320. $usage = "Usage: set $name dim <percent(0..100)> [seconds(0..x)] [flags(l=long path|q=don't clear queue)]";
  321. return $usage if (($args[0] !~ /^\d+$/) || ($args[0] < 0) || ($args[0] > 100)); # Decimal value for percent between 0..100
  322. if (defined($args[1]))
  323. {
  324. return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
  325. $ramp = $args[1];
  326. }
  327. if (defined($args[2]))
  328. {
  329. return $usage if ($args[2] !~ m/.*[lLqQ].*/); # Flags l=Long way round for transition, q=don't clear queue (add to end)
  330. $flags = $args[2];
  331. }
  332. return MilightDevice_RGBW_Dim($hash, $args[0], $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
  333. return MilightDevice_White_Dim($hash, $args[0], $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
  334. return MilightDevice_RGB_Dim($hash, $args[0], $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
  335. }
  336. # Set night mode
  337. elsif ($cmd eq 'night')
  338. {
  339. if (defined($args[0]))
  340. {
  341. return "Usage: set $name night";
  342. }
  343. return MilightDevice_RGBW_Night($hash) if ($hash->{LEDTYPE} eq 'RGBW');
  344. return MilightDevice_White_Night($hash) if ($hash->{LEDTYPE} eq 'White');
  345. }
  346. # Set hue
  347. elsif ($cmd eq 'hue')
  348. {
  349. $usage = "Usage: set $name hue <h(0..360)> [seconds(0..x)] [flags(l=long path|q=don't clear queue)]";
  350. return $usage if (($args[0] !~ /^(\d+)$/) || ($args[0] < 0) || ($args[0] > 360));
  351. if (defined($args[1]))
  352. {
  353. return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
  354. $ramp = $args[1];
  355. }
  356. if (defined($args[2]))
  357. {
  358. return $usage if ($args[2] !~ m/.*[lLqQ].*/); # Flags l=Long way round for transition, q=don't clear queue (add to end)
  359. $flags = $args[2];
  360. }
  361. my $sat = ReadingsVal($hash->{NAME}, "saturation", 100);
  362. $sat = 100 if(ReadingsVal($hash->{NAME}, "saturation", 0) == 0);
  363. return MilightDevice_HSV_Transition($hash, $args[0], $sat, ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), $ramp, $flags);
  364. }
  365. # Set color temperature
  366. elsif ($cmd eq 'ct')
  367. {
  368. if (defined($args[0]))
  369. {
  370. return "Usage: set $name ct <3000=Warm..6500=Cool>" if (($args[0] !~ /^\d+$/) || ($args[0] < 2500 || $args[0] > 7000));
  371. }
  372. if (defined($args[1]))
  373. {
  374. return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
  375. $ramp = $args[1];
  376. }
  377. if (defined($args[2]))
  378. {
  379. return $usage if ($args[2] !~ m/.*[lLqQ].*/); # Flags l=Long way round for transition, q=don't clear queue (add to end)
  380. $flags = $args[2];
  381. }
  382. return MilightDevice_White_SetColourTemp($hash, $args[0], $ramp, $flags);
  383. }
  384. # Set RGB value
  385. elsif( $cmd eq "rgb")
  386. {
  387. $usage = "Usage: set $name rgb RRGGBB [seconds(0..x)] [flags(l=long path|q=don't clear queue)]";
  388. return $usage if ($args[0] !~ /^([0-9A-Fa-f]{1,2})([0-9A-Fa-f]{1,2})([0-9A-Fa-f]{1,2})$/);
  389. my( $r, $g, $b ) = (hex($1), hex($2), hex($3)); #change to color.pm?
  390. my( $h, $s, $v ) = Color::rgb2hsv($r/255.0,$g/255.0,$b/255.0);
  391. $h = MilightDevice_roundfunc($h * 360);
  392. $s = MilightDevice_roundfunc($s * 100);
  393. $v = MilightDevice_roundfunc($v * 100);
  394. if (defined($args[1]))
  395. {
  396. return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
  397. $ramp = $args[1];
  398. }
  399. if (defined($args[2]))
  400. {
  401. return $usage if ($args[2] !~ m/.*[lLqQ].*/); # Flags l=Long way round for transition, q=don't clear queue (add to end)
  402. $flags = $args[2];
  403. }
  404. return MilightDevice_HSV_Transition($hash, $h, $s, $v, $ramp, $flags);
  405. }
  406. # Dim up by 1 "dimStep" or by a percentage with transition if requested
  407. elsif ($cmd eq 'dimup')
  408. {
  409. $usage = "Usage: set $name dimup [percent change(0..100)] [seconds(0..x)]";
  410. my $percentChange = MilightDevice_roundfunc(100 / MilightDevice_DimSteps($hash)); # Default one dimStep
  411. if (defined($args[0]))
  412. { # Percent change (0..100%)
  413. return $usage if (($args[0] !~ /^\d+$/) || ($args[0] < 0) || ($args[0] > 100)); # Decimal value for percent between 0..100
  414. $percentChange = $args[0]; # Percentage to change, will be converted in dev specific function
  415. }
  416. if (defined($args[1]))
  417. { # Seconds for transition (0..x)
  418. return $usage if (($args[1] !~ /^\d+$/) && ($args[1] >= 0)); # Decimal value for ramp > 0
  419. $ramp = $args[1];
  420. # Special case, if percent=100 adjust the ramp so it matches the actual amount required.
  421. # Eg. start: 80%. ramp 5seconds. Amount change: 100-80=20. Ramp time req: 20/100*5 = 1second.
  422. if ($percentChange == 100)
  423. {
  424. my $difference = $percentChange - ReadingsVal($hash->{NAME}, "brightness", 0);
  425. $ramp = ($difference/100) * $ramp;
  426. Log3 ($hash, 5, "$hash->{NAME}_Set: dimdown. Adjusted ramp to $ramp");
  427. }
  428. }
  429. my $newBrightness = ReadingsVal($hash->{NAME}, "brightness", 0) + $percentChange;
  430. $newBrightness = 100 if $newBrightness > 100;
  431. return MilightDevice_RGBW_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
  432. return MilightDevice_White_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
  433. return MilightDevice_RGB_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
  434. }
  435. # Dim down by 1 "dimStep" or by a percentage with transition if requested
  436. elsif ($cmd eq 'dimdown')
  437. {
  438. $usage = "Usage: set $name dimdown [percent change(0..100)] [seconds(0..x)]";
  439. my $percentChange = MilightDevice_roundfunc(100 / MilightDevice_DimSteps($hash)); # Default one dimStep
  440. if (defined($args[0]))
  441. { # Percent change (0..100%)
  442. return $usage if (($args[0] !~ /^\d+$/) || ($args[0] < 0) || ($args[0] > 100)); # Decimal value for percent between 0..100
  443. $percentChange = $args[0]; # Percentage to change, will be converted in dev specific function
  444. }
  445. if (defined($args[1]))
  446. { # Seconds for transition (0..x)
  447. return $usage if (($args[1] !~ /^\d+$/) && ($args[1] >= 0)); # Decimal value for ramp > 0
  448. $ramp = $args[1];
  449. # Special case, if percent=100 adjust the ramp so it matches the actual amount required.
  450. # Eg. start: 80%. ramp 5seconds. Amount change: 80. Ramp time req: 80/100*5 = 4second.
  451. if ($percentChange == 100)
  452. {
  453. my $difference = ReadingsVal($hash->{NAME}, "brightness", 0);
  454. $ramp = ($difference/100) * $ramp;
  455. Log3 ($hash, 5, "$hash->{NAME}_Set: dimdown. Adjusted ramp to $ramp");
  456. }
  457. }
  458. my $newBrightness = ReadingsVal($hash->{NAME}, "brightness", 0) - $percentChange;
  459. $newBrightness = 0 if $newBrightness < 0;
  460. return MilightDevice_RGBW_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
  461. return MilightDevice_White_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
  462. return MilightDevice_RGB_Dim($hash, $newBrightness, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
  463. }
  464. elsif ($cmd eq 'saturation')
  465. {
  466. $usage = "Usage: set $name saturation <h(0..100)> [seconds(0..x)] [flags(q=don't clear queue)]";
  467. return $usage if (($args[0] !~ /^\d+$/) || ($args[0] < 0) || ($args[0] > 100));
  468. if (defined($args[1]))
  469. {
  470. return $usage if (($args[1] !~ /^\d+$/) && ($args[1] > 0)); # Decimal value for ramp > 0
  471. $ramp = $args[1];
  472. }
  473. if (defined($args[2]))
  474. {
  475. return $usage if ($args[2] !~ m/.*[qQ].*/); # Flags q=don't clear queue (add to end)
  476. $flags = $args[2];
  477. }
  478. return MilightDevice_HSV_Transition($hash, ReadingsVal($hash->{NAME}, "hue", 0), $args[0], ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), $ramp, $flags);
  479. }
  480. elsif ($cmd eq 'discoModeUp')
  481. {
  482. return MilightDevice_RGBW_DiscoModeStep($hash, 1);
  483. }
  484. elsif ($cmd eq 'discoModeDown')
  485. {
  486. return MilightDevice_RGBW_DiscoModeStep($hash, 0);
  487. }
  488. elsif ($cmd eq 'discoSpeedUp')
  489. {
  490. return MilightDevice_RGBW_DiscoModeSpeed($hash, 1);
  491. }
  492. elsif ($cmd eq 'discoSpeedDown')
  493. {
  494. return MilightDevice_RGBW_DiscoModeSpeed($hash, 0);
  495. }
  496. elsif ($cmd eq 'restorePreviousState')
  497. {
  498. # Restore the previous state (as store in previous* readings)
  499. my ($h, $s, $v) = MilightDevice_HSVFromStr($hash, ReadingsVal($hash->{NAME}, "previousState", MilightDevice_HSVToStr($hash, 0, 0, 0)));
  500. if($v eq 0)
  501. {
  502. if (defined($attr{$name}{defaultRampOff}))
  503. {
  504. $ramp = $attr{$name}{defaultRampOff};
  505. }
  506. return MilightDevice_RGBW_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGBW');
  507. return MilightDevice_White_DimOff($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White' && AttrVal($hash->{NAME}, "dimOffWhite", 0) == 1);
  508. return MilightDevice_White_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'White');
  509. return MilightDevice_RGB_Off($hash, $ramp, $flags) if ($hash->{LEDTYPE} eq 'RGB');
  510. }
  511. MilightDevice_HSV_Transition($hash, $h, $s, $v, 0, '');
  512. return undef;
  513. }
  514. elsif ($cmd eq 'saveState')
  515. {
  516. # Save the hsv state as a string
  517. readingsSingleUpdate($hash, "savedState", MilightDevice_HSVToStr($hash, ReadingsVal($hash->{NAME}, "hue", 0), ReadingsVal($hash->{NAME}, "saturation", 0), ReadingsVal($hash->{NAME}, "brightness", 0)), 1);
  518. return undef;
  519. }
  520. elsif ($cmd eq 'restoreState')
  521. {
  522. my ($h, $s, $v) = MilightDevice_HSVFromStr($hash, ReadingsVal($hash->{NAME}, "savedState", MilightDevice_HSVToStr($hash, 0, 0, 0)));
  523. return MilightDevice_HSV_Transition($hash, $h, $s, $v, 0, '');
  524. }
  525. elsif ($cmd eq 'preset')
  526. {
  527. my $preset = "+";
  528. # Default to "preset +" if no args defined
  529. if (defined($args[0]))
  530. {
  531. return "Usage: set $name preset <0..X|+>" if ($args[0] !~ /^(\d+|\+)$/);
  532. $preset = $args[0];
  533. }
  534. # Get presets, if not defined default to 1 preset 0,0,100.
  535. my @presets = split(/ /, AttrVal($hash->{NAME}, "presets", MilightDevice_HSVToStr($hash, 0, 0, 100)));
  536. # Load the next preset (and loop back to the first) if "+" specified.
  537. if ("$preset" eq "+")
  538. {
  539. $preset = (ReadingsVal($hash->{NAME}, "lastPreset", -1) + 1);
  540. if ($#presets < $preset) { $preset = 0; }
  541. }
  542. return "No preset defined at index $preset" if ($#presets < $preset);
  543. # Update reading and load preset
  544. readingsSingleUpdate($hash, "lastPreset", $preset, 1);
  545. my ($h, $s, $v) = MilightDevice_HSVFromStr($hash, $presets[$preset]);
  546. return MilightDevice_HSV_Transition($hash, $h, $s, $v, 0, '');
  547. }
  548. elsif ($cmd eq 'pair')
  549. {
  550. if (defined($args[0]))
  551. {
  552. return "Usage: set $name pair [seconds(0..X)(default 3)]" if ($args[0] !~ /^\d+$/);
  553. $ramp = $args[0];
  554. }
  555. else { $ramp = 3; } # Default pair for 3 seconds
  556. MilightDevice_CmdQueue_Clear($hash);
  557. return MilightDevice_RGBW_Pair($hash, $ramp) if ($hash->{LEDTYPE} eq 'RGBW');
  558. return MilightDevice_White_Pair($hash, $ramp) if ($hash->{LEDTYPE} eq 'White');
  559. return MilightDevice_RGB_Pair($hash, $ramp) if ($hash->{LEDTYPE} eq 'RGB');
  560. }
  561. elsif ($cmd eq 'unpair')
  562. {
  563. if (defined($args[0]))
  564. {
  565. return "Usage: set $name unpair [seconds(0..X)(default 3)]" if ($args[0] !~ /^\d+$/);
  566. $ramp = $args[0];
  567. }
  568. else { $ramp = 3; } # Default unpair for 3 seconds
  569. MilightDevice_CmdQueue_Clear($hash);
  570. return MilightDevice_RGBW_UnPair($hash, $ramp) if ($hash->{LEDTYPE} eq 'RGBW');
  571. return MilightDevice_White_UnPair($hash, $ramp) if ($hash->{LEDTYPE} eq 'White');
  572. return MilightDevice_RGB_UnPair($hash, $ramp) if ($hash->{LEDTYPE} eq 'RGB');
  573. }
  574. return SetExtensions($hash, $hash->{helper}->{COMMANDSET}, $name, $cmd, @args);
  575. }
  576. #####################################
  577. # Get functions
  578. sub MilightDevice_Get(@)
  579. {
  580. my ($hash, @args) = @_;
  581. my $name = $args[0];
  582. return "$name: get needs at least one parameter" if(@args < 2);
  583. my $cmd= $args[1];
  584. if($cmd eq "rgb" || $cmd eq "RGB") {
  585. return ReadingsVal($name, "rgb", "FFFFFF");
  586. }
  587. elsif($cmd eq "hsv") {
  588. return MilightDevice_HSVToStr($hash, ReadingsVal($hash->{NAME}, "ct", 3000), 0, ReadingsVal($hash->{NAME}, "brightness", 0)) if ($hash->{LEDTYPE} eq 'White');
  589. return MilightDevice_HSVToStr($hash, ReadingsVal($hash->{NAME}, "hue", 0), ReadingsVal($hash->{NAME}, "saturation", 0), ReadingsVal($hash->{NAME}, "brightness", 0));
  590. }
  591. return "Unknown argument $cmd, choose one of rgb:noArg hsv:noArg";
  592. }
  593. #####################################
  594. # Attribute functions
  595. sub MilightDevice_Attr(@)
  596. {
  597. my ($cmd, $device, $attribName, $attribVal) = @_;
  598. my $hash = $defs{$device};
  599. $attribVal = "" if (!defined($attribVal));
  600. Log3 ($hash, 4, "$hash->{NAME}_Attr: Cmd: $cmd; Attribute: $attribName; Value: $attribVal");
  601. if ($cmd eq 'set' && $attribName eq 'gamma')
  602. {
  603. return "gamma is required as numerical value with one decimal (eg. 0.5 or 2.2)" if ($attribVal !~ /^\d*\.\d*$/);
  604. $hash->{helper}->{GAMMAMAP} = MilightDevice_CreateGammaMapping($hash, $attribVal);
  605. if($attribVal ne "1.0")
  606. {
  607. $hash->{helper}->{COMMANDSET} =~ s/dim:slider,0,.*,100/dim:slider,0,1,100/g;
  608. $hash->{helper}->{COMMANDSET} =~ s/brightness:slider,0,.*,100/brightness:slider,0,1,100/g;
  609. }
  610. }
  611. # Allows you to modify the default number of dimSteps for a device
  612. elsif ($cmd eq 'set' && $attribName eq 'dimStep')
  613. {
  614. return "dimStep is required as numerical value [1..100]" if ($attribVal !~ /^\d*$/) || (($attribVal < 1) || ($attribVal > 100));
  615. }
  616. # Allows you to set a default transition time for on/off
  617. elsif ($cmd eq 'set' && (($attribName eq 'defaultRampOn') || ($attribName eq 'defaultRampOff')))
  618. {
  619. return "defaultRampOn/Off is required as numerical value [0..100]" if ($attribVal !~ /^[0-9]*\.?[0-9]*$/) || (($attribVal < 0) || ($attribVal > 100));
  620. }
  621. # List of presets in hsv separated by space. Loaded by set command preset X
  622. elsif ($cmd eq 'set' && ($attribName eq 'presets'))
  623. {
  624. 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)]*$/);
  625. }
  626. elsif ($cmd eq 'set' && $attribName eq 'colorCast')
  627. {
  628. return "colorCast: only works with RGB(W) devices" if ($hash->{LEDTYPE} eq 'White');
  629. my @a = split(',', $attribVal);
  630. my $msg = "colorCast: correction requires red, yellow, green ,cyan, blue, magenta (each in a range of -29 .. 29)";
  631. return $msg unless (@a == 6);
  632. foreach my $tc (@a)
  633. {
  634. return $msg unless ($tc =~ m/^\s*[\-]{0,1}[0-9]+[\.]{0,1}[0-9]*\s*$/g);
  635. return $msg if (abs($tc) >= 30);
  636. }
  637. $hash->{helper}->{COLORMAP} = MilightDevice_ColorConverter($hash, @a);
  638. #MilightDevice_RGB_ColorConverter($hash, @a);
  639. if ($init_done && !(@{$hash->{helper}->{cmdQueue}} > 0))
  640. {
  641. my $hue = $hash->{READINGS}->{hue}->{VAL};
  642. my $sat = $hash->{READINGS}->{saturation}->{VAL};
  643. my $val = $hash->{READINGS}->{brightness}->{VAL};
  644. return MilightDevice_RGBW_SetHSV($hash, $hue, $sat, $val, 1) if ($hash->{LEDTYPE} eq 'RGBW');
  645. return MilightDevice_RGB_SetHSV($hash, $hue, $sat, $val, 1) if ($hash->{LEDTYPE} eq 'RGB');
  646. }
  647. }
  648. elsif ($cmd eq 'set' && $attribName eq 'defaultBrightness')
  649. {
  650. return "defaultBrighness: has to be between ".MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash))." and 100" if ($attribVal < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)) || $attribVal > 100);
  651. }
  652. return undef;
  653. }
  654. #####################################
  655. # Notify functions
  656. sub MilightDevice_Notify(@)
  657. {
  658. my ($hash,$dev) = @_;
  659. return MilightDevice_Restore($hash);
  660. }
  661. #####################################
  662. # Restore HSV settings from readings.
  663. # Called after initialization to synchronise lamp state with fhem.
  664. sub MilightDevice_Restore(@)
  665. {
  666. my ($hash) = @_;
  667. return if ($hash->{INIT});
  668. if ($init_done)
  669. {
  670. return if (AttrVal($hash->{NAME}, "restoreAtStart", 0) == 0);
  671. Log3 ($hash, 4, "$hash->{NAME}_Restore: Restoring saved HSV values");
  672. $hash->{INIT} = 1;
  673. # Initialize device
  674. MilightDevice_Init($hash);
  675. # Clear inProgress flag: MJW Do we still need to do this?
  676. readingsSingleUpdate($hash, "transitionInProgress", 0, 1);
  677. # Default to OFF if not defined
  678. my ($hue, $sat, $val);
  679. $hue = ReadingsVal($hash->{NAME}, "hue", 0);
  680. $sat = ReadingsVal($hash->{NAME}, "saturation", 0);
  681. $val = ReadingsVal($hash->{NAME}, "brightness", 0);
  682. # Restore state
  683. return MilightDevice_RGBW_SetHSV($hash, $hue, $sat, $val, 1) if ($hash->{LEDTYPE} eq 'RGBW');
  684. return MilightDevice_White_SetHSV($hash, $hue, $sat, $val, 1) if ($hash->{LEDTYPE} eq 'White');
  685. return MilightDevice_RGB_SetHSV($hash, $hue, $sat, $val, 1) if ($hash->{LEDTYPE} eq 'RGB');
  686. }
  687. }
  688. ###############################################################################
  689. # device specific controller functions RGB
  690. # LED Strip or bulb, no white, controller V2+. No longer manufactured Jan2014
  691. ###############################################################################
  692. sub MilightDevice_RGB_Pair(@)
  693. {
  694. my ($hash, $numSeconds) = @_;
  695. $numSeconds = 3 if (($numSeconds || 0) == 0);
  696. Log3 ($hash, 4, "$hash->{NAME}_RGB_Pair: RGB LED slot $hash->{SLOT} pair $numSeconds s");
  697. # DISCO SPEED FASTER 0x25 (SYNC/PAIR RGB Bulb within 2 seconds of Wall Switch Power being turned ON)
  698. my $ctrl = "\x25\x00\x55";
  699. for (my $i = 0; $i < $numSeconds; $i++)
  700. {
  701. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 1000, undef);
  702. }
  703. return undef;
  704. }
  705. #####################################
  706. sub MilightDevice_RGB_UnPair(@)
  707. {
  708. my ($hash) = @_;
  709. my $numSeconds = 8;
  710. Log3 ($hash, 4, "$hash->{NAME}_RGB_UnPair: RGB LED slot $hash->{SLOT} unpair $numSeconds s");
  711. # DISCO SPEED FASTER 0x25 (SYNC/PAIR RGB Bulb within 2 seconds of Wall Switch Power being turned ON)
  712. my $ctrl = "\x25\x00\x55";
  713. for (my $i = 0; $i < $numSeconds; $i++)
  714. {
  715. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  716. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  717. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  718. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  719. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  720. }
  721. return undef;
  722. }
  723. #####################################
  724. sub MilightDevice_RGB_On(@)
  725. {
  726. my ($hash, $ramp, $flags) = @_;
  727. my $name = $hash->{NAME};
  728. my $v = AttrVal($hash->{NAME}, "defaultBrightness", 36);
  729. Log3 ($hash, 4, "$hash->{NAME}_RGB_On: RGB slot $hash->{SLOT} set on $ramp");
  730. # Switch on with same brightness it was switched off with, or max if undefined.
  731. if (ReadingsVal($hash->{NAME}, "state", "off") eq "off")
  732. {
  733. $v = ReadingsVal($hash->{NAME}, "brightness_on", AttrVal($hash->{NAME}, "defaultBrightness", 36));
  734. }
  735. else
  736. {
  737. $v = ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36));
  738. }
  739. # When turning on, make sure we request at least minimum dim step.
  740. if ($v < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)))
  741. {
  742. $v = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash));
  743. }
  744. return MilightDevice_RGB_Dim($hash, $v, $ramp, $flags);
  745. }
  746. #####################################
  747. sub MilightDevice_RGB_Off(@)
  748. {
  749. my ($hash, $ramp, $flags) = @_;
  750. my $name = $hash->{NAME};
  751. Log3 ($hash, 4, "$hash->{NAME}_RGB_Off: RGB slot $hash->{SLOT} set off $ramp");
  752. # Store value of brightness before turning off
  753. # "on" will be of the form "on 50" where 50 is current dimlevel
  754. if (ReadingsVal($hash->{NAME}, "state", "off") ne "off")
  755. {
  756. readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), 1);
  757. MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
  758. # Dim down to min brightness then send off command (avoid flicker on turn on)
  759. MilightDevice_RGB_Dim($hash, MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)), $ramp, $flags);
  760. return MilightDevice_RGB_Dim($hash, 0, 0, 'qP');
  761. }
  762. else
  763. {
  764. # If we are already off just send the off command again
  765. return MilightDevice_RGB_Dim($hash, 0, 0, 'P');
  766. }
  767. }
  768. #####################################
  769. sub MilightDevice_RGB_Dim(@)
  770. {
  771. my ($hash, $level, $ramp, $flags) = @_;
  772. my $h = ReadingsVal($hash->{NAME}, "hue", 0);
  773. my $s = ReadingsVal($hash->{NAME}, "saturation", 0);
  774. Log3 ($hash, 4, "$hash->{NAME}_RGB_Dim: RGB slot $hash->{SLOT} dim $level $ramp $flags");
  775. return MilightDevice_HSV_Transition($hash, $h, $s, $level, $ramp, $flags);
  776. }
  777. #####################################
  778. sub MilightDevice_RGB_SetHSV(@)
  779. {
  780. my ($hash, $hue, $sat, $val, $repeat) = @_;
  781. Log3 ($hash, 4, "$hash->{NAME}_RGB_setHSV: RGB slot $hash->{SLOT} set h:$hue, s:$sat, v:$val");
  782. $sat = 100;
  783. MilightDevice_SetHSV_Readings($hash, $hue, $sat, $val);
  784. # apply gamma correction
  785. my $gammaVal = $hash->{helper}->{GAMMAMAP}[$val];
  786. # convert to device specs
  787. my ($cv, $cl, $wl) = MilightDevice_RGB_ColorConverter($hash, $hue, $sat, $gammaVal);
  788. Log3 ($hash, 4, "$hash->{NAME}_RGB_setHSV: RGB slot $hash->{SLOT} set levels: $cv, $cl, $wl");
  789. $repeat = 1 if (!defined($repeat));
  790. # On first load, colorLevel won't be defined, define it.
  791. $hash->{helper}->{colorLevel} = $cl if (!defined($hash->{helper}->{colorLevel}));
  792. # NOTE: All commands sent twice for reliability (it's udp with no feedback)
  793. if (($wl < 1) && ($cl < 1)) # off
  794. {
  795. # if no white or colour switch off
  796. IOWrite($hash, "\x21\x00\x55"); # switch off
  797. $hash->{helper}->{colorLevel} = 0;
  798. }
  799. else # on
  800. {
  801. if (($wl > 0) || ($cl > 0)) # Colour/White on
  802. {
  803. IOWrite($hash, "\x22\x00\x55"); # switch on
  804. IOWrite($hash, "\x20".chr($cv)."\x55"); # set color
  805. if ($repeat eq 1) {
  806. IOWrite($hash, "\x22\x00\x55"); # switch on
  807. IOWrite($hash, "\x20".chr($cv)."\x55"); # set color
  808. }
  809. # cl decrease
  810. if ($hash->{helper}->{colorLevel} > $cl)
  811. {
  812. for (my $i=$hash->{helper}->{colorLevel}; $i > $cl; $i--)
  813. {
  814. IOWrite($hash, "\x24\x00\x55"); # brightness down
  815. $hash->{helper}->{colorLevel} = $i - 1;
  816. }
  817. }
  818. # cl increase
  819. if ($hash->{helper}->{colorLevel} < $cl)
  820. {
  821. for (my $i=$hash->{helper}->{colorLevel}; $i < $cl; $i++)
  822. {
  823. IOWrite($hash, "\x23\x00\x55"); # brightness up
  824. $hash->{helper}->{colorLevel} = $i + 1;
  825. }
  826. }
  827. }
  828. }
  829. return undef;
  830. }
  831. #####################################
  832. sub MilightDevice_RGB_ColorConverter(@)
  833. {
  834. my ($hash, $h, $s, $v) = @_;
  835. my $color = $hash->{helper}->{COLORMAP}[$h % 360];
  836. # there are 0..9 dim level, setup correction
  837. my $valueSpread = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash));
  838. my $totalVal = MilightDevice_roundfunc($v / $valueSpread);
  839. # saturation 100..50: color full, white increase. 50..0 white full, color decrease
  840. my $colorVal = ($s >= 50) ? $totalVal : int(($s / 50 * $totalVal) +0.5);
  841. my $whiteVal = ($s >= 50) ? int(((100-$s) / 50 * $totalVal) +0.5) : $totalVal;
  842. return ($color, $colorVal, $whiteVal);
  843. }
  844. ###############################################################################
  845. # RGBW device specific: Bridge V3+ only.
  846. # Available as GU10, E14, E27, B22, led strip controller...
  847. ###############################################################################
  848. sub MilightDevice_RGBW_Pair(@)
  849. {
  850. my ($hash, $numSeconds) = @_;
  851. $numSeconds = 3 if (($numSeconds || 0) == 0);
  852. Log3 ($hash, 4, "$hash->{NAME}_RGBW_Pair: $hash->{LEDTYPE} at $hash->{CONNECTION}, slot $hash->{SLOT}: pair $numSeconds");
  853. # find my slot and get my group-all-on cmd
  854. my $ctrl = @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd;
  855. # Send on command once a second
  856. for (my $i = 0; $i < $numSeconds; $i++)
  857. {
  858. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 1000, undef);
  859. }
  860. return undef;
  861. }
  862. #####################################
  863. sub MilightDevice_RGBW_UnPair(@)
  864. {
  865. my ($hash, $numSeconds, $releaseFromSlot) = @_;
  866. $numSeconds = 3 if (($numSeconds || 0) == 0);
  867. Log3 ($hash, 4, "$hash->{NAME}_RGBW_UnPair: $hash->{LEDTYPE} at $hash->{CONNECTION}, slot $hash->{SLOT}: unpair $numSeconds");
  868. # find my slot and get my group-all-on cmd
  869. my $ctrl = @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd;
  870. # Send on command every 200ms
  871. for (my $i = 0; $i < $numSeconds; $i++)
  872. {
  873. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  874. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  875. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  876. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  877. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  878. }
  879. return undef;
  880. }
  881. #####################################
  882. sub MilightDevice_RGBW_On(@)
  883. {
  884. my ($hash, $ramp, $flags) = @_;
  885. my $name = $hash->{NAME};
  886. my $v = AttrVal($hash->{NAME}, "defaultBrightness", 36);
  887. Log3 ($hash, 4, "$hash->{NAME}_RGBW_On: Set ON; Ramp: $ramp");
  888. # Switch on with same brightness it was switched off with, or max if undefined.
  889. if (ReadingsVal($hash->{NAME}, "state", "off") eq "off" || ReadingsVal($hash->{NAME}, "state", "off") eq "night")
  890. {
  891. $v = ReadingsVal($hash->{NAME}, "brightness_on", AttrVal($hash->{NAME}, "defaultBrightness", 36));
  892. }
  893. else
  894. {
  895. $v = ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36));
  896. }
  897. # When turning on, make sure we request at least minimum dim step.
  898. if ($v < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)))
  899. {
  900. $v = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash));
  901. }
  902. return MilightDevice_RGBW_Dim($hash, $v, $ramp, $flags);
  903. }
  904. #####################################
  905. sub MilightDevice_RGBW_Off(@)
  906. {
  907. my ($hash, $ramp, $flags) = @_;
  908. my $name = $hash->{NAME};
  909. Log3 ($hash, 4, "$hash->{NAME}_RGBW_Off: Set OFF; Ramp: $ramp");
  910. # Store value of brightness before turning off
  911. # "on" will be of the form "on 50" where 50 is current dimlevel
  912. if (ReadingsVal($hash->{NAME}, "state", "off") ne "off" && ReadingsVal($hash->{NAME}, "state", "off") ne "night")
  913. {
  914. readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", 0), 1);
  915. MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
  916. # Dim down to min brightness then send off command (avoid flicker on turn on)
  917. MilightDevice_RGBW_Dim($hash, MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)), $ramp, $flags);
  918. return MilightDevice_RGBW_Dim($hash, 0, 0, 'qP');
  919. }
  920. else
  921. {
  922. # If we are already off just send the off command again
  923. return MilightDevice_RGBW_Dim($hash, 0, 0, 'P');
  924. }
  925. }
  926. #####################################
  927. sub MilightDevice_RGBW_Night(@)
  928. {
  929. my ($hash) = @_;
  930. my $name = $hash->{NAME};
  931. Log3 ($hash, 4, "$hash->{NAME}_RGBW_Night: Set NIGHTMODE");
  932. if(ReadingsVal($hash->{NAME}, "state", "off") ne "night") {
  933. if (ReadingsVal($hash->{NAME}, "brightness", 0) > 0)
  934. {
  935. readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", 4), 1);
  936. MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
  937. }
  938. IOWrite($hash, @RGBWCmdsOff[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd); # off
  939. }
  940. IOWrite($hash, @RGBWCmdsNt[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd); # night
  941. readingsSingleUpdate($hash, "state", "night", 1);
  942. MilightDevice_BridgeDevices_Update($hash, "state") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
  943. return undef;
  944. }
  945. #####################################
  946. sub MilightDevice_RGBW_Dim(@)
  947. {
  948. my ($hash, $v, $ramp, $flags) = @_;
  949. my $h = ReadingsVal($hash->{NAME}, "hue", 0);
  950. my $s = ReadingsVal($hash->{NAME}, "saturation", 0);
  951. Log3 ($hash, 4, "$hash->{NAME}_RGBW_Dim: Brightness: $v; Ramp: $ramp; Flags: ". $flags || '');
  952. return MilightDevice_HSV_Transition($hash, $h, $s, $v, $ramp, $flags);
  953. }
  954. #####################################
  955. sub MilightDevice_RGBW_SetHSV(@)
  956. {
  957. my ($hash, $hue, $sat, $val, $repeat) = @_;
  958. my ($cl, $wl);
  959. $repeat = 1 if (!defined($repeat));
  960. my $cv = $hash->{helper}->{COLORMAP}[$hue % 360];
  961. #check dim levels to decide wether to change color or brightness first
  962. my $dimup = 0;
  963. $dimup = 1 if($val > ReadingsVal($hash->{NAME}, "brightness", 100));
  964. # apply gamma correction
  965. my $gammaVal = $hash->{helper}->{GAMMAMAP}[$val];
  966. # brightness 2..27 (x02..x1b) | 25 dim levels
  967. my $cf = MilightDevice_roundfunc((($gammaVal / 100) * MilightDevice_DimSteps($hash)) + 1);
  968. if ($sat < 20)
  969. {
  970. $wl = $cf;
  971. $cl = 0;
  972. $sat = 0;
  973. }
  974. else
  975. {
  976. $cl = $cf;
  977. $wl = 0;
  978. $sat = 100;
  979. }
  980. Log3 ($hash, 5, "MilightDevice_RGBW_SetHSV: h:$hue s:$sat v:$val / cv:$cv wl:$wl cl:$cl ");
  981. # Set readings in FHEM
  982. MilightDevice_SetHSV_Readings($hash, $hue, $sat, $val);
  983. # NOTE: All commands sent twice for reliability (it's udp with no feedback)
  984. # Off is shifted to "2" above so check for < 2
  985. if (($wl < 2) && ($cl < 2)) # off
  986. {
  987. IOWrite($hash, @RGBWCmdsOff[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd); # group off
  988. IOWrite($hash, @RGBWCmdsOff[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if ($repeat eq 1); # group off
  989. $hash->{helper}->{whiteLevel} = 0;
  990. $hash->{helper}->{colorLevel} = 0;
  991. }
  992. else # on
  993. {
  994. if ($wl > 0) # white
  995. {
  996. IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($wl > 0) || ($cl > 0)); # group on
  997. IOWrite($hash, @RGBWCmdsWT[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd); # white
  998. IOWrite($hash, $RGBWCmdBri.chr($wl).$RGBWCmdEnd); # brightness
  999. if ($repeat eq 1) {
  1000. IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($wl > 0) || ($cl > 0)); # group on
  1001. IOWrite($hash, @RGBWCmdsWT[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd); # white
  1002. IOWrite($hash, $RGBWCmdBri.chr($wl).$RGBWCmdEnd); # brightness
  1003. }
  1004. }
  1005. elsif ($cl > 0) # color
  1006. {
  1007. IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($wl > 0) || ($cl > 0)); # group on
  1008. if($dimup)
  1009. {
  1010. IOWrite($hash, $RGBWCmdCol.chr($cv).$RGBWCmdEnd); # color
  1011. IOWrite($hash, $RGBWCmdBri.chr($cl).$RGBWCmdEnd); # brightness
  1012. } else {
  1013. IOWrite($hash, $RGBWCmdBri.chr($cl).$RGBWCmdEnd); # brightness
  1014. IOWrite($hash, $RGBWCmdCol.chr($cv).$RGBWCmdEnd); # color
  1015. }
  1016. if ($repeat eq 1) {
  1017. IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($wl > 0) || ($cl > 0)); # group on
  1018. if($dimup)
  1019. {
  1020. IOWrite($hash, $RGBWCmdCol.chr($cv).$RGBWCmdEnd); # color
  1021. IOWrite($hash, $RGBWCmdBri.chr($cl).$RGBWCmdEnd); # brightness
  1022. } else {
  1023. IOWrite($hash, $RGBWCmdBri.chr($cl).$RGBWCmdEnd); # brightness
  1024. IOWrite($hash, $RGBWCmdCol.chr($cv).$RGBWCmdEnd); # color
  1025. }
  1026. }
  1027. }
  1028. $hash->{helper}->{colorValue} = $cv;
  1029. $hash->{helper}->{colorLevel} = $cl;
  1030. $hash->{helper}->{whiteLevel} = $wl;
  1031. }
  1032. return undef;
  1033. }
  1034. ####################################
  1035. # RGB and RGBW types
  1036. sub MilightDevice_RGBW_DiscoModeStep(@)
  1037. {
  1038. my ($hash, $step) = @_;
  1039. MilightDevice_CmdQueue_Clear($hash);
  1040. $step = 0 if ($step < 0);
  1041. $step = 1 if ($step > 1);
  1042. # Set readings in FHEM
  1043. MilightDevice_SetDisco_Readings($hash, $step, ReadingsVal($hash->{NAME}, 'discoSpeed', 5));
  1044. # NOTE: Only sending commands once, because it makes changes on each successive command
  1045. IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($hash->{LEDTYPE} eq 'RGBW')); # group on
  1046. IOWrite($hash, "\x22\x00\x55") if (($hash->{LEDTYPE} eq 'RGB')); # switch on
  1047. if ($step == 1)
  1048. {
  1049. IOWrite($hash, $RGBWCmdDiscoUp."\x00".$RGBWCmdEnd) if (($hash->{LEDTYPE} eq 'RGBW')); # discoMode step up
  1050. IOWrite($hash, "\x27\x00\x55") if (($hash->{LEDTYPE} eq 'RGB')); # discoMode step up
  1051. }
  1052. elsif ($step == 0)
  1053. {
  1054. IOWrite($hash, "\x28\x00\x55") if (($hash->{LEDTYPE} eq 'RGB')); # discoMode step down
  1055. # There is no discoMode step down for RGBW
  1056. }
  1057. return undef;
  1058. }
  1059. #####################################
  1060. # RGB and RGBW types
  1061. sub MilightDevice_RGBW_DiscoModeSpeed(@)
  1062. {
  1063. my ($hash, $speed) = @_;
  1064. MilightDevice_CmdQueue_Clear($hash);
  1065. $speed = 0 if ($speed < 0);
  1066. $speed = 1 if ($speed > 1);
  1067. # Set readings in FHEM
  1068. MilightDevice_SetDisco_Readings($hash, ReadingsVal($hash->{NAME}, 'discoMode', 1), $speed);
  1069. # NOTE: Only sending commands once, because it makes changes on each successive command
  1070. IOWrite($hash, @RGBWCmdsOn[$hash->{SLOTID} -5]."\x00".$RGBWCmdEnd) if (($hash->{LEDTYPE} eq 'RGBW')); # group on
  1071. IOWrite($hash, "\x22\x00\x55") if (($hash->{LEDTYPE} eq 'RGB')); # switch on
  1072. if ($speed == 1)
  1073. {
  1074. IOWrite($hash, $RGBWCmdDiscoInc."\x00".$RGBWCmdEnd) if ($hash->{LEDTYPE} eq 'RGBW'); # discoMode speed up
  1075. IOWrite($hash, "\x25\x00\x55") if ($hash->{LEDTYPE} eq 'RGB'); # discoMode speed up
  1076. }
  1077. elsif ($speed == 0)
  1078. {
  1079. IOWrite($hash, $RGBWCmdDiscoDec."\x00".$RGBWCmdEnd) if ($hash->{LEDTYPE} eq 'RGBW'); # discoMode speed down
  1080. IOWrite($hash, "\x26\x00\x55") if ($hash->{LEDTYPE} eq 'RGB'); # discoMode speed down
  1081. }
  1082. return undef;
  1083. }
  1084. ###############################################################################
  1085. # White device specific: Warm/Cold White with Dim - Bridge V2+
  1086. ###############################################################################
  1087. sub MilightDevice_White_Pair(@)
  1088. {
  1089. my ($hash, $numSeconds) = @_;
  1090. $numSeconds = 3 if (($numSeconds || 0) == 0);
  1091. Log3 ($hash, 4, "$hash->{NAME}_White_Pair: $hash->{LEDTYPE} at $hash->{CONNECTION}, slot $hash->{SLOT}: pair $numSeconds");
  1092. # find my slot and get my group-all-on cmd
  1093. my $ctrl = @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd;
  1094. # Send on command once a second
  1095. for (my $i = 0; $i < $numSeconds; $i++)
  1096. {
  1097. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 1000, undef);
  1098. }
  1099. return undef;
  1100. }
  1101. #####################################
  1102. sub MilightDevice_White_UnPair(@)
  1103. {
  1104. my ($hash, $numSeconds, $releaseFromSlot) = @_;
  1105. $numSeconds = 3 if (($numSeconds || 0) == 0);
  1106. Log3 ($hash, 4, "$hash->{NAME}_White_UnPair: $hash->{LEDTYPE} at $hash->{CONNECTION}, slot $hash->{SLOT}: unpair $numSeconds");
  1107. # find my slot and get my group-all-on cmd
  1108. my $ctrl = @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd;
  1109. for (my $i = 0; $i < $numSeconds; $i++)
  1110. {
  1111. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  1112. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  1113. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  1114. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  1115. MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  1116. }
  1117. return undef;
  1118. }
  1119. #####################################
  1120. sub MilightDevice_White_On(@)
  1121. {
  1122. my ($hash, $ramp, $flags) = @_;
  1123. my $name = $hash->{NAME};
  1124. my $v = AttrVal($hash->{NAME}, "defaultBrightness", 36);
  1125. Log3 ($hash, 4, "$hash->{NAME}_White_On: Set ON: Ramp: $ramp");
  1126. # Switch on with same brightness it was switched off with, or max if undefined.
  1127. if (ReadingsVal($hash->{NAME}, "state", "off") eq "off" || ReadingsVal($hash->{NAME}, "state", "off") eq "night")
  1128. {
  1129. $v = ReadingsVal($hash->{NAME}, "brightness_on", AttrVal($hash->{NAME}, "defaultBrightness", 36));
  1130. }
  1131. else
  1132. {
  1133. $v = ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36));
  1134. }
  1135. # When turning on, make sure we request at least minimum dim step.
  1136. if ($v < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)))
  1137. {
  1138. $v = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash));
  1139. }
  1140. return MilightDevice_White_Dim($hash, $v, $ramp, $flags);
  1141. }
  1142. #####################################
  1143. sub MilightDevice_White_Off(@)
  1144. {
  1145. my ($hash, $ramp, $flags) = @_;
  1146. my $name = $hash->{NAME};
  1147. Log3 ($hash, 4, "$hash->{NAME}_White_Off: Set OFF; Ramp: $ramp");
  1148. # Store value of brightness before turning off
  1149. # "on" will be of the form "on 50" where 50 is current dimlevel
  1150. if (ReadingsVal($hash->{NAME}, "state", "off") ne "off" && ReadingsVal($hash->{NAME}, "state", "off") ne "night")
  1151. {
  1152. if (ReadingsVal($hash->{NAME}, "brightness", 0) > 0)
  1153. {
  1154. readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), 1);
  1155. MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
  1156. }
  1157. # Dim down to min brightness then send off command (avoid flicker on turn on)
  1158. MilightDevice_White_Dim($hash, MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)), $ramp, $flags);
  1159. return MilightDevice_White_Dim($hash, 0, 0, 'q');
  1160. }
  1161. else
  1162. {
  1163. # If we are already off just send the off command again
  1164. return MilightDevice_White_Dim($hash, 0, 0, 'P');
  1165. }
  1166. }
  1167. #####################################
  1168. sub MilightDevice_White_DimOff(@)
  1169. {
  1170. my ($hash, $ramp, $flags) = @_;
  1171. my $name = $hash->{NAME};
  1172. Log3 ($hash, 4, "$hash->{NAME}_White_DimOff: Set OFF; Ramp: $ramp");
  1173. if (ReadingsVal($hash->{NAME}, "brightness", 0) > 0)
  1174. {
  1175. readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), 1);
  1176. MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
  1177. }
  1178. for (my $i = 0; $i < 12; $i++)
  1179. {
  1180. IOWrite($hash, @WhiteCmdBriDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1181. }
  1182. IOWrite($hash, @WhiteCmdsOff[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1183. return MilightDevice_White_Dim($hash, 0, 0, 'q');
  1184. }
  1185. #####################################
  1186. sub MilightDevice_White_DimOn(@)
  1187. {
  1188. my ($hash, $ramp, $flags) = @_;
  1189. my $name = $hash->{NAME};
  1190. Log3 ($hash, 4, "$hash->{NAME}_White_DimOn: Set ON; Ramp: $ramp");
  1191. my $v = AttrVal($hash->{NAME}, "defaultBrightness", 36);
  1192. if (ReadingsVal($hash->{NAME}, "state", "off") eq "off" || ReadingsVal($hash->{NAME}, "state", "off") eq "night")
  1193. {
  1194. $v = ReadingsVal($hash->{NAME}, "brightness_on", AttrVal($hash->{NAME}, "defaultBrightness", 36));
  1195. }
  1196. else
  1197. {
  1198. $v = ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36));
  1199. }
  1200. # When turning on, make sure we request at least minimum dim step.
  1201. if ($v < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)))
  1202. {
  1203. $v = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash));
  1204. }
  1205. MilightDevice_White_Dim($hash, $v, $ramp, $flags);
  1206. for (my $i = 0; $i < ($v/(100/MilightDevice_DimSteps($hash))); $i++)
  1207. {
  1208. #IOWrite($hash, @WhiteCmdBriUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1209. }
  1210. #$ctrl = @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd;
  1211. #MilightDevice_CmdQueue_Add($hash, undef, undef, undef, $ctrl, 200, undef);
  1212. #return MilightDevice_White_Dim($hash, 0, 0, 'q');
  1213. }
  1214. #####################################
  1215. sub MilightDevice_White_Night(@)
  1216. {
  1217. my ($hash) = @_;
  1218. my $name = $hash->{NAME};
  1219. Log3 ($hash, 4, "$hash->{NAME}_White_NIGHT: Set NIGHTMODE");
  1220. if(ReadingsVal($hash->{NAME}, "state", "off") ne "night")
  1221. {
  1222. if (ReadingsVal($hash->{NAME}, "brightness", 0) > 0)
  1223. {
  1224. readingsSingleUpdate($hash, "brightness_on", ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36)), 1);
  1225. MilightDevice_BridgeDevices_Update($hash, "brightness_on") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
  1226. }
  1227. IOWrite($hash, @WhiteCmdsOff[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # off
  1228. }
  1229. IOWrite($hash, @WhiteCmdsNt[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # night
  1230. readingsSingleUpdate($hash, "state", "night", 1);
  1231. MilightDevice_BridgeDevices_Update($hash, "state") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
  1232. return undef;
  1233. }
  1234. #####################################
  1235. sub MilightDevice_White_Dim(@)
  1236. {
  1237. my ($hash, $level, $ramp, $flags) = @_;
  1238. Log3 ($hash, 4, "$hash->{NAME}_White_Dim: Brightness: $level; Ramp: $ramp; Flags: $flags");
  1239. return MilightDevice_HSV_Transition($hash, ReadingsVal($hash->{NAME}, "ct", 3000), 0, $level, $ramp, $flags);
  1240. }
  1241. #####################################
  1242. # $hue is colourTemperature, $val is brightness
  1243. sub MilightDevice_White_SetHSV(@)
  1244. {
  1245. my ($hash, $hue, $sat, $val, $repeat) = @_;
  1246. my $name = $hash->{NAME};
  1247. $repeat = 1 if (!defined($repeat));
  1248. # Validate brightness
  1249. $val = 100 if ($val > 100);
  1250. $val = 0 if ($val < 0);
  1251. # Validate colour temperature
  1252. $hue = 6500 if ($hue > 6500);
  1253. $hue = 3000 if ($hue < 3000);
  1254. my $oldHueStep = MilightDevice_White_ct_hwValue($hash, ReadingsVal($hash->{NAME}, "ct", 6500));
  1255. my $newHueStep = MilightDevice_White_ct_hwValue($hash, $hue);
  1256. $hue = MilightDevice_White_ct_hwValue($hash, $newHueStep);
  1257. # Set colour temperature
  1258. if ($oldHueStep != $newHueStep)
  1259. {
  1260. IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on
  1261. if ($oldHueStep > $newHueStep)
  1262. {
  1263. Log3 ($hash, 4, "$hash->{NAME}_setColourTemp: Decrease from $oldHueStep to $newHueStep");
  1264. for (my $i=$oldHueStep; $i > $newHueStep; $i--)
  1265. {
  1266. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # Cooler (colourtemp up)
  1267. }
  1268. }
  1269. elsif ($oldHueStep < $newHueStep)
  1270. {
  1271. Log3 ($hash, 4, "$hash->{NAME}_setColourTemp: Increase from $oldHueStep to $newHueStep");
  1272. for (my $i=$oldHueStep; $i < $newHueStep; $i++)
  1273. {
  1274. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # Warmer (colourtemp down)
  1275. }
  1276. }
  1277. if(AttrVal($hash->{NAME}, "dimOffWhite", 0) == 1)
  1278. {
  1279. if($newHueStep == 1)
  1280. {
  1281. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1282. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1283. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1284. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1285. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1286. }
  1287. elsif($newHueStep == 11)
  1288. {
  1289. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1290. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1291. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1292. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1293. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1294. }
  1295. }
  1296. }
  1297. # apply gamma correction
  1298. my $gammaVal = $hash->{helper}->{GAMMAMAP}[$val];
  1299. # Calculate brightness hardware value (10 steps / 11 positions for white)
  1300. my $maxWl = (100 / MilightDevice_DimSteps($hash));
  1301. my $wl = MilightDevice_roundfunc($gammaVal / $maxWl);
  1302. # On first load, whiteLevel won't be defined, define it.
  1303. $hash->{helper}->{whiteLevel} = $wl if (!defined($hash->{helper}->{whiteLevel}));
  1304. if (ReadingsVal($hash, "brightness", 0) > 0)
  1305. {
  1306. # We are transitioning from on to off so store new value of wl and stop brightness up/down being triggered below
  1307. $hash->{helper}->{whiteLevel} = $wl;
  1308. }
  1309. # Store new values for colourTemperature and Brightness
  1310. MilightDevice_SetHSV_Readings($hash, $hue, 0, $val);
  1311. # Make sure we actually send off command if we should be off
  1312. if ($wl == 0)
  1313. {
  1314. IOWrite($hash, @WhiteCmdsOff[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group off
  1315. IOWrite($hash, @WhiteCmdsOff[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd) if ($repeat eq 1); # group off
  1316. Log3 ($hash, 4, "$hash->{NAME}_White_setHSV: OFF");
  1317. }
  1318. elsif ($wl == $maxWl)
  1319. {
  1320. IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on
  1321. IOWrite($hash, @WhiteCmdsOnFull[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on full
  1322. if ($repeat eq 1) {
  1323. IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on
  1324. IOWrite($hash, @WhiteCmdsOnFull[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on full
  1325. }
  1326. Log3 ($hash, 4, "$hash->{NAME}_White_setHSV: Full Brightness");
  1327. }
  1328. else
  1329. {
  1330. # Not off or MAX brightness, so make sure we are on
  1331. IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on
  1332. IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd) if ($repeat eq 1); # group on
  1333. if ($hash->{helper}->{whiteLevel} > $wl)
  1334. {
  1335. # Brightness level should be decreased
  1336. Log3 ($hash, 4, "$hash->{NAME}_White_setHSV: Brightness decrease from $hash->{helper}->{whiteLevel} to $wl");
  1337. for (my $i=$hash->{helper}->{whiteLevel}; $i > $wl; $i--)
  1338. {
  1339. IOWrite($hash, @WhiteCmdBriDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # brightness down
  1340. $hash->{helper}->{whiteLevel} = $i - 1;
  1341. }
  1342. }
  1343. elsif ($hash->{helper}->{whiteLevel} < $wl)
  1344. {
  1345. # Brightness level should be increased
  1346. $hash->{helper}->{whiteLevel} = 1 if ($hash->{helper}->{whiteLevel} == 0);
  1347. Log3 ($hash, 4, "$hash->{NAME}_White_setHSV: Brightness increase from $hash->{helper}->{whiteLevel} to $wl");
  1348. for (my $i=$hash->{helper}->{whiteLevel}; $i < $wl; $i++)
  1349. {
  1350. IOWrite($hash, @WhiteCmdBriUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # brightness up
  1351. $hash->{helper}->{whiteLevel} = $i + 1;
  1352. }
  1353. }
  1354. else
  1355. {
  1356. Log3 ($hash, 4, "$hash->{NAME}_White_setHSV: ON");
  1357. }
  1358. }
  1359. $hash->{helper}->{whiteLevel} = $wl;
  1360. return undef;
  1361. }
  1362. #####################################
  1363. sub MilightDevice_White_SetColourTemp(@)
  1364. {
  1365. # $hue is colourTemperature (1-11), $val is brightness (0-100%)
  1366. my ($hash, $hue) = @_;
  1367. my $name = $hash->{NAME};
  1368. MilightDevice_CmdQueue_Clear($hash);
  1369. # Save old value of ct
  1370. my $oldHue = MilightDevice_White_ct_hwValue($hash, ReadingsVal($hash->{NAME}, "ct", 6500));
  1371. # Store new values for colourTemperature and Brightness
  1372. MilightDevice_SetHSV_Readings($hash, $hue, 0, ReadingsVal($hash->{NAME}, "brightness", AttrVal($hash->{NAME}, "defaultBrightness", 36) ) );
  1373. # Validate colourTemperature (11 steps)
  1374. # 3000-6500 (350 per step) Warm-White to Cool White
  1375. # Maps backwards 1=6500 11=3000
  1376. $hue = MilightDevice_White_ct_hwValue($hash, $hue);
  1377. Log3 ($hash, 4, "$hash->{NAME}_setColourTemp: $oldHue to $hue");
  1378. # Set colour temperature
  1379. if ($oldHue != $hue)
  1380. {
  1381. IOWrite($hash, @WhiteCmdsOn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # group on
  1382. if ($oldHue > $hue)
  1383. {
  1384. Log3 ($hash, 4, "$hash->{NAME}_setColourTemp: Decrease from $oldHue to $hue");
  1385. for (my $i=$oldHue; $i > $hue; $i--)
  1386. {
  1387. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # Cooler (colourtemp up)
  1388. }
  1389. }
  1390. elsif ($oldHue < $hue)
  1391. {
  1392. Log3 ($hash, 4, "$hash->{NAME}_setColourTemp: Increase from $oldHue to $hue");
  1393. for (my $i=$oldHue; $i < $hue; $i++)
  1394. {
  1395. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd); # Warmer (colourtemp down)
  1396. }
  1397. }
  1398. }
  1399. if(AttrVal($hash->{NAME}, "dimOffWhite", 0) == 1)
  1400. {
  1401. if($hue == 1)
  1402. {
  1403. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1404. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1405. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1406. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1407. IOWrite($hash, @WhiteCmdColDn[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1408. }
  1409. elsif($hue == 11)
  1410. {
  1411. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1412. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1413. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1414. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1415. IOWrite($hash, @WhiteCmdColUp[$hash->{SLOTID} -1]."\x00".$WhiteCmdEnd);
  1416. }
  1417. }
  1418. return undef;
  1419. }
  1420. # Convert from 3000-6500 colourtemperature to hardware value
  1421. sub MilightDevice_White_ct_hwValue(@)
  1422. {
  1423. my ($hash, $ct) = @_;
  1424. # Couldn't get switch statement to work so using if
  1425. if ($ct == 11) { return 3000; }
  1426. elsif ($ct == 10) { return 3350; }
  1427. elsif ($ct == 9) { return 3700; }
  1428. elsif ($ct == 8) { return 4050; }
  1429. elsif ($ct == 7) { return 4400; }
  1430. elsif ($ct == 6) { return 4750; }
  1431. elsif ($ct == 5) { return 5100; }
  1432. elsif ($ct == 4) { return 5450; }
  1433. elsif ($ct == 3) { return 5800; }
  1434. elsif ($ct == 2) { return 6150; }
  1435. elsif ($ct == 1) { return 6500; }
  1436. elsif ($ct < 3350) { return 11; }
  1437. elsif ($ct < 3700) { return 10; }
  1438. elsif ($ct < 4050) { return 9; }
  1439. elsif ($ct < 4400) { return 8; }
  1440. elsif ($ct < 4750) { return 7; }
  1441. elsif ($ct < 5100) { return 6; }
  1442. elsif ($ct < 5450) { return 5; }
  1443. elsif ($ct < 5800) { return 4; }
  1444. elsif ($ct < 6150) { return 3; }
  1445. elsif ($ct < 6500) { return 2; }
  1446. return 1;
  1447. }
  1448. ###############################################################################
  1449. # Device independent routines
  1450. ###############################################################################
  1451. sub MilightDevice_HSVFromStr(@)
  1452. {
  1453. # Convert HSV values from string in format "h,s,v"
  1454. my ($hash, @args) = @_;
  1455. if ((!defined($args[0])) || ($args[0] !~ /^(\d{1,4}),(\d{1,3}),(\d{1,3})$/))
  1456. {
  1457. Log3 ($hash, 3, "MilightDevice_HSVFromStr: Could not parse h,s,v values from $args[0]");
  1458. return (0, 0, 0);
  1459. }
  1460. Log3 ($hash, 5, "MilightDevice_HSVFromStr: Parsed hsv string: h:$1,s:$2,v:$3");
  1461. return ($1, $2, $3);
  1462. }
  1463. #####################################
  1464. sub MilightDevice_HSVToStr(@)
  1465. {
  1466. # Convert HSV values to string in format "h,s,v"
  1467. my ($hash, $h, $s, $v) = @_;
  1468. $h=0 if (!defined($h));
  1469. $s=0 if (!defined($s));
  1470. $v=0 if (!defined($v));
  1471. Log3 ($hash, 5, "MilightDevice_HSVToStr: h:$h,s:$s,v:$v");
  1472. return "$h,$s,$v";
  1473. }
  1474. #####################################
  1475. sub MilightDevice_ValidateHSV(@)
  1476. {
  1477. # Validate and return valid values for HSV
  1478. my ($hash, $h, $s, $v) = @_;
  1479. $h = 0 if ($h < 0);
  1480. $h = 360 if ($h > 360 && $hash->{LEDTYPE} ne 'White');
  1481. $h = 3000 if ($h < 3000 && $hash->{LEDTYPE} eq 'White');
  1482. $h = 6500 if ($h > 6500);
  1483. $s = 0 if ($s < 0);
  1484. $s = 100 if ($s > 100);
  1485. $v = 0 if ($v < 0);
  1486. $v = 100 if ($v > 100);
  1487. return ($h, $s, $v);
  1488. }
  1489. #####################################
  1490. # Return number of steps for each type of bulb
  1491. # White: 11 steps (step = 9.1)
  1492. # RGB: 9 steps (step = 11)
  1493. # RGBW: 25 steps (step = 4)
  1494. sub MilightDevice_DimSteps(@)
  1495. {
  1496. my ($hash) = @_;
  1497. return AttrVal($hash->{NAME}, "dimStep", 25) if ($hash->{LEDTYPE} eq 'RGBW');
  1498. return AttrVal($hash->{NAME}, "dimStep", 11) if ($hash->{LEDTYPE} eq 'White');
  1499. return AttrVal($hash->{NAME}, "dimStep", 9) if ($hash->{LEDTYPE} eq 'RGB');
  1500. }
  1501. #####################################
  1502. # Return number of colour steps for each type of bulb
  1503. # White: 11 steps (this is colour temperature)
  1504. # RGB: 255 steps (not mentioned in API?)
  1505. # RGBW: 255 steps
  1506. sub MilightDevice_ColourSteps(@)
  1507. {
  1508. my ($hash) = @_;
  1509. return 255 if ($hash->{LEDTYPE} eq 'RGBW');
  1510. return 11 if ($hash->{LEDTYPE} eq 'White');
  1511. return 255 if ($hash->{LEDTYPE} eq 'RGB');
  1512. }
  1513. #####################################
  1514. # dispatcher
  1515. sub MilightDevice_SetHSV(@)
  1516. {
  1517. my ($hash, $hue, $sat, $val, $repeat) = @_;
  1518. MilightDevice_RGBW_SetHSV($hash, $hue, $sat, $val, $repeat) if ($hash->{LEDTYPE} eq 'RGBW');
  1519. MilightDevice_White_SetHSV($hash, $hue, $sat, $val, $repeat) if ($hash->{LEDTYPE} eq 'White');
  1520. MilightDevice_RGB_SetHSV($hash, $hue, $sat, $val, $repeat) if ($hash->{LEDTYPE} eq 'RGB');
  1521. return undef;
  1522. }
  1523. #####################################
  1524. sub MilightDevice_HSV_Transition(@)
  1525. {
  1526. my ($hash, $hue, $sat, $val, $ramp, $flags) = @_;
  1527. my ($hueFrom, $satFrom, $valFrom, $timeFrom)=0;
  1528. # Clear command queue if flag "q" not specified
  1529. MilightDevice_CmdQueue_Clear($hash) if ($flags !~ m/.*[qQ].*/);
  1530. # if queue in progress set start vals to last cached hsv target, else set start to actual hsv
  1531. if (@{$hash->{helper}->{cmdQueue}} > 0)
  1532. {
  1533. $hueFrom = $hash->{helper}->{targetHue};
  1534. $satFrom = $hash->{helper}->{targetSat};
  1535. $valFrom = $hash->{helper}->{targetVal};
  1536. $timeFrom = $hash->{helper}->{targetTime};
  1537. $hueFrom = 0 if(!defined($hueFrom));
  1538. $satFrom = 100 if(!defined($satFrom));
  1539. $valFrom = 0 if(!defined($valFrom));
  1540. $timeFrom = 0 if(!defined($timeFrom));
  1541. Log3 ($hash, 5, "$hash->{NAME}_HSV_Transition: Prepare Start (cached): $hueFrom,$satFrom,$valFrom@".$timeFrom);
  1542. }
  1543. else
  1544. {
  1545. $hueFrom = ReadingsVal($hash->{NAME}, "hue", 0);
  1546. $satFrom = ReadingsVal($hash->{NAME}, "saturation", 0);
  1547. $valFrom = ReadingsVal($hash->{NAME}, "brightness", 0);
  1548. $timeFrom = gettimeofday();
  1549. Log3 ($hash, 5, "$hash->{NAME}_HSV_Transition: Prepare Start (actual): $hueFrom,$satFrom,$valFrom@".$timeFrom);
  1550. if ($flags !~ m/.*[pP].*/ and ($hash->{LEDTYPE} eq 'RGB') || ($hash->{LEDTYPE} eq 'RGBW'))
  1551. {
  1552. # Store previous state if different to requested state
  1553. if (($hueFrom != $hue) || ($satFrom != $sat) || ($valFrom != $val))
  1554. {
  1555. readingsSingleUpdate($hash, "previousState", MilightDevice_HSVToStr($hash, $hueFrom, $satFrom, $valFrom),1);
  1556. }
  1557. }
  1558. }
  1559. Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Current: $hueFrom,$satFrom,$valFrom");
  1560. Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Set: $hue,$sat,$val; Ramp: $ramp; Flags: ". $flags);
  1561. # Store target vales
  1562. $hash->{helper}->{targetHue} = $hue;
  1563. $hash->{helper}->{targetSat} = $sat;
  1564. $hash->{helper}->{targetVal} = $val;
  1565. # if there is no ramp we don't need transition
  1566. if (($ramp || 0) == 0)
  1567. {
  1568. Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Set: $hue,$sat,$val; No Ramp");
  1569. $hash->{helper}->{targetTime} = $timeFrom;
  1570. return MilightDevice_CmdQueue_Add($hash, $hue, $sat, $val, undef, 0, undef);
  1571. }
  1572. # calculate the left and right turn length based
  1573. # startAngle +360 -endAngle % 360 = counter clock
  1574. # endAngle +360 -startAngle % 360 = clockwise
  1575. my $hueTo = ($hue == 0) ? 1 : ($hue == 360) ? 359 : $hue;
  1576. my $fadeLeft = ($hueFrom + 360 - $hue) % 360;
  1577. my $fadeRight = ($hue + 360 - $hueFrom) % 360;
  1578. my $direction = ($fadeLeft <=> $fadeRight); # -1 = counterclock, +1 = clockwise
  1579. $direction = ($direction == 0)?1:$direction; # in dupt cw
  1580. Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Colour rotation: cc(-1): $fadeLeft, cw(+1): $fadeRight; Shortest: $direction;");
  1581. $direction *= -1 if ($flags =~ m/.*[lL].*/); # reverse if long path desired (flag l or L is set)
  1582. my $rotation = ($direction == 1)?$fadeRight:$fadeLeft; # angle of hue rotation in based on flags
  1583. my $sFade = abs($sat - $satFrom);
  1584. my $vFade = abs($val - $valFrom);
  1585. # No transition, so set immediately and ignore ramp setting
  1586. if ($rotation == 0 && $sFade == 0 && $vFade == 0)
  1587. {
  1588. Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Unchanged. Set: $hue,$sat,$val; Ignoring Ramp");
  1589. $hash->{helper}->{targetTime} = $timeFrom;
  1590. return MilightDevice_CmdQueue_Add($hash, $hue, $sat, $val, undef, 0, undef);
  1591. }
  1592. my ($stepWidth, $steps, $maxSteps, $hueToSet, $hueStep, $satToSet, $satStep, $valToSet, $valStep);
  1593. # Calculate stepWidth
  1594. if ($rotation >= ($sFade || $vFade))
  1595. {
  1596. # Transition based on Hue, so max steps = colourSteps
  1597. $stepWidth = ($ramp * 1000 / $rotation); # how long is one step (set hsv) in ms based on hue
  1598. $maxSteps = MilightDevice_ColourSteps($hash);
  1599. }
  1600. elsif ($sFade >= ($rotation || $vFade))
  1601. {
  1602. # Transition based on Saturation, so max steps = 2 (devices don't support sat, so set to 0 or 100 mostly)
  1603. $stepWidth = ($ramp * 1000 / $sFade); # how long is one step (set hsv) in ms based on sat
  1604. $maxSteps = 2;
  1605. }
  1606. else
  1607. {
  1608. # Transition based on Brightness, so max steps = dimSteps
  1609. $stepWidth = ($ramp * 1000 / $vFade); # how long is one step (set hsv) in ms based on val
  1610. $maxSteps = MilightDevice_DimSteps($hash);
  1611. }
  1612. # Calculate number of steps, limit to max number (no point running more if they are the same)
  1613. $steps = int($ramp * 1000 / $stepWidth);
  1614. if ($steps > $maxSteps)
  1615. {
  1616. $stepWidth *= ($steps/$maxSteps);
  1617. $steps = $maxSteps;
  1618. }
  1619. # Calculate number of steps, limit to max number (no point running more if they are the same)
  1620. $steps = int($ramp * 1000 / $stepWidth);
  1621. if ($steps > $maxSteps)
  1622. {
  1623. $stepWidth *= ($steps/$maxSteps);
  1624. $steps = $maxSteps;
  1625. }
  1626. # Calculate minimum stepWidth
  1627. # Min bridge delay as specified by Bridge * 3 (eg. 100*3=300ms).
  1628. # On average min 3 commands need to be sent per step (eg. Group On; Mode; Brightness;) so this gets it approximately right
  1629. my $minStepWidth = $hash->{IODev}->{INTERVAL} * 3;
  1630. $stepWidth = $minStepWidth if ($stepWidth < $minStepWidth); # Make sure we have min stepWidth
  1631. Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Steps: $steps; Step Interval(ms): $stepWidth");
  1632. # Calculate hue step
  1633. $hueToSet = $hueFrom; # Start at current hue
  1634. $hueStep = $rotation / $steps * $direction;
  1635. # Calculate saturation step
  1636. $satToSet = $satFrom; # Start at current saturation
  1637. $satStep = ($sat - $satFrom) / $steps;
  1638. # Calculate brightness step
  1639. $valToSet = $valFrom; # Start at current brightness
  1640. $valStep = ($val - $valFrom) / $steps;
  1641. for (my $i=1; $i <= $steps; $i++)
  1642. {
  1643. $hueToSet += $hueStep; # Increment new hue by step (negative step decrements)
  1644. $hueToSet -= 360 if ($hueToSet > 360); #handle turn over zero
  1645. $hueToSet += 360 if ($hueToSet < 0);
  1646. $satToSet += $satStep; # Increment new saturation by step (negative step decrements)
  1647. $valToSet += $valStep; # Increment new brightness by step (negative step decrements)
  1648. Log3 ($hash, 4, "$hash->{NAME}_HSV_Transition: Add to Queue: h:".($hueToSet).", s:".($satToSet).", v:".($valToSet)." ($i/$steps)");
  1649. MilightDevice_CmdQueue_Add($hash, MilightDevice_roundfunc($hueToSet), MilightDevice_roundfunc($satToSet), MilightDevice_roundfunc($valToSet), undef, $stepWidth, $timeFrom + (($i-1) * $stepWidth / 1000) );
  1650. }
  1651. # Set target time for completion of sequence.
  1652. # This may be slightly higher than what was requested since $stepWidth > minDelay (($steps * $stepWidth) > $ramp)
  1653. $hash->{helper}->{targetTime} = $timeFrom + ($steps * $stepWidth / 1000);
  1654. Log3 ($hash, 5, "$hash->{NAME}_HSV_Transition: TargetTime: $hash->{helper}->{targetTime}");
  1655. return undef;
  1656. }
  1657. #####################################
  1658. sub MilightDevice_White_Transition(@)
  1659. {
  1660. my ($hash, $ct, $sat, $val, $ramp, $flags) = @_;
  1661. my ($ctFrom, $valFrom, $timeFrom)=0;
  1662. # Clear command queue if flag "q" not specified
  1663. MilightDevice_CmdQueue_Clear($hash) if ($flags !~ m/.*[qQ].*/);
  1664. # if queue in progress set start vals to last cached hsv target, else set start to actual hsv
  1665. if (@{$hash->{helper}->{cmdQueue}} > 0)
  1666. {
  1667. $ctFrom = $hash->{helper}->{targetCt};
  1668. $valFrom = $hash->{helper}->{targetVal};
  1669. $timeFrom = $hash->{helper}->{targetTime};
  1670. $ctFrom = 3000 if(!defined($ctFrom));
  1671. $valFrom = 0 if(!defined($valFrom));
  1672. $timeFrom = 0 if(!defined($timeFrom));
  1673. Log3 ($hash, 5, "$hash->{NAME}_White_Transition: Prepare Start (cached): $ctFrom,$valFrom@".$timeFrom);
  1674. }
  1675. else
  1676. {
  1677. $ctFrom = ReadingsVal($hash->{NAME}, "ct", 3000);
  1678. $valFrom = ReadingsVal($hash->{NAME}, "brightness", 0);
  1679. $timeFrom = gettimeofday();
  1680. Log3 ($hash, 5, "$hash->{NAME}_White_Transition: Prepare Start (actual): $ctFrom,$valFrom@".$timeFrom);
  1681. if ($flags !~ m/.*[pP].*/)
  1682. {
  1683. # Store previous state if different to requested state
  1684. if (($ctFrom != $ct) || ($valFrom != $val))
  1685. {
  1686. readingsSingleUpdate($hash, "previousState", MilightDevice_HSVToStr($hash, $ctFrom, 0, $valFrom),1);
  1687. }
  1688. }
  1689. }
  1690. Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Current: $ctFrom,$valFrom");
  1691. Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Set: $ct,$val; Ramp: $ramp; Flags: ". $flags);
  1692. # Store target vales
  1693. $hash->{helper}->{targetCt} = $ct;
  1694. $hash->{helper}->{targetVal} = $val;
  1695. # if there is no ramp we don't need transition
  1696. if (($ramp || 0) == 0)
  1697. {
  1698. Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Set: $ct,$val; No Ramp");
  1699. $hash->{helper}->{targetTime} = $timeFrom;
  1700. return MilightDevice_CmdQueue_Add($hash, $ct, 0, $val, undef, 0, undef);
  1701. }
  1702. my $vFade = abs($val - $valFrom);
  1703. my $ctFade = abs($ct - $ctFrom);
  1704. Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Colour temp: $ctFade, Brightness: $vFade;");
  1705. # No transition, so set immediately and ignore ramp setting
  1706. if ($ctFade == 0 && $vFade == 0)
  1707. {
  1708. Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Unchanged. Set: $ct,0,$val; Ignoring Ramp");
  1709. $hash->{helper}->{targetTime} = $timeFrom;
  1710. return MilightDevice_CmdQueue_Add($hash, $ct, 0, $val, undef, 0, undef);
  1711. }
  1712. my ($stepWidth, $steps, $maxSteps, $ctToSet, $ctStep, $valToSet, $valStep);
  1713. # Calculate stepWidth
  1714. if ($ctFade >= $vFade)
  1715. {
  1716. # Transition based on ct, so max steps = colourSteps
  1717. $stepWidth = ($ramp * 1000 / $ctFade /100); # how long is one step (set hsv) in ms based on ct
  1718. $maxSteps = MilightDevice_ColourSteps($hash);
  1719. }
  1720. else
  1721. {
  1722. # Transition based on Brightness, so max steps = dimSteps
  1723. $stepWidth = ($ramp * 1000 / $vFade); # how long is one step (set hsv) in ms based on val
  1724. $maxSteps = MilightDevice_DimSteps($hash);
  1725. }
  1726. # Calculate number of steps, limit to max number (no point running more if they are the same)
  1727. $steps = int($ramp * 1000 / $stepWidth);
  1728. if ($steps > $maxSteps)
  1729. {
  1730. $stepWidth *= ($steps/$maxSteps);
  1731. $steps = $maxSteps;
  1732. }
  1733. # Calculate number of steps, limit to max number (no point running more if they are the same)
  1734. $steps = int($ramp * 1000 / $stepWidth);
  1735. if ($steps > $maxSteps)
  1736. {
  1737. $stepWidth *= ($steps/$maxSteps);
  1738. $steps = $maxSteps;
  1739. }
  1740. # Calculate minimum stepWidth
  1741. # Min bridge delay as specified by Bridge * 3 (eg. 100*3=300ms).
  1742. # On average min 3 commands need to be sent per step (eg. Group On; Mode; Brightness;) so this gets it approximately right
  1743. my $minStepWidth = $hash->{IODev}->{INTERVAL} * 3;
  1744. $stepWidth = $minStepWidth if ($stepWidth < $minStepWidth); # Make sure we have min stepWidth
  1745. Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Steps: $steps; Step Interval(ms): $stepWidth");
  1746. # Calculate hue step
  1747. $ctToSet = $ctFrom; # Start at current hue
  1748. $ctStep = ($ct - $ctFrom) / $steps;
  1749. # Calculate brightness step
  1750. $valToSet = $valFrom; # Start at current brightness
  1751. $valStep = ($val - $valFrom) / $steps;
  1752. for (my $i=1; $i <= $steps; $i++)
  1753. {
  1754. $ctToSet += $ctStep; # Increment new hue by step (negative step decrements)
  1755. $valToSet += $valStep; # Increment new brightness by step (negative step decrements)
  1756. Log3 ($hash, 4, "$hash->{NAME}_White_Transition: Add to Queue: ct:".(int($ctToSet)).", s:0, v:".(int($valToSet))." ($i/$steps)");
  1757. MilightDevice_CmdQueue_Add($hash, MilightDevice_roundfunc($ctToSet), 0, MilightDevice_roundfunc($valToSet), undef, $stepWidth, $timeFrom + (($i-1) * $stepWidth / 1000) );
  1758. }
  1759. # Set target time for completion of sequence.
  1760. # This may be slightly higher than what was requested since $stepWidth > minDelay (($steps * $stepWidth) > $ramp)
  1761. $hash->{helper}->{targetTime} = $timeFrom + ($steps * $stepWidth / 1000);
  1762. Log3 ($hash, 5, "$hash->{NAME}_White_Transition: TargetTime: $hash->{helper}->{targetTime}");
  1763. return undef;
  1764. }
  1765. #####################################
  1766. sub MilightDevice_SetHSV_Readings(@)
  1767. {
  1768. my ($hash, $hue, $sat, $val, $val_on) = @_;
  1769. my $name = $hash->{NAME};
  1770. readingsBeginUpdate($hash); # Start update readings
  1771. # Store requested values
  1772. readingsBulkUpdate($hash, "brightness", $val);
  1773. # Store on brightness so we can turn on at a set brightness
  1774. readingsBulkUpdate($hash, "brightness_on", $val_on);
  1775. if (($hash->{LEDTYPE} eq 'RGB') || ($hash->{LEDTYPE} eq 'RGBW'))
  1776. {
  1777. # Store previous state if different to requested state
  1778. my $prevHue = ReadingsVal($hash->{NAME}, "hue", 0);
  1779. my $prevSat = ReadingsVal($hash->{NAME}, "saturation", 0);
  1780. my $prevVal = ReadingsVal($hash->{NAME}, "brightness", 0);
  1781. if (($prevHue != $hue) || ($prevSat != $sat) || ($prevVal != $val))
  1782. {
  1783. readingsBulkUpdate($hash, "previousState", MilightDevice_HSVToStr($hash, $prevHue, $prevSat, $prevVal)) if ReadingsVal($hash->{NAME}, "transitionInProgress", 1) eq 0;
  1784. }
  1785. readingsBulkUpdate($hash, "saturation", $sat);
  1786. readingsBulkUpdate($hash, "hue", $hue);
  1787. readingsBulkUpdate($hash, "hsv", MilightDevice_HSVToStr($hash, $hue,$sat,$val));
  1788. # Calc RGB values from HSV
  1789. my ($r,$g,$b) = Color::hsv2rgb($hue/360.0,$sat/100.0,$val/100.0);
  1790. $r *=255; $g *=255; $b*=255;
  1791. # Store values
  1792. readingsBulkUpdate($hash, "rgb", sprintf("%02X%02X%02X",$r,$g,$b)); # Int to Hex convert
  1793. readingsBulkUpdate($hash, "discoMode", 0);
  1794. readingsBulkUpdate($hash, "discoSpeed", 0);
  1795. }
  1796. elsif ($hash->{LEDTYPE} eq 'White')
  1797. {
  1798. readingsBulkUpdate($hash, "ct", $hue);
  1799. readingsBulkUpdate($hash, "hsv", MilightDevice_HSVToStr($hash, $hue,0,$val));
  1800. }
  1801. readingsBulkUpdate($hash, "state", "on $val") if ($val > 1);
  1802. readingsBulkUpdate($hash, "state", "off") if ($val < 2);
  1803. readingsEndUpdate($hash, 1);
  1804. MilightDevice_BridgeDevices_Update($hash, "bulk") if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1);
  1805. }
  1806. #####################################
  1807. sub MilightDevice_SetDisco_Readings(@)
  1808. {
  1809. # Step/Speed can be "1" or "0" when active
  1810. my ($hash, $step, $speed) = @_;
  1811. my $name = $hash->{NAME};
  1812. if (($hash->{LEDTYPE} eq 'RGBW') || ($hash->{LEDTYPE} eq 'RGB'))
  1813. {
  1814. my $discoMode = ReadingsVal($hash->{NAME}, "discoMode", 0);
  1815. $discoMode = "on";
  1816. my $discoSpeed = ReadingsVal($hash->{NAME}, "discoSpeed", 5);
  1817. $discoSpeed = "-" if ($speed == 0);
  1818. $discoSpeed = "+" if ($speed == 1);
  1819. readingsBeginUpdate($hash);
  1820. readingsBulkUpdate($hash, "discoMode", $step);
  1821. readingsBulkUpdate($hash, "discoSpeed", $speed);
  1822. readingsEndUpdate($hash, 1);
  1823. if ($hash->{SLOT} eq 'A' && AttrVal($hash->{NAME}, "updateGroupDevices", 0) == 1)
  1824. {
  1825. MilightDevice_BridgeDevices_Update($hash, "discoMode");
  1826. MilightDevice_BridgeDevices_Update($hash, "discoSpeed");
  1827. }
  1828. }
  1829. }
  1830. #####################################
  1831. sub MilightDevice_ColorConverter(@)
  1832. {
  1833. my ($hash, $cr, $cy, $cg, $cc, $cb, $cm) = @_;
  1834. my @colorMap;
  1835. my $adjRed = 0 + $cr;
  1836. my $adjYellow = 60 + $cy;
  1837. my $adjGreen = 120 + $cg;
  1838. my $adjCyan = 180 + $cc;
  1839. my $adjBlue = 240 + $cb;
  1840. my $adjLilac = 300 + $cm;
  1841. my $devRed = 176; # (0xB0)
  1842. #my $devYellow = 128; # (0x80)
  1843. my $devYellow = 144;
  1844. my $devGreen = 96; # (0x60)
  1845. #my $devCyan = 48; # (0x30)
  1846. my $devCyan = 56;
  1847. my $devBlue = 16; # (0x10)
  1848. my $devLilac = 224; # (0xE0)
  1849. my $i= 360;
  1850. # red to yellow
  1851. $adjRed += 360 if ($adjRed < 0); # in case of negative adjustment
  1852. $devRed += 256 if ($devRed < $devYellow);
  1853. $adjYellow += 360 if ($adjYellow < $adjRed);
  1854. for ($i = $adjRed; $i <= $adjYellow; $i++)
  1855. {
  1856. $colorMap[$i % 360] = ($devRed - int((($devRed - $devYellow) / ($adjYellow - $adjRed) * ($i - $adjRed)) +0.5)) % 255;
  1857. Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
  1858. }
  1859. #yellow to green
  1860. $devYellow += 256 if ($devYellow < $devGreen);
  1861. $adjGreen += 360 if ($adjGreen < $adjYellow);
  1862. for ($i = $adjYellow; $i <= $adjGreen; $i++)
  1863. {
  1864. $colorMap[$i % 360] = ($devYellow - int((($devYellow - $devGreen) / ($adjGreen - $adjYellow) * ($i - $adjYellow)) +0.5)) % 255;
  1865. Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
  1866. }
  1867. #green to cyan
  1868. $devGreen += 256 if ($devGreen < $devCyan);
  1869. $adjCyan += 360 if ($adjCyan < $adjGreen);
  1870. for ($i = $adjGreen; $i <= $adjCyan; $i++)
  1871. {
  1872. $colorMap[$i % 360] = ($devGreen - int((($devGreen - $devCyan) / ($adjCyan - $adjGreen) * ($i - $adjGreen)) +0.5)) % 255;
  1873. Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
  1874. }
  1875. #cyan to blue
  1876. $devCyan += 256 if ($devCyan < $devCyan);
  1877. $adjBlue += 360 if ($adjBlue < $adjCyan);
  1878. for ($i = $adjCyan; $i <= $adjBlue; $i++)
  1879. {
  1880. $colorMap[$i % 360] = ($devCyan - int((($devCyan - $devBlue) / ($adjBlue - $adjCyan) * ($i - $adjCyan)) +0.5)) % 255;
  1881. Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
  1882. }
  1883. #blue to lilac
  1884. $devBlue += 256 if ($devBlue < $devLilac);
  1885. $adjLilac += 360 if ($adjLilac < $adjBlue);
  1886. for ($i = $adjBlue; $i <= $adjLilac; $i++)
  1887. {
  1888. $colorMap[$i % 360] = ($devBlue - int((($devBlue - $devLilac) / ($adjLilac - $adjBlue) * ($i- $adjBlue)) +0.5)) % 255;
  1889. Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
  1890. }
  1891. #lilac to red
  1892. $devLilac += 256 if ($devLilac < $devRed);
  1893. $adjRed += 360 if ($adjRed < $adjLilac);
  1894. for ($i = $adjLilac; $i <= $adjRed; $i++)
  1895. {
  1896. $colorMap[$i % 360] = ($devLilac - int((($devLilac - $devRed) / ($adjRed - $adjLilac) * ($i - $adjLilac)) +0.5)) % 255;
  1897. Log3 ($hash, 5, "$hash->{NAME}_ColorConverter: create colormap h: ".($i % 360)." d: ".$colorMap[$i % 360]);
  1898. }
  1899. return \@colorMap;
  1900. }
  1901. #####################################
  1902. sub MilightDevice_CreateGammaMapping(@)
  1903. {
  1904. my ($hash, $gamma) = @_;
  1905. #original wifilight gamma was inverted
  1906. $gamma = 1/$gamma;
  1907. my @gammaMap;
  1908. $gammaMap[0] = 0;
  1909. for (my $i = 1; $i <= 100; $i += 1)
  1910. {
  1911. my $correction = ($i / 100) ** (1 / $gamma);
  1912. $gammaMap[$i] = $correction * 100;
  1913. $gammaMap[$i] = MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)) if($gammaMap[$i] < MilightDevice_roundfunc(100/MilightDevice_DimSteps($hash)));
  1914. Log3 ($hash, 5, "$hash->{NAME} create gammamap v-in: ".$i.", v-out: $gammaMap[$i]");
  1915. }
  1916. return \@gammaMap;
  1917. }
  1918. ###############################################################################
  1919. # Device Command Queue
  1920. # Triggers commands for long running transitions for a device
  1921. ###############################################################################
  1922. sub MilightDevice_CmdQueue_Add(@)
  1923. {
  1924. my ($hash, $hue, $sat, $val, $ctrl, $delay, $targetTime) = @_;
  1925. my $cmd;
  1926. # Validate input
  1927. ($hue, $sat, $val) = MilightDevice_ValidateHSV($hash, $hue, $sat, $val);
  1928. $cmd->{hue} = $hue;
  1929. $cmd->{sat} = $sat;
  1930. $cmd->{val} = $val;
  1931. $cmd->{ctrl} = $ctrl;
  1932. $cmd->{delay} = $delay;
  1933. $cmd->{targetTime} = $targetTime;
  1934. $cmd->{inProgess} = 0;
  1935. push @{$hash->{helper}->{cmdQueue}}, $cmd;
  1936. my $hexStr = defined($cmd->{ctrl})? unpack("H*", $cmd->{ctrl} || '') : "";
  1937. 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}});
  1938. my $actualCmd = @{$hash->{helper}->{cmdQueue}}[0];
  1939. # sender busy ?
  1940. if(defined($actualCmd))
  1941. {
  1942. return undef if (ref($actualCmd) ne 'HASH');
  1943. return undef if (!defined($actualCmd->{inProgess}));
  1944. return undef if (($actualCmd->{inProgess} || 0) == 1);
  1945. }
  1946. return MilightDevice_CmdQueue_Exec($hash);
  1947. }
  1948. #####################################
  1949. sub MilightDevice_CmdQueue_Exec(@)
  1950. {
  1951. my ($hash) = @_;
  1952. RemoveInternalTimer($hash);
  1953. #if ($hash->{IODev}->{STATE} ne "ok" && $hash->{IODev}->{STATE} ne "Initialized") {
  1954. # InternalTimer(gettimeofday() + 60, "MilightDevice_CmdQueue_Exec", $hash, 0);
  1955. # return undef;
  1956. #}
  1957. my $actualCmd = @{$hash->{helper}->{cmdQueue}}[0];
  1958. # transmission complete, remove
  1959. shift @{$hash->{helper}->{cmdQueue}} if ($actualCmd->{inProgess});
  1960. # next in queue
  1961. $actualCmd = @{$hash->{helper}->{cmdQueue}}[0];
  1962. my $nextCmd = @{$hash->{helper}->{cmdQueue}}[1];
  1963. # return if no more elements in queue
  1964. if (!defined($actualCmd->{inProgess}))
  1965. {
  1966. readingsSingleUpdate($hash, "transitionInProgress", 0, 1); # Clear transitionInProgress flag
  1967. return undef;
  1968. }
  1969. readingsSingleUpdate($hash, "transitionInProgress", 1, 1); # Set transitionInProgress flag
  1970. # 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
  1971. while (defined($nextCmd->{targetTime}) && ($nextCmd->{targetTime} < gettimeofday()) && !$actualCmd->{ctrl})
  1972. {
  1973. shift @{$hash->{helper}->{cmdQueue}};
  1974. $actualCmd = @{$hash->{helper}->{cmdQueue}}[0];
  1975. $nextCmd = @{$hash->{helper}->{cmdQueue}}[1];
  1976. Log3 ($hash, 4, "$hash->{NAME}_CmdQueue_Exec: Drop Frame. Queue Length: ".@{$hash->{helper}->{cmdQueue}});
  1977. }
  1978. Log3 ($hash, 5, "$hash->{NAME}_CmdQueue_Exec: Dropper Delay: ".($actualCmd->{targetTime} - gettimeofday())) if (defined($actualCmd->{targetTime}));
  1979. # set hsv or if a device ctrl command is scheduled: send it and ignore hsv
  1980. if ($actualCmd->{ctrl})
  1981. {
  1982. my $dbgStr = unpack("H*", $actualCmd->{ctrl});
  1983. Log3 ($hash, 4, "$hash->{NAME}_CmdQueue_Exec: Send ctrl: $dbgStr; Queue Length: ".@{$hash->{helper}->{cmdQueue}});
  1984. IOWrite($hash, $actualCmd->{ctrl});
  1985. }
  1986. else
  1987. {
  1988. # Send an HSV Command.
  1989. my $repeat = 0;
  1990. # If queue length < 2 (ie. 1) we are last command so repeat sending (takes twice as long...)
  1991. $repeat = 1 if (@{$hash->{helper}->{cmdQueue}} < 2);
  1992. MilightDevice_SetHSV($hash, $actualCmd->{hue}, $actualCmd->{sat}, $actualCmd->{val}, $repeat);
  1993. }
  1994. $actualCmd->{inProgess} = 1;
  1995. my $next = defined($nextCmd->{targetTime})?$nextCmd->{targetTime}:gettimeofday() + ($actualCmd->{delay} / 1000);
  1996. Log3 ($hash, 5, "$hash->{NAME}_CmdQueue_Exec: Next Exec: $next");
  1997. InternalTimer($next, "MilightDevice_CmdQueue_Exec", $hash, 0);
  1998. return undef;
  1999. }
  2000. #####################################
  2001. sub MilightDevice_CmdQueue_Clear(@)
  2002. {
  2003. my ($hash) = @_;
  2004. Log3 ($hash, 4, "$hash->{NAME}_CmdQueue_Clear");
  2005. RemoveInternalTimer($hash);
  2006. #if ($hash->{IODev}->{STATE} ne "ok" && $hash->{IODev}->{STATE} ne "Initialized") {
  2007. # InternalTimer(gettimeofday() + 60, "MilightDevice_CmdQueue_Exec", $hash, 0);
  2008. # return undef;
  2009. #}
  2010. readingsSingleUpdate($hash, "transitionInProgress", 0, 1); # Clear inProgress flag
  2011. #foreach my $args (keys %intAt)
  2012. #{
  2013. # if (($intAt{$args}{ARG} eq $hash) && ($intAt{$args}{FN} eq 'MilightDevice_CmdQueue_Exec'))
  2014. # {
  2015. # Log3 ($hash, 5, "$hash->{NAME}_CmdQueue_Clear: Remove timer at: ".$intAt{$args}{TRIGGERTIME});
  2016. # delete($intAt{$args});
  2017. # }
  2018. #}
  2019. $hash->{helper}->{cmdQueue} = [];
  2020. return undef;
  2021. }
  2022. #####################################
  2023. sub MilightDevice_BridgeDevices_Update(@)
  2024. {
  2025. my ($hash, $attr) = @_;
  2026. my @rdlist = ($attr);
  2027. if($attr eq 'bulk')
  2028. {
  2029. @rdlist = ("state","brightness","brightness_on","hue", "saturation", "hsv", "rgb", "discoMode", "discoSpeed")if ($hash->{LEDTYPE} eq 'RGBW');
  2030. @rdlist = ("state","brightness","brightness_on","ct")if ($hash->{LEDTYPE} eq 'White');
  2031. }
  2032. my $sl = 5;
  2033. $sl = 1 if ($hash->{LEDTYPE} eq 'White');
  2034. for (my $i = 0; $i < 4; $i++)
  2035. {
  2036. my $devname = $hash->{IODev}->{$sl+$i}->{NAME};
  2037. next if (!defined($defs{$devname}));
  2038. my $device = $defs{$devname};
  2039. $devname = "?" if(!defined($devname));
  2040. readingsSingleUpdate($device, "transitionInProgress", 1, 1);
  2041. readingsBeginUpdate($device);
  2042. foreach my $rdname (@rdlist)
  2043. {
  2044. if (exists ($device->{READINGS}{$rdname}))
  2045. {
  2046. readingsBulkUpdate($device, $rdname, $hash->{READINGS}{$rdname}{VAL}, 1);
  2047. Log3 ($hash, 4, $rdname.": ".$device->{READINGS}{$rdname}{VAL}." for ".$devname);
  2048. }
  2049. }
  2050. readingsEndUpdate($device, 1);
  2051. readingsSingleUpdate($device, "transitionInProgress", 0, 1);
  2052. }
  2053. return undef;
  2054. }
  2055. sub MilightDevice_roundfunc($) {
  2056. my ($number) = @_;
  2057. return sprintf("%.0f", $number);
  2058. #return Math::Round::round($number);
  2059. }
  2060. 1;
  2061. =pod
  2062. =item device
  2063. =item summary This module represents a Milight LED Bulb or LED strip controller
  2064. =begin html
  2065. <a name="MilightDevice"></a>
  2066. <h3>MilightDevice</h3>
  2067. <ul>
  2068. <p>This module represents a Milight LED Bulb or LED strip controller. It is controlled by a <a href="#MilightBridge">MilightBridge</a>.</p>
  2069. <p>The Milight system is sold under various brands around the world including "LimitlessLED, EasyBulb, AppLamp"</p>
  2070. <p>The API documentation is available here: <a href="http://www.limitlessled.com/dev/">http://www.limitlessled.com/dev/</a></p>
  2071. <p>Requires perl module Math::Round</p>
  2072. <a name="MilightDevice_define"></a>
  2073. <p><b>Define</b></p>
  2074. <ul>
  2075. <p><code>define &lt;name&gt; MilightDevice &lt;devType(RGB|RGBW|White)&gt; &lt;IODev&gt; &lt;slot&gt;</code></p>
  2076. <p>Specifies the Milight device.<br/>
  2077. &lt;devType&gt; One of RGB, RGBW, White depending on your device.<br/>
  2078. &lt;IODev&gt; The <a href="#MilightBridge">MilightBridge</a> which the device is paired with.<br/>
  2079. &lt;slot&gt; The slot on the <a href="#MilightBridge">MilightBridge</a> that the device is paired with or 'A' to group all slots.</p>
  2080. </ul>
  2081. <a name="MilightDevice_readings"></a>
  2082. <p><b>Readings</b></p>
  2083. <ul>
  2084. <li>
  2085. <b>state</b><br/>
  2086. [on xxx|off|night]: Current state of the device / night mode (xxx = 0-100%).
  2087. </li>
  2088. <li>
  2089. <b>brightness</b><br/>
  2090. [0-100]: Current brightness level in %.
  2091. </li>
  2092. <li>
  2093. <b>brightness_on</b><br/>
  2094. [0-100]: The brightness level before the off command was sent. This allows the light to turn back on to the last brightness level.
  2095. </li>
  2096. <li>
  2097. <b>rgb</b><br/>
  2098. [FFFFFF]: HEX value for RGB.
  2099. </li>
  2100. <li>
  2101. <b>previousState</b><br/>
  2102. [hsv]: hsv value before last change. Can be used with <b>restorePreviousState</b> set command.
  2103. </li>
  2104. <li>
  2105. <b>savedState</b><br/>
  2106. [hsv]: hsv value that was saved using <b>saveState</b> set function
  2107. </li>
  2108. <li>
  2109. <b>hue</b><br/>
  2110. [0-360]: Current hue value.
  2111. </li>
  2112. <li>
  2113. <b>saturation</b><br/>
  2114. [0-100]: Current saturation value.
  2115. </li>
  2116. <li>
  2117. <b>transitionInProgress</b><br/>
  2118. [0|1]: Set to 1 if a transition is currently in progress for this device (eg. fade).
  2119. </li>
  2120. <li>
  2121. <b>discoMode</b><br/>
  2122. [0|1]: 1 if discoMode is enabled, 0 otherwise.
  2123. </li>
  2124. <li>
  2125. <b>discoSpeed</b><br/>
  2126. [0|1]: 1 if discoSpeed is increased, 0 if decreased. Does not mean much for RGBW
  2127. </li>
  2128. <li>
  2129. <b>lastPreset</b><br/>
  2130. [0..X]: Last selected preset.
  2131. </li>
  2132. <li>
  2133. <b>ct</b><br/>
  2134. [1-10]: Current colour temperature (3000=Warm,6500=Cold) for White devices.
  2135. </li>
  2136. </ul>
  2137. <a name="MilightDevice_set"></a>
  2138. <p><b>Set</b></p>
  2139. <ul>
  2140. <li>
  2141. <b>on &lt;ramp_time (seconds)></b>
  2142. </li>
  2143. <li>
  2144. <b>off &lt;ramp_time (seconds)></b>
  2145. </li>
  2146. <li>
  2147. <b>toggle</b>
  2148. </li>
  2149. <li>
  2150. <b>night</b>
  2151. </li>
  2152. <li>
  2153. <b>dim &lt;percent(0..100)&gt; [seconds(0..x)] [flags(l=long path|q=don't clear queue)]</b><br/>
  2154. Will be replaced by <i>brightness</i> at some point
  2155. </li>
  2156. <li>
  2157. <b>dimup &lt;percent change(0..100)&gt; [seconds(0..x)]</b><br/>
  2158. Special case: If percent change=100, seconds will be adjusted for actual change to go from current brightness.
  2159. </li>
  2160. <li>
  2161. <b>dimdown &lt;percent change(0..100)&gt; [seconds(0..x)]</b><br/>
  2162. Special case: If percent change=100, seconds will be adjusted for actual change to go from current brightness.
  2163. </li>
  2164. <li>
  2165. <b>pair</b><br/>
  2166. May not work properly. Sometimes it is necessary to use a remote to clear pairing first.
  2167. </li>
  2168. <li>
  2169. <b>unpair</b><br/>
  2170. May not work properly. Sometimes it is necessary to use a remote to clear pairing first.
  2171. </li>
  2172. <li>
  2173. <b>restorePreviousState</b><br/>
  2174. Set device to previous hsv state as stored in <b>previousState</b> reading.
  2175. </li>
  2176. <li>
  2177. <b>saveState</b><br/>
  2178. Save current hsv state to <b>savedState</b> reading.
  2179. </li>
  2180. <li>
  2181. <b>restoreState</b><br/>
  2182. Set device to saved hsv state as stored in <b>savedState</b> reading.
  2183. </li>
  2184. <li>
  2185. <b>preset (0..X|+)</b><br/>
  2186. Load preset (+ for next preset).
  2187. </li>
  2188. <li>
  2189. <b>hsv &lt;h(0..360)&gt;,&lt;s(0..100)&gt;,&lt;v(0..100)&gt; [seconds(0..x)] [flags(l=long path|q=don't clear queue)]</b><br/>
  2190. Set hsv value directly
  2191. </li>
  2192. <li>
  2193. <b>rgb RRGGBB [seconds(0..x)] [flags(l=long path|q=don't clear queue)]</b><br/>
  2194. Set rgb value directly or using colorpicker.
  2195. </li>
  2196. <li>
  2197. <b>hue &lt;(0..360)&gt; [seconds(0..x)] [flags(l=long path|q=don't clear queue)]</b><br/>
  2198. Set hue value.
  2199. </li>
  2200. <li>
  2201. <b>saturation &lt;s(0..100)&gt; [seconds(0..x)] [flags(q=don't clear queue)]</b><br/>
  2202. Set saturation value directly
  2203. </li>
  2204. <li>
  2205. <b>discoModeUp</b><br/>
  2206. Next disco Mode setting (for RGB and RGBW).
  2207. </li>
  2208. <li>
  2209. <b>discoModeDown</b><br/>
  2210. Previous disco Mode setting (for RGB).
  2211. </li>
  2212. <li>
  2213. <b>discoSpeedUp</b><br/>
  2214. Increase speed of disco mode (for RGB and RGBW).
  2215. </li>
  2216. <li>
  2217. <b>discoSpeedDown</b><br/>
  2218. Decrease speed of disco mode (for RGB and RGBW).
  2219. </li>
  2220. <li>
  2221. <b>ct &lt;3000-6500&gt;</b><br/>
  2222. Colour temperature 3000=Warm White,6500=Cold White (10 steps) (for White devices only).
  2223. </li>
  2224. <li>
  2225. <a href="#setExtensions"> set extensions</a> are supported.
  2226. </li>
  2227. </ul>
  2228. <a name="MilightDevice_get"></a>
  2229. <p><b>Get</b></p>
  2230. <ul>
  2231. <li>
  2232. <b>rgb</b>
  2233. </li>
  2234. <li>
  2235. <b>hsv</b>
  2236. </li>
  2237. </ul>
  2238. <a name="MilightDevice_attr"></a>
  2239. <p><b>Attributes</b></p>
  2240. <ul>
  2241. <li>
  2242. <b>dimStep</b><br/>
  2243. Allows you to modify the default dimStep if required.
  2244. </li>
  2245. <li>
  2246. <b>defaultRampOn</b><br/>
  2247. Set the default ramp time if not specified for on command.
  2248. </li>
  2249. <li>
  2250. <b>defaultRampOff</b><br/>
  2251. Set the default ramp time if not specified for off command.
  2252. </li>
  2253. <li>
  2254. <b>presets</b><br/>
  2255. List of hsv presets separated by spaces (eg 0,0,100 9,0,50).
  2256. </li>
  2257. <li>
  2258. <b>colorCast</b><br/>
  2259. Color shift values for red,yellow,green,cyan,blue,magenta (-29..29) for HSV color correction (eg 0,5,10,-5,0,0)
  2260. </li>
  2261. <li>
  2262. <b>gamma</b><br/>
  2263. Set gamma correction value for device (eg 0.8)
  2264. </li>
  2265. <li>
  2266. <b>dimOffWhite</b><br/>
  2267. Use a different switching logic for White bulbs to better handle packet loss.
  2268. </li>
  2269. <li>
  2270. <b>updateGroupDevices</b><br/>
  2271. Update the state of single devices switched with slot 'A'.
  2272. </li>
  2273. <li>
  2274. <b>restoreAtStart</b><br/>
  2275. Restore the state of devices at startup. Default 0 for slot 'A', 1 otherwise.
  2276. </li>
  2277. <li>
  2278. <b>defaultBrightness</b><br/>
  2279. Set the default brightness if not known. (Default: 36)
  2280. </li>
  2281. </ul>
  2282. </ul>
  2283. =end html
  2284. =cut