Relativ simples LCR AGI-/Perl-Script

streawkceur

Neuer User
Mitglied seit
2 Nov 2004
Beiträge
26
Punkte für Reaktionen
0
Punkte
0
Hi!

Ich hab mir mal ein kleines LCR-Script in Perl geschrieben, das über AGI aufgerufen wird.

Es sucht beim Aufruf die zur Zielnummer und aktuellen Uhrzeit passenden günstigsten Provider aus einer vordefinierten Liste und führt für den günstigsten Provider einen Befehl (z.B. Anwahl) aus.

Die Konfiguration sieht wie folgt aus (nicht vor der Länge erschrecken, sind fast nur Kommentare):

/etc/asterisk/least_cost_routing_table.conf
Code:
#Specification of your location:
#In numbers with only one leading 0 the 0 will be replaced by 00<countrty_prefix>.
#In numbers without a leading 0 the 0 will be replaced by 00<country_prefix><city_prefix>.
#So all numbers will be normalized to 00<country><city><number>.
#Numbers with at least two 0's won't be normalized.

country prefix 49
city prefix    221

#Classification of telephone numbers:
#You can classify your telephone numbers using the Pattern Syntax known from the extensions.conf.
#X         matches any digit from 0-9
#Z         matches any digit form 1-9
#N         matches any digit from 2-9
#[1237-9]  matches any digit or letter in the brackets (in this example, 1,2,3,7,8,9)
#.         wildcard, matches one or more characters
#If you want to create a class "german_mobile" for all numbers that match 00491[567] you would do it like this:
# class german_mobile 00491[567]
#You can also define multiple patterns per class:
# class german_mobile 004915
# class german_mobile 004916
# class german_mobile 004917
#Note that a telephone number will be checked against the class pattern in the order it occurs in this config file.
#So it makes sense to put a catch all class at the end of the class list.

class german_mobile 00491[567]
class germany_01803 00491803
class germany_01805 00491805
class city          0049221
class germany       0049
class other         .

#Definition of the available providers and how to use them:
#You can create rules in this format:
# provider <name> <command-string>
#Where the <command-string> is the command which shall be executed when using this provider and looks like this:
#Within this string you may use the variable $number$, which will be replaced by the target number.
#Most probably you will call the Dial()-command or a user-defined macro.
#Example:
# provider sipgate Dial(SIP/$number$@$sipgate,60,tT)
#Note that you have to create the context "sipgate" in your sip.conf to use the above.

provider sipgate Dial(SIP/$number$@sipgate,60,tT)
provider webde   Dial(SIP/$number$@webde,60,tT)

#Definition of the rates at a specific time and for a specific number class.
#You may define the rates in this format:
# rate <class> <date-range> <provider> <rate>
#<class> is the number class, to which the dialed number matches.
#<date-range> is the range for which the rates are defined.
#It can be specified in two formats:
# Weekday:hh-Weekday-hh #from hh at weekday 1 to hh at weekday 2
# Weekday-Weekday:hh-hh #from weekday 1 to weekday 2 but only between hh and hh at each day
#Weekday will be one of Mon,Tue,Wed,Thi,Fri,Sat,Sun and hh will of 00-23.
#hh will match the time hh-00-00 to hh-59-59.
#If the end date within the range is before the start date, the range will be
#from the start date until the end date in the next week.
#<provider> will be one of the above specified providers.
#<rate> will be the rate per minute. Note that the <rate> doesn't has to match
#the actual rate of your provider. If may be any number. But a cheaper provider
#should always be defined with a lower number.
#Example:
# rate german_mobile Mon:00-Sun:23 sipgate 19.9
#The script will take the number, normalize it, classify it and then calling the
#Dial()-command of the provider, which has the lowest rates for the number class
#and the current date.
#Every number class should have at least one provider at every hour at every
#day of the week.
#If there is no matching provider for a given a number class at a given time,
#the default provider will be used:
# default provider sipgate
#Note also that you should not specify a rate for a provider that you haven't
#defined above!

