package main; use HTTP::Request; use LWP::UserAgent; use IO::Socket::SSL; use utf8; my @gets = ('dummy'); sub gcmsend_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "gcmsend_Define"; $hash->{NotifyFn} = "gcmsend_notify"; $hash->{SetFn} = "gcmsend_set"; $hash->{AttrList} = "loglevel:0,1,2,3,4,5 regIds apiKey stateFilter vibrate deviceFilter playSound"; } sub gcmsend_set { my ($hash, @a) = @_; my $v = @a[1]; if ($v eq "delete_saved_states") { $hash->{STATES} = {}; return "deleted"; } elsif($v eq "send") { my $msg = ""; for (my $i = 2; $i < int(@a); $i++) { if (! ($msg eq "")) { $msg .= " "; } $msg .= @a[$i]; } return gcmsend_sendMessage($hash, $msg); } else { return "unknown set value, choose one of delete_saved_states send"; } } sub gcmsend_Define($$) { my ($hash, $def) = @_; my @args = split("[ \t]+", $def); if (int(@args) < 1) { return "gcmsend_Define: too many arguments. Usage:\n" . "define gcmsend"; } return "Invalid arguments. Usage: \n define gcmsend" if(int(@a) != 0); $hash->{STATE} = 'Initialized'; return undef; } sub gcmsend_array_to_json(@) { my (@array) = @_; my $ret = ""; for (my $i = 0; $i < int(@array); $i++) { if ($i != 0) { $ret .= ","; } my $value = @array[$i]; $ret .= ("\"" . $value . "\""); } return "[" . $ret . "]"; } sub gcmsend_sendPayload($$) { my ($hash, $payload) = @_; my $name = $hash->{NAME}; my $logLevel = GetLogLevel($name,5); my $client = LWP::UserAgent->new(); my $regIdsText = AttrVal($name, "regIds", ""); my $apikey = AttrVal($name, "apiKey", ""); my @registrationIds = split(/\|/, $regIdsText); if (int(@registrationIds) == 0) { Log $logLevel, "$name no registrationIds set."; return undef; } return undef if (int(@registrationIds) == 0); my $unixTtimestamp = time*1000; my $data = "{" . "\"registration_ids\":" . gcmsend_array_to_json(@registrationIds) . "," . "\"data\": $payload". "}"; Log $logLevel, "data is $payload"; my $req = HTTP::Request->new(POST => "https://android.googleapis.com/gcm/send"); $req->header(Authorization => 'key='.$apikey); $req->header('Content-Type' => 'application/json; charset=UTF-8'); $req->content($data); my $response = $client->request($req); if (! $response->is_success) { Log $logLevel, "error during request: " . $response->status_line; $hash->{STATE} = $response->status_line; } $hash->{STATE} = "OK"; return undef; } sub gcmsend_fillGeneralPayload($$) { my ($hash, $payloadString) = @_; my $name = $hash->{NAME}; my $vibrate = "false"; if (AttrVal($name, "vibrate", "false") eq "true") { $vibrate = "true"; } my $playSound = "false"; if (AttrVal($name, "playSound", "false") eq "true") { $playSound = "true"; } return $payloadString . "," . "\"source\":\"gcmsend_fhem\"," . "\"vibrate\":\"$vibrate\"," . "\"playSound\":\"$playSound\""; } sub gcmsend_sendNotify($$$) { my ($hash, $deviceName, $changes) = @_; my $payload = "\"deviceName\": \"$deviceName\"," . "\"changes\":\"$changes\"," . "\"type\":\"notify\""; $payload = "{" . gcmsend_fillGeneralPayload($hash, $payload) . "}"; gcmsend_sendPayload($hash, $payload); } sub gcmsend_sendMessage($$) { my ($hash, $message) = @_; my @parts = split(/\|/, $message); my $tickerText; my $contentTitle; my $contentText; my $notifyId = 1; my $length = int(@parts); if ($length == 3 || $length == 4) { $tickerText = @parts[0]; $contentTitle = @parts[1]; $contentText = @parts[2]; if ($length == 4) { my $notifyIdText = @parts[3]; if (!(@parts[3] =~ m/[1-9][0-9]*/)) { return "notifyId must be numeric and positive"; } $notifyId = @parts[3]; } } else { return "Illegal message format. Required format is \r\n " . "tickerText|contentTitle|contentText[|NotifyID]"; } my $payload = "\"tickerText\":\"$tickerText\"," . "\"contentTitle\":\"$contentTitle\"," . "\"contentText\":\"$contentText\"," . "\"notifyId\":\"$notifyId\"," . "\"source\":\"gcmsend_fhem\"," . "\"type\":\"message\"" ; $payload = "{" . gcmsend_fillGeneralPayload($hash, $payload) . "}"; gcmsend_sendPayload($hash, $payload); return undef; } sub gcmsend_getLastDeviceStatesFor($$) { my ($gcm, $deviceName) = @_; if (! $gcm->{STATES}) { $gcm->{STATES} = {}; } my $states = $gcm->{STATES}; if (!$states->{$deviceName}) { $states->{$deviceName} = {}; } return $states->{$deviceName}; } sub gcmsend_notify($$) { my ($gcm, $dev) = @_; my $logLevel = GetLogLevel($gcm,5); my $name = $dev->{NAME}; my $gcmName = $gcm->{NAME}; my $deviceFilter = AttrVal($gcm->{NAME}, "deviceFilter", ""); return if $name eq $gcmName; return if(!$dev->{CHANGED}); # Some previous notify deleted the array. return if (! ($deviceFilter eq "") && !($name =~ m/$deviceFilter/)); my $stateFilter = AttrVal($gcm->{NAME}, "stateFilter", ""); my $lastDeviceStates = gcmsend_getLastDeviceStatesFor($gcm, $name); my $val = ""; my $nrOfFieldChanges = int(@{$dev->{CHANGED}}); my $sendFieldCount = 0; for (my $i = 0; $i < $nrOfFieldChanges; $i++) { my @keyValue = split(":", $dev->{CHANGED}[$i]); my $length = int($keyValue); my $change = $dev->{CHANGED}[$i]; # We need to find out a key and a value for each field update. # For state updates, we have not field, which is why we simply # put it to "state". # For all other updates the notify value is delimited by ":", # which we use to find out the value and the key. my $key; my $value; my $position = index($change, ':'); if ($position == -1) { $key = "state"; $value = $keyValue[0]; } else { $key = substr($change, 0, $position); $value = substr($change, $position + 2, length($change)); } if (! ($stateFilter eq "") && ! ($value =~ m/$stateFilter/)) { Log $logLevel, "$gcmName $name: ignoring $key, as value $value is blocked by stateFilter regexp."; } elsif ($value eq "") { Log $logLevel, "$gcmName $name: ignoring $key, as value is empty."; } elsif ($lastDeviceStates->{$key} && $lastDeviceStates->{$key} eq $value) { my $savedValue = $lastDeviceStates->{$key}; Log $logLevel, "$gcmName $name: ignoring $key, save value is $savedValue, value is $value"; } else { $lastDeviceStates->{$key} = $value; # Multiple field updates are separated by <|>. if ($sendFieldCount != 0) { $val .= "<|>"; } $sendFieldCount += 1; $val .= "$key:$value"; } } if ($sendFieldCount > 0) { gcmsend_sendNotify($gcm, $name, $val); } } 1; =pod =begin html

GCMSend

=end html =cut