##############################################
# $Id: 32_YeeLight.pm 2017-07-30 thaliondrambor $

##### special thanks to herrmannj for permission to use code from 32_WifiLight.pm
##### currently in use: WifiLight_HSV2RGB
#####
##### also thanks to f-zappa for testing and reporting bugs

# TODO
# light functions: timer, schedules
# scenes
# autodetect model
# software bridge (UDP): search request

# help
# attributes: adjust brightness level

# versions
# 00 start
# 01 added dimup, dimdown, colortemperature, toggle
# 02 changed colortemperature to ct, added hex input for rgb,
#	 added attribute defaultramp, added hue and sat
# 03 added reading of notification messages -> no more active reading of bulb status
#    added setting name, added start_cf and stop_cf
#	 added scene (sunrise, sunset, happy_birthday)
#	 added saving default status
# 04 added reopen, added queues for sended commands and received answers to match them 
# 05 improved control of parameters for colorflow
# 06 fixed a bug with reading answers, when 2 or more arrive at the same time
#	 strings are evaluated before de-/encoding, so that no more crashes should occure
#	 because of invalid json strings
#	 added queue for errors
#	 added commands raw and flush
# 07 small bugfix, changed "rgb 000000" to "off"
# 08 added blink
# 09 added software bridge support
# 10 added timeout, keepAlive, SetExtensions (on-for-timer, off-for-timer, intervals)
#    bugfixes
# 11 bugfix for ramp = 0 with defaultramp set
#    bugfix for bug with scene command
# 12 added attribut userScene[0-9], started to add model of lamp
# 13 added devStateIcon, webCmd and widgetOverride, changed from JSON::XS to JSON
# 14 fixed some errors
# 15 optional parameter for define: model
# 16 fixed bugs blocking fhem when a lamp is not reachable, not able to set keepAlive to 0

# verbose level
# 0: quit
# 1: error
# 2: warning
# 3: user command
# 4: 1st technical level (detailed internal reporting)
# 5: 2nd technical level (full internal reporting)
#
# installed CPAN modules: JSON

package main;

use strict;
use warnings;
use POSIX;
use Socket;
use JSON;
use SetExtensions;
use Color;

sub
YeeLight_Initialize
{
	my ($hash) = @_;
	  
	require "$attr{global}{modpath}/FHEM/DevIo.pm";

	$hash->{ReadFn}			= "YeeLight_Read";
	$hash->{NotifyFn}		= "YeeLight_Notify";
	$hash->{DefFn}			= "YeeLight_Define";
	$hash->{UndefFn}		= "YeeLight_Undef";
	$hash->{ShutdownFn}		= "YeeLight_Shutdown";
	$hash->{SetFn}			= "YeeLight_Set";
	$hash->{ReadyFn}		= "YeeLight_Ready";
	$hash->{AttrFn}			= "YeeLight_Attr";
	$hash->{AttrList}		= ""
		."defaultramp "
		."updateIP:0,1 "
		."timeout "
		."keepAlive "
		."userScene[0-9] "
		."$readingFnAttributes";
	
	# Comm from Bridge
	$hash->{Match}			= "^.*";
	
	$hash->{ParseFn}		= "YeeLightBridge_Parse";

	FHEM_colorpickerInit();
	
	return undef;
}

sub
YeeLight_Define
{
	my ($hash, $def) = @_;
	my @a = split("[ \t][ \t]*", $def); 
	my $name = $a[0];
	
	return "wrong syntax: define [NAME] YeeLight [IP] <MODEL>" if (@a != 3 ) && (@a != 4) && (@a != 5);
	return "wrong input for IP-address: 'xxx.xxx.xxx.xxx' (0 <= xxx <= 255)" if (!IsValidIP($a[2]));
	return "wrong input for model: choose one of color, stripe, mono, desklamp, ceiling1" if (@a >= 4) && ($a[3] ne "color") && ($a[3] ne "stripe") && ($a[3] ne "mono") && ($a[3] ne "desklamp") && ($a[3] ne "ceiling1");
	
	DevIo_CloseDev($hash);
	
	$hash->{NAME} 				= $name;
	$hash->{HOST}				= $a[2];
    $hash->{PORT}				= 55443;
    $hash->{PROTO}				= 1;
	$hash->{NOTIFYDEV}			= "global";
	$hash->{MODEL}				= $a[3] if defined($a[3]);
	$hash->{ID}					= $a[2] if (!$a[4]);
	$hash->{ID}					= $a[4] if ($a[4]);
	
	Log3 $name, 3, "YeeLight $name defined at $hash->{HOST}:$hash->{PORT}";
	
	$attr{$name}{room}			= "YeeLight" if !defined( $attr{$name}{room});
        
	my $dev = $hash->{HOST}.':'.$hash->{PORT};
	$hash->{DeviceName} = $dev;
	$hash->{DEF}		= $hash->{HOST};
	$hash->{DEF}		= $hash->{HOST} . " " . $hash->{MODEL} if defined($hash->{MODEL});
			
	DevIo_OpenDev($hash, 0,, sub(){ 
		my ($hash, $err) = @_;
		Log3 $name, 2, "$name: $err" if($err);
		return "$err" if($err);
	});
	
	YeeLight_GetUpdate($hash);
	
	my @ansQue = ();
	$hash->{helper}->{AnsQue} = \@ansQue;
	
	my @errQue = ();
	$hash->{helper}->{ErrQue} = \@errQue;
	
	my %sendQue;
	$hash->{helper}->{SendQue} = \%sendQue;
	
	$modules{YeeLight}{defptr}{$hash->{ID}} = $hash;
	
	my $model;
	$model = $hash->{MODEL} if defined($hash->{MODEL});
	$attr{$name}{devStateIcon}	= '{my $power=ReadingsVal($name,"power","off");my $mode=ReadingsVal($name,"color_mode","RGB");if($power eq "off"){Color::devStateIcon($name,"rgb","rgb","power");}else{if($mode eq "RGB"){Color::devStateIcon($name,"rgb","rgb","bright");}elsif($mode eq "color temperature"){Color::devStateIcon($name,"rgb",undef,"bright");}}}' if (!defined($attr{$name}{devStateIcon}) && (!defined($model) || ($model eq "color") || ($model eq "stripe")));
	$attr{$name}{webCmd}		= 'rgb:bright:ct:rgb ffffff:rgb ff0000:rgb 00ff00:rgb 0000ff:on:off'					if (!defined($attr{$name}{webCmd}) && (!defined($model) || ($model eq "color") || ($model eq "stripe")));
	$attr{$name}{widgetOverride}= 'bright:colorpicker,BRI,0,1,100 ct:colorpicker,CT,1700,10,6500 rgb:colorpicker,RGB'	if (!defined($attr{$name}{widgetOverride}) && (!defined($model) || ($model eq "color") || ($model eq "stripe")));
	$attr{$name}{devStateIcon}	= '{my $power=ReadingsVal($name,"power","off");if($power eq "off"){Color::devStateIcon($name,"dimmer",undef,"power");}else{Color::devStateIcon($name,"dimmer",undef,"bright")}}' if (!defined($attr{$name}{devStateIcon}) && defined($model) && ($model eq "mono" || $model eq "desklamp"));
	$attr{$name}{webCmd}		= 'bright:on:off'																		if (!defined($attr{$name}{webCmd}) && defined($model) && ($model eq "mono" || $model eq "desklamp"));
	$attr{$name}{widgetOverride}= 'bright:colorpicker,BRI,0,1,100'														if (!defined($attr{$name}{widgetOverride}) && defined($model) && ($model eq "mono" || $model eq "desklamp"));
	$attr{$name}{devStateIcon}	= '{my $power=ReadingsVal($name,"power","off");if($power eq "off"){Color::devStateIcon($name,"dimmer",undef,"power");}else{Color::devStateIcon($name,"dimmer",undef,"bright")}}' if (!defined($attr{$name}{devStateIcon}) && defined($model) && ($model eq "ceiling1"));
	$attr{$name}{webCmd}		= 'bright:on:off:ct'																	if (!defined($attr{$name}{webCmd}) && defined($model) && ($model eq "ceiling1"));
	$attr{$name}{widgetOverride}= 'bright:colorpicker,BRI,0,1,100 ct:colorpicker,CT,2700,10,6500'						if (!defined($attr{$name}{widgetOverride}) && defined($model) && ($model eq "ceiling1"));
	my $list = "";
	# Commands supported by every yeelight
	$list .= "on ";
	$list .= "off ";
	$list .= "toggle ";
	$list .= "on-for-timer ";
	$list .= "off-for-timer ";
	$list .= "intervals ";
	$list .= "bright ";
	$list .= "dimup ";
	$list .= "dimdown ";
	$list .= "name ";
	$list .= "default:noArg ";
	$list .= "reopen:noArg ";
	$list .= "statusrequest:noArg ";
	# Commands supported by color and led stripe
	if (($model eq "color") || ($model eq "stripe") || !defined($model))
	{
		$list .= "hsv ";
		$list .= "hue ";
		$list .= "sat ";
		$list .= "rgb ";
		$list .= "color ";
		$list .= "start_cf ";
		$list .= "stop_cf ";
		$list .= "scene ";
		$list .= "circlecolor:noArg ";
		$list .= "blink ";
	}
	# Commands supported by ceiling1
	if (($model eq "ceiling1") || !defined($model))
	{
		$list .= "active_mode:daylight,nightlight ";
	}
	# Commands supported by color, led stripe and ceiling1
	if (($model eq "color") || ($model eq "stripe") || ($model eq "ceiling1") || !defined($model))
	{
		$list .= "ct ";
	}
	
	$hash->{helper}->{CommandSet} = $list;
	
	return undef;
}

