Created
September 26, 2019 10:36
-
-
Save kepsic/7111186f4005e2076d937cefb1821001 to your computer and use it in GitHub Desktop.
ProDino MKR GSM Ethernet V1 -
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// WebRelay.ino | |
// Company: KMP Electronics Ltd, Bulgaria | |
// Web: https://kmpelectronics.eu/ | |
// Supported boards: | |
// - KMP ProDino MKR Zero Ethernet V1 (https://kmpelectronics.eu/products/prodino-mkr-zero-ethernet-v1/) | |
// - KMP ProDino MKR GSM Ethernet V1 (https://kmpelectronics.eu/products/prodino-mkr-gsm-ethernet-v1/) | |
// Description: | |
// Manage relays through web page. In the page you can view a board relays statuses and change them. | |
// Example link: https://kmpelectronics.eu/tutorials-examples/prodino-mkr-versions-examples/ | |
// Version: 1.0.0 | |
// Date: 17.09.2018 | |
// Author: Plamen Kovandjiev <p.kovandiev@kmpelectronics.eu> | |
#include "KMPProDinoMKRZero.h" | |
#include "KMPCommon.h" | |
// include the GSM library | |
#include <MKRGSM.h> | |
#include <ArduinoOTA.h> | |
// If in debug mode - print debug information in Serial. Comment in production code, this bring performance. | |
// This method is good for development and verification of results. But increases the amount of code and decreases productivity. | |
#define DEBUG | |
#define RELAY_ON true | |
#define RELAY_OFF true | |
String dev_names[4] = { "Switch", "RPi", "P2P", "Camera" }; | |
String dtmfCodes[8] = { "*11#", "*10#", | |
"*21#", "*20#", | |
"*31#", "*30#", | |
"*31#", "*40#" | |
}; | |
String messageCodes[11] = { "R1ON", "R1OFF", | |
"R2ON", "R2OFF", | |
"R3ON", "R3OFF", | |
"R4ON", "R4OFF", | |
"STATUS", | |
"ALLON", "ALLOFF" | |
}; | |
// Enter a MAC address and IP address for your controller below. | |
byte _mac[] = { 0x00, 0x08, 0xDC, 0x53, 0x09, 0x72 }; | |
// The IP address will be dependent on your local network. | |
IPAddress _ip(192, 168, 88, 198); | |
// Local port. | |
// Port 80 is default for HTTP | |
const uint16_t LOCAL_PORT = 80; | |
// Define text colors. | |
const char GREEN[] = "#90EE90"; // LightGreen | |
const char RED[] = "#FF4500"; // OrangeRed | |
// Initialize the Ethernet server library. | |
// with the IP address and port you want to use. | |
EthernetServer _server(LOCAL_PORT); | |
// Client. | |
EthernetClient _client; | |
// Please enter your sensitive data in the Secret tab or arduino_secrets.h | |
// PIN Number | |
const char PINNUMBER[] = "0000"; | |
String allowed_numbers[] = { "+3725253642", "+37256694455" }; | |
// initialize the library instances | |
GSM gsmAccess; | |
GSMScanner scannerNetworks; | |
GSMModem modemTest; | |
GSM_SMS sms; | |
GSMVoiceCall vcs; | |
GSMBand band; | |
// Array to hold the number for the incoming call | |
char numtel[20]; | |
// Array to hold the number a SMS is retreived from | |
char senderNumber[20]; | |
// Save data variables | |
String IMEI = ""; | |
String remoteNumber = ""; // the number you will call | |
char charbuffer[20]; | |
void setup_gsm() { | |
scannerNetworks.begin(); | |
Serial.println("SMS/Voice Messages Receiver"); | |
// connection state | |
bool connected = false; | |
// Start GSM connection | |
while (!connected) { | |
if (gsmAccess.begin(PINNUMBER) == GSM_READY) { | |
connected = true; | |
} else { | |
#ifdef DEBUG | |
Serial.println("Not connected"); | |
#endif | |
delay(1000); | |
} | |
} | |
// This makes sure the modem correctly reports incoming events | |
vcs.hangCall(); | |
#ifdef DEBUG | |
// get modem parameters | |
// IMEI, modem unique identifier | |
Serial.println("Modem IMEI: "); | |
IMEI = modemTest.getIMEI(); | |
IMEI.replace("\n", ""); | |
if (IMEI != NULL) { | |
Serial.println(IMEI); | |
} | |
// scan for existing networks, displays a list of networks | |
Serial.println("Scanning available networks. May take some seconds."); | |
Serial.println(scannerNetworks.readNetworks()); | |
// currently connected carrier | |
Serial.println("Current carrier: "); | |
Serial.println(scannerNetworks.getCurrentCarrier()); | |
// returns strength and ber | |
// signal strength in 0-31 scale. 31 means power > 51dBm | |
// BER is the Bit Error Rate. 0-7 scale. 99=not detectable | |
Serial.println("Signal Strength: "); | |
Serial.print(scannerNetworks.getSignalStrength()); | |
Serial.println(" [0-31]"); | |
Serial.println("GSM initialized"); | |
Serial.println("Waiting for messages/voice"); | |
#endif | |
} | |
/** | |
* @brief Setup void. Ii is Arduino executed first. Initialize DiNo board. | |
* | |
* | |
* @return void | |
*/ | |
void setup() | |
{ | |
delay(5000); | |
#ifdef DEBUG | |
Serial.begin(115200); | |
#endif | |
// Init Dino board. Set pins, start W5500. | |
KMPProDinoMKRZero.init(ProDino_MKR_Zero_Ethernet); | |
// Start the Ethernet connection and the server. | |
Ethernet.begin(_mac, _ip); | |
_server.begin(); | |
#ifdef DEBUG | |
Serial.println("The SMS+WebRelay is started."); | |
Serial.println("IPs:"); | |
Serial.println(Ethernet.localIP()); | |
Serial.println(Ethernet.gatewayIP()); | |
Serial.println(Ethernet.subnetMask()); | |
#endif | |
setup_gsm(); | |
allRelaysOn(); | |
String startup; | |
startup += "START!"; | |
startup += "Status:"; | |
startup += smsStatus(); | |
sendSMS(allowed_numbers[0].c_str(), startup.c_str()); | |
// start the OTEthernet library with internal (flash) based storage | |
ArduinoOTA.begin(Ethernet.localIP(), "Arduino", "password", InternalStorage); | |
} | |
/** | |
* @brief Loop void. Arduino executed second. | |
* | |
* | |
* @return void | |
*/ | |
void loop() | |
{ | |
// check for updates | |
ArduinoOTA.poll(); | |
//Handle Incomming messages | |
incomming_sms_handler(); | |
// Handle incomming voice; | |
incomming_voice_handler(); | |
// Handle http connections | |
http_handler(); | |
} | |
void http_handler() { | |
// Waiting for a client. | |
_client = _server.available(); | |
if (!_client.connected()) | |
{ | |
return; | |
} | |
#ifdef DEBUG | |
Serial.println(">> Client connected."); | |
#endif | |
// If client connected switch On status led. | |
KMPProDinoMKRZero.OnStatusLed(); | |
// Read client request. | |
ReadClientRequest(); | |
WriteClientResponse(); | |
// Close the client connection. | |
_client.stop(); | |
// If client disconnected switch Off status led. | |
KMPProDinoMKRZero.OffStatusLed(); | |
#ifdef DEBUG | |
Serial.println(">> Client disconnected."); | |
Serial.println(); | |
#endif | |
} | |
/** | |
* @brief ReadClientRequest void. Read and parse client request. | |
* First row of client request is similar to: | |
* GET / HTTP/1.1 | |
* -or- | |
* POST / HTTP/1.1 | |
* Host: 192.168.0.177 | |
* Connection: keep-alive | |
* Content-Length: 5 | |
* | |
* r1=On | |
* You can check communication client-server get program Smart Sniffer: http://www.nirsoft.net/utils/smsniff.html | |
* | |
* @return bool Returns true if the request was expected. | |
*/ | |
bool ReadClientRequest() | |
{ | |
#ifdef DEBUG | |
Serial.println(">> Starts client request."); | |
#endif | |
// Loop while read all request. | |
// Read first and last row from request. | |
String firstRow; | |
String lastRow; | |
String msg; | |
String number; | |
if (ReadHttpRequestLine(&_client, &firstRow)) | |
{ | |
while (ReadHttpRequestLine(&_client, &lastRow)); | |
} | |
if ( endsWith(firstRow.c_str(),"/call HTTP/1.1") ) { | |
outgoing_voice_handler(allowed_numbers[0]); | |
} | |
#ifdef DEBUG | |
Serial.println("--firstRow--"); | |
Serial.println(firstRow); | |
Serial.println("--lastRow--"); | |
Serial.println(lastRow); | |
#endif | |
// If the request is GET we write only response. | |
if (GetRequestType(firstRow.c_str()) == GET) | |
{ | |
return true; | |
} | |
// Invalid request type. | |
if (GetRequestType(firstRow.c_str()) != POST || lastRow.length() < 2) | |
{ | |
#ifdef DEBUG | |
Serial.println(">> Invalid request type."); | |
#endif | |
return false; | |
} | |
// From POST parameters we get relay number and new status. | |
uint8_t relay = CharToInt(lastRow[1]) - 1; | |
bool newState = lastRow.endsWith(W_ON); | |
KMPProDinoMKRZero.SetRelayState(relay, newState); | |
number = urldecode(GetValue(lastRow, "tel")); | |
msg = GetValue(lastRow, "msg"); | |
if (number.length() != 0 && msg.length() != 0 && msg.length() <= 200 && number.length() <= 20) { | |
if (isAllowedNumber(number)) { | |
sendSMS((char*)number.c_str(), (char*)msg.c_str()); | |
} | |
} | |
#ifdef DEBUG | |
Serial.println(">> End client request."); | |
#endif | |
return true; | |
} | |
String urldecode(String str) | |
{ | |
String encodedString=""; | |
char c; | |
char code0; | |
char code1; | |
for (int i =0; i < str.length(); i++){ | |
c=str.charAt(i); | |
if (c == '+'){ | |
encodedString+=' '; | |
}else if (c == '%') { | |
i++; | |
code0=str.charAt(i); | |
i++; | |
code1=str.charAt(i); | |
c = (h2int(code0) << 4) | h2int(code1); | |
encodedString+=c; | |
} else{ | |
encodedString+=c; | |
} | |
yield(); | |
} | |
return encodedString; | |
} | |
String urlencode(String str) | |
{ | |
String encodedString=""; | |
char c; | |
char code0; | |
char code1; | |
char code2; | |
for (int i =0; i < str.length(); i++){ | |
c=str.charAt(i); | |
if (c == ' '){ | |
encodedString+= '+'; | |
} else if (isalnum(c)){ | |
encodedString+=c; | |
} else{ | |
code1=(c & 0xf)+'0'; | |
if ((c & 0xf) >9){ | |
code1=(c & 0xf) - 10 + 'A'; | |
} | |
c=(c>>4)&0xf; | |
code0=c+'0'; | |
if (c > 9){ | |
code0=c - 10 + 'A'; | |
} | |
code2='\0'; | |
encodedString+='%'; | |
encodedString+=code0; | |
encodedString+=code1; | |
//encodedString+=code2; | |
} | |
yield(); | |
} | |
return encodedString; | |
} | |
unsigned char h2int(char c) | |
{ | |
if (c >= '0' && c <='9'){ | |
return((unsigned char)c - '0'); | |
} | |
if (c >= 'a' && c <='f'){ | |
return((unsigned char)c - 'a' + 10); | |
} | |
if (c >= 'A' && c <='F'){ | |
return((unsigned char)c - 'A' + 10); | |
} | |
return(0); | |
} | |
/** | |
* @brief WriteClientResponse void. Write html response. | |
* | |
* | |
* @return void | |
*/ | |
void WriteClientResponse() | |
{ | |
if (!_client.connected()) | |
{ | |
return; | |
} | |
// Response write. | |
// Send a standard http header. | |
_client.write(HEADER_200_TEXT_HTML); | |
// Add web page Relay HTML. | |
String content = BuildPage(); | |
_client.write(content.c_str()); | |
} | |
/** | |
* @brief Build HTML page. | |
* | |
* @return void | |
*/ | |
String BuildPage() | |
{ | |
// Add table rows which includes relays information. | |
String rows = ""; | |
for (uint8_t i = 0; i < RELAY_COUNT; i++) | |
{ | |
// Row i, cell 1 | |
String relayNumber = String(i + 1); | |
String devName = dev_names[i]; | |
rows += "<tr><td>"+devName+"</td>"; | |
char* cellColor; | |
char* cellStatus; | |
char* nextRelayStatus; | |
if (KMPProDinoMKRZero.GetRelayState(i)) | |
{ | |
cellColor = (char*)RED; | |
cellStatus = (char*)W_ON; | |
nextRelayStatus = (char*)W_OFF; | |
} | |
else | |
{ | |
cellColor = (char*)GREEN; | |
cellStatus = (char*)W_OFF; | |
nextRelayStatus = (char*)W_ON; | |
} | |
char* icellColor; | |
char* icellStatus; | |
if (KMPProDinoMKRZero.GetOptoInState(i)) | |
{ | |
icellColor = (char*)RED; | |
icellStatus = (char*)W_ON; | |
} | |
else | |
{ | |
icellColor = (char*)GREEN; | |
icellStatus = (char*)W_OFF; | |
} | |
// Cell i,2 | |
rows += "<td bgcolor='" + String(cellColor) + "'>" + String(cellStatus) + "</td>"; | |
// Cell i,3 | |
rows += "<td><input type='submit' name='r" + String(relayNumber) + "' value='" + String(nextRelayStatus) + "'/ ></td>"; | |
// Cell i,4 | |
rows += "<td bgcolor='" + String(icellColor) + "'>" + String(icellStatus) + "</td></tr>"; | |
} | |
return "<html><head><title>Power control #" + String(PRODINO_MKRZERO) + " - Web Relay</title>" | |
+ "<meta charset='utf-8'>" | |
+ "<meta name='viewport' content='width=device-width, initial-scale=1'>" | |
+ "<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css'>" | |
+ "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js'></script>" | |
+ "<script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js'></script>" | |
+ "</head>" | |
+ "<body><div id='relay_div' style='text-align: center'>" | |
+ "<br><hr />" | |
+ "<h1 style = 'color: #0066FF;'> Power Control</h1>" | |
+ "<hr /><br><br>" | |
+ "<form method='post'>" | |
+ "<table id='relays' border='1' width='640' cellpadding='5' cellspacing='0' align='center' style='text-align:center; font-size:large; font-family:Arial,Helvetica,sans-serif;'>" | |
+ "<tr><th>Name</th><th>Relay Status</th> <th>Action</th> <th>Input status</th> </tr>" | |
+ rows | |
+ "</table></form><br><br>" | |
+ "<div class='text-center'>" | |
+ "<form method='post'>" | |
+ "<div class='input-group'>" | |
+ "<div class='input-group-prepend'>" | |
+ "<span class='input-group-text' id='sms'>Telphone and message name</span>" | |
+ "</div>" | |
+ "<input type='text' name='tel' maxlength='20' pattern='/^+372[5]{1}[0-9]{9}$/' class='form-control'>" | |
+ "<input type='text' name='msg' maxlength='200' class='form-control'>" | |
+ "<input class='btn btn-primary' type='submit' value='Send'>" | |
+ "</div>" | |
+ "</div>" | |
+ "</form>" | |
+ "</body></html>"; | |
} | |
boolean isAllowedNumber(String number) { | |
for (uint8_t i = 0; i < sizeof(allowed_numbers); i++) | |
{ | |
return allowed_numbers[i] == number; | |
} | |
return false; | |
} | |
int getDtmCodefIndex(String code) { | |
for (uint8_t i = 0; i < sizeof(dtmfCodes); i++) | |
{ | |
if ( code == dtmfCodes[i] ) { | |
return i; | |
} | |
} | |
return -1; | |
} | |
int getMessageIndex(String code) { | |
for (uint8_t i = 0; i < sizeof(messageCodes); i++) | |
{ | |
if ( code == messageCodes[i] ) { | |
return i; | |
} | |
} | |
return -1; | |
} | |
void incomming_voice_handler() { | |
String msg; | |
// Check the status of the voice call | |
switch (vcs.getvoiceCallStatus()) { | |
case IDLE_CALL: // Nothing is happening | |
break; | |
case RECEIVINGCALL: // Yes! Someone is calling us | |
#ifdef DEBUG | |
Serial.println("RECEIVING CALL"); | |
#endif | |
// Retrieve the calling number | |
vcs.retrieveCallingNumber(numtel, 20); | |
#ifdef DEBUG | |
// Print the calling number | |
Serial.print("Number:"); | |
Serial.println(numtel); | |
#endif | |
if (isAllowedNumber(numtel)) | |
{ | |
String status_sms = smsStatus(); | |
sendSMS(numtel, (char*)status_sms.c_str()); | |
vcs.answerCall(); | |
} else { | |
vcs.hangCall(); | |
msg += "Called from "; | |
msg += numtel; | |
sendSMS(allowed_numbers[0].c_str(), msg.c_str()); | |
} | |
break; | |
case TALKING: // In this case the call would be established | |
handle_dtmf(); | |
break; | |
} | |
} | |
void handle_dtmf() { | |
String dtmfDial; | |
int dtmf = -1; | |
do { | |
if (vcs.getvoiceCallStatus() != TALKING) { | |
vcs.hangCall(); | |
#ifdef DEBUG | |
Serial.println("Hanging..."); | |
#endif | |
} | |
else { | |
dtmf = vcs.readDTMF(); // parse the pressed number | |
switch (dtmf) { | |
default: ; break; | |
case '*': dtmfDial += '*'; break; | |
case '#': dtmfDial += '#'; break; | |
case '0': dtmfDial += '0'; break; | |
case '1': dtmfDial += '1'; break; | |
case '2': dtmfDial += '2'; break; | |
case '3': dtmfDial += '3'; break; | |
case '4': dtmfDial += '4'; break; | |
case '5': dtmfDial += '5'; break; | |
case '6': dtmfDial += '6'; break; | |
case '7': dtmfDial += '7'; break; | |
case '8': dtmfDial += '8'; break; | |
case '9': dtmfDial += '0'; break; | |
} | |
} | |
} | |
while ((dtmf < 0) && (vcs.getvoiceCallStatus() == TALKING)); // read DTMF tones till a number is pressed or the call is hanged | |
#ifdef DEBUG | |
// print out the parsed DTMF tone | |
Serial.println("Pressed button: "); | |
Serial.println(dtmf); | |
Serial.println("DTMF Buffer: "); | |
Serial.println(dtmfDial); | |
#endif | |
switch (getDtmCodefIndex(dtmfDial)) { | |
default: ; break; | |
case '0': setRelayAndHangup(0, RELAY_ON); break; | |
case '1': setRelayAndHangup(0, RELAY_OFF); break; | |
case '2': setRelayAndHangup(1, RELAY_ON); break; | |
case '3': setRelayAndHangup(1, RELAY_OFF); break; | |
case '4': setRelayAndHangup(2, RELAY_ON); break; | |
case '5': setRelayAndHangup(2, RELAY_OFF); break; | |
case '6': setRelayAndHangup(3, RELAY_ON); break; | |
case '7': setRelayAndHangup(3, RELAY_OFF); break; | |
} | |
dtmfDial = ""; | |
} | |
void setRelayAndHangup(int relay, boolean state) { | |
KMPProDinoMKRZero.SetRelayState(relay, state); | |
vcs.hangCall(); | |
} | |
void incomming_sms_handler() { | |
int c; | |
String msg; | |
// If there are any SMSs available() | |
if (sms.available()) { | |
#ifdef DEBUG | |
Serial.println("Message received from:"); | |
#endif | |
// Get remote number | |
sms.remoteNumber(senderNumber, 20); | |
#ifdef DEBUG | |
Serial.print(String(senderNumber) + ":"); | |
#endif | |
// Read message bytes and print them | |
while ((c = sms.read()) != -1) { | |
#ifdef DEBUG | |
Serial.print((char)c); | |
#endif | |
msg += (char)c; | |
} | |
if (isAllowedNumber(senderNumber)) | |
{ | |
handle_message(msg); | |
} else { | |
String msg0; | |
msg0 += senderNumber; | |
msg0 += ":"; | |
msg0 += msg; | |
sendSMS(allowed_numbers[0].c_str(), msg0.c_str()); | |
} | |
// Delete message from modem memory | |
sms.flush(); | |
} | |
} | |
void handle_message(String msg) { | |
switch (getMessageIndex(msg)) { | |
default: ; break; | |
case '0': KMPProDinoMKRZero.SetRelayState(0, true); break; | |
case '1': KMPProDinoMKRZero.SetRelayState(0, false); break; | |
case '2': KMPProDinoMKRZero.SetRelayState(1, true); break; | |
case '3': KMPProDinoMKRZero.SetRelayState(1, false); break; | |
case '4': KMPProDinoMKRZero.SetRelayState(2, true); break; | |
case '5': KMPProDinoMKRZero.SetRelayState(2, false); break; | |
case '6': KMPProDinoMKRZero.SetRelayState(3, true); break; | |
case '7': KMPProDinoMKRZero.SetRelayState(3, false); break; | |
case '8': sendSMS(senderNumber, smsStatus().c_str()); break; | |
case '9': allRelaysOn(); break; | |
case '10': AllRelaysOff(); break; | |
} | |
} | |
void allRelaysOn() { | |
for (uint8_t i = 0; i < RELAY_COUNT; i++) | |
{ | |
KMPProDinoMKRZero.SetRelayState(i, RELAY_ON); | |
} | |
} | |
void AllRelaysOff() { | |
for (uint8_t i = 0; i < RELAY_COUNT; i++) | |
{ | |
KMPProDinoMKRZero.SetRelayState(i, RELAY_OFF); | |
} | |
} | |
String smsStatus() { | |
String status_sms = ""; | |
for (uint8_t i = 0; i < RELAY_COUNT; i++) | |
{ | |
// Row i, cell 1 | |
String relayNumber = String(i + 1); | |
char* relayStatus; | |
if (KMPProDinoMKRZero.GetRelayState(i)) | |
{ | |
relayStatus = (char*)W_ON; | |
} | |
else | |
{ | |
relayStatus = (char*)W_OFF; | |
} | |
status_sms += dev_names[i] +"=" + relayStatus; | |
if (i<RELAY_COUNT) { | |
status_sms += ","; | |
} | |
} | |
for (uint8_t i = 0; i < OPTOIN_COUNT; i++) | |
{ | |
// Row i, cell 1 | |
String inputNumber = String(i + 1); | |
char* inputStatus; | |
if (KMPProDinoMKRZero.GetOptoInState(i)) | |
{ | |
inputStatus = (char*)W_ON; | |
} | |
else | |
{ | |
inputStatus = (char*)W_OFF; | |
} | |
status_sms += "Input"+ inputNumber +"=" + inputStatus; | |
if (i<OPTOIN_COUNT) { | |
status_sms += ","; | |
} | |
} | |
status_sms += "GSM_SIGNAL="; | |
status_sms += scannerNetworks.getSignalStrength(); | |
status_sms += ",GSM_BAND="; | |
status_sms += band.getBand(); | |
return status_sms; | |
} | |
void sendSMS(const char* remoteNum, const char* txtMsg) { | |
#ifdef DEBUG | |
Serial.println("SENDING:"); | |
Serial.println(remoteNum); | |
Serial.println("Message:"); | |
Serial.println(txtMsg); | |
#endif | |
// send the message | |
sms.beginSMS(remoteNum); | |
sms.print(txtMsg); | |
sms.endSMS(); | |
Serial.println("\nCOMPLETE!\n"); | |
} | |
void outgoing_voice_handler(String remoteNumber) { | |
// make sure the phone number is not too long: | |
if (remoteNumber.length() < 20) { | |
#ifdef DEBUG | |
// let the user know you're calling: | |
Serial.print("Calling to : "); | |
Serial.println(remoteNumber); | |
Serial.println(); | |
#endif | |
// Call the remote number | |
remoteNumber.toCharArray(charbuffer, 20); | |
// Check if the receiving end has picked up the call | |
if (vcs.voiceCall(charbuffer)) { | |
#ifdef DEBUG | |
Serial.println("Call Established. Enter line to end"); | |
#endif | |
// Wait for some input from the line | |
while (vcs.getvoiceCallStatus() == TALKING); | |
// And hang up | |
vcs.hangCall(); | |
} | |
#ifdef DEBUG | |
Serial.println("Call Finished"); | |
#endif | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment