Skip to content

Instantly share code, notes, and snippets.

@kepsic
Created September 26, 2019 10:36
Show Gist options
  • Save kepsic/7111186f4005e2076d937cefb1821001 to your computer and use it in GitHub Desktop.
Save kepsic/7111186f4005e2076d937cefb1821001 to your computer and use it in GitHub Desktop.
ProDino MKR GSM Ethernet V1 -
// 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