Skip to content

Instantly share code, notes, and snippets.

@SukkoPera
Last active September 4, 2017 20:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SukkoPera/3e87aacedf3939fc50bbe893016dabab to your computer and use it in GitHub Desktop.
Save SukkoPera/3e87aacedf3939fc50bbe893016dabab to your computer and use it in GitHub Desktop.
Toggle motion detection on a Foscam IPcam through a Mifare RFID card
#include "strtoll.h"
#include <PString.h>
#define REQ_BUF_SIZE 512
char urlBuf[REQ_BUF_SIZE];
PString urlString (urlBuf, REQ_BUF_SIZE);
#include <TinyXML.h>
#define XML_BUF_SIZE 128
byte xmlBuf[XML_BUF_SIZE];
TinyXML xml;
#include <MFRC522.h>
#if defined (ARDUINO_ESP8266_WEMOS_D1MINI)
#define RST_PIN D3
#define SS_PIN D8
#define LED_PIN LED_BUILTIN
#elif defined (ARDUINO_ESP8266_ESP01)
// Generic ESP8266 board
#define RST_PIN 4
#define SS_PIN 16
// Built-in led is pin 2 (and active-low), but it's not broken out
//#define LED_ACTIVE_LOW
#define LED_PIN 5
#endif
MFRC522 mfrc522 (SS_PIN, RST_PIN);
const MFRC522::Uid GOOD_UID = {
4, // Size of key
{0x00, 0x00, 0x00, 0x00}, // Actual key
0 // Always 0
};
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
// Wi-Fi parameters
#define WIFI_SSID "xxx"
#define WIFI_PASSWORD "yyy"
#define CAM_ADDR "1.2.3.4:88"
#define CAM_USER "admin"
#define CAM_PASSWD "zzz"
#define CMD_GET_MOTION_DETECT_CONFIG "getMotionDetectConfig"
#define CMD_SET_MOTION_DETECT_CONFIG "setMotionDetectConfig"
#define CMD_GET_DEV_INFO "getDevInfo"
boolean networkAvailable = false;
boolean camOnline = false;
boolean motionDetectionEnabled = false;
void mypanic (int interval) {
pinMode (LED_PIN, OUTPUT);
while (42) {
digitalWrite (LED_PIN, HIGH);
delay (interval);
digitalWrite (LED_PIN, LOW);
delay (interval);
}
}
boolean compareUid (const MFRC522::Uid& k1, const MFRC522::Uid& k2) {
boolean match = k1.size == k2.size;
for (byte i = 0; match && i < k1.size; i++) {
match = k1.uidByte[i] == k2.uidByte[i];
}
return match;
}
void checkCards () {
if (mfrc522.PICC_IsNewCardPresent () &&
mfrc522.PICC_ReadCardSerial ()) {
// New card presented!
// Dump debug info about the card; PICC_HaltA() is automatically called
//~ mfrc522.PICC_DumpToSerial (&(mfrc522.uid));
if (compareUid (mfrc522.uid, GOOD_UID)) {
Serial.println ("GOOD!");
toggleMotionDetection ();
} else {
Serial.println ("Bad!");
for (byte i = 0; i < 4; i++) {
digitalWrite (LED_PIN, HIGH);
delay (100);
digitalWrite (LED_PIN, LOW);
delay (100);
}
}
mfrc522.PICC_HaltA ();
}
}
#if 0
void xmlCallback (uint8_t statusflags, char *tagName, uint16_t tagNameLen,
char *data, uint16_t dataLen) {
if (statusflags & STATUS_START_TAG) {
if (tagNameLen) {
Serial.print ("Start tag ");
Serial.println (tagName);
}
} else if (statusflags & STATUS_END_TAG) {
Serial.print ("End tag ");
Serial.println (tagName);
} else if (statusflags & STATUS_TAG_TEXT) {
Serial.print ("Tag:");
Serial.print (tagName);
Serial.print (" text:");
Serial.println (data);
} else if (statusflags & STATUS_ATTR_TEXT) {
Serial.print ("Attribute:");
Serial.print (tagName);
Serial.print (" text:");
Serial.println (data);
} else if (statusflags & STATUS_ERROR) {
Serial.print ("XML Parsing error in tag:");
Serial.print (tagName);
Serial.print (" text:");
Serial.println (data);
}
yield ();
}
#endif
class FoscamCGI {
private:
static const int MODEL_NAME_LEN = 16;
static const int DEV_NAME_LEN = 16;
static const int FIRMWARE_VER_LEN = 16;
static const int HARDWARE_VER_LEN = 16;
const char *addr;
const char *user;
const char *passwd;
public:
struct ParamDevInfo {
boolean result = false;
char modelName[MODEL_NAME_LEN];
char devName[DEV_NAME_LEN];
char firmwareVer[FIRMWARE_VER_LEN];
char hardwareVer[HARDWARE_VER_LEN];
};
struct ParamMotionDetectConfig {
static const byte N_SCHEDULES = 7;
static const byte N_AREAS = 10;
boolean result = false;
boolean enabled;
byte linkage;
byte snapInterval;
byte sensitivity;
byte triggerInterval;
boolean movAlarmEnabled;
boolean pirAlarmEnabled;
uint64_t schedules[N_SCHEDULES]; // Only lower 48 bits are used
word areas[N_AREAS];
};
private:
static void parseCommandReply (uint8_t statusflags, char *tagName, uint16_t tagNameLen,
char *data, uint16_t dataLen, void* userdata) {
(void) tagNameLen;
(void) dataLen;
boolean* ret = static_cast<boolean*> (userdata);
if (statusflags & TinyXML::STATUS_TAG_TEXT) {
if (strcmp (tagName, "/CGI_Result/result") == 0) {
int i = atoi (data);
*ret = i == 0;
}
}
}
static void parseGetDevInfo (uint8_t statusflags, char *tagName, uint16_t tagNameLen,
char *data, uint16_t dataLen, void* userdata) {
(void) tagNameLen;
(void) dataLen;
ParamDevInfo* param = static_cast<ParamDevInfo*> (userdata);
if (statusflags & TinyXML::STATUS_TAG_TEXT) {
if (strcmp (tagName, "/CGI_Result/result") == 0) {
int i = atoi (data);
param -> result = i == 0;
} else if (strcmp (tagName, "/CGI_Result/productName") == 0) {
strncpy (param -> modelName, data, MODEL_NAME_LEN);
} else if (strcmp (tagName, "/CGI_Result/devName") == 0) {
strncpy (param -> devName, data, DEV_NAME_LEN);
} else if (strcmp (tagName, "/CGI_Result/firmwareVer") == 0) {
strncpy (param -> firmwareVer, data, FIRMWARE_VER_LEN);
} else if (strcmp (tagName, "/CGI_Result/hardwareVer") == 0) {
strncpy (param -> hardwareVer, data, HARDWARE_VER_LEN);
}
}
}
static void parseGetMotionDetectConfig (uint8_t statusflags, char *tagName, uint16_t tagNameLen,
char *data, uint16_t dataLen, void* userdata) {
(void) tagNameLen;
(void) dataLen;
ParamMotionDetectConfig* param = static_cast<ParamMotionDetectConfig*> (userdata);
if (statusflags & TinyXML::STATUS_TAG_TEXT) {
if (strcmp (tagName, "/CGI_Result/result") == 0) {
int i = atoi (data);
param -> result = i == 0;
} else if (strcmp (tagName, "/CGI_Result/isEnable") == 0) {
int i = atoi (data);
param -> enabled = i != 0;
} else if (strcmp (tagName, "/CGI_Result/linkage") == 0) {
param -> linkage = atoi (data);
} else if (strcmp (tagName, "/CGI_Result/snapInterval") == 0) {
param -> snapInterval = atoi (data);
} else if (strcmp (tagName, "/CGI_Result/sensitivity") == 0) {
param -> sensitivity = atoi (data);
} else if (strcmp (tagName, "/CGI_Result/triggerInterval") == 0) {
param -> triggerInterval = atoi (data);
} else if (strcmp (tagName, "/CGI_Result/isMovAlarmEnable") == 0) {
int i = atoi (data);
param -> movAlarmEnabled = i != 0;
} else if (strcmp (tagName, "/CGI_Result/isPirAlarmEnable") == 0) {
int i = atoi (data);
param -> pirAlarmEnabled = i != 0;
} else if (strncmp (tagName, "/CGI_Result/schedule", 20) == 0) {
int i = atoi (tagName + 20);
if (i < ParamMotionDetectConfig::N_SCHEDULES)
param -> schedules[i] = strtoull (data, NULL, 10);
} else if (strncmp (tagName, "/CGI_Result/area", 16) == 0) {
int i = atoi (tagName + 16);
if (i < ParamMotionDetectConfig::N_AREAS)
param -> areas[i] = atoi (data);
}
}
}
// extraParams must NOT contain a leading '&'
void runCommand (const char *cmd, const char *extraParams = NULL) {
/* We must come up with something along the lines of:
* http://192.168.1.x:88/cgi-bin/CGIProxy.fcgi?cmd=xxx&usr=yyy&pwd=zzz
*/
urlString.begin ();
urlString.print ("http://");
urlString.print (CAM_ADDR);
urlString.print ("/cgi-bin/CGIProxy.fcgi?cmd=");
urlString.print (cmd);
urlString.print ("&usr=");
urlString.print (user);
urlString.print ("&pwd=");
urlString.print (passwd);
if (extraParams) {
urlString.print ('&');
urlString.print (extraParams);
}
HTTPClient http;
const char *url = (const char *) urlString;
//~ Serial.print ("URL is: '");
//~ Serial.print (url);
//~ Serial.println ("'");
http.begin (url);
int httpCode = http.GET ();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString ();
//~ Serial.println (payload);
// Process XML reply
for (unsigned int i = 0; i < payload.length (); i++) {
xml.processChar (payload[i]);
}
} else {
Serial.print ("HTTP request failed: ");
Serial.println (http.errorToString (httpCode).c_str ());
}
http.end ();
}
public:
void begin (const char* _addr, const char* _user, const char* _passwd) {
addr = _addr;
user = _user;
passwd = _passwd;
}
void getDevInfo (ParamDevInfo& param) {
xml.init (xmlBuf, XML_BUF_SIZE, FoscamCGI::parseGetDevInfo, &param);
runCommand (CMD_GET_DEV_INFO);
}
void getMotionDetectConfig (ParamMotionDetectConfig& param) {
xml.init (xmlBuf, XML_BUF_SIZE, FoscamCGI::parseGetMotionDetectConfig, &param);
runCommand (CMD_GET_MOTION_DETECT_CONFIG);
}
void setMotionDetectConfig (ParamMotionDetectConfig& param) {
const int BUFLEN = 384;
char bufStr[BUFLEN];
PString buf (bufStr, BUFLEN);
buf.print ("isEnable=");
buf.print (param.enabled ? 1 : 0);
buf.print ("&linkage=");
buf.print (param.linkage);
buf.print ("&snapInterval=");
buf.print (param.snapInterval);
buf.print ("&sensitivity=");
buf.print (param.sensitivity);
buf.print ("&triggerInterval=");
buf.print (param.triggerInterval);
buf.print ("&isMovAlarmEnable=");
buf.print (param.movAlarmEnabled ? 1 : 0);
buf.print ("&isPirAlarmEnable=");
buf.print (param.pirAlarmEnabled ? 1 : 0);
for (byte i = 0; i < ParamMotionDetectConfig::N_SCHEDULES; i++) {
buf.print ("&schedule");
buf.print (i);
buf.print ('=');
// print() does not support 64-bit integers
char s64[32];
ulltostr (s64, 32, param.schedules[i], 10);
buf.print (s64);
}
for (byte i = 0; i < ParamMotionDetectConfig::N_AREAS; i++) {
buf.print ("&area");
buf.print (i);
buf.print ('=');
buf.print (param.areas[i]);
}
const char *p = (const char *) buf;
boolean ok = false;
xml.init (xmlBuf, XML_BUF_SIZE, FoscamCGI::parseGetMotionDetectConfig, &ok);
runCommand (CMD_SET_MOTION_DETECT_CONFIG, p);
if (!ok) {
Serial.println ("setMotionDetectConfig failed");
}
}
};
FoscamCGI cam;
// Time when motion detection status was last polled
unsigned long lastStatusPollTime = 0;
void toggleMotionDetection () {
FoscamCGI::ParamMotionDetectConfig config;
cam.getMotionDetectConfig (config);
if (config.result) {
config.enabled = !config.enabled;
cam.setMotionDetectConfig (config);
lastStatusPollTime = 0; // Force refresh
}
}
void setup () {
//~ delay (2000);
Serial.begin (9600);
pinMode (LED_PIN, OUTPUT);
// Init cam
cam.begin (CAM_ADDR, CAM_USER, CAM_PASSWD);
// Init RFID reader
SPI.begin (); // Init SPI bus
mfrc522.PCD_Init (); // Init MFRC522
if (mfrc522.PCD_PerformSelfTest ()) {
mfrc522.PCD_Init (); // Need to reinit after self test
Serial.print (F("RFID Reader inited - "));
mfrc522.PCD_DumpVersionToSerial ();
} else {
Serial.println (F("RFID Reader not found"));
mypanic (100);
}
}
#define CONFIG_POLL_INTERVAL 60000UL
void updateStatusLed () {
if (!networkAvailable) {
// Not connected to network: Blink fast
digitalWrite (LED_PIN, (millis () / 300) % 2);
} else if (!camOnline) {
// CAM offline: Blink slow
digitalWrite (LED_PIN, (millis () / 1000) % 2);
} else {
// Show MD status, on = enabled
#ifdef LED_ACTIVE_LOW
digitalWrite (LED_PIN, !motionDetectionEnabled);
#else
digitalWrite (LED_PIN, motionDetectionEnabled);
#endif
}
}
#define WIFI_CONNECT_INTERVAL 30000
void checkWifi () {
static unsigned long lastAttemptTime = 0;
networkAvailable = WiFi.status () == WL_CONNECTED;
if (!networkAvailable && (lastAttemptTime == 0 ||
millis () - lastAttemptTime >= WIFI_CONNECT_INTERVAL)) {
Serial.print ("Connecting to SSID: ");
Serial.println (WIFI_SSID);
WiFi.mode (WIFI_STA);
WiFi.begin (WIFI_SSID, WIFI_PASSWORD);
lastAttemptTime = millis ();
}
if (lastAttemptTime > 0 && WiFi.status () == WL_CONNECTED) {
Serial.print ("Connected! IP address: ");
Serial.println (WiFi.localIP());
lastAttemptTime = 0;
}
}
#define CAM_SEARCH_INTERVAL 20000
void checkCam () {
static unsigned long lastAttemptTime = 0;
if (!networkAvailable) {
camOnline = false;
} else if (!camOnline && (lastAttemptTime == 0 ||
millis () - lastAttemptTime >= CAM_SEARCH_INTERVAL)) {
// Try to connect to cam
FoscamCGI::ParamDevInfo devInfo;
cam.getDevInfo (devInfo);
camOnline = devInfo.result;
if (camOnline) {
Serial.print ("Found cam: ");
Serial.print (devInfo.modelName);
Serial.print (" (");
Serial.print (devInfo.devName);
Serial.print (") - FW ");
Serial.println (devInfo.firmwareVer);
// Signal we're ready!
for (byte i = 0; i < 3; i++) {
digitalWrite (LED_PIN, LOW);
delay (100);
digitalWrite (LED_PIN, HIGH);
delay (100);
}
}
lastAttemptTime = millis ();
lastStatusPollTime = 0;
} else if (camOnline && (lastStatusPollTime == 0 || millis () - lastStatusPollTime >= CONFIG_POLL_INTERVAL)) {
// Cam is online, check status
FoscamCGI::ParamMotionDetectConfig config;
cam.getMotionDetectConfig (config);
if (config.result) {
if ((motionDetectionEnabled = config.enabled)) {
Serial.println ("Motion detection is enabled");
} else {
Serial.println ("Motion detection is disabled");
}
} else {
Serial.println ("Cannot retrieve motion detection status");
camOnline = false;
}
lastStatusPollTime = millis ();
}
}
void loop () {
checkWifi ();
checkCam ();
updateStatusLed ();
checkCards ();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment