Определение того, что вы дома, используя WiFi-роутер (для автоматизации «умного дома»)
В предыдущей статье я описал устройство для управления климатом на ESP8266. Возникает вопрос, а при каких событиях мы должны выполнять это управление? Самое простое — при наступлении определенного времени.
Второе что приходит в голову — присутствие в доме. Если вас нет дома, то нет смысла (или есть?) проветривать, отапливать и кондиционировать помещение.
В этой статье рассмотрим возможность определения присутствия используя wifi роутер. Нет, мы не будем следить за людьми сквозь стены используя wifi сигнал, а воспользуемся страничкой состояния в веб интерфейсе wifi роутера, и по наличию в списке вашего смартфона сможем понять дома вы или нет.
Естественно этот способ не сработает, если вы, или кто-то из вашей семьи, не использует смартфон, отключает wifi, если у вас нет wifi, уходя, оставляет устройство дома. Также в статье описан «рецепт» для конкретного dlink роутера. Если у вас другая модель — вероятно, что вам придется «доработать напильником».
Страница, отображающая список wifi клиентов выглядит примерно так:
Для доступа к этой странице нам необходимо авторизоваться на роутере. Изучаем исходный код страницы ввода пароля и видим:
1) в странице ввода пароля роутер отправляет salt и authid.
2) роутер берет из пароля первые 16 цифр, объединяет их с salt, «добивает» строку символом chr (1) до 64х символов.
3) Для полученной 64x символьной строки считает MD5.
4) объединяет salt + md5
5) формирует строку вида
http://192.168.0.1/post_login.xml?hash=a33403f9aded48e57FF9e09d37d9009026e1ce85&auth_code=&auth_id=09CFF
где hash это строка полученная в п4., auth_id — строка полученная в п1.
6) если авторизация прошла успешно, то роутер возвращает xml с адресом страницы для редиректа.
Код примерно следующий:
var salt = 'a33403f9';
var password = document.forms.myform.old_password.value;
password = password.substr(0,16);
for (var i = password.length; i < 16; i++) {
password += String.fromCharCode(1);
}
var input = salt + password;
for (var i = input.length; i < 63; i++) {
input += String.fromCharCode(1);
}
input += (document.forms.myform.old_username.value == 'user') ? 'U' : String.fromCharCode(1);
var hash = hex_md5(input);
var login_hash = salt.concat(hash);
var auth_url = '';
auth_url = '&auth_code=' + document.forms.myform.auth_code.value + '&auth_id=09C05';
var xml_loader = new ajax_xmlhttp('/post_login.xml?hash=' + login_hash + auth_url, xml_ready, xml_timeout);
После того как мы авторизовались на страничке роутера достаточно запросить:
http://192.168.0.1/wifi_assoc.xml
и мы получим XML вида:
18FEFFFFCCFF
bingo
8
65
100
802.11n (2.4GHz)
192.168.0.3
001DFEFF70FF
bingo
8
65
84
802.11n (2.4GHz)
192.168.0.4
AC37FFFFDCFF
bingo
8
104
100
802.11n (2.4GHz)
192.168.0.5
18FEFFFFFFDF
bingo
8
58
100
802.11n (2.4GHz)
192.168.0.6
Проверив наличие MAC своего смартфона в этом списке, мы легко определим дома вы* или нет.
#include
#include
#include
MD5Builder _md5;
HTTPClient http;
const char* ssid = "bingo";
const char* password = "qqq_zzz_xxx";
const char* ap_ssid = "esp";
const char* ap_password = "espqw3454#1";
// salt
const char* patternSalt = "var salt = \"";
const int patternsaltLen = strlen(patternSalt);
int patternsaltPos = 0;
char salt[20] = "";
int saltLen = sizeof(salt);
int saltPos = 0;
// auth
const char* patternAuthID = "\"&auth_id=";
const int patternAuthIDLen = strlen(patternAuthID);
int patternAuthIDPos = 0;
char authID[20] = "";
int authIDLen = sizeof(authID);
int authIDPos = 0;
// mac
const char* patternMac = "";
const int patternMacLen = strlen(patternMac);
int patternMacPos = 0;
char mac[20] = "";
int macLen = sizeof(mac);
int macPos = 0;
typedef void (*patternSearchFinishedHandler)();
void patternSearchFinishedDummy() {}
void patternSearchFinishedMac() {
Serial.print("mac=");
Serial.println(mac);
mac[0] = (char) 0;
macPos = 0;
}
void setup() {
Serial.begin(115200);
WiFi.softAP(ap_ssid, ap_password);
WiFi.mode(WIFI_AP_STA);
WiFi.begin(ssid, password);
Serial.println("Setup Completed");
}
void charBuf_to_u8buf(const char buf1[128], uint8_t buf2[128], int buffSize){
for (int i=0; i < buffSize; i++){
buf2[i] = (uint8_t)buf1[i];
}
}
void u8buf_to_charBuf(const uint8_t buf1[128], char buf2[128], int buffSize){
for (int i=0; i < buffSize; i++){
buf2[i] = (char)buf1[i];
}
}
void checkPattern(int* tpos, int tlen, char c, const char* templ, char* data, int* datapos, int datalen, char finishChar, patternSearchFinishedHandler handler){
if (*tpos == tlen)
{
if (finishChar == c){
if (patternSearchFinishedDummy != handler){
delay(10);
handler();
}
*tpos = 0;
}
else
{
if (*datapos < datalen-2){
data[*datapos] = c;
data[*datapos + 1] = (char) 0;
*datapos += 1;
}
}
}
else
{
if (templ[*tpos] == c){
*tpos += 1;
}else{
*tpos = 0;
}
}
}
void processBuffer(uint8_t buff[128], int buffSize){
char cbuf[128] = {};
u8buf_to_charBuf(buff, cbuf, buffSize);
for (int i=0; i < buffSize; i++){
checkPattern(&patternsaltPos, patternsaltLen, cbuf[i], patternSalt, salt, &saltPos, saltLen, '"', patternSearchFinishedDummy);
checkPattern(&patternAuthIDPos, patternAuthIDLen, cbuf[i], patternAuthID, authID, &authIDPos, authIDLen, '"', patternSearchFinishedDummy);
checkPattern(&patternMacPos, patternMacLen, cbuf[i], patternMac, mac, &macPos, macLen, '<', patternSearchFinishedMac);
}
}
String md5(String str) {
_md5.begin();
_md5.add(String(str));
_md5.calculate();
return _md5.toString();
}
void intVars() {
// init vars
salt[0] = (char) 0;
saltPos = 0;
authID[0] = (char) 0;
authIDPos = 0;
mac[0] = (char) 0;
macPos = 0;
}
void queryAddress(String address, bool dumpOutput, bool doProcessBuffer){
delay(10);
// configure server and url
http.begin(address);
// Serial.print("[HTTP] GET...\n");
// start connection and send HTTP header
int httpCode = http.GET();
if(httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
if(httpCode == HTTP_CODE_OK) {
// get lenght of document (is -1 when Server sends no Content-Length header)
int len = http.getSize();
// create buffer for read
uint8_t buff[128] = { 0 };
// get tcp stream
WiFiClient * stream = http.getStreamPtr();
// read all data from server
while(http.connected() && (len > 0 || len == -1)) {
// get available data size
size_t size = stream->available();
if(size) {
// read up to 128 byte
int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
if (doProcessBuffer){
processBuffer(buff,c);
}
// write it to Serial
if (dumpOutput){
Serial.write(buff, c);
}
if(len > 0) {
len -= c;
}
}
delay(10);
}
Serial.println();
Serial.print("[HTTP] connection closed or file end.\n");
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
void queryData(){
intVars();
queryAddress("http://192.168.0.1/", false, true);
Serial.print("salt=");
Serial.println(salt);
Serial.print("authID=");
Serial.println(authID);
String data = "";
data.concat(salt);
// password
data.concat("bingo_fff#xxx ");
data = md5(data);
String addr = "http://192.168.0.1/post_login.xml?hash=";
addr.concat(salt);
addr.concat(data);
addr.concat("&auth_code=&auth_id=");
addr.concat(authID);
queryAddress(addr, false, false);
queryAddress("http://192.168.0.1/wifi_assoc.xml", false, true);
delay(10000);
}
void loop() {
// Wait for connection
if (WiFi.status() == WL_CONNECTED) {
queryData();
} else {
delay(500);
Serial.print(".");
}
}
Прошивка периодически выводит в консоль список подключенных устройств.