#sipgate
rate german_mobile Mon-Sun:00-23 sipgate  19.90
rate german_01803  Mon-Sun:00-23 sipgate   9.00
rate german_01805  Mon-Sun:00-23 sipgate  12.00
rate germany       Mon-Sun:00-23 sipgate   1.79
rate city          Mon-Sun:00-23 sipgate   1.79

#webde
rate german_mobile Mon-Sun:00-23 webde    22.90
rate germany       Mon-Sun:00-23 webde     1.49
rate city          Mon-Sun:00-23 webde     1.49

#very cheap city provider
rate city          Sat-Sun:00-23 xxl       0.00
rate germany       Sat-Sun:00-23 xxl       0.00

#even cheaper
rate city          Sun-Mon:10-12 billitc  -1.00
rate germany       Sun-Mon:10-12 billitc  -1.00

#default provider. should be a very reliable one since it is a "catch-all" option.
default provider sipgate

Jetzt mal Stück für Stück:

country prefix 49
city prefix 221

Man gibt zunächst die Landes- und Ortsvorwahl an.
Die werden gebraucht, um sie evtl. der Nummer vorne anzustellen, falls die Nummer nicht mit einer 0 oder einer 00 beginnt.
Die Nummern werden nämlich immer auf das Format 00<land><ort><teilnehmer> normiert, damit man sie leichter vergleichen kann. Außerdem spart man sich z.B. bei Festnetz-Gesprächen über SIP die Vorwahl, da sie automatisch ergänzt wird (was natürlich auch ein Nachteil sein kann, wenn man direkt einen SIP-Teilnehmer anhand seiner internen Nummer anrufen möchte, aber diesen Teilnehmer kann man ja i.d.R. auch per Festnetznummer erreichen...)

class german_mobile 00491[567]
class germany_01803 00491803
class germany_01805 00491805
class city 0049221
class germany 0049
class other .

Da verschiedene Nummern verschiedene Tarife haben, werden Rufnummernklassen spezifiziert. Anhand einem Muster weist man bestimmten Nummern eine Klasse zu.
Es ist zu beachten, dass eine Nummer immer der ersten Klasse zugeordnet wird, deren Muster auf die Nummer passt. Eine Nummer wird immer nur _einer_ Klasse zugeordnet, auch wenn sie theoretisch auf mehrere Klassen passt. So würde die Nummer 0049172123456 sowohl als deutsche Mobilfunknummer, als auch als deutsche Nummer erkannt werden. Da die "normalen" landesweite Festnetztelefonate aber i.d.R. billiger sind als Mobilfunktelefonate, würde das Programm fälschlicherweise den günstigen Festnetztarif wählen.
Um das zu verhindern wird eine Nummer wie gesagt nur der _ersten_ Klasse zugeordnet. Enger spezifizierte Muster sollten also oben stehen, allgemeingültige also unten.

provider sipgate Dial(SIP/$number$@sipgate,60,tT)
provider webde Dial(SIP/$number$@webde,60,tT)

Nun definiert man die einzelnen Provider mit ihrem Namen und dem Befehl, den das Script ausführen soll, wenn dieser Provider als günstigster gefunden wurde. Normalerweise gibt man hier einfach den Befehl zum Wählen an. $number$ wird in dem Befehl logischerweise durch die Zielnummer ersetzt.
Genauso kann man natürlich auch per ISDN rauswählen und z.B. eine CBC-Vorwahl voranstellen.
Man kann auch ein Makro aufrufen oder jedweden anderen Befehl ausführen.

#sipgate
rate german_mobile Mon-Sun:00-23 sipgate 19.90
rate german_01803 Mon-Sun:00-23 sipgate 9.00
rate german_01805 Mon-Sun:00-23 sipgate 12.00
rate germany Mon-Sun:00-23 sipgate 1.79
rate city Mon-Sun:00-23 sipgate 1.79

