From ee3465b8be43ded952a6a730e5def4071944b125 Mon Sep 17 00:00:00 2001 From: markusbloch <> Date: Sat, 13 Jun 2015 15:10:24 +0000 Subject: [PATCH] new module: FB_CALLLIST for creating a call list based on events generated by FB_CALLMONITOR (original idea by Elektolurch, Forum: #27218) git-svn-id: https://svn.fhem.de/fhem/trunk@8739 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 2 + fhem/FHEM/72_FB_CALLLIST.pm | 1179 +++++++++++++++++++++++++++ fhem/MAINTAINER.txt | 2 + fhem/docs/commandref_frame.html | 1 + fhem/docs/commandref_frame_DE.html | 1 + fhem/www/pgm2/fhemweb_fbcalllist.js | 95 +++ 6 files changed, 1280 insertions(+) create mode 100755 fhem/FHEM/72_FB_CALLLIST.pm create mode 100755 fhem/www/pgm2/fhemweb_fbcalllist.js diff --git a/fhem/CHANGED b/fhem/CHANGED index 483a4e4bb..857f95d93 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: new module FB_CALLLIST for creating a call list based on + events generated by FB_CALLMONITOR (original idea by Elektolurch) - feature: 01_FHEMWEB: add cmdIcon - bugfix: 10_pilight_ctrl: set reset - lost connection to submodules - bugfix: 70_Pushalot: corrected parameter order for image diff --git a/fhem/FHEM/72_FB_CALLLIST.pm b/fhem/FHEM/72_FB_CALLLIST.pm new file mode 100755 index 000000000..4ef483075 --- /dev/null +++ b/fhem/FHEM/72_FB_CALLLIST.pm @@ -0,0 +1,1179 @@ +# $Id$ +############################################################################## +# +# 72_FB_CALLLIST.pm +# Creates a call list based on the events generated by a FB_CALLMONITOR instance +# +# Copyright by Markus Bloch +# e-mail: Notausstieg0309@googlemail.com +# +# This file is part of fhem. +# +# Fhem is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Fhem is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . +# +############################################################################## + +package main; + +use strict; +use warnings; + +use Time::HiRes qw(gettimeofday); +use MIME::Base64; +use Data::Dumper; + + +sub +FB_CALLLIST_Initialize($) +{ + my ($hash) = @_; + + $hash->{SetFn} = "FB_CALLLIST_Set"; + $hash->{DefFn} = "FB_CALLLIST_Define"; + $hash->{NotifyFn} = "FB_CALLLIST_Notify"; + $hash->{AttrFn} = "FB_CALLLIST_Attr"; + $hash->{AttrList} = "number-of-calls:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 ". + "internal-number-filter ". + "connection-mapping ". + "visible-columns:sortable-strict,row,state,timestamp,name,number,internal,connection,duration ". + "show-icons:0,1 ". + "list-type:all,incoming,outgoing,missed-calls,completed,active " . + "time-format-string ". + "list-order:ascending,descending ". + "language:de,en ". + "disable:0,1 ". + "disabledForIntervals"; + + $hash->{FW_detailFn} = "FB_CALLLIST_makeTable"; + $hash->{FW_summaryFn} = "FB_CALLLIST_makeTable"; + $hash->{FW_atPageEnd} = 1; +} + + +##################################### +# Define function +sub FB_CALLLIST_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + my $retval = undef; + my $name = $a[0]; + + if(!defined($a[2])) + { + $retval = "FB_CALLLIST_define: you must specify a FB_CALLMONITOR object for using FB_CALLLIST"; + return $retval; + } + + if(@a != 3) + { + $retval = "wrong define syntax: define FB_CALLLIST "; + return $retval; + } + + unless(defined($defs{$a[2]}) and $defs{$a[2]}->{TYPE} eq "FB_CALLMONITOR") + { + $retval = "FB_CALLLIST_Define: $a[2] does not exists or ist not of type FB_CALLMONITOR"; + return $retval; + } + + $hash->{FB} = $a[2]; + $hash->{NOTIFYDEV} = $a[2]; + $hash->{STATE} = 'Initialized'; + + FB_CALLLIST_loadList($hash); + + return undef; +} + + +##################################### +# AttrFn for importing filter expressions and cleanup list when user set an attribute +sub FB_CALLLIST_Attr($@) +{ + my ($cmd, $name, $attrib, $value) = @_; + my $hash = $defs{$name}; + + if($cmd eq "set") + { + if($attrib eq "internal-number-filter") + { + if( $value =~ m/^{.*}$/ ) + { + my $table = eval $value; + + if($table and (ref($table) eq 'HASH')) + { + $hash->{helper}{LINE_FILTER} = $table; + Log3 $name, 4, "FB_CALLLIST ($name) - filter stored as hash: $value"; + } + else + { + return "(line-filter) is not a valid hash: $value"; + } + } + else + { + my %lines; + foreach my $item (split("[ \t,][ \t,]*",$value)) + { + $lines{$item} = $item; + } + + $hash->{helper}{LINE_FILTER} = \%lines; + Log3 $name, 4, "FB_CALLLIST ($name) - filter stored as list $value"; + } + + # delete all outdated calls according to attribute list-type, internal-number-filter and number-of-calls + FB_CALLLIST_cleanupList($hash); + + # Inform all FHEMWEB clients + FB_CALLLIST_updateFhemWebClients($hash); + } + elsif($attrib eq "connection-mapping") + { + if( $value =~ m/^{.*}$/ ) + { + my $table = eval $value; + + if( $table &&(ref($table) eq 'HASH')) + { + $hash->{helper}{CONNECTION_MAP} = $table; + Log3 $name, 4, "FB_CALLLIST ($name) - connection map stored as hash: $value"; + + # Inform all FHEMWEB clients + FB_CALLLIST_updateFhemWebClients($hash); + } + else + { + return "invalid connection mapping table: $value"; + } + } + else + { + return "invalid connection mapping table: $value"; + } + } + elsif($attrib eq "list-type") + { + if($value =~ /^incoming|outgoing|missed-call|completed|active$/) + { + $attr{$name}{$attrib} = $value; + FB_CALLLIST_cleanupList($hash); + + # Inform all FHEMWEB clients + FB_CALLLIST_updateFhemWebClients($hash); + } + } + } + elsif($cmd eq "del") + { + if($attrib eq "internal-number-filter") + { + delete($hash->{helper}{LINE_FILTER}) if(exists($hash->{helper}{LINE_FILTER})); + return undef; + } + elsif($attrib eq "connection-mapping") + { + delete($hash->{helper}{CONNECTION_MAP}) if(exists($hash->{helper}{CONNECTION_MAP})); + return undef; + } + } +} + +##################################### +# SetFn for clearing the list +sub FB_CALLLIST_Set($@) +{ + my ($hash, $name, $cmd, $value) = @_; + + my $usage = "Unknown argument $cmd, choose one of clear:noArg"; + + if($cmd eq "clear") + { + delete($hash->{helper}{DATA}) if(exists($hash->{helper}{DATA})); + + # Inform all FHEMWEB clients + FB_CALLLIST_updateFhemWebClients($hash); + + return undef; + } + else + { + return $usage; + } +} + +##################################### +# NotifyFn is trigger upon changes on FB_CALLMONITOR device. Imports the call data into call list +sub FB_CALLLIST_Notify($$) +{ + my ($hash,$d) = @_; + + return undef if(!defined($hash)); + + my $name = $hash->{NAME}; + + return undef if(IsDisabled($name)); + + my $fb = $d->{NAME}; + + if ($fb ne $hash->{FB}) + { + return undef; + } + + my $event = ReadingsVal($fb, "event", undef); + my $call_id = ReadingsVal($fb, "call_id", undef); + + Log3 $name, 4, "FB_CALLLIST ($name) - start processing event $event for Call-ID $call_id"; + + + if(exists($hash->{helper}{LAST_EVENT}) and exists($hash->{helper}{LAST_CALL_ID}) and $event eq $hash->{helper}{LAST_EVENT} and $call_id eq $hash->{helper}{LAST_CALL_ID}) + { + Log3 $name, 4, "FB_CALLLIST ($name) - already processed event $event for Call-ID $call_id, skipping..."; + return undef + } + + if(exists($hash->{helper}{LINE_FILTER})) + { + Log3 $name, 5, "FB_CALLLIST ($name) - internal-number-filter is defined, checking if internal number is allowed"; + + my $line_number = ReadingsVal($fb, "internal_number", undef); + + if(defined($line_number) and not exists($hash->{helper}{LINE_FILTER}{$line_number})) + { + Log3 $name, 5, "FB_CALLLIST ($name) - internal number $line_number does not match the current internal-number-filter. skipping call."; + return undef; + } + + Log3 $name, 5, "FB_CALLLIST ($name) - call passed the internal-number-filter. proceeding..."; + } + + + + my $data; + + if($event =~ /^call|ring$/) + { + my $timestamp = gettimeofday(); + + $hash->{helper}{DATA}{$timestamp} = undef; + + $data = \%{$hash->{helper}{DATA}{$timestamp}}; + + $data->{external_number} = ReadingsVal($fb, "external_number", undef); + $data->{external_name} = ReadingsVal($fb, "external_name", undef); + $data->{internal_number} = ReadingsVal($fb, "internal_number", undef); + $data->{direction} = ReadingsVal($fb, "direction", undef); + $data->{running_call} = 1; + $data->{call_id} = $call_id; + $data->{last_event} = $event; + + if($data->{direction} eq "outgoing") + { + $data->{internal_connection} = ReadingsVal($fb, "internal_connection", undef); + } + + Log3 $name, 5, "FB_CALLLIST ($name) - created new data hash: $data"; + } + else + { + $data = FB_CALLLIST_getDataReference($hash, $call_id); + Log3 $name, 5, "FB_CALLLIST ($name) - found old data hash: $data" if($data); + } + + if(!$data) + { + Log3 $name, 4, "FB_CALLLIST ($name) - no data for this call in list. seams to be filtered out. skipping further processing..."; + return undef; + } + + if($event eq "connect") + { + $data->{internal_connection} = ReadingsVal($fb, "internal_connection", undef) if($data->{direction} eq "incoming"); + $data->{last_event} = $event; + + Log3 $name, 5, "FB_CALLLIST ($name) - processed connect event for call id $call_id"; + } + + if($event eq "disconnect" ) + { + $data->{call_duration} = ReadingsVal($fb, "call_duration", undef); + + if($data->{last_event} =~ /^call|ring$/) + { + $data->{missed_call} = 1; + } + + $data->{last_event} = $event; + + delete($data->{running_call}) if(defined($data->{running_call})); + + Log3 $name, 5, "FB_CALLLIST ($name) - processed disconnect event for call id $call_id"; + } + + + # clean up the list + FB_CALLLIST_cleanupList($hash); + + # Inform all FHEMWEB clients + FB_CALLLIST_updateFhemWebClients($hash); + + # save current list state to file/configDB + FB_CALLLIST_saveList($hash); +} + + +##################################### +# returns a hash reference to the data set of a specific running call +sub FB_CALLLIST_getDataReference($$) +{ + my ($hash, $call_id) = @_; + + my @result = grep {$hash->{helper}{DATA}{$_}{call_id} eq $call_id and defined($hash->{helper}{DATA}{$_}{running_call}) and $hash->{helper}{DATA}{$_}{running_call} == 1} keys %{$hash->{helper}{DATA}}; + + return \%{$hash->{helper}{DATA}{$result[0]}} if(exists($result[0])); + return undef; +} + +##################################### +# cleans up the list from all unwanted entries +sub FB_CALLLIST_cleanupList($) +{ + my ($hash) = @_; + + my $name = $hash->{NAME}; + my $limit = int(AttrVal($hash->{NAME}, "number-of-calls", 5)); + my $listtype = AttrVal($hash->{NAME}, "list-type", "all"); + my $count = 0; + my $index; + + my @list; + + if(exists($hash->{helper}{DATA}) and (scalar keys %{$hash->{helper}{DATA}}) > 0) + { + Log3 $name, 4, "FB_CALLLIST ($name) - cleaning up call list"; + + # delete calls which not matched the configured list-type and number-of-calls + if($listtype ne "all") + { + @list = grep { ($hash->{helper}{DATA}{$_}{direction} ne "incoming") or ($hash->{helper}{DATA}{$_}{direction} eq "incoming" and ++$count > $limit) } sort {$b <=> $a} keys %{$hash->{helper}{DATA}} if($listtype eq "incoming"); + + @list = grep { ($hash->{helper}{DATA}{$_}{direction} ne "outgoing") or ($hash->{helper}{DATA}{$_}{direction} eq "outgoing" and ++$count > $limit) } sort {$b <=> $a} keys %{$hash->{helper}{DATA}} if($listtype eq "outgoing"); + + @list = grep { ((not $hash->{helper}{DATA}{$_}{running_call}) and (not $hash->{helper}{DATA}{$_}{missed_call}) or $hash->{helper}{DATA}{$_}{direction} eq "outgoing") or ($hash->{helper}{DATA}{$_}{direction} eq "incoming" and $hash->{helper}{DATA}{$_}{missed_call} and ++$count > $limit) } sort {$b <=> $a} keys %{$hash->{helper}{DATA}} if($listtype eq "missed-calls"); + + @list = grep { (not $hash->{helper}{DATA}{$_}{running_call}) and ++$count > $limit } sort {$b <=> $a} keys %{$hash->{helper}{DATA}} if($listtype eq "completed"); + + @list = grep { (not $hash->{helper}{DATA}{$_}{running_call}) or ($hash->{helper}{DATA}{$_}{running_call} and ++$count > $limit)} sort {$b <=> $a} keys %{$hash->{helper}{DATA}} if($listtype eq "active"); + } + else + { + @list = grep { ++$count > $limit } sort {$b <=> $a} keys %{$hash->{helper}{DATA}}; + } + + # delete calls which do not match the configured internal-number-filter + if(exists($hash->{helper}{LINE_FILTER})) + { + Log3 $name, 5, "FB_CALLLIST ($name) - internal-number-filter is defined, checking if MSN is allowed"; + push @list, grep { not FB_CALLLIST_checkForInternalNumberFilter($hash, $hash->{helper}{DATA}{$_}{internal_number}) } keys %{$hash->{helper}{DATA}}; + } + + # delete the collected list of unwanted calls + foreach $index (@list) + { + Log3 $name, 5, "FB_CALLLIST ($name) - deleting old call $index"; + delete($hash->{helper}{DATA}{$index}) if(exists($hash->{helper}{DATA}{$index})); + } + } + else + { + Log3 $name, 4, "FB_CALLLIST ($name) - list is empty. no cleanup needed"; + } +} + +##################################### +# returns the call state of a specific call as icon or text +sub FB_CALLLIST_returnCallState($$) +{ + my ($hash, $index) = @_; + + return undef unless(exists($hash->{helper}{DATA}{$index})); + + my $data = \%{$hash->{helper}{DATA}{$index}}; + my $icons = AttrVal($hash->{NAME}, "show-icons", 1); + my $state; + + if($data->{running_call}) + { + if($data->{direction} eq "incoming" and $data->{last_event} eq "connect" ) + { + $state = "=> [=]"; + $state = FW_makeImage("phone_ring_in\@blue",$state) if($icons); + } + elsif($data->{direction} eq "incoming" and $data->{last_event} eq "ring") + { + $state = "=> ((o))"; + $state = FW_makeImage("phone_ring\@blue",$state) if($icons); + + } + elsif($data->{direction} eq "outgoing" and $data->{last_event} eq "connect" ) + { + $state = "<= [=]"; + $state = FW_makeImage("phone_ring_out\@green",$state) if($icons); + } + elsif($data->{direction} eq "outgoing" and $data->{last_event} eq "call") + { + $state = "<= ((o))"; + $state = FW_makeImage("phone_ring\@green",$state) if($icons); + } + } + else + { + if($data->{direction} eq "incoming") + { + $state = "=>".($data->{missed_call} ? " X" : ""); + $state = FW_makeImage("phone_missed_in\@red",$state) if($icons and $data->{missed_call}); + $state = FW_makeImage("phone_call_end_in\@blue",$state) if($icons and !$data->{missed_call}); + + if(exists($data->{internal_connection}) and $data->{internal_connection} =~ /Answering_Machine/) + { + $state = "=> O_O"; + $state = FW_makeImage("phone_answersing\@blue",$state) if($icons);; + } + } + elsif($data->{direction} eq "outgoing") + { + $state = "<=".($data->{missed_call} ? " X" : ""); + $state = FW_makeImage("phone_missed_out\@green",$state) if($icons and $data->{missed_call}); + $state = FW_makeImage("phone_call_end_out\@green",$state) if($icons and !$data->{missed_call}); + } + } + + return $state; +} + + +##################################### +# FW_detailFn & FW_summaryFn handler for creating the html output in FHEMWEB +sub FB_CALLLIST_makeTable($$$$) +{ + my ($FW_wname, $devname, $room, $extPage) = @_; + + my $hash = $defs{$devname}; + + return FB_CALLLIST_list2html($hash) +} + +##################################### +# creating the call list as html string or json array +sub FB_CALLLIST_list2html($;$) +{ + my ($hash, $to_json) = @_; + + return undef if( !$hash ); + + my $name = $hash->{NAME}; + my $alias = AttrVal($hash->{NAME}, "alias", $hash->{NAME}); + + my $ret = ""; + my $td_style = "style=\"padding-left:6px;padding-right:6px;\""; + my @json_output = (); + my $line; + + my $old_locale = setlocale(LC_ALL); + $ret .= ""; + $ret .= "
"; + $ret .= "
$alias".(IsDisabled($name) ? " (disabled)" : "")."
" unless($FW_webArgs{"detail"}); + $ret .= "
"; + $ret .= "
"; # div tag to support inform updates + $ret .= ""; + + $ret .= FB_CALLLIST_returnOrderedHTMLOutput($hash, FB_CALLLIST_returnTableHeader($hash), "class=\"fbcalllist header\"",""); + + if(exists($hash->{helper}{DATA}) and (scalar keys %{$hash->{helper}{DATA}}) > 0) + { + my $count = 1; + + my @json_list; + + my @list = sort { if(AttrVal($name, "list-order","descending") eq "descending") {return $b <=> $a;} else {return $a <=> $b;} } keys %{$hash->{helper}{DATA}}; + + if(AttrVal($hash->{NAME}, "list-type", "all") eq "missed-calls") + { + @list = grep { !$hash->{helper}{DATA}{$_}{running_call} } @list; + } + + if(AttrVal($hash->{NAME}, "list-type", "all") eq "completed") + { + @list = grep { !$hash->{helper}{DATA}{$_}{running_call} } @list; + } + + foreach my $index (@list) + { + my $data = \%{$hash->{helper}{DATA}{$index}}; + + my $state = FB_CALLLIST_returnCallState($hash, $index); + my $time = strftime(AttrVal($name, "time-format-string", "%a, %d %b %Y %H:%M:%S"), localtime($index)); + my $name = ($data->{external_name} eq "unknown" ? "-" : $data->{external_name}); + my $number = $data->{external_number}; + my $internal = ((exists($hash->{helper}{LINE_FILTER}) and exists($hash->{helper}{LINE_FILTER}{$data->{internal_number}})) ? $hash->{helper}{LINE_FILTER}{$data->{internal_number}} : $data->{internal_number} ); + my $connection = ($data->{internal_connection} ? ((exists($hash->{helper}{CONNECTION_MAP}) and exists($hash->{helper}{CONNECTION_MAP}{$data->{internal_connection}})) ? $hash->{helper}{CONNECTION_MAP}{$data->{internal_connection}} : $data->{internal_connection} ) : "-"); + my $duration = FB_CALLLIST_formatDuration($hash, $index); + + $line = { + line => $count, + row => $count, + state => $state, + timestamp => $time, + name => $name, + number => $number, + internal => $internal, + connection => $connection, + duration => $duration + }; + + push @json_output, FB_CALLLIST_returnOrderedJSONOutput($hash, $line); + + $ret .= FB_CALLLIST_returnOrderedHTMLOutput($hash, $line, "number=\"$count\" class=\"fbcalllist ".($count % 2 == 1 ? "odd" : "even")."\"", "class=\"fbcalllist\" $td_style"); + $count++; + } + } + else + { + my $string; + + if(AttrVal($name, "language", "en") eq "de") + { + $string = "leer"; + } + else + { + $string = "empty"; + } + + my @columns = split(",",AttrVal($name, "visible-columns", "row,state,timestamp,name,number,internal,connection,duration")); + my $additional_columns = scalar(@columns); + + $ret .= ""; + } + + $ret .= "
$string
"; + $ret .= "
"; + setlocale(LC_ALL, $old_locale); + + return ($to_json ? @json_output : $ret); +} + +##################################### +# format duration in seconds into hh:mm:ss +sub FB_CALLLIST_formatDuration($$) +{ + my ($hash, $index) = @_; + + my $data = \%{$hash->{helper}{DATA}{$index}}; + + if($data->{running_call}) + { + if(AttrVal($hash->{NAME}, "language", "en") eq "de") + { + return "läuft"; + } + else + { + return "ongoing"; + } + } + + my $hour = int($data->{call_duration} / (60 * 60)); + my $minute = ($data->{call_duration} / 60) % 60; + my $seconds = int($data->{call_duration} % 60); + + if($data->{missed_call}) + { + return "-"; + } + else + { + return sprintf("%02d:%02d:%02d", $hour, $minute, $seconds); + } +} + +##################################### +# save the current call list to file or configDB +sub FB_CALLLIST_saveList($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + if(exists($hash->{helper}{DATA})) + { + Log3 $name, 5, "FB_CALLLIST ($name) - start dumping of list to file"; + + my $dumper = Data::Dumper->new([$hash->{helper}{DATA}], [qw($hash->{helper}{DATA})] ); + $dumper->Purity(1); + + my $dump = $dumper->Dump; + + eval { require Compress::Zlib; }; + + unless($@) + { + Log3 $name, 5, "FB_CALLLIST ($name) - found Compress::Zlib module, compressing dump"; + $dump = Compress::Zlib::compress($dump); + $dump = "compressed:".encode_base64($dump, ""); + } + else + { + Log3 $name, 5, "FB_CALLLIST ($name) - unable to load Compress::Zlib module: $@"; + Log3 $name, 5, "FB_CALLLIST ($name) - using just plain base64 encoding for dump"; + $dump = encode_base64($dump, ""); + } + + Log3 $name, 5, "FB_CALLLIST ($name) - saving list dump: ".$dump; + + my $err = setKeyValue("FB_CALLLIST-$name", $dump); + + Log3 $name, 3, "FB_CALLLIST ($name) - error while saving the current call list - $err" if(defined($err)); + } +} + +##################################### +# load the call list from file or configDB +sub FB_CALLLIST_loadList($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 5, "FB_CALLLIST ($name) - loading old call list from file"; + + delete($hash->{helper}{DATA}) if(exists($hash->{helper}{DATA})); + + my ($err, $dump) = getKeyValue("FB_CALLLIST-$name"); + + if(defined($err)) + { + Log3 $name, 3, "FB_CALLLIST ($name) - error while loading the old call list state- $err"; + return undef; + } + + if(defined($dump)) + { + if($dump =~ /^compressed:(.+)$/) + { + Log3 $name, 5, "FB_CALLLIST ($name) - found compressed list dump in file"; + + $dump = $1; + + eval { require Compress::Zlib; }; + + unless($@) + { + $dump = decode_base64($dump); + $dump = Compress::Zlib::uncompress($dump); + } + else + { + Log3 $name, 3, "FB_CALLLIST ($name) - unable to load module Compress::Zlib to unpack compressed old call list: $@"; + return undef; + } + } + else + { + $dump = decode_base64($dump); + } + + Log3 $name, 5, "FB_CALLLIST ($name) - importing list..."; + + eval($dump); + + Log3 $name, 3, "FB_CALLLIST ($name) - error while importing old call list state: $@" if($@); + } + else + { + Log3 $name, 5, "FB_CALLLIST ($name) - no list found for restoring"; + } +} + +##################################### +# produce a HTML -Output for a specific data set depending on visible-columns setting +sub FB_CALLLIST_returnOrderedHTMLOutput($$$$) +{ + + my ($hash,$line, $tr_additions, $td_additions) = @_; + + my $name = $hash->{NAME}; + + my @order = split(",", AttrVal($name, "visible-columns", "row,state,timestamp,name,number,internal,connection,duration")); + + my @ret = (); + + push @ret, ""; + + foreach my $col (@order) + { + push @ret, "".$line->{$col}."", + } + + return join("",@ret).""; +} + + +##################################### +# produce a JSON Output for a specific data set depending on visible-columns setting +sub FB_CALLLIST_returnOrderedJSONOutput($$) +{ + my ($hash,$line) = @_; + + my $name = $hash->{NAME}; + + my @order = split(",", AttrVal($name, "visible-columns", "row,state,timestamp,name,number,internal,connection,duration")); + + my @ret = (); + + push @ret, "\"line\":\"".$line->{line}."\""; + + foreach my $col (@order) + { + my $val = $line->{$col}; + $val =~ s/"/\\"/g; + push @ret, "\"$col\":\"$val\"", + } + + return "{".join(",",@ret)."}"; +} + +##################################### +# Check, if a given internal number (MSN) matches the configured internal-number-filter (if set). returns true if number matches +sub FB_CALLLIST_checkForInternalNumberFilter($$) +{ + my ($hash, $line_number) = @_; + my $name = $hash->{NAME}; + + if(exists($hash->{helper}{LINE_FILTER})) + { + Log3 $name, 5, "FB_CALLLIST ($name) - internal-number-filter is defined, checking if MSN is allowed"; + + if(defined($line_number) and not exists($hash->{helper}{LINE_FILTER}{$line_number})) + { + Log3 $name, 5, "FB_CALLLIST ($name) - MSN $line_number does not match the current internal-number-filter: ".Dumper($hash->{helper}{LINE_FILTER}); + return undef; + } + else + { + Log3 $name, 5, "FB_CALLLIST ($name) - call passed the internal-number-filter. proceeding..."; + } + } + + return 1; +} + +##################################### +# update the call list of all connected FHEMWEB clients via inform mechanism +sub FB_CALLLIST_updateFhemWebClients($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + if(exists($hash->{helper}{DATA}) and (scalar keys %{$hash->{helper}{DATA}}) > 0) + { + Log3 $name, 5, "FB_CALLLIST ($name) - inform all FHEMWEB clients"; + + # inform all FHEMWEB clients about changes + foreach my $line (FB_CALLLIST_list2html($hash, 1)) + { + FW_directNotify($name, $line, 1); + } + + # send the current row count to ensure all other rows are deleted via JS + FW_directNotify($name,"max-lines,".(scalar keys %{$hash->{helper}{DATA}}), 1); + } + else + { + Log3 $name, 5, "FB_CALLLIST ($name) - list is empty, sending a clear command to all FHEMWEB clients"; + + # inform all FHEMWEB clients about empty list + my @columns = split(",",AttrVal($name, "visible-columns", "row,state,timestamp,name,number,internal,connection,duration")); + my $additional_columns = scalar(@columns); + my $string; + + if(AttrVal($name, "language", "en") eq "de") + { + $string = "leer"; + } + else + { + $string = "empty"; + } + + FW_directNotify($name, "clear,$additional_columns,$string", 1); + } +} + + +sub FB_CALLLIST_returnTableHeader($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + my $line; + + if(AttrVal($name, "language", "en") eq "de") + { + setlocale(LC_ALL, "de_DE.utf8"); + + $line = { + row => "", + state => "Status", + timestamp => "Zeitpunkt", + name => "Name", + number => "Rufnummer", + internal => "Intern", + connection => "Via", + duration => "Dauer" + }; + } + else + { + setlocale(LC_ALL, "en_US.utf8"); + + $line = { + row => "", + state => "State", + timestamp => "Timestamp", + name => "Name", + number => "Number", + internal => "Internal", + connection => "Via", + duration => "Duration" + }; + } + + return $line; +} + +1; + +=pod +=begin html + + +

FB_CALLLIST

+
    + + The FB_CALLLIST module creates a call history list by processing events of a FB_CALLMONITOR definition. + It logs all calls and displays them in a history table. +

    + You need a defined FB_CALLMONITOR instance where you can attach FB_CALLLIST to use this module

    + Depending on your configuration the status will be shown as icons or as text. You need to have the fhemSVG icon set configured in your corresponding FHEMWEB instance (see FHEMWEB attribute iconPath). +

    + The icons have different colors.

    +
      +
    • blue - incoming call (active or finished)
    • +
    • green - outgoing call (active or finished)
    • +
    • red - missed incoming call
    • +
    +
    + If you use no icons (see show-icons) the following states will be shown:

    +
      + + + + + + + + + + +
      <= ((o)) - outgoing call (ringing)
      => ((o)) - incoming call (ringing)
       
      <= [=] - outgoing call (currently active)
      => [=] - incoming call (currently active)
       
      <= X - outgoing unsuccessful call (nobody picked up)
      => X - incoming unsuccessful call (missed call)
       
      => O_O - incoming finished call recorded on answering machine
       
      <= - outgoing finished call
      => - incoming finished call
      +
    +
    + + + Define +
      + define <name> FB_CALLLIST <FB_CALLMONITOR name>
      +
    +
    + + Set
    +
      +
    • clear - clears the list completely
    • +
    +
    + + + Get
    +
      + No get commands implemented. +
    +
    + + + Attributes

    +
      + +
    • disable 0,1
    • + Optional attribute to disable the call list update. When disabled, call events will be processed and the list will not be updated. +

      + Possible values: 0 => FB_CALLLIST is activated, 1 => FB_CALLLIST is deactivated.
      + Default Value is 0 (activated)

      +
    • disabledForIntervals HH:MM-HH:MM HH:MM-HH-MM...
    • + Optional attribute to disable the call list update during a specific time interval. The attribute contains a space separated list of HH:MM tupels. + If the current time is between the two time specifications, the callist will be disabled and no longer updated. + Instead of HH:MM you can also specify HH or HH:MM:SS. +

      To specify an interval spawning midnight, you have to specify two intervals, e.g.: +
      23:00-24:00 00:00-01:00
      + Default Value is empty (no intervals defined, calllist is always active)

      +
    • number-of-calls 1..20
    • + Defines the maximum number of displayed call entries in the list.

      + Default Value is 5 calls

      +
    • list-type all,incoming,outgoing,missed-calls,completed,active
    • + Defines what type of calls should be displayed in the list.

      + Default Value is "all"

      +
    • list-order descending,ascending
    • + Defines whether the newest call should be on top of the list (descending) or on the bottom of the list (ascending).

      + Default Value is descending (first call at top of the list)

      +
    • internal-number-filter <hash>
    • + This attribute accepts a list of comma seperated internal numbers for + filtering incoming or outgoing calls by a specific list of internal numbers + or a hash for filtering and mapping numbers to text.
      +
      + e.g.
      +
        + attr <name> internal-number-filter 304050,304060

        + attr <name> internal-number-filter {'304050' => 'business', '304060' => 'private'}
        +
      +
      Important: Depending on your provider, the internal number can contain a location area code. + The internal-number-filter must contain the same number as it is displayed in the call list. + This can be with or without location area code depending on your provider. +

      + If this attribute is set, only the configured internal numbers will be shown in the list. All calls which are not taken via the configured internal numbers, were not be shown in the call list. +

      + Default Value: empty (all internal numbers should be used, no exclusions and no mapping is performed) +

      +
    • connection-mapping <hash>
    • + Defines a custom mapping of connection names to custom values. The mapping is performed in a hash table.

      + e.g.
      +
        + attr <name> connection-mapping {'DECT_1' => 'Mobile Kitchen', 'FON1' => 'Fax'} +

      + The mapped name will be displayed in the table instead of the original value from FB_CALLMONITOR. +

      + Default Value: empty (no mapping is performed) +

      +
    • time-format-string <string>
    • + Defines a format string which should be used to format the timestamp values. + It contains several placeholders for different elements of a date/time. + The possible values are standard POSIX strftime() values. Common placeholders are:

      +
        +
      • %a - The abbreviated weekday name
      • +
      • %b - The abbreviated month name
      • +
      • %S - The second as a decimal number
      • +
      • %M - The minutes as a decimal number
      • +
      • %H - The hours as a decimal number
      • +
      • %d - The day of the month as a decimal number
      • +
      • %m - The month as a decimal number
      • +
      • %Y - The year as a decimal number including the century.
      • +

      + There are further placeholders available. + Please consult the manpage of strftime() or the documentation of your perl interpreter to find out more. +

      + Default value is "%a, %d %b %Y %H:%M:%S" ( = "Sun, 07 Jun 2015 12:50:09")

      +
    • language en,de
    • + Defines the language of the table header, some keywords and the timestamp format. You need to have the selected locale installed and available in your operating system.

      + Possible values: en => English , de => German
      + Default Value is en (English)

      +
    • show-icons 0,1
    • + Normally the call state is shown with icons (used from the fhemSVG icon set). + You need to have fhemSVG in your iconpath attribute of your appropriate FHEMWEB definition to use this icons. + If you don't want to use icons you can deactivate them with this attribute.

      + Possible values: 0 => no icons , 1 => use icons
      + Default Value is 1 (use icons)

      +
    • visible-columns row,state,timestamp,name,number,internal,connection,duration
    • + Defines the visible columns, as well as the order in which these columns are displayed in the call list (from left to right). + Not all columns must be displayed, you can select only a subset of columns which will be displayed. +

      + The possible values represents the corresponding column. + The column "row" represents the row number within the current list. +

      + Possible values: a combination of row,state,timestamp,name,number,internal,connection,duration
      + Default Value is "row,state,timestamp,name,number,internal,connection,duration" (show all columns)

      +
    +
    + + Generated Events:

    +
      + This module does not generate any events or readings. +
    +
