├── LICENSE ├── README.md ├── examples ├── DualDoorbell │ └── DualDoorbell.ino └── LaundryNotifier │ └── LaundryNotifier.ino ├── keywords.txt ├── library.properties └── src ├── ArduinoSIP.cpp └── ArduinoSIP.h /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018 Juergen Liegner All rights reserved. 4 | Copyright (c) 2019 Thorsten Godau (dl9sec). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the author(s) nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArduinoSIP 2 | Arduino SIP library with UDP communications. Original class and methods authored by Juergen Liegner (SIP protocoll not fully implemented. Discussed at https://www.mikrocontroller.net/topic/444994). 3 | Can be used do do a notifying SIP call through e.g. a FRITZ!Box. 4 | 5 | Tested with NodeMCU 1.0 (AI-Thinker ESP8266MOD ESP-12E) and D1 mini clone (ESP8266MOD ESP-12F). 6 | Because of this, _"architectures"_ in _"library.properties"_ is currently set to _"esp8266"-only_. If you want to use the library on another Arduino architecture, replace the _"esp8266"_ with a _"*"_ and let me know, if it works. 7 | 8 | # Prequisites for the examples 9 | 10 | * **General** 11 | * You need a FRITZ!Box or equivalent which supports SIP and TR-064. 12 | * Grant access for applications to the FRITZ!Box:
13 | `Heimnetz -> Netzwerk -> Netzwerkeinstellungen -> Heimnetzfreigaben: Zufriff für Anwendungen zulassen / Statusinformationen über UPnP übertragen` 14 | 15 | * **For SIP call functionality ("DualDoorbell" and "LaundryNotifier" example)** 16 | * The ESP8266 acts as a VOIP telephone for the FRITZ!Box, so a new phone has to be set up:
17 | `Telefonie -> Telefoniegeräte -> Neues Gerät einrichten: Telefon (mit und ohne Anrufbeantworter) -> LAN/WLAN (IP-Telefon) -> Benutzername / Kennwort ...` 18 | * Follow the setup assistant. Benutzername (Username) will be used for "SipUSER" and Kennwort (Password) will be used for "SipPW" in the code. 19 | 20 | * **For TR-064 SOAP communications ("LaundryNotifier" example)** 21 | * You need at least a FRITZ!DECT 200 (10A max.) or FRITZ!DECT 210 (15A max.) depending on your laundry devices. The example is made for two devices. 22 | * Install Aypac's "Arduino-TR-064-SOAP-Library" from https://github.com/Aypac/Arduino-TR-064-SOAP-Library to your Arduino library path. 23 | * Set up a net FRITZ!Box user for interaction with the TR-064 SOAP interface:
24 | `System -> FRITZ!Box-Benutzer -> Benutzer: Benutzer hinzufügen -> Benutzername / Kennwort, Berechtigungen: Fritz!Box Einstellungen / Sprachnachrichten, Faxnachrichten, FRITZ!App Fon und Anrufliste / Smart Home` 25 | * Follow the setup assistant. "Benutzername" (Username) will be used for "FbApiUSER" and "Kennwort" (Password) will be used for "FbApiPW" in the code. Check the mentioned boxes for that user. 26 | 27 | *Maybe someone can give me the correct translations from an english localized FRITZ!Box browser interface.* 28 | 29 | # Examples 30 | 31 |
32 |
DualDoorbell
33 |
A dual doorbell example adapted from Florian Wernze (see https://wernze-home.net/wordpress/hobbys/arduino-esp8266-tuerklingel/).
34 |
LaundryNotifier
35 |
A laundry notifier example using the TR-064 SOAP library (https://github.com/Aypac/Arduino-TR-064-SOAP-Library) to aquire the power information from two home automation smartplugs and notify via SIP call, that the washing machine and/or dryer is ready.
36 | 37 | -------------------------------------------------------------------------------- /examples/DualDoorbell/DualDoorbell.ino: -------------------------------------------------------------------------------- 1 | /* ==================================================================== 2 | 3 | Copyright (c) 2018 Juergen Liegner All rights reserved. 4 | (https://www.mikrocontroller.net/topic/444994) 5 | 6 | Copyright (c) 2019 Florian Wernze 7 | (https://wernze-home.net/wordpress/hobbys/arduino-esp8266-tuerklingel/) 8 | 9 | Copyright (c) 2019 Thorsten Godau (dl9sec) 10 | (Did some optimizations, extensions and beautification of Florian's code 11 | and integrated the library) 12 | 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions 16 | are met: 17 | 18 | 1. Redistributions of source code must retain the above copyright 19 | notice, this list of conditions and the following disclaimer. 20 | 21 | 2. Redistributions in binary form must reproduce the above copyright 22 | notice, this list of conditions and the following disclaimer in 23 | the documentation and/or other materials provided with the 24 | distribution. 25 | 26 | 3. Neither the name of the author(s) nor the names of any contributors 27 | may be used to endorse or promote products derived from this software 28 | without specific prior written permission. 29 | 30 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 31 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 32 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 34 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 35 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 36 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 37 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 38 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 39 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 | SUCH DAMAGE. 41 | 42 | ====================================================================*/ 43 | 44 | #include 45 | #include 46 | 47 | //#define DEBUGLOG 48 | 49 | #define LED_ESP12E 2 50 | #define LED_NODEMCU 16 51 | 52 | //------------------------------------------------ 53 | // Configuration with static IP 54 | //------------------------------------------------ 55 | 56 | // WiFi parameters 57 | const char* WiFiSSID = "myWiFiSSID"; // WiFi SSID 58 | const char* WiFiPSK = "myWiFiPreSharedKey"; // WiFi WPA2 preshared key 59 | 60 | const char *WiFiIP = "192.168.2.69"; // WiFi IP of the ESP 61 | const char *WiFiGW = "192.168.2.1"; // WiFi GW 62 | const char *WiFiNM = "255.255.255.0"; // WiFi NM 63 | const char *WiFiDNS = "192.168.2.1"; // WiFi DNS 64 | 65 | 66 | // Sip parameters 67 | const char *SipIP = "192.168.2.1"; // IP of the FRITZ!Box 68 | const int SipPORT = 5060; // SIP port of the FRITZ!Box 69 | const char *SipUSER = "Doorbell"; // SIP-Call username at the FRITZ!Box 70 | const char *SipPW = "SIP-Password"; // SIP-Call password at the FRITZ!Box 71 | 72 | // Dial parameters 73 | const char *SipDIAL = "**9"; // Dial number 74 | const char *SipTEXT_1 = "Doorbell #1"; // Dial text 1 for doorbell #1 75 | const char *SipTEXT_2 = "Doorbell #2"; // Dial text 2 for doorbell #2 76 | 77 | //------------------------------------------------ 78 | 79 | 80 | char acSipIn[2048]; 81 | char acSipOut[2048]; 82 | 83 | Sip aSip(acSipOut, sizeof(acSipOut)); 84 | 85 | void setup() 86 | { 87 | int i = 0; 88 | 89 | IPAddress myIP; 90 | IPAddress myGW; 91 | IPAddress myNM; 92 | IPAddress myDNS; 93 | 94 | ESP.wdtDisable(); 95 | ESP.wdtEnable(WDTO_8S); 96 | 97 | Serial.begin(115200); 98 | Serial.setDebugOutput(false); 99 | delay(10); 100 | 101 | pinMode(LED_ESP12E, OUTPUT); 102 | pinMode(LED_NODEMCU, OUTPUT); 103 | 104 | pinMode(4, INPUT_PULLUP); 105 | pinMode(5, INPUT_PULLUP); 106 | 107 | digitalWrite(LED_ESP12E, 1); // LED off 108 | digitalWrite(LED_NODEMCU, 1); // LED off 109 | 110 | Serial.printf("\r\n\r\nConnecting to %s\r\n", WiFiSSID); 111 | 112 | WiFi.setAutoConnect (true); 113 | WiFi.setAutoReconnect (true); 114 | WiFi.softAPdisconnect (true); 115 | 116 | myIP.fromString(WiFiIP); 117 | myGW.fromString(WiFiGW); 118 | myNM.fromString(WiFiNM); 119 | myDNS.fromString(WiFiDNS); 120 | 121 | WiFi.config(myIP, myGW, myNM, myDNS); 122 | 123 | if ( String(WiFiSSID) != WiFi.SSID() ) 124 | { 125 | Serial.print("Wifi initializing...\r\n"); 126 | WiFi.begin(WiFiSSID, WiFiPSK); 127 | } 128 | 129 | while ( WiFi.status() != WL_CONNECTED ) 130 | { 131 | delay(500); 132 | Serial.print("."); 133 | } 134 | 135 | WiFi.persistent(true); 136 | 137 | Serial.printf("\r\nWiFi connected to: %s\r\n", WiFi.localIP().toString().c_str()); 138 | digitalWrite(LED_ESP12E, 0); 139 | 140 | aSip.Init(SipIP, SipPORT, WiFiIP, SipPORT, SipUSER, SipPW, 15); 141 | 142 | } 143 | 144 | 145 | void loop(void) 146 | { 147 | // SIP processing 148 | aSip.Processing(acSipIn, sizeof(acSipIn)); 149 | 150 | // Doorbell handling begin =========================== 151 | 152 | if ( digitalRead(4) == LOW ) 153 | { 154 | aSip.Dial(SipDIAL, SipTEXT_1); 155 | } 156 | 157 | if ( digitalRead(5) == LOW ) 158 | { 159 | aSip.Dial(SipDIAL, SipTEXT_2); 160 | } 161 | 162 | // Doorbell handling end ============================= 163 | 164 | ESP.wdtFeed(); // Retrigger watchdog 165 | 166 | } 167 | -------------------------------------------------------------------------------- /examples/LaundryNotifier/LaundryNotifier.ino: -------------------------------------------------------------------------------- 1 | /* ==================================================================== 2 | 3 | Copyright (c) 2019 Thorsten Godau (dl9sec). All rights reserved. 4 | 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | 3. Neither the name of the author(s) nor the names of any contributors 19 | may be used to endorse or promote products derived from this software 20 | without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 26 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 | SUCH DAMAGE. 33 | 34 | ====================================================================*/ 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | //#define DEBUGLOG 41 | 42 | #define LED_ESP12E 2 43 | #define LED_NODEMCU 16 44 | 45 | //------------------------------------------------ 46 | // Configuration with static IP 47 | //------------------------------------------------ 48 | 49 | // WiFi parameters 50 | const char* WiFiSSID = "myWiFiSSID"; // WiFi SSID 51 | const char* WiFiPSK = "myWiFiPreSharedKey"; // WiFi WPA2 preshared key 52 | 53 | const char *WiFiIP = "192.168.2.69"; // WiFi IP of the ESP 54 | const char *WiFiGW = "192.168.2.1"; // WiFi GW 55 | const char *WiFiNM = "255.255.255.0"; // WiFi NM 56 | const char *WiFiDNS = "192.168.2.1"; // WiFi DNS 57 | 58 | // Sip parameters 59 | const char *SipIP = "192.168.2.1"; // IP of the FRITZ!Box 60 | const int SipPORT = 5060; // SIP port of the FRITZ!Box 61 | const char *SipUSER = "SIP-User"; // SIP-Call username at the FRITZ!Box 62 | const char *SipPW = "SIP-Password"; // SIP-Call password at the FRITZ!Box 63 | 64 | // Dial parameters 65 | const char *SipDIAL = "**9"; // Dial number 66 | const char *SipTEXT_1 = "Washing machine ready"; // Dial text 1 for washing machine 67 | const char *SipTEXT_2 = "Dryer ready"; // Dial text 2 for dryer 68 | 69 | // FRITZ!Box parameters 70 | const char *FbApiUSER = "laundry"; // FRITZ!Box API user 71 | const char *FbApiPW = "mypsaaword"; // FRITZ!Box API password 72 | const char *FbApiIP = SipIP; // FRITZ!Box IP 73 | const int FbApiPORT = 49000; // Fritz"Box API port 74 | 75 | const char *FbApiAIN01 = "11657 1234567"; // AIN of the washing machine 76 | const char *FbApiAIN02 = "11657 7654321"; // AIN of the dryer 77 | 78 | //------------------------------------------------ 79 | 80 | char acSipIn[2048]; 81 | char acSipOut[2048]; 82 | 83 | float afPwrAIN01[4] = { 0.0, 0.0, 0.0, 0.0 }; 84 | uint8_t u8IdxPwrAIN01 = 0; 85 | 86 | float afPwrAIN02[4] = { 0.0, 0.0, 0.0, 0.0 }; 87 | uint8_t u8IdxPwrAIN02 = 0; 88 | 89 | uint8_t u8StateAIN01 = 0; // 0: idle, 1: in progress 90 | uint8_t u8StateAIN02 = 0; // 0: idle, 1: in progress 91 | 92 | uint32_t u32Interval = 30000; // Every 30s 93 | 94 | uint32_t u32MillisTmp; 95 | 96 | TR064 tr064conn(FbApiPORT, FbApiIP, FbApiUSER, FbApiPW); 97 | Sip aSip(acSipOut, sizeof(acSipOut)); 98 | 99 | 100 | void setup() 101 | { 102 | int i = 0; 103 | 104 | IPAddress myIP; 105 | IPAddress myGW; 106 | IPAddress myNM; 107 | IPAddress myDNS; 108 | 109 | ESP.wdtDisable(); 110 | ESP.wdtEnable(WDTO_8S); 111 | 112 | Serial.begin(115200); 113 | Serial.setDebugOutput(false); 114 | delay(10); 115 | 116 | pinMode(LED_ESP12E, OUTPUT); 117 | pinMode(LED_NODEMCU, OUTPUT); 118 | 119 | pinMode(4, INPUT_PULLUP); 120 | pinMode(5, INPUT_PULLUP); 121 | 122 | digitalWrite(LED_ESP12E, 1); // LED off 123 | digitalWrite(LED_NODEMCU, 1); // LED off 124 | 125 | Serial.printf("\r\n\r\nConnecting to %s\r\n", WiFiSSID); 126 | 127 | WiFi.setAutoConnect (true); 128 | WiFi.setAutoReconnect (true); 129 | WiFi.softAPdisconnect (true); 130 | 131 | myIP.fromString(WiFiIP); 132 | myGW.fromString(WiFiGW); 133 | myNM.fromString(WiFiNM); 134 | myDNS.fromString(WiFiDNS); 135 | 136 | WiFi.config(myIP, myGW, myNM, myDNS); 137 | 138 | if ( String(WiFiSSID) != WiFi.SSID() ) 139 | { 140 | Serial.print("Wifi initializing...\r\n"); 141 | WiFi.begin(WiFiSSID, WiFiPSK); 142 | } 143 | 144 | while ( WiFi.status() != WL_CONNECTED ) 145 | { 146 | delay(500); 147 | Serial.print("."); 148 | } 149 | 150 | WiFi.persistent(true); 151 | Serial.printf("\r\nWiFi connected to: %s\r\n", WiFi.localIP().toString().c_str()); 152 | digitalWrite(LED_ESP12E, 0); 153 | 154 | aSip.Init(SipIP, SipPORT, WiFiIP, SipPORT, SipUSER, SipPW, 38); 155 | 156 | // Initialize the TR-064 library 157 | Serial.printf("Initialize TR-064 connection...\r\n"); 158 | tr064conn.init(); 159 | 160 | u32MillisTmp = millis(); 161 | 162 | } 163 | 164 | 165 | float getPwrAIN(const char *FbApiAIN) 166 | { 167 | String params[][2] = {{"NewAIN", FbApiAIN}}; 168 | String req[][2] = {{"NewMultimeterPower", ""}}; 169 | 170 | tr064conn.action("urn:dslforum-org:service:X_AVM-DE_Homeauto:1", "GetSpecificDeviceInfos", params, 1, req, 1); 171 | 172 | float power = (float)((req[0][1]).toInt()) / 100.0; 173 | 174 | return power; 175 | } 176 | 177 | 178 | void refreshNonce(void) 179 | { 180 | // Do a dummy request which will result in an authentication error, but will refresh the nonce 181 | String req[][2] = {{"NewAssociatedDeviceIndex", "1"}}; 182 | 183 | tr064conn.action("urn:dslforum-org:service:WLANConfiguration:1", "GetGenericAssociatedDeviceInfo", req, 1); 184 | 185 | } 186 | 187 | 188 | void loop(void) 189 | { 190 | float fAvgPwrAIN01; 191 | float fAvgPwrAIN02; 192 | 193 | uint8_t u8count; 194 | 195 | // SIP processing 196 | aSip.Processing(acSipIn, sizeof(acSipIn)); 197 | 198 | // Smartplug processing 199 | if ( ( millis() - u32MillisTmp ) > u32Interval ) 200 | { 201 | refreshNonce(); 202 | 203 | afPwrAIN01[u8IdxPwrAIN01] = getPwrAIN(FbApiAIN01); 204 | 205 | if ( u8StateAIN01 == 0 && afPwrAIN01[u8IdxPwrAIN01] > 5.0 ) 206 | { 207 | u8StateAIN01 = 1; // Idle -> In Progress 208 | } 209 | 210 | if ( ++u8IdxPwrAIN01 > 3 ) u8IdxPwrAIN01 = 0; 211 | 212 | delay(100); 213 | 214 | afPwrAIN02[u8IdxPwrAIN02] = getPwrAIN(FbApiAIN02); 215 | 216 | if ( u8StateAIN02 == 0 && afPwrAIN02[u8IdxPwrAIN02] > 5.0 ) 217 | { 218 | u8StateAIN02 = 1; // Idle -> In Progress 219 | } 220 | 221 | if ( ++u8IdxPwrAIN02 > 3 ) u8IdxPwrAIN02 = 0; 222 | 223 | // Accumulate arrays and average 224 | fAvgPwrAIN01 = 0; 225 | fAvgPwrAIN02 = 0; 226 | for ( u8count = 0; u8count < 4; u8count++ ) 227 | { 228 | fAvgPwrAIN01 += afPwrAIN01[u8count]; 229 | fAvgPwrAIN02 += afPwrAIN02[u8count]; 230 | } 231 | 232 | fAvgPwrAIN01 = fAvgPwrAIN01 / 4.0; 233 | fAvgPwrAIN02 = fAvgPwrAIN02 / 4.0; 234 | 235 | Serial.printf("AIN01: %.2f W (avg.), State; %d\r\n", fAvgPwrAIN01, u8StateAIN01); 236 | Serial.printf("AIN02: %.2f W (avg.), State; %d\r\n", fAvgPwrAIN02, u8StateAIN02); 237 | 238 | if ( u8StateAIN01 == 1 && fAvgPwrAIN01 <= 1.0 ) 239 | { 240 | aSip.Dial(SipDIAL, SipTEXT_1); 241 | u8StateAIN01 = 0; // Ready -> Idle 242 | } 243 | 244 | if ( u8StateAIN02 == 1 && fAvgPwrAIN02 <= 1.0 ) 245 | { 246 | aSip.Dial(SipDIAL, SipTEXT_2); 247 | u8StateAIN02 = 0; // Ready -> Idle 248 | } 249 | 250 | u32MillisTmp = millis(); 251 | } 252 | 253 | ESP.wdtFeed(); 254 | } 255 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | Sip KEYWORD1 2 | Init KEYWORD2 3 | Processing KEYWORD2 4 | Dial KEYWORD2 5 | IsBusy KEYWORD2 6 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ArduinoSIP 2 | version=0.1.0 3 | author=Thorsten Godau (dl9sec) 4 | license=BSD-3-clause 5 | maintainer=Thorsten Godau 6 | sentence=Arduino SIP library with UDP communications 7 | paragraph=Original class and methods authored by Juergen Liegner (SIP protocol not fully implemented) 8 | category=Device Control 9 | url=https://github.com/dl9sec/ArduinoSIP 10 | architectures=esp8266 11 | includes=WiFiUdp.h, ArduinoSIP.h 12 | -------------------------------------------------------------------------------- /src/ArduinoSIP.cpp: -------------------------------------------------------------------------------- 1 | /* ==================================================================== 2 | 3 | Copyright (c) 2018 Juergen Liegner All rights reserved. 4 | (https://www.mikrocontroller.net/topic/444994) 5 | 6 | Copyright (c) 2019 Thorsten Godau (dl9sec) 7 | (Created an Arduino library from the original code and did some beautification) 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions 11 | are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright 14 | notice, this list of conditions and the following disclaimer. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright 17 | notice, this list of conditions and the following disclaimer in 18 | the documentation and/or other materials provided with the 19 | distribution. 20 | 21 | 3. Neither the name of the author(s) nor the names of any contributors 22 | may be used to endorse or promote products derived from this software 23 | without specific prior written permission. 24 | 25 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 26 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 29 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 | SUCH DAMAGE. 36 | 37 | ====================================================================*/ 38 | #include 39 | 40 | #include "ArduinoSIP.h" 41 | 42 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 43 | // 44 | // Hardware and API independent Sip class 45 | // 46 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 47 | 48 | Sip::Sip(char *pBuf, size_t lBuf) { 49 | 50 | pbuf = pBuf; 51 | lbuf = lBuf; 52 | pDialNr = ""; 53 | pDialDesc = ""; 54 | 55 | } 56 | 57 | 58 | Sip::~Sip() { 59 | 60 | } 61 | 62 | 63 | void Sip::Init(const char *SipIp, int SipPort, const char *MyIp, int MyPort, const char *SipUser, const char *SipPassWd, int MaxDialSec) { 64 | 65 | Udp.begin(SipPort); 66 | 67 | caRead[0] = 0; 68 | pbuf[0] = 0; 69 | pSipIp = SipIp; 70 | iSipPort = SipPort; 71 | pSipUser = SipUser; 72 | pSipPassWd = SipPassWd; 73 | pMyIp = MyIp; 74 | iMyPort = MyPort; 75 | iAuthCnt = 0; 76 | iRingTime = 0; 77 | iMaxTime = MaxDialSec * 1000; 78 | } 79 | 80 | 81 | bool Sip::Dial(const char *DialNr, const char *DialDesc) { 82 | 83 | if ( iRingTime ) 84 | return false; 85 | 86 | iDialRetries = 0; 87 | pDialNr = DialNr; 88 | pDialDesc = DialDesc; 89 | Invite(); 90 | iDialRetries++; 91 | iRingTime = Millis(); 92 | 93 | return true; 94 | } 95 | 96 | 97 | void Sip::Processing(char *pBuf, size_t lBuf) { 98 | 99 | int packetSize = Udp.parsePacket(); 100 | 101 | if ( packetSize > 0 ) 102 | { 103 | pBuf[0] = 0; 104 | packetSize = Udp.read(pBuf, lBuf); 105 | if ( packetSize > 0 ) 106 | { 107 | pBuf[packetSize] = 0; 108 | 109 | #ifdef DEBUGLOG 110 | IPAddress remoteIp = Udp.remoteIP(); 111 | Serial.printf("\r\n----- read %i bytes from: %s:%i ----\r\n", (int)packetSize, remoteIp.toString().c_str(), Udp.remotePort()); 112 | Serial.print(pBuf); 113 | Serial.printf("----------------------------------------------------\r\n"); 114 | #endif 115 | 116 | } 117 | } 118 | 119 | HandleUdpPacket((packetSize > 0) ? pBuf : 0 ); 120 | } 121 | 122 | 123 | void Sip::HandleUdpPacket(const char *p) { 124 | 125 | uint32_t iWorkTime = iRingTime ? (Millis() - iRingTime) : 0; 126 | 127 | if ( iRingTime && iWorkTime > iMaxTime ) 128 | { 129 | // Cancel(3); 130 | Bye(3); 131 | iRingTime = 0; 132 | } 133 | 134 | if ( !p ) 135 | { 136 | // max 5 dial retry when loos first invite packet 137 | if ( iAuthCnt == 0 && iDialRetries < 5 && iWorkTime > (iDialRetries * 200) ) 138 | { 139 | iDialRetries++; 140 | delay(30); 141 | Invite(); 142 | } 143 | 144 | return; 145 | } 146 | 147 | if ( strstr(p, "SIP/2.0 401 Unauthorized") == p ) 148 | { 149 | Ack(p); 150 | // call Invite with response data (p) to build auth md5 hashes 151 | Invite(p); 152 | } 153 | else if ( strstr(p, "BYE") == p ) 154 | { 155 | Ok(p); 156 | iRingTime = 0; 157 | } 158 | else if ( strstr(p, "SIP/2.0 200") == p ) // OK 159 | { 160 | ParseReturnParams(p); 161 | Ack(p); 162 | } 163 | else if ( strstr(p, "SIP/2.0 183 ") == p // Session Progress 164 | || strstr(p, "SIP/2.0 180 ") == p ) // Ringing 165 | { 166 | ParseReturnParams(p); 167 | } 168 | else if ( strstr(p, "SIP/2.0 100 ") == p ) // Trying 169 | { 170 | ParseReturnParams(p); 171 | Ack(p); 172 | } 173 | else if ( strstr(p, "SIP/2.0 486 ") == p // Busy Here 174 | || strstr(p, "SIP/2.0 603 ") == p // Decline 175 | || strstr(p, "SIP/2.0 487 ") == p) // Request Terminatet 176 | { 177 | Ack(p); 178 | iRingTime = 0; 179 | } 180 | else if (strstr(p, "INFO") == p) 181 | { 182 | iLastCSeq = GrepInteger(p, "\nCSeq: "); 183 | Ok(p); 184 | } 185 | 186 | } 187 | 188 | 189 | void Sip::AddSipLine(const char* constFormat , ... ) { 190 | 191 | va_list arglist; 192 | va_start(arglist, constFormat); 193 | uint16_t l = (uint16_t)strlen(pbuf); 194 | char *p = pbuf + l; 195 | vsnprintf(p, lbuf - l, constFormat, arglist ); 196 | va_end(arglist); 197 | l = (uint16_t)strlen(pbuf); 198 | if ( l < (lbuf - 2) ) 199 | { 200 | pbuf[l] = '\r'; 201 | pbuf[l + 1] = '\n'; 202 | pbuf[l + 2] = 0; 203 | } 204 | } 205 | 206 | 207 | // Search a line in response date (p) and append on pbuf 208 | bool Sip::AddCopySipLine(const char *p, const char *psearch) { 209 | 210 | char *pa = strstr((char*)p, psearch); 211 | 212 | if ( pa ) 213 | { 214 | char *pe = strstr(pa, "\r"); 215 | 216 | if ( pe == 0 ) 217 | pe = strstr(pa, "\n"); 218 | 219 | if ( pe > pa ) 220 | { 221 | char c = *pe; 222 | *pe = 0; 223 | AddSipLine("%s", pa); 224 | *pe = c; 225 | 226 | return true; 227 | } 228 | } 229 | 230 | return false; 231 | } 232 | 233 | 234 | // Parse parameter value from http formated string 235 | bool Sip::ParseParameter(char *dest, int destlen, const char *name, const char *line, char cq) { 236 | 237 | const char *qp; 238 | const char *r; 239 | 240 | if ( ( r = strstr(line, name) ) != NULL ) 241 | { 242 | r = r + strlen(name); 243 | qp = strchr(r, cq); 244 | int l = qp - r; 245 | if ( l < destlen ) 246 | { 247 | strncpy(dest, r, l); 248 | dest[l] = 0; 249 | 250 | return true; 251 | } 252 | } 253 | 254 | return false; 255 | } 256 | 257 | 258 | // Copy Call-ID, From, Via and To from response to caRead using later for BYE or CANCEL the call 259 | bool Sip::ParseReturnParams(const char *p) { 260 | 261 | pbuf[0] = 0; 262 | 263 | AddCopySipLine(p, "Call-ID: "); 264 | AddCopySipLine(p, "From: "); 265 | AddCopySipLine(p, "Via: "); 266 | AddCopySipLine(p, "To: "); 267 | 268 | if ( strlen(pbuf) >= 2 ) 269 | { 270 | strcpy(caRead, pbuf); 271 | caRead[strlen(caRead) - 2] = 0; 272 | } 273 | 274 | return true; 275 | } 276 | 277 | 278 | int Sip::GrepInteger(const char *p, const char *psearch) { 279 | 280 | int param = -1; 281 | const char *pc = strstr(p, psearch); 282 | 283 | if ( pc ) 284 | { 285 | param = atoi(pc + strlen(psearch)); 286 | } 287 | 288 | return param; 289 | } 290 | 291 | 292 | void Sip::Ack(const char *p) { 293 | 294 | char ca[32]; 295 | 296 | bool b = ParseParameter(ca, (int)sizeof(ca), "To: <", p, '>'); 297 | 298 | if ( !b ) 299 | return; 300 | 301 | pbuf[0] = 0; 302 | AddSipLine("ACK %s SIP/2.0", ca); 303 | AddCopySipLine(p, "Call-ID: "); 304 | int cseq = GrepInteger(p, "\nCSeq: "); 305 | AddSipLine("CSeq: %i ACK", cseq); 306 | AddCopySipLine(p, "From: "); 307 | AddCopySipLine(p, "Via: "); 308 | AddCopySipLine(p, "To: "); 309 | AddSipLine("Content-Length: 0"); 310 | AddSipLine(""); 311 | SendUdp(); 312 | } 313 | 314 | 315 | void Sip::Cancel(int cseq) { 316 | 317 | if ( caRead[0] == 0 ) 318 | return; 319 | 320 | pbuf[0] = 0; 321 | AddSipLine("%s sip:%s@%s SIP/2.0", "CANCEL", pDialNr, pSipIp); 322 | AddSipLine("%s", caRead); 323 | AddSipLine("CSeq: %i %s", cseq, "CANCEL"); 324 | AddSipLine("Max-Forwards: 70"); 325 | AddSipLine("User-Agent: sip-client/0.0.1"); 326 | AddSipLine("Content-Length: 0"); 327 | AddSipLine(""); 328 | SendUdp(); 329 | } 330 | 331 | 332 | void Sip::Bye(int cseq) { 333 | 334 | if ( caRead[0] == 0 ) 335 | return; 336 | 337 | pbuf[0] = 0; 338 | AddSipLine("%s sip:%s@%s SIP/2.0", "BYE", pDialNr, pSipIp); 339 | AddSipLine("%s", caRead); 340 | AddSipLine("CSeq: %i %s", cseq, "BYE"); 341 | AddSipLine("Max-Forwards: 70"); 342 | AddSipLine("User-Agent: sip-client/0.0.1"); 343 | AddSipLine("Content-Length: 0"); 344 | AddSipLine(""); 345 | SendUdp(); 346 | } 347 | 348 | 349 | void Sip::Ok(const char *p) { 350 | 351 | pbuf[0] = 0; 352 | AddSipLine("SIP/2.0 200 OK"); 353 | AddCopySipLine(p, "Call-ID: "); 354 | AddCopySipLine(p, "CSeq: "); 355 | AddCopySipLine(p, "From: "); 356 | AddCopySipLine(p, "Via: "); 357 | AddCopySipLine(p, "To: "); 358 | AddSipLine("Content-Length: 0"); 359 | AddSipLine(""); 360 | SendUdp(); 361 | } 362 | 363 | 364 | // Call invite without or with the response from peer 365 | void Sip::Invite(const char *p) { 366 | 367 | // prevent loops 368 | if ( p && iAuthCnt > 3 ) 369 | return; 370 | 371 | // using caRead for temp. store realm and nonce 372 | char *caRealm = caRead; 373 | char *caNonce = caRead + 128; 374 | 375 | char *haResp = 0; 376 | int cseq = 1; 377 | 378 | if ( !p ) 379 | { 380 | iAuthCnt = 0; 381 | 382 | if ( iDialRetries == 0 ) 383 | { 384 | callid = Random(); 385 | tagid = Random(); 386 | branchid = Random(); 387 | } 388 | } 389 | else 390 | { 391 | cseq = 2; 392 | 393 | if ( ParseParameter(caRealm, 128, " realm=\"", p) 394 | && ParseParameter(caNonce, 128, " nonce=\"", p) ) 395 | { 396 | // using output buffer to build the md5 hashes 397 | // store the md5 haResp to end of buffer 398 | char *ha1Hex = pbuf; 399 | char *ha2Hex = pbuf + 33; 400 | haResp = pbuf + lbuf - 34; 401 | char *pTemp = pbuf + 66; 402 | 403 | snprintf(pTemp, lbuf - 100, "%s:%s:%s", pSipUser, caRealm, pSipPassWd); 404 | MakeMd5Digest(ha1Hex, pTemp); 405 | 406 | snprintf(pTemp, lbuf - 100, "INVITE:sip:%s@%s", pDialNr, pSipIp); 407 | MakeMd5Digest(ha2Hex, pTemp); 408 | 409 | snprintf(pTemp, lbuf - 100, "%s:%s:%s", ha1Hex, caNonce, ha2Hex); 410 | MakeMd5Digest(haResp, pTemp); 411 | } 412 | else 413 | { 414 | caRead[0] = 0; 415 | 416 | return; 417 | } 418 | } 419 | 420 | pbuf[0] = 0; 421 | AddSipLine("INVITE sip:%s@%s SIP/2.0", pDialNr, pSipIp); 422 | AddSipLine("Call-ID: %010u@%s", callid, pMyIp); 423 | AddSipLine("CSeq: %i INVITE", cseq); 424 | AddSipLine("Max-Forwards: 70"); 425 | // not needed for fritzbox 426 | // AddSipLine("User-Agent: sipdial by jl"); 427 | AddSipLine("From: \"%s\" ;tag=%010u", pDialDesc, pSipUser, pSipIp, tagid); 428 | AddSipLine("Via: SIP/2.0/UDP %s:%i;branch=%010u;rport=%i", pMyIp, iMyPort, branchid, iMyPort); 429 | AddSipLine("To: ", pDialNr, pSipIp); 430 | AddSipLine("Contact: \"%s\" ", pSipUser, pSipUser, pMyIp, iMyPort); 431 | 432 | if ( p ) 433 | { 434 | // authentication 435 | AddSipLine("Authorization: Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"sip:%s@%s\", response=\"%s\"", pSipUser, caRealm, caNonce, pDialNr, pSipIp, haResp); 436 | iAuthCnt++; 437 | } 438 | 439 | AddSipLine("Content-Type: application/sdp"); 440 | // not needed for fritzbox 441 | // AddSipLine("Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO"); 442 | AddSipLine("Content-Length: 0"); 443 | AddSipLine(""); 444 | caRead[0] = 0; 445 | SendUdp(); 446 | } 447 | 448 | 449 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 450 | // 451 | // Hardware dependent interface functions 452 | // 453 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 454 | 455 | uint32_t Sip::Millis() { 456 | 457 | return (uint32_t)millis() + 1; 458 | } 459 | 460 | 461 | // Generate a 30 bit random number 462 | uint32_t Sip::Random() { 463 | 464 | // return ((((uint32_t)rand())&0x7fff)<<15) + ((((uint32_t)rand())&0x7fff)); 465 | return secureRandom(0x3fffffff); 466 | } 467 | 468 | 469 | int Sip::SendUdp() { 470 | 471 | Udp.beginPacket(pSipIp, iSipPort); 472 | Udp.write(pbuf, strlen(pbuf)); 473 | Udp.endPacket(); 474 | #ifdef DEBUGLOG 475 | Serial.printf("\r\n----- send %i bytes -----------------------\r\n%s", strlen(pbuf), pbuf); 476 | Serial.printf("------------------------------------------------\r\n"); 477 | #endif 478 | 479 | return 0; 480 | } 481 | 482 | 483 | void Sip::MakeMd5Digest(char *pOutHex33, char *pIn) { 484 | 485 | MD5Builder aMd5; 486 | 487 | aMd5.begin(); 488 | aMd5.add(pIn); 489 | aMd5.calculate(); 490 | aMd5.getChars(pOutHex33); 491 | } 492 | -------------------------------------------------------------------------------- /src/ArduinoSIP.h: -------------------------------------------------------------------------------- 1 | /* ==================================================================== 2 | 3 | Copyright (c) 2018 Juergen Liegner All rights reserved. 4 | (https://www.mikrocontroller.net/topic/444994) 5 | 6 | Copyright (c) 2019 Thorsten Godau (dl9sec) 7 | (Created an Arduino library encapsulation from the original code and did 8 | some beautification) 9 | 10 | Redistribution and use in source and binary forms, with or without 11 | modification, are permitted provided that the following conditions 12 | are met: 13 | 14 | 1. Redistributions of source code must retain the above copyright 15 | notice, this list of conditions and the following disclaimer. 16 | 17 | 2. Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in 19 | the documentation and/or other materials provided with the 20 | distribution. 21 | 22 | 3. Neither the name of the author(s) nor the names of any contributors 23 | may be used to endorse or promote products derived from this software 24 | without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 27 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 | SUCH DAMAGE. 37 | 38 | ====================================================================*/ 39 | #ifndef ARDUINO_SIP_H 40 | #define ARDUINO_SIP_H 41 | 42 | #if defined(ARDUINO) && ARDUINO >= 100 43 | #include "Arduino.h" 44 | #else 45 | #include "WProgram.h" 46 | #endif 47 | 48 | #include 49 | 50 | class Sip 51 | { 52 | public: 53 | Sip(char *pBuf, size_t lBuf); 54 | ~Sip(); 55 | 56 | 57 | void Init(const char *SipIp, int SipPort, const char *MyIp, int MyPort, const char *SipUser, const char *SipPassWd, int MaxDialSec = 10); 58 | bool Dial(const char *DialNr, const char *DialDesc = ""); 59 | void Processing(char *pBuf, size_t lBuf); 60 | bool IsBusy() { return iRingTime != 0; } 61 | 62 | private: 63 | char *pbuf; 64 | size_t lbuf; 65 | char caRead[256]; 66 | 67 | const char *pSipIp; 68 | int iSipPort; 69 | const char *pSipUser; 70 | const char *pSipPassWd; 71 | const char *pMyIp; 72 | int iMyPort; 73 | const char *pDialNr; 74 | const char *pDialDesc; 75 | 76 | uint32_t callid; 77 | uint32_t tagid; 78 | uint32_t branchid; 79 | 80 | int iAuthCnt; 81 | uint32_t iRingTime; 82 | uint32_t iMaxTime; 83 | int iDialRetries; 84 | int iLastCSeq; 85 | 86 | WiFiUDP Udp; 87 | 88 | void HandleUdpPacket(const char *p); 89 | void AddSipLine(const char* constFormat , ... ); 90 | bool AddCopySipLine(const char *p, const char *psearch); 91 | bool ParseParameter(char *dest, int destlen, const char *name, const char *line, char cq = '\"'); 92 | bool ParseReturnParams(const char *p); 93 | int GrepInteger(const char *p, const char *psearch); 94 | void Ack(const char *pIn); 95 | void Cancel(int seqn); 96 | void Bye(int cseq); 97 | void Ok(const char *pIn); 98 | void Invite(const char *pIn = 0); 99 | 100 | uint32_t Millis(); 101 | uint32_t Random(); 102 | int SendUdp(); 103 | void MakeMd5Digest(char *pOutHex33, char *pIn); 104 | 105 | }; 106 | 107 | #endif // ARDUINO_SIP_H --------------------------------------------------------------------------------