Nun definiert man die Gebühren je Nummern-Klasse und Wochentag/Uhrzeit. Für den Zeitrahmen sind zwei Formate zulässig, wie im Beispiel oben kommentiert wurde.
Die Gebühren müssen nicht den tatsächlichen Gebühren entsprechen. Das Script sortiert sie nur nach Preis. Man kann z.B. auch negative Gebühren oder sehr hohe verwenden, um einen Provider besonders zu bevorzugen oder um einen Provider "künstlich" schlecht zu stellen.

Schlussendlich sollte man noch einen Default-Provider definieren, der so zu sagen "als letzte Reserve" da ist und benutzt wird, wenn kein anderer passt.

Natürlich ist meine Beispiel-Config nur recht einfach gestrickt und für viele nicht ausreichend. Aber ich denke, dass die Syntax recht klar sein sollte und jeder die Config leicht anpassen kann.

Die Qualität des LCR basiert voll und ganz auf dieser Config und deren Vollständigkeit und Genauigkeit.
Die Config ist statisch und muss manuell erstellt werden. Ein automatisches LCR mittels aus dem Internet heruntergeladenen Tarifen wird nicht unterstützt.

Die Einbindung im Dialplan ist recht einfach:
Code:
;Generally dial through the least cost routing script
exten => _X.,1,Agi(least_cost_router.pl|${EXTEN},1)
exten => _X.,2,Congestion
exten => _X.,3,Busy
exten => _X.,4,Hangup

Man übergibt dem Script also die Nummer und eine weitere Zahl als Parameter. Die zweite Zahl gibt an, welcher Provider in der sortierten Liste billigster Provider gewählt werden soll. 1 = erstbilligster, 2 = zweitbilligster, usw. Das ist nützlich, um z.B. ein (mehrstufiges) Fallback einzurichten, falls ein Provider mal nicht erreichbar ist:

Code:
;Generally dial through the least cost routing script
exten => _X.,1,Macro(lcr-out-fallback,${EXTEN},1)

[macro-lcr-out-fallback]
;Places an outgoing call to a number (ARG1) over 3 providers returned by the LCR script.
;Start with the ARG2-th-cheapest provider. (e.g. start with the 2nd cheapest, when ARG2 = 2)
;If the call fails (DIALSTATUS = CHANUNAVAIL or CONGESTION) we will try it with the next provider

;Init
exten => s,1,GoTo(10)
;Try 1st provider
exten => s,10,Agi(least_cost_router.pl|${ARG1},$[${ARG2}])
exten => s,11,GotoIf($[$[${DIALSTATUS} = CHANUNAVAIL] | $[${DIALSTATUS} = CONGESTION]]? 20 : 90)
;Try 2nd provider
exten => s,20,Agi(least_cost_router.pl|${ARG1},$[${ARG2} + 1])
exten => s,21,GotoIf($[$[${DIALSTATUS} = CHANUNAVAIL] | $[${DIALSTATUS} = CONGESTION]]? 30 : 90)
;Try 3rd provider
exten => s,30,Agi(least_cost_router.pl|${ARG1},$[${ARG2} + 2])
exten => s,31,Goto(90)
;Final commands
exten => s,90,Congestion
exten => s,91,Busy
exten => s,92,Hangup

Viel blabla, hier ist das eigentliche AGI-Script:

/var/lib/asterisk/agi-bin/least_cost_router.pl
Code:
#!/usr/bin/perl -w
#!/usr/bin/perl

#Least cost routing AGI extension.
#(C) 2004 by Thomas Wittek tw (at) zentrifuge (dot) biz
#Released under the GNU General Public License
#
#This script will dial the cheapest provider currently available.
#Use it in your extensions.conf like this:
#  ;Explicitly dial through the least cost routing script
#  exten => _*0.,1,Agi(least_cost_router.pl|${EXTEN:2},1)
#  exten => _*0.,1,Agi(least_cost_router.pl|${EXTEN:2},1)
#You have to pass the target number and which provider of the list of cheapest
#providers you want to use as arguments to the script.
#"1" will use the cheapest, "2" will use the 2nd cheapest and so on.
#This may be interesting to construct a fallback, if the cheapest provider isn't
#available.
#
#Confuguration of the providers and rates will be done through
#/etc/asterisk/least_cost_routing_table.conf or any file
#specified in the constant CONFIG_FILE below.

use strict;
use Asterisk::AGI;
use constant CONFIG_FILE       => '/etc/asterisk/least_cost_routing_table.conf';
use constant WEEKDAY_TO_NUMBER => {qw/mon 0 tue 1 wed 2 thi 3 fri 4 sat 5 sun 6/};
use constant NUMBER_TO_WEEKDAY => {qw/0 mon 1 tue 2 wed 3 thi 4 fri 5 sat 6 sun/};
use constant DEBUG             => 1;

#returns the content of the given filename.
#returns undef on error.
sub read_file {
	my $filename = shift;
	
	if (-e $filename) {
		open(FILE, $filename);
		local($/) = undef;
		my ($file) = <FILE>;
		close(FILE);
		return $file;
	} else {
		return undef;
	}
}

#parse the config and return the results as an hash reference
sub parse_config {
	my ($config) = @_;
	
	#to convert weekday names to numbers...
	my $weekday_to_number = {qw/mon 0 tue 1 wed 2 thi 3 fri 4 sat 5 sun 6/};
	
	my $result = {};
	foreach my $line (split /(\r?\n|\r)/, $config) {
		#strip comments
		$line =~ s/\#(.*)$//g;
		#normalize white spaces
		$line =~ s/\s+/ /g;
		#remove trailing white spaces
		$line =~ s/ $//;
		#check for a valid directive
		if ($line =~ /country prefix\s+(.*)$/i) {
			$result->{country_prefix} = $1;
		} elsif ($line =~ /city prefix\s+(.*)$/i) {
			$result->{city_prefix} = $1;
		} elsif ($line =~ /class\s+(.*?)\s+(.*)$/i) {
			my ($class, $pattern) = (lc($1), $2);
			#convert asterisk pattern to perl regexp
			$pattern =~ s/x/\[0-9\]/gi;
			$pattern =~ s/z/\[1-9\]/gi;
			$pattern =~ s/n/\[2-9\]/gi;
			$pattern =~ s/\./\.\*/gi;
			$pattern .= '.*';
			#evtl. init array
			if (!exists($result->{classes})) {
				$result->{classes} = [];
			}
			#push class pattern
			push @{$result->{classes}}, [$class, $pattern];
		} elsif ($line =~ /provider\s+(.*?)\s+(.*)$/i) {
			$result->{providers}->{lc($1)} = $2;
		} elsif ($line =~ /default provider\s+(.*?)$/i) {
			$result->{default_provider} = $1;
		} elsif ($line =~ /rate\s+\S+?\s+\S+\s+\S+?\s+\S+$/i) {
			my ($class, $day1, $hour1, $day2, $hour2, $provider, $rate, $style);
			if ($line =~ /rate\s+(\S+?)\s+(\S\S\S)\:(\d|[01]\d|2[0-3])-(\S\S\S)\:(\d|[01]\d|2[0-3])\s+(\S+?)\s+(\S+)$/i) {
				# Weekday:hh-Weekday-hh #from hh at weekday 1 to hh at weekday 2
				($class, $day1, $hour1, $day2, $hour2, $provider, $rate) = ($1, $2, $3, $4, $5, $6, $7);
				$style = 1;
			} elsif ($line =~ /rate\s+(\S+?)\s+(\S\S\S)-(\S\S\S)\:(\d|[01]\d|2[0-3])-(\d|[01]\d|2[0-3])\s+(\S+?)\s+(\S+)$/i) {
				# Weekday-Weekday:hh-hh #from weekday 1 to weekday 2 but only between hh and hh at each day
				($class, $day1, $day2, $hour1, $hour2, $provider, $rate) = ($1, $2, $3, $4, $5, $6, $7);
				$style = 2;
			}
			if ($style) {
				#go through every hour at every day of the week within the specified range and add the new rate:
				$day1 = $weekday_to_number->{lc($day1)};
				$day2 = $weekday_to_number->{lc($day2)};
				if ($style == 1) {
					if ($day1 > $day2 or ($day1 == $day2 and $hour1 > $hour2)) { #wrap around one week
						$day2 += 7;
					}
				} else {
					if ($day1 > $day2) { #wrap around one week
						$day2 += 7;
					}
					if ($hour1 > $hour2) {
						$hour2 += 24;
					}
				}
				for (my $day = $day1; $day <= $day2; $day++) {
					my ($start_hour, $end_hour) = (0, 23);
					if ($style == 1) {
						if ($day == $day1) {
							$start_hour = $hour1;
						}
						if ($day == $day2) {
							$end_hour = $hour2;
						}
					} else {
						($start_hour, $end_hour) = ($hour1, $hour2);
					}
					for (my $hour = $start_hour; $hour <= $end_hour; $hour++) {
						#evtl. init array
						if (!exists($result->{rates}->{$day % 7}->{sprintf('%02d', $hour % 24)}->{$class})) {
							$result->{rates}->{$day % 7}->{sprintf('%02d', $hour % 24)}->{$class} = [];
						}
						push @{$result->{rates}->{$day % 7}->{sprintf('%02d', $hour % 24)}->{$class}}, {provider => $provider, rate => $rate};
					}
				}
			}
		} elsif ($line =~ /rate\s+(.*?)\s+(\S\S\S)-(\S\S\S)\:(\d|[01]\d|2[0-3])-(\d|[01]\d|2[0-3])\s+(.*?)\s+(.*)$/i) {
			#go through every hour at every day of the week within the specified range and add the new rate:
			my ($class, $day1, $day2, $hour1, $hour2, $provider, $rate) = ($1, $2, $3, $4, $5, $6, $7);
			$day1 = $weekday_to_number->{lc($day1)};
			$day2 = $weekday_to_number->{lc($day2)};
			if ($day1 > $day2 or ($day1 == $day2 and $hour1 > $hour2)) { #wrap around one week
				$day2 += 7;
			}
			for (my $day = $day1; $day <= $day2; $day++) {
				my ($start_hour, $end_hour) = ($hour1, $hour2);
				for (my $hour = $start_hour; $hour <= $end_hour; $hour++) {
					#evtl. init array
					if (!exists($result->{rates}->{$day % 7}->{sprintf('%02d', $hour)}->{$class})) {
						$result->{rates}->{$day % 7}->{sprintf('%02d', $hour)}->{$class} = [];
					}
					push @{$result->{rates}->{$day % 7}->{sprintf('%02d', $hour)}->{$class}}, {provider => $provider, rate => $rate};
				}
			}
		}
	}
	
	return $result;
}