sub
YeeLight_Bridge_GetID
{
	my ($hash)	= @_;
	my $curID	= 0;
	if (defined($modules{YeeLightBridge}{defptr}))
	{
		my $bHash 	= $modules{YeeLightBridge}{defptr};
		$bHash->{msgID} = 0 if !defined($bHash->{msgID});
		$bHash->{msgID} = 0 if defined($bHash->{msgID}) && ($bHash->{msgID} >= 9999);
		$bHash->{msgID}++;
		$curID = $bHash->{msgID}
	}
	else
	{
		$data{YeeLight}{msgID} = 0 if !defined($data{YeeLight}{msgID});
		$data{YeeLight}{msgID} = 0 if defined($data{YeeLight}{msgID}) && ($data{YeeLight}{msgID} >= 9999);
		$data{YeeLight}{msgID}++;
		$curID = $data{YeeLight}{msgID}
	}
	return $curID;
}

sub
YeeLight_Notify
{
	my ($own_hash,$dev_hash) = @_;
	my $ownName = $own_hash->{NAME};
	return undef if (IsDisabled($ownName));
	
	my $devName = $dev_hash->{NAME};
	my $events = deviceEvents($dev_hash,1);
	return undef if(!$events);
 
	foreach my $event (@{$events})
	{
		if ($devName eq "global" && $event eq "INITIALIZED")
		{
			InternalTimer(gettimeofday() + 1, "YeeLight_GetUpdate", $own_hash);
		}
		elsif ($devName eq "global" && $event =~ /^DELETEATTR $ownName keepAlive/)
		{
			RemoveInternalTimer($own_hash,"YeeLight_GetUpdate");
			Log3 $ownName, 3, "$ownName: stopped sending periodic status requests.";
		}
		elsif ($devName eq "global" && $event =~ /^ATTR $ownName keepAlive/ )
		{
			YeeLight_GetUpdate($own_hash);
			Log3 $ownName, 3, "$ownName: started sending periodic status requests.";
		}
	}
}

sub
YeeLight_GetUpdate
{
	my ($hash) = @_;
	my $name = $hash->{NAME};
	
	Log3 $name, 4, "$name: GetUpdate";
	YeeLight_StatusRequest($hash);
	
	my $bHash = $modules{YeeLightBridge}{defptr};
	my $bName = $bHash->{NAME};
	my $keepAlive	= 0;
	$keepAlive		= $attr{$bName}{keepAlive} if (defined($bName) && defined($attr{$bName}{keepAlive}));
	$keepAlive		= $attr{$name}{keepAlive} if defined($attr{$name}{keepAlive});
	
	if ($keepAlive != 0)
	{
		InternalTimer(gettimeofday() + $keepAlive,"YeeLight_GetUpdate",$hash);
	}
	
	return undef;
}

sub
YeeLight_Shutdown
{
	my ($hash, $arg) = @_;
    my $name = $hash->{NAME};
	
	Log3 $name, 4, "$name: shutdown $name";
	DevIo_CloseDev($hash);
	RemoveInternalTimer($hash);
	
	Log3 $name, 4, "$name: do flush because of shutdown";
	YeeLight_Flush($hash);
    
    return undef;
}

sub
YeeLight_Undef
{
	my ($hash, $arg) = @_;
    my $name = $hash->{NAME};
	my $id = $hash->{ID};

	Log3 $name, 3, "YeeLight: undefined $name";
	DevIo_CloseDev($hash);
	RemoveInternalTimer($hash);
	
	Log3 $name, 4, "$name: do flush because of undefine";
	YeeLight_Flush($hash);
	
	delete($modules{YeeLight}{defptr}{$id}) if defined($modules{YeeLight}{defptr}{$id});
    
    return undef;
}

sub
YeeLight_Set
{
    
    my ($hash, $name, $cmd, @val) = @_;
	my $model = $hash->{MODEL};
	
	my $list = $hash->{helper}->{CommandSet};

	if (lc $cmd eq 'on'
		|| lc $cmd eq 'off'
		|| lc $cmd eq 'toggle'
		|| lc $cmd eq 'on-for-timer'
		|| lc $cmd eq 'off-for-timer'
		|| lc $cmd eq 'intervals'
		|| lc $cmd eq 'bright'
		|| lc $cmd eq 'dimup'
		|| lc $cmd eq 'dimdown'
		|| lc $cmd eq 'name'
		|| lc $cmd eq 'default'
		|| lc $cmd eq 'reopen'
		|| lc $cmd eq 'statusrequest'
		|| lc $cmd eq 'raw'
		|| lc $cmd eq 'flush'
		|| ((lc $cmd eq 'ct'
		&& (!defined($model))
		|| $model eq "stripe"
		|| $model eq "color"
		|| $model eq "ceiling1"))
		|| ((lc $cmd eq 'hue'
		|| lc $cmd eq 'sat'
		|| lc $cmd eq 'rgb'
		|| lc $cmd eq 'color'
		|| lc $cmd eq 'hsv'
		|| lc $cmd eq 'start_cf'
		|| lc $cmd eq 'stop_cf'
		|| lc $cmd eq 'scene'
		|| lc $cmd eq 'circlecolor'
		|| lc $cmd eq 'blink')
		&& (!defined($model)
		|| $model eq "stripe"
		|| $model eq "color"))
		|| (lc $cmd eq "active_mode"
		&& (!defined($model)
		|| $model eq "ceiling1" )))
	{
	    Log3 $name, 3, "YeeLight $name - set $name $cmd ".join(" ", @val);
		return YeeLight_SelectSetCmd($hash, $cmd, @val);
	}

	return "Unknown argument $cmd, bearword as argument or wrong parameter(s), choose one of $list";
}

