Jekyll2021-04-12T07:39:21+00:00/feed.xmlEike’s BlogJust some random technical tidbits and opinions, written mostly for my benefit. You are welcome to any insights you may accidentally find here.
Fun with an old Cisco 7941G IP Phone, Part 1 - Getting started2017-06-05T13:01:00+00:002017-06-05T13:01:00+00:00/2017/06/05/fun-with-old-cisco-ip-phones-part1<p>For quite some time I have enjoyed playing around with the SIP protocol. I’ve run an asterisk installation on a repurposed Seagate Dockstar (go to [http://projects.doozan.com/debian/] if
you’re intersted in more details on running Debian on one of those boxes), I’ve played around with a number of WiFi SIP phones (all of which sucked, unfortunately) and currently my FritzBox 3490 takes care of all my telephony needs.</p>
<p>Anyways, recently I decided I needed another phone and set my mind on obtaining an older Cisco IP phone, flashing that to a SIP firmware and adding an account for that to the FritzBox.
All of which I managed to do eventually, although the ride turned out to be a bit bumpier than I expected.</p>
<p>Most importantly, if you want to set out on the same journey, <strong>do NOT perform a factory reset on the phone</strong>. It is not the most terrible thing in the world but it makes things unnecessarily complicated.</p>
<h1 id="what-you-will-need">What you will need</h1>
<ul>
<li>Firmware files for the latest SIP firmware for your Cisco phone. I was able to obtain those directly from Cisco by registering for a free account. Some very old files require an additional license but at least in my case I did not need those.</li>
<li>Depending on the age of the firmware installed on your phone, you’ll need one or two older sets of firmware files. You will know in case your phone refuses to update to the newest firmware files in one go. I just grabbed some versions in between the one that was one the phone (which was the Skinny/SCP firmware, but the numbers appear to be in sync) and the one I wanted to upgrade to, and used each on in succession.</li>
<li>A TFTP server. I just did an <code class="language-plaintext highlighter-rouge">apt-get install tfptd</code>. According to most accounts TFTPD32 is the weapon of choice on Windows, but I can claim not first-hand knowledge of that.</li>
<li>Only if you did a factory-reset of your phone: A DHCP server that allows you to set option 150. TFTPD32 seems to be DHCP and TFTPD rolled into one, so you should be set. On Linux, you can simply use dnsmasq. If, like me, you run some closed box like a FritzBox, you’ll need to disable its DHCP server and use another one until the phone is back in working state, hence my recommendation to not perform a factory reset at all.</li>
</ul>
<h1 id="configure-the-phone-to-find-your-tftp-server">Configure the phone to find your TFTP server.</h1>
<p>By default, the phone will expect your DHCP server to provide the name/address of a TFTP server through option 150. If your setup allows you to easily do that,
feel free to just add that option in your DHCP config. Otherwise, go into your phone’s settings (checkmark key on the 7941G) and the go to <strong>Network Configuration</strong>, from there to <strong>IPv4 Configuration</strong> and then go down to <strong>Alternate TFTP Server</strong>. You may notice that there is no apparent way to change this, and in fact you will need to unlock the settings in order to be able
to make any changes. Hit <code class="language-plaintext highlighter-rouge">**#</code> on the keypad and after a second or two the phone will tell you that the settings are unlocked. Now set <strong>Alternate TFTP Server</strong> to <strong>Yes</strong> and on the next line provide the name or IP of your TFTP server.</p>
<h1 id="place-required-files-on-the-tftp-server">Place required files on the TFTP server</h1>
<ul>
<li>Unzip the firmware ZIP file(s) and place <strong>all</strong> of the extracted files in the tftp server directory. (They should have names like <code class="language-plaintext highlighter-rouge">SIP41.9-4-2SR3-1S.loads</code>, <code class="language-plaintext highlighter-rouge">apps41.9-4-2ES26.sbn</code>, <code class="language-plaintext highlighter-rouge">cnu41.9-4-2ES26.sbn</code> and so on.) Except for the <code class="language-plaintext highlighter-rouge">termXX.default.loads</code> files the file names don’t clash and you can keep the files side-by-side. Unless you did a factory reset (did I mention that I recommend not do do that?) this will not matter.</li>
<li>Create an XML file called <code class="language-plaintext highlighter-rouge">SEP<Mac of your phone without colons>.cnf.xml</code> i.e. if the MAC address of your phone is <code class="language-plaintext highlighter-rouge">00:80:41:ae:fd:7e</code> the file must be called <code class="language-plaintext highlighter-rouge">SEP008041AEFD7E.cnf.xml</code>.Unfortunately it is next to impossible to find documentation on the format of this file, but you can use the following template. Feel free to scour the internet for other example files that might better illustrate the purpose of the individual elements, but this one has served me well so far. Below the full file I will summarize the elements that you need to change to end up with a working configuration.</li>
</ul>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><device></span>
<span class="nt"><deviceProtocol></span>SIP<span class="nt"></deviceProtocol></span>
<span class="nt"><sshUserId></span>admin<span class="nt"></sshUserId></span>
<span class="nt"><sshPassword></span>admin<span class="nt"></sshPassword></span>
<span class="nt"><devicePool></span>
<span class="nt"><dateTimeSetting></span>
<span class="c"><!-- Only two digits for the year --></span>
<span class="nt"><dateTemplate></span>D.M.YY<span class="nt"></dateTemplate></span>
<span class="nt"><timeZone></span>Central Europe Standard/Daylight Time<span class="nt"></timeZone></span>
<span class="nt"><ntps></span>
<span class="nt"><ntp></span>
<span class="c"><!-- IP of an NTP server to use for setting the phone date/time. --></span>
<span class="nt"><name></span>192.168.1.1<span class="nt"></name></span>
<span class="nt"><ntpMode></span>Unicast<span class="nt"></ntpMode></span>
<span class="nt"></ntp></span>
<span class="nt"></ntps></span>
<span class="nt"></dateTimeSetting></span>
<span class="nt"><callManagerGroup></span>
<span class="nt"><members></span>
<span class="nt"><member</span> <span class="na">priority=</span><span class="s">"0"</span><span class="nt">></span>
<span class="nt"><callManager></span>
<span class="nt"><ports></span>
<span class="nt"><ethernetPhonePort></span>2000<span class="nt"></ethernetPhonePort></span>
<span class="nt"><sipPort></span>5060<span class="nt"></sipPort></span>
<span class="nt"><securedSipPort></span>5061<span class="nt"></securedSipPort></span>
<span class="nt"></ports></span>
<span class="c"><!-- IP address of your SIP server --></span>
<span class="nt"><processNodeName></span>192.168.1.1<span class="nt"></processNodeName></span>
<span class="nt"></callManager></span>
<span class="nt"></member></span>
<span class="nt"></members></span>
<span class="nt"></callManagerGroup></span>
<span class="nt"></devicePool></span>
<span class="nt"><commonProfile></span>
<span class="nt"><phonePassword></phonePassword></span>
<span class="nt"><backgroundImageAccess></span>true<span class="nt"></backgroundImageAccess></span>
<span class="nt"><callLogBlfEnabled></span>2<span class="nt"></callLogBlfEnabled></span>
<span class="nt"></commonProfile></span>
<span class="c"><!-- Here you can set the firmware version the phone should load --></span>
<span class="nt"><loadInformation></span>SIP41.9-4-2SR3-1S<span class="nt"></loadInformation></span>
<span class="nt"><vendorConfig></span>
<span class="nt"><disableSpeaker></span>false<span class="nt"></disableSpeaker></span>
<span class="nt"><disableSpeakerAndHeadset></span>false<span class="nt"></disableSpeakerAndHeadset></span>
<span class="nt"><pcPort></span>0<span class="nt"></pcPort></span>
<span class="nt"><settingsAccess></span>1<span class="nt"></settingsAccess></span>
<span class="nt"><garp></span>0<span class="nt"></garp></span>
<span class="nt"><voiceVlanAccess></span>0<span class="nt"></voiceVlanAccess></span>
<span class="nt"><videoCapability></span>0<span class="nt"></videoCapability></span>
<span class="nt"><autoSelectLineEnable></span>0<span class="nt"></autoSelectLineEnable></span>
<span class="nt"><sshAccess></span>0<span class="nt"></sshAccess></span>
<span class="nt"><sshPort></span>22<span class="nt"></sshPort></span>
<span class="nt"><webAccess></span>0<span class="nt"></webAccess></span>
<span class="nt"><spanToPCPort></span>1<span class="nt"></spanToPCPort></span>
<span class="nt"><loggingDisplay></span>1<span class="nt"></loggingDisplay></span>
<span class="nt"><loadServer></loadServer></span>
<span class="nt"><daysDisplayNotActive></daysDisplayNotActive></span>
<span class="nt"><displayOnTime></span>03:00<span class="nt"></displayOnTime></span>
<span class="nt"><displayOnDuration></span>00:01<span class="nt"></displayOnDuration></span>
<span class="nt"><displayIdleTimeout></span>00:05<span class="nt"></displayIdleTimeout></span>
<span class="nt"><displayOnWhenIncomingCall></span>1<span class="nt"></displayOnWhenIncomingCall></span>
<span class="nt"></vendorConfig></span>
<span class="nt"><deviceSecurityMode></span>1<span class="nt"></deviceSecurityMode></span>
<span class="c"><!-- unused --></span>
<span class="nt"><authenticationURL></span>http://192.168.44.1/ciscoauth.php<span class="nt"></authenticationURL></span>
<span class="c"><!-- an URL that points to a place that provides phonebook access, unused for now --></span>
<span class="nt"><directoryURL></span>http://192.168.44.1/directory.php<span class="nt"></directoryURL></span>
<span class="c"><!-- an URL that points to an image that the phone should show in idle mode, we'll get to that in another post --></span>
<span class="nt"><idleURL></span>http://valhalla.fritz.box/idle.xml<span class="nt"></idleURL></span>
<span class="c"><!-- timeout (in seconds) after which the idle URI is called --></span>
<span class="nt"><idleTimeout></span>60<span class="nt"></idleTimeout></span>
<span class="c"><!-- the URL that is called by the phone when hitting the "?" button --></span>
<span class="nt"><informationURL></informationURL></span>
<span class="nt"><messagesURL></messagesURL></span>
<span class="nt"><proxyServerURL></proxyServerURL></span>
<span class="c"><!-- URL that is called by the phone when opening the "services" menu, we'll get to that in another post --></span>
<span class="nt"><servicesURL></span>http://valhalla.fritz.box/services.xml<span class="nt"></servicesURL></span>
<span class="nt"><dscpForSCCPPhoneConfig></span>96<span class="nt"></dscpForSCCPPhoneConfig></span>
<span class="nt"><dscpForSCCPPhoneServices></span>0<span class="nt"></dscpForSCCPPhoneServices></span>
<span class="nt"><dscpForCm2Dvce></span>96<span class="nt"></dscpForCm2Dvce></span>
<span class="nt"><transportLayerProtocol></span>2<span class="nt"></transportLayerProtocol></span>
<span class="nt"><capfAuthMode></span>0<span class="nt"></capfAuthMode></span>
<span class="nt"><capfList></span>
<span class="nt"><capf></span>
<span class="nt"><phonePort></span>3804<span class="nt"></phonePort></span>
<span class="nt"></capf></span>
<span class="nt"></capfList></span>
<span class="nt"><certHash></certHash></span>
<span class="nt"><encrConfig></span>false<span class="nt"></encrConfig></span>
<span class="nt"><sipProfile></span>
<span class="nt"><sipProxies></span>
<span class="nt"><backupProxy></backupProxy></span>
<span class="nt"><backupProxyPort></backupProxyPort></span>
<span class="nt"><emergencyProxy></emergencyProxy></span>
<span class="nt"><emergencyProxyPort></emergencyProxyPort></span>
<span class="c"><!-- name/IP of the outbound proxy to use --></span>
<span class="nt"><outboundProxy></span>192.168.1.1<span class="nt"></outboundProxy></span>
<span class="c"><!-- SIP port of the outbound proxy --></span>
<span class="nt"><outboundProxyPort></span>5060<span class="nt"></outboundProxyPort></span>
<span class="c"><!-- whether to register with the proxy, normally you want "true" here --></span>
<span class="nt"><registerWithProxy></span>true<span class="nt"></registerWithProxy></span>
<span class="nt"></sipProxies></span>
<span class="nt"><sipCallFeatures></span>
<span class="nt"><cnfJoinEnabled></span>true<span class="nt"></cnfJoinEnabled></span>
<span class="nt"><callForwardURI></span>x--serviceuri-cfwdall<span class="nt"></callForwardURI></span>
<span class="nt"><callPickupURI></span>x-cisco-serviceuri-pickup<span class="nt"></callPickupURI></span>
<span class="nt"><callPickupListURI></span>x-cisco-serviceuri-opickup<span class="nt"></callPickupListURI></span>
<span class="nt"><callPickupGroupURI></span>x-cisco-serviceuri-gpickup<span class="nt"></callPickupGroupURI></span>
<span class="nt"><meetMeServiceURI></span>x-cisco-serviceuri-meetme<span class="nt"></meetMeServiceURI></span>
<span class="nt"><abbreviatedDialURI></span>x-cisco-serviceuri-abbrdial<span class="nt"></abbreviatedDialURI></span>
<span class="nt"><rfc2543Hold></span>false<span class="nt"></rfc2543Hold></span>
<span class="nt"><callHoldRingback></span>2<span class="nt"></callHoldRingback></span>
<span class="nt"><localCfwdEnable></span>true<span class="nt"></localCfwdEnable></span>
<span class="nt"><semiAttendedTransfer></span>true<span class="nt"></semiAttendedTransfer></span>
<span class="nt"><anonymousCallBlock></span>2<span class="nt"></anonymousCallBlock></span>
<span class="nt"><callerIdBlocking></span>2<span class="nt"></callerIdBlocking></span>
<span class="nt"><dndControl></span>0<span class="nt"></dndControl></span>
<span class="nt"><remoteCcEnable></span>true<span class="nt"></remoteCcEnable></span>
<span class="nt"></sipCallFeatures></span>
<span class="nt"><sipStack></span>
<span class="nt"><sipInviteRetx></span>6<span class="nt"></sipInviteRetx></span>
<span class="nt"><sipRetx></span>10<span class="nt"></sipRetx></span>
<span class="nt"><timerInviteExpires></span>180<span class="nt"></timerInviteExpires></span>
<span class="nt"><timerRegisterExpires></span>3600<span class="nt"></timerRegisterExpires></span>
<span class="nt"><timerRegisterDelta></span>5<span class="nt"></timerRegisterDelta></span>
<span class="nt"><timerKeepAliveExpires></span>120<span class="nt"></timerKeepAliveExpires></span>
<span class="nt"><timerSubscribeExpires></span>120<span class="nt"></timerSubscribeExpires></span>
<span class="nt"><timerSubscribeDelta></span>5<span class="nt"></timerSubscribeDelta></span>
<span class="nt"><timerT1></span>500<span class="nt"></timerT1></span>
<span class="nt"><timerT2></span>4000<span class="nt"></timerT2></span>
<span class="nt"><maxRedirects></span>70<span class="nt"></maxRedirects></span>
<span class="nt"><remotePartyID></span>false<span class="nt"></remotePartyID></span>
<span class="nt"><userInfo></span>None<span class="nt"></userInfo></span>
<span class="nt"></sipStack></span>
<span class="nt"><autoAnswerTimer></span>1<span class="nt"></autoAnswerTimer></span>
<span class="nt"><autoAnswerAltBehavior></span>false<span class="nt"></autoAnswerAltBehavior></span>
<span class="nt"><autoAnswerOverride></span>true<span class="nt"></autoAnswerOverride></span>
<span class="nt"><transferOnhookEnabled></span>false<span class="nt"></transferOnhookEnabled></span>
<span class="nt"><enableVad></span>false<span class="nt"></enableVad></span>
<span class="nt"><preferredCodec></span>none<span class="nt"></preferredCodec></span>
<span class="nt"><dtmfAvtPayload></span>101<span class="nt"></dtmfAvtPayload></span>
<span class="nt"><dtmfDbLevel></span>3<span class="nt"></dtmfDbLevel></span>
<span class="nt"><dtmfOutofBand></span>avt<span class="nt"></dtmfOutofBand></span>
<span class="nt"><alwaysUsePrimeLine></span>false<span class="nt"></alwaysUsePrimeLine></span>
<span class="nt"><alwaysUsePrimeLineVoiceMail></span>false<span class="nt"></alwaysUsePrimeLineVoiceMail></span>
<span class="nt"><kpml></span>3<span class="nt"></kpml></span>
<span class="nt"><natEnabled></span>false<span class="nt"></natEnabled></span>
<span class="nt"><natAddress></natAddress></span>
<span class="nt"><stutterMsgWaiting></span>0<span class="nt"></stutterMsgWaiting></span>
<span class="nt"><callStats></span>false<span class="nt"></callStats></span>
<span class="nt"><silentPeriodBetweenCallWaitingBursts></span>10<span class="nt"></silentPeriodBetweenCallWaitingBursts></span>
<span class="nt"><disableLocalSpeedDialConfig></span>false<span class="nt"></disableLocalSpeedDialConfig></span>
<span class="nt"><startMediaPort></span>16384<span class="nt"></startMediaPort></span>
<span class="nt"><stopMediaPort></span>32766<span class="nt"></stopMediaPort></span>
<span class="nt"><voipControlPort></span>5060<span class="nt"></voipControlPort></span>
<span class="nt"><dscpForAudio></span>184<span class="nt"></dscpForAudio></span>
<span class="nt"><ringSettingBusyStationPolicy></span>0<span class="nt"></ringSettingBusyStationPolicy></span>
<span class="nt"><dialTemplate></span>dialplan.xml<span class="nt"></dialTemplate></span>
<span class="nt"><phoneLabel></span>Office<span class="nt"></phoneLabel></span>
<span class="nt"><sipLines></span>
<span class="c"><!-- Configures the soft function keys to the right of the display
<featureID>9</featureID> use this for primary lines (i.e. selecting the line for an outbound call)
<featureID>2</featureID> Speed dial
--></span>
<span class="nt"><line</span> <span class="na">button=</span><span class="s">"1"</span><span class="nt">></span>
<span class="nt"><featureID></span>9<span class="nt"></featureID></span>
<span class="c"><!-- Text displayed next to the soft key --></span>
<span class="nt"><featureLabel></span>Line 1<span class="nt"></featureLabel></span>
<span class="c"><!-- name of the SIP account associated to this line. Attention FritzBox users: must be identical to authName --></span>
<span class="nt"><name></span>office<span class="nt"></name></span>
<span class="nt"><displayName></span>620<span class="nt"></displayName></span>
<span class="nt"><contact></span>620<span class="nt"></contact></span>
<span class="c"><!-- Leave this set to USECALLMANAGER. If you expicitly enter the SIP proxy here again, things will not work. USECALLMANAGER
uses the proxy configured in the <callManagerGroup> section --></span>
<span class="nt"><proxy></span>USECALLMANAGER<span class="nt"></proxy></span>
<span class="nt"><port></span>5060<span class="nt"></port></span>
<span class="nt"><autoAnswer></span>
<span class="nt"><autoAnswerEnabled></span>2<span class="nt"></autoAnswerEnabled></span>
<span class="nt"></autoAnswer></span>
<span class="nt"><callWaiting></span>3<span class="nt"></callWaiting></span>
<span class="c"><!-- Authentication name of the SIP user. Must be identical to "name" for FRITZ!box --></span>
<span class="nt"><authName></span>office<span class="nt"></authName></span>
<span class="c"><!-- Authentication password of the SIP user. --></span>
<span class="nt"><authPassword></span>SECRET_PASSWORD<span class="nt"></authPassword></span>
<span class="nt"><sharedLine></span>false<span class="nt"></sharedLine></span>
<span class="nt"><messageWaitingLampPolicy></span>1<span class="nt"></messageWaitingLampPolicy></span>
<span class="c"><!-- the extension to dial when the user presses the "messages" key --></span>
<span class="nt"><messagesNumber></span>**600<span class="nt"></messagesNumber></span>
<span class="nt"><ringSettingIdle></span>4<span class="nt"></ringSettingIdle></span>
<span class="nt"><ringSettingActive></span>5<span class="nt"></ringSettingActive></span>
<span class="nt"><forwardCallInfoDisplay></span>
<span class="nt"><callerName></span>true<span class="nt"></callerName></span>
<span class="nt"><callerNumber></span>true<span class="nt"></callerNumber></span>
<span class="nt"><redirectedNumber></span>false<span class="nt"></redirectedNumber></span>
<span class="nt"><dialedNumber></span>true<span class="nt"></dialedNumber></span>
<span class="nt"></forwardCallInfoDisplay></span>
<span class="nt"></line></span>
<span class="c"><!-- You can freely assign function to buttons, here is an example for speed dial on button "6" --></span>
<span class="nt"><line</span> <span class="na">button=</span><span class="s">"6"</span><span class="nt">></span>
<span class="nt"><featureID></span>2<span class="nt"></featureID></span>
<span class="nt"><featureLabel></span>Emergency Call<span class="nt"></featureLabel></span>
<span class="nt"><speedDialNumber></span>911<span class="nt"></speedDialNumber></span>
<span class="nt"></line></span>
<span class="nt"></sipLines></span>
<span class="nt"></sipProfile></span>
<span class="nt"></device></span>
</code></pre></div></div>
<h1 id="key-configuration-elements">Key configuration elements</h1>
<ul>
<li><code class="language-plaintext highlighter-rouge">loadInformation</code>: This element tells your phone which firmware to load. Each firmware ZIP includes a file like <code class="language-plaintext highlighter-rouge">SIP41.9-4-2SR3-1S.loads</code>. Use that name, minus the “.loads” extension, to identify the desired firmware version. (As mentioned above, if your phone refuses to go straight to the desired version, try a lower version first and approach the desired version step-by-step.)</li>
<li><code class="language-plaintext highlighter-rouge">processNodeName</code> inside the <code class="language-plaintext highlighter-rouge">callManager</code> element: The name/IP of your SIP server.</li>
<li><code class="language-plaintext highlighter-rouge">sipProfile/sipProxies</code>: Of note in this element are <code class="language-plaintext highlighter-rouge">outboundProxy</code>, <code class="language-plaintext highlighter-rouge">outboundProxyPort</code> and <code class="language-plaintext highlighter-rouge">registerWithProxy</code> to configure the IP/name and port of your outbound SIP proxy and to enable proxy usage.</li>
<li><code class="language-plaintext highlighter-rouge">sipLines</code>: This section is of course important in order to be able to make any calls. Make sure you configure at least one <code class="language-plaintext highlighter-rouge">line</code> block, be sure to leave in <code class="language-plaintext highlighter-rouge"><proxy>USECALLMANAGER</proxy></code> and substitute the corect values for <code class="language-plaintext highlighter-rouge">name</code>, <code class="language-plaintext highlighter-rouge">authName</code> (watch out: the FritzBox won’t work if the two are not identical) and <code class="language-plaintext highlighter-rouge">authPassword</code>. Set <code class="language-plaintext highlighter-rouge">messagesNumber</code> so it points to the correct extension for accessing your voice messages.</li>
</ul>
<h1 id="wrapping-things-up">Wrapping things up</h1>
<p>If everything went according to plan you should now be able to make and receive calls from your Cisco IP phone. If you want to get rid of the log messages concerning the dialplan you can simply put an empty dialplan on the TFTP server, like so:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dialplan></span>
<span class="nt"></dialplan></span>
</code></pre></div></div>
<p>I will follow up with some more posts on:</p>
<ul>
<li>Configuring an idle url and having fun with the services menu.</li>
<li>Adding custom ringtones.</li>
<li>Adding some plumbing to allow the phone to use a CalDAV server as a directory back-end.</li>
</ul>
<h1 id="references">References</h1>
<p>In the process of getting my phone to work the way I want it to, I pulled together information from various sources. If this guide is of any use to you it is by virtue of the following sources. The list is non-exhaustive and in no particular order:</p>
<ul>
<li>
<table>
<tbody>
<tr>
<td>(German) [Marvin Menzerath on getting a Cisco 7940/7960 working on a FritzBox</td>
<td>https://blog.marvin-menzerath.de/artikel/cisco-ip-phone-7940-7960-mit-fritzbox-verwenden/]</td>
</tr>
</tbody>
</table>
</li>
<li>
<table>
<tbody>
<tr>
<td>(German) [Getting a Cisco 7970 to work with a FritzBox, plus some general configuration info</td>
<td>http://www.arbeitsplatzvernichtung-durch-outsourcing.de/marty44/fritzcisco7970.html]</td>
</tr>
</tbody>
</table>
</li>
<li>
<table>
<tbody>
<tr>
<td>(German) [Getting a Cisco 7961G to work with a FritzBox</td>
<td>https://ctx4tom.wordpress.com/2014/03/16/cisco-ip-phone-an-fritzbox-teil-1-sip-firmware-aufspielen/]</td>
</tr>
</tbody>
</table>
</li>
<li>
<table>
<tbody>
<tr>
<td>[How to unlock the settings on Cisco IP Phones</td>
<td>https://supportforums.cisco.com/discussion/10936506/unlocking-settings-7945-sip-phone]</td>
</tr>
</tbody>
</table>
</li>
<li>
<table>
<tbody>
<tr>
<td>[Installing SIP firmware on a Cisco IP Phone</td>
<td>http://www.pearsonitcertification.com/articles/article.aspx?p=1320201&seqNum=6]</td>
</tr>
</tbody>
</table>
</li>
<li>
<table>
<tbody>
<tr>
<td>[Rebooting a Cisco IP Phone</td>
<td>http://www.runpcrun.com/rebootciscophone]</td>
</tr>
</tbody>
</table>
</li>
<li>
<table>
<tbody>
<tr>
<td>(Bonus) [Fixing the hook switch on Cisco IP Phones</td>
<td>http://www.nelsonet.net/wordpress/blog/2009/05/28/cisco-7941-and-7961-phone-handset-will-not-pick-up-or-answer/]</td>
</tr>
</tbody>
</table>
</li>
<li>
<table>
<tbody>
<tr>
<td>[The VoIP info wiki has some documentation regarding the phone configuration</td>
<td>https://www.voip-info.org/wiki/view/Standalone+Cisco+7941/7961+without+a+local+PBX]</td>
</tr>
</tbody>
</table>
</li>
</ul>For quite some time I have enjoyed playing around with the SIP protocol. I’ve run an asterisk installation on a repurposed Seagate Dockstar (go to [http://projects.doozan.com/debian/] if you’re intersted in more details on running Debian on one of those boxes), I’ve played around with a number of WiFi SIP phones (all of which sucked, unfortunately) and currently my FritzBox 3490 takes care of all my telephony needs.Date arithmetic is scary2017-05-15T17:29:00+00:002017-05-15T17:29:00+00:00/2017/05/15/how-to-shoot-yourself-in-the-foot-with-date-arithmetic<p>I don’t need to tell you that. If you ever watched <a href="https://www.youtube.com/channel/UC9-y-6csu5WGm29I7JiwpnA">Computerphile</a> explain the <a href="https://www.youtube.com/watch?v=-5wpm-gesOY">horror that lurks behind dealing with timezones</a>, chances are you got the point.</p>
<p>So, hopefully, you use a library to take care of your Date/Time needs. If you are a Java programmer, this will most likely be JodaTime or the Java 8 DateTime API. Even so, you still have quite enough rope in hand to hang yourself. Consider the following example (which uses the <code class="language-plaintext highlighter-rouge">java.time</code> library, but applies to JodaTime in the same way):</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">de.elang.datetime</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.Period</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.ZoneId</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.ZonedDateTime</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">java</span><span class="o">.</span><span class="na">time</span><span class="o">.</span><span class="na">ZoneOffset</span><span class="o">.</span><span class="na">UTC</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">org</span><span class="o">.</span><span class="na">junit</span><span class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">;</span>
<span class="cm">/**
* Created by eike on 14.05.17.
*/</span>
<span class="kd">class</span> <span class="nc">DateTimeBehaviourFuckery</span> <span class="o">{</span>
<span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">addingTheSamePeriodToTwoRepresentationsOfTheSameTimeWillResultInIdenticalInstants</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">final</span> <span class="nc">ZonedDateTime</span> <span class="n">centralEuropeanTimeBeforeDST</span> <span class="o">=</span> <span class="nc">ZonedDateTime</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">2017</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">20</span><span class="o">,</span> <span class="mi">11</span><span class="o">,</span> <span class="mi">27</span><span class="o">,</span> <span class="mi">33</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="nc">ZoneId</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"CET"</span><span class="o">));</span>
<span class="nc">ZonedDateTime</span> <span class="n">centralEuropeanTimeInDST</span> <span class="o">=</span> <span class="n">centralEuropeanTimeBeforeDST</span><span class="o">.</span><span class="na">plus</span><span class="o">(</span><span class="nc">Period</span><span class="o">.</span><span class="na">ofMonths</span><span class="o">(</span><span class="mi">3</span><span class="o">));</span>
<span class="kd">final</span> <span class="nc">ZonedDateTime</span> <span class="n">greenwichTime</span> <span class="o">=</span> <span class="n">centralEuropeanTimeBeforeDST</span><span class="o">.</span><span class="na">withZoneSameInstant</span><span class="o">(</span><span class="no">UTC</span><span class="o">);</span>
<span class="kd">final</span> <span class="nc">ZonedDateTime</span> <span class="n">greenwichLater</span> <span class="o">=</span> <span class="n">greenwichTime</span><span class="o">.</span><span class="na">plus</span><span class="o">(</span><span class="nc">Period</span><span class="o">.</span><span class="na">ofMonths</span><span class="o">(</span><span class="mi">3</span><span class="o">));</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="n">centralEuropeanTimeInDST</span><span class="o">.</span><span class="na">toInstant</span><span class="o">().</span><span class="na">toEpochMilli</span><span class="o">(),</span> <span class="n">greenwichLater</span><span class="o">.</span><span class="na">toInstant</span><span class="o">().</span><span class="na">toEpochMilli</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>If, like me, you naïvely assumed this test to succeed, do read on. Otherwise, feel free to gloat or just consider yourself lucky you dodged that bullet.</p>
<p>So, unexepectedly, this test <strong>fails</strong> with a time difference of 3600000 milliseconds (i.e. one hour) between the two dates. What happened?</p>
<p>In adding three months to the date of March 20, 2017 we inadvertedly crossed the point in time at which daylight savings time comes into effect, i.e. on Sunday, March 26, 2017 the clock went from 02:00 in the morning to 03:00 in the morning. Adding three months to our initial date in CET brings us from <code class="language-plaintext highlighter-rouge">2017-03-20T11:27:33+01:00[CET]</code> to <code class="language-plaintext highlighter-rouge">2017-06-20T11:27:33+02:00[CET]</code>, which is of course <code class="language-plaintext highlighter-rouge">2017-06-20T09:27:33Z</code>.</p>
<p>UTC does not have the notion of daylight savings time, so we go straight from <code class="language-plaintext highlighter-rouge">2017-03-20T10:27:33Z</code> to <code class="language-plaintext highlighter-rouge">2017-06-20T10:27:33Z</code> without saving that extra hour, so our three months are actually an hour longer in this case.</p>
<h1 id="what-to-do">What to do?</h1>
<p>There is no absolute answer to that question, you will have to decide on a solution for yourself.</p>
<p>I am assuming that:</p>
<ul>
<li>your are storing your dates as “milliseconds since the epoch” in your database</li>
<li>you don’t want to duplicate any of the efforts that have gone into the Java8 DateTime library or JodaTime and therefore want to deserialize your DB entries into proper objects in order to do date calculations.</li>
<li>you don’t have the luxury of just doing everything in UTC and presenting it as such</li>
<li>the result of the calculation is something you want to store (e.g. a due date, reminder, etc.)</li>
</ul>
<p>Based on those assumptions you can:</p>
<ul>
<li>Convert all dates to UTC as soon as possible and convert them into a local timezone at the last moment, in which case any period spanning a DST gap could be an hour too long (northern hemisphere) or too short (southern hemisphere) compared to a calculation in local time.</li>
<li>Do all calculations in local time, which makes them valid only in the context of their timezone.</li>
<li>Work around the issue with custom code that addresses your specific need.</li>
</ul>
<h1 id="conclusion">Conclusion</h1>
<p>Date calcuations can be nasty even when using mature date/time libraries. Be mindful of these pitfalls and address them in a way that best fits your needs.</p>I don’t need to tell you that. If you ever watched Computerphile explain the horror that lurks behind dealing with timezones, chances are you got the point.