#normalize number
sub normalize_number {
	my ($number, $config) = @_;
	
	#In numbers with only one leading 0 the 0 will be replaced by 00<countrty_prefix>.
	if ($number =~ /^0([^0].*)$/) {
		$number = '00' . $config->{country_prefix} . $1;
	}
	#In numbers without a leading 0 the 0 will be replaced by 00<country_prefix><city_prefix>.
	if ($number !~ /^0/) {
		$number = '00' . $config->{country_prefix} . $config->{city_prefix} . $number;
	}
	#So all numbers will be normalized to 00<country><city><number>.
	
	return $number;
}

#return the class which matches the number
sub classify_number {
	my ($number, $config) = @_;
	
	foreach my $class (@{$config->{classes}}) {
		if ($number =~ $class->[1]) {
			return $class->[0];
		}
	}
	
	return undef;
}

#return array with availaple providers for the given time and class sorted by rates. 1st = cheapest
sub cheapest_providers {
	my ($class, $day, $hour, $config) = @_;
	
	if (exists($config->{rates}->{$day % 7}->{sprintf('%02d', $hour)}->{$class})) {
		my @cheapest = sort {$a->{rate} <=> $b->{rate}} @{$config->{rates}->{$day % 7}->{sprintf('%02d', $hour)}->{$class}};
		return @cheapest;
	} else {
		return ();
	}
}