+=end html +=begin html_DE + + +

FB_CALLLIST

+
    + + Das FB_CALLLIST Modul erstellt eine Anrufliste für eine konfigurierte FB_CALLMONITOR Definition. + Es speichert alle Anrufe und zeigt sie in einer historischen Tabelle an. +

    + Es wird eine bereits konfigurierte FB_CALLMONITOR Definition benötigt, von der FB_CALLLIST die Events entsprechend verarbeiten kann.

    + Abhängig von der Konfiguration der Attribute wird der Status als Icon oder als Textzeichen ausgegeben. Um die Icons korrekt anzeigen zu können muss das fhemSVG Icon-Set in der entsprechenden FHEMWEB-Instanz konfiguriert sein (siehe dazu FHEMWEB Attribut iconPath). +

    + Die Icons haben verschiedene Farben.

    +
      +
    • blau - Eingehender Anruf (aktiv oder beendet)
    • +
    • grün - Ausgehender Anruf (aktiv oder beendet))
    • +
    • rot - Verpasster Anruf (eingehend)
    • +
    +
    + Falls keine Icons verwendet werden sollen (siehe Attribut show-icons), wird der Status wie folgt angezeigt:

    +
      + + + + + + + + + + +
      <= ((o)) - Ausgehender Anruf (klingelt)
      => ((o)) - Eingehender Anruf (klingelt)
       
      <= [=] - Ausgehender Anruf (laufendes Gespräch)
      => [=] - Eingehender Anruf (laufendes Gespräch)
       
      <= X - Ausgehender erfolgloser Anruf (Gegenseite nicht abgenommen)
      => X - Eingehender erfolgloser Anruf (Verpasster Anruf)
       
      => O_O - Eingehender Anruf der durch einen Anrufbeantworter entgegen genommen wurde
       
      <= - Ausgehender Anruf (beendet)
      => - Eingehender Anruf (beendet)
      +
    +
    + + Definition +
      + define <Name> FB_CALLLIST <FB_CALLMONITOR Name>
      +
    +
    + + Set-Kommandos
    +
      +
    • clear - löscht die gesamte Anrufliste
    • +
    +
    + + + Get
    +
      + N/A +
    +
    + + + Attributes

    +
      +
    • disable
    • + Optionales Attribut zur Deaktivierung der Anrufliste. Es werden dann keine Anruf-Events mehr verarbeitet und die Liste nicht weiter aktualisiert. +

      + Mögliche Werte: 0 => Anrufliste ist aktiv, 1 => Anrufliste ist deaktiviert.
      + Standardwert ist 0 (aktiv)

      +
    • disableForIntervals
    • + Optionales Attribut zur Deaktivierung der Anrufliste innerhalb von bestimten Zeitintervallen. + Das Argument ist eine Leerzeichen-getrennte Liste von Minuszeichen-getrennten HH:MM Paaren (Stunde : Minute). + Falls die aktuelle Uhrzeit zwischen diese Werte fällt, dann wird die Ausführung, wie beim disable, ausgesetzt. + Statt HH:MM kann man auch HH oder HH:MM:SS angeben.

      + Um einen Intervall um Mitternacht zu spezifizieren, muss man zwei einzelne Intervalle angeben, z.Bsp.: +
      23:00-24:00 00:00-01:00
      + Standardwert ist nicht gesetzt (aktiv)

      +
    • number-of-calls 1..20
    • + Setzt die maximale Anzahl an Einträgen in der Anrufliste. Sollte die Anrufliste voll sein, wird das älteste Gespräch gelöscht.

      + Standardwert sind 5 Einträge

      +
    • list-type all,incoming,outgoing,missed-calls,completed,active
    • + Ist dieses Attribut gesetzt, werden nur bestimmte Typen von Anrufen in der Liste angezeigt:

      +
        +
      • all - Alle Anrufe werden angezeigt
      • +
      • incoming - Alle eingehenden Anrufe werden angezeigt (aktive und abgeschlossen)
      • +
      • outgoing - Alle ausgehenden Anrufe werden angezeigt (aktive und abgeschlossene)
      • +
      • missed-calls - Alle eingehenden, verpassten Anrufe werden angezeigt.
      • +
      • completed - Alle abgeschlossenen Anrufe werden angezeigt (eingehen und ausgehend)
      • +
      • active - Alle aktuell laufenden Anrufe werden angezeigt (eingehend und ausgehend)
      • +

      + Standardwert ist "all" (alle Anrufe anzeigen)

      +
    • list-order descending,ascending
    • + Gibt an ob der neueste Anruf in der ersten Zeile (aufsteigend => descending) oder in der letzten Zeile (absteigend => ascending) in der Liste angezeigt werden soll. Dementsprechend rollt die Liste dann nach oben durch oder nach unten.

      + Standardwert ist "descending" (absteigend, neuester Anruf in der ersten Zeile)

      +
    • internal-number-filter <hash>
    • + Dieses Attribut ermöglicht das Filtern der angezeigten Anrufe auf bestimmte interne Rufnummern sowie das Zuordnen von Namen zu den internen Rufnummern.

      + Es ist möglich eine kommaseparierte Liste an internen Rufnummern anzugeben oder eine Hash-Tabelle in der man den internen Rufnummern eine eigene Bezeichnung zuweist. +
      +
      + z.B.
      +
        + attr <name> internal-number-filter 304050,304060

        + attr <name> internal-number-filter {'304050' => 'geschftl.', '304060' => 'privat'}
        +
      +
      + Wichtig: Je nach Telefonanbieter kann der Wert die Ortsvorwahl enthalten. Die Rufnummer muss genauso angegeben werden, wie sie ohne eine Zuordnung in der Anrufliste auftaucht.

      + Wenn dieses Attribut gesetzt ist, werden nur die eingestellten Rufnummern in der Liste angezeigt. +

      + Standardwert ist nicht gesetzt (alle internen Rufnummern werden angezeigt) +
      +
      +
    • connection-mapping <hash>
    • + Definiert eine eigene Zuordnung der Endgeräte (Reading: internal_connection) zu eigenen Bezeichnungen. Die Zuordnung erfolgt über eine Hash-Struktur.

      + z.B.
      +
        + attr <name> connection-mapping {'DECT_1' => 'Mobilteil Küche', 'FON1' => 'Fax', 'Answering_Machine_1' => 'Anrufbeantworter'} +

      + Die jeweils zugeordnete Bezeichnung wird in der Anrufliste dann entsprechend angezeigt anstatt des originalen Werten von FB_CALLMONITOR. +

      + Standardwert ist nicht gesetzt (Keine Zuordnung, es werden die Originalwerte verwendet) +

      +
    • time-format-string <string>
    • + Definiert einen Formatierungs-String welcher benutzt wird um die Zeitangaben in der Anrufliste nach eigenen Wünschen anzupassen. Es stehen hier eine ganze Reihe an Platzhaltern zur Verfügung um die einzelnen Elemente einer Datums-/Zeitangabe einzeln zu setzen. Die möglichen Werte sind alle Standard POSIX strftime() Platzhalter. Gängige Platzhalter sind:

      +
        +
      • %a - Der abgekürzte Wochentagname
      • +
      • %b - Der abgekürzte Monatsname
      • +
      • %S - Die Sekunden als Dezimalzahl
      • +
      • %M - Die Minuten als Dezimalzahl
      • +
      • %H - Die Stunden als Dezimalzahl
      • +
      • %d - Der Tag im Monat als Dezimalzahl
      • +
      • %m - Der Monat als Dezimalzahl
      • +
      • %Y - Das Jahr als Dezimalzahl (4-stellig).
      • +

      + Es gibt hierfür noch weitere Platzhalter. Weitere Informationen dazu findet man in der Manpage von strftime() oder der Dokumentation des entsprechenden Perl Interpreters. +

      + Standardwert ist "%a, %d %b %Y %H:%M:%S" (entspricht "So, 07 Jun 2015 12:50:09")

      +
    • language en,de
    • + Definiert die Sprache in der die Anrufliste angezeigt werden soll (Tabellenkopf, Datum). Die entsprechende Sprache muss auch im Betriebssystem installiert und unterstützt werden.

      + Mögliche Werte: en => Englisch , de => Deutsch
      + Standardwert ist en (Englisch)

      +
    • show-icons 0,1
    • + Im Normalfall wird der Status eines jeden Anrufs mit einem Icon angezeigt. Dazu muss das fhemSVG Icon-Set im iconpath-Attribut der entsprechenden FHEMWEB Instanz konfiguriert sein. Sollte man keine Icons wünschen, so kann man diese hiermit abschalten. Der Status wird dann mittels Textzeichen dargestellt.

      + Mögliche Werte: 0 => keine Icons , 1 => benutze Icons
      + Standardwert ist 1 (benutze Icons)

      +
    • visible-columns row,state,timestamp,name,number,internal,connection,duration
    • + Legt fest, welche Spalten in welcher Reihenfolge (von links nach rechts) in der Anrufliste angezeigt werden sollen. + Es müssen nicht alle verfügbaren Spalten angezeigt werden. + Es kann auch eine Auswahl von einzelnen Spalten angezeigt werden. +

      + Die möglichen Werte repräsentieren die jeweilige Spalte. + Der Wert "row" steht für die Zeilennummer innerhalb der Liste. +

      + Mögliche Werte: Eine Kombination der folgenden Werte in der gewünschten Reihenfolge: row,state,timestamp,name,number,internal,connection,duration
      + Standardwert ist "row,state,timestamp,name,number,internal,connection,duration" (Anzeige aller Spalten)

      +
    +
    + + Generierte Events:

    +
      + Dieses Modul generiert keine Events oder Readings. +
    +