sub
YeeLight_SelectSetCmd
{
	my ($hash, $cmd, @args) = @_;
	my $descriptor = '';
	my $name = $hash->{NAME};
	
	my $list = $hash->{helper}->{CommandSet};
  
	# remove descriptor from @args
	for (my $i = $#args; $i >= 0; --$i )
	{
		if ($args[$i] =~ /\/d\:(.*)/)
		{
			$descriptor = $1;
			splice (@args, $i, 1);
		}
	}
	
	if ($hash->{STATE} ne "opened" && lc $cmd ne 'reopen')
	{
		Log3 $name, 3, "$name can't send $cmd ".join(" ", @args)." with state \"$hash->{STATE}".
		return "Can't send command if bulb is not connected. Try \"reopen\" and check, if bulb is powered. Current state is $hash->{STATE}";
	}
  
	my $cnt = @args;
	Log3 $name, 5, "$name: Kommando wird ausgeführt. ($cmd).";
	# Commands supported by every yeelight
	if (lc $cmd eq 'on' || lc $cmd eq 'off')
	{
		Log3 $name, 5, "$name: ein oder aus";
		my $sCmd;
		$sCmd->{'method'}		= "set_power";							# method:set_power
		$sCmd->{'params'}->[0]	= $cmd;									# on/off
		$sCmd->{'params'}->[2]	= $args[0] if (defined($args[0]));		# ramp time
		
		YeeLight_SendCmd($hash,$sCmd,$cmd,2);
	}
	
	elsif (lc $cmd eq "hsv")
	{
		return "usage: set $name $cmd [hue] [saturation]" if !(($cnt == 2) || ($cnt == 3)) || ($args[0] !~ /^\d?.?\d+$/) || ($args[1] !~ /^\d?.?\d+$/);
		return "choose hue between 0 and 359" if ($args[0] < 0) || ($args[0] > 359);
		return "choose saturation between 0 and 100" if ($args[1] < 0) || ($args[1] > 100);
		
		my $sCmd;
		$sCmd->{'method'}		= "set_hsv";							# method:set_hsv
		$sCmd->{'params'}->[0]	= int($args[0]);						# hue
		$sCmd->{'params'}->[1]	= int($args[1]);						# saturation
		$sCmd->{'params'}->[3]	= $args[2] if ($args[2]);				# ramp time
		
		YeeLight_SendCmd($hash,$sCmd,$cmd,3);
	}
	
	elsif (lc $cmd eq "hue")
	{
		return "usage: set $name $cmd [hue]" if !(($cnt == 1) || ($cnt == 2)) || ($args[0] !~ /^\d?.?\d+$/);
		return "choose hue between 0 and 359" if ($args[0] < 0) || ($args[0] > 359);
		
		my $sCmd;
		$sCmd->{'method'}		= "set_hsv";							# method:set_hsv
		$sCmd->{'params'}->[0]	= int($args[0]);						# hue
		$sCmd->{'params'}->[1]	= $hash->{READINGS}{sat}{VAL} + 0;		# saturation
		$sCmd->{'params'}->[3]	= $args[1] if ($args[1]);				# ramp time
		
		YeeLight_SendCmd($hash,$sCmd,$cmd,3);
	}
	
	elsif (lc $cmd eq "sat")
	{
		return "usage: set $name $cmd [saturation]" if !(($cnt == 1) || ($cnt == 2)) || ($args[0] !~ /^\d?.?\d+$/);
		return "choose hue between 0 and 100" if ($args[0] < 0) || ($args[0] > 359);

		my $sCmd;
		$sCmd->{'method'}		= "set_hsv";							# method:set_hsv
		$sCmd->{'params'}->[0]	= $hash->{READINGS}{hue}{VAL} + 0;		# hue
		$sCmd->{'params'}->[1]	= int($args[0]);						# saturation
		$sCmd->{'params'}->[3]	= $args[1] if ($args[1]);				# ramp time
		
		YeeLight_SendCmd($hash,$sCmd,$cmd,3);
	}
	
	elsif (lc $cmd eq "statusrequest")
	{
		YeeLight_StatusRequest($hash);
	}
	
	elsif (lc $cmd eq "rgb")
	{
		my $sCmd;
		my $rgb		= undef;

		if ((@args == 1 || @args == 2) && $args[0] eq "000000")
		{
			$sCmd->{'method'}		= "set_power";
			$sCmd->{'params'}->[0]	= "off";
			$sCmd->{'params'}->[2]	= $args[1] if ($args[1]);
			
			YeeLight_SendCmd($hash,$sCmd,$cmd,2);
		}
		elsif ((@args == 3 || @args == 4) && ($args[0] =~ /^[0-9]+/ && $args[1] =~ /^[0-9]+/ && $args[2] =~ /^[0-9]+/))
		{
			if ($args[0] == 0 && $args[1] == 0 && $args[2] == 0)
			{
				$sCmd->{'method'}		= "set_power";
				$sCmd->{'params'}->[0]	= "off";
				$sCmd->{'params'}->[2]	= $args[3] if ($args[3]);
			
				YeeLight_SendCmd($hash,$sCmd,$cmd,2);
			}
		}
		else
		{
			if (defined($args[0]) && $args[0] =~ /^[0-9A-Fa-f]{6}$/)
			{
				$rgb	= hex($args[0]);
				$sCmd->{'params'}->[2] = $args[1] if ($args[1]);			# ramp time
			}
			elsif((($cnt == 3) || ($cnt == 4)) && ($args[0] =~ /^\d?.?\d+$/) && ($args[1] =~ /^\d?.?\d+$/) && ($args[2] =~ /^\d?.?\d+$/))
			{
				return "choose color (red, green, blue) between 0 and 255" if ($args[0] < 0) || ($args[0] > 255) || ($args[1] < 0) || ($args[1] > 255) || ($args[2] < 0) || ($args[2] > 255);
				my $r	= int($args[0]);
				my $g	= int($args[1]);
				my $b	= int($args[2]);
				$rgb	= ($r * 256 * 256) + ($g * 256) + $b;
				$sCmd->{'params'}->[2] = $args[3] if ($args[3]);			# ramp time
			}
			else
			{
				return "usage: set $name $cmd [red] [green] [blue] or set $name $cmd RRGGBB";
			}
			
			$sCmd->{'method'}		= "set_rgb";							# method:set_rgb
			$sCmd->{'params'}->[0]	= $rgb;									# rgb

			YeeLight_SendCmd($hash,$sCmd,$cmd,2);
		}
	}

	elsif (lc $cmd eq "bright")
	{
		return "usage: set $name $cmd [brightness]" if !(($cnt == 1) || ($cnt == 2)) || ($args[0] !~ /^\d?.?\d+$/);
		return "choose brightness between 0 and 100" if ($args[0] < 0) || ($args[0] > 100);
		
		my $sCmd;
		if ($args[0] == 0)
		{
			$sCmd->{'method'}		= "set_power";							# method:set_power
			$sCmd->{'params'}->[0]	= "off";								# on
			$sCmd->{'params'}->[2]	= $args[1] if ($args[1]);				# ramp time
		}
		else
		{
			$sCmd->{'method'}		= "set_bright";							# method:set_bright
			$sCmd->{'params'}->[0]	= int($args[0]);						# brightness
			$sCmd->{'params'}->[2]	= $args[1] if ($args[1]);				# ramp time
		}
		
		YeeLight_SendCmd($hash,$sCmd,$cmd,2);
	}
	
	elsif (lc $cmd eq "color")
	{
			my %color = (
			"red"			=> "255,0,0",		"rot"			=> "255,0,0",
			"yellow"		=> "255,255,0",		"gelb"			=> "255,255,0",
			"white"			=> "255,255,255",	"weiß"			=> "255,255,255",
			"green"			=> "0,255,0",		"grün"			=> "0,255,0",
			"cyan"			=> "0,255,255",
			"blue"			=> "0,0,255",		"blau"			=> "0,0,255",
			"magenta"		=> "255,0,255",
			"orange"		=> "254,127,0",
			"violett"		=> "140,0,254",
			"bluegreen"		=> "0,254,127",		"blaugrün"		=> "0,254,127",
			"greenblue"		=> "0,127,254",		"grünblau"		=> "0,127,254",
			"greenyellow"	=> "127,254,0",		"grüngelb"		=> "127,254,0",
			"bluered"		=> "254,0,127",		"blaurot"		=> "254,0,127",
			"vermilion"		=> "252,63,0",		"zinnober"		=> "252,63,0",
			"indigo"		=> "63,0,252",
			"bluecyan"		=> "0,189,252",		"blaucyan"		=> "0,189,252",
			"redmagenta"	=> "252,0,189",		"rotmagenta"	=> "252,0,189",
			"safran"		=> "252,189,0",
			"bluemagenta"	=> "189,0,252",		"blaumagenta"	=> "189,0,252",
			"greencyan"		=> "0,252,189",		"grüncyan"		=> "0,252,189",
			"limett"		=> "63,252,0",
			"brown"			=> "216,108,54",	"braun"			=> "216,108,54",
		);

		if (!$color{lc($args[0])})
		{
			my @colorKeys	= keys(%color);
			my $keys		= join(', ',@colorKeys);
			return "Unknown color. Choose from: $keys";
		}
		my @rgb = split(/\,/,$color{lc($args[0])});
		my $setColor = ($rgb[0] * 256 *256) + ($rgb[1] * 256) + $rgb[2];
				
		my $sCmd;
		$sCmd->{'method'}		= "set_rgb";							# method:set_hsv
		$sCmd->{'params'}->[0]	= $setColor;							# color in rgb
		$sCmd->{'params'}->[2]	= $args[1] if ($args[1]);				# ramp time
		
		YeeLight_SendCmd($hash,$sCmd,$cmd,2);
	}
	
	elsif ($cmd eq 'dimup' || $cmd eq 'dimdown')
	{
		if ($cnt == 0)
		{
			my $sCmd;
			$sCmd->{'method'}		= "set_adjust";						# method:set_adjust
			$sCmd->{'params'}->[0]	= "increase" if ($cmd eq "dimup");	# dimup
			$sCmd->{'params'}->[0]	= "decrease" if ($cmd eq "dimdown");# dimdown
			$sCmd->{'params'}->[1]	= "bright";							# brightness
			
			YeeLight_SendCmd($hash,$sCmd,$cmd);
		}
		elsif ($cnt == 2 || $cnt == 1)
		{		
			my $oldBright = $hash->{READINGS}{bright}{VAL};
			$args[0] = $oldBright + $args[0] if ($cmd eq "dimup");
			$args[0] = $oldBright - $args[0] if ($cmd eq "dimdown");
			$args[0] = 0 if ($args[0] < 0);
			$args[0] = 100 if ($args[0] > 100);
		
			YeeLight_SelectSetCmd($hash,'bright',@args);
		}
		else
		{
			return "usage: set $name $cmd [dimup/dimdown] <VALUE>";
		}
	}
	
	elsif (lc $cmd eq "ct")
	{
		return "usage: set $name $cmd [COLORTEMPERATUR]" if !(($cnt == 1) || ($cnt == 2)) || ($args[0] !~ /^\d?.?\d+$/);
		return "choose color temperature between 1700 and 6500" if ($args[0] < 1700) || ($args[0] > 6500);
		
		my $sCmd;
		$sCmd->{'method'}		= "set_ct_abx";							# method:set_ct_abx
		$sCmd->{'params'}->[0]	= int($args[0]);						# color temperature
		$sCmd->{'params'}->[2]	= $args[1] if ($args[1]);				# ramp time
		
		YeeLight_SendCmd($hash,$sCmd,$cmd,2);
	}
	
	elsif (lc $cmd eq "toggle")
	{
		my $power = $hash->{READINGS}{power}{VAL};
		YeeLight_SelectSetCmd($hash,"on",@args) if ($power eq "off");
		YeeLight_SelectSetCmd($hash,"off",@args) if ($power eq "on");
	}
	
	elsif (lc $cmd eq "name")
	{
		return "usage: set $name $cmd [NAME]" if ($cnt != 1);
		
		my $sCmd;
		$sCmd->{'method'}		= "set_name";							# method:set_name
		$sCmd->{'params'}->[0]	= $args[0];								# name
		
		YeeLight_SendCmd($hash,$sCmd,$cmd);
	}
	
	elsif (lc $cmd eq "default")
	{
		return "$name: Bulb needs to be on, for saving the state." if ($hash->{READINGS}{power}{VAL} ne "on");
		return "usage: set $name $cmd" if ($cnt != 0);
			
		my $sCmd;
		$sCmd->{'method'}		= "set_default";						# method:set_default
		$sCmd->{'params'}->[0]	= "";									# no parameter
		
		YeeLight_SendCmd($hash,$sCmd,$cmd);
	}
	
	elsif (lc $cmd eq "scene")
	{
		if ($cnt != 1
			&& $args[0] ne "sunset"
			&& $args[0] ne "sunrise"
			&& $args[0] ne "happy_birthday"
			&& $args[0] < 0
			&& $args[0] > 9)
		{
			my $sceneList;
			$sceneList .= "sunrise ";
			$sceneList .= "sunset ";
			$sceneList .= "happy_birthday ";
			$sceneList .= "0-9 ";
			return "Unknown scene. Choose from: $sceneList";
		}
		
		my %scene;
		$scene{sunset}{type}			= "start_cf";
		$scene{sunset}{count}			= "3";
		$scene{sunset}{action}			= "2";
		$scene{sunset}{val}				= "50,2,2700,10,180000,2,1700,5,420000,1,16731136,1";
		$scene{sunrise}{type}			= "start_cf";
		$scene{sunrise}{count}			= "3";
		$scene{sunrise}{action}			= "1";
		$scene{sunrise}{val}			= "50,1,16731136,1,360000,2,1700,10,540000,2,2700,100";
		$scene{happy_birthday}{type}	= "start_cf";
		$scene{happy_birthday}{count}	= "0";
		$scene{happy_birthday}{action}	= "1";
		$scene{happy_birthday}{val}		= "2000,1,14438425,80,2000,1,14448670,80,2000,1,11153940,80";

		my @newArgs;
		
		if ($args[0] =~ /^\d?.?\d+$/ && $args[0] >= 0 && $args[0] <= 9)
		{
			my $bHash = $modules{YeeLightBridge}{defptr};
			my $bName = $bHash->{NAME};
			my $userSceneName = "userScene".$args[0];
			my $userScene = undef;
			$userScene = $attr{$bName}{$userSceneName} if (defined($bName) && defined($attr{$bName}{$userSceneName}));
			$userScene = $attr{$name}{$userSceneName} if defined($attr{$name}{$userSceneName});
			return "scene \"".$args[0]."\" not set. Set attr userScene".$args[0]." first." if !defined($userScene);
			my @cf = split(/ /,$userScene);
			YeeLight_SelectSetCmd($hash,"start_cf",@cf);
		}
		elsif ($scene{$args[0]}{type} eq "start_cf")
		{
			push(@newArgs,$scene{$args[0]}{count});	
			push(@newArgs,$scene{$args[0]}{action});
			push(@newArgs,$scene{$args[0]}{val});
			YeeLight_SelectSetCmd($hash,$scene{$args[0]}{type},@newArgs);
		}
	}
	
	elsif (lc $cmd eq "start_cf")
	{
		return "usage: set $name $cmd [count] [action] [flowparams]" if ($cnt != 3);
		return "$name $cmd: count ($args[0]) must be numeric." if ($args[0] !~ /^\d?.?\d+$/);
		return "$name $cmd: action ($args[1]) must be numeric." if ($args[1] !~ /^\d?.?\d+$/);
		return "$name $cmd: action ($args[1]) must be 0 (previous state), 1 (stay on) or 2 (off)." if ($args[1] < 0) || ($args[1] > 2);
		
		my @params = split(/,/,$args[2]);
		my $pCnt = @params;
		return "$name $cmd: wrong count of parameter elements \"$pCnt\". Parameter must contain tuples of 4 elements (duration, mode, value, brightness)" if (($pCnt%4) != 0);
		my $i = 1;
		my $ret = "";
		
		foreach my $param (@params)
		{
			if (($i%4) == 1)
			{
				$ret.= "$name $cmd flow parameters: wrong parameter \"$param\" at $i. place. Duration must be numeric and equal or greater than 50.\n" if ($param !~ /^\d?.?\d+$/) || ($param < 50);
			}
			elsif (($i%4) == 2)
			{
				$ret.= "$name $cmd flow parameters: wrong parameter \"$param\" at $i. place. Choose mode from 1 (color), 2 (color temperature) or 7 (sleep).\n" if ($param != 1) && ($param != 2) && ($param != 7);
			}
			elsif (($i%4) == 3)
			{
				$ret.= "$name $cmd flow parameters: wrong parameter \"$param\" at $i. place. Value for rgb must be numeric and between 1 and 16777215.\n" if ($params[$i - 2] == 1) && (($param !~ /^\d?.?\d+$/) || ($param < 1) || ($param > 16777215));
				$ret.= "$name $cmd flow parameters: wrong parameter \"$param\" at $i. place. Value for color temperature must be numeric and between 1 and 16777215.\n" if ($params[$i - 2] == 2) && (($param !~ /^\d?.?\d+$/) || ($param < 1700) || ($param > 6500));
			}
			elsif (($i%4) == 0)
			{
				$ret.= "$name $cmd flow parameters: wrong parameter \"$param\" at $i. place. Brightness must be numeric and between 1 and 100.\n" if ($params[$i - 3] != 7) && (($param < 1) || ($param > 100));
			}
			
			$i++;
		}
		
		return "input parameter: $args[2]\n$ret" if ($ret ne "");
		
		my $action = $args[1] + 0;
		$action = 2 if ($action == 0) && ($hash->{READINGS}{power}{VAL} eq "off");		# override "previous state" with "off" if bulb state is off
		
		my $sCmd;
		$sCmd->{'method'}		= "start_cf";							# method:start_cf
		$sCmd->{'params'}->[0]	= $args[0] + 0;							# count
		$sCmd->{'params'}->[1]	= $action;								# action
		$sCmd->{'params'}->[2]	= $args[2];								# color flow tuples
		
		YeeLight_SendCmd($hash,$sCmd,$cmd);
	}
	
	elsif (lc $cmd eq "stop_cf")
	{
		my $sCmd;
		$sCmd->{'method'}		= "stop_cf";							# method:stop_cf
		$sCmd->{'params'}->[0]	= "";									# no parameter
		
		YeeLight_SendCmd($hash,$sCmd,$cmd);
	}
	
	elsif (lc $cmd eq "reopen")
	{
		DevIo_CloseDev($hash);
		DevIo_OpenDev($hash, 0,, sub(){
			my ($hash, $err) = @_;
			Log3 $name, 2, "$name: $err" if($err);
		});
		Log3 $name, 3, "$name: reconnected.";
	}
	
	elsif (lc $cmd eq "raw")											# Answer won't be deleted in AnsQue
	{
		return "$name: Raw command can't be empty." if (!defined($args[0]));
		DevIo_OpenDev($hash, 0,, sub(){
			my ($hash, $err) = @_;
			Log3 $name, 2, "$name: $err" if($err);
			return "$err" if($err);		
		}) if ($hash->{STATE} ne "opened");
		return "$name: Can't send command, if bulb is not connected." if ($hash->{STATE} ne "opened");
		my $arg = join("",@args);
		Log3 $name, 2, "$name: sending raw command to bulb: $arg";
		Add_SendQue($hash,$arg,"raw");
		DevIo_SimpleWrite($hash, qq($arg\r\n), 2);
	}
	
	elsif (lc $cmd eq "flush")
	{
		Log3 $name, 2, "$name: user initiated flush of queues";
		YeeLight_Flush($hash);
	}
	
	elsif (lc $cmd eq "blink")
	{
		return "usage: set $name $cmd [count] [mode] [color] [time]" if ($cnt != 0) && ($cnt != 1) && ($cnt != 4);
		return "count must be numeric and at least 1" if ($cnt > 0) && (($args[0] < 1) || ($args[0] !~ /^\d?.?\d+$/));
		return "choose color_mode from 1 (RGB) or 2 (color temperatur)" if ($cnt > 1) && (($args[1] < 1) || ($args[1] > 2));
		return "color in RGB must be numeric, noted in hex and between 000001 - FFFFFF" if ($cnt > 1) && ($args[1] == 1) && (($args[2] !~ /^[0-9A-Fa-f]{6}$/) || ($args[2] eq "000000"));
		return "color temperature must be numeric and between 1700 - 6500" if ($cnt > 1) && ($args[1] == 2) && (($args[2] !~ /^\d?.?\d+$/) || ($args[2] < 1700) || ($args[2] > 6500));
		return "time must be numeric and at least 100" if ($cnt == 4) && (($args[3] !~ /^\d?.?\d+$/) || ($args[3] < 100));
		
		my $count	= $args[0]	if ($args[0]);
		my $mode	= $args[1]	if ($args[1]);
		my $color;
		$color		= $args[2]	if ($args[2]) && ($args[1] == 2);
		$color		= hex($args[2]) if ($args[2]) && ($args[1] == 1);
		my $time	= int($args[3] / 2) if ($args[3]);
		my $curMode = $hash->{READINGS}{color_mode}{VAL};
		my $curPower= $hash->{READINGS}{power}{VAL};
		my $curRGBorCT;
		
		if ($curMode eq "RGB")
		{
			$curMode	= 1;
			$curRGBorCT	= hex($hash->{READINGS}{rgb}{VAL});
			
		}
		elsif ($curMode eq "color temperature")
		{
			$curMode	= 2;
			$curRGBorCT	= $hash->{READINGS}{ct}{VAL};
		}
		elsif ($curMode eq "HSV")
		{
			$curMode	= 1;
			my $hue		= $hash->{READINGS}{hue}{VAL} + 0;
			my $sat		= $hash->{READINGS}{sat}{VAL} + 0;
			my $val		= 100;
			$curRGBorCT = HSVtoRGB($hue,$sat,$val);
			Log3 $name, 5, "$name: convertet HSV ($hue $sat 100) to RGB ($curRGBorCT)";
		}
		
		my $sCmd;
		$sCmd->{'method'}		= "start_cf";							# method:start_cf
		$sCmd->{'params'}->[0]	= 6 if ($cnt == 0);						# 6 visible changes (3 blink)
		$sCmd->{'params'}->[0]	= 2 * $count if ($count);				# 2 * $count visible changes ($count blink)
		$sCmd->{'params'}->[1]	= 0 if ($curPower eq "on");
		$sCmd->{'params'}->[1]	= 2 if ($curPower eq "off");
		
		my $flow = "500,".$curMode.",".$curRGBorCT.",";
		$flow = $time.",".$mode.",".$color."," if ($mode);
		
		$sCmd->{'params'}->[2]	= $flow."100,".$flow."1" if ($curPower eq "on");
		$sCmd->{'params'}->[2]	= $flow."1,".$flow."100" if ($curPower eq "off");
		
		YeeLight_SendCmd($hash,$sCmd,$cmd);
	}
	
	elsif (lc $cmd eq "circlecolor")
	{
		my $sCmd;
		$sCmd->{'method'} = "set_adjust";
		$sCmd->{'params'}->[0] = "circle";
		$sCmd->{'params'}->[1] = "color";
	}

	elsif (lc $cmd eq "active_mode")
	{
		return "usage: set $name $cmd mode [ramptime]" if ($cnt != 1) && ($cnt != 2);
		my $sCmd;
		$sCmd->{'method'} = "set_power";
		$sCmd->{'params'}->[0]	= "on";
		$sCmd->{'params'}->[2]	= $args[1] if (defined($args[1]));		# ramp time
		$sCmd->{'params'}->[3]	= 1	if lc $args[0] eq "daylight";
		$sCmd->{'params'}->[3]	= 5	if lc $args[0] eq "nightlight";
		YeeLight_SendCmd($hash,$sCmd,$cmd,2);
	}
	else
	{
		return SetExtensions($hash, $list, $name, $cmd, @args);
	}
	
	return undef;
}

sub
YeeLight_SendCmd
{
	my ($hash,$sCmd,$cmd,$rCnt) = @_;
	my $name		= $hash->{NAME};
	my $error		= undef;
	my $bHash		= $modules{YeeLightBridge}{defptr};
	my $bName		= $bHash->{NAME};
	my $defaultRamp = 0;
	$defaultRamp	= $attr{$bName}{defaultramp} if (defined($bName) && $attr{$bName}{defaultramp});
	$defaultRamp	= $attr{$name}{defaultramp} if ($attr{$name}{defaultramp});
	
	if (lc $cmd eq "name"
		|| lc $cmd eq "default"
		|| lc $cmd eq "start_cf"
		|| lc $cmd eq "stop_cf"
		|| lc $cmd eq "dimdown"
		|| lc $cmd eq "dimup"
		|| lc $cmd eq "blink")
	{}
	elsif (defined($sCmd->{'params'}->[$rCnt]))
	{
		$error = "usage: set $name $cmd [milliseconds]" if ($sCmd->{'params'}->[$rCnt] !~ /^\d?.?\d+$/);
		$error = "minimum for milliseconds is 30 or 0 for sudden" if ($sCmd->{'params'}->[$rCnt] < 30) && ($sCmd->{'params'}->[$rCnt] != 0);
		Log3 $name, 4, "$name: $error" if defined($error);
		return $error if defined($error);
		$sCmd->{'params'}->[$rCnt - 1] = "smooth";						# flow
		$sCmd->{'params'}->[$rCnt] += 0;								# force ramp time to be int
	}
	elsif ($defaultRamp != 0)
	{
		$sCmd->{'params'}->[$rCnt - 1] = "smooth";						# flow
		$sCmd->{'params'}->[$rCnt] = $defaultRamp + 0;					# force default ramp time to be int
	}
	elsif ($sCmd->{'method'} eq "set_ct_abx" || $sCmd->{'method'} eq "active_mode")
	{

		$sCmd->{'params'}->[$rCnt - 1] = "sudden";						# no flow
		$sCmd->{'params'}->[$rCnt] = 0;									# no flow
	}
	
	YeeLight_IsOn($hash) if (lc $cmd ne "statusrequest") && (lc $cmd ne "on") && (lc $cmd ne "off") && (lc $cmd ne "toggle") && (lc $cmd ne "name") && (lc $cmd ne "active_mode");
	
	$sCmd->{'id'}	= YeeLight_Bridge_GetID($hash);
	my $send		= encode_json($sCmd);
	
	DevIo_OpenDev($hash, 0,, sub(){
		my ($hash, $err) = @_;
		Log3 $name, 2, "$name: $err" if($err);
		return "$err" if($err);		
	}) if ($hash->{STATE} ne "opened");
	return "$name: Can't send command, if bulb is not connected." if ($hash->{STATE} ne "opened");
	Add_SendQue($hash,$send,$sCmd->{'id'});
	DevIo_SimpleWrite($hash, qq($send\r\n), 2);
	Log3 $name, 4, "$name is sending: $send";

	return undef;
}

sub
YeeLight_StatusRequest
{
	my ($hash)	= @_;
	my $name	= $hash->{NAME};
	my $msgID	= YeeLight_Bridge_GetID($hash);
	my $send	= '{"id":'.$msgID.',"method":"get_prop","params":["power","bright","ct","rgb","hue","sat","color_mode","flowing","delayoff","flow_params","music_on","name", "active_mode", "nl_br"]}';
	
	DevIo_OpenDev($hash, 0,, sub(){ 
		my ($hash, $err) = @_;
		Log3 $name, 2, "$name: $err" if($err);
		return "$err" if($err);
	}) if ($hash->{STATE} ne "opened");
	return "$name: Can't do status request, if bulb is not connected." if ($hash->{STATE} ne "opened");
	Add_SendQue($hash,$send,$msgID);
	DevIo_SimpleWrite($hash, qq($send\r\n), 2);
	Log3 $name, 4, "$name is sending $send";
	
	return undef;
}

sub
YeeLight_Read
{
	my ($hash) = @_;
	my $name = $hash->{NAME};
	
	my $buf = DevIo_SimpleRead($hash);
	return undef if(!defined($buf));
	
	$buf =~ s/\r\n||\n//g;
	
	Log3 $name, 5, "$name: Reading raw: $buf";
	
	my $read;
	my $search = "}{";
	my $offset = 0;
	my $result = index($buf, $search, $offset);
	
	if ($result == -1)
	{
		Log3 $name, 4, "reading from $name: $buf";
		Add_AnsQue($hash,$buf);
	}
	else
	{
		while ($result != -1)
		{
			$read = substr($buf,$offset,($result - $offset + 1));
			Log3 $name, 4, "reading from $name: $read";
		
			Add_AnsQue($hash,$read);
			$offset = index($buf, "{", $result);
			$result = index($buf, $search, $offset);
		}
		
		$read = substr($buf,$offset,length($buf));
		Log3 $name, 4, "reading from $name: $read";
		
		Add_AnsQue($hash,$read);
	}
	
	return undef;
}

sub
Add_SendQue
{
	my ($hash,$send,$id) = @_;
	my $name = $hash->{NAME};
	
	if ($id eq "raw")
	{
		my $json;
		eval { $json = decode_json($send); };
		if ($@)
		{
			my $ret = $id.': '.$send;
			Log3 $name, 1, "$name ErrorQueue: added send command $ret (not a valid json string). Error: $@";
			push(@{$hash->{helper}->{ErrQue}},"not valid json: ".$ret);
		}
		else
		{
			$id = $json->{'id'} if (defined($json->{'id'}));
			$hash->{helper}->{SendQue}->{$id} = $send;
			Log3 $name, 5, "$name SendQueue: added $hash->{helper}->{SendQue}->{$id} with id:$id";
		}
	}
	else
	{
		$hash->{helper}->{SendQue}->{$id} = $send;
		Log3 $name, 5, "$name SendQueue: added $hash->{helper}->{SendQue}->{$id} with id:$id";
	}
	
	my $bHash = $modules{YeeLightBridge}{defptr};
	my $bName = $bHash->{NAME};
	my $timeout	= 3;
	$timeout	= $attr{$bName}{timeout} if (defined($bName) && $attr{$bName}{timeout});
	$timeout	= $attr{$name}{timeout} if ($attr{$name}{timeout});
	if ($timeout != 0)
	{
		RemoveInternalTimer($hash,"YeeLight_IsReachable");
		InternalTimer(gettimeofday() + $timeout,"YeeLight_IsReachable",$hash);
	}
	
	return undef;
}

sub
Add_AnsQue
{
	my ($hash,$ans) = @_;
	my $name = $hash->{NAME};
	
	my $json;
	eval { $json = decode_json($ans); };
	if ($@ && $ans !~ /error/)
	{
		my $ret = RepairJson($hash,$ans);
		Log3 $name, 1, "$name ErrorQueue: added answer \"$ans\" (not a valid json string). Error: $@";
		push(@{$hash->{helper}->{ErrQue}},"not valid json: ".$ans);
	}
	else
	{
		push(@{$hash->{helper}->{AnsQue}},$ans);
		my $length = @{$hash->{helper}->{AnsQue}};
		Log3 $name, 5, "$name AnswerQueue: added $hash->{helper}->{AnsQue}[($length - 1)]";
		
		Do_AnsQue($hash);
	}
	
	return undef;
}

sub
Do_AnsQue
{
	my ($hash) = @_;
	my $name = $hash->{NAME};
	my $i = 0;
	
	foreach my $ans (@{$hash->{helper}->{AnsQue}})
	{
		if ($ans =~ /\"id\"\:\(null\)/)
		{
			Log3 $name, 1, "$name ErrorQueue: received answer with unknown id ($ans)";
			push(@{$hash->{helper}->{ErrQue}},"unknown id: ".$ans);
			Log3 $name, 5, "$name AnswerQueue: deleted $ans";
			splice(@{$hash->{helper}->{AnsQue}}, $i, 1);
		}
		else
		{
			my $jsonAns = decode_json($ans);
			if (defined($jsonAns->{'method'}) && $jsonAns->{'method'} eq "props")
			{
				Log3 $name, 4, "$name: detected notification broadcast ($ans)";
				YeeLight_Parse($hash,$jsonAns);
				Log3 $name, 5, "$name AnswerQueue: deleted $ans";
				splice(@{$hash->{helper}->{AnsQue}}, $i, 1);
			}
			elsif (defined($jsonAns->{'id'}))
			{
				if (defined($hash->{helper}->{SendQue}->{$jsonAns->{'id'}}))
				{
					my $send = $hash->{helper}->{SendQue}->{$jsonAns->{'id'}};
					my $jsonSend = decode_json($send);
					if ((defined($jsonAns->{'result'})) && ($jsonAns->{'result'}->[0] eq "ok"))
					{
						Log3 $name, 3, "$name success sending $jsonSend->{'id'}: $send";
						Log3 $name, 5, "$name SendQueue: deleted $send";
						delete $hash->{helper}->{SendQue}->{$jsonAns->{'id'}};
					}
					elsif ($ans =~ /error/)
					{
						Log3 $name, 1, "$name error sending $jsonSend->{'id'}: $send";
						Log3 $name, 5, "$name SendQueue deleted $send";
						delete $hash->{helper}->{SendQue}->{$jsonAns->{'id'}};
					}
					elsif (defined($jsonAns->{'result'}) && defined($jsonAns->{'result'}->[11]))
					{
						YeeLight_ParseStatusRequest($hash,$jsonAns);
						Log3 $name, 5, "$name SendQueue: deleted $send";
						delete $hash->{helper}->{SendQue}->{$jsonAns->{'id'}};
					}
					else
					{
						Log3 $name, 1, "$name SendQueue: couldn't match answer ($ans)";
					}
					
					Log3 $name, 5, "$name AnswerQueue: deleted $ans";
					splice(@{$hash->{helper}->{AnsQue}}, $i, 1);
				}
			}
			
			$i++;
		}
	}
	
	return undef;
}

sub
YeeLight_Parse
{
	my ($hash,$json) = @_;
	my $name = $hash->{NAME};
	
	my $rgb		= undef;
	my $hexrgb	= undef;
	my $b		= undef;
	my $g		= undef;
	my $r		= undef;
	
	if (defined($json->{'params'}->{'rgb'}))
	{
		$rgb	= $json->{'params'}->{'rgb'};
		$hexrgb	= sprintf("%06X",$rgb);
		$b		= $rgb % 256;
		$g		= (($rgb - $b) / 256) % 256;
		$r		= ($rgb - $b - ($g * 256)) / (256 * 256);
	}
	
	my $colormode	= undef;
	my $colorflow	= undef;
	my $musicmode	= undef;
	my $activemode	= undef;
	
	if (defined($json->{'params'}->{'color_mode'}))
	{
		$colormode	= "RGB"					if ($json->{'params'}->{'color_mode'} eq 1);
		$colormode	= "color temperature"	if ($json->{'params'}->{'color_mode'} eq 2);
		$colormode	= "HSV" 				if ($json->{'params'}->{'color_mode'} eq 3);
	}
	if (defined($json->{'params'}->{'flowing'}))
	{
		$colorflow	= "off"					if ($json->{'params'}->{'flowing'} eq 0);
		$colorflow	= "on"					if ($json->{'params'}->{'flowing'} eq 1);
	}
	if (defined($json->{'params'}->{'music_on'}))
	{
		$musicmode	= "off"					if ($json->{'params'}->{'music_on'} eq 0);
		$musicmode	= "on"					if ($json->{'params'}->{'music_on'} eq 1);
	}
	if (defined($json->{'params'}->{'active_mode'}))
	{
		$activemode = "daylight"			if ($json->{'params'}->{'active_mode'} eq 0);
		$activemode = "nightlight"			if ($json->{'params'}->{'active_mode'} eq 1);
	}
	
	readingsBeginUpdate($hash);
		readingsBulkUpdate($hash,"power",$json->{'params'}->{'power'})				if defined($json->{'params'}->{'power'});
		readingsBulkUpdate($hash,"bright",$json->{'params'}->{'bright'})			if defined($json->{'params'}->{'bright'});
		readingsBulkUpdate($hash,"ct",$json->{'params'}->{'ct'})					if defined($json->{'params'}->{'ct'});
		readingsBulkUpdate($hash,"rgb",$hexrgb)										if defined($hexrgb);
		readingsBulkUpdate($hash,"rgb_blue",$b)										if defined($b);	
		readingsBulkUpdate($hash,"rgb_green",$g)									if defined($g);
		readingsBulkUpdate($hash,"rgb_red",$r)										if defined($r);
		readingsBulkUpdate($hash,"hue",$json->{'params'}->{'hue'})					if defined($json->{'params'}->{'hue'});
		readingsBulkUpdate($hash,"sat",$json->{'params'}->{'sat'})					if defined($json->{'params'}->{'sat'});
		readingsBulkUpdate($hash,"color_mode",$colormode)							if defined($colormode);
		readingsBulkUpdate($hash,"color_flow",$colorflow)							if defined($colorflow);
		readingsBulkUpdate($hash,"sleeptimer",$json->{'params'}->{'delayoff'})		if defined($json->{'params'}->{'delayoff'});
		readingsBulkUpdate($hash,"flow_params",$json->{'params'}->{'flow_params'})	if defined($json->{'params'}->{'flow_params'});
		readingsBulkUpdate($hash,"music_mode",$musicmode)							if defined($musicmode);
		readingsBulkUpdate($hash,"name",$json->{'params'}->{'name'})				if defined($json->{'params'}->{'name'});
		readingsBulkUpdate($hash,"active_mode", $activemode)						if defined($activemode);
		readingsBulkUpdate($hash,"nl_br", $json->{'params'}->{'nl_br'})				if defined($json->{'params'}->{'nl_br'});
	readingsEndUpdate($hash,1);
	
	Log3 $name, 3, "$name updated readings.";

	return undef;
}

sub
YeeLight_ParseStatusRequest
{
	my ($hash,$answer)	= @_;
	my $name = $hash->{NAME};

	my $rgb		= $answer->{'result'}->[3];
	my $hexrgb;
	my $b;
	my $g;
	my $r;
	if ($rgb ne "")
	{
		$hexrgb	= sprintf("%06x",$rgb);
		$b		= $rgb % 256;
		$g		= (($rgb - $b) / 256) % 256;
		$r		= ($rgb - $b - ($g * 256)) / (256 * 256);
	}
	my $colormode;
	my $colorflow;
	my $musicmode;
	my $activemode;
	$colormode	= "RGB"					if ($answer->{'result'}->[6] eq 1);
	$colormode	= "color temperature"	if ($answer->{'result'}->[6] eq 2);
	$colormode	= "HSV" 				if ($answer->{'result'}->[6] eq 3);
	$colorflow	= "off"					if ($answer->{'result'}->[7] eq 0);
	$colorflow	= "on"					if ($answer->{'result'}->[7] eq 1);
	$musicmode	= "off"					if ($answer->{'result'}->[10] eq 0);
	$musicmode	= "on"					if ($answer->{'result'}->[10] eq 1);
	$activemode	= "daylight"			if ($answer->{'result'}->[12] eq 0);
	$activemode = "nightlight"			if ($answer->{'result'}->[12] eq 1);
	
	if ($answer)
	{
		readingsBeginUpdate($hash);
			readingsBulkUpdate($hash,"power",$answer->{'result'}->[0]);
			readingsBulkUpdate($hash,"bright",$answer->{'result'}->[1]);
			readingsBulkUpdate($hash,"ct",$answer->{'result'}->[2]);
			readingsBulkUpdate($hash,"rgb",$hexrgb);
			readingsBulkUpdate($hash,"rgb_blue",$b);
			readingsBulkUpdate($hash,"rgb_green",$g);
			readingsBulkUpdate($hash,"rgb_red",$r);
			readingsBulkUpdate($hash,"hue",$answer->{'result'}->[4]);
			readingsBulkUpdate($hash,"sat",$answer->{'result'}->[5]);
			readingsBulkUpdate($hash,"color_mode",$colormode);
			readingsBulkUpdate($hash,"color_flow",$colorflow);
			readingsBulkUpdate($hash,"sleeptimer",$answer->{'result'}->[8]);
			readingsBulkUpdate($hash,"flow_params",$answer->{'result'}->[9]);
			readingsBulkUpdate($hash,"music_mode",$musicmode);
			readingsBulkUpdate($hash,"name",$answer->{'result'}->[11]);
			readingsBulkUpdate($hash,"active_mode", $activemode);
			readingsBulkUpdate($hash,"nl_br", $answer->{'result'}->[13]);
		readingsEndUpdate($hash,1);
		
		Log3 $name, 3, "$name full statusrequest";
	}
	
	return undef;
}

sub
YeeLight_Get
{
  return undef;
}

sub
YeeLight_Attr
{
	my ($cmd,$name,$attrName,$attrVal) = @_;
	
	if ($cmd eq "set")
	{
		if ($attrName eq "defaultramp")
		{
			return "Invalid parameter for $attrName. $attrName must be numeric and at least 30." if ($attrVal !~ /^\d?.?\d+$/) || ($attrVal < 30);
		}
		elsif ($attrName eq "updateIP")
		{
			return "Invalid parameter for $attrName. Choose \"0\" (don't update IP) or \"1\" (update IP)." if ($attrVal != 0) && ($attrVal != 1);
		}
		elsif ($attrName eq "timeout")
		{
			return "Invalid parameter for $attrName. $attrName must be numeric." if ($attrVal !~ /^\d?.?\d+$/);
		}
		elsif ($attrName eq "keepAlive")
		{
			return "Invalid parameter for $attrName. $attrName must be numeric and at least 60 or 0." if ($attrVal !~ /^\d?.?\d+$/) || (($attrVal < 60) && ($attrVal != 0));
		}
		elsif ($attrName =~ /userScene[0-9]/)
		{
			
		}
	}
	
	return undef;
}

sub
YeeLight_Init
{
	return undef;
}

sub
YeeLight_Flush
{
	my ($hash) = @_;
	my $name = $hash->{NAME};
	
	my $ret = "";
	my $answer = "";
	my $send = "";
	my $error = "";

	my $ans;
	while(@{$hash->{helper}->{AnsQue}} != 0)
	{
		$ans	 = shift(@{$hash->{helper}->{AnsQue}});
		$answer .= "   $ans\n";
	}
	
	$answer .= "AnswerQueue:\n".$answer if ($answer ne "");
	
	foreach my $s(sort keys %{$hash->{helper}->{SendQue}})
	{
		$send .= $hash->{helper}->{SendQue}->{$s}."\n";
		delete $hash->{helper}->{SendQue}->{$s};
	}
	
	$send .= "SendQueue:\n".$send if ($send ne "");
	
	my $err;
	while (@{$hash->{helper}->{ErrQue}} != 0)
	{
		$err	= shift(@{$hash->{helper}->{ErrQue}});
		$error .= "   $err\n";
	}
	
	$error .= "ErrorQueue:\n".$error if ($error ne "");
	
	$ret .= $send if ($send ne "");
	$ret .= $answer if ($answer ne "");
	$ret .= $error if ($error ne "");
		
	if ($ret ne "")
	{
		$ret = "$name: doing flush:\n".$ret;
	}
	else
	{
		$ret .= "$name: Tried to empty queues, but all three were empty.";
	}
	
	Log3 $name, 4, "$ret";
}

sub
YeeLight_IsOn
{
	my ($hash) = @_;
	my $name = $hash->{NAME};

	Log3 $name, 5, "$name: YeeLight_IsOn";
	YeeLight_SelectSetCmd($hash,"on") if ($hash->{READINGS}{power}{VAL} eq "off");
	return undef;
}

sub
YeeLight_Ready
{
	my ($hash) = @_;
	my $name = $hash->{NAME};
 
	# Versuch eines Verbindungsaufbaus, sofern die Verbindung beendet ist.
	return DevIo_OpenDev($hash, 1, undef, sub(){ 
		my ($hash, $err) = @_;
		Log3 $name, 2, "$name: $err" if($err);
		return "$err" if($err);
	}) if ( $hash->{STATE} eq "disconnected" );
}

sub
YeeLight_IsReachable
{
	my ($hash) = @_;
	my $name = $hash->{NAME};
	
	if (%{$hash->{helper}->{SendQue}} != 0)
	{
		foreach my $s (keys %{$hash->{helper}->{SendQue}})
		{
			my $send = $hash->{helper}->{SendQue}->{$s};
			Log3 $name, 1, "$name ErrorQueue: command wasn't answered in time ($send)";
			push(@{$hash->{helper}->{ErrQue}},"not in time: ".$send);
			Log3 $name, 5, "$name SendQueue: deleted $send";
			delete $hash->{helper}->{SendQue}->{$s};
		}
		DevIo_Disconnected($hash);
	}
	
	return undef;	
}

# subroutines for bridge communication

sub
YeeLightBridge_Parse
{
	my ($io_hash,$incData) = @_;
	my $name = $io_hash->{NAME};
	
	Log3 $name, 5, "$name (YeeLightBridge): $incData";
	
	my $sHash;
	if ($incData =~ /NOTIFY/ || $incData =~ /HTTP\/1.1 200 OK/)
	{
		foreach my $data (split(/\r\n/,$incData))
		{
			my @d = split(/: /,$data);
			$sHash->{$d[0]} = $d[1];
		}
		
		my $updateIP = 1;
		$updateIP = $attr{$name}{updateIP} if ($attr{$name}{updateIP});
		
		my $host = $sHash->{"Location"};
		$host = substr($host,11,length($host)-11);
		$host = substr($host,0,length($host)-6);
		
		my $hash;
		if ($modules{YeeLight}{defptr}{$sHash->{"id"}})
		{
			$hash = $modules{YeeLight}{defptr}{$sHash->{"id"}};
			$updateIP = $attr{$hash->{NAME}}{updateIP} if defined($attr{$hash->{NAME}}{updateIP});
			YeeLightBridge_UpdateDev($hash,$sHash,$updateIP);
			
			return $hash->{NAME};
		}
		elsif ($modules{YeeLight}{defptr}{$host})
		{
			$hash = $modules{YeeLight}{defptr}{$host};
			$updateIP = $attr{$hash->{NAME}}{updateIP} if defined($attr{$hash->{NAME}}{updateIP});
			if ($updateIP == 1)		# update IP true
			{
				$modules{YeeLight}{defptr}{$sHash->{"id"}} = $hash;
				delete($modules{YeeLight}{defptr}{$host});
			}
			YeeLightBridge_UpdateDev($hash,$sHash,$updateIP);
			
			return $hash->{NAME};
		}
		else
		{
			foreach my $y (%{$modules{YeeLight}{defptr}})
			{
				$hash = $modules{YeeLight}{defptr}{$y};
				if ($hash->{IP} eq $host)
				{
					$updateIP = $attr{$hash->{NAME}}{updateIP} if defined($attr{$hash->{NAME}}{updateIP});
					if ($updateIP == 1)		# update IP true
					{
						$modules{YeeLight}{defptr}{$sHash->{"id"}} = $hash;
						delete($modules{YeeLight}{defptr}{$host});
					}
					YeeLightBridge_UpdateDev($hash,$sHash,$updateIP);
				
					return $hash->{NAME};
				}
			}
		}

		my $newName = "YeeLight_".$sHash->{"id"};
		$newName	= "YeeLight_".$sHash->{"name"} if ($sHash->{"name"});
		
		return "UNDEFINED ".$newName." YeeLight ".$sHash->{"model"}." ".$host." ".$sHash->{"id"};
	}	
}

sub
YeeLightBridge_UpdateDev
{
	my ($hash,$mcHash,$updateIP) = @_;
	my $name = $hash->{NAME};
	
	my $DeviceName	= $mcHash->{"Location"};
	$DeviceName		= substr($DeviceName,11,length($DeviceName)-11);
	my $host		= substr($DeviceName,0,length($DeviceName)-6);
	my $port		= substr($DeviceName,length($host) + 1);
	my $id			= $mcHash->{"id"};
	my $model		= $mcHash->{"model"};
	my $fw_ver		= $mcHash->{"fw_ver"};
	my $support		= $mcHash->{"support"};
	my $power		= $mcHash->{"power"};
	my $bright		= $mcHash->{"bright"};
	my $color_mode	= $mcHash->{"color_mode"};
	$color_mode		= "RGB"					if ($color_mode eq 1);
	$color_mode		= "color temperature"	if ($color_mode eq 2);
	$color_mode		= "HSV" 				if ($color_mode eq 3);
	my $ct			= $mcHash->{"ct"};
	my $rgb			= $mcHash->{"rgb"};
	my $hexrgb		= sprintf("%06x",$rgb);
	my $b			= $rgb % 256;
	my $g			= (($rgb - $b) / 256) % 256;
	my $r			= ($rgb - $b - ($g * 256)) / (256 * 256);
	my $hue			= $mcHash->{"hue"};
	my $sat			= $mcHash->{"sat"};
	my $bulbName	= $mcHash->{"name"};
	
	if ($updateIP == 1)	# update IP true
	{
		$hash->{HOST}				= $host				if !($hash->{HOST}) || ($hash->{HOST} ne $host);
		$hash->{DeviceName}			= $DeviceName		if !($hash->{DeviceName}) || ($hash->{DeviceName} ne $DeviceName);
		$hash->{ID}					= $id				if !($hash->{ID}) || ($hash->{ID} ne $id);
	}
	$hash->{PORT}				= $port				if !($hash->{PORT}) || ($hash->{PORT} ne $port);
	$hash->{MODEL}				= $model			if !($hash->{MODEL}) || ($hash->{MODEL} ne $model);
	$hash->{FW_VER}				= $fw_ver			if !($hash->{FW_VER}) || ($hash->{FW_VER} ne $fw_ver);
	$hash->{helper}->{support}	= $support			if !($hash->{helper}->{support}) || ($hash->{helper}->{support} ne $support);
	
	readingsBeginUpdate($hash);
		readingsBulkUpdateIfChanged($hash,"power",$power);
		readingsBulkUpdateIfChanged($hash,"bright",$bright);
		readingsBulkUpdateIfChanged($hash,"ct",$ct);
		readingsBulkUpdateIfChanged($hash,"rgb",$hexrgb);
		readingsBulkUpdateIfChanged($hash,"rgb_blue",$b);
		readingsBulkUpdateIfChanged($hash,"rgb_green",$g);
		readingsBulkUpdateIfChanged($hash,"rgb_red",$r);
		readingsBulkUpdateIfChanged($hash,"hue",$hue);
		readingsBulkUpdateIfChanged($hash,"sat",$sat);
		readingsBulkUpdateIfChanged($hash,"color_mode",$color_mode);
		readingsBulkUpdateIfChanged($hash,"name",$bulbName);
	readingsEndUpdate($hash,1);
	
	return undef;
}

# helper subroutines

sub
IsValidIP
{
	return $_[0] =~ /^[\d\.]*$/ && inet_aton($_[0]);
}

sub
RepairJson
{
	my ($hash,$json) = @_;
	my $name = $hash->{NAME};
	my $length = length($json);
	my $oldJson = $json;
	
	$json .= "}" if (($length - 1) != rindex($json,"}"));
	$json = "{".$json if (index($json,"{") != 0);
	
	if ($json eq $oldJson && ($length - 2) != rindex($json,"}}")) 
	{
		$json .= "}";
	}
	
	if ($json ne $oldJson)
	{
		Log3 $name, 1, "$name: Invalid json $oldJson repaired to $json";
		my $ret;
		eval { $ret = encode_json($json); };
		if ($@)
		{
			Log3 $name, 1, "$name ErrorQueue: added repaired answer \"$json\" (not a valid json string). Error: $@";
			push(@{$hash->{helper}->{ErrQue}},$json);
		}
		else
		{
			push(@{$hash->{helper}->{AnsQue}},$json);
			my $length = @{$hash->{helper}->{AnsQue}};
			Log3 $name, 1, "$name AnswerQueue: added $hash->{helper}->{AnsQue}[($length - 1)] after repair";
			
			Do_AnsQue($hash);
		}
	}	
}

sub
HSVtoRGB
{
	my ($hue, $sat, $val) = @_;

	if ($sat == 0) 
	{
		my $r = int(($val * 2.55) + 0.5);
		my $g = int(($val * 2.55) + 0.5);
		my $b = int(($val * 2.55) + 0.5);
		my $rgb = ($r * 256 * 256) + ($g * 256) + $b;
	
		return $rgb;
	}
	$hue %= 360;
	$hue /= 60;
	$sat /= 100;
	$val /= 100;
	
	my $i = int($hue);

	my $f = $hue - $i;
	my $p = $val * (1 - $sat);
	my $q = $val * (1 - $sat * $f);
	my $t = $val * (1 - $sat * (1 - $f));
	
	my ($r, $g, $b);
	
	if ( $i == 0 )
	{
		($r, $g, $b) = ($val, $t, $p);
	}
	elsif ( $i == 1 )
	{
		($r, $g, $b) = ($q, $val, $p);
	}
	elsif ( $i == 2 ) 
	{
		($r, $g, $b) = ($p, $val, $t);
	}
	elsif ( $i == 3 ) 
	{
		($r, $g, $b) = ($p, $q, $val);
	}
	elsif ( $i == 4 )
	{
		($r, $g, $b) = ($t, $p, $val);
	}
	else
	{
		($r, $g, $b) = ($val, $p, $q);
	}
	$r = int(($r * 255) + 0.5);
	$g = int(($g * 255) + 0.5);
	$b = int(($b * 255) + 0.5);
	my $rgb = ($r * 256 * 256) + ($g * 256) + $b;
	
	return $rgb;
}

1;
