├── 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
--------------------------------------------------------------------------------