Skip to content

Instantly share code, notes, and snippets.

@SaimShuja
Created December 29, 2019 14:52
Show Gist options
  • Save SaimShuja/34915888b176c42f9ad4be05838b7a59 to your computer and use it in GitHub Desktop.
Save SaimShuja/34915888b176c42f9ad4be05838b7a59 to your computer and use it in GitHub Desktop.
arduino Sip example
/* ====================================================================
Copyright (c) 2018 Juerge Liegner All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
3. Neither the name of the author(s) nor the names of any contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
====================================================================*/
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#define DEBUGLOG
//------------------------------------------------
// configuration with fix ip
//------------------------------------------------
// wlan param
const char* ssid = "Hier eure SSID eintragen";
const char* password = "Hier euer WLAN Passwort eintragen";
// sip params
const char *sipip = "192.168.2.1"; // IP der FritzBox
int sipport = 5060; // Port so lassen
const char *sipuser = "Tuerklingel"; // Benutzername SIP-Call FritzBox
const char *sippasswd = "SIP-Passwort"; // Passwort SIP-Call FritzBox
// dial params
const char *sipdialnr = "**701";////unten auskomentiert
const char *sipdialtext = "Türklingel";////unten auskomentiert
// network params
const char *ip = "192.168.2.69"; // Eigene IP vom ESP
const char *gw = "192.168.2.1"; //Gateway
const char *mask = "255.255.255.0"; //Subnetmask
const char *dns = "192.168.2.1"; // DNS
//------------------------------------------------
//#define DEBUGLOG
WiFiUDP Udp;
/////////////////////////////////////////////////////////////////////////////////////////////////////
//
// hardware and api independent Sip class
//
/////////////////////////////////////////////////////////////////////////////////////////////////////
char set = 0;
class Sip
{
char *pbuf;
size_t lbuf;
char caRead[256];
const char *pSipIp;
int iSipPort;
const char *pSipUser;
const char *pSipPassWd;
const char *pMyIp;
int iMyPort;
const char *pDialNr;
const char *pDialDesc;
uint32_t callid;
uint32_t tagid;
uint32_t branchid;
int iAuthCnt;
uint32_t iRingTime;
uint32_t iMaxTime;
int iDialRetries;
int iLastCSeq;
void AddSipLine(const char* constFormat , ... );
bool AddCopySipLine(const char *p, const char *psearch);
bool ParseParameter(char *dest, int destlen, const char *name, const char *line, char cq = '\"');
bool ParseReturnParams(const char *p);
int GrepInteger(const char *p, const char *psearch);
void Ack(const char *pIn);
void Cancel(int seqn);
void Bye(int cseq);
void Ok(const char *pIn);
void Invite(const char *pIn = 0);
uint32_t Millis();
uint32_t Random();
int SendUdp();
void MakeMd5Digest(char *pOutHex33, char *pIn);
public:
Sip(char *pBuf, size_t lBuf);
void Init(const char *SipIp, int SipPort, const char *MyIp, int MyPort, const char *SipUser, const char *SipPassWd, int MaxDialSec = 10);
void HandleUdpPacket(const char *p);
bool Dial(const char *DialNr, const char *DialDesc = "");
bool IsBusy() {
return iRingTime != 0;
}
};
Sip::Sip(char *pBuf, size_t lBuf)
{
pbuf = pBuf;
lbuf = lBuf;
pDialNr = "";
pDialDesc = "";
}
bool Sip::Dial(const char *DialNr, const char *DialDesc)
{
if (iRingTime)
return false;
iDialRetries = 0;
pDialNr = DialNr;
pDialDesc = DialDesc;
Invite();
iDialRetries++;
iRingTime = Millis();
return true;
}
void Sip::Cancel(int cseq)
{
if (caRead[0] == 0)
return;
pbuf[0] = 0;
AddSipLine("%s sip:%s@%s SIP/2.0", "CANCEL", pDialNr, pSipIp);
AddSipLine("%s", caRead);
AddSipLine("CSeq: %i %s", cseq, "CANCEL");
AddSipLine("Max-Forwards: 70");
AddSipLine("User-Agent: sip-client/0.0.1");
AddSipLine("Content-Length: 0");
AddSipLine("");
SendUdp();
}
void Sip::Bye(int cseq)
{
if (caRead[0] == 0)
return;
pbuf[0] = 0;
AddSipLine("%s sip:%s@%s SIP/2.0", "BYE", pDialNr, pSipIp);
AddSipLine("%s", caRead);
AddSipLine("CSeq: %i %s", cseq, "BYE");
AddSipLine("Max-Forwards: 70");
AddSipLine("User-Agent: sip-client/0.0.1");
AddSipLine("Content-Length: 0");
AddSipLine("");
SendUdp();
}
void Sip::Ack(const char *p)
{
char ca[32];
bool b = ParseParameter(ca, (int)sizeof(ca), "To: <", p, '>');
if (!b)
return;
pbuf[0] = 0;
AddSipLine("ACK %s SIP/2.0", ca);
AddCopySipLine(p, "Call-ID: ");
int cseq = GrepInteger(p, "\nCSeq: ");
AddSipLine("CSeq: %i ACK", cseq);
AddCopySipLine(p, "From: ");
AddCopySipLine(p, "Via: ");
AddCopySipLine(p, "To: ");
AddSipLine("Content-Length: 0");
AddSipLine("");
SendUdp();
}
void Sip::Ok(const char *p)
{
pbuf[0] = 0;
AddSipLine("SIP/2.0 200 OK");
AddCopySipLine(p, "Call-ID: ");
AddCopySipLine(p, "CSeq: ");
AddCopySipLine(p, "From: ");
AddCopySipLine(p, "Via: ");
AddCopySipLine(p, "To: ");
AddSipLine("Content-Length: 0");
AddSipLine("");
SendUdp();
}
void Sip::Init(const char *SipIp, int SipPort, const char *MyIp, int MyPort, const char *SipUser, const char *SipPassWd, int MaxDialSec)
{
caRead[0] = 0;
pbuf[0] = 0;
pSipIp = SipIp;
iSipPort = SipPort;
pSipUser = SipUser;
pSipPassWd = SipPassWd;
pMyIp = MyIp;
iMyPort = MyPort;
iAuthCnt = 0;
iRingTime = 0;
iMaxTime = MaxDialSec * 1000;
}
void Sip::AddSipLine(const char* constFormat , ... )
{
va_list arglist;
va_start( arglist, constFormat);
uint16_t l = (uint16_t)strlen(pbuf);
char *p = pbuf + l;
vsnprintf(p, lbuf - l, constFormat, arglist );
va_end( arglist );
l = (uint16_t)strlen(pbuf);
if (l < (lbuf - 2))
{
pbuf[l] = '\r';
pbuf[l + 1] = '\n';
pbuf[l + 2] = 0;
}
}
// call invite without or with the response from peer
void Sip::Invite(const char *p)
{
// prevent loops
if (p && iAuthCnt > 3)
return;
// using caRead for temp. store realm and nonce
char *caRealm = caRead;
char *caNonce = caRead + 128;
char *haResp = 0;
int cseq = 1;
if (!p)
{
iAuthCnt = 0;
if (iDialRetries == 0)
{
callid = Random();
tagid = Random();
branchid = Random();
}
}
else
{
cseq = 2;
if ( ParseParameter(caRealm, 128, " realm=\"", p)
&& ParseParameter(caNonce, 128, " nonce=\"", p))
{
// using output buffer to build the md5 hashes
// store the md5 haResp to end of buffer
char *ha1Hex = pbuf;
char *ha2Hex = pbuf + 33;
haResp = pbuf + lbuf - 34;
char *pTemp = pbuf + 66;
snprintf(pTemp, lbuf - 100, "%s:%s:%s", pSipUser, caRealm, pSipPassWd);
MakeMd5Digest(ha1Hex, pTemp);
snprintf(pTemp, lbuf - 100, "INVITE:sip:%s@%s", pDialNr, pSipIp);
MakeMd5Digest(ha2Hex, pTemp);
snprintf(pTemp, lbuf - 100, "%s:%s:%s", ha1Hex, caNonce, ha2Hex);
MakeMd5Digest(haResp, pTemp);
}
else
{
caRead[0] = 0;
return;
}
}
pbuf[0] = 0;
AddSipLine("INVITE sip:%s@%s SIP/2.0", pDialNr, pSipIp);
AddSipLine("Call-ID: %010u@%s", callid, pMyIp);
AddSipLine("CSeq: %i INVITE", cseq);
AddSipLine("Max-Forwards: 70");
// not needed for fritzbox
// AddSipLine("User-Agent: sipdial by jl");
AddSipLine("From: \"%s\" <sip:%s@%s>;tag=%010u", pDialDesc, pSipUser, pSipIp, tagid);
AddSipLine("Via: SIP/2.0/UDP %s:%i;branch=%010u;rport=%i", pMyIp, iMyPort, branchid, iMyPort);
AddSipLine("To: <sip:%s@%s>", pDialNr, pSipIp);
AddSipLine("Contact: \"%s\" <sip:%s@%s:%i;transport=udp>", pSipUser, pSipUser, pMyIp, iMyPort);
if (p)
{
// authentication
AddSipLine("Authorization: Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"sip:%s@%s\", response=\"%s\"", pSipUser, caRealm, caNonce, pDialNr, pSipIp, haResp);
iAuthCnt++;
}
AddSipLine("Content-Type: application/sdp");
// not needed for fritzbox
// AddSipLine("Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO");
AddSipLine("Content-Length: 0");
AddSipLine("");
caRead[0] = 0;
SendUdp();
}
// parse parameter value from http formated string
bool Sip::ParseParameter(char *dest, int destlen, const char *name, const char *line, char cq)
{
const char *qp;
const char *r;
if ((r = strstr(line, name)) != NULL)
{
r = r + strlen(name);
qp = strchr(r, cq);
int l = qp - r;
if (l < destlen)
{
strncpy(dest, r, l);
dest[l] = 0;
return true;
}
}
return false;
}
// search a line in response date (p) and append on
// pbuf
bool Sip::AddCopySipLine(const char *p, const char *psearch)
{
char *pa = strstr((char*)p, psearch);
if (pa)
{
char *pe = strstr(pa, "\r");
if (pe == 0)
pe = strstr(pa, "\n");
if (pe > pa)
{
char c = *pe;
*pe = 0;
AddSipLine("%s", pa);
*pe = c;
return true;
}
}
return false;
}
int Sip::GrepInteger(const char *p, const char *psearch)
{
int param = -1;
const char *pc = strstr(p, psearch);
if (pc)
{
param = atoi(pc + strlen(psearch));
}
return param;
}
// copy Call-ID, From, Via and To from response
// to caRead
// using later for BYE or CANCEL the call
bool Sip::ParseReturnParams(const char *p)
{
pbuf[0] = 0;
AddCopySipLine(p, "Call-ID: ");
AddCopySipLine(p, "From: ");
AddCopySipLine(p, "Via: ");
AddCopySipLine(p, "To: ");
if (strlen(pbuf) >= 2)
{
strcpy(caRead, pbuf);
caRead[strlen(caRead) - 2] = 0;
}
return true;
}
void Sip::HandleUdpPacket(const char *p)
{
uint32_t iWorkTime = iRingTime ? (Millis() - iRingTime) : 0;
if (iRingTime && iWorkTime > iMaxTime)
{
// Cancel(3);
Bye(3);
iRingTime = 0;
}
if (!p)
{
// max 5 dial retry when loos first invite packet
if (iAuthCnt == 0 && iDialRetries < 5 && iWorkTime > (iDialRetries * 200))
{
iDialRetries++;
delay(30);
Invite();
}
return;
}
if (strstr(p, "SIP/2.0 401 Unauthorized") == p)
{
Ack(p);
// call Invite with response data (p) to build auth md5 hashes
Invite(p);
}
else if (strstr(p, "BYE") == p)
{
Ok(p);
iRingTime = 0;
}
else if (strstr(p, "SIP/2.0 200") == p) // OK
{
ParseReturnParams(p);
Ack(p);
}
else if ( strstr(p, "SIP/2.0 183 ") == p // Session Progress
|| strstr(p, "SIP/2.0 180 ") == p ) // Ringing
{
ParseReturnParams(p);
}
else if (strstr(p, "SIP/2.0 100 ") == p) // Trying
{
ParseReturnParams(p);
Ack(p);
}
else if ( strstr(p, "SIP/2.0 486 ") == p // Busy Here
|| strstr(p, "SIP/2.0 603 ") == p // Decline
|| strstr(p, "SIP/2.0 487 ") == p) // Request Terminatet
{
Ack(p);
iRingTime = 0;
}
else if (strstr(p, "INFO") == p)
{
iLastCSeq = GrepInteger(p, "\nCSeq: ");
Ok(p);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//
// hardware dependent interface functions
//
/////////////////////////////////////////////////////////////////////////////////////////////////////
int Sip::SendUdp()
{
Udp.beginPacket(pSipIp, iSipPort);
Udp.write(pbuf, strlen(pbuf));
Udp.endPacket();
return 0;
}
// generate a 30 bit random number
uint32_t Sip::Random()
{
// return ((((uint32_t)rand())&0x7fff)<<15) + ((((uint32_t)rand())&0x7fff));
return secureRandom(0x3fffffff);
}
uint32_t Sip::Millis()
{
return (uint32_t)millis() + 1;
}
void Sip::MakeMd5Digest(char *pOutHex33, char *pIn)
{
MD5Builder aMd5;
aMd5.begin();
aMd5.add(pIn);
aMd5.calculate();
aMd5.getChars(pOutHex33);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Arduino setup() and loop()
//
/////////////////////////////////////////////////////////////////////////////////////////////////////
char caSipIn[2048];
char caSipOut[2048];
Sip aSip(caSipOut, sizeof(caSipOut));
void setup()
{
WiFi.softAPdisconnect (true);
pinMode(12, INPUT_PULLUP);
pinMode(13, INPUT_PULLUP);
IPAddress Ip;
IPAddress Gw;
IPAddress Mask;
IPAddress Dns;
Ip.fromString(ip);
Gw.fromString(gw);
Mask.fromString(mask);
Dns.fromString(dns);
WiFi.config(Ip, Gw, Mask, Dns);
if (String(ssid) != WiFi.SSID())
{
WiFi.begin(ssid, password);
}
// Verbindung zum router Herstellen
int i = 0;
for (i = 0; i < 100; i++)
{
if (WiFi.status() == WL_CONNECTED)
break;
delay(100);
}
if (i >= 100)
{
// without connection go to sleep
delay(1000);
}
WiFi.persistent(true);
Udp.begin(sipport);
aSip.Init(sipip, sipport, ip, sipport, sipuser, sippasswd, 15);
//aSip.Dial(sipdialnr, sipdialtext);
}
int deepSleepDelay = 0;
void loop(void)
{
int packetSize = Udp.parsePacket();
if (packetSize > 0)
{
caSipIn[0] = 0;
packetSize = Udp.read(caSipIn, sizeof(caSipIn));
if (packetSize > 0)
{
caSipIn[packetSize] = 0;
}
}
aSip.HandleUdpPacket((packetSize > 0) ? caSipIn : 0 );
if (!aSip.IsBusy() && deepSleepDelay == 0)
deepSleepDelay = millis();
if (deepSleepDelay && (millis() - deepSleepDelay) > 500)
{}
// Ab Hier ist euer Programm
// GPIO 13 auf minus gezogen wird
if (digitalRead(13) == LOW) {
//aSip.Dial("**9", "GPIO D7 !");
aSip.Dial("**9", "Klingel oben");
}
if (digitalRead(12) == LOW) {
//aSip.Dial("**9", "GPIO D6 !");
aSip.Dial("**9", "Klingel unten");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment