The Grandstream GXP2020EXT is an accessory “sidecar” (or in Grandstream-speak “Expansion Module”) that adds 56 speed dials with BLF to Grandstream model GXP20xx VoIP telephones.
In the article below I’ve provided two scripts to help GXP2020EXT users keep their units programed with fresh directory information and matching card inserts.
The Problem
As the phone system administrator at work, one of the more annoying duties I have is updating the labels on telephone sidecars. You see these on receptionist phones, switchboard operator phones, lobby phones, etc. Grandstream’s GXP2020EXT is a 56 button panel with an indicator light to see if the line is busy (Busy Lamp Field, or BLF). You can actually connect two GXP2020EXT units together to support 112 lines.
In addition to programming the GXP2020EXT to make it aware of which line is which, there are also some supplied card inserts for writing down names associated with each line.
I wanted an easy way to not only program the GXP2020EXT, but also create a printable card insert.
The layout of the GXP2020EXT buttons and card insert leave something to be desired in my opinion. The bottom row of buttons lines up pretty well with the bottom line of the card insert, but as you work your way up from the bottom toward the top, the buttons start ending up more or less in the middle of the space for the name entry on the card. By the time you get all the way to the top the button is at the very top of the name position.
The Solution
I created two scripts to help me turn a list of names and extensions into a printable card insert and fully provisoned GXP2020EXT.
I spent a lot of time trying different spacings with the card insert to see if I could work out lines that were less confusing to my users, but ultimately I just opted to skip the first and last rows, and this seems to have cleared up the user confusion I was getting occasional reports about.
Feeding data to two scripts seemed not only easier, but less likely to introduce error (displaying one name, but programming for a different line, etc).
I also added a vertical line down the center of the card as a demarcation between the sides. The GXP2020EXT is pretty flexible, and can be programmed in different ways. Each line on the card could have two buttons for example, one that shows the user’s status (busy etc) and another one that calls the user, maybe one that transfers, or even one that just taps into the line to let a call center supervisor listen in, all depending on how you go about programming it and your phone system capabilities. I just use the BLF feature, pressing the corresponding button calls the user, so the vertical separator made sense to me. This way I’ll put two names on each line of the card (one party on the left, and one on the right).
Below is my printed card (right) shown next to a Grandstream supplied card (left). If all you want is a blank card like this you can just download a PDF of it here: gxp202ext_blank.pdf
Here is what it looks like with some names filled in
Template Script
The script I wrote to create these template files requires ImageMagick (with Ghostscript support). I’m using Ubuntu in this example. To install ImageMagick (if it isn’t already), just run:
Choose (y)es to accept the recommended packages and perform the install.
In order to create a PDF with ImageMagick on Ubuntu you’ll probably also want to modify /etc/ImageMagick-6/policy.xml, otherwise you might see an error message like:
Edit /etc/ImageMagick-6/policy.xml and in the <policymap> section, find the “ghostscript” comment area
<policy domain="coder" rights="none" pattern="PS" />
<policy domain="coder" rights="none" pattern="EPS" />
<policy domain="coder" rights="none" pattern="PDF" />
<policy domain="coder" rights="none" pattern="XPS" />
Change the pattern=PDF line to read
Keep in mind there are security implications to doing this, which is why the rights weren’t set in the first place. I’m running this script on my desktop, and I don’t have any processes likely to be a vector to throw malicious or untrustworthy data at ImageMagick, so I feel OK changing this on my system, but your situation may be different. Strictly speaking the step of turning the PNGs into PDF isn’t required, it’s just helpful to get them to print at the right size. Alternatively if you import the PNGs into LibreOffice and print from there you’ll probably be fine too.
If you want to run this on the same system as an ancient CentOS 5.5 Elastix server you’ll want to temporarily move your yum repos, create a new one to the CentOS vault, install ImageMagick, and then move your broken old yum repos back in to place.
$ cd /etc/yum.repos.d/
$ mkdir /root/backup
$ mv * !$
$ cat <<EOF >vault.repo
[vault]
name=CentOS Vault
baseurl=http://vault.centos.org/5.5/os/i386/
enable=1
EOF
$ yum check-update
$ yum install ImageMagick
# optionally get some extra fonts
$ yum install liberation-font
$ mv /root/backup .
$ rm vault.repo
This is all you need, but ImageMagick might still try to use the Microsoft Arial font. You can get the redistributable MS Core Fonts and use cabextract to dump them into /usr/share/fonts/default/TrueType/ or you could specify an alternative font in make_template.sh, or you could just upload a bunch of TTF fonts into /usr/share/fonts/default/TrueType/ from another system.
Once the ImageMagick requirements are met, the next thing you’ll need is your list of names. You’ll also want a list of extension numbers too, this will be handy when we move on to provisioning the GXP2020EXT. You can use the same list for both tasks. I’d suggest making a list in the following format:
Linda Fields <1104>
Venessa Lovett <1115>
Kevin Robertson <1172>
Andrew Dial <1108>
Paula Dahil <1165>
Jerry Hudson <1112>
Roger Holder <1126>
Ashley Deblois <1116>
...
My office runs Elastix/FreePBX/Asterisk. The format above is the same as you might expect to see in the extensions list of the Elastix web interface (PBX -> PBX Configuration -> Extensions). You can just copy those and paste them into a new file. If you’re using a different system or a newer version of Elastix you could just make the list by hand in a pinch. I’m going to save the list to a file named names.txt for the purpose of example and documentation below.
Due to the limited space, we probably don’t want to include the extension number in the printed card (only the name). However we do want to have those extensions in the file for later use by the provisioning script.
An easy solution is to use awk to output only the name portion (the words before the ‘<‘ character appears) on each line of our file
Eduardo Chamberlain
Linda Fields
Venessa Lovett
Kevin Robertson
Andrew Dial
Paula Dahil
Jerry Hudson
Roger Holder
Ashley Deblois
...
You probably also want to be sure to alphabetically sort your list so that it’s easier to find a particular name on the display card. You can use the sort command to accomplish this. Pipe the output of sort into awk.
Andrew Dial
Ashley Deblois
Eduardo Chamberlain
Jerry Hudson
Kevin Robertson
Linda Fields
Paula Dahil
Roger Holder
Venessa Lovett
...
OK, so put it all together and pipe it into the make_template.sh script (supplied below)
You should now have a file named template.pdf that you can print out. Cut along the dashed lines with a pair of scissors and if everything worked out right the two sheets of paper you have left should fit in the card slots of the GXP2020EXT sidecar.
If like me you’d prefer to keep the top and bottom row of each card blank, then you’ll want to manually edit your names.txt file. Perform a sort ahead of time on the file, you won’t want to sort it in a pipeline as the order of the items in the file will need to be preserved to accomplish this task. Edit the file and insert a blank/empty line on each line-n where n % 14 = 1 or 0, or more verbosely, add a blank line to lines number 1, 14, 15, 28, 29, 42, 43, and 56.
download make_template.sh
i=0
while read -t1 line
do
name_part1[$i]="${line%' '*}"
name_part2[$i]="${line##*' '}"
((++i))
done
unset i
if [ -z "${name_part1[0]}" ] && [ "${#name_part1[@]}" == "1" ]; then
echo -e "\nUsage: [ pipe | ] $0 [ < stdin ]"
echo -e "\nExamples:"
echo -e "\t$0 < names.txt"
echo -e "\t\t - or -"
echo -e "\tcat names.txt | $0"
echo -e "\nScript requires an input stream!\n"
exit 1
fi
get_names() {
echo "-fill black -pointsize 14"
i=0
# Two maintain alphabetical order vertically rather
# than horizontal + vertically we put the draw
# statements into two different loops to process
# all of one side of the card first
# Left side of card
while [ "$i" -lt "14" ]
do
# adjust placement for bottom half
if [ "$i" -lt "8" ]; then
y1=20
y2=34
else
y1=16
y2=30
fi
echo -e "-draw "text 8,$(($y1 + 43 * $i)) '${name_part1[$i]}'""
echo -e "-draw "text 8,$(($y2 + 43 * $i)) '${name_part2[$i]}'""
((i++))
done
# Right side of card
while [ "$i" -lt "28" ]
do
# adjust placement for bottom half
if [ "$i" -lt "15" ]; then
y1=20
y2=34
else
y1=16
y2=30
fi
echo -e "-draw "text 103,$(($y1 + 43 * $((i - 14)))) '${name_part1[$i]}'""
echo -e "-draw "text 103,$(($y2 + 43 * $((i - 14)))) '${name_part2[$i]}'""
((i++))
done
}
offset() {
skew="$1"
((skew--))
skew="$((skew * 10))"
skew="$((skew * 5 / 10))"
if [ "${skew: -1:1}" == "5" ]; then
skew="$((skew+ 10))"
fi
skew="${skew:0:1}"
echo "$skew"
}
draw_box() {
echo "-fill none -strokewidth 2 -stroke black "
echo "-draw 'stroke-dasharray 5 3 rectangle 1,1,187,595' "
echo "-fill none -strokewidth 1 -stroke black "
echo "-draw 'line 95,0 95,609' "
row=1
while [ "$row" -le "13" ];
do
echo "-draw 'line 0,$((43 * $row - $(offset $row) )) 190,$((43 * $row - $(offset $row) ))' "
((++row))
done
}
# Generate left template
bash -c "$(echo convert -size 189x597 xc:white $(get_names) $(draw_box) template1.png)"
# shift remaining names down the array
if [ "${#name_part1[@]}" -gt "27" ]; then
i=0
while [ "$i" -le "27" ]
do
name_part1[$i]="${name_part1[$((i + 28))]}"
name_part2[$i]="${name_part2[$((i + 28))]}"
((i++))
done
fi
# Generate right template
bash -c "$(echo convert -size 189x597 xc:white $(get_names) $(draw_box) template2.png)"
# side by side on one peice of paper
#montage -units PixelsPerInch -density 96 template?.png -mode concatenate -tile 2x template.pdf
# Or one per page
convert -units PixelsPerInch -density 96 template?.png template.pdf
if [ -f "template.pdf" ]; then
rm template1.png template2.png
fi
Provisioning Script
Some of this script is repurposed from my earlier gspush.sh script which can be used to program other features beyond the GXP2020EXT. (If you work a lot with older Grandstream phones you might be interested in that article as well).
This script is meant to be used with the GXP2020EXT connected to a Grandstream GXP21xx phone. If you somehow have it hooked up to something else you may need to adjust the values in the script first.
The provisioning script is easy to use. Feed it the list of names and specify the IP address of the phone with the GXP2020EXT connected to it. If you are using a password other than the default specify it after the IP address. If you forget how to run the command just run it without input for a reminder:
Usage: stdout |./provision_gxp2020ext.sh <IP Address of Grandstream GXP2020EXT device> [password]
Supply a list of names and extensions (one per line) via stdio in the format:
Name <Extension>
Output looks something like this, but will vary depending on the firmware of the GXP21xx phone
Sid: 8beeff00d42
Status:
Status: 1
Done.
download provision_gxp2020ext.sh
GXP_PHONE_IP="$1"
if [ -n "$2" ]; then
GXP_PASSWD="$2"
else
GXP_PASSWD="admin" # default
fi
get_sid() {
while read -r || [[ -n "$REPLY" ]]
do
if [ -n "$sid" ]; then
break
fi
if [ "${REPLY:0:23}" == "Set-Cookie: session_id=" ]; then
sid="${REPLY:23}"
sid="${sid//;}"
fi
if [ "${REPLY//'"sid"'}" != "$REPLY" ]; then
sid="${REPLY#*'"sid"'}"
sid="${sid#*'"'}"
sid="${sid%%'"'*}"
fi
done
echo "$sid"
}
post_status() {
# Older firmware says need to reboot, newer firmware says success
while read -r || [[ -n "$REPLY" ]]
do
if [ "${REPLY//eboot}" != "$REPLY" ]; then
echo "1"
break
fi
if [ "${REPLY//success}" != "$REPLY" ]; then
echo "1"
break
fi
done
}
urlencode () {
tab="`echo -en "\x9"`"
i="$@"
i=${i//%/%25} ; i=${i//' '/%20} ; i=${i//$tab/%09}
i=${i//!/%21} ; i=${i//'"'/%22} ; i=${i//#/%23}
i=${i//\$/%24} ; i=${i//\&/%26} ; i=${i//\'/%27}
i=${i//(/%28} ; i=${i//)/%29} ; i=${i//\*/%2a}
i=${i//+/%2b} ; i=${i//,/%2c} ; i=${i//-/%2d}
i=${i//\./%2e} ; i=${i//\//%2f} ; i=${i//:/%3a}
i=${i//;/%3b} ; i=${i//</%3c} ; i=${i//=/%3d}
i=${i//>/%3e} ; i=${i//\?/%3f} ; i=${i//@/%40}
i=${i//\[/%5b} ; i=${i//\\/%5c} ; i=${i//\]/%5d}
i=${i//\^/%5e} ; i=${i//_/%5f} ; i=${i//\`/%60}
i=${i//\{/%7b} ; i=${i//|/%7c} ; i=${i//\}/%7d}
i=${i//\~/%7e}
echo "$i"
i=""
}
# Read stdin and create the string of Grandstream config values
# Valid Key mode values:
# 0 = Speed Dial, 1 = BLF, 2 = Presence Watcher, 3 = eventlist BLF,
# 4 = Speed Dial via active account, 5 = DialDTMF, 6 = Voicemail,
# 7 = CallReturn, 8 = Transfer, 9 = CallPark, 10 = Intercom,
# 11 = LDAP Search
Pvalues=""
i=0
while read -t1 line
do
name="${line%%'<'*}"
extension="${line##*'<'}"
extension="${extension%%'>'*}"
if [ -z "$name" ]; then # Turn off any unused/old BLF
Pvalues+="P$((6001 + i))=0&" # Key mode, speed dial
else
Pvalues+="P$((6001 + i))=1&" # Key mode, BLF
fi
Pvalues+="P$((6201 + i))=0&" # Account
Pvalues+="P$((6401 + i))=$(urlencode "$name")&" # Name
Pvalues+="P$((6601 + i))=$(urlencode "$extension")&" # UserID
((i++))
done
if [ -z "$Pvalues" ] || [ -z "$GXP_PHONE_IP" ]; then
echo "Usage: stdout |$0 <IP Address of Grandstream GXP2020EXT device> [password]" >&2
echo -e "\tSupply a list of names and extensions (one per line) via stdio in the format:" >&2
echo -e "\tName <Extension>" >&2
exit 1
fi
# Provision the GXP2020EXT
# Older firmware passed password as "P2", newer firmware passes as "password"
# <form> action is the same
sid="$(curl -s -i -d "P2=$GXP_PASSWD&password=$GXP_PASSWD" "http://${GXP_PHONE_IP}/cgi-bin/dologin" |get_sid)"
echo "Sid: $sid"
# <form> action for config update is different across firmware versions
# Try old firmware style first
status="$(curl -s -i -b "session_id=$sid" -d "${Pvalues}update=Update" "http://${GXP_PHONE_IP}/cgi-bin/update" |post_status)"
echo "Status: $status"
# Try new firmware way if the last attempt failed
if [ -z "$status" ]; then
# # Try new firmware config update action
status="$(curl -s -i -b "session_id=$sid" -d "$Pvalues&sid=$sid" "http://${GXP_PHONE_IP}/cgi-bin/api.values.post" |post_status)"
echo "Status: $status"
fi
if [ -z "$status" ]; then
# Couldn't update speed dial on phone
echo "Could not update speed dial correctly"
exit 1
else
echo "Done."
fi
That’s all there is to do now when we have a change of staff. Actually, at our company I took this one step further, by using my Grandstream / Elastix Phonebook Provisioning URL in a cron task to periodically export the names and look for changes between polling intervals. When changes are detected, the GXP2020EXT is automatically re-provisoned to reflect them, and an updated template is emailed to IT staff to print, cut, and replace. I’m now free to ignore this task completely.
Post your comment