#main program
{
	#stop time for benchmarking
	my $starttime = (times)[0];
	#read config
	my $config = parse_config(read_file(CONFIG_FILE));
	
	#how has the script been started?
	if (@ARGV > 0 and lc($ARGV[0]) eq 'test') { #from the command line
		if (@ARGV == 3 and $ARGV[1] =~ /^(\S\S\S)\:(\d|[01]\d|2[0-3])$/) {
			my ($day, $hour) = (WEEKDAY_TO_NUMBER->{lc($1)}, $2);
			my $number   = normalize_number($ARGV[2], $config);
			my $class    = classify_number($number, $config);
			my @cheapest = cheapest_providers($class, $day, $hour, $config);
			if (@cheapest) {
				#put out list of available providers sorted by rate
				print "Available providers for $number (class $class) at " . NUMBER_TO_WEEKDAY->{$day} . ":$hour:\n";
				foreach my $pro (@cheapest) {
					print "$pro->{provider} \@ $pro->{rate}\n";
				}
			} else {
				my $provider = { provider => $config->{providers}->{$config->{default_provider}}, rate => '??.??' };
				print "No providers available for $number (class $class) at " . NUMBER_TO_WEEKDAY->{$day} . ":$hour! Using default provider $provider->{provider}\n";
			}
		} else {
			#output the config/plan
			use Data::Dump 'dump';
			print dump($config->{providers}) . "\n";
			print dump($config->{default_provider}) . "\n";
			print dump($config->{providers}->{$config->{default_provider}}) . "\n";
		}
	} else {
		#that should be the case when it is called from asteisk
		my $AGI = new Asterisk::AGI;
		#parse info from asterisk
		my %input  = $AGI->ReadParse();
		my $myself = $input{request};
		#get current local time
		my @localtime      = localtime(time());
		my ($day, $hour)   = (($localtime[6] - 1) % 7, $localtime[2]);
		#get normalized telephone number and the index of the n-th cheapest provider
		my $number         = normalize_number($ARGV[0], $config);
		my $provider_index = $ARGV[1] || 1;
		#classify telephone number
		my $class          = classify_number($number, $config);
		#get cheapest providers
		my @cheapest       = cheapest_providers($class, $day, $hour, $config);
		#put out some info and select provider
		my $duration  = ((times)[0]-$starttime);
		warn sprintf("$myself: Calculated cheapest providers in %.4f seconds", $duration) if DEBUG;
		my $provider;
		if (@cheapest) {
			if (DEBUG) {
				#put out list of available providers sorted by rate
				warn "$myself: Available providers for $number (class $class) at ". NUMBER_TO_WEEKDAY->{$day} . ":$hour:";
				foreach my $pro (@cheapest) {
					warn "$myself: $pro->{provider} \@ $pro->{rate}";
				}
				warn "$myself: Requested " . ($provider_index < 4 ? qw/1st 2nd 3rd/[$provider_index - 1] : $provider_index . 'th') . " cheapest provider";
			}
			if ($provider_index <= @cheapest) {
				$provider = $cheapest[$provider_index - 1];
				warn "$myself: Using provider $provider->{provider} \@ $provider->{rate}" if DEBUG;
			} else {
				$provider = { provider => $config->{providers}->{$config->{default_provider}}, rate => '??.??' };
				warn "$myself: Only " . scalar(@cheapest) . " providers available! Using default provider $provider->{provider}" if DEBUG;
			}
		} else {
			$provider = { provider => $config->{providers}->{$config->{default_provider}}, rate => '??.??' };
			warn "$myself: No providers available for $number (class $class) at " . NUMBER_TO_WEEKDAY->{$day} . ":$hour! Using default provider $provider->{provider}" if DEBUG;
		}
		#get command that should be executed
		my $command = $config->{providers}->{$provider->{provider}};
		#replace variable $number$
		$command =~ s/\$number\$/$number/gi;
		warn "$myself: Executing $command" if DEBUG;
		#split Command(parameters) into Command and parameters:
		my ($application, $parameters) = split /\(/, $command, 2; $parameters =~ s/\)$//;
		#split comma separated parameters and join them with pipes
		$parameters = join('|', split /,/, $parameters);
		#execute command
		$AGI->exec($application, $parameters);
	}
}

exit 0;

Diese Datei sollte auf 755 ge'chmod'ded werden, damit Asterisk sie ausführen kann.

Ich benutzte das Perl-Modul "Asterisk::AGI", das man sich hier herunterladen kann:
http://asterisk.gnuinter.net/

Man kann das Script auch aus der shell testen:
$ perl least_cost_router.pl test
Liest die Config ein und schreibt sie zurück in die shell, wie sie intern verarbeitet wird.
$ perl least_cost_router.pl test mon:10 0321123456
Gibt die günstigsten Provider für die Nummer 0321123456 am Montag zwischen 10:00 und 10:59 Uhr aus.

Das war's eigentlich :)
Viel Spaß denjenigen, die ich nicht völlig abgeschreckt habe. Aber ich denke es ist eigentlich ein recht einfaches Konzept. Aber ich erkläre Dinge lieber zu genau, als dass man sich die Hälfte dazureimen muss.

Wem es zu lästig ist die Sachen manuell in Text-Dateien zu kopieren, der kann auch das Attachment runterladen.

-Thomas
 

Anhänge

  • lcr_agi.tar.gz
    11.6 KB · Aufrufe: 26
Hi streawkceur,

sehr gute Arbeit - ich hab das Script gerade runter geladen und in der Konsole getestet --> funktioniert sehr gut;
Jetzt muss ich nur noch im dailplan testen ;)

THX 2streawkceur
 
Habs mal überflogen - sieht sehr nett aus und werde in Kürze in meine LCR-Line Select Thread Konfig einbauen.
 
kennt ihr isdnrate und das rates4linux Projekt?

Hallo zusammen,

bevor ihr euch vielleicht die mühe macht und etwas neu
erfindet, was es schon gibt,
hier mal die Ausgabe von isdnrate -b5 001360266999

01081_0:01081 11.100
01013_1:Tele2 Privat P...ion 12.000
01070_7:Arcor ISDN 760 60/60 12.000
01900732_0:Tele2 iHear 12.000
01013_0:Tele2 Privat CbC 13.500


Hinter isdnrate steckt die Tarifdatenbank rates4linux ( suche auf sourceforge ).

Mittels isdnrate und asterisk kann man sicherlich ein komfortables LCR realisieren.
Hatte ich auch längst vor, leider fehlte mir bisher die Zeit.

Gruss
Holger
 
Werden von rates4linux/isdnrate auch SIP-Provider unterstützt?
Wie leicht kann man die Datenbank selbst erweitern/kürzen?
Isdnrate würde ja "nur" die Berechnung der günstigsten Provider durchführen, was in meinem Script recht simpel ist und nicht viel Implementieraufwand war.

Interessant fänd ich vor allem Möglichkeiten, evtl. LCR-Datenbanken anderer Projekte zu integrieren/zu laden. Das lässt sich mit Perl normalerweise sehr gut anstellen.

Ein bash-/AGI-Script um isndrate herum zu schreiben wäre vermutlich auch nicht viel weniger Aufwand als der, den ich hatte, das Script zu schreiben :)
 
Hier mal meine aktuelle Config. Vielleicht ist sie ja für den ein oder anderen interessant.
Praktisch finde ich hier besonders, dass über das LCR-Script Sipgate-/Web.de Nummern auch primär über den entsprechenden Provider gewählt werden, damit man keine Gebühr zahlt.
Die Sipgate-Nummern habe ich aus dem Anmeldeformular auf deren Website entnommen. Hoffentlich ist kein Tippfehler in den Nummern. Wenn jemand einen findet, bitte melden!

Code:
#Specification of your location:
country prefix 49
city prefix    221

#Classification of telephone numbers:
#Sipgate numbers
class sipgate          00493086870
class sipgate          00494041431
class sipgate          00496938097
class sipgate          00498942095
class sipgate          00492014263
class sipgate          00492115800
class sipgate          004922135533
class sipgate          004923110872
class sipgate          00493416001
class sipgate          00493514172
class sipgate          00494215728
class sipgate          004951169604
class sipgate          00497115088
class sipgate          00499113083
#Web.de numbers
class webde            00491212
class webde            00492222948
#General numbers
class germany_mobile_d 004915[012]
class germany_mobile_d 004916[012]
class germany_mobile_d 004917[012345]
class germany_mobile_e 0049163
class germany_mobile_e 004917[6789]
class germany_01803    00491803
class germany_01805    00491805
class germany_free     0049800
class germany_free     0049130
class germany_city     0049221
class germany          0049
class other            .

#Definition of the available providers and how to use them:
provider sipgate Dial(SIP/$number$@sipgate,60,tT)
provider webde   Dial(SIP/$number$@webde,60,tT)
provider gmx     Dial(SIP/$number$@gmx,60,tT)

#Definition of the rates at a specific time and for a specific number class.
#sipgate
rate germany_mobile_d Mon-Sun:00-23 sipgate  19.90
rate germany_mobile_e Mon-Sun:00-23 sipgate  19.90
rate germany_01803    Mon-Sun:00-23 sipgate   9.00
rate germany_01805    Mon-Sun:00-23 sipgate  12.00
rate germany_free     Mon-Sun:00-23 sipgate   0.00
rate germany_city     Mon-Sun:00-23 sipgate   1.79
rate germany          Mon-Sun:00-23 sipgate   1.79
rate sipgate          Mon-Sun:00-23 sipgate   0.00
rate webde            Mon-Sun:00-23 sipgate   1.79

#webde
rate germany_mobile_d Mon-Sun:00-23 webde    22.90
rate germany_mobile_e Mon-Sun:00-23 webde    22.90
rate germany_free     Mon-Sun:00-23 webde    -0.01 #make webde cheaper than sipgate to favour webde
rate germany_city     Mon-Sun:00-23 webde     1.49
rate germany          Mon-Sun:00-23 webde     1.49
rate sipgate          Mon-Sun:00-23 webde     1.49
rate webde            Mon-Sun:00-23 webde     0.00

#gmx
rate germany_mobile_d Mon-Sun:00-23 gmx      19.89 #make gmx cheaper than sipgate to favour gmx
rate germany_mobile_e Mon-Sun:00-23 gmx      24.90
rate germany_city     Mon-Sun:00-23 gmx       1.00
rate germany          Mon-Sun:00-23 gmx       1.00
rate sipgate          Mon-Sun:00-23 gmx       1.00
rate webde            Mon-Sun:00-23 gmx       1.00

#default provider. Should be a very reliable one since it is a "catch-all" option.
default provider sipgate

edit: germany_free hinzugefügt, damit diese Nummern nicht mehr ueber gmx laufen, weil gmx keine Sonderrufnummern unterstützt (auch keine 0800er...).
 
Holen Sie sich 3CX - völlig kostenlos!
Verbinden Sie Ihr Team und Ihre Kunden Telefonie Livechat Videokonferenzen

Gehostet oder selbst-verwaltet. Für bis zu 10 Nutzer dauerhaft kostenlos. Keine Kreditkartendetails erforderlich. Ohne Risiko testen.

3CX
Für diese E-Mail-Adresse besteht bereits ein 3CX-Konto. Sie werden zum Kundenportal weitergeleitet, wo Sie sich anmelden oder Ihr Passwort zurücksetzen können, falls Sie dieses vergessen haben.