The netscanner interface of the smart home is generic but important part. It would not be useful alone, but its support for the the other modules is very valuable (guess how do I know that all my IP Cams are online?).
What it is? To answer it very briefly: a service, which continuously pings (so scans) our LAN endlessly and saves what it saw.
You think is not possible to write a netscanner using Arduino?Then waaaaaiit for it :).
As a picture is worth more than thousand words:
The Ping library does the hard stuff (Check it here: https://www.arduino.cc/en/tutorial/ping)
The Netscan function is called every 10 seconds, and then a ping is going to be executed. As this service is generally a blocking service (it has to wait for endpoint response, with an realistic but not to short timeout - and that is blocking), and we have a webserver running, some optimization was needed to be done - which is rather simple but effective: the netscanner will be only called, if there is no recent web activity. So the webserver (and thus the user experience) has always priority...
if (runEvery10Seconds())
{
if (!needsWebServerPriority())
{
#ifdef ServerDEBUG
Serial.print(F(" and web server is idle."));
#endif
ScanNetwork();
}
}
Let us have a look on the implementation of this function. Quite complex :)
First, we store only the very necessary information in RAM, as RAM is very precious.
- maximal 50 endpoints (when the table is full, the old entries will be deleted
- MAC address
- IP Address, but only the last octet (I have only one subnet)
- The Vendor name is loaded dynamically from the SD Card database (more about that later)
#define MAXHOST 50
#define PING_REQUEST_TIMEOUT_MS 50
#define PING_REQUEST_TIMEOUT_MS2 100
uint8_t mactable[6][MAXHOST];
uint8_t iplastbyte[MAXHOST];
unsigned long lastAliveTime[MAXHOST];
Ok, when we call the function, we set our target IP:
targetScanIP++; //begin with 1.
if (targetScanIP==255)
{
targetScanIP=1;
}
Then we check where we could store it
for (int k=0;k<MAXHOST;k++)
{
unsigned long temp=0;
//a legkiesebb idoflaget keressuk, az a legregibb.
if (temp>=lastAliveTime[k])
{
indexToStore = k;
temp = lastAliveTime[k];
}
}
Then we have to assemble our full local IP, and the full target IP.
Local IP is easy, as the Ethernet library already has a function for that.
Let use it for our advantage to assemble the target IP:
uint32_t loc_ipAddress = Ethernet.localIP();
uint8_t *ipPtr = (uint8_t*) &loc_ipAddress; //ipPtr[0], ipPtr[1], ipPtr[2], and ipPtr[3] a négy byte
IPAddress targetPingAddr(ipPtr[0],ipPtr[1],ipPtr[2],targetScanIP);
...do the scan:
ICMPPing::setTimeout(PING_REQUEST_TIMEOUT_MS);
ICMPEchoReply echoReply = ping(targetPingAddr, 1);
if (echoReply.status == SUCCESS)
{
Ok. That quite nice that we found something, let us check the received MAC. Maybe we already know it with different IP address.
So we have to compare the echoReply.MACAddressSocket array against our uint8_t mactable[6][MAXHOST] "database"....
So much about the generic network scanner. I may post more details about it when needed.
Lets have a word about the web interface (there will be a whole blog entry about the webserver responses, as it has a "tokeniser" - a combined dynamic/static response capability).
The content of the right column is loaded all the time from the SD card. It won't be stored in RAM.
It is a standard text file, where names are paired with the appropriate MAC addresses.
9C:D6:43:5B:XX:XX,Router1
00:14:6C:21:XX:XX,Router2
So, when we come to display our detected endpoints, the last column will be loaded
bool resolveMACfromDB(char * macaddr, char * macName)
{
checkMinRam();
char readBuf[100];
char tBuf[30];
char macBuf[20];
byte readBufPos = 0;
strcpy(tBuf,macaddr);
strtoupper(tBuf);
if (strlen(macaddr)<6)
{
return false;
}
memset(macBuf,0,sizeof(macBuf));
memset(readBuf,0,sizeof(readBuf));
File dataFile = SD.open(F("endpoi.cfg"), FILE_READ);
if (dataFile)
{
while (dataFile.available())
{
char inputChar = dataFile.read();
if (inputChar != '\n')
{
if (inputChar != '\r')
{
readBuf[readBufPos]=inputChar;
readBufPos++;
if (readBufPos>99)
{
dataFile.close();
return false;//emergency exit
}
}
}
else
{
//process buf line
sscanf(readBuf, "%[^,],%s", macBuf, macName);
strtoupper(macBuf);
if (strlen(macBuf)>3)
{
if (strcmp(macBuf,tBuf)==0)
{
//match
// Serial.println(" found ok");
dataFile.close();
return true;
}
}
memset(readBuf,0,sizeof(readBuf));
memset(macBuf,0,sizeof(macBuf));
memset(macName,0,sizeof(macName));
readBufPos=0;
}
}
dataFile.close();
}
else
{
#ifdef ServerDEBUG
Serial.println(F("endpoi.cfg load failed."));
#endif
return false;
}
return false;
}
Of course, there will be endpoints we do not know, but some information would be handy there too to get.
There is a free service at MACVendors.com, which has a simply URL format to call:
http://api.macvendors.com/BC:4C:C4:1A
So for unknown endpoint, a html link to Macvendors.com will be generated. (That is the "Resolve" on the image above).
else
{
thisClient.print(F("<a target=\"_blank\" href=\"http://api.macvendors.com/"));
thisClient.print(macBuf);
thisClient.print(F("\">Resolve</a> "));
}
Meantime you may request my CV in email as I am looking for new challenges (job) - my main experience lies in the ICT project/program manager area for German multi companies. Contact is stingraycayman@gmail.com.