+=end html_DE +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index d90af10e6..a1bcf8f4e 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -215,6 +215,7 @@ FHEM/71_YAMAHA_AVR.pm markusbloch http://forum.fhem.de Multimedi FHEM/71_YAMAHA_BD.pm markusbloch http://forum.fhem.de Multimedia FHEM/71_YAMAHA_NP.pm ra666ack http://forum.fhem.de Multimedia FHEM/72_FB_CALLMONITOR.pm markusbloch http://forum.fhem.de Unterstuetzende Dienste +FHEM/72_FB_CALLLIST.pm markusbloch http://forum.fhem.de Frontends FHEM/72_FRITZBOX.pm tupol http://forum.fhem.de FRITZBOX (link als PM an tupol) FHEM/73_km200.pm sailor http://forum.fhem.de Heizungssteuerung/Raumklima FHEM/73_PRESENCE.pm markusbloch http://forum.fhem.de Unterstuetzende Dienste @@ -346,6 +347,7 @@ www/images/* ulimaass http://forum.fhem.de Frontends www/pgm2/dashboard/* svenson08 http://forum.fhem.de Frontends www/pgm2/fhemweb_readingsHistory.js justme1968 http://forum.fhem.de Frontends www/pgm2/fhemweb_sortable.js markusbloch http://forum.fhem.de Frontends +www/pgm2/fhemweb_fbcalllist.js markusbloch http://forum.fhem.de Frontends www/pgm2/fhemweb_uzsu.js justme1968 http://forum.fhem.de Frontends www/pgm2/* rudolfkoenig http://forum.fhem.de Frontends www/jscolor/* justme1968 http://forum.fhem.de Frontends diff --git a/fhem/docs/commandref_frame.html b/fhem/docs/commandref_frame.html index 0b25e40de..d3e81ddd6 100644 --- a/fhem/docs/commandref_frame.html +++ b/fhem/docs/commandref_frame.html @@ -105,6 +105,7 @@ FHEM2FHEM   FHEMWEB   FB_CALLMONITOR   + FB_CALLLIST   FileLog   FLOORPLAN   GEOFANCY   diff --git a/fhem/docs/commandref_frame_DE.html b/fhem/docs/commandref_frame_DE.html index e61f40638..e0ea15c13 100644 --- a/fhem/docs/commandref_frame_DE.html +++ b/fhem/docs/commandref_frame_DE.html @@ -103,6 +103,7 @@ FHEM2FHEM   FHEMWEB   FB_CALLMONITOR   + FB_CALLLIST   FileLog   FLOORPLAN   GEOFANCY   diff --git a/fhem/www/pgm2/fhemweb_fbcalllist.js b/fhem/www/pgm2/fhemweb_fbcalllist.js new file mode 100755 index 000000000..4cf46e360 --- /dev/null +++ b/fhem/www/pgm2/fhemweb_fbcalllist.js @@ -0,0 +1,95 @@ +// $Id$ + +// WORKAROUND - should be removed if a more suitable solution is found +// remove all similar informid's in all parent elements to ensure further updates +$(function () { + $("div[arg=fbcalllist][informid]").each(function (index, obj) { + name = $(obj).attr("dev"); + $(obj).parents("[informid="+name+"]").removeAttr("informid"); + }); +}); + +function FW_processCallListUpdate(data) +{ + var table = $(this).find("table.fbcalllist").first(); + + // clear the list if data starts with "clear" + if(/^clear/.test(data)) + { + // if the table isn't already empty + if(!table.find("tr[name=empty]").length) + { + var tmp = data.split(","); + + table.find("tr[number]").remove(); + table.append(""+tmp[2]+""); + } + return; + } + + // clear all lines greater than max-lines (e.g. after activate a filter statement) + if(/^max-lines/.test(data)) + { + var tmp = data.split(","); + table.find("tr[number]").filter(function(index,obj) {return ($(obj).attr("number") > tmp[1]);}).remove(); + return; + } + + // else it's JSON data with row updates + var json_data = jQuery.parseJSON(data) + + if(table.find("tr[number="+json_data.line+"]").length) + { + $.each(json_data, function (key, val) { + + if(key == "line") + { return true; } + + FW_setCallListValue(table,json_data.line,key,val); + }); + } + else // add new tr row with the values) + { + // delete the empty tr row if it may exist + table.find("tr[name=empty]").remove(); + + var new_tr = ''; + var style = "style=\"padding-left:6px;padding-right:6px;\""; + + + // create the corresponding tags with the received data + $.each(json_data, function (key, val) { + if(key == "line") + { return true; } + new_tr += ''+val+''; + }); + + new_tr += ""; + + // insert new tr into table + table.append(new_tr); + } +} + +function FW_setCallListValue(table,line,key,val) +{ + table.find("tr[number="+line+"] td[name="+key+"]").each(function(index, obj) { + $(obj).html(val); + }); +} + +function FW_FbCalllistCreate(elName, devName, vArr, currVal, set, params, cmd) +{ + if(vArr[0] == "fbcalllist") + { + var newEl = $('div[informid='+devName+']').get(0); + + newEl.setValueFn = FW_processCallListUpdate; + + return newEl; + } +} + +FW_widgets['fbcalllist'] = { + createFn:FW_FbCalllistCreate +};