Last active
September 4, 2017 20:58
-
-
Save SukkoPera/3e87aacedf3939fc50bbe893016dabab to your computer and use it in GitHub Desktop.
Toggle motion detection on a Foscam IPcam through a Mifare RFID card
This file contains hidden or 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
#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, ¶m); | |
runCommand (CMD_GET_DEV_INFO); | |
} | |
void getMotionDetectConfig (ParamMotionDetectConfig& param) { | |
xml.init (xmlBuf, XML_BUF_SIZE, FoscamCGI::parseGetMotionDetectConfig, ¶m); | |
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